@deksden-com/dd-flow-cli 0.9.0-beta.137 → 0.9.0-beta.139
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 +19 -0
- package/README.md +9 -0
- package/dist/build-info.json +6 -6
- package/dist/cli/argument-values.js +5 -5
- package/dist/cli/command-inputs.js +89 -20
- package/dist/cli/command-metadata.js +197 -0
- package/dist/cli/command-option-descriptions.js +191 -0
- package/dist/cli/completion-cli.js +70 -0
- package/dist/cli/completion.js +57 -0
- package/dist/cli/error-output.js +52 -0
- package/dist/cli/executor-onboarding.js +76 -0
- package/dist/cli/flows.js +29 -0
- package/dist/cli/harnesses.js +84 -0
- package/dist/cli/help.js +240 -6
- package/dist/cli/input-preparation.js +54 -5
- package/dist/cli/memory-flow-admission.js +44 -0
- package/dist/cli/memory-init-invocation.js +78 -0
- package/dist/cli/memory-init.js +240 -0
- package/dist/cli/memory-storage-admission.js +53 -0
- package/dist/cli/models.js +39 -0
- package/dist/cli/native-hook-ingress.js +1 -1
- package/dist/cli/operator-profiles.js +61 -0
- package/dist/cli/output-options.js +35 -0
- package/dist/cli/response-file.js +71 -0
- package/dist/cli/run-cli.js +283 -160
- package/dist/cli/runtime-process.js +1 -1
- package/dist/cli/setup.js +149 -0
- package/dist/cli.js +56 -2
- package/dist/domain/stage-handler-identities.js +20 -0
- package/dist/harness-runtime/lib/daemon-operations.d.mts +1 -0
- package/dist/harness-runtime/lib/daemon-operations.mjs +4 -3
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +53 -22
- package/dist/harness-runtime/lib/dd-agy.mjs +16 -5
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +12 -6
- package/dist/harness-runtime/lib/dd-codex.mjs +24 -12
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +2 -1
- package/dist/harness-runtime/lib/dd-droid.mjs +32 -9
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +11 -3
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +100 -15
- package/dist/harness-runtime/lib/dd-opencode.mjs +51 -20
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +22 -10
- package/dist/harness-runtime/lib/dd-zcode.mjs +17 -4
- package/dist/harness-runtime/lib/delegation-instructions.d.mts +1 -0
- package/dist/harness-runtime/lib/delegation-instructions.mjs +16 -3
- package/dist/harness-runtime/lib/lease-renewal.mjs +2 -1
- package/dist/harness-runtime/lib/lifecycle-command.mjs +1 -0
- package/dist/harness-runtime/lib/managed-daemon.d.mts +1 -0
- package/dist/harness-runtime/lib/managed-daemon.mjs +50 -15
- package/dist/harness-runtime/lib/managed-flow-env.mjs +15 -0
- package/dist/harness-runtime/lib/native-children.d.mts +2 -0
- package/dist/harness-runtime/lib/native-children.mjs +20 -1
- package/dist/harness-runtime/lib/native-hook-command.mjs +1 -1
- package/dist/harness-runtime/lib/native-request-proof.d.mts +2 -0
- package/dist/harness-runtime/lib/native-request-proof.mjs +65 -0
- package/dist/harness-runtime/lib/opencode-plugin.mjs +32 -9
- package/dist/harness-runtime/lib/operation-progress.mjs +3 -1
- package/dist/harness-runtime/lib/provider-overload-policy.d.mts +13 -0
- package/dist/harness-runtime/lib/provider-overload-policy.mjs +138 -0
- package/dist/runtime/context.js +1 -1
- package/dist/schemas/discovery-result.schema.json +184 -0
- package/dist/schemas/execution-bundle.schema.json +205 -0
- package/dist/schemas/execution-settings.schema.json +162 -0
- package/dist/schemas/flow-descriptor.schema.json +54 -0
- package/dist/schemas/flow-run.schema.json +26 -0
- package/dist/schemas/harness-catalog.schema.json +80 -0
- package/dist/schemas/mb-init-compose-result.schema.json +457 -0
- package/dist/schemas/mb-init-discover-result.schema.json +500 -0
- package/dist/schemas/mb-init-finish-result.schema.json +422 -0
- package/dist/schemas/mb-init-preflight-result.schema.json +367 -0
- package/dist/schemas/mb-init-review-result.schema.json +396 -0
- package/dist/schemas/mb-init-verify-result.schema.json +391 -0
- package/dist/schemas/memory-bootstrap-receipt-v2.schema.json +67 -0
- package/dist/schemas/memory-bootstrap-receipt.schema.json +354 -0
- package/dist/schemas/memory-composition-work-result.schema.json +38 -0
- package/dist/schemas/memory-repair-work-result.schema.json +49 -0
- package/dist/schemas/memory-review-correction-plan.schema.json +30 -0
- package/dist/schemas/memory-review-work-result.schema.json +43 -0
- package/dist/schemas/model-catalog.schema.json +153 -0
- package/dist/schemas/profile-settings.schema.json +122 -0
- package/dist/schemas/release-manifest.schema.json +291 -0
- package/dist/schemas/run-engine-binding.schema.json +1 -1
- package/dist/services/check-runner-evidence.js +51 -4
- package/dist/services/cli-operation-classifier.js +8 -47
- package/dist/services/cli-upgrade-allowlist.js +27 -0
- package/dist/services/code-checks.js +387 -34
- package/dist/services/controller-child-capacity.js +13 -0
- package/dist/services/controller-child-observer.js +18 -2
- package/dist/services/controller-execution.js +88 -0
- package/dist/services/controller-fanout.js +71 -12
- package/dist/services/dashboard.js +8 -2
- package/dist/services/descriptor-coordinator-session.js +35 -0
- package/dist/services/descriptor-root-work.js +53 -0
- package/dist/services/descriptor-run-settlement.js +105 -0
- package/dist/services/descriptor-run-start.js +449 -0
- package/dist/services/descriptor-stage-coordinator.js +197 -0
- package/dist/services/descriptor-stage-settlement.js +222 -0
- package/dist/services/descriptor-stage-start.js +139 -0
- package/dist/services/discovery/agy-discovery.js +2 -0
- package/dist/services/discovery/codex-discovery.js +2 -0
- package/dist/services/discovery/droid-discovery.js +2 -0
- package/dist/services/discovery/grok-discovery.js +2 -0
- package/dist/services/discovery/opencode-discovery.js +2 -0
- package/dist/services/discovery/static.js +31 -0
- package/dist/services/discovery/zcode-discovery.js +2 -0
- package/dist/services/engines.js +39 -22
- package/dist/services/eval-snapshots.js +83 -25
- package/dist/services/execution-bundles.js +263 -0
- package/dist/services/execution-policy.js +20 -16
- package/dist/services/executor-onboarding.js +56 -0
- package/dist/services/external-work-launch.js +93 -15
- package/dist/services/flow-catalog.js +21 -0
- package/dist/services/flow-descriptors.js +82 -0
- package/dist/services/frozen-engine.js +250 -0
- package/dist/services/generic-execution.js +164 -0
- package/dist/services/harness-adapter.js +28 -2
- package/dist/services/harness-discovery.js +29 -0
- package/dist/services/harness-doctor.js +75 -0
- package/dist/services/harness-probe-effects.js +51 -0
- package/dist/services/harness-version-probe.js +100 -0
- package/dist/services/hooks.js +24 -6
- package/dist/services/hybrid-contracts.js +483 -0
- package/dist/services/ids.js +29 -1
- package/dist/services/installation-lock.js +95 -0
- package/dist/services/installed-flow-source.js +183 -0
- package/dist/services/lifecycle-invocations.js +257 -78
- package/dist/services/managed-processes.js +31 -10
- package/dist/services/memory-accepted-composition.js +77 -0
- package/dist/services/memory-baseline-preservation.js +65 -0
- package/dist/services/memory-completed-correction-wave.js +320 -0
- package/dist/services/memory-compose-handler.js +258 -0
- package/dist/services/memory-composition-acceptance.js +199 -0
- package/dist/services/memory-composition-graph.js +134 -0
- package/dist/services/memory-composition-registration.js +146 -0
- package/dist/services/memory-composition-work-custody.js +151 -0
- package/dist/services/memory-composition-work-results.js +96 -0
- package/dist/services/memory-corrected-review-acceptance.js +129 -0
- package/dist/services/memory-corrected-review-batch.js +30 -0
- package/dist/services/memory-corrected-review-findings.js +106 -0
- package/dist/services/memory-corrected-review-inputs.js +210 -0
- package/dist/services/memory-corrected-review-projection.js +78 -0
- package/dist/services/memory-corrected-review-receipt.js +170 -0
- package/dist/services/memory-corrected-review-registration.js +191 -0
- package/dist/services/memory-correction-check-receipt.js +219 -0
- package/dist/services/memory-correction-input-owner.js +6 -0
- package/dist/services/memory-discover-handler.js +104 -0
- package/dist/services/memory-finish-evidence.js +435 -0
- package/dist/services/memory-finish-handler.js +215 -0
- package/dist/services/memory-finish-integration.js +280 -0
- package/dist/services/memory-finish-readiness.js +122 -0
- package/dist/services/memory-git-integration.js +151 -0
- package/dist/services/memory-historical-corrected-review.js +206 -0
- package/dist/services/memory-historical-original-review.js +130 -0
- package/dist/services/memory-historical-repair-registration.js +99 -0
- package/dist/services/memory-historical-second-review.js +128 -0
- package/dist/services/memory-init-bootstrap-readiness.js +210 -0
- package/dist/services/memory-init-contexts.js +89 -0
- package/dist/services/memory-init-controller-start.js +84 -0
- package/dist/services/memory-init-engine-release.js +223 -0
- package/dist/services/memory-init-git-policy.js +136 -0
- package/dist/services/memory-init-lint-capability.js +185 -0
- package/dist/services/memory-init-native-capacity.js +296 -0
- package/dist/services/memory-init-preflight.js +330 -0
- package/dist/services/memory-init-project-bootstrap.js +599 -0
- package/dist/services/memory-init-request-candidate.js +157 -0
- package/dist/services/memory-init-request-input.js +66 -0
- package/dist/services/memory-init-resource-storage.js +125 -0
- package/dist/services/memory-init-run-allocation.js +81 -0
- package/dist/services/memory-init-run-bundle.js +98 -0
- package/dist/services/memory-init-start.js +181 -0
- package/dist/services/memory-init-startup-custody.js +80 -0
- package/dist/services/memory-init-startup-input.js +240 -0
- package/dist/services/memory-init-storage.js +81 -0
- package/dist/services/memory-init-stores.js +359 -0
- package/dist/services/memory-init-worktrunk-capability.js +178 -0
- package/dist/services/memory-layer-publication.js +611 -0
- package/dist/services/memory-layer.js +156 -0
- package/dist/services/memory-lint-config.js +85 -0
- package/dist/services/memory-native-review-copy.js +165 -0
- package/dist/services/memory-normal-flow-entry.js +75 -0
- package/dist/services/memory-ordinary-defaults.js +112 -0
- package/dist/services/memory-preflight-bootstrap-custody.js +115 -0
- package/dist/services/memory-preflight-handler.js +259 -0
- package/dist/services/memory-repair-current-view.js +299 -0
- package/dist/services/memory-repair-document-delta.js +451 -0
- package/dist/services/memory-repair-history.js +491 -0
- package/dist/services/memory-repair-physical-binding.js +312 -0
- package/dist/services/memory-repair-publication-files.js +152 -0
- package/dist/services/memory-repair-publication-intent.js +27 -0
- package/dist/services/memory-repair-publication-plan.js +160 -0
- package/dist/services/memory-repair-publication-source.js +225 -0
- package/dist/services/memory-repair-publication.js +794 -0
- package/dist/services/memory-repair-registration.js +239 -0
- package/dist/services/memory-repair-work-baseline.js +413 -0
- package/dist/services/memory-repair-work-completion.js +97 -0
- package/dist/services/memory-repair-work-custody.js +73 -0
- package/dist/services/memory-repair-work-results.js +89 -0
- package/dist/services/memory-repair-workspace.js +252 -0
- package/dist/services/memory-repair-writer-copy.js +735 -0
- package/dist/services/memory-result-binding.js +173 -0
- package/dist/services/memory-retained-review-authority.js +129 -0
- package/dist/services/memory-retained-review-results.js +70 -0
- package/dist/services/memory-retained-second-baseline.js +102 -0
- package/dist/services/memory-retained-second-repairs.js +156 -0
- package/dist/services/memory-review-acceptance.js +214 -0
- package/dist/services/memory-review-correction-plan.js +171 -0
- package/dist/services/memory-review-handler.js +585 -0
- package/dist/services/memory-review-inputs.js +200 -0
- package/dist/services/memory-review-receipt.js +180 -0
- package/dist/services/memory-review-registration.js +108 -0
- package/dist/services/memory-review-work-custody.js +124 -0
- package/dist/services/memory-review-work-input.js +72 -0
- package/dist/services/memory-review-work-results.js +73 -0
- package/dist/services/memory-second-corrected-review-inputs.js +165 -0
- package/dist/services/memory-second-correction-plan.js +76 -0
- package/dist/services/memory-second-repair-baseline.js +91 -0
- package/dist/services/memory-second-repair-launch.js +614 -0
- package/dist/services/memory-second-repair-registration.js +88 -0
- package/dist/services/memory-second-repair-source.js +187 -0
- package/dist/services/memory-second-review-acceptance.js +115 -0
- package/dist/services/memory-source-observations.js +117 -0
- package/dist/services/memory-stage-dispatch.js +28 -0
- package/dist/services/memory-stage-results.js +199 -0
- package/dist/services/memory-verification-acceptance.js +135 -0
- package/dist/services/memory-verification-checks.js +252 -0
- package/dist/services/memory-verification-receipt.js +159 -0
- package/dist/services/memory-verify-handler.js +135 -0
- package/dist/services/memory-work-result-preparation.js +63 -0
- package/dist/services/merge-server.js +2 -4
- package/dist/services/model-catalog.js +109 -0
- package/dist/services/model-discovery.js +46 -0
- package/dist/services/operator-profiles.js +236 -0
- package/dist/services/projects.js +17 -2
- package/dist/services/{codex-capacity.js → provider-capacity.js} +34 -16
- package/dist/services/recovery-snapshot-database.js +417 -2
- package/dist/services/release-archive-producer.js +52 -0
- package/dist/services/release-archive.js +143 -0
- package/dist/services/release-cache.js +142 -0
- package/dist/services/release-compatibility.js +18 -0
- package/dist/services/release-download.js +67 -0
- package/dist/services/release-envelope.js +104 -0
- package/dist/services/release-manifest.js +45 -0
- package/dist/services/release-payload.js +114 -0
- package/dist/services/release-producer.js +63 -0
- package/dist/services/release-provenance.js +160 -0
- package/dist/services/release-registry.js +66 -0
- package/dist/services/release-source.js +194 -0
- package/dist/services/repair-intents.js +1 -1
- package/dist/services/review-copy.js +10 -2
- package/dist/services/run-control.js +80 -9
- package/dist/services/run-controller-adapter.js +1 -1
- package/dist/services/run-controller-capture.js +10 -3
- package/dist/services/run-controller-process.js +21 -0
- package/dist/services/run-controller-recovery.js +5 -5
- package/dist/services/run-controller-state.js +1 -1
- package/dist/services/run-controller.js +158 -64
- package/dist/services/run-recovery-guard.js +5 -3
- package/dist/services/run-recovery-runtime.js +3 -3
- package/dist/services/run-recovery.js +145 -5
- package/dist/services/run-start-custody.js +177 -0
- package/dist/services/runs.js +413 -30
- package/dist/services/setup.js +377 -0
- package/dist/services/stage-context.js +9 -1
- package/dist/services/stage-handlers.js +74 -0
- package/dist/services/stage-report-renderer.js +63 -0
- package/dist/services/stage-work-graph.js +17 -0
- package/dist/services/vnext-code-review.js +1 -1
- package/dist/services/vnext-code.js +6 -6
- package/dist/services/vnext-execution-profile.js +2 -2
- package/dist/services/vnext-fanout.js +73 -11
- package/dist/services/vnext-merge.js +8 -3
- package/dist/services/vnext-plan-review.js +7 -5
- package/dist/services/vnext-plan.js +3 -3
- package/dist/services/vnext-protocolize.js +20 -6
- package/dist/services/vnext-workspace-policy.js +7 -2
- package/dist/services/work-registry.js +616 -71
- package/dist/services/workspace-files.js +1 -1
- package/dist/storage/database.js +84 -6
- package/dist/storage/writer-contract.js +11 -7
- package/package.json +13 -3
- package/tools/create-canon-release.mjs +33 -0
- package/tools/produce-hybrid-release.mjs +41 -0
- package/dist/harness-runtime/lib/codex-capacity-policy.d.mts +0 -10
- package/dist/harness-runtime/lib/codex-capacity-policy.mjs +0 -102
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { AppError, errorRecord } from "../shared/errors.js";
|
|
5
|
+
import { publishImmutableInput } from "../storage/immutable-input.js";
|
|
6
|
+
import { assertImportAdmission } from "../storage/import-guard.js";
|
|
7
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
8
|
+
import { readBoundedReleaseFile } from "./release-source.js";
|
|
9
|
+
import { validateMemoryInitObjective, validateMemoryInitStartupScope } from "./memory-init-startup-custody.js";
|
|
10
|
+
import { prepareMemoryInitRequestInput } from "./memory-init-request-input.js";
|
|
11
|
+
const preparedCandidates = new WeakMap();
|
|
12
|
+
/** Only a genuinely retained candidate can supply fresh startup input. Copied
|
|
13
|
+
* objects and caller-authored snapshots never acquire candidate authority. */
|
|
14
|
+
export function captureMemoryInitRequestCandidate(candidate, home, scope, objective) {
|
|
15
|
+
const capture = candidate && typeof candidate === "object" ? preparedCandidates.get(candidate) : undefined;
|
|
16
|
+
if (!capture)
|
|
17
|
+
refuse("unprepared_request_candidate");
|
|
18
|
+
return capture(home, scope, objective);
|
|
19
|
+
}
|
|
20
|
+
const sha = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
21
|
+
function refuse(reason) {
|
|
22
|
+
throw new AppError("memory_init_request_candidate_invalid", "Init request candidate is unavailable or changed", 1, { reason, effect: "no_effect", next_action: "reconcile_exact_request" });
|
|
23
|
+
}
|
|
24
|
+
function exists(file) {
|
|
25
|
+
try {
|
|
26
|
+
fs.lstatSync(file);
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (error.code === "ENOENT")
|
|
31
|
+
return false;
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function directory(file) {
|
|
36
|
+
const stat = fs.lstatSync(file);
|
|
37
|
+
if (!stat.isDirectory() || fs.realpathSync(file) !== file)
|
|
38
|
+
refuse("linked_candidate_directory");
|
|
39
|
+
return { dev: stat.dev, ino: stat.ino };
|
|
40
|
+
}
|
|
41
|
+
function syncDirectory(file) {
|
|
42
|
+
const fd = fs.openSync(file, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0));
|
|
43
|
+
try {
|
|
44
|
+
fs.fsyncSync(fd);
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
fs.closeSync(fd);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** Read-only preparation and exact byte capture. A candidate is not startup,
|
|
51
|
+
* storage, engine or dispatch authority. Retain only after prerequisite checks;
|
|
52
|
+
* no missing candidate component is reconstructed from ambient selections. */
|
|
53
|
+
export function prepareMemoryInitRequestCandidate(home, scope, selection, intake) {
|
|
54
|
+
validateMemoryInitStartupScope(scope);
|
|
55
|
+
scope = structuredClone(scope);
|
|
56
|
+
const selectionBytes = Buffer.from(selection), intakeBytes = Buffer.from(intake);
|
|
57
|
+
if (!selectionBytes.length || selectionBytes.length > 256 * 1024 || !intakeBytes.length || intakeBytes.length > 64 * 1024)
|
|
58
|
+
refuse("unbounded_request");
|
|
59
|
+
let objective;
|
|
60
|
+
try {
|
|
61
|
+
objective = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(intakeBytes);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return refuse("invalid_intake_utf8");
|
|
65
|
+
}
|
|
66
|
+
validateMemoryInitObjective(objective);
|
|
67
|
+
const homeOwner = directory(home);
|
|
68
|
+
if ([scope.projectRoot, scope.workspaceRoot].some(root => {
|
|
69
|
+
const relative = path.relative(root, home);
|
|
70
|
+
return relative === "" || !path.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path.sep}`);
|
|
71
|
+
}))
|
|
72
|
+
refuse("candidate_home_inside_project");
|
|
73
|
+
assertImportAdmission(home);
|
|
74
|
+
const namespace = path.join(home, "memory-init-request-candidates"), key = hybridDigest(scope);
|
|
75
|
+
const root = path.join(namespace, key), marker = path.join(namespace, `${key}.json`);
|
|
76
|
+
const inputFile = path.join(root, "intake.txt"), selectionFile = path.join(root, "selection.json");
|
|
77
|
+
const facts = { schema_id: "dd-flow/memory-init-request-candidate@1", home, home_owner: homeOwner, scope,
|
|
78
|
+
selection_sha256: sha(selectionBytes), intake_sha256: sha(intakeBytes) };
|
|
79
|
+
const verify = () => {
|
|
80
|
+
if (hybridDigest(directory(home)) !== hybridDigest(homeOwner))
|
|
81
|
+
refuse("candidate_home_changed");
|
|
82
|
+
directory(namespace);
|
|
83
|
+
if (!exists(root) || !exists(marker))
|
|
84
|
+
refuse("candidate_custody_missing");
|
|
85
|
+
const expected = { ...facts, owner: directory(root) };
|
|
86
|
+
const bytes = readBoundedReleaseFile(marker, 16 * 1024);
|
|
87
|
+
if (!bytes.equals(Buffer.from(JSON.stringify(expected))))
|
|
88
|
+
refuse("candidate_request_changed");
|
|
89
|
+
if (!exists(inputFile) || !exists(selectionFile))
|
|
90
|
+
refuse("candidate_input_missing");
|
|
91
|
+
if (!readBoundedReleaseFile(inputFile, 64 * 1024).equals(intakeBytes)
|
|
92
|
+
|| !readBoundedReleaseFile(selectionFile, 256 * 1024).equals(selectionBytes))
|
|
93
|
+
refuse("candidate_input_changed");
|
|
94
|
+
};
|
|
95
|
+
if (exists(namespace))
|
|
96
|
+
directory(namespace);
|
|
97
|
+
const found = exists(root) || exists(marker);
|
|
98
|
+
if (found)
|
|
99
|
+
verify();
|
|
100
|
+
let retained = false;
|
|
101
|
+
const complete = (created) => { retained = true; return { created, file: inputFile, sha256: facts.intake_sha256 }; };
|
|
102
|
+
const candidate = {
|
|
103
|
+
objective,
|
|
104
|
+
retain: () => {
|
|
105
|
+
if (found) {
|
|
106
|
+
verify();
|
|
107
|
+
return complete(false);
|
|
108
|
+
}
|
|
109
|
+
let started = false;
|
|
110
|
+
try {
|
|
111
|
+
if (hybridDigest(directory(home)) !== hybridDigest(homeOwner))
|
|
112
|
+
refuse("candidate_home_changed");
|
|
113
|
+
assertImportAdmission(home);
|
|
114
|
+
if (!exists(namespace)) {
|
|
115
|
+
try {
|
|
116
|
+
fs.mkdirSync(namespace, { mode: 0o700 });
|
|
117
|
+
started = true;
|
|
118
|
+
syncDirectory(home);
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
if (error.code !== "EEXIST")
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
directory(namespace);
|
|
126
|
+
// A concurrent publication is read-only to this caller. Never fill in
|
|
127
|
+
// another process's partial directory or claim its writes as ours.
|
|
128
|
+
if (exists(root) || exists(marker)) {
|
|
129
|
+
verify();
|
|
130
|
+
return complete(false);
|
|
131
|
+
}
|
|
132
|
+
fs.mkdirSync(root, { mode: 0o700 });
|
|
133
|
+
started = true;
|
|
134
|
+
syncDirectory(namespace);
|
|
135
|
+
publishImmutableInput(marker, Buffer.from(JSON.stringify({ ...facts, owner: directory(root) })), "memory_init_request_candidate_conflict");
|
|
136
|
+
publishImmutableInput(inputFile, intakeBytes, "memory_init_request_candidate_conflict");
|
|
137
|
+
publishImmutableInput(selectionFile, selectionBytes, "memory_init_request_candidate_conflict");
|
|
138
|
+
verify();
|
|
139
|
+
return complete(true);
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
if (!started)
|
|
143
|
+
throw error;
|
|
144
|
+
throw new AppError("memory_init_request_candidate_incomplete", "Request candidate requires exact reconciliation", 1, { effect: "partial", request_id: scope.requestId, cause: errorRecord(error), next_action: "reconcile_exact_request" });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
preparedCandidates.set(candidate, (expectedHome, expectedScope, expectedObjective) => {
|
|
149
|
+
if (!retained)
|
|
150
|
+
refuse("request_candidate_not_retained");
|
|
151
|
+
if (home !== expectedHome || hybridDigest(scope) !== hybridDigest(expectedScope) || objective !== expectedObjective)
|
|
152
|
+
refuse("foreign_request_candidate");
|
|
153
|
+
verify();
|
|
154
|
+
return prepareMemoryInitRequestInput(scope, selectionBytes, intakeBytes, objective);
|
|
155
|
+
});
|
|
156
|
+
return Object.freeze(candidate);
|
|
157
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { AppError } from "../shared/errors.js";
|
|
3
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
4
|
+
const sha = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
5
|
+
const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
6
|
+
const exact = (value, keys) => object(value) && Object.keys(value).sort().join() === [...keys].sort().join();
|
|
7
|
+
const digest = (value) => typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
|
|
8
|
+
function refuse(reason) {
|
|
9
|
+
throw new AppError("memory_init_request_input_invalid", "Original init input is unavailable or inconsistent", 1, { reason, effect: "no_effect", next_action: "reconcile_exact_request" });
|
|
10
|
+
}
|
|
11
|
+
function decode(value, expected, limit) {
|
|
12
|
+
if (typeof value !== "string" || !value.length || value.length > 4 * Math.ceil(limit / 3) || !digest(expected))
|
|
13
|
+
refuse("invalid_input_encoding");
|
|
14
|
+
const bytes = Buffer.from(value, "base64");
|
|
15
|
+
if (!bytes.length || bytes.length > limit || bytes.toString("base64") !== value || sha(bytes) !== expected)
|
|
16
|
+
refuse("input_bytes_changed");
|
|
17
|
+
return bytes;
|
|
18
|
+
}
|
|
19
|
+
function text(bytes) {
|
|
20
|
+
try {
|
|
21
|
+
return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return refuse("invalid_input_utf8");
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** Data-only decoding for SQL custody/recovery. No Home, files, engine registry,
|
|
28
|
+
* installed canon, current profiles or provider is consulted. */
|
|
29
|
+
export function readMemoryInitRequestInput(value, scope, objective) {
|
|
30
|
+
if (!exact(value, ["schema_id", "scope", "intake_base64", "intake_sha256", "selection_base64", "selection_sha256"])
|
|
31
|
+
|| !object(value) || value.schema_id !== "dd-flow/memory-init-request-input@1"
|
|
32
|
+
|| hybridDigest(value.scope) !== hybridDigest(scope))
|
|
33
|
+
refuse("invalid_input_scope");
|
|
34
|
+
const intake = decode(value.intake_base64, value.intake_sha256, 64 * 1024);
|
|
35
|
+
const selectionBytes = decode(value.selection_base64, value.selection_sha256, 256 * 1024);
|
|
36
|
+
if (typeof objective !== "string" || !objective.trim() || text(intake) !== objective)
|
|
37
|
+
refuse("input_objective_changed");
|
|
38
|
+
let selection;
|
|
39
|
+
try {
|
|
40
|
+
selection = JSON.parse(text(selectionBytes));
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (error instanceof AppError)
|
|
44
|
+
throw error;
|
|
45
|
+
return refuse("invalid_selection_json");
|
|
46
|
+
}
|
|
47
|
+
if (!exact(selection, ["schema_id", "scope", "execution", "git_policy", "objective", "engine_checksum", "receipts"])
|
|
48
|
+
|| !object(selection) || selection.schema_id !== "dd-flow/memory-init-request-selection@1"
|
|
49
|
+
|| hybridDigest(selection.scope) !== hybridDigest(scope) || selection.objective !== objective
|
|
50
|
+
|| !object(selection.execution) || !exact(selection.git_policy, ["route", "integrationBranch"])
|
|
51
|
+
|| !object(selection.git_policy) || !["integration_branch_direct", "feature_worktree"].includes(String(selection.git_policy.route))
|
|
52
|
+
|| typeof selection.git_policy.integrationBranch !== "string" || !selection.git_policy.integrationBranch
|
|
53
|
+
|| selection.engine_checksum !== null && !digest(selection.engine_checksum)
|
|
54
|
+
|| !Array.isArray(selection.receipts) || selection.receipts.some(entry => !Array.isArray(entry) || entry.length !== 2 || !digest(entry[0]) || !object(entry[1])))
|
|
55
|
+
refuse("invalid_original_selection");
|
|
56
|
+
return { intake, selectionBytes, selection: selection };
|
|
57
|
+
}
|
|
58
|
+
export function prepareMemoryInitRequestInput(scope, selection, intake, objective) {
|
|
59
|
+
if (!selection.byteLength || selection.byteLength > 256 * 1024 || !intake.byteLength || intake.byteLength > 64 * 1024)
|
|
60
|
+
refuse("unbounded_original_input");
|
|
61
|
+
const value = { schema_id: "dd-flow/memory-init-request-input@1", scope: structuredClone(scope),
|
|
62
|
+
intake_base64: Buffer.from(intake).toString("base64"), intake_sha256: sha(intake),
|
|
63
|
+
selection_base64: Buffer.from(selection).toString("base64"), selection_sha256: sha(selection) };
|
|
64
|
+
readMemoryInitRequestInput(value, scope, objective);
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { getResourceDatabase } from "../storage/database.js";
|
|
4
|
+
import { STORE_WRITER_VERSION, storeWriterTriggerSql } from "../storage/writer-contract.js";
|
|
5
|
+
import { withStoreCleanup } from "../storage/native-cleanup.js";
|
|
6
|
+
import { AppError } from "../shared/errors.js";
|
|
7
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
8
|
+
import { resourceHome } from "./managed-processes.js";
|
|
9
|
+
const footprint = {
|
|
10
|
+
managed_processes: ["id", "kind", "pid", "pid_started_at", "owner_id", "lease_token", "lease_expires_at", "project_id", "run_id", "work_id", "check_id", "operation_id", "stdout_path", "stderr_path", "state", "started_at", "updated_at", "finished_at", "termination_reason", "metadata_json"],
|
|
11
|
+
managed_resources: ["resource_kind", "resource_key", "owner_id", "lease_token", "lease_expires_at", "process_id", "metadata_json", "created_at", "updated_at"]
|
|
12
|
+
};
|
|
13
|
+
const keys = { managed_processes: ["id"], managed_resources: ["resource_kind", "resource_key"] };
|
|
14
|
+
const indexes = {
|
|
15
|
+
idx_managed_processes_lease: { table: "managed_processes", columns: ["state", "lease_expires_at", "updated_at"] },
|
|
16
|
+
idx_managed_resources_lease: { table: "managed_resources", columns: ["lease_expires_at", "updated_at"] }
|
|
17
|
+
};
|
|
18
|
+
const normalize = (sql) => sql.replace(/\s+/g, " ").trim().replace(/;$/, "");
|
|
19
|
+
function refuse(reason) {
|
|
20
|
+
throw new AppError("memory_init_resource_storage_incompatible", "Memory init cannot admit the selected shared process registry without preparation", 1, { reason, effect: "no_effect", next_action: "prepare_selected_resource_store_then_retry" });
|
|
21
|
+
}
|
|
22
|
+
function inspect(db, home, owner) {
|
|
23
|
+
if (db.writable || db.path !== path.join(home, "runtime.sqlite"))
|
|
24
|
+
refuse("foreign_or_writable_reader");
|
|
25
|
+
const contract = db.all("SELECT id, version FROM dd_flow_writer_contract");
|
|
26
|
+
if (contract.length !== 1 || contract[0]?.id !== 1 || contract[0].version !== STORE_WRITER_VERSION)
|
|
27
|
+
refuse("writer_contract_mismatch");
|
|
28
|
+
const schema = {};
|
|
29
|
+
for (const [table, required] of Object.entries(footprint)) {
|
|
30
|
+
const columns = db.all(`PRAGMA table_info("${table}")`);
|
|
31
|
+
if (required.some(name => !columns.some(column => column.name === name)))
|
|
32
|
+
refuse(`missing_operation_column:${table}`);
|
|
33
|
+
if (required.some(name => columns.find(column => column.name === name)?.type.toUpperCase() !== (table === "managed_processes" && name === "pid" ? "INTEGER" : "TEXT")))
|
|
34
|
+
refuse(`operation_column_type_mismatch:${table}`);
|
|
35
|
+
if (JSON.stringify(columns.filter(column => column.pk > 0).sort((a, b) => a.pk - b.pk).map(column => column.name)) !== JSON.stringify(keys[table]))
|
|
36
|
+
refuse(`operation_primary_key_mismatch:${table}`);
|
|
37
|
+
schema[table] = columns.map(({ name, type, notnull, pk }) => ({ name, type, notnull, pk }));
|
|
38
|
+
}
|
|
39
|
+
for (const table of [...Object.keys(footprint), "dd_flow_writer_contract"]) {
|
|
40
|
+
const fences = [];
|
|
41
|
+
for (const action of ["INSERT", "UPDATE", "DELETE"]) {
|
|
42
|
+
const expected = storeWriterTriggerSql(table, action);
|
|
43
|
+
const actual = db.get("SELECT sql FROM sqlite_schema WHERE type = 'trigger' AND name = ? AND tbl_name = ?", [expected.name, table]);
|
|
44
|
+
if (!actual || normalize(actual.sql) !== normalize(expected.sql))
|
|
45
|
+
refuse(`writer_fence_mismatch:${table}`);
|
|
46
|
+
fences.push(normalize(actual.sql));
|
|
47
|
+
}
|
|
48
|
+
schema[`${table}:fences`] = fences;
|
|
49
|
+
}
|
|
50
|
+
for (const [name, expected] of Object.entries(indexes)) {
|
|
51
|
+
const entry = db.all(`PRAGMA index_list("${expected.table}")`).find(index => index.name === name);
|
|
52
|
+
if (!entry || entry.unique !== 0 || entry.partial !== 0
|
|
53
|
+
|| JSON.stringify(db.all(`PRAGMA index_info("${name}")`).map(column => column.name)) !== JSON.stringify(expected.columns))
|
|
54
|
+
refuse(`operation_index_mismatch:${name}`);
|
|
55
|
+
schema[name] = { table: expected.table, columns: expected.columns };
|
|
56
|
+
}
|
|
57
|
+
return { schema_id: "dd-flow/memory-init-resource-storage@1", operation: "memory-init",
|
|
58
|
+
store_kind: "resource", store_path: db.path, store_dev: owner.dev, store_ino: owner.ino, executing_writer_version: STORE_WRITER_VERSION,
|
|
59
|
+
schema_sha256: hybridDigest(schema), route: "verified-no-op", outcome: "no-op" };
|
|
60
|
+
}
|
|
61
|
+
/** Retained proof shape, not current physical/storage admission. */
|
|
62
|
+
export function isMemoryInitResourceStorageFacts(value) {
|
|
63
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
64
|
+
return false;
|
|
65
|
+
const facts = value;
|
|
66
|
+
return Object.keys(facts).length === 10 && facts.schema_id === "dd-flow/memory-init-resource-storage@1"
|
|
67
|
+
&& facts.operation === "memory-init" && facts.store_kind === "resource"
|
|
68
|
+
&& typeof facts.store_path === "string" && path.isAbsolute(facts.store_path)
|
|
69
|
+
&& ["store_dev", "store_ino"].every(key => Number.isSafeInteger(facts[key]) && Number(facts[key]) >= 0)
|
|
70
|
+
&& facts.executing_writer_version === 2 && typeof facts.schema_sha256 === "string" && /^[a-f0-9]{64}$/.test(facts.schema_sha256)
|
|
71
|
+
&& facts.route === "verified-no-op" && facts.outcome === "no-op";
|
|
72
|
+
}
|
|
73
|
+
/** Readonly pre-open admission for the executing CLI's selected process registry.
|
|
74
|
+
* Never initializes/migrates an absent or incompatible store, and never reuses
|
|
75
|
+
* an ambient cached writer. Does not attest provider, frozen-engine support or
|
|
76
|
+
* draining/settlement of live processes.
|
|
77
|
+
*/
|
|
78
|
+
export class PreparedMemoryInitResourceStorage {
|
|
79
|
+
sha256;
|
|
80
|
+
#home;
|
|
81
|
+
#facts;
|
|
82
|
+
constructor(home, facts) {
|
|
83
|
+
this.#home = home;
|
|
84
|
+
this.#facts = facts;
|
|
85
|
+
this.sha256 = hybridDigest(facts);
|
|
86
|
+
Object.freeze(this);
|
|
87
|
+
}
|
|
88
|
+
static prepare(context) {
|
|
89
|
+
const home = resourceHome(context);
|
|
90
|
+
if (!path.isAbsolute(home) || path.resolve(home) !== home)
|
|
91
|
+
refuse("resource_home_not_absolute_canonical");
|
|
92
|
+
const file = path.join(home, "runtime.sqlite");
|
|
93
|
+
try {
|
|
94
|
+
if (fs.realpathSync(home) !== home || !fs.lstatSync(home).isDirectory())
|
|
95
|
+
refuse("resource_home_linked_or_unavailable");
|
|
96
|
+
const owner = fs.lstatSync(file);
|
|
97
|
+
if (!owner.isFile() || owner.nlink !== 1)
|
|
98
|
+
refuse("resource_store_linked_or_unavailable");
|
|
99
|
+
const reader = getResourceDatabase(home, "read_isolated", context.databaseDeadlineAt);
|
|
100
|
+
return withStoreCleanup(() => {
|
|
101
|
+
const facts = reader.readSnapshot(() => {
|
|
102
|
+
const facts = inspect(reader, home, owner), integrity = reader.all("PRAGMA quick_check");
|
|
103
|
+
if (integrity.length !== 1 || integrity[0]?.quick_check !== "ok" || reader.all("PRAGMA foreign_key_check").length)
|
|
104
|
+
refuse("storage_integrity_failed");
|
|
105
|
+
return facts;
|
|
106
|
+
});
|
|
107
|
+
const current = fs.lstatSync(file);
|
|
108
|
+
if (!current.isFile() || current.nlink !== 1 || current.dev !== owner.dev || current.ino !== owner.ino)
|
|
109
|
+
refuse("resource_store_owner_changed");
|
|
110
|
+
return new PreparedMemoryInitResourceStorage(home, facts);
|
|
111
|
+
}, () => reader.close?.(), () => ({ store_kind: "resource", store_path: file }));
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
if (error instanceof AppError)
|
|
115
|
+
throw error;
|
|
116
|
+
return refuse("resource_storage_observation_failed");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
facts() { return { ...this.#facts }; }
|
|
120
|
+
revalidate(context) {
|
|
121
|
+
const selected = PreparedMemoryInitResourceStorage.prepare(context);
|
|
122
|
+
if (selected.#home !== this.#home || selected.sha256 !== this.sha256)
|
|
123
|
+
refuse("resource_storage_changed_after_preparation");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { AppError } from "../shared/errors.js";
|
|
3
|
+
import { parseFullEntityId } from "../domain/entity-ids.js";
|
|
4
|
+
import { readMemoryInitBootstrapReadiness } from "./memory-init-bootstrap-readiness.js";
|
|
5
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
6
|
+
import { nextRunId } from "./ids.js";
|
|
7
|
+
import { appendAudit } from "./audit.js";
|
|
8
|
+
const event = "memory.init_run_allocation", intentEvent = "memory.init_run_allocation_intent";
|
|
9
|
+
function refuse(reason) {
|
|
10
|
+
throw new AppError("memory_init_run_allocation_invalid", "Memory init RUN allocation requires exact retained bootstrap request custody", 1, { reason, effect: "no_effect", next_action: "reconcile_exact_request" });
|
|
11
|
+
}
|
|
12
|
+
function selected(context, scope) {
|
|
13
|
+
if (!scope || Object.keys(scope).sort().join() !== "projectRoot,requestId,workspaceRoot"
|
|
14
|
+
|| typeof scope.requestId !== "string" || !scope.requestId.trim() || scope.requestId.length > 256
|
|
15
|
+
|| typeof scope.projectRoot !== "string" || !path.isAbsolute(scope.projectRoot)
|
|
16
|
+
|| typeof scope.workspaceRoot !== "string" || !path.isAbsolute(scope.workspaceRoot))
|
|
17
|
+
refuse("invalid_request_scope");
|
|
18
|
+
const ready = readMemoryInitBootstrapReadiness(context, scope);
|
|
19
|
+
if (!ready)
|
|
20
|
+
refuse("registered_bootstrap_readiness_required");
|
|
21
|
+
return { projectId: ready.projectId, sha256: hybridDigest(ready.authority) };
|
|
22
|
+
}
|
|
23
|
+
function retained(context, scope, selection) {
|
|
24
|
+
const rows = (type) => context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type=? AND json_extract(payload_json,'$.scope.requestId')=? LIMIT 2", [selection.projectId, type, scope.requestId]);
|
|
25
|
+
const allocations = rows(event), intents = rows(intentEvent);
|
|
26
|
+
if (!allocations.length && !intents.length)
|
|
27
|
+
return null;
|
|
28
|
+
if (allocations.length !== 1 || intents.length !== 1)
|
|
29
|
+
refuse("allocation_pair_missing_or_ambiguous");
|
|
30
|
+
try {
|
|
31
|
+
if (Buffer.byteLength(allocations[0].payload_json) > 16 * 1024 || Buffer.byteLength(intents[0].payload_json) > 16 * 1024)
|
|
32
|
+
refuse("unbounded_allocation");
|
|
33
|
+
const allocation = JSON.parse(allocations[0].payload_json);
|
|
34
|
+
const intent = JSON.parse(intents[0].payload_json);
|
|
35
|
+
if (typeof allocation?.run_id !== "string")
|
|
36
|
+
refuse("invalid_allocation_identity");
|
|
37
|
+
const parsed = parseFullEntityId(allocation.run_id), sequence = Number(parsed.shortId.slice(4));
|
|
38
|
+
if (parsed.type !== "RUN" || !Number.isSafeInteger(sequence) || sequence < 1)
|
|
39
|
+
refuse("invalid_allocation_identity");
|
|
40
|
+
if (Object.keys(allocation).sort().join() !== "bootstrap_authority_sha256,project_id,run_id,schema_id,scope"
|
|
41
|
+
|| allocation.schema_id !== "dd-flow/memory-init-run-allocation@1" || allocation.project_id !== selection.projectId
|
|
42
|
+
|| hybridDigest(allocation.scope) !== hybridDigest(scope) || allocation.bootstrap_authority_sha256 !== selection.sha256
|
|
43
|
+
|| hybridDigest(intent) !== hybridDigest({ schema_id: "dd-flow/memory-init-run-allocation-intent@1", scope, run_id: allocation.run_id, allocation_sha256: hybridDigest(allocation) }))
|
|
44
|
+
refuse("allocation_custody_changed");
|
|
45
|
+
return Object.freeze(structuredClone(allocation));
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
if (error instanceof AppError && error.code === "memory_init_run_allocation_invalid")
|
|
49
|
+
throw error;
|
|
50
|
+
return refuse("invalid_allocation_bytes");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Read only. Neither metadata nor an allocated id authorizes a Session,
|
|
54
|
+
* bundle, RUN row or project mutation. */
|
|
55
|
+
export function readMemoryInitRunAllocation(context, scope) {
|
|
56
|
+
return context.db.readSnapshot(() => retained(context, scope, selected(context, scope)));
|
|
57
|
+
}
|
|
58
|
+
/** SQL-only atomic reservation after real bootstrap readiness. The canonical
|
|
59
|
+
* request retains one RUN identity before bundle or RUN filesystem effects. */
|
|
60
|
+
export function reserveMemoryInitRunAllocation(context, scope) {
|
|
61
|
+
const prepared = context.db.readSnapshot(() => selected(context, scope));
|
|
62
|
+
return context.db.writeTransaction(() => {
|
|
63
|
+
if (context.db.isOwnedWriteTransaction?.() !== true)
|
|
64
|
+
refuse("owned_allocation_writer_required");
|
|
65
|
+
const current = selected(context, scope);
|
|
66
|
+
if (hybridDigest(current) !== hybridDigest(prepared))
|
|
67
|
+
refuse("bootstrap_changed_before_allocation");
|
|
68
|
+
const prior = retained(context, scope, current);
|
|
69
|
+
if (prior)
|
|
70
|
+
return { allocation: prior, changed: false };
|
|
71
|
+
const allocation = { schema_id: "dd-flow/memory-init-run-allocation@1", scope: structuredClone(scope),
|
|
72
|
+
project_id: current.projectId, run_id: nextRunId(context, current.projectId, "mb-init"), bootstrap_authority_sha256: current.sha256 };
|
|
73
|
+
appendAudit(context, { projectId: current.projectId, eventType: intentEvent,
|
|
74
|
+
payload: { schema_id: "dd-flow/memory-init-run-allocation-intent@1", scope, run_id: allocation.run_id, allocation_sha256: hybridDigest(allocation) } });
|
|
75
|
+
appendAudit(context, { projectId: current.projectId, eventType: event, payload: allocation });
|
|
76
|
+
const accepted = retained(context, scope, current);
|
|
77
|
+
if (!accepted || hybridDigest(accepted) !== hybridDigest(allocation))
|
|
78
|
+
refuse("allocation_readback_failed");
|
|
79
|
+
return { allocation: accepted, changed: true };
|
|
80
|
+
}, "memoryInit.reserveRun");
|
|
81
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { AppError } from "../shared/errors.js";
|
|
3
|
+
import { PreparedInstalledFlowSource } from "./installed-flow-source.js";
|
|
4
|
+
import { FrozenGenericExecution } from "./generic-execution.js";
|
|
5
|
+
import { AdmittedFrozenEngine } from "./frozen-engine.js";
|
|
6
|
+
import { RetainedExecutionBundle } from "./execution-bundles.js";
|
|
7
|
+
import { readMemoryInitBootstrapReadiness } from "./memory-init-bootstrap-readiness.js";
|
|
8
|
+
import { readMemoryInitRunAllocation } from "./memory-init-run-allocation.js";
|
|
9
|
+
import { readMemoryInitProjectBootstrap } from "./memory-init-project-bootstrap.js";
|
|
10
|
+
import { bundledHandlerContext } from "./stage-handlers.js";
|
|
11
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
12
|
+
const bytes = (value) => Buffer.from(JSON.stringify(value));
|
|
13
|
+
const hash = (value) => createHash("sha256").update(value).digest("hex");
|
|
14
|
+
function refuse(reason) {
|
|
15
|
+
throw new AppError("memory_init_run_bundle_invalid", "Memory RUN bundle requires the exact installed bootstrap selection", 1, { reason, phase: "prepare", effect: "no_effect", next_action: "reconcile_exact_request" });
|
|
16
|
+
}
|
|
17
|
+
/** Initial bundle construction only, outside the SQL writer. Existing RUNs
|
|
18
|
+
* restore their retained bundle; they must not re-read current operator canon.
|
|
19
|
+
* No reservation, RUN, Session, Work or filesystem publication occurs here.
|
|
20
|
+
*/
|
|
21
|
+
export function prepareMemoryInitRunBundle(context, input) {
|
|
22
|
+
if (!(input.source instanceof PreparedInstalledFlowSource) || !(input.execution instanceof FrozenGenericExecution)
|
|
23
|
+
|| !(input.engine instanceof AdmittedFrozenEngine))
|
|
24
|
+
refuse("unprepared_selection");
|
|
25
|
+
const snapshot = input.execution.snapshot(), scope = { requestId: snapshot.scope.requestId,
|
|
26
|
+
projectRoot: snapshot.scope.projectRoot, workspaceRoot: snapshot.scope.workspace };
|
|
27
|
+
const ready = readMemoryInitBootstrapReadiness(context, scope), allocation = readMemoryInitRunAllocation(context, scope);
|
|
28
|
+
const bootstrap = readMemoryInitProjectBootstrap(context, scope);
|
|
29
|
+
if (!ready || !allocation || !bootstrap || allocation.project_id !== ready.projectId
|
|
30
|
+
|| ready.authority.binding.execution_sha256 !== input.execution.sha256)
|
|
31
|
+
refuse("bootstrap_selection_changed");
|
|
32
|
+
const { bootstrapIdentity, receipts: bootstrapReceipts } = ready.admission;
|
|
33
|
+
if (!bootstrapIdentity || !bootstrapReceipts)
|
|
34
|
+
refuse("bootstrap_admission_missing");
|
|
35
|
+
const receipts = new Map(bootstrapReceipts);
|
|
36
|
+
for (const reference of [snapshot.settings.attached_session_receipt, snapshot.settings.delegation.native_capacity_receipt]) {
|
|
37
|
+
if (reference === null)
|
|
38
|
+
continue;
|
|
39
|
+
const fact = input.receipts?.get(reference.sha256);
|
|
40
|
+
if (!fact)
|
|
41
|
+
refuse("execution_receipt_custody_missing");
|
|
42
|
+
if (receipts.has(reference.sha256) && hybridDigest(receipts.get(reference.sha256)) !== hybridDigest(fact))
|
|
43
|
+
refuse("execution_receipt_collision");
|
|
44
|
+
receipts.set(reference.sha256, structuredClone(fact));
|
|
45
|
+
}
|
|
46
|
+
input.source.revalidate();
|
|
47
|
+
const flow = input.source.resolveRegisteredFlow(), canon = input.source.canonProvenance();
|
|
48
|
+
const release = JSON.parse(Buffer.from(ready.authority.binding.installation_manifest_base64, "base64").toString("utf8"));
|
|
49
|
+
if (input.source.installationSha256() !== ready.authority.binding.installation_manifest_sha256
|
|
50
|
+
|| flow.sha256 !== snapshot.settings.descriptor_sha256 || snapshot.settings.flow_id !== "mb-init"
|
|
51
|
+
|| canon.version !== release.canon.version || canon.commit !== release.canon.commit
|
|
52
|
+
|| canon.archive_sha256 !== release.canon.archive.sha256
|
|
53
|
+
|| !input.engine.manifestBytes().equals(Buffer.from(ready.authority.binding.engine_manifest_base64, "base64")))
|
|
54
|
+
refuse("frozen_source_changed");
|
|
55
|
+
const manifest = input.engine.manifest(), runId = allocation.run_id;
|
|
56
|
+
if (typeof manifest.integrity.checksum !== "string")
|
|
57
|
+
refuse("missing_engine_checksum");
|
|
58
|
+
// The accepted bootstrap timestamp makes reconstruction stable after a lost
|
|
59
|
+
// reply. Current time is not a new selector for this request's engine binding.
|
|
60
|
+
const binding = { schema_id: "dd-flow/run-engine-binding@1", run_id: runId, project_root: scope.projectRoot,
|
|
61
|
+
bound_at: bootstrap.receipt.created_at, source: "run_creation", reason: "Request-bound memory init bootstrap",
|
|
62
|
+
engine: { package_name: manifest.package_name, package_version: manifest.package_version, engine_version: manifest.engine_version,
|
|
63
|
+
integrity_checksum: manifest.integrity.checksum, snapshot_root: manifest.snapshot_root }, probe: { status: "not_required", command: null } };
|
|
64
|
+
const blobs = input.source.blobs();
|
|
65
|
+
const put = (logical, content) => {
|
|
66
|
+
if (blobs.has(logical))
|
|
67
|
+
refuse("bundle_path_collision");
|
|
68
|
+
blobs.set(logical, content);
|
|
69
|
+
return { path: logical, sha256: hash(content) };
|
|
70
|
+
};
|
|
71
|
+
for (const reference of [snapshot.settings.attached_session_receipt, snapshot.settings.delegation.native_capacity_receipt]) {
|
|
72
|
+
if (reference === null)
|
|
73
|
+
continue;
|
|
74
|
+
const content = input.receiptBytes?.get(reference.sha256);
|
|
75
|
+
if (!content || hash(content) !== reference.sha256)
|
|
76
|
+
refuse("execution_receipt_evidence_missing_or_changed");
|
|
77
|
+
put(reference.path, Buffer.from(content));
|
|
78
|
+
}
|
|
79
|
+
const profiles = { schema_id: "dd-flow/profile-settings@1", profiles: snapshot.profiles, aliases: {}, default_profile: null };
|
|
80
|
+
const profile = put("runtime/profiles.json", bytes({ ...profiles, revision: hybridDigest(profiles) }));
|
|
81
|
+
const execution = put("runtime/execution.json", bytes(snapshot.settings));
|
|
82
|
+
const engineBinding = put("runtime/engine-binding.json", bytes(binding));
|
|
83
|
+
const engineManifest = put("runtime/engine-manifest.json", input.engine.manifestBytes());
|
|
84
|
+
for (const evidence of ready.authority.evidence)
|
|
85
|
+
put(evidence.path, Buffer.from(evidence.base64, "base64"));
|
|
86
|
+
const receipt = put("receipts/bootstrap.json", bytes(ready.authority.receipt));
|
|
87
|
+
const provenance = { canon, project_pack: { sha256: ready.authority.binding.pack_sha256, source_commit: canon.commit } };
|
|
88
|
+
const bundle = { schema_id: "dd-flow/execution-bundle@1", run_id: runId, flow_id: "mb-init",
|
|
89
|
+
descriptor: input.source.descriptorReference(), execution_settings: execution, profile_settings: profile,
|
|
90
|
+
engine_binding: engineBinding, engine_manifest: engineManifest, bootstrap_receipts: [receipt], ...provenance,
|
|
91
|
+
inventory: [...blobs].map(([logical, content]) => ({ path: logical, sha256: hash(content) })) };
|
|
92
|
+
const encoded = bytes(bundle);
|
|
93
|
+
return RetainedExecutionBundle.admit(encoded, hash(encoded), blobs, { runId, executionScope: snapshot.scope,
|
|
94
|
+
bundleProjectRoot: scope.projectRoot, descriptorSha256: flow.sha256, engineChecksum: manifest.integrity.checksum,
|
|
95
|
+
engineBindingSha256: engineBinding.sha256, engineManifestSha256: engineManifest.sha256,
|
|
96
|
+
descriptorContext: bundledHandlerContext(new Map()), bundleProvenance: provenance,
|
|
97
|
+
bootstrapIdentity, receipts });
|
|
98
|
+
}
|