@deksden-com/dd-flow-cli 0.9.0-beta.152 → 0.9.0-beta.156

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 (227) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/build-info.json +8 -8
  3. package/dist/cli/command-inputs.js +33 -3
  4. package/dist/cli/command-metadata.js +73 -2
  5. package/dist/cli/delivery.js +108 -0
  6. package/dist/cli/diagnostic-invocation.js +23 -7
  7. package/dist/cli/diagnostic.js +14 -3
  8. package/dist/cli/help.js +117 -1
  9. package/dist/cli/hook-ingress.js +36 -49
  10. package/dist/cli/interactive-invocation.js +33 -0
  11. package/dist/cli/interactive.js +114 -0
  12. package/dist/cli/memory-fix-invocation.js +52 -0
  13. package/dist/cli/memory-fix.js +100 -0
  14. package/dist/cli/memory-flow-admission.js +7 -3
  15. package/dist/cli/memory-init.js +5 -4
  16. package/dist/cli/native-hook-ingress.js +5 -1
  17. package/dist/cli/native-hook-preparation.js +31 -4
  18. package/dist/cli/run-cli.js +274 -19
  19. package/dist/cli.js +17 -3
  20. package/dist/domain/delivery-flows.js +8 -0
  21. package/dist/domain/distill-flow.js +20 -0
  22. package/dist/domain/interactive-flow.js +1 -0
  23. package/dist/domain/memory-fix-flow.js +2 -0
  24. package/dist/domain/review-fix-flow.js +2 -0
  25. package/dist/domain/source-snapshot-flows.js +4 -0
  26. package/dist/harness-runtime/bin/dd-agy.mjs +18 -3
  27. package/dist/harness-runtime/bin/dd-grok.mjs +15 -5
  28. package/dist/harness-runtime/bin/dd-zcode.mjs +15 -7
  29. package/dist/harness-runtime/lib/agy-error-boundary.mjs +18 -10
  30. package/dist/harness-runtime/lib/agy-native-inspection.mjs +12 -3
  31. package/dist/harness-runtime/lib/agy-native-inventory.mjs +19 -0
  32. package/dist/harness-runtime/lib/agy-terminal-envelope.mjs +13 -6
  33. package/dist/harness-runtime/lib/agy-transcript-stream.mjs +54 -0
  34. package/dist/harness-runtime/lib/daemon-observation.d.mts +1 -0
  35. package/dist/harness-runtime/lib/daemon-observation.mjs +12 -4
  36. package/dist/harness-runtime/lib/daemon-operations.mjs +20 -5
  37. package/dist/harness-runtime/lib/dd-agy-daemon.d.mts +1 -0
  38. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +371 -114
  39. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +43 -3
  40. package/dist/harness-runtime/lib/dd-codex.mjs +36 -4
  41. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +25 -11
  42. package/dist/harness-runtime/lib/dd-droid.mjs +124 -11
  43. package/dist/harness-runtime/lib/dd-grok-daemon.d.mts +1 -0
  44. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +137 -25
  45. package/dist/harness-runtime/lib/dd-grok.mjs +4 -2
  46. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +115 -17
  47. package/dist/harness-runtime/lib/dd-opencode.mjs +10 -7
  48. package/dist/harness-runtime/lib/dd-zcode-daemon.d.mts +1 -0
  49. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +115 -22
  50. package/dist/harness-runtime/lib/dd-zcode.mjs +75 -36
  51. package/dist/harness-runtime/lib/delegation-instructions.d.mts +2 -1
  52. package/dist/harness-runtime/lib/delegation-instructions.mjs +15 -5
  53. package/dist/harness-runtime/lib/delivery-command.mjs +84 -0
  54. package/dist/harness-runtime/lib/driver-recovery.mjs +0 -19
  55. package/dist/harness-runtime/lib/droid-observation.mjs +9 -5
  56. package/dist/harness-runtime/lib/lifecycle-contract.d.mts +1 -0
  57. package/dist/harness-runtime/lib/lifecycle-contract.mjs +2 -1
  58. package/dist/harness-runtime/lib/native-hook-command.d.mts +3 -2
  59. package/dist/harness-runtime/lib/native-hook-command.mjs +63 -8
  60. package/dist/harness-runtime/lib/native-streams.mjs +3 -10
  61. package/dist/harness-runtime/lib/opencode-plugin.mjs +36 -9
  62. package/dist/harness-runtime/lib/operation-errors.mjs +13 -0
  63. package/dist/harness-runtime/lib/operation-progress.mjs +6 -2
  64. package/dist/harness-runtime/lib/process-json.mjs +4 -5
  65. package/dist/harness-runtime/lib/tool-observations.mjs +20 -10
  66. package/dist/harness-runtime/lib/zcode-native-hook-ownership.mjs +109 -0
  67. package/dist/schemas/code-work-result.schema.json +2 -1
  68. package/dist/schemas/delivery-stage-result.schema.json +286 -0
  69. package/dist/schemas/distill-stage-result.schema.json +667 -0
  70. package/dist/schemas/distill-work-result.schema.json +428 -0
  71. package/dist/schemas/execution-bundle.schema.json +6 -2
  72. package/dist/schemas/interactive-stage-result.schema.json +47 -0
  73. package/dist/schemas/interactive-work-result.schema.json +63 -0
  74. package/dist/schemas/mb-fix-stage-result.schema.json +93 -0
  75. package/dist/schemas/project-flow-pack-manifest.schema.json +2 -2
  76. package/dist/schemas/review-fix-stage-result.schema.json +210 -0
  77. package/dist/schemas/run-control-receipt.schema.json +1 -0
  78. package/dist/services/assigned-lifecycle.js +382 -0
  79. package/dist/services/check-runner-evidence.js +45 -8
  80. package/dist/services/cli-operation-classifier.js +6 -1
  81. package/dist/services/code-checks.js +46 -29
  82. package/dist/services/codex-hook-delivery.js +11 -2
  83. package/dist/services/controller-child-repair.js +95 -0
  84. package/dist/services/controller-execution.js +4 -1
  85. package/dist/services/controller-fanout.js +19 -9
  86. package/dist/services/controller-memory-worker.js +6 -4
  87. package/dist/services/controller-result-authority.js +122 -0
  88. package/dist/services/dashboard.js +4 -2
  89. package/dist/services/delivery-access.js +123 -0
  90. package/dist/services/delivery-approval.js +68 -0
  91. package/dist/services/delivery-artifact.js +86 -0
  92. package/dist/services/delivery-checks.js +178 -0
  93. package/dist/services/delivery-execution.js +181 -0
  94. package/dist/services/delivery-intents.js +212 -0
  95. package/dist/services/delivery-operation.js +81 -0
  96. package/dist/services/delivery-policy.js +91 -0
  97. package/dist/services/delivery-procedures.js +43 -0
  98. package/dist/services/delivery-request.js +99 -0
  99. package/dist/services/delivery-result.js +144 -0
  100. package/dist/services/delivery-stage-commands.js +26 -0
  101. package/dist/services/delivery-stage-entry.js +56 -0
  102. package/dist/services/delivery-stage-handlers.js +135 -0
  103. package/dist/services/delivery-stage-results.js +94 -0
  104. package/dist/services/descriptor-coordinator-session.js +3 -1
  105. package/dist/services/descriptor-run-start.js +24 -4
  106. package/dist/services/descriptor-stage-coordinator.js +78 -10
  107. package/dist/services/descriptor-stage-settlement.js +59 -14
  108. package/dist/services/diagnostic-repair-intake.js +123 -0
  109. package/dist/services/diagnostic-repair-source-intake.js +102 -0
  110. package/dist/services/diagnostic-report.js +30 -0
  111. package/dist/services/diagnostic-run-bundle.js +48 -9
  112. package/dist/services/diagnostic-snapshot.js +2 -2
  113. package/dist/services/diagnostic-stage-handlers.js +3 -2
  114. package/dist/services/diagnostic-stage-results.js +26 -0
  115. package/dist/services/diagnostic-start.js +56 -6
  116. package/dist/services/diagnostic-work-collection.js +3 -3
  117. package/dist/services/distill-document-publication.js +280 -0
  118. package/dist/services/distill-promotion.js +197 -0
  119. package/dist/services/distill-publication-custody.js +162 -0
  120. package/dist/services/distill-stage-handlers.js +249 -0
  121. package/dist/services/distill-stage-results.js +105 -0
  122. package/dist/services/distill-user-inputs.js +4 -0
  123. package/dist/services/distill-work-batch.js +42 -0
  124. package/dist/services/distill-work-collection.js +74 -0
  125. package/dist/services/distill-work-registration.js +93 -0
  126. package/dist/services/distill-work-results.js +98 -0
  127. package/dist/services/eval-snapshots.js +17 -7
  128. package/dist/services/external-work-launch.js +8 -2
  129. package/dist/services/flow-catalog.js +24 -0
  130. package/dist/services/frozen-engine.js +19 -2
  131. package/dist/services/generic-execution.js +4 -3
  132. package/dist/services/harness-adapter.js +2 -14
  133. package/dist/services/hook-memory-routing.js +1 -1
  134. package/dist/services/hooks.js +31 -6
  135. package/dist/services/hybrid-contracts.js +6 -1
  136. package/dist/services/installed-flow-source.js +37 -2
  137. package/dist/services/interactive-attached-session.js +215 -0
  138. package/dist/services/interactive-baseline-custody.js +54 -0
  139. package/dist/services/interactive-before-versions.js +148 -0
  140. package/dist/services/interactive-bundle-data.js +85 -0
  141. package/dist/services/interactive-checks.js +179 -0
  142. package/dist/services/interactive-execution.js +33 -0
  143. package/dist/services/interactive-finish-integration.js +236 -0
  144. package/dist/services/interactive-git-baseline.js +230 -0
  145. package/dist/services/interactive-git-dispatch.js +278 -0
  146. package/dist/services/interactive-input-delivery.js +82 -0
  147. package/dist/services/interactive-mutation-scope.js +231 -0
  148. package/dist/services/interactive-request.js +287 -0
  149. package/dist/services/interactive-run-bundle.js +109 -0
  150. package/dist/services/interactive-stage-entry.js +41 -0
  151. package/dist/services/interactive-stage-handlers.js +289 -0
  152. package/dist/services/interactive-stage-observation.js +136 -0
  153. package/dist/services/interactive-stage-results.js +111 -0
  154. package/dist/services/interactive-user-inputs.js +142 -0
  155. package/dist/services/interactive-work-batch.js +43 -0
  156. package/dist/services/interactive-work-collection.js +119 -0
  157. package/dist/services/interactive-work-completion.js +180 -0
  158. package/dist/services/interactive-work-context.js +249 -0
  159. package/dist/services/interactive-work-history.js +112 -0
  160. package/dist/services/interactive-work-registration.js +124 -0
  161. package/dist/services/interactive-work-results.js +115 -0
  162. package/dist/services/interactive-work-start.js +155 -0
  163. package/dist/services/lifecycle-invocations.js +250 -33
  164. package/dist/services/managed-daemon-binding.js +52 -1
  165. package/dist/services/memory-compose-handler.js +2 -22
  166. package/dist/services/memory-finish-integration.js +58 -17
  167. package/dist/services/memory-fix-finish-integration.js +129 -0
  168. package/dist/services/memory-fix-git-dispatch.js +139 -0
  169. package/dist/services/memory-fix-intake-custody.js +11 -0
  170. package/dist/services/memory-fix-intake-settlement.js +127 -0
  171. package/dist/services/memory-fix-publication.js +196 -0
  172. package/dist/services/memory-fix-repair-admission.js +116 -0
  173. package/dist/services/memory-fix-request.js +141 -0
  174. package/dist/services/memory-fix-run-bundle.js +83 -0
  175. package/dist/services/memory-fix-scope.js +78 -0
  176. package/dist/services/memory-fix-sources.js +85 -0
  177. package/dist/services/memory-fix-stage-entry.js +24 -0
  178. package/dist/services/memory-fix-stage-handlers.js +282 -0
  179. package/dist/services/memory-fix-stage-results.js +106 -0
  180. package/dist/services/memory-fix-workspace.js +127 -0
  181. package/dist/services/memory-flow-user-inputs.js +88 -0
  182. package/dist/services/memory-init-contexts.js +55 -5
  183. package/dist/services/memory-init-controller-start.js +20 -4
  184. package/dist/services/memory-layer.js +7 -3
  185. package/dist/services/memory-native-review-copy.js +27 -3
  186. package/dist/services/memory-repair-document-delta.js +1 -0
  187. package/dist/services/project-flow-source.js +195 -0
  188. package/dist/services/project-git-integration.js +92 -0
  189. package/dist/services/repair-intents.js +2 -7
  190. package/dist/services/retained-native-hook.js +274 -0
  191. package/dist/services/review-fix-handoff.js +82 -0
  192. package/dist/services/review-fix-intake-custody.js +17 -0
  193. package/dist/services/review-fix-stage-entry.js +43 -0
  194. package/dist/services/review-fix-stage-handlers.js +176 -0
  195. package/dist/services/review-fix-stage-results.js +86 -0
  196. package/dist/services/run-control-worker.js +42 -1
  197. package/dist/services/run-control.js +40 -5
  198. package/dist/services/run-controller-adapter.js +37 -3
  199. package/dist/services/run-controller-process.js +25 -4
  200. package/dist/services/run-controller-recovery.js +46 -6
  201. package/dist/services/run-controller-state.js +3 -2
  202. package/dist/services/run-controller.js +257 -44
  203. package/dist/services/run-recovery.js +31 -2
  204. package/dist/services/run-timeline.js +59 -0
  205. package/dist/services/runs.js +18 -31
  206. package/dist/services/sessions.js +2 -1
  207. package/dist/services/stage-handlers.js +55 -3
  208. package/dist/services/stage-lifecycle.js +4 -3
  209. package/dist/services/stage-pause.js +26 -9
  210. package/dist/services/stage-report-renderer.js +2 -6
  211. package/dist/services/technical-notes.js +31 -0
  212. package/dist/services/verification-matrix.js +1 -1
  213. package/dist/services/version-status.js +1 -1
  214. package/dist/services/vnext-code-review.js +74 -5
  215. package/dist/services/vnext-code.js +8 -32
  216. package/dist/services/vnext-fanout.js +53 -8
  217. package/dist/services/vnext-merge.js +10 -5
  218. package/dist/services/vnext-plan-review.js +1 -1
  219. package/dist/services/vnext-plan.js +1 -1
  220. package/dist/services/vnext-protocolize.js +1 -1
  221. package/dist/services/vnext-specify.js +10 -5
  222. package/dist/services/work-registry.js +255 -69
  223. package/dist/services/worktrees.js +7 -4
  224. package/dist/storage/database.js +2 -1
  225. package/dist/verification-components.json +82 -0
  226. package/package.json +3 -2
  227. package/tools/verification-jobs.mjs +35 -10
