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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +9 -0
  3. package/dist/build-info.json +6 -6
  4. package/dist/cli/argument-values.js +5 -5
  5. package/dist/cli/command-inputs.js +89 -20
  6. package/dist/cli/command-metadata.js +197 -0
  7. package/dist/cli/command-option-descriptions.js +191 -0
  8. package/dist/cli/completion-cli.js +70 -0
  9. package/dist/cli/completion.js +57 -0
  10. package/dist/cli/error-output.js +52 -0
  11. package/dist/cli/executor-onboarding.js +76 -0
  12. package/dist/cli/flows.js +29 -0
  13. package/dist/cli/harnesses.js +84 -0
  14. package/dist/cli/help.js +240 -6
  15. package/dist/cli/input-preparation.js +54 -5
  16. package/dist/cli/memory-flow-admission.js +44 -0
  17. package/dist/cli/memory-init-invocation.js +78 -0
  18. package/dist/cli/memory-init.js +240 -0
  19. package/dist/cli/memory-storage-admission.js +53 -0
  20. package/dist/cli/models.js +39 -0
  21. package/dist/cli/native-hook-ingress.js +1 -1
  22. package/dist/cli/operator-profiles.js +61 -0
  23. package/dist/cli/output-options.js +35 -0
  24. package/dist/cli/response-file.js +71 -0
  25. package/dist/cli/run-cli.js +283 -160
  26. package/dist/cli/setup.js +149 -0
  27. package/dist/cli.js +54 -2
  28. package/dist/domain/stage-handler-identities.js +20 -0
  29. package/dist/harness-runtime/lib/daemon-operations.d.mts +1 -0
  30. package/dist/harness-runtime/lib/daemon-operations.mjs +4 -3
  31. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +4 -2
  32. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +11 -5
  33. package/dist/harness-runtime/lib/dd-codex.mjs +22 -10
  34. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +2 -1
  35. package/dist/harness-runtime/lib/dd-droid.mjs +2 -1
  36. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +4 -2
  37. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +6 -4
  38. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +9 -9
  39. package/dist/harness-runtime/lib/delegation-instructions.mjs +5 -3
  40. package/dist/harness-runtime/lib/lifecycle-command.mjs +1 -0
  41. package/dist/harness-runtime/lib/managed-daemon.d.mts +1 -0
  42. package/dist/harness-runtime/lib/managed-daemon.mjs +50 -15
  43. package/dist/harness-runtime/lib/managed-flow-env.mjs +15 -0
  44. package/dist/harness-runtime/lib/native-hook-command.mjs +1 -1
  45. package/dist/harness-runtime/lib/native-request-proof.d.mts +2 -0
  46. package/dist/harness-runtime/lib/native-request-proof.mjs +65 -0
  47. package/dist/harness-runtime/lib/opencode-plugin.mjs +2 -0
  48. package/dist/harness-runtime/lib/operation-progress.mjs +3 -1
  49. package/dist/runtime/context.js +1 -1
  50. package/dist/schemas/discovery-result.schema.json +184 -0
  51. package/dist/schemas/execution-bundle.schema.json +205 -0
  52. package/dist/schemas/execution-settings.schema.json +162 -0
  53. package/dist/schemas/flow-descriptor.schema.json +54 -0
  54. package/dist/schemas/flow-run.schema.json +22 -0
  55. package/dist/schemas/harness-catalog.schema.json +80 -0
  56. package/dist/schemas/mb-init-compose-result.schema.json +457 -0
  57. package/dist/schemas/mb-init-discover-result.schema.json +500 -0
  58. package/dist/schemas/mb-init-finish-result.schema.json +422 -0
  59. package/dist/schemas/mb-init-preflight-result.schema.json +367 -0
  60. package/dist/schemas/mb-init-review-result.schema.json +396 -0
  61. package/dist/schemas/mb-init-verify-result.schema.json +391 -0
  62. package/dist/schemas/memory-bootstrap-receipt-v2.schema.json +67 -0
  63. package/dist/schemas/memory-bootstrap-receipt.schema.json +354 -0
  64. package/dist/schemas/memory-composition-work-result.schema.json +38 -0
  65. package/dist/schemas/memory-repair-work-result.schema.json +49 -0
  66. package/dist/schemas/memory-review-correction-plan.schema.json +30 -0
  67. package/dist/schemas/memory-review-work-result.schema.json +43 -0
  68. package/dist/schemas/model-catalog.schema.json +153 -0
  69. package/dist/schemas/profile-settings.schema.json +122 -0
  70. package/dist/schemas/release-manifest.schema.json +291 -0
  71. package/dist/schemas/run-engine-binding.schema.json +1 -1
  72. package/dist/services/check-runner-evidence.js +51 -4
  73. package/dist/services/cli-operation-classifier.js +8 -47
  74. package/dist/services/cli-upgrade-allowlist.js +27 -0
  75. package/dist/services/code-checks.js +387 -34
  76. package/dist/services/controller-execution.js +88 -0
  77. package/dist/services/controller-fanout.js +71 -12
  78. package/dist/services/dashboard.js +8 -2
  79. package/dist/services/descriptor-coordinator-session.js +35 -0
  80. package/dist/services/descriptor-root-work.js +53 -0
  81. package/dist/services/descriptor-run-settlement.js +105 -0
  82. package/dist/services/descriptor-run-start.js +449 -0
  83. package/dist/services/descriptor-stage-coordinator.js +197 -0
  84. package/dist/services/descriptor-stage-settlement.js +222 -0
  85. package/dist/services/descriptor-stage-start.js +139 -0
  86. package/dist/services/discovery/agy-discovery.js +2 -0
  87. package/dist/services/discovery/codex-discovery.js +2 -0
  88. package/dist/services/discovery/droid-discovery.js +2 -0
  89. package/dist/services/discovery/grok-discovery.js +2 -0
  90. package/dist/services/discovery/opencode-discovery.js +2 -0
  91. package/dist/services/discovery/static.js +31 -0
  92. package/dist/services/discovery/zcode-discovery.js +2 -0
  93. package/dist/services/engines.js +39 -22
  94. package/dist/services/eval-snapshots.js +83 -25
  95. package/dist/services/execution-bundles.js +263 -0
  96. package/dist/services/execution-policy.js +20 -16
  97. package/dist/services/executor-onboarding.js +56 -0
  98. package/dist/services/external-work-launch.js +91 -11
  99. package/dist/services/flow-catalog.js +21 -0
  100. package/dist/services/flow-descriptors.js +82 -0
  101. package/dist/services/frozen-engine.js +242 -0
  102. package/dist/services/generic-execution.js +164 -0
  103. package/dist/services/harness-adapter.js +28 -2
  104. package/dist/services/harness-discovery.js +29 -0
  105. package/dist/services/harness-doctor.js +75 -0
  106. package/dist/services/harness-probe-effects.js +51 -0
  107. package/dist/services/harness-version-probe.js +100 -0
  108. package/dist/services/hooks.js +24 -6
  109. package/dist/services/hybrid-contracts.js +483 -0
  110. package/dist/services/ids.js +29 -1
  111. package/dist/services/installation-lock.js +95 -0
  112. package/dist/services/installed-flow-source.js +183 -0
  113. package/dist/services/lifecycle-invocations.js +257 -78
  114. package/dist/services/memory-accepted-composition.js +77 -0
  115. package/dist/services/memory-baseline-preservation.js +65 -0
  116. package/dist/services/memory-completed-correction-wave.js +320 -0
  117. package/dist/services/memory-compose-handler.js +258 -0
  118. package/dist/services/memory-composition-acceptance.js +199 -0
  119. package/dist/services/memory-composition-graph.js +134 -0
  120. package/dist/services/memory-composition-registration.js +146 -0
  121. package/dist/services/memory-composition-work-custody.js +151 -0
  122. package/dist/services/memory-composition-work-results.js +96 -0
  123. package/dist/services/memory-corrected-review-acceptance.js +129 -0
  124. package/dist/services/memory-corrected-review-batch.js +30 -0
  125. package/dist/services/memory-corrected-review-findings.js +106 -0
  126. package/dist/services/memory-corrected-review-inputs.js +210 -0
  127. package/dist/services/memory-corrected-review-projection.js +78 -0
  128. package/dist/services/memory-corrected-review-receipt.js +170 -0
  129. package/dist/services/memory-corrected-review-registration.js +191 -0
  130. package/dist/services/memory-correction-check-receipt.js +219 -0
  131. package/dist/services/memory-correction-input-owner.js +6 -0
  132. package/dist/services/memory-discover-handler.js +104 -0
  133. package/dist/services/memory-finish-evidence.js +435 -0
  134. package/dist/services/memory-finish-handler.js +215 -0
  135. package/dist/services/memory-finish-integration.js +280 -0
  136. package/dist/services/memory-finish-readiness.js +122 -0
  137. package/dist/services/memory-git-integration.js +151 -0
  138. package/dist/services/memory-historical-corrected-review.js +206 -0
  139. package/dist/services/memory-historical-original-review.js +130 -0
  140. package/dist/services/memory-historical-repair-registration.js +99 -0
  141. package/dist/services/memory-historical-second-review.js +128 -0
  142. package/dist/services/memory-init-bootstrap-readiness.js +210 -0
  143. package/dist/services/memory-init-contexts.js +89 -0
  144. package/dist/services/memory-init-controller-start.js +84 -0
  145. package/dist/services/memory-init-engine-release.js +223 -0
  146. package/dist/services/memory-init-git-policy.js +136 -0
  147. package/dist/services/memory-init-lint-capability.js +185 -0
  148. package/dist/services/memory-init-native-capacity.js +241 -0
  149. package/dist/services/memory-init-preflight.js +312 -0
  150. package/dist/services/memory-init-project-bootstrap.js +599 -0
  151. package/dist/services/memory-init-request-candidate.js +157 -0
  152. package/dist/services/memory-init-request-input.js +66 -0
  153. package/dist/services/memory-init-resource-storage.js +125 -0
  154. package/dist/services/memory-init-run-allocation.js +81 -0
  155. package/dist/services/memory-init-run-bundle.js +98 -0
  156. package/dist/services/memory-init-start.js +175 -0
  157. package/dist/services/memory-init-startup-custody.js +80 -0
  158. package/dist/services/memory-init-startup-input.js +240 -0
  159. package/dist/services/memory-init-storage.js +81 -0
  160. package/dist/services/memory-init-stores.js +359 -0
  161. package/dist/services/memory-init-worktrunk-capability.js +172 -0
  162. package/dist/services/memory-layer-publication.js +611 -0
  163. package/dist/services/memory-layer.js +156 -0
  164. package/dist/services/memory-lint-config.js +85 -0
  165. package/dist/services/memory-native-review-copy.js +165 -0
  166. package/dist/services/memory-normal-flow-entry.js +75 -0
  167. package/dist/services/memory-ordinary-defaults.js +112 -0
  168. package/dist/services/memory-preflight-bootstrap-custody.js +115 -0
  169. package/dist/services/memory-preflight-handler.js +259 -0
  170. package/dist/services/memory-repair-current-view.js +299 -0
  171. package/dist/services/memory-repair-document-delta.js +451 -0
  172. package/dist/services/memory-repair-history.js +491 -0
  173. package/dist/services/memory-repair-physical-binding.js +312 -0
  174. package/dist/services/memory-repair-publication-files.js +152 -0
  175. package/dist/services/memory-repair-publication-intent.js +27 -0
  176. package/dist/services/memory-repair-publication-plan.js +160 -0
  177. package/dist/services/memory-repair-publication-source.js +225 -0
  178. package/dist/services/memory-repair-publication.js +794 -0
  179. package/dist/services/memory-repair-registration.js +239 -0
  180. package/dist/services/memory-repair-work-baseline.js +413 -0
  181. package/dist/services/memory-repair-work-completion.js +97 -0
  182. package/dist/services/memory-repair-work-custody.js +73 -0
  183. package/dist/services/memory-repair-work-results.js +89 -0
  184. package/dist/services/memory-repair-workspace.js +252 -0
  185. package/dist/services/memory-repair-writer-copy.js +735 -0
  186. package/dist/services/memory-result-binding.js +173 -0
  187. package/dist/services/memory-retained-review-authority.js +129 -0
  188. package/dist/services/memory-retained-review-results.js +70 -0
  189. package/dist/services/memory-retained-second-baseline.js +102 -0
  190. package/dist/services/memory-retained-second-repairs.js +156 -0
  191. package/dist/services/memory-review-acceptance.js +214 -0
  192. package/dist/services/memory-review-correction-plan.js +171 -0
  193. package/dist/services/memory-review-handler.js +585 -0
  194. package/dist/services/memory-review-inputs.js +200 -0
  195. package/dist/services/memory-review-receipt.js +180 -0
  196. package/dist/services/memory-review-registration.js +108 -0
  197. package/dist/services/memory-review-work-custody.js +124 -0
  198. package/dist/services/memory-review-work-input.js +72 -0
  199. package/dist/services/memory-review-work-results.js +73 -0
  200. package/dist/services/memory-second-corrected-review-inputs.js +165 -0
  201. package/dist/services/memory-second-correction-plan.js +76 -0
  202. package/dist/services/memory-second-repair-baseline.js +91 -0
  203. package/dist/services/memory-second-repair-launch.js +614 -0
  204. package/dist/services/memory-second-repair-registration.js +88 -0
  205. package/dist/services/memory-second-repair-source.js +187 -0
  206. package/dist/services/memory-second-review-acceptance.js +115 -0
  207. package/dist/services/memory-source-observations.js +117 -0
  208. package/dist/services/memory-stage-dispatch.js +28 -0
  209. package/dist/services/memory-stage-results.js +199 -0
  210. package/dist/services/memory-verification-acceptance.js +135 -0
  211. package/dist/services/memory-verification-checks.js +252 -0
  212. package/dist/services/memory-verification-receipt.js +159 -0
  213. package/dist/services/memory-verify-handler.js +135 -0
  214. package/dist/services/memory-work-result-preparation.js +63 -0
  215. package/dist/services/model-catalog.js +109 -0
  216. package/dist/services/model-discovery.js +46 -0
  217. package/dist/services/operator-profiles.js +236 -0
  218. package/dist/services/projects.js +17 -2
  219. package/dist/services/recovery-snapshot-database.js +416 -1
  220. package/dist/services/release-archive-producer.js +52 -0
  221. package/dist/services/release-archive.js +143 -0
  222. package/dist/services/release-cache.js +142 -0
  223. package/dist/services/release-compatibility.js +18 -0
  224. package/dist/services/release-download.js +67 -0
  225. package/dist/services/release-envelope.js +104 -0
  226. package/dist/services/release-manifest.js +45 -0
  227. package/dist/services/release-payload.js +114 -0
  228. package/dist/services/release-producer.js +63 -0
  229. package/dist/services/release-provenance.js +160 -0
  230. package/dist/services/release-registry.js +66 -0
  231. package/dist/services/release-source.js +194 -0
  232. package/dist/services/repair-intents.js +1 -1
  233. package/dist/services/review-copy.js +10 -2
  234. package/dist/services/run-control.js +80 -9
  235. package/dist/services/run-controller-capture.js +10 -3
  236. package/dist/services/run-controller-process.js +21 -0
  237. package/dist/services/run-controller-recovery.js +3 -1
  238. package/dist/services/run-controller-state.js +1 -1
  239. package/dist/services/run-controller.js +101 -40
  240. package/dist/services/run-recovery-guard.js +2 -0
  241. package/dist/services/run-recovery.js +145 -5
  242. package/dist/services/run-start-custody.js +177 -0
  243. package/dist/services/runs.js +319 -25
  244. package/dist/services/setup.js +377 -0
  245. package/dist/services/stage-context.js +9 -1
  246. package/dist/services/stage-handlers.js +74 -0
  247. package/dist/services/stage-report-renderer.js +63 -0
  248. package/dist/services/stage-work-graph.js +17 -0
  249. package/dist/services/vnext-code.js +3 -3
  250. package/dist/services/vnext-execution-profile.js +2 -2
  251. package/dist/services/vnext-fanout.js +70 -9
  252. package/dist/services/vnext-merge.js +8 -3
  253. package/dist/services/vnext-plan-review.js +6 -4
  254. package/dist/services/work-registry.js +616 -71
  255. package/dist/services/workspace-files.js +1 -1
  256. package/dist/storage/database.js +84 -6
  257. package/dist/storage/writer-contract.js +11 -7
  258. package/package.json +13 -3
  259. package/tools/create-canon-release.mjs +33 -0
  260. package/tools/produce-hybrid-release.mjs +41 -0
