@deksden-com/dd-flow-cli 0.9.0-beta.136 → 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 (230) hide show
  1. package/CHANGELOG.md +12 -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 +56 -3
  7. package/dist/cli/command-option-descriptions.js +5 -0
  8. package/dist/cli/executor-onboarding.js +8 -3
  9. package/dist/cli/flows.js +29 -0
  10. package/dist/cli/help.js +240 -6
  11. package/dist/cli/input-preparation.js +54 -5
  12. package/dist/cli/memory-flow-admission.js +44 -0
  13. package/dist/cli/memory-init-invocation.js +78 -0
  14. package/dist/cli/memory-init.js +240 -0
  15. package/dist/cli/memory-storage-admission.js +53 -0
  16. package/dist/cli/native-hook-ingress.js +1 -1
  17. package/dist/cli/response-file.js +2 -0
  18. package/dist/cli/run-cli.js +283 -160
  19. package/dist/cli/setup.js +9 -7
  20. package/dist/cli.js +54 -2
  21. package/dist/domain/stage-handler-identities.js +20 -0
  22. package/dist/harness-runtime/DD-ZCODE.md +13 -0
  23. package/dist/harness-runtime/bin/dd-opencode.mjs +3 -3
  24. package/dist/harness-runtime/lib/daemon-operations.d.mts +1 -0
  25. package/dist/harness-runtime/lib/daemon-operations.mjs +4 -3
  26. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +4 -2
  27. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +11 -5
  28. package/dist/harness-runtime/lib/dd-codex.mjs +22 -10
  29. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +2 -1
  30. package/dist/harness-runtime/lib/dd-droid.mjs +2 -1
  31. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +4 -2
  32. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +20 -5
  33. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +45 -10
  34. package/dist/harness-runtime/lib/dd-zcode.mjs +7 -3
  35. package/dist/harness-runtime/lib/delegation-instructions.mjs +5 -3
  36. package/dist/harness-runtime/lib/lifecycle-command.mjs +1 -0
  37. package/dist/harness-runtime/lib/managed-daemon.d.mts +1 -0
  38. package/dist/harness-runtime/lib/managed-daemon.mjs +50 -15
  39. package/dist/harness-runtime/lib/managed-flow-env.mjs +15 -0
  40. package/dist/harness-runtime/lib/native-hook-command.mjs +1 -1
  41. package/dist/harness-runtime/lib/native-request-proof.d.mts +2 -0
  42. package/dist/harness-runtime/lib/native-request-proof.mjs +65 -0
  43. package/dist/harness-runtime/lib/opencode-plugin.mjs +5 -1
  44. package/dist/harness-runtime/lib/operation-progress.mjs +3 -1
  45. package/dist/runtime/context.js +1 -1
  46. package/dist/schemas/discovery-result.schema.json +184 -0
  47. package/dist/schemas/execution-bundle.schema.json +205 -0
  48. package/dist/schemas/execution-settings.schema.json +162 -0
  49. package/dist/schemas/flow-descriptor.schema.json +54 -0
  50. package/dist/schemas/flow-run.schema.json +22 -0
  51. package/dist/schemas/harness-catalog.schema.json +80 -0
  52. package/dist/schemas/mb-init-compose-result.schema.json +457 -0
  53. package/dist/schemas/mb-init-discover-result.schema.json +500 -0
  54. package/dist/schemas/mb-init-finish-result.schema.json +422 -0
  55. package/dist/schemas/mb-init-preflight-result.schema.json +367 -0
  56. package/dist/schemas/mb-init-review-result.schema.json +396 -0
  57. package/dist/schemas/mb-init-verify-result.schema.json +391 -0
  58. package/dist/schemas/memory-bootstrap-receipt-v2.schema.json +67 -0
  59. package/dist/schemas/memory-bootstrap-receipt.schema.json +354 -0
  60. package/dist/schemas/memory-composition-work-result.schema.json +38 -0
  61. package/dist/schemas/memory-repair-work-result.schema.json +49 -0
  62. package/dist/schemas/memory-review-correction-plan.schema.json +30 -0
  63. package/dist/schemas/memory-review-work-result.schema.json +43 -0
  64. package/dist/schemas/model-catalog.schema.json +153 -0
  65. package/dist/schemas/profile-settings.schema.json +122 -0
  66. package/dist/schemas/release-manifest.schema.json +291 -0
  67. package/dist/schemas/run-engine-binding.schema.json +1 -1
  68. package/dist/services/check-runner-evidence.js +51 -4
  69. package/dist/services/cli-operation-classifier.js +8 -47
  70. package/dist/services/code-checks.js +387 -34
  71. package/dist/services/controller-child-observer.js +99 -0
  72. package/dist/services/controller-execution.js +88 -0
  73. package/dist/services/controller-fanout.js +79 -13
  74. package/dist/services/dashboard.js +8 -2
  75. package/dist/services/descriptor-coordinator-session.js +35 -0
  76. package/dist/services/descriptor-root-work.js +53 -0
  77. package/dist/services/descriptor-run-settlement.js +105 -0
  78. package/dist/services/descriptor-run-start.js +449 -0
  79. package/dist/services/descriptor-stage-coordinator.js +197 -0
  80. package/dist/services/descriptor-stage-settlement.js +222 -0
  81. package/dist/services/descriptor-stage-start.js +139 -0
  82. package/dist/services/engines.js +39 -22
  83. package/dist/services/eval-snapshots.js +83 -25
  84. package/dist/services/execution-bundles.js +263 -0
  85. package/dist/services/execution-policy.js +20 -16
  86. package/dist/services/external-work-launch.js +91 -11
  87. package/dist/services/flow-catalog.js +21 -0
  88. package/dist/services/flow-descriptors.js +82 -0
  89. package/dist/services/frozen-engine.js +242 -0
  90. package/dist/services/generic-execution.js +164 -0
  91. package/dist/services/harness-adapter.js +28 -2
  92. package/dist/services/hooks.js +24 -6
  93. package/dist/services/hybrid-contracts.js +31 -3
  94. package/dist/services/ids.js +29 -1
  95. package/dist/services/installation-lock.js +7 -4
  96. package/dist/services/installed-flow-source.js +183 -0
  97. package/dist/services/lifecycle-invocations.js +257 -78
  98. package/dist/services/memory-accepted-composition.js +77 -0
  99. package/dist/services/memory-baseline-preservation.js +65 -0
  100. package/dist/services/memory-completed-correction-wave.js +320 -0
  101. package/dist/services/memory-compose-handler.js +258 -0
  102. package/dist/services/memory-composition-acceptance.js +199 -0
  103. package/dist/services/memory-composition-graph.js +134 -0
  104. package/dist/services/memory-composition-registration.js +146 -0
  105. package/dist/services/memory-composition-work-custody.js +151 -0
  106. package/dist/services/memory-composition-work-results.js +96 -0
  107. package/dist/services/memory-corrected-review-acceptance.js +129 -0
  108. package/dist/services/memory-corrected-review-batch.js +30 -0
  109. package/dist/services/memory-corrected-review-findings.js +106 -0
  110. package/dist/services/memory-corrected-review-inputs.js +210 -0
  111. package/dist/services/memory-corrected-review-projection.js +78 -0
  112. package/dist/services/memory-corrected-review-receipt.js +170 -0
  113. package/dist/services/memory-corrected-review-registration.js +191 -0
  114. package/dist/services/memory-correction-check-receipt.js +219 -0
  115. package/dist/services/memory-correction-input-owner.js +6 -0
  116. package/dist/services/memory-discover-handler.js +104 -0
  117. package/dist/services/memory-finish-evidence.js +435 -0
  118. package/dist/services/memory-finish-handler.js +215 -0
  119. package/dist/services/memory-finish-integration.js +280 -0
  120. package/dist/services/memory-finish-readiness.js +122 -0
  121. package/dist/services/memory-git-integration.js +151 -0
  122. package/dist/services/memory-historical-corrected-review.js +206 -0
  123. package/dist/services/memory-historical-original-review.js +130 -0
  124. package/dist/services/memory-historical-repair-registration.js +99 -0
  125. package/dist/services/memory-historical-second-review.js +128 -0
  126. package/dist/services/memory-init-bootstrap-readiness.js +210 -0
  127. package/dist/services/memory-init-contexts.js +89 -0
  128. package/dist/services/memory-init-controller-start.js +84 -0
  129. package/dist/services/memory-init-engine-release.js +223 -0
  130. package/dist/services/memory-init-git-policy.js +136 -0
  131. package/dist/services/memory-init-lint-capability.js +185 -0
  132. package/dist/services/memory-init-native-capacity.js +241 -0
  133. package/dist/services/memory-init-preflight.js +312 -0
  134. package/dist/services/memory-init-project-bootstrap.js +599 -0
  135. package/dist/services/memory-init-request-candidate.js +157 -0
  136. package/dist/services/memory-init-request-input.js +66 -0
  137. package/dist/services/memory-init-resource-storage.js +125 -0
  138. package/dist/services/memory-init-run-allocation.js +81 -0
  139. package/dist/services/memory-init-run-bundle.js +98 -0
  140. package/dist/services/memory-init-start.js +175 -0
  141. package/dist/services/memory-init-startup-custody.js +80 -0
  142. package/dist/services/memory-init-startup-input.js +240 -0
  143. package/dist/services/memory-init-storage.js +81 -0
  144. package/dist/services/memory-init-stores.js +359 -0
  145. package/dist/services/memory-init-worktrunk-capability.js +172 -0
  146. package/dist/services/memory-layer-publication.js +611 -0
  147. package/dist/services/memory-layer.js +156 -0
  148. package/dist/services/memory-lint-config.js +85 -0
  149. package/dist/services/memory-native-review-copy.js +165 -0
  150. package/dist/services/memory-normal-flow-entry.js +75 -0
  151. package/dist/services/memory-ordinary-defaults.js +112 -0
  152. package/dist/services/memory-preflight-bootstrap-custody.js +115 -0
  153. package/dist/services/memory-preflight-handler.js +259 -0
  154. package/dist/services/memory-repair-current-view.js +299 -0
  155. package/dist/services/memory-repair-document-delta.js +451 -0
  156. package/dist/services/memory-repair-history.js +491 -0
  157. package/dist/services/memory-repair-physical-binding.js +312 -0
  158. package/dist/services/memory-repair-publication-files.js +152 -0
  159. package/dist/services/memory-repair-publication-intent.js +27 -0
  160. package/dist/services/memory-repair-publication-plan.js +160 -0
  161. package/dist/services/memory-repair-publication-source.js +225 -0
  162. package/dist/services/memory-repair-publication.js +794 -0
  163. package/dist/services/memory-repair-registration.js +239 -0
  164. package/dist/services/memory-repair-work-baseline.js +413 -0
  165. package/dist/services/memory-repair-work-completion.js +97 -0
  166. package/dist/services/memory-repair-work-custody.js +73 -0
  167. package/dist/services/memory-repair-work-results.js +89 -0
  168. package/dist/services/memory-repair-workspace.js +252 -0
  169. package/dist/services/memory-repair-writer-copy.js +735 -0
  170. package/dist/services/memory-result-binding.js +173 -0
  171. package/dist/services/memory-retained-review-authority.js +129 -0
  172. package/dist/services/memory-retained-review-results.js +70 -0
  173. package/dist/services/memory-retained-second-baseline.js +102 -0
  174. package/dist/services/memory-retained-second-repairs.js +156 -0
  175. package/dist/services/memory-review-acceptance.js +214 -0
  176. package/dist/services/memory-review-correction-plan.js +171 -0
  177. package/dist/services/memory-review-handler.js +585 -0
  178. package/dist/services/memory-review-inputs.js +200 -0
  179. package/dist/services/memory-review-receipt.js +180 -0
  180. package/dist/services/memory-review-registration.js +108 -0
  181. package/dist/services/memory-review-work-custody.js +124 -0
  182. package/dist/services/memory-review-work-input.js +72 -0
  183. package/dist/services/memory-review-work-results.js +73 -0
  184. package/dist/services/memory-second-corrected-review-inputs.js +165 -0
  185. package/dist/services/memory-second-correction-plan.js +76 -0
  186. package/dist/services/memory-second-repair-baseline.js +91 -0
  187. package/dist/services/memory-second-repair-launch.js +614 -0
  188. package/dist/services/memory-second-repair-registration.js +88 -0
  189. package/dist/services/memory-second-repair-source.js +187 -0
  190. package/dist/services/memory-second-review-acceptance.js +115 -0
  191. package/dist/services/memory-source-observations.js +117 -0
  192. package/dist/services/memory-stage-dispatch.js +28 -0
  193. package/dist/services/memory-stage-results.js +199 -0
  194. package/dist/services/memory-verification-acceptance.js +135 -0
  195. package/dist/services/memory-verification-checks.js +252 -0
  196. package/dist/services/memory-verification-receipt.js +159 -0
  197. package/dist/services/memory-verify-handler.js +135 -0
  198. package/dist/services/memory-work-result-preparation.js +63 -0
  199. package/dist/services/projects.js +17 -2
  200. package/dist/services/recovery-snapshot-database.js +416 -1
  201. package/dist/services/repair-intents.js +1 -1
  202. package/dist/services/review-copy.js +10 -2
  203. package/dist/services/run-control.js +80 -9
  204. package/dist/services/run-controller-capture.js +10 -3
  205. package/dist/services/run-controller-process.js +21 -0
  206. package/dist/services/run-controller-recovery.js +3 -1
  207. package/dist/services/run-controller-state.js +1 -1
  208. package/dist/services/run-controller.js +184 -48
  209. package/dist/services/run-recovery-guard.js +2 -0
  210. package/dist/services/run-recovery.js +145 -5
  211. package/dist/services/run-start-custody.js +177 -0
  212. package/dist/services/runs.js +319 -25
  213. package/dist/services/stage-context.js +9 -1
  214. package/dist/services/stage-handlers.js +74 -0
  215. package/dist/services/stage-lifecycle.js +2 -1
  216. package/dist/services/stage-report-renderer.js +76 -0
  217. package/dist/services/stage-work-graph.js +17 -0
  218. package/dist/services/vnext-code.js +3 -3
  219. package/dist/services/vnext-execution-profile.js +2 -2
  220. package/dist/services/vnext-fanout.js +136 -10
  221. package/dist/services/vnext-merge.js +8 -3
  222. package/dist/services/vnext-plan-review.js +6 -4
  223. package/dist/services/vnext-protocolize.js +60 -2
  224. package/dist/services/work-registry.js +616 -71
  225. package/dist/services/workspace-files.js +1 -1
  226. package/dist/storage/database.js +84 -6
  227. package/dist/storage/writer-contract.js +11 -7
  228. package/package.json +13 -3
  229. package/tools/create-canon-release.mjs +33 -0
  230. package/tools/produce-hybrid-release.mjs +41 -0
