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