@deksden-com/dd-flow-cli 0.9.0-beta.137 → 0.9.0-beta.138

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 (260) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +9 -0
  3. package/dist/build-info.json +6 -6
  4. package/dist/cli/argument-values.js +5 -5
  5. package/dist/cli/command-inputs.js +89 -20
  6. package/dist/cli/command-metadata.js +197 -0
  7. package/dist/cli/command-option-descriptions.js +191 -0
  8. package/dist/cli/completion-cli.js +70 -0
  9. package/dist/cli/completion.js +57 -0
  10. package/dist/cli/error-output.js +52 -0
  11. package/dist/cli/executor-onboarding.js +76 -0
  12. package/dist/cli/flows.js +29 -0
  13. package/dist/cli/harnesses.js +84 -0
  14. package/dist/cli/help.js +240 -6
  15. package/dist/cli/input-preparation.js +54 -5
  16. package/dist/cli/memory-flow-admission.js +44 -0
  17. package/dist/cli/memory-init-invocation.js +78 -0
  18. package/dist/cli/memory-init.js +240 -0
  19. package/dist/cli/memory-storage-admission.js +53 -0
  20. package/dist/cli/models.js +39 -0
  21. package/dist/cli/native-hook-ingress.js +1 -1
  22. package/dist/cli/operator-profiles.js +61 -0
  23. package/dist/cli/output-options.js +35 -0
  24. package/dist/cli/response-file.js +71 -0
  25. package/dist/cli/run-cli.js +283 -160
  26. package/dist/cli/setup.js +149 -0
  27. package/dist/cli.js +54 -2
  28. package/dist/domain/stage-handler-identities.js +20 -0
  29. package/dist/harness-runtime/lib/daemon-operations.d.mts +1 -0
  30. package/dist/harness-runtime/lib/daemon-operations.mjs +4 -3
  31. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +4 -2
  32. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +11 -5
  33. package/dist/harness-runtime/lib/dd-codex.mjs +22 -10
  34. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +2 -1
  35. package/dist/harness-runtime/lib/dd-droid.mjs +2 -1
  36. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +4 -2
  37. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +6 -4
  38. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +9 -9
  39. package/dist/harness-runtime/lib/delegation-instructions.mjs +5 -3
  40. package/dist/harness-runtime/lib/lifecycle-command.mjs +1 -0
  41. package/dist/harness-runtime/lib/managed-daemon.d.mts +1 -0
  42. package/dist/harness-runtime/lib/managed-daemon.mjs +50 -15
  43. package/dist/harness-runtime/lib/managed-flow-env.mjs +15 -0
  44. package/dist/harness-runtime/lib/native-hook-command.mjs +1 -1
  45. package/dist/harness-runtime/lib/native-request-proof.d.mts +2 -0
  46. package/dist/harness-runtime/lib/native-request-proof.mjs +65 -0
  47. package/dist/harness-runtime/lib/opencode-plugin.mjs +2 -0
  48. package/dist/harness-runtime/lib/operation-progress.mjs +3 -1
  49. package/dist/runtime/context.js +1 -1
  50. package/dist/schemas/discovery-result.schema.json +184 -0
  51. package/dist/schemas/execution-bundle.schema.json +205 -0
  52. package/dist/schemas/execution-settings.schema.json +162 -0
  53. package/dist/schemas/flow-descriptor.schema.json +54 -0
  54. package/dist/schemas/flow-run.schema.json +22 -0
  55. package/dist/schemas/harness-catalog.schema.json +80 -0
  56. package/dist/schemas/mb-init-compose-result.schema.json +457 -0
  57. package/dist/schemas/mb-init-discover-result.schema.json +500 -0
  58. package/dist/schemas/mb-init-finish-result.schema.json +422 -0
  59. package/dist/schemas/mb-init-preflight-result.schema.json +367 -0
  60. package/dist/schemas/mb-init-review-result.schema.json +396 -0
  61. package/dist/schemas/mb-init-verify-result.schema.json +391 -0
  62. package/dist/schemas/memory-bootstrap-receipt-v2.schema.json +67 -0
  63. package/dist/schemas/memory-bootstrap-receipt.schema.json +354 -0
  64. package/dist/schemas/memory-composition-work-result.schema.json +38 -0
  65. package/dist/schemas/memory-repair-work-result.schema.json +49 -0
  66. package/dist/schemas/memory-review-correction-plan.schema.json +30 -0
  67. package/dist/schemas/memory-review-work-result.schema.json +43 -0
  68. package/dist/schemas/model-catalog.schema.json +153 -0
  69. package/dist/schemas/profile-settings.schema.json +122 -0
  70. package/dist/schemas/release-manifest.schema.json +291 -0
  71. package/dist/schemas/run-engine-binding.schema.json +1 -1
  72. package/dist/services/check-runner-evidence.js +51 -4
  73. package/dist/services/cli-operation-classifier.js +8 -47
  74. package/dist/services/cli-upgrade-allowlist.js +27 -0
  75. package/dist/services/code-checks.js +387 -34
  76. package/dist/services/controller-execution.js +88 -0
  77. package/dist/services/controller-fanout.js +71 -12
  78. package/dist/services/dashboard.js +8 -2
  79. package/dist/services/descriptor-coordinator-session.js +35 -0
  80. package/dist/services/descriptor-root-work.js +53 -0
  81. package/dist/services/descriptor-run-settlement.js +105 -0
  82. package/dist/services/descriptor-run-start.js +449 -0
  83. package/dist/services/descriptor-stage-coordinator.js +197 -0
  84. package/dist/services/descriptor-stage-settlement.js +222 -0
  85. package/dist/services/descriptor-stage-start.js +139 -0
  86. package/dist/services/discovery/agy-discovery.js +2 -0
  87. package/dist/services/discovery/codex-discovery.js +2 -0
  88. package/dist/services/discovery/droid-discovery.js +2 -0
  89. package/dist/services/discovery/grok-discovery.js +2 -0
  90. package/dist/services/discovery/opencode-discovery.js +2 -0
  91. package/dist/services/discovery/static.js +31 -0
  92. package/dist/services/discovery/zcode-discovery.js +2 -0
  93. package/dist/services/engines.js +39 -22
  94. package/dist/services/eval-snapshots.js +83 -25
  95. package/dist/services/execution-bundles.js +263 -0
  96. package/dist/services/execution-policy.js +20 -16
  97. package/dist/services/executor-onboarding.js +56 -0
  98. package/dist/services/external-work-launch.js +91 -11
  99. package/dist/services/flow-catalog.js +21 -0
  100. package/dist/services/flow-descriptors.js +82 -0
  101. package/dist/services/frozen-engine.js +242 -0
  102. package/dist/services/generic-execution.js +164 -0
  103. package/dist/services/harness-adapter.js +28 -2
  104. package/dist/services/harness-discovery.js +29 -0
  105. package/dist/services/harness-doctor.js +75 -0
  106. package/dist/services/harness-probe-effects.js +51 -0
  107. package/dist/services/harness-version-probe.js +100 -0
  108. package/dist/services/hooks.js +24 -6
  109. package/dist/services/hybrid-contracts.js +483 -0
  110. package/dist/services/ids.js +29 -1
  111. package/dist/services/installation-lock.js +95 -0
  112. package/dist/services/installed-flow-source.js +183 -0
  113. package/dist/services/lifecycle-invocations.js +257 -78
  114. package/dist/services/memory-accepted-composition.js +77 -0
  115. package/dist/services/memory-baseline-preservation.js +65 -0
  116. package/dist/services/memory-completed-correction-wave.js +320 -0
  117. package/dist/services/memory-compose-handler.js +258 -0
  118. package/dist/services/memory-composition-acceptance.js +199 -0
  119. package/dist/services/memory-composition-graph.js +134 -0
  120. package/dist/services/memory-composition-registration.js +146 -0
  121. package/dist/services/memory-composition-work-custody.js +151 -0
  122. package/dist/services/memory-composition-work-results.js +96 -0
  123. package/dist/services/memory-corrected-review-acceptance.js +129 -0
  124. package/dist/services/memory-corrected-review-batch.js +30 -0
  125. package/dist/services/memory-corrected-review-findings.js +106 -0
  126. package/dist/services/memory-corrected-review-inputs.js +210 -0
  127. package/dist/services/memory-corrected-review-projection.js +78 -0
  128. package/dist/services/memory-corrected-review-receipt.js +170 -0
  129. package/dist/services/memory-corrected-review-registration.js +191 -0
  130. package/dist/services/memory-correction-check-receipt.js +219 -0
  131. package/dist/services/memory-correction-input-owner.js +6 -0
  132. package/dist/services/memory-discover-handler.js +104 -0
  133. package/dist/services/memory-finish-evidence.js +435 -0
  134. package/dist/services/memory-finish-handler.js +215 -0
  135. package/dist/services/memory-finish-integration.js +280 -0
  136. package/dist/services/memory-finish-readiness.js +122 -0
  137. package/dist/services/memory-git-integration.js +151 -0
  138. package/dist/services/memory-historical-corrected-review.js +206 -0
  139. package/dist/services/memory-historical-original-review.js +130 -0
  140. package/dist/services/memory-historical-repair-registration.js +99 -0
  141. package/dist/services/memory-historical-second-review.js +128 -0
  142. package/dist/services/memory-init-bootstrap-readiness.js +210 -0
  143. package/dist/services/memory-init-contexts.js +89 -0
  144. package/dist/services/memory-init-controller-start.js +84 -0
  145. package/dist/services/memory-init-engine-release.js +223 -0
  146. package/dist/services/memory-init-git-policy.js +136 -0
  147. package/dist/services/memory-init-lint-capability.js +185 -0
  148. package/dist/services/memory-init-native-capacity.js +241 -0
  149. package/dist/services/memory-init-preflight.js +312 -0
  150. package/dist/services/memory-init-project-bootstrap.js +599 -0
  151. package/dist/services/memory-init-request-candidate.js +157 -0
  152. package/dist/services/memory-init-request-input.js +66 -0
  153. package/dist/services/memory-init-resource-storage.js +125 -0
  154. package/dist/services/memory-init-run-allocation.js +81 -0
  155. package/dist/services/memory-init-run-bundle.js +98 -0
  156. package/dist/services/memory-init-start.js +175 -0
  157. package/dist/services/memory-init-startup-custody.js +80 -0
  158. package/dist/services/memory-init-startup-input.js +240 -0
  159. package/dist/services/memory-init-storage.js +81 -0
  160. package/dist/services/memory-init-stores.js +359 -0
  161. package/dist/services/memory-init-worktrunk-capability.js +172 -0
  162. package/dist/services/memory-layer-publication.js +611 -0
  163. package/dist/services/memory-layer.js +156 -0
  164. package/dist/services/memory-lint-config.js +85 -0
  165. package/dist/services/memory-native-review-copy.js +165 -0
  166. package/dist/services/memory-normal-flow-entry.js +75 -0
  167. package/dist/services/memory-ordinary-defaults.js +112 -0
  168. package/dist/services/memory-preflight-bootstrap-custody.js +115 -0
  169. package/dist/services/memory-preflight-handler.js +259 -0
  170. package/dist/services/memory-repair-current-view.js +299 -0
  171. package/dist/services/memory-repair-document-delta.js +451 -0
  172. package/dist/services/memory-repair-history.js +491 -0
  173. package/dist/services/memory-repair-physical-binding.js +312 -0
  174. package/dist/services/memory-repair-publication-files.js +152 -0
  175. package/dist/services/memory-repair-publication-intent.js +27 -0
  176. package/dist/services/memory-repair-publication-plan.js +160 -0
  177. package/dist/services/memory-repair-publication-source.js +225 -0
  178. package/dist/services/memory-repair-publication.js +794 -0
  179. package/dist/services/memory-repair-registration.js +239 -0
  180. package/dist/services/memory-repair-work-baseline.js +413 -0
  181. package/dist/services/memory-repair-work-completion.js +97 -0
  182. package/dist/services/memory-repair-work-custody.js +73 -0
  183. package/dist/services/memory-repair-work-results.js +89 -0
  184. package/dist/services/memory-repair-workspace.js +252 -0
  185. package/dist/services/memory-repair-writer-copy.js +735 -0
  186. package/dist/services/memory-result-binding.js +173 -0
  187. package/dist/services/memory-retained-review-authority.js +129 -0
  188. package/dist/services/memory-retained-review-results.js +70 -0
  189. package/dist/services/memory-retained-second-baseline.js +102 -0
  190. package/dist/services/memory-retained-second-repairs.js +156 -0
  191. package/dist/services/memory-review-acceptance.js +214 -0
  192. package/dist/services/memory-review-correction-plan.js +171 -0
  193. package/dist/services/memory-review-handler.js +585 -0
  194. package/dist/services/memory-review-inputs.js +200 -0
  195. package/dist/services/memory-review-receipt.js +180 -0
  196. package/dist/services/memory-review-registration.js +108 -0
  197. package/dist/services/memory-review-work-custody.js +124 -0
  198. package/dist/services/memory-review-work-input.js +72 -0
  199. package/dist/services/memory-review-work-results.js +73 -0
  200. package/dist/services/memory-second-corrected-review-inputs.js +165 -0
  201. package/dist/services/memory-second-correction-plan.js +76 -0
  202. package/dist/services/memory-second-repair-baseline.js +91 -0
  203. package/dist/services/memory-second-repair-launch.js +614 -0
  204. package/dist/services/memory-second-repair-registration.js +88 -0
  205. package/dist/services/memory-second-repair-source.js +187 -0
  206. package/dist/services/memory-second-review-acceptance.js +115 -0
  207. package/dist/services/memory-source-observations.js +117 -0
  208. package/dist/services/memory-stage-dispatch.js +28 -0
  209. package/dist/services/memory-stage-results.js +199 -0
  210. package/dist/services/memory-verification-acceptance.js +135 -0
  211. package/dist/services/memory-verification-checks.js +252 -0
  212. package/dist/services/memory-verification-receipt.js +159 -0
  213. package/dist/services/memory-verify-handler.js +135 -0
  214. package/dist/services/memory-work-result-preparation.js +63 -0
  215. package/dist/services/model-catalog.js +109 -0
  216. package/dist/services/model-discovery.js +46 -0
  217. package/dist/services/operator-profiles.js +236 -0
  218. package/dist/services/projects.js +17 -2
  219. package/dist/services/recovery-snapshot-database.js +416 -1
  220. package/dist/services/release-archive-producer.js +52 -0
  221. package/dist/services/release-archive.js +143 -0
  222. package/dist/services/release-cache.js +142 -0
  223. package/dist/services/release-compatibility.js +18 -0
  224. package/dist/services/release-download.js +67 -0
  225. package/dist/services/release-envelope.js +104 -0
  226. package/dist/services/release-manifest.js +45 -0
  227. package/dist/services/release-payload.js +114 -0
  228. package/dist/services/release-producer.js +63 -0
  229. package/dist/services/release-provenance.js +160 -0
  230. package/dist/services/release-registry.js +66 -0
  231. package/dist/services/release-source.js +194 -0
  232. package/dist/services/repair-intents.js +1 -1
  233. package/dist/services/review-copy.js +10 -2
  234. package/dist/services/run-control.js +80 -9
  235. package/dist/services/run-controller-capture.js +10 -3
  236. package/dist/services/run-controller-process.js +21 -0
  237. package/dist/services/run-controller-recovery.js +3 -1
  238. package/dist/services/run-controller-state.js +1 -1
  239. package/dist/services/run-controller.js +101 -40
  240. package/dist/services/run-recovery-guard.js +2 -0
  241. package/dist/services/run-recovery.js +145 -5
  242. package/dist/services/run-start-custody.js +177 -0
  243. package/dist/services/runs.js +319 -25
  244. package/dist/services/setup.js +377 -0
  245. package/dist/services/stage-context.js +9 -1
  246. package/dist/services/stage-handlers.js +74 -0
  247. package/dist/services/stage-report-renderer.js +63 -0
  248. package/dist/services/stage-work-graph.js +17 -0
  249. package/dist/services/vnext-code.js +3 -3
  250. package/dist/services/vnext-execution-profile.js +2 -2
  251. package/dist/services/vnext-fanout.js +70 -9
  252. package/dist/services/vnext-merge.js +8 -3
  253. package/dist/services/vnext-plan-review.js +6 -4
  254. package/dist/services/work-registry.js +616 -71
  255. package/dist/services/workspace-files.js +1 -1
  256. package/dist/storage/database.js +84 -6
  257. package/dist/storage/writer-contract.js +11 -7
  258. package/package.json +13 -3
  259. package/tools/create-canon-release.mjs +33 -0
  260. package/tools/produce-hybrid-release.mjs +41 -0
