@deksden-com/dd-flow-cli 0.9.0-beta.144 → 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 +59 -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 +69 -8
- 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
|
@@ -12,6 +12,31 @@ import { AppError } from "../shared/errors.js";
|
|
|
12
12
|
import { hybridDigest } from "./hybrid-contracts.js";
|
|
13
13
|
import { ObservedMemorySources } from "./memory-source-observations.js";
|
|
14
14
|
import { memorySourceKey } from "./memory-stage-results.js";
|
|
15
|
+
export function isReviewedMemoryGitFile(ref) {
|
|
16
|
+
return !!ref && ref.root === "workspace" && typeof ref.path === "string"
|
|
17
|
+
&& (ref.path.startsWith(".memory-bank/") || ref.path === ".mb-lint.json") && ref.path.length <= 4096 && !/[\\\0]/.test(ref.path)
|
|
18
|
+
&& !ref.path.split("/").some(segment => !segment || segment === "." || segment === "..")
|
|
19
|
+
&& typeof ref.sha256 === "string" && /^[a-f0-9]{64}$/.test(ref.sha256)
|
|
20
|
+
&& (Object.keys(ref).sort().join() === "path,root,sha256"
|
|
21
|
+
|| Object.keys(ref).sort().join() === "git_mode,path,root,sha256" && ["100644", "100755"].includes(ref.git_mode));
|
|
22
|
+
}
|
|
23
|
+
/** A removed file is absent, not a directory or a path through a symlink. */
|
|
24
|
+
export function assertMemoryGitRemovalSources(directory, removals) {
|
|
25
|
+
for (const ref of removals) {
|
|
26
|
+
let current = directory;
|
|
27
|
+
const segments = ref.path.split("/");
|
|
28
|
+
for (const [index, segment] of segments.entries()) {
|
|
29
|
+
current = path.join(current, segment);
|
|
30
|
+
const stat = fs.lstatSync(current, { throwIfNoEntry: false });
|
|
31
|
+
if (!stat)
|
|
32
|
+
break;
|
|
33
|
+
if (index === segments.length - 1)
|
|
34
|
+
refuse("removed_memory_file_present");
|
|
35
|
+
if (!stat.isDirectory())
|
|
36
|
+
refuse("unsafe_removal_parent");
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
15
40
|
function refuse(reason) {
|
|
16
41
|
throw new AppError("memory_git_integration_invalid", "Memory finish lacks observed Git integration", 2, { reason, effect: "no_effect" });
|
|
17
42
|
}
|
|
@@ -39,7 +64,7 @@ export function observeMemoryGitIntegration(input, env) {
|
|
|
39
64
|
|| !path.isAbsolute(input.gitExecutable))
|
|
40
65
|
refuse("explicit_git_selection_required");
|
|
41
66
|
const projectRoot = root(input.projectRoot), workspaceRoot = root(input.workspaceRoot);
|
|
42
|
-
const cleanEnv = Object.fromEntries(Object.entries(env).filter(([key]) => !key.startsWith("GIT_")));
|
|
67
|
+
const cleanEnv = { ...Object.fromEntries(Object.entries(env).filter(([key]) => !key.startsWith("GIT_"))), GIT_LITERAL_PATHSPECS: "1" };
|
|
43
68
|
const git = (directory, args, expectedStatus = 0) => {
|
|
44
69
|
const result = spawnSync(input.gitExecutable, ["-c", "core.fsmonitor=false", "-c", "core.untrackedCache=false",
|
|
45
70
|
"-C", directory, ...args], { env: { ...cleanEnv, GIT_OPTIONAL_LOCKS: "0" }, encoding: "utf8",
|
|
@@ -91,39 +116,82 @@ export function observeMemoryGitIntegration(input, env) {
|
|
|
91
116
|
/** Exact reviewed MB/owned root lint bytes in the physical workspace and integrated Git
|
|
92
117
|
* tree. References must be selected by the accepted review owner; this helper
|
|
93
118
|
* validates physical correspondence only and grants no semantic authority. */
|
|
94
|
-
export function observeReviewedMemoryGitIntegration(input, env, references) {
|
|
119
|
+
export function observeReviewedMemoryGitIntegration(input, env, references, removals = []) {
|
|
95
120
|
if (!Array.isArray(references) || !references.length || references.length > 4096
|
|
96
|
-
|| references.some(ref => !ref
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|| Object.keys(ref).sort().join() !== "path,root,sha256")
|
|
101
|
-
|| new Set(references.map(ref => ref.path)).size !== references.length
|
|
121
|
+
|| references.some(ref => !isReviewedMemoryGitFile(ref))
|
|
122
|
+
|| !Array.isArray(removals) || references.length + removals.length > 4096
|
|
123
|
+
|| removals.some(ref => !isReviewedMemoryGitFile(ref) || !ref.path.startsWith(".memory-bank/") || ref.path === ".memory-bank/index.md")
|
|
124
|
+
|| new Set([...references, ...removals].map(ref => ref.path)).size !== references.length + removals.length
|
|
102
125
|
|| !references.some(ref => ref.path === ".memory-bank/index.md"))
|
|
103
126
|
refuse("invalid_reviewed_memory_inventory");
|
|
104
127
|
const selected = structuredClone([...references]).sort((a, b) => a.path.localeCompare(b.path, "en"));
|
|
128
|
+
const removed = structuredClone([...removals]).sort((a, b) => a.path.localeCompare(b.path, "en"));
|
|
105
129
|
const integration = observeMemoryGitIntegration(input, env);
|
|
106
130
|
const observed = ObservedMemorySources.capture({ workspace: integration.workspace_root }, selected.map(({ root, path }) => ({ root, path })));
|
|
107
131
|
const hashes = observed.hashes();
|
|
108
|
-
const cleanEnv = Object.fromEntries(Object.entries(env).filter(([key]) => !key.startsWith("GIT_")));
|
|
109
|
-
const git = (args, directory = integration.workspace_root) => {
|
|
132
|
+
const cleanEnv = { ...Object.fromEntries(Object.entries(env).filter(([key]) => !key.startsWith("GIT_"))), GIT_LITERAL_PATHSPECS: "1" };
|
|
133
|
+
const git = (args, directory = integration.workspace_root, stdin) => {
|
|
110
134
|
const result = spawnSync(input.gitExecutable, ["-c", "core.fsmonitor=false", "-c", "core.untrackedCache=false",
|
|
111
135
|
"-C", directory, ...args], { env: { ...cleanEnv, GIT_OPTIONAL_LOCKS: "0" },
|
|
112
|
-
timeout: 10000, maxBuffer: 16 * 1024 * 1024, windowsHide: true, shell: false });
|
|
136
|
+
input: stdin, timeout: 10000, maxBuffer: (stdin === undefined ? 16 : 65) * 1024 * 1024, windowsHide: true, shell: false });
|
|
113
137
|
if (result.error || result.signal || result.status !== 0)
|
|
114
138
|
refuse("reviewed_git_blob_unavailable");
|
|
115
139
|
return result.stdout;
|
|
116
140
|
};
|
|
117
141
|
let total = 0;
|
|
142
|
+
const entries = new Map();
|
|
143
|
+
const listing = git(["ls-tree", "-r", "-t", "-z", integration.workspace_head, "--", ".memory-bank", ".mb-lint.json"]).toString("utf8");
|
|
144
|
+
if (listing && !listing.endsWith("\0"))
|
|
145
|
+
refuse("reviewed_regular_git_blob_required");
|
|
146
|
+
for (const entry of listing.split("\0").filter(Boolean)) {
|
|
147
|
+
const match = /^([0-7]{6}) (blob|tree|commit) ([a-f0-9]{40,64})\t([\s\S]+)$/.exec(entry);
|
|
148
|
+
if (!match || entries.has(match[4]))
|
|
149
|
+
refuse("reviewed_regular_git_blob_required");
|
|
150
|
+
entries.set(match[4], { mode: match[1], type: match[2], oid: match[3] });
|
|
151
|
+
}
|
|
152
|
+
for (const directory of new Set([integration.workspace_root, integration.project_root]))
|
|
153
|
+
assertMemoryGitRemovalSources(directory, removed);
|
|
154
|
+
for (const ref of removed) {
|
|
155
|
+
if (entries.has(ref.path))
|
|
156
|
+
refuse("removed_memory_file_still_committed");
|
|
157
|
+
}
|
|
158
|
+
const oids = [...new Set(selected.map(ref => {
|
|
159
|
+
const entry = entries.get(ref.path);
|
|
160
|
+
if (!entry || entry.mode !== (ref.git_mode ?? "100644") || entry.type !== "blob")
|
|
161
|
+
refuse("reviewed_regular_git_blob_required");
|
|
162
|
+
return entry.oid;
|
|
163
|
+
}))];
|
|
164
|
+
const batch = git(["cat-file", "--batch"], integration.workspace_root, `${oids.join("\n")}\n`);
|
|
165
|
+
const contents = new Map();
|
|
166
|
+
let offset = 0;
|
|
167
|
+
for (const oid of oids) {
|
|
168
|
+
const end = batch.indexOf(10, offset);
|
|
169
|
+
if (end < 0)
|
|
170
|
+
refuse("reviewed_git_blob_unavailable");
|
|
171
|
+
const header = /^([a-f0-9]{40,64}) blob (0|[1-9][0-9]*)$/.exec(batch.subarray(offset, end).toString("ascii"));
|
|
172
|
+
const size = Number(header?.[2]);
|
|
173
|
+
if (!header || header[1] !== oid || !Number.isSafeInteger(size) || size > 16 * 1024 * 1024
|
|
174
|
+
|| end + 1 + size >= batch.length || batch[end + 1 + size] !== 10)
|
|
175
|
+
refuse("reviewed_git_blob_unavailable");
|
|
176
|
+
contents.set(oid, batch.subarray(end + 1, end + 1 + size));
|
|
177
|
+
offset = end + size + 2;
|
|
178
|
+
}
|
|
179
|
+
if (offset !== batch.length)
|
|
180
|
+
refuse("reviewed_git_blob_unavailable");
|
|
118
181
|
for (const ref of selected) {
|
|
119
182
|
if (hashes.get(memorySourceKey("workspace", ref.path)) !== ref.sha256)
|
|
120
183
|
refuse("reviewed_workspace_bytes_changed");
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
184
|
+
const expectedMode = ref.git_mode ?? "100644";
|
|
185
|
+
// Git may ignore chmod when core.filemode=false. Bytes and clean status
|
|
186
|
+
// alone do not prove that a preserved executable still is executable.
|
|
187
|
+
if (process.platform !== "win32") {
|
|
188
|
+
for (const directory of new Set([integration.workspace_root, integration.project_root])) {
|
|
189
|
+
const executable = (fs.lstatSync(path.join(directory, ref.path)).mode & 0o111) !== 0;
|
|
190
|
+
if (executable !== (expectedMode === "100755"))
|
|
191
|
+
refuse("reviewed_workspace_mode_changed");
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const content = contents.get(entries.get(ref.path).oid);
|
|
127
195
|
total += content.length;
|
|
128
196
|
if (total > 64 * 1024 * 1024)
|
|
129
197
|
refuse("reviewed_git_bytes_exceed_bound");
|
|
@@ -147,5 +215,6 @@ export function observeReviewedMemoryGitIntegration(input, env, references) {
|
|
|
147
215
|
observed.revalidate();
|
|
148
216
|
if (hybridDigest(observeMemoryGitIntegration(input, env)) !== hybridDigest(integration))
|
|
149
217
|
refuse("integration_changed_during_observation");
|
|
150
|
-
return { ...integration, reviewed_files_sha256: hybridDigest(selected)
|
|
218
|
+
return { ...integration, reviewed_files_sha256: hybridDigest(selected),
|
|
219
|
+
...(removed.length ? { removed_files_sha256: hybridDigest(removed) } : {}) };
|
|
151
220
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertMemorySqlCustody } from "./memory-custody-snapshot.js";
|
|
1
2
|
import { AppError } from "../shared/errors.js";
|
|
2
3
|
import { hybridDigest } from "./hybrid-contracts.js";
|
|
3
4
|
import { PreparedHistoricalOriginalMemoryReview } from "./memory-historical-original-review.js";
|
|
@@ -33,7 +34,7 @@ export function assertHistoricalMemoryReviewSources(histories, paths, runInputs,
|
|
|
33
34
|
for (const history of retainedHistories) {
|
|
34
35
|
if (!prefixes.has(hybridDigest(history.before().entries())))
|
|
35
36
|
refuse("historical_input_prefix_changed");
|
|
36
|
-
baseline = baseline.composeSettledDelta(history.before(), history.after());
|
|
37
|
+
baseline = baseline.composeSettledDelta(history.before(), history.after(), history.result().outcome);
|
|
37
38
|
prefixes.add(hybridDigest(baseline.entries()));
|
|
38
39
|
const before = new Map(history.before().entries().map(entry => [entry.path, entry]));
|
|
39
40
|
for (const entry of history.after().entries())
|
|
@@ -57,6 +58,7 @@ export function assertHistoricalMemoryReviewSources(histories, paths, runInputs,
|
|
|
57
58
|
* input/check hashes must be selected from the independent accepted receipt.
|
|
58
59
|
* Current workspace/copies/policy are never adopted or used for re-admission. */
|
|
59
60
|
export class PreparedHistoricalCorrectedMemoryReview {
|
|
61
|
+
#custodyKey = {};
|
|
60
62
|
static isPrepared(value) {
|
|
61
63
|
return typeof value === "object" && value !== null && #original in value;
|
|
62
64
|
}
|
|
@@ -104,7 +106,7 @@ export class PreparedHistoricalCorrectedMemoryReview {
|
|
|
104
106
|
assertHistoricalMemoryReviewSources(histories, paths, view.run_inputs);
|
|
105
107
|
const repairResults = histories.map(history => ({ work_id: history.workId, result: history.result(), reference: history.reference(), authority_sha256: history.sha256,
|
|
106
108
|
unit_ids: repairBatch.works.find(item => repairReceipt.work_ids[item.key] === history.workId).payload.memory_repair.unit_ids }));
|
|
107
|
-
const compose = readAcceptedStageData(context, { projectId: scope.project_id, runId: scope.run_id, stage: "compose" });
|
|
109
|
+
const compose = readAcceptedStageData(context, { projectId: scope.project_id, runId: scope.run_id, stage: bundle.descriptor.descriptor.id === "mb-upgrade" ? "upgrade" : "compose" });
|
|
108
110
|
const affected = memoryCorrectionAffectedUnits(initial.view.unit_inventory, repairResults, JSON.parse(compose.bytes.toString("utf8")).details.works);
|
|
109
111
|
const assignments = correctedMemoryReviewAssignments({ units: initial.view.unit_inventory, initialAssignments: initial.view.assignments, affected,
|
|
110
112
|
files: view.run_inputs.filter(ref => ref.root === "workspace"), repairs: repairResults });
|
|
@@ -112,6 +114,7 @@ export class PreparedHistoricalCorrectedMemoryReview {
|
|
|
112
114
|
|| view.original_review_sha256 !== original.reviewSha256() || view.base !== initial.view.base
|
|
113
115
|
|| hybridDigest(view.unit_inventory) !== hybridDigest(initial.view.unit_inventory)
|
|
114
116
|
|| hybridDigest(view.instruction) !== hybridDigest(initial.view.instruction)
|
|
117
|
+
|| hybridDigest([view.upgrade_baseline, view.preservation_invariants]) !== hybridDigest([initial.view.upgrade_baseline, initial.view.preservation_invariants])
|
|
115
118
|
|| hybridDigest(view.affected_unit_ids) !== hybridDigest(affected) || hybridDigest(view.assignments) !== hybridDigest(assignments)
|
|
116
119
|
|| hybridDigest(view.repairs) !== hybridDigest(repairResults.map(repair => ({ work_id: repair.work_id, authority_sha256: repair.authority_sha256, result: repair.reference }))))
|
|
117
120
|
refuse("historical_correction_projection_changed");
|
|
@@ -155,7 +158,7 @@ export class PreparedHistoricalCorrectedMemoryReview {
|
|
|
155
158
|
for (const history of this.#histories) {
|
|
156
159
|
if (!prefixes.has(hybridDigest(history.before().entries())))
|
|
157
160
|
refuse("historical_input_prefix_changed");
|
|
158
|
-
baseline = baseline.composeSettledDelta(history.before(), history.after());
|
|
161
|
+
baseline = baseline.composeSettledDelta(history.before(), history.after(), history.result().outcome);
|
|
159
162
|
prefixes.add(hybridDigest(baseline.entries()));
|
|
160
163
|
}
|
|
161
164
|
return baseline;
|
|
@@ -169,7 +172,7 @@ export class PreparedHistoricalCorrectedMemoryReview {
|
|
|
169
172
|
return structuredClone(this.#effective);
|
|
170
173
|
}
|
|
171
174
|
bundle() { return this.#original.bundle(); }
|
|
172
|
-
verificationProfileHash(context) { this.assertCustody(context); return this.#
|
|
175
|
+
verificationProfileHash(context) { this.assertCustody(context); return this.#checks.value(context).profile_sha256; }
|
|
173
176
|
/** Exact live fan-in identity reconstructed from retained settlements.
|
|
174
177
|
* A digest is an identity, not independent plan or Stage acceptance. */
|
|
175
178
|
reviewSha256() {
|
|
@@ -182,17 +185,20 @@ export class PreparedHistoricalCorrectedMemoryReview {
|
|
|
182
185
|
authority_sha256: worker.authority, result: { ...worker.result } })) };
|
|
183
186
|
}
|
|
184
187
|
assertCustody(context) {
|
|
185
|
-
this.#original
|
|
186
|
-
this.#
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
188
|
+
const original = this.#original;
|
|
189
|
+
assertMemorySqlCustody(context, this.#custodyKey, () => {
|
|
190
|
+
original.assertCustody(context);
|
|
191
|
+
this.#repairs.assertCustody(context);
|
|
192
|
+
this.#checks.assertCustody(context);
|
|
193
|
+
if (hybridDigest(readRetainedCorrectedMemoryReviewRegistration(context, this.#registration.scope, this.#registration.input_sha256, this.#registration.check_receipt_sha256)) !== hybridDigest(this.#registration))
|
|
194
|
+
refuse("historical_registration_changed");
|
|
195
|
+
for (const history of this.#histories)
|
|
196
|
+
history.assertCustody(context);
|
|
197
|
+
const harness = runtimeHarness(this.#original.bundle().execution.work("review").worker);
|
|
198
|
+
for (const worker of this.#workers)
|
|
199
|
+
if (readMemoryReviewWorkSettlement(context, this.#registration, worker.workId, harness).authority !== worker.authority)
|
|
200
|
+
refuse("historical_reviewer_settlement_changed");
|
|
201
|
+
});
|
|
196
202
|
}
|
|
197
203
|
revalidate(context) {
|
|
198
204
|
this.#original.revalidate(context);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertMemorySqlCustody } from "./memory-custody-snapshot.js";
|
|
1
2
|
import { AppError } from "../shared/errors.js";
|
|
2
3
|
import { hybridDigest } from "./hybrid-contracts.js";
|
|
3
4
|
import { RetainedExecutionBundle } from "./execution-bundles.js";
|
|
@@ -27,13 +28,14 @@ function selected(context, scope, bundle, mode) {
|
|
|
27
28
|
const phase = { project_id: scope.projectId, run_id: scope.runId, stage: "review", attempt: stage.attempt,
|
|
28
29
|
input_sha256: stage.input_sha256, parent_work_id: root.workId, execution_sha256: bundle.execution.sha256, bundle_sha256: bundle.sha256 };
|
|
29
30
|
const registration = readRegisteredMemoryReview(context, phase, 0), view = registration.view;
|
|
30
|
-
const
|
|
31
|
+
const upgrade = bundle.descriptor.descriptor.id === "mb-upgrade", authoring = upgrade ? "upgrade" : "compose";
|
|
32
|
+
const compose = readAcceptedStageData(context, { ...scope, stage: authoring }), verify = readAcceptedStageData(context, { ...scope, stage: "verify" });
|
|
31
33
|
const review = mode === "closed" ? readAcceptedStageData(context, { ...scope, stage: "review" }) : null;
|
|
32
34
|
const composed = JSON.parse(compose.bytes.toString("utf8"));
|
|
33
|
-
if (compose.data_schema_id !== "dd-flow/mb-init-compose-result@1" || composed.schema_id !== compose.data_schema_id
|
|
34
|
-
|| composed.stage !==
|
|
35
|
+
if (compose.data_schema_id !== (upgrade ? "dd-flow/mb-upgrade-stage-result@1" : "dd-flow/mb-init-compose-result@1") || composed.schema_id !== compose.data_schema_id
|
|
36
|
+
|| composed.stage !== authoring || composed.run_id !== scope.runId || composed.outcome !== "accepted"
|
|
35
37
|
|| composed.input_sha256 !== compose.input_sha256 || hybridDigest(composed.units) !== hybridDigest(view.unit_inventory)
|
|
36
|
-
|| (review !== null && (review.data_schema_id !== "dd-flow/mb-init-review-result@1" || review.input_sha256 !== phase.input_sha256))
|
|
38
|
+
|| (review !== null && (review.data_schema_id !== (upgrade ? "dd-flow/mb-upgrade-stage-result@1" : "dd-flow/mb-init-review-result@1") || review.input_sha256 !== phase.input_sha256))
|
|
37
39
|
|| view.source_policy !== "review-inputs@1" || hybridDigest(view.instruction) !== hybridDigest({
|
|
38
40
|
sha256: bundle.descriptor.stage("review").instruction.sha256, base64: bundle.descriptor.stageBlob("review", "instruction").toString("base64")
|
|
39
41
|
}))
|
|
@@ -47,11 +49,19 @@ function selected(context, scope, bundle, mode) {
|
|
|
47
49
|
const workers = batch.works.map(item => readMemoryReviewWorkSettlement(context, registration, registration.work_ids[item.key], harness));
|
|
48
50
|
return { mode, registration, batch, workers, compose, verify, review, runRoot: run.run_root ?? run.run_dir };
|
|
49
51
|
}
|
|
52
|
+
// Private comparison only: retain every byte without expanding Buffers into
|
|
53
|
+
// numerically keyed JSON objects. SQL and physical custody are still reread.
|
|
54
|
+
function authority(value) {
|
|
55
|
+
const stage = (data) => ({ ...data, bytes: data.bytes.toString("base64") });
|
|
56
|
+
return hybridDigest({ ...value, compose: stage(value.compose), verify: stage(value.verify), review: value.review && stage(value.review),
|
|
57
|
+
workers: value.workers.map(worker => ({ ...worker, bytes: worker.bytes.toString("base64") })) });
|
|
58
|
+
}
|
|
50
59
|
/** Original wave proof after Stage closure, not accepted corrected-review or
|
|
51
60
|
* repair authority. Never reused by fresh Work allocation or current-view
|
|
52
61
|
* admission. Only retained RUN files and scoped SQL are observed; no active
|
|
53
62
|
* coordinator Session or obsolete workspace copy is required. */
|
|
54
63
|
export class PreparedHistoricalOriginalMemoryReview {
|
|
64
|
+
#custodyKey = {};
|
|
55
65
|
static isPrepared(value) {
|
|
56
66
|
return typeof value === "object" && value !== null && #scope in value;
|
|
57
67
|
}
|
|
@@ -71,7 +81,7 @@ export class PreparedHistoricalOriginalMemoryReview {
|
|
|
71
81
|
this.#verification = verification;
|
|
72
82
|
this.#observed = observed;
|
|
73
83
|
this.#results = structuredClone(results);
|
|
74
|
-
this.#authority =
|
|
84
|
+
this.#authority = authority(value);
|
|
75
85
|
Object.freeze(this);
|
|
76
86
|
}
|
|
77
87
|
static prepare(context, input) {
|
|
@@ -84,7 +94,7 @@ export class PreparedHistoricalOriginalMemoryReview {
|
|
|
84
94
|
return this.#prepare(context, input, "open");
|
|
85
95
|
}
|
|
86
96
|
static #prepare(context, input, mode) {
|
|
87
|
-
if (!(input.bundle instanceof RetainedExecutionBundle) || input.bundle.descriptor.descriptor.id
|
|
97
|
+
if (!(input.bundle instanceof RetainedExecutionBundle) || !["mb-init", "mb-upgrade"].includes(input.bundle.descriptor.descriptor.id))
|
|
88
98
|
refuse("registered_memory_bundle_required");
|
|
89
99
|
if (context.db.isOwnedWriteTransaction?.())
|
|
90
100
|
refuse("preparation_outside_writer_required");
|
|
@@ -122,9 +132,12 @@ export class PreparedHistoricalOriginalMemoryReview {
|
|
|
122
132
|
result: { ...worker.result }, unit_ids: [results[index].unit_id] }));
|
|
123
133
|
}
|
|
124
134
|
assertCustody(context) {
|
|
125
|
-
this.#verification
|
|
126
|
-
|
|
127
|
-
|
|
135
|
+
const verification = this.#verification;
|
|
136
|
+
assertMemorySqlCustody(context, this.#custodyKey, () => {
|
|
137
|
+
verification.assertCustody(context);
|
|
138
|
+
if (authority(selected(context, this.#scope, this.#bundle, this.#value.mode)) !== this.#authority)
|
|
139
|
+
refuse("historical_original_custody_changed");
|
|
140
|
+
});
|
|
128
141
|
}
|
|
129
142
|
revalidate(context) { this.#verification.revalidate(context); this.#observed.revalidate(); this.assertCustody(context); }
|
|
130
143
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { assertMemorySqlCustody } from "./memory-custody-snapshot.js";
|
|
1
2
|
import { AppError } from "../shared/errors.js";
|
|
2
3
|
import { hybridDigest } from "./hybrid-contracts.js";
|
|
3
4
|
import { PreparedHistoricalOriginalMemoryReview } from "./memory-historical-original-review.js";
|
|
4
5
|
import { readRetainedMemoryRepairRegistration, assertRetainedMemoryRepairAllocation, memoryRepairBatch, memoryRepairWorkerSchema } from "./memory-repair-registration.js";
|
|
5
|
-
import { validateMemoryReviewCorrectionPlan } from "./memory-review-correction-plan.js";
|
|
6
|
+
import { validateMemoryReviewCorrectionPlan, memoryRepairCanonicalPaths } from "./memory-review-correction-plan.js";
|
|
6
7
|
import { memoryReviewWaveProjection } from "./memory-review-acceptance.js";
|
|
7
8
|
const token = Symbol("historical-memory-repair-registration");
|
|
8
9
|
function refuse(reason) {
|
|
@@ -18,6 +19,7 @@ function selected(context, original) {
|
|
|
18
19
|
const engine = bundle.engine();
|
|
19
20
|
const plan = validateMemoryReviewCorrectionPlan(Buffer.from(receipt.plan_base64, "base64"), engine, {
|
|
20
21
|
runId: registration.run_id, wave: 1, reviewSha256: original.reviewSha256(), units: registration.view.unit_inventory,
|
|
22
|
+
canonicalPaths: memoryRepairCanonicalPaths(context, bundle),
|
|
21
23
|
findings: memoryReviewWaveProjection(results, registration.view.diagnostic_findings).findings
|
|
22
24
|
});
|
|
23
25
|
for (const item of plan.works)
|
|
@@ -34,6 +36,7 @@ function selected(context, original) {
|
|
|
34
36
|
* type: fresh repair baseline/copy/start/allocation APIs do not accept it.
|
|
35
37
|
* Settled-history validation may consume it without requiring live review. */
|
|
36
38
|
export class PreparedHistoricalMemoryRepairRegistration {
|
|
39
|
+
#custodyKey = {};
|
|
37
40
|
static isPrepared(value) {
|
|
38
41
|
return typeof value === "object" && value !== null && #original in value;
|
|
39
42
|
}
|
|
@@ -87,13 +90,16 @@ export class PreparedHistoricalMemoryRepairRegistration {
|
|
|
87
90
|
return rows.map(row => row.work_id);
|
|
88
91
|
}
|
|
89
92
|
assertCustody(context) {
|
|
90
|
-
this.#original
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
const original = this.#original;
|
|
94
|
+
assertMemorySqlCustody(context, this.#custodyKey, () => {
|
|
95
|
+
original.assertCustody(context);
|
|
96
|
+
const receipt = readRetainedMemoryRepairRegistration(context, this.#receipt.scope);
|
|
97
|
+
if (hybridDigest(receipt) !== this.sha256)
|
|
98
|
+
refuse("historical_repair_registration_changed");
|
|
99
|
+
// The immutable plan/batch was admitted by prepare. Custody checks reread
|
|
100
|
+
// scoped SQL allocation, not the same frozen engine and plan again.
|
|
101
|
+
assertRetainedMemoryRepairAllocation(context, receipt, this.#batch);
|
|
102
|
+
});
|
|
97
103
|
}
|
|
98
104
|
revalidate(context) { this.#original.revalidate(context); this.assertCustody(context); }
|
|
99
105
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertMemorySqlCustody } from "./memory-custody-snapshot.js";
|
|
1
2
|
import { AppError } from "../shared/errors.js";
|
|
2
3
|
import { hybridDigest } from "./hybrid-contracts.js";
|
|
3
4
|
import { PreparedHistoricalSecondMemoryCorrectionWave } from "./memory-completed-correction-wave.js";
|
|
@@ -22,6 +23,7 @@ function refuse(reason) {
|
|
|
22
23
|
* come from an independently accepted receipt, not current workspace policy.
|
|
23
24
|
* This owner alone neither accepts a Stage nor admits another correction. */
|
|
24
25
|
export class PreparedHistoricalSecondMemoryReview {
|
|
26
|
+
#custodyKey = {};
|
|
25
27
|
static isPrepared(value) {
|
|
26
28
|
return typeof value === "object" && value !== null && #wave in value;
|
|
27
29
|
}
|
|
@@ -64,6 +66,7 @@ export class PreparedHistoricalSecondMemoryReview {
|
|
|
64
66
|
|| view.first_check_receipt_sha256 !== receipt.first_check_receipt_sha256
|
|
65
67
|
|| view.first_review_registration_sha256 !== receipt.first_review_registration_sha256
|
|
66
68
|
|| hybridDigest(view.instruction) !== hybridDigest(first.instruction)
|
|
69
|
+
|| hybridDigest([view.upgrade_baseline, view.preservation_invariants]) !== hybridDigest([first.upgrade_baseline, first.preservation_invariants])
|
|
67
70
|
|| hybridDigest(view.unit_inventory) !== hybridDigest(projected.units)
|
|
68
71
|
|| hybridDigest(view.assignments) !== hybridDigest(projected.assignments)
|
|
69
72
|
|| hybridDigest(view.affected_unit_ids) !== hybridDigest(wave.affectedUnitIds())
|
|
@@ -98,6 +101,19 @@ export class PreparedHistoricalSecondMemoryReview {
|
|
|
98
101
|
return prepared;
|
|
99
102
|
}
|
|
100
103
|
projection() { return structuredClone(this.#projection); }
|
|
104
|
+
settledDocumentBaseline() {
|
|
105
|
+
const registration = this.#wave.registration();
|
|
106
|
+
const paths = [...new Set(registration.plan().works.flatMap(work => work.write_paths))].sort();
|
|
107
|
+
let baseline = registration.initialDocumentBaseline().withWritePaths(paths);
|
|
108
|
+
const prefixes = new Set([hybridDigest(baseline.entries())]);
|
|
109
|
+
for (const history of this.#wave.histories()) {
|
|
110
|
+
if (!prefixes.has(hybridDigest(history.before().entries())))
|
|
111
|
+
refuse("historical_input_prefix_changed");
|
|
112
|
+
baseline = baseline.composeSettledDelta(history.before(), history.after(), history.result().outcome);
|
|
113
|
+
prefixes.add(hybridDigest(baseline.entries()));
|
|
114
|
+
}
|
|
115
|
+
return baseline;
|
|
116
|
+
}
|
|
101
117
|
retainedInputView() { return structuredClone(this.#registration.view); }
|
|
102
118
|
finalChecks(context) { this.assertCustody(context); return this.#checks.value(context).checks; }
|
|
103
119
|
settlementProof(context) {
|
|
@@ -107,17 +123,20 @@ export class PreparedHistoricalSecondMemoryReview {
|
|
|
107
123
|
workers: this.#workers.map(worker => ({ work_id: worker.workId, authority_sha256: worker.authority, result: { ...worker.result } })) };
|
|
108
124
|
}
|
|
109
125
|
assertCustody(context) {
|
|
110
|
-
this.#wave
|
|
111
|
-
this.#
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
126
|
+
const wave = this.#wave;
|
|
127
|
+
assertMemorySqlCustody(context, this.#custodyKey, () => {
|
|
128
|
+
wave.assertCustody(context);
|
|
129
|
+
this.#checks.assertCustody(context);
|
|
130
|
+
const receipt = this.#registration;
|
|
131
|
+
if (hybridDigest(readRetainedSecondCorrectedMemoryReviewRegistration(context, receipt.scope, receipt.input_sha256, receipt.check_receipt_sha256)) !== hybridDigest(receipt))
|
|
132
|
+
refuse("historical_second_registration_changed");
|
|
133
|
+
if (hybridDigest(this.#wave.registration().firstEffectiveReviewProof(context)) !== hybridDigest(this.#previous))
|
|
134
|
+
refuse("first_review_history_changed");
|
|
135
|
+
const harness = runtimeHarness(this.#wave.registration().bundle().execution.work("review").worker);
|
|
136
|
+
for (const worker of this.#workers)
|
|
137
|
+
if (readMemoryReviewWorkSettlement(context, receipt, worker.workId, harness).authority !== worker.authority)
|
|
138
|
+
refuse("historical_second_reviewer_settlement_changed");
|
|
139
|
+
});
|
|
121
140
|
}
|
|
122
141
|
revalidate(context) {
|
|
123
142
|
this.#wave.revalidate(context);
|
|
@@ -6,6 +6,23 @@ import { RetainedExecutionBundle } from "./execution-bundles.js";
|
|
|
6
6
|
import { publishImmutableInput } from "../storage/immutable-input.js";
|
|
7
7
|
import { readBoundedReleaseFile } from "./release-source.js";
|
|
8
8
|
import { readMemoryInitStartupInput } from "./memory-init-startup-custody.js";
|
|
9
|
+
import { readUpgradeStorageReadiness } from "./memory-upgrade-storage-readiness.js";
|
|
10
|
+
/** Original upgrade intake comes from the independently retained storage
|
|
11
|
+
* selection, including after project publication changes the old tree. */
|
|
12
|
+
export function prepareMemoryUpgradeContextsFromStartup(context, bundle, directory) {
|
|
13
|
+
if (!(bundle instanceof RetainedExecutionBundle) || bundle.manifest.flow_id !== "mb-upgrade") {
|
|
14
|
+
throw new AppError("memory_upgrade_context_invalid", "Upgrade contexts require a retained upgrade bundle", 2, { effect: "no_effect" });
|
|
15
|
+
}
|
|
16
|
+
const scope = bundle.execution.snapshot().scope;
|
|
17
|
+
const retained = readUpgradeStorageReadiness(context, { requestId: scope.requestId, projectRoot: scope.projectRoot, workspaceRoot: scope.workspace });
|
|
18
|
+
if (!retained?.receipt.selection || !retained.selection
|
|
19
|
+
|| retained.selection.payload.execution.sha256 !== bundle.execution.sha256
|
|
20
|
+
|| retained.selection.payload.engine.manifest_sha256 !== bundle.manifest.engine_manifest.sha256) {
|
|
21
|
+
throw new AppError("memory_upgrade_context_invalid", "Upgrade contexts require the original request selection", 1, { effect: "no_effect" });
|
|
22
|
+
}
|
|
23
|
+
const objective = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(Buffer.from(retained.receipt.selection.request_input.intake_base64, "base64"));
|
|
24
|
+
return { ...prepareMemoryContexts(bundle, objective, directory, "mb-upgrade"), objective };
|
|
25
|
+
}
|
|
9
26
|
/** Public init consumes the independently retained intake, never a newly
|
|
10
27
|
* inferred objective. Historical v1 SDK requests must not invent one. */
|
|
11
28
|
export function prepareMemoryInitContextsFromStartup(context, bundle, directory) {
|
|
@@ -24,7 +41,10 @@ export function prepareMemoryInitContextsFromStartup(context, bundle, directory)
|
|
|
24
41
|
/** Prepare bytes only. Immutable publication and the controller retain their
|
|
25
42
|
* existing ownership; this does not create directories, Sessions or RUNs. */
|
|
26
43
|
export function prepareMemoryInitContexts(bundle, objective, directory) {
|
|
27
|
-
|
|
44
|
+
return prepareMemoryContexts(bundle, objective, directory, "mb-init");
|
|
45
|
+
}
|
|
46
|
+
function prepareMemoryContexts(bundle, objective, directory, flowId) {
|
|
47
|
+
if (!(bundle instanceof RetainedExecutionBundle) || bundle.manifest.flow_id !== flowId
|
|
28
48
|
|| typeof objective !== "string" || !objective.trim() || Buffer.byteLength(objective) > 64 * 1024
|
|
29
49
|
|| !path.isAbsolute(directory) || path.resolve(directory) !== directory) {
|
|
30
50
|
throw new AppError("memory_init_context_invalid", "Init contexts require a retained memory bundle, bounded objective and absolute directory", 2, { effect: "no_effect" });
|
|
@@ -45,7 +65,13 @@ export function prepareMemoryInitContexts(bundle, objective, directory) {
|
|
|
45
65
|
* their execution authority. The caller supplies an already owned directory
|
|
46
66
|
* and independently retained package digest. Never replaces conflicting files. */
|
|
47
67
|
export function publishMemoryInitContexts(bundle, objective, directory, expectedSha256, expectedOwner) {
|
|
48
|
-
|
|
68
|
+
return publishMemoryContexts(bundle, objective, directory, expectedSha256, expectedOwner, "mb-init");
|
|
69
|
+
}
|
|
70
|
+
export function publishMemoryUpgradeContexts(bundle, objective, directory, expectedSha256, expectedOwner) {
|
|
71
|
+
return publishMemoryContexts(bundle, objective, directory, expectedSha256, expectedOwner, "mb-upgrade");
|
|
72
|
+
}
|
|
73
|
+
function publishMemoryContexts(bundle, objective, directory, expectedSha256, expectedOwner, flowId) {
|
|
74
|
+
const prepared = prepareMemoryContexts(bundle, objective, directory, flowId);
|
|
49
75
|
if (prepared.sha256 !== expectedSha256)
|
|
50
76
|
throw new AppError("memory_init_context_invalid", "Context package selection changed", 1, { effect: "no_effect" });
|
|
51
77
|
const owner = fs.lstatSync(directory);
|
|
@@ -3,12 +3,13 @@ import path from "node:path";
|
|
|
3
3
|
import { AppError, errorRecord } from "../shared/errors.js";
|
|
4
4
|
import { validateMemoryInitStartupScope, readMemoryInitStartupInput } from "./memory-init-startup-custody.js";
|
|
5
5
|
import { startMemoryInitRun } from "./memory-init-start.js";
|
|
6
|
+
import { startMemoryUpgradeRun } from "./memory-upgrade-start.js";
|
|
6
7
|
import { requireProjectByRoot } from "./projects.js";
|
|
7
8
|
import { readRunStartRequest } from "./run-start-custody.js";
|
|
8
9
|
import { readFlowRunExecutionBundle } from "./runs.js";
|
|
9
10
|
import { requireRun } from "./run-controller-state.js";
|
|
10
11
|
import { prepareRunControllerLaunch } from "./run-controller.js";
|
|
11
|
-
import { prepareMemoryInitContextsFromStartup, publishMemoryInitContexts } from "./memory-init-contexts.js";
|
|
12
|
+
import { prepareMemoryInitContextsFromStartup, publishMemoryInitContexts, prepareMemoryUpgradeContextsFromStartup, publishMemoryUpgradeContexts } from "./memory-init-contexts.js";
|
|
12
13
|
import { parseRuntimeBudget } from "./runtime-budget.js";
|
|
13
14
|
function refuse(reason) {
|
|
14
15
|
throw new AppError("memory_init_controller_start_invalid", "Init controller requires its exact published startup request", 1, { reason, effect: "no_effect", next_action: "reconcile_exact_request" });
|
|
@@ -18,28 +19,37 @@ function refuse(reason) {
|
|
|
18
19
|
* An existing controller replays before candidate publication, so missing
|
|
19
20
|
* productive inputs are never reconstructed here. No process is launched. */
|
|
20
21
|
export function prepareMemoryInitControllerStart(context, scope) {
|
|
22
|
+
return prepareMemoryControllerStart(context, scope, "mb-init");
|
|
23
|
+
}
|
|
24
|
+
export function prepareMemoryUpgradeControllerStart(context, scope) {
|
|
25
|
+
return prepareMemoryControllerStart(context, scope, "mb-upgrade");
|
|
26
|
+
}
|
|
27
|
+
function prepareMemoryControllerStart(context, scope, flowId) {
|
|
21
28
|
validateMemoryInitStartupScope(scope);
|
|
22
29
|
if (context.db.isOwnedWriteTransaction?.())
|
|
23
30
|
refuse("preparation_outside_writer_required");
|
|
24
31
|
parseRuntimeBudget(context.env.DD_FLOW_RUNTIME_BUDGET);
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
32
|
+
if (flowId === "mb-init") {
|
|
33
|
+
const startup = readMemoryInitStartupInput(context, scope);
|
|
34
|
+
if (!startup || !["dd-flow/memory-init-startup-input@2", "dd-flow/memory-init-startup-input@3"].includes(startup.payload.schema_id))
|
|
35
|
+
refuse("retained_objective_required");
|
|
36
|
+
}
|
|
28
37
|
const project = requireProjectByRoot(context, scope.projectRoot);
|
|
29
38
|
const request = readRunStartRequest(context, project.id, scope.requestId);
|
|
30
|
-
if (!request?.published || request.payload.initial_index.flow_kind !==
|
|
39
|
+
if (!request?.published || request.payload.initial_index.flow_kind !== flowId
|
|
31
40
|
|| request.payload.workspace_root !== scope.workspaceRoot)
|
|
32
41
|
refuse("published_run_required");
|
|
33
42
|
// Restores the exact request and verifies independent SQL/physical root
|
|
34
43
|
// ownership without consulting current canon/profiles or creating a RUN.
|
|
35
|
-
const started = startMemoryInitRun(context, scope);
|
|
44
|
+
const started = flowId === "mb-init" ? startMemoryInitRun(context, scope) : startMemoryUpgradeRun(context, scope);
|
|
36
45
|
const run = requireRun(context, project.id, started.runId);
|
|
37
46
|
const bundle = readFlowRunExecutionBundle(context, { projectRoot: scope.projectRoot, runId: run.id });
|
|
38
47
|
if (!bundle || bundle.sha256 !== started.bundleSha256)
|
|
39
48
|
refuse("retained_bundle_required");
|
|
40
49
|
if (bundle.execution.settings.execution !== "managed")
|
|
41
50
|
refuse("managed_execution_required");
|
|
42
|
-
const contexts = prepareMemoryInitContextsFromStartup(context, bundle, run.run_root)
|
|
51
|
+
const contexts = flowId === "mb-init" ? prepareMemoryInitContextsFromStartup(context, bundle, run.run_root)
|
|
52
|
+
: prepareMemoryUpgradeContextsFromStartup(context, bundle, run.run_root);
|
|
43
53
|
const input = { projectRoot: scope.projectRoot, runId: run.id, requestId: scope.requestId,
|
|
44
54
|
contextFile: contexts.file, contextSha256: contexts.sha256 };
|
|
45
55
|
const controllers = context.db.get("SELECT 1 FROM sqlite_schema WHERE type='table' AND name='run_controllers'")
|
|
@@ -73,7 +83,8 @@ export function prepareMemoryInitControllerStart(context, scope) {
|
|
|
73
83
|
});
|
|
74
84
|
if (!rootOwner || !Number.isSafeInteger(rootOwner.dev) || !Number.isSafeInteger(rootOwner.ino))
|
|
75
85
|
refuse("root_owner_changed");
|
|
76
|
-
|
|
86
|
+
const publish = flowId === "mb-init" ? publishMemoryInitContexts : publishMemoryUpgradeContexts;
|
|
87
|
+
publish(bundle, contexts.objective, run.run_root, contexts.sha256, rootOwner);
|
|
77
88
|
try {
|
|
78
89
|
return { input, prepared: prepareRunControllerLaunch(context, input) };
|
|
79
90
|
}
|