@deksden-com/dd-flow-cli 0.9.0-beta.144 → 0.9.0-beta.149
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 +130 -0
- package/README.md +13 -8
- package/dist/build-info.json +7 -7
- package/dist/cli/command-inputs.js +13 -3
- package/dist/cli/command-metadata.js +51 -16
- package/dist/cli/diagnostic-invocation.js +46 -0
- package/dist/cli/diagnostic.js +120 -0
- package/dist/cli/help.js +67 -1
- package/dist/cli/hook-ingress.js +27 -3
- package/dist/cli/memory-flow-admission.js +9 -3
- package/dist/cli/memory-init-invocation.js +10 -3
- package/dist/cli/memory-init.js +77 -68
- package/dist/cli/memory-storage-admission.js +3 -2
- 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/native-hook-ingress.js +14 -4
- package/dist/cli/native-hook-preparation.js +55 -0
- package/dist/cli/response-file.js +2 -1
- package/dist/cli/run-cli.js +79 -40
- package/dist/cli/setup.js +2 -1
- package/dist/cli.js +18 -0
- package/dist/domain/diagnostic-aspects.js +103 -0
- package/dist/domain/diagnostic-flows.js +8 -0
- package/dist/domain/stage-handler-identities.js +8 -0
- package/dist/harness-runtime/bin/dd-agy.mjs +3 -3
- package/dist/harness-runtime/bin/dd-grok.mjs +3 -3
- package/dist/harness-runtime/bin/dd-zcode.mjs +5 -5
- package/dist/harness-runtime/lib/agy-error-boundary.mjs +58 -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-operations.d.mts +1 -0
- package/dist/harness-runtime/lib/daemon-operations.mjs +84 -10
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +366 -52
- package/dist/harness-runtime/lib/dd-agy.mjs +17 -3
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +27 -13
- package/dist/harness-runtime/lib/dd-codex.mjs +4 -0
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +4 -3
- package/dist/harness-runtime/lib/dd-droid.mjs +71 -29
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +73 -16
- package/dist/harness-runtime/lib/dd-grok.mjs +6 -3
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +66 -20
- package/dist/harness-runtime/lib/dd-opencode.mjs +30 -3
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +7 -6
- package/dist/harness-runtime/lib/dd-zcode.mjs +44 -14
- package/dist/harness-runtime/lib/delegation-instructions.mjs +1 -1
- package/dist/harness-runtime/lib/driver-recovery.mjs +9 -8
- 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/managed-daemon.d.mts +1 -1
- package/dist/harness-runtime/lib/managed-daemon.mjs +24 -9
- package/dist/harness-runtime/lib/native-children.d.mts +8 -1
- package/dist/harness-runtime/lib/native-children.mjs +114 -8
- package/dist/harness-runtime/lib/native-hook-command.mjs +4 -4
- package/dist/harness-runtime/lib/native-request-proof.mjs +1 -1
- package/dist/harness-runtime/lib/observation-clock.d.mts +2 -2
- package/dist/harness-runtime/lib/observation-clock.mjs +7 -4
- package/dist/harness-runtime/lib/opencode-plugin.mjs +3 -3
- package/dist/harness-runtime/lib/operation-errors.mjs +1 -1
- package/dist/harness-runtime/lib/provider-overload-policy.d.mts +5 -3
- package/dist/harness-runtime/lib/provider-overload-policy.mjs +20 -7
- package/dist/harness-runtime/lib/runner-events.mjs +3 -1
- package/dist/harness-runtime/lib/setup-progress.mjs +8 -6
- package/dist/harness-runtime/lib/tool-observations.mjs +1 -1
- package/dist/schemas/diagnostic-stage-result.schema.json +354 -0
- package/dist/schemas/diagnostic-work-result.schema.json +296 -0
- package/dist/schemas/execution-bundle.schema.json +7 -1
- package/dist/schemas/mb-upgrade-stage-result.schema.json +863 -0
- package/dist/schemas/memory-repair-work-result.schema.json +9 -2
- package/dist/schemas/project-flow-pack-manifest.schema.json +1 -1
- package/dist/schemas/release-impact.schema.json +3 -3
- package/dist/schemas/stage-start-response.schema.json +4 -0
- package/dist/services/accepted-intent.js +13 -1
- package/dist/services/canon.js +43 -7
- package/dist/services/check-environment.js +21 -0
- package/dist/services/check-runner-evidence.js +267 -15
- package/dist/services/cli-operation-classifier.js +1 -1
- package/dist/services/cli-upgrade-allowlist.js +1 -1
- package/dist/services/code-checks.js +34 -19
- package/dist/services/compatibility-preflight.js +5 -2
- package/dist/services/controller-execution.js +10 -4
- package/dist/services/controller-fanout.js +34 -10
- package/dist/services/controller-memory-task.js +45 -0
- package/dist/services/controller-memory-worker.js +49 -0
- package/dist/services/dashboard.js +2 -2
- package/dist/services/descriptor-run-settlement.js +5 -3
- package/dist/services/descriptor-run-start.js +46 -20
- package/dist/services/descriptor-stage-coordinator.js +66 -13
- package/dist/services/descriptor-stage-settlement.js +2 -1
- package/dist/services/descriptor-stage-start.js +40 -4
- 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/engines.js +64 -14
- package/dist/services/eval-snapshots.js +186 -119
- package/dist/services/execution-bundles.js +19 -7
- package/dist/services/external-work-launch.js +5 -3
- package/dist/services/flow-catalog.js +13 -4
- package/dist/services/frozen-engine.js +148 -11
- 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 +27 -8
- package/dist/services/hybrid-contracts.js +4 -13
- package/dist/services/hybrid-digest.js +14 -0
- package/dist/services/installed-flow-source.js +47 -4
- package/dist/services/lifecycle-invocations.js +179 -38
- package/dist/services/memory-accepted-composition.js +5 -4
- package/dist/services/memory-completed-correction-wave.js +23 -15
- package/dist/services/memory-compose-handler.js +163 -21
- package/dist/services/memory-composition-acceptance.js +186 -10
- package/dist/services/memory-composition-graph.js +42 -11
- package/dist/services/memory-composition-registration.js +23 -8
- package/dist/services/memory-composition-work-custody.js +14 -9
- package/dist/services/memory-composition-work-results.js +15 -3
- package/dist/services/memory-corrected-review-batch.js +10 -3
- package/dist/services/memory-corrected-review-findings.js +4 -2
- package/dist/services/memory-corrected-review-inputs.js +10 -5
- package/dist/services/memory-corrected-review-receipt.js +17 -9
- package/dist/services/memory-corrected-review-registration.js +3 -2
- package/dist/services/memory-correction-check-receipt.js +20 -4
- package/dist/services/memory-custody-snapshot.js +36 -0
- package/dist/services/memory-finish-evidence.js +44 -22
- package/dist/services/memory-finish-handler.js +37 -23
- package/dist/services/memory-finish-integration.js +218 -48
- package/dist/services/memory-finish-readiness.js +66 -19
- package/dist/services/memory-git-integration.js +87 -18
- package/dist/services/memory-historical-corrected-review.js +21 -15
- package/dist/services/memory-historical-original-review.js +22 -9
- package/dist/services/memory-historical-repair-registration.js +14 -8
- package/dist/services/memory-historical-second-review.js +30 -11
- package/dist/services/memory-init-contexts.js +44 -2
- package/dist/services/memory-init-controller-start.js +27 -8
- package/dist/services/memory-init-git-policy.js +50 -21
- package/dist/services/memory-init-lint-capability.js +24 -6
- package/dist/services/memory-init-native-capacity.js +17 -4
- package/dist/services/memory-init-preflight.js +21 -17
- package/dist/services/memory-init-request-candidate.js +73 -17
- package/dist/services/memory-init-request-input.js +23 -7
- package/dist/services/memory-init-run-bundle.js +49 -12
- package/dist/services/memory-init-storage.js +17 -7
- package/dist/services/memory-init-worktrunk-capability.js +3 -7
- package/dist/services/memory-layer.js +71 -6
- package/dist/services/memory-lint-config.js +111 -2
- package/dist/services/memory-native-review-copy.js +5 -67
- package/dist/services/memory-normal-flow-entry.js +12 -0
- package/dist/services/memory-ordinary-defaults.js +22 -5
- package/dist/services/memory-preflight-handler.js +15 -0
- package/dist/services/memory-repair-current-view.js +1 -1
- package/dist/services/memory-repair-document-delta.js +70 -33
- package/dist/services/memory-repair-history.js +12 -7
- package/dist/services/memory-repair-physical-binding.js +1 -119
- package/dist/services/memory-repair-publication-plan.js +5 -3
- package/dist/services/memory-repair-publication-source.js +1 -1
- package/dist/services/memory-repair-publication.js +3 -2
- package/dist/services/memory-repair-registration.js +2 -1
- package/dist/services/memory-repair-work-baseline.js +4 -2
- package/dist/services/memory-repair-work-completion.js +32 -12
- package/dist/services/memory-repair-work-results.js +21 -2
- package/dist/services/memory-repair-workspace.js +11 -2
- package/dist/services/memory-repair-writer-copy.js +2 -1
- package/dist/services/memory-result-binding.js +78 -12
- package/dist/services/memory-retained-review-authority.js +17 -8
- package/dist/services/memory-retained-second-baseline.js +1 -1
- package/dist/services/memory-retained-second-repairs.js +12 -6
- package/dist/services/memory-review-correction-plan.js +47 -12
- package/dist/services/memory-review-handler.js +17 -17
- package/dist/services/memory-review-inputs.js +55 -9
- package/dist/services/memory-review-limits.js +10 -0
- package/dist/services/memory-review-receipt.js +2 -2
- package/dist/services/memory-review-registration.js +13 -4
- package/dist/services/memory-review-work-custody.js +3 -1
- package/dist/services/memory-review-work-input.js +26 -7
- package/dist/services/memory-review-work-results.js +26 -4
- package/dist/services/memory-second-corrected-review-inputs.js +5 -3
- package/dist/services/memory-second-correction-plan.js +2 -1
- package/dist/services/memory-second-repair-launch.js +4 -4
- package/dist/services/memory-second-repair-source.js +1 -1
- package/dist/services/memory-stage-dispatch.js +38 -1
- package/dist/services/memory-stage-results.js +113 -22
- 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 +38 -17
- package/dist/services/memory-verification-checks.js +92 -27
- package/dist/services/memory-verification-receipt.js +6 -5
- package/dist/services/memory-verify-handler.js +15 -13
- package/dist/services/memory-work-result-json.js +12 -0
- package/dist/services/memory-work-result-preparation.js +5 -4
- package/dist/services/merge-server.js +4 -2
- package/dist/services/migrations.js +127 -98
- package/dist/services/native-daemon-history.js +2 -1
- package/dist/services/physical-startup.js +45 -0
- package/dist/services/project-registry.js +97 -0
- package/dist/services/project-summary.js +1 -1
- package/dist/services/projects.js +3 -93
- package/dist/services/prompt-delivery.js +71 -0
- package/dist/services/provider-capacity.js +112 -46
- package/dist/services/recovery-snapshot-database.js +32 -4
- package/dist/services/release-cache.js +8 -0
- package/dist/services/repair-intents.js +1 -12
- package/dist/services/repair-receipt.js +13 -0
- package/dist/services/run-control-worker.js +35 -18
- package/dist/services/run-control.js +2 -1
- package/dist/services/run-controller-adapter.js +2 -1
- package/dist/services/run-controller-process.js +4 -7
- package/dist/services/run-controller-recovery.js +8 -2
- package/dist/services/run-controller.js +215 -94
- package/dist/services/run-fork.js +1 -1
- package/dist/services/run-recovery-guard.js +30 -0
- package/dist/services/run-recovery.js +11 -44
- package/dist/services/run-start-custody.js +17 -5
- package/dist/services/runs.js +6 -1
- package/dist/services/runtime-scope-resume.js +12 -6
- package/dist/services/runtime-scope-stop.js +3 -2
- package/dist/services/runtime-scope-worker.js +46 -19
- package/dist/services/runtime-service.js +2 -1
- package/dist/services/sessions.js +3 -24
- package/dist/services/setup.js +63 -13
- package/dist/services/stage-handlers.js +22 -7
- package/dist/services/stage-pause.js +111 -44
- package/dist/services/version-status.js +1 -1
- package/dist/services/vnext-code.js +2 -2
- package/dist/services/vnext-fanout.js +51 -9
- package/dist/services/vnext-plan-review.js +3 -5
- package/dist/services/work-registry.js +117 -39
- package/dist/services/workspace-bootstrap.js +2 -1
- package/dist/services/workspace-files.js +1 -1
- package/dist/shared/entity-references.js +1 -8
- package/dist/storage/data-preservation.js +65 -0
- package/dist/storage/database.js +53 -19
- package/dist/storage/import-guard.js +84 -27
- package/dist/storage/memory-upgrade-schema-bootstrap.js +38 -0
- package/dist/storage/paired-writer-migration.js +424 -0
- package/dist/storage/writer-contract.js +11 -4
- package/dist/storage/writer-migration.js +313 -52
- package/dist/storage/writer-restore.js +217 -0
- package/package.json +13 -6
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { AppError } from "../shared/errors.js";
|
|
2
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
3
|
+
/** Only applicable aspects receive analytical Work. Critics receive all candidates,
|
|
4
|
+
* including candidates they may reject; no empty pseudo-Work proves coverage. */
|
|
5
|
+
export function diagnosticWorkBatch(input) {
|
|
6
|
+
const review = input.stage === "review";
|
|
7
|
+
if (!review && input.stage !== (input.flow_id === "mb-audit" ? "audit" : "analyze")) {
|
|
8
|
+
throw new AppError("diagnostic_work_batch_invalid", "Diagnostic Work stage belongs to another flow", 2, { effect: "no_effect" });
|
|
9
|
+
}
|
|
10
|
+
const applicable = input.coverage.filter(value => value.applicability === "applicable");
|
|
11
|
+
const architecture = applicable.some(value => value.id ===
|
|
12
|
+
(input.flow_id === "mb-audit" ? "05" : "architecture_harmonization_review"));
|
|
13
|
+
const assignments = review
|
|
14
|
+
? ["universal", ...(architecture ? ["architecture"] : [])].map(id => ({ id, role: "critic" }))
|
|
15
|
+
: applicable.map(value => ({ id: value.id, role: "aspect" }));
|
|
16
|
+
return { works: assignments.map((assignment, index) => {
|
|
17
|
+
const key = `${assignment.role}-${String(index + 1).padStart(3, "0")}`;
|
|
18
|
+
const packet = { schema_id: "dd-flow/diagnostic-work-input@1", flow_id: input.flow_id, run_id: input.run_id,
|
|
19
|
+
stage: input.stage, key, aspect_id: assignment.id, role: assignment.role,
|
|
20
|
+
input_sha256: input.input_sha256, snapshot_sha256: input.snapshot_sha256,
|
|
21
|
+
instruction: input.instruction, coverage: input.coverage, support: input.support ?? [],
|
|
22
|
+
candidates: review ? input.candidates : [] };
|
|
23
|
+
return { key, depends_on: [], launch_policy: "fresh_agent_required",
|
|
24
|
+
result_schema: "dd-flow/diagnostic-work-result@1",
|
|
25
|
+
task: (review
|
|
26
|
+
? `Independently assess every supplied candidate as the ${assignment.id} critic. Read the assigned immutable snapshot. Retain rejected findings and explain disagreement. Do not invent implementation, checks, sources or completion. Return exactly one decision per candidate using the assigned diagnostic result schema. Do not edit project files, repair findings, allocate Work or coordinate the Stage.`
|
|
27
|
+
: `Examine only the assigned aspect ${JSON.stringify(assignment.id)} using the frozen instruction and actual snapshot sources. Distinguish implemented behavior from plans and absent evidence. Cite exact sources for material findings; retain uncertainty and active DEFs. Do not edit project files, execute project setup, repair findings, allocate Work or claim independent review. Return the assigned diagnostic Work result.`)
|
|
28
|
+
+ " Read applicable criterion files in diagnostic.support relative to run_home. They are frozen reference criteria, not executable/manual lifecycle instructions or implemented project facts. Cite snapshot sources as project evidence, not canon references."
|
|
29
|
+
+ " Every sources/evidence path must be relative to the assigned snapshot workspace root, using forward slashes (for example src/index.ts or .memory-bank/index.md), with the SHA256 of its exact snapshot bytes. Never cite absolute paths, .git, intake transport files, RUN packets or canon support as snapshot evidence; never invent a relative path for a file outside the snapshot."
|
|
30
|
+
+ " Files under .dd-flow/review-evidence are CLI-generated observations, not original project sources: do not put them in sources/evidence or infer the original repository's history or unborn state from the working copy's Git metadata. For missing linked targets cite the existing referring project document and describe the absence as a limitation, not as a source file.",
|
|
31
|
+
payload: { kind: review ? "diagnostic-critic" : "diagnostic-aspect", read_only: true,
|
|
32
|
+
diagnostic: { ...packet, packet_sha256: hybridDigest(packet) } } };
|
|
33
|
+
}) };
|
|
34
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { AppError } from "../shared/errors.js";
|
|
2
|
+
import { isDiagnosticFlow } from "../domain/diagnostic-flows.js";
|
|
3
|
+
import { readFlowRunExecutionBundle, assertPreparedRunBundleSqlAuthority } from "./runs.js";
|
|
4
|
+
import { requireProjectByRoot } from "./projects.js";
|
|
5
|
+
import { resolveProjectRoot } from "../storage/paths.js";
|
|
6
|
+
import { readRegisteredDiagnosticGraph } from "./diagnostic-work-registration.js";
|
|
7
|
+
import { diagnosticWorkBatch } from "./diagnostic-work-batch.js";
|
|
8
|
+
import { validateDiagnosticWorkResult } from "./diagnostic-work-results.js";
|
|
9
|
+
import { diagnosticSnapshotPaths, readDiagnosticSnapshot, readDiagnosticSnapshotCustody } from "./diagnostic-snapshot.js";
|
|
10
|
+
import { readDescriptorRootWork } from "./descriptor-root-work.js";
|
|
11
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
12
|
+
function refuse(reason) { throw new AppError("diagnostic_work_collection_invalid", "Diagnostic graph is not independently settled", 2, { reason, effect: "no_effect" }); }
|
|
13
|
+
const physicalKey = (session) => `${session.harness}\0${session.provider_session_id}`;
|
|
14
|
+
export function assertDiagnosticSessionIndependence(workers, excluded) {
|
|
15
|
+
const keys = workers.map(physicalKey), excludedKeys = new Set(excluded.map(physicalKey));
|
|
16
|
+
if (workers.some(value => !value.harness || !value.provider_session_id)
|
|
17
|
+
|| new Set(keys).size !== keys.length || keys.some(key => excludedKeys.has(key)))
|
|
18
|
+
refuse("physical_session_reused");
|
|
19
|
+
}
|
|
20
|
+
function completedWork(context, scope, workId) {
|
|
21
|
+
const work = context.db.get("SELECT status,result FROM works WHERE project_id=? AND run_id=? AND work_id=?", [scope.projectId, scope.runId, workId]);
|
|
22
|
+
if (work?.status !== "completed" || !work.result || Buffer.byteLength(work.result) > 1024 * 1024)
|
|
23
|
+
refuse("work_not_completed");
|
|
24
|
+
const sessions = context.db.all("SELECT ws.id,ws.session_id,ws.status,s.harness,s.provider_session_id,ws.hook_event_id FROM work_sessions ws JOIN sessions s ON s.project_id=? AND s.session_id=ws.session_id AND s.run_id=? WHERE ws.work_id=? AND ws.status IN ('running','completed') LIMIT 3", [scope.projectId, scope.runId, workId]);
|
|
25
|
+
if (sessions.length !== 1 || sessions[0].status !== "completed")
|
|
26
|
+
refuse("work_session_not_settled");
|
|
27
|
+
const session = sessions[0];
|
|
28
|
+
const hook = context.db.get("SELECT session_id,harness,provider_session_id,status FROM hook_events WHERE project_id=? AND event_key=?", [scope.projectId, session.hook_event_id]);
|
|
29
|
+
if (!hook || hook.status !== "claimed" || hook.session_id !== session.session_id
|
|
30
|
+
|| physicalKey(hook) !== physicalKey(session))
|
|
31
|
+
refuse("physical_work_owner_missing");
|
|
32
|
+
return { workId, result: work.result, session };
|
|
33
|
+
}
|
|
34
|
+
function collectSql(context, scope, rootId) {
|
|
35
|
+
const graph = readRegisteredDiagnosticGraph(context, scope);
|
|
36
|
+
if (!graph || graph.parent_work_id !== rootId)
|
|
37
|
+
refuse("registration_required");
|
|
38
|
+
const batch = diagnosticWorkBatch(graph.input);
|
|
39
|
+
const works = batch.works.map(item => completedWork(context, scope, graph.work_ids[item.key]));
|
|
40
|
+
const coordinators = context.db.all("SELECT s.harness,s.provider_session_id FROM work_sessions ws JOIN sessions s ON s.project_id=? AND s.run_id=? AND s.session_id=ws.session_id WHERE ws.work_id=?", [scope.projectId, scope.runId, rootId]);
|
|
41
|
+
const authorGraph = scope.stage === "review" ? readRegisteredDiagnosticGraph(context, { ...scope, stage: graph.input.flow_id === "mb-audit" ? "audit" : "analyze" }) : null;
|
|
42
|
+
if (scope.stage === "review" && !authorGraph)
|
|
43
|
+
refuse("author_graph_missing");
|
|
44
|
+
const authors = authorGraph ? Object.values(authorGraph.work_ids).map(id => completedWork(context, scope, id)) : [];
|
|
45
|
+
assertDiagnosticSessionIndependence(works.map(value => value.session), [...coordinators, ...authors.map(value => value.session)]);
|
|
46
|
+
return { graph, works, authors, coordinators };
|
|
47
|
+
}
|
|
48
|
+
/** File/schema/evidence checks occur before settlement. assertCustody is SQL-only
|
|
49
|
+
* and keeps the selected results and physical owners stable in its writer. */
|
|
50
|
+
export class PreparedDiagnosticWorkCollection {
|
|
51
|
+
#scope;
|
|
52
|
+
#rootId;
|
|
53
|
+
#sql;
|
|
54
|
+
#snapshot;
|
|
55
|
+
#results;
|
|
56
|
+
constructor(scope, rootId, sql, snapshot, results) {
|
|
57
|
+
this.#scope = scope;
|
|
58
|
+
this.#rootId = rootId;
|
|
59
|
+
this.#sql = sql;
|
|
60
|
+
this.#snapshot = snapshot;
|
|
61
|
+
this.#results = results;
|
|
62
|
+
Object.freeze(this);
|
|
63
|
+
}
|
|
64
|
+
static prepare(context, input) {
|
|
65
|
+
const bundle = readFlowRunExecutionBundle(context, input), project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
|
|
66
|
+
if (!bundle || !isDiagnosticFlow(bundle.manifest.flow_id))
|
|
67
|
+
refuse("diagnostic_bundle_required");
|
|
68
|
+
const scope = { projectId: project.id, runId: input.runId, stage: input.stage };
|
|
69
|
+
const frozen = bundle.execution.snapshot().scope;
|
|
70
|
+
const snapshot = readDiagnosticSnapshot(context, { projectId: project.id, projectRoot: frozen.projectRoot,
|
|
71
|
+
sourceRoot: frozen.projectRoot, requestId: frozen.requestId, flowId: bundle.manifest.flow_id }, input.runId);
|
|
72
|
+
if (!snapshot || snapshot.root !== frozen.workspace)
|
|
73
|
+
refuse("snapshot_required");
|
|
74
|
+
const root = readDescriptorRootWork(context, scope, bundle);
|
|
75
|
+
const sql = context.db.readSnapshot(() => {
|
|
76
|
+
assertPreparedRunBundleSqlAuthority(context, scope, bundle);
|
|
77
|
+
return collectSql(context, scope, root.workId);
|
|
78
|
+
});
|
|
79
|
+
if (sql.graph.bundle_sha256 !== bundle.sha256 || sql.graph.input.snapshot_sha256 !== snapshot.source_sha256)
|
|
80
|
+
refuse("foreign_graph");
|
|
81
|
+
const batch = diagnosticWorkBatch(sql.graph.input), schema = bundle.blob("schemas/diagnostic-work-result.schema.json");
|
|
82
|
+
const results = sql.works.map((work, index) => ({ workId: work.workId,
|
|
83
|
+
result: validateDiagnosticWorkResult(Buffer.from(work.result), schema, diagnosticSnapshotPaths(context, snapshot, input.runId).root, sql.graph.input, {
|
|
84
|
+
workId: work.workId, aspectId: batch.works[index].payload.diagnostic.aspect_id,
|
|
85
|
+
role: batch.works[index].payload.diagnostic.role
|
|
86
|
+
}) }));
|
|
87
|
+
return new PreparedDiagnosticWorkCollection(scope, root.workId, sql, snapshot, results);
|
|
88
|
+
}
|
|
89
|
+
results() { return structuredClone(this.#results); }
|
|
90
|
+
assertCustody(context) {
|
|
91
|
+
const s = this.#snapshot;
|
|
92
|
+
if (hybridDigest(collectSql(context, this.#scope, this.#rootId)) !== hybridDigest(this.#sql)
|
|
93
|
+
|| hybridDigest(readDiagnosticSnapshotCustody(context, { projectId: s.projectId, projectRoot: s.projectRoot,
|
|
94
|
+
sourceRoot: s.sourceRoot, requestId: s.requestId, flowId: s.flowId }, this.#scope.runId)) !== hybridDigest(s))
|
|
95
|
+
refuse("collection_changed");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { AppError } from "../shared/errors.js";
|
|
2
|
+
import { isDiagnosticFlow } from "../domain/diagnostic-flows.js";
|
|
3
|
+
import { requireProjectByRoot } from "./projects.js";
|
|
4
|
+
import { resolveProjectRoot } from "../storage/paths.js";
|
|
5
|
+
import { readFlowRunExecutionBundle, assertPreparedRunBundleSqlAuthority } from "./runs.js";
|
|
6
|
+
import { readDescriptorStageInput } from "./descriptor-stage-coordinator.js";
|
|
7
|
+
import { readDescriptorRootWork } from "./descriptor-root-work.js";
|
|
8
|
+
import { diagnosticSnapshotPaths, readDiagnosticSnapshot, readDiagnosticSnapshotCustody } from "./diagnostic-snapshot.js";
|
|
9
|
+
import { readAcceptedStageData } from "./stage-report-renderer.js";
|
|
10
|
+
import { validateDiagnosticStageResult } from "./diagnostic-stage-results.js";
|
|
11
|
+
import { diagnosticWorkBatch } from "./diagnostic-work-batch.js";
|
|
12
|
+
import { addWorkBatch, prepareWorkBatch, readWorkBatchCustody } from "./work-registry.js";
|
|
13
|
+
import { stageGraphScope } from "./stage-work-graph.js";
|
|
14
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
15
|
+
import { appendAudit } from "./audit.js";
|
|
16
|
+
const event = "diagnostic.work_registration";
|
|
17
|
+
function refuse(reason) { throw new AppError("diagnostic_work_registration_invalid", "Diagnostic graph lacks its assigned stage authority", 2, { reason, effect: "no_effect" }); }
|
|
18
|
+
/** SQL-only reader. An explicitly registered empty graph is distinct from no graph. */
|
|
19
|
+
export function readRegisteredDiagnosticGraph(context, scope) {
|
|
20
|
+
const graph = stageGraphScope(context, scope.projectId, scope.runId, scope.stage);
|
|
21
|
+
const rows = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type=? AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.stage')=? AND json_extract(payload_json,'$.attempt')=? LIMIT 2", [scope.projectId, event, scope.runId, scope.stage, graph.attempt]);
|
|
22
|
+
if (!rows.length)
|
|
23
|
+
return null;
|
|
24
|
+
if (rows.length !== 1 || Buffer.byteLength(rows[0].payload_json) > 2 * 1024 * 1024)
|
|
25
|
+
refuse("ambiguous_registration");
|
|
26
|
+
const receipt = JSON.parse(rows[0].payload_json);
|
|
27
|
+
if (receipt.schema_id !== "dd-flow/diagnostic-work-registration@1" || receipt.run_id !== scope.runId
|
|
28
|
+
|| receipt.stage !== scope.stage || receipt.attempt !== graph.attempt || !receipt.work_ids
|
|
29
|
+
|| receipt.input.run_id !== scope.runId || receipt.input.stage !== scope.stage
|
|
30
|
+
|| receipt.input.input_sha256 !== receipt.input_sha256)
|
|
31
|
+
refuse("foreign_registration");
|
|
32
|
+
const batch = diagnosticWorkBatch(receipt.input);
|
|
33
|
+
if (receipt.batch_sha256 !== hybridDigest(batch.works) || batch.works.length !== Object.keys(receipt.work_ids).length
|
|
34
|
+
|| new Set(Object.values(receipt.work_ids)).size !== batch.works.length)
|
|
35
|
+
refuse("changed_batch");
|
|
36
|
+
for (const item of batch.works) {
|
|
37
|
+
const id = receipt.work_ids[item.key];
|
|
38
|
+
if (!id)
|
|
39
|
+
refuse("missing_work");
|
|
40
|
+
const custody = readWorkBatchCustody(context, id);
|
|
41
|
+
if (custody.item_key !== item.key || custody.batch_sha256 !== receipt.batch_sha256
|
|
42
|
+
|| hybridDigest(custody.work_ids) !== hybridDigest(receipt.work_ids)
|
|
43
|
+
|| hybridDigest(custody.scope) !== hybridDigest({ ...graph, stage: scope.stage,
|
|
44
|
+
parent_work_id: receipt.parent_work_id, request_key: "diagnostic-graph" }))
|
|
45
|
+
refuse("changed_work_custody");
|
|
46
|
+
}
|
|
47
|
+
return receipt;
|
|
48
|
+
}
|
|
49
|
+
/** The registered handler calls this only after generic Stage entry. Applicability
|
|
50
|
+
* and candidates come from the accepted predecessor, not a caller-owned batch. */
|
|
51
|
+
export function registerDiagnosticStageGraph(context, input) {
|
|
52
|
+
const bundle = readFlowRunExecutionBundle(context, input), project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
|
|
53
|
+
if (!bundle || !isDiagnosticFlow(bundle.manifest.flow_id) || !["audit", "analyze", "review"].includes(input.stage))
|
|
54
|
+
refuse("diagnostic_stage_required");
|
|
55
|
+
const flowId = bundle.manifest.flow_id, frozen = bundle.execution.snapshot().scope;
|
|
56
|
+
const scope = { projectId: project.id, runId: input.runId, stage: input.stage };
|
|
57
|
+
const selection = readDescriptorStageInput(context, scope, bundle), root = readDescriptorRootWork(context, scope, bundle);
|
|
58
|
+
const snapshotSelection = { projectId: project.id, projectRoot: frozen.projectRoot, sourceRoot: frozen.projectRoot, requestId: frozen.requestId, flowId };
|
|
59
|
+
const snapshot = readDiagnosticSnapshot(context, snapshotSelection, input.runId);
|
|
60
|
+
if (!snapshot || snapshot.root !== frozen.workspace)
|
|
61
|
+
refuse("snapshot_required");
|
|
62
|
+
const previousStage = input.stage === "review" ? flowId === "mb-audit" ? "audit" : "analyze" : "preflight";
|
|
63
|
+
if (selection.input.predecessor?.stage !== previousStage)
|
|
64
|
+
refuse("predecessor_required");
|
|
65
|
+
const previousScope = { ...scope, stage: previousStage };
|
|
66
|
+
const accepted = readAcceptedStageData(context, previousScope), previousInput = readDescriptorStageInput(context, previousScope, bundle, true, true);
|
|
67
|
+
const previous = validateDiagnosticStageResult(accepted.bytes, bundle.descriptor.stageBlob(previousStage, "result_schema"), {
|
|
68
|
+
flowId, runId: input.runId, stage: previousStage, inputSha256: previousInput.inputSha256,
|
|
69
|
+
snapshotRoot: diagnosticSnapshotPaths(context, snapshot, input.runId).root, snapshotSha256: snapshot.source_sha256,
|
|
70
|
+
predecessors: previousInput.input.predecessor ? [{ stage: previousInput.input.predecessor.stage,
|
|
71
|
+
path: previousInput.input.predecessor.result.path, sha256: previousInput.input.predecessor.result.sha256 }] : []
|
|
72
|
+
});
|
|
73
|
+
const workInput = { flow_id: flowId, run_id: input.runId, stage: input.stage,
|
|
74
|
+
input_sha256: selection.inputSha256, snapshot_sha256: snapshot.source_sha256,
|
|
75
|
+
instruction: bundle.descriptor.stageBlob(input.stage, "instruction").toString("utf8"),
|
|
76
|
+
coverage: previous.details.coverage, candidates: input.stage === "review" ? previous.details.findings : [],
|
|
77
|
+
support: bundle.manifest.inventory.filter(ref => ref.path.startsWith("support/")).map(ref => ({ path: `execution-bundle/${ref.path}`, sha256: ref.sha256 })) };
|
|
78
|
+
const batch = diagnosticWorkBatch(workInput), batchInput = { parentWorkId: root.workId, stage: input.stage, requestKey: "diagnostic-graph", value: batch };
|
|
79
|
+
const prepared = batch.works.length ? prepareWorkBatch(context, batchInput) : [];
|
|
80
|
+
return context.db.writeTransaction(() => {
|
|
81
|
+
assertPreparedRunBundleSqlAuthority(context, scope, bundle);
|
|
82
|
+
if (hybridDigest(readDiagnosticSnapshotCustody(context, snapshotSelection, input.runId)) !== hybridDigest(snapshot)
|
|
83
|
+
|| hybridDigest(readDescriptorStageInput(context, scope, bundle)) !== hybridDigest(selection)
|
|
84
|
+
|| hybridDigest(readAcceptedStageData(context, previousScope)) !== hybridDigest(accepted)
|
|
85
|
+
|| readDescriptorRootWork(context, scope, bundle).workId !== root.workId)
|
|
86
|
+
refuse("stage_authority_changed");
|
|
87
|
+
const expected = { schema_id: "dd-flow/diagnostic-work-registration@1", run_id: input.runId, stage: input.stage,
|
|
88
|
+
attempt: selection.attempt, parent_work_id: root.workId, bundle_sha256: bundle.sha256,
|
|
89
|
+
input_sha256: selection.inputSha256, input: workInput, batch_sha256: hybridDigest(batch.works) };
|
|
90
|
+
const prior = readRegisteredDiagnosticGraph(context, scope);
|
|
91
|
+
if (prior) {
|
|
92
|
+
if (hybridDigest({ ...prior, work_ids: undefined }) !== hybridDigest(expected))
|
|
93
|
+
refuse("registration_changed");
|
|
94
|
+
return prior;
|
|
95
|
+
}
|
|
96
|
+
const workIds = batch.works.length ? addWorkBatch(context, { ...batchInput, prepared }).work_ids : {};
|
|
97
|
+
const receipt = { ...expected, work_ids: workIds };
|
|
98
|
+
appendAudit(context, { projectId: project.id, eventType: event, payload: receipt });
|
|
99
|
+
return readRegisteredDiagnosticGraph(context, scope);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
2
|
+
import { verifyDiagnosticSources, verifyDiagnosticCritic } from "./diagnostic-evidence.js";
|
|
3
|
+
import { AppError } from "../shared/errors.js";
|
|
4
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
5
|
+
const refuse = (reason) => {
|
|
6
|
+
throw new AppError("diagnostic_work_result_invalid", "Diagnostic Work result does not match its assigned task", 2, { reason, effect: "no_effect", ...(reason === "invalid_schema" ? { phase: "prepare", recoverable: true } : {}) });
|
|
7
|
+
};
|
|
8
|
+
/** Accepts semantic data only after the caller has established completed Work,
|
|
9
|
+
* immutable batch custody and physical Session independence. */
|
|
10
|
+
export function validateDiagnosticWorkResult(bytes, schemaBytes, snapshotRoot, input, assignment) {
|
|
11
|
+
if (!bytes.length || bytes.length > 1024 * 1024 || schemaBytes.length > 1024 * 1024)
|
|
12
|
+
refuse("result_exceeds_bound");
|
|
13
|
+
const validate = (() => {
|
|
14
|
+
try {
|
|
15
|
+
const schema = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(schemaBytes));
|
|
16
|
+
if (schema.$id !== "dd-flow/diagnostic-work-result@1")
|
|
17
|
+
refuse("frozen_schema_invalid");
|
|
18
|
+
return new Ajv2020({ strict: false, validateFormats: false }).compile(schema);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return refuse("frozen_schema_invalid");
|
|
22
|
+
}
|
|
23
|
+
})();
|
|
24
|
+
let result;
|
|
25
|
+
try {
|
|
26
|
+
result = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return refuse("invalid_schema");
|
|
30
|
+
}
|
|
31
|
+
if (!validate(result))
|
|
32
|
+
refuse("invalid_schema");
|
|
33
|
+
if (result.flow_id !== input.flow_id || result.run_id !== input.run_id || result.stage !== input.stage
|
|
34
|
+
|| result.work_id !== assignment.workId || result.aspect_id !== assignment.aspectId)
|
|
35
|
+
refuse("foreign_assignment");
|
|
36
|
+
verifyDiagnosticSources(snapshotRoot, result.sources);
|
|
37
|
+
if (assignment.role === "critic") {
|
|
38
|
+
if (input.stage !== "review" || result.findings.length)
|
|
39
|
+
refuse("critic_cannot_replace_candidates");
|
|
40
|
+
verifyDiagnosticCritic(snapshotRoot, input.candidates, result.decisions);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
if (input.stage === "review" || result.decisions.length
|
|
44
|
+
|| !input.coverage.some(value => value.id === assignment.aspectId && value.applicability === "applicable"))
|
|
45
|
+
refuse("foreign_aspect");
|
|
46
|
+
const ids = new Set();
|
|
47
|
+
for (const finding of result.findings) {
|
|
48
|
+
if (ids.has(finding.id) || finding.aspect_id !== assignment.aspectId || !finding.evidence.length)
|
|
49
|
+
refuse("foreign_or_duplicate_finding");
|
|
50
|
+
ids.add(finding.id);
|
|
51
|
+
verifyDiagnosticSources(snapshotRoot, finding.evidence);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
/** Work-local IDs are not globally unique. Keep their origin when assigning
|
|
57
|
+
* stable report IDs, so one author's FIND-1 cannot overwrite another's. */
|
|
58
|
+
export function collectDiagnosticCandidates(results) {
|
|
59
|
+
const findings = [], origins = [];
|
|
60
|
+
const seen = new Set();
|
|
61
|
+
for (const [workIndex, item] of results.entries()) {
|
|
62
|
+
if (seen.has(item.workId) || item.result.work_id !== item.workId || item.result.stage === "review")
|
|
63
|
+
refuse("duplicate_or_foreign_work");
|
|
64
|
+
seen.add(item.workId);
|
|
65
|
+
for (const [findingIndex, finding] of item.result.findings.entries()) {
|
|
66
|
+
const id = `FIND-${String(workIndex + 1).padStart(3, "0")}-${String(findingIndex + 1).padStart(3, "0")}`;
|
|
67
|
+
findings.push({ ...structuredClone(finding), id });
|
|
68
|
+
origins.push({ id, work_id: item.workId, original_id: finding.id });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { findings, origins };
|
|
72
|
+
}
|
|
73
|
+
/** Assign CLI-owned global IDs only to an exact, ordered copy of admitted Work
|
|
74
|
+
* findings. Any other proposal still goes through the ordinary strict guards. */
|
|
75
|
+
export function sealDiagnosticCandidateIds(bytes, results) {
|
|
76
|
+
if (!bytes.length || bytes.length > 1024 * 1024)
|
|
77
|
+
return bytes;
|
|
78
|
+
let value;
|
|
79
|
+
try {
|
|
80
|
+
value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return bytes;
|
|
84
|
+
}
|
|
85
|
+
if (!value || !Array.isArray(value.details?.findings)
|
|
86
|
+
|| hybridDigest(value.details.findings) !== hybridDigest(results.flatMap(item => item.result.findings)))
|
|
87
|
+
return bytes;
|
|
88
|
+
const canonical = collectDiagnosticCandidates(results).findings;
|
|
89
|
+
if (hybridDigest(value.details.findings) === hybridDigest(canonical))
|
|
90
|
+
return bytes;
|
|
91
|
+
value.details.findings = canonical;
|
|
92
|
+
return Buffer.from(JSON.stringify(value));
|
|
93
|
+
}
|
package/dist/services/engines.js
CHANGED
|
@@ -9,7 +9,8 @@ import { createRequire } from "node:module";
|
|
|
9
9
|
import { valid } from "semver";
|
|
10
10
|
import { getCliBuildInfo } from "./build-info.js";
|
|
11
11
|
import { AppError } from "../shared/errors.js";
|
|
12
|
-
import { ensureDir, engineStoreRoot, engineVersionRoot, resolveProjectRoot } from "../storage/paths.js";
|
|
12
|
+
import { canonicalPath, ensureDir, engineStoreRoot, engineVersionRoot, resolveProjectRoot } from "../storage/paths.js";
|
|
13
|
+
import { resolveWorkReference } from "../storage/work-references.js";
|
|
13
14
|
import { classifyCliOperation, isRouterNative } from "./cli-operation-classifier.js";
|
|
14
15
|
import { requireProtocol } from "./protocols.js";
|
|
15
16
|
import { resolveCanonRootBeforeContext } from "./canon.js";
|
|
@@ -346,9 +347,21 @@ export async function upgradeForkedRunEngine(context, input) {
|
|
|
346
347
|
fs.renameSync(temporary, located.binding_path);
|
|
347
348
|
return binding;
|
|
348
349
|
}
|
|
349
|
-
function boundRunEngineForArgs(context, args, projectRoot) {
|
|
350
|
-
|
|
351
|
-
|
|
350
|
+
function boundRunEngineForArgs(context, args, projectRoot, options = {}) {
|
|
351
|
+
if (!projectRoot)
|
|
352
|
+
return null;
|
|
353
|
+
let runId = runIdForArgs(args);
|
|
354
|
+
const parsed = parseLightArgs(args), [family, command] = parsed.positional;
|
|
355
|
+
if (family === "work" && command !== "repair") {
|
|
356
|
+
const reference = option(parsed, "parent") ?? positional(parsed, command === "deps" ? 3 : 2);
|
|
357
|
+
if (reference) {
|
|
358
|
+
const project = context.db.get("SELECT id FROM projects WHERE root = ?", [canonicalPath(projectRoot, false)]);
|
|
359
|
+
if (!project)
|
|
360
|
+
return null;
|
|
361
|
+
runId = resolveWorkReference(context, reference, { projectId: project.id, runId }).run_id;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
if (!runId)
|
|
352
365
|
return null;
|
|
353
366
|
const located = locateRunRoot(context.db, projectRoot, runId);
|
|
354
367
|
if (!located)
|
|
@@ -356,24 +369,58 @@ function boundRunEngineForArgs(context, args, projectRoot) {
|
|
|
356
369
|
const binding = readRunEngineBinding(located.binding_path, { allowMissing: true });
|
|
357
370
|
if (!binding)
|
|
358
371
|
return null;
|
|
372
|
+
// SQL locates the current project; a restored binding retains its historical
|
|
373
|
+
// project_root bytes. Do not reinterpret that provenance as a live locator.
|
|
374
|
+
if (binding.run_id !== located.run_id)
|
|
375
|
+
throw new AppError("run_engine_binding_invalid", "RUN engine binding belongs to another scope", 1, { run_id: located.run_id });
|
|
359
376
|
const manifest = requireBoundManifest(context.ddFlowHome, {
|
|
360
377
|
packageName: binding.engine.package_name,
|
|
361
378
|
packageVersion: binding.engine.package_version,
|
|
362
379
|
integrityChecksum: binding.engine.integrity_checksum,
|
|
363
380
|
snapshotRoot: binding.engine.snapshot_root
|
|
364
|
-
});
|
|
381
|
+
}, options);
|
|
365
382
|
return { binding, manifest };
|
|
366
383
|
}
|
|
384
|
+
/** Detached RUN owners may be launched by a scope spanning several engines.
|
|
385
|
+
* Resolve the same retained pin as the public router before spawning directly. */
|
|
386
|
+
export function boundRunEngineEntrypoint(context, input) {
|
|
387
|
+
const bound = boundRunEngineForArgs(context, ["run", "status", input.runId], input.projectRoot);
|
|
388
|
+
if (!bound)
|
|
389
|
+
throw new AppError("run_engine_binding_missing", "Detached RUN ownership requires its retained engine", 1, { run_id: input.runId });
|
|
390
|
+
return resolveManifestEntrypoint(bound.manifest);
|
|
391
|
+
}
|
|
392
|
+
/** Routing already admitted the complete snapshot. Inside that executor the
|
|
393
|
+
* RUN pin, not a mutable project's old/new compatibility range, owns writes.
|
|
394
|
+
* Compare the actual physical module identity; environment aliases cannot
|
|
395
|
+
* impersonate another installed engine. No snapshot scan on each lease ACK.
|
|
396
|
+
*/
|
|
397
|
+
export function selectBoundExecutingEngine(context, args, projectRoot, env) {
|
|
398
|
+
if (!isEngineMode(env))
|
|
399
|
+
return null;
|
|
400
|
+
const bound = boundRunEngineForArgs(context, args, projectRoot, { verifyContent: false });
|
|
401
|
+
if (!bound)
|
|
402
|
+
return null;
|
|
403
|
+
const current = currentBuildManifest(context, getCliBuildInfo(), false);
|
|
404
|
+
if (!sameEngineIdentity(engineIdentity(current), bound.binding.engine))
|
|
405
|
+
throw new AppError("run_engine_binding_invalid", "Executing engine differs from the retained RUN pin", 1, { run_id: bound.binding.run_id });
|
|
406
|
+
return { status: "selected", package_name: current.package_name, required_range: `=${current.package_version}`,
|
|
407
|
+
recommended_version: current.package_version, selected: current, install_hint: null, project_root: projectRoot,
|
|
408
|
+
memory_bank_version: readCompatibilityForProject(projectRoot)?.memory_bank_version ?? null, diagnostics: [] };
|
|
409
|
+
}
|
|
367
410
|
function runIdForArgs(args) {
|
|
368
|
-
const [family, command, ...rest] =
|
|
369
|
-
const parsed =
|
|
411
|
+
const input = parseLightArgs(args), [family, command, ...rest] = input.positional;
|
|
412
|
+
const parsed = { ...input, positional: rest };
|
|
370
413
|
if (family === "run" && command !== "start" && command !== "list") {
|
|
371
|
-
if (["drive", "control"
|
|
414
|
+
if (["drive", "control"].includes(command ?? ""))
|
|
372
415
|
return option(parsed, "run");
|
|
373
|
-
if (command
|
|
416
|
+
if (["flags", "vars", "config", "recovery", "capacity"].includes(command ?? ""))
|
|
374
417
|
return positional(parsed, 1);
|
|
418
|
+
if (command === "snapshot")
|
|
419
|
+
return positional(parsed, 0) === "create" ? positional(parsed, 1) : null;
|
|
375
420
|
return positional(parsed, 0);
|
|
376
421
|
}
|
|
422
|
+
if (family === "stage" && ["fanout", "native"].includes(command ?? ""))
|
|
423
|
+
return positional(parsed, 1);
|
|
377
424
|
if (family === "stage" && command !== "start")
|
|
378
425
|
return positional(parsed, 0);
|
|
379
426
|
if (family === "stage" && command === "start" && !parsed.options.has("bootstrap"))
|
|
@@ -384,7 +431,7 @@ function runIdForArgs(args) {
|
|
|
384
431
|
return option(parsed, "run");
|
|
385
432
|
if (family === "protocol" && command === "sync-from-run")
|
|
386
433
|
return option(parsed, "run");
|
|
387
|
-
return
|
|
434
|
+
return option(parsed, "run");
|
|
388
435
|
}
|
|
389
436
|
function requireBoundManifest(ddFlowHome, input, options = {}) {
|
|
390
437
|
const manifest = input.snapshotRoot
|
|
@@ -1049,7 +1096,6 @@ function spawnEngine(manifest, entrypoint, args, io, stdin, env, routedFromVersi
|
|
|
1049
1096
|
return new Promise((resolve, reject) => {
|
|
1050
1097
|
const child = spawn(process.execPath, [entrypoint, ...args], {
|
|
1051
1098
|
env: {
|
|
1052
|
-
...process.env,
|
|
1053
1099
|
...env,
|
|
1054
1100
|
DD_FLOW_ENGINE_MODE: "1",
|
|
1055
1101
|
DD_FLOW_ENGINE_HOME: manifest.snapshot_root,
|
|
@@ -1143,8 +1189,12 @@ function findPackageRoot(start) {
|
|
|
1143
1189
|
current = parent;
|
|
1144
1190
|
}
|
|
1145
1191
|
}
|
|
1192
|
+
let checksumScanOrdinal = 0;
|
|
1146
1193
|
export function engineSnapshotChecksum(root) {
|
|
1147
|
-
|
|
1194
|
+
// A CLI can legitimately revalidate the same snapshot at several boundaries.
|
|
1195
|
+
// Distinguish actual scans without making duplicate phase notifications progress.
|
|
1196
|
+
const scan = `engine-digest:${root}:scan-${++checksumScanOrdinal}`;
|
|
1197
|
+
publishSetup('probe_started', scan);
|
|
1148
1198
|
const files = [];
|
|
1149
1199
|
collectFiles(root, root, files);
|
|
1150
1200
|
const checksum = crypto.createHash("sha256");
|
|
@@ -1154,11 +1204,11 @@ export function engineSnapshotChecksum(root) {
|
|
|
1154
1204
|
checksum.update("\0");
|
|
1155
1205
|
checksum.update(fs.readFileSync(path.join(root, file)));
|
|
1156
1206
|
if (++checked % 64 === 0)
|
|
1157
|
-
publishSetup('identity_digest_progress', `${
|
|
1207
|
+
publishSetup('identity_digest_progress', `${scan}:${checked}`);
|
|
1158
1208
|
checksum.update("\0");
|
|
1159
1209
|
}
|
|
1160
1210
|
const result = checksum.digest("hex");
|
|
1161
|
-
publishSetup('identity_digest_completed',
|
|
1211
|
+
publishSetup('identity_digest_completed', scan);
|
|
1162
1212
|
return result;
|
|
1163
1213
|
}
|
|
1164
1214
|
function collectFiles(root, current, files) {
|