@deksden-com/dd-flow-cli 0.9.0-beta.144 → 0.9.0-beta.149

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 (254) hide show
  1. package/CHANGELOG.md +130 -0
  2. package/README.md +13 -8
  3. package/dist/build-info.json +7 -7
  4. package/dist/cli/command-inputs.js +13 -3
  5. package/dist/cli/command-metadata.js +51 -16
  6. package/dist/cli/diagnostic-invocation.js +46 -0
  7. package/dist/cli/diagnostic.js +120 -0
  8. package/dist/cli/help.js +67 -1
  9. package/dist/cli/hook-ingress.js +27 -3
  10. package/dist/cli/memory-flow-admission.js +9 -3
  11. package/dist/cli/memory-init-invocation.js +10 -3
  12. package/dist/cli/memory-init.js +77 -68
  13. package/dist/cli/memory-storage-admission.js +3 -2
  14. package/dist/cli/memory-storage-qualification.js +105 -0
  15. package/dist/cli/memory-storage-transition.js +123 -0
  16. package/dist/cli/memory-upgrade.js +189 -0
  17. package/dist/cli/native-hook-ingress.js +14 -4
  18. package/dist/cli/native-hook-preparation.js +55 -0
  19. package/dist/cli/response-file.js +2 -1
  20. package/dist/cli/run-cli.js +79 -40
  21. package/dist/cli/setup.js +2 -1
  22. package/dist/cli.js +18 -0
  23. package/dist/domain/diagnostic-aspects.js +103 -0
  24. package/dist/domain/diagnostic-flows.js +8 -0
  25. package/dist/domain/stage-handler-identities.js +8 -0
  26. package/dist/harness-runtime/bin/dd-agy.mjs +3 -3
  27. package/dist/harness-runtime/bin/dd-grok.mjs +3 -3
  28. package/dist/harness-runtime/bin/dd-zcode.mjs +5 -5
  29. package/dist/harness-runtime/lib/agy-error-boundary.mjs +58 -0
  30. package/dist/harness-runtime/lib/agy-native-inspection.mjs +216 -0
  31. package/dist/harness-runtime/lib/agy-native_pb.js +117 -0
  32. package/dist/harness-runtime/lib/daemon-operations.d.mts +1 -0
  33. package/dist/harness-runtime/lib/daemon-operations.mjs +84 -10
  34. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +366 -52
  35. package/dist/harness-runtime/lib/dd-agy.mjs +17 -3
  36. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +27 -13
  37. package/dist/harness-runtime/lib/dd-codex.mjs +4 -0
  38. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +4 -3
  39. package/dist/harness-runtime/lib/dd-droid.mjs +71 -29
  40. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +73 -16
  41. package/dist/harness-runtime/lib/dd-grok.mjs +6 -3
  42. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +66 -20
  43. package/dist/harness-runtime/lib/dd-opencode.mjs +30 -3
  44. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +7 -6
  45. package/dist/harness-runtime/lib/dd-zcode.mjs +44 -14
  46. package/dist/harness-runtime/lib/delegation-instructions.mjs +1 -1
  47. package/dist/harness-runtime/lib/driver-recovery.mjs +9 -8
  48. package/dist/harness-runtime/lib/entity-references.d.mts +2 -0
  49. package/dist/harness-runtime/lib/entity-references.mjs +9 -0
  50. package/dist/harness-runtime/lib/managed-daemon.d.mts +1 -1
  51. package/dist/harness-runtime/lib/managed-daemon.mjs +24 -9
  52. package/dist/harness-runtime/lib/native-children.d.mts +8 -1
  53. package/dist/harness-runtime/lib/native-children.mjs +114 -8
  54. package/dist/harness-runtime/lib/native-hook-command.mjs +4 -4
  55. package/dist/harness-runtime/lib/native-request-proof.mjs +1 -1
  56. package/dist/harness-runtime/lib/observation-clock.d.mts +2 -2
  57. package/dist/harness-runtime/lib/observation-clock.mjs +7 -4
  58. package/dist/harness-runtime/lib/opencode-plugin.mjs +3 -3
  59. package/dist/harness-runtime/lib/operation-errors.mjs +1 -1
  60. package/dist/harness-runtime/lib/provider-overload-policy.d.mts +5 -3
  61. package/dist/harness-runtime/lib/provider-overload-policy.mjs +20 -7
  62. package/dist/harness-runtime/lib/runner-events.mjs +3 -1
  63. package/dist/harness-runtime/lib/setup-progress.mjs +8 -6
  64. package/dist/harness-runtime/lib/tool-observations.mjs +1 -1
  65. package/dist/schemas/diagnostic-stage-result.schema.json +354 -0
  66. package/dist/schemas/diagnostic-work-result.schema.json +296 -0
  67. package/dist/schemas/execution-bundle.schema.json +7 -1
  68. package/dist/schemas/mb-upgrade-stage-result.schema.json +863 -0
  69. package/dist/schemas/memory-repair-work-result.schema.json +9 -2
  70. package/dist/schemas/project-flow-pack-manifest.schema.json +1 -1
  71. package/dist/schemas/release-impact.schema.json +3 -3
  72. package/dist/schemas/stage-start-response.schema.json +4 -0
  73. package/dist/services/accepted-intent.js +13 -1
  74. package/dist/services/canon.js +43 -7
  75. package/dist/services/check-environment.js +21 -0
  76. package/dist/services/check-runner-evidence.js +267 -15
  77. package/dist/services/cli-operation-classifier.js +1 -1
  78. package/dist/services/cli-upgrade-allowlist.js +1 -1
  79. package/dist/services/code-checks.js +34 -19
  80. package/dist/services/compatibility-preflight.js +5 -2
  81. package/dist/services/controller-execution.js +10 -4
  82. package/dist/services/controller-fanout.js +34 -10
  83. package/dist/services/controller-memory-task.js +45 -0
  84. package/dist/services/controller-memory-worker.js +49 -0
  85. package/dist/services/dashboard.js +2 -2
  86. package/dist/services/descriptor-run-settlement.js +5 -3
  87. package/dist/services/descriptor-run-start.js +46 -20
  88. package/dist/services/descriptor-stage-coordinator.js +66 -13
  89. package/dist/services/descriptor-stage-settlement.js +2 -1
  90. package/dist/services/descriptor-stage-start.js +40 -4
  91. package/dist/services/diagnostic-evidence.js +73 -0
  92. package/dist/services/diagnostic-report-publication.js +72 -0
  93. package/dist/services/diagnostic-report.js +67 -0
  94. package/dist/services/diagnostic-run-bundle.js +74 -0
  95. package/dist/services/diagnostic-snapshot.js +159 -0
  96. package/dist/services/diagnostic-stage-handlers.js +108 -0
  97. package/dist/services/diagnostic-stage-results.js +132 -0
  98. package/dist/services/diagnostic-start.js +84 -0
  99. package/dist/services/diagnostic-work-batch.js +34 -0
  100. package/dist/services/diagnostic-work-collection.js +97 -0
  101. package/dist/services/diagnostic-work-registration.js +101 -0
  102. package/dist/services/diagnostic-work-results.js +93 -0
  103. package/dist/services/engines.js +64 -14
  104. package/dist/services/eval-snapshots.js +186 -119
  105. package/dist/services/execution-bundles.js +19 -7
  106. package/dist/services/external-work-launch.js +5 -3
  107. package/dist/services/flow-catalog.js +13 -4
  108. package/dist/services/frozen-engine.js +148 -11
  109. package/dist/services/hook-memory-routing.js +188 -0
  110. package/dist/services/hook-recovery-guard.js +14 -0
  111. package/dist/services/hook-session-observation.js +24 -0
  112. package/dist/services/hooks.js +27 -8
  113. package/dist/services/hybrid-contracts.js +4 -13
  114. package/dist/services/hybrid-digest.js +14 -0
  115. package/dist/services/installed-flow-source.js +47 -4
  116. package/dist/services/lifecycle-invocations.js +179 -38
  117. package/dist/services/memory-accepted-composition.js +5 -4
  118. package/dist/services/memory-completed-correction-wave.js +23 -15
  119. package/dist/services/memory-compose-handler.js +163 -21
  120. package/dist/services/memory-composition-acceptance.js +186 -10
  121. package/dist/services/memory-composition-graph.js +42 -11
  122. package/dist/services/memory-composition-registration.js +23 -8
  123. package/dist/services/memory-composition-work-custody.js +14 -9
  124. package/dist/services/memory-composition-work-results.js +15 -3
  125. package/dist/services/memory-corrected-review-batch.js +10 -3
  126. package/dist/services/memory-corrected-review-findings.js +4 -2
  127. package/dist/services/memory-corrected-review-inputs.js +10 -5
  128. package/dist/services/memory-corrected-review-receipt.js +17 -9
  129. package/dist/services/memory-corrected-review-registration.js +3 -2
  130. package/dist/services/memory-correction-check-receipt.js +20 -4
  131. package/dist/services/memory-custody-snapshot.js +36 -0
  132. package/dist/services/memory-finish-evidence.js +44 -22
  133. package/dist/services/memory-finish-handler.js +37 -23
  134. package/dist/services/memory-finish-integration.js +218 -48
  135. package/dist/services/memory-finish-readiness.js +66 -19
  136. package/dist/services/memory-git-integration.js +87 -18
  137. package/dist/services/memory-historical-corrected-review.js +21 -15
  138. package/dist/services/memory-historical-original-review.js +22 -9
  139. package/dist/services/memory-historical-repair-registration.js +14 -8
  140. package/dist/services/memory-historical-second-review.js +30 -11
  141. package/dist/services/memory-init-contexts.js +44 -2
  142. package/dist/services/memory-init-controller-start.js +27 -8
  143. package/dist/services/memory-init-git-policy.js +50 -21
  144. package/dist/services/memory-init-lint-capability.js +24 -6
  145. package/dist/services/memory-init-native-capacity.js +17 -4
  146. package/dist/services/memory-init-preflight.js +21 -17
  147. package/dist/services/memory-init-request-candidate.js +73 -17
  148. package/dist/services/memory-init-request-input.js +23 -7
  149. package/dist/services/memory-init-run-bundle.js +49 -12
  150. package/dist/services/memory-init-storage.js +17 -7
  151. package/dist/services/memory-init-worktrunk-capability.js +3 -7
  152. package/dist/services/memory-layer.js +71 -6
  153. package/dist/services/memory-lint-config.js +111 -2
  154. package/dist/services/memory-native-review-copy.js +5 -67
  155. package/dist/services/memory-normal-flow-entry.js +12 -0
  156. package/dist/services/memory-ordinary-defaults.js +22 -5
  157. package/dist/services/memory-preflight-handler.js +15 -0
  158. package/dist/services/memory-repair-current-view.js +1 -1
  159. package/dist/services/memory-repair-document-delta.js +70 -33
  160. package/dist/services/memory-repair-history.js +12 -7
  161. package/dist/services/memory-repair-physical-binding.js +1 -119
  162. package/dist/services/memory-repair-publication-plan.js +5 -3
  163. package/dist/services/memory-repair-publication-source.js +1 -1
  164. package/dist/services/memory-repair-publication.js +3 -2
  165. package/dist/services/memory-repair-registration.js +2 -1
  166. package/dist/services/memory-repair-work-baseline.js +4 -2
  167. package/dist/services/memory-repair-work-completion.js +32 -12
  168. package/dist/services/memory-repair-work-results.js +21 -2
  169. package/dist/services/memory-repair-workspace.js +11 -2
  170. package/dist/services/memory-repair-writer-copy.js +2 -1
  171. package/dist/services/memory-result-binding.js +78 -12
  172. package/dist/services/memory-retained-review-authority.js +17 -8
  173. package/dist/services/memory-retained-second-baseline.js +1 -1
  174. package/dist/services/memory-retained-second-repairs.js +12 -6
  175. package/dist/services/memory-review-correction-plan.js +47 -12
  176. package/dist/services/memory-review-handler.js +17 -17
  177. package/dist/services/memory-review-inputs.js +55 -9
  178. package/dist/services/memory-review-limits.js +10 -0
  179. package/dist/services/memory-review-receipt.js +2 -2
  180. package/dist/services/memory-review-registration.js +13 -4
  181. package/dist/services/memory-review-work-custody.js +3 -1
  182. package/dist/services/memory-review-work-input.js +26 -7
  183. package/dist/services/memory-review-work-results.js +26 -4
  184. package/dist/services/memory-second-corrected-review-inputs.js +5 -3
  185. package/dist/services/memory-second-correction-plan.js +2 -1
  186. package/dist/services/memory-second-repair-launch.js +4 -4
  187. package/dist/services/memory-second-repair-source.js +1 -1
  188. package/dist/services/memory-stage-dispatch.js +38 -1
  189. package/dist/services/memory-stage-results.js +113 -22
  190. package/dist/services/memory-tool-probe.js +25 -0
  191. package/dist/services/memory-upgrade-analysis-handler.js +243 -0
  192. package/dist/services/memory-upgrade-bootstrap-readiness.js +251 -0
  193. package/dist/services/memory-upgrade-evidence.js +100 -0
  194. package/dist/services/memory-upgrade-inventory.js +442 -0
  195. package/dist/services/memory-upgrade-publication-custody.js +73 -0
  196. package/dist/services/memory-upgrade-publication.js +413 -0
  197. package/dist/services/memory-upgrade-selection.js +430 -0
  198. package/dist/services/memory-upgrade-start.js +60 -0
  199. package/dist/services/memory-upgrade-storage-readiness.js +340 -0
  200. package/dist/services/memory-verification-acceptance.js +38 -17
  201. package/dist/services/memory-verification-checks.js +92 -27
  202. package/dist/services/memory-verification-receipt.js +6 -5
  203. package/dist/services/memory-verify-handler.js +15 -13
  204. package/dist/services/memory-work-result-json.js +12 -0
  205. package/dist/services/memory-work-result-preparation.js +5 -4
  206. package/dist/services/merge-server.js +4 -2
  207. package/dist/services/migrations.js +127 -98
  208. package/dist/services/native-daemon-history.js +2 -1
  209. package/dist/services/physical-startup.js +45 -0
  210. package/dist/services/project-registry.js +97 -0
  211. package/dist/services/project-summary.js +1 -1
  212. package/dist/services/projects.js +3 -93
  213. package/dist/services/prompt-delivery.js +71 -0
  214. package/dist/services/provider-capacity.js +112 -46
  215. package/dist/services/recovery-snapshot-database.js +32 -4
  216. package/dist/services/release-cache.js +8 -0
  217. package/dist/services/repair-intents.js +1 -12
  218. package/dist/services/repair-receipt.js +13 -0
  219. package/dist/services/run-control-worker.js +35 -18
  220. package/dist/services/run-control.js +2 -1
  221. package/dist/services/run-controller-adapter.js +2 -1
  222. package/dist/services/run-controller-process.js +4 -7
  223. package/dist/services/run-controller-recovery.js +8 -2
  224. package/dist/services/run-controller.js +215 -94
  225. package/dist/services/run-fork.js +1 -1
  226. package/dist/services/run-recovery-guard.js +30 -0
  227. package/dist/services/run-recovery.js +11 -44
  228. package/dist/services/run-start-custody.js +17 -5
  229. package/dist/services/runs.js +6 -1
  230. package/dist/services/runtime-scope-resume.js +12 -6
  231. package/dist/services/runtime-scope-stop.js +3 -2
  232. package/dist/services/runtime-scope-worker.js +46 -19
  233. package/dist/services/runtime-service.js +2 -1
  234. package/dist/services/sessions.js +3 -24
  235. package/dist/services/setup.js +63 -13
  236. package/dist/services/stage-handlers.js +22 -7
  237. package/dist/services/stage-pause.js +111 -44
  238. package/dist/services/version-status.js +1 -1
  239. package/dist/services/vnext-code.js +2 -2
  240. package/dist/services/vnext-fanout.js +51 -9
  241. package/dist/services/vnext-plan-review.js +3 -5
  242. package/dist/services/work-registry.js +117 -39
  243. package/dist/services/workspace-bootstrap.js +2 -1
  244. package/dist/services/workspace-files.js +1 -1
  245. package/dist/shared/entity-references.js +1 -8
  246. package/dist/storage/data-preservation.js +65 -0
  247. package/dist/storage/database.js +53 -19
  248. package/dist/storage/import-guard.js +84 -27
  249. package/dist/storage/memory-upgrade-schema-bootstrap.js +38 -0
  250. package/dist/storage/paired-writer-migration.js +424 -0
  251. package/dist/storage/writer-contract.js +11 -4
  252. package/dist/storage/writer-migration.js +313 -52
  253. package/dist/storage/writer-restore.js +217 -0
  254. package/package.json +13 -6
