@deksden-com/dd-flow-cli 0.9.0-beta.143 → 0.9.0-beta.145

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +13 -8
  3. package/dist/build-info.json +7 -7
  4. package/dist/cli/command-inputs.js +9 -3
  5. package/dist/cli/command-metadata.js +33 -14
  6. package/dist/cli/help.js +32 -1
  7. package/dist/cli/hook-ingress.js +27 -3
  8. package/dist/cli/memory-flow-admission.js +7 -3
  9. package/dist/cli/memory-init-invocation.js +10 -3
  10. package/dist/cli/memory-init.js +74 -68
  11. package/dist/cli/memory-storage-admission.js +3 -2
  12. package/dist/cli/memory-storage-qualification.js +105 -0
  13. package/dist/cli/memory-storage-transition.js +123 -0
  14. package/dist/cli/memory-upgrade.js +189 -0
  15. package/dist/cli/native-hook-ingress.js +14 -4
  16. package/dist/cli/native-hook-preparation.js +55 -0
  17. package/dist/cli/response-file.js +2 -1
  18. package/dist/cli/run-cli.js +70 -32
  19. package/dist/cli/setup.js +2 -1
  20. package/dist/cli.js +12 -0
  21. package/dist/domain/stage-handler-identities.js +8 -0
  22. package/dist/harness-runtime/bin/dd-agy.mjs +3 -3
  23. package/dist/harness-runtime/bin/dd-grok.mjs +3 -3
  24. package/dist/harness-runtime/bin/dd-zcode.mjs +5 -5
  25. package/dist/harness-runtime/lib/agy-error-boundary.mjs +58 -0
  26. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +62 -20
  27. package/dist/harness-runtime/lib/dd-agy.mjs +17 -3
  28. package/dist/harness-runtime/lib/dd-droid.mjs +1 -1
  29. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +70 -13
  30. package/dist/harness-runtime/lib/dd-grok.mjs +6 -3
  31. package/dist/harness-runtime/lib/dd-zcode.mjs +44 -14
  32. package/dist/harness-runtime/lib/delegation-instructions.mjs +1 -1
  33. package/dist/harness-runtime/lib/entity-references.d.mts +2 -0
  34. package/dist/harness-runtime/lib/entity-references.mjs +9 -0
  35. package/dist/harness-runtime/lib/managed-daemon.mjs +13 -6
  36. package/dist/harness-runtime/lib/native-hook-command.mjs +4 -4
  37. package/dist/harness-runtime/lib/opencode-plugin.mjs +3 -3
  38. package/dist/harness-runtime/lib/operation-errors.mjs +1 -1
  39. package/dist/harness-runtime/lib/provider-overload-policy.d.mts +4 -2
  40. package/dist/harness-runtime/lib/provider-overload-policy.mjs +19 -6
  41. package/dist/harness-runtime/lib/runner-events.mjs +3 -1
  42. package/dist/harness-runtime/lib/setup-progress.mjs +8 -6
  43. package/dist/schemas/mb-upgrade-stage-result.schema.json +863 -0
  44. package/dist/schemas/memory-repair-work-result.schema.json +9 -2
  45. package/dist/schemas/release-impact.schema.json +3 -3
  46. package/dist/schemas/stage-start-response.schema.json +4 -0
  47. package/dist/services/accepted-intent.js +13 -1
  48. package/dist/services/canon.js +43 -7
  49. package/dist/services/cli-operation-classifier.js +1 -1
  50. package/dist/services/cli-upgrade-allowlist.js +1 -1
  51. package/dist/services/code-checks.js +11 -4
  52. package/dist/services/compatibility-preflight.js +5 -2
  53. package/dist/services/controller-fanout.js +21 -4
  54. package/dist/services/controller-memory-task.js +45 -0
  55. package/dist/services/controller-memory-worker.js +48 -0
  56. package/dist/services/dashboard.js +2 -2
  57. package/dist/services/descriptor-run-start.js +26 -19
  58. package/dist/services/descriptor-stage-coordinator.js +30 -10
  59. package/dist/services/descriptor-stage-start.js +40 -4
  60. package/dist/services/engines.js +64 -14
  61. package/dist/services/eval-snapshots.js +104 -93
  62. package/dist/services/external-work-launch.js +5 -3
  63. package/dist/services/flow-catalog.js +8 -4
  64. package/dist/services/frozen-engine.js +113 -11
  65. package/dist/services/hook-memory-routing.js +188 -0
  66. package/dist/services/hook-recovery-guard.js +14 -0
  67. package/dist/services/hook-session-observation.js +24 -0
  68. package/dist/services/hooks.js +27 -8
  69. package/dist/services/hybrid-contracts.js +2 -13
  70. package/dist/services/hybrid-digest.js +14 -0
  71. package/dist/services/installed-flow-source.js +25 -4
  72. package/dist/services/lifecycle-invocations.js +33 -5
  73. package/dist/services/memory-accepted-composition.js +5 -4
  74. package/dist/services/memory-completed-correction-wave.js +23 -15
  75. package/dist/services/memory-compose-handler.js +163 -21
  76. package/dist/services/memory-composition-acceptance.js +186 -10
  77. package/dist/services/memory-composition-graph.js +42 -11
  78. package/dist/services/memory-composition-registration.js +23 -8
  79. package/dist/services/memory-composition-work-custody.js +14 -9
  80. package/dist/services/memory-composition-work-results.js +15 -3
  81. package/dist/services/memory-corrected-review-batch.js +10 -3
  82. package/dist/services/memory-corrected-review-findings.js +4 -2
  83. package/dist/services/memory-corrected-review-inputs.js +10 -5
  84. package/dist/services/memory-corrected-review-receipt.js +17 -9
  85. package/dist/services/memory-corrected-review-registration.js +3 -2
  86. package/dist/services/memory-correction-check-receipt.js +20 -4
  87. package/dist/services/memory-custody-snapshot.js +36 -0
  88. package/dist/services/memory-finish-evidence.js +44 -22
  89. package/dist/services/memory-finish-handler.js +37 -23
  90. package/dist/services/memory-finish-integration.js +218 -48
  91. package/dist/services/memory-finish-readiness.js +66 -19
  92. package/dist/services/memory-git-integration.js +87 -18
  93. package/dist/services/memory-historical-corrected-review.js +21 -15
  94. package/dist/services/memory-historical-original-review.js +22 -9
  95. package/dist/services/memory-historical-repair-registration.js +14 -8
  96. package/dist/services/memory-historical-second-review.js +30 -11
  97. package/dist/services/memory-init-contexts.js +28 -2
  98. package/dist/services/memory-init-controller-start.js +19 -8
  99. package/dist/services/memory-init-git-policy.js +50 -21
  100. package/dist/services/memory-init-lint-capability.js +21 -3
  101. package/dist/services/memory-init-native-capacity.js +13 -0
  102. package/dist/services/memory-init-preflight.js +11 -1
  103. package/dist/services/memory-init-request-candidate.js +73 -17
  104. package/dist/services/memory-init-request-input.js +23 -7
  105. package/dist/services/memory-init-run-bundle.js +49 -12
  106. package/dist/services/memory-init-storage.js +17 -7
  107. package/dist/services/memory-layer.js +68 -4
  108. package/dist/services/memory-lint-config.js +111 -2
  109. package/dist/services/memory-native-review-copy.js +5 -67
  110. package/dist/services/memory-normal-flow-entry.js +12 -0
  111. package/dist/services/memory-ordinary-defaults.js +22 -5
  112. package/dist/services/memory-preflight-handler.js +15 -0
  113. package/dist/services/memory-repair-current-view.js +1 -1
  114. package/dist/services/memory-repair-document-delta.js +70 -33
  115. package/dist/services/memory-repair-history.js +12 -7
  116. package/dist/services/memory-repair-physical-binding.js +1 -119
  117. package/dist/services/memory-repair-publication-plan.js +5 -3
  118. package/dist/services/memory-repair-publication-source.js +1 -1
  119. package/dist/services/memory-repair-publication.js +3 -2
  120. package/dist/services/memory-repair-registration.js +2 -1
  121. package/dist/services/memory-repair-work-baseline.js +4 -2
  122. package/dist/services/memory-repair-work-completion.js +32 -12
  123. package/dist/services/memory-repair-work-results.js +21 -2
  124. package/dist/services/memory-repair-workspace.js +11 -2
  125. package/dist/services/memory-repair-writer-copy.js +2 -1
  126. package/dist/services/memory-result-binding.js +78 -12
  127. package/dist/services/memory-retained-review-authority.js +17 -8
  128. package/dist/services/memory-retained-second-baseline.js +1 -1
  129. package/dist/services/memory-retained-second-repairs.js +12 -6
  130. package/dist/services/memory-review-correction-plan.js +47 -12
  131. package/dist/services/memory-review-handler.js +17 -17
  132. package/dist/services/memory-review-inputs.js +55 -9
  133. package/dist/services/memory-review-limits.js +10 -0
  134. package/dist/services/memory-review-receipt.js +2 -2
  135. package/dist/services/memory-review-registration.js +13 -4
  136. package/dist/services/memory-review-work-custody.js +3 -1
  137. package/dist/services/memory-review-work-input.js +26 -7
  138. package/dist/services/memory-review-work-results.js +26 -4
  139. package/dist/services/memory-second-corrected-review-inputs.js +5 -3
  140. package/dist/services/memory-second-correction-plan.js +2 -1
  141. package/dist/services/memory-second-repair-launch.js +4 -4
  142. package/dist/services/memory-second-repair-source.js +1 -1
  143. package/dist/services/memory-stage-dispatch.js +38 -1
  144. package/dist/services/memory-stage-results.js +113 -22
  145. package/dist/services/memory-upgrade-analysis-handler.js +243 -0
  146. package/dist/services/memory-upgrade-bootstrap-readiness.js +251 -0
  147. package/dist/services/memory-upgrade-evidence.js +100 -0
  148. package/dist/services/memory-upgrade-inventory.js +442 -0
  149. package/dist/services/memory-upgrade-publication-custody.js +73 -0
  150. package/dist/services/memory-upgrade-publication.js +413 -0
  151. package/dist/services/memory-upgrade-selection.js +430 -0
  152. package/dist/services/memory-upgrade-start.js +60 -0
  153. package/dist/services/memory-upgrade-storage-readiness.js +340 -0
  154. package/dist/services/memory-verification-acceptance.js +38 -17
  155. package/dist/services/memory-verification-checks.js +77 -23
  156. package/dist/services/memory-verification-receipt.js +6 -5
  157. package/dist/services/memory-verify-handler.js +15 -13
  158. package/dist/services/memory-work-result-json.js +12 -0
  159. package/dist/services/memory-work-result-preparation.js +5 -4
  160. package/dist/services/merge-server.js +4 -2
  161. package/dist/services/migrations.js +127 -98
  162. package/dist/services/physical-startup.js +45 -0
  163. package/dist/services/project-registry.js +97 -0
  164. package/dist/services/project-summary.js +1 -1
  165. package/dist/services/projects.js +3 -93
  166. package/dist/services/prompt-delivery.js +71 -0
  167. package/dist/services/provider-capacity.js +74 -9
  168. package/dist/services/recovery-snapshot-database.js +32 -4
  169. package/dist/services/repair-intents.js +1 -12
  170. package/dist/services/repair-receipt.js +13 -0
  171. package/dist/services/run-control-worker.js +35 -18
  172. package/dist/services/run-controller-process.js +2 -6
  173. package/dist/services/run-controller-recovery.js +6 -1
  174. package/dist/services/run-controller.js +131 -80
  175. package/dist/services/run-recovery-guard.js +30 -0
  176. package/dist/services/run-recovery.js +11 -44
  177. package/dist/services/run-start-custody.js +17 -5
  178. package/dist/services/runtime-scope-worker.js +46 -19
  179. package/dist/services/sessions.js +3 -24
  180. package/dist/services/setup.js +63 -13
  181. package/dist/services/stage-handlers.js +9 -7
  182. package/dist/services/stage-pause.js +108 -44
  183. package/dist/services/version-status.js +1 -1
  184. package/dist/services/vnext-fanout.js +21 -9
  185. package/dist/services/vnext-plan-review.js +3 -5
  186. package/dist/services/work-registry.js +80 -33
  187. package/dist/services/workspace-files.js +1 -1
  188. package/dist/shared/entity-references.js +1 -8
  189. package/dist/storage/data-preservation.js +65 -0
  190. package/dist/storage/database.js +53 -19
  191. package/dist/storage/import-guard.js +84 -27
  192. package/dist/storage/memory-upgrade-schema-bootstrap.js +38 -0
  193. package/dist/storage/paired-writer-migration.js +424 -0
  194. package/dist/storage/writer-contract.js +11 -4
  195. package/dist/storage/writer-migration.js +313 -52
  196. package/dist/storage/writer-restore.js +217 -0
  197. package/package.json +8 -4
