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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/CHANGELOG.md +130 -0
  2. package/README.md +13 -8
  3. package/dist/build-info.json +7 -7
  4. package/dist/cli/command-inputs.js +13 -3
  5. package/dist/cli/command-metadata.js +51 -16
  6. package/dist/cli/diagnostic-invocation.js +46 -0
  7. package/dist/cli/diagnostic.js +120 -0
  8. package/dist/cli/help.js +67 -1
  9. package/dist/cli/hook-ingress.js +27 -3
  10. package/dist/cli/memory-flow-admission.js +9 -3
  11. package/dist/cli/memory-init-invocation.js +10 -3
  12. package/dist/cli/memory-init.js +77 -68
  13. package/dist/cli/memory-storage-admission.js +3 -2
  14. package/dist/cli/memory-storage-qualification.js +105 -0
  15. package/dist/cli/memory-storage-transition.js +123 -0
  16. package/dist/cli/memory-upgrade.js +189 -0
  17. package/dist/cli/native-hook-ingress.js +14 -4
  18. package/dist/cli/native-hook-preparation.js +55 -0
  19. package/dist/cli/response-file.js +2 -1
  20. package/dist/cli/run-cli.js +79 -40
  21. package/dist/cli/setup.js +2 -1
  22. package/dist/cli.js +18 -0
  23. package/dist/domain/diagnostic-aspects.js +103 -0
  24. package/dist/domain/diagnostic-flows.js +8 -0
  25. package/dist/domain/stage-handler-identities.js +8 -0
  26. package/dist/harness-runtime/bin/dd-agy.mjs +3 -3
  27. package/dist/harness-runtime/bin/dd-grok.mjs +3 -3
  28. package/dist/harness-runtime/bin/dd-zcode.mjs +5 -5
  29. package/dist/harness-runtime/lib/agy-error-boundary.mjs +58 -0
  30. package/dist/harness-runtime/lib/agy-native-inspection.mjs +216 -0
  31. package/dist/harness-runtime/lib/agy-native_pb.js +117 -0
  32. package/dist/harness-runtime/lib/daemon-operations.d.mts +1 -0
  33. package/dist/harness-runtime/lib/daemon-operations.mjs +84 -10
  34. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +366 -52
  35. package/dist/harness-runtime/lib/dd-agy.mjs +17 -3
  36. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +27 -13
  37. package/dist/harness-runtime/lib/dd-codex.mjs +4 -0
  38. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +4 -3
  39. package/dist/harness-runtime/lib/dd-droid.mjs +71 -29
  40. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +73 -16
  41. package/dist/harness-runtime/lib/dd-grok.mjs +6 -3
  42. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +66 -20
  43. package/dist/harness-runtime/lib/dd-opencode.mjs +30 -3
  44. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +7 -6
  45. package/dist/harness-runtime/lib/dd-zcode.mjs +44 -14
  46. package/dist/harness-runtime/lib/delegation-instructions.mjs +1 -1
  47. package/dist/harness-runtime/lib/driver-recovery.mjs +9 -8
  48. package/dist/harness-runtime/lib/entity-references.d.mts +2 -0
  49. package/dist/harness-runtime/lib/entity-references.mjs +9 -0
  50. package/dist/harness-runtime/lib/managed-daemon.d.mts +1 -1
  51. package/dist/harness-runtime/lib/managed-daemon.mjs +24 -9
  52. package/dist/harness-runtime/lib/native-children.d.mts +8 -1
  53. package/dist/harness-runtime/lib/native-children.mjs +114 -8
  54. package/dist/harness-runtime/lib/native-hook-command.mjs +4 -4
  55. package/dist/harness-runtime/lib/native-request-proof.mjs +1 -1
  56. package/dist/harness-runtime/lib/observation-clock.d.mts +2 -2
  57. package/dist/harness-runtime/lib/observation-clock.mjs +7 -4
  58. package/dist/harness-runtime/lib/opencode-plugin.mjs +3 -3
  59. package/dist/harness-runtime/lib/operation-errors.mjs +1 -1
  60. package/dist/harness-runtime/lib/provider-overload-policy.d.mts +5 -3
  61. package/dist/harness-runtime/lib/provider-overload-policy.mjs +20 -7
  62. package/dist/harness-runtime/lib/runner-events.mjs +3 -1
  63. package/dist/harness-runtime/lib/setup-progress.mjs +8 -6
  64. package/dist/harness-runtime/lib/tool-observations.mjs +1 -1
  65. package/dist/schemas/diagnostic-stage-result.schema.json +354 -0
  66. package/dist/schemas/diagnostic-work-result.schema.json +296 -0
  67. package/dist/schemas/execution-bundle.schema.json +7 -1
  68. package/dist/schemas/mb-upgrade-stage-result.schema.json +863 -0
  69. package/dist/schemas/memory-repair-work-result.schema.json +9 -2
  70. package/dist/schemas/project-flow-pack-manifest.schema.json +1 -1
  71. package/dist/schemas/release-impact.schema.json +3 -3
  72. package/dist/schemas/stage-start-response.schema.json +4 -0
  73. package/dist/services/accepted-intent.js +13 -1
  74. package/dist/services/canon.js +43 -7
  75. package/dist/services/check-environment.js +21 -0
  76. package/dist/services/check-runner-evidence.js +267 -15
  77. package/dist/services/cli-operation-classifier.js +1 -1
  78. package/dist/services/cli-upgrade-allowlist.js +1 -1
  79. package/dist/services/code-checks.js +34 -19
  80. package/dist/services/compatibility-preflight.js +5 -2
  81. package/dist/services/controller-execution.js +10 -4
  82. package/dist/services/controller-fanout.js +34 -10
  83. package/dist/services/controller-memory-task.js +45 -0
  84. package/dist/services/controller-memory-worker.js +49 -0
  85. package/dist/services/dashboard.js +2 -2
  86. package/dist/services/descriptor-run-settlement.js +5 -3
  87. package/dist/services/descriptor-run-start.js +46 -20
  88. package/dist/services/descriptor-stage-coordinator.js +66 -13
  89. package/dist/services/descriptor-stage-settlement.js +2 -1
  90. package/dist/services/descriptor-stage-start.js +40 -4
  91. package/dist/services/diagnostic-evidence.js +73 -0
  92. package/dist/services/diagnostic-report-publication.js +72 -0
  93. package/dist/services/diagnostic-report.js +67 -0
  94. package/dist/services/diagnostic-run-bundle.js +74 -0
  95. package/dist/services/diagnostic-snapshot.js +159 -0
  96. package/dist/services/diagnostic-stage-handlers.js +108 -0
  97. package/dist/services/diagnostic-stage-results.js +132 -0
  98. package/dist/services/diagnostic-start.js +84 -0
  99. package/dist/services/diagnostic-work-batch.js +34 -0
  100. package/dist/services/diagnostic-work-collection.js +97 -0
  101. package/dist/services/diagnostic-work-registration.js +101 -0
  102. package/dist/services/diagnostic-work-results.js +93 -0
  103. package/dist/services/engines.js +64 -14
  104. package/dist/services/eval-snapshots.js +186 -119
  105. package/dist/services/execution-bundles.js +19 -7
  106. package/dist/services/external-work-launch.js +5 -3
  107. package/dist/services/flow-catalog.js +13 -4
  108. package/dist/services/frozen-engine.js +148 -11
  109. package/dist/services/hook-memory-routing.js +188 -0
  110. package/dist/services/hook-recovery-guard.js +14 -0
  111. package/dist/services/hook-session-observation.js +24 -0
  112. package/dist/services/hooks.js +27 -8
  113. package/dist/services/hybrid-contracts.js +4 -13
  114. package/dist/services/hybrid-digest.js +14 -0
  115. package/dist/services/installed-flow-source.js +47 -4
  116. package/dist/services/lifecycle-invocations.js +179 -38
  117. package/dist/services/memory-accepted-composition.js +5 -4
  118. package/dist/services/memory-completed-correction-wave.js +23 -15
  119. package/dist/services/memory-compose-handler.js +163 -21
  120. package/dist/services/memory-composition-acceptance.js +186 -10
  121. package/dist/services/memory-composition-graph.js +42 -11
  122. package/dist/services/memory-composition-registration.js +23 -8
  123. package/dist/services/memory-composition-work-custody.js +14 -9
  124. package/dist/services/memory-composition-work-results.js +15 -3
  125. package/dist/services/memory-corrected-review-batch.js +10 -3
  126. package/dist/services/memory-corrected-review-findings.js +4 -2
  127. package/dist/services/memory-corrected-review-inputs.js +10 -5
  128. package/dist/services/memory-corrected-review-receipt.js +17 -9
  129. package/dist/services/memory-corrected-review-registration.js +3 -2
  130. package/dist/services/memory-correction-check-receipt.js +20 -4
  131. package/dist/services/memory-custody-snapshot.js +36 -0
  132. package/dist/services/memory-finish-evidence.js +44 -22
  133. package/dist/services/memory-finish-handler.js +37 -23
  134. package/dist/services/memory-finish-integration.js +218 -48
  135. package/dist/services/memory-finish-readiness.js +66 -19
  136. package/dist/services/memory-git-integration.js +87 -18
  137. package/dist/services/memory-historical-corrected-review.js +21 -15
  138. package/dist/services/memory-historical-original-review.js +22 -9
  139. package/dist/services/memory-historical-repair-registration.js +14 -8
  140. package/dist/services/memory-historical-second-review.js +30 -11
  141. package/dist/services/memory-init-contexts.js +44 -2
  142. package/dist/services/memory-init-controller-start.js +27 -8
  143. package/dist/services/memory-init-git-policy.js +50 -21
  144. package/dist/services/memory-init-lint-capability.js +24 -6
  145. package/dist/services/memory-init-native-capacity.js +17 -4
  146. package/dist/services/memory-init-preflight.js +21 -17
  147. package/dist/services/memory-init-request-candidate.js +73 -17
  148. package/dist/services/memory-init-request-input.js +23 -7
  149. package/dist/services/memory-init-run-bundle.js +49 -12
  150. package/dist/services/memory-init-storage.js +17 -7
  151. package/dist/services/memory-init-worktrunk-capability.js +3 -7
  152. package/dist/services/memory-layer.js +71 -6
  153. package/dist/services/memory-lint-config.js +111 -2
  154. package/dist/services/memory-native-review-copy.js +5 -67
  155. package/dist/services/memory-normal-flow-entry.js +12 -0
  156. package/dist/services/memory-ordinary-defaults.js +22 -5
  157. package/dist/services/memory-preflight-handler.js +15 -0
  158. package/dist/services/memory-repair-current-view.js +1 -1
  159. package/dist/services/memory-repair-document-delta.js +70 -33
  160. package/dist/services/memory-repair-history.js +12 -7
  161. package/dist/services/memory-repair-physical-binding.js +1 -119
  162. package/dist/services/memory-repair-publication-plan.js +5 -3
  163. package/dist/services/memory-repair-publication-source.js +1 -1
  164. package/dist/services/memory-repair-publication.js +3 -2
  165. package/dist/services/memory-repair-registration.js +2 -1
  166. package/dist/services/memory-repair-work-baseline.js +4 -2
  167. package/dist/services/memory-repair-work-completion.js +32 -12
  168. package/dist/services/memory-repair-work-results.js +21 -2
  169. package/dist/services/memory-repair-workspace.js +11 -2
  170. package/dist/services/memory-repair-writer-copy.js +2 -1
  171. package/dist/services/memory-result-binding.js +78 -12
  172. package/dist/services/memory-retained-review-authority.js +17 -8
  173. package/dist/services/memory-retained-second-baseline.js +1 -1
  174. package/dist/services/memory-retained-second-repairs.js +12 -6
  175. package/dist/services/memory-review-correction-plan.js +47 -12
  176. package/dist/services/memory-review-handler.js +17 -17
  177. package/dist/services/memory-review-inputs.js +55 -9
  178. package/dist/services/memory-review-limits.js +10 -0
  179. package/dist/services/memory-review-receipt.js +2 -2
  180. package/dist/services/memory-review-registration.js +13 -4
  181. package/dist/services/memory-review-work-custody.js +3 -1
  182. package/dist/services/memory-review-work-input.js +26 -7
  183. package/dist/services/memory-review-work-results.js +26 -4
  184. package/dist/services/memory-second-corrected-review-inputs.js +5 -3
  185. package/dist/services/memory-second-correction-plan.js +2 -1
  186. package/dist/services/memory-second-repair-launch.js +4 -4
  187. package/dist/services/memory-second-repair-source.js +1 -1
  188. package/dist/services/memory-stage-dispatch.js +38 -1
  189. package/dist/services/memory-stage-results.js +113 -22
  190. package/dist/services/memory-tool-probe.js +25 -0
  191. package/dist/services/memory-upgrade-analysis-handler.js +243 -0
  192. package/dist/services/memory-upgrade-bootstrap-readiness.js +251 -0
  193. package/dist/services/memory-upgrade-evidence.js +100 -0
  194. package/dist/services/memory-upgrade-inventory.js +442 -0
  195. package/dist/services/memory-upgrade-publication-custody.js +73 -0
  196. package/dist/services/memory-upgrade-publication.js +413 -0
  197. package/dist/services/memory-upgrade-selection.js +430 -0
  198. package/dist/services/memory-upgrade-start.js +60 -0
  199. package/dist/services/memory-upgrade-storage-readiness.js +340 -0
  200. package/dist/services/memory-verification-acceptance.js +38 -17
  201. package/dist/services/memory-verification-checks.js +92 -27
  202. package/dist/services/memory-verification-receipt.js +6 -5
  203. package/dist/services/memory-verify-handler.js +15 -13
  204. package/dist/services/memory-work-result-json.js +12 -0
  205. package/dist/services/memory-work-result-preparation.js +5 -4
  206. package/dist/services/merge-server.js +4 -2
  207. package/dist/services/migrations.js +127 -98
  208. package/dist/services/native-daemon-history.js +2 -1
  209. package/dist/services/physical-startup.js +45 -0
  210. package/dist/services/project-registry.js +97 -0
  211. package/dist/services/project-summary.js +1 -1
  212. package/dist/services/projects.js +3 -93
  213. package/dist/services/prompt-delivery.js +71 -0
  214. package/dist/services/provider-capacity.js +112 -46
  215. package/dist/services/recovery-snapshot-database.js +32 -4
  216. package/dist/services/release-cache.js +8 -0
  217. package/dist/services/repair-intents.js +1 -12
  218. package/dist/services/repair-receipt.js +13 -0
  219. package/dist/services/run-control-worker.js +35 -18
  220. package/dist/services/run-control.js +2 -1
  221. package/dist/services/run-controller-adapter.js +2 -1
  222. package/dist/services/run-controller-process.js +4 -7
  223. package/dist/services/run-controller-recovery.js +8 -2
  224. package/dist/services/run-controller.js +215 -94
  225. package/dist/services/run-fork.js +1 -1
  226. package/dist/services/run-recovery-guard.js +30 -0
  227. package/dist/services/run-recovery.js +11 -44
  228. package/dist/services/run-start-custody.js +17 -5
  229. package/dist/services/runs.js +6 -1
  230. package/dist/services/runtime-scope-resume.js +12 -6
  231. package/dist/services/runtime-scope-stop.js +3 -2
  232. package/dist/services/runtime-scope-worker.js +46 -19
  233. package/dist/services/runtime-service.js +2 -1
  234. package/dist/services/sessions.js +3 -24
  235. package/dist/services/setup.js +63 -13
  236. package/dist/services/stage-handlers.js +22 -7
  237. package/dist/services/stage-pause.js +111 -44
  238. package/dist/services/version-status.js +1 -1
  239. package/dist/services/vnext-code.js +2 -2
  240. package/dist/services/vnext-fanout.js +51 -9
  241. package/dist/services/vnext-plan-review.js +3 -5
  242. package/dist/services/work-registry.js +117 -39
  243. package/dist/services/workspace-bootstrap.js +2 -1
  244. package/dist/services/workspace-files.js +1 -1
  245. package/dist/shared/entity-references.js +1 -8
  246. package/dist/storage/data-preservation.js +65 -0
  247. package/dist/storage/database.js +53 -19
  248. package/dist/storage/import-guard.js +84 -27
  249. package/dist/storage/memory-upgrade-schema-bootstrap.js +38 -0
  250. package/dist/storage/paired-writer-migration.js +424 -0
  251. package/dist/storage/writer-contract.js +11 -4
  252. package/dist/storage/writer-migration.js +313 -52
  253. package/dist/storage/writer-restore.js +217 -0
  254. package/package.json +13 -6