@@ -5,7 +5,7 @@ import { PreparedMemoryFinishReview } from "./memory-finish-handler.js";
5
5
  import { readMemoryInitBootstrapReadiness } from "./memory-init-bootstrap-readiness.js";
6
6
  import { readMemoryInitGitPolicy } from "./memory-init-git-policy.js";
7
7
  import { hybridDigest } from "./hybrid-contracts.js";
8
- import { observeReviewedMemoryGitIntegration, reviewedMemoryGitFiles } from "./memory-git-integration.js";
8
+ import { observeReviewedMemoryGitIntegration, reviewedMemoryGitFiles, isReviewedMemoryGitFile, assertMemoryGitRemovalSources } from "./memory-git-integration.js";
9
9
  import { createHash } from "node:crypto";
10
10
  import { readBoundedReleaseFile } from "./release-source.js";
11
11
  import { spawnSync } from "node:child_process";
@@ -17,14 +17,34 @@ import { readDescriptorStageInput } from "./descriptor-stage-coordinator.js";
17
17
  import { appendAudit } from "./audit.js";
18
18
  import fs from "node:fs";
19
19
  import path from "node:path";
20
+ import { readMemoryUpgradeBootstrapReadiness } from "./memory-upgrade-bootstrap-readiness.js";
21
+ import { readUpgradeStorageReadiness } from "./memory-upgrade-storage-readiness.js";
22
+ import { readCompletedMemoryUpgradePublication } from "./memory-upgrade-publication-custody.js";
23
+ import { readDescriptorRootWork } from "./descriptor-root-work.js";
24
+ import { stageGraphScope } from "./stage-work-graph.js";
25
+ import { selectMemoryCompositionRegistration } from "./memory-composition-registration.js";
26
+ import { readAcceptedStageData } from "./stage-report-renderer.js";
27
+ import { localEnvironmentAllowed } from "./workspace-files.js";
28
+ import { observeMemoryDocumentInventory } from "./memory-repair-document-delta.js";
20
29
  const token = Symbol("memory-finish-integration");
