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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/CHANGELOG.md +130 -0
  2. package/README.md +13 -8
  3. package/dist/build-info.json +7 -7
  4. package/dist/cli/command-inputs.js +13 -3
  5. package/dist/cli/command-metadata.js +51 -16
  6. package/dist/cli/diagnostic-invocation.js +46 -0
  7. package/dist/cli/diagnostic.js +120 -0
  8. package/dist/cli/help.js +67 -1
  9. package/dist/cli/hook-ingress.js +27 -3
  10. package/dist/cli/memory-flow-admission.js +9 -3
  11. package/dist/cli/memory-init-invocation.js +10 -3
  12. package/dist/cli/memory-init.js +77 -68
  13. package/dist/cli/memory-storage-admission.js +3 -2
  14. package/dist/cli/memory-storage-qualification.js +105 -0
  15. package/dist/cli/memory-storage-transition.js +123 -0
  16. package/dist/cli/memory-upgrade.js +189 -0
  17. package/dist/cli/native-hook-ingress.js +14 -4
  18. package/dist/cli/native-hook-preparation.js +55 -0
  19. package/dist/cli/response-file.js +2 -1
  20. package/dist/cli/run-cli.js +79 -40
  21. package/dist/cli/setup.js +2 -1
  22. package/dist/cli.js +18 -0
  23. package/dist/domain/diagnostic-aspects.js +103 -0
  24. package/dist/domain/diagnostic-flows.js +8 -0
  25. package/dist/domain/stage-handler-identities.js +8 -0
  26. package/dist/harness-runtime/bin/dd-agy.mjs +3 -3
  27. package/dist/harness-runtime/bin/dd-grok.mjs +3 -3
  28. package/dist/harness-runtime/bin/dd-zcode.mjs +5 -5
  29. package/dist/harness-runtime/lib/agy-error-boundary.mjs +58 -0
  30. package/dist/harness-runtime/lib/agy-native-inspection.mjs +216 -0
  31. package/dist/harness-runtime/lib/agy-native_pb.js +117 -0
  32. package/dist/harness-runtime/lib/daemon-operations.d.mts +1 -0
  33. package/dist/harness-runtime/lib/daemon-operations.mjs +84 -10
  34. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +366 -52
  35. package/dist/harness-runtime/lib/dd-agy.mjs +17 -3
  36. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +27 -13
  37. package/dist/harness-runtime/lib/dd-codex.mjs +4 -0
  38. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +4 -3
  39. package/dist/harness-runtime/lib/dd-droid.mjs +71 -29
  40. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +73 -16
  41. package/dist/harness-runtime/lib/dd-grok.mjs +6 -3
  42. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +66 -20
  43. package/dist/harness-runtime/lib/dd-opencode.mjs +30 -3
  44. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +7 -6
  45. package/dist/harness-runtime/lib/dd-zcode.mjs +44 -14
  46. package/dist/harness-runtime/lib/delegation-instructions.mjs +1 -1
  47. package/dist/harness-runtime/lib/driver-recovery.mjs +9 -8
  48. package/dist/harness-runtime/lib/entity-references.d.mts +2 -0
  49. package/dist/harness-runtime/lib/entity-references.mjs +9 -0
  50. package/dist/harness-runtime/lib/managed-daemon.d.mts +1 -1
  51. package/dist/harness-runtime/lib/managed-daemon.mjs +24 -9
  52. package/dist/harness-runtime/lib/native-children.d.mts +8 -1
  53. package/dist/harness-runtime/lib/native-children.mjs +114 -8
  54. package/dist/harness-runtime/lib/native-hook-command.mjs +4 -4
  55. package/dist/harness-runtime/lib/native-request-proof.mjs +1 -1
  56. package/dist/harness-runtime/lib/observation-clock.d.mts +2 -2
  57. package/dist/harness-runtime/lib/observation-clock.mjs +7 -4
  58. package/dist/harness-runtime/lib/opencode-plugin.mjs +3 -3
  59. package/dist/harness-runtime/lib/operation-errors.mjs +1 -1
  60. package/dist/harness-runtime/lib/provider-overload-policy.d.mts +5 -3
  61. package/dist/harness-runtime/lib/provider-overload-policy.mjs +20 -7
  62. package/dist/harness-runtime/lib/runner-events.mjs +3 -1
  63. package/dist/harness-runtime/lib/setup-progress.mjs +8 -6
  64. package/dist/harness-runtime/lib/tool-observations.mjs +1 -1
  65. package/dist/schemas/diagnostic-stage-result.schema.json +354 -0
  66. package/dist/schemas/diagnostic-work-result.schema.json +296 -0
  67. package/dist/schemas/execution-bundle.schema.json +7 -1
  68. package/dist/schemas/mb-upgrade-stage-result.schema.json +863 -0
  69. package/dist/schemas/memory-repair-work-result.schema.json +9 -2
  70. package/dist/schemas/project-flow-pack-manifest.schema.json +1 -1
  71. package/dist/schemas/release-impact.schema.json +3 -3
  72. package/dist/schemas/stage-start-response.schema.json +4 -0
  73. package/dist/services/accepted-intent.js +13 -1
  74. package/dist/services/canon.js +43 -7
  75. package/dist/services/check-environment.js +21 -0
  76. package/dist/services/check-runner-evidence.js +267 -15
  77. package/dist/services/cli-operation-classifier.js +1 -1
  78. package/dist/services/cli-upgrade-allowlist.js +1 -1
  79. package/dist/services/code-checks.js +34 -19
  80. package/dist/services/compatibility-preflight.js +5 -2
  81. package/dist/services/controller-execution.js +10 -4
  82. package/dist/services/controller-fanout.js +34 -10
  83. package/dist/services/controller-memory-task.js +45 -0
  84. package/dist/services/controller-memory-worker.js +49 -0
  85. package/dist/services/dashboard.js +2 -2
  86. package/dist/services/descriptor-run-settlement.js +5 -3
  87. package/dist/services/descriptor-run-start.js +46 -20
  88. package/dist/services/descriptor-stage-coordinator.js +66 -13
  89. package/dist/services/descriptor-stage-settlement.js +2 -1
  90. package/dist/services/descriptor-stage-start.js +40 -4
  91. package/dist/services/diagnostic-evidence.js +73 -0
  92. package/dist/services/diagnostic-report-publication.js +72 -0
  93. package/dist/services/diagnostic-report.js +67 -0
  94. package/dist/services/diagnostic-run-bundle.js +74 -0
  95. package/dist/services/diagnostic-snapshot.js +159 -0
  96. package/dist/services/diagnostic-stage-handlers.js +108 -0
  97. package/dist/services/diagnostic-stage-results.js +132 -0
  98. package/dist/services/diagnostic-start.js +84 -0
  99. package/dist/services/diagnostic-work-batch.js +34 -0
  100. package/dist/services/diagnostic-work-collection.js +97 -0
  101. package/dist/services/diagnostic-work-registration.js +101 -0
  102. package/dist/services/diagnostic-work-results.js +93 -0
  103. package/dist/services/engines.js +64 -14
  104. package/dist/services/eval-snapshots.js +186 -119
  105. package/dist/services/execution-bundles.js +19 -7
  106. package/dist/services/external-work-launch.js +5 -3
  107. package/dist/services/flow-catalog.js +13 -4
  108. package/dist/services/frozen-engine.js +148 -11
  109. package/dist/services/hook-memory-routing.js +188 -0
  110. package/dist/services/hook-recovery-guard.js +14 -0
  111. package/dist/services/hook-session-observation.js +24 -0
  112. package/dist/services/hooks.js +27 -8
  113. package/dist/services/hybrid-contracts.js +4 -13
  114. package/dist/services/hybrid-digest.js +14 -0
  115. package/dist/services/installed-flow-source.js +47 -4
  116. package/dist/services/lifecycle-invocations.js +179 -38
  117. package/dist/services/memory-accepted-composition.js +5 -4
  118. package/dist/services/memory-completed-correction-wave.js +23 -15
  119. package/dist/services/memory-compose-handler.js +163 -21
  120. package/dist/services/memory-composition-acceptance.js +186 -10
  121. package/dist/services/memory-composition-graph.js +42 -11
  122. package/dist/services/memory-composition-registration.js +23 -8
  123. package/dist/services/memory-composition-work-custody.js +14 -9
  124. package/dist/services/memory-composition-work-results.js +15 -3
  125. package/dist/services/memory-corrected-review-batch.js +10 -3
  126. package/dist/services/memory-corrected-review-findings.js +4 -2
  127. package/dist/services/memory-corrected-review-inputs.js +10 -5
  128. package/dist/services/memory-corrected-review-receipt.js +17 -9
  129. package/dist/services/memory-corrected-review-registration.js +3 -2
  130. package/dist/services/memory-correction-check-receipt.js +20 -4
  131. package/dist/services/memory-custody-snapshot.js +36 -0
  132. package/dist/services/memory-finish-evidence.js +44 -22
  133. package/dist/services/memory-finish-handler.js +37 -23
  134. package/dist/services/memory-finish-integration.js +218 -48
  135. package/dist/services/memory-finish-readiness.js +66 -19
  136. package/dist/services/memory-git-integration.js +87 -18
  137. package/dist/services/memory-historical-corrected-review.js +21 -15
  138. package/dist/services/memory-historical-original-review.js +22 -9
  139. package/dist/services/memory-historical-repair-registration.js +14 -8
  140. package/dist/services/memory-historical-second-review.js +30 -11
  141. package/dist/services/memory-init-contexts.js +44 -2
  142. package/dist/services/memory-init-controller-start.js +27 -8
  143. package/dist/services/memory-init-git-policy.js +50 -21
  144. package/dist/services/memory-init-lint-capability.js +24 -6
  145. package/dist/services/memory-init-native-capacity.js +17 -4
  146. package/dist/services/memory-init-preflight.js +21 -17
  147. package/dist/services/memory-init-request-candidate.js +73 -17
  148. package/dist/services/memory-init-request-input.js +23 -7
  149. package/dist/services/memory-init-run-bundle.js +49 -12
  150. package/dist/services/memory-init-storage.js +17 -7
  151. package/dist/services/memory-init-worktrunk-capability.js +3 -7
  152. package/dist/services/memory-layer.js +71 -6
  153. package/dist/services/memory-lint-config.js +111 -2
  154. package/dist/services/memory-native-review-copy.js +5 -67
  155. package/dist/services/memory-normal-flow-entry.js +12 -0
  156. package/dist/services/memory-ordinary-defaults.js +22 -5
  157. package/dist/services/memory-preflight-handler.js +15 -0
  158. package/dist/services/memory-repair-current-view.js +1 -1
  159. package/dist/services/memory-repair-document-delta.js +70 -33
  160. package/dist/services/memory-repair-history.js +12 -7
  161. package/dist/services/memory-repair-physical-binding.js +1 -119
  162. package/dist/services/memory-repair-publication-plan.js +5 -3
  163. package/dist/services/memory-repair-publication-source.js +1 -1
  164. package/dist/services/memory-repair-publication.js +3 -2
  165. package/dist/services/memory-repair-registration.js +2 -1
  166. package/dist/services/memory-repair-work-baseline.js +4 -2
  167. package/dist/services/memory-repair-work-completion.js +32 -12
  168. package/dist/services/memory-repair-work-results.js +21 -2
  169. package/dist/services/memory-repair-workspace.js +11 -2
  170. package/dist/services/memory-repair-writer-copy.js +2 -1
  171. package/dist/services/memory-result-binding.js +78 -12
  172. package/dist/services/memory-retained-review-authority.js +17 -8
  173. package/dist/services/memory-retained-second-baseline.js +1 -1
  174. package/dist/services/memory-retained-second-repairs.js +12 -6
  175. package/dist/services/memory-review-correction-plan.js +47 -12
  176. package/dist/services/memory-review-handler.js +17 -17
  177. package/dist/services/memory-review-inputs.js +55 -9
  178. package/dist/services/memory-review-limits.js +10 -0
  179. package/dist/services/memory-review-receipt.js +2 -2
  180. package/dist/services/memory-review-registration.js +13 -4
  181. package/dist/services/memory-review-work-custody.js +3 -1
  182. package/dist/services/memory-review-work-input.js +26 -7
  183. package/dist/services/memory-review-work-results.js +26 -4
  184. package/dist/services/memory-second-corrected-review-inputs.js +5 -3
  185. package/dist/services/memory-second-correction-plan.js +2 -1
  186. package/dist/services/memory-second-repair-launch.js +4 -4
  187. package/dist/services/memory-second-repair-source.js +1 -1
  188. package/dist/services/memory-stage-dispatch.js +38 -1
  189. package/dist/services/memory-stage-results.js +113 -22
  190. package/dist/services/memory-tool-probe.js +25 -0
  191. package/dist/services/memory-upgrade-analysis-handler.js +243 -0
  192. package/dist/services/memory-upgrade-bootstrap-readiness.js +251 -0
  193. package/dist/services/memory-upgrade-evidence.js +100 -0
  194. package/dist/services/memory-upgrade-inventory.js +442 -0
  195. package/dist/services/memory-upgrade-publication-custody.js +73 -0
  196. package/dist/services/memory-upgrade-publication.js +413 -0
  197. package/dist/services/memory-upgrade-selection.js +430 -0
  198. package/dist/services/memory-upgrade-start.js +60 -0
  199. package/dist/services/memory-upgrade-storage-readiness.js +340 -0
  200. package/dist/services/memory-verification-acceptance.js +38 -17
  201. package/dist/services/memory-verification-checks.js +92 -27
  202. package/dist/services/memory-verification-receipt.js +6 -5
  203. package/dist/services/memory-verify-handler.js +15 -13
  204. package/dist/services/memory-work-result-json.js +12 -0
  205. package/dist/services/memory-work-result-preparation.js +5 -4
  206. package/dist/services/merge-server.js +4 -2
  207. package/dist/services/migrations.js +127 -98
  208. package/dist/services/native-daemon-history.js +2 -1
  209. package/dist/services/physical-startup.js +45 -0
  210. package/dist/services/project-registry.js +97 -0
  211. package/dist/services/project-summary.js +1 -1
  212. package/dist/services/projects.js +3 -93
  213. package/dist/services/prompt-delivery.js +71 -0
  214. package/dist/services/provider-capacity.js +112 -46
  215. package/dist/services/recovery-snapshot-database.js +32 -4
  216. package/dist/services/release-cache.js +8 -0
  217. package/dist/services/repair-intents.js +1 -12
  218. package/dist/services/repair-receipt.js +13 -0
  219. package/dist/services/run-control-worker.js +35 -18
  220. package/dist/services/run-control.js +2 -1
  221. package/dist/services/run-controller-adapter.js +2 -1
  222. package/dist/services/run-controller-process.js +4 -7
  223. package/dist/services/run-controller-recovery.js +8 -2
  224. package/dist/services/run-controller.js +215 -94
  225. package/dist/services/run-fork.js +1 -1
  226. package/dist/services/run-recovery-guard.js +30 -0
  227. package/dist/services/run-recovery.js +11 -44
  228. package/dist/services/run-start-custody.js +17 -5
  229. package/dist/services/runs.js +6 -1
  230. package/dist/services/runtime-scope-resume.js +12 -6
  231. package/dist/services/runtime-scope-stop.js +3 -2
  232. package/dist/services/runtime-scope-worker.js +46 -19
  233. package/dist/services/runtime-service.js +2 -1
  234. package/dist/services/sessions.js +3 -24
  235. package/dist/services/setup.js +63 -13
  236. package/dist/services/stage-handlers.js +22 -7
  237. package/dist/services/stage-pause.js +111 -44
  238. package/dist/services/version-status.js +1 -1
  239. package/dist/services/vnext-code.js +2 -2
  240. package/dist/services/vnext-fanout.js +51 -9
  241. package/dist/services/vnext-plan-review.js +3 -5
  242. package/dist/services/work-registry.js +117 -39
  243. package/dist/services/workspace-bootstrap.js +2 -1
  244. package/dist/services/workspace-files.js +1 -1
  245. package/dist/shared/entity-references.js +1 -8
  246. package/dist/storage/data-preservation.js +65 -0
  247. package/dist/storage/database.js +53 -19
  248. package/dist/storage/import-guard.js +84 -27
  249. package/dist/storage/memory-upgrade-schema-bootstrap.js +38 -0
  250. package/dist/storage/paired-writer-migration.js +424 -0
  251. package/dist/storage/writer-contract.js +11 -4
  252. package/dist/storage/writer-migration.js +313 -52
  253. package/dist/storage/writer-restore.js +217 -0
  254. package/package.json +13 -6