@@ -0,0 +1,89 @@
1
+ import { Ajv2020 } from "ajv/dist/2020.js";
2
+ import { createHash } from "node:crypto";
3
+ import { AppError } from "../shared/errors.js";
4
+ import { AdmittedFrozenEngine } from "./frozen-engine.js";
5
+ import { memoryRepairWorkerSchema } from "./memory-repair-registration.js";
6
+ import { memorySourceKey } from "./memory-stage-results.js";
7
+ function refuse(reason) {
8
+ throw new AppError("memory_repair_work_result_invalid", "Repair result differs from registered findings and bounded write paths", 2, { reason, effect: "no_effect" });
9
+ }
10
+ /** Metadata projection, not authority. Only a separately admitted current
11
+ * view may replace the frozen original workspace references with these.
12
+ */
13
+ export function memoryRepairCurrentReferences(packet, workspace) {
14
+ return [...packet.read_inputs.filter(ref => ref.root === "run").map(ref => ({ ...ref })),
15
+ ...workspace.entries().filter(entry => entry.kind === "file").map(entry => ({ root: "workspace", path: entry.path, sha256: entry.sha256 }))];
16
+ }
17
+ /** Claim validation only. Before/after hashes are not physical evidence until
18
+ * a registered writer proves its start baseline, exact delta and publication.
19
+ * An accepted repair is not an independently accepted re-review or closure.
20
+ */
21
+ export function validateMemoryRepairWorkResult(bytes, engine, binding) {
22
+ try {
23
+ if (!(bytes instanceof Uint8Array) || !bytes.byteLength || bytes.byteLength > 1024 * 1024 || !(engine instanceof AdmittedFrozenEngine)
24
+ || !binding.workId || binding.packet?.result_schema !== memoryRepairWorkerSchema)
25
+ refuse("invalid_preparation_binding");
26
+ const schema = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(engine.schema("memory-repair-work-result")));
27
+ const value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
28
+ if (schema.$id !== memoryRepairWorkerSchema)
29
+ refuse("invalid_result_schema");
30
+ const packet = binding.packet;
31
+ if (value.schema_id !== memoryRepairWorkerSchema || value.run_id !== packet.run_id || value.work_id !== binding.workId || value.packet_sha256 !== packet.packet_sha256
32
+ || value.wave !== packet.wave)
33
+ refuse("foreign_result_identity");
34
+ const validate = new Ajv2020({ strict: true, validateFormats: false }).compile(schema);
35
+ if (!validate(value))
36
+ throw new AppError("memory_repair_work_result_invalid", "Repair result proposal does not match its schema; correct the reported fields without changing the private repair or assigned packet", 2, { reason: "invalid_result_schema", phase: "prepare", effect: "no_effect", recoverable: true,
37
+ errors: structuredClone(validate.errors), result_sha256: createHash("sha256").update(bytes).digest("hex") });
38
+ const nonblank = (text) => Boolean(text.trim()) && !text.includes("\0");
39
+ if (!nonblank(value.summary))
40
+ refuse("missing_repair_summary");
41
+ const paths = new Set();
42
+ for (const change of value.changes) {
43
+ if (!packet.write_paths.includes(change.path) || paths.has(change.path))
44
+ refuse("foreign_or_repeated_delta");
45
+ paths.add(change.path);
46
+ if (change.before_sha256 === change.after_sha256)
47
+ refuse("unchanged_repair_delta");
48
+ }
49
+ const sources = new Map((binding.effectiveInputs ?? packet.read_inputs).map(ref => [memorySourceKey(ref.root, ref.path), ref.sha256]));
50
+ for (const change of value.changes) {
51
+ const key = memorySourceKey("workspace", change.path);
52
+ sources.delete(key);
53
+ if (change.after_sha256 !== null)
54
+ sources.set(key, change.after_sha256);
55
+ }
56
+ const ids = new Set();
57
+ for (const resolution of value.resolutions) {
58
+ if (!packet.findings.some(finding => finding.id === resolution.finding_id) || ids.has(resolution.finding_id))
59
+ refuse("foreign_or_repeated_resolution");
60
+ ids.add(resolution.finding_id);
61
+ if (!nonblank(resolution.summary))
62
+ refuse("missing_resolution_summary");
63
+ if (resolution.delta_paths.some(logical => !paths.has(logical)))
64
+ refuse("resolution_without_actual_delta");
65
+ for (const ref of resolution.evidence) {
66
+ const key = memorySourceKey(ref.root, ref.path);
67
+ if (sources.get(key) !== ref.sha256)
68
+ refuse("foreign_or_stale_resolution_evidence");
69
+ }
70
+ const causal = value.changes.filter(change => resolution.delta_paths.includes(change.path));
71
+ if (causal.some(change => change.after_sha256 !== null)) {
72
+ if (!resolution.evidence.some(ref => ref.root === "workspace" && causal.some(change => change.path === ref.path && change.after_sha256 !== null && change.after_sha256 === ref.sha256)))
73
+ refuse("resolution_without_changed_output");
74
+ }
75
+ else if (!resolution.evidence.some(ref => ref.root === "run"))
76
+ refuse("deletion_without_retained_evidence");
77
+ }
78
+ if (value.outcome === "accepted" && (!value.changes.length || ids.size !== packet.findings.length))
79
+ refuse("incomplete_repair_claim");
80
+ if (value.outcome === "blocked" && (value.changes.length || value.resolutions.length))
81
+ refuse("blocked_repair_has_productive_claims");
82
+ return structuredClone(value);
83
+ }
84
+ catch (error) {
85
+ if (error instanceof AppError && ["memory_repair_work_result_invalid", "frozen_engine_invalid"].includes(error.code))
86
+ throw error;
87
+ return refuse("invalid_result_or_binding");
88
+ }
89
+ }
@@ -0,0 +1,252 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { AppError } from "../shared/errors.js";
4
+ import { hybridDigest } from "./hybrid-contracts.js";
5
+ import { ObservedMemorySources } from "./memory-source-observations.js";
6
+ import { memorySourceKey } from "./memory-stage-results.js";
7
+ import { reviewEnvironmentAllowed, reviewSourceFilter, reviewSourcePaths } from "./review-copy.js";
8
+ function refuse(reason) {
9
+ throw new AppError("memory_repair_workspace_invalid", "Private writer workspace differs from its selected source", 1, { reason, effect: "no_effect" });
10
+ }
11
+ function inventory(root) {
12
+ if (!path.isAbsolute(root) || fs.realpathSync(root) !== path.resolve(root) || !fs.lstatSync(root).isDirectory())
13
+ refuse("noncanonical_workspace");
14
+ if ((fs.lstatSync(root).mode & 0o7777) !== 0o700)
15
+ refuse("nonprivate_workspace_root");
16
+ const entries = [], aliases = new Set();
17
+ const visit = (relative, depth) => {
18
+ if (depth > 64 || relative.length > 4096 || entries.length >= 20_000)
19
+ refuse("unbounded_inventory");
20
+ const stat = fs.lstatSync(path.join(root, relative));
21
+ if (!stat.isDirectory() && !stat.isFile() || stat.isFile() && stat.nlink !== 1)
22
+ refuse("linked_or_special_entry");
23
+ const alias = relative.normalize("NFC").toLowerCase();
24
+ if (aliases.has(alias))
25
+ refuse("aliased_inventory");
26
+ aliases.add(alias);
27
+ entries.push({ path: relative, kind: stat.isDirectory() ? "directory" : "file", mode: stat.mode & 0o7777, sha256: null });
28
+ if (stat.isDirectory())
29
+ for (const name of fs.readdirSync(path.join(root, relative)).sort()) {
30
+ if (name.includes("\\") || name.includes("\0"))
31
+ refuse("unsafe_entry");
32
+ visit(`${relative}/${name}`, depth + 1);
33
+ }
34
+ };
35
+ for (const name of fs.readdirSync(root).sort())
36
+ visit(name, 0);
37
+ const observations = ObservedMemorySources.capture({ workspace: root }, entries.filter(entry => entry.kind === "file")
38
+ .map(entry => ({ root: "workspace", path: entry.path })));
39
+ const initial = structuredClone(entries);
40
+ entries.length = 0;
41
+ aliases.clear();
42
+ for (const name of fs.readdirSync(root).sort())
43
+ visit(name, 0);
44
+ observations.revalidate();
45
+ if (hybridDigest(entries) !== hybridDigest(initial))
46
+ refuse("inventory_changed_during_observation");
47
+ const hashes = observations.hashes();
48
+ return entries.map(entry => ({ ...entry, sha256: entry.kind === "file" ? hashes.get(memorySourceKey("workspace", entry.path)) : null }));
49
+ }
50
+ /** Physical selected-input copy only. Its manifest also protects non-MB
51
+ * inputs at settlement. It does not establish SQL, Session or launch custody.
52
+ */
53
+ export class PreparedMemoryRepairWorkspace {
54
+ sha256;
55
+ #entries;
56
+ #source;
57
+ #observed;
58
+ #exported;
59
+ constructor(source, entries, observed, exported) {
60
+ this.#source = source;
61
+ this.#entries = structuredClone(entries);
62
+ this.#observed = observed;
63
+ this.#exported = exported;
64
+ this.sha256 = hybridDigest(entries);
65
+ Object.freeze(this);
66
+ }
67
+ static capture(source, input, exported) {
68
+ const candidates = reviewSourcePaths(source, input.explicit_inputs, input.base);
69
+ if (!candidates)
70
+ refuse("git_source_inventory_required");
71
+ const allowed = reviewSourceFilter(source, input.explicit_inputs, input.base);
72
+ const exportedFiles = new Set(exported.entries().filter(entry => entry.kind === "file").map(entry => entry.path));
73
+ const selected = candidates.filter(logical => allowed(path.join(source, logical))
74
+ && logical.split("/").every(part => ![".git", "node_modules", ".dd-flow"].includes(part.normalize("NFC").toLowerCase()))).filter(logical => {
75
+ if (!logical.startsWith(".memory-bank/") || exportedFiles.has(logical))
76
+ return true;
77
+ // Git's cached inventory includes tracked deletions. A retained export
78
+ // can explain an absent MB file, never a present unselected object.
79
+ if (fs.lstatSync(path.join(source, logical), { throwIfNoEntry: false }))
80
+ refuse("exported_source_changed");
81
+ return false;
82
+ });
83
+ const observed = ObservedMemorySources.capture({ workspace: source }, selected.map(logical => ({ root: "workspace", path: logical })));
84
+ const hashes = observed.hashes(), entries = new Map(exported.entries().map(entry => [entry.path, entry]));
85
+ for (const logical of selected) {
86
+ const file = path.join(source, logical), stat = fs.lstatSync(file);
87
+ const entry = { path: logical, kind: "file", mode: stat.mode & 0o7777, sha256: hashes.get(memorySourceKey("workspace", logical)) };
88
+ if (logical.startsWith(".memory-bank/")) {
89
+ if (hybridDigest(entries.get(logical)) !== hybridDigest(entry))
90
+ refuse("exported_source_changed");
91
+ }
92
+ else
93
+ entries.set(logical, entry);
94
+ const parts = logical.split("/");
95
+ for (let length = 1; length < parts.length; length++) {
96
+ const parent = parts.slice(0, length).join("/");
97
+ if (!entries.has(parent))
98
+ entries.set(parent, { path: parent, kind: "directory", mode: fs.lstatSync(path.join(source, parent)).mode & 0o7777, sha256: null });
99
+ }
100
+ }
101
+ const prepared = new PreparedMemoryRepairWorkspace(source, [...entries.values()].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0), observed, exported);
102
+ if (entries.size > 20_000 || new Set([...entries.keys()].map(logical => logical.normalize("NFC").toLowerCase())).size !== entries.size)
103
+ refuse("unbounded_or_aliased_selection");
104
+ prepared.revalidateSource();
105
+ return prepared;
106
+ }
107
+ entries() { return structuredClone(this.#entries); }
108
+ bytes() {
109
+ const bytes = Buffer.from(JSON.stringify({ schema_id: "dd-flow/memory-repair-workspace-manifest@1", entries: this.#entries }));
110
+ if (bytes.length > 8 * 1024 * 1024)
111
+ refuse("unbounded_manifest_receipt");
112
+ return bytes;
113
+ }
114
+ /** Pure restoration for later start/settlement from independently retained
115
+ * SQL bytes. It cannot create a copy or claim fresh source observations.
116
+ */
117
+ static restore(bytes, expectedSha256, exported) {
118
+ try {
119
+ if (!(bytes instanceof Uint8Array) || bytes.length > 8 * 1024 * 1024)
120
+ refuse("invalid_manifest_receipt");
121
+ const value = JSON.parse(Buffer.from(bytes).toString("utf8"));
122
+ if (!value || Object.keys(value).sort().join(",") !== "entries,schema_id"
123
+ || value.schema_id !== "dd-flow/memory-repair-workspace-manifest@1" || !Array.isArray(value.entries)
124
+ || value.entries.length > 20_000)
125
+ refuse("invalid_manifest_receipt");
126
+ const entries = [], byPath = new Map(), aliases = new Set();
127
+ for (const entry of value.entries) {
128
+ if (!entry || Object.keys(entry).sort().join(",") !== "kind,mode,path,sha256" || typeof entry.path !== "string"
129
+ || !entry.path || entry.path.length > 4096 || entry.path.includes("\\") || entry.path.includes("\0")
130
+ || entry.path.split("/").length > 65
131
+ || path.posix.isAbsolute(entry.path) || path.win32.isAbsolute(entry.path)
132
+ || entry.path.split("/").some((part) => !part || part === "." || part === ".."
133
+ || [".git", "node_modules", ".dd-flow"].includes(part.normalize("NFC").toLowerCase()))
134
+ || !reviewEnvironmentAllowed(entry.path) || !["file", "directory"].includes(entry.kind)
135
+ || !Number.isSafeInteger(entry.mode) || entry.mode < 0 || entry.mode > 0o7777
136
+ || (entry.kind === "file" ? typeof entry.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(entry.sha256) : entry.sha256 !== null))
137
+ refuse("invalid_manifest_receipt");
138
+ const alias = entry.path.normalize("NFC").toLowerCase();
139
+ if (aliases.has(alias))
140
+ refuse("invalid_manifest_receipt");
141
+ aliases.add(alias);
142
+ entries.push({ path: entry.path, kind: entry.kind, mode: entry.mode, sha256: entry.sha256 });
143
+ byPath.set(entry.path, entry);
144
+ }
145
+ for (const entry of entries) {
146
+ const parts = entry.path.split("/");
147
+ for (let length = 1; length < parts.length; length++)
148
+ if (byPath.get(parts.slice(0, length).join("/"))?.kind !== "directory")
149
+ refuse("invalid_manifest_receipt");
150
+ }
151
+ const prepared = new PreparedMemoryRepairWorkspace(null, entries, null, exported);
152
+ if (entries.some((entry, index) => index > 0 && entries[index - 1].path >= entry.path))
153
+ refuse("invalid_manifest_receipt");
154
+ const mb = entries.filter(entry => entry.path === ".memory-bank" || entry.path.startsWith(".memory-bank/"));
155
+ if (prepared.sha256 !== expectedSha256 || !prepared.bytes().equals(Buffer.from(bytes))
156
+ || hybridDigest(mb) !== hybridDigest(exported.entries().sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0)))
157
+ refuse("manifest_receipt_changed");
158
+ return prepared;
159
+ }
160
+ catch (error) {
161
+ if (error instanceof AppError && error.code === "memory_repair_workspace_invalid")
162
+ throw error;
163
+ return refuse("invalid_manifest_receipt");
164
+ }
165
+ }
166
+ revalidateSource() {
167
+ if (!this.#observed || !this.#source)
168
+ refuse("source_observation_required");
169
+ this.#observed.revalidate();
170
+ for (const entry of this.#entries) {
171
+ const stat = fs.lstatSync(path.join(this.#source, entry.path));
172
+ if ((stat.mode & 0o7777) !== entry.mode || (entry.kind === "directory" ? !stat.isDirectory() : !stat.isFile() || stat.nlink !== 1))
173
+ refuse("source_metadata_changed");
174
+ }
175
+ }
176
+ verify(root) {
177
+ const actual = inventory(root).sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
178
+ if (hybridDigest(actual) !== this.sha256)
179
+ refuse("copy_changed_or_incomplete");
180
+ this.#exported.revalidate(root);
181
+ }
182
+ /** MB changes are separately admitted by the registered physical delta
183
+ * owner. Nothing elsewhere in the private workspace may change or appear.
184
+ */
185
+ verifyUnchangedInputs(root) {
186
+ const outsideMemory = (entry) => entry.path !== ".memory-bank" && !entry.path.startsWith(".memory-bank/");
187
+ const actual = inventory(root).filter(outsideMemory).sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
188
+ if (hybridDigest(actual) !== hybridDigest(this.#entries.filter(outsideMemory)))
189
+ refuse("non_memory_input_changed");
190
+ }
191
+ /** Verify shared selected inputs against this immutable start manifest.
192
+ * A separate publication owner must admit the expected MB prefix; this
193
+ * physical helper neither grants that admission nor writes any document.
194
+ */
195
+ verifySelectedSourceInputs(source, input, expected) {
196
+ expected.revalidate(source);
197
+ const candidates = reviewSourcePaths(source, input.explicit_inputs, input.base);
198
+ if (!candidates)
199
+ refuse("git_source_inventory_required");
200
+ const available = new Set(expected.entries().filter(entry => entry.kind === "file").map(entry => entry.path));
201
+ const exported = expected.exported(candidates.filter(logical => logical.startsWith(".memory-bank/") && available.has(logical)));
202
+ const fresh = PreparedMemoryRepairWorkspace.capture(source, input, exported);
203
+ const outsideMemory = (entry) => entry.path !== ".memory-bank" && !entry.path.startsWith(".memory-bank/");
204
+ if (hybridDigest(fresh.entries().filter(outsideMemory)) !== hybridDigest(this.#entries.filter(outsideMemory)))
205
+ refuse("shared_non_memory_input_changed");
206
+ expected.revalidate(source);
207
+ }
208
+ create(root) {
209
+ this.revalidateSource();
210
+ if (!path.isAbsolute(root) || fs.realpathSync(path.dirname(root)) !== path.dirname(root))
211
+ refuse("noncanonical_copy_parent");
212
+ if (fs.lstatSync(root, { throwIfNoEntry: false }))
213
+ refuse("copy_already_exists");
214
+ fs.mkdirSync(root, { mode: 0o700 });
215
+ this.#populate(root, new Set());
216
+ }
217
+ /** Only an independently journaled, unstarted build may call this. Exact
218
+ * retained files are never rewritten; unknown or partial file bytes refuse.
219
+ */
220
+ resume(root) {
221
+ this.revalidateSource();
222
+ const partial = inventory(root), expected = new Map(this.#entries.map(entry => [entry.path, entry]));
223
+ for (const entry of partial) {
224
+ const original = expected.get(entry.path);
225
+ if (!original || original.kind !== entry.kind || original.sha256 !== entry.sha256
226
+ || entry.mode !== original.mode && entry.mode !== (entry.kind === "file" ? 0o600 : 0o700))
227
+ refuse("foreign_or_changed_partial_copy");
228
+ }
229
+ this.#populate(root, new Set(partial.map(entry => entry.path)));
230
+ }
231
+ #populate(root, retained) {
232
+ try {
233
+ for (const entry of this.#entries.filter(entry => entry.kind === "directory").sort((a, b) => a.path.split("/").length - b.path.split("/").length)) {
234
+ if (!retained.has(entry.path))
235
+ fs.mkdirSync(path.join(root, entry.path), { mode: 0o700 });
236
+ }
237
+ for (const entry of this.#entries.filter(entry => entry.kind === "file")) {
238
+ const file = path.join(root, entry.path);
239
+ if (!retained.has(entry.path))
240
+ fs.writeFileSync(file, this.#observed.bytes({ root: "workspace", path: entry.path }), { flag: "wx", mode: 0o600 });
241
+ fs.chmodSync(file, entry.mode);
242
+ }
243
+ for (const entry of this.#entries.filter(entry => entry.kind === "directory").reverse())
244
+ fs.chmodSync(path.join(root, entry.path), entry.mode);
245
+ this.revalidateSource();
246
+ this.verify(root);
247
+ }
248
+ catch (error) {
249
+ throw new AppError("memory_repair_workspace_publication_pending", "Private copy was created but is not admitted; retain it for owned recovery", 1, { effect: "publication_pending", copy_root: root, cause: error instanceof Error ? error.message : String(error) });
250
+ }
251
+ }
252
+ }