@@ -0,0 +1,320 @@
1
+ import { AppError } from "../shared/errors.js";
2
+ import { hybridDigest } from "./hybrid-contracts.js";
3
+ import { PreparedRegisteredMemoryCorrections } from "./memory-repair-registration.js";
4
+ import { PreparedRetainedSecondMemoryRepairs } from "./memory-retained-second-repairs.js";
5
+ import { PreparedSettledMemoryRepair } from "./memory-repair-history.js";
6
+ import { PreparedMemoryRepairWorkspace } from "./memory-repair-workspace.js";
7
+ import { reviewSourcePaths } from "./review-copy.js";
8
+ import { readAcceptedStageData } from "./stage-report-renderer.js";
9
+ const token = Symbol("completed-memory-correction-wave");
10
+ const historicalToken = Symbol("historical-second-memory-correction-wave");
11
+ function refuse(reason) {
12
+ throw new AppError("memory_correction_wave_invalid", "Re-review lacks a completely settled correction graph and current document custody", 2, { reason, effect: "no_effect" });
13
+ }
14
+ /** Pure selection only. A repair claim does not establish current bytes or
15
+ * resolve a finding. Registered wave custody must validate these claims first.
16
+ * Assigned units always need independent re-review; previously accepted units
17
+ * only need it when their own evidence/artifacts intersect an actual delta. */
18
+ export function memoryCorrectionCurrentUnits(units, assignments) {
19
+ if (!Array.isArray(units) || !units.length || units.length > 256 || !Array.isArray(assignments) || assignments.length > 256
20
+ || units.some(unit => !unit || typeof unit.id !== "string" || !unit.id)
21
+ || new Set(units.map(unit => unit.id)).size !== units.length
22
+ || assignments.some(item => !item?.unit || !units.some(unit => unit.id === item.unit.id && unit.aspect === item.unit.aspect
23
+ && unit.applicability === item.unit.applicability))
24
+ || new Set(assignments.map(item => item.unit.id)).size !== assignments.length)
25
+ refuse("invalid_current_unit_inventory");
26
+ const current = new Map(assignments.map(item => [item.unit.id, item.unit]));
27
+ return units.map(unit => structuredClone(current.get(unit.id) ?? unit));
28
+ }
29
+ export function memoryCorrectionAffectedUnits(units, repairs, composition = []) {
30
+ if (!Array.isArray(units) || !units.length || units.length > 256 || !Array.isArray(repairs) || !repairs.length || repairs.length > 256
31
+ || units.some(unit => !unit || typeof unit.id !== "string" || !unit.id || !["applicable", "not-applicable"].includes(unit.applicability)
32
+ || !Array.isArray(unit.evidence) || !Array.isArray(unit.artifacts)
33
+ || [...unit.evidence, ...unit.artifacts].some(ref => !ref || typeof ref.root !== "string"
34
+ || !["project", "workspace", "canon", "run"].includes(ref.root) || typeof ref.path !== "string" || !ref.path
35
+ || ref.path.startsWith("/") || /^[A-Za-z]:/.test(ref.path) || ref.path.includes("\\") || ref.path.includes("\0")
36
+ || ref.path.split("/").some((part) => !part || part === "." || part === "..")
37
+ || typeof ref.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(ref.sha256)))
38
+ || new Set(units.map(unit => unit.id)).size !== units.length)
39
+ refuse("invalid_unit_inventory");
40
+ const applicable = new Set(units.filter(unit => unit.applicability === "applicable").map(unit => unit.id));
41
+ const affected = new Set(), changed = new Set();
42
+ for (const repair of repairs) {
43
+ if (!repair || !Array.isArray(repair.unit_ids) || !repair.unit_ids.length || new Set(repair.unit_ids).size !== repair.unit_ids.length
44
+ || repair.unit_ids.some((id) => !applicable.has(id)) || repair.result?.outcome !== "accepted"
45
+ || !Array.isArray(repair.result.changes) || !repair.result.changes.length)
46
+ refuse("invalid_settled_repair_projection");
47
+ for (const id of repair.unit_ids)
48
+ affected.add(id);
49
+ for (const delta of repair.result.changes) {
50
+ if (!delta || typeof delta.path !== "string" || !delta.path.startsWith(".memory-bank/") || delta.path.includes("\\")
51
+ || delta.path.includes("\0") || delta.path.split("/").some((part) => !part || part === "." || part === "..")
52
+ || [delta.before_sha256, delta.after_sha256].some(hash => hash !== null && (typeof hash !== "string" || !/^[a-f0-9]{64}$/.test(hash)))
53
+ || delta.before_sha256 === delta.after_sha256)
54
+ refuse("invalid_settled_delta_projection");
55
+ changed.add(delta.path);
56
+ }
57
+ }
58
+ for (const unit of units)
59
+ if (unit.applicability === "applicable" && [...unit.evidence, ...unit.artifacts].some(ref => ["project", "workspace"].includes(ref.root) && changed.has(ref.path)))
60
+ affected.add(unit.id);
61
+ if (!Array.isArray(composition) || composition.length > 256 || composition.some(work => !work || typeof work.key !== "string" || !work.key
62
+ || !Array.isArray(work.unit_ids) || !work.unit_ids.length || work.unit_ids.some((id) => !applicable.has(id))
63
+ || !Array.isArray(work.depends_on) || new Set(work.depends_on).size !== work.depends_on.length)
64
+ || new Set(composition.map(work => work.key)).size !== composition.length)
65
+ refuse("invalid_composition_dependencies");
66
+ const remaining = new Map(composition.map(work => [work.key, work])), done = new Set(), impacted = new Set();
67
+ if (composition.some(work => work.depends_on.some((key) => !remaining.has(key) || key === work.key)))
68
+ refuse("invalid_composition_dependencies");
69
+ while (remaining.size) {
70
+ const ready = [...remaining.values()].filter(work => work.depends_on.every((key) => done.has(key)));
71
+ if (!ready.length)
72
+ refuse("invalid_composition_dependencies");
73
+ for (const work of ready) {
74
+ remaining.delete(work.key);
75
+ done.add(work.key);
76
+ }
77
+ }
78
+ // Units may span several composition Works. Reach a fixed point rather
79
+ // than making coverage depend on which independent branch sorted first.
80
+ let expanded;
81
+ do {
82
+ expanded = false;
83
+ for (const work of composition) {
84
+ const dependent = work.depends_on.some((key) => impacted.has(key));
85
+ if (!impacted.has(work.key) && (dependent || work.unit_ids.some((id) => affected.has(id)))) {
86
+ impacted.add(work.key);
87
+ expanded = true;
88
+ }
89
+ // Co-packing independent units into one Work is not a dependency.
90
+ // Only a declared changed predecessor expands its consumer units.
91
+ if (dependent)
92
+ for (const id of work.unit_ids)
93
+ if (!affected.has(id)) {
94
+ affected.add(id);
95
+ expanded = true;
96
+ }
97
+ }
98
+ } while (expanded);
99
+ return units.filter(unit => affected.has(unit.id)).map(unit => unit.id);
100
+ }
101
+ function assertNoActiveChildren(context, registration) {
102
+ const scope = registration.receipt().scope;
103
+ if (context.db.get("SELECT 1 FROM works w JOIN stage_work_memberships m ON m.work_id=w.work_id AND m.project_id=w.project_id AND m.run_id=w.run_id WHERE w.project_id=? AND w.run_id=? AND w.parent_work_id=? AND m.stage='review' AND m.attempt=? AND w.status IN ('created','running','paused') LIMIT 1", [scope.project_id, scope.run_id, scope.parent_work_id, scope.attempt]))
104
+ refuse("review_children_active");
105
+ }
106
+ function selection(context, registration) {
107
+ registration.assertCustody(context);
108
+ const receipt = registration.receipt(), batch = registration.batch(), scope = receipt.scope;
109
+ if (receipt.wave !== (PreparedRetainedSecondMemoryRepairs.isPrepared(registration) ? 2 : 1))
110
+ refuse("unsupported_correction_wave");
111
+ const states = batch.works.map(item => {
112
+ const workId = receipt.work_ids[item.key];
113
+ const row = context.db.get("SELECT status FROM works WHERE project_id=? AND run_id=? AND work_id=? AND parent_work_id=?", [scope.project_id, scope.run_id, workId, scope.parent_work_id]);
114
+ if (row?.status !== "completed")
115
+ refuse("correction_graph_not_completed");
116
+ return { key: item.key, work_id: workId };
117
+ });
118
+ const ids = new Set(states.map(item => item.work_id));
119
+ if (!ids.size)
120
+ refuse("settlement_history_missing_or_ambiguous");
121
+ const all = context.db.all(`SELECT id,json_extract(payload_json,'$.work_id') AS work_id FROM audit_events WHERE project_id=? AND event_type='memory.repair_work_settlement' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.work_id') IN (${[...ids].map(() => "?").join(",")}) ORDER BY id LIMIT 257`, [scope.project_id, scope.run_id, ...ids]);
122
+ if (all.length > 256)
123
+ refuse("unbounded_settlement_history");
124
+ const settlements = all.filter(row => ids.has(row.work_id));
125
+ if (!states.length || settlements.length !== states.length || new Set(settlements.map(row => row.work_id)).size !== states.length)
126
+ refuse("settlement_history_missing_or_ambiguous");
127
+ const order = new Map(settlements.map((row, index) => [row.work_id, index]));
128
+ for (const item of batch.works)
129
+ for (const dependency of item.depends_on) {
130
+ const predecessor = receipt.work_ids[dependency], workId = receipt.work_ids[item.key];
131
+ if (!predecessor || order.get(predecessor) >= order.get(workId))
132
+ refuse("dependency_settlement_order_changed");
133
+ }
134
+ return { registration_sha256: registration.sha256, states, settlements };
135
+ }
136
+ const nonMemory = (entry) => entry.path !== ".memory-bank" && !entry.path.startsWith(".memory-bank/");
137
+ /** Closed second graph custody, not current workspace admission or accepted
138
+ * review. Reconstruction starts at independently retained first-wave state;
139
+ * neither current files nor a second writer's claimed before-view are anchors. */
140
+ export class PreparedHistoricalSecondMemoryCorrectionWave {
141
+ static isPrepared(value) {
142
+ return typeof value === "object" && value !== null && #registration in value;
143
+ }
144
+ #registration;
145
+ #selected;
146
+ #histories;
147
+ #baseline;
148
+ #affected;
149
+ constructor(key, registration, selected, histories, baseline, affected) {
150
+ if (key !== historicalToken)
151
+ refuse("historical_second_wave_token_required");
152
+ this.#registration = registration;
153
+ this.#selected = structuredClone(selected);
154
+ this.#histories = histories;
155
+ this.#baseline = baseline;
156
+ this.#affected = [...affected];
157
+ Object.freeze(this);
158
+ }
159
+ static prepare(context, input) {
160
+ if (context.db.isOwnedWriteTransaction?.())
161
+ refuse("preparation_outside_writer_required");
162
+ const registration = PreparedRetainedSecondMemoryRepairs.prepareAfterReview(context, input);
163
+ const selected = context.db.readSnapshot(() => selection(context, registration));
164
+ const histories = selected.settlements.map(row => PreparedSettledMemoryRepair.prepare(context, registration, row.work_id));
165
+ let baseline = registration.initialDocumentBaseline();
166
+ const prefixes = new Set([hybridDigest(baseline.entries())]);
167
+ for (const history of histories) {
168
+ if (!prefixes.has(hybridDigest(history.before().entries())))
169
+ refuse("unexplained_historical_input_view");
170
+ baseline = baseline.composeSettledDelta(history.before(), history.after());
171
+ prefixes.add(hybridDigest(baseline.entries()));
172
+ }
173
+ const batch = registration.batch(), receipt = registration.receipt(), first = registration.initialInputView();
174
+ const compose = readAcceptedStageData(context, { ...input.scope, stage: "compose" });
175
+ const affected = memoryCorrectionAffectedUnits(memoryCorrectionCurrentUnits(first.unit_inventory, first.assignments), histories.map(history => ({ result: history.result(),
176
+ unit_ids: batch.works.find(item => receipt.work_ids[item.key] === history.workId).payload.memory_repair.unit_ids })), JSON.parse(compose.bytes.toString("utf8")).details.works);
177
+ const prepared = new PreparedHistoricalSecondMemoryCorrectionWave(historicalToken, registration, selected, histories, baseline, affected);
178
+ prepared.assertCustody(context);
179
+ return prepared;
180
+ }
181
+ registration() { return this.#registration; }
182
+ baseline() { return this.#baseline; }
183
+ histories() { return [...this.#histories]; }
184
+ affectedUnitIds() { return [...this.#affected]; }
185
+ repairResults() {
186
+ const batch = this.#registration.batch(), receipt = this.#registration.receipt();
187
+ return this.#histories.map(history => ({ work_id: history.workId, result: history.result(), reference: history.reference(),
188
+ authority_sha256: history.sha256,
189
+ unit_ids: batch.works.find(item => receipt.work_ids[item.key] === history.workId).payload.memory_repair.unit_ids }));
190
+ }
191
+ assertCustody(context) {
192
+ if (hybridDigest(selection(context, this.#registration)) !== hybridDigest(this.#selected))
193
+ refuse("historical_second_graph_changed");
194
+ for (const history of this.#histories)
195
+ history.assertCustody(context);
196
+ }
197
+ revalidate(context) {
198
+ this.#registration.revalidate(context);
199
+ for (const history of this.#histories)
200
+ history.revalidate(context);
201
+ this.assertCustody(context);
202
+ }
203
+ }
204
+ /** Current post-correction admission, not a historical acceptance receipt.
205
+ * Every Work must have independently settled publication custody. Shared
206
+ * documents are compared with composed retained transitions, never adopted
207
+ * from today's filesystem. This owner neither allocates reviewers nor marks
208
+ * repair findings resolved; independent re-review must consume it separately. */
209
+ export class PreparedCompletedMemoryCorrectionWave {
210
+ static isPrepared(value) {
211
+ return typeof value === "object" && value !== null && #selected in value;
212
+ }
213
+ sha256;
214
+ #registration;
215
+ #selected;
216
+ #histories;
217
+ #baseline;
218
+ #workspace;
219
+ #root;
220
+ #input;
221
+ #affected;
222
+ constructor(key, registration, selected, histories, baseline, workspace, root, input, affected) {
223
+ if (key !== token)
224
+ refuse("completed_wave_token_required");
225
+ this.#registration = registration;
226
+ this.#selected = structuredClone(selected);
227
+ this.#histories = histories;
228
+ this.#baseline = baseline;
229
+ this.#workspace = workspace;
230
+ this.#root = root;
231
+ this.#input = structuredClone(input);
232
+ this.#affected = [...affected];
233
+ this.sha256 = hybridDigest({ selected, histories: histories.map(history => history.sha256), baseline_sha256: baseline.sha256,
234
+ workspace_sha256: workspace.sha256, input, affected_unit_ids: affected });
235
+ Object.freeze(this);
236
+ }
237
+ static prepare(context, registration) {
238
+ if (!PreparedRegisteredMemoryCorrections.isPrepared(registration)
239
+ && !PreparedRetainedSecondMemoryRepairs.isPrepared(registration))
240
+ refuse("registered_corrections_required");
241
+ if (context.db.isOwnedWriteTransaction?.())
242
+ refuse("preparation_outside_writer_required");
243
+ // Preparing a read-only current view must also work after its successor
244
+ // reviewers have been allocated. Only fresh allocation requires no active
245
+ // children; that fence is consumed by its writer below.
246
+ const selected = context.db.readSnapshot(() => selection(context, registration));
247
+ const histories = selected.settlements.map(row => PreparedSettledMemoryRepair.prepare(context, registration, row.work_id));
248
+ const paths = [...new Set(registration.plan().works.flatMap(work => work.write_paths))].sort();
249
+ let baseline = (PreparedRetainedSecondMemoryRepairs.isPrepared(registration)
250
+ ? registration.initialDocumentBaseline() : histories[0].before()).withWritePaths(paths);
251
+ const known = new Set([hybridDigest(baseline.entries())]);
252
+ for (const history of histories) {
253
+ if (!known.has(hybridDigest(history.before().entries())))
254
+ refuse("unexplained_historical_input_view");
255
+ baseline = baseline.composeSettledDelta(history.before(), history.after());
256
+ known.add(hybridDigest(baseline.entries()));
257
+ }
258
+ const view = PreparedRetainedSecondMemoryRepairs.isPrepared(registration) ? registration.initialInputView()
259
+ : registration.review().original().registration().view;
260
+ const bundle = PreparedRetainedSecondMemoryRepairs.isPrepared(registration) ? registration.bundle()
261
+ : registration.review().original().bundle();
262
+ const root = bundle.execution.snapshot().scope.workspace;
263
+ baseline.revalidate(root);
264
+ const input = { base: view.base, explicit_inputs: view.run_inputs.filter(ref => ref.root === "workspace").map(ref => ref.path) };
265
+ const candidates = reviewSourcePaths(root, input.explicit_inputs, input.base);
266
+ if (!candidates)
267
+ refuse("source_inventory_unavailable");
268
+ const available = new Set(baseline.entries().filter(entry => entry.kind === "file").map(entry => entry.path));
269
+ const exported = baseline.exported(candidates.filter(logical => logical.startsWith(".memory-bank/") && available.has(logical)));
270
+ const workspace = PreparedMemoryRepairWorkspace.capture(root, input, exported);
271
+ const sourceSha256 = hybridDigest(workspace.entries().filter(nonMemory));
272
+ if (histories.some(history => hybridDigest(history.workspaceManifest().entries().filter(nonMemory)) !== sourceSha256))
273
+ refuse("non_memory_source_changed");
274
+ const batch = registration.batch(), receipt = registration.receipt();
275
+ const compose = readAcceptedStageData(context, { projectId: receipt.scope.project_id, runId: receipt.run_id, stage: "compose" });
276
+ const dependencies = JSON.parse(compose.bytes.toString("utf8")).details.works;
277
+ const currentUnits = PreparedRetainedSecondMemoryRepairs.isPrepared(registration)
278
+ ? memoryCorrectionCurrentUnits(registration.initialInputView().unit_inventory, registration.initialInputView().assignments)
279
+ : view.unit_inventory;
280
+ const affected = memoryCorrectionAffectedUnits(currentUnits, histories.map(history => ({
281
+ unit_ids: batch.works.find(item => receipt.work_ids[item.key] === history.workId).payload.memory_repair.unit_ids,
282
+ result: history.result()
283
+ })), dependencies);
284
+ const prepared = new PreparedCompletedMemoryCorrectionWave(token, registration, selected, histories, baseline, workspace, root, input, affected);
285
+ prepared.revalidate(context);
286
+ return prepared;
287
+ }
288
+ registration() { return this.#registration; }
289
+ affectedUnitIds() { return [...this.#affected]; }
290
+ baseline() { return this.#baseline; }
291
+ workspaceManifest() { return this.#workspace; }
292
+ repairResults() { return this.#histories.map(history => ({ work_id: history.workId, result: history.result(), reference: history.reference(), authority_sha256: history.sha256 })); }
293
+ /** Only SQL custody is inspected inside the future review-allocation writer. */
294
+ assertCustody(context) {
295
+ const selected = this.#selected;
296
+ if (hybridDigest(selection(context, this.#registration)) !== hybridDigest(selected))
297
+ refuse("completed_wave_custody_changed");
298
+ for (const history of this.#histories)
299
+ history.assertCustody(context);
300
+ }
301
+ /** Fresh allocation fence, before adding successor reviewers. General
302
+ * custody deliberately stays valid when those admitted children exist. */
303
+ assertAllocationAllowed(context) {
304
+ this.assertCustody(context);
305
+ assertNoActiveChildren(context, this.#registration);
306
+ }
307
+ revalidate(context) {
308
+ const root = this.#root;
309
+ this.assertCustody(context);
310
+ this.#registration.revalidate(context);
311
+ for (const history of this.#histories)
312
+ history.revalidate(context);
313
+ this.#baseline.revalidate(root);
314
+ this.#workspace.revalidateSource();
315
+ // Re-read the selected inventory as well as observed files: a newly added
316
+ // non-MB source must not slip between preparation and reviewer allocation.
317
+ this.#workspace.verifySelectedSourceInputs(root, this.#input, this.#baseline);
318
+ this.assertCustody(context);
319
+ }
320
+ }
@@ -0,0 +1,258 @@
1
+ import path from "node:path";
2
+ import { AppError } from "../shared/errors.js";
3
+ import { assertMemoryOrdinaryConfigurationCoverage, assertMemoryOrdinaryConfigurationOutputs } from "./memory-ordinary-defaults.js";
4
+ import { hybridDigest } from "./hybrid-contracts.js";
5
+ import { runtimeHarness } from "./execution-policy.js";
6
+ import { readFlowRunExecutionBundle, assertPreparedRunBundleSqlAuthority } from "./runs.js";
7
+ import { requireProjectByRoot } from "./projects.js";
8
+ import { resolveProjectRoot } from "../storage/paths.js";
9
+ import { PreparedDescriptorStageCoordinator, readDescriptorStageInput } from "./descriptor-stage-coordinator.js";
10
+ import { readDescriptorRootWork } from "./descriptor-root-work.js";
11
+ import { stageGraphScope } from "./stage-work-graph.js";
12
+ import { PreparedMemoryResultBinding } from "./memory-result-binding.js";
13
+ import { PreparedMemoryStageResult, memorySourceKey } from "./memory-stage-results.js";
14
+ import { captureMemoryPreflightSources } from "./memory-preflight-handler.js";
15
+ import { registerMemoryComposition, selectMemoryCompositionRegistration } from "./memory-composition-registration.js";
16
+ import { PreparedMemoryBaselinePreservation } from "./memory-baseline-preservation.js";
17
+ import { PreparedDescriptorStageStart } from "./descriptor-stage-start.js";
18
+ import { PreparedDescriptorStageSettlement } from "./descriptor-stage-settlement.js";
19
+ import { PreparedMemoryCompositionAcceptance } from "./memory-composition-acceptance.js";
20
+ import { readAcceptedStageData } from "./stage-report-renderer.js";
21
+ import { ObservedMemorySources } from "./memory-source-observations.js";
22
+ import { PreparedCheckEvidence } from "./code-checks.js";
23
+ function refuse(reason) {
24
+ throw new AppError("memory_compose_handler_invalid", "Memory composition lacks retained scope, plan or output custody", 2, { reason, effect: "no_effect" });
25
+ }
26
+ function selected(context, input) {
27
+ if (!(input.bytes instanceof Uint8Array) || input.bytes.length > 1024 * 1024)
28
+ refuse("invalid_input_bytes");
29
+ const bundle = readFlowRunExecutionBundle(context, input);
30
+ if (!bundle || bundle.descriptor.descriptor.id !== "mb-init" || bundle.descriptor.successor("discover", "accepted") !== "compose"
31
+ || bundle.descriptor.successor("compose", "accepted") !== "verify")
32
+ refuse("registered_memory_bundle_required");
33
+ const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot)), scope = { projectId: project.id, runId: input.runId, stage: "compose" };
34
+ const run = assertPreparedRunBundleSqlAuthority(context, scope, bundle), index = JSON.parse(run.index_json);
35
+ return { bundle, scope, run, accepted: index.stage_runs.find((stage) => stage.stage === "compose")?.status === "done" };
36
+ }
37
+ /** SQL-only physical binding proof; allocation/prompt custody is checked
38
+ * separately. No new Session, hook claim or provider invocation is created.
39
+ */
40
+ function coordinatorSession(context, scope, bundle, workId) {
41
+ const rows = context.db.all("SELECT ws.id,ws.session_id,ws.hook_event_id,ws.prompt_path,ws.result_path,s.harness,s.provider_session_id FROM work_sessions ws JOIN sessions s ON s.session_id=ws.session_id AND s.project_id=? WHERE ws.work_id=? AND ws.status='running' AND s.run_id=? AND s.current_stage=? AND s.status='active' LIMIT 2", [scope.projectId, workId, scope.runId, scope.stage]);
42
+ if (rows.length !== 1)
43
+ refuse("active_coordinator_session_required");
44
+ const session = rows[0], route = bundle.execution.stage(scope.stage);
45
+ const input = readDescriptorStageInput(context, scope, bundle), directory = bundle.descriptor.stage(scope.stage).directory;
46
+ if (session.prompt_path !== path.join(input.runRoot, directory, "coordinator-prompt.md")
47
+ || session.result_path !== path.join(input.runRoot, directory, "result-input.json"))
48
+ refuse("coordinator_packet_link_changed");
49
+ if (session.harness !== runtimeHarness(route.profile)
50
+ || !context.db.get("SELECT 1 FROM hook_events WHERE project_id=? AND event_key=? AND session_id=? AND harness=? AND provider_session_id=? AND status='claimed' AND event_name='PreToolUse'", [scope.projectId, session.hook_event_id, session.session_id, session.harness, session.provider_session_id]))
51
+ refuse("coordinator_start_custody_missing");
52
+ const bindings = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type='stage.coordinator_binding' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.scope.stage')=? AND json_extract(payload_json,'$.work_session_id')=? LIMIT 33", [scope.projectId, scope.runId, scope.stage, session.id]);
53
+ if (!bindings.length || bindings.length > 32 || bindings.some(row => Buffer.byteLength(row.payload_json) > 32768))
54
+ refuse("physical_binding_missing");
55
+ const binding = JSON.parse(bindings[0].payload_json);
56
+ if (bindings.some(row => hybridDigest(JSON.parse(row.payload_json)) !== hybridDigest(binding))
57
+ || binding.work_id !== workId || binding.session_id !== session.session_id || binding.harness !== session.harness || binding.provider_session_id !== session.provider_session_id
58
+ || binding.execution_sha256 !== bundle.execution.sha256 || binding.profile_id !== route.profile.id || binding.profile_sha256 !== hybridDigest(route.profile)
59
+ || binding.session_mode !== route.session_mode || hybridDigest(binding.scope) !== hybridDigest({ ...stageGraphScope(context, scope.projectId, scope.runId, scope.stage), stage: scope.stage }))
60
+ refuse("physical_binding_changed");
61
+ return hybridDigest({ session, binding });
62
+ }
63
+ /** Actual registration path for the composition handler, awaiting complete
64
+ * memory-flow admission. Registers the immutable blueprint before workers run;
65
+ * it is NOT final composition acceptance or proof of produced artifacts.
66
+ */
67
+ export function registerMemoryCompositionFromStage(context, input) {
68
+ if (!(input.bytes instanceof Uint8Array) || !input.bytes.length || input.bytes.length > 1024 * 1024)
69
+ refuse("invalid_plan_bytes");
70
+ const { bundle, scope, accepted } = selected(context, input);
71
+ if (accepted)
72
+ refuse("use_accepted_finish_replay");
73
+ const coordinator = PreparedDescriptorStageCoordinator.prepare(context, { ...input, stage: "compose" });
74
+ const allocated = context.db.readSnapshot(() => {
75
+ coordinator.assertAllocatedCustody(context);
76
+ const root = readDescriptorRootWork(context, scope, bundle);
77
+ return { workId: root.workId, physical: coordinatorSession(context, scope, bundle, root.workId) };
78
+ });
79
+ const binding = PreparedMemoryResultBinding.prepare(context, { ...scope, flow: bundle.descriptor, execution: bundle.execution, bundle,
80
+ sources: captureMemoryPreflightSources(context, bundle, input.bytes) });
81
+ let composition;
82
+ try {
83
+ composition = PreparedMemoryStageResult.prepare(input.bytes, bundle.descriptor, binding.binding());
84
+ }
85
+ catch (error) {
86
+ // Only proposal validation is correctable here: no registration writer has
87
+ // run. Source/coordinator custody failures and registration conflicts remain
88
+ // outside this boundary and cannot authorize another authoring attempt.
89
+ if (error instanceof AppError && error.code === "memory_stage_result_invalid"
90
+ && error.details.phase === "prepare" && error.details.effect === "no_effect")
91
+ error.details.recoverable = true;
92
+ throw error;
93
+ }
94
+ const ordinaryDefaults = readDescriptorStageInput(context, scope, bundle).input.ordinary_defaults;
95
+ if (ordinaryDefaults) {
96
+ assertMemoryOrdinaryConfigurationCoverage(composition.value(), ordinaryDefaults);
97
+ if (ordinaryDefaults.missing.length && !context.db.get("SELECT 1 FROM audit_events WHERE project_id=? AND event_type='flow_run.stage_resumed' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.stage')='compose' LIMIT 1", [scope.projectId, scope.runId])) {
98
+ throw new AppError("memory_stage_result_invalid", "Resolve the missing ordinary configuration choice through the existing compose pause/answer path before registering Work", 2, { phase: "prepare", effect: "no_effect", recoverable: true, reason: "ordinary_configuration_choice_required", missing: ordinaryDefaults.missing });
99
+ }
100
+ }
101
+ binding.revalidate(context);
102
+ return registerMemoryComposition(context, { parentWorkId: allocated.workId, requestKey: input.requestKey, composition,
103
+ binding: { runId: input.runId, inputSha256: binding.binding().inputSha256, executionSha256: bundle.execution.sha256 } }, writer => {
104
+ binding.assertCustody(writer);
105
+ coordinator.assertAllocatedCustody(writer);
106
+ if (coordinatorSession(writer, scope, bundle, allocated.workId) !== allocated.physical)
107
+ refuse("coordinator_changed");
108
+ });
109
+ }
110
+ /** Unregistered until all six memory handlers are complete. Entry consumes
111
+ * accepted discovery/preflight; it does not create a second RUN or dispatch.
112
+ */
113
+ export function startMemoryComposition(context, input) {
114
+ const { bundle, scope, accepted } = selected(context, input);
115
+ if (accepted)
116
+ refuse("use_accepted_finish_replay");
117
+ const preservation = PreparedMemoryBaselinePreservation.prepare(context, bundle.execution);
118
+ const inherited = context.db.readSnapshot(() => ({ discovery: readAcceptedStageData(context, { ...scope, stage: "discover" }), preflight: readAcceptedStageData(context, { ...scope, stage: "preflight" }) }));
119
+ const preflight = JSON.parse(inherited.preflight.bytes.toString("utf8")), discovery = JSON.parse(inherited.discovery.bytes.toString("utf8"));
120
+ if (preflight.schema_id !== "dd-flow/mb-init-preflight-result@1" || preflight.run_id !== input.runId || preflight.outcome !== "accepted"
121
+ || discovery.schema_id !== "dd-flow/mb-init-discover-result@1"
122
+ || discovery.run_id !== input.runId || discovery.outcome !== "accepted")
123
+ refuse("accepted_discovery_and_baseline_required");
124
+ const fence = (writer) => {
125
+ preservation.assertCustody(writer);
126
+ if (readAcceptedStageData(writer, { ...scope, stage: "discover" }).authority_sha256 !== inherited.discovery.authority_sha256
127
+ || readAcceptedStageData(writer, { ...scope, stage: "preflight" }).authority_sha256 !== inherited.preflight.authority_sha256)
128
+ refuse("inherited_custody_changed");
129
+ };
130
+ const start = PreparedDescriptorStageStart.prepare(context, { ...input, stage: "compose" });
131
+ preservation.revalidate(context);
132
+ const stage = start.apply(context, fence), coordinator = PreparedDescriptorStageCoordinator.prepare(context, { ...input, stage: "compose" });
133
+ preservation.revalidate(context);
134
+ return { ...stage, ...coordinator.apply(context, fence) };
135
+ }
136
+ /** Agent references are selectors, not permissions. Supplement independent
137
+ * baseline evidence only with current outputs admitted by completed Work.
138
+ */
139
+ export function captureMemoryCompositionSources(context, bundle, runRoot, bytes, outputs, checks, proposalSha256) {
140
+ let value;
141
+ try {
142
+ value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
143
+ }
144
+ catch {
145
+ refuse("invalid_result_json");
146
+ }
147
+ const pending = [{ value, role: "source" }], hashes = outputs.outputHashes(), historical = outputs.historicalEvidence();
148
+ let count = 0;
149
+ const rejectSelectedReference = (reason, root, path, sha256) => {
150
+ if (proposalSha256 === undefined)
151
+ refuse(reason);
152
+ throw new AppError("memory_compose_handler_invalid", "Correct the proposed evidence reference to the selected bytes below, or omit the unsupported citation. Do not rerun settled checks or change the accepted output", 2, { reason, phase: "prepare", effect: "no_effect", recoverable: true, result_sha256: proposalSha256, selected_reference: { root, path, sha256 } });
153
+ };
154
+ const executionScope = bundle.execution.snapshot().scope;
155
+ const sharedProjectWorkspace = resolveProjectRoot(executionScope.projectRoot) === resolveProjectRoot(executionScope.workspace);
156
+ const workResults = new Map(outputs.resultEvidence().map(ref => [memorySourceKey(ref.root, ref.path), ref.sha256])), resultLocations = new Map();
157
+ if (checks !== undefined) {
158
+ if (!(checks instanceof PreparedCheckEvidence))
159
+ refuse("prepared_checks_required");
160
+ const scope = bundle.execution.snapshot().scope;
161
+ checks.assertOwner({ projectId: requireProjectByRoot(context, scope.projectRoot).id, runId: bundle.manifest.run_id });
162
+ checks.assertCustody(context);
163
+ for (const ref of checks.sourceReferences()) {
164
+ hashes.set(memorySourceKey(ref.root, ref.path), ref.sha256);
165
+ resultLocations.set(memorySourceKey(ref.root, ref.path), { root: ref.root, path: ref.path });
166
+ }
167
+ }
168
+ while (pending.length) {
169
+ if (++count > 100000)
170
+ refuse("result_tree_exceeds_bound");
171
+ const { value: node, role } = pending.pop();
172
+ if (Array.isArray(node)) {
173
+ for (const child of node)
174
+ pending.push({ value: child, role });
175
+ continue;
176
+ }
177
+ if (!node || typeof node !== "object")
178
+ continue;
179
+ const record = node;
180
+ if (typeof record.root === "string" && typeof record.path === "string" && typeof record.sha256 === "string") {
181
+ // Inherited blueprint evidence describes the prior input, not today's
182
+ // artifact bytes. Its prepared original owner retains source custody.
183
+ const inherited = role === "evidence" && historical.has(`${memorySourceKey(record.root, record.path)}\0${record.sha256}`);
184
+ const resultSha = role === "evidence" ? workResults.get(memorySourceKey(record.root, record.path)) : undefined;
185
+ if (resultSha !== undefined) {
186
+ if (record.sha256 !== resultSha)
187
+ rejectSelectedReference("unsettled_work_result_reference", record.root, record.path, resultSha);
188
+ resultLocations.set(memorySourceKey(record.root, record.path), { root: record.root, path: record.path });
189
+ }
190
+ const key = memorySourceKey(record.root, record.path);
191
+ const expected = hashes.get(key) ?? (sharedProjectWorkspace && record.root === "project"
192
+ && ["evidence", "artifacts"].includes(role) ? hashes.get(memorySourceKey("workspace", record.path)) : undefined);
193
+ if (expected !== undefined && !inherited) {
194
+ if (record.sha256 !== expected)
195
+ rejectSelectedReference("unsettled_output_reference", record.root, record.path, expected);
196
+ hashes.set(key, expected);
197
+ resultLocations.set(key, { root: record.root, path: record.path });
198
+ }
199
+ if (expected !== undefined || inherited || resultSha !== undefined) {
200
+ delete record.root;
201
+ delete record.path;
202
+ delete record.sha256;
203
+ }
204
+ }
205
+ for (const [key, child] of Object.entries(record))
206
+ pending.push({ value: child, role: key === "evidence" || key === "artifacts" ? key : role });
207
+ }
208
+ // Keep evidence/artifact roles for the shared collector; settled child output
209
+ // references already have their independent output owner above.
210
+ const observedBaseline = captureMemoryPreflightSources(context, bundle, Buffer.from(JSON.stringify(value))), locations = new Map();
211
+ for (const key of observedBaseline.hashes().keys()) {
212
+ const [root, logical] = key.split("\0");
213
+ locations.set(key, { root: root, path: logical });
214
+ }
215
+ for (const location of outputs.outputLocations())
216
+ locations.set(memorySourceKey(location.root, location.path), location);
217
+ for (const [key, location] of resultLocations)
218
+ locations.set(key, location);
219
+ const scope = bundle.execution.snapshot().scope, observations = ObservedMemorySources.capture({ project: scope.projectRoot, workspace: scope.workspace,
220
+ canon: path.join(scope.workspace, ".memory-bank"), run: runRoot }, [...locations.values()]);
221
+ const actual = observations.hashes();
222
+ for (const [key, expected] of [...observedBaseline.hashes(), ...hashes, ...[...workResults].filter(([key]) => resultLocations.has(key))])
223
+ if (actual.get(key) !== expected)
224
+ refuse("source_changed_during_final_preparation");
225
+ return observations;
226
+ }
227
+ /** Final acceptance keeps the registration blueprint immutable. Fresh/replay
228
+ * RUN/input/coordinator authority is selected independently; registered child
229
+ * outputs and their current bytes are rechecked before and within settlement.
230
+ * Semantic quality remains the following VERIFY and independent REVIEW gates.
231
+ */
232
+ export function finishMemoryComposition(context, input) {
233
+ const { bundle, scope, run, accepted } = selected(context, input), root = readDescriptorRootWork(context, scope, bundle);
234
+ if (!input.bytes.length)
235
+ refuse("invalid_result_bytes");
236
+ const registration = selectMemoryCompositionRegistration(context, { ...stageGraphScope(context, scope.projectId, scope.runId, scope.stage), stage: "compose", parent_work_id: root.workId });
237
+ const registrationScope = registration.scope;
238
+ const blueprintBinding = PreparedMemoryResultBinding.prepareRegisteredComposition(context, { ...scope, flow: bundle.descriptor, execution: bundle.execution, bundle,
239
+ registrationScope, acceptedReplay: accepted });
240
+ const outputs = PreparedMemoryCompositionAcceptance.prepare(context, { scope: registrationScope, flow: bundle.descriptor, engine: bundle.engine(), binding: blueprintBinding.binding() });
241
+ const sources = captureMemoryCompositionSources(context, bundle, run.run_root ?? run.run_dir, input.bytes, outputs);
242
+ const binding = PreparedMemoryResultBinding.prepare(context, { ...scope, flow: bundle.descriptor, execution: bundle.execution, bundle, sources, acceptedReplay: accepted, historicalEvidence: outputs.historicalEvidence() });
243
+ const replay = binding.acceptedBytes();
244
+ if (replay && !replay.equals(input.bytes))
245
+ refuse("accepted_result_changed");
246
+ const result = PreparedMemoryStageResult.prepare(input.bytes, bundle.descriptor, binding.binding());
247
+ outputs.assertResult(result);
248
+ const ordinaryDefaults = readDescriptorStageInput(context, scope, bundle, true, accepted).input.ordinary_defaults;
249
+ if (ordinaryDefaults && !replay) {
250
+ const selected = bundle.execution.snapshot().scope;
251
+ assertMemoryOrdinaryConfigurationOutputs(context, selected.workspace, ordinaryDefaults, result.value().units.flatMap(unit => unit.artifacts), { requestId: selected.requestId, projectRoot: selected.projectRoot, workspaceRoot: selected.workspace });
252
+ }
253
+ const settlement = PreparedDescriptorStageSettlement.prepare(context, { ...input, stage: "compose", bytes: result.bytes() });
254
+ blueprintBinding.revalidate(context);
255
+ binding.revalidate(context);
256
+ outputs.revalidate(context);
257
+ return settlement.apply(context, writer => { blueprintBinding.assertCustody(writer); binding.assertCustody(writer); outputs.assertCustody(writer); outputs.assertResult(result); });
258
+ }