@@ -23,7 +23,7 @@ function readReceipt(context, scope) {
23
23
  || receipt.run_id !== scope.run_id || hybridDigest(receipt.scope) !== requestSha256 || receipt.result_schema !== workerSchema
24
24
  || [receipt.batch_sha256, receipt.compose_result_sha256, receipt.input_sha256, receipt.execution_sha256].some(hash => typeof hash !== "string" || !/^[a-f0-9]{64}$/.test(hash))
25
25
  || typeof receipt.result_base64 !== "string" || !receipt.work_ids || Array.isArray(receipt.work_ids)
26
- || typeof receipt.work_ids !== "object" || !Object.keys(receipt.work_ids).length || Object.keys(receipt.work_ids).length > 4096
26
+ || typeof receipt.work_ids !== "object" || !Object.keys(receipt.work_ids).length && scope.stage !== "upgrade" || Object.keys(receipt.work_ids).length > 4096
27
27
  || Object.values(receipt.work_ids).some(id => typeof id !== "string")
28
28
  || new Set(Object.values(receipt.work_ids)).size !== Object.keys(receipt.work_ids).length)
29
29
  refuse("foreign_receipt_binding");
@@ -37,6 +37,15 @@ function readReceipt(context, scope) {
37
37
  || hybridDigest(custody.work_ids) !== hybridDigest(receipt.work_ids) || hybridDigest(custody.scope) !== requestSha256)