@@ -32,12 +32,16 @@ function inventory(root) {
32
32
  if (depth > 64 || entries.length >= 20_000 || logical.length > 4096)
33
33
  refuse("unbounded_document_inventory");
34
34
  const file = path.join(root, logical), stat = fs.lstatSync(file);
35
- if (stat.isSymbolicLink() || !stat.isDirectory() && !stat.isFile() || stat.isFile() && stat.nlink !== 1)
35
+ if (!stat.isSymbolicLink() && !stat.isDirectory() && !stat.isFile() || stat.isFile() && stat.nlink !== 1)
36
36
  refuse("linked_or_special_document");
37
37
  if (aliases.has(alias(logical)))
38
38
  refuse("aliased_document_inventory");
39
39
  aliases.add(alias(logical));
40
- entries.push({ path: logical, kind: stat.isDirectory() ? "directory" : "file", mode: stat.mode & 0o7777, sha256: null });
40
+ const link = stat.isSymbolicLink() ? fs.readlinkSync(file, { encoding: "buffer" }) : null;
41
+ if (link && link.length > 64 * 1024)
42
+ refuse("unbounded_document_inventory");
43
+ entries.push({ path: logical, kind: stat.isSymbolicLink() ? "link" : stat.isDirectory() ? "directory" : "file", mode: stat.mode & 0o7777,
44
+ sha256: link ? createHash("sha256").update(link).digest("hex") : null });
41
45
  if (stat.isDirectory())
42
46
  for (const name of fs.readdirSync(file).sort()) {
43
47
  if (!name || name.includes("\\") || name.includes("\0") || name === "." || name === "..")
@@ -83,7 +87,7 @@ function capture(root) {
83
87
  if (hybridDigest(inventory(root)) !== hybridDigest(initial))
84
88
  refuse("inventory_changed_during_capture");
85
89
  const hashes = new Map([...observed.hashes(), ...protectedBefore]), entries = initial.map(entry => ({ ...entry,
86
- sha256: entry.kind === "file" ? hashes.get(memorySourceKey("workspace", entry.path)) : null }));
90
+ sha256: entry.kind === "file" ? hashes.get(memorySourceKey("workspace", entry.path)) : entry.sha256 }));
87
91
  return { entries, observed, sha256: hybridDigest(entries) };