@@ -0,0 +1,127 @@
1
+ import path from "node:path";
2
+ import { createHash } from "node:crypto";
3
+ import { AppError } from "../shared/errors.js";
4
+ import { resolveProjectRoot } from "../storage/paths.js";
5
+ import { requireProjectByRoot } from "./projects.js";
6
+ import { readFlowRunExecutionBundle } from "./runs.js";
7
+ import { readDescriptorStageInput } from "./descriptor-stage-coordinator.js";
8
+ import { PreparedDescriptorStageSettlement } from "./descriptor-stage-settlement.js";
9
+ import { readAcceptedStageData, prepareStagePacket, acceptStagePacket, resolveStagePublicationPath } from "./stage-report-renderer.js";
10
+ import { readMemoryFixSourceIntake, assertMemoryFixSourceIntakeCustody } from "./memory-fix-intake-custody.js";
11
+ import { prepareMemoryFixScopeEvidence } from "./memory-fix-scope.js";
12
+ import { validateMemoryFixStageResult } from "./memory-fix-stage-results.js";
13
+ import { hybridDigest } from "./hybrid-contracts.js";
14
+ const hash = (bytes) => createHash("sha256").update(bytes).digest("hex");
15
+ function refuse(reason) {
16
+ throw new AppError("memory_fix_intake_settlement_invalid", "Repair intake requires accepted scope and original source observations", 2, { reason, effect: "no_effect" });
17
+ }
18
+ /** Historical SQL read only: repaired files and later user turns must not
19
+ * replace an already accepted intake baseline. This grants no new write scope.
20
+ * Reads accepted Stage bytes itself, never caller-provided result JSON. The
21
+ * caller still needs ordinary coordinator settlement for Stage replay. Retained
22
+ * workspace_root is provenance only after restore, never a fresh write target.
23
+ */
24
+ export function readAcceptedMemoryFixIntakeEvidence(context, scope, file) {
25
+ const accepted = readAcceptedStageData(context, { ...scope, stage: "intake" });
26
+ const rows = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type='flow_run.stage_packet' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.stage')='intake' LIMIT 33", [scope.projectId, scope.runId]);
27
+ if (rows.length > 32)
28
+ refuse("ambiguous_intake_evidence");
29
+ const packets = rows.map(row => {
30
+ if (Buffer.byteLength(row.payload_json) > 8 * 1024 * 1024)
31
+ refuse("intake_evidence_exceeds_bound");
32
+ return JSON.parse(row.payload_json);
33
+ }).filter(packet => packet && typeof packet.file === "string"
34
+ && resolveStagePublicationPath(context, scope.projectId, scope.runId, packet.file) === file);
35
+ if (packets.length !== 1)
36
+ refuse("intake_evidence_missing");
37
+ const packet = packets[0];
38
+ if (typeof packet.base64 !== "string")
39
+ refuse("intake_evidence_changed");
40
+ const bytes = Buffer.from(packet.base64, "base64");
41
+ if (!bytes.length || bytes.length > 4 * 1024 * 1024 || bytes.toString("base64") !== packet.base64
42
+ || hash(bytes) !== packet.sha256)
43
+ refuse("intake_evidence_changed");
44
+ let evidence, result;
45
+ try {
46
+ evidence = JSON.parse(bytes.toString("utf8"));
47
+ result = JSON.parse(accepted.bytes.toString("utf8"));
48
+ }
49
+ catch {
50
+ return refuse("intake_evidence_changed");
51
+ }
52
+ if (!evidence || !result?.details || accepted.data_schema_id !== "dd-flow/mb-fix-stage-result@1"
53
+ || result.schema_id !== "dd-flow/mb-fix-stage-result@1" || result.run_id !== scope.runId || result.stage !== "intake"
54
+ || result.input_sha256 !== accepted.input_sha256 || evidence.intake_sha256 !== result.intake_sha256
55
+ || evidence.source_observation_sha256 !== result.source_observation_sha256
56
+ || hybridDigest(evidence.source_observation) !== result.source_observation_sha256
57
+ || hybridDigest(evidence.routing) !== hybridDigest(result.details.routing)
58
+ || hybridDigest(evidence.write_scope) !== hybridDigest(result.details.write_scope)
59
+ || evidence.write_scope && evidence.user_inputs_sha256 !== evidence.write_scope.user_inputs_sha256)
60
+ refuse("accepted_intake_changed");
61
+ return structuredClone(evidence);
62
+ }
63
+ /** Internal family handler. Generic runtime owns hook/Session identity and
64
+ * atomic completion; the scope packet is retained in that same SQL writer.
65
+ * Public registration waits for the complete repair/verification family.
66
+ */
67
+ export function finishMemoryFixIntake(context, input) {
68
+ if (input.stage !== "intake")
69
+ refuse("intake_stage_required");
70
+ const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
71
+ const bundle = readFlowRunExecutionBundle(context, input);
72
+ if (!bundle || bundle.manifest.flow_id !== "mb-fix")
73
+ refuse("memory_fix_bundle_required");
74
+ const scope = { projectId: project.id, runId: input.runId, stage: "intake" };
75
+ const source = readMemoryFixSourceIntake(context, input, bundle);
76
+ const index = context.db.get("SELECT index_json FROM runs WHERE project_id=? AND id=?", [project.id, input.runId]);
77
+ const replay = JSON.parse(index.index_json).stage_runs.some((stage) => stage.stage === "intake" && stage.status === "done");
78
+ const packet = readDescriptorStageInput(context, scope, bundle, true, replay);
79
+ const file = path.join(packet.runRoot, bundle.descriptor.stage("intake").directory, "intake-evidence.json");
80
+ if (replay) {
81
+ const accepted = readAcceptedStageData(context, scope);
82
+ if (!accepted.bytes.equals(Buffer.from(input.bytes)))
83
+ refuse("accepted_result_changed");
84
+ const evidence = readAcceptedMemoryFixIntakeEvidence(context, scope, file);
85
+ if (evidence.intake_sha256 !== source.sha256)
86
+ refuse("source_intake_changed");
87
+ const settled = PreparedDescriptorStageSettlement.prepare(context, input);
88
+ return settled.apply(context, writer => {
89
+ assertMemoryFixSourceIntakeCustody(writer, scope, bundle, source.sha256);
90
+ const current = readAcceptedStageData(writer, scope);
91
+ if (!current.bytes.equals(accepted.bytes)
92
+ || hybridDigest(readAcceptedMemoryFixIntakeEvidence(writer, scope, file)) !== hybridDigest(evidence))
93
+ refuse("accepted_intake_changed");
94
+ });
95
+ }
96
+ if (!input.bytes.length || input.bytes.length > 1024 * 1024)
97
+ refuse("result_exceeds_bound");
98
+ let proposed;
99
+ try {
100
+ proposed = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(input.bytes));
101
+ }
102
+ catch {
103
+ return refuse("invalid_result_json");
104
+ }
105
+ if (!proposed?.details || typeof proposed.details !== "object")
106
+ refuse("invalid_result_details");
107
+ const prepared = prepareMemoryFixScopeEvidence(context, { ...input, routing: proposed.details.routing, writeScope: proposed.details.write_scope });
108
+ const evidence = prepared.snapshot();
109
+ validateMemoryFixStageResult(input.bytes, bundle.descriptor.stageBlob("intake", "result_schema"), {
110
+ runId: input.runId, stage: "intake", inputSha256: packet.inputSha256, intakeSha256: source.sha256,
111
+ sourceObservationSha256: evidence.source_observation_sha256, findingIds: source.intake.selected_findings.map(finding => finding.id),
112
+ projectRoot: evidence.workspace_root, runRoot: packet.runRoot, predecessors: [], writeScope: evidence.write_scope, repairReceipt: null
113
+ });
114
+ const evidenceBytes = Buffer.from(JSON.stringify(evidence));
115
+ if (evidenceBytes.length > 4 * 1024 * 1024)
116
+ refuse("intake_evidence_exceeds_bound");
117
+ const retained = prepareStagePacket(file, evidenceBytes);
118
+ const settled = PreparedDescriptorStageSettlement.prepare(context, input);
119
+ prepared.assertSourcesCurrent();
120
+ return settled.apply(context, writer => {
121
+ prepared.assertCustody(writer);
122
+ const current = writer.db.get("SELECT index_json FROM runs WHERE project_id=? AND id=?", [project.id, input.runId]);
123
+ if (JSON.parse(current.index_json).stage_runs.some((stage) => stage.stage === "intake" && stage.status === "done"))
124
+ refuse("intake_already_accepted");
125
+ acceptStagePacket(writer, { ...scope, prepared: retained });
126
+ });
127
+ }
@@ -0,0 +1,196 @@
1
+ import path from "node:path";
2
+ import fs from "node:fs";
3
+ import { createHash } from "node:crypto";
4
+ import { AppError } from "../shared/errors.js";
5
+ import { hybridDigest } from "./hybrid-contracts.js";
6
+ import { appendAudit } from "./audit.js";
7
+ import { projectCheckoutRoot } from "../storage/paths.js";
8
+ import { prepareMemoryFixRepairAdmission } from "./memory-fix-repair-admission.js";
9
+ import { RetainedMemoryRepairPublicationPlan } from "./memory-repair-publication-plan.js";
10
+ import { prepareMemoryFixDocumentPlan, applyMemoryFixDocumentStep, captureDistillBeforeDocuments, validateDistillBeforeDocuments, publishMemoryFixBeforeDocuments, memoryFixBeforeDocumentReferences } from "./distill-document-publication.js";
11
+ import { readFlowRunExecutionBundle } from "./runs.js";
12
+ import { prepareStagePacket, acceptStagePacket, resolveStagePublicationPath } from "./stage-report-renderer.js";
13
+ const intent = "memory_fix.publication_intent", complete = "memory_fix.publication_complete";
14
+ const key = (scope) => `memory_fix_publication:${hybridDigest({ project_id: scope.project_id, run_id: scope.run_id })}`;
15
+ function refuse(reason) {
16
+ throw new AppError("memory_fix_publication_invalid", "Repair publication requires its original admitted output and retained plan", 2, { reason, effect: "no_effect" });
17
+ }
18
+ const receipt = (binding, plan, completed = false) => ({
19
+ schema_id: completed ? "dd-flow/mb-fix-publication-complete@1" : "dd-flow/mb-fix-publication-intent@1",
20
+ run_id: binding.run_id, binding_sha256: hybridDigest(binding), plan_sha256: plan.sha256
21
+ });
22
+ /** SQL custody only, never current write admission or proof of independent
23
+ * verification. Consumers must match this binding to their own frozen RUN.
24
+ * Missing either the retained value or independent receipt is not a new intent.
25
+ */
26
+ export function readMemoryFixPublication(context, scope) {
27
+ return context.db.readSnapshot(() => {
28
+ const row = context.db.get("SELECT value_json FROM runtime_config WHERE key=?", [key(scope)]);
29
+ const anchor = context.db.get("SELECT json_extract(index_json,'$.variables.\"memory_fix.publication\"') anchor FROM runs WHERE project_id=? AND id=?", [scope.project_id, scope.run_id])?.anchor;
30
+ const events = (type) => context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type=? AND json_extract(payload_json,'$.run_id')=? LIMIT 2", [scope.project_id, type, scope.run_id]);
31
+ const starts = events(intent), ends = events(complete);
32
+ if (!row && !starts.length && !ends.length && !anchor)
33
+ return null;
34
+ if (!row || !anchor || starts.length !== 1 || ends.length > 1 || Buffer.byteLength(row.value_json) > 180 * 1024 * 1024
35
+ || [...starts, ...ends].some(event => Buffer.byteLength(event.payload_json) > 32768))
36
+ refuse("publication_custody_missing");
37
+ try {
38
+ const value = JSON.parse(row.value_json), binding = value.binding;
39
+ if (Object.keys(value).sort().join() !== "before_documents,binding,plan_json,plan_sha256" || !binding
40
+ || binding.project_id !== scope.project_id || binding.run_id !== scope.run_id || typeof value.plan_json !== "string")
41
+ refuse("publication_binding_changed");
42
+ const plan = RetainedMemoryRepairPublicationPlan.restore(Buffer.from(value.plan_json), value.plan_sha256);
43
+ const beforeDocuments = validateDistillBeforeDocuments(plan, value.before_documents);
44
+ const paths = JSON.parse(plan.state(0).bytes().toString("utf8")).write_paths;
45
+ if (hybridDigest([...paths].sort()) !== hybridDigest([...binding.write_scope.paths].sort())
46
+ || anchor !== hybridDigest(receipt(binding, plan))
47
+ || hybridDigest(JSON.parse(starts[0].payload_json)) !== hybridDigest(receipt(binding, plan))
48
+ || ends.length && hybridDigest(JSON.parse(ends[0].payload_json)) !== hybridDigest(receipt(binding, plan, true)))
49
+ refuse("publication_receipt_changed");
50
+ return { binding: structuredClone(binding), plan, beforeDocuments, completed: ends.length === 1 };
51
+ }
52
+ catch (error) {
53
+ if (error instanceof AppError)
54
+ throw error;
55
+ return refuse("invalid_publication_custody");
56
+ }
57
+ });
58
+ }
59
+ /** Exact handoff bytes include removed paths and original retained text refs.
60
+ * Serialization alone is not publication, Stage acceptance or finding closure. */
61
+ export function memoryFixPublicationArtifact(binding, plan) {
62
+ const entries = new Map(plan.state(plan.actions().length).entries().map(entry => [entry.path, entry]));
63
+ return Buffer.from(JSON.stringify({ schema_id: "dd-flow/mb-fix-publication@1", binding, plan_sha256: plan.sha256,
64
+ documents: binding.write_scope.paths.map(logical => ({ path: logical, after: entries.get(logical) ?? null })),
65
+ before_documents: memoryFixBeforeDocumentReferences(plan), receipt: receipt(binding, plan, true) }));
66
+ }
67
+ export function readMemoryFixPublicationReference(context, retained, logical) {
68
+ if (!retained.completed)
69
+ refuse("publication_not_complete");
70
+ if (!logical || path.isAbsolute(logical) || logical.includes("\\") || logical.split("/").some(part => !part || part === "." || part === ".."))
71
+ refuse("invalid_publication_locator");
72
+ const { binding, plan } = retained;
73
+ const run = context.db.get("SELECT run_root FROM runs WHERE project_id=? AND id=?", [binding.project_id, binding.run_id]);
74
+ if (!run?.run_root)
75
+ refuse("run_home_required");
76
+ const file = path.join(run.run_root, logical), bytes = memoryFixPublicationArtifact(binding, plan);
77
+ const rows = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type='flow_run.stage_packet' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.stage')='repair' AND json_extract(payload_json,'$.attempt')=? LIMIT 33", [binding.project_id, binding.run_id, binding.attempt]);
78
+ if (rows.length > 32 || rows.some(row => Buffer.byteLength(row.payload_json) > 8 * 1024 * 1024))
79
+ refuse("unbounded_publication_packets");
80
+ const packets = rows.map(row => JSON.parse(row.payload_json)).filter(packet => typeof packet.file === "string"
81
+ && resolveStagePublicationPath(context, binding.project_id, binding.run_id, packet.file) === file);
82
+ if (!packets.length)
83
+ return null;
84
+ const sha256 = createHash("sha256").update(bytes).digest("hex");
85
+ if (packets.some(packet => packet.sha256 !== sha256 || packet.base64 !== bytes.toString("base64")))
86
+ refuse("publication_packet_changed");
87
+ return { root: "run", path: logical, sha256 };
88
+ }
89
+ /** Internal publisher: actual active author + accepted intake, SQL intent before
90
+ * effects, exact retained prefix and effect-aware retry. No public registration,
91
+ * new Session allocation, independent verification or finding closure here. */
92
+ export function prepareMemoryFixPublication(context, input) {
93
+ const admission = prepareMemoryFixRepairAdmission(context, input), selected = admission.snapshot();
94
+ const proposal = structuredClone(input.proposal), scope = selected.intake.write_scope;
95
+ if (!proposal || Object.keys(proposal).join() !== "outputs" || !Array.isArray(proposal.outputs) || !proposal.outputs.length || proposal.outputs.length > 512
96
+ || proposal.outputs.some(output => !output || Object.keys(output).sort().join() !== "content,finding_ids,path"
97
+ || typeof output.path !== "string" || output.content !== null && typeof output.content !== "string"
98
+ || !Array.isArray(output.finding_ids) || !output.finding_ids.length || new Set(output.finding_ids).size !== output.finding_ids.length
99
+ || output.finding_ids.some(id => !scope.finding_ids.includes(id) || !selected.intake.routing.some(decision => decision.finding_id === id && decision.route === "memory_repair" && decision.write_paths.includes(output.path))))
100
+ || hybridDigest(proposal.outputs.map(output => output.path).sort()) !== hybridDigest([...scope.paths].sort())
101
+ || scope.finding_ids.some(id => !proposal.outputs.some(output => output.finding_ids.includes(id))))
102
+ refuse("output_scope_changed");
103
+ if (proposal.outputs.some(output => output.content !== null && Buffer.byteLength(output.content) > 16 * 1024 * 1024)
104
+ || proposal.outputs.reduce((total, output) => total + (output.content === null ? 0 : Buffer.byteLength(output.content)), 0) > 64 * 1024 * 1024)
105
+ refuse("unbounded_output");
106
+ const { intake, run_root: runRoot, ...identity } = selected;
107
+ const checkoutRelative = path.relative(projectCheckoutRoot(context.ddFlowHome, selected.project_id), selected.workspace_root);
108
+ const managedCheckout = !!checkoutRelative && !path.isAbsolute(checkoutRelative)
109
+ && checkoutRelative !== ".." && !checkoutRelative.startsWith(`..${path.sep}`);
110
+ // The bound project checkout is productive workspace, even when its owner
111
+ // stores it in Home. Other Home areas and every RUN root remain protected.
112
+ for (const protectedRoot of [...(managedCheckout ? [] : [context.ddFlowHome]), runRoot]) {
113
+ const root = fs.realpathSync(protectedRoot);
114
+ for (const output of proposal.outputs) {
115
+ const destination = path.join(selected.workspace_root, output.path), relative = path.relative(root, destination);
116
+ if (!relative || !path.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path.sep}`))
117
+ refuse("output_overlaps_runtime");
118
+ }
119
+ }
120
+ const binding = { ...identity, intake_sha256: intake.intake_sha256, source_observation_sha256: intake.source_observation_sha256,
121
+ user_inputs_sha256: intake.user_inputs_sha256, write_scope: scope, proposal_sha256: hybridDigest(proposal),
122
+ assignments: proposal.outputs.map(output => ({ path: output.path, finding_ids: [...output.finding_ids] })) };
123
+ const selection = { project_id: binding.project_id, run_id: binding.run_id };
124
+ const prior = readMemoryFixPublication(context, selection);
125
+ if (prior && hybridDigest(prior.binding) !== hybridDigest(binding))
126
+ refuse("original_publication_required");
127
+ if (!prior)
128
+ admission.assertOriginalSources();
129
+ const plan = prior?.plan ?? prepareMemoryFixDocumentPlan(selected.workspace_root, proposal.outputs.map(output => ({ path: output.path, bytes: output.content === null ? null : Buffer.from(output.content) })));
130
+ const beforeDocuments = prior?.beforeDocuments ?? captureDistillBeforeDocuments(selected.workspace_root, plan);
131
+ const bundle = readFlowRunExecutionBundle(context, input);
132
+ if (!bundle || bundle.sha256 !== selected.bundle_sha256)
133
+ refuse("bundle_changed");
134
+ const logical = `${bundle.descriptor.stage("repair").directory}/publication.json`, file = path.join(runRoot, logical);
135
+ const assertIntent = () => {
136
+ const current = readMemoryFixPublication(context, selection);
137
+ if (!current || hybridDigest(current.binding) !== hybridDigest(binding) || current.plan.sha256 !== plan.sha256)
138
+ refuse("publication_intent_changed");
139
+ return current;
140
+ };
141
+ const fence = () => { admission.assertCustody(context); admission.assertPlanSources(plan); assertIntent(); };
142
+ const applyStep = () => {
143
+ admission.assertPhysical();
144
+ admission.assertPlanSources(plan);
145
+ context.db.writeTransaction(() => {
146
+ admission.assertCustody(context);
147
+ const current = readMemoryFixPublication(context, selection);
148
+ if (current) {
149
+ assertIntent();
150
+ return;
151
+ }
152
+ if (prior)
153
+ refuse("publication_custody_missing");
154
+ const value = JSON.stringify({ binding, plan_json: plan.bytes().toString("utf8"), plan_sha256: plan.sha256, before_documents: beforeDocuments });
155
+ if (Buffer.byteLength(value) > 180 * 1024 * 1024)
156
+ refuse("unbounded_publication_custody");
157
+ context.db.run("INSERT INTO runtime_config(key,value_json,created_at,updated_at) VALUES (?,?,?,?)", [key(selection), value, context.now(), context.now()]);
158
+ appendAudit(context, { projectId: binding.project_id, eventType: intent, payload: receipt(binding, plan) });
159
+ context.db.run("UPDATE runs SET index_json=json_set(index_json,'$.variables.\"memory_fix.publication\"',?) WHERE project_id=? AND id=?", [hybridDigest(receipt(binding, plan)), binding.project_id, binding.run_id]);
160
+ assertIntent();
161
+ });
162
+ publishMemoryFixBeforeDocuments(runRoot, plan, beforeDocuments);
163
+ const result = applyMemoryFixDocumentStep(selected.workspace_root, plan, fence);
164
+ if (result.status !== "applied")
165
+ return result;
166
+ try {
167
+ fence();
168
+ const packet = prepareStagePacket(file, memoryFixPublicationArtifact(binding, plan));
169
+ context.db.writeTransaction(() => {
170
+ admission.assertCustody(context);
171
+ const current = assertIntent();
172
+ if (!current.completed)
173
+ appendAudit(context, { projectId: binding.project_id, eventType: complete, payload: receipt(binding, plan, true) });
174
+ const completed = assertIntent();
175
+ if (!readMemoryFixPublicationReference(context, completed, logical))
176
+ acceptStagePacket(context, { projectId: binding.project_id, runId: binding.run_id, stage: "repair", prepared: packet });
177
+ });
178
+ fence();
179
+ const reference = readMemoryFixPublicationReference(context, assertIntent(), logical);
180
+ if (!reference)
181
+ refuse("publication_reference_missing");
182
+ return { ...result, publication: reference };
183
+ }
184
+ catch (error) {
185
+ throw new AppError("memory_fix_publication_pending", "Reconcile this retained publication before claiming repair completion", 1, { effect: "publication_pending", plan_sha256: plan.sha256, cause: error instanceof Error ? error.message : String(error) });
186
+ }
187
+ };
188
+ return { binding: () => structuredClone(binding), applyStep, apply: () => {
189
+ for (let step = 0; step <= plan.actions().length; step++) {
190
+ const result = applyStep();
191
+ if (result.status === "applied")
192
+ return result;
193
+ }
194
+ return refuse("publication_did_not_progress");
195
+ } };
196
+ }
@@ -0,0 +1,116 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { AppError } from "../shared/errors.js";
4
+ import { entityReferenceVariants } from "../shared/entity-references.js";
5
+ import { resolveProjectRoot } from "../storage/paths.js";
6
+ import { requireProjectByRoot } from "./projects.js";
7
+ import { readFlowRunExecutionBundle, assertPreparedRunBundleSqlAuthority } from "./runs.js";
8
+ import { readDescriptorStageInput } from "./descriptor-stage-coordinator.js";
9
+ import { readAcceptedDescriptorStageSettlement, readActiveDescriptorStageCoordinator } from "./descriptor-stage-settlement.js";
10
+ import { readAcceptedStageData } from "./stage-report-renderer.js";
11
+ import { stageLifecycleMatchKey } from "./hooks.js";
12
+ import { readMemoryFixSourceIntake, assertMemoryFixSourceIntakeCustody } from "./memory-fix-intake-custody.js";
13
+ import { readAcceptedMemoryFixIntakeEvidence } from "./memory-fix-intake-settlement.js";
14
+ import { readPreparedMemoryFlowUserInputs } from "./memory-flow-user-inputs.js";
15
+ import { observeRetainedMemoryFixSources, observeMemoryFixWriteTargets, memoryFixWriteTargetsFromBaseline } from "./memory-fix-sources.js";
16
+ import { RetainedMemoryRepairPublicationPlan } from "./memory-repair-publication-plan.js";
17
+ import { hybridDigest } from "./hybrid-contracts.js";
18
+ function refuse(reason) {
19
+ throw new AppError("memory_fix_repair_admission_invalid", "Repair requires its accepted intake and current delivered scope", 2, { reason, effect: "no_effect" });
20
+ }
21
+ /** Intake/Stage/user and active author Work custody, not publication permission.
22
+ * The publisher must also admit the author's exact output and retain its original
23
+ * plan before effects. Fresh plans require assertOriginalSources; resumed plans
24
+ * require assertPlanSources on that same SQL-retained plan, never a new baseline.
25
+ */
26
+ export function prepareMemoryFixRepairAdmission(context, input) {
27
+ if (context.db.isOwnedWriteTransaction?.())
28
+ refuse("preparation_outside_writer_required");
29
+ const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
30
+ const bundle = readFlowRunExecutionBundle(context, input);
31
+ if (!bundle || bundle.manifest.flow_id !== "mb-fix")
32
+ refuse("memory_fix_bundle_required");
33
+ const scope = { projectId: project.id, runId: input.runId, stage: "repair" };
34
+ const source = readMemoryFixSourceIntake(context, input, bundle);
35
+ const run = assertPreparedRunBundleSqlAuthority(context, scope, bundle);
36
+ const stage = readDescriptorStageInput(context, scope, bundle);
37
+ const accepted = readAcceptedStageData(context, { ...scope, stage: "intake" });
38
+ if (stage.input.predecessor?.stage !== "intake" || hybridDigest(stage.input.predecessor.result) !== hybridDigest(accepted.result))
39
+ refuse("accepted_intake_predecessor_required");
40
+ const keys = entityReferenceVariants(input.runId).map(reference => stageLifecycleMatchKey("stage_finish", reference, "intake", input.projectRoot));
41
+ const settlement = readAcceptedDescriptorStageSettlement(context, { ...scope, stage: "intake" }, bundle, keys);
42
+ const author = readActiveDescriptorStageCoordinator(context, scope, bundle);
43
+ const file = path.join(stage.runRoot, bundle.descriptor.stage("intake").directory, "intake-evidence.json");
44
+ const evidence = readAcceptedMemoryFixIntakeEvidence(context, scope, file);
45
+ if (evidence.intake_sha256 !== source.sha256 || !evidence.write_scope)
46
+ refuse("accepted_repair_scope_required");
47
+ const workspace = resolveProjectRoot(run.workspace_root);
48
+ const physical = () => {
49
+ if (fs.realpathSync(workspace) !== workspace)
50
+ refuse("workspace_identity_changed");
51
+ const stat = fs.lstatSync(workspace);
52
+ if (!stat.isDirectory())
53
+ refuse("workspace_identity_changed");
54
+ return { dev: stat.dev, ino: stat.ino };
55
+ };
56
+ const identity = physical();
57
+ const assertPhysical = () => {
58
+ if (context.db.isOwnedWriteTransaction?.())
59
+ refuse("physical_check_outside_writer_required");
60
+ if (hybridDigest(physical()) !== hybridDigest(identity))
61
+ refuse("workspace_identity_changed");
62
+ };
63
+ const assertCustody = (writer) => {
64
+ assertMemoryFixSourceIntakeCustody(writer, scope, bundle, source.sha256);
65
+ const current = assertPreparedRunBundleSqlAuthority(writer, scope, bundle);
66
+ if (current.workspace_root !== workspace || current.run_root !== stage.runRoot
67
+ || hybridDigest(readDescriptorStageInput(writer, scope, bundle)) !== hybridDigest(stage)
68
+ || hybridDigest(readAcceptedStageData(writer, { ...scope, stage: "intake" })) !== hybridDigest(accepted)
69
+ || hybridDigest(readAcceptedDescriptorStageSettlement(writer, { ...scope, stage: "intake" }, bundle, keys)) !== hybridDigest(settlement)
70
+ || hybridDigest(readActiveDescriptorStageCoordinator(writer, scope, bundle)) !== hybridDigest(author)
71
+ || hybridDigest(readAcceptedMemoryFixIntakeEvidence(writer, scope, file)) !== hybridDigest(evidence)
72
+ || hybridDigest(readPreparedMemoryFlowUserInputs(writer, scope, "mb-fix", bundle)) !== evidence.user_inputs_sha256)
73
+ refuse("accepted_scope_changed");
74
+ };
75
+ assertCustody(context);
76
+ const observe = () => { assertPhysical(); return observeRetainedMemoryFixSources(workspace, source.intake).snapshot(); };
77
+ return {
78
+ snapshot: () => structuredClone({ project_id: project.id, run_id: input.runId, workspace_root: workspace,
79
+ workspace_identity: identity, run_root: stage.runRoot, intake: evidence,
80
+ intake_result: accepted.result, bundle_sha256: bundle.sha256, author, attempt: stage.attempt, input_sha256: stage.inputSha256 }),
81
+ assertCustody, assertPhysical,
82
+ assertOriginalSources: () => {
83
+ const current = { ...observe(), write_targets: observeMemoryFixWriteTargets(workspace, evidence.write_scope.paths) };
84
+ if (hybridDigest(current) !== hybridDigest(evidence.source_observation))
85
+ refuse("sources_changed_since_intake");
86
+ },
87
+ assertPlanSources: (plan) => {
88
+ assertPhysical();
89
+ if (!(plan instanceof RetainedMemoryRepairPublicationPlan))
90
+ refuse("retained_plan_required");
91
+ const before = JSON.parse(plan.state(0).bytes().toString("utf8"));
92
+ if (hybridDigest([...before.write_paths].sort()) !== hybridDigest([...evidence.write_scope.paths].sort())
93
+ || plan.state(0).canonicalWritePaths().size)
94
+ refuse("plan_scope_changed");
95
+ if (hybridDigest(memoryFixWriteTargetsFromBaseline(plan.state(0))) !== hybridDigest(evidence.source_observation.write_targets))
96
+ refuse("write_targets_changed_since_intake");
97
+ const current = plan.classify(workspace), entries = plan.state(current.completed).entries();
98
+ const actual = observe();
99
+ for (const original of evidence.source_observation.observations) {
100
+ const observed = actual.observations.find(item => item.path === original.path);
101
+ if (!observed)
102
+ refuse("source_inventory_changed");
103
+ if (before.write_paths.includes(original.path)) {
104
+ const baseline = before.entries.find((entry) => entry.path === original.path);
105
+ if ((baseline?.sha256 ?? null) !== original.current_sha256)
106
+ refuse("plan_rebased_after_intake");
107
+ const expected = entries.find(entry => entry.path === original.path);
108
+ if ((expected?.sha256 ?? null) !== observed.current_sha256)
109
+ refuse("source_differs_from_retained_prefix");
110
+ }
111
+ else if (hybridDigest(observed) !== hybridDigest(original))
112
+ refuse("sources_changed_since_intake");
113
+ }
114
+ }
115
+ };
116
+ }
@@ -0,0 +1,141 @@
1
+ import { AppError } from "../shared/errors.js";
2
+ import { requireProjectByRoot } from "./projects.js";
3
+ import { hybridDigest } from "./hybrid-contracts.js";
4
+ import { appendAudit } from "./audit.js";
5
+ import { prepareMemoryFixRunBundle } from "./memory-fix-run-bundle.js";
6
+ import { PreparedInstalledFlowSource } from "./installed-flow-source.js";
7
+ import { FrozenGenericExecution } from "./generic-execution.js";
8
+ import { AdmittedFrozenEngine } from "./frozen-engine.js";
9
+ import { PreparedDescriptorRunStart } from "./descriptor-run-start.js";
10
+ import { readRunStartRequest } from "./run-start-custody.js";
11
+ import { prepareMemoryFixGitPolicy } from "./memory-fix-finish-integration.js";
12
+ const event = "memory_fix.request";
13
+ function refuse(reason) {
14
+ throw new AppError("memory_fix_request_invalid", "Repair launch requires its original retained request", 2, { reason, effect: "no_effect" });
15
+ }
16
+ function selection(input) {
17
+ return hybridDigest({ source_sha256: input.source.sha256, execution_sha256: input.execution.sha256,
18
+ engine_manifest_sha256: input.engine.manifestSha256, request_text: input.requestText,
19
+ source_run_id: input.sourceRunId, finding_ids: input.findingIds, git_executable: input.gitExecutable, git_policy: input.gitPolicy,
20
+ receipts: [...input.receipts ?? []], receipt_bytes: [...input.receiptBytes ?? []].map(([key, bytes]) => [key, Buffer.from(bytes).toString("base64")]) });
21
+ }
22
+ /** SQL-only lookup, also used by the allocation writer. No neighboring audit,
23
+ * current canon/profile settings or filesystem observation is consulted. */
24
+ export function readMemoryFixRequest(context, { projectRoot, requestId }) {
25
+ const scope = { projectRoot, requestId };
26
+ const project = context.db.get("SELECT id FROM projects WHERE root=? AND status='active'", [scope.projectRoot]);
27
+ if (!project)
28
+ refuse("project_not_registered");
29
+ const rows = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type=? AND json_extract(payload_json,'$.scope.requestId')=? LIMIT 2", [project.id, event, scope.requestId]);
30
+ if (!rows.length)
31
+ return null;
32
+ if (rows.length !== 1 || Buffer.byteLength(rows[0].payload_json) > 48 * 1024 * 1024)
33
+ refuse("ambiguous_request");
34
+ let value;
35
+ try {
36
+ value = JSON.parse(rows[0].payload_json);
37
+ }
38
+ catch {
39
+ return refuse("invalid_request");
40
+ }
41
+ if (value.schema_id !== "dd-flow/memory-fix-request@1" || value.project_id !== project.id
42
+ || hybridDigest(value.scope) !== hybridDigest(scope) || !/^[a-f0-9]{64}$/.test(value.selection_sha256)
43
+ || !Number.isFinite(Date.parse(value.bound_at)) || typeof value.request_text !== "string" || !value.request_text.trim()
44
+ || Buffer.byteLength(value.request_text) > 64 * 1024 || !Array.isArray(value.artifacts) || value.artifacts.length !== 2
45
+ || new Set(value.artifacts.map(item => item.path)).size !== 2
46
+ || value.artifacts.some(item => !["runtime/repair-intake.json", "runtime/git-policy.json"].includes(item.path)
47
+ || typeof item.base64 !== "string" || !item.base64 || item.base64.length > 2 * 1024 * 1024
48
+ || Buffer.from(item.base64, "base64").toString("base64") !== item.base64)
49
+ || !Array.isArray(value.receipts) || !Array.isArray(value.receipt_bytes)
50
+ || value.launch_selection !== null && (!value.launch_selection || typeof value.launch_selection !== "object" || Array.isArray(value.launch_selection))
51
+ || value.execution?.scope?.projectRoot !== scope.projectRoot || value.execution.scope.requestId !== scope.requestId
52
+ || value.execution.settings.flow_id !== "mb-fix")
53
+ refuse("request_scope_changed");
54
+ return value;
55
+ }
56
+ export function readMemoryFixRequestArtifacts(context, input) {
57
+ const frozen = input.execution.snapshot(), request = readMemoryFixRequest(context, { projectRoot: frozen.scope.projectRoot, requestId: frozen.scope.requestId });
58
+ if (request && request.selection_sha256 !== selection(input))
59
+ refuse("request_changed");
60
+ return request;
61
+ }
62
+ /** Allocation fence: metadata and retained bytes only. Historical RUN replay
63
+ * owns its bundle independently and does not need the neighboring source RUN. */
64
+ export function assertMemoryFixRequestBundle(context, projectId, bundle) {
65
+ const frozen = bundle.execution.snapshot(), request = readMemoryFixRequest(context, { projectRoot: frozen.scope.projectRoot, requestId: frozen.scope.requestId });
66
+ if (!request || request.project_id !== projectId || bundle.manifest.flow_id !== "mb-fix" || bundle.manifest.bootstrap_receipts.length
67
+ || bundle.execution.sha256 !== request.execution_sha256 || bundle.manifest.descriptor.sha256 !== request.source.descriptor.sha256
68
+ || bundle.blob("runtime/source-request.md").toString("utf8") !== request.request_text
69
+ || bundle.manifest.engine_manifest.sha256 !== request.engine.manifest_sha256
70
+ || request.artifacts.some(item => bundle.blob(item.path).toString("base64") !== item.base64)
71
+ || request.source.blobs.some(item => bundle.blob(item.path).toString("base64") !== item.base64))
72
+ refuse("request_bundle_changed");
73
+ }
74
+ /** Preserve prepared input before allocating a RUN. IDs are selected only by
75
+ * common RUN-start, so another request may allocate between retain and start. */
76
+ export function retainMemoryFixRequest(context, input, launchSelection) {
77
+ if (context.db.isOwnedWriteTransaction?.())
78
+ refuse("preparation_outside_writer_required");
79
+ const frozen = input.execution.snapshot(), scope = { projectRoot: frozen.scope.projectRoot, requestId: frozen.scope.requestId };
80
+ if (!/^[A-Za-z0-9][A-Za-z0-9_.-]{0,95}$/.test(scope.requestId))
81
+ refuse("invalid_request_id");
82
+ const project = requireProjectByRoot(context, scope.projectRoot), digest = selection(input), prior = readMemoryFixRequest(context, scope);
83
+ const raw = structuredClone(launchSelection ?? null);
84
+ const assertSame = (request) => {
85
+ if (request.selection_sha256 !== digest || launchSelection !== undefined && hybridDigest(request.launch_selection) !== hybridDigest(raw))
86
+ refuse("request_changed");
87
+ };
88
+ if (prior) {
89
+ assertSame(prior);
90
+ return prior;
91
+ }
92
+ if (readRunStartRequest(context, project.id, scope.requestId))
93
+ refuse("foreign_run_request");
94
+ const bundle = prepareMemoryFixRunBundle(context, input), manifest = input.engine.manifest();
95
+ const value = { schema_id: "dd-flow/memory-fix-request@1", project_id: project.id, scope, selection_sha256: digest,
96
+ bound_at: input.boundAt, request_text: input.requestText, source_run_id: input.sourceRunId, finding_ids: [...input.findingIds],
97
+ source: input.source.snapshot(), source_sha256: input.source.sha256, execution: frozen, execution_sha256: input.execution.sha256,
98
+ engine: { identity: { package_name: manifest.package_name, package_version: manifest.package_version, engine_version: manifest.engine_version,
99
+ integrity_checksum: manifest.integrity.checksum, snapshot_root: manifest.snapshot_root }, manifest_sha256: input.engine.manifestSha256 },
100
+ git_executable: input.gitExecutable, git_policy: structuredClone(input.gitPolicy),
101
+ launch_selection: raw,
102
+ artifacts: ["runtime/repair-intake.json", "runtime/git-policy.json"].map(path => ({ path, base64: bundle.blob(path).toString("base64") })),
103
+ receipts: [...input.receipts ?? []], receipt_bytes: [...input.receiptBytes ?? []].map(([key, bytes]) => [key, Buffer.from(bytes).toString("base64")]) };
104
+ if (Buffer.byteLength(JSON.stringify(value)) > 48 * 1024 * 1024)
105
+ refuse("request_exceeds_bound");
106
+ return context.db.writeTransaction(() => {
107
+ const current = readMemoryFixRequest(context, scope);
108
+ if (current) {
109
+ assertSame(current);
110
+ return current;
111
+ }
112
+ if (readRunStartRequest(context, project.id, scope.requestId))
113
+ refuse("foreign_run_request");
114
+ appendAudit(context, { projectId: project.id, eventType: event, payload: value });
115
+ return value;
116
+ });
117
+ }
118
+ /** Existing allocations resume the common publisher. Before allocation only
119
+ * retained source/execution and input artifacts are restored, never refreshed. */
120
+ export function startMemoryFixRun(context, scope) {
121
+ const request = readMemoryFixRequest(context, scope);
122
+ if (!request)
123
+ refuse("original_request_required");
124
+ const allocation = readRunStartRequest(context, request.project_id, scope.requestId);
125
+ if (allocation) {
126
+ if (allocation.payload.initial_index.flow_kind !== "mb-fix")
127
+ refuse("foreign_run_request");
128
+ return PreparedDescriptorRunStart.restore(context, scope).apply(context);
129
+ }
130
+ const source = PreparedInstalledFlowSource.restoreRetained(request.source, request.source_sha256), receipts = new Map(request.receipts);
131
+ const execution = FrozenGenericExecution.restore(request.execution, request.execution_sha256, source.resolveRegisteredFlow(), request.execution.scope, receipts);
132
+ const engine = AdmittedFrozenEngine.admitSelection(request.engine.identity, request.engine.manifest_sha256);
133
+ const actualGit = prepareMemoryFixGitPolicy(context.env, { scope: { ...scope, workspaceRoot: request.execution.scope.workspace },
134
+ gitExecutable: request.git_executable, selection: request.git_policy });
135
+ if (actualGit.bytes.toString("base64") !== request.artifacts.find(item => item.path === actualGit.path)?.base64)
136
+ refuse("git_selection_changed_before_start");
137
+ const bundle = prepareMemoryFixRunBundle(context, { source, execution, engine, boundAt: request.bound_at, requestText: request.request_text,
138
+ sourceRunId: request.source_run_id, findingIds: request.finding_ids, gitExecutable: request.git_executable, gitPolicy: request.git_policy,
139
+ receipts, receiptBytes: new Map(request.receipt_bytes.map(([key, bytes]) => [key, Buffer.from(bytes, "base64")])) });
140
+ return PreparedDescriptorRunStart.prepare(context, { bundle }).apply(context);
141
+ }