38
38
  refuse("changed_work_custody");
39
39
  }
40
+ if (!Object.keys(receipt.work_ids).length) {
41
+ // No invented worker for deterministic-only upgrade. Its empty batch
42
+ // still has the same independent allocator receipt and active scope.
43
+ const rows = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type='work.batch_registration' AND json_extract(payload_json,'$.request_sha256')=? LIMIT 2", [scope.project_id, requestSha256]);
44
+ if (rows.length !== 1 || Buffer.byteLength(rows[0].payload_json) > 32768 || hybridDigest(JSON.parse(rows[0].payload_json)) !== hybridDigest({
45
+ run_id: scope.run_id, scope, request_sha256: requestSha256, batch_sha256: hybridDigest([]), work_ids: {}, work_sha256: {}
46
+ }) || receipt.batch_sha256 !== hybridDigest([]))
47
+ refuse("changed_empty_batch_custody");
48
+ }
40
49
  return receipt;
41
50
  }
42
51
  catch (error) {
@@ -85,24 +94,28 @@ export function selectMemoryCompositionRegistration(context, scope) {
85
94
  export function registerMemoryComposition(context, input, assertHandlerCustody) {
86
95
  if (!(input.composition instanceof PreparedMemoryStageResult))
87
96
  refuse("foreign_prepared_result");
97
+ const stage = input.composition.authoringStage();
88
98
  const value = input.composition.compositionBatch({ ...input.binding, resultSchema: workerSchema });
89
99
  const bytes = input.composition.bytes();
90
- const batchInput = { parentWorkId: input.parentWorkId, requestKey: input.requestKey, stage: "compose", value };
91
- const prepared = prepareWorkBatch(context, batchInput);
100
+ const batchInput = { parentWorkId: input.parentWorkId, requestKey: input.requestKey, stage, value };
101
+ // Generic CLI batches remain non-empty. The admitted upgrade graph alone
102
+ // may register zero agent tasks; addWorkBatch still checks parent/run/stage
103
+ // and records exact batch custody in the existing writer transaction.
104
+ const prepared = stage === "upgrade" && !value.works.length ? [] : prepareWorkBatch(context, batchInput);
92
105
  return context.db.writeTransaction(() => {
93
106
  assertHandlerCustody?.(context);
94
107
  const parent = context.db.get("SELECT project_id, run_id FROM works WHERE work_id = ?", [input.parentWorkId]);
95
108
  if (!parent || parent.run_id !== input.binding.runId)
96
109
  refuse("foreign_coordinator");
97
- const scope = { ...stageGraphScope(context, parent.project_id, parent.run_id, "compose"),
98
- stage: "compose", parent_work_id: input.parentWorkId, request_key: input.requestKey };
110
+ const scope = { ...stageGraphScope(context, parent.project_id, parent.run_id, stage),
111
+ stage, parent_work_id: input.parentWorkId, request_key: input.requestKey };
99
112
  // One immutable blueprint per coordinator/attempt. A different request key
100
113
  // cannot create another productive graph or bypass a lost original receipt.
101
- const originals = context.db.all("SELECT json_extract(payload_json,'$.request_sha256') AS request_sha256 FROM audit_events WHERE project_id=? AND event_type='memory.composition_registration' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.scope.stage')='compose' AND json_extract(payload_json,'$.scope.attempt')=? AND json_extract(payload_json,'$.scope.cycle')=? AND json_extract(payload_json,'$.scope.parent_work_id')=? LIMIT 2", [scope.project_id, scope.run_id, scope.attempt, scope.cycle, scope.parent_work_id]);
114
+ const originals = context.db.all("SELECT json_extract(payload_json,'$.request_sha256') AS request_sha256 FROM audit_events WHERE project_id=? AND event_type='memory.composition_registration' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.scope.stage')=? AND json_extract(payload_json,'$.scope.attempt')=? AND json_extract(payload_json,'$.scope.cycle')=? AND json_extract(payload_json,'$.scope.parent_work_id')=? LIMIT 2", [scope.project_id, scope.run_id, stage, scope.attempt, scope.cycle, scope.parent_work_id]);
102
115
  if (originals.length > 1 || originals.length === 1 && originals[0].request_sha256 !== hybridDigest(scope))
103
116
  refuse("original_composition_already_registered");
104
117
  const prior = readReceipt(context, scope);
105
- if (!prior && 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 w.result_schema=? AND m.stage='compose' AND m.attempt=? AND m.cycle=? LIMIT 1", [scope.project_id, scope.run_id, scope.parent_work_id, workerSchema, scope.attempt, scope.cycle]))
118
+ if (!prior && 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 w.result_schema=? AND m.stage=? AND m.attempt=? AND m.cycle=? LIMIT 1", [scope.project_id, scope.run_id, scope.parent_work_id, workerSchema, stage, scope.attempt, scope.cycle]))
106
119
  refuse("composition_receipt_missing");
107
120
  if (prior && (prior.compose_result_sha256 !== input.composition.sha256 || prior.batch_sha256 !== hybridDigest(value.works)
108
121
  || prior.input_sha256 !== input.binding.inputSha256 || prior.execution_sha256 !== input.binding.executionSha256))
@@ -133,10 +146,12 @@ export function readRegisteredMemoryComposition(context, input) {
133
146
  const receipt = readReceipt(context, input.scope);
134
147
  if (!receipt)
135
148
  refuse("composition_receipt_missing");
136
- if (input.binding.runId !== input.scope.run_id || input.binding.stage !== "compose"
149
+ if (input.binding.runId !== input.scope.run_id || input.binding.stage !== input.scope.stage
137
150
  || input.binding.inputSha256 !== receipt.input_sha256 || input.binding.executionSha256 !== receipt.execution_sha256)
138
151
  refuse("foreign_restore_binding");
139
152
  const result = PreparedMemoryStageResult.prepare(Buffer.from(receipt.result_base64, "base64"), input.flow, input.binding);
153
+ if (result.authoringStage() !== input.scope.stage)
154
+ refuse("foreign_restore_binding");
140
155
  const batch = result.compositionBatch({ runId: input.binding.runId, inputSha256: receipt.input_sha256,
141
156
  executionSha256: receipt.execution_sha256, resultSchema: workerSchema });
142
157
  if (result.sha256 !== receipt.compose_result_sha256 || hybridDigest(batch.works) !== receipt.batch_sha256
@@ -46,10 +46,11 @@ export class PreparedRegisteredMemoryCompositionWorkResult {
46
46
  */
47
47
  static prepareFromStageCustody(context, input, engine, binding) {
48
48
  const custody = readWorkBatchCustody(context, binding.workId);
49
+ const stage = binding.flow.descriptor.id === "mb-init" ? "compose" : binding.flow.descriptor.id === "mb-upgrade" ? "upgrade" : refuse();
49
50
  const selected = binding.bundle ? PreparedMemoryResultBinding.prepareRegisteredComposition(context, { ...binding, bundle: binding.bundle,
50
- registrationScope: { project_id: binding.projectId, run_id: binding.runId, stage: "compose", attempt: custody.scope.attempt, cycle: custody.scope.cycle,
51
+ registrationScope: { project_id: binding.projectId, run_id: binding.runId, stage, attempt: custody.scope.attempt, cycle: custody.scope.cycle,
51
52
  parent_work_id: binding.coordinatorWorkId, request_key: binding.requestKey } })
52
- : binding.sources ? PreparedMemoryResultBinding.prepare(context, { ...binding, sources: binding.sources, stage: "compose" }) : refuse();
53
+ : binding.sources ? PreparedMemoryResultBinding.prepare(context, { ...binding, sources: binding.sources, stage }) : refuse();
53
54
  const prepared = this.prepareFromRegistration(context, input, engine, { ...binding, resultBinding: selected.binding() });
54
55
  prepared.#stageBinding = selected;
55
56
  prepared.assertCustody(context);
@@ -57,11 +58,12 @@ export class PreparedRegisteredMemoryCompositionWorkResult {
57
58
  }
58
59
  static prepareFromRegistration(context, input, engine, binding) {
59
60
  const custody = readWorkBatchCustody(context, binding.workId);
60
- if (custody.scope.stage !== "compose" || custody.scope.parent_work_id !== binding.coordinatorWorkId
61
+ const stage = binding.flow.descriptor.id === "mb-init" ? "compose" : binding.flow.descriptor.id === "mb-upgrade" ? "upgrade" : refuse();
62
+ if (custody.scope.stage !== stage || custody.scope.parent_work_id !== binding.coordinatorWorkId
61
63
  || custody.scope.request_key !== binding.requestKey || !binding.resultBinding.executionSha256)
62
64
  refuse();
63
65
  const scope = { project_id: custody.scope.project_id, run_id: custody.scope.run_id,
64
- stage: "compose", attempt: custody.scope.attempt, cycle: custody.scope.cycle,
66
+ stage, attempt: custody.scope.attempt, cycle: custody.scope.cycle,
65
67
  parent_work_id: binding.coordinatorWorkId, request_key: binding.requestKey };
66
68
  const registrationSha256 = memoryCompositionRegistrationDigest(context, scope);
67
69
  const composition = readRegisteredMemoryComposition(context, { scope, flow: binding.flow, binding: binding.resultBinding });
@@ -75,7 +77,7 @@ export class PreparedRegisteredMemoryCompositionWorkResult {
75
77
  static prepare(context, input, engine, binding) {
76
78
  const custody = readWorkBatchCustody(context, binding.workId);
77
79
  if (!(binding.composition instanceof PreparedMemoryStageResult) || binding.composition.sha256 !== binding.compositionSha256
78
- || custody.scope.run_id !== binding.runId || custody.scope.stage !== "compose"
80
+ || custody.scope.run_id !== binding.runId || custody.scope.stage !== binding.composition.authoringStage()
79
81
  || custody.scope.parent_work_id !== binding.coordinatorWorkId || custody.scope.request_key !== binding.requestKey)
80
82
  refuse();
81
83
  const batch = binding.composition.compositionBatch({ ...binding, resultSchema: "dd-flow/memory-composition-work-result@1" });
@@ -116,11 +118,14 @@ export class PreparedRegisteredMemoryCompositionWorkResult {
116
118
  const scope = this.#custody.scope;
117
119
  const run = context.db.get("SELECT index_json FROM runs WHERE project_id = ? AND id = ?", [scope.project_id, scope.run_id]);
118
120
  const index = run ? JSON.parse(run.index_json) : null;
121
+ const authoringStage = scope.stage;
122
+ if (!["compose", "upgrade"].includes(authoringStage))
123
+ refuse();
119
124
  assertRunBundleSqlAuthority(context, { projectId: scope.project_id, runId: scope.run_id }, index?.execution_bundle);
120
- const stage = index?.stage_runs?.findLast((item) => item.stage === "compose" && item.status === "running");
121
- if (!this.#registration || !this.#stageBinding || index?.current_stage !== "compose" || !stage || (stage.attempt ?? "try-001") !== scope.attempt
122
- || !stageWorkIds(context, scope.project_id, scope.run_id, "compose").has(this.workId)
123
- || !stageWorkIds(context, scope.project_id, scope.run_id, "compose").has(scope.parent_work_id)
125
+ const stage = index?.stage_runs?.findLast((item) => item.stage === authoringStage && item.status === "running");
126
+ if (!this.#registration || !this.#stageBinding || index?.current_stage !== authoringStage || !stage || (stage.attempt ?? "try-001") !== scope.attempt
127
+ || !stageWorkIds(context, scope.project_id, scope.run_id, authoringStage).has(this.workId)
128
+ || !stageWorkIds(context, scope.project_id, scope.run_id, authoringStage).has(scope.parent_work_id)
124
129
  || !context.db.get("SELECT 1 FROM work_sessions WHERE id = ? AND work_id = ? AND session_id = ? AND status = 'running'", [workSessionId, this.workId, sessionId])
125
130
  || !context.db.get("SELECT 1 FROM works WHERE work_id = ? AND project_id = ? AND run_id = ? AND status = 'running'", [scope.parent_work_id, scope.project_id, scope.run_id])
126
131
  || !context.db.get("SELECT 1 FROM sessions s JOIN runs r ON r.project_id = s.project_id AND r.id = s.run_id WHERE s.project_id = ? AND s.session_id = ? AND s.run_id = ? AND s.worker_id = ? AND s.harness = ? AND s.status = 'active' AND s.current_stage = 'work' AND s.project_root = r.project_root AND s.workspace_path = r.workspace_root", [scope.project_id, sessionId, scope.run_id, this.workId, this.#harness])) {
@@ -5,7 +5,7 @@ import { AdmittedFrozenEngine } from "./frozen-engine.js";
5
5
  import { hybridDigest } from "./hybrid-contracts.js";
6
6
  import { PreparedMemoryStageResult, memorySourceKey } from "./memory-stage-results.js";
7
7
  import { ObservedMemorySources } from "./memory-source-observations.js";
8
- import { memoryCompositionOutputAllowed } from "./memory-composition-graph.js";
8
+ import { parseMemoryWorkResultProposal } from "./memory-work-result-json.js";
9
9
  const schemaId = "dd-flow/memory-composition-work-result@1";
10
10
  function refuse(reason) {
11
11
  throw new AppError("memory_composition_work_result_invalid", "Composition Work result lacks assigned output proof", 2, { reason, phase: "prepare", effect: "no_effect" });
@@ -20,7 +20,7 @@ export function validateMemoryCompositionWorkResult(input, engine, binding) {
20
20
  || !(engine instanceof AdmittedFrozenEngine) || !(binding.composition instanceof PreparedMemoryStageResult)
21
21
  || binding.composition.sha256 !== binding.compositionSha256)
22
22
  refuse("foreign_preparation_binding");
23
- const value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(input));
23
+ const value = parseMemoryWorkResultProposal(input, "memory_composition_work_result_invalid");
24
24
  const schema = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(engine.schema("memory-composition-work-result")));
25
25
  if (schema.$id !== schemaId || !new Ajv2020({ strict: true, validateFormats: false }).compile(schema)(value))
26
26
  refuse("invalid_result_schema");
@@ -43,7 +43,7 @@ export function validateMemoryCompositionWorkResult(input, engine, binding) {
43
43
  refuse("unsafe_artifact_path");
44
44
  if (!packet.write_scopes.some(scope => scope.root === artifact.root && (logical === scope.path || logical.startsWith(`${scope.path}/`))))
45
45
  refuse("artifact_outside_write_scope");
46
- if (!memoryCompositionOutputAllowed(logical))
46
+ if (!binding.composition.allowsCompositionOutput(artifact.root, logical))
47
47
  refuse("private_composition_artifact");
48
48
  const alias = memorySourceKey(artifact.root, logical.normalize("NFC").toLowerCase());
49
49
  const identity = hybridDigest({ path: logical, sha256: artifact.sha256 });
@@ -80,6 +80,18 @@ export class PreparedMemoryCompositionWorkResult {
80
80
  for (const artifact of artifacts) {
81
81
  selected.set(memorySourceKey(artifact.root, artifact.path), { root: artifact.root, path: artifact.path });
82
82
  }
83
+ // Upgrade scopes are exact admitted file paths, not init-style output
84
+ // directories. Retain their output hashes while this Work is still
85
+ // correctable; publication cannot repair an already settled omission.
86
+ if (binding.composition.authoringStage() === "upgrade") {
87
+ const work = binding.composition.compositionBatch({ ...binding, resultSchema: schemaId }).works.find(item => item.key === binding.workKey);
88
+ const missing = work.payload.memory_composition.write_scopes
89
+ .filter(scope => scope.root === "workspace" && !selected.has(memorySourceKey(scope.root, scope.path)))
90
+ .map(scope => scope.path);
91
+ if (missing.length)
92
+ throw new AppError("memory_composition_work_result_invalid", "Include every assigned upgrade workspace file with its current hash in this Work result; correct only the result proposal, not settled peer Work or retained inputs", 2, { reason: "missing_workspace_outputs", phase: "prepare", effect: "no_effect", recoverable: true,
93
+ missing_count: missing.length, missing_paths: missing.slice(0, 32), result_sha256: createHash("sha256").update(bytes).digest("hex") });
94
+ }
83
95
  const observed = ObservedMemorySources.capture(binding.roots, [...selected.values()]), hashes = observed.hashes();
84
96
  if (artifacts.some(artifact => hashes.get(memorySourceKey(artifact.root, artifact.path)) !== artifact.sha256))
85
97
  refuse("changed_artifact_bytes");
@@ -1,6 +1,7 @@
1
1
  import { AppError } from "../shared/errors.js";
2
2
  import { hybridDigest } from "./hybrid-contracts.js";
3
3
  import { memoryReviewWorkerSchema } from "./memory-review-registration.js";
4
+ import { memoryReviewWaveByteLimit } from "./memory-review-limits.js";
4
5
  /** Payload projection only. Fresh check, registered input/copy/Session and
5
6
  * independent Work settlement are not supplied by a coordinator's JSON. */
6
7
  export function memoryCorrectedReviewBatch(view) {
@@ -10,20 +11,26 @@ export function memoryCorrectedReviewBatch(view) {
10
11
  throw new AppError("memory_corrected_review_batch_invalid", "Corrected reviewer batch lacks bounded current assignments", 2, { effect: "no_effect" });
11
12
  }
12
13
  let total = 0;
14
+ const inputSha256 = hybridDigest(view);
13
15
  return { works: view.assignments.map((assignment, index) => {
14
16
  const key = `review-${String(index + 1).padStart(3, "0")}`;
17
+ const inputPaths = view.upgrade_baseline ? new Set(assignment.inputs.map(ref => `${ref.root}\0${ref.path}`)) : null;
15
18
  const packet = { schema_id: "dd-flow/memory-review-work-packet@2", run_id: view.scope.run_id,
16
19
  stage: "review", wave: view.wave, key, unit: structuredClone(assignment.unit), unit_inputs: structuredClone(assignment.inputs),
17
20
  original_unit_sha256: assignment.original_unit_sha256, deleted_references: structuredClone(assignment.deleted_references),
18
21
  completed_correction_sha256: view.completed_correction_sha256, repairs: structuredClone(view.repairs),
19
- review_input_sha256: hybridDigest(view), result_schema: memoryReviewWorkerSchema,
20
- instruction: structuredClone(view.instruction) };
22
+ review_input_sha256: inputSha256, result_schema: memoryReviewWorkerSchema,
23
+ instruction: structuredClone(view.instruction),
24
+ ...(view.upgrade_baseline ? {
25
+ historical_inputs: view.upgrade_baseline.entries.filter(entry => inputPaths.has(`${entry.retained.root}\0${entry.retained.path}`)),
26
+ preservation_invariants: (view.preservation_invariants ?? []).filter(item => item.unit_id === assignment.unit.id)
27
+ } : {}) };
21
28
  const work = { key, task: `Independently review the assigned knowledge unit ${JSON.stringify(assignment.unit.id)}. Read the frozen review instruction in memory_review as review criteria, not permission to coordinate or repair. Check grounding, evidence, coverage and cross-file consistency using the registered wave inputs. Assigned unit.evidence may retain historical source versions; cite those exact versions only as historical context, never as bytes read from or current in this copy. Direct assessment and findings must still cite the assigned current unit.artifacts/unit_inputs. Do not edit project documents, allocate Work or repair findings. Report concrete material defects with unit/source evidence; do not block on taste. Return only the assigned memory-review-work-result JSON; do not claim other units or Stage completion.`,
22
29
  depends_on: [], launch_policy: "fresh_agent_required", result_schema: memoryReviewWorkerSchema,
23
30
  payload: { kind: "memory-review", read_only: true, memory_review: { ...packet, packet_sha256: hybridDigest(packet) } } };
24
31
  const size = Buffer.byteLength(JSON.stringify(work));
25
32
  total += size;
26
- if (size > 1024 * 1024 || total > 64 * 1024 * 1024)
33
+ if (size > 1024 * 1024 || total > memoryReviewWaveByteLimit)
27
34
  throw new AppError("memory_corrected_review_batch_invalid", "Corrected reviewer batch exceeds its bound", 2, { effect: "no_effect" });
28
35
  return work;
29
36
  }) };
@@ -1,6 +1,7 @@
1
1
  import { AppError } from "../shared/errors.js";
2
2
  import { hybridDigest } from "./hybrid-contracts.js";
3
3
  import { memoryReviewWaveProjection } from "./memory-review-acceptance.js";
4
+ import { isCompletedMemoryRepairClaim } from "./memory-repair-work-results.js";
4
5
  function refuse(reason) {
5
6
  throw new AppError("memory_corrected_review_findings_invalid", "Finding resolution lacks causal repair and independent re-review references", 2, { reason, effect: "no_effect" });
6
7
  }
@@ -71,12 +72,13 @@ export function correctedMemoryReviewFindings(input) {
71
72
  const previousIds = new Set(previous.findings.map(finding => finding.id)), claims = new Map();
72
73
  for (const repair of input.repairs) {
73
74
  const reference = ref(repair.reference);
74
- if (repair.result.outcome !== "accepted" || !repair.result.changes.length || !repair.unit_ids.length)
75
+ if (!isCompletedMemoryRepairClaim(repair.result) || !repair.unit_ids.length)
75
76
  refuse("unsettled_repair_claim");
76
77
  for (const resolution of repair.result.resolutions) {
77
78
  const finding = previous.findings.find(finding => finding.id === resolution.finding_id);
78
79
  if (!finding || finding.status !== "open" || !repair.unit_ids.includes(finding.unit_id) || claims.has(finding.id)
79
- || !resolution.delta_paths.length || resolution.delta_paths.some(path => !repair.result.changes.some(change => change.path === path && change.before_sha256 !== change.after_sha256)))
80
+ || repair.result.outcome !== "reconciled" && !resolution.delta_paths.length
81
+ || resolution.delta_paths.some(path => !repair.result.changes.some(change => change.path === path && change.before_sha256 !== change.after_sha256)))
80
82
  refuse("foreign_or_noncausal_resolution");
81
83
  claims.set(finding.id, { repair, reference });
82
84
  }
@@ -1,6 +1,7 @@
1
1
  import { AppError } from "../shared/errors.js";
2
2
  import { hybridDigest } from "./hybrid-contracts.js";
3
3
  import { PreparedCompletedMemoryCorrectionWave } from "./memory-completed-correction-wave.js";
4
+ import { isCompletedMemoryRepairClaim } from "./memory-repair-work-results.js";
4
5
  import { ObservedMemorySources } from "./memory-source-observations.js";
5
6
  import { memorySourceKey } from "./memory-stage-results.js";
6
7
  import { assertPreparedRunBundleSqlAuthority } from "./runs.js";
@@ -9,6 +10,7 @@ import { RetainedExecutionBundle } from "./execution-bundles.js";
9
10
  import { PreparedRetainedMemoryReviewAuthority } from "./memory-retained-review-authority.js";
10
11
  import { PreparedRetainedMemoryReviewResults } from "./memory-retained-review-results.js";
11
12
  import { PreparedRegisteredMemoryCorrections } from "./memory-repair-registration.js";
13
+ import { memoryReviewViewWithinLimit } from "./memory-review-limits.js";
12
14
  const token = Symbol("corrected-memory-review-inputs");
13
15
  function refuse(reason) {
14
16
  throw new AppError("memory_corrected_review_input_invalid", "Re-review inputs lack settled corrections and current source custody", 2, { reason, effect: "no_effect" });
@@ -46,9 +48,8 @@ export function correctedMemoryReviewAssignments(input) {
46
48
  const finalProof = new Map();
47
49
  for (const repair of input.repairs) {
48
50
  reference(repair.reference.path, repair.reference.sha256, "run");
49
- if (repair.result.outcome !== "accepted" || !Array.isArray(repair.unit_ids) || !repair.unit_ids.length
50
- || repair.unit_ids.some((id) => !input.units.some(unit => unit.id === id && unit.applicability === "applicable"))
51
- || !Array.isArray(repair.result.changes) || !repair.result.changes.length)
51
+ if (!isCompletedMemoryRepairClaim(repair.result) || !Array.isArray(repair.unit_ids) || !repair.unit_ids.length
52
+ || repair.unit_ids.some((id) => !input.units.some(unit => unit.id === id && unit.applicability === "applicable")))
52
53
  refuse("invalid_repair_projection");
53
54
  for (const change of repair.result.changes) {
54
55
  reference(change.path, change.after_sha256 ?? change.before_sha256);
@@ -97,6 +98,9 @@ export function correctedMemoryReviewAssignments(input) {
97
98
  unit.artifacts.push({ ...current });
98
99
  }
99
100
  const inputs = new Map();
101
+ for (const ref of input.initialAssignments?.find(item => item.unit.id === original.id)?.inputs ?? [])
102
+ if (ref.root === "run" && /^upgrade-baseline-[a-f0-9]{64}\.bin$/.test(ref.path))
103
+ inputs.set(memorySourceKey(ref.root, ref.path), { ...ref });
100
104
  for (const ref of [...unit.evidence, ...unit.artifacts, ...input.repairs.map(repair => reference(repair.reference.path, repair.reference.sha256, "run"))]) {
101
105
  const physical = reference(ref.root === "canon" ? `.memory-bank/${ref.path}` : ref.path, ref.sha256, ref.root === "run" ? "run" : "workspace");
102
106
  const key = memorySourceKey(physical.root, physical.path), prior = inputs.get(key);
@@ -151,7 +155,7 @@ export class PreparedCorrectedMemoryReviewInputs {
151
155
  return prepared;
152
156
  }
153
157
  static prepareFromBundle(context, input) {
154
- if (!(input.bundle instanceof RetainedExecutionBundle) || input.bundle.descriptor.descriptor.id !== "mb-init")
158
+ if (!(input.bundle instanceof RetainedExecutionBundle) || !["mb-init", "mb-upgrade"].includes(input.bundle.descriptor.descriptor.id))
155
159
  refuse("registered_memory_bundle_required");
156
160
  const original = PreparedRetainedMemoryReviewAuthority.prepare(context, input.scope, input.bundle);
157
161
  const review = PreparedRetainedMemoryReviewResults.prepare(context, original);
@@ -203,8 +207,9 @@ function prepareView(wave) {
203
207
  completed_correction_sha256: wave.sha256, original_review_sha256: review.sha256,
204
208
  source_policy: "review-inputs@1", source_sha256: source, base: original.base, run_inputs: runInputs,
205
209
  unit_inventory: structuredClone(original.unit_inventory), affected_unit_ids: wave.affectedUnitIds(), assignments,
210
+ ...(original.upgrade_baseline ? { upgrade_baseline: structuredClone(original.upgrade_baseline), preservation_invariants: structuredClone(original.preservation_invariants ?? []) } : {}),
206
211
  instruction: structuredClone(original.instruction), repairs: repairs.map(repair => ({ work_id: repair.work_id, authority_sha256: repair.authority_sha256, result: repair.reference })) };
207
- if (Buffer.byteLength(JSON.stringify(view)) > 1024 * 1024)
212
+ if (!memoryReviewViewWithinLimit(view))
208
213
  refuse("unbounded_corrected_view");
209
214
  return view;
210
215
  }
@@ -1,3 +1,4 @@
1
+ import { assertMemorySqlCustody } from "./memory-custody-snapshot.js";
1
2
  import { AppError } from "../shared/errors.js";
2
3
  import { appendAudit } from "./audit.js";
3
4
  import { hybridDigest } from "./hybrid-contracts.js";
@@ -81,7 +82,7 @@ export class PreparedMemoryCorrectedReviewReceipt {
81
82
  refuse("receipt_readback_failed");
82
83
  }
83
84
  }
84
- function acceptedReceipt(context, scope) {
85
+ function acceptedReceipt(context, scope, flowId) {
85
86
  const data = readAcceptedStageData(context, { ...scope, stage: "review" }), rows = records(context, scope);
86
87
  if (rows.length !== 1 || Buffer.byteLength(rows[0].payload_json) > 2 * 1024 * 1024)
87
88
  refuse("accepted_receipt_missing");
@@ -99,7 +100,7 @@ function acceptedReceipt(context, scope) {
99
100
  || !/^[a-f0-9]{64}$/.test(retained.first_review_proof_sha256)))
100
101
  || typeof retained.corrected_input_sha256 !== "string" || !/^[a-f0-9]{64}$/.test(retained.corrected_input_sha256)
101
102
  || typeof retained.check_receipt_sha256 !== "string" || !/^[a-f0-9]{64}$/.test(retained.check_receipt_sha256)
102
- || data.data_schema_id !== "dd-flow/mb-init-review-result@1" || retained.result_sha256 !== data.result.sha256
103
+ || data.data_schema_id !== (flowId === "mb-upgrade" ? "dd-flow/mb-upgrade-stage-result@1" : "dd-flow/mb-init-review-result@1") || retained.result_sha256 !== data.result.sha256
103
104
  || retained.input_sha256 !== data.input_sha256)
104
105
  refuse("accepted_receipt_changed");
105
106
  return { data, retained, authority: hybridDigest({ data: data.authority_sha256, receipt: retained }) };
@@ -109,24 +110,27 @@ function acceptedReceipt(context, scope) {
109
110
  * commit: generic settlement restores it; reviewers/checks/repairs may not be
110
111
  * substituted or re-admitted from today's project policy or documents. */
111
112
  export class PreparedAcceptedMemoryCorrectedReview {
113
+ #custodyKey = {};
112
114
  #scope;
113
115
  #authority;
114
116
  #history;
115
- constructor(key, scope, authority, history) {
117
+ #flowId;
118
+ constructor(key, scope, authority, history, flowId) {
116
119
  if (key !== token)
117
120
  refuse("receipt_token_required");
118
121
  this.#scope = { ...scope };
119
122
  this.#authority = authority;
120
123
  this.#history = history;
124
+ this.#flowId = flowId;
121
125
  Object.freeze(this);
122
126
  }
123
127
  static prepare(context, input) {
124
- if (!(input.bundle instanceof RetainedExecutionBundle) || input.bundle.descriptor.descriptor.id !== "mb-init"
128
+ if (!(input.bundle instanceof RetainedExecutionBundle) || !["mb-init", "mb-upgrade"].includes(input.bundle.descriptor.descriptor.id)
125
129
  || !(input.bytes instanceof Uint8Array) || !input.bytes.length || input.bytes.length > 1024 * 1024)
126
130
  refuse("registered_memory_result_required");
127
131
  if (context.db.isOwnedWriteTransaction?.())
128
132
  refuse("preparation_outside_writer_required");
129
- const accepted = context.db.readSnapshot(() => acceptedReceipt(context, input.scope));
133
+ const accepted = context.db.readSnapshot(() => acceptedReceipt(context, input.scope, input.bundle.descriptor.descriptor.id));
130
134
  if (!accepted.data.bytes.equals(Buffer.from(input.bytes)))
131
135
  refuse("accepted_result_bytes_changed");
132
136
  const hashes = { correctedInputSha256: accepted.retained.corrected_input_sha256, checkReceiptSha256: accepted.retained.check_receipt_sha256 };
@@ -151,20 +155,24 @@ export class PreparedAcceptedMemoryCorrectedReview {
151
155
  const schema = JSON.parse(input.bundle.descriptor.stageBlob("review", "result_schema").toString("utf8"));
152
156
  if (!new Ajv2020({ strict: true, strictRequired: false, validateFormats: false }).compile(schema)(value))
153
157
  refuse("accepted_result_schema_changed");
154
- const prepared = new PreparedAcceptedMemoryCorrectedReview(token, input.scope, accepted.authority, history);
158
+ const prepared = new PreparedAcceptedMemoryCorrectedReview(token, input.scope, accepted.authority, history, input.bundle.descriptor.descriptor.id);
155
159
  prepared.revalidate(context);
156
160
  return prepared;
157
161
  }
158
162
  assertCustody(context) {
159
- this.#history.assertCustody(context);
160
- if (acceptedReceipt(context, this.#scope).authority !== this.#authority)
161
- refuse("accepted_review_custody_changed");
163
+ const history = this.#history;
164
+ assertMemorySqlCustody(context, this.#custodyKey, () => {
165
+ history.assertCustody(context);
166
+ if (acceptedReceipt(context, this.#scope, this.#flowId).authority !== this.#authority)
167
+ refuse("accepted_review_custody_changed");
168
+ });
162
169
  }
163
170
  /** Final wave inputs selected by the independently accepted receipt. */
164
171
  reviewedSources(context) {
165
172
  this.assertCustody(context);
166
173
  return this.#history.retainedInputView().run_inputs;
167
174
  }
175
+ settledDocumentBaseline(context) { this.assertCustody(context); return this.#history.settledDocumentBaseline(); }
168
176
  finalChecks(context) { this.assertCustody(context); return this.#history.finalChecks(context); }
169
177
  revalidate(context) { this.#history.revalidate(context); this.assertCustody(context); }
170
178
  }
@@ -6,6 +6,7 @@ import { isMemoryCorrectionInputOwner } from "./memory-correction-input-owner.js
6
6
  import { PreparedMemoryCorrectionCheckReceipt } from "./memory-correction-check-receipt.js";
7
7
  import { memoryCorrectedReviewBatch } from "./memory-corrected-review-batch.js";
8
8
  import { addWorkBatch, prepareWorkBatch, readWorkBatchCustody } from "./work-registry.js";
9
+ import { memoryReviewRegistrationByteLimit } from "./memory-review-limits.js";
9
10
  const token = Symbol("registered-corrected-memory-review");
10
11
  const event = "memory.corrected_review_registration";
11
12
  function refuse(reason) {
@@ -40,7 +41,7 @@ function readRetainedRegistration(context, scope, inputSha256, checkReceiptSha25
40
41
  const rows = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type=? AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.scope.attempt')=? AND json_extract(payload_json,'$.scope.parent_work_id')=? AND json_extract(payload_json,'$.wave')=? LIMIT 2", [scope.project_id, event, scope.run_id, scope.attempt, scope.parent_work_id, wave]);
41
42
  if (!rows.length)
42
43
  return null;
43
- if (rows.length !== 1 || Buffer.byteLength(rows[0].payload_json) > 2 * 1024 * 1024)
44
+ if (rows.length !== 1 || Buffer.byteLength(rows[0].payload_json) > memoryReviewRegistrationByteLimit)
44
45
  refuse("ambiguous_or_unbounded_registration");
45
46
  let receipt;
46
47
  try {
@@ -171,7 +172,7 @@ export function registerCorrectedMemoryReview(context, inputs, checks) {
171
172
  refuse("review_registration_missing");
172
173
  const receipt = { schema_id: "dd-flow/memory-corrected-review-registration@1", run_id: scope.run_id, scope: view.scope, wave: view.wave,
173
174
  input_sha256: inputs.sha256, view, check_receipt_sha256: checks.sha256, batch_sha256: hybridDigest(batch.works), work_ids: ids };
174
- if (Buffer.byteLength(JSON.stringify(receipt)) > 2 * 1024 * 1024)
175
+ if (Buffer.byteLength(JSON.stringify(receipt)) > memoryReviewRegistrationByteLimit)
175
176
  refuse("unbounded_registration");
176
177
  appendAudit(context, { projectId: scope.project_id, eventType: event, payload: receipt });
177
178
  }
@@ -65,7 +65,7 @@ export class PreparedMemoryCorrectionCheckReceipt {
65
65
  evidence.assertOwner({ projectId: view.scope.project_id, runId: view.scope.run_id, scope: "aggregate" });
66
66
  const receipt = { schema_id: "dd-flow/memory-correction-checks@1", project_id: view.scope.project_id, run_id: view.scope.run_id,
67
67
  wave: view.wave, review_input_sha256: view.scope.input_sha256, corrected_input_sha256: corrected.sha256,
68
- completed_correction_sha256: view.completed_correction_sha256, profile_sha256: corrected.verificationProfileHash(context),
68
+ completed_correction_sha256: view.completed_correction_sha256, profile_sha256: policy.profileHash(),
69
69
  declarations_sha256: hybridDigest(policy.declarations()), checks: evidence.retainedReferences(context) };
70
70
  if (!receipt.checks.length || receipt.checks.length > 4096 || Buffer.byteLength(JSON.stringify(receipt)) > 2 * 1024 * 1024)
71
71
  refuse("unbounded_check_receipt");
@@ -81,7 +81,8 @@ export class PreparedMemoryCorrectionCheckReceipt {
81
81
  refuse("prepared_check_owners_required");
82
82
  if (context.db.isOwnedWriteTransaction?.())
83
83
  refuse("preparation_outside_writer_required");
84
- if (!context.db.readSnapshot(() => stored(context, corrected))) {
84
+ const existing = context.db.readSnapshot(() => stored(context, corrected));
85
+ if (!existing) {
85
86
  const view = corrected.value(), projectRoot = corrected.bundle().execution.snapshot().scope.projectRoot;
86
87
  const args = ["--run", view.scope.run_id, "--project-root", projectRoot, "--wave", String(view.wave), "--json"];
87
88
  throw new AppError("memory_correction_checks_invalid", "Correction checks have not been registered for the settled corrected view", 2, { reason: "registered_check_receipt_required", phase: "prepare", effect: "no_effect", recoverable: true,
@@ -90,7 +91,22 @@ export class PreparedMemoryCorrectionCheckReceipt {
90
91
  const bundle = corrected.bundle(), scope = corrected.value().scope;
91
92
  const policy = PreparedMemoryVerificationChecks.prepare(context, { projectId: scope.project_id, runId: scope.run_id,
92
93
  execution: bundle.execution, flow: bundle.descriptor, corrected });
93
- return PreparedMemoryCorrectionCheckReceipt.prepare(context, corrected, policy);
94
+ // Registered execution belongs to its original environment, not the
95
+ // controller/reviewer's current shell. Keep exact current document and
96
+ // policy custody, then restore the sealed execution instead of selecting
97
+ // fresh evidence using the observer's environment.
98
+ const view = corrected.value(), receipt = historicalReceipt(context, { projectId: scope.project_id, runId: scope.run_id,
99
+ wave: view.wave, receiptSha256: hybridDigest(existing), correctedInputSha256: corrected.sha256 });
100
+ const expected = { schema_id: "dd-flow/memory-correction-checks@1", project_id: scope.project_id, run_id: scope.run_id,
101
+ wave: view.wave, review_input_sha256: scope.input_sha256, corrected_input_sha256: corrected.sha256,
102
+ completed_correction_sha256: view.completed_correction_sha256, profile_sha256: policy.profileHash(),
103
+ declarations_sha256: hybridDigest(policy.declarations()), checks: receipt.checks };
104
+ if (hybridDigest(receipt) !== hybridDigest(expected))
105
+ refuse("registered_check_receipt_changed");
106
+ const evidence = PreparedRetainedCheckEvidence.prepare(context, { projectId: scope.project_id, runId: scope.run_id, references: receipt.checks });
107
+ const prepared = new PreparedMemoryCorrectionCheckReceipt(token, corrected, policy, evidence, receipt, true);
108
+ prepared.revalidate(context);
109
+ return prepared;
94
110
  }
95
111
  value() {
96
112
  return structuredClone(this.#receipt);
@@ -111,7 +127,7 @@ export class PreparedMemoryCorrectionCheckReceipt {
111
127
  const existing = stored(context, this.#corrected);
112
128
  if ((this.#registered && !existing) || (existing && hybridDigest(existing) !== this.sha256))
113
129
  refuse("registered_check_receipt_changed");
114
- if (hybridDigest(this.#evidence.retainedReferences(context)) !== hybridDigest(this.#receipt.checks))
130
+ if ("retainedReferences" in this.#evidence && hybridDigest(this.#evidence.retainedReferences(context)) !== hybridDigest(this.#receipt.checks))
115
131
  refuse("selected_check_evidence_changed");
116
132
  }
117
133
  revalidate(context) {
@@ -0,0 +1,36 @@
1
+ import { AppError } from "../shared/errors.js";
2
+ const snapshots = new WeakMap();
3
+ function synchronous(read) {
4
+ const value = read();
5
+ if (value && typeof value.then === "function") {
6
+ void Promise.resolve(value).catch(() => { });
7
+ throw new AppError("read_snapshot_async", "Memory custody traversal must be synchronous", 1);
8
+ }
9
+ return value;
10
+ }
11
+ export function withMemoryCustodySnapshot(context, read) {
12
+ if (snapshots.has(context))
13
+ return synchronous(read);
14
+ return context.db.readSnapshot(() => {
15
+ snapshots.set(context, new Set());
16
+ try {
17
+ return synchronous(read);
18
+ }
19
+ finally {
20
+ snapshots.delete(context);
21
+ }
22
+ });
23
+ }
24
+ /** Only SQL custody may share a completed proof within this synchronous
25
+ * snapshot. Physical revalidation stays outside this memo, and the next
26
+ * traversal always rereads SQL. Failed checks never become reusable proof.
27
+ * Prepared owners supply private keys, never their externally visible object. */
28
+ export function assertMemorySqlCustody(context, owner, validate) {
29
+ withMemoryCustodySnapshot(context, () => {
30
+ const checked = snapshots.get(context);
31
+ if (checked.has(owner))
32
+ return;
33
+ synchronous(validate);
34
+ checked.add(owner);
35
+ });
36
+ }