@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.
Files changed (197) hide show
  1. package/CHANGELOG.md +59 -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 +69 -8
  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,340 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { createHash } from "node:crypto";
4
+ import { AppError } from "../shared/errors.js";
5
+ import { assertUpgradeRunStorageReady } from "./migrations.js";
6
+ import { hybridDigest } from "./hybrid-contracts.js";
7
+ import { appendAudit } from "./audit.js";
8
+ import { restoreMemoryUpgradeSelection, restoreMemoryUpgradeSelectionData, decodeMemoryUpgradeSelection } from "./memory-upgrade-selection.js";
9
+ import { prepareMemoryUpgradeInventory, matchesRetainedMemoryUpgradeInventory } from "./memory-upgrade-inventory.js";
10
+ import { observeDescriptorWorkspace } from "./descriptor-run-start.js";
11
+ import { PreparedMemoryInitRuntimeStorage } from "./memory-init-storage.js";
12
+ import { PreparedMemoryInitResourceStorage } from "./memory-init-resource-storage.js";
13
+ import { resourceHome } from "./managed-processes.js";
14
+ import semver from "semver";
15
+ import { PreparedMemoryLayer } from "./memory-layer.js";
16
+ import { readBoundedReleaseFile } from "./release-source.js";
17
+ import { observeMemoryGitPolicySelection } from "./memory-init-git-policy.js";
18
+ import { readRunStartRequest, hasRunStartTable } from "./run-start-custody.js";
19
+ import { runContextRelocations, projectPacketLocators } from "./stage-context.js";
20
+ // One historical decode per connection. SQL bytes, logical scope and audit
21
+ // custody are still observed on every read; callers receive private copies.
22
+ const retainedDecodes = new WeakMap();
23
+ function refuse(reason) {
24
+ throw new AppError("upgrade_storage_request_invalid", "Upgrade storage readiness must retain its original request", 1, { reason, effect: "no_effect", next_action: "reconcile_exact_request" });
25
+ }
26
+ function project(context, scope) {
27
+ if (!scope || Object.keys(scope).sort().join() !== "projectRoot,requestId,workspaceRoot"
28
+ || typeof scope.requestId !== "string" || !/^[A-Za-z0-9][A-Za-z0-9_.-]{0,95}$/.test(scope.requestId))
29
+ refuse("invalid_scope");
30
+ if ([scope.projectRoot, scope.workspaceRoot].some(root => typeof root !== "string" || !path.isAbsolute(root) || path.resolve(root) !== root))
31
+ refuse("invalid_scope");
32
+ const selected = context.db.get("SELECT id FROM projects WHERE root = ? AND status = 'active'", [scope.projectRoot]);
33
+ if (selected)
34
+ return selected.id;
35
+ const candidates = hasRunStartTable(context) ? context.db.all("SELECT p.id,p.root,s.run_id FROM projects p JOIN run_start_requests s ON s.project_id=p.id WHERE p.status='active' AND s.request_id=? AND json_extract(s.payload_json,'$.project_root')=? AND json_extract(s.payload_json,'$.workspace_root')=?", [scope.requestId, scope.projectRoot, scope.workspaceRoot]) : [];
36
+ const matches = candidates.filter(row => {
37
+ const request = readRunStartRequest(context, row.id, scope.requestId);
38
+ return request?.published && request.payload.initial_index.flow_kind === 'mb-upgrade'
39
+ && projectPacketLocators(scope.projectRoot, runContextRelocations(context, row.id, row.run_id)) === row.root;
40
+ });
41
+ if (matches.length !== 1)
42
+ refuse("project_not_active");
43
+ return matches[0].id;
44
+ }
45
+ /** Physical imports use the source request's immutable logical scope. This is
46
+ * a historical read projection, never new storage/RUN admission. */
47
+ export function resolveUpgradeHistoricalScope(context, scope) {
48
+ const projectId = project(context, scope);
49
+ const row = hasRunStartTable(context) ? context.db.get("SELECT run_id FROM run_start_requests WHERE project_id=? AND request_id=?", [projectId, scope.requestId]) : undefined;
50
+ const relocations = row ? runContextRelocations(context, projectId, row.run_id) : [];
51
+ if (relocations.length) {
52
+ const request = readRunStartRequest(context, projectId, scope.requestId);
53
+ if (request?.published && request.payload.initial_index.flow_kind === 'mb-upgrade') {
54
+ const original = { requestId: scope.requestId, projectRoot: request.payload.project_root, workspaceRoot: request.payload.workspace_root };
55
+ if (projectPacketLocators(original.projectRoot, relocations) === scope.projectRoot && projectPacketLocators(original.workspaceRoot, relocations) === scope.workspaceRoot)
56
+ return { projectId, scope: original };
57
+ }
58
+ }
59
+ return { projectId, scope };
60
+ }
61
+ function scopeDirectories(scope) {
62
+ for (const root of [scope.projectRoot, scope.workspaceRoot]) {
63
+ try {
64
+ if (typeof root !== "string" || fs.realpathSync(root) !== root || !fs.statSync(root).isDirectory())
65
+ refuse("noncanonical_scope");
66
+ }
67
+ catch (error) {
68
+ if (error instanceof AppError)
69
+ throw error;
70
+ refuse("scope_unavailable");
71
+ }
72
+ }
73
+ }
74
+ const key = (projectId, requestId) => `mb-upgrade.storage.${projectId}.${requestId}`;
75
+ /** The selected engine issues these facts; SQL custody preserves them after
76
+ * the pre-context files are retired. Never rebuild historical evidence. */
77
+ export function upgradeStorageMigration(value, selectionSha256) {
78
+ const migration = value, sha = (value) => typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
79
+ if (!migration || !migration.request || !path.isAbsolute(migration.request.path) || !sha(migration.request.sha256)
80
+ || !migration.proof?.reference || migration.proof.reference.path !== migration.request.path + ".result.json"
81
+ || !sha(migration.proof.reference.sha256) || !Array.isArray(migration.proof.evidence) || migration.proof.evidence.length !== 5)
82
+ refuse("invalid_migration_proof");
83
+ const kinds = ["writer-drain", "storage-backup", "storage-plan", "storage-apply", "storage-verification"];
84
+ migration.proof.evidence.forEach((entry, index) => {
85
+ if (!entry || typeof entry.base64 !== "string" || entry.base64.length > 1024 * 1024)
86
+ refuse("invalid_migration_evidence");
87
+ const raw = Buffer.from(entry.base64, "base64");
88
+ if (entry.kind !== kinds[index] || entry.path !== `receipts/${entry.kind}.json` || raw.toString("base64") !== entry.base64
89
+ || createHash("sha256").update(raw).digest("hex") !== entry.sha256)
90
+ refuse("invalid_migration_evidence");
91
+ let evidence;
92
+ try {
93
+ evidence = JSON.parse(raw.toString("utf8"));
94
+ }
95
+ catch {
96
+ return refuse("invalid_migration_evidence");
97
+ }
98
+ if (evidence.schema_id !== "dd-flow/paired-storage-evidence@1" || evidence.kind !== entry.kind
99
+ || evidence.request_sha256 !== migration.request.sha256 || evidence.selection_sha256 !== selectionSha256)
100
+ refuse("foreign_migration_evidence");
101
+ });
102
+ return migration;
103
+ }
104
+ /** Shared read-only project fence before either storage bootstrap or cutover. */
105
+ export function assertMemoryUpgradeProjectCurrent(selected, scope, env) {
106
+ const p = selected.payload;
107
+ if (p.git && createHash("sha256").update(readBoundedReleaseFile(p.git.executable, 64 * 1024 * 1024)).digest("hex") !== p.git.probe.executable_sha256)
108
+ refuse("selected_git_changed");
109
+ if (p.git && hybridDigest(observeMemoryGitPolicySelection(env, { scope, gitExecutable: p.git.executable,
110
+ selection: { route: p.git.policy.route, integrationBranch: p.git.policy.integration_branch } })) !== hybridDigest(p.git.policy))
111
+ refuse("git_policy_changed");
112
+ if (hybridDigest(observeDescriptorWorkspace({ env }, scope.projectRoot, scope.workspaceRoot, p.git?.executable)) !== hybridDigest(p.workspace)
113
+ || !matchesRetainedMemoryUpgradeInventory(prepareMemoryUpgradeInventory(scope.workspaceRoot, selected.layer, selected.baselineLayer ?? undefined), p.current.inventory))
114
+ refuse("current_context_changed");
115
+ }
116
+ /** Current physical admission in the bound engine, separate from historical
117
+ * migration receipts. Shared by initial bootstrap and relocated RUN stages. */
118
+ export function assertUpgradeExecutionStorage(context, engine) {
119
+ if (context.db.isOwnedWriteTransaction?.())
120
+ refuse("preparation_outside_writer_required");
121
+ const proof = engine.admitMemoryUpgradeStorage(context.ddFlowHome, resourceHome(context));
122
+ const stores = [PreparedMemoryInitRuntimeStorage.prepareUpgrade(context).facts(), PreparedMemoryInitResourceStorage.prepare(context).facts()];
123
+ const runProof = proof.facts.run, resourceProof = proof.facts.resource;
124
+ if (stores.some(local => {
125
+ const remote = local.store_kind === "run" ? runProof : resourceProof;
126
+ return local.store_path !== remote.store_path || local.schema_sha256 !== remote.schema_sha256
127
+ || local.executing_writer_version !== remote.executing_writer_version;
128
+ }))
129
+ refuse("executing_store_context_changed");
130
+ return proof;
131
+ }
132
+ /** New bootstrap path: source/target policy comes only from retained input.
133
+ * Physical stores are observed now, not inferred from a project's MB version. */
134
+ function frozenProof(context, scope, retained, retainedMigration) {
135
+ const original = retained ? null : restoreMemoryUpgradeSelection(context.ddFlowHome, scope);
136
+ const selected = original ?? restoreMemoryUpgradeSelectionData(Buffer.from(retained.payload_json, "utf8"), retained.sha256, retained.request_input, scope), p = selected.payload;
137
+ const requestInput = retained?.request_input ?? original.original.input;
138
+ if (p.impact.chain.status !== "ok")
139
+ refuse("incomplete_retained_impact_chain");
140
+ if (!retained && selected.baselineLayer) {
141
+ const baseline = PreparedMemoryLayer.fromCommittedCanon(context.ddFlowHome, JSON.parse(Buffer.from(p.current.manifest_base64, "base64").toString("utf8")), selected.baselineLayer.installationSha256);
142
+ if (!baseline || baseline.sha256 !== selected.baselineLayer.sha256 || baseline.installationSha256 !== selected.baselineLayer.installationSha256)
143
+ refuse("canonical_baseline_changed");
144
+ }
145
+ assertMemoryUpgradeProjectCurrent(selected, scope, context.env);
146
+ const compatibility = JSON.parse(selected.layer.files().get("dd-flow/compatibility.json").toString("utf8"));
147
+ const contract = JSON.parse(selected.layer.files().get("dd-flow/flow-contract.json").toString("utf8"));
148
+ const manifest = selected.engine.manifest();
149
+ if (compatibility.memory_bank_version !== selected.release.canon.version || compatibility.dd_flow_cli.flow_contract !== contract.id
150
+ || compatibility.dd_flow_cli.package_name !== manifest.package_name || compatibility.engine.package_name !== manifest.package_name
151
+ || !semver.valid(compatibility.dd_flow_cli.min_version) || semver.lt(manifest.package_version, compatibility.dd_flow_cli.min_version)
152
+ || !semver.satisfies(manifest.package_version, compatibility.engine.version_range, { includePrerelease: true }))
153
+ refuse("retained_engine_incompatible");
154
+ const proof = assertUpgradeExecutionStorage(context, selected.engine);
155
+ let migration;
156
+ const file = original ? path.join(path.dirname(original.original.candidate.retain().file), "storage-migration.json")
157
+ : retainedMigration ? upgradeStorageMigration(retainedMigration, selected.sha256).request.path : undefined;
158
+ if (file && fs.existsSync(file)) {
159
+ const request = { path: file, sha256: createHash("sha256").update(readBoundedReleaseFile(file, 16 * 1024 * 1024)).digest("hex") };
160
+ const response = selected.engine.transitionMemoryUpgradeStorage({ home: context.ddFlowHome, resourceHome: resourceHome(context), scope,
161
+ selectionSha256: selected.sha256, action: "status", expectedSha256: request.sha256 });
162
+ const result = response.result;
163
+ if (result?.status !== "applied" || !result.proof)
164
+ refuse("migration_evidence_incomplete");
165
+ migration = upgradeStorageMigration({ request, proof: result.proof }, selected.sha256);
166
+ if (retainedMigration && hybridDigest(migration) !== hybridDigest(retainedMigration))
167
+ refuse("migration_evidence_changed");
168
+ }
169
+ else if (retainedMigration)
170
+ refuse("migration_request_unavailable");
171
+ return { selectionSha256: selected.sha256, selection: { sha256: selected.sha256,
172
+ payload_json: selected.bytes.toString("utf8"), request_input: requestInput }, proof: {
173
+ schema_id: migration ? "dd-flow/mb-upgrade-storage-readiness@2" : "dd-flow/mb-upgrade-storage-readiness@1",
174
+ route: migration ? "migration" : "verified-no-op", ...(migration ? { migration } : {}), selection_sha256: selected.sha256,
175
+ source_version: p.current.canon_version, target_version: selected.release.canon.version,
176
+ chain: p.impact.chain.steps, current_provenance: p.current.inventory.inventory.current_manifest,
177
+ target: { canon: selected.source.canonProvenance(), installation_sha256: p.release.manifest_sha256,
178
+ layer_sha256: selected.layer.sha256, pack_sha256: selected.layer.packSha256 }, selected_engine_storage: proof,
179
+ stores: [proof.facts.run, proof.facts.resource].map(store => ({ store_kind: store.store_kind, store_path: store.store_path,
180
+ store_dev: store.store_dev, store_ino: store.store_ino, schema_sha256: store.schema_sha256, executing_writer_version: store.executing_writer_version }))
181
+ } };
182
+ }
183
+ /** Historical evidence, not present-day physical admission. Project sync may
184
+ * change current files without rewriting the original upgrade's storage proof.
185
+ * Workspace/repository eligibility belongs to the upgrade target preflight.
186
+ */
187
+ export function readUpgradeStorageReadiness(context, scope) {
188
+ return context.db.readSnapshot(() => retainedStorage(context, scope));
189
+ }
190
+ function retainedStorage(context, scope) {
191
+ let logical;
192
+ try {
193
+ logical = resolveUpgradeHistoricalScope(context, scope);
194
+ }
195
+ catch (error) {
196
+ // A first upgrade may observe an existing MB in a new runtime Home. This
197
+ // is absence of history, not registration or permission to revive it.
198
+ if (error instanceof AppError && error.details.reason === 'project_not_active'
199
+ && !context.db.get('SELECT 1 FROM projects WHERE root=?', [scope.projectRoot])
200
+ && !(hasRunStartTable(context) && context.db.get("SELECT 1 FROM run_start_requests WHERE request_id=? AND json_extract(payload_json,'$.project_root')=?", [scope.requestId, scope.projectRoot])))
201
+ return null;
202
+ throw error;
203
+ }
204
+ const projectId = logical.projectId;
205
+ scope = logical.scope;
206
+ const row = context.db.get("SELECT value_json FROM runtime_config WHERE key = ?", [key(projectId, scope.requestId)]);
207
+ if (!row) {
208
+ if (context.db.get("SELECT 1 FROM audit_events WHERE project_id=? AND event_type='memory.upgrade_storage_ready' AND json_extract(payload_json,'$.request_id')=? AND json_extract(payload_json,'$.selection_sha256') IS NOT NULL LIMIT 1", [projectId, scope.requestId]))
209
+ refuse("storage_readiness_custody_missing");
210
+ return null;
211
+ }
212
+ if (Buffer.byteLength(row.value_json) > 68 * 1024 * 1024)
213
+ refuse("unbounded_retained_receipt");
214
+ const cached = retainedDecodes.get(context.db);
215
+ if (cached?.raw === row.value_json && cached.value.receipt.project_id === projectId
216
+ && hybridDigest(cached.value.receipt.scope) === hybridDigest(scope)) {
217
+ assertRetainedReadinessAudit(context, projectId, scope, cached.value);
218
+ return structuredClone(cached.value);
219
+ }
220
+ let receipt;
221
+ try {
222
+ receipt = JSON.parse(row.value_json);
223
+ }
224
+ catch {
225
+ return refuse("invalid_retained_receipt");
226
+ }
227
+ const frozen = receipt?.schema_id === "dd-flow/mb-upgrade-storage-request@2" || receipt?.schema_id === "dd-flow/mb-upgrade-storage-request@3";
228
+ const migrated = receipt?.schema_id === "dd-flow/mb-upgrade-storage-request@3";
229
+ if (!receipt || Object.keys(receipt).sort().join() !== (frozen ? "project_id,proof,schema_id,scope,selection" : "project_id,proof,schema_id,scope")
230
+ || !["dd-flow/mb-upgrade-storage-request@1", "dd-flow/mb-upgrade-storage-request@2", "dd-flow/mb-upgrade-storage-request@3"].includes(receipt.schema_id) || receipt.project_id !== projectId
231
+ || hybridDigest(receipt.scope) !== hybridDigest(scope) || !receipt.proof
232
+ || receipt.proof.schema_id !== (migrated ? "dd-flow/mb-upgrade-storage-readiness@2" : "dd-flow/mb-upgrade-storage-readiness@1")
233
+ || receipt.proof.route !== (migrated ? "migration" : "verified-no-op"))
234
+ refuse("changed_request");
235
+ if (receipt.proof.selection_sha256 !== undefined && (typeof receipt.proof.selection_sha256 !== "string"
236
+ || !/^[a-f0-9]{64}$/.test(receipt.proof.selection_sha256)))
237
+ refuse("changed_request");
238
+ const sha256 = hybridDigest(receipt);
239
+ if (!frozen)
240
+ return { receipt, sha256 };
241
+ const retained = receipt.selection;
242
+ if (!retained || Object.keys(retained).sort().join() !== "payload_json,request_input,sha256" || typeof retained.payload_json !== "string"
243
+ || retained.sha256 !== receipt.proof.selection_sha256)
244
+ refuse("invalid_retained_selection");
245
+ const selection = decodeMemoryUpgradeSelection(Buffer.from(retained.payload_json, "utf8"), retained.sha256, retained.request_input, scope);
246
+ if (migrated)
247
+ upgradeStorageMigration(receipt.proof.migration, retained.sha256);
248
+ const value = { receipt, sha256, selection };
249
+ assertRetainedReadinessAudit(context, projectId, scope, value);
250
+ retainedDecodes.set(context.db, { raw: row.value_json, value });
251
+ return structuredClone(value);
252
+ }
253
+ function assertRetainedReadinessAudit(context, projectId, scope, value) {
254
+ const { receipt, sha256 } = value, retained = receipt.selection;
255
+ if (!retained)
256
+ refuse("invalid_retained_selection");
257
+ const audits = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type='memory.upgrade_storage_ready' AND json_extract(payload_json,'$.request_id')=? LIMIT 2", [projectId, scope.requestId]);
258
+ if (audits.length !== 1 || audits.some(event => Buffer.byteLength(event.payload_json) > 16 * 1024)
259
+ || hybridDigest(JSON.parse(audits[0].payload_json)) !== hybridDigest({ request_id: scope.requestId, project_root: scope.projectRoot,
260
+ workspace_root: scope.workspaceRoot, receipt_sha256: sha256, route: receipt.proof.route, selection_sha256: retained.sha256 }))
261
+ refuse("storage_readiness_custody_missing");
262
+ }
263
+ /** Retains physical admission plus selected-engine phase evidence when storage
264
+ * changed. Legacy apply markers are insufficient for migration bootstrap.
265
+ * This is a storage dependency, not complete bootstrap or provider readiness.
266
+ */
267
+ export class PreparedUpgradeStorageReadiness {
268
+ #receipt;
269
+ #engine;
270
+ #selectionSha256;
271
+ constructor(receipt, engine, selectionSha256) {
272
+ if (Buffer.byteLength(JSON.stringify(receipt)) > 68 * 1024 * 1024)
273
+ refuse("unbounded_retained_receipt");
274
+ this.#receipt = structuredClone(receipt);
275
+ this.#engine = engine;
276
+ this.#selectionSha256 = selectionSha256;
277
+ Object.freeze(this);
278
+ }
279
+ static prepareFromSelection(context, scope) {
280
+ if (context.db.isOwnedWriteTransaction?.())
281
+ refuse("preparation_outside_writer_required");
282
+ const projectId = project(context, scope);
283
+ scopeDirectories(scope);
284
+ const prior = readUpgradeStorageReadiness(context, scope);
285
+ const selected = frozenProof(context, scope, prior?.receipt.selection, prior?.receipt.proof.migration);
286
+ const receipt = { schema_id: selected.proof.route === "migration" ? "dd-flow/mb-upgrade-storage-request@3" : "dd-flow/mb-upgrade-storage-request@2", scope: { ...scope }, project_id: projectId,
287
+ proof: selected.proof, selection: selected.selection };
288
+ if (prior && prior.sha256 !== hybridDigest(receipt))
289
+ refuse("changed_request");
290
+ return new PreparedUpgradeStorageReadiness(receipt, undefined, selected.selectionSha256);
291
+ }
292
+ static prepare(context, scope, engine) {
293
+ const projectId = project(context, scope);
294
+ scopeDirectories(scope);
295
+ const proof = assertUpgradeRunStorageReady(context, scope.projectRoot, engine);
296
+ if (proof.route !== "verified-no-op")
297
+ refuse("storage_migration_requires_physical_proof");
298
+ const receipt = { schema_id: "dd-flow/mb-upgrade-storage-request@1", scope: { ...scope }, project_id: projectId, proof };
299
+ const prior = readUpgradeStorageReadiness(context, scope);
300
+ if (prior && prior.sha256 !== hybridDigest(receipt))
301
+ refuse("changed_request");
302
+ return new PreparedUpgradeStorageReadiness(receipt, engine);
303
+ }
304
+ apply(context) {
305
+ const scope = this.#receipt.scope;
306
+ if (this.#selectionSha256 !== undefined) {
307
+ const prior = readUpgradeStorageReadiness(context, scope);
308
+ if (prior) {
309
+ if (prior.sha256 !== hybridDigest(this.#receipt))
310
+ refuse("changed_request");
311
+ // Historical acknowledgement only, never fresh RUN/storage admission.
312
+ return { ...prior, changed: false };
313
+ }
314
+ }
315
+ const proof = this.#selectionSha256 === undefined ? assertUpgradeRunStorageReady(context, scope.projectRoot, this.#engine)
316
+ : frozenProof(context, scope).proof;
317
+ if (project(context, scope) !== this.#receipt.project_id
318
+ || hybridDigest(proof) !== hybridDigest(this.#receipt.proof))
319
+ refuse("changed_after_preparation");
320
+ return context.db.writeTransaction(() => {
321
+ const prior = readUpgradeStorageReadiness(context, scope);
322
+ if (prior) {
323
+ if (prior.sha256 !== hybridDigest(this.#receipt))
324
+ refuse("changed_request");
325
+ return { ...prior, changed: false };
326
+ }
327
+ const now = context.now();
328
+ context.db.run("INSERT INTO runtime_config (key,value_json,created_at,updated_at) VALUES (?,?,?,?)", [key(this.#receipt.project_id, scope.requestId), JSON.stringify(this.#receipt), now, now]);
329
+ appendAudit(context, { projectId: this.#receipt.project_id, eventType: "memory.upgrade_storage_ready", payload: {
330
+ request_id: scope.requestId, project_root: scope.projectRoot, workspace_root: scope.workspaceRoot,
331
+ receipt_sha256: hybridDigest(this.#receipt), route: this.#receipt.proof.route,
332
+ ...(this.#selectionSha256 ? { selection_sha256: this.#selectionSha256 } : {})
333
+ } });
334
+ const accepted = readUpgradeStorageReadiness(context, scope);
335
+ if (!accepted || accepted.sha256 !== hybridDigest(this.#receipt))
336
+ refuse("storage_readiness_readback_failed");
337
+ return { ...accepted, changed: true };
338
+ }, "memoryUpgrade.storageReadiness");
339
+ }
340
+ }
@@ -81,14 +81,16 @@ export class PreparedMemoryVerificationAcceptance {
81
81
  || input.composition.result().sha256 !== input.compose.sha256))
82
82
  refuse("prepared_authority_required");
83
83
  const outputs = input.composition?.outputReferences() ?? [];
84
+ const upgrade = compose.stage === "upgrade", authoring = upgrade ? "upgrade" : "compose";
84
85
  input.checks.assertOwner({ ...input.scope, scope: "aggregate" });
85
86
  const diagnostic = result.outcome === "needs_repair";
86
- if (compose.stage !== "compose" || compose.outcome !== "accepted" || result.stage !== "verify" || !["accepted", "needs_repair"].includes(result.outcome)
87
- || result.run_id !== compose.run_id || result.run_id !== input.scope.runId || result.predecessors.length !== 1 || result.predecessors[0].stage !== "compose"
87
+ if (!["compose", "upgrade"].includes(compose.stage) || compose.outcome !== "accepted" || result.stage !== "verify" || !["accepted", "needs_repair"].includes(result.outcome)
88
+ || result.run_id !== compose.run_id || result.run_id !== input.scope.runId || result.predecessors.length !== 1 || result.predecessors[0].stage !== authoring
88
89
  || result.predecessors[0].input_sha256 !== compose.input_sha256 || result.predecessors[0].result.sha256 !== input.compose.sha256
89
- || compose.findings.some(finding => finding.severity !== "minor" && finding.status !== "resolved")
90
- || (!diagnostic && (!input.checks.qualityPassed() || result.findings.some(finding => finding.severity !== "minor" && finding.status !== "resolved"))))
90
+ || compose.findings.some(finding => finding.severity !== "minor" && finding.status !== "resolved"))
91
91
  refuse("unaccepted_or_foreign_predecessor");
92
+ if (!diagnostic && (!input.checks.qualityPassed() || result.findings.some(finding => finding.severity !== "minor" && finding.status !== "resolved")))
93
+ throw new AppError("memory_verification_acceptance_invalid", "Accepted verification requires passed selected checks and no unresolved material findings. For a real gap use needs_repair with an open material finding; a pending later REVIEW is a separate gate, not a current defect. Do not mark unresolved findings resolved or rerun settled checks merely to pass", 2, { reason: "verification_not_accepted", phase: "prepare", effect: "no_effect", recoverable: true, result_sha256: input.proposalSha256 ?? input.result.sha256 });
92
94
  const expected = input.checks.resultReferences().map(reference => hybridDigest(reference)), actual = result.details.checks.map(reference => hybridDigest(reference));
93
95
  const allowedChecks = new Set(input.checks.sourceReferences().map(({ path, sha256 }) => hybridDigest({ path, sha256 })));
94
96
  if (expected.some(reference => !actual.includes(reference))
@@ -97,26 +99,45 @@ export class PreparedMemoryVerificationAcceptance {
97
99
  const units = new Map(compose.units.map(unit => [unit.id, unit]));
98
100
  if (result.units.length !== units.size || new Set(result.units.map(unit => unit.id)).size !== units.size)
99
101
  refuse("changed_unit_coverage");
100
- const assessments = new Map(result.details.semantic_assessments.map(assessment => [assessment.unit_id, assessment]));
101
- if (assessments.size !== result.details.semantic_assessments.length || [...assessments.keys()].some(id => !units.has(id))
102
+ const selectedAssessments = upgrade ? result.details.preservation_assessments?.map(item => ({ ...item, verdict: item.outcome === "preserved" ? "accepted" : item.outcome })) : result.details.semantic_assessments;
103
+ if (!selectedAssessments)
104
+ refuse("missing_unit_assessments");
105
+ const assessments = new Map();
106
+ for (const assessment of selectedAssessments)
107
+ assessments.set(assessment.unit_id, [...assessments.get(assessment.unit_id) ?? [], assessment]);
108
+ if (!upgrade && assessments.size !== selectedAssessments.length || [...assessments.keys()].some(id => !units.has(id))
102
109
  || compose.units.some(unit => unit.applicability !== "not-applicable" && !assessments.has(unit.id)))
103
110
  refuse("incomplete_or_duplicate_assessments");
111
+ if (upgrade && hybridDigest(input.result.preservationInvariants()) !== hybridDigest(input.compose.preservationInvariants()))
112
+ refuse("changed_preservation_invariants");
104
113
  const key = (source) => memorySourceKey(input.roots && input.roots.projectRoot === input.roots.workspace && source.root === "project" ? "workspace" : source.root, source.path);
105
114
  for (const unit of result.units) {
106
- const original = units.get(unit.id), assessment = assessments.get(unit.id);
107
- if (!original || unit.aspect !== original.aspect || unit.applicability !== original.applicability || unit.applicability === "unknown"
115
+ const original = units.get(unit.id), selected = assessments.get(unit.id) ?? [];
116
+ // IDs bind assessments to accepted units; aspect is descriptive prose.
117
+ // REVIEW derives its assignments from composition, not this wording.
118
+ if (!original || unit.applicability !== original.applicability || unit.applicability === "unknown"
108
119
  || (unit.applicability === "not-applicable" ? unit.outcome !== "not-applicable" : !["accepted", ...(diagnostic ? ["needs_repair"] : [])].includes(unit.outcome))
109
- || (assessment && (!["accepted", ...(diagnostic ? ["needs_repair"] : [])].includes(assessment.verdict) || !assessment.summary.trim())))
120
+ || selected.some(assessment => !["accepted", ...(diagnostic ? ["needs_repair"] : [])].includes(assessment.verdict) || !assessment.summary.trim()))
110
121
  refuse("unit_not_accepted_or_changed");
111
122
  assertMemoryVerificationArtifactReferences(original.artifacts, unit.artifacts, outputs);
112
- if (!assessment)
113
- continue;
114
- // PreparedMemoryStageResult already binds every evidence reference to
115
- // admitted bytes. Cross-unit and earlier-stage evidence is useful context,
116
- // but cannot replace examination of this unit's own composed output.
117
- if (unit.applicability !== "not-applicable" && !assessment.evidence.some(source => original.artifacts.some(artifact => key(artifact) === key(source) && artifact.sha256 === source.sha256))) {
118
- throw new AppError("memory_verification_acceptance_invalid", "Assess the composed documents themselves and cite a direct output reference for EACH applicable unit; the listed references identify this unit's outputs. Work result receipts do not replace direct output examination", 2, { reason: "assessment_does_not_examine_composed_output", phase: "prepare", effect: "no_effect", recoverable: true,
119
- unit_id: unit.id, output_references: structuredClone(original.artifacts), result_sha256: input.result.sha256 });
123
+ for (const assessment of selected) {
124
+ // PreparedMemoryStageResult already binds every evidence reference to
125
+ // admitted bytes. Cross-unit and earlier-stage evidence is useful context,
126
+ // but cannot replace examination of this unit's own composed output.
127
+ if (unit.applicability !== "not-applicable" && !assessment.evidence.some(source => original.artifacts.some(artifact => key(artifact) === key(source) && artifact.sha256 === source.sha256))) {
128
+ throw new AppError("memory_verification_acceptance_invalid", "Assess the composed documents themselves and cite a direct output reference for EACH applicable unit; the listed references identify this unit's outputs. Work result receipts do not replace direct output examination", 2, { reason: "assessment_does_not_examine_composed_output", phase: "prepare", effect: "no_effect", recoverable: true,
129
+ unit_id: unit.id, output_references: structuredClone(original.artifacts), result_sha256: input.result.sha256 });
130
+ }
131
+ if (upgrade) {
132
+ const id = assessment.invariant_id;
133
+ const invariant = input.result.preservationInvariants().find(item => item.id === id);
134
+ const old = invariant?.evidence;
135
+ // A current output alone cannot substantiate old-to-new preservation.
136
+ // Identical old bytes may be examined through the retained RUN copy.
137
+ if (!old?.some(before => assessment.evidence.some(source => source.sha256 === before.sha256 && (key(source) === key(before) || source.root === "run"))))
138
+ throw new AppError("memory_verification_acceptance_invalid", "Compare the retained old evidence as well as the current unit output; correct the assessment without rerunning settled checks", 2, { reason: "preservation_assessment_does_not_examine_old_evidence", phase: "prepare", effect: "no_effect", recoverable: true,
139
+ unit_id: unit.id, invariant_id: id, old_references: structuredClone(old ?? []), result_sha256: input.result.sha256 });
140
+ }
120
141
  }
121
142
  }
122
143
  if (diagnostic && !result.findings.some(finding => finding.severity !== "minor" && finding.status === "open")) {
@@ -14,15 +14,29 @@ import { PreparedMemoryBaselinePreservation } from "./memory-baseline-preservati
14
14
  import { readBoundedReleaseFile } from "./release-source.js";
15
15
  import { codeCheckProfileHash, projectPolicyCheckDeclarationsFromProfile, readCodeCheckProfile, runCodeChecks, PreparedCheckEvidence } from "./code-checks.js";
16
16
  import { ObservedMemorySources } from "./memory-source-observations.js";
17
- import { memoryVerificationActivePaths, validateMemoryLintConfig } from "./memory-lint-config.js";
17
+ import { memoryVerificationActivePaths, memoryUpgradeLintGuidance, memoryVerificationSupplementalLintFiles, memoryCanonicalLintExceptions, memoryCanonicalTemplatePaths } from "./memory-lint-config.js";
18
18
  import { isMemoryCorrectionInputOwner } from "./memory-correction-input-owner.js";
19
+ import { readMemoryUpgradeBootstrapReadiness } from "./memory-upgrade-bootstrap-readiness.js";
20
+ import { readUpgradeStorageReadiness } from "./memory-upgrade-storage-readiness.js";
21
+ import { PreparedAcceptedMemoryComposition } from "./memory-accepted-composition.js";
22
+ import { RetainedExecutionBundle } from "./execution-bundles.js";
19
23
  const token = Symbol("memory-verification-checks");
20
24
  const hash = (bytes) => createHash("sha256").update(bytes).digest("hex");
21
25
  const quote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
22
26
  const profilePath = ".memory-bank/spec/engineering/code-check-profile.json";
27
+ export function memorySupplementalLintCommand(executable, workspace, files, exceptions = []) {
28
+ const config = JSON.stringify(exceptions.length ? { ignoreFindings: exceptions } : {});
29
+ return `printf '%s\\n' ${quote(config)} > "$DD_FLOW_EVIDENCE_DIR/mb-lint-active-config.json" && ${quote(executable)} --root ${quote(workspace)} --config "$DD_FLOW_EVIDENCE_DIR/mb-lint-active-config.json" --files ${files.map(quote).join(" ")} --format json > "$DD_FLOW_EVIDENCE_DIR/mb-lint-active.json"`;
30
+ }
23
31
  function refuse(reason) {
24
32
  throw new AppError("memory_verification_checks_invalid", "Verification checks lack admitted tool, policy or accepted composition custody", 2, { reason, effect: "no_effect" });
25
33
  }
34
+ /** First introduction adds obligations to mandatory lint; an existing policy
35
+ * remains immutable, including in the next correction wave. */
36
+ export function assertRetainedMemoryVerificationPolicy(previous, current) {
37
+ if (previous !== null && current !== previous)
38
+ refuse("correction_policy_requires_readmission");
39
+ }
26
40
  /** Finite project checks use the original Codex home, never a temporary
27
41
  * provider Session overlay. Keep every other application environment input. */
28
42
  export function memoryCheckEnvironment(context, projectId, runId) {
@@ -86,19 +100,29 @@ function accepted(context, input) {
86
100
  }
87
101
  const frozen = input.execution.snapshot(), scope = { requestId: frozen.scope.requestId, projectRoot: frozen.scope.projectRoot, workspaceRoot: frozen.scope.workspace };
88
102
  const run = context.db.get("SELECT workspace_root,project_root,run_root,run_dir,index_json FROM runs WHERE project_id=? AND id=?", [input.projectId, input.runId]);
89
- const bootstrap = readMemoryInitBootstrapReadiness(context, scope), lint = readMemoryInitProjectBootstrapLintCapability(context, scope);
103
+ const upgrade = input.flow.descriptor.id === "mb-upgrade";
104
+ const init = upgrade ? null : readMemoryInitBootstrapReadiness(context, scope);
105
+ const updated = upgrade ? readMemoryUpgradeBootstrapReadiness(context, scope) : null;
106
+ const bootstrap = init ?? updated;
107
+ const lint = upgrade ? updated?.authority.binding.lint ? { facts: updated.authority.binding.lint, sha256: hybridDigest(updated.authority.binding.lint) } : null
108
+ : readMemoryInitProjectBootstrapLintCapability(context, scope);
90
109
  if (!run || run.project_root !== scope.projectRoot || run.workspace_root !== scope.workspaceRoot || !bootstrap || !lint
91
110
  || bootstrap.projectId !== input.projectId || bootstrap.authority.binding.execution_sha256 !== input.execution.sha256)
92
111
  refuse("accepted_bootstrap_scope_required");
93
- const compose = readAcceptedStageData(context, { projectId: input.projectId, runId: input.runId, stage: "compose" });
94
- if (compose.data_schema_id !== "dd-flow/mb-init-compose-result@1")
112
+ const authoring = upgrade ? "upgrade" : "compose";
113
+ const compose = readAcceptedStageData(context, { projectId: input.projectId, runId: input.runId, stage: authoring });
114
+ if (compose.data_schema_id !== (upgrade ? "dd-flow/mb-upgrade-stage-result@1" : "dd-flow/mb-init-compose-result@1"))
95
115
  refuse("accepted_composition_required");
116
+ const baselineProfile = upgrade ? readUpgradeStorageReadiness(context, scope)?.selection?.payload.current.inventory.inventory.entries
117
+ .find(entry => entry.path === profilePath.slice(13))?.current?.sha256 ?? null : null;
96
118
  const active = input.corrected ? "review" : "verify";
97
119
  const { index_json, ...roots } = run, index = JSON.parse(index_json), stage = index.stage_runs.find((stage) => stage.stage === active);
98
120
  if (index.current_stage !== active || stage?.status !== "running" || typeof stage.input_sha256 !== "string" || !/^[a-f0-9]{64}$/.test(stage.input_sha256))
99
121
  refuse(input.corrected ? "active_corrected_review_required" : "active_verification_required");
100
122
  const phase = { attempt: stage.attempt ?? null, cycle: stage.cycle ?? null, input_sha256: stage.input_sha256 };
101
- return { run: roots, bootstrap, lint, compose, scope, authority: hybridDigest({ run: roots, phase, bootstrap: bootstrap.authority, lint, compose: compose.authority_sha256,
123
+ const probe = init?.authority.binding.target.tool_probes?.["mb-lint"] ?? updated?.authority.binding.lint;
124
+ const executable = init?.authority.binding.target.tools["mb-lint"] ?? updated?.authority.binding.lint?.executable;
125
+ return { run: roots, bootstrap, lint, compose, scope, probe, executable, baselineProfile, authority: hybridDigest({ run: roots, phase, bootstrap: bootstrap.authority, lint, compose: compose.authority_sha256,
102
126
  ...(input.corrected ? { corrected_sha256: input.corrected.sha256 } : {}) }) };
103
127
  }
104
128
  /** Owning CLI policy, not agent-proposed commands. The bootstrap-selected
@@ -147,30 +171,52 @@ export class PreparedMemoryVerificationChecks {
147
171
  if (context.db.isOwnedWriteTransaction?.())
148
172
  refuse("preparation_outside_writer_required");
149
173
  if (!(input.execution instanceof FrozenGenericExecution) || !(input.flow instanceof ResolvedFlowDescriptor)
150
- || input.flow.descriptor.id !== "mb-init" || input.execution.settings.flow_id !== "mb-init"
174
+ || !["mb-init", "mb-upgrade"].includes(input.flow.descriptor.id) || input.execution.settings.flow_id !== input.flow.descriptor.id
151
175
  || input.execution.settings.descriptor_sha256 !== input.flow.sha256)
152
176
  refuse("frozen_memory_selection_required");
153
177
  input.corrected?.revalidate(context);
154
178
  const selected = context.db.readSnapshot(() => accepted(context, input)), value = JSON.parse(selected.compose.bytes.toString("utf8"));
155
- const schema = JSON.parse(input.flow.stageBlob("compose", "result_schema").toString("utf8"));
179
+ const upgrade = input.flow.descriptor.id === "mb-upgrade", authoring = upgrade ? "upgrade" : "compose";
180
+ const schema = JSON.parse(input.flow.stageBlob(authoring, "result_schema").toString("utf8"));
156
181
  if (!new Ajv2020({ strict: true, strictRequired: false, validateFormats: false }).compile(schema)(value))
157
182
  refuse("accepted_composition_required");
158
183
  const compose = value;
159
- if (compose.run_id !== input.runId || compose.stage !== "compose" || compose.outcome !== "accepted")
184
+ if (compose.run_id !== input.runId || compose.stage !== authoring || compose.outcome !== "accepted")
160
185
  refuse("accepted_composition_required");
161
- const probe = selected.bootstrap.authority.binding.target.tool_probes?.["mb-lint"];
162
- if (!probe || selected.bootstrap.authority.binding.target.tools["mb-lint"] !== selected.lint.facts.executable
186
+ const probe = selected.probe;
187
+ if (!probe || selected.executable !== selected.lint.facts.executable
163
188
  || probe.executable_sha256 !== selected.lint.facts.executable_sha256 || probe.version !== selected.lint.facts.version
164
189
  || hash(readBoundedReleaseFile(selected.lint.facts.executable, 64 * 1024 * 1024)) !== probe.executable_sha256)
165
190
  refuse("selected_linter_changed");
166
- const preservation = PreparedMemoryBaselinePreservation.prepare(context, input.execution);
191
+ const bundle = input.bundle ?? input.corrected?.bundle();
192
+ if (upgrade && (!(bundle instanceof RetainedExecutionBundle) || bundle.execution.sha256 !== input.execution.sha256 || bundle.descriptor.sha256 !== input.flow.sha256))
193
+ refuse("registered_upgrade_bundle_required");
194
+ if (input.composition && (!(input.composition instanceof PreparedAcceptedMemoryComposition) || input.composition.result().sha256 !== selected.compose.result.sha256))
195
+ refuse("foreign_accepted_composition");
196
+ const preservation = upgrade ? input.corrected ?? input.composition ?? PreparedAcceptedMemoryComposition.prepare(context, { scope: input, bundle: bundle }) : PreparedMemoryBaselinePreservation.prepare(context, input.execution);
167
197
  const workspace = selected.run.workspace_root;
168
198
  const config = lintConfig(workspace);
169
199
  const corrected = input.corrected?.value();
170
- if (config)
171
- validateMemoryLintConfig(config, corrected
172
- ? memoryVerificationActivePaths(corrected.unit_inventory, corrected.assignments)
173
- : memoryVerificationActivePaths(compose.units));
200
+ let guidance;
201
+ let canonicalEntries = [];
202
+ let canonicalFiles = new Map();
203
+ let templates;
204
+ if (upgrade) {
205
+ const storage = readUpgradeStorageReadiness(context, selected.scope);
206
+ if (!storage?.selection)
207
+ refuse("retained_upgrade_inventory_required");
208
+ const diff = readAcceptedStageData(context, { projectId: input.projectId, runId: input.runId, stage: "diff-analysis" });
209
+ guidance = memoryUpgradeLintGuidance(storage.selection.payload.current.inventory.inventory.entries, JSON.parse(diff.bytes.toString("utf8")).details.path_map);
210
+ canonicalEntries = storage.selection.payload.current.inventory.inventory.entries;
211
+ canonicalFiles = new Map(storage.selection.payload.layer.files.map(file => [file.path, Buffer.from(file.base64, "base64")]));
212
+ const current = new Map(canonicalEntries.filter(entry => /^mbb\/templates\/.+\.(?:md|markdown)$/.test(entry.path) && entry.current?.kind === "file" && entry.target && ["canonical", "local-customized"].includes(entry.ownership))
213
+ .map(entry => [entry.path, readBoundedReleaseFile(path.join(workspace, ".memory-bank", entry.path), 64 * 1024 * 1024)]));
214
+ templates = memoryCanonicalTemplatePaths(canonicalEntries, canonicalFiles, current);
215
+ }
216
+ const supplementalFiles = config ? memoryVerificationSupplementalLintFiles(config, corrected
217
+ ? memoryVerificationActivePaths(corrected.unit_inventory, corrected.assignments, guidance, templates)
218
+ : memoryVerificationActivePaths(compose.units, [], guidance, templates)) : [];
219
+ const canonicalExceptions = memoryCanonicalLintExceptions(canonicalEntries, canonicalFiles, supplementalFiles);
174
220
  let observed = null;
175
221
  let exists = false;
176
222
  try {
@@ -183,6 +229,8 @@ export class PreparedMemoryVerificationChecks {
183
229
  }
184
230
  const profileRefs = input.corrected ? input.corrected.value().run_inputs : compose.units.flatMap(unit => unit.artifacts);
185
231
  const expectedProfiles = new Set(profileRefs.filter(artifact => artifact.root === "workspace" && artifact.path === profilePath).map(artifact => artifact.sha256));
232
+ if (upgrade && exists && !expectedProfiles.size && selected.baselineProfile)
233
+ expectedProfiles.add(selected.baselineProfile);
186
234
  if (expectedProfiles.size !== (exists ? 1 : 0))
187
235
  refuse("profile_presence_differs_from_composition");
188
236
  if (exists) {
@@ -194,21 +242,27 @@ export class PreparedMemoryVerificationChecks {
194
242
  const { profile: policy, hash: profileHash } = readCodeCheckProfile(workspace);
195
243
  if (profileHash !== (observed?.hashes().get(`workspace\0${profilePath}`) ?? null))
196
244
  refuse("profile_changed_during_preparation");
197
- const checks = [{ id: "MEMORY/verify/lint-schema-links", canonical_ref: "MEMORY/verify/lint-schema-links",
198
- command: `${quote(selected.lint.facts.executable)} --root ${quote(workspace)} --format json > "$DD_FLOW_EVIDENCE_DIR/mb-lint.json"`,
199
- purpose: "Mandatory bootstrap-selected Memory Bank lint, frontmatter/schema and Markdown target checks", run_at: "readiness", availability: "available", required_artifacts: ["mb-lint.json"], inputs: ["."],
200
- // The pinned linter reads project files/config and invokes Git; provider
201
- // Session configuration is not one of its execution dependencies.
202
- environment_inputs: ["PATH", "PATHEXT", "HOME", "XDG_CONFIG_HOME", "NODE_*", "UV_*", "GIT_*", "LANG", "LC_*", "TZ", "DYLD_*", "LD_*", "SYSTEMROOT", "COMSPEC", "TMPDIR", "TMP", "TEMP"] },
245
+ const lintCheck = { id: "MEMORY/verify/lint-schema-links", canonical_ref: "MEMORY/verify/lint-schema-links",
246
+ command: `${quote(selected.lint.facts.executable)} --root ${quote(workspace)} --format json > "$DD_FLOW_EVIDENCE_DIR/mb-lint.json"`,
247
+ purpose: "Mandatory bootstrap-selected Memory Bank lint, frontmatter/schema and Markdown target checks", run_at: "readiness", availability: "available", required_artifacts: ["mb-lint.json"], inputs: ["."],
248
+ // The pinned linter reads project files/config and invokes Git; provider
249
+ // Session configuration is not one of its execution dependencies.
250
+ environment_inputs: ["PATH", "PATHEXT", "HOME", "XDG_CONFIG_HOME", "NODE_*", "UV_*", "GIT_*", "LANG", "LC_*", "TZ", "DYLD_*", "LD_*", "SYSTEMROOT", "COMSPEC", "TMPDIR", "TMP", "TEMP"] };
251
+ const checks = [lintCheck,
252
+ ...(supplementalFiles.length ? [{ ...lintCheck, id: "MEMORY/verify/lint-active-documents", canonical_ref: "MEMORY/verify/lint-active-documents",
253
+ command: memorySupplementalLintCommand(selected.lint.facts.executable, workspace, supplementalFiles, canonicalExceptions),
254
+ purpose: "Mandatory independent lint of active Markdown excluded by preserved project policy",
255
+ required_artifacts: ["mb-lint-active-config.json", "mb-lint-active.json"] }] : []),
203
256
  ...projectPolicyCheckDeclarationsFromProfile(policy, "readiness")];
204
- if (input.corrected && codeCheckProfileHash(workspace, checks) !== input.corrected.verificationProfileHash(context))
205
- refuse("correction_policy_requires_readmission");
257
+ if (input.corrected)
258
+ assertRetainedMemoryVerificationPolicy(input.corrected.verificationProfileHash(context), codeCheckProfileHash(workspace, checks));
206
259
  const prepared = new PreparedMemoryVerificationChecks(token, input, selected, observed, preservation, checks, config ? hash(config) : null);
207
260
  prepared.#checkEnvironment = memoryCheckEnvironment(context, input.projectId, input.runId);
208
261
  prepared.revalidate(context);
209
262
  return prepared;
210
263
  }
211
264
  declarations() { return structuredClone(this.#checks); }
265
+ profileHash() { return this.#profileSha256; }
212
266
  assertCorrectionOwner(corrected) {
213
267
  if (!isMemoryCorrectionInputOwner(corrected) || this.#input.corrected !== corrected)
214
268
  refuse("foreign_correction_check_owner");