@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,236 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { randomUUID } from "node:crypto";
4
+ import { AppError } from "../shared/errors.js";
5
+ import { hybridDigest, validateHybridContract } from "./hybrid-contracts.js";
6
+ import { acquireInstallationLock } from "./installation-lock.js";
7
+ import { readBoundedReleaseFile } from "./release-source.js";
8
+ export function operatorSettingsPath(home) { return path.join(home, "profile-settings.json"); }
9
+ function profileError(code, message, exitCode = 2) {
10
+ return new AppError(code, `${message}; use dd-flow profiles --help`, exitCode, { phase: "profiles", effect: "no_effect", recoverable: true });
11
+ }
12
+ function completeSettings(input) {
13
+ const value = { ...input, profiles: input.profiles.map(entry => ({ ...entry, revision: hybridDigest(entry.profile) })) };
14
+ return { ...value, revision: hybridDigest(value) };
15
+ }
16
+ function admittedSettings(input) {
17
+ const issues = validateHybridContract("profile-settings", input);
18
+ if (issues.length)
19
+ throw profileError("profile_settings_invalid", "Profile settings violate the schema or aliases/revision contract");
20
+ const settings = input;
21
+ for (const { profile } of settings.profiles) {
22
+ if ([profile.provider, profile.model, profile.reasoning, profile.mode].some(value => !value.trim())) {
23
+ throw profileError("profile_settings_invalid", "Profile execution values must not be blank");
24
+ }
25
+ }
26
+ return settings;
27
+ }
28
+ function readRegularJson(file) {
29
+ try {
30
+ return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(readBoundedReleaseFile(file, 4 * 1024 * 1024)));
31
+ }
32
+ catch {
33
+ // Parser errors can contain submitted secrets. Never retain input text or cause.
34
+ throw profileError("profile_settings_invalid", "Profile input is unreadable or malformed JSON");
35
+ }
36
+ }
37
+ /** First successful write imports valid legacy IDs; thereafter the settings file is the sole owner. */
38
+ export function readOperatorProfiles(home) {
39
+ const file = operatorSettingsPath(home);
40
+ if (fs.existsSync(file))
41
+ return admittedSettings(readRegularJson(file));
42
+ const profiles = [];
43
+ const legacy = path.join(home, "agent-profiles");
44
+ if (fs.existsSync(legacy)) {
45
+ for (const entry of fs.readdirSync(legacy).sort()) {
46
+ if (!entry.endsWith(".json"))
47
+ continue;
48
+ const profile = readRegularJson(path.join(legacy, entry));
49
+ const id = entry.slice(0, -5);
50
+ profiles.push({ id, revision: hybridDigest(profile), profile });
51
+ }
52
+ }
53
+ return admittedSettings(completeSettings({ schema_id: "dd-flow/profile-settings@1", profiles,
54
+ aliases: {}, default_profile: null }));
55
+ }
56
+ export function resolveOperatorProfile(settings, id) {
57
+ const selected = id ?? settings.default_profile;
58
+ if (!selected)
59
+ throw profileError("agent_profile_missing", "Select an explicit profile or configure a default", 1);
60
+ const target = Object.hasOwn(settings.aliases, selected) ? settings.aliases[selected] : selected;
61
+ const entry = settings.profiles.find(item => item.id === target);
62
+ if (!entry)
63
+ throw profileError("agent_profile_missing", "Selected profile is unavailable", 1);
64
+ return structuredClone(entry.profile);
65
+ }
66
+ /** Used by explicit files and onboarding alike; extra credential fields fail before any write. */
67
+ export function prepareOperatorProfile(input) {
68
+ if (!input || typeof input !== "object" || Array.isArray(input))
69
+ throw profileError("profile_settings_invalid", "Profile must be an object");
70
+ const sample = input;
71
+ const value = completeSettings({ schema_id: "dd-flow/profile-settings@1", profiles: [
72
+ { id: sample?.id, revision: hybridDigest(input), profile: sample }
73
+ ], aliases: {}, default_profile: null });
74
+ return admittedSettings(value).profiles[0].profile;
75
+ }
76
+ export function readOperatorProfileInput(file) {
77
+ return prepareOperatorProfile(readRegularJson(file));
78
+ }
79
+ function changedSettings(current, mutation) {
80
+ const next = structuredClone(current);
81
+ if (mutation.action === "create" || mutation.action === "edit") {
82
+ if (mutation.action === "create" && mutation.setDefault !== undefined && typeof mutation.setDefault !== "boolean") {
83
+ throw profileError("profile_settings_invalid", "Default selection must be an explicit boolean");
84
+ }
85
+ const profile = prepareOperatorProfile(mutation.profile);
86
+ const index = next.profiles.findIndex(entry => entry.id === profile.id);
87
+ if (mutation.action === "create" && index >= 0)
88
+ throw profileError("profile_conflict", "Profile ID already exists", 1);
89
+ if (mutation.action === "edit" && index < 0)
90
+ throw profileError("agent_profile_missing", "Cannot edit a missing profile", 1);
91
+ const entry = { id: profile.id, revision: hybridDigest(profile), profile };
92
+ if (index >= 0)
93
+ next.profiles[index] = entry;
94
+ else
95
+ next.profiles.push(entry);
96
+ if (mutation.action === "create" && mutation.setDefault === true)
97
+ next.default_profile = profile.id;
98
+ }
99
+ else if (mutation.action === "remove") {
100
+ if (!next.profiles.some(entry => entry.id === mutation.id))
101
+ throw profileError("agent_profile_missing", "Cannot remove a missing profile", 1);
102
+ if (Object.values(next.aliases).includes(mutation.id) || next.default_profile === mutation.id
103
+ || next.default_profile !== null && next.aliases[next.default_profile] === mutation.id) {
104
+ throw profileError("profile_in_use", "Remove aliases and change the default before removing this profile", 1);
105
+ }
106
+ next.profiles = next.profiles.filter(entry => entry.id !== mutation.id);
107
+ }
108
+ else if (mutation.action === "alias") {
109
+ if (mutation.target === null) {
110
+ if (!Object.hasOwn(next.aliases, mutation.alias))
111
+ throw profileError("profile_alias_missing", "Alias is unavailable", 1);
112
+ if (next.default_profile === mutation.alias)
113
+ throw profileError("profile_in_use", "Change the default before removing its alias", 1);
114
+ delete next.aliases[mutation.alias];
115
+ }
116
+ else
117
+ Object.defineProperty(next.aliases, mutation.alias, { value: mutation.target, enumerable: true, writable: true, configurable: true });
118
+ }
119
+ else if (mutation.action === "set-default")
120
+ next.default_profile = mutation.id;
121
+ else
122
+ throw profileError("profile_settings_invalid", "Unknown profile mutation");
123
+ const { revision: _revision, ...content } = next;
124
+ void _revision;
125
+ return admittedSettings(completeSettings(content));
126
+ }
127
+ /** Pure preflight suitable for freezing into an installation request before any effects. */
128
+ export function prepareProfileCreation(settings, profile, setDefault) {
129
+ if (typeof setDefault !== "boolean")
130
+ throw profileError("profile_settings_invalid", "Default selection requires explicit consent");
131
+ const before = admittedSettings(structuredClone(settings)), admitted = prepareOperatorProfile(profile);
132
+ const after = changedSettings(before, { action: "create", profile: admitted, setDefault });
133
+ return { schema_id: "dd-flow/profile-creation-intent@1", before, after, profile: structuredClone(admitted), set_default: setDefault };
134
+ }
135
+ /** Exact frozen intent reconciliation: an already-applied write is not applied a second time.
136
+ * A later edit/default change is a conflict, never a reason to restore old settings.
137
+ */
138
+ export function validateProfileCreationIntent(input) {
139
+ if (!input || typeof input !== "object" || Array.isArray(input)
140
+ || Object.keys(input).some(key => !["schema_id", "before", "after", "profile", "set_default"].includes(key))
141
+ || ["schema_id", "before", "after", "profile", "set_default"].some(key => !Object.hasOwn(input, key))
142
+ || input.schema_id !== "dd-flow/profile-creation-intent@1")
143
+ throw profileError("profile_settings_invalid", "Invalid profile creation intent");
144
+ const expected = prepareProfileCreation(input.before, input.profile, input.set_default);
145
+ admittedSettings(input.after);
146
+ if (hybridDigest(expected) !== hybridDigest(input))
147
+ throw profileError("profile_settings_invalid", "Profile creation intent changed");
148
+ }
149
+ export function readProfileCreationIntent(file) {
150
+ try {
151
+ const input = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(readBoundedReleaseFile(file, 4 * 1024 * 1024)));
152
+ validateProfileCreationIntent(input);
153
+ return input;
154
+ }
155
+ catch {
156
+ throw profileError("profile_settings_invalid", "Profile creation intent must be a bounded valid regular JSON file");
157
+ }
158
+ }
159
+ export function commitProfileCreation(home, input) {
160
+ validateProfileCreationIntent(input);
161
+ const current = readOperatorProfiles(home);
162
+ if (current.revision === input.after.revision)
163
+ return { replayed: true, settings: current };
164
+ return { replayed: false, settings: mutateOperatorProfiles(home, input.before.revision, { action: "create", profile: input.profile, setDefault: input.set_default }) };
165
+ }
166
+ /** Compare-and-swap under an exclusive owned lock; no dual writer for legacy profile files. */
167
+ export function mutateOperatorProfiles(home, expectedRevision, mutation) {
168
+ const before = readOperatorProfiles(home);
169
+ if (before.revision !== expectedRevision)
170
+ throw profileError("profile_revision_conflict", "Profile settings changed; reread before retrying", 1);
171
+ const prepared = changedSettings(before, mutation); // Fully validate before creating the home/lock.
172
+ const serialized = JSON.stringify(prepared, null, 2) + "\n";
173
+ if (Buffer.byteLength(serialized) > 4 * 1024 * 1024)
174
+ throw profileError("profile_settings_invalid", "Profile settings exceed the bounded reader limit");
175
+ fs.mkdirSync(home, { recursive: true, mode: 0o700 });
176
+ const lock = path.join(home, "profile-settings.lock");
177
+ // Profiles use storage-owner identity, not a setup request ledger. A dead
178
+ // writer may be recovered by a later CAS mutation, including after commit.
179
+ let lease;
180
+ try {
181
+ lease = acquireInstallationLock(lock, hybridDigest({ resource: "profile-settings", home: path.resolve(home) }));
182
+ }
183
+ catch {
184
+ throw profileError("profile_write_conflict", "Another profile writer or unreconciled lock exists", 1);
185
+ }
186
+ const temporary = path.join(home, `.profile-settings-${randomUUID()}.tmp`);
187
+ let ownsTemporary = false;
188
+ let renameAttempted = false;
189
+ let failure;
190
+ let result;
191
+ try {
192
+ if (readOperatorProfiles(home).revision !== expectedRevision)
193
+ throw profileError("profile_revision_conflict", "Profile settings changed while acquiring the lock", 1);
194
+ const fd = fs.openSync(temporary, "wx", 0o600);
195
+ ownsTemporary = true;
196
+ try {
197
+ fs.writeFileSync(fd, serialized);
198
+ fs.fsyncSync(fd);
199
+ }
200
+ finally {
201
+ fs.closeSync(fd);
202
+ }
203
+ renameAttempted = true;
204
+ fs.renameSync(temporary, operatorSettingsPath(home));
205
+ result = readOperatorProfiles(home);
206
+ }
207
+ catch (error) {
208
+ failure = error instanceof AppError && !renameAttempted ? error : new AppError("profile_write_interrupted", "Profile write requires reconciliation; reread settings before retrying", 1, { phase: "profiles", effect: "unknown", recoverable: true,
209
+ next_action: "dd-flow profiles list --json" });
210
+ }
211
+ finally {
212
+ let cleanupFailed = false;
213
+ try {
214
+ if (ownsTemporary && fs.existsSync(temporary))
215
+ fs.unlinkSync(temporary);
216
+ }
217
+ catch {
218
+ cleanupFailed = true;
219
+ }
220
+ try {
221
+ lease.release();
222
+ }
223
+ catch {
224
+ cleanupFailed = true;
225
+ }
226
+ if (cleanupFailed) {
227
+ if (failure)
228
+ failure.details.cleanup = "not_confirmed";
229
+ else
230
+ failure = new AppError("profile_cleanup_failed", "Profile writer cleanup requires reconciliation", 1, { phase: "profiles", effect: "unknown", cleanup: "not_confirmed", next_action: "dd-flow profiles list --json" });
231
+ }
232
+ }
233
+ if (failure)
234
+ throw failure;
235
+ return result;
236
+ }
@@ -11,9 +11,24 @@ import { loadProjectFlowContract } from "../domain/flow-contract.js";
11
11
  import { buildStaticFlowGuidance } from "./flow-guidance.js";