@@ -0,0 +1,430 @@
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 { assertImportAdmission } from "../storage/import-guard.js";
6
+ import { publishImmutableInput } from "../storage/immutable-input.js";
7
+ import { captureMemoryUpgradeRequestCandidate, readMemoryUpgradeRequestCandidate } from "./memory-init-request-candidate.js";
8
+ import { readMemoryUpgradeRequestInput } from "./memory-init-request-input.js";
9
+ import { PreparedInstalledFlowSource } from "./installed-flow-source.js";
10
+ import { PreparedMemoryLayer } from "./memory-layer.js";
11
+ import { FrozenGenericExecution } from "./generic-execution.js";
12
+ import { ResolvedFlowDescriptor } from "./flow-descriptors.js";
13
+ import { readOperatorProfiles } from "./operator-profiles.js";
14
+ import { AdmittedFrozenEngine } from "./frozen-engine.js";
15
+ import { prepareMemoryInitEngineRelease, revalidateMemoryInitEngineRelease, selectMemoryInitInstalledEngine } from "./memory-init-engine-release.js";
16
+ import { prepareMemoryUpgradeInventory, captureUpgradePreservationBaseline, readUpgradePreservationBaseline, captureUpgradeProjectLinks } from "./memory-upgrade-inventory.js";
17
+ import { getProjectVersionStatus } from "./version-status.js";
18
+ import { observeDescriptorWorkspace } from "./descriptor-run-start.js";
19
+ import { readBoundedReleaseFile } from "./release-source.js";
20
+ import { hybridDigest } from "./hybrid-contracts.js";
21
+ import { adjacentMigrationChain, readImpacts } from "./migrations.js";
22
+ import { readSetupStatus } from "./setup.js";
23
+ import { inspectMemoryGitExecutable } from "./memory-init-preflight.js";
24
+ import { parseDocument } from "yaml";
25
+ import semver from "semver";
26
+ import { observeMemoryGitPolicySelection } from "./memory-init-git-policy.js";
27
+ import { captureUpgradeProjectFiles, readUpgradeProjectFiles } from "./memory-upgrade-inventory.js";
28
+ // ponytail: one bounded immutable record, like startup@3; split blobs only if
29
+ // real canonical packs outgrow the existing 64 MiB startup-input ceiling.
30
+ const limit = 64 * 1024 * 1024;
31
+ const hash = (bytes) => createHash("sha256").update(bytes).digest("hex");
32
+ function refuse(reason) {
33
+ throw new AppError("memory_upgrade_selection_invalid", "Upgrade requires its original resolved selection", 1, { reason, effect: "no_effect", next_action: "reconcile_exact_request" });
34
+ }
35
+ function exists(file) {
36
+ try {
37
+ fs.lstatSync(file);
38
+ return true;
39
+ }
40
+ catch (error) {
41
+ if (error.code === "ENOENT")
42
+ return false;
43
+ throw error;
44
+ }
45
+ }
46
+ /** Data admission only. These fixed declaration names do not register code.
47
+ * RUN/bootstrap must still admit the actual selected engine's handler registry. */
48
+ function descriptorData(source) {
49
+ const reference = source.descriptorReference();
50
+ if (reference.path !== "mb-upgrade/flow-descriptor.json")
51
+ refuse("foreign_flow");
52
+ const blobs = source.blobs(), flow = ResolvedFlowDescriptor.admit(blobs.get(reference.path), reference.sha256, {
53
+ blobs, entryHandlers: new Set(["memory-upgrade-stage-entry"]), finishHandlers: new Set(["memory-upgrade-stage-finish"])
54
+ });
55
+ const stages = ["preflight", "diff-analysis", "upgrade", "verify", "review", "finish"];
56
+ if (flow.descriptor.id !== "mb-upgrade" || flow.descriptor.entry_stage !== "preflight"
57
+ || flow.descriptor.stages.length !== stages.length || flow.descriptor.stages.some((stage, i) => stage.id !== stages[i]))
58
+ refuse("foreign_flow_layout");
59
+ return flow;
60
+ }
61
+ function files(input, baseline = false) {
62
+ if (!input || !Array.isArray(input.files) || input.files.length > 20_000)
63
+ refuse("invalid_layer");
64
+ const entries = new Map();
65
+ for (const entry of input.files) {
66
+ if (!entry || Object.keys(entry).sort().join() !== "base64,path" || typeof entry.base64 !== "string"
67
+ || entry.base64.length > 2 * 1024 * 1024 || entries.has(entry.path))
68
+ refuse("invalid_layer");
69
+ const bytes = Buffer.from(entry.base64, "base64");
70
+ if (bytes.toString("base64") !== entry.base64)
71
+ refuse("invalid_layer");
72
+ entries.set(entry.path, bytes);
73
+ }
74
+ return PreparedMemoryLayer.restoreRetained(entries, input.sha256, input.installation_sha256, baseline);
75
+ }
76
+ function current(home, scope, layer, env, baselineInstallationSha256, gitExecutable, includeProjectLinks = true, includeProjectFiles = true) {
77
+ const workspace = observeDescriptorWorkspace({ env }, scope.projectRoot, scope.workspaceRoot, gitExecutable);
78
+ let inventory = prepareMemoryUpgradeInventory(scope.workspaceRoot, layer);
79
+ const status = getProjectVersionStatus({ projectRoot: scope.workspaceRoot, rootSource: "explicit", canon: null });
80
+ const missingMarker = status?.memory_bank.status === "unknown" && status.memory_bank.reason === "memory_bank_version_missing"
81
+ || status?.memory_bank.source === ".memory-bank/mbb/index.md";
82
+ const canonVersion = missingMarker ? status?.flow_pack.canon_version_at_source_commit : status?.memory_bank.version;
83
+ const packVersion = status?.flow_pack.canon_version_at_source_commit;
84
+ if (!status || status.memory_bank.status !== "present" && !missingMarker || status.flow_pack.status !== "present" || !canonVersion
85
+ || !packVersion || semver.valid(canonVersion) !== canonVersion || semver.valid(packVersion) !== packVersion
86
+ || semver.gt(canonVersion, packVersion) || !/^[a-f0-9]{40}$/.test(status.flow_pack.source_commit ?? ""))
87
+ refuse("invalid_current_provenance");
88
+ const versionPath = missingMarker ? "index.md" : status.memory_bank.source?.replace(/^\.memory-bank\//, "");
89
+ if (typeof versionPath !== "string" || !inventory.inventory.entries.some(entry => entry.path === versionPath && entry.current?.kind === "file"))
90
+ refuse("unowned_version_source");
91
+ const manifest = readBoundedReleaseFile(inventory.inventory.current_manifest.path, 1024 * 1024);
92
+ if (hash(manifest) !== inventory.inventory.current_manifest.sha256)
93
+ refuse("current_changed");
94
+ const declared = JSON.parse(manifest.toString("utf8"));
95
+ if (declared.canon_version_at_source_commit !== packVersion || declared.source_commit !== status.flow_pack.source_commit)
96
+ refuse("current_changed");
97
+ const baseline = baselineInstallationSha256 === null ? null : PreparedMemoryLayer.fromCommittedCanon(home, declared, baselineInstallationSha256);
98
+ if (missingMarker) {
99
+ // Older real init outputs omitted this project marker. Their independently
100
+ // installed old canon, not mutable manifest text alone, supplies provenance.
101
+ const index = readBoundedReleaseFile(path.join(scope.workspaceRoot, ".memory-bank", versionPath), 1024 * 1024).toString("utf8");
102
+ const frontmatter = index.match(/^---\n([\s\S]*?)\n---(?:\n|$)/);
103
+ const document = frontmatter ? parseDocument(frontmatter[1]) : null;
104
+ if (!baseline || !document || document.errors.length || document.has("memory_bank_version"))
105
+ refuse("missing_version_marker_requires_canonical_baseline");
106
+ if (status.memory_bank.version && status.memory_bank.version !== canonVersion) {
107
+ // Canonical guidance historically carried stale prose version labels.
108
+ // Ignore that fallback only when it is the exact authenticated old guide;
109
+ // a divergent local version claim is an explicit conflict, not legacy.
110
+ const guide = inventory.inventory.entries.find(entry => entry.path === "mbb/index.md")?.current;
111
+ const original = baseline.files().get("mbb/index.md");
112
+ if (!original || guide?.kind !== "file" || guide.sha256 !== hash(original))
113
+ refuse("legacy_guidance_version_diverged");
114
+ }
115
+ }
116
+ if (baseline)
117
+ inventory = prepareMemoryUpgradeInventory(scope.workspaceRoot, layer, baseline);
118
+ const projectLinks = includeProjectLinks ? captureUpgradeProjectLinks(scope.workspaceRoot, gitExecutable ?? "git", env) : [];
119
+ return { workspace, current: { canon_version: canonVersion, source_commit: status.flow_pack.source_commit,
120
+ ...(includeProjectFiles ? { project_files: captureUpgradeProjectFiles(scope.workspaceRoot, gitExecutable ?? "git", env) } : {}),
121
+ ...(projectLinks.length ? { project_links: projectLinks } : {}),
122
+ ...(canonVersion !== packVersion ? { flow_pack_canon_version: packVersion } : {}),
123
+ manifest_base64: manifest.toString("base64"), version_source: versionPath, inventory, old_tree: captureUpgradePreservationBaseline(inventory),
124
+ ...(baseline ? { baseline_layer: { sha256: baseline.sha256, installation_sha256: baseline.installationSha256,
125
+ files: [...baseline.files()].map(([logical, bytes]) => ({ path: logical, base64: bytes.toString("base64") })) } } : {}) } };
126
+ }
127
+ function paths(candidate) {
128
+ const root = path.dirname(candidate.retain().file);
129
+ return { payload: path.join(root, "resolved-selection.json"), receipt: path.join(root, "resolved-selection.receipt.json") };
130
+ }
131
+ function impacts(layer, sourceVersion, targetVersion) {
132
+ const manifest = JSON.parse(layer.files().get("dd-flow/manifest.json").toString("utf8"));
133
+ const entries = readImpacts(path.join(manifest.canon_memory_bank_root, "release-impact"));
134
+ return { chain: adjacentMigrationChain(sourceVersion, targetVersion, entries),
135
+ files: entries.map(entry => {
136
+ const reference = entry.source_reference;
137
+ const content = readBoundedReleaseFile(reference.path, 1024 * 1024);
138
+ if (hash(content) !== reference.sha256)
139
+ refuse("impact_changed");
140
+ return { ...reference, base64: content.toString("base64") };
141
+ }) };
142
+ }
143
+ function assertMixedCurrentContext(current, impact) {
144
+ const pack = current.flow_pack_canon_version;
145
+ if (pack === undefined)
146
+ return;
147
+ // An independently updated process pack is a real intermediate node, not
148
+ // permission to invent a migration hop or to claim the MB already upgraded.
149
+ if (semver.valid(current.canon_version) !== current.canon_version || semver.valid(pack) !== pack
150
+ || !semver.lt(current.canon_version, pack) || impact.chain.status !== "ok"
151
+ || !impact.chain.steps.some(step => step.to === pack))
152
+ refuse("invalid_mixed_current_context");
153
+ }
154
+ /** Historical input custody only. No current project/cache/profile or engine
155
+ * code is inspected; actual input restoration is a separate operation below. */
156
+ export function readMemoryUpgradeSelection(home, scope) {
157
+ const original = readMemoryUpgradeRequestCandidate(home, scope);
158
+ if (!original)
159
+ return null;
160
+ const locations = paths(original.candidate), hasPayload = exists(locations.payload), hasReceipt = exists(locations.receipt);
161
+ if (!hasPayload && !hasReceipt)
162
+ return null;
163
+ if (!hasPayload || !hasReceipt)
164
+ refuse("selection_custody_missing");
165
+ try {
166
+ const bytes = readBoundedReleaseFile(locations.payload, limit), sha256 = hash(bytes);
167
+ const receipt = JSON.parse(readBoundedReleaseFile(locations.receipt, 16 * 1024).toString("utf8"));
168
+ const requestSha256 = hybridDigest(original.input);
169
+ if (hybridDigest(receipt) !== hybridDigest({ schema_id: "dd-flow/memory-upgrade-selection-receipt@1", scope,
170
+ request_input_sha256: requestSha256, selection_sha256: sha256 }))
171
+ refuse("selection_bytes_changed");
172
+ return { ...decodeMemoryUpgradeSelection(bytes, sha256, original.input, scope), original, bytes };
173
+ }
174
+ catch (error) {
175
+ if (error instanceof AppError)
176
+ throw error;
177
+ return refuse("invalid_retained_selection");
178
+ }
179
+ }
180
+ /** SQL-safe data decoding for accepted custody/recovery. No filesystem, current
181
+ * defaults, engine registry, storage probe or provider is consulted here. */
182
+ export function decodeMemoryUpgradeSelection(bytes, expectedSha256, input, scope) {
183
+ try {
184
+ if (bytes.byteLength > limit || hash(bytes) !== expectedSha256)
185
+ refuse("selection_bytes_changed");
186
+ const objective = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(Buffer.from(input.intake_base64, "base64"));
187
+ const raw = readMemoryUpgradeRequestInput(input, scope, objective).selection;
188
+ const payload = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
189
+ if (!payload || Object.keys(payload).filter(key => key !== "git").sort().join() !== "current,engine,execution,impact,layer,release,request_input_sha256,schema_id,scope,source,workspace"
190
+ || payload.schema_id !== "dd-flow/memory-upgrade-selection@1" || hybridDigest(payload.scope) !== hybridDigest(scope)
191
+ || payload.request_input_sha256 !== hybridDigest(input))
192
+ refuse("foreign_selection");
193
+ if (payload.git !== undefined) {
194
+ const { executable, probe, policy } = payload.git;
195
+ if (Object.keys(payload.git).sort().join() !== "executable,policy,probe"
196
+ || typeof executable !== "string" || !path.isAbsolute(executable)
197
+ || !probe || Object.keys(probe).sort().join() !== "executable_sha256,stderr_sha256,stdout_sha256,version"
198
+ || typeof probe.version !== "string" || !probe.version
199
+ || [probe.executable_sha256, probe.stdout_sha256, probe.stderr_sha256].some(value => typeof value !== "string" || !/^[a-f0-9]{64}$/.test(value))
200
+ || !policy || Object.keys(policy).sort().join() !== "initial_head,integration_branch,push_authorized,repository,route,source,workspace_branch"
201
+ || typeof policy.repository !== "string" || !path.isAbsolute(policy.repository)
202
+ || policy.route !== raw.git_policy.route || policy.integration_branch !== raw.git_policy.integrationBranch
203
+ || policy.source !== "explicit_request" || policy.push_authorized !== false
204
+ || policy.workspace_branch !== payload.workspace.branch || policy.initial_head !== payload.workspace.head)
205
+ refuse("invalid_git_selection");
206
+ }
207
+ if (!payload.release || typeof payload.release.manifest_base64 !== "string" || payload.release.manifest_base64.length > 2 * 1024 * 1024)
208
+ refuse("invalid_release_bytes");
209
+ const releaseBytes = Buffer.from(payload.release.manifest_base64, "base64");
210
+ if (releaseBytes.toString("base64") !== payload.release.manifest_base64 || hash(releaseBytes) !== raw.target_installation_sha256
211
+ || payload.release.manifest_sha256 !== raw.target_installation_sha256)
212
+ refuse("release_bytes_changed");
213
+ const release = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(releaseBytes));
214
+ if (payload.current?.old_tree !== undefined)
215
+ readUpgradePreservationBaseline(payload.current.old_tree, payload.current.inventory);
216
+ if (payload.current?.project_files !== undefined)
217
+ readUpgradeProjectFiles(payload.current.project_files);
218
+ if (payload.current?.project_links !== undefined) {
219
+ const links = payload.current.project_links, seen = new Set();
220
+ if (!Array.isArray(links) || links.length > 20_000 || JSON.stringify(links).length > 2 * 1024 * 1024)
221
+ refuse("invalid_project_links");
222
+ for (const link of links) {
223
+ if (!link || Object.keys(link).sort().join() !== "mode,path,sha256,target_base64"
224
+ || typeof link.path !== "string" || path.isAbsolute(link.path)
225
+ || link.path.split("/").some(part => !part || part === "." || part === "..")
226
+ || seen.has(link.path) || typeof link.target_base64 !== "string"
227
+ || Buffer.from(link.target_base64, "base64").toString("base64") !== link.target_base64
228
+ || hash(Buffer.from(link.target_base64, "base64")) !== link.sha256
229
+ || !Number.isInteger(link.mode) || link.mode < 0 || link.mode > 0o777)
230
+ refuse("invalid_project_links");
231
+ seen.add(link.path);
232
+ }
233
+ }
234
+ const baseline = payload.current?.baseline_layer ? files(payload.current.baseline_layer, true) : null;
235
+ assertMixedCurrentContext(payload.current, payload.impact);
236
+ if ((baseline?.sha256 ?? null) !== payload.current.inventory.inventory.baseline_layer_sha256)
237
+ refuse("changed_canonical_baseline");
238
+ if (baseline) {
239
+ const manifest = JSON.parse(baseline.files().get("dd-flow/manifest.json").toString("utf8"));
240
+ if (manifest.source_commit !== payload.current.source_commit || manifest.canon_version_at_source_commit !== (payload.current.flow_pack_canon_version ?? payload.current.canon_version))
241
+ refuse("foreign_canonical_baseline");
242
+ }
243
+ if (payload.current.version_source !== undefined && !payload.current.inventory.inventory.entries.some(entry => entry.path === payload.current.version_source && entry.current?.kind === "file"))
244
+ refuse("unowned_version_source");
245
+ return { payload, sha256: expectedSha256, release };
246
+ }
247
+ catch (error) {
248
+ if (error instanceof AppError)
249
+ throw error;
250
+ return refuse("invalid_retained_selection");
251
+ }
252
+ }
253
+ /** Restore prepared data and the exact immutable code, not storage readiness
254
+ * or productive handler admission. Never consults current operator selections. */
255
+ export function restoreMemoryUpgradeSelection(home, scope) {
256
+ const retained = readMemoryUpgradeSelection(home, scope);
257
+ if (!retained)
258
+ refuse("selection_required");
259
+ return { ...restoreMemoryUpgradeSelectionData(retained.bytes, retained.sha256, retained.original.input, scope), original: retained.original };
260
+ }
261
+ /** Restore the same owners from independently retained SQL bytes after the
262
+ * pre-storage candidate has been retired. This still admits the exact code;
263
+ * readable historical metadata alone is not engine execution authority. */
264
+ export function restoreMemoryUpgradeSelectionData(bytes, expectedSha256, input, scope) {
265
+ const { payload, sha256, release } = decodeMemoryUpgradeSelection(bytes, expectedSha256, input, scope);
266
+ try {
267
+ const objective = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(Buffer.from(input.intake_base64, "base64"));
268
+ const raw = readMemoryUpgradeRequestInput(input, scope, objective).selection;
269
+ const source = PreparedInstalledFlowSource.restoreRetainedData(payload.source.snapshot, payload.source.sha256), layer = files(payload.layer);
270
+ if (source.installationSha256() !== raw.target_installation_sha256 || layer.installationSha256 !== raw.target_installation_sha256)
271
+ refuse("foreign_target");
272
+ const receipts = new Map(payload.execution.receipts), receiptBytes = new Map();
273
+ if (receipts.size !== payload.execution.receipts.length || receipts.size > 2)
274
+ refuse("invalid_execution_receipts");
275
+ for (const [checksum, encoded] of payload.execution.receipt_blobs ?? []) {
276
+ const content = Buffer.from(encoded, "base64");
277
+ if (content.length > 256 * 1024 || content.toString("base64") !== encoded || hash(content) !== checksum || receiptBytes.has(checksum))
278
+ refuse("invalid_execution_receipt_bytes");
279
+ receiptBytes.set(checksum, content);
280
+ }
281
+ const execution = FrozenGenericExecution.restore(payload.execution.snapshot, payload.execution.sha256, descriptorData(source), { requestId: scope.requestId, projectRoot: scope.projectRoot, workspace: scope.workspaceRoot }, receipts);
282
+ const consumed = [execution.settings.attached_session_receipt, execution.settings.delegation.native_capacity_receipt].filter(ref => ref !== null);
283
+ if (receipts.size !== consumed.length || receiptBytes.size !== consumed.length || consumed.some(ref => !receiptBytes.has(ref.sha256)))
284
+ refuse("execution_receipt_bytes_missing");
285
+ const engine = AdmittedFrozenEngine.admitSelection(payload.engine.identity, payload.engine.manifest_sha256);
286
+ if (payload.engine.release_pairing.facts.installation_sha256 !== raw.target_installation_sha256
287
+ || raw.engine_checksum !== null && raw.engine_checksum !== payload.engine.identity.integrity_checksum)
288
+ refuse("foreign_engine");
289
+ revalidateMemoryInitEngineRelease(payload.engine.release_pairing.facts, payload.engine.release_pairing.sha256, engine);
290
+ const canon = source.canonProvenance(), engineManifest = engine.manifest();
291
+ const layerManifest = JSON.parse(layer.files().get("dd-flow/manifest.json").toString("utf8"));
292
+ if (release.canon.version !== canon.version || release.canon.commit !== canon.commit || release.canon.archive.sha256 !== canon.archive_sha256
293
+ || release.engine.package !== engineManifest.package_name || release.engine.version !== engineManifest.package_version
294
+ || release.engine.archive.sha256 !== payload.engine.release_pairing.facts.archive_sha256
295
+ || layerManifest.source_commit !== canon.commit || layerManifest.canon_version_at_source_commit !== canon.version)
296
+ refuse("foreign_release_context");
297
+ const impactEntries = payload.impact.files.map(reference => {
298
+ const bytes = Buffer.from(reference.base64, "base64");
299
+ if (bytes.length > 1024 * 1024 || bytes.toString("base64") !== reference.base64 || hash(bytes) !== reference.sha256)
300
+ refuse("impact_bytes_changed");
301
+ return { ...JSON.parse(bytes.toString("utf8")), source_reference: { path: reference.path, sha256: reference.sha256 } };
302
+ });
303
+ if (hybridDigest(adjacentMigrationChain(payload.current.canon_version, source.canonProvenance().version, impactEntries)) !== hybridDigest(payload.impact.chain))
304
+ refuse("changed_impact_chain");
305
+ if (payload.current.inventory.sha256 !== hybridDigest(payload.current.inventory.inventory)
306
+ || payload.current.inventory.inventory.target_layer_sha256 !== layer.sha256
307
+ || payload.current.inventory.inventory.workspace_root !== scope.workspaceRoot
308
+ || hash(Buffer.from(payload.current.manifest_base64, "base64")) !== payload.current.inventory.inventory.current_manifest.sha256)
309
+ refuse("foreign_current_context");
310
+ const oldManifest = JSON.parse(Buffer.from(payload.current.manifest_base64, "base64").toString("utf8"));
311
+ if (oldManifest.source_commit !== payload.current.source_commit || oldManifest.canon_version_at_source_commit !== (payload.current.flow_pack_canon_version ?? payload.current.canon_version))
312
+ refuse("foreign_current_context");
313
+ const oldTree = payload.current.old_tree ? readUpgradePreservationBaseline(payload.current.old_tree, payload.current.inventory) : null;
314
+ const baselineLayer = payload.current.baseline_layer ? files(payload.current.baseline_layer, true) : null;
315
+ return { payload, sha256, source, layer, execution, engine, receipts, receiptBytes, release, oldTree, baselineLayer, bytes: Buffer.from(bytes) };
316
+ }
317
+ catch (error) {
318
+ if (error instanceof AppError)
319
+ throw error;
320
+ return refuse("invalid_retained_selection");
321
+ }
322
+ }
323
+ /** Pre-storage selection only: no SQLite open, project sync, RUN or provider.
324
+ * The receipt distinguishes never-selected from lost/partial selection, so a
325
+ * retry cannot silently replace its resolved profiles or selected release. */
326
+ export class PreparedMemoryUpgradeSelection {
327
+ sha256;
328
+ #bytes;
329
+ #payload;
330
+ #home;
331
+ #candidate;
332
+ #source;
333
+ #layer;
334
+ #engine;
335
+ #env;
336
+ constructor(home, candidate, payload, source, layer, engine, env) {
337
+ this.#home = home;
338
+ this.#candidate = candidate;
339
+ this.#payload = structuredClone(payload);
340
+ this.#bytes = Buffer.from(JSON.stringify(payload));
341
+ if (this.#bytes.length > limit)
342
+ refuse("unbounded_selection");
343
+ this.sha256 = hash(this.#bytes);
344
+ this.#source = source;
345
+ this.#layer = layer;
346
+ this.#engine = engine;
347
+ this.#env = { ...env };
348
+ Object.freeze(this);
349
+ }
350
+ static prepare(home, scope, candidate, options) {
351
+ const original = captureMemoryUpgradeRequestCandidate(candidate, home, scope, candidate.objective);
352
+ const locations = paths(candidate);
353
+ if (exists(locations.payload) || exists(locations.receipt))
354
+ refuse("restore_existing_selection");
355
+ assertImportAdmission(home);
356
+ const raw = readMemoryUpgradeRequestInput(original, scope, candidate.objective).selection;
357
+ const source = PreparedInstalledFlowSource.fromInstalledRelease(home, raw.target_installation_sha256, "mb-upgrade");
358
+ const layer = PreparedMemoryLayer.fromInstalledRelease(home, raw.target_installation_sha256, options.now);
359
+ const engine = selectMemoryInitInstalledEngine(home, raw.target_installation_sha256, raw.engine_checksum ?? undefined);
360
+ const receipts = new Map(options.receipts ?? []);
361
+ const ordered = (entries) => [...entries].sort(([a], [b]) => a.localeCompare(b));
362
+ if (hybridDigest(ordered([...receipts])) !== hybridDigest(ordered(raw.receipts)))
363
+ refuse("execution_receipt_authority_missing");
364
+ const execution = FrozenGenericExecution.prepare(descriptorData(source), readOperatorProfiles(home), raw.execution, { requestId: scope.requestId, projectRoot: scope.projectRoot, workspace: scope.workspaceRoot }, receipts);
365
+ const consumed = [execution.settings.attached_session_receipt, execution.settings.delegation.native_capacity_receipt].filter(ref => ref !== null);
366
+ const selectedReceipts = [];
367
+ const receiptBlobs = [];
368
+ for (const reference of consumed) {
369
+ const content = options.receiptBytes?.get(reference.sha256);
370
+ if (!content || content.byteLength > 256 * 1024 || hash(content) !== reference.sha256)
371
+ refuse("execution_receipt_bytes_missing");
372
+ selectedReceipts.push([reference.sha256, structuredClone(receipts.get(reference.sha256))]);
373
+ receiptBlobs.push([reference.sha256, Buffer.from(content).toString("base64")]);
374
+ }
375
+ const pairing = prepareMemoryInitEngineRelease(home, raw.target_installation_sha256, engine), manifest = engine.manifest();
376
+ const selected = readSetupStatus(home), installation = selected.installation;
377
+ if (!installation || selected.installation_readiness !== "installed" || installation.manifest_sha256 !== raw.target_installation_sha256)
378
+ refuse("target_changed");
379
+ const git = inspectMemoryGitExecutable(options.env);
380
+ const policy = observeMemoryGitPolicySelection(options.env, { scope, gitExecutable: git.executable, selection: raw.git_policy });
381
+ const baseline = current(home, scope, layer, options.env, undefined, git.executable);
382
+ if (policy.workspace_branch !== baseline.workspace.branch || policy.initial_head !== baseline.workspace.head)
383
+ refuse("git_selection_changed");
384
+ const payload = { schema_id: "dd-flow/memory-upgrade-selection@1", scope: { ...scope }, request_input_sha256: hybridDigest(original),
385
+ ...baseline, git: { ...git, policy }, impact: impacts(layer, baseline.current.canon_version, source.canonProvenance().version), source: { sha256: source.sha256, snapshot: source.snapshot() },
386
+ release: { manifest_sha256: installation.manifest_sha256, manifest_base64: installation.manifest_bytes_base64 },
387
+ layer: { sha256: layer.sha256, installation_sha256: layer.installationSha256, files: [...layer.files()].map(([logical, content]) => ({ path: logical, base64: content.toString("base64") })) },
388
+ execution: { sha256: execution.sha256, snapshot: execution.snapshot(), receipts: selectedReceipts, receipt_blobs: receiptBlobs },
389
+ engine: { identity: { package_name: manifest.package_name, package_version: manifest.package_version, engine_version: manifest.engine_version,
390
+ snapshot_root: manifest.snapshot_root, integrity_checksum: manifest.integrity.checksum }, manifest_sha256: engine.manifestSha256, release_pairing: pairing } };
391
+ assertMixedCurrentContext(payload.current, payload.impact);
392
+ return new PreparedMemoryUpgradeSelection(home, candidate, payload, source, layer, engine, options.env);
393
+ }
394
+ retain() {
395
+ const p = this.#payload, prior = readMemoryUpgradeSelection(this.#home, p.scope);
396
+ if (prior) {
397
+ if (prior.sha256 !== this.sha256)
398
+ refuse("selection_changed");
399
+ return { sha256: this.sha256, changed: false };
400
+ }
401
+ assertImportAdmission(this.#home);
402
+ if (hybridDigest(captureMemoryUpgradeRequestCandidate(this.#candidate, this.#home, p.scope, this.#candidate.objective)) !== p.request_input_sha256)
403
+ refuse("request_changed");
404
+ this.#source.revalidate();
405
+ if (p.git) {
406
+ const raw = readMemoryUpgradeRequestInput(captureMemoryUpgradeRequestCandidate(this.#candidate, this.#home, p.scope, this.#candidate.objective), p.scope, this.#candidate.objective).selection;
407
+ if (hash(readBoundedReleaseFile(p.git.executable, 64 * 1024 * 1024)) !== p.git.probe.executable_sha256
408
+ || hybridDigest(observeMemoryGitPolicySelection(this.#env, { scope: p.scope, gitExecutable: p.git.executable, selection: raw.git_policy })) !== hybridDigest(p.git.policy))
409
+ refuse("git_selection_changed");
410
+ }
411
+ const installedAt = JSON.parse(this.#layer.files().get("dd-flow/manifest.json").toString("utf8")).installed_at;
412
+ if (PreparedMemoryLayer.fromInstalledRelease(this.#home, this.#layer.installationSha256, installedAt).sha256 !== this.#layer.sha256
413
+ || hybridDigest(current(this.#home, p.scope, this.#layer, this.#env, p.current.baseline_layer?.installation_sha256 ?? null, p.git?.executable, p.current.project_links !== undefined, p.current.project_files !== undefined)) !== hybridDigest({ current: p.current, workspace: p.workspace })
414
+ || hybridDigest(impacts(this.#layer, p.current.canon_version, this.#source.canonProvenance().version)) !== hybridDigest(p.impact))
415
+ refuse("selection_changed_before_retention");
416
+ revalidateMemoryInitEngineRelease(p.engine.release_pairing.facts, p.engine.release_pairing.sha256, this.#engine);
417
+ const locations = paths(this.#candidate);
418
+ try {
419
+ publishImmutableInput(locations.receipt, Buffer.from(JSON.stringify({ schema_id: "dd-flow/memory-upgrade-selection-receipt@1", scope: p.scope,
420
+ request_input_sha256: p.request_input_sha256, selection_sha256: this.sha256 })), "memory_upgrade_selection_conflict");
421
+ publishImmutableInput(locations.payload, this.#bytes, "memory_upgrade_selection_conflict");
422
+ if (readMemoryUpgradeSelection(this.#home, p.scope)?.sha256 !== this.sha256)
423
+ refuse("selection_readback_failed");
424
+ return { sha256: this.sha256, changed: true };
425
+ }
426
+ catch (error) {
427
+ throw new AppError("memory_upgrade_selection_incomplete", "Upgrade selection requires exact request reconciliation", 1, { effect: "partial", request_id: p.scope.requestId, cause: errorRecord(error), next_action: "reconcile_exact_request" });
428
+ }
429
+ }
430
+ }
@@ -0,0 +1,60 @@
1
+ import { AppError } from "../shared/errors.js";
2
+ import { PreparedDescriptorRunStart } from "./descriptor-run-start.js";
3
+ import { readRunStartRequest } from "./run-start-custody.js";
4
+ import { PreparedMemoryUpgradeBootstrapReadiness } from "./memory-upgrade-bootstrap-readiness.js";
5
+ import { readUpgradeStorageReadiness, assertMemoryUpgradeProjectCurrent, assertUpgradeExecutionStorage } from "./memory-upgrade-storage-readiness.js";
6
+ import { restoreMemoryUpgradeSelection, restoreMemoryUpgradeSelectionData } from "./memory-upgrade-selection.js";
7
+ import { PreparedProjectRegistration, registerPreparedProject } from "./projects.js";
8
+ import { prepareMemoryUpgradeRunBundle } from "./memory-init-run-bundle.js";
9
+ import { validateMemoryInitStartupScope } from "./memory-init-startup-custody.js";
10
+ function refuse(reason) {
11
+ throw new AppError("memory_upgrade_start_invalid", "Upgrade RUN requires its retained request and accepted storage", 1, { reason, effect: "no_effect", next_action: "reconcile_exact_request" });
12
+ }
13
+ /** Register only metadata for an existing MB after target storage admission.
14
+ * Never use init's draft publication or revive an archived project. */
15
+ export function registerMemoryUpgradeProject(context, scope) {
16
+ const existing = context.db.get("SELECT status FROM projects WHERE root=?", [scope.projectRoot]);
17
+ if (existing) {
18
+ if (existing.status !== "active")
19
+ refuse("project_not_active");
20
+ return;
21
+ }
22
+ const selected = restoreMemoryUpgradeSelection(context.ddFlowHome, scope);
23
+ assertMemoryUpgradeProjectCurrent(selected, scope, context.env);
24
+ assertUpgradeExecutionStorage(context, selected.engine);
25
+ const registration = PreparedProjectRegistration.prepare({ root: scope.projectRoot });
26
+ context.db.writeTransaction(() => {
27
+ const current = context.db.get("SELECT status FROM projects WHERE root=?", [scope.projectRoot]);
28
+ if (current) {
29
+ if (current.status !== "active")
30
+ refuse("project_not_active");
31
+ return;
32
+ }
33
+ registerPreparedProject(context, registration);
34
+ }, "memoryUpgrade.registerProject");
35
+ }
36
+ /** Storage admission precedes this operation. The existing RUN owner handles
37
+ * allocation/publication and exact retries; no Session or agent is dispatched.
38
+ * Restore a published request before observing mutable project/tool state. */
39
+ export function startMemoryUpgradeRun(context, scope) {
40
+ validateMemoryInitStartupScope(scope);
41
+ if (context.db.isOwnedWriteTransaction?.())
42
+ refuse("startup_outside_writer_required");
43
+ const project = context.db.get("SELECT id FROM projects WHERE root=? AND status='active'", [scope.projectRoot]);
44
+ if (!project)
45
+ refuse("project_not_active");
46
+ const request = readRunStartRequest(context, project.id, scope.requestId);
47
+ if (request) {
48
+ if (request.payload.project_root !== scope.projectRoot || request.payload.workspace_root !== scope.workspaceRoot
49
+ || request.payload.initial_index.flow_kind !== "mb-upgrade")
50
+ refuse("run_request_changed");
51
+ return PreparedDescriptorRunStart.restore(context, { projectRoot: scope.projectRoot, requestId: scope.requestId }).apply(context);
52
+ }
53
+ const storage = readUpgradeStorageReadiness(context, scope);
54
+ if (!storage?.receipt.selection)
55
+ refuse("accepted_storage_required");
56
+ PreparedMemoryUpgradeBootstrapReadiness.prepareForRun(context, scope).apply(context);
57
+ const retained = storage.receipt.selection;
58
+ const selected = restoreMemoryUpgradeSelectionData(Buffer.from(retained.payload_json), retained.sha256, retained.request_input, scope);
59
+ return PreparedDescriptorRunStart.prepare(context, { bundle: prepareMemoryUpgradeRunBundle(context, selected) }).apply(context);
60
+ }