88
92
  }
89
93
  catch (error) {
@@ -92,6 +96,15 @@ function capture(root) {
92
96
  return refuse("documents_unavailable_or_changed");
93
97
  }
94
98
  }
99
+ /** Reuse the protected inventory without inventing repair write permissions.
100
+ * Private files and symlinks remain opaque hashes, never exported bytes. */
101
+ export function observeMemoryDocumentInventory(workspace) {
102
+ const snapshot = capture(workspace);
103
+ return { entries: () => structuredClone(snapshot.entries), revalidate: () => {
104
+ if (hybridDigest(capture(workspace).entries) !== hybridDigest(snapshot.entries))
105
+ refuse("documents_changed_after_baseline");
106
+ } };
107
+ }
95
108
  /** Physical MB-only observation, not provider/Session authority or permission to
96
109
  * write the shared workspace. A registered owner must bind this start baseline,
97
110
  * inspect the rest of its private workspace and journal bounded publication.
@@ -101,47 +114,60 @@ export class MemoryRepairDocumentBaseline {
101
114
  sha256;
102
115
  #entries;
103
116
  #paths;
104
- constructor(entries, paths) {
117
+ #canonical;
118
+ #material(entries = this.#entries, paths = this.#paths) {
119
+ return { entries, write_paths: paths, ...(this.#canonical.length ? { canonical_write_paths: this.#canonical } : {}) };
120
+ }
121
+ constructor(entries, paths, canonical = []) {
105
122
  this.#entries = structuredClone(entries);
106
123
  this.#paths = [...paths];
107
- this.sha256 = hybridDigest({ entries, write_paths: paths });
124
+ this.#canonical = [...canonical].sort();
125
+ this.sha256 = hybridDigest(this.#material());
108
126
  Object.freeze(this);
109
127
  }
110
- static capture(workspace, writePaths) {
111
- assertMemoryRepairWritePaths(writePaths);
128
+ static capture(workspace, writePaths, canonicalPaths = new Set()) {
129
+ assertMemoryRepairWritePaths(writePaths, canonicalPaths);
112
130
  const snapshot = capture(workspace), byPath = new Map(snapshot.entries.map(entry => [entry.path, entry]));
131
+ const aliases = new Map(snapshot.entries.map(entry => [alias(entry.path), entry.path]));
113
132
  for (const logical of writePaths) {
114
133
  if (byPath.get(logical)?.kind === "directory")
115
134
  refuse("write_path_is_directory");
116
- if (snapshot.entries.some(entry => alias(entry.path) === alias(logical) && entry.path !== logical))
135
+ if (byPath.get(logical)?.kind === "link")
136
+ refuse("write_path_is_link");
137
+ const existingAlias = aliases.get(alias(logical));
138
+ if (existingAlias !== undefined && existingAlias !== logical)
117
139
  refuse("aliased_write_path");
118
140
  const parts = logical.split("/");
119
141
  for (let index = 1; index < parts.length; index++) {
120
142
  const parent = parts.slice(0, index).join("/");
121
- if (byPath.get(parent)?.kind === "file" || snapshot.entries.some(entry => alias(entry.path) === alias(parent) && entry.path !== parent))
143
+ const parentAlias = aliases.get(alias(parent));
144
+ if (byPath.has(parent) && byPath.get(parent)?.kind !== "directory" || parentAlias !== undefined && parentAlias !== parent)
122
145
  refuse("invalid_write_parent");
123
146
  }
124
147
  }
125
- return new MemoryRepairDocumentBaseline(snapshot.entries, writePaths);
148
+ const prepared = new MemoryRepairDocumentBaseline(snapshot.entries, writePaths, [...canonicalPaths]);
149
+ return MemoryRepairDocumentBaseline.restore(prepared.bytes(), prepared.sha256);
126
150
  }
127
151
  entries() { return structuredClone(this.#entries); }
152
+ canonicalWritePaths() { return new Set(this.#canonical); }
128
153
  /** Adapt an independently accepted document view to the next registered
129
154
  * Work's approved paths without re-capturing mutable workspace bytes.
130
155
  * The caller still owns graph/current-view authority; this grants none.
131
156
  */
