@deksden-com/dd-flow-cli 0.9.0-beta.13 → 0.9.0-beta.133
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 +666 -0
- package/README.md +101 -1
- package/dist/build-info.json +10 -10
- package/dist/cli/argument-values.js +38 -0
- package/dist/cli/command-inputs.js +306 -0
- package/dist/cli/help.js +88 -12
- package/dist/cli/hook-ingress.js +78 -0
- package/dist/cli/input-preparation.js +116 -0
- package/dist/cli/native-hook-ingress.js +80 -0
- package/dist/cli/run-cli.js +1465 -450
- 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.js +17 -2
- package/dist/domain/stage-catalog.js +1 -1
- package/dist/harness-runtime/DD-ZCODE.md +99 -0
- package/dist/harness-runtime/bin/dd-agy.mjs +56 -0
- package/dist/harness-runtime/bin/dd-codex.mjs +27 -0
- package/dist/harness-runtime/bin/dd-droid.mjs +34 -0
- package/dist/harness-runtime/bin/dd-grok.mjs +40 -0
- package/dist/harness-runtime/bin/dd-opencode.mjs +22 -0
- package/dist/harness-runtime/bin/dd-zcode.mjs +87 -0
- package/dist/harness-runtime/lib/adapter-timeouts.d.mts +5 -0
- package/dist/harness-runtime/lib/adapter-timeouts.mjs +34 -0
- package/dist/harness-runtime/lib/agy-hooks.d.mts +1 -0
- package/dist/harness-runtime/lib/agy-hooks.mjs +9 -0
- package/dist/harness-runtime/lib/codex-capacity-policy.d.mts +10 -0
- package/dist/harness-runtime/lib/codex-capacity-policy.mjs +89 -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.mjs +370 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.d.mts +6 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +693 -0
- package/dist/harness-runtime/lib/dd-agy.mjs +59 -0
- package/dist/harness-runtime/lib/dd-codex-daemon.d.mts +1 -0
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +190 -0
- package/dist/harness-runtime/lib/dd-codex.d.mts +1 -0
- package/dist/harness-runtime/lib/dd-codex.mjs +805 -0
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +128 -0
- package/dist/harness-runtime/lib/dd-droid.mjs +561 -0
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +490 -0
- package/dist/harness-runtime/lib/dd-grok.mjs +177 -0
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +255 -0
- package/dist/harness-runtime/lib/dd-opencode.mjs +128 -0
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +703 -0
- package/dist/harness-runtime/lib/dd-zcode.mjs +1108 -0
- package/dist/harness-runtime/lib/delegation-instructions.d.mts +13 -0
- package/dist/harness-runtime/lib/delegation-instructions.mjs +144 -0
- package/dist/harness-runtime/lib/dispatch-fence.mjs +14 -0
- package/dist/harness-runtime/lib/driver-recovery.mjs +139 -0
- package/dist/harness-runtime/lib/droid-observation.mjs +66 -0
- package/dist/harness-runtime/lib/lease-renewal.d.mts +8 -0
- package/dist/harness-runtime/lib/lease-renewal.mjs +36 -0
- package/dist/harness-runtime/lib/lifecycle-command.d.mts +20 -0
- package/dist/harness-runtime/lib/lifecycle-command.mjs +272 -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 +12 -0
- package/dist/harness-runtime/lib/managed-daemon.mjs +619 -0
- package/dist/harness-runtime/lib/model-observations.mjs +101 -0
- package/dist/harness-runtime/lib/native-children.d.mts +13 -0
- package/dist/harness-runtime/lib/native-children.mjs +78 -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-streams.mjs +45 -0
- package/dist/harness-runtime/lib/observation-clock.d.mts +18 -0
- package/dist/harness-runtime/lib/observation-clock.mjs +92 -0
- package/dist/harness-runtime/lib/operation-context.mjs +5 -0
- package/dist/harness-runtime/lib/operation-errors.d.mts +14 -0
- package/dist/harness-runtime/lib/operation-errors.mjs +116 -0
- package/dist/harness-runtime/lib/operation-progress.d.mts +7 -0
- package/dist/harness-runtime/lib/operation-progress.mjs +112 -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/runner-events.mjs +200 -0
- package/dist/harness-runtime/lib/runner-lock.mjs +42 -0
- package/dist/harness-runtime/lib/session-settlement.mjs +30 -0
- package/dist/harness-runtime/lib/setup-progress.d.mts +9 -0
- package/dist/harness-runtime/lib/setup-progress.mjs +51 -0
- package/dist/harness-runtime/lib/tool-observations.d.mts +21 -0
- package/dist/harness-runtime/lib/tool-observations.mjs +228 -0
- package/dist/harness-runtime/lib/zcode-usage.mjs +55 -0
- package/dist/protocol/local-files.js +18 -9
- package/dist/runtime/context.js +8 -4
- package/dist/schemas/agent-profile.schema.json +1 -1
- package/dist/schemas/code-review-result.schema.json +1 -1
- package/dist/schemas/code-verification.schema.json +7 -5
- package/dist/schemas/code-work-batch.schema.json +2 -2
- package/dist/schemas/flow-run.schema.json +2 -0
- package/dist/schemas/plan-review-decision.schema.json +3 -3
- package/dist/schemas/plan-review-result.schema.json +2 -2
- package/dist/schemas/run-control-receipt.schema.json +118 -0
- package/dist/schemas/run-control-request.schema.json +36 -0
- package/dist/schemas/vnext-protocol-plan.schema.json +2 -2
- package/dist/services/branch-context.js +3 -3
- package/dist/services/canon.js +11 -8
- package/dist/services/check-runner-evidence.js +121 -0
- package/dist/services/cleanup.js +133 -40
- package/dist/services/cli-operation-classifier.js +22 -24
- package/dist/services/code-checks.js +851 -109
- package/dist/services/codex-capacity.js +367 -0
- package/dist/services/codex-hook-delivery.js +32 -0
- package/dist/services/command-context.js +80 -0
- package/dist/services/compatibility-preflight.js +13 -3
- package/dist/services/config.js +11 -8
- package/dist/services/continuation-outcome.js +20 -0
- package/dist/services/controller-fanout.js +119 -0
- package/dist/services/dashboard.js +135 -56
- package/dist/services/engines.js +245 -119
- package/dist/services/eval-snapshots.js +1146 -105
- package/dist/services/execution-policy.js +181 -0
- package/dist/services/external-work-launch.js +230 -0
- package/dist/services/harness-adapter.js +329 -36
- package/dist/services/harness-config.js +2 -0
- package/dist/services/hooks.js +747 -325
- 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 +1774 -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 +409 -51
- package/dist/services/merge-queue.js +228 -149
- package/dist/services/merge-server.js +163 -35
- package/dist/services/migrations.js +37 -16
- package/dist/services/native-daemon-history.js +49 -0
- package/dist/services/native-session-control.js +39 -0
- package/dist/services/plan-runtime.js +26 -11
- package/dist/services/plans.js +28 -25
- package/dist/services/projects.js +20 -15
- package/dist/services/prompts.js +28 -17
- package/dist/services/protocols.js +163 -87
- package/dist/services/recovery-observation-budget.js +121 -0
- package/dist/services/recovery-snapshot-database.js +145 -0
- package/dist/services/repair-intents.js +42 -0
- package/dist/services/review-copy.js +157 -0
- package/dist/services/review-deferral.js +23 -0
- package/dist/services/run-control-receipt.js +56 -0
- package/dist/services/run-control-worker.js +411 -0
- package/dist/services/run-control.js +985 -0
- package/dist/services/run-controller-adapter.js +399 -0
- package/dist/services/run-controller-capture.js +179 -0
- package/dist/services/run-controller-process.js +187 -0
- package/dist/services/run-controller-recovery.js +289 -0
- package/dist/services/run-controller-state.js +97 -0
- package/dist/services/run-controller.js +1035 -0
- package/dist/services/run-engine-bindings.js +1 -1
- package/dist/services/run-fork.js +147 -0
- package/dist/services/run-observations.js +152 -0
- package/dist/services/run-projection.js +22 -64
- package/dist/services/run-recovery-guard.js +75 -0
- package/dist/services/run-recovery-runtime.js +112 -0
- package/dist/services/run-recovery.js +349 -0
- package/dist/services/runs.js +558 -217
- package/dist/services/runtime-budget.js +430 -0
- package/dist/services/runtime-command.js +43 -0
- package/dist/services/runtime-scope-capture.js +52 -0
- package/dist/services/runtime-scope-control.js +468 -0
- package/dist/services/runtime-scope-resume.js +599 -0
- package/dist/services/runtime-scope-stop.js +180 -0
- package/dist/services/runtime-scope-worker.js +287 -0
- package/dist/services/runtime-service.js +96 -0
- package/dist/services/schema-validation.js +10 -8
- package/dist/services/scope-writer-cleanup.js +27 -0
- package/dist/services/sessions.js +35 -69
- package/dist/services/stage-blocker.js +17 -7
- package/dist/services/stage-context.js +185 -19
- package/dist/services/stage-lifecycle.js +98 -107
- package/dist/services/stage-pause.js +89 -75
- package/dist/services/stage-report-renderer.js +158 -6
- package/dist/services/stage-work-graph.js +35 -0
- package/dist/services/tooling.js +32 -23
- package/dist/services/usage.js +49 -101
- package/dist/services/verification-matrix.js +374 -0
- package/dist/services/vnext-code-review.js +222 -87
- package/dist/services/vnext-code.js +324 -251
- package/dist/services/vnext-contracts.js +10 -1
- package/dist/services/vnext-execution-profile.js +79 -4
- package/dist/services/vnext-fanout.js +177 -17
- package/dist/services/vnext-merge.js +335 -113
- package/dist/services/vnext-plan-review.js +276 -126
- package/dist/services/vnext-plan.js +227 -102
- package/dist/services/vnext-protocolize.js +335 -82
- package/dist/services/vnext-specify.js +119 -81
- package/dist/services/work-registry.js +1038 -192
- package/dist/services/workspace-bootstrap.js +87 -0
- package/dist/services/workspace-files.js +85 -0
- package/dist/services/worktrees.js +98 -44
- package/dist/shared/entity-references.js +8 -0
- package/dist/shared/errors.js +17 -0
- package/dist/storage/agent-output.js +26 -0
- package/dist/storage/database.js +781 -37
- package/dist/storage/foreign-key-transaction.js +39 -0
- package/dist/storage/immutable-input.js +45 -0
- package/dist/storage/import-guard.js +102 -0
- package/dist/storage/native-cleanup.js +52 -0
- package/dist/storage/paths.js +17 -4
- package/dist/storage/projection-lock.js +73 -0
- package/dist/storage/work-references.js +23 -0
- package/dist/storage/writer-contract.js +77 -0
- package/dist/storage/writer-migration.js +85 -0
- package/package.json +30 -13
- package/tools/repair-paused-run-status.mjs +84 -0
package/dist/services/engines.js
CHANGED
|
@@ -2,7 +2,9 @@ import crypto from "node:crypto";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { spawn
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
import { commandText } from '../harness-runtime/lib/process-json.mjs';
|
|
7
|
+
import { SETUP_STARTUP_MS, publishSetup } from '../harness-runtime/lib/setup-progress.mjs';
|
|
6
8
|
import { createRequire } from "node:module";
|
|
7
9
|
import { getCliBuildInfo } from "./build-info.js";
|
|
8
10
|
import { AppError } from "../shared/errors.js";
|
|
@@ -12,6 +14,7 @@ import { requireProtocol } from "./protocols.js";
|
|
|
12
14
|
import { resolveCanonRootBeforeContext } from "./canon.js";
|
|
13
15
|
import { appendAudit } from "./audit.js";
|
|
14
16
|
import { findProjectByRoot } from "./projects.js";
|
|
17
|
+
import { parseCommandArgs } from "./lifecycle-command.js";
|
|
15
18
|
import { allRunEngineBindings, locateRunRoot, readRunEngineBinding, runEngineBindingSchemaId, writeRunEngineBinding } from "./run-engine-bindings.js";
|
|
16
19
|
export const engineManifestSchemaId = "dd-flow/engine-manifest@1";
|
|
17
20
|
const routerNativeFamilies = new Set(["engine", "version", "schema"]);
|
|
@@ -31,6 +34,24 @@ export function isRouterNativeCommand(args) {
|
|
|
31
34
|
return routerNativeFamilies.has(first) || first === "codex" && args[1] === "hook" && args[2] === "handle";
|
|
32
35
|
}
|
|
33
36
|
export function installCurrentEngine(context, input = {}) {
|
|
37
|
+
const installation = prepareInstallation(context, input);
|
|
38
|
+
const first = installation.next();
|
|
39
|
+
if (first.done && input.forRouting)
|
|
40
|
+
return first.value;
|
|
41
|
+
const manifest = first.done ? requireManifest(path.join(String(first.value.path), 'engine.json')) : first.value;
|
|
42
|
+
return probeEntrypoint(manifest, undefined, context.ddFlowHome, process.env).then(() => first.done ? first.value : installation.next().value)
|
|
43
|
+
.catch(error => {
|
|
44
|
+
let cleanup_error;
|
|
45
|
+
try {
|
|
46
|
+
installation.return({});
|
|
47
|
+
}
|
|
48
|
+
catch (cleanup) {
|
|
49
|
+
cleanup_error = { message: String(cleanup).slice(0, 2000) };
|
|
50
|
+
}
|
|
51
|
+
throw new AppError('engine_install_failed', 'Staged engine admission failed', 1, { ...error.details, cause: error.code, probe_error: { code: error.code, details: error.details }, ...(cleanup_error ? { cleanup_error } : {}) });
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
function* prepareInstallation(context, input) {
|
|
34
55
|
const build = getCliBuildInfo();
|
|
35
56
|
const packageRoot = findPackageRoot(path.dirname(sourceFile));
|
|
36
57
|
if (!packageRoot) {
|
|
@@ -66,6 +87,7 @@ export function installCurrentEngine(context, input = {}) {
|
|
|
66
87
|
const tmp = path.join(parent, `.${path.basename(target)}.tmp-${process.pid}-${Date.now()}`);
|
|
67
88
|
fs.rmSync(tmp, { recursive: true, force: true });
|
|
68
89
|
ensureDir(tmp);
|
|
90
|
+
publishSetup('probe_started', `engine-staging:${tmp}`);
|
|
69
91
|
copyPackageSnapshot(packageRoot, tmp);
|
|
70
92
|
const stagedManifest = buildManifest(context, build.package_name, build.version, packageRoot, target, tmp);
|
|
71
93
|
const stagedChecksum = stagedManifest.integrity.checksum;
|
|
@@ -81,7 +103,7 @@ export function installCurrentEngine(context, input = {}) {
|
|
|
81
103
|
packageVersion: build.version,
|
|
82
104
|
integrityChecksum: stagedChecksum
|
|
83
105
|
}) : null;
|
|
84
|
-
if (duplicate) {
|
|
106
|
+
if (duplicate && manifestDiagnostics(duplicate, { checkEntrypoint: false }).length === 0) {
|
|
85
107
|
fs.rmSync(tmp, { recursive: true, force: true });
|
|
86
108
|
return {
|
|
87
109
|
ok: true,
|
|
@@ -94,13 +116,15 @@ export function installCurrentEngine(context, input = {}) {
|
|
|
94
116
|
const manifest = { ...stagedManifest, package_root: activationTarget, snapshot_root: activationTarget };
|
|
95
117
|
fs.writeFileSync(path.join(tmp, "engine.json"), `${JSON.stringify(manifest, null, 2)}\n`);
|
|
96
118
|
const checkedManifest = requireManifest(path.join(tmp, "engine.json"));
|
|
97
|
-
const stagedDiagnostics = manifestDiagnostics({ ...checkedManifest, package_root: tmp, snapshot_root: tmp }, { checkEntrypoint:
|
|
119
|
+
const stagedDiagnostics = manifestDiagnostics({ ...checkedManifest, package_root: tmp, snapshot_root: tmp }, { checkEntrypoint: false });
|
|
98
120
|
if (stagedDiagnostics.length > 0) {
|
|
99
121
|
throw new AppError("engine_install_failed", "Staged dd-flow engine failed its integrity or dependency health check", 1, {
|
|
100
122
|
diagnostics: stagedDiagnostics,
|
|
101
123
|
path: tmp
|
|
102
124
|
});
|
|
103
125
|
}
|
|
126
|
+
if (!input.forRouting)
|
|
127
|
+
yield { ...checkedManifest, package_root: tmp, snapshot_root: tmp };
|
|
104
128
|
activateEngineAtomically(tmp, activationTarget);
|
|
105
129
|
const installed = requireManifest(path.join(activationTarget, "engine.json"));
|
|
106
130
|
return {
|
|
@@ -117,7 +141,7 @@ export function installCurrentEngine(context, input = {}) {
|
|
|
117
141
|
}
|
|
118
142
|
export function listEngines(context) {
|
|
119
143
|
const root = engineStoreRoot(context.ddFlowHome);
|
|
120
|
-
const engines = readInstalledManifests(root).map(manifestSummary);
|
|
144
|
+
const engines = readInstalledManifests(root).map(manifest => manifestSummary(manifest));
|
|
121
145
|
return { ok: true, schema_id: "dd-flow/engine-list@1", engine_store: root, engines };
|
|
122
146
|
}
|
|
123
147
|
export function engineInfo(context, input) {
|
|
@@ -135,14 +159,23 @@ export function resolveEngine(context, input = {}) {
|
|
|
135
159
|
}
|
|
136
160
|
return { ok: true, schema_id: "dd-flow/engine-resolve@1", selection, contexts: compatibilityContexts(context, { ...input, classification }) };
|
|
137
161
|
}
|
|
138
|
-
export function doctorEngines(context, input = {}) {
|
|
162
|
+
export async function doctorEngines(context, input = {}) {
|
|
139
163
|
const root = engineStoreRoot(context.ddFlowHome);
|
|
140
164
|
const installed = readInstalledManifests(root);
|
|
141
|
-
const checks =
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
165
|
+
const checks = [];
|
|
166
|
+
for (const manifest of installed) {
|
|
167
|
+
const diagnostics = manifestDiagnostics(manifest);
|
|
168
|
+
let probe_error;
|
|
169
|
+
if (!diagnostics.length)
|
|
170
|
+
try {
|
|
171
|
+
await probeEntrypoint(manifest, undefined, context.ddFlowHome, context.env);
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
probe_error = error instanceof AppError ? error.details : { message: String(error) };
|
|
175
|
+
diagnostics.push(error instanceof AppError && error.code === 'engine_probe_failed' ? 'entrypoint_dependency_load_failed' : 'entrypoint_probe_unavailable');
|
|
176
|
+
}
|
|
177
|
+
checks.push({ engine: manifestSummary(manifest, diagnostics), status: diagnostics.includes("entrypoint_probe_unavailable") ? "unverified" : diagnostics.length ? "corrupt" : "ok", diagnostics, ...(probe_error ? { probe_error } : {}) });
|
|
178
|
+
}
|
|
146
179
|
const classification = input.classification ?? classifyCliOperation(["engine", "doctor"], input.env ?? context.env);
|
|
147
180
|
const selection = selectEngine(context, input, {}, classification);
|
|
148
181
|
return {
|
|
@@ -155,13 +188,14 @@ export function doctorEngines(context, input = {}) {
|
|
|
155
188
|
exit_code: checks.every((check) => check.status === "ok") && selection.status === "selected" ? 0 : 1
|
|
156
189
|
};
|
|
157
190
|
}
|
|
158
|
-
export function routeArgsThroughEngine(context, args, io, stdin, env, resolvedProjectRoot) {
|
|
191
|
+
export async function routeArgsThroughEngine(context, args, io, stdin, env, resolvedProjectRoot) {
|
|
159
192
|
if (isEngineMode(env) || isRouterNativeCommand(args))
|
|
160
193
|
return Promise.resolve(null);
|
|
161
194
|
const classification = classifyCliOperation(args, env);
|
|
162
195
|
const bound = boundRunEngineForArgs(context, args, resolvedProjectRoot ?? resolveExplicitProjectRoot(args) ?? undefined);
|
|
163
196
|
if (bound) {
|
|
164
197
|
const entrypoint = resolveManifestEntrypoint(bound.manifest);
|
|
198
|
+
await probeEntrypoint(bound.manifest, undefined, context.ddFlowHome, process.env);
|
|
165
199
|
return spawnEngine(bound.manifest, entrypoint, args, io, stdin, env, getCliBuildInfo().version);
|
|
166
200
|
}
|
|
167
201
|
const selection = selectEngine(context, { projectRoot: resolvedProjectRoot ?? resolveExplicitProjectRoot(args) ?? undefined, env }, { allowCurrentInProcess: true }, classification);
|
|
@@ -174,13 +208,25 @@ export function routeArgsThroughEngine(context, args, io, stdin, env, resolvedPr
|
|
|
174
208
|
if (!selection.selected)
|
|
175
209
|
return Promise.resolve(null);
|
|
176
210
|
const current = getCliBuildInfo();
|
|
177
|
-
|
|
211
|
+
const currentRoot = findPackageRoot(path.dirname(sourceFile));
|
|
212
|
+
if (selection.selected.package_name === current.package_name && selection.selected.package_version === current.version
|
|
213
|
+
&& currentRoot !== null && fs.realpathSync(selection.selected.snapshot_root) === fs.realpathSync(currentRoot)) {
|
|
178
214
|
return Promise.resolve({ routed: true, exitCode: 0, inProcess: true });
|
|
179
215
|
}
|
|
180
216
|
const entrypoint = resolveManifestEntrypoint(selection.selected);
|
|
217
|
+
try {
|
|
218
|
+
await probeEntrypoint(selection.selected, undefined, context.ddFlowHome, process.env);
|
|
219
|
+
}
|
|
220
|
+
catch (cause) {
|
|
221
|
+
const error = cause;
|
|
222
|
+
if (error.code !== 'engine_probe_failed')
|
|
223
|
+
throw error;
|
|
224
|
+
throw new AppError('missing_engine', 'Selected engine cannot load its runtime dependencies', 1, { probe: error.details });
|
|
225
|
+
}
|
|
181
226
|
return spawnEngine(selection.selected, entrypoint, args, io, stdin, env, current.version);
|
|
182
227
|
}
|
|
183
|
-
export function
|
|
228
|
+
export async function prepareRunEngineBind(context, input) {
|
|
229
|
+
const reason = requiredText(input.reason, "reason");
|
|
184
230
|
const projectRoot = resolveProjectRoot(input.projectRoot);
|
|
185
231
|
const located = locateRunRoot(context.db, projectRoot, input.runId);
|
|
186
232
|
if (!located)
|
|
@@ -190,40 +236,43 @@ export function bindRunEngine(context, input) {
|
|
|
190
236
|
packageName: input.packageName ?? getCliBuildInfo().package_name,
|
|
191
237
|
packageVersion: input.engineVersion,
|
|
192
238
|
...(input.integrityChecksum ? { integrityChecksum: input.integrityChecksum } : {})
|
|
193
|
-
});
|
|
239
|
+
}, { checkEntrypoint: false });
|
|
194
240
|
const requested = engineIdentity(manifest);
|
|
195
241
|
if (existing) {
|
|
196
242
|
if (sameEngineIdentity(existing.engine, requested))
|
|
197
|
-
return {
|
|
243
|
+
return { kind: "replay", binding: existing };
|
|
198
244
|
throw new AppError("run_engine_binding_immutable", "RUN engine binding cannot be changed", 1, {
|
|
199
245
|
run_id: located.run_id,
|
|
200
246
|
current: existing.engine,
|
|
201
247
|
requested
|
|
202
248
|
});
|
|
203
249
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
250
|
+
await probeEntrypoint(manifest, ['run', 'status', located.run_id, '--project-root', projectRoot, '--json'], context.ddFlowHome, context.env, 'run_engine');
|
|
251
|
+
return { kind: "create", projectRoot, located, manifest, requested, reason, entrypoint: resolveManifestEntrypoint(manifest) };
|
|
252
|
+
}
|
|
253
|
+
export async function bindRunEngine(context, input, supplied) {
|
|
254
|
+
const prepared = supplied ?? await prepareRunEngineBind(context, input);
|
|
255
|
+
if (prepared.kind === "replay")
|
|
256
|
+
return { ok: true, action: "run_engine_bind", changed: false, binding: prepared.binding };
|
|
257
|
+
const { projectRoot, located, manifest, requested, reason } = prepared;
|
|
258
|
+
const current = locateRunRoot(context.db, projectRoot, input.runId);
|
|
259
|
+
if (!current || current.run_root !== located.run_root)
|
|
260
|
+
throw new AppError('run_engine_binding_conflict', 'RUN identity changed during probe', 1);
|
|
261
|
+
requireBoundManifest(context.ddFlowHome, { packageName: manifest.package_name, packageVersion: manifest.package_version, integrityChecksum: requested.integrity_checksum });
|
|
262
|
+
const existing = readRunEngineBinding(located.binding_path, { allowMissing: true });
|
|
263
|
+
if (existing) {
|
|
264
|
+
if (sameEngineIdentity(existing.engine, requested))
|
|
265
|
+
return { ok: true, action: "run_engine_bind", changed: false, binding: existing };
|
|
266
|
+
throw new AppError("run_engine_binding_immutable", "RUN engine binding cannot be changed", 1, { run_id: located.run_id, current: existing.engine, requested });
|
|
219
267
|
}
|
|
268
|
+
const probeArgs = ["run", "status", located.run_id, "--project-root", projectRoot, "--json"];
|
|
220
269
|
const binding = {
|
|
221
270
|
schema_id: runEngineBindingSchemaId,
|
|
222
271
|
run_id: located.run_id,
|
|
223
272
|
project_root: projectRoot,
|
|
224
273
|
bound_at: context.now(),
|
|
225
274
|
source: "legacy_recovery",
|
|
226
|
-
reason
|
|
275
|
+
reason,
|
|
227
276
|
engine: requested,
|
|
228
277
|
probe: { status: "passed", command: `dd-flow ${probeArgs.join(" ")}` }
|
|
229
278
|
};
|
|
@@ -252,6 +301,36 @@ export function bindCurrentEngineToRun(context, input) {
|
|
|
252
301
|
};
|
|
253
302
|
return writeRunEngineBinding(path.join(input.runRoot, "engine-binding.json"), binding).binding;
|
|
254
303
|
}
|
|
304
|
+
/** Forks are a new execution authority. The source binding stays immutable;
|
|
305
|
+
* this replaces only the copied binding after proving the selected engine can
|
|
306
|
+
* read the copied RUN. */
|
|
307
|
+
export async function upgradeForkedRunEngine(context, input) {
|
|
308
|
+
const projectRoot = resolveProjectRoot(input.projectRoot);
|
|
309
|
+
const located = locateRunRoot(context.db, projectRoot, input.runId);
|
|
310
|
+
if (!located)
|
|
311
|
+
throw new AppError("not_found", `Forked RUN is not registered: ${input.runId}`, 1);
|
|
312
|
+
const manifest = requireBoundManifest(context.ddFlowHome, {
|
|
313
|
+
packageName: getCliBuildInfo().package_name,
|
|
314
|
+
packageVersion: input.engineVersion,
|
|
315
|
+
...(input.integrityChecksum ? { integrityChecksum: input.integrityChecksum } : {})
|
|
316
|
+
});
|
|
317
|
+
const requested = engineIdentity(manifest);
|
|
318
|
+
const priorBinding = fs.readFileSync(located.binding_path, 'utf8');
|
|
319
|
+
const lineage = path.join(located.run_root, "fork-lineage.json");
|
|
320
|
+
if (!fs.existsSync(lineage))
|
|
321
|
+
throw new AppError("fork_lineage_missing", "Engine upgrade is permitted only for a derived RUN", 1, { run_id: located.run_id });
|
|
322
|
+
const probeArgs = ["run", "status", located.run_id, "--project-root", projectRoot, "--json"];
|
|
323
|
+
const priorLineage = fs.readFileSync(lineage, 'utf8');
|
|
324
|
+
await probeEntrypoint(manifest, probeArgs, context.ddFlowHome, context.env, 'fork_engine');
|
|
325
|
+
if (fs.readFileSync(located.binding_path, 'utf8') !== priorBinding || fs.readFileSync(lineage, 'utf8') !== priorLineage)
|
|
326
|
+
throw new AppError('fork_engine_binding_conflict', 'Fork identity changed during probe', 1);
|
|
327
|
+
requireBoundManifest(context.ddFlowHome, { packageName: manifest.package_name, packageVersion: manifest.package_version, integrityChecksum: manifest.integrity.checksum });
|
|
328
|
+
const binding = { schema_id: runEngineBindingSchemaId, run_id: located.run_id, project_root: projectRoot, bound_at: context.now(), source: "fork_upgrade", reason: requiredText(input.reason, "reason"), engine: requested, probe: { status: "passed", command: `dd-flow ${probeArgs.join(" ")}` } };
|
|
329
|
+
const temporary = `${located.binding_path}.fork-${process.pid}-${Date.now()}.tmp`;
|
|
330
|
+
fs.writeFileSync(temporary, `${JSON.stringify(binding, null, 2)}\n`);
|
|
331
|
+
fs.renameSync(temporary, located.binding_path);
|
|
332
|
+
return binding;
|
|
333
|
+
}
|
|
255
334
|
function boundRunEngineForArgs(context, args, projectRoot) {
|
|
256
335
|
const runId = runIdForArgs(args);
|
|
257
336
|
if (!runId || !projectRoot)
|
|
@@ -274,6 +353,8 @@ function runIdForArgs(args) {
|
|
|
274
353
|
const [family, command, ...rest] = args;
|
|
275
354
|
const parsed = parseLightArgs(rest);
|
|
276
355
|
if (family === "run" && command !== "start" && command !== "list") {
|
|
356
|
+
if (["drive", "control", "recovery", "capacity"].includes(command ?? ""))
|
|
357
|
+
return option(parsed, "run");
|
|
277
358
|
if (command === "flags")
|
|
278
359
|
return positional(parsed, 1);
|
|
279
360
|
return positional(parsed, 0);
|
|
@@ -290,7 +371,7 @@ function runIdForArgs(args) {
|
|
|
290
371
|
return option(parsed, "run");
|
|
291
372
|
return null;
|
|
292
373
|
}
|
|
293
|
-
function requireBoundManifest(ddFlowHome, input) {
|
|
374
|
+
function requireBoundManifest(ddFlowHome, input, options = {}) {
|
|
294
375
|
const manifest = input.snapshotRoot
|
|
295
376
|
? requireManifest(path.join(input.snapshotRoot, "engine.json"))
|
|
296
377
|
: findInstalledManifest(ddFlowHome, {
|
|
@@ -303,7 +384,7 @@ function requireBoundManifest(ddFlowHome, input) {
|
|
|
303
384
|
|| manifest.package_version !== input.packageVersion
|
|
304
385
|
|| input.integrityChecksum !== undefined && manifest.integrity.checksum !== input.integrityChecksum
|
|
305
386
|
|| input.snapshotRoot !== undefined && path.resolve(manifest.snapshot_root) !== path.resolve(input.snapshotRoot)
|
|
306
|
-
||
|
|
387
|
+
|| manifestDiagnostics(manifest, options).length !== 0) {
|
|
307
388
|
throw new AppError("run_engine_binding_invalid", "RUN engine binding points to a missing or unhealthy engine snapshot", 1, {
|
|
308
389
|
package_name: input.packageName,
|
|
309
390
|
package_version: input.packageVersion,
|
|
@@ -344,7 +425,7 @@ function executionManifest(context) {
|
|
|
344
425
|
return requireManifest(path.join(String(installed.path), "engine.json"));
|
|
345
426
|
}
|
|
346
427
|
function requiredText(value, label) {
|
|
347
|
-
const normalized = value.trim();
|
|
428
|
+
const normalized = typeof value === "string" ? value.trim() : "";
|
|
348
429
|
if (!normalized)
|
|
349
430
|
throw new AppError("usage", `Missing required value: ${label}`, 2);
|
|
350
431
|
return normalized;
|
|
@@ -380,14 +461,14 @@ export function selectEngine(context, input, options = {}, classification = inpu
|
|
|
380
461
|
const requiredRange = stringValue(compatibility?.engine?.version_range) ?? (classification.mode === "mb_upgrade" ? null : `>=${build.version} <9999.0.0`);
|
|
381
462
|
const recommended = stringValue(compatibility?.engine?.recommended_version) ?? build.version;
|
|
382
463
|
const installHintTemplate = stringValue(compatibility?.engine?.old_engine_install_hint) ?? `npx ${packageName}@<version> engine install`;
|
|
383
|
-
|
|
464
|
+
// An engine-mode caller already executes this module's build. Another
|
|
465
|
+
// installed engine cannot establish compatibility for its current writes.
|
|
466
|
+
// Keep full snapshot validation in routing/binding, not lease maintenance.
|
|
467
|
+
const executingCurrent = options.allowCurrentInProcess && isEngineMode(input.env ?? process.env);
|
|
468
|
+
const manifests = executingCurrent ? [] : readInstalledManifests(engineStoreRoot(context.ddFlowHome))
|
|
384
469
|
.filter((manifest) => manifest.package_name === packageName)
|
|
385
470
|
.filter((manifest) => requiredRange !== null && satisfiesRange(manifest.package_version, requiredRange))
|
|
386
|
-
.filter((manifest) =>
|
|
387
|
-
&& manifest.package_name === build.package_name
|
|
388
|
-
&& manifest.package_version === build.version
|
|
389
|
-
? true
|
|
390
|
-
: manifestHealthy(manifest))
|
|
471
|
+
.filter((manifest) => manifestHealthy(manifest))
|
|
391
472
|
.sort((a, b) => {
|
|
392
473
|
const versionOrder = compareSemverStrings(b.package_version, a.package_version);
|
|
393
474
|
if (versionOrder !== 0)
|
|
@@ -402,7 +483,7 @@ export function selectEngine(context, input, options = {}, classification = inpu
|
|
|
402
483
|
requiredRange !== null &&
|
|
403
484
|
packageName === build.package_name &&
|
|
404
485
|
satisfiesRange(build.version, requiredRange)
|
|
405
|
-
? currentBuildManifest(context, build)
|
|
486
|
+
? currentBuildManifest(context, build, options.verifyCurrentContent !== false)
|
|
406
487
|
: null);
|
|
407
488
|
const diagnostics = [
|
|
408
489
|
...targetBlockers,
|
|
@@ -420,9 +501,38 @@ export function selectEngine(context, input, options = {}, classification = inpu
|
|
|
420
501
|
diagnostics
|
|
421
502
|
};
|
|
422
503
|
}
|
|
423
|
-
function currentBuildManifest(context, build) {
|
|
504
|
+
function currentBuildManifest(context, build, verifyContent) {
|
|
424
505
|
const packageRoot = findPackageRoot(path.dirname(sourceFile)) ?? process.cwd();
|
|
425
|
-
|
|
506
|
+
const manifestFile = path.join(packageRoot, "engine.json");
|
|
507
|
+
const installedLayout = path.basename(path.dirname(path.dirname(packageRoot))) === "engines"
|
|
508
|
+
&& path.basename(path.dirname(packageRoot)) === path.basename(path.dirname(engineVersionRoot(context.ddFlowHome, build.package_name, build.version)));
|
|
509
|
+
if (fs.existsSync(manifestFile) || installedLayout) {
|
|
510
|
+
const installed = readManifest(manifestFile);
|
|
511
|
+
// Use the executing module's physical root, never an environment alias.
|
|
512
|
+
// A damaged installed manifest must not become a development fallback.
|
|
513
|
+
if (!installed || installed.package_name !== build.package_name || installed.package_version !== build.version
|
|
514
|
+
|| installed.engine_version !== build.version || installed.engine_id !== "dd-flow-engine"
|
|
515
|
+
|| typeof installed.snapshot_root !== "string" || !fs.existsSync(installed.snapshot_root)
|
|
516
|
+
|| fs.realpathSync(installed.snapshot_root) !== fs.realpathSync(packageRoot)
|
|
517
|
+
|| typeof installed.entrypoint !== "string" || installed.integrity?.source !== "package_snapshot"
|
|
518
|
+
|| typeof installed.integrity?.checksum !== "string" || !/^[a-f0-9]{64}$/.test(installed.integrity.checksum)
|
|
519
|
+
|| (installed.schema_registry && (!Array.isArray(installed.schema_registry.entries)
|
|
520
|
+
|| installed.schema_registry.entries.some(entry => !entry || typeof entry.path !== "string"
|
|
521
|
+
|| typeof entry.id !== "string" || typeof entry.name !== "string" || !/^[a-f0-9]{64}$/.test(entry.checksum ?? ""))))) {
|
|
522
|
+
throw new AppError("unhealthy_engine", "Executing engine snapshot manifest does not match its build", 1, { manifest: manifestFile });
|
|
523
|
+
}
|
|
524
|
+
const diagnostics = manifestDiagnostics(installed, { checkEntrypoint: false, verifyContent });
|
|
525
|
+
if (diagnostics.length)
|
|
526
|
+
throw new AppError("unhealthy_engine", "Executing engine snapshot is unhealthy", 1, { manifest: manifestFile, diagnostics });
|
|
527
|
+
return installed;
|
|
528
|
+
}
|
|
529
|
+
// Only an unmaterialized package has no snapshot proof. Canon and
|
|
530
|
+
// package-manager aliases are expected in development checkouts.
|
|
531
|
+
const manifest = buildManifest(context, build.package_name, build.version, packageRoot, packageRoot, packageRoot, null);
|
|
532
|
+
// An ephemeral routing choice has no snapshot proof. Hash bytes only when
|
|
533
|
+
// materializing/validating an installed snapshot, never on every CLI call.
|
|
534
|
+
manifest.integrity.mode = "degraded_local_development";
|
|
535
|
+
return manifest;
|
|
426
536
|
}
|
|
427
537
|
export function compatibilityContexts(context, input = {}) {
|
|
428
538
|
const env = input.env ?? process.env;
|
|
@@ -560,40 +670,16 @@ function canonCompatibilitySha(memorybankRoot) {
|
|
|
560
670
|
return null;
|
|
561
671
|
}
|
|
562
672
|
}
|
|
563
|
-
|
|
564
|
-
const positional = [];
|
|
565
|
-
const options = new Map();
|
|
566
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
567
|
-
const value = args[index];
|
|
568
|
-
if (value?.startsWith("--")) {
|
|
569
|
-
const equal = value.indexOf("=");
|
|
570
|
-
if (equal > 2) {
|
|
571
|
-
const key = value.slice(2, equal);
|
|
572
|
-
options.set(key, [...(options.get(key) ?? []), value.slice(equal + 1)]);
|
|
573
|
-
continue;
|
|
574
|
-
}
|
|
575
|
-
const key = value.slice(2);
|
|
576
|
-
const next = args[index + 1];
|
|
577
|
-
if (!next || next.startsWith("--"))
|
|
578
|
-
options.set(key, [...(options.get(key) ?? []), ""]);
|
|
579
|
-
else {
|
|
580
|
-
options.set(key, [...(options.get(key) ?? []), next]);
|
|
581
|
-
index += 1;
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
else if (value)
|
|
585
|
-
positional.push(value);
|
|
586
|
-
}
|
|
587
|
-
return { positional, options };
|
|
588
|
-
}
|
|
673
|
+
const parseLightArgs = parseCommandArgs;
|
|
589
674
|
function option(parsed, key) {
|
|
590
675
|
const values = parsed.options.get(key);
|
|
591
|
-
|
|
676
|
+
const value = values?.[values.length - 1];
|
|
677
|
+
return value && value !== "__dd_flow_flag__" ? value : null;
|
|
592
678
|
}
|
|
593
679
|
function positional(parsed, index) {
|
|
594
680
|
return parsed.positional[index] ?? null;
|
|
595
681
|
}
|
|
596
|
-
function buildManifest(context, packageName, version, packageRoot, snapshotRoot, checksumRoot) {
|
|
682
|
+
function buildManifest(context, packageName, version, packageRoot, snapshotRoot, checksumRoot, checksum = engineSnapshotChecksum(checksumRoot)) {
|
|
597
683
|
return {
|
|
598
684
|
schema_id: engineManifestSchemaId,
|
|
599
685
|
package_name: packageName,
|
|
@@ -614,29 +700,46 @@ function buildManifest(context, packageName, version, packageRoot, snapshotRoot,
|
|
|
614
700
|
schema_registry: buildSchemaRegistry(checksumRoot),
|
|
615
701
|
integrity: {
|
|
616
702
|
source: "package_snapshot",
|
|
617
|
-
checksum
|
|
703
|
+
checksum,
|
|
618
704
|
mode: "full_content"
|
|
619
705
|
}
|
|
620
706
|
};
|
|
621
707
|
}
|
|
622
708
|
function copyPackageSnapshot(packageRoot, target) {
|
|
709
|
+
let copied = 0;
|
|
710
|
+
for (const [relative, source] of packageSnapshotFiles(packageRoot)) {
|
|
711
|
+
const destination = path.join(target, relative);
|
|
712
|
+
ensureDir(path.dirname(destination));
|
|
713
|
+
fs.copyFileSync(source, destination);
|
|
714
|
+
if (++copied % 64 === 0)
|
|
715
|
+
publishSetup('transfer_progress', `${target}:${copied}`);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
/** The same source inventory drives validation and copying; no staging writes. */
|
|
719
|
+
function packageSnapshotFiles(packageRoot) {
|
|
720
|
+
const files = new Map();
|
|
721
|
+
const collect = (source, relative, skipNodeModules = false) => {
|
|
722
|
+
if (!fs.statSync(source).isDirectory()) {
|
|
723
|
+
files.set(relative, source);
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
for (const entry of fs.readdirSync(source, { withFileTypes: true })) {
|
|
727
|
+
if (skipNodeModules && entry.name === "node_modules")
|
|
728
|
+
continue;
|
|
729
|
+
const child = path.join(source, entry.name), name = path.join(relative, entry.name);
|
|
730
|
+
if (entry.isDirectory())
|
|
731
|
+
collect(child, name, skipNodeModules);
|
|
732
|
+
else if (entry.isFile())
|
|
733
|
+
files.set(name, child);
|
|
734
|
+
}
|
|
735
|
+
};
|
|
623
736
|
for (const item of ["dist", "package.json", "README.md", "CHANGELOG.md", "tools"]) {
|
|
624
737
|
const from = path.join(packageRoot, item);
|
|
625
738
|
if (!fs.existsSync(from))
|
|
626
739
|
continue;
|
|
627
|
-
|
|
628
|
-
const stat = fs.statSync(from);
|
|
629
|
-
if (stat.isDirectory())
|
|
630
|
-
copyDir(from, to);
|
|
631
|
-
else {
|
|
632
|
-
ensureDir(path.dirname(to));
|
|
633
|
-
fs.copyFileSync(from, to);
|
|
634
|
-
}
|
|
740
|
+
collect(from, item);
|
|
635
741
|
}
|
|
636
|
-
|
|
637
|
-
}
|
|
638
|
-
function copyProductionDependencies(packageRoot, target) {
|
|
639
|
-
const queue = [{ sourceRoot: packageRoot, targetRoot: target, ancestors: new Set() }];
|
|
742
|
+
const queue = [{ sourceRoot: packageRoot, targetRoot: "", ancestors: new Set() }];
|
|
640
743
|
const copied = new Set();
|
|
641
744
|
while (queue.length > 0) {
|
|
642
745
|
const current = queue.shift();
|
|
@@ -651,12 +754,39 @@ function copyProductionDependencies(packageRoot, target) {
|
|
|
651
754
|
if (copied.has(copyKey))
|
|
652
755
|
continue;
|
|
653
756
|
copied.add(copyKey);
|
|
654
|
-
|
|
757
|
+
collect(sourceRoot, targetRoot, true);
|
|
655
758
|
if (current.ancestors.has(sourceRoot))
|
|
656
759
|
continue;
|
|
657
760
|
queue.push({ sourceRoot, targetRoot, ancestors: new Set([...current.ancestors, sourceRoot]) });
|
|
658
761
|
}
|
|
659
762
|
}
|
|
763
|
+
return files;
|
|
764
|
+
}
|
|
765
|
+
export function prepareForkEngine(engineVersion, integrityChecksum) {
|
|
766
|
+
const build = getCliBuildInfo();
|
|
767
|
+
if (engineVersion !== build.version)
|
|
768
|
+
throw new AppError("fork_engine_unavailable", "Fork installs the invoking CLI engine; invoke the requested version to upgrade the fork", 2, { requested: engineVersion, available: build.version });
|
|
769
|
+
const packageRoot = findPackageRoot(path.dirname(sourceFile));
|
|
770
|
+
if (!packageRoot || !fs.existsSync(path.join(packageRoot, "dist", "cli.js")))
|
|
771
|
+
throw new AppError("harness_runtime_unbuilt", "Build the CLI before preparing a fork", 1);
|
|
772
|
+
const files = packageSnapshotFiles(packageRoot);
|
|
773
|
+
if (integrityChecksum !== undefined) {
|
|
774
|
+
const checksum = snapshotFilesChecksum(files);
|
|
775
|
+
if (checksum !== integrityChecksum)
|
|
776
|
+
throw new AppError("fork_engine_unavailable", "Requested engine checksum differs from the invoking CLI artifact", 2, { requested: integrityChecksum });
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
function snapshotFilesChecksum(files) {
|
|
780
|
+
const checksum = crypto.createHash("sha256");
|
|
781
|
+
for (const [relative, source] of [...files].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
|
|
782
|
+
if (path.basename(relative) === "engine.json")
|
|
783
|
+
continue;
|
|
784
|
+
checksum.update(relative);
|
|
785
|
+
checksum.update("\0");
|
|
786
|
+
checksum.update(fs.readFileSync(source));
|
|
787
|
+
checksum.update("\0");
|
|
788
|
+
}
|
|
789
|
+
return checksum.digest("hex");
|
|
660
790
|
}
|
|
661
791
|
function readProductionDependencies(packageRoot) {
|
|
662
792
|
try {
|
|
@@ -713,19 +843,6 @@ function hasAncestorFile(start, name) {
|
|
|
713
843
|
current = parent;
|
|
714
844
|
}
|
|
715
845
|
}
|
|
716
|
-
function copyDir(from, to, skipNodeModules = false) {
|
|
717
|
-
ensureDir(to);
|
|
718
|
-
for (const entry of fs.readdirSync(from, { withFileTypes: true })) {
|
|
719
|
-
if (skipNodeModules && entry.name === "node_modules")
|
|
720
|
-
continue;
|
|
721
|
-
const source = path.join(from, entry.name);
|
|
722
|
-
const target = path.join(to, entry.name);
|
|
723
|
-
if (entry.isDirectory())
|
|
724
|
-
copyDir(source, target, skipNodeModules);
|
|
725
|
-
else if (entry.isFile())
|
|
726
|
-
fs.copyFileSync(source, target);
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
846
|
function acquireInstallLock(lockDir) {
|
|
730
847
|
ensureDir(path.dirname(lockDir));
|
|
731
848
|
try {
|
|
@@ -750,6 +867,9 @@ function readInstalledManifests(root) {
|
|
|
750
867
|
if (!fs.statSync(packageRoot).isDirectory())
|
|
751
868
|
continue;
|
|
752
869
|
for (const versionDir of fs.readdirSync(packageRoot)) {
|
|
870
|
+
// Failed staging, activation backups and locks are not installations.
|
|
871
|
+
if (versionDir.startsWith(".") || /\.previous-\d+-\d+$/.test(versionDir) || versionDir.endsWith(".lock"))
|
|
872
|
+
continue;
|
|
753
873
|
const manifest = readManifest(path.join(packageRoot, versionDir, "engine.json"));
|
|
754
874
|
if (manifest)
|
|
755
875
|
manifests.push(manifest);
|
|
@@ -802,7 +922,7 @@ function manifestDiagnostics(manifest, options = {}) {
|
|
|
802
922
|
if (!manifest.integrity || manifest.integrity.mode !== "full_content" || !manifest.integrity.checksum) {
|
|
803
923
|
diagnostics.push("content_checksum_missing");
|
|
804
924
|
}
|
|
805
|
-
else if (fs.existsSync(snapshotRoot)) {
|
|
925
|
+
else if (options.verifyContent !== false && fs.existsSync(snapshotRoot)) {
|
|
806
926
|
try {
|
|
807
927
|
if (engineSnapshotChecksum(snapshotRoot) !== manifest.integrity.checksum)
|
|
808
928
|
diagnostics.push("content_checksum_mismatch");
|
|
@@ -816,6 +936,8 @@ function manifestDiagnostics(manifest, options = {}) {
|
|
|
816
936
|
const schemaPath = safeResolveSnapshotPath(manifest, entry.path);
|
|
817
937
|
if (!schemaPath || !fs.existsSync(schemaPath))
|
|
818
938
|
diagnostics.push(`schema_registry_missing:${entry.name}`);
|
|
939
|
+
else if (options.verifyContent === false)
|
|
940
|
+
continue;
|
|
819
941
|
else if (fileChecksum(schemaPath) !== entry.checksum)
|
|
820
942
|
diagnostics.push(`schema_registry_checksum_mismatch:${entry.name}`);
|
|
821
943
|
else {
|
|
@@ -829,25 +951,21 @@ function manifestDiagnostics(manifest, options = {}) {
|
|
|
829
951
|
}
|
|
830
952
|
}
|
|
831
953
|
}
|
|
832
|
-
if (options.checkEntrypoint !== false && entrypoint && fs.existsSync(entrypoint) && !diagnostics.includes("entrypoint_outside_snapshot")) {
|
|
833
|
-
const health = spawnSync(process.execPath, [entrypoint, "--version", "--json"], {
|
|
834
|
-
cwd: snapshotRoot,
|
|
835
|
-
env: {
|
|
836
|
-
...process.env,
|
|
837
|
-
DD_FLOW_ENGINE_MODE: "1",
|
|
838
|
-
DD_FLOW_ENGINE_HOME: snapshotRoot,
|
|
839
|
-
DD_FLOW_ENGINE_HEALTHCHECK: "1"
|
|
840
|
-
},
|
|
841
|
-
encoding: "utf8",
|
|
842
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
843
|
-
timeout: 15_000
|
|
844
|
-
});
|
|
845
|
-
if (health.status !== 0)
|
|
846
|
-
diagnostics.push("entrypoint_dependency_load_failed");
|
|
847
|
-
}
|
|
848
954
|
return diagnostics;
|
|
849
955
|
}
|
|
850
|
-
function
|
|
956
|
+
async function probeEntrypoint(manifest, args = ['--version', '--json'], runtimeHome, env = {}, purpose = 'engine') {
|
|
957
|
+
publishSetup('probe_started', manifest.snapshot_root);
|
|
958
|
+
try {
|
|
959
|
+
await commandText(resolveManifestEntrypoint(manifest), args, { cwd: manifest.snapshot_root,
|
|
960
|
+
env: { ...env, DD_FLOW_ENGINE_MODE: '1', DD_FLOW_ENGINE_HOME: manifest.snapshot_root, ...(runtimeHome ? { DD_FLOW_HOME: runtimeHome } : {}), ...(args[0] === '--version' ? { DD_FLOW_ENGINE_HEALTHCHECK: '1' } : {}) }, startupMs: SETUP_STARTUP_MS, phase: 'engine-probe' });
|
|
961
|
+
publishSetup('probe_completed', manifest.snapshot_root);
|
|
962
|
+
}
|
|
963
|
+
catch (cause) {
|
|
964
|
+
const error = cause;
|
|
965
|
+
throw new AppError(error.details?.reason === 'child_exit' && error.details?.signal === null && error.details?.exit_code !== null ? `${purpose}_probe_failed` : `${purpose}_probe_unavailable`, 'Selected engine probe did not complete successfully', 1, { cause: error.code, message: error.message, ...error.details });
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
function manifestSummary(manifest, diagnostics = manifestDiagnostics(manifest, { checkEntrypoint: false })) {
|
|
851
969
|
return {
|
|
852
970
|
schema_id: manifest.schema_id,
|
|
853
971
|
package_name: manifest.package_name,
|
|
@@ -859,8 +977,8 @@ function manifestSummary(manifest) {
|
|
|
859
977
|
snapshot_root: manifest.snapshot_root,
|
|
860
978
|
resolved_entrypoint: safeResolveManifestEntrypoint(manifest),
|
|
861
979
|
installed_at: manifest.installed_at,
|
|
862
|
-
healthy:
|
|
863
|
-
diagnostics
|
|
980
|
+
healthy: diagnostics.length === 0,
|
|
981
|
+
diagnostics
|
|
864
982
|
};
|
|
865
983
|
}
|
|
866
984
|
function resolveManifestEntrypoint(manifest) {
|
|
@@ -1011,22 +1129,30 @@ function findPackageRoot(start) {
|
|
|
1011
1129
|
}
|
|
1012
1130
|
}
|
|
1013
1131
|
export function engineSnapshotChecksum(root) {
|
|
1132
|
+
publishSetup('probe_started', `engine-digest:${root}`);
|
|
1014
1133
|
const files = [];
|
|
1015
1134
|
collectFiles(root, root, files);
|
|
1016
1135
|
const checksum = crypto.createHash("sha256");
|
|
1136
|
+
let checked = 0;
|
|
1017
1137
|
for (const file of files.sort()) {
|
|
1018
1138
|
checksum.update(file);
|
|
1019
1139
|
checksum.update("\0");
|
|
1020
1140
|
checksum.update(fs.readFileSync(path.join(root, file)));
|
|
1141
|
+
if (++checked % 64 === 0)
|
|
1142
|
+
publishSetup('identity_digest_progress', `${root}:${checked}`);
|
|
1021
1143
|
checksum.update("\0");
|
|
1022
1144
|
}
|
|
1023
|
-
|
|
1145
|
+
const result = checksum.digest("hex");
|
|
1146
|
+
publishSetup('identity_digest_completed', root);
|
|
1147
|
+
return result;
|
|
1024
1148
|
}
|
|
1025
1149
|
function collectFiles(root, current, files) {
|
|
1026
1150
|
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
1027
1151
|
if (entry.name === "engine.json")
|
|
1028
1152
|
continue;
|
|
1029
1153
|
const full = path.join(current, entry.name);
|
|
1154
|
+
if (entry.isSymbolicLink())
|
|
1155
|
+
throw new AppError("engine_integrity_symlink", "Pinned engine snapshot contains an unhashed symbolic link", 1, { path: path.relative(root, full) });
|
|
1030
1156
|
if (entry.isDirectory())
|
|
1031
1157
|
collectFiles(root, full, files);
|
|
1032
1158
|
else if (entry.isFile())
|