@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,698 @@
|
|
|
1
|
+
import { lstat, readFile, readdir, mkdir, appendFile } from "node:fs/promises";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { commandJson } from "./process-json.mjs";
|
|
5
|
+
import { errorRecord, reportedError, diagnosticJson } from "./operation-errors.mjs";
|
|
6
|
+
import { writeJsonAtomic } from "./runner-events.mjs";
|
|
7
|
+
import { runBoundedCommand } from "./native-hook-command.mjs";
|
|
8
|
+
import { processSnapshot } from "./process-snapshot.mjs";
|
|
9
|
+
import { createRenewalState, maintenanceRetryable, renewalConfirmed, renewalDelay, renewalFailure, renewalRemaining, RENEWAL_POLICY } from "./lease-renewal.mjs";
|
|
10
|
+
|
|
11
|
+
const leases = new Map();
|
|
12
|
+
// A rejected Promise is not physical exit. Do not start another writer for
|
|
13
|
+
// this exact binding after an unconfirmed maintenance subtree cleanup.
|
|
14
|
+
const maintenancePoison = new Map();
|
|
15
|
+
const maintenanceFlights = new Map();
|
|
16
|
+
|
|
17
|
+
/** Freeze resource ownership independently of whether Subject hooks are enabled. */
|
|
18
|
+
export function bindRuntimeOwner(config, env = process.env) {
|
|
19
|
+
const raw = Object.hasOwn(config, "runtime_owner") ? config.runtime_owner === null ? undefined : JSON.stringify(config.runtime_owner) : env.DD_FLOW_RUNTIME_OWNER;
|
|
20
|
+
if (raw === undefined) return config.runtime_owner = null;
|
|
21
|
+
let owner;
|
|
22
|
+
try { if (raw.length > 16_384) throw new Error(); owner = JSON.parse(raw); }
|
|
23
|
+
catch { throw Object.assign(new Error("runtime ownership descriptor is invalid"), { code: "runtime_owner_invalid" }); }
|
|
24
|
+
const fields = ["schema_id", "owner_id", "role", "project_id", "run_id", "work_id", "operation_id", "state_dir", "dd_flow_home", "dd_flow_bin", "resource_home", "adapter_executable", "budget"];
|
|
25
|
+
const text = value => typeof value === "string" && value.trim().length > 0;
|
|
26
|
+
if (!owner || typeof owner !== "object" || Array.isArray(owner) || Object.keys(owner).some(key => !fields.includes(key))
|
|
27
|
+
|| owner.schema_id !== "dd-flow/runtime-owner@1" || !text(owner.owner_id) || !text(owner.operation_id)
|
|
28
|
+
|| !["subject", "worker", "merge", "judge", "probe"].includes(owner.role)
|
|
29
|
+
|| ["state_dir", "dd_flow_home", "dd_flow_bin", "resource_home"].some(key => !text(owner[key]) || !path.isAbsolute(owner[key]))
|
|
30
|
+
|| (owner.adapter_executable !== undefined && (!text(owner.adapter_executable) || !path.isAbsolute(owner.adapter_executable)))
|
|
31
|
+
|| ["project_id", "run_id", "work_id"].some(key => owner[key] !== undefined && !text(owner[key]))
|
|
32
|
+
|| (["subject", "worker", "merge"].includes(owner.role) && (!text(owner.project_id) || !text(owner.run_id)))
|
|
33
|
+
|| (["worker", "merge"].includes(owner.role) && !text(owner.work_id))
|
|
34
|
+
|| (["judge", "probe"].includes(owner.role) && owner.work_id !== undefined)) {
|
|
35
|
+
throw Object.assign(new Error("runtime ownership descriptor does not match its role and scope"), { code: "runtime_owner_invalid" });
|
|
36
|
+
}
|
|
37
|
+
if (owner.budget !== undefined) {
|
|
38
|
+
const budget = owner.budget, names = ["codex-desktop", "zcode-acp", "grok-acp", "antigravity-cli", "opencode-server", "droid-cli"];
|
|
39
|
+
if (!budget || typeof budget !== "object" || Array.isArray(budget) || Object.keys(budget).some(key => !["schema_id", "scope_id", "per_harness"].includes(key)) || budget.schema_id !== "dd-flow/runtime-budget@1" || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(budget.scope_id ?? "") || !budget.per_harness || typeof budget.per_harness !== "object" || Array.isArray(budget.per_harness) || Object.entries(budget.per_harness).some(([name, limit]) => !names.includes(name) || !Number.isSafeInteger(limit) || limit < 1 || limit > 1024)) throw Object.assign(new Error("runtime owner budget is invalid"), { code: "runtime_budget_invalid" });
|
|
40
|
+
}
|
|
41
|
+
config.runtime_owner = Object.fromEntries(fields.filter(key => owner[key] !== undefined).map(key => [key, owner[key]]));
|
|
42
|
+
return config.runtime_owner;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Reject rebinding before provider setup, including the already-running path. */
|
|
46
|
+
export async function prepareRuntimeOwner(config, stateDir, env = process.env) {
|
|
47
|
+
config.stateDir = path.resolve(stateDir);
|
|
48
|
+
const owner = bindRuntimeOwner(config, env);
|
|
49
|
+
if (owner && path.resolve(owner.state_dir) !== path.resolve(stateDir)) {
|
|
50
|
+
throw Object.assign(new Error("runtime owner belongs to another state directory"), { code: "runtime_owner_mismatch" });
|
|
51
|
+
}
|
|
52
|
+
let previous;
|
|
53
|
+
try { previous = JSON.parse(await readFile(path.join(stateDir, "daemon.json"), "utf8")); }
|
|
54
|
+
catch (error) { if (error.code === "ENOENT") return owner; throw error; }
|
|
55
|
+
const retained = bindRuntimeOwner({ runtime_owner: previous?.config?.runtime_owner ?? null }, {});
|
|
56
|
+
if (JSON.stringify(retained) !== JSON.stringify(owner)) {
|
|
57
|
+
throw Object.assign(new Error("retained daemon ownership differs; use explicit recovery instead of rebinding its sessions"), { code: "runtime_owner_mismatch" });
|
|
58
|
+
}
|
|
59
|
+
return owner;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function assertDaemonOwnership(stateDir, dispatch = {}, assertCurrent = () => {}, progress = async () => {}, waiting = async () => {}) {
|
|
63
|
+
const gate = async () => {
|
|
64
|
+
assertCurrent();
|
|
65
|
+
let confirmed = false;
|
|
66
|
+
for (const lease of leases.values()) {
|
|
67
|
+
if (lease.stateDir !== path.resolve(stateDir)) continue;
|
|
68
|
+
await reconfirmLease(lease, assertCurrent);
|
|
69
|
+
assertCurrent();
|
|
70
|
+
confirmed = true;
|
|
71
|
+
}
|
|
72
|
+
if (confirmed) await progress('ownership_confirmed');
|
|
73
|
+
};
|
|
74
|
+
await gate();
|
|
75
|
+
const state = JSON.parse(await readFile(path.join(stateDir, "daemon.json"), "utf8"));
|
|
76
|
+
const owner = bindRuntimeOwner({ runtime_owner: state.config?.runtime_owner ?? null }, {});
|
|
77
|
+
if (!owner) return;
|
|
78
|
+
const record = state.resource_process;
|
|
79
|
+
if (owner.state_dir !== stateDir || !record?.id || !record.lease_token) throw Object.assign(new Error("owned daemon has no confirmed resource binding"), { code: "process_ownership_unknown" });
|
|
80
|
+
scheduleDaemonProcessHeartbeat(state.config, record);
|
|
81
|
+
const admissionRenewal = createRenewalState({ leaseExpiresAt: record.lease_expires_at });
|
|
82
|
+
for (;;) {
|
|
83
|
+
await gate();
|
|
84
|
+
// daemon.json is a snapshot, not the current authority. gate() verified
|
|
85
|
+
// the exact retained owner and its latest committed renewal above.
|
|
86
|
+
const retained = leases.get(leaseKey(state.config, record));
|
|
87
|
+
if (!retained || record.registration_sha256 && record.registration_sha256 !== retained.record.registration_sha256) throw Object.assign(new Error("Admission registration differs from confirmed owner"), { code: "process_maintenance_receipt_invalid" });
|
|
88
|
+
retainReceipt(record, retained.record);
|
|
89
|
+
if (!renewalConfirmed(admissionRenewal, performance.now(), record.lease_expires_at)) requireTimelyMaintenance(admissionRenewal, "admission", retained.record, record);
|
|
90
|
+
admissionRenewal.uncertainSince ??= performance.now();
|
|
91
|
+
let result;
|
|
92
|
+
try { result = await runtimeProcess(state.config, "admission", { id: record.id, "lease-token": record.lease_token, owner: owner.owner_id, "state-dir": stateDir, "daemon-id": state.daemon_id ?? state.config?.daemonId, ...dispatch }, { timeoutMs: renewalRemaining(admissionRenewal) }); }
|
|
93
|
+
catch (error) {
|
|
94
|
+
if (!maintenanceRetryable(error) || !renewalFailure(admissionRenewal)) throw error;
|
|
95
|
+
await new Promise(resolve => setTimeout(resolve, Math.min(renewalDelay(admissionRenewal), renewalRemaining(admissionRenewal))));
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
assertCurrent();
|
|
99
|
+
if (result?.ok !== true || result.process_id !== record.id || result.admitted !== true && result.reason !== "provider_capacity") throw Object.assign(new Error("productive runtime admission was not confirmed"), { code: "process_ownership_unknown" });
|
|
100
|
+
requireTimelyMaintenance(admissionRenewal, "admission", result, record);
|
|
101
|
+
// This bound ACK ends admission uncertainty. The post-ACK lease checks
|
|
102
|
+
// have their own episodes; successful renewal must not exhaust an already
|
|
103
|
+
// confirmed admission while the provider/daemon still retain ownership.
|
|
104
|
+
if (!renewalConfirmed(admissionRenewal)) requireTimelyMaintenance(admissionRenewal, "admission", result, record);
|
|
105
|
+
await gate();
|
|
106
|
+
await waiting({ ...result, lease_expires_at: result.lease_expires_at ?? record.lease_expires_at });
|
|
107
|
+
if (result.admitted === true) { await progress('admission_confirmed'); return; }
|
|
108
|
+
const predecessor = result.blocking_turns?.find(turn => turn?.process_id === record.id
|
|
109
|
+
&& turn?.session_id === dispatch["native-session-id"]
|
|
110
|
+
&& turn?.operation_id !== dispatch["operation-id"]);
|
|
111
|
+
if (predecessor) throw Object.assign(new Error("the same managed Session has an unsettled productive predecessor; reconcile it before dispatching another Turn"), {
|
|
112
|
+
code: "settlement_reconciliation_required",
|
|
113
|
+
details: { session_id: dispatch["native-session-id"], predecessor_operation_id: predecessor.operation_id, predecessor_operation: predecessor.operation }
|
|
114
|
+
});
|
|
115
|
+
// The daemon, not its disposable socket client, owns this queue. Recheck
|
|
116
|
+
// the RUN fence and process lease on every admission attempt.
|
|
117
|
+
await new Promise(resolve => setTimeout(resolve, 250));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Snapshot terminal operations BEFORE observing native idle. A later Turn
|
|
122
|
+
* cannot be released by an older observation of the same Session. */
|
|
123
|
+
export async function budgetObservationScope(stateDir, sessionId) {
|
|
124
|
+
const state = await budgetState(stateDir);
|
|
125
|
+
if (!state?.config?.runtime_owner?.budget || !sessionId) return null;
|
|
126
|
+
const daemonId = state.daemon_id ?? state.config?.daemonId, operationIds = [];
|
|
127
|
+
for (const name of await readdir(path.join(stateDir, "operations")).catch(error => { if (error.code === "ENOENT") return []; throw error; })) {
|
|
128
|
+
const directory = path.join(stateDir, "operations", name);
|
|
129
|
+
try {
|
|
130
|
+
const requested = JSON.parse(await readFile(path.join(directory, "requested.json"), "utf8"));
|
|
131
|
+
const result = JSON.parse(await readFile(path.join(directory, "result.json"), "utf8"));
|
|
132
|
+
if (requested.daemon_id === daemonId && requested.session_id === sessionId && ["completed", "failed", "interrupted"].includes(result.state) && ["session.prompt", "session.start", "session.resume"].includes(requested.operation)) operationIds.push(requested.operation_id);
|
|
133
|
+
} catch (error) { if (error.code !== "ENOENT") throw error; }
|
|
134
|
+
}
|
|
135
|
+
return { stateDir, state, daemonId, sessionId, operationIds };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export async function releaseBudgetObservation(scope, observation) {
|
|
139
|
+
if (!scope || !scope.operationIds.length || observation?.settled !== true) return;
|
|
140
|
+
const id = observation.provider_session_id ?? observation.session_id ?? observation.adapter_session_id;
|
|
141
|
+
if (id !== scope.sessionId) throw Object.assign(new Error("budget observation belongs to another Session"), { code: "session_identity_mismatch" });
|
|
142
|
+
const directory = path.join(scope.stateDir, "budget-observations");
|
|
143
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
144
|
+
const file = path.join(directory, `${randomUUID()}.json`);
|
|
145
|
+
await writeJsonAtomic(file, { daemon_id: scope.daemonId, session_id: scope.sessionId, settled: true, operation_ids: scope.operationIds, observed_at: new Date().toISOString() });
|
|
146
|
+
await releaseBudget(scope.state, { "observation-file": file });
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function completeBudgetOperation(stateDir, request, result, observe) {
|
|
150
|
+
const state = await budgetState(stateDir);
|
|
151
|
+
if (!state?.config?.runtime_owner?.budget) return { state: "not_required" };
|
|
152
|
+
if (["session.create", "session.fork"].includes(request.operation)) {
|
|
153
|
+
await releaseBudget(state, { "operation-id": request.id });
|
|
154
|
+
return { state: "settled", operation_ids: [request.id], source: "creation_receipt" };
|
|
155
|
+
}
|
|
156
|
+
const sessionId = request.params?.sessionId;
|
|
157
|
+
// A productive daemon receipt is bound to this exact dispatch. Reuse its
|
|
158
|
+
// complete tree proof instead of immediately asking the provider again.
|
|
159
|
+
if (["session.prompt", "session.start", "session.resume"].includes(request.operation)
|
|
160
|
+
&& result?.settled === true
|
|
161
|
+
&& typeof sessionId === "string"
|
|
162
|
+
&& (result.provider_session_id ?? result.session_id ?? result.adapter_session_id) === sessionId) {
|
|
163
|
+
const scope = { stateDir, state, daemonId: state.daemon_id ?? state.config?.daemonId, sessionId, operationIds: [request.id] };
|
|
164
|
+
await releaseBudgetObservation(scope, result);
|
|
165
|
+
return { state: "settled", operation_ids: [request.id], source: "operation_receipt" };
|
|
166
|
+
}
|
|
167
|
+
// A known live tree is not a reason to poll it again. Preserve capacity and
|
|
168
|
+
// let the next explicit reconciliation obtain fresh proof.
|
|
169
|
+
if (result?.settled === false) return { state: "pending", reason: "tree_unsettled" };
|
|
170
|
+
const scope = await budgetObservationScope(stateDir, request.params?.sessionId);
|
|
171
|
+
if (!scope?.operationIds.length) return { state: "not_required" };
|
|
172
|
+
// Reconciliation requires a fresh, explicit proof. Treating an arbitrary
|
|
173
|
+
// productive reply as proof would silently release a live turn.
|
|
174
|
+
if (!observe) return { state: "pending", reason: "settlement_evidence_required" };
|
|
175
|
+
const observation = await observe();
|
|
176
|
+
if (observation?.settled !== true) return { state: "pending", reason: "tree_unsettled" };
|
|
177
|
+
await releaseBudgetObservation(scope, observation);
|
|
178
|
+
return { state: "settled", operation_ids: scope.operationIds, source: "reconciliation_observation" };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function budgetState(stateDir) {
|
|
182
|
+
try { return JSON.parse(await readFile(path.join(stateDir, "daemon.json"), "utf8")); }
|
|
183
|
+
catch (error) { if (error.code === "ENOENT") return null; throw error; }
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function releaseBudget(state, options) {
|
|
187
|
+
const record = state.resource_process, owner = state.config.runtime_owner;
|
|
188
|
+
const receipt = await retryBookkeeping(timeoutMs => runtimeProcess(state.config, "release-turn", { id: record.id, "lease-token": record.lease_token, owner: owner.owner_id, "daemon-id": state.daemon_id ?? state.config?.daemonId, ...options }, { timeoutMs }));
|
|
189
|
+
if (receipt?.ok !== true) throw Object.assign(new Error("runtime budget release was not confirmed"), { code: "runtime_budget_release_unconfirmed" });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export async function runtimeProcess(config, action, options = {}, { timeoutMs = RENEWAL_POLICY.budgetMs, signal } = {}) {
|
|
193
|
+
const resourceHome = config.runtime_owner?.resource_home ?? config.resourceHome;
|
|
194
|
+
if (action === "status" || !options.id || !resourceHome) return executeRuntimeProcess(config, action, options, { timeoutMs, signal });
|
|
195
|
+
const key = JSON.stringify([path.resolve(resourceHome), options.id, options["lease-token"] ?? null]);
|
|
196
|
+
const validBudget = Number.isFinite(timeoutMs) && timeoutMs > 0;
|
|
197
|
+
const deadline = performance.now() + (validBudget ? timeoutMs : 0);
|
|
198
|
+
const prior = maintenanceFlights.get(key);
|
|
199
|
+
let started = false;
|
|
200
|
+
let settle;
|
|
201
|
+
const flight = new Promise(resolve => { settle = resolve; });
|
|
202
|
+
maintenanceFlights.set(key, flight);
|
|
203
|
+
try {
|
|
204
|
+
if (!validBudget) throw Object.assign(new Error("Managed maintenance budget is invalid or exhausted before dispatch"), { code: "process_maintenance_timeout", details: { effect: "no_effect", native_dispatch_started: false } });
|
|
205
|
+
if (prior) {
|
|
206
|
+
let timer;
|
|
207
|
+
try {
|
|
208
|
+
await Promise.race([prior, new Promise((_, reject) => {
|
|
209
|
+
const expire = () => {
|
|
210
|
+
const remaining = deadline - performance.now();
|
|
211
|
+
// A timer wakeup is not proof that its monotonic deadline passed.
|
|
212
|
+
if (remaining > 0) { timer = setTimeout(expire, Math.min(2_147_483_647, Math.ceil(remaining))); return; }
|
|
213
|
+
reject(Object.assign(new Error("managed maintenance queue deadline expired"), { code: "process_maintenance_timeout", details: { effect: "no_effect" } }));
|
|
214
|
+
};
|
|
215
|
+
expire();
|
|
216
|
+
})]);
|
|
217
|
+
} finally { clearTimeout(timer); }
|
|
218
|
+
}
|
|
219
|
+
const remaining = deadline - performance.now();
|
|
220
|
+
if (remaining <= 0) throw Object.assign(new Error("managed maintenance queue deadline expired"), { code: "process_maintenance_timeout", details: { effect: "no_effect" } });
|
|
221
|
+
started = true;
|
|
222
|
+
return await executeRuntimeProcess(config, action, options, { timeoutMs: remaining, signal });
|
|
223
|
+
} catch (error) {
|
|
224
|
+
const lease = leases.get(key);
|
|
225
|
+
// A settled transport may have lost its ACK after COMMIT. Observe the exact
|
|
226
|
+
// owner, never replay the writer. Productive admission still checks RUN.
|
|
227
|
+
const registration = lease?.record.registration_sha256 ?? options["registration-sha256"];
|
|
228
|
+
if (action === "heartbeat" && error.details?.effect === "unknown" && error.details?.cleanup_unconfirmed !== true && /^[a-f0-9]{64}$/.test(registration ?? "")) {
|
|
229
|
+
const readback = await executeRuntimeProcess(config, "status", { id: options.id, "lease-token": options["lease-token"], "registration-sha256": registration }, { timeoutMs: RENEWAL_POLICY.budgetMs, signal });
|
|
230
|
+
if (readback?.ok === true && readback.process_id === options.id && readback.registration_sha256 === registration) return readback;
|
|
231
|
+
throw Object.assign(new Error("Managed heartbeat readback did not establish the exact effect"), { code: "process_maintenance_receipt_invalid" });
|
|
232
|
+
}
|
|
233
|
+
const text = value => typeof value === "string" ? value.slice(0, 128) : undefined;
|
|
234
|
+
const request = { phase: started ? "request" : "queue", action, process_id: text(options.id), operation_id: text(options["operation-id"] ?? options.operation ?? config.runtime_owner?.operation_id), role: text(lease?.record.kind ?? options.role ?? config.runtime_owner?.role), attempt_kind: prior ? "join" : "fresh", attempt: (lease?.renewal.attempt ?? 0) + 1, remaining_ms: Math.max(0, Math.round(deadline - performance.now())), native_dispatch_started: false, maintenance_effect: started ? "unknown" : "no_effect" };
|
|
235
|
+
// Parent queue/import failures precede any child trace. Keep only bounded,
|
|
236
|
+
// credential-free context and the existing child phase allowlist.
|
|
237
|
+
const childPhases = Array.isArray(error.details?.hook_diagnostics) ? error.details.hook_diagnostics : [];
|
|
238
|
+
error.details = { ...error.details, hook_diagnostics: [request, ...childPhases.slice(-15)] };
|
|
239
|
+
throw error;
|
|
240
|
+
} finally {
|
|
241
|
+
// Even a waiter whose deadline expires must retain its predecessor's
|
|
242
|
+
// physical fence until that predecessor settles.
|
|
243
|
+
const finish = () => {
|
|
244
|
+
settle();
|
|
245
|
+
if (maintenanceFlights.get(key) === flight) maintenanceFlights.delete(key);
|
|
246
|
+
};
|
|
247
|
+
if (prior) void prior.then(finish); else finish();
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** Only after the original maintenance transport physically settled. This
|
|
252
|
+
* endpoint reads exact current authority and cannot renew or reserve a turn. */
|
|
253
|
+
export async function readbackDaemonProcess(config, record, { signal } = {}) {
|
|
254
|
+
if (!record?.id || !record.lease_token || !/^[a-f0-9]{64}$/.test(record.registration_sha256 ?? "")) throw Object.assign(new Error("Readback has no exact registration"), { code: "process_maintenance_receipt_invalid" });
|
|
255
|
+
const result = await runtimeProcess(config, "status", { id: record.id, "lease-token": record.lease_token, "registration-sha256": record.registration_sha256 }, { timeoutMs: RENEWAL_POLICY.budgetMs, signal });
|
|
256
|
+
if (result?.ok !== true || result.process_id !== record.id || result.registration_sha256 !== record.registration_sha256) throw Object.assign(new Error("Readback returned foreign authority"), { code: "process_maintenance_receipt_invalid" });
|
|
257
|
+
retainReceipt(record, result);
|
|
258
|
+
return result;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async function executeRuntimeProcess(config, action, options = {}, { timeoutMs, signal }) {
|
|
262
|
+
const owner = config.runtime_owner;
|
|
263
|
+
const resourceHome = owner?.resource_home ?? config.resourceHome;
|
|
264
|
+
const ddFlowHome = owner?.dd_flow_home ?? config.ddFlowHome;
|
|
265
|
+
const ddFlowBin = owner?.dd_flow_bin ?? config.ddFlowBin;
|
|
266
|
+
if (!resourceHome || !ddFlowHome || !ddFlowBin) return null;
|
|
267
|
+
const poisonKey = token => JSON.stringify([path.resolve(resourceHome), options.id, token ?? null]);
|
|
268
|
+
if (action !== "status" && options.id) {
|
|
269
|
+
const poison = maintenancePoison.get(poisonKey(options["lease-token"])) ?? maintenancePoison.get(poisonKey(null));
|
|
270
|
+
if (poison && !(action === "finish" && poison.finish === JSON.stringify(options))) throw poison.error;
|
|
271
|
+
}
|
|
272
|
+
const args = ["runtime", "process", action];
|
|
273
|
+
for (const [key, value] of Object.entries(options)) if (value !== null && value !== undefined) args.push(`--${key}`, String(value));
|
|
274
|
+
if (["register", "confirm", "heartbeat", "admission", "check-admission", "finish", "release-turn", "status"].includes(action)) {
|
|
275
|
+
const maintenanceStarted = performance.now();
|
|
276
|
+
process.stderr.write(`${JSON.stringify({ kind: "process_maintenance_phase", phase: "request", observed_at: new Date().toISOString(), operation_id: options['operation-id'] ?? config.runtime_owner?.operation_id ?? null, action, process_id: options.id ?? null, remaining_ms: Math.round(timeoutMs), native_dispatch_started: false })}\n`);
|
|
277
|
+
let output;
|
|
278
|
+
try { output = await runBoundedCommand({ bin: ddFlowBin, args: [...args, "--json"], cwd: config.cwd,
|
|
279
|
+
env: { ...(config.env ?? {}), DD_FLOW_HOME: ddFlowHome, DD_FLOW_RESOURCE_HOME: resourceHome }, timeoutMs,
|
|
280
|
+
timeoutCode: action === "heartbeat" ? "process_heartbeat_timeout" : "process_maintenance_timeout", inheritDeadline: false, cleanupWithinBudget: true,
|
|
281
|
+
// Storage silence is not loss of authority: the original command/lease
|
|
282
|
+
// deadline remains finite, including slow I/O and multiple bounded reads.
|
|
283
|
+
maintenanceProgress: { action, processId: options.id ?? null, startupMs: Math.min(timeoutMs, RENEWAL_POLICY.startupMs), inactivityMs: timeoutMs }, signal }); }
|
|
284
|
+
catch (error) {
|
|
285
|
+
if (action !== "status" && options.id && error.details?.cleanup_unconfirmed) maintenancePoison.set(poisonKey(options["lease-token"]), {
|
|
286
|
+
error, ...(action === "finish" && error.code === "process_maintenance_timeout" ? { finish: JSON.stringify(options) } : {})
|
|
287
|
+
});
|
|
288
|
+
throw error;
|
|
289
|
+
}
|
|
290
|
+
let result;
|
|
291
|
+
try { result = JSON.parse(output); } catch { throw Object.assign(new Error("Invalid managed process maintenance response"), { code: "process_maintenance_receipt_invalid", details: { action } }); }
|
|
292
|
+
if (result?.ok === false && result.error?.code) throw Object.assign(new Error(result.error.message ?? "Managed process maintenance failed"), { code: result.error.code, details: result.error.details });
|
|
293
|
+
process.stderr.write(`${JSON.stringify({ kind: "process_maintenance_phase", phase: "ack", observed_at: new Date().toISOString(), duration_ms: Math.round(performance.now() - maintenanceStarted), operation_id: options['operation-id'] ?? config.runtime_owner?.operation_id ?? null, action, process_id: options.id ?? null, lease_expires_at: result.lease_expires_at ?? result.process?.lease_expires_at ?? null, native_dispatch_started: false })}\n`);
|
|
294
|
+
return result;
|
|
295
|
+
}
|
|
296
|
+
return await commandJson(ddFlowBin, args, {
|
|
297
|
+
cwd: config.cwd,
|
|
298
|
+
env: { ...(config.env ?? {}), DD_FLOW_HOME: ddFlowHome, DD_FLOW_RESOURCE_HOME: resourceHome }
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export async function registerDaemonProcess(config, input) {
|
|
303
|
+
const owner = bindRuntimeOwner(config);
|
|
304
|
+
const harness = ({ "codex-daemon": "codex-desktop", "codex-provider": "codex-desktop", "zcode-daemon": "zcode-acp", "zcode-provider": "zcode-acp", "grok-daemon": "grok-acp", "grok-provider": "grok-acp", "agy-daemon": "antigravity-cli", "opencode-daemon": "opencode-server", "opencode-provider": "opencode-server", "droid-daemon": "droid-cli" })[input.kind];
|
|
305
|
+
const scope = owner ? { owner: owner.owner_id, "project-id": owner.project_id, run: owner.run_id, work: owner.work_id, role: owner.role, "state-dir": owner.state_dir, "adapter-executable": owner.adapter_executable, ...(owner.budget ? { "budget-json": JSON.stringify(owner.budget), harness } : {}), ...(owner.project_id && owner.run_id || owner.budget ? { "dispatch-barrier": "run-generation@1" } : {}), operation: `${owner.operation_id}:${input.operation ?? input.kind}` } : {};
|
|
306
|
+
const options = { ...input, ...scope, id: `PROC-${input.kind}-${randomUUID()}`, "owner-pid": process.pid, "lease-ms": 300_000 };
|
|
307
|
+
const renewal = createRenewalState();
|
|
308
|
+
renewal.uncertainSince = performance.now();
|
|
309
|
+
let result;
|
|
310
|
+
for (;;) {
|
|
311
|
+
try { result = await runtimeProcess(config, "register", options, { timeoutMs: renewalRemaining(renewal) }); break; }
|
|
312
|
+
catch (error) {
|
|
313
|
+
const unknownSettled = error.details?.effect === "unknown" && error.details?.cleanup_unconfirmed !== true;
|
|
314
|
+
if ((!maintenanceRetryable(error) && !unknownSettled) || !renewalFailure(renewal)) throw error;
|
|
315
|
+
// The child has exited before a timeout is retryable. Reconcile the exact
|
|
316
|
+
// startup identity, not an arbitrary PID or another operation's owner.
|
|
317
|
+
let status;
|
|
318
|
+
try { status = await retryBookkeeping(timeoutMs => runtimeProcess(config, "status", {}, { timeoutMs }), renewal); }
|
|
319
|
+
catch (reconciliationError) {
|
|
320
|
+
// Retain the failed write's phases without changing the reconciliation
|
|
321
|
+
// failure's classification or authorizing another startup attempt.
|
|
322
|
+
reconciliationError.details = { ...reconciliationError.details, registration_error: errorRecord(error) };
|
|
323
|
+
throw reconciliationError;
|
|
324
|
+
}
|
|
325
|
+
const retained = status?.processes?.find(record => record.id === options.id);
|
|
326
|
+
if (retained) {
|
|
327
|
+
let metadata; try { metadata = JSON.parse(retained.metadata_json); } catch { /* Invalid binding below. */ }
|
|
328
|
+
if (retained.kind !== options.kind || retained.owner_id !== options.owner || retained.operation_id !== options.operation || retained.state !== "starting" || metadata?.owner_pid !== process.pid) throw Object.assign(new Error("Startup reconciliation changed managed process binding"), { code: "process_maintenance_receipt_invalid" });
|
|
329
|
+
result = { process: retained }; break;
|
|
330
|
+
}
|
|
331
|
+
if (!Array.isArray(status?.processes)) throw Object.assign(new Error("Startup reconciliation has no registry proof"), { code: "process_maintenance_receipt_invalid" });
|
|
332
|
+
await new Promise(resolve => setTimeout(resolve, Math.min(renewalDelay(renewal), renewalRemaining(renewal))));
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (!result) return null;
|
|
336
|
+
if (result.process?.id !== options.id || !result.process.lease_token) throw Object.assign(new Error("Managed startup receipt belongs to another registration"), { code: "process_maintenance_receipt_invalid" });
|
|
337
|
+
requireTimelyMaintenance(renewal, "register", result, result.process);
|
|
338
|
+
return { id: result.process.id, lease_token: result.process.lease_token, kind: input.kind, lease_expires_at: result.process.lease_expires_at };
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export async function confirmDaemonProcess(config, record, child) {
|
|
342
|
+
if (!record) return;
|
|
343
|
+
if (!child?.pid) {
|
|
344
|
+
await finishDaemonProcess(config, record, "failed", "daemon_spawn_missing_pid");
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
const renewal = createRenewalState();
|
|
348
|
+
const result = await retryBookkeeping(timeoutMs => runtimeProcess(config, "confirm", { id: record.id, "lease-token": record.lease_token, pid: child.pid, "process-group-id": child.pid, "owner-pid": record.kind?.endsWith("-daemon") ? child.pid : process.pid, "lease-ms": 300_000 }, { timeoutMs }), renewal);
|
|
349
|
+
if (!result?.process || result.process.id !== record.id || result.process.lease_token !== record.lease_token) throw Object.assign(new Error("Managed process confirmation binding is invalid"), { code: "process_maintenance_receipt_invalid" });
|
|
350
|
+
retainReceipt(record, result.process);
|
|
351
|
+
requireTimelyMaintenance(renewal, "confirm", result, record);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/** Provider bridges use a different process group from their daemon. Publish
|
|
355
|
+
* the exact binding before initialize can start productive provider traffic. */
|
|
356
|
+
export async function startManagedBridge({ config, state, bridge, kind, publish, log }) {
|
|
357
|
+
let provider;
|
|
358
|
+
try {
|
|
359
|
+
state.pid = process.pid;
|
|
360
|
+
await publish(state);
|
|
361
|
+
provider = config.runtime_owner ? await registerDaemonProcess(config, { kind, owner: config.runtime_owner.owner_id, operation: kind }) : null;
|
|
362
|
+
state.provider_process = provider;
|
|
363
|
+
state.startup_phase = "owner_registered";
|
|
364
|
+
await publish(state);
|
|
365
|
+
bridge.options.onSpawn = async child => {
|
|
366
|
+
await confirmDaemonProcess(config, provider, child);
|
|
367
|
+
state.provider_pid = child.pid;
|
|
368
|
+
state.startup_phase = "provider_spawned";
|
|
369
|
+
await publish(state);
|
|
370
|
+
scheduleDaemonProcessHeartbeat(config, provider);
|
|
371
|
+
};
|
|
372
|
+
const initialized = await bridge.start();
|
|
373
|
+
state.startup_phase = "native_initialized";
|
|
374
|
+
await publish(state);
|
|
375
|
+
return initialized;
|
|
376
|
+
}
|
|
377
|
+
catch (error) {
|
|
378
|
+
await cleanupFailedStart(config, provider, bridge.child, error);
|
|
379
|
+
state.shutdown_state = error.details?.cleanup_failed ? "cleanup_failed" : "failed";
|
|
380
|
+
await retainStartupFailure({ state, publish, log, error });
|
|
381
|
+
throw error;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/** A retained cause belongs to this startup generation, never a previous PID. */
|
|
386
|
+
export function assertStartupChild(child, expected, observed, log) {
|
|
387
|
+
const sameGeneration = observed?.daemon_id === expected.daemon_id && (observed?.generation ?? null) === (expected.generation ?? null)
|
|
388
|
+
&& (observed?.config?.runtime_owner?.owner_id ?? null) === (expected.config?.runtime_owner?.owner_id ?? null)
|
|
389
|
+
&& (observed?.config?.runtime_owner?.operation_id ?? null) === (expected.config?.runtime_owner?.operation_id ?? null);
|
|
390
|
+
if (sameGeneration && observed?.startup_error) throw reportedError(observed.startup_error, "Native startup failed");
|
|
391
|
+
if (child.exitCode !== null || child.signalCode !== null) throw Object.assign(new Error("Native daemon exited before readiness"), {
|
|
392
|
+
code: "daemon_start_failed", retryable: false, details: { daemon_id: expected.daemon_id,
|
|
393
|
+
pid: child.pid ?? null, exit_code: child.exitCode, signal: child.signalCode, daemon_log: log }
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export async function retainStartupFailure({ state, publish, log, error }) {
|
|
398
|
+
state.startup_error = JSON.parse(diagnosticJson(errorRecord(error)));
|
|
399
|
+
try { await publish(state); }
|
|
400
|
+
catch (publication) {
|
|
401
|
+
error.details = { ...error.details, startup_publication_error: errorRecord(publication), daemon_log: log };
|
|
402
|
+
// No credentials/config/argv: the log is only a bounded error record fallback.
|
|
403
|
+
try { if (log) await appendFile(log, `${diagnosticJson({ daemon_id: state.daemon_id, startup_error: errorRecord(error) })}\n`, { mode: 0o600 }); }
|
|
404
|
+
catch (logging) { error.details.startup_log_error = errorRecord(logging); }
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export async function closeManagedBridge(config, state, bridge) {
|
|
409
|
+
await bridge.close();
|
|
410
|
+
await finishDaemonProcess(config, state.provider_process, "stopped", "daemon_stopped");
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export function scheduleDaemonProcessHeartbeat(config, record, { signal } = {}) {
|
|
414
|
+
if (!record?.id || !record.lease_token) return;
|
|
415
|
+
const key = leaseKey(config, record);
|
|
416
|
+
if (leases.has(key)) {
|
|
417
|
+
if (signal && leases.get(key).signal !== signal) throw Object.assign(new Error("Heartbeat cancellation owner differs from the retained lease"), { code: "process_maintenance_signal_conflict" });
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
const lease = { pending: null, error: null, timer: null, lost: false, closing: false, signal, expiresAt: Date.parse(record.lease_expires_at ?? ""), renewal: createRenewalState({ leaseExpiresAt: record.lease_expires_at }), stateDir: path.resolve(config.runtime_owner?.state_dir ?? config.stateDir ?? config.cwd), config, record };
|
|
421
|
+
leases.set(key, lease);
|
|
422
|
+
// Retry no-effect contention inside the same authority episode while native
|
|
423
|
+
// work is active; scheduling ticks are not ownership evidence.
|
|
424
|
+
lease.timer = setInterval(() => reconfirmLease(lease, () => {}).catch(() => {}), 60_000);
|
|
425
|
+
lease.timer.unref();
|
|
426
|
+
if (signal) {
|
|
427
|
+
signal.addEventListener("abort", () => clearInterval(lease.timer), { once: true });
|
|
428
|
+
if (signal.aborted) clearInterval(lease.timer);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
export async function heartbeatDaemonProcess(config, record, { signal } = {}) {
|
|
433
|
+
if (!record?.id || !record.lease_token) return;
|
|
434
|
+
scheduleDaemonProcessHeartbeat(config, record, { signal });
|
|
435
|
+
const lease = leases.get(leaseKey(config, record));
|
|
436
|
+
if (lease.closing) throw Object.assign(new Error("Managed process is closing"), { code: "process_closing" });
|
|
437
|
+
if (lease.lost) throw lease.error;
|
|
438
|
+
if (lease.error && !maintenanceRetryable(lease.error)) throw lease.error;
|
|
439
|
+
if (!renewalRemaining(lease.renewal)) throw lease.error ?? Object.assign(new Error("Managed lease authority has expired before heartbeat dispatch"), {
|
|
440
|
+
code: "process_ownership_unknown", details: { process_id: record.id, reason: "lease_authority_expired" }
|
|
441
|
+
});
|
|
442
|
+
if (lease.pending) return await lease.pending;
|
|
443
|
+
lease.pending = (async () => {
|
|
444
|
+
try {
|
|
445
|
+
lease.renewal.uncertainSince ??= performance.now();
|
|
446
|
+
const result = await runtimeProcess(config, "heartbeat", { id: record.id, "lease-token": record.lease_token, "lease-ms": 300_000 }, { timeoutMs: renewalRemaining(lease.renewal), signal: lease.signal });
|
|
447
|
+
if (result?.ok === false) {
|
|
448
|
+
lease.lost = true; clearInterval(lease.timer);
|
|
449
|
+
throw Object.assign(new Error("managed process lease is no longer owned; new productive operations are blocked"), { code: "process_lease_lost", details: { process_id: record.id } });
|
|
450
|
+
}
|
|
451
|
+
if (result?.ok !== true || result.process_id !== record.id) throw Object.assign(new Error("Managed renewal response belongs to another process"), { code: "process_maintenance_receipt_invalid", details: { process_id: record.id } });
|
|
452
|
+
if (!lease.closing) {
|
|
453
|
+
retainReceipt(record, result);
|
|
454
|
+
if (!renewalConfirmed(lease.renewal, performance.now(), record.lease_expires_at)) requireTimelyMaintenance(lease.renewal, "heartbeat", result, record);
|
|
455
|
+
lease.error = null; lease.expiresAt = Date.parse(record.lease_expires_at);
|
|
456
|
+
}
|
|
457
|
+
} catch (cause) {
|
|
458
|
+
if (!lease.closing) renewalFailure(lease.renewal);
|
|
459
|
+
lease.error = cause.code === "process_lease_lost" ? cause : Object.assign(new Error("cannot confirm managed process ownership"), { code: "process_ownership_unknown", cause, details: { process_id: record.id, cause: errorRecord(cause) } });
|
|
460
|
+
throw lease.error;
|
|
461
|
+
}
|
|
462
|
+
})();
|
|
463
|
+
try { await lease.pending; } finally { lease.pending = null; }
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
export async function finishDaemonProcess(config, process, state = "stopped", reason = "daemon_stopped") {
|
|
467
|
+
if (process?.id && process.lease_token) {
|
|
468
|
+
const key = leaseKey(config, process), lease = leases.get(key);
|
|
469
|
+
if (lease) { lease.closing = true; clearInterval(lease.timer); await lease.pending?.catch(() => {}); if (lease.error?.cause?.details?.cleanup_unconfirmed) throw lease.error; }
|
|
470
|
+
// A predecessor owns its transport fence; finish gets an independent
|
|
471
|
+
// settlement window only after that flight physically settled.
|
|
472
|
+
const settlement = createRenewalState(); settlement.uncertainSince = performance.now();
|
|
473
|
+
const result = await retryBookkeeping(timeoutMs => runtimeProcess(config, "finish", { id: process.id, "lease-token": process.lease_token, state, reason }, { timeoutMs }), settlement, true);
|
|
474
|
+
if (result?.ok !== true) throw Object.assign(new Error("managed process finish was not accepted"), { code: "process_lease_lost", details: { process_id: process.id } });
|
|
475
|
+
leases.delete(key);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function leaseKey(config, record) { return JSON.stringify([path.resolve(config.runtime_owner?.resource_home ?? config.resourceHome), record.id, record.lease_token]); }
|
|
480
|
+
function retainReceipt(record, receipt) {
|
|
481
|
+
const expiresAt = Date.parse(receipt.lease_expires_at ?? "");
|
|
482
|
+
if (!Number.isFinite(expiresAt) || expiresAt <= Date.now() + RENEWAL_POLICY.safetyMs || !/^[a-f0-9]{64}$/.test(receipt.registration_sha256 ?? "") || record.registration_sha256 && record.registration_sha256 !== receipt.registration_sha256) throw Object.assign(new Error("Managed lease receipt has no authoritative expiry/binding"), { code: "process_maintenance_receipt_invalid", details: { process_id: record.id } });
|
|
483
|
+
record.lease_expires_at = receipt.lease_expires_at;
|
|
484
|
+
record.registration_sha256 = receipt.registration_sha256;
|
|
485
|
+
}
|
|
486
|
+
async function reconfirmLease(lease, assertCurrent) {
|
|
487
|
+
for (;;) {
|
|
488
|
+
assertCurrent();
|
|
489
|
+
if (lease.closing || lease.lost) throw lease.error ?? Object.assign(new Error("Managed process is closing"), { code: "process_closing" });
|
|
490
|
+
try { await heartbeatDaemonProcess(lease.config, lease.record); }
|
|
491
|
+
catch (error) {
|
|
492
|
+
if (!maintenanceRetryable(error) || !renewalRemaining(lease.renewal)) throw error;
|
|
493
|
+
await new Promise(resolve => setTimeout(resolve, Math.min(renewalDelay(lease.renewal), renewalRemaining(lease.renewal))));
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
assertCurrent();
|
|
497
|
+
if (lease.closing || lease.error || !Number.isFinite(lease.expiresAt) || Date.now() >= lease.expiresAt) throw lease.error ?? Object.assign(new Error("Committed lease receipt expired before admission"), { code: "process_ownership_unknown" });
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
async function retryBookkeeping(action, renewal = createRenewalState(), repeatFinish = false) {
|
|
502
|
+
renewal.uncertainSince ??= performance.now();
|
|
503
|
+
for (;;) {
|
|
504
|
+
try { return await action(renewalRemaining(renewal)); }
|
|
505
|
+
catch (error) {
|
|
506
|
+
// Exact finish is an idempotent terminal-state CAS, not productive IO.
|
|
507
|
+
// Its old helper may still commit the same finish; all other writes stay fenced.
|
|
508
|
+
if (!(maintenanceRetryable(error) || repeatFinish && error.code === "process_maintenance_timeout" && error.details?.cleanup_unconfirmed === true) || !renewalFailure(renewal)) throw error;
|
|
509
|
+
await new Promise(resolve => setTimeout(resolve, Math.min(renewalDelay(renewal), renewalRemaining(renewal))));
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// A current exact committed ACK establishes authority, not an arbitrary
|
|
515
|
+
// elapsed-time cutoff. Cleanup finish/release remains a non-productive fact.
|
|
516
|
+
function requireTimelyMaintenance(renewal, action, receipt, record) {
|
|
517
|
+
const expiry = receipt?.process?.lease_expires_at ?? receipt?.lease_expires_at;
|
|
518
|
+
if (expiry && renewalConfirmed(renewal, performance.now(), expiry)) return;
|
|
519
|
+
if (!expiry && renewalRemaining(renewal)) return;
|
|
520
|
+
const safeText = value => typeof value === "string" ? value.slice(0, 128) : undefined;
|
|
521
|
+
const hash = receipt?.process?.registration_sha256 ?? receipt?.registration_sha256 ?? record?.registration_sha256;
|
|
522
|
+
throw Object.assign(new Error("Managed ownership confirmation arrived after its uncertainty budget"), {
|
|
523
|
+
code: "process_ownership_unknown", details: { action, process_id: safeText(record?.id ?? receipt?.process_id),
|
|
524
|
+
registration_sha256: typeof hash === "string" && /^[a-f0-9]{64}$/.test(hash) ? hash : undefined,
|
|
525
|
+
lease_expires_at: safeText(receipt?.process?.lease_expires_at ?? receipt?.lease_expires_at ?? record?.lease_expires_at),
|
|
526
|
+
effect: action === "admission" && receipt?.admitted !== true ? "no_effect" : "committed", native_dispatch_started: false }
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/** Preserve the startup error and retain the lease/resources if cleanup cannot
|
|
531
|
+
* prove termination. A failed cleanup is not a terminal managed process. */
|
|
532
|
+
export async function cleanupFailedStart(config, record, child, original) {
|
|
533
|
+
try {
|
|
534
|
+
await stopProcessGroup(child);
|
|
535
|
+
const stopped = child?.pid ? { pid: child.pid, pgid: process.platform === "win32" ? null : child.pid,
|
|
536
|
+
settled: true, physical_exit: "ESRCH", observed_at: new Date().toISOString() } : undefined;
|
|
537
|
+
await finishDaemonProcess(config, record, "failed", original.code ?? "startup_failed");
|
|
538
|
+
return stopped;
|
|
539
|
+
} catch (cleanup) {
|
|
540
|
+
original.details = { ...original.details, cleanup_failed: errorRecord(cleanup), process_id: record?.id ?? null, pid: child?.pid ?? null };
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/** The provider has its own process group. A failed daemon start must settle
|
|
545
|
+
* that exact retained process as well as the daemon; PID-only cleanup is unsafe. */
|
|
546
|
+
export async function cleanupFailedDaemonStart(config, daemonRecord, daemonChild, original, readState, daemonId) {
|
|
547
|
+
const stopped = await cleanupFailedStart(config, daemonRecord, daemonChild, original);
|
|
548
|
+
if (!daemonRecord) return;
|
|
549
|
+
try {
|
|
550
|
+
const state = await readState();
|
|
551
|
+
const owner = config.runtime_owner;
|
|
552
|
+
if (!state || state.daemon_id !== daemonId || state.resource_process?.id !== daemonRecord.id || state.resource_process?.lease_token !== daemonRecord.lease_token
|
|
553
|
+
|| (owner && (state.config?.runtime_owner?.owner_id !== owner.owner_id || path.resolve(state.config?.runtime_owner?.state_dir ?? "") !== path.resolve(owner.state_dir)))) {
|
|
554
|
+
throw Object.assign(new Error("Failed daemon state no longer matches the startup owner"), { code: "daemon_cleanup_identity_mismatch" });
|
|
555
|
+
}
|
|
556
|
+
const provider = state.provider_process;
|
|
557
|
+
if (provider) await runtimeProcess(config, "stop", { id: provider.id, "lease-token": provider.lease_token });
|
|
558
|
+
// Confirmation can fail before the registry learns the child's PID. The
|
|
559
|
+
// owned child handle still proves its physical exit, not a productive ACK.
|
|
560
|
+
if (stopped && !original.details?.cleanup_failed && !original.details?.provider_cleanup_failed && state.pid === stopped.pid && !provider && state.provider_pid == null && owner) {
|
|
561
|
+
original.details = { ...original.details, startup_child_cleanup: {
|
|
562
|
+
...stopped, daemon_id: daemonId, process_id: daemonRecord.id,
|
|
563
|
+
owner_id: owner.owner_id, project_id: owner.project_id, run_id: owner.run_id,
|
|
564
|
+
operation_id: owner.operation_id, state_dir: owner.state_dir
|
|
565
|
+
} };
|
|
566
|
+
}
|
|
567
|
+
} catch (cleanup) {
|
|
568
|
+
original.details = { ...original.details, provider_cleanup_failed: errorRecord(cleanup) };
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const processStops = new WeakMap();
|
|
573
|
+
export async function closeBridgeProcess(bridge, drain, stopOptions = {}) {
|
|
574
|
+
if (bridge.closePending) return await bridge.closePending;
|
|
575
|
+
const pending = (async () => {
|
|
576
|
+
let primary;
|
|
577
|
+
try {
|
|
578
|
+
if (!bridge.physicallyClosed && bridge.child) {
|
|
579
|
+
const child = bridge.child;
|
|
580
|
+
if (!bridge.stdinEnded && !child.stdin?.destroyed) { bridge.stdinEnded = true; child.stdin?.end(); }
|
|
581
|
+
if (child.exitCode == null && child.signalCode == null) {
|
|
582
|
+
await new Promise(resolve => {
|
|
583
|
+
const done = () => { clearTimeout(timer); child.removeListener("exit", done); resolve(); };
|
|
584
|
+
const timer = setTimeout(done, 2_000); child.once("exit", done);
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
await stopProcessGroup(child, 2_000, stopOptions);
|
|
588
|
+
bridge.physicallyClosed = true;
|
|
589
|
+
}
|
|
590
|
+
} catch (error) { primary = error; }
|
|
591
|
+
let drainTimer;
|
|
592
|
+
try { await Promise.race([drain(), new Promise((_, reject) => { drainTimer = setTimeout(() => reject(Object.assign(new Error("bridge drain did not finish"), { code: "bridge_drain_timeout" })), 10_000); })]); }
|
|
593
|
+
catch (error) { if (primary) { primary.cleanup_error = errorRecord(error); primary.details = { ...primary.details, cleanup_error: primary.cleanup_error }; } else primary = error; }
|
|
594
|
+
finally { clearTimeout(drainTimer); }
|
|
595
|
+
if (primary) throw primary;
|
|
596
|
+
})();
|
|
597
|
+
bridge.closePending = pending;
|
|
598
|
+
try { return await pending; }
|
|
599
|
+
finally { bridge.closePending = null; }
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
export async function stopProcessGroup(child, graceMs = 1_000, { detached = true, settlementMs = 10_000, started = child?.started ?? null } = {}) {
|
|
603
|
+
if (!child?.pid) return;
|
|
604
|
+
if (processStops.has(child)) return await processStops.get(child);
|
|
605
|
+
const pending = stopOwnedChild(child, graceMs, detached, settlementMs, started);
|
|
606
|
+
processStops.set(child, pending);
|
|
607
|
+
try { return await pending; } finally { processStops.delete(child); }
|
|
608
|
+
}
|
|
609
|
+
async function stopOwnedChild(child, graceMs, detached, settlementMs, started) {
|
|
610
|
+
const target = process.platform === "win32" || !detached ? child.pid : -child.pid;
|
|
611
|
+
const details = signal => ({ pid: child.pid, pgid: target < 0 ? child.pid : null, signal, platform: process.platform, exit_code: child.exitCode ?? null, signal_code: child.signalCode ?? null, observed_at: new Date().toISOString() });
|
|
612
|
+
const alive = () => { try { process.kill(target, 0); return true; } catch (error) { if (error.code === "ESRCH") return false; if (error.code === "EPERM") return true; throw error; } };
|
|
613
|
+
const gone = async ms => {
|
|
614
|
+
const deadline = performance.now() + ms;
|
|
615
|
+
while (alive() && performance.now() < deadline) await new Promise(resolve => setTimeout(resolve, 25));
|
|
616
|
+
return !alive();
|
|
617
|
+
};
|
|
618
|
+
const signal = async name => {
|
|
619
|
+
// A retained raw PID is not a ChildProcess handle. Revalidate its birth at
|
|
620
|
+
// every signal, including escalation after the TERM grace period.
|
|
621
|
+
if (started !== null) {
|
|
622
|
+
if (typeof started !== 'string' || !started) throw Object.assign(new Error('process birth identity is invalid'), { code: 'process_identity_unproven' });
|
|
623
|
+
const observed = (await processSnapshot()).find(item => item.pid === child.pid);
|
|
624
|
+
if (!observed || observed.started !== started || observed.zombie) return false;
|
|
625
|
+
}
|
|
626
|
+
try { process.kill(target, name); return true; }
|
|
627
|
+
catch (error) {
|
|
628
|
+
if (error.code === "ESRCH") return false;
|
|
629
|
+
error.details = { ...error.details, ...details(name), syscall: error.syscall ?? "kill" };
|
|
630
|
+
// Permission denial is not permission to escalate. Observe only the retained target.
|
|
631
|
+
let absent = false;
|
|
632
|
+
try { absent = await gone(Math.min(250, Math.max(25, graceMs))); }
|
|
633
|
+
catch (observation) { error.details.observation_error = errorRecord(observation); }
|
|
634
|
+
if (absent) {
|
|
635
|
+
process.stderr.write(`${JSON.stringify({ cleanup_reobserved: errorRecord(error), physical_exit: "ESRCH" })}\n`);
|
|
636
|
+
return false;
|
|
637
|
+
}
|
|
638
|
+
throw error;
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
if (!alive()) return;
|
|
642
|
+
const leaderExited = () => child.exitCode != null || child.signalCode != null;
|
|
643
|
+
const observeLeaderless = async () => {
|
|
644
|
+
// Signal grace is not a drain deadline. Helpers can retire naturally after
|
|
645
|
+
// their leader exits; observing them does not authorize another signal.
|
|
646
|
+
if (await gone(Math.max(1_000, graceMs, settlementMs))) return;
|
|
647
|
+
throw Object.assign(new Error("process group remains after its leader exited; ownership cannot be proven from this child handle"), { code: "process_group_ownership_unknown", details: { ...details(null), settlement_ms: Math.max(1_000, graceMs, settlementMs) } });
|
|
648
|
+
};
|
|
649
|
+
if (leaderExited()) return await observeLeaderless();
|
|
650
|
+
if (!await signal("SIGTERM")) return;
|
|
651
|
+
if (await gone(graceMs)) return;
|
|
652
|
+
if (leaderExited()) return await observeLeaderless();
|
|
653
|
+
if (!await signal("SIGKILL")) return;
|
|
654
|
+
if (!await gone(Math.max(1_000, graceMs))) throw Object.assign(new Error(`process group ${child.pid} did not stop`), { code: "process_group_stop_incomplete", details: details("SIGKILL") });
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/** A daemon acknowledgement is not a completed stop until its endpoint closes. */
|
|
658
|
+
export async function confirmDaemonStopped(request, socket, timeoutMs = 30_000, { readState } = {}) {
|
|
659
|
+
const expected = await readState?.();
|
|
660
|
+
const identity = state => state?.daemon_id ?? state?.config?.daemonId;
|
|
661
|
+
if (readState && (!identity(expected) || !Number.isSafeInteger(expected?.pid) || expected.pid < 1)) throw Object.assign(new Error('daemon stop has no retained incarnation/PID proof'), { code: 'daemon_cleanup_identity_mismatch' });
|
|
662
|
+
const failure = async observer => {
|
|
663
|
+
const state = await readState?.();
|
|
664
|
+
if (expected && (identity(state) !== identity(expected) || state?.pid !== expected.pid)) throw Object.assign(new Error("daemon incarnation changed during stop"), { code: "daemon_identity_mismatch" });
|
|
665
|
+
const primary = state?.shutdown_state === "cleanup_failed" ? state.cleanup_error : null;
|
|
666
|
+
if (primary) throw Object.assign(new Error(primary.message), primary, { details: { ...primary.details, socket, phase: "confirm_daemon_stop", observer_error: errorRecord(observer) } });
|
|
667
|
+
throw observer;
|
|
668
|
+
};
|
|
669
|
+
let result, observationError;
|
|
670
|
+
try { result = await request(); }
|
|
671
|
+
catch (error) {
|
|
672
|
+
if (!expected || !["daemon_timeout", "daemon_not_running", "daemon_connection_closed", "operation_observation_lost"].includes(error.code)) return await failure(error);
|
|
673
|
+
observationError = error;
|
|
674
|
+
}
|
|
675
|
+
if (!observationError && result?.stopped !== true) return await failure(Object.assign(new Error("stop acknowledgement does not prove shutdown"), { code: "daemon_stop_incomplete" }));
|
|
676
|
+
// The RPC wait and physical retirement are separate phases. A lost ACK
|
|
677
|
+
// consumes the former, not the existing confirmation budget.
|
|
678
|
+
const deadline = performance.now() + timeoutMs;
|
|
679
|
+
for (;;) {
|
|
680
|
+
const state = await readState?.();
|
|
681
|
+
if (expected && (identity(state) !== identity(expected) || state?.pid !== expected.pid)) return await failure(Object.assign(new Error("daemon incarnation changed"), { code: "daemon_identity_mismatch" }));
|
|
682
|
+
if (state?.shutdown_state === "cleanup_failed") return await failure(observationError ?? Object.assign(new Error("daemon cleanup failed"), { code: "daemon_stop_incomplete" }));
|
|
683
|
+
let endpointPresent = true;
|
|
684
|
+
try { await lstat(socket); } catch (error) { if (error.code === "ENOENT") endpointPresent = false; else throw error; }
|
|
685
|
+
let daemonPresent = false;
|
|
686
|
+
if (expected?.pid) { try { process.kill(expected.pid, 0); daemonPresent = true; } catch (error) { if (error.code !== "ESRCH") daemonPresent = true; } }
|
|
687
|
+
if (!endpointPresent && !daemonPresent) {
|
|
688
|
+
if (observationError) {
|
|
689
|
+
if (state?.shutdown_state === "clean" && state.active_tree === false && !state.cleanup_error) return { stopped: true, clean: true };
|
|
690
|
+
return await failure(observationError);
|
|
691
|
+
}
|
|
692
|
+
if (readState && result.clean === true && (state?.shutdown_state !== 'clean' || state.active_tree !== false)) return await failure(Object.assign(new Error('daemon clean ACK conflicts with its retained state'), { code: 'daemon_stop_incomplete' }));
|
|
693
|
+
return result;
|
|
694
|
+
}
|
|
695
|
+
if (performance.now() >= deadline) return await failure(Object.assign(new Error("daemon stop could not confirm endpoint retirement and owned PID exit"), { code: "daemon_stop_incomplete", details: { socket, daemon_pid: expected?.pid ?? null, endpoint_present: endpointPresent, daemon_present: daemonPresent } }));
|
|
696
|
+
await new Promise(resolve => setTimeout(resolve, 25));
|
|
697
|
+
}
|
|
698
|
+
}
|