@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,735 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { createHash } from "node:crypto";
4
+ import { AppError } from "../shared/errors.js";
5
+ import { appendAudit } from "./audit.js";
6
+ import { hybridDigest } from "./hybrid-contracts.js";
7
+ import { PreparedMemoryRepairWorkBaseline } from "./memory-repair-work-baseline.js";
8
+ import { PreparedSecondMemoryRepairBaseline } from "./memory-second-repair-baseline.js";
9
+ import { PreparedMemoryRepairWorkspace } from "./memory-repair-workspace.js";
10
+ import { runtimeHarness } from "./execution-policy.js";
11
+ import { hookSessionIdentity } from "./hooks.js";
12
+ import { RetainedMemoryRepairPublicationPlan } from "./memory-repair-publication-plan.js";
13
+ import { PreparedMemoryRepairPublicationSource } from "./memory-repair-publication-source.js";
14
+ import { retainedMemoryRepairPublicationPlans } from "./memory-repair-publication-intent.js";
15
+ import { recordMemoryRepairNativeBinding, memoryRepairExecutionIdentity } from "./memory-repair-physical-binding.js";
16
+ const event = "memory.repair_writer_copy_intent";
17
+ const preparationToken = Symbol("memory-repair-writer-copy-preparation");
18
+ function sqlHookIdentity(context, projectId, eventKey, execution) {
19
+ if (execution)
20
+ return memoryRepairExecutionIdentity(context, { projectId, eventKey, ...execution }).identity;
21
+ const reader = { ...context };
22
+ // Lifecycle observation may read daemon files; custody rechecks are SQL-only.
23
+ delete reader.observeLifecycleHook;
24
+ return hookSessionIdentity(reader, projectId, eventKey);
25
+ }
26
+ function refuse(reason) {
27
+ throw new AppError("memory_repair_writer_copy_invalid", "Writer workspace lacks independently registered copy custody", 1, { reason, effect: "no_effect" });
28
+ }
29
+ function selected(context, expected, eventType = event) {
30
+ 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", [expected.project_id, eventType, expected.run_id, expected.work_id, expected.generation]);
31
+ if (!rows.length)
32
+ return null;
33
+ try {
34
+ const bound = eventType === event ? 1024 * 1024 : 12 * 1024 * 1024;
35
+ if (rows.length !== 1 || Buffer.byteLength(rows[0].payload_json) > bound)
36
+ refuse("ambiguous_or_unbounded_copy_intent");
37
+ const value = JSON.parse(rows[0].payload_json);
38
+ if (hybridDigest(value) !== hybridDigest(expected))
39
+ refuse("foreign_or_changed_copy_intent");
40
+ return value;
41
+ }
42
+ catch (error) {
43
+ if (error instanceof AppError && error.code === "memory_repair_writer_copy_invalid")
44
+ throw error;
45
+ return refuse("invalid_copy_intent");
46
+ }
47
+ }
48
+ function safeDestination(intent) {
49
+ try {
50
+ if (!path.isAbsolute(intent.run_root) || fs.realpathSync(intent.run_root) !== path.resolve(intent.run_root)
51
+ || !fs.lstatSync(intent.run_root).isDirectory())
52
+ refuse("noncanonical_run_root");
53
+ let current = intent.run_root;
54
+ for (const part of intent.copy_relative.split("/")) {
55
+ current = path.join(current, part);
56
+ const stat = fs.lstatSync(current, { throwIfNoEntry: false });
57
+ if (stat && !stat.isDirectory())
58
+ refuse("linked_or_non_directory_copy_path");
59
+ if (!stat)
60
+ break;
61
+ }
62
+ }
63
+ catch (error) {
64
+ if (error instanceof AppError && error.code === "memory_repair_writer_copy_invalid")
65
+ throw error;
66
+ return refuse("copy_destination_unavailable");
67
+ }
68
+ }
69
+ function intentFor(context, baseline) {
70
+ const receipt = baseline.receipt();
71
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,255}$/.test(receipt.work_id)
72
+ || !Number.isSafeInteger(receipt.generation) || receipt.generation < 0)
73
+ refuse("unsafe_copy_scope");
74
+ const run = context.db.get("SELECT run_root,run_dir FROM runs WHERE project_id=? AND id=?", [receipt.project_id, receipt.run_id]);
75
+ if (!run)
76
+ refuse("owned_run_required");
77
+ const intent = { schema_id: "dd-flow/memory-repair-writer-copy-intent@1",
78
+ project_id: receipt.project_id, run_id: receipt.run_id, work_id: receipt.work_id, generation: receipt.generation,
79
+ baseline_registration_sha256: baseline.sha256, protected_baseline_sha256: receipt.baseline_sha256,
80
+ exported_baseline_sha256: receipt.exported_baseline_sha256, run_root: run.run_root ?? run.run_dir,
81
+ source_root: receipt.workspace_root, copy_relative: `works/${receipt.work_id}/writer-workspaces/generation-${receipt.generation}`,
82
+ input: baseline.copyInput() };
83
+ if (Buffer.byteLength(JSON.stringify(intent)) > 1024 * 1024)
84
+ refuse("unbounded_copy_intent");
85
+ return intent;
86
+ }
87
+ /** Reserve a CLI-owned destination before filesystem/provider effects. An
88
+ * intent never proves a complete copy, actual CWD, physical Session or repair.
89
+ * Existing files without this receipt cannot be adopted after receipt loss.
90
+ */
91
+ export class PreparedMemoryRepairWriterCopy {
92
+ sha256;
93
+ #baseline;
94
+ #intent;
95
+ constructor(token, baseline, intent) {
96
+ if (token !== preparationToken)
97
+ refuse("prepared_copy_token_required");
98
+ this.#baseline = baseline;
99
+ this.#intent = structuredClone(intent);
100
+ this.sha256 = hybridDigest(intent);
101
+ Object.freeze(this);
102
+ }
103
+ static prepare(context, baseline) {
104
+ if (!PreparedMemoryRepairWorkBaseline.isPrepared(baseline) && !PreparedSecondMemoryRepairBaseline.isPrepared(baseline))
105
+ refuse("registered_work_baseline_required");
106
+ baseline.assertPrelaunchCustody(context);
107
+ baseline.revalidate(context);
108
+ baseline.assertRegisteredCustody(context);
109
+ const intent = intentFor(context, baseline);
110
+ const prepared = new PreparedMemoryRepairWriterCopy(preparationToken, baseline, intent);
111
+ prepared.revalidate(context);
112
+ return prepared;
113
+ }
114
+ intent() { return structuredClone(this.#intent); }
115
+ root() { return path.join(this.#intent.run_root, this.#intent.copy_relative); }
116
+ assertCustody(context) {
117
+ this.#baseline.assertRegisteredCustody(context);
118
+ const run = context.db.get("SELECT run_root,run_dir FROM runs WHERE project_id=? AND id=?", [this.#intent.project_id, this.#intent.run_id]);
119
+ if (!run || (run.run_root ?? run.run_dir) !== this.#intent.run_root)
120
+ refuse("run_root_changed");
121
+ selected(context, this.#intent);
122
+ }
123
+ revalidate(context) {
124
+ this.#baseline.revalidate(context);
125
+ this.assertCustody(context);
126
+ safeDestination(this.#intent);
127
+ if (!selected(context, this.#intent) && fs.existsSync(this.root()))
128
+ refuse("unowned_copy_root");
129
+ }
130
+ reserve(context) {
131
+ this.revalidate(context);
132
+ return context.db.writeTransaction(() => {
133
+ this.assertCustody(context);
134
+ const prior = selected(context, this.#intent);
135
+ if (!prior)
136
+ appendAudit(context, { projectId: this.#intent.project_id, eventType: event, payload: this.#intent });
137
+ if (hybridDigest(selected(context, this.#intent)) !== this.sha256)
138
+ refuse("copy_intent_readback_failed");
139
+ return { work_id: this.#intent.work_id, copy_root: this.root(), copy_intent_sha256: this.sha256, status: "reserved", replayed: Boolean(prior) };
140
+ });
141
+ }
142
+ /** Prepare actual frozen second-wave launch inputs from a completed owned
143
+ * copy, without dispatching or manufacturing a provider Session/start. */
144
+ secondWaveLaunchPacket(context) {
145
+ if (!PreparedSecondMemoryRepairBaseline.isPrepared(this.#baseline))
146
+ refuse("second_prelaunch_baseline_required");
147
+ return this.launchPacket(context);
148
+ }
149
+ /** Both waves use the same independently registered ready copy boundary.
150
+ * A packet is preparation data, never authority to claim or dispatch. */
151
+ launchPacket(context) {
152
+ const baseline = this.#baseline;
153
+ if (context.db.isOwnedWriteTransaction?.())
154
+ refuse("preparation_outside_writer_required");
155
+ this.revalidate(context);
156
+ if (!selected(context, this.#intent))
157
+ refuse("registered_copy_intent_required");
158
+ const rows = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type='memory.repair_writer_copy_ready' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.work_id')=? AND json_extract(payload_json,'$.generation')=? LIMIT 2", [this.#intent.project_id, this.#intent.run_id, this.#intent.work_id, this.#intent.generation]);
159
+ if (rows.length !== 1 || Buffer.byteLength(rows[0].payload_json) > 12 * 1024 * 1024)
160
+ refuse("ready_copy_receipt_required");
161
+ let ready;
162
+ try {
163
+ ready = JSON.parse(rows[0].payload_json);
164
+ }
165
+ catch {
166
+ return refuse("invalid_ready_receipt");
167
+ }
168
+ if (!ready || typeof ready.workspace_manifest_base64 !== "string")
169
+ refuse("ready_manifest_required");
170
+ const bytes = Buffer.from(ready.workspace_manifest_base64, "base64");
171
+ if (bytes.toString("base64") !== ready.workspace_manifest_base64)
172
+ refuse("changed_ready_manifest");
173
+ const workspace = PreparedMemoryRepairWorkspace.restore(bytes, ready.workspace_manifest_sha256, baseline.exportedBaseline());
174
+ const build = { ...this.#intent, workspace_manifest_sha256: workspace.sha256, workspace_manifest_base64: bytes.toString("base64") };
175
+ if (!selected(context, build, "memory.repair_writer_copy_build") || !selected(context, build, "memory.repair_writer_copy_ready"))
176
+ refuse("registered_ready_build_required");
177
+ workspace.verify(this.root());
178
+ const route = baseline.workerRoute();
179
+ const frozen = { coordinator: route.coordinator, worker: route.worker,
180
+ delegation: { mode: route.delegation.mode, max_parallel: route.delegation.mode === "external" ? route.delegation.max_parallel : null } };
181
+ const metadata = { root: build.copy_relative, copy_intent_sha256: this.sha256, workspace_manifest_sha256: workspace.sha256,
182
+ baseline_registration_sha256: baseline.sha256 };
183
+ this.revalidate(context);
184
+ return { work_id: this.#intent.work_id, copy_root: this.root(), profile: { ...frozen,
185
+ profile_hash: createHash("sha256").update(JSON.stringify(frozen)).digest("hex"), memory_repair_copy: metadata } };
186
+ }
187
+ /** Exact completed copies can reconcile a missing completion projection.
188
+ * Exact partial builds may fill missing files, never overwrite known bytes.
189
+ * Provider launch/start still requires separate physical Session custody.
190
+ */
191
+ materialize(context) {
192
+ this.revalidate(context);
193
+ if (!selected(context, this.#intent))
194
+ refuse("registered_copy_intent_required");
195
+ const workspace = PreparedMemoryRepairWorkspace.capture(this.#intent.source_root, this.#intent.input, this.#baseline.exportedBaseline());
196
+ const build = { ...this.#intent, workspace_manifest_sha256: workspace.sha256, workspace_manifest_base64: workspace.bytes().toString("base64") };
197
+ const buildEvent = "memory.repair_writer_copy_build", readyEvent = "memory.repair_writer_copy_ready";
198
+ const prior = selected(context, build, buildEvent);
199
+ if (!prior && fs.lstatSync(this.root(), { throwIfNoEntry: false }))
200
+ refuse("copy_without_build_receipt");
201
+ context.db.writeTransaction(() => {
202
+ this.assertCustody(context);
203
+ if (!selected(context, this.#intent))
204
+ refuse("registered_copy_intent_required");
205
+ if (!selected(context, build, buildEvent))
206
+ appendAudit(context, { projectId: build.project_id, eventType: buildEvent, payload: build });
207
+ if (!selected(context, build, buildEvent))
208
+ refuse("build_receipt_readback_failed");
209
+ });
210
+ try {
211
+ this.revalidate(context);
212
+ workspace.revalidateSource();
213
+ const ready = selected(context, build, readyEvent);
214
+ if (ready)
215
+ workspace.verify(this.root());
216
+ else if (fs.lstatSync(this.root(), { throwIfNoEntry: false }))
217
+ workspace.resume(this.root());
218
+ else {
219
+ fs.mkdirSync(path.dirname(this.root()), { recursive: true, mode: 0o700 });
220
+ safeDestination(this.#intent);
221
+ workspace.create(this.root());
222
+ }
223
+ this.revalidate(context);
224
+ workspace.verify(this.root());
225
+ return context.db.writeTransaction(() => {
226
+ this.assertCustody(context);
227
+ if (!selected(context, this.#intent) || !selected(context, build, buildEvent))
228
+ refuse("copy_build_custody_lost");
229
+ const replay = selected(context, build, readyEvent);
230
+ if (!replay)
231
+ appendAudit(context, { projectId: build.project_id, eventType: readyEvent, payload: build });
232
+ if (!selected(context, build, readyEvent))
233
+ refuse("ready_receipt_readback_failed");
234
+ return { work_id: build.work_id, copy_root: this.root(), copy_intent_sha256: this.sha256,
235
+ workspace_manifest_sha256: workspace.sha256, status: "materialized", replayed: Boolean(replay),
236
+ memory_repair_copy: { root: build.copy_relative, copy_intent_sha256: this.sha256,
237
+ workspace_manifest_sha256: workspace.sha256, baseline_registration_sha256: this.#baseline.sha256 } };
238
+ });
239
+ }
240
+ catch (error) {
241
+ throw new AppError("memory_repair_writer_copy_publication_pending", "Copy build is registered but not admitted; reconcile its owned workspace before dispatch", 1, { effect: "publication_pending", work_id: build.work_id, copy_root: this.root(), cause: error instanceof Error ? error.message : String(error) });
242
+ }
243
+ }
244
+ }
245
+ function intentForValue(build) {
246
+ const value = structuredClone(build);
247
+ delete value.workspace_manifest_sha256;
248
+ delete value.workspace_manifest_base64;
249
+ return value;
250
+ }
251
+ /** Prepared physical-start boundary, not permission to dispatch or settle a
252
+ * repair. The actual Hook claimant must match the independently retained
253
+ * physical provider Session; ready files alone cannot manufacture that proof.
254
+ */
255
+ export class PreparedBoundMemoryRepairCopy {
256
+ static isPrepared(value) {
257
+ return typeof value === "object" && value !== null && #build in value;
258
+ }
259
+ #baseline;
260
+ #build;
261
+ #workspace;
262
+ #providerSessionId;
263
+ #claimWriterToken = null;
264
+ #startReceipt = null;
265
+ #restoredStart = false;
266
+ #claimIdentity = null;
267
+ constructor(token, baseline, build, workspace, providerSessionId) {
268
+ if (token !== preparationToken)
269
+ refuse("prepared_copy_token_required");
270
+ this.#baseline = baseline;
271
+ this.#build = structuredClone(build);
272
+ this.#workspace = workspace;
273
+ this.#providerSessionId = providerSessionId;
274
+ Object.freeze(this);
275
+ }
276
+ static prepare(context, baseline, requestedRoot) {
277
+ return this.#prepare(context, baseline, requestedRoot, null);
278
+ }
279
+ static restoreForStartedWork(context, baseline, requestedRoot, workSessionId) {
280
+ if (typeof workSessionId !== "string" || !workSessionId || workSessionId.length > 256)
281
+ refuse("registered_start_session_required");
282
+ return this.#prepare(context, baseline, requestedRoot, workSessionId);
283
+ }
284
+ static restoreForPublication(context, baseline, requestedRoot, workSessionId) {
285
+ if (!PreparedMemoryRepairWorkBaseline.isPrepared(baseline))
286
+ refuse("registered_work_baseline_required");
287
+ baseline.registeredPublicationIntent(context, workSessionId);
288
+ return this.#prepare(context, baseline, requestedRoot, workSessionId, true);
289
+ }
290
+ /** Retained start/private-input restoration, not shared publication admission. */
291
+ static restoreForOutput(context, baseline, requestedRoot, workSessionId) {
292
+ if (typeof workSessionId !== "string" || !workSessionId || workSessionId.length > 256)
293
+ refuse("registered_start_session_required");
294
+ return this.#prepare(context, baseline, requestedRoot, workSessionId, false, true);
295
+ }
296
+ static #prepare(context, baseline, requestedRoot, workSessionId, publicationOnly = false, outputOnly = false) {
297
+ if (!PreparedMemoryRepairWorkBaseline.isPrepared(baseline))
298
+ refuse("registered_work_baseline_required");
299
+ baseline.assertRegisteredCustody(context);
300
+ const launch = baseline.launch();
301
+ if (!launch?.provider_session_id)
302
+ refuse("registered_physical_launch_required");
303
+ const intent = intentFor(context, baseline);
304
+ if (!selected(context, intent))
305
+ refuse("registered_copy_intent_required");
306
+ const rows = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type='memory.repair_writer_copy_ready' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.work_id')=? AND json_extract(payload_json,'$.generation')=? LIMIT 2", [intent.project_id, intent.run_id, intent.work_id, intent.generation]);
307
+ try {
308
+ if (rows.length !== 1 || Buffer.byteLength(rows[0].payload_json) > 12 * 1024 * 1024)
309
+ refuse("ready_copy_receipt_required");
310
+ const ready = JSON.parse(rows[0].payload_json);
311
+ if (typeof ready.workspace_manifest_base64 !== "string")
312
+ refuse("ready_manifest_required");
313
+ const bytes = Buffer.from(ready.workspace_manifest_base64, "base64");
314
+ if (bytes.toString("base64") !== ready.workspace_manifest_base64)
315
+ refuse("changed_ready_manifest");
316
+ const build = { ...intent, workspace_manifest_sha256: ready.workspace_manifest_sha256, workspace_manifest_base64: ready.workspace_manifest_base64 };
317
+ if (!selected(context, build, "memory.repair_writer_copy_build") || !selected(context, build, "memory.repair_writer_copy_ready"))
318
+ refuse("registered_ready_build_required");
319
+ const workspace = PreparedMemoryRepairWorkspace.restore(bytes, build.workspace_manifest_sha256, baseline.exportedBaseline());
320
+ const metadata = JSON.parse(launch.profile_json).memory_repair_copy;
321
+ const expected = { root: intent.copy_relative, copy_intent_sha256: hybridDigest(intent), workspace_manifest_sha256: workspace.sha256,
322
+ baseline_registration_sha256: baseline.sha256 };
323
+ if (hybridDigest(metadata) !== hybridDigest(expected) || typeof requestedRoot !== "string"
324
+ || !path.isAbsolute(requestedRoot) || requestedRoot !== path.resolve(requestedRoot)
325
+ || requestedRoot !== path.join(intent.run_root, intent.copy_relative))
326
+ refuse("launch_copy_or_cwd_mismatch");
327
+ const prepared = new PreparedBoundMemoryRepairCopy(preparationToken, baseline, build, workspace, launch.provider_session_id);
328
+ if (workSessionId)
329
+ prepared.#restoreStart(context, workSessionId, publicationOnly, outputOnly);
330
+ else
331
+ prepared.revalidate(context);
332
+ return prepared;
333
+ }
334
+ catch (error) {
335
+ if (error instanceof AppError && error.code === "memory_repair_writer_copy_invalid")
336
+ throw error;
337
+ return refuse("invalid_bound_copy_receipt");
338
+ }
339
+ }
340
+ root() { return path.join(this.#build.run_root, this.#build.copy_relative); }
341
+ /** Observe a result against the retained start, never agent-supplied baseline
342
+ * authority. This preparation does not publish or settle the Work.
343
+ */
344
+ prepareOutput(context, bytes, workSessionId) {
345
+ if (!(bytes instanceof Uint8Array) || !bytes.byteLength || bytes.byteLength > 1024 * 1024)
346
+ refuse("bounded_result_bytes_required");
347
+ if (!this.#restoredStart || !this.#startReceipt || this.#startReceipt.work_session_id !== workSessionId)
348
+ refuse("restored_registered_start_required");
349
+ this.revalidateCommittedStart(context, workSessionId);
350
+ const retained = Buffer.from(bytes);
351
+ const result = this.#baseline.validateResult(context, retained);
352
+ const exported = this.#baseline.exportedBaseline();
353
+ const delta = exported.observeResult(this.root(), result);
354
+ const transition = this.#baseline.baseline().publicationTransition(exported, delta);
355
+ this.revalidateCommittedStart(context, workSessionId);
356
+ delta.revalidate();
357
+ return new PreparedMemoryRepairPrivateOutput(preparationToken, this, workSessionId, retained, result, delta, transition);
358
+ }
359
+ /** The frozen private input remains unchanged. Only an independently
360
+ * registered running-Work source owner can admit a different shared view. */
361
+ prepareOutputAgainstSource(context, bytes, workSessionId, source) {
362
+ if (!PreparedMemoryRepairPublicationSource.isPrepared(source) || !source.matches(this.#baseline, this, workSessionId))
363
+ refuse("registered_publication_source_required");
364
+ if (!(bytes instanceof Uint8Array) || !bytes.byteLength || bytes.byteLength > 1024 * 1024)
365
+ refuse("bounded_result_bytes_required");
366
+ if (!this.#restoredStart || !this.#startReceipt || this.#startReceipt.work_session_id !== workSessionId)
367
+ refuse("restored_registered_start_required");
368
+ source.assertRegisteredCustody(context);
369
+ source.revalidate(context);
370
+ const retained = Buffer.from(bytes), result = this.#baseline.validateResult(context, retained);
371
+ const exported = this.#baseline.exportedBaseline(), start = this.#baseline.baseline();
372
+ const delta = exported.observeResult(this.root(), result);
373
+ const privateTransition = start.publicationTransition(exported, delta);
374
+ const transition = source.before().publicationTransition(exported, delta, start);
375
+ source.revalidate(context);
376
+ delta.revalidate();
377
+ return new PreparedMemoryRepairPrivateOutput(preparationToken, this, workSessionId, retained, result, delta, transition, source, privateTransition);
378
+ }
379
+ input() {
380
+ const receipt = this.#baseline.receipt(), launch = this.#baseline.launch();
381
+ const current = this.#baseline.currentInput();
382
+ return { schema_id: current ? "dd-flow/memory-repair-work-input@2" : "dd-flow/memory-repair-work-input@1", work_id: this.#build.work_id, generation: this.#build.generation,
383
+ packet_sha256: receipt.packet_sha256, baseline_registration_sha256: this.#baseline.sha256,
384
+ protected_baseline_sha256: this.#build.protected_baseline_sha256, exported_baseline_sha256: this.#build.exported_baseline_sha256,
385
+ copy_intent_sha256: hybridDigest(intentForValue(this.#build)), copy_build_sha256: hybridDigest(this.#build),
386
+ workspace_manifest_sha256: this.#build.workspace_manifest_sha256, workspace_root: this.root(),
387
+ launch_operation_id: launch.operation_id, provider_session_id: this.#providerSessionId, ...(current ?? {}) };
388
+ }
389
+ startedReceipt() {
390
+ if (!this.#startReceipt)
391
+ refuse("registered_start_receipt_required");
392
+ return structuredClone(this.#startReceipt);
393
+ }
394
+ sourceRoot() { return this.#build.source_root; }
395
+ assertClaimIdentity(context, identity) {
396
+ if (this.#restoredStart)
397
+ refuse("restored_start_cannot_claim");
398
+ const token = context.db.ownedWriteTransactionToken?.();
399
+ if (!token || context.db.isOwnedWriteTransaction?.() !== true)
400
+ refuse("owned_work_claim_writer_required");
401
+ this.assertProviderSession(context, identity.providerSessionId);
402
+ this.#assertPhysicalIdentity(context, identity);
403
+ this.#claimWriterToken = token;
404
+ this.#claimIdentity = { hookEventId: identity.hookEventId, providerSessionId: identity.providerSessionId,
405
+ sessionId: identity.sessionId, harness: identity.harness, cwd: identity.cwd };
406
+ }
407
+ #assertPhysicalIdentity(context, identity) {
408
+ const profile = JSON.parse(this.#baseline.launch().profile_json);
409
+ if (identity.providerSessionId !== this.#providerSessionId || identity.harness !== runtimeHarness(profile.worker)
410
+ || identity.cwd !== this.root())
411
+ refuse("repair_session_or_cwd_mismatch");
412
+ if (context.db.get("SELECT 1 FROM work_sessions ws JOIN works w ON w.work_id=ws.work_id JOIN sessions s ON s.project_id=w.project_id AND s.session_id=ws.session_id WHERE w.project_id=? AND w.run_id=? AND w.work_id<>? AND s.harness=? AND s.provider_session_id=? LIMIT 1", [this.#build.project_id, this.#build.run_id, this.#build.work_id, identity.harness, identity.providerSessionId]))
413
+ refuse("repair_session_not_fresh");
414
+ }
415
+ /** Called inside the generic Work claim writer, after its WorkSession row
416
+ * exists. The independent start receipt commits atomically with that claim.
417
+ */
418
+ recordStart(context, workSessionId) {
419
+ if (context.db.isOwnedWriteTransaction?.() !== true)
420
+ refuse("owned_work_claim_writer_required");
421
+ if (!this.#claimIdentity || !this.#claimWriterToken || context.db.ownedWriteTransactionToken?.() !== this.#claimWriterToken)
422
+ refuse("same_work_claim_writer_required");
423
+ recordMemoryRepairNativeBinding(context, this.#build.work_id, this.#baseline.workerRoute(), this.#baseline.launch(), workSessionId);
424
+ const physical = this.#baseline.assertRegisteredStartCustody(context, workSessionId);
425
+ const identity = sqlHookIdentity(context, this.#build.project_id, physical.hook_event_id, { runId: this.#build.run_id, workId: this.#build.work_id, root: this.root() });
426
+ if (hybridDigest({ hookEventId: identity.hookEventId, providerSessionId: identity.providerSessionId, sessionId: identity.sessionId,
427
+ harness: identity.harness, cwd: identity.cwd }) !== hybridDigest(this.#claimIdentity))
428
+ refuse("repair_claim_identity_changed");
429
+ this.#assertPhysicalIdentity(context, identity);
430
+ if (identity.sessionId !== physical.session_id || physical.cwd !== this.root()
431
+ || !context.db.get("SELECT 1 FROM hook_events WHERE project_id=? AND event_key=? AND status='claimed' AND event_name='PreToolUse' AND session_id=? AND provider_session_id=? AND harness=?", [this.#build.project_id, physical.hook_event_id, physical.session_id, this.#providerSessionId, identity.harness]))
432
+ refuse("registered_start_hook_required");
433
+ const intent = intentFor(context, this.#baseline);
434
+ if (hybridDigest(intent) !== hybridDigest(intentForValue(this.#build)) || !selected(context, intent)
435
+ || !selected(context, this.#build, "memory.repair_writer_copy_build") || !selected(context, this.#build, "memory.repair_writer_copy_ready"))
436
+ refuse("bound_copy_custody_changed");
437
+ try {
438
+ if (typeof physical.start_receipt_json !== "string" || Buffer.byteLength(physical.start_receipt_json) > 16 * 1024 * 1024)
439
+ refuse("invalid_repair_start_receipt");
440
+ const start = JSON.parse(physical.start_receipt_json), hash = (value) => createHash("sha256").update(value).digest("hex");
441
+ if (start.version !== 2 || start.generation !== this.#build.generation || typeof start.context !== "string"
442
+ || start.context_sha256 !== hash(start.context) || typeof start.prompt !== "string" || start.prompt_sha256 !== hash(start.prompt)
443
+ || start.response?.work_session_id !== workSessionId || start.response?.worker_prompt_markdown !== start.prompt
444
+ || start.response_sha256 !== hash(JSON.stringify(start.response)))
445
+ refuse("repair_start_receipt_changed");
446
+ const input = JSON.parse(start.context);
447
+ if (input.work_id !== this.#build.work_id || input.run_id !== this.#build.run_id || input.workspace_root !== this.root()
448
+ || input.read_only !== false || input.result_schema !== "dd-flow/memory-repair-work-result@1"
449
+ || hybridDigest(input.memory_repair_input) !== hybridDigest(this.input()))
450
+ refuse("repair_start_input_changed");
451
+ const value = { schema_id: "dd-flow/memory-repair-start@1", project_id: this.#build.project_id, run_id: this.#build.run_id,
452
+ work_id: this.#build.work_id, generation: this.#build.generation, work_session_id: workSessionId,
453
+ session_id: physical.session_id, hook_event_id: physical.hook_event_id, harness: identity.harness,
454
+ provider_session_id: this.#providerSessionId, context_sha256: start.context_sha256, prompt_sha256: start.prompt_sha256,
455
+ input_sha256: hybridDigest(this.input()) };
456
+ const rows = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type='memory.repair_work_start' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.work_id')=? AND json_extract(payload_json,'$.generation')=? LIMIT 2", [value.project_id, value.run_id, value.work_id, value.generation]);
457
+ if (rows.length > 1 || rows.length && (Buffer.byteLength(rows[0].payload_json) > 1024 * 1024 || hybridDigest(JSON.parse(rows[0].payload_json)) !== hybridDigest(value)))
458
+ refuse("repair_start_registration_changed");
459
+ if (!rows.length)
460
+ appendAudit(context, { projectId: value.project_id, eventType: "memory.repair_work_start", payload: value });
461
+ const retained = context.db.get("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type='memory.repair_work_start' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.work_id')=? AND json_extract(payload_json,'$.generation')=?", [value.project_id, value.run_id, value.work_id, value.generation]);
462
+ if (!retained || hybridDigest(JSON.parse(retained.payload_json)) !== hybridDigest(value))
463
+ refuse("repair_start_readback_failed");
464
+ this.#startReceipt = structuredClone(value);
465
+ return value;
466
+ }
467
+ catch (error) {
468
+ if (error instanceof AppError && error.code === "memory_repair_writer_copy_invalid")
469
+ throw error;
470
+ return refuse("invalid_repair_start_receipt");
471
+ }
472
+ }
473
+ assertProviderSession(context, providerSessionId) {
474
+ this.assertCustody(context);
475
+ if (providerSessionId !== this.#providerSessionId)
476
+ refuse("foreign_physical_session");
477
+ }
478
+ /** Before projecting the committed prompt, prove that SQL still owns the
479
+ * same start and neither original inputs nor the private copy have drifted.
480
+ * Filesystem observation deliberately happens after the claim writer.
481
+ */
482
+ revalidateCommittedStart(context, workSessionId) {
483
+ if (context.db.isOwnedWriteTransaction?.())
484
+ refuse("start_verification_outside_writer_required");
485
+ this.assertCommittedStartCustody(context, workSessionId);
486
+ this.#baseline.revalidateSource(context);
487
+ safeDestination(this.#build);
488
+ if (this.#restoredStart)
489
+ this.#workspace.verifyUnchangedInputs(this.root());
490
+ else
491
+ this.#workspace.verify(this.root());
492
+ }
493
+ /** SQL-only fence usable inside a publication reservation writer. */
494
+ assertCommittedStartCustody(context, workSessionId) {
495
+ if (!this.#startReceipt)
496
+ refuse("registered_start_receipt_required");
497
+ const physical = this.#baseline.assertRegisteredStartCustody(context, workSessionId);
498
+ if (physical.session_id !== this.#startReceipt.session_id || physical.hook_event_id !== this.#startReceipt.hook_event_id
499
+ || physical.harness !== this.#startReceipt.harness || physical.provider_session_id !== this.#providerSessionId
500
+ || physical.cwd !== this.root())
501
+ refuse("repair_start_identity_changed");
502
+ const identity = sqlHookIdentity(context, this.#build.project_id, physical.hook_event_id, { runId: this.#build.run_id, workId: this.#build.work_id, root: this.root() });
503
+ this.#assertPhysicalIdentity(context, identity);
504
+ if (identity.sessionId !== physical.session_id
505
+ || !context.db.get("SELECT 1 FROM hook_events WHERE project_id=? AND event_key=? AND status='claimed' AND event_name='PreToolUse' AND session_id=? AND provider_session_id=? AND harness=?", [this.#build.project_id, physical.hook_event_id, physical.session_id, this.#providerSessionId, identity.harness]))
506
+ refuse("registered_start_hook_required");
507
+ const intent = intentFor(context, this.#baseline);
508
+ if (hybridDigest(intent) !== hybridDigest(intentForValue(this.#build)) || !selected(context, intent)
509
+ || !selected(context, this.#build, "memory.repair_writer_copy_build")
510
+ || !selected(context, this.#build, "memory.repair_writer_copy_ready"))
511
+ refuse("bound_copy_custody_changed");
512
+ const rows = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type='memory.repair_work_start' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.work_id')=? AND json_extract(payload_json,'$.generation')=? LIMIT 2", [this.#build.project_id, this.#build.run_id, this.#build.work_id, this.#build.generation]);
513
+ try {
514
+ if (Buffer.byteLength(physical.start_receipt_json) > 16 * 1024 * 1024)
515
+ refuse("invalid_repair_start_receipt");
516
+ const start = JSON.parse(physical.start_receipt_json), hash = (value) => createHash("sha256").update(value).digest("hex");
517
+ if (start.version !== 2 || start.generation !== this.#build.generation
518
+ || typeof start.context !== "string" || start.context_sha256 !== this.#startReceipt.context_sha256
519
+ || hash(start.context) !== start.context_sha256 || typeof start.prompt !== "string"
520
+ || start.prompt_sha256 !== this.#startReceipt.prompt_sha256 || hash(start.prompt) !== start.prompt_sha256
521
+ || start.response?.work_session_id !== workSessionId || start.response?.worker_prompt_markdown !== start.prompt
522
+ || hash(JSON.stringify(start.response)) !== start.response_sha256)
523
+ refuse("repair_start_receipt_changed");
524
+ if (rows.length !== 1 || Buffer.byteLength(rows[0].payload_json) > 1024 * 1024
525
+ || hybridDigest(JSON.parse(rows[0].payload_json)) !== hybridDigest(this.#startReceipt))
526
+ refuse("repair_start_registration_changed");
527
+ }
528
+ catch (error) {
529
+ if (error instanceof AppError && error.code === "memory_repair_writer_copy_invalid")
530
+ throw error;
531
+ refuse("invalid_repair_start_receipt");
532
+ }
533
+ }
534
+ #restoreStart(context, workSessionId, publicationOnly = false, outputOnly = false) {
535
+ const physical = this.#baseline.assertRegisteredStartCustody(context, workSessionId);
536
+ const identity = sqlHookIdentity(context, this.#build.project_id, physical.hook_event_id, { runId: this.#build.run_id, workId: this.#build.work_id, root: this.root() });
537
+ this.#assertPhysicalIdentity(context, identity);
538
+ if (identity.sessionId !== physical.session_id
539
+ || !context.db.get("SELECT 1 FROM hook_events WHERE project_id=? AND event_key=? AND status='claimed' AND event_name='PreToolUse' AND session_id=? AND provider_session_id=? AND harness=?", [this.#build.project_id, physical.hook_event_id, physical.session_id, this.#providerSessionId, identity.harness]))
540
+ refuse("registered_start_hook_required");
541
+ try {
542
+ if (Buffer.byteLength(physical.start_receipt_json) > 16 * 1024 * 1024)
543
+ refuse("invalid_repair_start_receipt");
544
+ const start = JSON.parse(physical.start_receipt_json), input = JSON.parse(start.context);
545
+ if (input.work_id !== this.#build.work_id || input.run_id !== this.#build.run_id || input.workspace_root !== this.root()
546
+ || input.read_only !== false || input.result_schema !== "dd-flow/memory-repair-work-result@1"
547
+ || hybridDigest(input.memory_repair_input) !== hybridDigest(this.input()))
548
+ refuse("repair_start_input_changed");
549
+ this.#startReceipt = { schema_id: "dd-flow/memory-repair-start@1", project_id: this.#build.project_id, run_id: this.#build.run_id,
550
+ work_id: this.#build.work_id, generation: this.#build.generation, work_session_id: workSessionId,
551
+ session_id: physical.session_id, hook_event_id: physical.hook_event_id, harness: identity.harness,
552
+ provider_session_id: this.#providerSessionId, context_sha256: start.context_sha256, prompt_sha256: start.prompt_sha256,
553
+ input_sha256: hybridDigest(this.input()) };
554
+ this.#restoredStart = true;
555
+ if (publicationOnly) {
556
+ this.assertCommittedStartCustody(context, workSessionId);
557
+ if (this.#baseline.registeredPublicationIntent(context, workSessionId).start_receipt_sha256 !== hybridDigest(this.#startReceipt))
558
+ refuse("publication_start_changed");
559
+ }
560
+ else if (outputOnly)
561
+ this.verifyPrivateInputs(context, workSessionId);
562
+ else
563
+ this.revalidateCommittedStart(context, workSessionId);
564
+ }
565
+ catch (error) {
566
+ if (error instanceof AppError && error.code === "memory_repair_writer_copy_invalid")
567
+ throw error;
568
+ refuse("invalid_repair_start_receipt");
569
+ }
570
+ }
571
+ /** Immutable retained manifest and independent start digest are metadata,
572
+ * not permission to recapture shared inputs or publish a private result. */
573
+ workspaceManifest() { return this.#workspace; }
574
+ startReceiptSha256(context, workSessionId) {
575
+ this.assertCommittedStartCustody(context, workSessionId);
576
+ return hybridDigest(this.#startReceipt);
577
+ }
578
+ verifyPrivateInputs(context, workSessionId) {
579
+ if (context.db.isOwnedWriteTransaction?.())
580
+ refuse("publication_verification_outside_writer_required");
581
+ this.assertCommittedStartCustody(context, workSessionId);
582
+ safeDestination(this.#build);
583
+ this.#workspace.verifyUnchangedInputs(this.root());
584
+ this.assertCommittedStartCustody(context, workSessionId);
585
+ }
586
+ /** Private inputs and reserved output must still match; shared source is
587
+ * deliberately not recaptured or compared to its original pre-apply view.
588
+ */
589
+ verifyPublicationPrivateView(context, workSessionId, expected) {
590
+ if (context.db.isOwnedWriteTransaction?.())
591
+ refuse("publication_verification_outside_writer_required");
592
+ this.assertCommittedStartCustody(context, workSessionId);
593
+ safeDestination(this.#build);
594
+ this.#workspace.verifyUnchangedInputs(this.root());
595
+ expected.revalidate(this.root());
596
+ }
597
+ /** Shared MB changes require the separately admitted exact prefix; every
598
+ * selected input outside MB must still equal the immutable start manifest.
599
+ * This is read-only verification, not current-view or publication admission.
600
+ */
601
+ verifyPublicationSourceInputs(context, workSessionId, expected) {
602
+ if (context.db.isOwnedWriteTransaction?.())
603
+ refuse("publication_verification_outside_writer_required");
604
+ this.assertCommittedStartCustody(context, workSessionId);
605
+ this.#workspace.verifySelectedSourceInputs(this.sourceRoot(), this.#build.input, expected);
606
+ this.assertCommittedStartCustody(context, workSessionId);
607
+ }
608
+ assertCustody(context) {
609
+ this.#baseline.assertRegisteredCustody(context);
610
+ const intent = intentFor(context, this.#baseline);
611
+ if (hybridDigest({ ...intent, workspace_manifest_sha256: this.#build.workspace_manifest_sha256, workspace_manifest_base64: this.#build.workspace_manifest_base64 }) !== hybridDigest(this.#build)
612
+ || !selected(context, intent) || !selected(context, this.#build, "memory.repair_writer_copy_build")
613
+ || !selected(context, this.#build, "memory.repair_writer_copy_ready"))
614
+ refuse("bound_copy_custody_changed");
615
+ }
616
+ revalidate(context) {
617
+ this.#baseline.revalidate(context);
618
+ this.assertCustody(context);
619
+ safeDestination(this.#build);
620
+ this.#workspace.verify(this.root());
621
+ }
622
+ }
623
+ /** Immutable private-output observation. Accepted is the worker's claim, not
624
+ * publication, independent re-review, finding closure or Work settlement.
625
+ */
626
+ export class PreparedMemoryRepairPrivateOutput {
627
+ static isPrepared(value) {
628
+ return typeof value === "object" && value !== null && #copy in value;
629
+ }
630
+ sha256;
631
+ #copy;
632
+ #workSessionId;
633
+ #bytes;
634
+ #result;
635
+ #delta;
636
+ #transition;
637
+ #source;
638
+ #privatePlan;
639
+ constructor(token, copy, workSessionId, bytes, result, delta, transition, source = null, privateTransition = null) {
640
+ if (token !== preparationToken)
641
+ refuse("private_output_preparation_required");
642
+ this.#copy = copy;
643
+ this.#workSessionId = workSessionId;
644
+ this.#bytes = Buffer.from(bytes);
645
+ this.#result = structuredClone(result);
646
+ this.#delta = delta;
647
+ this.#transition = transition;
648
+ this.#source = source;
649
+ this.#privatePlan = null;
650
+ if (source) {
651
+ if (!privateTransition)
652
+ refuse("private_transition_required");
653
+ const retained = { before_sha256: privateTransition.state(0).sha256, before_base64: privateTransition.state(0).bytes().toString("base64"),
654
+ after_sha256: privateTransition.after().sha256, after_base64: privateTransition.after().bytes().toString("base64"),
655
+ actions: privateTransition.actions().map(action => ({ ...action, bytes_base64: action.after?.kind === "file" ? privateTransition.bytes(action.path).toString("base64") : null })) };
656
+ this.#privatePlan = RetainedMemoryRepairPublicationPlan.restore(Buffer.from(JSON.stringify(retained)), hybridDigest(retained));
657
+ }
658
+ this.sha256 = hybridDigest({ input: copy.input(), start_receipt_sha256: hybridDigest(copy.startedReceipt()), work_session_id: workSessionId,
659
+ result_sha256: createHash("sha256").update(bytes).digest("hex"), delta_sha256: delta.sha256, transition_sha256: transition.sha256,
660
+ ...(source ? { source_view_sha256: source.sha256, source_receipt_sha256: hybridDigest(source.receipt()), private_plan_sha256: this.#privatePlan.sha256 } : {}) });
661
+ Object.freeze(this);
662
+ }
663
+ result() { return structuredClone(this.#result); }
664
+ bytes() { return Buffer.from(this.#bytes); }
665
+ delta() { return this.#delta; }
666
+ transition() { return this.#transition; }
667
+ /** Retain exact output bytes and expected views before any shared effect.
668
+ * Reservation alone grants no permission to apply files or settle Work.
669
+ */
670
+ reservePublication(context) {
671
+ if (this.#result.outcome !== "accepted")
672
+ refuse("accepted_private_output_required");
673
+ this.revalidate(context);
674
+ const start = this.#copy.startedReceipt(), actions = this.#transition.actions();
675
+ const retained = { schema_id: this.#source ? "dd-flow/memory-repair-publication-intent@2" : "dd-flow/memory-repair-publication-intent@1", project_id: start.project_id,
676
+ run_id: start.run_id, work_id: this.#result.work_id, generation: start.generation, work_session_id: this.#workSessionId,
677
+ source_root: this.#copy.sourceRoot(), private_root: this.#copy.root(), output_sha256: this.sha256,
678
+ start_receipt_sha256: hybridDigest(start), result_sha256: createHash("sha256").update(this.#bytes).digest("hex"),
679
+ result_base64: this.#bytes.toString("base64"), delta_sha256: this.#delta.sha256, transition_sha256: this.#transition.sha256,
680
+ before_sha256: this.#transition.state(0).sha256, before_base64: this.#transition.state(0).bytes().toString("base64"),
681
+ after_sha256: this.#transition.after().sha256, after_base64: this.#transition.after().bytes().toString("base64"),
682
+ actions: actions.map(action => ({ ...action, bytes_base64: action.after?.kind === "file" ? this.#transition.bytes(action.path).toString("base64") : null })),
683
+ ...(this.#source ? { source_view_sha256: this.#source.sha256, source_receipt_sha256: hybridDigest(this.#source.receipt()),
684
+ private_plan_sha256: this.#privatePlan.sha256, private_plan: JSON.parse(this.#privatePlan.bytes().toString("utf8")) } : {}) };
685
+ if (Buffer.byteLength(JSON.stringify(retained)) > 160 * 1024 * 1024)
686
+ refuse("unbounded_publication_intent");
687
+ retainedMemoryRepairPublicationPlans(retained);
688
+ const expected = hybridDigest(retained), eventType = "memory.repair_publication_intent";
689
+ const select = () => {
690
+ 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", [retained.project_id, eventType, retained.run_id, retained.work_id, retained.generation]);
691
+ if (!rows.length)
692
+ return null;
693
+ try {
694
+ if (rows.length !== 1 || Buffer.byteLength(rows[0].payload_json) > 160 * 1024 * 1024
695
+ || hybridDigest(JSON.parse(rows[0].payload_json)) !== expected)
696
+ refuse("publication_intent_changed");
697
+ return expected;
698
+ }
699
+ catch (error) {
700
+ if (error instanceof AppError && error.code === "memory_repair_writer_copy_invalid")
701
+ throw error;
702
+ return refuse("invalid_publication_intent");
703
+ }
704
+ };
705
+ const result = context.db.writeTransaction(() => {
706
+ if (context.db.isOwnedWriteTransaction?.() !== true)
707
+ refuse("owned_publication_writer_required");
708
+ this.#copy.assertCommittedStartCustody(context, this.#workSessionId);
709
+ this.#source?.assertRegisteredCustody(context);
710
+ const prior = select();
711
+ if (!prior)
712
+ appendAudit(context, { projectId: retained.project_id, eventType, payload: retained });
713
+ if (select() !== expected)
714
+ refuse("publication_intent_readback_failed");
715
+ return { work_id: retained.work_id, work_session_id: this.#workSessionId, publication_intent_sha256: expected,
716
+ status: "reserved", replayed: Boolean(prior) };
717
+ });
718
+ try {
719
+ this.revalidate(context);
720
+ }
721
+ catch (error) {
722
+ throw new AppError("memory_repair_publication_pending", "Publication intent committed but output/input custody changed before apply", 1, { effect: "committed", publication_intent_sha256: expected, cause: error instanceof Error ? error.message : String(error) });
723
+ }
724
+ return result;
725
+ }
726
+ revalidate(context) {
727
+ if (this.#source) {
728
+ this.#source.assertRegisteredCustody(context);
729
+ this.#source.revalidate(context);
730
+ }
731
+ else
732
+ this.#copy.revalidateCommittedStart(context, this.#workSessionId);
733
+ this.#delta.revalidate();
734
+ }
735
+ }