@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,173 @@
1
+ import { Ajv2020 } from "ajv/dist/2020.js";
2
+ import { AppError } from "../shared/errors.js";
3
+ import { ResolvedFlowDescriptor } from "./flow-descriptors.js";
4
+ import { FrozenGenericExecution } from "./generic-execution.js";
5
+ import { hybridDigest } from "./hybrid-contracts.js";
6
+ import { ObservedMemorySources } from "./memory-source-observations.js";
7
+ import { memorySourceKey } from "./memory-stage-results.js";
8
+ import { assertPreparedRunBundleAuthority, assertPreparedRunBundleSqlAuthority, assertRunBundleSqlAuthority } from "./runs.js";
9
+ import { readAcceptedStageData } from "./stage-report-renderer.js";
10
+ import { PreparedMemoryBaselinePreservation } from "./memory-baseline-preservation.js";
11
+ import { readAcceptedDescriptorStageSettlement } from "./descriptor-stage-settlement.js";
12
+ import { entityReferenceVariants } from "../shared/entity-references.js";
13
+ import { stageLifecycleMatchKey } from "./hooks.js";
14
+ import { captureRegisteredMemoryCompositionSources } from "./memory-preflight-handler.js";
15
+ import { memoryCompositionRegistrationDigest } from "./memory-composition-registration.js";
16
+ import { PreparedCheckEvidence } from "./code-checks.js";
17
+ function refuse() { throw new AppError("memory_result_binding_custody_invalid", "Memory result binding lacks independently accepted stage custody", 1, { effect: "no_effect" }); }
18
+ /** Select identity, predecessor and unit coverage from SQL, not result fields. */
19
+ function select(context, input) {
20
+ if (input.checks !== undefined) {
21
+ if (!(input.checks instanceof PreparedCheckEvidence))
22
+ refuse();
23
+ input.checks.assertOwner({ ...input, scope: "aggregate" });
24
+ input.checks.assertCustody(context);
25
+ }
26
+ const run = context.db.get("SELECT index_json, project_root, workspace_root FROM runs WHERE project_id = ? AND id = ?", [input.projectId, input.runId]);
27
+ if (!run)
28
+ refuse();
29
+ const index = JSON.parse(run.index_json), frozen = input.execution.snapshot();
30
+ if (assertRunBundleSqlAuthority(context, input, index.execution_bundle)) {
31
+ if (!input.bundle || input.bundle.execution.sha256 !== input.execution.sha256 || input.bundle.descriptor.sha256 !== input.flow.sha256)
32
+ refuse();
33
+ assertPreparedRunBundleSqlAuthority(context, input, input.bundle);
34
+ }
35
+ const stage = index.stage_runs?.find((item) => item.stage === input.stage);
36
+ if (input.flow.descriptor.id !== "mb-init" || frozen.settings.flow_id !== "mb-init" || frozen.settings.descriptor_sha256 !== input.flow.sha256
37
+ || index.execution_bundle && (index.execution_bundle.flow_id !== "mb-init" || index.execution_bundle.descriptor_sha256 !== input.flow.sha256)
38
+ || frozen.scope.projectRoot !== run.project_root || frozen.scope.workspace !== run.workspace_root
39
+ || (input.acceptedReplay ? stage?.status !== "done" : index.current_stage !== input.stage || stage?.status !== "running") || stage.dir !== input.flow.stage(input.stage).directory
40
+ || typeof stage.attempt !== "string" || !/^try-[0-9]{3,}$/.test(stage.attempt)
41
+ || typeof stage.input_sha256 !== "string" || !/^[a-f0-9]{64}$/.test(stage.input_sha256))
42
+ refuse();
43
+ const accepted = input.acceptedReplay ? readAcceptedStageData(context, input) : null;
44
+ const acceptedValue = accepted ? JSON.parse(accepted.bytes.toString("utf8")) : null;
45
+ if (accepted && (accepted.data_schema_id !== `dd-flow/mb-init-${input.stage}-result@1`
46
+ || acceptedValue.schema_id !== accepted.data_schema_id || acceptedValue.run_id !== input.runId
47
+ || acceptedValue.stage !== input.stage || !(acceptedValue.outcome === "accepted" || input.stage === "verify" && acceptedValue.outcome === "needs_repair" && input.flow.successor("verify", "needs_repair") === "review")
48
+ || acceptedValue.input_sha256 !== stage.input_sha256))
49
+ refuse();
50
+ const settlement = accepted && input.bundle ? readAcceptedDescriptorStageSettlement(context, input, input.bundle, input.matchKeys) : null;
51
+ const edges = input.flow.descriptor.edges.filter(edge => edge.to === input.stage && edge.on === "accepted");
52
+ const entry = input.stage === input.flow.descriptor.entry_stage;
53
+ if (edges.length !== (entry ? 0 : 1))
54
+ refuse();
55
+ const predecessor = entry ? null : readAcceptedStageData(context, { ...input, stage: edges[0].from });
56
+ const value = predecessor ? JSON.parse(predecessor.bytes.toString("utf8")) : null;
57
+ const preflight = entry ? null : readAcceptedStageData(context, { ...input, stage: "preflight" });
58
+ const preflightValue = preflight ? JSON.parse(preflight.bytes.toString("utf8")) : null;
59
+ if (preflight && (preflight.data_schema_id !== "dd-flow/mb-init-preflight-result@1"
60
+ || preflightValue.schema_id !== preflight.data_schema_id || preflightValue.run_id !== input.runId
61
+ || preflightValue.stage !== "preflight" || preflightValue.outcome !== "accepted"
62
+ || preflightValue.input_sha256 !== preflight.input_sha256
63
+ || typeof preflight.input_sha256 !== "string" || !/^[a-f0-9]{64}$/.test(preflight.input_sha256)))
64
+ refuse();
65
+ if (predecessor && (predecessor.data_schema_id !== `dd-flow/mb-init-${edges[0].from}-result@1`
66
+ || value.schema_id !== predecessor.data_schema_id || value.run_id !== input.runId || value.stage !== edges[0].from
67
+ || !(value.outcome === "accepted" || edges[0].from === "verify" && value.outcome === "needs_repair" && input.flow.successor("verify", "needs_repair") === input.stage)
68
+ || typeof predecessor.input_sha256 !== "string" || !/^[a-f0-9]{64}$/.test(predecessor.input_sha256)
69
+ || value.input_sha256 !== predecessor.input_sha256 || !Array.isArray(value.units)))
70
+ refuse();
71
+ const unitIds = ["preflight", "discover"].includes(input.stage) ? null : value?.units.map((unit) => unit.id);
72
+ if (unitIds !== null && (!Array.isArray(unitIds) || unitIds.some(id => typeof id !== "string") || new Set(unitIds).size !== unitIds.length))
73
+ refuse();
74
+ const binding = { runId: input.runId, stage: input.stage, inputSha256: stage.input_sha256,
75
+ predecessors: predecessor ? [{ stage: edges[0].from, result: predecessor.result, input_sha256: predecessor.input_sha256 }] : [],
76
+ unitIds, sources: new Map([...input.sources.hashes(), ...(input.checks?.artifactReferences() ?? []).map(ref => [memorySourceKey(ref.root, ref.path), ref.sha256])]),
77
+ historicalEvidence: new Set([...input.historicalEvidence ?? [], ...(value?.units ?? []).flatMap((unit) => unit.evidence.map(ref => `${memorySourceKey(ref.root, ref.path)}\0${ref.sha256}`))]),
78
+ execution: input.execution, executionSha256: input.execution.sha256,
79
+ ...(preflight ? { preflight: { result: preflight.result, input_sha256: preflight.input_sha256,
80
+ semantic_scope: preflightValue.details.semantic_scope } } : {}) };
81
+ const authority = hybridDigest({ projectId: input.projectId, runId: input.runId, stage: input.stage,
82
+ attempt: stage.attempt, input: stage.input_sha256, directory: stage.dir, predecessor: predecessor?.authority_sha256 ?? null,
83
+ anchor: index.execution_bundle ?? null, execution: input.execution.sha256, preflight: preflight?.authority_sha256 ?? null,
84
+ accepted: accepted?.authority_sha256 ?? null, settlement: settlement ? hybridDigest(settlement) : null });
85
+ return { binding, authority, predecessorStage: edges[0]?.from, value, preflightValue, accepted, acceptedValue };
86
+ }
87
+ /** Controller-owned input for result preparation and writer-safe rechecking.
88
+ * Semantic/check acceptance still belongs to productive stage handlers.
89
+ */
90
+ export class PreparedMemoryResultBinding {
91
+ #selection;
92
+ #binding;
93
+ #authority;
94
+ #preservation;
95
+ #accepted;
96
+ #registration = null;
97
+ constructor(input, selected, preservation) {
98
+ this.#selection = { ...input };
99
+ this.#binding = selected.binding;
100
+ this.#authority = selected.authority;
101
+ this.#preservation = preservation;
102
+ this.#accepted = selected.accepted ? Buffer.from(selected.accepted.bytes) : null;
103
+ Object.freeze(this);
104
+ }
105
+ static prepare(context, input) {
106
+ if (!(input.flow instanceof ResolvedFlowDescriptor) || !(input.execution instanceof FrozenGenericExecution) || !(input.sources instanceof ObservedMemorySources))
107
+ refuse();
108
+ if (input.acceptedReplay !== undefined && typeof input.acceptedReplay !== "boolean")
109
+ refuse();
110
+ if (input.bundle)
111
+ assertPreparedRunBundleAuthority(context, input, input.bundle);
112
+ const preparedInput = { ...input, matchKeys: input.acceptedReplay && input.bundle
113
+ ? entityReferenceVariants(input.runId).map(reference => stageLifecycleMatchKey("stage_finish", reference, input.stage, input.execution.snapshot().scope.projectRoot)) : [] };
114
+ const selected = context.db.readSnapshot(() => select(context, preparedInput));
115
+ if (selected.acceptedValue) {
116
+ const schema = JSON.parse(input.flow.stageBlob(input.stage, "result_schema").toString("utf8"));
117
+ if (!new Ajv2020({ strict: true, strictRequired: false, validateFormats: false }).compile(schema)(selected.acceptedValue))
118
+ refuse();
119
+ }
120
+ if (selected.predecessorStage) {
121
+ const schema = JSON.parse(input.flow.stageBlob(selected.predecessorStage, "result_schema").toString("utf8"));
122
+ if (!new Ajv2020({ strict: true, strictRequired: false, validateFormats: false }).compile(schema)(selected.value))
123
+ refuse();
124
+ }
125
+ if (selected.preflightValue) {
126
+ const schema = JSON.parse(input.flow.stageBlob("preflight", "result_schema").toString("utf8"));
127
+ if (!new Ajv2020({ strict: true, strictRequired: false, validateFormats: false }).compile(schema)(selected.preflightValue))
128
+ refuse();
129
+ }
130
+ // Productive bound discovery/composition preserve independently accepted source
131
+ // bytes. Unbound data-only SDK fixtures gain no bootstrap qualification.
132
+ const preservation = input.bundle && ["discover", "compose"].includes(input.stage) ? PreparedMemoryBaselinePreservation.prepare(context, input.execution) : null;
133
+ const prepared = new PreparedMemoryResultBinding(preparedInput, selected, preservation);
134
+ prepared.assertCustody(context);
135
+ return prepared;
136
+ }
137
+ static prepareRegisteredComposition(context, input) {
138
+ if (input.registrationScope.project_id !== input.projectId || input.registrationScope.run_id !== input.runId
139
+ || input.bundle.execution.sha256 !== input.execution.sha256 || input.bundle.descriptor.sha256 !== input.flow.sha256)
140
+ refuse();
141
+ const observed = captureRegisteredMemoryCompositionSources(context, input.bundle, input.registrationScope);
142
+ const prepared = this.prepare(context, { ...input, stage: "compose", sources: observed.sources });
143
+ if (prepared.#binding.inputSha256 !== observed.inputSha256)
144
+ refuse();
145
+ prepared.#registration = { scope: structuredClone(input.registrationScope), sha256: observed.registrationSha256 };
146
+ prepared.#binding = { ...prepared.#binding, sources: new Map([...prepared.#binding.sources, ...observed.historical]) };
147
+ prepared.assertCustody(context);
148
+ return prepared;
149
+ }
150
+ binding() {
151
+ return { ...this.#binding, historicalEvidence: new Set(this.#binding.historicalEvidence), predecessors: structuredClone(this.#binding.predecessors), unitIds: structuredClone(this.#binding.unitIds),
152
+ ...(this.#binding.preflight ? { preflight: structuredClone(this.#binding.preflight) } : {}), sources: new Map(this.#binding.sources) };
153
+ }
154
+ /** Retained accepted data for handler replay, never caller-supplied bytes. */
155
+ acceptedBytes() { return this.#accepted ? Buffer.from(this.#accepted) : null; }
156
+ assertCustody(context) {
157
+ context.db.readSnapshot(() => {
158
+ this.#preservation?.assertCustody(context);
159
+ if (this.#registration && memoryCompositionRegistrationDigest(context, this.#registration.scope) !== this.#registration.sha256)
160
+ refuse();
161
+ if (select(context, this.#selection).authority !== this.#authority)
162
+ refuse();
163
+ });
164
+ }
165
+ revalidate(context) {
166
+ this.#selection.checks?.revalidate(context);
167
+ this.#preservation?.revalidate(context);
168
+ this.#selection.sources.revalidate();
169
+ if (this.#selection.bundle)
170
+ assertPreparedRunBundleAuthority(context, this.#selection, this.#selection.bundle);
171
+ this.assertCustody(context);
172
+ }
173
+ }
@@ -0,0 +1,129 @@
1
+ import { AppError } from "../shared/errors.js";
2
+ import { hybridDigest } from "./hybrid-contracts.js";
3
+ import { RetainedExecutionBundle } from "./execution-bundles.js";
4
+ import { assertPreparedRunBundleSqlAuthority } from "./runs.js";
5
+ import { assertRunMutationAllowed } from "./run-recovery-guard.js";
6
+ import { readMemoryReviewPhase } from "./memory-review-inputs.js";
7
+ import { readRegisteredMemoryReview } from "./memory-review-registration.js";
8
+ import { PreparedDescriptorStageCoordinator } from "./descriptor-stage-coordinator.js";
9
+ import { readDescriptorCoordinatorSession } from "./descriptor-coordinator-session.js";
10
+ import { PreparedMemoryBaselinePreservation } from "./memory-baseline-preservation.js";
11
+ import { PreparedAcceptedMemoryVerification } from "./memory-verification-receipt.js";
12
+ import { PreparedMemoryResultBinding } from "./memory-result-binding.js";
13
+ import { ObservedMemorySources } from "./memory-source-observations.js";
14
+ import { readAcceptedStageData } from "./stage-report-renderer.js";
15
+ import { memorySourceKey } from "./memory-stage-results.js";
16
+ function refuse(reason) {
17
+ throw new AppError("memory_retained_review_invalid", "Retained review lacks independent original-wave authority", 2, { reason, effect: "no_effect" });
18
+ }
19
+ function original(context, scope, bundle) {
20
+ const phase = readMemoryReviewPhase(context, scope, bundle), registration = readRegisteredMemoryReview(context, phase, 0);
21
+ const compose = readAcceptedStageData(context, { ...scope, stage: "compose" });
22
+ const verify = readAcceptedStageData(context, { ...scope, stage: "verify" });
23
+ const composed = JSON.parse(compose.bytes.toString("utf8")), verified = JSON.parse(verify.bytes.toString("utf8"));
24
+ const view = registration.view;
25
+ if (registration.input_sha256 !== hybridDigest(view) || view.wave !== 0 || view.scope.bundle_sha256 !== bundle.sha256
26
+ || view.scope.execution_sha256 !== bundle.execution.sha256 || view.source_policy !== "review-inputs@1"
27
+ || compose.data_schema_id !== "dd-flow/mb-init-compose-result@1" || composed.schema_id !== compose.data_schema_id
28
+ || composed.run_id !== scope.runId || composed.stage !== "compose" || composed.outcome !== "accepted"
29
+ || composed.input_sha256 !== compose.input_sha256 || hybridDigest(composed.units) !== hybridDigest(view.unit_inventory)
30
+ || verify.data_schema_id !== "dd-flow/mb-init-verify-result@1" || verified.schema_id !== verify.data_schema_id
31
+ || verified.run_id !== scope.runId || verified.stage !== "verify" || !["accepted", "needs_repair"].includes(verified.outcome)
32
+ || verified.input_sha256 !== verify.input_sha256)
33
+ refuse("changed_original_review_binding");
34
+ for (const accepted of [compose, verify]) {
35
+ const refs = view.run_inputs.filter(ref => ref.root === "run" && ref.path === accepted.result.path);
36
+ if (refs.length !== 1 || refs[0].sha256 !== accepted.result.sha256)
37
+ refuse("original_predecessor_missing_or_changed");
38
+ }
39
+ return { registration, compose, verify,
40
+ sha256: hybridDigest({ phase, registration, compose: compose.authority_sha256, verify: verify.authority_sha256 }) };
41
+ }
42
+ /** Historical original-wave proof for correction/restart, not current document
43
+ * admission. Changed MB outputs must be justified separately by registered
44
+ * repair settlements; this owner never rebases them or grants Work writes.
45
+ * Original user sources, accepted SQL predecessors, independent verification
46
+ * executions and their retained result files remain mandatory.
47
+ */
48
+ export class PreparedRetainedMemoryReviewAuthority {
49
+ sha256;
50
+ #scope;
51
+ #bundle;
52
+ #registration;
53
+ #original;
54
+ #generation;
55
+ #coordinator;
56
+ #session;
57
+ #preservation;
58
+ #compose;
59
+ #verify;
60
+ #observed;
61
+ constructor(scope, bundle, selected, generation, coordinator, session, preservation, compose, verify, observed) {
62
+ this.#scope = { ...scope };
63
+ this.#bundle = bundle;
64
+ this.#registration = structuredClone(selected.registration);
65
+ this.#original = selected.sha256;
66
+ this.#generation = generation;
67
+ this.#coordinator = coordinator;
68
+ this.#session = session;
69
+ this.#preservation = preservation;
70
+ this.#compose = compose;
71
+ this.#verify = verify;
72
+ this.#observed = observed;
73
+ this.sha256 = hybridDigest({ registration: selected.registration, original: selected.sha256 });
74
+ Object.freeze(this);
75
+ }
76
+ static prepare(context, scope, bundle) {
77
+ if (!(bundle instanceof RetainedExecutionBundle) || bundle.descriptor.descriptor.id !== "mb-init")
78
+ refuse("registered_memory_bundle_required");
79
+ const generation = assertRunMutationAllowed(context, scope.projectId, scope.runId);
80
+ const selected = context.db.readSnapshot(() => original(context, scope, bundle));
81
+ const run = assertPreparedRunBundleSqlAuthority(context, scope, bundle), root = run.run_root ?? run.run_dir;
82
+ const observed = ObservedMemorySources.capture({ run: root }, [selected.compose, selected.verify].map(data => ({ root: "run", path: data.result.path })));
83
+ const hashes = observed.hashes();
84
+ if ([selected.compose, selected.verify].some(data => hashes.get(memorySourceKey("run", data.result.path)) !== data.result.sha256))
85
+ refuse("retained_predecessor_bytes_changed");
86
+ const coordinator = PreparedDescriptorStageCoordinator.prepare(context, {
87
+ projectRoot: bundle.execution.snapshot().scope.projectRoot, runId: scope.runId, stage: "review"
88
+ });
89
+ coordinator.assertAllocatedCustody(context);
90
+ const session = readDescriptorCoordinatorSession(context, { scope: { ...scope, stage: "review" }, bundle,
91
+ workId: selected.registration.scope.parent_work_id }, refuse);
92
+ const preservation = PreparedMemoryBaselinePreservation.prepare(context, bundle.execution);
93
+ // Historical accepted compose binding intentionally observes only retained
94
+ // predecessor files. It does not claim today's repaired documents still
95
+ // have the original output hashes or rerun current project policy.
96
+ const compose = PreparedMemoryResultBinding.prepare(context, { ...scope, stage: "compose", flow: bundle.descriptor,
97
+ execution: bundle.execution, bundle, acceptedReplay: true, sources: observed });
98
+ if (!compose.acceptedBytes()?.equals(selected.compose.bytes))
99
+ refuse("accepted_composition_changed");
100
+ const verify = PreparedAcceptedMemoryVerification.prepare(context, { ...scope, bytes: selected.verify.bytes });
101
+ const prepared = new PreparedRetainedMemoryReviewAuthority(scope, bundle, selected, generation, coordinator, session, preservation, compose, verify, observed);
102
+ prepared.assertCustody(context);
103
+ return prepared;
104
+ }
105
+ registration() { return structuredClone(this.#registration); }
106
+ bundle() { return this.#bundle; }
107
+ verificationProfileHash(context) {
108
+ this.assertCustody(context);
109
+ return this.#verify.profileHash(context);
110
+ }
111
+ assertCustody(context) {
112
+ assertRunMutationAllowed(context, this.#scope.projectId, this.#scope.runId, this.#generation);
113
+ this.#coordinator.assertAllocatedCustody(context);
114
+ if (original(context, this.#scope, this.#bundle).sha256 !== this.#original
115
+ || readDescriptorCoordinatorSession(context, { scope: { ...this.#scope, stage: "review" }, bundle: this.#bundle,
116
+ workId: this.#registration.scope.parent_work_id }, refuse) !== this.#session)
117
+ refuse("original_review_authority_changed");
118
+ this.#preservation.assertCustody(context);
119
+ this.#compose.assertCustody(context);
120
+ this.#verify.assertCustody(context);
121
+ }
122
+ revalidate(context) {
123
+ this.#preservation.revalidate(context);
124
+ this.#compose.revalidate(context);
125
+ this.#verify.revalidate(context);
126
+ this.#observed.revalidate();
127
+ this.assertCustody(context);
128
+ }
129
+ }
@@ -0,0 +1,70 @@
1
+ import { AppError } from "../shared/errors.js";
2
+ import { hybridDigest } from "./hybrid-contracts.js";
3
+ import { runtimeHarness } from "./execution-policy.js";
4
+ import { PreparedRetainedMemoryReviewAuthority } from "./memory-retained-review-authority.js";
5
+ import { readMemoryReviewWorkSettlement } from "./memory-review-acceptance.js";
6
+ import { memoryReviewBatch } from "./memory-review-registration.js";
7
+ import { validateMemoryReviewWorkResult } from "./memory-review-work-results.js";
8
+ import { ObservedMemorySources } from "./memory-source-observations.js";
9
+ import { memorySourceKey } from "./memory-stage-results.js";
10
+ function refuse(reason) {
11
+ throw new AppError("memory_retained_review_results_invalid", "Correction lacks retained independent review results", 2, { reason, effect: "no_effect" });
12
+ }
13
+ /** Independent initial-review findings survive authorized document corrections.
14
+ * This proves historical results, not current document completeness, permission
15
+ * to mutate, resolution of a finding, or admission of another reviewer wave.
16
+ */
17
+ export class PreparedRetainedMemoryReviewResults {
18
+ sha256;
19
+ #owner;
20
+ #workers;
21
+ #results;
22
+ #observed;
23
+ #harness;
24
+ constructor(owner, workers, results, observed, harness) {
25
+ this.#owner = owner;
26
+ this.#workers = workers;
27
+ this.#results = results;
28
+ this.#observed = observed;
29
+ this.#harness = harness;
30
+ this.sha256 = hybridDigest({ original: owner.sha256, workers: workers.map(worker => ({ work_id: worker.workId,
31
+ authority: worker.authority, result: worker.result })), results });
32
+ Object.freeze(this);
33
+ }
34
+ static prepare(context, owner) {
35
+ if (!(owner instanceof PreparedRetainedMemoryReviewAuthority))
36
+ refuse("prepared_original_review_required");
37
+ owner.revalidate(context);
38
+ const registration = owner.registration(), bundle = owner.bundle(), view = registration.view;
39
+ const batch = memoryReviewBatch(view), harness = runtimeHarness(bundle.execution.work("review").worker);
40
+ const workers = context.db.readSnapshot(() => batch.works.map(item => readMemoryReviewWorkSettlement(context, registration, registration.work_ids[item.key], harness)));
41
+ const results = workers.map((worker, index) => validateMemoryReviewWorkResult(worker.bytes, bundle.engine(), {
42
+ workId: worker.workId, packet: batch.works[index].payload.memory_review, references: view.run_inputs
43
+ }));
44
+ const root = context.db.get("SELECT run_root,run_dir FROM runs WHERE project_id=? AND id=?", [view.scope.project_id, view.scope.run_id]);
45
+ if (!root)
46
+ refuse("owned_run_missing");
47
+ const observed = ObservedMemorySources.capture({ run: root.run_root ?? root.run_dir }, workers.map(worker => ({ root: "run", path: worker.result.path })));
48
+ const hashes = observed.hashes();
49
+ if (workers.some(worker => hashes.get(memorySourceKey("run", worker.result.path)) !== worker.result.sha256))
50
+ refuse("retained_review_result_changed");
51
+ const prepared = new PreparedRetainedMemoryReviewResults(owner, workers, results, observed, harness);
52
+ prepared.assertCustody(context);
53
+ return prepared;
54
+ }
55
+ original() { return this.#owner; }
56
+ results() { return structuredClone(this.#results); }
57
+ references() {
58
+ return this.#workers.map(worker => ({ reviewer_work_id: worker.workId,
59
+ input_sha256: this.#owner.registration().input_sha256, result: { ...worker.result },
60
+ unit_ids: [this.#results.find(result => result.work_id === worker.workId).unit_id] }));
61
+ }
62
+ assertCustody(context) {
63
+ this.#owner.assertCustody(context);
64
+ const registration = this.#owner.registration();
65
+ for (const worker of this.#workers)
66
+ if (readMemoryReviewWorkSettlement(context, registration, worker.workId, this.#harness).authority !== worker.authority)
67
+ refuse("retained_review_settlement_changed");
68
+ }
69
+ revalidate(context) { this.#owner.revalidate(context); this.#observed.revalidate(); this.assertCustody(context); }
70
+ }
@@ -0,0 +1,102 @@
1
+ import { AppError } from "../shared/errors.js";
2
+ import { RetainedExecutionBundle } from "./execution-bundles.js";
3
+ import { PreparedRetainedSecondMemoryRepairs } from "./memory-retained-second-repairs.js";
4
+ import { readWorkBatchCustody } from "./work-registry.js";
5
+ import { assertRunMutationAllowed } from "./run-recovery-guard.js";
6
+ import { hybridDigest } from "./hybrid-contracts.js";
7
+ import { PreparedMemoryRepairCurrentView } from "./memory-repair-current-view.js";
8
+ const token = Symbol("retained-second-memory-baseline");
9
+ function refuse(reason) {
10
+ throw new AppError("memory_retained_second_baseline_invalid", "Second baseline lacks independent original receipt and settled source custody", 2, { reason, effect: "no_effect" });
11
+ }
12
+ function selected(context, scope, workId) {
13
+ const rows = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type='memory.repair_work_baseline' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.work_id')=? LIMIT 2", [scope.project_id, scope.run_id, workId]);
14
+ if (!rows.length)
15
+ refuse("registered_baseline_required");
16
+ if (rows.length !== 1 || Buffer.byteLength(rows[0].payload_json) > 24 * 1024 * 1024)
17
+ refuse("ambiguous_or_unbounded_baseline");
18
+ try {
19
+ return JSON.parse(rows[0].payload_json);
20
+ }
21
+ catch {
22
+ return refuse("invalid_baseline");
23
+ }
24
+ }
25
+ /** Retained initial or registered post-peer second baseline, not start authority.
26
+ * No obsolete workspace/private bytes are inspected or recaptured. */
27
+ export class PreparedRetainedSecondMemoryBaseline {
28
+ static isPrepared(value) {
29
+ return typeof value === "object" && value !== null && #registration in value;
30
+ }
31
+ #registration;
32
+ #receipt;
33
+ #baseline;
34
+ #exported;
35
+ #current;
36
+ constructor(key, registration, receipt, baseline, exported, current = null) {
37
+ if (key !== token)
38
+ refuse("retained_baseline_token_required");
39
+ this.#registration = registration;
40
+ this.#receipt = structuredClone(receipt);
41
+ this.#baseline = baseline;
42
+ this.#exported = exported;
43
+ this.#current = current;
44
+ Object.freeze(this);
45
+ }
46
+ static prepare(context, input) {
47
+ if (!PreparedRetainedSecondMemoryRepairs.isPrepared(input.registration))
48
+ refuse("retained_second_registration_required");
49
+ if (!(input.bundle instanceof RetainedExecutionBundle) || input.bundle.descriptor.descriptor.id !== "mb-init")
50
+ refuse("registered_memory_bundle_required");
51
+ if (context.db.isOwnedWriteTransaction?.())
52
+ refuse("preparation_outside_writer_required");
53
+ const registration = input.registration, owner = registration.receipt(), scope = owner.scope;
54
+ registration.revalidate(context);
55
+ if (scope.bundle_sha256 !== input.bundle.sha256 || scope.execution_sha256 !== input.bundle.execution.sha256)
56
+ refuse("foreign_bundle");
57
+ const assigned = registration.batch().works.find(item => owner.work_ids[item.key] === input.workId);
58
+ if (!assigned)
59
+ refuse("foreign_second_work");
60
+ const receipt = selected(context, scope, input.workId), generation = assertRunMutationAllowed(context, scope.project_id, scope.run_id);
61
+ const current = receipt.schema_id === "dd-flow/memory-repair-work-baseline@5"
62
+ ? PreparedMemoryRepairCurrentView.restore(context, registration, input.workId) : null;
63
+ if (assigned.depends_on.length && !current)
64
+ refuse("settled_second_dependency_view_required");
65
+ const baseline = current ? current.baseline() : registration.initialDocumentBaseline().withWritePaths(assigned.payload.memory_repair.write_paths);
66
+ const files = new Set(baseline.entries().filter(entry => entry.kind === "file").map(entry => entry.path));
67
+ const selectedFiles = registration.initialInputView().run_inputs.filter(ref => ref.root === "workspace" && ref.path.startsWith(".memory-bank/") && files.has(ref.path)).map(ref => ref.path);
68
+ const exported = current ? current.exportedBaseline() : baseline.exported(selectedFiles);
69
+ const expected = { schema_id: current ? "dd-flow/memory-repair-work-baseline@5" : "dd-flow/memory-repair-work-baseline@4", wave: 2, project_id: scope.project_id, run_id: scope.run_id,
70
+ work_id: input.workId, generation, registration_sha256: hybridDigest(owner), allocation_sha256: hybridDigest(readWorkBatchCustody(context, input.workId)),
71
+ packet_sha256: assigned.payload.memory_repair.packet_sha256, workspace_root: input.bundle.execution.snapshot().scope.workspace,
72
+ first_review_input_sha256: owner.first_review_input_sha256, first_check_receipt_sha256: owner.first_check_receipt_sha256,
73
+ baseline_sha256: baseline.sha256, baseline_base64: baseline.bytes().toString("base64"), export_policy: "memory-repair-export@1",
74
+ exported_baseline_sha256: exported.sha256, exported_baseline_base64: exported.bytes().toString("base64"),
75
+ ...(current ? { current_view_sha256: current.sha256, current_view_receipt_sha256: hybridDigest(current.receipt()) } : {}) };
76
+ if (hybridDigest(receipt) !== hybridDigest(expected))
77
+ refuse("registered_baseline_changed");
78
+ const prepared = new PreparedRetainedSecondMemoryBaseline(token, registration, receipt, baseline, exported, current);
79
+ prepared.assertCustody(context);
80
+ return prepared;
81
+ }
82
+ receipt() { return structuredClone(this.#receipt); }
83
+ baseline() { return this.#baseline; }
84
+ exportedBaseline() { return this.#exported; }
85
+ copyInput() {
86
+ if (this.#current)
87
+ return this.#current.copyInput();
88
+ const view = this.#registration.initialInputView();
89
+ return { source_sha256: view.source_sha256, base: view.base, explicit_inputs: view.run_inputs.filter(ref => ref.root === "workspace").map(ref => ref.path) };
90
+ }
91
+ currentView() { return this.#current?.receipt() ?? null; }
92
+ assertCustody(context) {
93
+ this.#registration.assertCustody(context);
94
+ this.#current?.assertCustody(context);
95
+ const receipt = this.#receipt;
96
+ assertRunMutationAllowed(context, receipt.project_id, receipt.run_id, receipt.generation);
97
+ if (hybridDigest(selected(context, receipt, receipt.work_id)) !== hybridDigest(receipt)
98
+ || hybridDigest(readWorkBatchCustody(context, receipt.work_id)) !== receipt.allocation_sha256)
99
+ refuse("retained_baseline_custody_changed");
100
+ }
101
+ revalidate(context) { this.#registration.revalidate(context); this.assertCustody(context); }
102
+ }
@@ -0,0 +1,156 @@
1
+ import { createHash } from "node:crypto";
2
+ import { AppError } from "../shared/errors.js";
3
+ import { RetainedExecutionBundle } from "./execution-bundles.js";
4
+ import { hybridDigest } from "./hybrid-contracts.js";
5
+ import { PreparedHistoricalOriginalMemoryReview } from "./memory-historical-original-review.js";
6
+ import { PreparedHistoricalCorrectedMemoryReview } from "./memory-historical-corrected-review.js";
7
+ import { readRetainedCorrectedMemoryReviewRegistration } from "./memory-corrected-review-registration.js";
8
+ import { validateMemoryReviewCorrectionPlan } from "./memory-review-correction-plan.js";
9
+ import { memoryRepairBatch, memoryRepairWorkerSchema } from "./memory-repair-registration.js";
10
+ import { readWorkBatchCustody } from "./work-registry.js";
11
+ const token = Symbol("retained-second-memory-repairs");
12
+ function refuse(reason) {
13
+ throw new AppError("memory_retained_second_repairs_invalid", "Second repair history lacks independent plan, review and allocation custody", 2, { reason, effect: "no_effect" });
14
+ }
15
+ function selected(context, scope) {
16
+ const rows = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type='memory.second_repair_registration' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.scope.attempt')=? AND json_extract(payload_json,'$.scope.parent_work_id')=? LIMIT 2", [scope.project_id, scope.run_id, scope.attempt, scope.parent_work_id]);
17
+ if (!rows.length)
18
+ refuse("registration_missing");
19
+ if (rows.length !== 1 || Buffer.byteLength(rows[0].payload_json) > 2 * 1024 * 1024)
20
+ refuse("ambiguous_or_unbounded_registration");
21
+ let receipt;
22
+ try {
23
+ receipt = JSON.parse(rows[0].payload_json);
24
+ }
25
+ catch {
26
+ return refuse("invalid_registration");
27
+ }
28
+ if (!receipt || receipt.schema_id !== "dd-flow/memory-second-repair-registration@1" || receipt.run_id !== scope.run_id
29
+ || receipt.wave !== 2 || hybridDigest(receipt.scope) !== hybridDigest(scope)
30
+ || [receipt.first_review_registration_sha256, receipt.first_review_input_sha256, receipt.first_check_receipt_sha256,
31
+ receipt.review_sha256, receipt.projection_sha256, receipt.plan_sha256, receipt.batch_sha256].some(hash => typeof hash !== "string" || !/^[a-f0-9]{64}$/.test(hash))
32
+ || typeof receipt.plan_base64 !== "string")
33
+ refuse("changed_registration");
34
+ const bytes = Buffer.from(receipt.plan_base64, "base64");
35
+ if (!bytes.length || bytes.length > 1024 * 1024 || bytes.toString("base64") !== receipt.plan_base64
36
+ || createHash("sha256").update(bytes).digest("hex") !== receipt.plan_sha256)
37
+ refuse("changed_plan_bytes");
38
+ return receipt;
39
+ }
40
+ function assertAllocation(context, receipt, batch) {
41
+ const ids = receipt.work_ids, scope = receipt.scope;
42
+ if (!ids || typeof ids !== "object" || Array.isArray(ids) || Object.keys(ids).length !== batch.works.length
43
+ || Object.values(ids).some(id => typeof id !== "string" || !id) || new Set(Object.values(ids)).size !== batch.works.length
44
+ || hybridDigest(batch.works) !== receipt.batch_sha256)
45
+ refuse("changed_repair_batch");
46
+ for (const item of batch.works) {
47
+ const id = ids[item.key];
48
+ if (!id)
49
+ refuse("missing_repair_work");
50
+ const custody = readWorkBatchCustody(context, id);
51
+ if (custody.item_key !== item.key || custody.batch_sha256 !== receipt.batch_sha256
52
+ || hybridDigest(custody.work_ids) !== hybridDigest(ids) || hybridDigest(custody.scope) !== hybridDigest({
53
+ project_id: scope.project_id, run_id: scope.run_id, stage: "review", attempt: scope.attempt, cycle: "",
54
+ parent_work_id: scope.parent_work_id, request_key: "memory-repair-wave-2"
55
+ }))
56
+ refuse("changed_work_custody");
57
+ }
58
+ }
59
+ /** Retained second graph only. Does not grant current source/write admission,
60
+ * allocate replacement Works or dispatch providers after receipt loss. */
61
+ export class PreparedRetainedSecondMemoryRepairs {
62
+ static isPrepared(value) {
63
+ return typeof value === "object" && value !== null && #receipt in value;
64
+ }
65
+ #history;
66
+ #receipt;
67
+ #batch;
68
+ #plan;
69
+ #bundle;
70
+ constructor(key, history, receipt, batch, plan) {
71
+ if (key !== token)
72
+ refuse("retained_token_required");
73
+ this.#history = history;
74
+ this.#receipt = structuredClone(receipt);
75
+ this.#batch = structuredClone(batch);
76
+ this.#plan = structuredClone(plan);
77
+ this.#bundle = history.bundle();
78
+ Object.freeze(this);
79
+ }
80
+ static prepare(context, input) {
81
+ return this.#prepare(context, input, false);
82
+ }
83
+ /** Closed-review history only. The original owner pins closed SQL custody;
84
+ * this never reopens review or grants new writer/launch admission. */
85
+ static prepareAfterReview(context, input) {
86
+ return this.#prepare(context, input, true);
87
+ }
88
+ static #prepare(context, input, closed) {
89
+ if (!(input.bundle instanceof RetainedExecutionBundle) || input.bundle.descriptor.descriptor.id !== "mb-init")
90
+ refuse("registered_memory_bundle_required");
91
+ if (context.db.isOwnedWriteTransaction?.())
92
+ refuse("preparation_outside_writer_required");
93
+ const original = closed ? PreparedHistoricalOriginalMemoryReview.prepare(context, input)
94
+ : PreparedHistoricalOriginalMemoryReview.prepareWhileReviewOpen(context, input);
95
+ const scope = original.registration().scope;
96
+ const receipt = context.db.readSnapshot(() => selected(context, scope)), bytes = Buffer.from(receipt.plan_base64, "base64");
97
+ if (input.expectedBytes !== undefined && (!(input.expectedBytes instanceof Uint8Array) || !bytes.equals(Buffer.from(input.expectedBytes))))
98
+ refuse("plan_replay_conflict");
99
+ const first = readRetainedCorrectedMemoryReviewRegistration(context, scope, receipt.first_review_input_sha256, receipt.first_check_receipt_sha256);
100
+ if (!first || hybridDigest(first) !== receipt.first_review_registration_sha256)
101
+ refuse("changed_first_registration");
102
+ const history = PreparedHistoricalCorrectedMemoryReview.prepare(context, { original, correctedInputSha256: first.input_sha256,
103
+ checkReceiptSha256: first.check_receipt_sha256 });
104
+ if (history.reviewSha256() !== receipt.review_sha256 || hybridDigest(history.projection()) !== receipt.projection_sha256)
105
+ refuse("changed_first_review");
106
+ const view = first.view, assignments = new Map(view.assignments.map(item => [item.unit.id, item.unit]));
107
+ const plan = validateMemoryReviewCorrectionPlan(bytes, input.bundle.engine(), { runId: scope.run_id, wave: 2,
108
+ reviewSha256: receipt.review_sha256, units: view.unit_inventory.map(unit => assignments.get(unit.id) ?? unit), findings: history.projection().findings });
109
+ if (history.projection().outcome === "blocked")
110
+ refuse("blocked_first_review");
111
+ for (const item of plan.works)
112
+ input.bundle.execution.work("review", item.profile_id ?? undefined);
113
+ if (JSON.parse(input.bundle.engine().schema("memory-repair-work-result").toString("utf8")).$id !== memoryRepairWorkerSchema)
114
+ refuse("frozen_worker_schema_required");
115
+ const refs = new Map(view.run_inputs.map(ref => [`${ref.root}:${ref.path}`, ref]));
116
+ for (const worker of history.settlementProof(context).workers) {
117
+ const ref = { root: "run", ...worker.result }, key = `run:${ref.path}`, prior = refs.get(key);
118
+ if (prior && prior.sha256 !== ref.sha256)
119
+ refuse("conflicting_review_reference");
120
+ refs.set(key, ref);
121
+ }
122
+ const batch = memoryRepairBatch({ scope, plan, planSha256: receipt.plan_sha256, originalRegistrationSha256: hybridDigest(first),
123
+ references: [...refs.values()], findings: history.projection().findings });
124
+ const expected = { schema_id: "dd-flow/memory-second-repair-registration@1", run_id: scope.run_id, scope, wave: 2,
125
+ first_review_registration_sha256: hybridDigest(first), first_review_input_sha256: first.input_sha256,
126
+ first_check_receipt_sha256: first.check_receipt_sha256, review_sha256: history.reviewSha256(), projection_sha256: hybridDigest(history.projection()),
127
+ plan_sha256: receipt.plan_sha256, plan_base64: bytes.toString("base64"), batch_sha256: hybridDigest(batch.works), work_ids: receipt.work_ids };
128
+ if (hybridDigest(expected) !== hybridDigest(receipt))
129
+ refuse("changed_registration");
130
+ const prepared = new PreparedRetainedSecondMemoryRepairs(token, history, receipt, batch, plan);
131
+ prepared.revalidate(context);
132
+ return prepared;
133
+ }
134
+ receipt() { return structuredClone(this.#receipt); }
135
+ get sha256() { return hybridDigest(this.#receipt); }
136
+ bundle() { return this.#bundle; }
137
+ batch() { return structuredClone(this.#batch); }
138
+ plan() { return structuredClone(this.#plan); }
139
+ /** The first wave's actual settled baseline, with only this registered
140
+ * second plan's admitted write paths. No current bytes are adopted here. */
141
+ initialDocumentBaseline() {
142
+ const paths = [...new Set(this.#batch.works.flatMap(item => item.payload.memory_repair.write_paths))].sort();
143
+ return this.#history.settledDocumentBaseline().withWritePaths(paths);
144
+ }
145
+ initialInputView() { return this.#history.retainedInputView(); }
146
+ firstReviewProof(context) { this.assertCustody(context); return this.#history.settlementProof(context); }
147
+ firstEffectiveReviewProof(context) { this.assertCustody(context); return this.#history.effectiveReviewProof(context); }
148
+ verificationProfileHash(context) { this.assertCustody(context); return this.#history.verificationProfileHash(context); }
149
+ assertCustody(context) {
150
+ this.#history.assertCustody(context);
151
+ if (hybridDigest(selected(context, this.#receipt.scope)) !== hybridDigest(this.#receipt))
152
+ refuse("registration_changed");
153
+ assertAllocation(context, this.#receipt, this.#batch);
154
+ }
155
+ revalidate(context) { this.#history.revalidate(context); this.assertCustody(context); }
156
+ }