@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,359 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { createHash } from "node:crypto";
4
+ import { createContext } from "../runtime/context.js";
5
+ import { getDatabase, getResourceDatabase } from "../storage/database.js";
6
+ import { canonicalPath, resolveDdFlowHome } from "../storage/paths.js";
7
+ import { assertImportAdmission } from "../storage/import-guard.js";
8
+ import { publishImmutableInput } from "../storage/immutable-input.js";
9
+ import { withStoreCleanup } from "../storage/native-cleanup.js";
10
+ import { AppError, errorRecord } from "../shared/errors.js";
11
+ import { hybridDigest } from "./hybrid-contracts.js";
12
+ import { readBoundedReleaseFile } from "./release-source.js";
13
+ import { validateMemoryInitStartupScope } from "./memory-init-startup-custody.js";
14
+ import { PreparedMemoryInitRuntimeStorage } from "./memory-init-storage.js";
15
+ import { PreparedMemoryInitResourceStorage } from "./memory-init-resource-storage.js";
16
+ const limit = 64 * 1024 * 1024;
17
+ const bytes = (value) => Buffer.from(JSON.stringify(value));
18
+ const hash = (value) => createHash("sha256").update(value).digest("hex");
19
+ function refuse(reason) {
20
+ throw new AppError("memory_init_stores_invalid", "Init stores require exact creation ownership or existing prepared storage", 1, { reason, effect: "no_effect", next_action: "reconcile_exact_store_owner" });
21
+ }
22
+ function exists(file) {
23
+ try {
24
+ fs.lstatSync(file);
25
+ return true;
26
+ }
27
+ catch (error) {
28
+ if (error.code === "ENOENT")
29
+ return false;
30
+ throw error;
31
+ }
32
+ }
33
+ function directory(file) {
34
+ const stat = fs.lstatSync(file);
35
+ if (!stat.isDirectory() || fs.realpathSync(file) !== file)
36
+ refuse("linked_or_unavailable_directory");
37
+ return stat;
38
+ }
39
+ function sync(file) {
40
+ const descriptor = fs.openSync(file, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0));
41
+ try {
42
+ fs.fsyncSync(descriptor);
43
+ }
44
+ finally {
45
+ fs.closeSync(descriptor);
46
+ }
47
+ }
48
+ function regular(file) {
49
+ let stat;
50
+ try {
51
+ stat = fs.lstatSync(file);
52
+ }
53
+ catch (error) {
54
+ if (error.code === "ENOENT")
55
+ return refuse("store_owner_missing");
56
+ throw error;
57
+ }
58
+ if (!stat.isFile() || stat.nlink < 1 || stat.nlink > 2)
59
+ refuse("linked_or_unavailable_store");
60
+ return stat;
61
+ }
62
+ function read(file) { return JSON.parse(readBoundedReleaseFile(file, 16 * 1024).toString("utf8")); }
63
+ function owner(file) {
64
+ const stat = regular(file);
65
+ sync(file);
66
+ const content = readBoundedReleaseFile(file, limit), current = regular(file);
67
+ if (stat.dev !== current.dev || stat.ino !== current.ino || stat.size !== content.length || current.size !== content.length)
68
+ refuse("staged_store_changed");
69
+ return { schema_id: "dd-flow/memory-init-staged-store@1", dev: stat.dev, ino: stat.ino, bytes: content.length, sha256: hash(content) };
70
+ }
71
+ function readOwner(file) {
72
+ const value = read(file);
73
+ if (!value || Object.keys(value).sort().join() !== "bytes,dev,ino,schema_id,sha256"
74
+ || value.schema_id !== "dd-flow/memory-init-staged-store@1" || !Number.isSafeInteger(value.dev) || value.dev < 0
75
+ || !Number.isSafeInteger(value.ino) || value.ino <= 0 || !Number.isSafeInteger(value.bytes) || value.bytes <= 0 || value.bytes > limit
76
+ || typeof value.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(value.sha256))
77
+ refuse("invalid_staged_store_owner");
78
+ return value;
79
+ }
80
+ function matches(file, expected, immutable) {
81
+ const actual = regular(file);
82
+ if (actual.dev !== expected.dev || actual.ino !== expected.ino
83
+ || immutable && (actual.size !== expected.bytes || hash(readBoundedReleaseFile(file, limit)) !== expected.sha256))
84
+ refuse("store_owner_changed");
85
+ }
86
+ function pristine(file) {
87
+ if (exists(file))
88
+ refuse("foreign_store_footprint");
89
+ noSidecars(file);
90
+ }
91
+ function noSidecars(file) {
92
+ for (const suffix of ["-wal", "-shm", "-journal"])
93
+ if (exists(`${file}${suffix}`))
94
+ refuse("foreign_store_footprint");
95
+ }
96
+ function inside(root, candidate) {
97
+ const relative = path.relative(root, candidate);
98
+ return relative === "" || relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
99
+ }
100
+ function freshHome(home) {
101
+ pristine(path.join(home, "db.sqlite"));
102
+ const namespace = path.join(home, "memory-init-store-creations");
103
+ if (["projects", "memory-layer-publications"].some(name => exists(path.join(home, name)))
104
+ || exists(namespace) && (directory(namespace), fs.readdirSync(namespace).length))
105
+ refuse("historical_home_missing_stores");
106
+ }
107
+ function existingResource(env) {
108
+ const home = resolveDdFlowHome(env), resource = env.DD_FLOW_RESOURCE_HOME ?? home;
109
+ directory(resource);
110
+ try {
111
+ fs.accessSync(resource, fs.constants.R_OK | fs.constants.W_OK | fs.constants.X_OK);
112
+ fs.accessSync(path.join(resource, "runtime.sqlite"), fs.constants.R_OK | fs.constants.W_OK);
113
+ }
114
+ catch {
115
+ return refuse("store_access_unavailable");
116
+ }
117
+ const facts = PreparedMemoryInitResourceStorage.prepare({ ddFlowHome: home, env }).facts();
118
+ return { dev: facts.store_dev, ino: facts.store_ino };
119
+ }
120
+ function admit(env, created) {
121
+ const home = resolveDdFlowHome(env), resourceHome = env.DD_FLOW_RESOURCE_HOME ?? home;
122
+ directory(resourceHome);
123
+ if (regular(path.join(home, "db.sqlite")).nlink !== 1 || regular(path.join(resourceHome, "runtime.sqlite")).nlink !== 1)
124
+ refuse("linked_published_store");
125
+ try {
126
+ for (const dir of new Set([home, resourceHome]))
127
+ fs.accessSync(dir, fs.constants.R_OK | fs.constants.W_OK | fs.constants.X_OK);
128
+ for (const file of [path.join(home, "db.sqlite"), path.join(resourceHome, "runtime.sqlite")])
129
+ fs.accessSync(file, fs.constants.R_OK | fs.constants.W_OK);
130
+ }
131
+ catch {
132
+ return refuse("store_access_unavailable");
133
+ }
134
+ const context = createContext(env, "prepared_existing", undefined, { resourceDatabaseMode: "prepared_existing" });
135
+ let admitted = false;
136
+ return withStoreCleanup(() => {
137
+ PreparedMemoryInitRuntimeStorage.prepare(context);
138
+ PreparedMemoryInitResourceStorage.prepare(context);
139
+ admitted = true;
140
+ return { context, created };
141
+ }, () => { if (!admitted)
142
+ context.db.close?.(); }, () => ({ store_path: context.db.path }));
143
+ }
144
+ /** Native qualification must discover an already incompatible/partial store
145
+ * before provider effects. Missing pristine stores remain the normal fresh
146
+ * startup owner's responsibility; this function never initializes them. */
147
+ export function assertMemoryInitStorePrerequisites(env, scope) {
148
+ validateMemoryInitStartupScope(scope);
149
+ const home = resolveDdFlowHome(env), resource = env.DD_FLOW_RESOURCE_HOME ?? home;
150
+ directory(home);
151
+ if (!path.isAbsolute(resource) || canonicalPath(resource, false) !== resource
152
+ || [scope.projectRoot, scope.workspaceRoot].some(root => inside(root, home) || inside(root, resource)))
153
+ refuse("foreign_store_scope");
154
+ assertImportAdmission(home);
155
+ assertImportAdmission(resource);
156
+ const runFile = path.join(home, "db.sqlite"), resourceFile = path.join(resource, "runtime.sqlite");
157
+ if (!exists(runFile) && exists(resourceFile)) {
158
+ freshHome(home);
159
+ existingResource(env);
160
+ return;
161
+ }
162
+ if (exists(runFile) !== exists(resourceFile))
163
+ refuse("partial_existing_store_pair");
164
+ if (exists(runFile)) {
165
+ const checked = admit(env, false);
166
+ checked.context.db.close?.();
167
+ }
168
+ else {
169
+ pristine(runFile);
170
+ pristine(resourceFile);
171
+ }
172
+ }
173
+ /** Fresh RUN publication and creation or reuse of the shared resource store.
174
+ * The caller supplies the digest of its independently prepared raw selection.
175
+ * No initializer ever opens an existing target. Incomplete publication may
176
+ * resume only from the original journal and the same staged file identities.
177
+ * Lost staging/receipts remain unknown; ready historical stores are never
178
+ * recreated. Creating both stores requires one filesystem; a reused prepared
179
+ * registry may live separately. The caller must close the returned connection. */
180
+ export function openMemoryInitStores(env, request) {
181
+ if (!request || Object.keys(request).sort().join() !== "scope,selectionSha256")
182
+ refuse("invalid_store_request");
183
+ validateMemoryInitStartupScope(request.scope);
184
+ if (typeof request.selectionSha256 !== "string" || !/^[a-f0-9]{64}$/.test(request.selectionSha256))
185
+ refuse("invalid_selection_digest");
186
+ const home = resolveDdFlowHome(env), resourceHome = env.DD_FLOW_RESOURCE_HOME ?? home;
187
+ if (!path.isAbsolute(resourceHome) || path.resolve(resourceHome) !== resourceHome || canonicalPath(resourceHome, false) !== resourceHome)
188
+ refuse("resource_home_not_canonical");
189
+ directory(home);
190
+ if ([request.scope.projectRoot, request.scope.workspaceRoot].some(root => inside(root, home) || inside(root, resourceHome)))
191
+ refuse("stores_inside_project");
192
+ assertImportAdmission(home);
193
+ assertImportAdmission(resourceHome);
194
+ const run = path.join(home, "db.sqlite"), resource = path.join(resourceHome, "runtime.sqlite");
195
+ const namespace = path.join(home, "memory-init-store-creations"), journal = path.join(namespace, hybridDigest([home, resourceHome]));
196
+ const intentFile = path.join(journal, "intent.json"), readyFile = path.join(journal, "ready.json");
197
+ let sharedResource;
198
+ if (exists(namespace))
199
+ directory(namespace);
200
+ if (!exists(journal)) {
201
+ if (exists(run) && exists(resource)) {
202
+ if (exists(namespace) && !fs.readdirSync(namespace).length)
203
+ refuse("creation_journal_missing");
204
+ return admit(env, false);
205
+ }
206
+ if (exists(run))
207
+ refuse("partial_existing_store_pair");
208
+ freshHome(home);
209
+ if (exists(resource))
210
+ sharedResource = existingResource(env);
211
+ else
212
+ pristine(resource);
213
+ let parent = resourceHome;
214
+ while (!exists(parent))
215
+ parent = path.dirname(parent);
216
+ if (!sharedResource && directory(parent).dev !== directory(home).dev)
217
+ refuse("cross_device_fresh_publication_unavailable");
218
+ }
219
+ let started = false;
220
+ try {
221
+ if (!exists(namespace)) {
222
+ fs.mkdirSync(namespace, { mode: 0o700 });
223
+ started = true;
224
+ sync(home);
225
+ }
226
+ directory(namespace);
227
+ if (!exists(journal)) {
228
+ fs.mkdirSync(journal, { mode: 0o700 });
229
+ started = true;
230
+ sync(namespace);
231
+ const stat = directory(journal);
232
+ const intent = { schema_id: "dd-flow/memory-init-store-creation@1", home, resource_home: resourceHome,
233
+ scope: request.scope, selection_sha256: request.selectionSha256, journal_owner: { dev: stat.dev, ino: stat.ino },
234
+ ...(sharedResource ? { resource_existing: sharedResource } : {}) };
235
+ publishImmutableInput(intentFile, bytes(intent), "memory_init_store_request_conflict");
236
+ }
237
+ const stat = directory(journal);
238
+ if (!exists(intentFile))
239
+ refuse("creation_intent_missing");
240
+ const intent = read(intentFile);
241
+ if (!intent || Object.keys(intent).sort().join() !== (intent.resource_existing
242
+ ? "home,journal_owner,resource_existing,resource_home,schema_id,scope,selection_sha256"
243
+ : "home,journal_owner,resource_home,schema_id,scope,selection_sha256")
244
+ || intent.schema_id !== "dd-flow/memory-init-store-creation@1" || intent.home !== home || intent.resource_home !== resourceHome
245
+ || !intent.journal_owner || Object.keys(intent.journal_owner).sort().join() !== "dev,ino"
246
+ || intent.journal_owner?.dev !== stat.dev || intent.journal_owner?.ino !== stat.ino
247
+ || typeof intent.selection_sha256 !== "string" || !/^[a-f0-9]{64}$/.test(intent.selection_sha256))
248
+ refuse("creation_intent_changed");
249
+ validateMemoryInitStartupScope(intent.scope);
250
+ const assertExistingResource = () => {
251
+ if (!intent.resource_existing)
252
+ return;
253
+ const expected = intent.resource_existing;
254
+ if (Object.keys(expected).sort().join() !== "dev,ino" || !Number.isSafeInteger(expected.dev) || expected.dev < 0
255
+ || !Number.isSafeInteger(expected.ino) || expected.ino <= 0
256
+ || hybridDigest(existingResource(env)) !== hybridDigest(expected))
257
+ refuse("store_owner_changed");
258
+ };
259
+ assertExistingResource();
260
+ const ready = { schema_id: "dd-flow/memory-init-store-ready@1", intent_sha256: hybridDigest(intent),
261
+ run: path.join(journal, "run-ready.json"), resource: intent.resource_existing ?? path.join(journal, "resource-ready.json") };
262
+ if (exists(readyFile)) {
263
+ if (hybridDigest(read(readyFile)) !== hybridDigest(ready))
264
+ refuse("store_ready_receipt_changed");
265
+ matches(run, readOwner(ready.run), false);
266
+ if (typeof ready.resource === "string")
267
+ matches(resource, readOwner(ready.resource), false);
268
+ if (regular(run).nlink !== 1 || regular(resource).nlink !== 1)
269
+ refuse("linked_published_store");
270
+ return admit(env, false);
271
+ }
272
+ if (hybridDigest(intent.scope) !== hybridDigest(request.scope) || intent.selection_sha256 !== request.selectionSha256)
273
+ refuse("foreign_creation_request");
274
+ started = true;
275
+ const assertJournalOwner = () => {
276
+ directory(home);
277
+ directory(namespace);
278
+ const current = directory(journal);
279
+ if (current.dev !== intent.journal_owner.dev || current.ino !== intent.journal_owner.ino)
280
+ refuse("creation_intent_changed");
281
+ };
282
+ const entries = [
283
+ { staged: path.join(journal, "db.sqlite"), target: run, receipt: ready.run,
284
+ create: () => getDatabase(journal, "create_new") },
285
+ ...(typeof ready.resource === "string" ? [{ staged: path.join(journal, "runtime.sqlite"), target: resource, receipt: ready.resource,
286
+ create: () => getResourceDatabase(journal, "create_new") }] : [])
287
+ ];
288
+ // New stores have durable staged identities; a reused registry stays mutable.
289
+ for (const entry of entries) {
290
+ assertJournalOwner();
291
+ if (!exists(entry.receipt)) {
292
+ pristine(entry.target);
293
+ pristine(entry.staged);
294
+ const database = entry.create();
295
+ database.close?.();
296
+ assertJournalOwner();
297
+ publishImmutableInput(entry.receipt, bytes(owner(entry.staged)), "memory_init_store_owner_conflict");
298
+ }
299
+ const expected = readOwner(entry.receipt);
300
+ noSidecars(entry.staged);
301
+ if (exists(entry.staged))
302
+ matches(entry.staged, expected, true);
303
+ else if (!exists(entry.target))
304
+ refuse("staged_store_missing");
305
+ }
306
+ if (!exists(resourceHome)) {
307
+ const missing = [];
308
+ let parent = resourceHome;
309
+ while (!exists(parent)) {
310
+ missing.push(parent);
311
+ parent = path.dirname(parent);
312
+ }
313
+ fs.mkdirSync(resourceHome, { recursive: true, mode: 0o700 });
314
+ for (const created of missing.reverse()) {
315
+ directory(created);
316
+ sync(path.dirname(created));
317
+ }
318
+ }
319
+ directory(resourceHome);
320
+ for (const entry of entries) {
321
+ assertJournalOwner();
322
+ directory(resourceHome);
323
+ assertExistingResource();
324
+ const expected = readOwner(entry.receipt);
325
+ noSidecars(entry.target);
326
+ if (!exists(entry.target)) {
327
+ pristine(entry.target);
328
+ if (!exists(entry.staged))
329
+ refuse("staged_store_missing");
330
+ try {
331
+ fs.linkSync(entry.staged, entry.target);
332
+ }
333
+ catch (error) {
334
+ if (error.code !== "EEXIST")
335
+ throw error;
336
+ }
337
+ }
338
+ matches(entry.target, expected, true);
339
+ sync(path.dirname(entry.target));
340
+ if (exists(entry.staged)) {
341
+ matches(entry.staged, expected, true);
342
+ fs.unlinkSync(entry.staged);
343
+ sync(journal);
344
+ }
345
+ if (regular(entry.target).nlink !== 1)
346
+ refuse("linked_published_store");
347
+ matches(entry.target, expected, true);
348
+ }
349
+ assertJournalOwner();
350
+ assertExistingResource();
351
+ publishImmutableInput(readyFile, bytes(ready), "memory_init_store_ready_conflict");
352
+ return admit(env, true);
353
+ }
354
+ catch (error) {
355
+ if (!started)
356
+ throw error;
357
+ throw new AppError("memory_init_store_creation_incomplete", "Store publication requires exact-request reconciliation", 1, { effect: "partial", journal_path: journal, cause: errorRecord(error), next_action: "reconcile_exact_store_owner" });
358
+ }
359
+ }
@@ -0,0 +1,172 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { spawnSync } from "node:child_process";
5
+ import { createHash } from "node:crypto";
6
+ import { AppError } from "../shared/errors.js";
7
+ import { hybridDigest } from "./hybrid-contracts.js";
8
+ import { PreparedMemoryInitTarget } from "./memory-init-preflight.js";
9
+ import { readBoundedReleaseFile } from "./release-source.js";
10
+ const hash = (bytes) => createHash("sha256").update(bytes).digest("hex");
11
+ function refuse(reason) {
12
+ throw new AppError("memory_init_worktrunk_capability_failed", "Selected Worktrunk cannot create the required workspace", 1, { reason, effect: "no_effect", next_action: "repair_prerequisite_then_retry" });
13
+ }
14
+ /** Historical custody shape only; it does not run or qualify an executable. */
15
+ export function isMemoryInitWorktrunkCapabilityFacts(value) {
16
+ if (!value || typeof value !== "object" || Array.isArray(value))
17
+ return false;
18
+ const facts = value;
19
+ return Object.keys(facts).length === 7 && facts.schema_id === "dd-flow/memory-init-worktrunk-capability@1"
20
+ && typeof facts.executable === "string" && path.isAbsolute(facts.executable)
21
+ && typeof facts.git_executable === "string" && path.isAbsolute(facts.git_executable)
22
+ && typeof facts.version === "string" && /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(facts.version)
23
+ && ["executable_sha256", "git_executable_sha256", "behavior_sha256"].every(key => typeof facts[key] === "string" && /^[a-f0-9]{64}$/.test(facts[key]));
24
+ }
25
+ /** Functional prerequisite, not productive workspace creation or provider readiness.
26
+ * Only an owned temporary Git repository is mutated. Explicit user/system/project
27
+ * configs, empty templates/hooks and private XDG directories exclude operator
28
+ * configuration. Raw probe paths/output never enter retained stable facts.
29
+ */
30
+ export class PreparedMemoryInitWorktrunkCapability {
31
+ sha256;
32
+ #facts;
33
+ constructor(facts) {
34
+ this.#facts = facts;
35
+ this.sha256 = hybridDigest(facts);
36
+ Object.freeze(this);
37
+ }
38
+ static prepare(target, env) {
39
+ if (!(target instanceof PreparedMemoryInitTarget))
40
+ refuse("unprepared_target");
41
+ const selected = target.facts(), probe = selected.tool_probes?.wt, gitProbe = selected.tool_probes?.git;
42
+ if (!probe || !gitProbe)
43
+ refuse("missing_selected_tool_probe");
44
+ const checksum = (file) => {
45
+ try {
46
+ return hash(readBoundedReleaseFile(file, 64 * 1024 * 1024));
47
+ }
48
+ catch {
49
+ return refuse("selected_executable_unavailable");
50
+ }
51
+ };
52
+ const unchanged = () => {
53
+ if (checksum(selected.tools.wt) !== probe.executable_sha256 || checksum(selected.tools.git) !== gitProbe.executable_sha256)
54
+ refuse("selected_executable_changed");
55
+ };
56
+ unchanged();
57
+ let scratch, owner, primary, prepared;
58
+ try {
59
+ scratch = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), "dd-flow-init-wt-")));
60
+ owner = fs.lstatSync(scratch);
61
+ const ownRoot = () => {
62
+ const actual = fs.lstatSync(scratch);
63
+ if (!actual.isDirectory() || actual.dev !== owner.dev || actual.ino !== owner.ino)
64
+ refuse("probe_root_owner_changed");
65
+ };
66
+ const repo = path.join(scratch, "repo"), workspace = path.join(scratch, "workspace"), bin = path.join(scratch, "bin"), hooks = path.join(scratch, "hooks");
67
+ for (const name of ["repo", "bin", "hooks", "template", "config", "cache", "data", "state", "tmp"])
68
+ fs.mkdirSync(path.join(scratch, name), { mode: 0o700 });
69
+ fs.symlinkSync(selected.tools.git, path.join(bin, process.platform === "win32" ? "git.exe" : "git"));
70
+ const config = path.join(scratch, "config", "empty.toml"), gitConfig = path.join(scratch, "config", "git.config");
71
+ fs.writeFileSync(config, "", { flag: "wx", mode: 0o600 });
72
+ fs.writeFileSync(gitConfig, "", { flag: "wx", mode: 0o600 });
73
+ // No ambient WORKTRUNK_* or GIT_* selection/configuration survives.
74
+ const baseEnv = Object.fromEntries(Object.entries(env).filter(([key]) => !key.startsWith("GIT_") && !key.startsWith("WORKTRUNK_") && key !== "RUST_LOG"));
75
+ const probeEnv = { ...baseEnv, PATH: bin + path.delimiter + path.dirname(selected.tools.git),
76
+ TMPDIR: path.join(scratch, "tmp"), TMP: path.join(scratch, "tmp"), TEMP: path.join(scratch, "tmp"),
77
+ XDG_CONFIG_HOME: path.join(scratch, "config"), XDG_CACHE_HOME: path.join(scratch, "cache"), XDG_DATA_HOME: path.join(scratch, "data"), XDG_STATE_HOME: path.join(scratch, "state"), XDG_CONFIG_DIRS: path.join(scratch, "config"),
78
+ WORKTRUNK_CONFIG_PATH: config, WORKTRUNK_SYSTEM_CONFIG_PATH: config, WORKTRUNK_PROJECT_CONFIG_PATH: config, WORKTRUNK_VERBOSE: "0",
79
+ GIT_CONFIG_GLOBAL: gitConfig, GIT_CONFIG_SYSTEM: gitConfig, GIT_CONFIG_NOSYSTEM: "1", GIT_OPTIONAL_LOCKS: "0",
80
+ GIT_CONFIG_COUNT: "2", GIT_CONFIG_KEY_0: "core.hooksPath", GIT_CONFIG_VALUE_0: hooks, GIT_CONFIG_KEY_1: "core.fsmonitor", GIT_CONFIG_VALUE_1: "false",
81
+ GIT_AUTHOR_NAME: "dd-flow isolated probe", GIT_AUTHOR_EMAIL: "probe@example.invalid", GIT_COMMITTER_NAME: "dd-flow isolated probe", GIT_COMMITTER_EMAIL: "probe@example.invalid",
82
+ GIT_AUTHOR_DATE: "2000-01-01T00:00:00Z", GIT_COMMITTER_DATE: "2000-01-01T00:00:00Z", NO_COLOR: "1", CLICOLOR_FORCE: "0" };
83
+ const run = (file, args) => {
84
+ ownRoot();
85
+ unchanged();
86
+ const result = spawnSync(file, args, { cwd: repo, env: probeEnv, shell: false, windowsHide: true, timeout: 5000, maxBuffer: 256 * 1024 });
87
+ if (result.error || result.signal || result.status !== 0)
88
+ refuse("worktrunk_probe_execution_failed");
89
+ ownRoot();
90
+ unchanged();
91
+ return new TextDecoder("utf-8", { fatal: true }).decode(result.stdout);
92
+ };
93
+ const git = (directory, args) => run(selected.tools.git, ["-C", directory, ...args]).trim();
94
+ git(repo, ["init", "--initial-branch=probe-main", `--template=${path.join(scratch, "template")}`]);
95
+ const seed = Buffer.from("dd-flow isolated Worktrunk capability\n");
96
+ fs.writeFileSync(path.join(repo, "probe.txt"), seed, { flag: "wx", mode: 0o600 });
97
+ git(repo, ["add", "--", "probe.txt"]);
98
+ git(repo, ["-c", "commit.gpgSign=false", "commit", "-m", "Isolated Worktrunk capability"]);
99
+ const head = git(repo, ["rev-parse", "--verify", "HEAD"]);
100
+ const output = run(selected.tools.wt, ["-C", repo, "--config", config, "--config-set", `worktree-path = ${JSON.stringify(workspace)}`,
101
+ "switch", "--create", "probe-work", "--base", head, "--no-hooks", "--no-cd", "--format=json", "--yes"]);
102
+ let response;
103
+ try {
104
+ response = JSON.parse(output);
105
+ }
106
+ catch {
107
+ return refuse("worktrunk_probe_invalid_json");
108
+ }
109
+ if (!response || typeof response !== "object" || Array.isArray(response))
110
+ refuse("worktrunk_probe_invalid_json");
111
+ const returned = response.workspace_path ?? response.path;
112
+ if (typeof returned !== "string" || returned !== workspace)
113
+ refuse("worktrunk_probe_foreign_workspace");
114
+ if (!fs.lstatSync(workspace).isDirectory() || fs.realpathSync(workspace) !== workspace)
115
+ refuse("worktrunk_probe_linked_workspace");
116
+ if (git(workspace, ["rev-parse", "--show-toplevel"]) !== workspace
117
+ || git(workspace, ["symbolic-ref", "--short", "HEAD"]) !== "probe-work"
118
+ || git(workspace, ["rev-parse", "--verify", "HEAD"]) !== head
119
+ || fs.realpathSync(path.resolve(workspace, git(workspace, ["rev-parse", "--git-common-dir"]))) !== path.join(repo, ".git")
120
+ || git(workspace, ["status", "--porcelain=v1", "--untracked-files=all"]) !== "")
121
+ refuse("worktrunk_probe_git_identity_changed");
122
+ if (!readBoundedReleaseFile(path.join(workspace, "probe.txt"), 1024).equals(seed)
123
+ || !readBoundedReleaseFile(path.join(repo, "probe.txt"), 1024).equals(seed)
124
+ || git(repo, ["status", "--porcelain=v1", "--untracked-files=all"]) !== ""
125
+ || git(repo, ["symbolic-ref", "--short", "HEAD"]) !== "probe-main" || git(repo, ["rev-parse", "HEAD"]) !== head)
126
+ refuse("worktrunk_probe_source_changed");
127
+ prepared = new PreparedMemoryInitWorktrunkCapability({ schema_id: "dd-flow/memory-init-worktrunk-capability@1",
128
+ executable: selected.tools.wt, executable_sha256: probe.executable_sha256, version: probe.version,
129
+ git_executable: selected.tools.git, git_executable_sha256: gitProbe.executable_sha256,
130
+ behavior_sha256: hybridDigest({ branch: "probe-work", seed_head: head, seed_sha256: hash(seed), returned_exact_workspace: true, shared_repository: true, hooks_disabled: true, source_unchanged: true, clean: true }) });
131
+ }
132
+ catch (error) {
133
+ primary = error instanceof AppError ? error : new AppError("memory_init_worktrunk_capability_failed", "Worktrunk probe observation failed", 1, { reason: "worktrunk_probe_observation_failed", effect: "no_effect", next_action: "repair_prerequisite_then_retry" });
134
+ }
135
+ if (scratch)
136
+ try {
137
+ const actual = fs.lstatSync(scratch);
138
+ if (!owner || !actual.isDirectory() || actual.dev !== owner.dev || actual.ino !== owner.ino)
139
+ refuse("probe_root_owner_changed");
140
+ fs.rmSync(scratch, { recursive: true });
141
+ }
142
+ catch {
143
+ throw new AppError("memory_init_worktrunk_probe_cleanup_failed", "Isolated Worktrunk probe requires cleanup", 1, { reason: primary instanceof AppError ? primary.details.reason : primary ? "probe_failed" : "cleanup_failed", effect: "partial", owned_probe_path: scratch, next_action: "reconcile_owned_probe" });
144
+ }
145
+ if (primary)
146
+ throw primary;
147
+ return prepared;
148
+ }
149
+ facts() { return { ...this.#facts }; }
150
+ revalidate(target, _env) {
151
+ if (!(target instanceof PreparedMemoryInitTarget))
152
+ refuse("unprepared_target");
153
+ const selected = target.facts(), probe = selected.tool_probes?.wt, gitProbe = selected.tool_probes?.git;
154
+ if (!probe || !gitProbe || selected.tools.wt !== this.#facts.executable || probe.version !== this.#facts.version
155
+ || probe.executable_sha256 !== this.#facts.executable_sha256 || selected.tools.git !== this.#facts.git_executable
156
+ || gitProbe.executable_sha256 !== this.#facts.git_executable_sha256)
157
+ refuse("worktrunk_capability_changed");
158
+ // Keep the initial isolated workspace proof; checkpoints only revalidate
159
+ // the selected Worktrunk/Git bytes, without creating another probe workspace.
160
+ const checksum = (file) => {
161
+ try {
162
+ return hash(readBoundedReleaseFile(file, 64 * 1024 * 1024));
163
+ }
164
+ catch {
165
+ return refuse("selected_executable_unavailable");
166
+ }
167
+ };
168
+ if (checksum(this.#facts.executable) !== this.#facts.executable_sha256
169
+ || checksum(this.#facts.git_executable) !== this.#facts.git_executable_sha256)
170
+ refuse("selected_executable_changed");
171
+ }
172
+ }