132
157
  withWritePaths(writePaths) {
133
- assertMemoryRepairWritePaths(writePaths);
134
- const value = { schema_id: "dd-flow/memory-repair-document-baseline@1", entries: this.entries(), write_paths: [...writePaths] };
135
- return MemoryRepairDocumentBaseline.restore(Buffer.from(JSON.stringify(value)), hybridDigest({ entries: value.entries, write_paths: value.write_paths }));
158
+ assertMemoryRepairWritePaths(writePaths, new Set(this.#canonical));
159
+ const material = this.#material(this.entries(), [...writePaths]);
160
+ return MemoryRepairDocumentBaseline.restore(Buffer.from(JSON.stringify({ schema_id: "dd-flow/memory-repair-document-baseline@1", ...material })), hybridDigest(material));
136
161
  }
137
162
  /** Metadata composition only, never admission or filesystem publication.
138
163
  * A caller must independently prove both retained views belong to a settled
139
164
  * repair. Disjoint concurrent deltas preserve the current inventory; file
140
165
  * conflicts never rebase. Identical new parent directories may be shared.
141
166
  */
142
- composeSettledDelta(before, after) {
167
+ composeSettledDelta(before, after, outcome = "accepted") {
143
168
  if (!(before instanceof MemoryRepairDocumentBaseline) || !(after instanceof MemoryRepairDocumentBaseline)
144
- || hybridDigest(before.#paths) !== hybridDigest(after.#paths))
169
+ || !["accepted", "reconciled"].includes(outcome)
170
+ || hybridDigest(before.#paths) !== hybridDigest(after.#paths) || hybridDigest(before.#canonical) !== hybridDigest(after.#canonical))
145
171
  refuse("invalid_settled_document_views");
146
172
  const prior = new Map(before.#entries.map(entry => [entry.path, entry]));
147
173
  const final = new Map(after.#entries.map(entry => [entry.path, entry]));
@@ -160,7 +186,7 @@ export class MemoryRepairDocumentBaseline {
160
186
  refuse("settled_document_conflict");
161
187
  }
162
188
  else {
163
- if (!before.#paths.includes(logical) || old?.kind === "directory" || next && next.kind !== "file"
189
+ if (!before.#paths.includes(logical) || old && old.kind !== "file" || next && next.kind !== "file"
164
190
  || next && (old ? next.mode !== old.mode : ![0o600, 0o644].includes(next.mode))
165
191
  || old && next && old.sha256 === next.sha256)
166
192
  refuse("invalid_settled_file_delta");
@@ -173,10 +199,10 @@ export class MemoryRepairDocumentBaseline {
173
199
  else
174
200
  current.delete(logical);
175
201
  }
176
- if (!changedFiles)
177
- refuse("empty_settled_document_delta");
178
- const entries = ordered(current.values()), value = { schema_id: "dd-flow/memory-repair-document-baseline@1", entries, write_paths: [...this.#paths] };
179
- return MemoryRepairDocumentBaseline.restore(Buffer.from(JSON.stringify(value)), hybridDigest({ entries, write_paths: value.write_paths }));
202
+ if (outcome === "reconciled" ? changedFiles !== 0 : changedFiles === 0)
203
+ refuse(outcome === "reconciled" ? "nonempty_reconciled_document_delta" : "empty_settled_document_delta");
204
+ const material = this.#material(ordered(current.values()));
205
+ return MemoryRepairDocumentBaseline.restore(Buffer.from(JSON.stringify({ schema_id: "dd-flow/memory-repair-document-baseline@1", ...material })), hybridDigest(material));
180
206
  }
181
207
  /** Project only the independently selected regular files into a sanitized
182
208
  * writer view. The protected source snapshot stays intact. This is metadata
@@ -214,12 +240,12 @@ export class MemoryRepairDocumentBaseline {
214
240
  refuse("write_target_not_exported");
215
241
  parents(logical);
216
242
  }
217
- const exported = new MemoryRepairDocumentBaseline(this.#entries.filter(entry => retained.has(entry.path)), this.#paths);
243
+ const exported = new MemoryRepairDocumentBaseline(this.#entries.filter(entry => retained.has(entry.path)), this.#paths, this.#canonical);
218
244
  // Apply the same bounded, strict representation used by SQL restoration.
219
245
  return MemoryRepairDocumentBaseline.restore(exported.bytes(), exported.sha256);
220
246
  }
221
247
  bytes() {
222
- const bytes = Buffer.from(JSON.stringify({ schema_id: "dd-flow/memory-repair-document-baseline@1", entries: this.#entries, write_paths: this.#paths }));
248
+ const bytes = Buffer.from(JSON.stringify({ schema_id: "dd-flow/memory-repair-document-baseline@1", ...this.#material() }));
223
249
  if (bytes.length > 8 * 1024 * 1024)
224
250
  refuse("unbounded_baseline_receipt");
225
251
  return bytes;
@@ -233,39 +259,48 @@ export class MemoryRepairDocumentBaseline {
233
259
  || typeof expectedSha256 !== "string" || !/^[a-f0-9]{64}$/.test(expectedSha256))
234
260
  refuse("invalid_baseline_receipt");
235
261
  const value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
236
- if (!value || Object.keys(value).sort().join(",") !== "entries,schema_id,write_paths"
262
+ if (!value || Object.keys(value).filter(key => key !== "canonical_write_paths").sort().join(",") !== "entries,schema_id,write_paths"
237
263
  || value.schema_id !== "dd-flow/memory-repair-document-baseline@1" || !Array.isArray(value.entries)
238
264
  || !value.entries.length || value.entries.length > 20_000)
239
265
  refuse("invalid_baseline_receipt");
240
- assertMemoryRepairWritePaths(value.write_paths);
241
- const byPath = new Map(), aliases = new Set();
266
+ const canonical = value.canonical_write_paths ?? [];
267
+ if (!Array.isArray(canonical) || canonical.length > 20_000 || new Set(canonical).size !== canonical.length
268
+ || canonical.some(logical => typeof logical !== "string" || ![".memory-bank/dd-flow/", ".memory-bank/mbb/"].some(prefix => logical.startsWith(prefix))))
269
+ refuse("invalid_baseline_receipt");
270
+ const permitted = new Set(canonical);
271
+ for (const logical of canonical)
272
+ assertMemoryRepairWritePaths([logical], permitted);
273
+ assertMemoryRepairWritePaths(value.write_paths, permitted);
274
+ const byPath = new Map(), aliases = new Map();
242
275
  for (const entry of value.entries) {
243
276
  if (!entry || Object.keys(entry).sort().join(",") !== "kind,mode,path,sha256"
244
277
  || typeof entry.path !== "string" || entry.path.length > 4096 || entry.path.split("/").length > 65
245
278
  || entry.path !== ".memory-bank" && !entry.path.startsWith(".memory-bank/")
246
279
  || entry.path.includes("\\") || entry.path.includes("\0") || entry.path.split("/").some(part => !part || part === "." || part === "..")
247
- || !["file", "directory"].includes(entry.kind) || !Number.isInteger(entry.mode) || entry.mode < 0 || entry.mode > 0o7777
280
+ || !["file", "directory", "link"].includes(entry.kind) || !Number.isInteger(entry.mode) || entry.mode < 0 || entry.mode > 0o7777
248
281
  || entry.kind === "directory" && entry.sha256 !== null
249
- || entry.kind === "file" && (typeof entry.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(entry.sha256))
282
+ || entry.kind !== "directory" && (typeof entry.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(entry.sha256))
250
283
  || aliases.has(alias(entry.path)))
251
284
  refuse("invalid_baseline_receipt");
252
285
  if (entry.path === ".memory-bank" ? byPath.size !== 0 || entry.kind !== "directory"
253
286
  : byPath.get(path.posix.dirname(entry.path))?.kind !== "directory")
254
287
  refuse("invalid_baseline_receipt");
255
- aliases.add(alias(entry.path));
288
+ aliases.set(alias(entry.path), entry.path);
256
289
  byPath.set(entry.path, entry);
257
290
  }
258
291
  for (const logical of value.write_paths) {
259
- if (byPath.get(logical)?.kind === "directory" || value.entries.some((entry) => alias(entry.path) === alias(logical) && entry.path !== logical))
292
+ const existingAlias = aliases.get(alias(logical));
293
+ if (byPath.has(logical) && byPath.get(logical)?.kind !== "file" || existingAlias !== undefined && existingAlias !== logical)
260
294
  refuse("invalid_baseline_receipt");
261
295
  const parts = logical.split("/");
262
296
  for (let index = 1; index < parts.length; index++) {
263
297
  const parent = parts.slice(0, index).join("/");
264
- if (byPath.get(parent)?.kind === "file" || value.entries.some((entry) => alias(entry.path) === alias(parent) && entry.path !== parent))
298
+ const parentAlias = aliases.get(alias(parent));
299
+ if (byPath.has(parent) && byPath.get(parent)?.kind !== "directory" || parentAlias !== undefined && parentAlias !== parent)
265
300
  refuse("invalid_baseline_receipt");
266
301
  }
267
302
  }
268
- const prepared = new MemoryRepairDocumentBaseline(value.entries, value.write_paths);
303
+ const prepared = new MemoryRepairDocumentBaseline(value.entries, value.write_paths, canonical);
269
304
  if (prepared.sha256 !== expectedSha256 || !prepared.bytes().equals(Buffer.from(bytes)))
270
305
  refuse("baseline_receipt_changed");
271
306
  return prepared;
@@ -344,6 +379,8 @@ export class MemoryRepairDocumentBaseline {
344
379
  }
345
380
  if (!this.#paths.includes(logical))
346
381
  refuse("undeclared_document_delta");
382
+ if (old?.kind === "link" || current?.kind === "link")
383
+ refuse("link_document_delta");
347
384
  if (old && current && old.mode !== current.mode || !old && current && ![0o600, 0o644].includes(current.mode))
348
385
  refuse("document_mode_delta");
349
386
  if (old?.sha256 === current?.sha256)
@@ -354,7 +391,7 @@ export class MemoryRepairDocumentBaseline {
354
391
  changes.sort((a, b) => a.path.localeCompare(b.path, "en"));
355
392
  if (hybridDigest(changes) !== hybridDigest(proposed))
356
393
  refuse("claimed_delta_differs_from_bytes");
357
- if (result.outcome === "blocked" && changed.length || result.outcome === "accepted" && !changes.length)
394
+ if (["blocked", "reconciled"].includes(result.outcome) && changed.length || result.outcome === "accepted" && !changes.length)
358
395
  refuse("outcome_differs_from_physical_delta");
359
396
  return new ObservedMemoryRepairDelta(observationToken, this.sha256, workspace, snapshot.sha256, changes, snapshot.observed, snapshot.entries);
360
397
  }
@@ -423,7 +460,7 @@ export class MemoryRepairDocumentTransition {
423
460
  }
424
461
  const retained = JSON.parse(this.#before.bytes().toString("utf8"));
425
462
  const value = { ...retained, entries: ordered(entries.values()) };
426
- const expected = hybridDigest({ entries: value.entries, write_paths: value.write_paths });
463
+ const expected = hybridDigest({ ...value, schema_id: undefined });
427
464
  return MemoryRepairDocumentBaseline.restore(Buffer.from(JSON.stringify(value)), expected);
428
465
  }
429
466
  after() { return this.state(this.#actions.length); }
@@ -1,6 +1,7 @@
1
1
  import path from "node:path";
2
2
  import { readMemoryRepairPhysicalBinding } from "./memory-repair-physical-binding.js";
3
3
  import { createHash } from "node:crypto";
4
+ import { assertMemorySqlCustody } from "./memory-custody-snapshot.js";
4
5
  import { AppError } from "../shared/errors.js";
5
6
  import { hybridDigest } from "./hybrid-contracts.js";
6
7
  import { PreparedRegisteredMemoryCorrections, memoryRepairWorkerSchema } from "./memory-repair-registration.js";
@@ -12,7 +13,7 @@ import { RetainedMemoryRepairPublicationPlan } from "./memory-repair-publication
12
13
  import { deriveRetainedMemoryRepairViews } from "./memory-repair-publication.js";
13
14
  import { retainedMemoryRepairPublicationPlans } from "./memory-repair-publication-intent.js";
14
15
  import { PreparedMemoryRepairWorkspace } from "./memory-repair-workspace.js";
15
- import { memoryRepairCurrentReferences, validateMemoryRepairWorkResult } from "./memory-repair-work-results.js";
16
+ import { isCompletedMemoryRepairClaim, memoryRepairCurrentReferences, validateMemoryRepairWorkResult } from "./memory-repair-work-results.js";
16
17
  import { readMemoryRepairCurrentViewReceipt } from "./memory-repair-current-view.js";
17
18
  import { runtimeHarness } from "./execution-policy.js";
18
19
  import { ObservedMemorySources } from "./memory-source-observations.js";
@@ -190,7 +191,7 @@ function snapshot(context, registration, workId, validateReferencedViews = true)
190
191
  const after = MemoryRepairDocumentBaseline.restore(bytes(peer.intent.after_base64, 8 * 1024 * 1024), peer.intent.after_sha256);
191
192
  if (!prefixes.has(hybridDigest(before.entries())))
192
193
  refuse("historical_second_current_view_prefix_changed");
193
- composed = composed.composeSettledDelta(before, after);
194
+ composed = composed.composeSettledDelta(before, after, JSON.parse(peer.work.result).outcome);
194
195
  prefixes.add(hybridDigest(composed.entries()));
195
196
  const peerInputs = JSON.parse(bytes(peer.build.workspace_manifest_base64, 8 * 1024 * 1024).toString("utf8")).entries
196
197
  .filter((entry) => entry.path !== ".memory-bank" && !entry.path.startsWith(".memory-bank/"));
@@ -332,7 +333,7 @@ function snapshot(context, registration, workId, validateReferencedViews = true)
332
333
  }
333
334
  if (!versions.has(hybridDigest(peerBefore.entries())))
334
335
  refuse("historical_publication_source_prefix_changed");
335
- composed = composed.composeSettledDelta(peerBefore, peerAfter);
336
+ composed = composed.composeSettledDelta(peerBefore, peerAfter, JSON.parse(peer.work.result).outcome);
336
337
  versions.add(hybridDigest(composed.entries()));
337
338
  const peerInputs = JSON.parse(bytes(peer.build.workspace_manifest_base64, 8 * 1024 * 1024).toString("utf8")).entries.filter((entry) => entry.path !== ".memory-bank" && !entry.path.startsWith(".memory-bank/"));
338
339
  if (hybridDigest(peerInputs) !== immutableInputs)
@@ -407,6 +408,7 @@ function viewInput(registration) {
407
408
  * and compare the physical shared inventory before admitting new work.
408
409
  */
409
410
  export class PreparedSettledMemoryRepair {
411
+ #custodyKey = {};
410
412
  sha256;
411
413
  workId;
412
414
  #registration;
@@ -448,7 +450,7 @@ export class PreparedSettledMemoryRepair {
448
450
  const result = validateMemoryRepairWorkResult(Buffer.from(value.work.result), historyBundle(registration).engine(), { workId, packet: value.assigned.payload.memory_repair,
449
451
  ...(value.currentView || PreparedRetainedSecondMemoryRepairs.isPrepared(registration)
450
452
  ? { effectiveInputs: memoryRepairCurrentReferences(value.assigned.payload.memory_repair, workspace) } : {}) });
451
- if (result.outcome !== "accepted")
453
+ if (!isCompletedMemoryRepairClaim(result))
452
454
  refuse("accepted_settled_repair_required");
453
455
  const { plan, privatePlan } = retainedMemoryRepairPublicationPlans(value.intent);
454
456
  const derived = deriveRetainedMemoryRepairViews(before, exported, plan, result, privatePlan);
@@ -478,14 +480,17 @@ export class PreparedSettledMemoryRepair {
478
480
  if (context.db.isOwnedWriteTransaction?.())
479
481
  refuse("historical_preparation_outside_writer_required");
480
482
  this.revalidate(context);
481
- const composed = current.composeSettledDelta(this.#before, this.#after);
483
+ const composed = current.composeSettledDelta(this.#before, this.#after, this.#result.outcome);
482
484
  this.assertCustody(context);
483
485
  return composed;
484
486
  }
485
487
  reference() { return { ...this.#reference }; }
486
488
  assertCustody(context) {
487
- if (hybridDigest(snapshot(context, this.#registration, this.workId)) !== this.sha256)
488
- refuse("historical_custody_changed");
489
+ const registration = this.#registration;
490
+ assertMemorySqlCustody(context, this.#custodyKey, () => {
491
+ if (hybridDigest(snapshot(context, registration, this.workId)) !== this.sha256)
492
+ refuse("historical_custody_changed");
493
+ });
489
494
  }
490
495
  revalidate(context) { this.assertCustody(context); this.#observed.revalidate(); }
491
496
  }
@@ -1,5 +1,4 @@
1
1
  import path from "node:path";
2
- import fs from "node:fs";
3
2
  import { createHash } from "node:crypto";
4
3
  import { AppError } from "../shared/errors.js";
5
4
  import { appendAudit } from "./audit.js";
@@ -8,7 +7,6 @@ import { hybridDigest } from "./hybrid-contracts.js";
8
7
  import { runtimeHarness } from "./execution-policy.js";
9
8
  import { parseLifecycleCommand, commandPosition } from "./lifecycle-command.js";
10
9
  import { resolveWorkReference } from "../storage/work-references.js";
11
- import { assertRunMutationAllowed } from "./run-recovery-guard.js";
12
10
  import { currentLifecycleAdmission } from "./lifecycle-invocations.js";
13
11
  import { recoveryGuard } from "./run-recovery-guard.js";
14
12
  const event = "memory.repair_native_binding";
@@ -53,123 +51,7 @@ export function memoryRepairExecutionIdentity(context, input) {
53
51
  return refuse("repair_execution_cwd_unproven");
54
52
  }
55
53
  }
56
- /** Hook ingress selects only an independently prepared repair copy. This
57
- * establishes a command's assigned directory, not claim/repair authority. */
58
- export function memoryRepairAssignedHookCwd(context, input) {
59
- const work = context.db.get("SELECT payload_json,result_schema,status FROM works WHERE project_id=? AND run_id=? AND work_id=?", [input.projectId, input.runId, input.workId]);
60
- let payload;
61
- try {
62
- payload = JSON.parse(work?.payload_json ?? "null");
63
- }
64
- catch {
65
- return null;
66
- }
67
- if (payload?.kind !== "memory-repair" && work?.result_schema !== "dd-flow/memory-repair-work-result@1")
68
- return null;
69
- const run = context.db.get("SELECT r.run_root,r.run_dir,r.flow_kind,p.root AS project_root FROM runs r JOIN projects p ON p.id=r.project_id WHERE r.project_id=? AND r.id=?", [input.projectId, input.runId]);
70
- if (!run || run.flow_kind !== "mb-init" || run.project_root !== input.projectRoot || payload.read_only !== false
71
- || ![1, 2].includes(payload.memory_repair?.wave ?? 0) || work?.result_schema !== "dd-flow/memory-repair-work-result@1")
72
- refuse("registered_memory_repair_required");
73
- const generation = assertRunMutationAllowed(context, input.projectId, input.runId), runRoot = run.run_root ?? run.run_dir;
74
- const external = context.db.get("SELECT 1 FROM sqlite_schema WHERE type='table' AND name='work_launches'")
75
- ? context.db.get("SELECT operation_id,status,profile_json,profile_hash,provider_session_id FROM work_launches WHERE work_id=?", [input.workId]) : undefined;
76
- const native = !external;
77
- const admitted = (root) => {
78
- if (native && input.observedCwd !== root && input.literalExecutionCwd !== root)
79
- throw new AppError("memory_repair_native_copy_route_unavailable", "Native repair requires a genuinely observed child execution directory inside its owned writer copy", 1, { phase: "prepare", effect: "no_effect", recoverable: false, expected: root, actual: input.observedCwd ?? null,
80
- next_action: "Use only a documented native child/tool capability that establishes this exact directory; otherwise stop and select external delegation in a new RUN." });
81
- return { root, native };
82
- };
83
- if (work.status === "created") {
84
- // Routing consumes registered copy custody only. Actual Work start owns
85
- // physical source/copy/profile admission after the durable native Hook.
86
- const rows = (eventType) => 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", [input.projectId, eventType, input.runId, input.workId, generation]);
87
- const intents = rows("memory.repair_writer_copy_intent"), copies = rows("memory.repair_writer_copy_ready");
88
- if (intents.length !== 1 || copies.length !== 1 || Buffer.byteLength(intents[0].payload_json) > 1024 * 1024
89
- || Buffer.byteLength(copies[0].payload_json) > 12 * 1024 * 1024)
90
- refuse("owned_ready_copy_required");
91
- try {
92
- const intent = JSON.parse(intents[0].payload_json), ready = JSON.parse(copies[0].payload_json);
93
- const workspace_manifest_sha256 = ready.workspace_manifest_sha256, readyIntent = { ...ready };
94
- delete readyIntent.workspace_manifest_sha256;
95
- delete readyIntent.workspace_manifest_base64;
96
- const relative = `works/${input.workId}/writer-workspaces/generation-${generation}`;
97
- if (intent.schema_id !== "dd-flow/memory-repair-writer-copy-intent@1" || intent.project_id !== input.projectId || intent.run_id !== input.runId
98
- || intent.work_id !== input.workId || intent.generation !== generation || intent.run_root !== runRoot || !path.isAbsolute(runRoot)
99
- || intent.copy_relative !== relative || !/^[a-f0-9]{64}$/.test(intent.baseline_registration_sha256)
100
- || !/^[a-f0-9]{64}$/.test(workspace_manifest_sha256) || hybridDigest(readyIntent) !== hybridDigest(intent))
101
- refuse("owned_ready_copy_required");
102
- if (external) {
103
- if (external.provider_session_id !== input.providerSessionId)
104
- refuse("foreign_physical_session");
105
- const profile = JSON.parse(external.profile_json), metadata = profile.memory_repair_copy;
106
- if (profile.delegation?.mode !== "external" || hybridDigest(metadata) !== hybridDigest({ root: relative, copy_intent_sha256: hybridDigest(intent),
107
- workspace_manifest_sha256, baseline_registration_sha256: intent.baseline_registration_sha256 }))
108
- refuse("native_copy_cwd_mismatch");
109
- }
110
- if (fs.realpathSync(runRoot) !== path.resolve(runRoot))
111
- refuse("owned_ready_copy_required");
112
- let destination = runRoot;
113
- for (const part of relative.split('/')) {
114
- destination = path.join(destination, part);
115
- if (!fs.lstatSync(destination).isDirectory())
116
- refuse("owned_ready_copy_required");
117
- }
118
- return admitted(destination);
119
- }
120
- catch (error) {
121
- if (error instanceof AppError)
122
- throw error;
123
- return refuse("owned_ready_copy_required");
124
- }
125
- }
126
- // Running writers retain their committed directory, not today's source bytes.
127
- let binding = external;
128
- let nativeStart;
129
- if (!binding) {
130
- const rows = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type='memory.repair_native_binding' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.work_id')=? LIMIT 2", [input.projectId, input.runId, input.workId]);
131
- if (rows.length !== 1 || Buffer.byteLength(rows[0].payload_json) > 1024 * 1024)
132
- refuse("native_registered_start_changed");
133
- try {
134
- const retained = JSON.parse(rows[0].payload_json);
135
- if (retained.schema_id !== "dd-flow/memory-repair-native-binding@1" || retained.project_id !== input.projectId || retained.run_id !== input.runId || retained.work_id !== input.workId
136
- || typeof retained.work_session_id !== "string" || typeof retained.hook_event_key !== "string")
137
- refuse("native_registered_start_changed");
138
- binding = retained.binding;
139
- nativeStart = retained;
140
- }
141
- catch (error) {
142
- if (error instanceof AppError)
143
- throw error;
144
- return refuse("native_registered_start_changed");
145
- }
146
- }
147
- const starts = context.db.all("SELECT ws.id,ws.hook_event_id,ws.start_receipt_json FROM work_sessions ws JOIN sessions s ON s.project_id=? AND s.session_id=ws.session_id WHERE ws.work_id=? AND ws.status='running' AND s.run_id=? AND s.worker_id=? AND s.provider_session_id=? LIMIT 2", [input.projectId, input.workId, input.runId, input.workId, input.providerSessionId]);
148
- if (work.status !== "running" || !binding || binding.provider_session_id !== input.providerSessionId || starts.length !== 1)
149
- refuse("native_registered_start_changed");
150
- if (nativeStart && (nativeStart.work_session_id !== starts[0].id || nativeStart.hook_event_key !== starts[0].hook_event_id
151
- || binding.operation_id !== `native-hook:${nativeStart.hook_event_key}`
152
- || !context.db.get("SELECT 1 FROM hook_events WHERE project_id=? AND event_key=? AND status='claimed' AND provider_session_id=?", [input.projectId, nativeStart.hook_event_key, input.providerSessionId])))
153
- refuse("native_registered_start_changed");
154
- try {
155
- const text = starts[0].start_receipt_json;
156
- if (typeof text !== "string" || Buffer.byteLength(text) > 16 * 1024 * 1024)
157
- refuse("native_registered_start_changed");
158
- const start = JSON.parse(text), packet = JSON.parse(start.context), profile = JSON.parse(binding.profile_json), metadata = profile.memory_repair_copy;
159
- if (profile.delegation?.mode !== (native ? "native" : "external") || start.version !== 2 || createHash("sha256").update(start.context).digest("hex") !== start.context_sha256
160
- || typeof metadata?.root !== "string" || metadata.root !== `works/${input.workId}/writer-workspaces/generation-${start.generation}`
161
- || packet.work_id !== input.workId || packet.run_id !== input.runId || packet.workspace_root !== path.join(runRoot, metadata.root)
162
- || packet.memory_repair_input?.provider_session_id !== input.providerSessionId
163
- || packet.memory_repair_input?.launch_operation_id !== binding.operation_id)
164
- refuse("native_registered_start_changed");
165
- return admitted(packet.workspace_root);
166
- }
167
- catch (error) {
168
- if (error instanceof AppError)
169
- throw error;
170
- return refuse("native_registered_start_changed");
171
- }
172
- }
54
+ export { memoryRepairAssignedHookCwd } from "./hook-memory-routing.js";
173
55
  /** Native children have no external launch row. Read the immutable physical
174
56
  * Hook and copy journal instead; preparation cannot manufacture a Session. */
175
57
  export function readMemoryRepairPhysicalBinding(context, workId, route, operationId) {
@@ -57,6 +57,8 @@ export class RetainedMemoryRepairPublicationPlan {
57
57
  const writes = JSON.parse(before.bytes().toString("utf8")).write_paths;
58
58
  if (hybridDigest(writes) !== hybridDigest(JSON.parse(after.bytes().toString("utf8")).write_paths))
59
59
  refuse("write_paths_changed");
60
+ if (hybridDigest([...before.canonicalWritePaths()]) !== hybridDigest([...after.canonicalWritePaths()]))
61
+ refuse("canonical_permissions_changed");
60
62
  const current = new Map(before.entries().map(entry => [entry.path, entry]));
61
63
  const final = new Map(after.entries().map(entry => [entry.path, entry]));
62
64
  const directories = new Set();
@@ -133,11 +135,11 @@ export class RetainedMemoryRepairPublicationPlan {
133
135
  else
134
136
  entries.delete(action.path);
135
137
  }
136
- const value = { schema_id: "dd-flow/memory-repair-document-baseline@1", entries: ordered(entries.values()), write_paths: this.#writePaths };
137
- return MemoryRepairDocumentBaseline.restore(Buffer.from(JSON.stringify(value)), hybridDigest({ entries: value.entries, write_paths: value.write_paths }));
138
+ const value = { ...JSON.parse(this.#before.bytes().toString("utf8")), entries: ordered(entries.values()) };
139
+ return MemoryRepairDocumentBaseline.restore(Buffer.from(JSON.stringify(value)), hybridDigest({ ...value, schema_id: undefined }));
138
140
  }
139
141
  classify(workspace) {
140
- const actual = MemoryRepairDocumentBaseline.capture(workspace, this.#writePaths);
142
+ const actual = MemoryRepairDocumentBaseline.capture(workspace, this.#writePaths, this.#before.canonicalWritePaths());
141
143
  const entries = new Map(actual.entries().map(entry => [entry.path, entry]));
142
144
  let completed = 0, pending = false;
143
145
  for (const action of this.#receipt.actions) {
@@ -54,7 +54,7 @@ function compose(start, histories, paths) {
54
54
  for (const history of histories) {
55
55
  if (!versions.has(hybridDigest(history.before().entries())))
56
56
  refuse("unexplained_peer_input_view");
57
- current = current.composeSettledDelta(history.before(), history.after());
57
+ current = current.composeSettledDelta(history.before(), history.after(), history.result().outcome);
58
58
  versions.add(hybridDigest(current.entries()));
59
59
  }
60
60
  if (!versions.has(hybridDigest(start.entries())))
@@ -5,6 +5,7 @@ import { AppError } from "../shared/errors.js";
5
5
  import { hybridDigest } from "./hybrid-contracts.js";
6
6
  import { PreparedMemoryRepairWorkBaseline } from "./memory-repair-work-baseline.js";
7
7
  import { PreparedBoundMemoryRepairCopy } from "./memory-repair-writer-copy.js";
8
+ import { isCompletedMemoryRepairClaim } from "./memory-repair-work-results.js";
8
9
  import { appendAudit } from "./audit.js";
9
10
  import { stageRetainedMemoryRepairFile, verifyRetainedMemoryRepairFile } from "./memory-repair-publication-files.js";
10
11
  import { acquireInstallationLock } from "./installation-lock.js";
@@ -30,7 +31,7 @@ export function deriveRetainedMemoryRepairViews(start, exported, plan, result, p
30
31
  || hybridDigest(ordered(changes(privatePlan))) !== hybridDigest(ordered(result.changes)))
31
32
  refuse("reserved_delta_differs_from_result");
32
33
  const after = privatePlan.state(privatePlan.actions().length);
33
- const merged = plan.state(0).composeSettledDelta(start, after);
34
+ const merged = plan.state(0).composeSettledDelta(start, after, result.outcome);
34
35
  if (merged.sha256 !== plan.state(actions.length).sha256)
35
36
  refuse("unexplained_retained_publication_view");
36
37
  const available = new Set(after.entries().filter(entry => entry.kind === "file").map(entry => entry.path));
@@ -103,7 +104,7 @@ export class PreparedRetainedMemoryRepairPublication {
103
104
  if (bytes.length > 1024 * 1024 || bytes.toString("base64") !== receipt.result_base64 || hash(bytes) !== receipt.result_sha256)
104
105
  refuse("retained_result_changed");
105
106
  const result = baseline.validateResult(context, bytes);
106
- if (result.outcome !== "accepted")
107
+ if (!isCompletedMemoryRepairClaim(result))
107
108
  refuse("accepted_reserved_result_required");
108
109
  const { exportedAfter, deltaSha256, transitionSha256 } = deriveRetainedMemoryRepairViews(baseline.baseline(), baseline.exportedBaseline(), plan, result, privatePlan);
109
110
  const outputSha256 = hybridDigest({ input: copy.input(), start_receipt_sha256: hybridDigest(copy.startedReceipt()), work_session_id: workSessionId,
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
2
2
  import { AppError } from "../shared/errors.js";
3
3
  import { appendAudit } from "./audit.js";
4
4
  import { hybridDigest } from "./hybrid-contracts.js";
5
- import { PreparedMemoryInitialCorrectionPlan, validateMemoryReviewCorrectionPlan } from "./memory-review-correction-plan.js";
5
+ import { PreparedMemoryInitialCorrectionPlan, validateMemoryReviewCorrectionPlan, memoryRepairCanonicalPaths } from "./memory-review-correction-plan.js";
6
6
  import { PreparedRetainedMemoryReviewResults } from "./memory-retained-review-results.js";
7
7
  import { memoryReviewWaveProjection } from "./memory-review-acceptance.js";
8
8
  import { addWorkBatch, prepareWorkBatch, readWorkBatchCustody } from "./work-registry.js";
@@ -155,6 +155,7 @@ export class PreparedRegisteredMemoryCorrections {
155
155
  if (expectedBytes !== undefined && (!(expectedBytes instanceof Uint8Array) || !bytes.equals(Buffer.from(expectedBytes))))
156
156
  refuse("plan_replay_conflict");
157
157
  const plan = validateMemoryReviewCorrectionPlan(bytes, bundle.engine(), { runId: original.run_id, wave: 1,
158
+ canonicalPaths: memoryRepairCanonicalPaths(context, bundle),
158
159
  reviewSha256: review.sha256, units: original.view.unit_inventory, findings: memoryReviewWaveProjection(review.results(), original.view.diagnostic_findings).findings });
159
160
  for (const work of plan.works)
160
161
  bundle.execution.work("review", work.profile_id ?? undefined);
@@ -1,3 +1,4 @@
1
+ import { memoryRepairCanonicalPaths } from "./memory-review-correction-plan.js";
1
2
  import { createHash } from "node:crypto";
2
3
  import { AppError } from "../shared/errors.js";
3
4
  import { appendAudit } from "./audit.js";
@@ -108,7 +109,7 @@ export class PreparedMemoryRepairWorkBaseline {
108
109
  const sourceHash = () => reviewSourceHash(workspace, view.run_inputs.filter(ref => ref.root === "workspace").map(ref => ref.path), view.base);
109
110
  if (sourceHash() !== view.source_sha256)
110
111
  refuse("original_source_changed_before_writer");
111
- const baseline = MemoryRepairDocumentBaseline.capture(workspace, assigned.payload.memory_repair.write_paths);
112
+ const baseline = MemoryRepairDocumentBaseline.capture(workspace, assigned.payload.memory_repair.write_paths, memoryRepairCanonicalPaths(context, registration.review().original().bundle()));
112
113
  const selected = reviewSourcePaths(workspace, view.run_inputs.filter(ref => ref.root === "workspace").map(ref => ref.path), view.base);
113
114
  if (selected === null)
114
115
  refuse("export_source_inventory_unavailable");
@@ -351,11 +352,12 @@ export class PreparedMemoryRepairWorkBaseline {
351
352
  refuse("registered_baseline_receipt_required");
352
353
  }
353
354
  assertCustody(context) {
354
- this.#assertCurrentView(context);
355
355
  if (this.#startedWorkSessionId) {
356
+ // The registered start owner also rechecks this baseline's current view.
356
357
  this.assertRegisteredStartCustody(context, this.#startedWorkSessionId);
357
358
  return;
358
359
  }
360
+ this.#assertCurrentView(context);
359
361
  this.#registration.assertCustody(context);
360
362
  assertRunMutationAllowed(context, this.#scope.project_id, this.#scope.run_id, this.#scope.generation);
361
363
  if (hybridDigest(readWorkBatchCustody(context, this.workId)) !== hybridDigest(this.#allocation))