@deksden-com/dd-flow-cli 0.9.0-beta.143 → 0.9.0-beta.145
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 +65 -0
- package/README.md +13 -8
- package/dist/build-info.json +7 -7
- package/dist/cli/command-inputs.js +9 -3
- package/dist/cli/command-metadata.js +33 -14
- package/dist/cli/help.js +32 -1
- package/dist/cli/hook-ingress.js +27 -3
- package/dist/cli/memory-flow-admission.js +7 -3
- package/dist/cli/memory-init-invocation.js +10 -3
- package/dist/cli/memory-init.js +74 -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 +70 -32
- package/dist/cli/setup.js +2 -1
- package/dist/cli.js +12 -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/dd-agy-daemon.mjs +62 -20
- package/dist/harness-runtime/lib/dd-agy.mjs +17 -3
- package/dist/harness-runtime/lib/dd-droid.mjs +1 -1
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +70 -13
- package/dist/harness-runtime/lib/dd-grok.mjs +6 -3
- 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/entity-references.d.mts +2 -0
- package/dist/harness-runtime/lib/entity-references.mjs +9 -0
- package/dist/harness-runtime/lib/managed-daemon.mjs +13 -6
- package/dist/harness-runtime/lib/native-hook-command.mjs +4 -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 +4 -2
- package/dist/harness-runtime/lib/provider-overload-policy.mjs +19 -6
- package/dist/harness-runtime/lib/runner-events.mjs +3 -1
- package/dist/harness-runtime/lib/setup-progress.mjs +8 -6
- 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/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/cli-operation-classifier.js +1 -1
- package/dist/services/cli-upgrade-allowlist.js +1 -1
- package/dist/services/code-checks.js +11 -4
- package/dist/services/compatibility-preflight.js +5 -2
- package/dist/services/controller-fanout.js +21 -4
- package/dist/services/controller-memory-task.js +45 -0
- package/dist/services/controller-memory-worker.js +48 -0
- package/dist/services/dashboard.js +2 -2
- package/dist/services/descriptor-run-start.js +26 -19
- package/dist/services/descriptor-stage-coordinator.js +30 -10
- package/dist/services/descriptor-stage-start.js +40 -4
- package/dist/services/engines.js +64 -14
- package/dist/services/eval-snapshots.js +104 -93
- package/dist/services/external-work-launch.js +5 -3
- package/dist/services/flow-catalog.js +8 -4
- package/dist/services/frozen-engine.js +113 -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 +2 -13
- package/dist/services/hybrid-digest.js +14 -0
- package/dist/services/installed-flow-source.js +25 -4
- package/dist/services/lifecycle-invocations.js +33 -5
- 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 +28 -2
- package/dist/services/memory-init-controller-start.js +19 -8
- package/dist/services/memory-init-git-policy.js +50 -21
- package/dist/services/memory-init-lint-capability.js +21 -3
- package/dist/services/memory-init-native-capacity.js +13 -0
- package/dist/services/memory-init-preflight.js +11 -1
- 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-layer.js +68 -4
- 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-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 +77 -23
- 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/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 +74 -9
- package/dist/services/recovery-snapshot-database.js +32 -4
- 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-controller-process.js +2 -6
- package/dist/services/run-controller-recovery.js +6 -1
- package/dist/services/run-controller.js +131 -80
- 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/runtime-scope-worker.js +46 -19
- package/dist/services/sessions.js +3 -24
- package/dist/services/setup.js +63 -13
- package/dist/services/stage-handlers.js +9 -7
- package/dist/services/stage-pause.js +108 -44
- package/dist/services/version-status.js +1 -1
- package/dist/services/vnext-fanout.js +21 -9
- package/dist/services/vnext-plan-review.js +3 -5
- package/dist/services/work-registry.js +80 -33
- 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 +8 -4
package/dist/cli/memory-init.js
CHANGED
|
@@ -13,7 +13,7 @@ import { PreparedMemoryInitLintCapability } from "../services/memory-init-lint-c
|
|
|
13
13
|
import { PreparedMemoryInitWorktrunkCapability } from "../services/memory-init-worktrunk-capability.js";
|
|
14
14
|
import { produceMemoryInitNativeCapacity } from "../services/memory-init-native-capacity.js";
|
|
15
15
|
import { openMemoryInitStores, assertMemoryInitStorePrerequisites } from "../services/memory-init-stores.js";
|
|
16
|
-
import { prepareMemoryInitControllerStart } from "../services/memory-init-controller-start.js";
|
|
16
|
+
import { prepareMemoryInitControllerStart, prepareMemoryUpgradeControllerStart } from "../services/memory-init-controller-start.js";
|
|
17
17
|
import { runControllerStatus } from "../services/run-controller.js";
|
|
18
18
|
import { requireRun } from "../services/run-controller-state.js";
|
|
19
19
|
import { requireProjectByRoot } from "../services/projects.js";
|
|
@@ -159,73 +159,7 @@ export async function runMemoryInitCli(args, env = process.env, io = process) {
|
|
|
159
159
|
// Beyond this boundary, absence of a success receipt is not no-effect.
|
|
160
160
|
effects = true;
|
|
161
161
|
const started = startMemoryInitRun(context, invocation.scope, prepared ? () => prepared : undefined);
|
|
162
|
-
|
|
163
|
-
const run = requireRun(context, requireProjectByRoot(context, invocation.scope.projectRoot).id, started.runId);
|
|
164
|
-
const response = { ok: true, action: "flow_mb_init_prepared", flow_id: "mb-init", ...started,
|
|
165
|
-
run_id: started.runId, request_id: invocation.scope.requestId, run_root: run.run_root,
|
|
166
|
-
project_root: run.project_root, workspace_root: run.workspace_root };
|
|
167
|
-
await writeMemoryInitObservation(io.stdout, `${JSON.stringify(writeResponseFile(response, output), null, output.json || output.progressJsonl ? undefined : 2)}\n`);
|
|
168
|
-
return 0;
|
|
169
|
-
}
|
|
170
|
-
const launch = prepareMemoryInitControllerStart(context, invocation.scope);
|
|
171
|
-
// Use the existing RUN-bound router: a global CLI must not launch its own
|
|
172
|
-
// controller code in place of the independently frozen RUN executable.
|
|
173
|
-
const receiptChunks = [];
|
|
174
|
-
let receiptSize = 0, oversizedReceipt = false;
|
|
175
|
-
const capture = new Writable({ write(chunk, _, next) {
|
|
176
|
-
receiptSize += Buffer.byteLength(chunk);
|
|
177
|
-
if (receiptSize > 1024 * 1024)
|
|
178
|
-
oversizedReceipt = true;
|
|
179
|
-
else
|
|
180
|
-
receiptChunks.push(Buffer.from(chunk));
|
|
181
|
-
next();
|
|
182
|
-
} });
|
|
183
|
-
const routed = await routeArgsThroughEngine(context, ["run", "drive", "launch", "--project-root", launch.input.projectRoot,
|
|
184
|
-
"--run", launch.input.runId, "--request-id", launch.input.requestId, "--context-file", launch.input.contextFile,
|
|
185
|
-
"--context-sha256", launch.input.contextSha256, "--json"], { stdout: capture, stderr: io.stderr }, undefined, { ...env, DD_FLOW_ENGINE_MODE: undefined }, invocation.scope.projectRoot);
|
|
186
|
-
if (!routed?.routed || routed.inProcess || routed.exitCode !== 0 || oversizedReceipt)
|
|
187
|
-
throw new AppError("memory_init_controller_launch_unknown", "Observe or reconcile the exact RUN-bound controller launch", 1, { effect: "unknown" });
|
|
188
|
-
let receipt;
|
|
189
|
-
try {
|
|
190
|
-
receipt = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(Buffer.concat(receiptChunks)));
|
|
191
|
-
}
|
|
192
|
-
catch {
|
|
193
|
-
throw new AppError("memory_init_controller_launch_unknown", "Selected engine returned no valid controller receipt", 1, { effect: "unknown" });
|
|
194
|
-
}
|
|
195
|
-
const accepted = receipt.controller;
|
|
196
|
-
if (receipt.ok !== true || !accepted || accepted.run_id !== started.runId || accepted.request_id !== invocation.scope.requestId)
|
|
197
|
-
throw new AppError("memory_init_controller_launch_unknown", "Selected engine returned a foreign controller receipt", 1, { effect: "unknown" });
|
|
198
|
-
const emit = (result) => writeMemoryInitObservation(io.stdout, `${JSON.stringify(result, null, output.json || output.progressJsonl ? undefined : 2)}\n`);
|
|
199
|
-
if (output.progressJsonl && !await emit({ ok: true, action: "flow_accepted", flow_id: "mb-init", ...started, ...receipt }))
|
|
200
|
-
return 0;
|
|
201
|
-
let result = receipt;
|
|
202
|
-
if (!invocation.detach) {
|
|
203
|
-
let after = 0;
|
|
204
|
-
let recentEvents = [];
|
|
205
|
-
for (;;) {
|
|
206
|
-
// The client has no ownership of the detached controller process.
|
|
207
|
-
if (io.stdout.destroyed)
|
|
208
|
-
return 0;
|
|
209
|
-
result = runControllerStatus(context, { projectRoot: invocation.scope.projectRoot, runId: started.runId, after });
|
|
210
|
-
const events = result.events, controller = result.controller;
|
|
211
|
-
after = result.next_cursor;
|
|
212
|
-
recentEvents = [...recentEvents, ...events].slice(-100);
|
|
213
|
-
if (output.progressJsonl && events.length && !await emit({ ok: true, action: "flow_progress", flow_id: "mb-init", ...result }))
|
|
214
|
-
return 0;
|
|
215
|
-
if (!controller)
|
|
216
|
-
throw new AppError("memory_init_controller_receipt_missing", "Observe or reconcile the retained controller", 1, { effect: "unknown" });
|
|
217
|
-
if (["completed", "cancelled", "superseded", "recovery_required", "failed", "waiting_for_user", "waiting_for_context"].includes(controller.status)) {
|
|
218
|
-
result = { ...result, events: recentEvents };
|
|
219
|
-
break;
|
|
220
|
-
}
|
|
221
|
-
await delay(250);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
const response = { ...result, action: "flow_mb_init", flow_id: "mb-init", run_id: started.runId, request_id: invocation.scope.requestId };
|
|
225
|
-
if (!await emit(writeResponseFile(response, output)))
|
|
226
|
-
return 0;
|
|
227
|
-
const status = result.controller?.status;
|
|
228
|
-
return ["recovery_required", "failed", "cancelled", "superseded"].includes(status ?? "") ? 1 : 0;
|
|
162
|
+
return await launchAndObserveMemoryFlow(context, invocation, started, "mb-init", output, env, io);
|
|
229
163
|
}
|
|
230
164
|
catch (error) {
|
|
231
165
|
const record = errorRecord(error);
|
|
@@ -238,3 +172,75 @@ export async function runMemoryInitCli(args, env = process.env, io = process) {
|
|
|
238
172
|
context?.db.close?.();
|
|
239
173
|
}
|
|
240
174
|
}
|
|
175
|
+
/** Shared foreground observer only; the existing RUN-bound controller owns
|
|
176
|
+
* physical Sessions. Neither alias gains another orchestration lifecycle. */
|
|
177
|
+
export async function launchAndObserveMemoryFlow(context, invocation, started, flowId, output, env, io) {
|
|
178
|
+
const action = flowId === "mb-init" ? "flow_mb_init" : "flow_mb_upgrade";
|
|
179
|
+
if (invocation.prepareOnly) {
|
|
180
|
+
const run = requireRun(context, requireProjectByRoot(context, invocation.scope.projectRoot).id, started.runId);
|
|
181
|
+
const response = { ok: true, action: `${action}_prepared`, flow_id: flowId, ...started,
|
|
182
|
+
run_id: started.runId, request_id: invocation.scope.requestId, run_root: run.run_root,
|
|
183
|
+
project_root: run.project_root, workspace_root: run.workspace_root };
|
|
184
|
+
await writeMemoryInitObservation(io.stdout, `${JSON.stringify(writeResponseFile(response, output), null, output.json || output.progressJsonl ? undefined : 2)}\n`);
|
|
185
|
+
return 0;
|
|
186
|
+
}
|
|
187
|
+
const launch = (flowId === "mb-init" ? prepareMemoryInitControllerStart : prepareMemoryUpgradeControllerStart)(context, invocation.scope);
|
|
188
|
+
// Use the existing RUN-bound router: a global CLI must not launch its own
|
|
189
|
+
// controller code in place of the independently frozen RUN executable.
|
|
190
|
+
const receiptChunks = [];
|
|
191
|
+
let receiptSize = 0, oversizedReceipt = false;
|
|
192
|
+
const capture = new Writable({ write(chunk, _, next) {
|
|
193
|
+
receiptSize += Buffer.byteLength(chunk);
|
|
194
|
+
if (receiptSize > 1024 * 1024)
|
|
195
|
+
oversizedReceipt = true;
|
|
196
|
+
else
|
|
197
|
+
receiptChunks.push(Buffer.from(chunk));
|
|
198
|
+
next();
|
|
199
|
+
} });
|
|
200
|
+
const routed = await routeArgsThroughEngine(context, ["run", "drive", "launch", "--project-root", launch.input.projectRoot,
|
|
201
|
+
"--run", launch.input.runId, "--request-id", launch.input.requestId, "--context-file", launch.input.contextFile,
|
|
202
|
+
"--context-sha256", launch.input.contextSha256, "--json"], { stdout: capture, stderr: io.stderr }, undefined, { ...env, DD_FLOW_ENGINE_MODE: undefined }, invocation.scope.projectRoot);
|
|
203
|
+
if (!routed?.routed || routed.inProcess || routed.exitCode !== 0 || oversizedReceipt)
|
|
204
|
+
throw new AppError("memory_init_controller_launch_unknown", "Observe or reconcile the exact RUN-bound controller launch", 1, { effect: "unknown" });
|
|
205
|
+
let receipt;
|
|
206
|
+
try {
|
|
207
|
+
receipt = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(Buffer.concat(receiptChunks)));
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
throw new AppError("memory_init_controller_launch_unknown", "Selected engine returned no valid controller receipt", 1, { effect: "unknown" });
|
|
211
|
+
}
|
|
212
|
+
const accepted = receipt.controller;
|
|
213
|
+
if (receipt.ok !== true || !accepted || accepted.run_id !== started.runId || accepted.request_id !== invocation.scope.requestId)
|
|
214
|
+
throw new AppError("memory_init_controller_launch_unknown", "Selected engine returned a foreign controller receipt", 1, { effect: "unknown" });
|
|
215
|
+
const emit = (result) => writeMemoryInitObservation(io.stdout, `${JSON.stringify(result, null, output.json || output.progressJsonl ? undefined : 2)}\n`);
|
|
216
|
+
if (output.progressJsonl && !await emit({ ok: true, action: "flow_accepted", flow_id: flowId, ...started, ...receipt }))
|
|
217
|
+
return 0;
|
|
218
|
+
let result = receipt;
|
|
219
|
+
if (!invocation.detach) {
|
|
220
|
+
let after = 0;
|
|
221
|
+
let recentEvents = [];
|
|
222
|
+
for (;;) {
|
|
223
|
+
// The client has no ownership of the detached controller process.
|
|
224
|
+
if (io.stdout.destroyed)
|
|
225
|
+
return 0;
|
|
226
|
+
result = runControllerStatus(context, { projectRoot: invocation.scope.projectRoot, runId: started.runId, after });
|
|
227
|
+
const events = result.events, controller = result.controller;
|
|
228
|
+
after = result.next_cursor;
|
|
229
|
+
recentEvents = [...recentEvents, ...events].slice(-100);
|
|
230
|
+
if (output.progressJsonl && events.length && !await emit({ ok: true, action: "flow_progress", flow_id: flowId, ...result }))
|
|
231
|
+
return 0;
|
|
232
|
+
if (!controller)
|
|
233
|
+
throw new AppError("memory_init_controller_receipt_missing", "Observe or reconcile the retained controller", 1, { effect: "unknown" });
|
|
234
|
+
if (["completed", "cancelled", "superseded", "recovery_required", "failed", "waiting_for_user", "waiting_for_context"].includes(controller.status)) {
|
|
235
|
+
result = { ...result, events: recentEvents };
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
await delay(250);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
const response = { ...result, action, flow_id: flowId, run_id: started.runId, request_id: invocation.scope.requestId };
|
|
242
|
+
if (!await emit(writeResponseFile(response, output)))
|
|
243
|
+
return 0;
|
|
244
|
+
const status = result.controller?.status;
|
|
245
|
+
return ["recovery_required", "failed", "cancelled", "superseded"].includes(status ?? "") ? 1 : 0;
|
|
246
|
+
}
|
|
@@ -17,6 +17,7 @@ export function runMemoryStorageAdmissionCli(args, env = process.env, io = proce
|
|
|
17
17
|
if (route.key !== "runtime storage admit" || output.responseFile || output.progressJsonl) {
|
|
18
18
|
throw new AppError("usage", "Storage admission accepts stdout output only", 2, { effect: "no_effect" });
|
|
19
19
|
}
|
|
20
|
+
const operation = route.parsed.options.get("operation")[0];
|
|
20
21
|
const home = env.DD_FLOW_HOME;
|
|
21
22
|
if (!home || !path.isAbsolute(home) || path.resolve(home) !== home || fs.realpathSync(home) !== home) {
|
|
22
23
|
throw new AppError("usage", "Storage admission requires an explicit canonical absolute DD_FLOW_HOME", 2, { effect: "no_effect" });
|
|
@@ -27,7 +28,7 @@ export function runMemoryStorageAdmissionCli(args, env = process.env, io = proce
|
|
|
27
28
|
const resource = PreparedMemoryInitResourceStorage.prepare({ ddFlowHome: home, env });
|
|
28
29
|
const context = createContext(env, "prepared_existing");
|
|
29
30
|
const facts = withStoreCleanup(() => {
|
|
30
|
-
const run = PreparedMemoryInitRuntimeStorage.prepare(context);
|
|
31
|
+
const run = operation === "memory-upgrade" ? PreparedMemoryInitRuntimeStorage.prepareUpgrade(context) : PreparedMemoryInitRuntimeStorage.prepare(context);
|
|
31
32
|
const resourceWriter = getResourceDatabase(path.dirname(resource.facts().store_path), "prepared_existing");
|
|
32
33
|
return withStoreCleanup(() => {
|
|
33
34
|
if (resourceWriter.get("SELECT dd_flow_writer_version() AS version")?.version !== resource.facts().executing_writer_version) {
|
|
@@ -38,7 +39,7 @@ export function runMemoryStorageAdmissionCli(args, env = process.env, io = proce
|
|
|
38
39
|
if (!current.isFile() || current.nlink !== 1 || current.dev !== owner.dev || current.ino !== owner.ino) {
|
|
39
40
|
throw new AppError("run_storage_changed", "RUN store changed during admission", 1, { effect: "no_effect" });
|
|
40
41
|
}
|
|
41
|
-
return { schema_id:
|
|
42
|
+
return { schema_id: `dd-flow/${operation}-engine-storage@1`, operation, route: "verified-no-op", outcome: "no-op",
|
|
42
43
|
run: { ...run.facts(), store_dev: owner.dev, store_ino: owner.ino }, resource: resource.facts() };
|
|
43
44
|
}, () => resourceWriter.close?.());
|
|
44
45
|
}, () => context.db.close?.());
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { Writable } from "node:stream";
|
|
5
|
+
import { DatabaseSync } from "node:sqlite";
|
|
6
|
+
import { getDatabase, getResourceDatabase } from "../storage/database.js";
|
|
7
|
+
import { canonicalPath } from "../storage/paths.js";
|
|
8
|
+
import { prepareCommandRoute } from "./command-inputs.js";
|
|
9
|
+
import { parseOutputOptions } from "./output-options.js";
|
|
10
|
+
import { runMemoryStorageAdmissionCli } from "./memory-storage-admission.js";
|
|
11
|
+
import { writePreparationError } from "./error-output.js";
|
|
12
|
+
import { AppError, errorRecord } from "../shared/errors.js";
|
|
13
|
+
import { hybridDigest } from "../services/hybrid-contracts.js";
|
|
14
|
+
import { captureStoreMigrationState } from "../storage/writer-migration.js";
|
|
15
|
+
import { PreparedPairedWriterMigration, observeDrainedMigrationStores } from "../storage/paired-writer-migration.js";
|
|
16
|
+
import { assertImportAdmission } from "../storage/import-guard.js";
|
|
17
|
+
/** Capability only. Exercise the actual target migration/admission on private
|
|
18
|
+
* WAL-aware copies before paying for a native probe. Never grants real-store
|
|
19
|
+
* write admission, productive migration proof or a runtime permit. */
|
|
20
|
+
export function runMemoryStorageQualificationCli(args, env = process.env, io = process) {
|
|
21
|
+
let temporary;
|
|
22
|
+
try {
|
|
23
|
+
const output = parseOutputOptions(args), route = prepareCommandRoute(output.args);
|
|
24
|
+
if (route.key !== "runtime storage qualify" || output.responseFile || output.progressJsonl)
|
|
25
|
+
throw new AppError("usage", "Storage qualification accepts stdout output only", 2, { effect: "no_effect" });
|
|
26
|
+
const home = env.DD_FLOW_HOME, resource = env.DD_FLOW_RESOURCE_HOME;
|
|
27
|
+
for (const root of [home, resource])
|
|
28
|
+
if (!root || !path.isAbsolute(root) || canonicalPath(root, false) !== root)
|
|
29
|
+
throw new AppError("usage", "Qualification requires both explicit canonical storage Homes", 2, { effect: "no_effect" });
|
|
30
|
+
for (const root of new Set([home, resource]))
|
|
31
|
+
assertImportAdmission(root);
|
|
32
|
+
const stores = [{ database: path.join(home, "db.sqlite") }, { database: path.join(resource, "runtime.sqlite") }];
|
|
33
|
+
const admit = (selected) => {
|
|
34
|
+
let stdout = "", stderr = "";
|
|
35
|
+
const collect = (write) => new Writable({ write(chunk, _encoding, done) { write(chunk.toString()); done(); } });
|
|
36
|
+
const code = runMemoryStorageAdmissionCli(["runtime", "storage", "admit", "--operation", "memory-upgrade", "--json"], selected, { stdout: collect(chunk => { stdout += chunk; }), stderr: collect(chunk => { stderr += chunk; }) });
|
|
37
|
+
return { code, stdout, stderr };
|
|
38
|
+
};
|
|
39
|
+
const current = admit(env);
|
|
40
|
+
let selectedRoute = "verified-no-op";
|
|
41
|
+
if (current.code !== 0) {
|
|
42
|
+
// No simulation of a live runtime: the normal admission above remains
|
|
43
|
+
// sufficient for compatible stores; changed stores must already drain.
|
|
44
|
+
temporary = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), "dd-upgrade-capability-")));
|
|
45
|
+
const runHome = path.join(temporary, "run"), resourceHome = path.join(temporary, "resource");
|
|
46
|
+
fs.mkdirSync(runHome);
|
|
47
|
+
fs.mkdirSync(resourceHome);
|
|
48
|
+
const copies = [path.join(runHome, "db.sqlite"), path.join(resourceHome, "runtime.sqlite")];
|
|
49
|
+
stores.forEach((store, index) => {
|
|
50
|
+
if (!fs.existsSync(store.database)) {
|
|
51
|
+
if (["-wal", "-shm", "-journal"].some(suffix => fs.existsSync(store.database + suffix)))
|
|
52
|
+
throw new AppError("storage_capability_invalid", "Missing source store retains sidecars", 1, { effect: "no_effect" });
|
|
53
|
+
const fresh = index === 0 ? getDatabase(runHome, "create_new") : getResourceDatabase(resourceHome, "create_new");
|
|
54
|
+
fresh.close?.();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const owner = fs.lstatSync(store.database);
|
|
58
|
+
if (!owner.isFile() || owner.nlink !== 1)
|
|
59
|
+
throw new AppError("storage_capability_invalid", "Source store must have one regular physical owner", 1, { effect: "no_effect" });
|
|
60
|
+
const reader = new DatabaseSync(store.database, { readOnly: true });
|
|
61
|
+
try {
|
|
62
|
+
reader.exec(`VACUUM INTO '${copies[index].replaceAll("'", "''")}'`);
|
|
63
|
+
const copy = new DatabaseSync(copies[index], { readOnly: true });
|
|
64
|
+
try {
|
|
65
|
+
if (hybridDigest(captureStoreMigrationState(reader)) !== hybridDigest(captureStoreMigrationState(copy)))
|
|
66
|
+
throw new AppError("storage_capability_invalid", "Source changed during private qualification", 1, { effect: "no_effect" });
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
copy.close();
|
|
70
|
+
}
|
|
71
|
+
const after = fs.lstatSync(store.database);
|
|
72
|
+
if (after.dev !== owner.dev || after.ino !== owner.ino || after.nlink !== 1)
|
|
73
|
+
throw new AppError("storage_capability_invalid", "Source store owner changed", 1, { effect: "no_effect" });
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
reader.close();
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
let checked = admit({ ...env, DD_FLOW_HOME: runHome, DD_FLOW_RESOURCE_HOME: resourceHome });
|
|
80
|
+
if (checked.code !== 0) {
|
|
81
|
+
observeDrainedMigrationStores(stores.filter(store => fs.existsSync(store.database)));
|
|
82
|
+
PreparedPairedWriterMigration.prepare({ requestId: "capability-only", requestFile: path.join(temporary, "plan.json"), projectRoot: temporary,
|
|
83
|
+
workspaceRoot: temporary, runHome, resourceHome, selectionSha256: hybridDigest(stores), bootstrapSchema: "memory-upgrade@1" }).apply();
|
|
84
|
+
checked = admit({ ...env, DD_FLOW_HOME: runHome, DD_FLOW_RESOURCE_HOME: resourceHome });
|
|
85
|
+
selectedRoute = "migration";
|
|
86
|
+
}
|
|
87
|
+
if (checked.code !== 0)
|
|
88
|
+
throw new AppError("storage_capability_invalid", "Target cannot admit its privately migrated copies", 1, { effect: "no_effect", admission: JSON.parse(checked.stderr) });
|
|
89
|
+
}
|
|
90
|
+
const facts = { schema_id: "dd-flow/memory-upgrade-storage-capability@1", operation: "memory-upgrade", route: selectedRoute,
|
|
91
|
+
source_stores: stores.map(store => store.database), productive_admission: false };
|
|
92
|
+
io.stdout.write(`${JSON.stringify({ ok: true, action: "runtime_storage_qualify", facts, sha256: hybridDigest(facts) })}\n`);
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
const record = errorRecord(error);
|
|
97
|
+
record.details = { ...record.details, effect: "no_effect" };
|
|
98
|
+
writePreparationError(io, args, record);
|
|
99
|
+
return record.exitCode;
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
if (temporary)
|
|
103
|
+
fs.rmSync(temporary, { recursive: true, force: true });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { prepareCommandRoute } from "./command-inputs.js";
|
|
5
|
+
import { parseOutputOptions } from "./output-options.js";
|
|
6
|
+
import { AppError, errorRecord } from "../shared/errors.js";
|
|
7
|
+
import { writePreparationError } from "./error-output.js";
|
|
8
|
+
import { restoreMemoryUpgradeSelection } from "../services/memory-upgrade-selection.js";
|
|
9
|
+
import { assertMemoryUpgradeProjectCurrent } from "../services/memory-upgrade-storage-readiness.js";
|
|
10
|
+
import { PreparedPairedWriterMigration, PreparedPairedWriterRecovery } from "../storage/paired-writer-migration.js";
|
|
11
|
+
import { readBoundedReleaseFile } from "../services/release-source.js";
|
|
12
|
+
import { shellArgument } from "../services/runtime-command.js";
|
|
13
|
+
function refuse(reason) { throw new AppError("memory_storage_transition_invalid", "Storage transition requires the original upgrade and its selected immutable engine", 1, { reason, effect: "no_effect" }); }
|
|
14
|
+
const hash = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
15
|
+
/** Pre-context transport: no initialize/prepared-existing database connection,
|
|
16
|
+
* router fallback, provider or RUN creation. Executed by the selected engine. */
|
|
17
|
+
export function runMemoryStorageTransitionCli(args, env = process.env, io = process) {
|
|
18
|
+
let effects = false;
|
|
19
|
+
try {
|
|
20
|
+
const output = parseOutputOptions(args), route = prepareCommandRoute(output.args);
|
|
21
|
+
if (route.key !== "runtime storage transition" || output.responseFile || output.progressJsonl)
|
|
22
|
+
throw new AppError("usage", "Storage transition accepts stdout output only", 2, { effect: "no_effect" });
|
|
23
|
+
const option = (key) => route.parsed.options.get(key)?.[0];
|
|
24
|
+
const action = option("action"), expected = option("expected-sha256"), recoveryId = option("recovery-id");
|
|
25
|
+
if (env.DD_FLOW_INVOCATION_READONLY === "1" && !["status", "recovery-status"].includes(action))
|
|
26
|
+
refuse("readonly_invocation");
|
|
27
|
+
if (action !== "plan" && (!expected || !/^[a-f0-9]{64}$/.test(expected)))
|
|
28
|
+
throw new AppError("usage", "This action requires --expected-sha256 from its retained request", 2, { effect: "no_effect" });
|
|
29
|
+
if ((action === "recovery-plan") !== Boolean(recoveryId))
|
|
30
|
+
throw new AppError("usage", "--recovery-id is required only for recovery-plan", 2, { effect: "no_effect" });
|
|
31
|
+
if (["apply", "recovery-plan", "recover"].includes(action) && !route.parsed.options.has("offline"))
|
|
32
|
+
throw new AppError("usage", "Storage mutation requires --offline after draining all runtime owners", 2, { effect: "no_effect" });
|
|
33
|
+
const home = env.DD_FLOW_HOME, resource = env.DD_FLOW_RESOURCE_HOME;
|
|
34
|
+
for (const root of [home, resource])
|
|
35
|
+
if (!root || !path.isAbsolute(root) || fs.realpathSync(root) !== root)
|
|
36
|
+
refuse("explicit_canonical_homes_required");
|
|
37
|
+
const scope = { requestId: option("request-id"), projectRoot: option("project-root"), workspaceRoot: option("workspace-root") };
|
|
38
|
+
const selected = restoreMemoryUpgradeSelection(home, scope), engine = selected.engine;
|
|
39
|
+
if (option("selection-sha256") !== selected.sha256)
|
|
40
|
+
refuse("original_selection_changed");
|
|
41
|
+
if (!process.argv[1] || fs.realpathSync(process.argv[1]) !== engine.entrypoint)
|
|
42
|
+
refuse("selected_engine_entrypoint_required");
|
|
43
|
+
const file = path.join(path.dirname(selected.original.candidate.retain().file), "storage-migration.json");
|
|
44
|
+
const pairedId = `upgrade-${selected.sha256.slice(0, 32)}`;
|
|
45
|
+
const assertProjectCurrent = () => {
|
|
46
|
+
if (!selected.payload.git || !selected.oldTree)
|
|
47
|
+
refuse("productive_upgrade_selection_required");
|
|
48
|
+
assertMemoryUpgradeProjectCurrent(selected, scope, env);
|
|
49
|
+
};
|
|
50
|
+
const restorePair = (sha) => {
|
|
51
|
+
const pair = PreparedPairedWriterMigration.restore(file, sha), material = pair.recoveryMaterial();
|
|
52
|
+
if (material.intent.selection_sha256 !== selected.sha256 || material.intent.request_id !== pairedId
|
|
53
|
+
|| material.intent.project_root !== scope.projectRoot || material.intent.workspace_root !== scope.workspaceRoot
|
|
54
|
+
|| material.intent.stores[0]?.home !== home || material.intent.stores[1]?.home !== resource)
|
|
55
|
+
refuse("retained_pair_scope_changed");
|
|
56
|
+
return pair;
|
|
57
|
+
};
|
|
58
|
+
const command = (next, sha) => `DD_FLOW_RESOURCE_HOME=${shellArgument(resource)} ${engine.command(home, [
|
|
59
|
+
"runtime", "storage", "transition", "--action", next, "--project-root", scope.projectRoot, "--workspace-root", scope.workspaceRoot,
|
|
60
|
+
"--request-id", scope.requestId, "--selection-sha256", selected.sha256, "--expected-sha256", sha, ...(["apply", "recover"].includes(next) ? ["--offline"] : []), "--json"
|
|
61
|
+
])}`;
|
|
62
|
+
let result;
|
|
63
|
+
if (action === "plan") {
|
|
64
|
+
let pair;
|
|
65
|
+
if (fs.existsSync(file))
|
|
66
|
+
pair = restorePair(expected ?? hash(readBoundedReleaseFile(file, 16 * 1024 * 1024)));
|
|
67
|
+
else {
|
|
68
|
+
if (expected)
|
|
69
|
+
refuse("original_pair_missing");
|
|
70
|
+
assertProjectCurrent();
|
|
71
|
+
pair = PreparedPairedWriterMigration.prepare({ requestId: pairedId, requestFile: file, projectRoot: scope.projectRoot, workspaceRoot: scope.workspaceRoot,
|
|
72
|
+
runHome: home, resourceHome: resource, selectionSha256: selected.sha256, bootstrapSchema: "memory-upgrade@1" });
|
|
73
|
+
}
|
|
74
|
+
effects = true;
|
|
75
|
+
const request = pair.retain();
|
|
76
|
+
result = { request, apply_command: command("apply", pair.sha256), status_command: command("status", pair.sha256) };
|
|
77
|
+
}
|
|
78
|
+
else if (action === "recover" || action === "recovery-status") {
|
|
79
|
+
const recovery = PreparedPairedWriterRecovery.restore(file + ".recovery.json", expected), reference = recovery.migrationReference();
|
|
80
|
+
if (reference.path !== file)
|
|
81
|
+
refuse("recovery_migration_scope_changed");
|
|
82
|
+
restorePair(reference.sha256);
|
|
83
|
+
if (action === "recover") {
|
|
84
|
+
effects = true;
|
|
85
|
+
result = recovery.apply();
|
|
86
|
+
}
|
|
87
|
+
else
|
|
88
|
+
result = recovery.reconcile();
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
const pair = restorePair(expected);
|
|
92
|
+
if (action === "status") {
|
|
93
|
+
const observed = pair.reconcile();
|
|
94
|
+
result = { status: observed.status,
|
|
95
|
+
proof: fs.existsSync(file + ".result.json") ? pair.readEvidence() : null,
|
|
96
|
+
outcomes: observed.outcomes.map(outcome => ({ status: outcome.status, ...(outcome.reason ? { reason: outcome.reason } : {}) })) };
|
|
97
|
+
}
|
|
98
|
+
else if (action === "apply") {
|
|
99
|
+
if (pair.reconcile().status === "not_applied")
|
|
100
|
+
assertProjectCurrent();
|
|
101
|
+
effects = true;
|
|
102
|
+
pair.apply();
|
|
103
|
+
result = { status: "applied", proof: pair.readEvidence() };
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
const recovery = PreparedPairedWriterRecovery.prepare(pair, recoveryId);
|
|
107
|
+
effects = true;
|
|
108
|
+
const request = recovery.retain();
|
|
109
|
+
result = { request, recover_command: command("recover", request.sha256), status_command: command("recovery-status", request.sha256) };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
io.stdout.write(`${JSON.stringify({ ok: true, action: "runtime_storage_transition", operation: action, scope, selection_sha256: selected.sha256,
|
|
113
|
+
engine: { manifest_sha256: engine.manifestSha256, sha256: engine.manifest().integrity.checksum }, result }, null, output.json ? undefined : 2)}\n`);
|
|
114
|
+
return 0;
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
const record = errorRecord(error);
|
|
118
|
+
if (effects)
|
|
119
|
+
record.details = { ...record.details, effect: "unknown", next_action: "reconcile_exact_request" };
|
|
120
|
+
writePreparationError(io, args, record);
|
|
121
|
+
return record.exitCode;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { createContext } from "../runtime/context.js";
|
|
5
|
+
import { AppError, errorRecord } from "../shared/errors.js";
|
|
6
|
+
import { resolveDdFlowHome } from "../storage/paths.js";
|
|
7
|
+
import { hybridDigest } from "../services/hybrid-contracts.js";
|
|
8
|
+
import { readSetupStatus } from "../services/setup.js";
|
|
9
|
+
import { readOperatorProfiles, readOperatorProfileInput } from "../services/operator-profiles.js";
|
|
10
|
+
import { onboardExecutor } from "../services/executor-onboarding.js";
|
|
11
|
+
import { readMemoryUpgradeRequestCandidate, prepareMemoryUpgradeRequestCandidate } from "../services/memory-init-request-candidate.js";
|
|
12
|
+
import { readMemoryUpgradeRequestInput } from "../services/memory-init-request-input.js";
|
|
13
|
+
import { PreparedMemoryUpgradeSelection, readMemoryUpgradeSelection, restoreMemoryUpgradeSelection } from "../services/memory-upgrade-selection.js";
|
|
14
|
+
import { PreparedUpgradeStorageReadiness, readUpgradeStorageReadiness, assertMemoryUpgradeProjectCurrent } from "../services/memory-upgrade-storage-readiness.js";
|
|
15
|
+
import { startMemoryUpgradeRun, registerMemoryUpgradeProject } from "../services/memory-upgrade-start.js";
|
|
16
|
+
import { openMemoryInitStores, assertMemoryInitStorePrerequisites } from "../services/memory-init-stores.js";
|
|
17
|
+
import { PreparedInstalledFlowSource } from "../services/installed-flow-source.js";
|
|
18
|
+
import { selectMemoryInitInstalledEngine } from "../services/memory-init-engine-release.js";
|
|
19
|
+
import { FrozenGenericExecution } from "../services/generic-execution.js";
|
|
20
|
+
import { prepareMemoryInitHarnessCommands } from "../services/memory-init-project-bootstrap.js";
|
|
21
|
+
import { produceMemoryInitNativeCapacity, restoreCompletedMemoryNativeCapacity } from "../services/memory-init-native-capacity.js";
|
|
22
|
+
import { inspectMemoryLintExecutable, inspectMemoryGitExecutable } from "../services/memory-init-preflight.js";
|
|
23
|
+
import { PreparedMemoryInitLintCapability } from "../services/memory-init-lint-capability.js";
|
|
24
|
+
import { observeMemoryGitPolicySelection } from "../services/memory-init-git-policy.js";
|
|
25
|
+
import { parseRuntimeBudget } from "../services/runtime-budget.js";
|
|
26
|
+
import { launchAndObserveMemoryFlow } from "./memory-init.js";
|
|
27
|
+
import { prepareMemoryUpgradeInvocation, assertMemoryInitInvocationReplay } from "./memory-init-invocation.js";
|
|
28
|
+
import { askExecutorChoice } from "./executor-onboarding.js";
|
|
29
|
+
import { parseOutputOptions } from "./output-options.js";
|
|
30
|
+
import { prepareResponseDestination } from "./response-file.js";
|
|
31
|
+
import { prepareFileBytes } from "./input-preparation.js";
|
|
32
|
+
import { writePreparationError } from "./error-output.js";
|
|
33
|
+
/** Upgrade never enters init's project publication path. Read-only original
|
|
34
|
+
* request lookup precedes ambient selection; writable stores are opened only
|
|
35
|
+
* after retaining the target/current selection, without initializing existing
|
|
36
|
+
* stores. Pristine stores use shared pair publication, never init documents.
|
|
37
|
+
* Storage-changing transitions must first settle their explicit migration.
|
|
38
|
+
*/
|
|
39
|
+
export async function runMemoryUpgradeCli(args, env = process.env, io = process) {
|
|
40
|
+
let context, effects = false;
|
|
41
|
+
try {
|
|
42
|
+
const output = parseOutputOptions(args), invocation = prepareMemoryUpgradeInvocation(output.args);
|
|
43
|
+
parseRuntimeBudget(env.DD_FLOW_RUNTIME_BUDGET);
|
|
44
|
+
if (output.responseFile)
|
|
45
|
+
prepareResponseDestination(output.responseFile);
|
|
46
|
+
const home = resolveDdFlowHome(env), scope = invocation.scope;
|
|
47
|
+
const reader = createContext(env, "read_existing");
|
|
48
|
+
let storage;
|
|
49
|
+
try {
|
|
50
|
+
storage = readUpgradeStorageReadiness(reader, scope);
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
reader.db.close?.();
|
|
54
|
+
}
|
|
55
|
+
const candidate = storage ? null : readMemoryUpgradeRequestCandidate(home, scope);
|
|
56
|
+
const original = storage?.receipt.selection?.request_input ?? candidate?.input;
|
|
57
|
+
if (storage && !original)
|
|
58
|
+
throw new AppError("memory_upgrade_original_request_required", "Historical storage metadata cannot select a fresh upgrade", 1, { effect: "no_effect" });
|
|
59
|
+
if (original) {
|
|
60
|
+
const objective = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(Buffer.from(original.intake_base64, "base64"));
|
|
61
|
+
const raw = readMemoryUpgradeRequestInput(original, scope, objective);
|
|
62
|
+
if (invocation.profileFile)
|
|
63
|
+
invocation.execution.rootProfile = readOperatorProfileInput(invocation.profileFile);
|
|
64
|
+
assertMemoryInitInvocationReplay(invocation, raw.selection, original.intake_sha256);
|
|
65
|
+
if (invocation.nativeConfigHome) {
|
|
66
|
+
const selected = storage?.selection?.payload.execution ?? readMemoryUpgradeSelection(home, scope)?.payload.execution;
|
|
67
|
+
const reference = selected?.snapshot.settings.delegation.native_capacity_receipt;
|
|
68
|
+
const encoded = selected?.receipt_blobs?.find(([sha]) => sha === reference?.sha256)?.[1];
|
|
69
|
+
if (!encoded || JSON.parse(Buffer.from(encoded, "base64").toString("utf8")).input.native_config_home !== invocation.nativeConfigHome)
|
|
70
|
+
throw new AppError("memory_upgrade_request_conflict", "Original native configuration selection changed", 1, { effect: "no_effect" });
|
|
71
|
+
}
|
|
72
|
+
if (!storage && !readMemoryUpgradeSelection(home, scope)) {
|
|
73
|
+
// A raw candidate is not resolved selection authority. Resume only its
|
|
74
|
+
// original target/profile request; never replace it with new CLI input.
|
|
75
|
+
effects = true;
|
|
76
|
+
const native = raw.selection.execution.delegation?.mode === "native"
|
|
77
|
+
? await restoreCompletedMemoryNativeCapacity({ home, scope, env }) : undefined;
|
|
78
|
+
if (native && hybridDigest(native.reference) !== hybridDigest(raw.selection.execution.delegation?.nativeCapacityReceipt))
|
|
79
|
+
throw new AppError("memory_upgrade_request_conflict", "Completed probe differs from the original request", 1, { effect: "unknown" });
|
|
80
|
+
PreparedMemoryUpgradeSelection.prepare(home, scope, candidate.candidate, { env, now: new Date().toISOString(),
|
|
81
|
+
...(native ? { receipts: native.receipts, receiptBytes: native.receiptBytes } : {}) }).retain();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
if (!invocation.intakeFile || !invocation.intakeSha256 || !invocation.gitPolicy)
|
|
86
|
+
throw new AppError("usage", "Fresh upgrade requires original intake file/hash and explicit Git policy", 2, { effect: "no_effect" });
|
|
87
|
+
const intake = prepareFileBytes(invocation.intakeFile, "original upgrade intake", 64 * 1024).bytes;
|
|
88
|
+
if (createHash("sha256").update(intake).digest("hex") !== invocation.intakeSha256)
|
|
89
|
+
throw new AppError("memory_upgrade_intake_changed", "Original intake differs from its declared hash", 2, { effect: "no_effect" });
|
|
90
|
+
let objective;
|
|
91
|
+
try {
|
|
92
|
+
objective = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(intake);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
throw new AppError("usage", "Original upgrade intake must be UTF-8", 2, { effect: "no_effect" });
|
|
96
|
+
}
|
|
97
|
+
const setup = readSetupStatus(home), installation = setup.installation;
|
|
98
|
+
if (!installation || setup.installation_readiness !== "installed")
|
|
99
|
+
throw new AppError("memory_upgrade_setup_required", "Install the target release with setup before project upgrade", 1, { effect: "no_effect" });
|
|
100
|
+
const stdin = io.stdin ?? process.stdin;
|
|
101
|
+
const selected = await onboardExecutor({
|
|
102
|
+
...(typeof invocation.execution.rootProfile === "string" ? { profileId: invocation.execution.rootProfile } : {}),
|
|
103
|
+
...(invocation.profileFile ? { profileFile: invocation.profileFile } : {})
|
|
104
|
+
}, () => readOperatorProfiles(home), { allowSkip: false, nonInteractive: invocation.nonInteractive || output.json || output.progressJsonl
|
|
105
|
+
|| !stdin.isTTY || !io.stderr.isTTY }, settings => askExecutorChoice(settings, { stdin, stderr: io.stderr }, { allowSave: false, allowSkip: false }));
|
|
106
|
+
if (selected.source === "explicit-file" || selected.source === "explicit-profile")
|
|
107
|
+
invocation.execution.rootProfile = selected.profile;
|
|
108
|
+
else if (invocation.execution.rootProfile === undefined && selected.source === "explicit-id")
|
|
109
|
+
invocation.execution.rootProfile = selected.profile.id;
|
|
110
|
+
let native;
|
|
111
|
+
if (invocation.execution.delegation?.mode === "native") {
|
|
112
|
+
if (!invocation.allowLiveProbe)
|
|
113
|
+
throw new AppError("usage", "Fresh native upgrade requires --allow-live-probe", 2, { effect: "no_effect" });
|
|
114
|
+
const source = PreparedInstalledFlowSource.fromInstalledRelease(home, installation.manifest_sha256, "mb-upgrade");
|
|
115
|
+
const engine = selectMemoryInitInstalledEngine(home, installation.manifest_sha256);
|
|
116
|
+
engine.admitMemoryUpgradeFlow(source);
|
|
117
|
+
if (!fs.existsSync(path.join(home, "db.sqlite")))
|
|
118
|
+
assertMemoryInitStorePrerequisites(env, scope);
|
|
119
|
+
engine.qualifyMemoryUpgradeStorage(home, env.DD_FLOW_RESOURCE_HOME ?? home);
|
|
120
|
+
observeMemoryGitPolicySelection(env, { scope, gitExecutable: inspectMemoryGitExecutable(env).executable, selection: invocation.gitPolicy });
|
|
121
|
+
const lint = inspectMemoryLintExecutable(env);
|
|
122
|
+
PreparedMemoryInitLintCapability.prepareExecutable(lint.executable, lint.probe, env, { temporaryConfiguration: true });
|
|
123
|
+
const execution = FrozenGenericExecution.prepare(source.resolveRegisteredFlow(), readOperatorProfiles(home), {
|
|
124
|
+
...invocation.execution, delegation: { mode: "external", max_parallel: invocation.execution.delegation.max_parallel ?? 1 }
|
|
125
|
+
}, { requestId: scope.requestId, projectRoot: scope.projectRoot, workspace: scope.workspaceRoot });
|
|
126
|
+
prepareMemoryInitHarnessCommands({ ddFlowHome: home }, execution);
|
|
127
|
+
const snapshot = execution.snapshot(), profile = snapshot.profiles.find(entry => entry.id === snapshot.settings.root_profile).profile;
|
|
128
|
+
if (snapshot.profiles.some(entry => hybridDigest({ ...entry.profile, id: profile.id }) !== hybridDigest(profile)))
|
|
129
|
+
throw new AppError("generic_execution_invalid", "Native delegation cannot change execution profiles", 2, { effect: "no_effect" });
|
|
130
|
+
effects = true;
|
|
131
|
+
native = await produceMemoryInitNativeCapacity({ home, scope, profile, maximum: invocation.execution.delegation.max_parallel ?? 1,
|
|
132
|
+
...(invocation.nativeConfigHome ? { nativeConfigHome: invocation.nativeConfigHome } : {}), allowLiveProbe: true, env,
|
|
133
|
+
requestSha256: hybridDigest({ flow: "mb-upgrade", scope, execution: invocation.execution, gitPolicy: invocation.gitPolicy,
|
|
134
|
+
objective, intake: invocation.intakeSha256, target: installation.manifest_sha256 }) });
|
|
135
|
+
invocation.execution.delegation.nativeCapacityReceipt = native.reference;
|
|
136
|
+
}
|
|
137
|
+
const raw = { schema_id: "dd-flow/memory-upgrade-request-selection@1", scope,
|
|
138
|
+
execution: invocation.execution, git_policy: invocation.gitPolicy, objective, engine_checksum: null,
|
|
139
|
+
receipts: [...native?.receipts ?? []], target_installation_sha256: installation.manifest_sha256 };
|
|
140
|
+
const prepared = prepareMemoryUpgradeRequestCandidate(home, scope, Buffer.from(JSON.stringify(raw)), intake);
|
|
141
|
+
effects = true;
|
|
142
|
+
prepared.retain();
|
|
143
|
+
PreparedMemoryUpgradeSelection.prepare(home, scope, prepared, { env, now: new Date().toISOString(),
|
|
144
|
+
...(native ? { receipts: native.receipts, receiptBytes: native.receiptBytes } : {}) }).retain();
|
|
145
|
+
}
|
|
146
|
+
if (!storage) {
|
|
147
|
+
const selected = restoreMemoryUpgradeSelection(home, scope);
|
|
148
|
+
if (!fs.existsSync(path.join(home, "db.sqlite"))) {
|
|
149
|
+
assertMemoryUpgradeProjectCurrent(selected, scope, env);
|
|
150
|
+
effects = true;
|
|
151
|
+
// Shared store creation owns pristine/replay checks and never writes
|
|
152
|
+
// project documents. Existing stores still take explicit migration.
|
|
153
|
+
const fresh = openMemoryInitStores(env, { scope, selectionSha256: selected.sha256 });
|
|
154
|
+
fresh.context.db.close?.();
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
selected.engine.admitMemoryUpgradeStorage(home, env.DD_FLOW_RESOURCE_HOME ?? home);
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
if (!(error instanceof AppError) || error.code !== "frozen_engine_storage_incompatible")
|
|
161
|
+
throw error;
|
|
162
|
+
// Retain only the selected engine's explicit plan. Never apply it as a
|
|
163
|
+
// side effect of starting upgrade or open the old stores for writes.
|
|
164
|
+
effects = true;
|
|
165
|
+
const planned = selected.engine.transitionMemoryUpgradeStorage({ home, resourceHome: env.DD_FLOW_RESOURCE_HOME ?? home,
|
|
166
|
+
scope, selectionSha256: selected.sha256, action: "plan" });
|
|
167
|
+
throw new AppError("memory_upgrade_storage_transition_required", "Drain runtime owners, apply the retained storage plan, then repeat this exact upgrade request", 1, { effect: "partial", storage_effect: "no_effect", migration: planned.result });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
context = createContext(env, "prepared_existing", undefined, { resourceDatabaseMode: "prepared_existing" });
|
|
171
|
+
effects = true;
|
|
172
|
+
if (!storage) {
|
|
173
|
+
registerMemoryUpgradeProject(context, scope);
|
|
174
|
+
PreparedUpgradeStorageReadiness.prepareFromSelection(context, scope).apply(context);
|
|
175
|
+
}
|
|
176
|
+
const started = startMemoryUpgradeRun(context, scope);
|
|
177
|
+
return await launchAndObserveMemoryFlow(context, invocation, started, "mb-upgrade", output, env, io);
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
const record = errorRecord(error);
|
|
181
|
+
if (effects)
|
|
182
|
+
record.details = { ...record.details, effect: record.details.effect === "partial" ? "partial" : "unknown", next_action: "observe_or_reconcile_exact_request" };
|
|
183
|
+
writePreparationError(io, args, record);
|
|
184
|
+
return record.exitCode;
|
|
185
|
+
}
|
|
186
|
+
finally {
|
|
187
|
+
context?.db.close?.();
|
|
188
|
+
}
|
|
189
|
+
}
|