@deksden-com/dd-flow-cli 0.9.0-beta.15 → 0.9.0-beta.150
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 +895 -0
- package/README.md +140 -6
- 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 +58 -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/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 +1096 -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 +622 -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 +1175 -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 +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 +101 -0
- package/dist/harness-runtime/lib/native-children.d.mts +23 -0
- package/dist/harness-runtime/lib/native-children.mjs +219 -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 +15 -0
- package/dist/harness-runtime/lib/provider-overload-policy.mjs +151 -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 +562 -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 +1398 -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 +13 -0
- package/dist/services/controller-child-observer.js +115 -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 +455 -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 +1326 -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 +193 -19
- 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/package.json +50 -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,424 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
4
|
+
import { DatabaseSync } from "node:sqlite";
|
|
5
|
+
import { AppError } from "../shared/errors.js";
|
|
6
|
+
import { PreparedStoreWriterMigration, assertStoreWriterDrain } from "./writer-migration.js";
|
|
7
|
+
import { publishImmutableInput } from "./immutable-input.js";
|
|
8
|
+
import { beginStorageMaintenance, beginStorageRecovery, completeStorageMaintenance, importGuardPath } from "./import-guard.js";
|
|
9
|
+
import { processTreeIsAlive } from "../services/managed-processes.js";
|
|
10
|
+
import { STORE_WRITER_VERSION } from "./writer-contract.js";
|
|
11
|
+
import { PreparedStoreWriterRestore } from "./writer-restore.js";
|
|
12
|
+
import { acquireInstallationLock } from "../services/installation-lock.js";
|
|
13
|
+
import { withStoreCleanup } from "./native-cleanup.js";
|
|
14
|
+
import { bootstrapMemoryUpgradeSchema } from "./memory-upgrade-schema-bootstrap.js";
|
|
15
|
+
const hash = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
16
|
+
function refuse(reason) {
|
|
17
|
+
throw new AppError("paired_storage_migration_invalid", "Reconcile the exact paired migration; recovery is a separate operation", 1, { reason, effect: "unknown", next_action: "reconcile_exact_request" });
|
|
18
|
+
}
|
|
19
|
+
function canonical(file) {
|
|
20
|
+
if (!path.isAbsolute(file) || path.join(fs.realpathSync(path.dirname(file)), path.basename(file)) !== file)
|
|
21
|
+
refuse("noncanonical_path");
|
|
22
|
+
return file;
|
|
23
|
+
}
|
|
24
|
+
function children(intent) {
|
|
25
|
+
return intent.stores.map(store => {
|
|
26
|
+
const bytes = Buffer.from(store.input.base64, "base64");
|
|
27
|
+
if (bytes.toString("base64") !== store.input.base64)
|
|
28
|
+
refuse("invalid_child_bytes");
|
|
29
|
+
const value = JSON.parse(bytes.toString("utf8"));
|
|
30
|
+
if (value.database !== store.database || value.backup !== store.backup || value.request_id !== `${intent.request_id}.${store.kind}`)
|
|
31
|
+
refuse("foreign_child_scope");
|
|
32
|
+
return PreparedStoreWriterMigration.restoreInput(store.input.path, bytes.toString("utf8"), store.input.sha256);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/** Existing logical drain plus actual physical owner observation. Never kills
|
|
36
|
+
* a process or infers drain from expired/terminal metadata alone. */
|
|
37
|
+
export function observeDrainedMigrationStores(stores) {
|
|
38
|
+
const observations = [];
|
|
39
|
+
for (const store of stores) {
|
|
40
|
+
const db = new DatabaseSync(store.database, { readOnly: true });
|
|
41
|
+
try {
|
|
42
|
+
db.exec("BEGIN");
|
|
43
|
+
assertStoreWriterDrain(db);
|
|
44
|
+
let processes = 0;
|
|
45
|
+
if (db.prepare("SELECT 1 FROM sqlite_schema WHERE type='table' AND name='managed_processes'").get())
|
|
46
|
+
for (const row of db.prepare("SELECT * FROM managed_processes").all()) {
|
|
47
|
+
processes++;
|
|
48
|
+
if (processTreeIsAlive(row))
|
|
49
|
+
refuse("physical_owner_alive");
|
|
50
|
+
}
|
|
51
|
+
observations.push({ database: store.database, unsettled_writers: 0, live_processes: 0, registered_processes: processes });
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
db.close();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return observations;
|
|
58
|
+
}
|
|
59
|
+
const drained = observeDrainedMigrationStores;
|
|
60
|
+
const evidenceKinds = ["writer-drain", "storage-backup", "storage-plan", "storage-apply", "storage-verification"];
|
|
61
|
+
function schemaStep(intent) { return intent.bootstrap_schema ? { bootstrap_schema: intent.bootstrap_schema } : {}; }
|
|
62
|
+
function plannedStores(intent) {
|
|
63
|
+
return intent.stores.map(store => ({ kind: store.kind, database: store.database, backup: store.backup,
|
|
64
|
+
source_request_sha256: store.input.sha256, ...schemaStep(intent) }));
|
|
65
|
+
}
|
|
66
|
+
/** Two existing one-store migrations under the existing shared admission fence.
|
|
67
|
+
* Default is writer-only. Memory upgrade may explicitly bind the executing
|
|
68
|
+
* engine's fixed additive bootstrap; it runs under the same retained permits,
|
|
69
|
+
* backup and old-data verification, never as a readonly admission side effect. */
|
|
70
|
+
export class PreparedPairedWriterMigration {
|
|
71
|
+
sha256;
|
|
72
|
+
#file;
|
|
73
|
+
#intent;
|
|
74
|
+
#children;
|
|
75
|
+
constructor(file, intent) {
|
|
76
|
+
this.#file = file;
|
|
77
|
+
this.#intent = structuredClone(intent);
|
|
78
|
+
this.#children = children(intent);
|
|
79
|
+
const bytes = JSON.stringify(intent);
|
|
80
|
+
if (Buffer.byteLength(bytes) > 16 * 1024 * 1024)
|
|
81
|
+
refuse("intent_exceeds_bound");
|
|
82
|
+
this.sha256 = hash(bytes);
|
|
83
|
+
Object.freeze(this);
|
|
84
|
+
}
|
|
85
|
+
static prepare(input) {
|
|
86
|
+
if (input.bootstrapSchema !== undefined && input.bootstrapSchema !== "memory-upgrade@1")
|
|
87
|
+
refuse("unsupported_schema_bootstrap");
|
|
88
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_.-]{0,85}$/.test(input.requestId) || !/^[a-f0-9]{64}$/.test(input.selectionSha256))
|
|
89
|
+
refuse("invalid_identity");
|
|
90
|
+
const file = canonical(input.requestFile);
|
|
91
|
+
if (fs.existsSync(file) || fs.existsSync(file + ".result.json"))
|
|
92
|
+
refuse("restore_original_request");
|
|
93
|
+
for (const root of [input.projectRoot, input.workspaceRoot, input.runHome, input.resourceHome])
|
|
94
|
+
if (fs.realpathSync(root) !== root || !fs.statSync(root).isDirectory())
|
|
95
|
+
refuse("noncanonical_scope");
|
|
96
|
+
const stores = ["run", "resource"].map(kind => {
|
|
97
|
+
const home = kind === "run" ? input.runHome : input.resourceHome;
|
|
98
|
+
const database = path.join(home, kind === "run" ? "db.sqlite" : "runtime.sqlite"), backup = `${file}.${kind}.backup.sqlite`;
|
|
99
|
+
const child = PreparedStoreWriterMigration.prepare({ requestId: `${input.requestId}.${kind}`, requestFile: `${file}.${kind}.json`, database, backup });
|
|
100
|
+
return { kind, home, database, backup, input: child.inputArtifact() };
|
|
101
|
+
});
|
|
102
|
+
const occupied = stores.flatMap(store => [store.database, store.database + "-wal", store.database + "-shm", store.database + "-journal", importGuardPath(store.home), importGuardPath(store.home) + ".lock"]);
|
|
103
|
+
if (stores.some(store => occupied.includes(store.backup) || occupied.includes(store.input.path)) || occupied.includes(file) || occupied.includes(file + ".result.json"))
|
|
104
|
+
refuse("path_alias");
|
|
105
|
+
if (stores.every(store => JSON.parse(Buffer.from(store.input.base64, "base64").toString("utf8")).source.writer_version === STORE_WRITER_VERSION))
|
|
106
|
+
refuse("content_only_route_required");
|
|
107
|
+
drained(stores);
|
|
108
|
+
return new PreparedPairedWriterMigration(file, { schema_id: "dd-flow/paired-writer-migration@1", request_id: input.requestId,
|
|
109
|
+
permit_id: randomUUID(), selection_sha256: input.selectionSha256, project_root: input.projectRoot, workspace_root: input.workspaceRoot, stores,
|
|
110
|
+
...(input.bootstrapSchema ? { bootstrap_schema: input.bootstrapSchema } : {}) });
|
|
111
|
+
}
|
|
112
|
+
static restore(file, expectedSha256) {
|
|
113
|
+
canonical(file);
|
|
114
|
+
const stat = fs.lstatSync(file);
|
|
115
|
+
if (!stat.isFile() || stat.nlink !== 1 || stat.size > 16 * 1024 * 1024)
|
|
116
|
+
refuse("invalid_intent_file");
|
|
117
|
+
const bytes = fs.readFileSync(file, "utf8");
|
|
118
|
+
if (!/^[a-f0-9]{64}$/.test(expectedSha256) || hash(bytes) !== expectedSha256)
|
|
119
|
+
refuse("intent_changed");
|
|
120
|
+
const intent = JSON.parse(bytes);
|
|
121
|
+
if (Object.keys(intent).sort().join() !== ["permit_id", "project_root", "request_id", "schema_id", "selection_sha256", "stores", "workspace_root",
|
|
122
|
+
...(intent.bootstrap_schema !== undefined ? ["bootstrap_schema"] : [])].sort().join()
|
|
123
|
+
|| intent.bootstrap_schema !== undefined && intent.bootstrap_schema !== "memory-upgrade@1"
|
|
124
|
+
|| intent.schema_id !== "dd-flow/paired-writer-migration@1" || !/^[0-9a-f-]{36}$/.test(intent.permit_id)
|
|
125
|
+
|| !/^[A-Za-z0-9][A-Za-z0-9_.-]{0,85}$/.test(intent.request_id) || !/^[a-f0-9]{64}$/.test(intent.selection_sha256)
|
|
126
|
+
|| !Array.isArray(intent.stores) || intent.stores.length !== 2 || intent.stores[0]?.kind !== "run" || intent.stores[1]?.kind !== "resource")
|
|
127
|
+
refuse("invalid_intent");
|
|
128
|
+
for (const store of intent.stores) {
|
|
129
|
+
if (Object.keys(store).sort().join() !== "backup,database,home,input,kind" || Object.keys(store.input).sort().join() !== "base64,path,sha256"
|
|
130
|
+
|| fs.realpathSync(store.home) !== store.home || !fs.statSync(store.home).isDirectory()
|
|
131
|
+
|| store.database !== path.join(store.home, store.kind === "run" ? "db.sqlite" : "runtime.sqlite")
|
|
132
|
+
|| store.backup !== `${file}.${store.kind}.backup.sqlite` || store.input.path !== `${file}.${store.kind}.json`)
|
|
133
|
+
refuse("foreign_store_binding");
|
|
134
|
+
}
|
|
135
|
+
const prepared = new PreparedPairedWriterMigration(file, intent);
|
|
136
|
+
if (prepared.sha256 !== expectedSha256)
|
|
137
|
+
refuse("intent_changed");
|
|
138
|
+
return prepared;
|
|
139
|
+
}
|
|
140
|
+
retain() {
|
|
141
|
+
canonical(this.#file);
|
|
142
|
+
// Parent first: a crash before child publication can be reconstructed from
|
|
143
|
+
// these independently retained exact bytes without selecting new sources.
|
|
144
|
+
publishImmutableInput(this.#file, Buffer.from(JSON.stringify(this.#intent)), "paired_migration_request_conflict");
|
|
145
|
+
for (const child of this.#children)
|
|
146
|
+
child.retain();
|
|
147
|
+
return { request_file: this.#file, sha256: this.sha256, selection_sha256: this.#intent.selection_sha256 };
|
|
148
|
+
}
|
|
149
|
+
reconcile() {
|
|
150
|
+
// Physical outcomes only: "applied" does not itself release admission.
|
|
151
|
+
const outcomes = this.#children.map(child => child.reconcile());
|
|
152
|
+
const count = outcomes.filter(outcome => outcome.status === "applied").length;
|
|
153
|
+
return { status: outcomes.some(outcome => outcome.status === "unknown") ? "unknown" : count === 2 ? "applied" : count ? "partial" : "not_applied", outcomes };
|
|
154
|
+
}
|
|
155
|
+
recoveryMaterial() { return { file: this.#file, sha256: this.sha256, intent: structuredClone(this.#intent), children: [...this.#children] }; }
|
|
156
|
+
apply() {
|
|
157
|
+
const lease = acquireInstallationLock(this.#file + ".lock", this.sha256);
|
|
158
|
+
return withStoreCleanup(() => this.applyLocked(lease.assertOwnership), () => lease.release());
|
|
159
|
+
}
|
|
160
|
+
applyLocked(assertOwnership) {
|
|
161
|
+
assertOwnership();
|
|
162
|
+
if (fs.existsSync(this.#file + ".recovery.json"))
|
|
163
|
+
refuse("recovery_selected");
|
|
164
|
+
for (const store of this.#intent.stores)
|
|
165
|
+
canonical(store.database);
|
|
166
|
+
this.retain();
|
|
167
|
+
const before = this.reconcile();
|
|
168
|
+
if (before.status === "unknown")
|
|
169
|
+
refuse("unknown_child_outcome");
|
|
170
|
+
const resultFile = this.#file + ".result.json";
|
|
171
|
+
if (fs.existsSync(resultFile) && before.status !== "applied")
|
|
172
|
+
refuse("completed_migration_changed");
|
|
173
|
+
const scope = { projectRoot: this.#intent.project_root, workspaceRoot: this.#intent.workspace_root, sourceManifestSha256: this.sha256, importId: this.#intent.permit_id };
|
|
174
|
+
const homes = [...new Set(this.#intent.stores.map(store => store.home))];
|
|
175
|
+
for (const home of homes)
|
|
176
|
+
beginStorageMaintenance({ home, ...scope });
|
|
177
|
+
let evidence;
|
|
178
|
+
if (!fs.existsSync(resultFile)) {
|
|
179
|
+
const drain = drained(this.#intent.stores);
|
|
180
|
+
for (const child of this.#children) {
|
|
181
|
+
assertOwnership();
|
|
182
|
+
drained(this.#intent.stores);
|
|
183
|
+
child.backup();
|
|
184
|
+
}
|
|
185
|
+
const backups = this.#children.map(child => child.backupFacts());
|
|
186
|
+
for (const child of this.#children) {
|
|
187
|
+
assertOwnership();
|
|
188
|
+
drained(this.#intent.stores);
|
|
189
|
+
child.apply();
|
|
190
|
+
}
|
|
191
|
+
if (this.#intent.bootstrap_schema) {
|
|
192
|
+
assertOwnership();
|
|
193
|
+
drained(this.#intent.stores);
|
|
194
|
+
bootstrapMemoryUpgradeSchema(this.#intent.stores[0].home, this.#intent.stores[1].home, this.#intent.permit_id);
|
|
195
|
+
}
|
|
196
|
+
const verification = this.#children.map(child => ({ ...child.verifyAppliedData(), ...schemaStep(this.#intent) }));
|
|
197
|
+
const applied = this.reconcile();
|
|
198
|
+
if (applied.status !== "applied")
|
|
199
|
+
refuse("verification_incomplete");
|
|
200
|
+
const plans = plannedStores(this.#intent);
|
|
201
|
+
const stores = plans.map((store, i) => ({ ...store,
|
|
202
|
+
receipt_sha256: hash(JSON.stringify(applied.outcomes[i].receipt)) }));
|
|
203
|
+
const payloads = [drain, backups, plans, stores, verification];
|
|
204
|
+
evidence = evidenceKinds.map((kind, i) => {
|
|
205
|
+
const bytes = Buffer.from(JSON.stringify({ schema_id: "dd-flow/paired-storage-evidence@1", kind, request_sha256: this.sha256,
|
|
206
|
+
selection_sha256: this.#intent.selection_sha256, stores: payloads[i] }));
|
|
207
|
+
return { kind, path: `receipts/${kind}.json`, sha256: hash(bytes), base64: bytes.toString("base64") };
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
else
|
|
211
|
+
evidence = this.readEvidence().evidence;
|
|
212
|
+
const after = this.reconcile();
|
|
213
|
+
if (after.status !== "applied")
|
|
214
|
+
refuse("verification_incomplete");
|
|
215
|
+
const receipt = { schema_id: "dd-flow/paired-writer-migration-result@2", request_sha256: this.sha256,
|
|
216
|
+
selection_sha256: this.#intent.selection_sha256, stores: this.#intent.stores.map((store, i) => ({ kind: store.kind, database: store.database, backup: store.backup,
|
|
217
|
+
receipt_sha256: hash(JSON.stringify(after.outcomes[i].receipt)) })), evidence };
|
|
218
|
+
publishImmutableInput(resultFile, Buffer.from(JSON.stringify(receipt)), "paired_migration_result_conflict");
|
|
219
|
+
for (const home of homes)
|
|
220
|
+
completeStorageMaintenance({ home, ...scope });
|
|
221
|
+
return receipt;
|
|
222
|
+
}
|
|
223
|
+
/** Historical evidence is verified against original request/backup custody,
|
|
224
|
+
* not re-issued from today's mutable runtime data after bootstrap writes. */
|
|
225
|
+
readEvidence() {
|
|
226
|
+
if (fs.existsSync(this.#file + ".recovery.json"))
|
|
227
|
+
refuse("recovery_selected");
|
|
228
|
+
const file = this.#file + ".result.json", stat = fs.lstatSync(file);
|
|
229
|
+
if (!stat.isFile() || stat.nlink !== 1 || stat.size > 1024 * 1024)
|
|
230
|
+
refuse("invalid_migration_evidence_file");
|
|
231
|
+
const bytes = fs.readFileSync(file), receipt = JSON.parse(bytes.toString("utf8")), observed = this.reconcile();
|
|
232
|
+
if (observed.status !== "applied" || receipt.schema_id !== "dd-flow/paired-writer-migration-result@2" || receipt.request_sha256 !== this.sha256
|
|
233
|
+
|| receipt.selection_sha256 !== this.#intent.selection_sha256 || !Array.isArray(receipt.evidence) || receipt.evidence.length !== evidenceKinds.length
|
|
234
|
+
|| JSON.stringify(receipt.stores) !== JSON.stringify(this.#intent.stores.map((store, i) => ({ kind: store.kind, database: store.database, backup: store.backup,
|
|
235
|
+
receipt_sha256: hash(JSON.stringify(observed.outcomes[i].receipt)) }))))
|
|
236
|
+
refuse("migration_evidence_custody_changed");
|
|
237
|
+
const evidence = receipt.evidence;
|
|
238
|
+
for (let i = 0; i < evidence.length; i++) {
|
|
239
|
+
const entry = evidence[i], raw = Buffer.from(entry.base64, "base64"), value = JSON.parse(raw.toString("utf8"));
|
|
240
|
+
if (entry.kind !== evidenceKinds[i] || entry.path !== `receipts/${entry.kind}.json` || raw.toString("base64") !== entry.base64 || hash(raw) !== entry.sha256
|
|
241
|
+
|| value.schema_id !== "dd-flow/paired-storage-evidence@1" || value.kind !== entry.kind || value.request_sha256 !== this.sha256
|
|
242
|
+
|| value.selection_sha256 !== this.#intent.selection_sha256 || !Array.isArray(value.stores) || value.stores.length !== 2
|
|
243
|
+
|| value.stores.some((store, index) => store.database !== this.#intent.stores[index].database))
|
|
244
|
+
refuse("migration_evidence_changed");
|
|
245
|
+
}
|
|
246
|
+
const backups = this.#children.map(child => child.backupFacts());
|
|
247
|
+
const facts = evidence.map(entry => JSON.parse(Buffer.from(entry.base64, "base64").toString("utf8")).stores);
|
|
248
|
+
if (JSON.stringify(facts[1]) !== JSON.stringify(backups))
|
|
249
|
+
refuse("backup_evidence_changed");
|
|
250
|
+
const plans = plannedStores(this.#intent);
|
|
251
|
+
const applies = plans.map((store, i) => ({ ...store, receipt_sha256: hash(JSON.stringify(observed.outcomes[i].receipt)) }));
|
|
252
|
+
const verification = backups.map(store => ({ database: store.database, source_request_sha256: store.source_request_sha256,
|
|
253
|
+
writer_version: STORE_WRITER_VERSION, source_data_sha256: store.source_data_sha256, integrity: "ok", foreign_key_violations: 0, ...schemaStep(this.#intent) }));
|
|
254
|
+
if (JSON.stringify(facts[2]) !== JSON.stringify(plans) || JSON.stringify(facts[3]) !== JSON.stringify(applies)
|
|
255
|
+
|| JSON.stringify(facts[4]) !== JSON.stringify(verification)
|
|
256
|
+
|| facts[0].some((store) => store.unsettled_writers !== 0 || store.live_processes !== 0 || !Number.isSafeInteger(store.registered_processes) || store.registered_processes < 0))
|
|
257
|
+
refuse("migration_evidence_changed");
|
|
258
|
+
return { reference: { path: file, sha256: hash(bytes) }, evidence: structuredClone(evidence) };
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
function assertOriginal(child, store) {
|
|
262
|
+
child.recoveryReference();
|
|
263
|
+
const db = new DatabaseSync(store.database, { readOnly: true });
|
|
264
|
+
try {
|
|
265
|
+
db.exec("BEGIN");
|
|
266
|
+
child.assertOriginal(db);
|
|
267
|
+
}
|
|
268
|
+
finally {
|
|
269
|
+
db.close();
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
/** Separate, explicitly selected recovery. A never-applied child is verified
|
|
273
|
+
* against its original state, not restored from a nonexistent backup. */
|
|
274
|
+
export class PreparedPairedWriterRecovery {
|
|
275
|
+
sha256;
|
|
276
|
+
#file;
|
|
277
|
+
#migration;
|
|
278
|
+
#intent;
|
|
279
|
+
#restores;
|
|
280
|
+
constructor(migration, file, intent) {
|
|
281
|
+
this.#file = file;
|
|
282
|
+
this.#migration = migration;
|
|
283
|
+
this.#intent = structuredClone(intent);
|
|
284
|
+
const material = migration.recoveryMaterial();
|
|
285
|
+
this.#restores = intent.stores.map((store, i) => {
|
|
286
|
+
if (Object.keys(store).sort().join() !== "input,kind" || store.kind !== material.intent.stores[i].kind)
|
|
287
|
+
refuse("recovery_child_scope_changed");
|
|
288
|
+
if (store.input === null)
|
|
289
|
+
return null;
|
|
290
|
+
if (!store.input || Object.keys(store.input).sort().join() !== "base64,path,sha256")
|
|
291
|
+
refuse("recovery_child_scope_changed");
|
|
292
|
+
const bytes = Buffer.from(store.input.base64, "base64");
|
|
293
|
+
if (store.input.path !== `${file}.${store.kind}.json` || bytes.toString("base64") !== store.input.base64
|
|
294
|
+
|| JSON.parse(bytes.toString("utf8")).recovery_id !== `${intent.recovery_id}.${store.kind}`)
|
|
295
|
+
refuse("recovery_child_scope_changed");
|
|
296
|
+
return PreparedStoreWriterRestore.restoreInput(material.children[i], store.input.path, bytes.toString("utf8"), store.input.sha256);
|
|
297
|
+
});
|
|
298
|
+
const bytes = JSON.stringify(intent);
|
|
299
|
+
if (Buffer.byteLength(bytes) > 128 * 1024)
|
|
300
|
+
refuse("recovery_intent_exceeds_bound");
|
|
301
|
+
this.sha256 = hash(bytes);
|
|
302
|
+
Object.freeze(this);
|
|
303
|
+
}
|
|
304
|
+
migrationReference() { return { ...this.#intent.migration }; }
|
|
305
|
+
static prepare(migration, recoveryId) {
|
|
306
|
+
if (!(migration instanceof PreparedPairedWriterMigration) || !/^[A-Za-z0-9][A-Za-z0-9_.-]{0,85}$/.test(recoveryId))
|
|
307
|
+
refuse("invalid_recovery_identity");
|
|
308
|
+
const material = migration.recoveryMaterial(), file = material.file + ".recovery.json";
|
|
309
|
+
const lease = acquireInstallationLock(material.file + ".lock", material.sha256);
|
|
310
|
+
return withStoreCleanup(() => {
|
|
311
|
+
lease.assertOwnership();
|
|
312
|
+
if (fs.existsSync(file) || fs.existsSync(file + ".result.json"))
|
|
313
|
+
refuse("restore_original_recovery");
|
|
314
|
+
migration.retain();
|
|
315
|
+
drained(material.intent.stores);
|
|
316
|
+
const stores = material.intent.stores.map((store, i) => {
|
|
317
|
+
const child = material.children[i];
|
|
318
|
+
try {
|
|
319
|
+
assertOriginal(child, store);
|
|
320
|
+
return { kind: store.kind, input: null };
|
|
321
|
+
}
|
|
322
|
+
catch (error) {
|
|
323
|
+
if (!(error instanceof AppError) || error.code !== "store_migration_outcome_unknown" || error.details.reason !== "source_changed")
|
|
324
|
+
throw error;
|
|
325
|
+
}
|
|
326
|
+
if (!fs.existsSync(store.backup))
|
|
327
|
+
refuse("changed_store_has_no_backup");
|
|
328
|
+
const restore = PreparedStoreWriterRestore.prepare(child, { recoveryId: `${recoveryId}.${store.kind}`, requestFile: `${file}.${store.kind}.json` });
|
|
329
|
+
return { kind: store.kind, input: restore.inputArtifact() };
|
|
330
|
+
});
|
|
331
|
+
return new PreparedPairedWriterRecovery(migration, file, { schema_id: "dd-flow/paired-writer-recovery@1", recovery_id: recoveryId,
|
|
332
|
+
migration: { path: material.file, sha256: material.sha256 }, stores });
|
|
333
|
+
}, () => lease.release());
|
|
334
|
+
}
|
|
335
|
+
static restore(file, expectedSha256) {
|
|
336
|
+
canonical(file);
|
|
337
|
+
const stat = fs.lstatSync(file);
|
|
338
|
+
if (!stat.isFile() || stat.nlink !== 1 || stat.size > 128 * 1024)
|
|
339
|
+
refuse("invalid_recovery_intent");
|
|
340
|
+
const bytes = fs.readFileSync(file, "utf8");
|
|
341
|
+
if (hash(bytes) !== expectedSha256)
|
|
342
|
+
refuse("recovery_intent_changed");
|
|
343
|
+
const intent = JSON.parse(bytes);
|
|
344
|
+
if (Object.keys(intent).sort().join() !== "migration,recovery_id,schema_id,stores" || intent.schema_id !== "dd-flow/paired-writer-recovery@1"
|
|
345
|
+
|| !/^[A-Za-z0-9][A-Za-z0-9_.-]{0,85}$/.test(intent.recovery_id) || Object.keys(intent.migration).sort().join() !== "path,sha256"
|
|
346
|
+
|| file !== intent.migration.path + ".recovery.json"
|
|
347
|
+
|| !Array.isArray(intent.stores) || intent.stores.length !== 2)
|
|
348
|
+
refuse("invalid_recovery_intent");
|
|
349
|
+
const migration = PreparedPairedWriterMigration.restore(intent.migration.path, intent.migration.sha256);
|
|
350
|
+
const prepared = new PreparedPairedWriterRecovery(migration, file, intent);
|
|
351
|
+
if (prepared.sha256 !== expectedSha256)
|
|
352
|
+
refuse("recovery_intent_changed");
|
|
353
|
+
return prepared;
|
|
354
|
+
}
|
|
355
|
+
retainLocked() {
|
|
356
|
+
canonical(this.#file);
|
|
357
|
+
this.#migration.retain();
|
|
358
|
+
const material = this.#migration.recoveryMaterial();
|
|
359
|
+
drained(material.intent.stores);
|
|
360
|
+
for (let i = 0; i < this.#restores.length; i++) {
|
|
361
|
+
const restore = this.#restores[i];
|
|
362
|
+
if (restore)
|
|
363
|
+
restore.revalidatePrepared();
|
|
364
|
+
else
|
|
365
|
+
assertOriginal(material.children[i], material.intent.stores[i]);
|
|
366
|
+
}
|
|
367
|
+
publishImmutableInput(this.#file, Buffer.from(JSON.stringify(this.#intent)), "paired_recovery_request_conflict");
|
|
368
|
+
// Selecting recovery fences the drained stores before returning its prepared
|
|
369
|
+
// apply command; it is not a promise to restore while new writers keep running.
|
|
370
|
+
for (const home of new Set(material.intent.stores.map(store => store.home)))
|
|
371
|
+
beginStorageRecovery({ home,
|
|
372
|
+
projectRoot: material.intent.project_root, workspaceRoot: material.intent.workspace_root, importId: material.intent.permit_id,
|
|
373
|
+
originalSha256: material.sha256, recoverySha256: this.sha256 });
|
|
374
|
+
for (const restore of this.#restores)
|
|
375
|
+
restore?.retain();
|
|
376
|
+
return { request_file: this.#file, sha256: this.sha256, migration_sha256: material.sha256 };
|
|
377
|
+
}
|
|
378
|
+
retain() {
|
|
379
|
+
const material = this.#migration.recoveryMaterial(), lease = acquireInstallationLock(material.file + ".lock", material.sha256);
|
|
380
|
+
return withStoreCleanup(() => { lease.assertOwnership(); return this.retainLocked(); }, () => lease.release());
|
|
381
|
+
}
|
|
382
|
+
/** Read-only physical outcome, not proof that admission was reopened. */
|
|
383
|
+
reconcile() {
|
|
384
|
+
const material = this.#migration.recoveryMaterial();
|
|
385
|
+
const outcomes = this.#restores.map((restore, i) => {
|
|
386
|
+
if (restore)
|
|
387
|
+
return { kind: material.intent.stores[i].kind, ...restore.reconcile() };
|
|
388
|
+
try {
|
|
389
|
+
assertOriginal(material.children[i], material.intent.stores[i]);
|
|
390
|
+
return { kind: material.intent.stores[i].kind, status: "restored" };
|
|
391
|
+
}
|
|
392
|
+
catch (error) {
|
|
393
|
+
return { kind: material.intent.stores[i].kind, status: "unknown", reason: error instanceof AppError ? String(error.details.reason ?? error.code) : "source_unavailable" };
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
return { status: outcomes.some(outcome => outcome.status === "unknown") ? "unknown" : outcomes.every(outcome => outcome.status === "restored") ? "restored" : "not_restored", outcomes };
|
|
397
|
+
}
|
|
398
|
+
apply() {
|
|
399
|
+
const material = this.#migration.recoveryMaterial(), lease = acquireInstallationLock(material.file + ".lock", material.sha256);
|
|
400
|
+
return withStoreCleanup(() => {
|
|
401
|
+
lease.assertOwnership();
|
|
402
|
+
this.retainLocked();
|
|
403
|
+
const scope = { projectRoot: material.intent.project_root, workspaceRoot: material.intent.workspace_root, importId: material.intent.permit_id };
|
|
404
|
+
const homes = [...new Set(material.intent.stores.map(store => store.home))];
|
|
405
|
+
const stores = this.#restores.map((restore, i) => {
|
|
406
|
+
lease.assertOwnership();
|
|
407
|
+
drained(material.intent.stores);
|
|
408
|
+
if (!restore) {
|
|
409
|
+
assertOriginal(material.children[i], material.intent.stores[i]);
|
|
410
|
+
return { kind: material.intent.stores[i].kind, status: "unchanged", migration_sha256: material.children[i].sha256 };
|
|
411
|
+
}
|
|
412
|
+
const receipt = restore.apply();
|
|
413
|
+
if (restore.reconcile().status !== "restored")
|
|
414
|
+
refuse("recovery_readback_failed");
|
|
415
|
+
return { kind: material.intent.stores[i].kind, status: "restored", receipt_sha256: hash(JSON.stringify(receipt)) };
|
|
416
|
+
});
|
|
417
|
+
const receipt = { schema_id: "dd-flow/paired-writer-recovery-result@1", request_sha256: this.sha256, migration_sha256: material.sha256, stores };
|
|
418
|
+
publishImmutableInput(this.#file + ".result.json", Buffer.from(JSON.stringify(receipt)), "paired_recovery_result_conflict");
|
|
419
|
+
for (const home of homes)
|
|
420
|
+
completeStorageMaintenance({ home, ...scope, sourceManifestSha256: this.sha256 });
|
|
421
|
+
return receipt;
|
|
422
|
+
}, () => lease.release());
|
|
423
|
+
}
|
|
424
|
+
}
|
package/dist/storage/paths.js
CHANGED
|
@@ -15,6 +15,8 @@ export function resolveProjectRoot(root) {
|
|
|
15
15
|
if (!fs.existsSync(absolute)) {
|
|
16
16
|
throw new AppError("not_found", `Project root does not exist: ${absolute}`, 1);
|
|
17
17
|
}
|
|
18
|
+
if (!fs.statSync(absolute).isDirectory())
|
|
19
|
+
throw new AppError("validation", "Project root must be a directory", 2, { project_root: absolute });
|
|
18
20
|
return fs.realpathSync(absolute);
|
|
19
21
|
}
|
|
20
22
|
/** Resolve an existing path to its filesystem identity, or normalize a future path. */
|
|
@@ -24,10 +26,16 @@ export function canonicalPath(value, mustExist = true) {
|
|
|
24
26
|
return fs.realpathSync(absolute);
|
|
25
27
|
if (mustExist)
|
|
26
28
|
throw new AppError("not_found", `Path does not exist: ${absolute}`, 1, { path: absolute });
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
const missing = [];
|
|
30
|
+
let parent = absolute;
|
|
31
|
+
while (!fs.existsSync(parent)) {
|
|
32
|
+
missing.unshift(path.basename(parent));
|
|
33
|
+
const next = path.dirname(parent);
|
|
34
|
+
if (next === parent)
|
|
35
|
+
return absolute;
|
|
36
|
+
parent = next;
|
|
37
|
+
}
|
|
38
|
+
return path.join(fs.realpathSync(parent), ...missing);
|
|
31
39
|
}
|
|
32
40
|
export function assertPathWithin(root, candidate, label = "path") {
|
|
33
41
|
const canonicalRoot = canonicalPath(root);
|
|
@@ -100,6 +108,11 @@ export function stateJsonPath(projectRoot, protocolId) {
|
|
|
100
108
|
export function planJsonPath(projectRoot, protocolId) {
|
|
101
109
|
return path.join(protocolDir(projectRoot, protocolId), "plan.json");
|
|
102
110
|
}
|
|
111
|
+
/** Project canonical plan takes precedence over a retained workspace location. */
|
|
112
|
+
export function resolveProtocolPlanPath(projectRoot, protocolId, recordedPath) {
|
|
113
|
+
const canonical = planJsonPath(projectRoot, protocolId);
|
|
114
|
+
return fs.existsSync(canonical) ? canonical : recordedPath;
|
|
115
|
+
}
|
|
103
116
|
export function projectHome(ddFlowHome, projectId) {
|
|
104
117
|
return path.join(ddFlowHome, "projects", projectId);
|
|
105
118
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import crypto from "node:crypto";
|
|
4
|
+
import { processStartedAt } from "../services/managed-processes.js";
|
|
5
|
+
import { AppError, errorRecord } from "../shared/errors.js";
|
|
6
|
+
/** One filesystem publisher per entity; no SQLite writer is held while waiting. */
|
|
7
|
+
export function withProjectionLock(lockPath, operation) {
|
|
8
|
+
fs.mkdirSync(path.dirname(lockPath), { recursive: true });
|
|
9
|
+
const owner = { pid: process.pid, started_at: selfStartedAt() };
|
|
10
|
+
const wait = new Int32Array(new SharedArrayBuffer(4));
|
|
11
|
+
const observed = new Map();
|
|
12
|
+
for (let tries = 0;; tries++) {
|
|
13
|
+
try {
|
|
14
|
+
fs.mkdirSync(lockPath);
|
|
15
|
+
try {
|
|
16
|
+
fs.writeFileSync(`${lockPath}/owner.json`, JSON.stringify(owner));
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
fs.rmdirSync(lockPath);
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
if (error.code !== "EEXIST")
|
|
26
|
+
throw error;
|
|
27
|
+
let stale = false;
|
|
28
|
+
let observedInode;
|
|
29
|
+
try {
|
|
30
|
+
observedInode = fs.statSync(lockPath).ino;
|
|
31
|
+
const current = JSON.parse(fs.readFileSync(`${lockPath}/owner.json`, "utf8"));
|
|
32
|
+
try {
|
|
33
|
+
process.kill(current.pid, 0);
|
|
34
|
+
if (!observed.has(current.pid))
|
|
35
|
+
observed.set(current.pid, processStartedAt(current.pid));
|
|
36
|
+
stale = Boolean(current.started_at && observed.get(current.pid) !== current.started_at);
|
|
37
|
+
}
|
|
38
|
+
catch (probe) {
|
|
39
|
+
stale = probe.code === "ESRCH";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
stale = false;
|
|
44
|
+
}
|
|
45
|
+
if (stale) {
|
|
46
|
+
try {
|
|
47
|
+
if (observedInode !== undefined && fs.statSync(lockPath).ino === observedInode) {
|
|
48
|
+
const moved = `${lockPath}.stale-${crypto.randomUUID()}`;
|
|
49
|
+
fs.renameSync(lockPath, moved);
|
|
50
|
+
fs.rmSync(moved, { recursive: true });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch { /* Another publisher may own it now. */ }
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (tries >= 80)
|
|
57
|
+
throw new AppError("publication_pending", "Projection publisher is busy", 1, { effect: "committed", lock_path: lockPath });
|
|
58
|
+
Atomics.wait(wait, 0, 0, 50);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
return operation();
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
throw new AppError("publication_pending", "Committed projection needs reconciliation", 1, { effect: "committed", cause: errorRecord(error), lock_path: lockPath });
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
fs.rmSync(lockPath, { recursive: true, force: true });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
let ownStart;
|
|
72
|
+
function selfStartedAt() { if (ownStart === undefined)
|
|
73
|
+
ownStart = processStartedAt(process.pid); return ownStart; }
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { AppError } from "../shared/errors.js";
|
|
2
|
+
/** Read-only lookup; scope applies to exact IDs as well as short aliases. */
|
|
3
|
+
export function resolveWorkReference(context, id, scope = {}) {
|
|
4
|
+
const clauses = ["1 = 1"];
|
|
5
|
+
const params = [];
|
|
6
|
+
if (scope.projectId) {
|
|
7
|
+
clauses.push("project_id = ?");
|
|
8
|
+
params.push(scope.projectId);
|
|
9
|
+
}
|
|
10
|
+
if (scope.runId) {
|
|
11
|
+
clauses.push("run_id = ?");
|
|
12
|
+
params.push(scope.runId);
|
|
13
|
+
}
|
|
14
|
+
const where = clauses.join(" AND ");
|
|
15
|
+
const exact = context.db.get(`SELECT * FROM works WHERE ${where} AND work_id = ?`, [...params, id]);
|
|
16
|
+
if (exact)
|
|
17
|
+
return exact;
|
|
18
|
+
const matches = /^WRK-\d{3,}$/.test(id)
|
|
19
|
+
? context.db.all(`SELECT * FROM works WHERE ${where} AND work_id LIKE ? ORDER BY work_id`, [...params, `${id}-%`]) : [];
|
|
20
|
+
if (matches.length !== 1)
|
|
21
|
+
throw new AppError(matches.length ? "ambiguous_work_alias" : "not_found", matches.length ? "Short Work alias is ambiguous in the current RUN" : "Work is not registered in the current RUN", 1, { work_id: id, matches: matches.map(work => work.work_id) });
|
|
22
|
+
return matches[0];
|
|
23
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { AppError } from "../shared/errors.js";
|
|
3
|
+
import { rollbackStoreTransaction } from "./native-cleanup.js";
|
|
4
|
+
export const STORE_WRITER_VERSION = 2;
|
|
5
|
+
const version = STORE_WRITER_VERSION;
|
|
6
|
+
const table = "dd_flow_writer_contract";
|
|
7
|
+
const admissions = new WeakMap();
|
|
8
|
+
/** Change admission without adopting a different writer version after cutover. */
|
|
9
|
+
export function bindStoreWriterAdmission(db, admission) { admissions.set(db, admission); }
|
|
10
|
+
const quote = (name) => `"${name.replaceAll('"', '""')}"`;
|
|
11
|
+
/** The executing writer's fence definition, also used for readonly admission. */
|
|
12
|
+
export function storeWriterTriggerSql(name, action) {
|
|
13
|
+
const key = crypto.createHash("sha256").update(name).digest("hex").slice(0, 24);
|
|
14
|
+
const trigger = `dd_flow_writer_${key}_${action.toLowerCase()}`;
|
|
15
|
+
return { name: trigger, sql: `CREATE TRIGGER ${quote(trigger)} BEFORE ${action} ON ${quote(name)} BEGIN
|
|
16
|
+
SELECT CASE WHEN dd_flow_writer_version() <> (SELECT version FROM ${table} WHERE id = 1)
|
|
17
|
+
OR (SELECT COUNT(*) FROM ${table}) <> 1 THEN RAISE(ABORT, 'store_writer_incompatible') END;
|
|
18
|
+
END;` };
|
|
19
|
+
}
|
|
20
|
+
/** Publish additive schema and its writer fences in the same transaction. */
|
|
21
|
+
export function executeStoreSchema(db, sql, initialize = false) {
|
|
22
|
+
const ownsTransaction = !db.isTransaction;
|
|
23
|
+
db.exec(ownsTransaction ? "BEGIN IMMEDIATE" : "SAVEPOINT dd_flow_schema_change");
|
|
24
|
+
try {
|
|
25
|
+
const protectedStore = readStoreWriterContract(db) !== null;
|
|
26
|
+
if (initialize && !protectedStore && db.prepare("SELECT 1 FROM sqlite_schema WHERE type = 'table' AND name NOT LIKE 'sqlite_%' LIMIT 1").get()) {
|
|
27
|
+
throw new AppError("store_initialization_conflict", "Another writer initialized this store; explicit migration is required", 1);
|
|
28
|
+
}
|
|
29
|
+
const before = db.prepare("PRAGMA schema_version").get();
|
|
30
|
+
if (typeof sql === "string")
|
|
31
|
+
db.exec(sql);
|
|
32
|
+
else
|
|
33
|
+
sql();
|
|
34
|
+
const after = db.prepare("PRAGMA schema_version").get();
|
|
35
|
+
if (initialize || (protectedStore && before.schema_version !== after.schema_version))
|
|
36
|
+
installStoreWriterContract(db);
|
|
37
|
+
db.exec(ownsTransaction ? "COMMIT" : "RELEASE dd_flow_schema_change");
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
rollbackStoreTransaction(db, ownsTransaction ? "ROLLBACK" : "ROLLBACK TO dd_flow_schema_change; RELEASE dd_flow_schema_change", error);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** Registration is connection-local and never changes historical input bytes. */
|
|
44
|
+
export function registerStoreWriter(db, admission) {
|
|
45
|
+
const current = readStoreWriterContract(db);
|
|
46
|
+
if (current !== null && current !== version)
|
|
47
|
+
throw new AppError("store_writer_incompatible", "This CLI cannot write the store's required contract", 1, { required: current, supported: version });
|
|
48
|
+
if (admission)
|
|
49
|
+
admissions.set(db, admission);
|
|
50
|
+
db.function("dd_flow_writer_version", () => { admissions.get(db)?.(); return version; });
|
|
51
|
+
}
|
|
52
|
+
/** Health maintenance uses only verified legacy lifecycle columns and never
|
|
53
|
+
* migrates schema. Keep the retained writer fence, including version 1. */
|
|
54
|
+
export function registerReconciliationWriter(db, admission) {
|
|
55
|
+
const current = readStoreWriterContract(db);
|
|
56
|
+
if (current !== null && current !== 1 && current !== version)
|
|
57
|
+
throw new AppError("store_writer_incompatible", "Unsupported reconciliation writer contract", 1, { required: current, supported: [1, version] });
|
|
58
|
+
if (admission)
|
|
59
|
+
admissions.set(db, admission);
|
|
60
|
+
db.function("dd_flow_writer_version", () => { admissions.get(db)?.(); return current ?? version; });
|
|
61
|
+
}
|
|
62
|
+
export function readStoreWriterContract(db) {
|
|
63
|
+
if (!db.prepare("SELECT 1 FROM sqlite_schema WHERE type = 'table' AND name = ?").get(table))
|
|
64
|
+
return null;
|
|
65
|
+
const rows = db.prepare(`SELECT id, version FROM ${table}`).all();
|
|
66
|
+
if (rows.length !== 1 || rows[0]?.id !== 1 || !Number.isSafeInteger(rows[0].version) || rows[0].version < 1)
|
|
67
|
+
throw new AppError("store_writer_contract_invalid", "Store writer contract is malformed", 1);
|
|
68
|
+
return rows[0].version;
|
|
69
|
+
}
|
|
70
|
+
/** Install only at an explicit migration boundary; ordinary reads never call it. */
|
|
71
|
+
export function installStoreWriterContract(db) {
|
|
72
|
+
registerStoreWriter(db);
|
|
73
|
+
const ownsTransaction = !db.isTransaction;
|
|
74
|
+
if (ownsTransaction)
|
|
75
|
+
db.exec("BEGIN IMMEDIATE");
|
|
76
|
+
try {
|
|
77
|
+
db.exec(`CREATE TABLE IF NOT EXISTS ${table} (id INTEGER PRIMARY KEY CHECK (id = 1), version INTEGER NOT NULL);
|
|
78
|
+
INSERT OR IGNORE INTO ${table} (id, version) VALUES (1, ${version});`);
|
|
79
|
+
if (readStoreWriterContract(db) !== version)
|
|
80
|
+
throw new AppError("store_writer_incompatible", "Store writer contract changed before migration", 1);
|
|
81
|
+
const tables = db.prepare("SELECT name FROM sqlite_schema WHERE type = 'table' AND name NOT LIKE 'sqlite_%'").all();
|
|
82
|
+
for (const target of tables) {
|
|
83
|
+
for (const action of ["INSERT", "UPDATE", "DELETE"]) {
|
|
84
|
+
const trigger = storeWriterTriggerSql(target.name, action);
|
|
85
|
+
db.exec(`DROP TRIGGER IF EXISTS ${quote(trigger.name)}; ${trigger.sql}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (ownsTransaction)
|
|
89
|
+
db.exec("COMMIT");
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
if (ownsTransaction)
|
|
93
|
+
rollbackStoreTransaction(db, "ROLLBACK", error);
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
}
|