12
12
  import { normalizeProtocolLifecycle } from "./protocol-lifecycle.js";
13
13
  import { buildProjectSummary, projectRegistrySummary, readPublishedProjectSummary } from "./project-summary.js";
14
+ /** Resolve filesystem identity before entering a composite SQL writer. */
15
+ export class PreparedProjectRegistration {
16
+ #root;
17
+ constructor(root) { this.#root = root; Object.freeze(this); }
18
+ static prepare(input) {
19
+ return new PreparedProjectRegistration(resolveProjectRoot(input.root));
20
+ }
21
+ root() { return this.#root; }
22
+ }
14
23
  export function registerProject(context, input) {
15
- const root = resolveProjectRoot(input.root);
16
- const existing = findProjectByRoot(context, root);
24
+ return registerPreparedProject(context, PreparedProjectRegistration.prepare(input));
25
+ }
26
+ /** Metadata-only registration; the composite caller owns its SQL transaction. */
27
+ export function registerPreparedProject(context, prepared) {
28
+ if (!(prepared instanceof PreparedProjectRegistration))
29
+ throw new AppError("validation", "Project registration must be prepared", 2);
30
+ const root = prepared.root();
31
+ const existing = context.db.get("SELECT * FROM projects WHERE root = ?", [root]);
17
32
  const now = context.now();
18
33
  const project = existing ?? newProjectRecord(context, root, now);
19
34
  if (existing) {