@@ -0,0 +1,794 @@
1
+ import { createHash } from "node:crypto";
2
+ import path from "node:path";
3
+ import fs from "node:fs";
4
+ import { AppError } from "../shared/errors.js";
5
+ import { hybridDigest } from "./hybrid-contracts.js";
6
+ import { PreparedMemoryRepairWorkBaseline } from "./memory-repair-work-baseline.js";
7
+ import { PreparedBoundMemoryRepairCopy } from "./memory-repair-writer-copy.js";
8
+ import { appendAudit } from "./audit.js";
9
+ import { stageRetainedMemoryRepairFile, verifyRetainedMemoryRepairFile } from "./memory-repair-publication-files.js";
10
+ import { acquireInstallationLock } from "./installation-lock.js";
11
+ import { PreparedMemoryRepairPublicationSource } from "./memory-repair-publication-source.js";
12
+ import { retainedMemoryRepairPublicationPlans } from "./memory-repair-publication-intent.js";
13
+ import { PreparedSecondMemoryRepairLaunch } from "./memory-second-repair-launch.js";
14
+ const token = Symbol("registered-memory-repair-publication");
15
+ const hash = (bytes) => createHash("sha256").update(bytes).digest("hex");
16
+ function refuse(reason) {
17
+ throw new AppError("memory_repair_publication_invalid", "Publication intent lacks retained output and physical start custody", 1, { reason, effect: "no_effect" });
18
+ }
19
+ /** Pure expected-view derivation, not SQL or physical observation authority. */
20
+ export function deriveRetainedMemoryRepairViews(start, exported, plan, result, privatePlan = plan) {
21
+ if (privatePlan.state(0).sha256 !== start.sha256)
22
+ refuse("foreign_retained_start_view");
23
+ if (start.exported(exported.entries().filter(entry => entry.kind === "file").map(entry => entry.path)).sha256 !== exported.sha256)
24
+ refuse("foreign_retained_export_view");
25
+ const actions = plan.actions();
26
+ const changes = (retained) => retained.actions().filter(action => action.after?.kind !== "directory").map(action => ({ path: action.path,
27
+ before_sha256: action.before?.sha256 ?? null, after_sha256: action.after?.sha256 ?? null }));
28
+ const ordered = (items) => [...items].sort((a, b) => a.path.localeCompare(b.path, "en"));
29
+ if (hybridDigest(ordered(changes(plan))) !== hybridDigest(ordered(result.changes))
30
+ || hybridDigest(ordered(changes(privatePlan))) !== hybridDigest(ordered(result.changes)))
31
+ refuse("reserved_delta_differs_from_result");
32
+ const after = privatePlan.state(privatePlan.actions().length);
33
+ const merged = plan.state(0).composeSettledDelta(start, after);
34
+ if (merged.sha256 !== plan.state(actions.length).sha256)
35
+ refuse("unexplained_retained_publication_view");
36
+ const available = new Set(after.entries().filter(entry => entry.kind === "file").map(entry => entry.path));
37
+ const selected = [...new Set([...exported.entries().filter(entry => entry.kind === "file" && available.has(entry.path)).map(entry => entry.path),
38
+ ...result.changes.filter(change => change.after_sha256 !== null).map(change => change.path)])];
39
+ const exportedAfter = after.exported(selected);
40
+ const deltaSha256 = hybridDigest({ baseline_sha256: exported.sha256,
41
+ snapshot_sha256: hybridDigest(exportedAfter.entries()), changes: ordered(result.changes) });
42
+ const transitionSha256 = hybridDigest({ before_sha256: plan.state(0).sha256, start_protected_sha256: start.sha256,
43
+ delta_sha256: deltaSha256, actions: actions.map(action => ({ path: action.path, before: action.before, after: action.after })) });
44
+ return { exportedAfter, deltaSha256, transitionSha256 };
45
+ }
46
+ /** SQL-selected intent restored independently from partially published source.
47
+ * This owner verifies retained output and exact physical prefixes. It does not
48
+ * apply files, settle Work or claim semantic review/closure.
49
+ */
50
+ export class PreparedRetainedMemoryRepairPublication {
51
+ sha256;
52
+ #baseline;
53
+ #copy;
54
+ #second;
55
+ #secondBinding;
56
+ #workSessionId;
57
+ #bytes;
58
+ #result;
59
+ #plan;
60
+ #exportedAfter;
61
+ #source;
62
+ constructor(key, baseline, copy, workSessionId, receipt, bytes, result, plan, exportedAfter, source = null, second = null, secondBinding = null) {
63
+ if (key !== token)
64
+ refuse("publication_token_required");
65
+ this.#baseline = baseline;
66
+ this.#copy = copy;
67
+ this.#workSessionId = workSessionId;
68
+ this.#bytes = Buffer.from(bytes);
69
+ this.#result = structuredClone(result);
70
+ this.#plan = plan;
71
+ this.#exportedAfter = exportedAfter;
72
+ this.#source = source;
73
+ this.#second = second;
74
+ this.#secondBinding = secondBinding;
75
+ this.sha256 = hybridDigest(receipt);
76
+ Object.freeze(this);
77
+ }
78
+ static prepare(context, baseline, copy, workSessionId, source = null) {
79
+ if (!PreparedMemoryRepairWorkBaseline.isPrepared(baseline) || !PreparedBoundMemoryRepairCopy.isPrepared(copy))
80
+ refuse("registered_publication_binding_required");
81
+ if (context.db.isOwnedWriteTransaction?.())
82
+ refuse("publication_preparation_outside_writer_required");
83
+ const receipt = baseline.registeredPublicationIntent(context, workSessionId);
84
+ copy.assertCommittedStartCustody(context, workSessionId);
85
+ try {
86
+ const { current, plan, privatePlan } = retainedMemoryRepairPublicationPlans(receipt);
87
+ if (current) {
88
+ if (!PreparedMemoryRepairPublicationSource.isPrepared(source) || !source.matches(baseline, copy, workSessionId))
89
+ refuse("registered_publication_source_required");
90
+ source.assertRegisteredCustody(context);
91
+ if (source.sha256 !== receipt.source_view_sha256 || hybridDigest(source.receipt()) !== receipt.source_receipt_sha256
92
+ || source.before().sha256 !== receipt.before_sha256 || source.before().bytes().toString("base64") !== receipt.before_base64)
93
+ refuse("registered_publication_source_changed");
94
+ }
95
+ else if (source)
96
+ refuse("foreign_legacy_publication_source");
97
+ if (receipt.private_root !== copy.root()
98
+ || receipt.source_root !== copy.sourceRoot() || copy.input().baseline_registration_sha256 !== baseline.sha256
99
+ || receipt.start_receipt_sha256 !== hybridDigest(copy.startedReceipt())
100
+ || typeof receipt.result_base64 !== "string" || receipt.result_base64.length > Math.ceil(1024 * 1024 / 3) * 4)
101
+ refuse("publication_scope_or_bytes_changed");
102
+ const bytes = Buffer.from(receipt.result_base64, "base64");
103
+ if (bytes.length > 1024 * 1024 || bytes.toString("base64") !== receipt.result_base64 || hash(bytes) !== receipt.result_sha256)
104
+ refuse("retained_result_changed");
105
+ const result = baseline.validateResult(context, bytes);
106
+ if (result.outcome !== "accepted")
107
+ refuse("accepted_reserved_result_required");
108
+ const { exportedAfter, deltaSha256, transitionSha256 } = deriveRetainedMemoryRepairViews(baseline.baseline(), baseline.exportedBaseline(), plan, result, privatePlan);
109
+ const outputSha256 = hybridDigest({ input: copy.input(), start_receipt_sha256: hybridDigest(copy.startedReceipt()), work_session_id: workSessionId,
110
+ result_sha256: hash(bytes), delta_sha256: deltaSha256, transition_sha256: transitionSha256,
111
+ ...(current ? { source_view_sha256: source.sha256, source_receipt_sha256: hybridDigest(source.receipt()), private_plan_sha256: privatePlan.sha256 } : {}) });
112
+ if (receipt.delta_sha256 !== deltaSha256 || receipt.transition_sha256 !== transitionSha256 || receipt.output_sha256 !== outputSha256)
113
+ refuse("reserved_output_identity_changed");
114
+ const prepared = new PreparedRetainedMemoryRepairPublication(token, baseline, copy, workSessionId, receipt, bytes, result, plan, exportedAfter, source);
115
+ prepared.revalidate(context);
116
+ return prepared;
117
+ }
118
+ catch (error) {
119
+ if (error instanceof AppError)
120
+ throw error;
121
+ return refuse("invalid_retained_publication");
122
+ }
123
+ }
124
+ static prepareSecond(context, second) {
125
+ if (!PreparedSecondMemoryRepairLaunch.isPrepared(second))
126
+ refuse("registered_second_publication_owner_required");
127
+ if (context.db.isOwnedWriteTransaction?.())
128
+ refuse("publication_preparation_outside_writer_required");
129
+ const binding = second.restorePublicationBinding(context), retained = binding.publication;
130
+ const prepared = new PreparedRetainedMemoryRepairPublication(token, null, null, binding.workSessionId, retained.receipt, retained.bytes, retained.result, retained.plan, retained.exportedAfter, null, second, binding);
131
+ prepared.revalidate(context);
132
+ return prepared;
133
+ }
134
+ #startedReceipt() {
135
+ if (this.#copy)
136
+ return this.#copy.startedReceipt();
137
+ if (!this.#secondBinding)
138
+ refuse("registered_publication_binding_required");
139
+ return structuredClone(this.#secondBinding.start);
140
+ }
141
+ #sourceRoot() {
142
+ if (this.#copy)
143
+ return this.#copy.sourceRoot();
144
+ if (!this.#secondBinding)
145
+ refuse("registered_publication_binding_required");
146
+ return this.#secondBinding.sourceRoot;
147
+ }
148
+ #privateRoot() {
149
+ if (this.#copy)
150
+ return this.#copy.root();
151
+ if (!this.#secondBinding)
152
+ refuse("registered_publication_binding_required");
153
+ return this.#secondBinding.privateRoot;
154
+ }
155
+ result() { return structuredClone(this.#result); }
156
+ static isPrepared(value) {
157
+ return value instanceof PreparedRetainedMemoryRepairPublication && #baseline in value;
158
+ }
159
+ bytes() { return Buffer.from(this.#bytes); }
160
+ plan() { return this.#plan; }
161
+ /** SQL-only current generation/physical start/independent intent fence. */
162
+ assertCustody(context) {
163
+ if (this.#second) {
164
+ this.#second.assertPublicationCustody(context, this.sha256);
165
+ return;
166
+ }
167
+ if (!this.#copy || !this.#baseline)
168
+ refuse("registered_publication_binding_required");
169
+ this.#copy.assertCommittedStartCustody(context, this.#workSessionId);
170
+ this.#source?.assertRegisteredCustody(context);
171
+ if (hybridDigest(this.#baseline.registeredPublicationIntent(context, this.#workSessionId)) !== this.sha256)
172
+ refuse("registered_intent_changed");
173
+ }
174
+ #actionReceipt(index) {
175
+ const action = this.#plan.actions()[index];
176
+ if (!action)
177
+ refuse("invalid_publication_action_index");
178
+ const start = this.#startedReceipt();
179
+ return { schema_id: "dd-flow/memory-repair-publication-action-intent@1", project_id: start.project_id,
180
+ run_id: start.run_id, work_id: this.#result.work_id, generation: start.generation,
181
+ work_session_id: this.#workSessionId, publication_intent_sha256: this.sha256, index,
182
+ source_root: this.#sourceRoot(), staging_directory: path.join(this.#sourceRoot(), ".dd-flow", "memory-repair-publications", this.sha256),
183
+ staging_slot: action.after ? `action-${index}.tmp` : null,
184
+ before_sha256: this.#plan.state(index).sha256, after_sha256: this.#plan.state(index + 1).sha256,
185
+ action_sha256: hybridDigest(action) };
186
+ }
187
+ #selectedAction(context, index) {
188
+ const expected = this.#actionReceipt(index);
189
+ const rows = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type='memory.repair_publication_action_intent' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.work_id')=? AND json_extract(payload_json,'$.generation')=? AND json_extract(payload_json,'$.index')=? LIMIT 2", [expected.project_id, expected.run_id, expected.work_id, expected.generation, index]);
190
+ if (!rows.length)
191
+ return null;
192
+ try {
193
+ if (rows.length !== 1 || Buffer.byteLength(rows[0].payload_json) > 16 * 1024
194
+ || hybridDigest(JSON.parse(rows[0].payload_json)) !== hybridDigest(expected))
195
+ refuse("publication_action_intent_changed");
196
+ return expected;
197
+ }
198
+ catch (error) {
199
+ if (error instanceof AppError)
200
+ throw error;
201
+ return refuse("invalid_publication_action_intent");
202
+ }
203
+ }
204
+ /** Commit SQL admission before any staging or destination effect. This is
205
+ * not proof of applied bytes, directory ownership, completion or settlement.
206
+ */
207
+ reserveNextAction(context) {
208
+ const view = this.revalidate(context);
209
+ if (view.completed === view.total)
210
+ refuse("publication_has_no_pending_action");
211
+ const receipt = this.#actionReceipt(view.completed);
212
+ const reservation = context.db.writeTransaction(() => {
213
+ if (context.db.isOwnedWriteTransaction?.() !== true)
214
+ refuse("owned_publication_writer_required");
215
+ this.assertCustody(context);
216
+ for (let index = 0; index < view.completed; index++) {
217
+ if (!this.#selectedAction(context, index))
218
+ refuse("unadmitted_publication_prefix");
219
+ }
220
+ const prior = this.#selectedAction(context, view.completed);
221
+ if (!prior)
222
+ appendAudit(context, { projectId: receipt.project_id,
223
+ eventType: "memory.repair_publication_action_intent", payload: receipt });
224
+ if (!this.#selectedAction(context, view.completed))
225
+ refuse("publication_action_readback_failed");
226
+ return { ...receipt, status: "reserved", replayed: Boolean(prior) };
227
+ });
228
+ try {
229
+ const current = this.revalidate(context);
230
+ if (current.completed !== view.completed)
231
+ refuse("publication_cursor_changed");
232
+ }
233
+ catch (error) {
234
+ throw new AppError("memory_repair_publication_pending", "Action admission committed; preserve owned publication state for reconciliation", 1, { effect: "committed", publication_intent_sha256: this.sha256, index: view.completed,
235
+ cause: error instanceof Error ? error.message : String(error) });
236
+ }
237
+ return reservation;
238
+ }
239
+ /** Reserve directory creation separately from action admission. Existing
240
+ * roots without this build receipt cannot be adopted. This creates only
241
+ * CLI service directories, never MB files, and does not apply an action.
242
+ */
243
+ prepareStagingDirectory(context) {
244
+ const view = this.revalidate(context), start = this.#startedReceipt();
245
+ if (view.completed === view.total)
246
+ refuse("publication_has_no_pending_action");
247
+ const directory = path.join(this.#sourceRoot(), ".dd-flow", "memory-repair-publications", this.sha256);
248
+ const build = { schema_id: "dd-flow/memory-repair-publication-staging@1", project_id: start.project_id,
249
+ run_id: start.run_id, work_id: this.#result.work_id, generation: start.generation,
250
+ work_session_id: this.#workSessionId, publication_intent_sha256: this.sha256, directory, mode: 0o700 };
251
+ const select = (event, expected) => {
252
+ 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,'$.work_id')=? AND json_extract(payload_json,'$.generation')=? LIMIT 2", [build.project_id, event, build.run_id, build.work_id, build.generation]);
253
+ if (!rows.length)
254
+ return false;
255
+ try {
256
+ if (rows.length !== 1 || Buffer.byteLength(rows[0].payload_json) > 16 * 1024)
257
+ refuse("staging_receipt_changed");
258
+ const value = JSON.parse(rows[0].payload_json);
259
+ if (expected ? hybridDigest(value) !== hybridDigest(expected)
260
+ : !Number.isSafeInteger(value.dev) || !Number.isSafeInteger(value.ino)
261
+ || hybridDigest(value) !== hybridDigest({ ...build, dev: value.dev, ino: value.ino }))
262
+ refuse("staging_receipt_changed");
263
+ return true;
264
+ }
265
+ catch (error) {
266
+ if (error instanceof AppError)
267
+ throw error;
268
+ return refuse("invalid_staging_receipt");
269
+ }
270
+ };
271
+ const buildEvent = "memory.repair_publication_staging_build", readyEvent = "memory.repair_publication_staging_ready";
272
+ const checkAncestors = () => {
273
+ for (const relative of ["", ".dd-flow", ".dd-flow/memory-repair-publications", `.dd-flow/memory-repair-publications/${this.sha256}`]) {
274
+ const named = path.join(this.#sourceRoot(), relative), stat = fs.lstatSync(named, { throwIfNoEntry: false });
275
+ if (stat && (!stat.isDirectory() || fs.realpathSync(named) !== named))
276
+ refuse("unsafe_staging_ancestor");
277
+ }
278
+ };
279
+ checkAncestors();
280
+ const wasReady = select(readyEvent);
281
+ if (wasReady && !fs.lstatSync(directory, { throwIfNoEntry: false }))
282
+ refuse("ready_staging_directory_missing");
283
+ if (!select(buildEvent, build) && fs.lstatSync(directory, { throwIfNoEntry: false }))
284
+ refuse("unowned_staging_directory");
285
+ context.db.writeTransaction(() => {
286
+ if (context.db.isOwnedWriteTransaction?.() !== true)
287
+ refuse("owned_publication_writer_required");
288
+ this.assertCustody(context);
289
+ for (let index = 0; index < view.completed; index++)
290
+ if (!this.#selectedAction(context, index))
291
+ refuse("unadmitted_publication_prefix");
292
+ if (view.completed < view.total && !this.#selectedAction(context, view.completed))
293
+ refuse("reserved_publication_action_required");
294
+ if (!select(buildEvent, build))
295
+ appendAudit(context, { projectId: build.project_id, eventType: buildEvent, payload: build });
296
+ if (!select(buildEvent, build))
297
+ refuse("staging_build_readback_failed");
298
+ });
299
+ try {
300
+ this.revalidate(context);
301
+ checkAncestors();
302
+ for (const relative of [".dd-flow", ".dd-flow/memory-repair-publications", `.dd-flow/memory-repair-publications/${this.sha256}`]) {
303
+ const named = path.join(this.#sourceRoot(), relative);
304
+ if (!fs.lstatSync(named, { throwIfNoEntry: false })) {
305
+ if (wasReady)
306
+ refuse("ready_staging_directory_missing");
307
+ fs.mkdirSync(named, { mode: 0o700 });
308
+ fs.chmodSync(named, 0o700);
309
+ checkAncestors();
310
+ for (const durable of [named, path.dirname(named)]) {
311
+ const descriptor = fs.openSync(durable, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0));
312
+ try {
313
+ fs.fsyncSync(descriptor);
314
+ }
315
+ finally {
316
+ fs.closeSync(descriptor);
317
+ }
318
+ }
319
+ }
320
+ }
321
+ checkAncestors();
322
+ const stat = fs.lstatSync(directory);
323
+ if ((stat.mode & 0o7777) !== 0o700 || stat.dev !== fs.lstatSync(this.#sourceRoot()).dev)
324
+ refuse("unsafe_staging_directory");
325
+ const ready = { ...build, dev: stat.dev, ino: stat.ino };
326
+ const replayed = select(readyEvent, ready);
327
+ if (!replayed && fs.readdirSync(directory).length)
328
+ refuse("unready_staging_directory_not_empty");
329
+ if (replayed) {
330
+ const actions = this.#plan.actions();
331
+ for (const slot of fs.readdirSync(directory)) {
332
+ const match = /^action-([0-9]{1,6})\.tmp$/.exec(slot), index = match ? Number(match[1]) : -1;
333
+ if (!match || slot !== `action-${index}.tmp` || !actions[index]?.after
334
+ || !this.#selectedAction(context, index))
335
+ refuse("unknown_staging_slot");
336
+ const entry = fs.lstatSync(path.join(directory, slot));
337
+ if (actions[index].after.kind === "file" ? !entry.isFile() || entry.nlink !== 1
338
+ : !entry.isDirectory() || fs.readdirSync(path.join(directory, slot)).length !== 0)
339
+ refuse("unsafe_staging_slot");
340
+ }
341
+ }
342
+ const fd = fs.openSync(directory, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0));
343
+ try {
344
+ const current = fs.fstatSync(fd);
345
+ if (current.dev !== stat.dev || current.ino !== stat.ino || !current.isDirectory())
346
+ refuse("staging_directory_identity_changed");
347
+ fs.fsyncSync(fd);
348
+ }
349
+ finally {
350
+ fs.closeSync(fd);
351
+ }
352
+ const parent = fs.openSync(path.dirname(directory), fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0));
353
+ try {
354
+ fs.fsyncSync(parent);
355
+ }
356
+ finally {
357
+ fs.closeSync(parent);
358
+ }
359
+ this.revalidate(context);
360
+ checkAncestors();
361
+ const retained = fs.lstatSync(directory);
362
+ if (retained.dev !== stat.dev || retained.ino !== stat.ino || (retained.mode & 0o7777) !== 0o700)
363
+ refuse("staging_directory_identity_changed");
364
+ return context.db.writeTransaction(() => {
365
+ if (context.db.isOwnedWriteTransaction?.() !== true)
366
+ refuse("owned_publication_writer_required");
367
+ this.assertCustody(context);
368
+ if (!select(buildEvent, build))
369
+ refuse("staging_build_custody_lost");
370
+ for (let index = 0; index <= view.completed; index++)
371
+ if (!this.#selectedAction(context, index))
372
+ refuse("publication_action_custody_lost");
373
+ if (!select(readyEvent, ready))
374
+ appendAudit(context, { projectId: build.project_id, eventType: readyEvent, payload: ready });
375
+ if (!select(readyEvent, ready))
376
+ refuse("staging_ready_readback_failed");
377
+ return { directory, publication_intent_sha256: this.sha256, staging_ready_sha256: hybridDigest(ready), status: "ready", replayed };
378
+ });
379
+ }
380
+ catch (error) {
381
+ throw new AppError("memory_repair_publication_pending", "Staging build committed; preserve its directory for reconciliation", 1, { effect: "publication_pending", publication_intent_sha256: this.sha256, directory,
382
+ cause: error instanceof Error ? error.message : String(error) });
383
+ }
384
+ }
385
+ /** Prepare exact retained file bytes behind action and directory admission.
386
+ * The destination is unchanged; a separate apply owner must consume this
387
+ * independent readiness receipt before atomic replacement.
388
+ */
389
+ stageNextFile(context) {
390
+ const view = this.revalidate(context), action = this.#plan.actions()[view.completed];
391
+ if (action?.after?.kind !== "file")
392
+ refuse("pending_file_action_required");
393
+ const reservation = this.reserveNextAction(context);
394
+ const staging = this.prepareStagingDirectory(context);
395
+ try {
396
+ if (this.revalidate(context).completed !== view.completed)
397
+ refuse("publication_cursor_changed");
398
+ const receipt = { ...this.#actionReceipt(view.completed), schema_id: "dd-flow/memory-repair-publication-file-ready@1",
399
+ output_sha256: action.after.sha256, mode: action.after.mode };
400
+ const ready = this.#actionJournal(context, "memory.repair_publication_file_ready", receipt);
401
+ const file = path.join(staging.directory, `action-${view.completed}.tmp`), bytes = this.#plan.output(action.path);
402
+ let output;
403
+ if (ready) {
404
+ verifyRetainedMemoryRepairFile(file, bytes, action.after.mode);
405
+ output = { path: file, sha256: action.after.sha256, status: "ready", replayed: true, bytes_appended: 0 };
406
+ }
407
+ else
408
+ output = stageRetainedMemoryRepairFile(staging.directory, `action-${view.completed}.tmp`, bytes, action.after.mode);
409
+ const verifiedStaging = this.prepareStagingDirectory(context);
410
+ if (verifiedStaging.staging_ready_sha256 !== staging.staging_ready_sha256)
411
+ refuse("staging_directory_identity_changed");
412
+ if (output.sha256 !== action.after.sha256 || this.revalidate(context).completed !== view.completed)
413
+ refuse("staged_output_changed");
414
+ return context.db.writeTransaction(() => {
415
+ if (context.db.isOwnedWriteTransaction?.() !== true)
416
+ refuse("owned_publication_writer_required");
417
+ this.assertCustody(context);
418
+ if (!this.#selectedAction(context, view.completed))
419
+ refuse("publication_action_custody_lost");
420
+ const directories = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type='memory.repair_publication_staging_ready' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.work_id')=? AND json_extract(payload_json,'$.generation')=? LIMIT 2", [receipt.project_id, receipt.run_id, receipt.work_id, receipt.generation]);
421
+ try {
422
+ if (directories.length !== 1 || Buffer.byteLength(directories[0].payload_json) > 16 * 1024
423
+ || hybridDigest(JSON.parse(directories[0].payload_json)) !== staging.staging_ready_sha256)
424
+ refuse("staging_directory_custody_lost");
425
+ }
426
+ catch (error) {
427
+ if (error instanceof AppError)
428
+ throw error;
429
+ refuse("invalid_staging_directory_custody");
430
+ }
431
+ const select = () => this.#actionJournal(context, "memory.repair_publication_file_ready", receipt);
432
+ const replayed = select();
433
+ if (!replayed)
434
+ appendAudit(context, { projectId: receipt.project_id, eventType: "memory.repair_publication_file_ready", payload: receipt });
435
+ if (!select())
436
+ refuse("staged_file_readback_failed");
437
+ return { ...output, index: view.completed, publication_intent_sha256: this.sha256,
438
+ action_intent_replayed: reservation.replayed, readiness_replayed: replayed };
439
+ });
440
+ }
441
+ catch (error) {
442
+ throw new AppError("memory_repair_publication_pending", "Reserved staging file remains pending; destination has not been applied by this operation", 1, { effect: "publication_pending", publication_intent_sha256: this.sha256, index: view.completed,
443
+ cause: error instanceof Error ? error.message : String(error) });
444
+ }
445
+ }
446
+ #actionJournal(context, event, receipt) {
447
+ 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,'$.work_id')=? AND json_extract(payload_json,'$.generation')=? AND json_extract(payload_json,'$.index')=? LIMIT 2", [receipt.project_id, event, receipt.run_id, receipt.work_id, receipt.generation, receipt.index]);
448
+ if (!rows.length)
449
+ return false;
450
+ try {
451
+ if (rows.length !== 1 || Buffer.byteLength(rows[0].payload_json) > 16 * 1024
452
+ || hybridDigest(JSON.parse(rows[0].payload_json)) !== hybridDigest(receipt))
453
+ refuse("publication_action_journal_changed");
454
+ return true;
455
+ }
456
+ catch (error) {
457
+ if (error instanceof AppError)
458
+ throw error;
459
+ return refuse("invalid_publication_action_journal");
460
+ }
461
+ }
462
+ #assertActionReady(context, index) {
463
+ if (!this.#selectedAction(context, index))
464
+ refuse("publication_action_custody_lost");
465
+ const action = this.#plan.actions()[index];
466
+ if (action.after) {
467
+ this.#registeredStagingReady(context);
468
+ const file = action.after.kind === "file";
469
+ const receipt = { ...this.#actionReceipt(index), schema_id: file ? "dd-flow/memory-repair-publication-file-ready@1" : "dd-flow/memory-repair-publication-directory-ready@1",
470
+ ...(file ? { output_sha256: action.after.sha256 } : {}), mode: action.after.mode };
471
+ if (!this.#actionJournal(context, file ? "memory.repair_publication_file_ready" : "memory.repair_publication_directory_ready", receipt))
472
+ refuse("publication_action_readiness_required");
473
+ }
474
+ }
475
+ #registeredStagingReady(context) {
476
+ const start = this.#startedReceipt();
477
+ const rows = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type='memory.repair_publication_staging_ready' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.work_id')=? AND json_extract(payload_json,'$.generation')=? LIMIT 2", [start.project_id, start.run_id, this.#result.work_id, start.generation]);
478
+ try {
479
+ if (rows.length !== 1 || Buffer.byteLength(rows[0].payload_json) > 16 * 1024)
480
+ refuse("staging_directory_custody_lost");
481
+ const value = JSON.parse(rows[0].payload_json);
482
+ const expected = { schema_id: "dd-flow/memory-repair-publication-staging@1", project_id: start.project_id,
483
+ run_id: start.run_id, work_id: this.#result.work_id, generation: start.generation,
484
+ work_session_id: this.#workSessionId, publication_intent_sha256: this.sha256,
485
+ directory: path.join(this.#sourceRoot(), ".dd-flow", "memory-repair-publications", this.sha256), mode: 0o700,
486
+ dev: value.dev, ino: value.ino };
487
+ if (!Number.isSafeInteger(value.dev) || !Number.isSafeInteger(value.ino) || hybridDigest(value) !== hybridDigest(expected))
488
+ refuse("staging_directory_custody_changed");
489
+ return expected;
490
+ }
491
+ catch (error) {
492
+ if (error instanceof AppError)
493
+ throw error;
494
+ return refuse("invalid_staging_directory_custody");
495
+ }
496
+ }
497
+ #completeAction(context, index) {
498
+ const receipt = { ...this.#actionReceipt(index), schema_id: "dd-flow/memory-repair-publication-action-complete@1" };
499
+ const event = "memory.repair_publication_action_complete";
500
+ return context.db.writeTransaction(() => {
501
+ if (context.db.isOwnedWriteTransaction?.() !== true)
502
+ refuse("owned_publication_writer_required");
503
+ this.assertCustody(context);
504
+ this.#assertActionReady(context, index);
505
+ const replayed = this.#actionJournal(context, event, receipt);
506
+ if (!replayed)
507
+ appendAudit(context, { projectId: receipt.project_id, eventType: event, payload: receipt });
508
+ if (!this.#actionJournal(context, event, receipt))
509
+ refuse("publication_completion_readback_failed");
510
+ return replayed;
511
+ });
512
+ }
513
+ #completionReceipt() {
514
+ const start = this.#startedReceipt();
515
+ const total = this.#plan.actions().length;
516
+ return { schema_id: "dd-flow/memory-repair-publication-complete@1", project_id: start.project_id,
517
+ run_id: start.run_id, work_id: this.#result.work_id, generation: start.generation, work_session_id: this.#workSessionId,
518
+ publication_intent_sha256: this.sha256, result_sha256: hash(this.#bytes), action_count: total,
519
+ before_sha256: this.#plan.state(0).sha256, after_sha256: this.#plan.state(total).sha256 };
520
+ }
521
+ #selectedCompletion(context) {
522
+ const receipt = this.#completionReceipt();
523
+ const expected = hybridDigest(receipt), event = "memory.repair_publication_complete";
524
+ 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,'$.work_id')=? AND json_extract(payload_json,'$.generation')=? LIMIT 2", [receipt.project_id, event, receipt.run_id, receipt.work_id, receipt.generation]);
525
+ if (!rows.length)
526
+ return false;
527
+ try {
528
+ if (rows.length !== 1 || Buffer.byteLength(rows[0].payload_json) > 16 * 1024
529
+ || hybridDigest(JSON.parse(rows[0].payload_json)) !== expected)
530
+ refuse("publication_completion_changed");
531
+ return true;
532
+ }
533
+ catch (error) {
534
+ if (error instanceof AppError)
535
+ throw error;
536
+ return refuse("invalid_publication_completion");
537
+ }
538
+ }
539
+ /** SQL-only completion proof for the active physical start. This is not
540
+ * current filesystem observation or independent semantic review acceptance.
541
+ */
542
+ assertAppliedCustody(context) {
543
+ this.assertCustody(context);
544
+ if (!this.#selectedCompletion(context))
545
+ refuse("registered_publication_completion_required");
546
+ const receipt = this.#completionReceipt();
547
+ for (let index = 0; index < receipt.action_count; index++) {
548
+ this.#assertActionReady(context, index);
549
+ const completed = { ...this.#actionReceipt(index), schema_id: "dd-flow/memory-repair-publication-action-complete@1" };
550
+ if (!this.#actionJournal(context, "memory.repair_publication_action_complete", completed))
551
+ refuse("action_completion_required");
552
+ }
553
+ return receipt;
554
+ }
555
+ revalidateApplied(context) {
556
+ const view = this.revalidate(context);
557
+ if (view.completed !== view.total)
558
+ refuse("complete_publication_view_required");
559
+ return hybridDigest(this.assertAppliedCustody(context));
560
+ }
561
+ settlementBinding(context) {
562
+ const completion = this.assertAppliedCustody(context), start = this.#startedReceipt();
563
+ if (typeof start.session_id !== "string" || typeof start.harness !== "string" || typeof start.provider_session_id !== "string")
564
+ refuse("physical_settlement_identity_required");
565
+ if (!context.db.get("SELECT 1 FROM works w JOIN works p ON p.work_id=w.parent_work_id AND p.project_id=w.project_id AND p.run_id=w.run_id JOIN runs r ON r.project_id=w.project_id AND r.id=w.run_id WHERE w.work_id=? AND w.project_id=? AND w.run_id=? AND w.status='running' AND p.status='running' AND json_extract(r.index_json,'$.current_stage')='review'", [this.#result.work_id, completion.project_id, completion.run_id]))
566
+ refuse("active_review_settlement_scope_required");
567
+ return { project_id: completion.project_id, run_id: completion.run_id, generation: completion.generation,
568
+ work_id: this.#result.work_id, work_session_id: this.#workSessionId, session_id: start.session_id,
569
+ harness: start.harness, provider_session_id: start.provider_session_id, private_root: this.#privateRoot(), source_root: this.#sourceRoot(),
570
+ start_receipt_sha256: hybridDigest(start), publication_intent_sha256: this.sha256,
571
+ publication_complete_sha256: hybridDigest(completion), result_sha256: completion.result_sha256,
572
+ before_sha256: completion.before_sha256, after_sha256: completion.after_sha256 };
573
+ }
574
+ #completePublication(context) {
575
+ const view = this.revalidate(context);
576
+ if (view.completed !== view.total)
577
+ refuse("complete_publication_view_required");
578
+ const receipt = this.#completionReceipt(), expected = hybridDigest(receipt), event = "memory.repair_publication_complete";
579
+ context.db.writeTransaction(() => {
580
+ if (context.db.isOwnedWriteTransaction?.() !== true)
581
+ refuse("owned_publication_writer_required");
582
+ this.assertCustody(context);
583
+ for (let index = 0; index < view.total; index++) {
584
+ this.#assertActionReady(context, index);
585
+ const completed = { ...this.#actionReceipt(index), schema_id: "dd-flow/memory-repair-publication-action-complete@1" };
586
+ if (!this.#actionJournal(context, "memory.repair_publication_action_complete", completed))
587
+ refuse("action_completion_required");
588
+ }
589
+ if (!this.#selectedCompletion(context))
590
+ appendAudit(context, { projectId: receipt.project_id, eventType: event, payload: receipt });
591
+ if (!this.#selectedCompletion(context))
592
+ refuse("publication_completion_readback_failed");
593
+ });
594
+ if (this.revalidate(context).completed !== view.total)
595
+ refuse("completed_publication_view_changed");
596
+ return expected;
597
+ }
598
+ /** Apply one ordered action under the same workspace lock used by initial
599
+ * layer publication. Exact already-applied prefixes reconcile lost completion
600
+ * receipts only with independently retained admission/readiness. This never
601
+ * settles Work or treats worker acceptance as independent semantic review.
602
+ */
603
+ applyNextAction(context) {
604
+ const initial = this.revalidate(context);
605
+ for (let index = 0; index < initial.completed; index++)
606
+ this.#assertActionReady(context, index);
607
+ try {
608
+ if (initial.completed < initial.total) {
609
+ const action = this.#plan.actions()[initial.completed];
610
+ if (action.after?.kind === "file")
611
+ this.stageNextFile(context);
612
+ else {
613
+ this.reserveNextAction(context);
614
+ if (action.after?.kind === "directory") {
615
+ const staging = this.prepareStagingDirectory(context), slot = path.join(staging.directory, `action-${initial.completed}.tmp`);
616
+ const receipt = { ...this.#actionReceipt(initial.completed), schema_id: "dd-flow/memory-repair-publication-directory-ready@1", mode: action.after.mode };
617
+ const ready = this.#actionJournal(context, "memory.repair_publication_directory_ready", receipt);
618
+ const prior = fs.lstatSync(slot, { throwIfNoEntry: false });
619
+ if (ready && (!prior || !prior.isDirectory() || (prior.mode & 0o7777) !== action.after.mode))
620
+ refuse("ready_directory_slot_changed");
621
+ if (!prior)
622
+ fs.mkdirSync(slot, { mode: 0o700 });
623
+ else if (!prior.isDirectory() || fs.readdirSync(slot).length || ![0o700, action.after.mode].includes(prior.mode & 0o7777))
624
+ refuse("unsafe_staging_directory_slot");
625
+ const fd = fs.openSync(slot, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0));
626
+ try {
627
+ const opened = fs.fstatSync(fd);
628
+ if (!opened.isDirectory() || prior && (opened.dev !== prior.dev || opened.ino !== prior.ino))
629
+ refuse("directory_slot_identity_changed");
630
+ if (!ready)
631
+ fs.fchmodSync(fd, action.after.mode);
632
+ fs.fsyncSync(fd);
633
+ const named = fs.lstatSync(slot), current = fs.fstatSync(fd);
634
+ if (!named.isDirectory() || named.dev !== opened.dev || named.ino !== opened.ino
635
+ || current.dev !== opened.dev || current.ino !== opened.ino || (current.mode & 0o7777) !== action.after.mode)
636
+ refuse("directory_slot_identity_changed");
637
+ }
638
+ finally {
639
+ fs.closeSync(fd);
640
+ }
641
+ const verified = this.prepareStagingDirectory(context);
642
+ if (verified.staging_ready_sha256 !== staging.staging_ready_sha256)
643
+ refuse("staging_directory_identity_changed");
644
+ context.db.writeTransaction(() => {
645
+ if (context.db.isOwnedWriteTransaction?.() !== true)
646
+ refuse("owned_publication_writer_required");
647
+ this.assertCustody(context);
648
+ if (!this.#selectedAction(context, initial.completed))
649
+ refuse("publication_action_custody_lost");
650
+ if (hybridDigest(this.#registeredStagingReady(context)) !== staging.staging_ready_sha256)
651
+ refuse("staging_directory_custody_changed");
652
+ if (!this.#actionJournal(context, "memory.repair_publication_directory_ready", receipt))
653
+ appendAudit(context, { projectId: receipt.project_id, eventType: "memory.repair_publication_directory_ready", payload: receipt });
654
+ if (!this.#actionJournal(context, "memory.repair_publication_directory_ready", receipt))
655
+ refuse("directory_readiness_readback_failed");
656
+ });
657
+ }
658
+ }
659
+ }
660
+ }
661
+ catch (error) {
662
+ throw new AppError("memory_repair_publication_pending", "Publication staging requires reconciliation before shared application", 1, { effect: "publication_pending", publication_intent_sha256: this.sha256, cause: error instanceof Error ? error.message : String(error) });
663
+ }
664
+ const namespace = path.join(context.ddFlowHome, "memory-layer-publications");
665
+ if (fs.realpathSync(context.ddFlowHome) !== path.resolve(context.ddFlowHome))
666
+ refuse("noncanonical_publication_home");
667
+ if (!fs.lstatSync(namespace, { throwIfNoEntry: false })) {
668
+ fs.mkdirSync(namespace, { mode: 0o700 });
669
+ fs.chmodSync(namespace, 0o700);
670
+ }
671
+ if (!fs.lstatSync(namespace).isDirectory() || fs.realpathSync(namespace) !== namespace)
672
+ refuse("unsafe_publication_lock_namespace");
673
+ const lock = acquireInstallationLock(path.join(namespace, `${hybridDigest(this.#sourceRoot())}.lock`), this.sha256);
674
+ let released = false;
675
+ const release = () => { lock.release(); released = true; };
676
+ try {
677
+ lock.assertOwnership();
678
+ const view = this.revalidate(context);
679
+ for (let index = 0; index < view.completed; index++) {
680
+ const action = this.#plan.actions()[index];
681
+ const directories = [path.dirname(path.join(this.#sourceRoot(), action.path))];
682
+ if (action.after) {
683
+ const retained = this.#registeredStagingReady(context), actual = fs.lstatSync(retained.directory);
684
+ if (!actual.isDirectory() || actual.dev !== retained.dev || actual.ino !== retained.ino
685
+ || (actual.mode & 0o7777) !== 0o700 || fs.realpathSync(retained.directory) !== retained.directory)
686
+ refuse("ready_staging_directory_changed");
687
+ directories.push(retained.directory);
688
+ }
689
+ for (const directory of directories) {
690
+ const fd = fs.openSync(directory, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0));
691
+ try {
692
+ fs.fsyncSync(fd);
693
+ }
694
+ finally {
695
+ fs.closeSync(fd);
696
+ }
697
+ }
698
+ lock.assertOwnership();
699
+ this.#completeAction(context, index);
700
+ }
701
+ if (view.completed === view.total) {
702
+ const publication_complete_sha256 = this.#completePublication(context);
703
+ release();
704
+ return { completed: view.completed, total: view.total, status: "applied", replayed: true, publication_complete_sha256 };
705
+ }
706
+ if (view.completed !== initial.completed)
707
+ refuse("publication_cursor_changed");
708
+ this.#assertActionReady(context, view.completed);
709
+ const action = this.#plan.actions()[view.completed], destination = path.join(this.#sourceRoot(), action.path);
710
+ const staged = path.join(this.#sourceRoot(), ".dd-flow", "memory-repair-publications", this.sha256, `action-${view.completed}.tmp`);
711
+ if (action.after) {
712
+ const retained = this.#registeredStagingReady(context), directory = path.dirname(staged), actual = fs.lstatSync(directory);
713
+ if (!actual.isDirectory() || (actual.mode & 0o7777) !== 0o700 || actual.dev !== retained.dev || actual.ino !== retained.ino
714
+ || fs.realpathSync(directory) !== directory)
715
+ refuse("ready_staging_directory_changed");
716
+ }
717
+ if (action.after?.kind === "file")
718
+ verifyRetainedMemoryRepairFile(staged, this.#plan.output(action.path), action.after.mode);
719
+ else if (action.after) {
720
+ const stat = fs.lstatSync(staged);
721
+ if (!stat.isDirectory() || (stat.mode & 0o7777) !== action.after.mode || fs.readdirSync(staged).length)
722
+ refuse("ready_directory_slot_changed");
723
+ }
724
+ // Revalidate the entire protected inventory immediately before the effect,
725
+ // not merely the selected target. Cooperative publications share this lock.
726
+ if (this.revalidate(context).completed !== view.completed)
727
+ refuse("publication_cursor_changed");
728
+ lock.assertOwnership();
729
+ if (action.after)
730
+ fs.renameSync(staged, destination);
731
+ else
732
+ fs.unlinkSync(destination);
733
+ const parent = fs.openSync(path.dirname(destination), fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0));
734
+ try {
735
+ fs.fsyncSync(parent);
736
+ }
737
+ finally {
738
+ fs.closeSync(parent);
739
+ }
740
+ if (action.after) {
741
+ const stagingParent = fs.openSync(path.dirname(staged), fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0));
742
+ try {
743
+ fs.fsyncSync(stagingParent);
744
+ }
745
+ finally {
746
+ fs.closeSync(stagingParent);
747
+ }
748
+ }
749
+ const current = this.revalidate(context);
750
+ if (current.completed !== view.completed + 1)
751
+ refuse("publication_effect_not_exact");
752
+ lock.assertOwnership();
753
+ this.#completeAction(context, view.completed);
754
+ const completion = current.completed === current.total ? this.#completePublication(context) : null;
755
+ release();
756
+ return { ...current, status: current.completed === current.total ? "applied" : "partial", replayed: false,
757
+ ...(completion ? { publication_complete_sha256: completion } : {}) };
758
+ }
759
+ catch (error) {
760
+ let releaseFailure;
761
+ if (!released)
762
+ try {
763
+ release();
764
+ }
765
+ catch (releaseError) {
766
+ releaseFailure = releaseError instanceof Error ? releaseError.message : String(releaseError);
767
+ }
768
+ throw new AppError("memory_repair_publication_pending", "Reserved publication must reconcile its exact admitted prefix before settlement", 1, { effect: "publication_pending", publication_intent_sha256: this.sha256, cause: error instanceof Error ? error.message : String(error),
769
+ ...(releaseFailure ? { release_failure: releaseFailure } : {}) });
770
+ }
771
+ }
772
+ revalidate(context) {
773
+ if (context.db.isOwnedWriteTransaction?.())
774
+ refuse("publication_verification_outside_writer_required");
775
+ this.assertCustody(context);
776
+ if (this.#second) {
777
+ const view = this.#second.verifyPublicationPrefix(context);
778
+ if (view.publication_intent_sha256 !== this.sha256 || view.work_session_id !== this.#workSessionId
779
+ || view.work_id !== this.#result.work_id || view.total !== this.#plan.actions().length)
780
+ refuse("registered_second_publication_changed");
781
+ return { completed: view.completed, total: view.total };
782
+ }
783
+ if (!this.#baseline || !this.#copy)
784
+ refuse("registered_publication_binding_required");
785
+ this.#baseline.validateResult(context, this.#bytes);
786
+ this.#copy.verifyPublicationPrivateView(context, this.#workSessionId, this.#exportedAfter);
787
+ const view = this.#plan.classify(this.#sourceRoot());
788
+ this.#copy.verifyPublicationSourceInputs(context, this.#workSessionId, this.#plan.state(view.completed));
789
+ const current = this.#plan.classify(this.#sourceRoot());
790
+ if (current.completed !== view.completed)
791
+ refuse("publication_cursor_changed");
792
+ return current;
793
+ }
794
+ }