21
30
  function refuse(reason) {
22
31
  throw new AppError("memory_finish_integration_invalid", "Finish lacks policy-bound reviewed Git integration", 2, { reason, effect: "no_effect" });
23
32
  }
24
- function retained(context, scope, bundle) {
33
+ export function readMemoryFinishGitSelection(context, scope, bundle) {
25
34
  assertPreparedRunBundleSqlAuthority(context, scope, bundle);
26
35
  const frozen = bundle.execution.snapshot().scope;
27
36
  const request = { requestId: frozen.requestId, projectRoot: frozen.projectRoot, workspaceRoot: frozen.workspace };
37
+ if (bundle.descriptor.descriptor.id === "mb-upgrade") {
38
+ const readiness = readMemoryUpgradeBootstrapReadiness(context, request), storage = readUpgradeStorageReadiness(context, request);
39
+ const git = storage?.selection?.payload.git;
40
+ if (!readiness?.hasGitPolicy || !storage?.selection || !git || readiness.projectId !== scope.projectId
41
+ || readiness.authority.binding.execution_sha256 !== bundle.execution.sha256
42
+ || readiness.authority.binding.storage_sha256 !== storage.sha256)
43
+ refuse("policy_bootstrap_binding_changed");
44
+ return { kind: "upgrade", readiness, storage, policy: { scope: request, ...git.policy },
45
+ selection: { projectRoot: request.projectRoot, workspaceRoot: request.workspaceRoot,
46
+ integrationBranch: git.policy.integration_branch, gitExecutable: git.executable }, gitSha256: git.probe.executable_sha256 };
47
+ }
28
48
  const readiness = readMemoryInitBootstrapReadiness(context, request), policy = readMemoryInitGitPolicy(context, request);
29
49
  if (!readiness || !policy || readiness.projectId !== scope.projectId
30
50
  || readiness.authority.binding.execution_sha256 !== bundle.execution.sha256
@@ -36,18 +56,103 @@ function retained(context, scope, bundle) {
36
56
  const gitSha256 = readiness.authority.binding.target.tool_probes?.git.executable_sha256;
37
57
  if (typeof gitSha256 !== "string" || !/^[a-f0-9]{64}$/.test(gitSha256))
38
58
  refuse("pinned_git_executable_required");
39
- return { readiness, policy, selection, gitSha256 };
59
+ return { kind: "init", readiness, policy, selection, gitSha256 };
40
60
  }
41
61
  function assertGitExecutable(selection, sha256) {
42
62
  if (createHash("sha256").update(readBoundedReleaseFile(selection.gitExecutable, 64 * 1024 * 1024)).digest("hex") !== sha256)
43
63
  refuse("pinned_git_executable_changed");
44
64
  }
65
+ /** Projection only: accepted review may advance project/customized documents,
66
+ * not canonical defaults, opaque links or CLI recovery records. */
67
+ export function projectMemoryUpgradeFinishInventory(plan, inventory, changes, reviewed, settledDocuments = null) {
68
+ const files = new Map(plan.beforeFiles().map(entry => [`.memory-bank/${entry.path}`, { ...entry, path: `.memory-bank/${entry.path}` }]));
69
+ const removed = new Set();
70
+ const removals = [];
71
+ for (const action of plan.actions()) {
72
+ const logical = `.memory-bank/${action.path}`;
73
+ if (action.after)
74
+ files.set(logical, { ...action.after, path: logical });
75
+ else {
76
+ files.delete(logical);
77
+ removed.add(logical);
78
+ if (!action.before || action.before.kind !== "file")
79
+ refuse("unsupported_upgrade_removal");
80
+ removals.push({ root: "workspace", path: logical, sha256: action.before.sha256,
81
+ ...((action.before.mode & 0o111) ? { git_mode: "100755" } : {}) });
82
+ }
83
+ }
84
+ const ownership = new Map(inventory.entries.map(entry => [`.memory-bank/${entry.path}`, entry.ownership]));
85
+ const semantic = new Set(changes.filter(change => change.action === "semantic-work" && change.to).map(change => change.to));
86
+ const rootFiles = [];
87
+ const settled = new Map(settledDocuments?.map(entry => [entry.path, entry]));
88
+ for (const ref of reviewedMemoryGitFiles(reviewed)) {
89
+ if (ref.path === ".mb-lint.json") {
90
+ rootFiles.push(ref);
91
+ continue;
92
+ }
93
+ const before = files.get(ref.path);
94
+ if (removed.has(ref.path) || before && before.kind !== "file")
95
+ refuse("reviewed_upgrade_path_not_current_file");
96
+ if (before?.sha256 === ref.sha256)
97
+ continue;
98
+ const owner = ownership.get(ref.path), namespace = /^\.memory-bank\/(?:dd-flow|mbb)\//.test(ref.path);
99
+ if (ref.path.startsWith(".memory-bank/archive/mb-upgrade/") || owner === "generated"
100
+ || (owner === "canonical" || namespace && owner !== "project" && owner !== "local-customized") && !semantic.has(ref.path))
101
+ refuse("review_changed_protected_upgrade_output");
102
+ const after = settled.get(ref.path);
103
+ if (!before && (!after || after.kind !== "file" || after.sha256 !== ref.sha256))
104
+ refuse("unretained_reviewed_output_mode");
105
+ files.set(ref.path, { path: ref.path, kind: "file", mode: before?.mode ?? (after.mode & 0o777), sha256: ref.sha256 });
106
+ }
107
+ const entries = [...files.values()].sort((a, b) => a.path.localeCompare(b.path, "en"));
108
+ if (settledDocuments && hybridDigest(settledDocuments.filter(entry => entry.kind !== "directory")
109
+ .map(entry => ({ ...entry, mode: entry.mode & 0o777 })).sort((a, b) => a.path.localeCompare(b.path, "en"))) !== hybridDigest(entries))
110
+ refuse("unreviewed_repair_inventory_change");
111
+ const references = [...rootFiles, ...entries.filter(entry => entry.kind === "file"
112
+ && entry.path.split("/").every(part => localEnvironmentAllowed(part) && ![".git", "node_modules", ".dd-flow"].includes(part)))
113
+ .map(entry => ({ root: "workspace", path: entry.path, sha256: entry.sha256,
114
+ ...((entry.mode & 0o111) ? { git_mode: "100755" } : {}) }))].sort((a, b) => a.path.localeCompare(b.path, "en"));
115
+ return { entries, references, removals: removals.sort((a, b) => a.path.localeCompare(b.path, "en")) };
116
+ }
117
+ export function readMemoryUpgradeFinishInventory(context, scope, bundle, review) {
118
+ if (!PreparedMemoryFinishReview.isPrepared(review))
119
+ refuse("accepted_review_required");
120
+ const reviewed = review.reviewedSources(context);
121
+ const selected = readMemoryFinishGitSelection(context, scope, bundle);
122
+ if (selected.kind !== "upgrade" || !selected.storage.selection)
123
+ refuse("upgrade_selection_required");
124
+ const root = readDescriptorRootWork(context, scope, bundle);
125
+ const registration = selectMemoryCompositionRegistration(context, { ...stageGraphScope(context, scope.projectId, scope.runId, "upgrade"),
126
+ stage: "upgrade", parent_work_id: root.workId });
127
+ const published = readCompletedMemoryUpgradePublication(context, { scope: registration.scope,
128
+ selection_sha256: selected.storage.selection.sha256, execution_sha256: bundle.execution.sha256,
129
+ registration_sha256: hybridDigest(registration), bootstrap_sha256: hybridDigest(selected.readiness.authority) });
130
+ const accepted = readAcceptedStageData(context, { ...scope, stage: "upgrade" }), value = JSON.parse(accepted.bytes.toString("utf8"));
131
+ const recordPath = `.memory-bank/archive/mb-upgrade/${selected.storage.selection.sha256}/migration.json`;
132
+ const record = published.plan.outputs().find(entry => `.memory-bank/${entry.path}` === recordPath);
133
+ const migrationRecord = record && { path: recordPath, sha256: record.sha256 };
134
+ if (!migrationRecord || accepted.data_schema_id !== "dd-flow/mb-upgrade-stage-result@1" || value.stage !== "upgrade" || value.outcome !== "accepted"
135
+ || accepted.input_sha256 !== registration.input_sha256 || hybridDigest([value.details?.migration_record]) !== hybridDigest([migrationRecord])
136
+ || !reviewed.some(ref => ref.root === "workspace" && ref.path === migrationRecord.path && ref.sha256 === migrationRecord.sha256))
137
+ refuse("accepted_upgrade_migration_record_changed");
138
+ const diff = readAcceptedStageData(context, { ...scope, stage: "diff-analysis" }), changes = JSON.parse(diff.bytes.toString("utf8")).details.path_map;
139
+ return { ...projectMemoryUpgradeFinishInventory(published.plan, selected.storage.selection.payload.current.inventory.inventory, changes, reviewed, review.reviewedDocumentInventory(context)),
140
+ migrationRecord, publicationSha256: published.plan.sha256, binding: published.binding };
141
+ }
142
+ export function observeMemoryUpgradeFinishInventory(root, expected) {
143
+ const snapshot = observeMemoryDocumentInventory(root);
144
+ const entries = snapshot.entries().filter(entry => entry.kind !== "directory").map(entry => ({ ...entry, mode: entry.mode & 0o777 }));
145
+ if (hybridDigest(entries.sort((a, b) => a.path.localeCompare(b.path, "en"))) !== hybridDigest(expected.entries))
146
+ refuse("upgrade_finish_inventory_changed");
147
+ return snapshot;
148
+ }
45
149
  /** Physical Git operation only; callers must independently select accepted files
46
150
  * and retain each mutation's intent. No ambient Git redirection or whole-tree add. */
47
- export function integrateReviewedMemoryFiles(selection, environment, references, runId, mutation, policy) {
48
- const env = Object.fromEntries(Object.entries(environment).filter(([key]) => !key.startsWith("GIT_")));
151
+ export function integrateReviewedMemoryFiles(selection, environment, references, runId, mutation, policy, removals = []) {
152
+ const env = { ...Object.fromEntries(Object.entries(environment).filter(([key]) => !key.startsWith("GIT_"))), GIT_LITERAL_PATHSPECS: "1" };
49
153
  const git = (directory, args, write = false, absent = false) => {
50
- const result = spawnSync(selection.gitExecutable, ["-c", "core.fsmonitor=false", "-C", directory, ...args], { env, encoding: "utf8", timeout: 30000, maxBuffer: 16 * 1024 * 1024, shell: false, windowsHide: true });
154
+ const result = spawnSync(selection.gitExecutable, ["-c", "core.fsmonitor=false",
155
+ ...(write && process.platform !== "win32" ? ["-c", "core.filemode=true"] : []), "-C", directory, ...args], { env, encoding: "utf8", timeout: 30000, maxBuffer: 16 * 1024 * 1024, shell: false, windowsHide: true });
51
156
  if (absent && !result.error && !result.signal && result.status !== 0)
52
157
  return "";
53
158
  if (result.error || result.signal || result.status !== 0)
@@ -55,13 +160,26 @@ export function integrateReviewedMemoryFiles(selection, environment, references,
55
160
  next_action: write ? "reconcile_original_integration" : "resolve_git_configuration_or_workspace" });
56
161
  return result.stdout.trim();
57
162
  };
58
- const paths = references.map(ref => ref.path);
59
- if (!paths.length || paths.length > 4096 || !paths.includes(".memory-bank/index.md") || new Set(paths).size !== paths.length
60
- || references.some(ref => ref.root !== "workspace" || !(ref.path.startsWith(".memory-bank/") || ref.path === ".mb-lint.json")
61
- || ref.path.split("/").some(part => !part || part === "." || part === "..") || /[\\\0]/.test(ref.path)
62
- || !/^[a-f0-9]{64}$/.test(ref.sha256)))
163
+ if (!Array.isArray(references) || !Array.isArray(removals) || !references.length
164
+ || references.length + removals.length > 4096 || !references.some(ref => ref?.path === ".memory-bank/index.md")
165
+ || [...references, ...removals].some(ref => !isReviewedMemoryGitFile(ref))
166
+ || removals.some(ref => !ref.path.startsWith(".memory-bank/") || ref.path === ".memory-bank/index.md"))
167
+ refuse("invalid_owned_inventory");
168
+ const paths = [...references, ...removals].map(ref => ref.path);
169
+ if (new Set(paths).size !== paths.length)
63
170
  refuse("invalid_owned_inventory");
64
171
  const source = fs.realpathSync(selection.workspaceRoot), target = fs.realpathSync(selection.projectRoot);
172
+ const assertModes = () => {
173
+ if (process.platform === "win32")
174
+ return;
175
+ for (const ref of references) {
176
+ const executable = (fs.lstatSync(path.join(source, ref.path)).mode & 0o111) !== 0;
177
+ if (executable !== (ref.git_mode === "100755"))
178
+ refuse("reviewed_workspace_mode_changed");
179
+ }
180
+ };
181
+ assertModes();
182
+ assertMemoryGitRemovalSources(source, removals);
65
183
  const branch = git(source, ["symbolic-ref", "--short", "HEAD"]);
66
184
  if (target === source ? branch !== selection.integrationBranch
67
185
  : git(target, ["symbolic-ref", "--short", "HEAD"]) !== selection.integrationBranch)
@@ -77,7 +195,7 @@ export function integrateReviewedMemoryFiles(selection, environment, references,
77
195
  if (hashes.get(memorySourceKey(ref.root, ref.path)) !== ref.sha256)
78
196
  refuse("reviewed_workspace_bytes_changed");
79
197
  try {
80
- return observeReviewedMemoryGitIntegration(selection, environment, references);
198
+ return observeReviewedMemoryGitIntegration(selection, environment, references, removals);
81
199
  }
82
200
  catch (error) {
83
201
  if (!(error instanceof AppError) || error.code !== "memory_git_integration_invalid")
@@ -89,6 +207,22 @@ export function integrateReviewedMemoryFiles(selection, environment, references,
89
207
  if (git(source, ["diff", "--name-only", "--diff-filter=U"]))
90
208
  refuse("unmerged_workspace");
91
209
  const sourceHead = git(source, ["rev-parse", "--verify", "HEAD"], false, true);
210
+ const deleting = [];
211
+ let removedBytes = 0;
212
+ for (const ref of removals) {
213
+ const entry = git(source, ["ls-tree", "-z", "HEAD", "--", ref.path], false, true);
214
+ if (!entry)
215
+ continue;
216
+ if (!new RegExp(`^${ref.git_mode ?? "100644"} blob [a-f0-9]{40,64}\\t`).test(entry))
217
+ refuse("removed_memory_source_changed");
218
+ const content = spawnSync(selection.gitExecutable, ["-C", source, "cat-file", "blob", `HEAD:${ref.path}`], { env, timeout: 10000, maxBuffer: 16 * 1024 * 1024, shell: false, windowsHide: true });
219
+ if (content.error || content.signal || content.status !== 0 || createHash("sha256").update(content.stdout).digest("hex") !== ref.sha256)
220
+ refuse("removed_memory_source_changed");
221
+ removedBytes += content.stdout.length;
222
+ if (removedBytes > 64 * 1024 * 1024)
223
+ refuse("removed_memory_bytes_exceed_bound");
224
+ deleting.push(ref.path);
225
+ }
92
226
  if (source !== target) {
93
227
  const targetHead = git(target, ["rev-parse", "HEAD"]), base = git(source, ["merge-base", targetHead, sourceHead]);
94
228
  const changes = git(source, ["diff", "--name-only", "-z", base, sourceHead]).split("\0").filter(Boolean);
@@ -97,20 +231,25 @@ export function integrateReviewedMemoryFiles(selection, environment, references,
97
231
  if (git(target, ["diff", "--cached", "--name-only"]) || git(target, ["diff", "--name-only", "--diff-filter=U"]))
98
232
  refuse("integration_target_index_not_clean");
99
233
  }
100
- const needsCommit = references.some(ref => {
234
+ const needsCommit = deleting.length > 0 || references.some(ref => {
101
235
  const bytes = spawnSync(selection.gitExecutable, ["-C", source, "cat-file", "blob", `HEAD:${ref.path}`], { env, timeout: 10000, maxBuffer: 16 * 1024 * 1024, shell: false, windowsHide: true });
102
- return bytes.status !== 0 || createHash("sha256").update(bytes.stdout).digest("hex") !== ref.sha256;
236
+ const entry = git(source, ["ls-tree", "-z", "HEAD", "--", ref.path], false, true);
237
+ return bytes.status !== 0 || createHash("sha256").update(bytes.stdout).digest("hex") !== ref.sha256
238
+ || !entry.startsWith(`${ref.git_mode ?? "100644"} blob `);
103
239
  });
104
240
  if (needsCommit) {
105
241
  git(source, ["var", "GIT_AUTHOR_IDENT"]);
106
242
  git(source, ["var", "GIT_COMMITTER_IDENT"]);
107
243
  mutation("commit", sourceHead, () => {
108
244
  observed.revalidate();
245
+ assertModes();
246
+ assertMemoryGitRemovalSources(source, removals);
109
247
  if (git(source, ["rev-parse", "--verify", "HEAD"], false, true) !== sourceHead
110
248
  || git(source, ["symbolic-ref", "--short", "HEAD"]) !== branch)
111
249
  refuse("source_git_baseline_changed");
112
- git(source, ["add", "-f", "--", ...paths], true);
113
- git(source, ["commit", "--only", "-m", `chore(${runId.toLowerCase()}): integrate reviewed Memory Bank`, "--", ...paths], true);
250
+ const commitPaths = [...references.map(ref => ref.path), ...deleting];
251
+ git(source, ["add", "-f", "--", ...commitPaths], true);
252
+ git(source, ["commit", "--only", "-m", `chore(${runId.toLowerCase()}): integrate reviewed Memory Bank`, "--", ...commitPaths], true);
114
253
  });
115
254
  }
116
255
  observed.revalidate();
@@ -126,40 +265,50 @@ export function integrateReviewedMemoryFiles(selection, environment, references,
126
265
  });
127
266
  }
128
267
  }
129
- return observeReviewedMemoryGitIntegration(selection, environment, references);
268
+ return observeReviewedMemoryGitIntegration(selection, environment, references, removals);
130
269
  }
131
270
  /** The admitted Memory finish operation. A retained intent never licenses a
132
271
  * second dispatch: replay first observes completed Git effects, or reports unknown. */
133
272
  export function integrateMemoryFinish(context, input) {
134
- if (!(input.bundle instanceof RetainedExecutionBundle) || input.bundle.descriptor.descriptor.id !== "mb-init")
273
+ if (!(input.bundle instanceof RetainedExecutionBundle) || !["mb-init", "mb-upgrade"].includes(input.bundle.descriptor.descriptor.id))
135
274
  refuse("registered_memory_bundle_required");
136
275
  if (context.db.isOwnedWriteTransaction?.())
137
276
  refuse("preparation_outside_writer_required");
138
277
  const stageScope = { ...input.scope, stage: "finish" };
139
278
  readDescriptorStageInput(context, stageScope, input.bundle);
140
279
  const review = PreparedMemoryFinishReview.prepare(context, input);
141
- const selected = context.db.readSnapshot(() => retained(context, input.scope, input.bundle));
280
+ const selected = context.db.readSnapshot(() => readMemoryFinishGitSelection(context, input.scope, input.bundle));
142
281
  const request = { requestId: selected.policy.scope.requestId, projectRoot: selected.selection.projectRoot,
143
282
  workspaceRoot: selected.selection.workspaceRoot };
144
- const bootstrap = readMemoryInitProjectBootstrap(context, request);
145
- if (!bootstrap)
146
- refuse("accepted_bootstrap_required");
147
- const published = readPublishedMemoryReferenceInventory(context.ddFlowHome, { ...request, targetSha256: bootstrap.receipt.target_sha256,
148
- layerSha256: bootstrap.receipt.layer_sha256, installationSha256: selected.readiness.authority.binding.installation_manifest_sha256,
149
- packSha256: selected.readiness.authority.binding.pack_sha256 }, selected.readiness.authority.binding.target, bootstrap.receipt.layer_publication_sha256, bootstrap.receipt.draft_identity_sha256);
150
- const files = new Map(reviewedMemoryGitFiles(review.reviewedSources(context)).map(ref => [ref.path, ref]));
151
- for (const [logical, sha256] of published.artifacts) {
152
- const ref = { root: "workspace", path: logical, sha256 };
153
- if (files.has(ref.path)) {
154
- if (published.canon.has(logical.slice(".memory-bank/".length)) && files.get(ref.path).sha256 !== ref.sha256)
155
- refuse("selected_canon_changed");
283
+ let upgrade = null;
284
+ let references;
285
+ if (selected.kind === "upgrade") {
286
+ upgrade = readMemoryUpgradeFinishInventory(context, input.scope, input.bundle, review);
287
+ references = upgrade.references;
288
+ }
289
+ else {
290
+ const bootstrap = readMemoryInitProjectBootstrap(context, request);
291
+ if (!bootstrap)
292
+ refuse("accepted_bootstrap_required");
293
+ const published = readPublishedMemoryReferenceInventory(context.ddFlowHome, { ...request, targetSha256: bootstrap.receipt.target_sha256,
294
+ layerSha256: bootstrap.receipt.layer_sha256, installationSha256: selected.readiness.authority.binding.installation_manifest_sha256,
295
+ packSha256: selected.readiness.authority.binding.pack_sha256 }, selected.readiness.authority.binding.target, bootstrap.receipt.layer_publication_sha256, bootstrap.receipt.draft_identity_sha256);
296
+ const files = new Map(reviewedMemoryGitFiles(review.reviewedSources(context)).map(ref => [ref.path, ref]));
297
+ for (const [logical, sha256] of published.artifacts) {
298
+ const ref = { root: "workspace", path: logical, sha256 };
299
+ if (files.has(ref.path)) {
300
+ if (published.canon.has(logical.slice(".memory-bank/".length)) && files.get(ref.path).sha256 !== ref.sha256)
301
+ refuse("selected_canon_changed");
302
+ }
303
+ else
304
+ files.set(ref.path, ref);
156
305
  }
157
- else
158
- files.set(ref.path, ref);
306
+ references = [...files.values()].sort((a, b) => a.path.localeCompare(b.path, "en"));
159
307
  }
160
- const references = [...files.values()].sort((a, b) => a.path.localeCompare(b.path, "en"));
308
+ const snapshot = upgrade ? observeMemoryUpgradeFinishInventory(selected.selection.workspaceRoot, upgrade) : null;
309
+ const removals = upgrade?.removals ?? [];
161
310
  const identity = hybridDigest({ scope: input.scope, bundle: input.bundle.sha256, policy: selected.policy,
162
- review: review.bytes().toString("base64"), references });
311
+ review: review.bytes().toString("base64"), references, ...(upgrade ? { upgrade } : {}) });
163
312
  const events = context.db.all(`SELECT payload_json FROM audit_events WHERE project_id=?
164
313
  AND event_type='memory.finish_integration_intent' AND json_extract(payload_json,'$.run_id')=?`, [input.scope.projectId, input.scope.runId]);
165
314
  const intents = events.map(row => JSON.parse(row.payload_json));
@@ -168,7 +317,7 @@ export function integrateMemoryFinish(context, input) {
168
317
  assertGitExecutable(selected.selection, selected.gitSha256);
169
318
  if (intents.length) {
170
319
  try {
171
- observeReviewedMemoryGitIntegration(selected.selection, context.env, references);
320
+ observeReviewedMemoryGitIntegration(selected.selection, context.env, references, removals);
172
321
  const accepted = PreparedMemoryFinishIntegration.prepare(context, input);
173
322
  return { integrated: true, replayed: true, integration: accepted.value() };
174
323
  }
@@ -189,11 +338,14 @@ export function integrateMemoryFinish(context, input) {
189
338
  throw new AppError("memory_finish_integration_pending", "Original Git mutation remains unresolved", 1, { effect: "unknown", next_action: "reconcile_original_integration" });
190
339
  review.revalidate(context);
191
340
  assertGitExecutable(selected.selection, selected.gitSha256);
341
+ snapshot?.revalidate();
192
342
  context.db.writeTransaction(() => {
193
343
  readDescriptorStageInput(context, stageScope, input.bundle);
194
344
  review.assertCustody(context);
195
- if (hybridDigest(retained(context, input.scope, input.bundle)) !== hybridDigest(selected))
345
+ if (hybridDigest(readMemoryFinishGitSelection(context, input.scope, input.bundle)) !== hybridDigest(selected))
196
346
  refuse("integration_custody_changed");
347
+ if (upgrade && hybridDigest(readMemoryUpgradeFinishInventory(context, input.scope, input.bundle, review)) !== hybridDigest(upgrade))
348
+ refuse("upgrade_inventory_custody_changed");
197
349
  const current = context.db.all(`SELECT payload_json FROM audit_events WHERE project_id=?
198
350
  AND event_type='memory.finish_integration_intent' AND json_extract(payload_json,'$.run_id')=?`, [input.scope.projectId, input.scope.runId])
199
351
  .map(row => JSON.parse(row.payload_json));
@@ -204,8 +356,11 @@ export function integrateMemoryFinish(context, input) {
204
356
  }, "memoryFinish.integrate");
205
357
  dispatched = true;
206
358
  action();
207
- }, selected.policy);
208
- return { integrated: true, replayed: false, integration };
359
+ }, selected.policy, removals);
360
+ snapshot?.revalidate();
361
+ if (upgrade)
362
+ observeMemoryUpgradeFinishInventory(selected.selection.projectRoot, upgrade);
363
+ return { integrated: true, replayed: false, integration: { ...integration, ...(upgrade ? { upgrade_inventory_sha256: hybridDigest(upgrade) } : {}) } };
209
364
  }
210
365
  catch (error) {
211
366
  if (!dispatched)
@@ -228,7 +383,8 @@ export class PreparedMemoryFinishIntegration {
228
383
  #selection;
229
384
  #observation;
230
385
  #gitSha256;
231
- constructor(key, scope, bundle, review, selected, observation) {
386
+ #upgradeSha256;
387
+ constructor(key, scope, bundle, review, selected, observation, upgradeSha256 = null) {
232
388
  if (key !== token)
233
389
  refuse("finish_integration_token_required");
234
390
  this.#scope = { ...scope };
@@ -238,22 +394,28 @@ export class PreparedMemoryFinishIntegration {
238
394
  this.#selection = structuredClone(selected.selection);
239
395
  this.#observation = structuredClone(observation);
240
396
  this.#gitSha256 = selected.gitSha256;
397
+ this.#upgradeSha256 = upgradeSha256;
241
398
  Object.freeze(this);
242
399
  }
243
400
  static prepare(context, input) {
244
- if (!(input.bundle instanceof RetainedExecutionBundle) || input.bundle.descriptor.descriptor.id !== "mb-init")
401
+ if (!(input.bundle instanceof RetainedExecutionBundle) || !["mb-init", "mb-upgrade"].includes(input.bundle.descriptor.descriptor.id))
245
402
  refuse("registered_memory_bundle_required");
246
403
  if (context.db.isOwnedWriteTransaction?.())
247
404
  refuse("preparation_outside_writer_required");
248
405
  const review = PreparedMemoryFinishReview.prepare(context, input);
249
- const selected = context.db.readSnapshot(() => retained(context, input.scope, input.bundle));
250
- const references = reviewedMemoryGitFiles(review.reviewedSources(context));
406
+ const selected = context.db.readSnapshot(() => readMemoryFinishGitSelection(context, input.scope, input.bundle));
407
+ const upgrade = selected.kind === "upgrade" ? readMemoryUpgradeFinishInventory(context, input.scope, input.bundle, review) : null;
408
+ const references = upgrade?.references ?? reviewedMemoryGitFiles(review.reviewedSources(context));
251
409
  assertGitExecutable(selected.selection, selected.gitSha256);
252
- const observation = observeReviewedMemoryGitIntegration(selected.selection, context.env, references);
410
+ if (upgrade)
411
+ for (const root of new Set([selected.selection.projectRoot, selected.selection.workspaceRoot]))
412
+ observeMemoryUpgradeFinishInventory(root, upgrade);
413
+ const observation = { ...observeReviewedMemoryGitIntegration(selected.selection, context.env, references, upgrade?.removals),
414
+ ...(upgrade ? { upgrade_inventory_sha256: hybridDigest(upgrade) } : {}) };
253
415
  assertGitExecutable(selected.selection, selected.gitSha256);
254
416
  if (observation.repository !== selected.policy.repository || observation.workspace_branch !== selected.policy.workspace_branch)
255
417
  refuse("policy_repository_or_branch_changed");
256
- const prepared = new PreparedMemoryFinishIntegration(token, input.scope, input.bundle, review, selected, observation);
418
+ const prepared = new PreparedMemoryFinishIntegration(token, input.scope, input.bundle, review, selected, observation, upgrade ? hybridDigest(upgrade) : null);
257
419
  prepared.revalidate(context);
258
420
  return prepared;
259
421
  }
@@ -261,8 +423,10 @@ export class PreparedMemoryFinishIntegration {
261
423
  /** SQL-only writer fence; physical observation must precede the writer. */
262
424
  assertCustody(context) {
263
425
  this.#review.assertCustody(context);
264
- if (hybridDigest(retained(context, this.#scope, this.#bundle)) !== this.#retainedSha256)
426
+ if (hybridDigest(readMemoryFinishGitSelection(context, this.#scope, this.#bundle)) !== this.#retainedSha256)
265
427
  refuse("finish_integration_custody_changed");
428
+ if (this.#upgradeSha256 !== null && hybridDigest(readMemoryUpgradeFinishInventory(context, this.#scope, this.#bundle, this.#review)) !== this.#upgradeSha256)
429
+ refuse("upgrade_inventory_custody_changed");
266
430
  }
267
431
  revalidate(context) {
268
432
  const review = this.#review;
@@ -270,9 +434,15 @@ export class PreparedMemoryFinishIntegration {
270
434
  refuse("physical_revalidation_outside_writer_required");
271
435
  review.revalidate(context);
272
436
  this.assertCustody(context);
273
- const references = reviewedMemoryGitFiles(this.#review.reviewedSources(context));
437
+ const upgrade = this.#upgradeSha256 === null ? null : readMemoryUpgradeFinishInventory(context, this.#scope, this.#bundle, this.#review);
438
+ const references = upgrade?.references ?? reviewedMemoryGitFiles(this.#review.reviewedSources(context));
274
439
  assertGitExecutable(this.#selection, this.#gitSha256);
275
- if (hybridDigest(observeReviewedMemoryGitIntegration(this.#selection, context.env, references)) !== hybridDigest(this.#observation))
440
+ if (upgrade)
441
+ for (const root of new Set([this.#selection.projectRoot, this.#selection.workspaceRoot]))
442
+ observeMemoryUpgradeFinishInventory(root, upgrade);
443
+ const observed = { ...observeReviewedMemoryGitIntegration(this.#selection, context.env, references, upgrade?.removals),
444
+ ...(upgrade ? { upgrade_inventory_sha256: hybridDigest(upgrade) } : {}) };
445
+ if (hybridDigest(observed) !== hybridDigest(this.#observation))
276
446
  refuse("finish_integration_observation_changed");
277
447
  assertGitExecutable(this.#selection, this.#gitSha256);
278
448
  this.assertCustody(context);
@@ -2,7 +2,7 @@ var _a;
2
2
  import { createHash } from "node:crypto";
3
3
  import { AppError } from "../shared/errors.js";
4
4
  import { RetainedExecutionBundle } from "./execution-bundles.js";
5
- import { PreparedMemoryFinishIntegration } from "./memory-finish-integration.js";
5
+ import { PreparedMemoryFinishIntegration, readMemoryUpgradeFinishInventory, observeMemoryUpgradeFinishInventory } from "./memory-finish-integration.js";
6
6
  import { PreparedMemoryFinishReview } from "./memory-finish-handler.js";
7
7
  import { observeMemoryNormalFlowEntry } from "./memory-normal-flow-entry.js";
8
8
  import { readMemoryInitBootstrapReadiness } from "./memory-init-bootstrap-readiness.js";
@@ -12,6 +12,9 @@ import { ObservedMemorySources } from "./memory-source-observations.js";
12
12
  import { readBoundedReleaseFile } from "./release-source.js";
13
13
  import { memorySourceKey } from "./memory-stage-results.js";
14
14
  import { hybridDigest } from "./hybrid-contracts.js";
15
+ import { readMemoryUpgradeBootstrapReadiness } from "./memory-upgrade-bootstrap-readiness.js";
16
+ import { readUpgradeStorageReadiness } from "./memory-upgrade-storage-readiness.js";
17
+ import { getProjectVersionStatus } from "./version-status.js";
15
18
  const token = Symbol("memory-finish-readiness");
16
19
  function refuse(reason) {
17
20
  throw new AppError("memory_finish_readiness_invalid", "Finish lacks reviewed ordinary-flow readiness", 2, { reason, effect: "no_effect" });
@@ -19,13 +22,32 @@ function refuse(reason) {
19
22
  function accepted(context, scope, bundle) {
20
23
  const frozen = bundle.execution.snapshot().scope;
21
24
  const request = { requestId: frozen.requestId, projectRoot: frozen.projectRoot, workspaceRoot: frozen.workspace };
25
+ if (bundle.descriptor.descriptor.id === "mb-upgrade") {
26
+ const bootstrap = readMemoryUpgradeBootstrapReadiness(context, request), storage = readUpgradeStorageReadiness(context, request);
27
+ if (!bootstrap?.hasGitPolicy || !bootstrap.hasLintCapability || !storage?.selection || bootstrap.projectId !== scope.projectId
28
+ || bootstrap.authority.binding.execution_sha256 !== bundle.execution.sha256
29
+ || bootstrap.authority.binding.storage_sha256 !== storage.sha256)
30
+ refuse("accepted_bootstrap_required");
31
+ return { kind: "upgrade", request, bootstrap, storage };
32
+ }
22
33
  const bootstrap = readMemoryInitBootstrapReadiness(context, request), registration = readMemoryInitProjectBootstrap(context, request);
23
34
  if (!bootstrap || !registration || bootstrap.projectId !== scope.projectId
24
35
  || bootstrap.authority.binding.execution_sha256 !== bundle.execution.sha256)
25
36
  refuse("accepted_bootstrap_required");
26
- return { request, bootstrap, registration };
37
+ return { kind: "init", request, bootstrap, registration };
27
38
  }
28
39
  function tools(selected) {
40
+ if (selected.kind === "upgrade") {
41
+ const git = selected.storage.selection.payload.git, lint = selected.bootstrap.authority.binding.lint;
42
+ if (!git || !lint)
43
+ refuse("selected_tool_changed");
44
+ for (const tool of [git, lint]) {
45
+ const expected = "probe" in tool ? tool.probe.executable_sha256 : tool.executable_sha256;
46
+ if (createHash("sha256").update(readBoundedReleaseFile(tool.executable, 64 * 1024 * 1024)).digest("hex") !== expected)
47
+ refuse("selected_tool_changed");
48
+ }
49
+ return;
50
+ }
29
51
  const target = selected.bootstrap.authority.binding.target;
30
52
  for (const name of ["git", "wt", "mb-lint"]) {
31
53
  const expected = target.tool_probes?.[name].executable_sha256;
@@ -61,25 +83,49 @@ export class PreparedMemoryFinishReadiness {
61
83
  static observe(context, scope, bundle, review, integration) {
62
84
  const selected = context.db.readSnapshot(() => accepted(context, scope, bundle));
63
85
  tools(selected);
64
- const layer = readRetainedMemoryLayer(context.ddFlowHome, { ...selected.request,
65
- targetSha256: selected.registration.receipt.target_sha256, layerSha256: selected.registration.receipt.layer_sha256,
66
- installationSha256: selected.bootstrap.authority.binding.installation_manifest_sha256 });
67
- if (layer.packSha256 !== selected.bootstrap.authority.binding.pack_sha256)
68
- refuse("selected_pack_changed");
69
- const files = layer.files();
70
- for (const root of new Set([selected.request.projectRoot, selected.request.workspaceRoot])) {
71
- const observed = ObservedMemorySources.capture({ project: root }, [...files.keys()].map(logical => ({ root: "project", path: `.memory-bank/${logical}` })));
72
- const hashes = observed.hashes();
73
- for (const [logical, bytes] of files)
74
- if (hashes.get(memorySourceKey("project", `.memory-bank/${logical}`)) !== createHash("sha256").update(bytes).digest("hex"))
75
- refuse("installed_pack_bytes_changed");
76
- observed.revalidate();
86
+ const files = new Map();
87
+ let packSha256;
88
+ let upgrade = null;
89
+ if (selected.kind === "upgrade") {
90
+ upgrade = readMemoryUpgradeFinishInventory(context, scope, bundle, review);
91
+ if (integration.value().upgrade_inventory_sha256 !== hybridDigest(upgrade))
92
+ refuse("upgrade_integration_inventory_changed");
93
+ for (const item of upgrade.entries)
94
+ if (item.kind === "file")
95
+ files.set(item.path.slice(".memory-bank/".length), item.sha256);
96
+ for (const root of new Set([selected.request.projectRoot, selected.request.workspaceRoot])) {
97
+ observeMemoryUpgradeFinishInventory(root, upgrade);
98
+ const version = getProjectVersionStatus({ projectRoot: root, rootSource: "explicit", canon: null });
99
+ const release = selected.storage.selection.release;
100
+ if (!version || version.memory_bank.version !== release.canon.version || version.flow_pack.canon_version_at_source_commit !== release.canon.version
101
+ || version.flow_pack.source_commit !== release.canon.commit)
102
+ refuse("upgrade_versions_inconsistent");
103
+ }
104
+ packSha256 = selected.bootstrap.authority.receipt.upgrade_target.pack_sha256;
105
+ }
106
+ else {
107
+ const layer = readRetainedMemoryLayer(context.ddFlowHome, { ...selected.request,
108
+ targetSha256: selected.registration.receipt.target_sha256, layerSha256: selected.registration.receipt.layer_sha256,
109
+ installationSha256: selected.bootstrap.authority.binding.installation_manifest_sha256 });
110
+ if (layer.packSha256 !== selected.bootstrap.authority.binding.pack_sha256)
111
+ refuse("selected_pack_changed");
112
+ for (const [logical, bytes] of layer.files())
113
+ files.set(logical, createHash("sha256").update(bytes).digest("hex"));
114
+ for (const root of new Set([selected.request.projectRoot, selected.request.workspaceRoot])) {
115
+ const observed = ObservedMemorySources.capture({ project: root }, [...files.keys()].map(logical => ({ root: "project", path: `.memory-bank/${logical}` })));
116
+ const hashes = observed.hashes();
117
+ for (const [logical, sha256] of files)
118
+ if (hashes.get(memorySourceKey("project", `.memory-bank/${logical}`)) !== sha256)
119
+ refuse("installed_pack_bytes_changed");
120
+ observed.revalidate();
121
+ }
122
+ packSha256 = layer.packSha256;
77
123
  }
78
124
  const entry = observeMemoryNormalFlowEntry(context, selected.request.projectRoot);
79
125
  const reviewed = new Map(review.reviewedSources(context).filter(ref => ref.root === "workspace").map(ref => [ref.path, ref.sha256]));
80
126
  for (const source of entry.sources) {
81
127
  const canonical = files.get(source.path.slice(".memory-bank/".length));
82
- if (canonical ? createHash("sha256").update(canonical).digest("hex") !== source.sha256 : reviewed.get(source.path) !== source.sha256)
128
+ if (canonical ? canonical !== source.sha256 : reviewed.get(source.path) !== source.sha256)
83
129
  refuse("entry_source_not_reviewed_or_selected_canon");
84
130
  }
85
131
  tools(selected);
@@ -87,11 +133,12 @@ export class PreparedMemoryFinishReadiness {
87
133
  review.assertCustody(context);
88
134
  return { schema_id: "dd-flow/memory-finish-readiness-observation@1", scope: { ...scope },
89
135
  bundle_sha256: bundle.sha256, execution_sha256: bundle.execution.sha256,
90
- bootstrap_authority_sha256: hybridDigest(selected.bootstrap.authority), pack_sha256: layer.packSha256,
91
- integration: integration.value(), entry, readiness: "entry_prepared", model_access: "unknown" };
136
+ bootstrap_authority_sha256: hybridDigest(selected.bootstrap.authority), pack_sha256: packSha256,
137
+ integration: integration.value(), entry, readiness: "entry_prepared", model_access: "unknown",
138
+ ...(upgrade ? { migration_record: upgrade.migrationRecord } : {}) };
92
139
  }
93
140
  static prepare(context, input) {
94
- if (!(input.bundle instanceof RetainedExecutionBundle) || input.bundle.descriptor.descriptor.id !== "mb-init")
141
+ if (!(input.bundle instanceof RetainedExecutionBundle) || !["mb-init", "mb-upgrade"].includes(input.bundle.descriptor.descriptor.id))
95
142
  refuse("registered_memory_bundle_required");
96
143
  if (context.db.isOwnedWriteTransaction?.())
97
144
  refuse("preparation_outside_writer_required");