@deksden-com/dd-flow-cli 0.9.0-beta.137 → 0.9.0-beta.139

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 (283) hide show
  1. package/CHANGELOG.md +19 -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/runtime-process.js +1 -1
  27. package/dist/cli/setup.js +149 -0
  28. package/dist/cli.js +56 -2
  29. package/dist/domain/stage-handler-identities.js +20 -0
  30. package/dist/harness-runtime/lib/daemon-operations.d.mts +1 -0
  31. package/dist/harness-runtime/lib/daemon-operations.mjs +4 -3
  32. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +53 -22
  33. package/dist/harness-runtime/lib/dd-agy.mjs +16 -5
  34. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +12 -6
  35. package/dist/harness-runtime/lib/dd-codex.mjs +24 -12
  36. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +2 -1
  37. package/dist/harness-runtime/lib/dd-droid.mjs +32 -9
  38. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +11 -3
  39. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +100 -15
  40. package/dist/harness-runtime/lib/dd-opencode.mjs +51 -20
  41. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +22 -10
  42. package/dist/harness-runtime/lib/dd-zcode.mjs +17 -4
  43. package/dist/harness-runtime/lib/delegation-instructions.d.mts +1 -0
  44. package/dist/harness-runtime/lib/delegation-instructions.mjs +16 -3
  45. package/dist/harness-runtime/lib/lease-renewal.mjs +2 -1
  46. package/dist/harness-runtime/lib/lifecycle-command.mjs +1 -0
  47. package/dist/harness-runtime/lib/managed-daemon.d.mts +1 -0
  48. package/dist/harness-runtime/lib/managed-daemon.mjs +50 -15
  49. package/dist/harness-runtime/lib/managed-flow-env.mjs +15 -0
  50. package/dist/harness-runtime/lib/native-children.d.mts +2 -0
  51. package/dist/harness-runtime/lib/native-children.mjs +20 -1
  52. package/dist/harness-runtime/lib/native-hook-command.mjs +1 -1
  53. package/dist/harness-runtime/lib/native-request-proof.d.mts +2 -0
  54. package/dist/harness-runtime/lib/native-request-proof.mjs +65 -0
  55. package/dist/harness-runtime/lib/opencode-plugin.mjs +32 -9
  56. package/dist/harness-runtime/lib/operation-progress.mjs +3 -1
  57. package/dist/harness-runtime/lib/provider-overload-policy.d.mts +13 -0
  58. package/dist/harness-runtime/lib/provider-overload-policy.mjs +138 -0
  59. package/dist/runtime/context.js +1 -1
  60. package/dist/schemas/discovery-result.schema.json +184 -0
  61. package/dist/schemas/execution-bundle.schema.json +205 -0
  62. package/dist/schemas/execution-settings.schema.json +162 -0
  63. package/dist/schemas/flow-descriptor.schema.json +54 -0
  64. package/dist/schemas/flow-run.schema.json +26 -0
  65. package/dist/schemas/harness-catalog.schema.json +80 -0
  66. package/dist/schemas/mb-init-compose-result.schema.json +457 -0
  67. package/dist/schemas/mb-init-discover-result.schema.json +500 -0
  68. package/dist/schemas/mb-init-finish-result.schema.json +422 -0
  69. package/dist/schemas/mb-init-preflight-result.schema.json +367 -0
  70. package/dist/schemas/mb-init-review-result.schema.json +396 -0
  71. package/dist/schemas/mb-init-verify-result.schema.json +391 -0
  72. package/dist/schemas/memory-bootstrap-receipt-v2.schema.json +67 -0
  73. package/dist/schemas/memory-bootstrap-receipt.schema.json +354 -0
  74. package/dist/schemas/memory-composition-work-result.schema.json +38 -0
  75. package/dist/schemas/memory-repair-work-result.schema.json +49 -0
  76. package/dist/schemas/memory-review-correction-plan.schema.json +30 -0
  77. package/dist/schemas/memory-review-work-result.schema.json +43 -0
  78. package/dist/schemas/model-catalog.schema.json +153 -0
  79. package/dist/schemas/profile-settings.schema.json +122 -0
  80. package/dist/schemas/release-manifest.schema.json +291 -0
  81. package/dist/schemas/run-engine-binding.schema.json +1 -1
  82. package/dist/services/check-runner-evidence.js +51 -4
  83. package/dist/services/cli-operation-classifier.js +8 -47
  84. package/dist/services/cli-upgrade-allowlist.js +27 -0
  85. package/dist/services/code-checks.js +387 -34
  86. package/dist/services/controller-child-capacity.js +13 -0
  87. package/dist/services/controller-child-observer.js +18 -2
  88. package/dist/services/controller-execution.js +88 -0
  89. package/dist/services/controller-fanout.js +71 -12
  90. package/dist/services/dashboard.js +8 -2
  91. package/dist/services/descriptor-coordinator-session.js +35 -0
  92. package/dist/services/descriptor-root-work.js +53 -0
  93. package/dist/services/descriptor-run-settlement.js +105 -0
  94. package/dist/services/descriptor-run-start.js +449 -0
  95. package/dist/services/descriptor-stage-coordinator.js +197 -0
  96. package/dist/services/descriptor-stage-settlement.js +222 -0
  97. package/dist/services/descriptor-stage-start.js +139 -0
  98. package/dist/services/discovery/agy-discovery.js +2 -0
  99. package/dist/services/discovery/codex-discovery.js +2 -0
  100. package/dist/services/discovery/droid-discovery.js +2 -0
  101. package/dist/services/discovery/grok-discovery.js +2 -0
  102. package/dist/services/discovery/opencode-discovery.js +2 -0
  103. package/dist/services/discovery/static.js +31 -0
  104. package/dist/services/discovery/zcode-discovery.js +2 -0
  105. package/dist/services/engines.js +39 -22
  106. package/dist/services/eval-snapshots.js +83 -25
  107. package/dist/services/execution-bundles.js +263 -0
  108. package/dist/services/execution-policy.js +20 -16
  109. package/dist/services/executor-onboarding.js +56 -0
  110. package/dist/services/external-work-launch.js +93 -15
  111. package/dist/services/flow-catalog.js +21 -0
  112. package/dist/services/flow-descriptors.js +82 -0
  113. package/dist/services/frozen-engine.js +250 -0
  114. package/dist/services/generic-execution.js +164 -0
  115. package/dist/services/harness-adapter.js +28 -2
  116. package/dist/services/harness-discovery.js +29 -0
  117. package/dist/services/harness-doctor.js +75 -0
  118. package/dist/services/harness-probe-effects.js +51 -0
  119. package/dist/services/harness-version-probe.js +100 -0
  120. package/dist/services/hooks.js +24 -6
  121. package/dist/services/hybrid-contracts.js +483 -0
  122. package/dist/services/ids.js +29 -1
  123. package/dist/services/installation-lock.js +95 -0
  124. package/dist/services/installed-flow-source.js +183 -0
  125. package/dist/services/lifecycle-invocations.js +257 -78
  126. package/dist/services/managed-processes.js +31 -10
  127. package/dist/services/memory-accepted-composition.js +77 -0
  128. package/dist/services/memory-baseline-preservation.js +65 -0
  129. package/dist/services/memory-completed-correction-wave.js +320 -0
  130. package/dist/services/memory-compose-handler.js +258 -0
  131. package/dist/services/memory-composition-acceptance.js +199 -0
  132. package/dist/services/memory-composition-graph.js +134 -0
  133. package/dist/services/memory-composition-registration.js +146 -0
  134. package/dist/services/memory-composition-work-custody.js +151 -0
  135. package/dist/services/memory-composition-work-results.js +96 -0
  136. package/dist/services/memory-corrected-review-acceptance.js +129 -0
  137. package/dist/services/memory-corrected-review-batch.js +30 -0
  138. package/dist/services/memory-corrected-review-findings.js +106 -0
  139. package/dist/services/memory-corrected-review-inputs.js +210 -0
  140. package/dist/services/memory-corrected-review-projection.js +78 -0
  141. package/dist/services/memory-corrected-review-receipt.js +170 -0
  142. package/dist/services/memory-corrected-review-registration.js +191 -0
  143. package/dist/services/memory-correction-check-receipt.js +219 -0
  144. package/dist/services/memory-correction-input-owner.js +6 -0
  145. package/dist/services/memory-discover-handler.js +104 -0
  146. package/dist/services/memory-finish-evidence.js +435 -0
  147. package/dist/services/memory-finish-handler.js +215 -0
  148. package/dist/services/memory-finish-integration.js +280 -0
  149. package/dist/services/memory-finish-readiness.js +122 -0
  150. package/dist/services/memory-git-integration.js +151 -0
  151. package/dist/services/memory-historical-corrected-review.js +206 -0
  152. package/dist/services/memory-historical-original-review.js +130 -0
  153. package/dist/services/memory-historical-repair-registration.js +99 -0
  154. package/dist/services/memory-historical-second-review.js +128 -0
  155. package/dist/services/memory-init-bootstrap-readiness.js +210 -0
  156. package/dist/services/memory-init-contexts.js +89 -0
  157. package/dist/services/memory-init-controller-start.js +84 -0
  158. package/dist/services/memory-init-engine-release.js +223 -0
  159. package/dist/services/memory-init-git-policy.js +136 -0
  160. package/dist/services/memory-init-lint-capability.js +185 -0
  161. package/dist/services/memory-init-native-capacity.js +296 -0
  162. package/dist/services/memory-init-preflight.js +330 -0
  163. package/dist/services/memory-init-project-bootstrap.js +599 -0
  164. package/dist/services/memory-init-request-candidate.js +157 -0
  165. package/dist/services/memory-init-request-input.js +66 -0
  166. package/dist/services/memory-init-resource-storage.js +125 -0
  167. package/dist/services/memory-init-run-allocation.js +81 -0
  168. package/dist/services/memory-init-run-bundle.js +98 -0
  169. package/dist/services/memory-init-start.js +181 -0
  170. package/dist/services/memory-init-startup-custody.js +80 -0
  171. package/dist/services/memory-init-startup-input.js +240 -0
  172. package/dist/services/memory-init-storage.js +81 -0
  173. package/dist/services/memory-init-stores.js +359 -0
  174. package/dist/services/memory-init-worktrunk-capability.js +178 -0
  175. package/dist/services/memory-layer-publication.js +611 -0
  176. package/dist/services/memory-layer.js +156 -0
  177. package/dist/services/memory-lint-config.js +85 -0
  178. package/dist/services/memory-native-review-copy.js +165 -0
  179. package/dist/services/memory-normal-flow-entry.js +75 -0
  180. package/dist/services/memory-ordinary-defaults.js +112 -0
  181. package/dist/services/memory-preflight-bootstrap-custody.js +115 -0
  182. package/dist/services/memory-preflight-handler.js +259 -0
  183. package/dist/services/memory-repair-current-view.js +299 -0
  184. package/dist/services/memory-repair-document-delta.js +451 -0
  185. package/dist/services/memory-repair-history.js +491 -0
  186. package/dist/services/memory-repair-physical-binding.js +312 -0
  187. package/dist/services/memory-repair-publication-files.js +152 -0
  188. package/dist/services/memory-repair-publication-intent.js +27 -0
  189. package/dist/services/memory-repair-publication-plan.js +160 -0
  190. package/dist/services/memory-repair-publication-source.js +225 -0
  191. package/dist/services/memory-repair-publication.js +794 -0
  192. package/dist/services/memory-repair-registration.js +239 -0
  193. package/dist/services/memory-repair-work-baseline.js +413 -0
  194. package/dist/services/memory-repair-work-completion.js +97 -0
  195. package/dist/services/memory-repair-work-custody.js +73 -0
  196. package/dist/services/memory-repair-work-results.js +89 -0
  197. package/dist/services/memory-repair-workspace.js +252 -0
  198. package/dist/services/memory-repair-writer-copy.js +735 -0
  199. package/dist/services/memory-result-binding.js +173 -0
  200. package/dist/services/memory-retained-review-authority.js +129 -0
  201. package/dist/services/memory-retained-review-results.js +70 -0
  202. package/dist/services/memory-retained-second-baseline.js +102 -0
  203. package/dist/services/memory-retained-second-repairs.js +156 -0
  204. package/dist/services/memory-review-acceptance.js +214 -0
  205. package/dist/services/memory-review-correction-plan.js +171 -0
  206. package/dist/services/memory-review-handler.js +585 -0
  207. package/dist/services/memory-review-inputs.js +200 -0
  208. package/dist/services/memory-review-receipt.js +180 -0
  209. package/dist/services/memory-review-registration.js +108 -0
  210. package/dist/services/memory-review-work-custody.js +124 -0
  211. package/dist/services/memory-review-work-input.js +72 -0
  212. package/dist/services/memory-review-work-results.js +73 -0
  213. package/dist/services/memory-second-corrected-review-inputs.js +165 -0
  214. package/dist/services/memory-second-correction-plan.js +76 -0
  215. package/dist/services/memory-second-repair-baseline.js +91 -0
  216. package/dist/services/memory-second-repair-launch.js +614 -0
  217. package/dist/services/memory-second-repair-registration.js +88 -0
  218. package/dist/services/memory-second-repair-source.js +187 -0
  219. package/dist/services/memory-second-review-acceptance.js +115 -0
  220. package/dist/services/memory-source-observations.js +117 -0
  221. package/dist/services/memory-stage-dispatch.js +28 -0
  222. package/dist/services/memory-stage-results.js +199 -0
  223. package/dist/services/memory-verification-acceptance.js +135 -0
  224. package/dist/services/memory-verification-checks.js +252 -0
  225. package/dist/services/memory-verification-receipt.js +159 -0
  226. package/dist/services/memory-verify-handler.js +135 -0
  227. package/dist/services/memory-work-result-preparation.js +63 -0
  228. package/dist/services/merge-server.js +2 -4
  229. package/dist/services/model-catalog.js +109 -0
  230. package/dist/services/model-discovery.js +46 -0
  231. package/dist/services/operator-profiles.js +236 -0
  232. package/dist/services/projects.js +17 -2
  233. package/dist/services/{codex-capacity.js → provider-capacity.js} +34 -16
  234. package/dist/services/recovery-snapshot-database.js +417 -2
  235. package/dist/services/release-archive-producer.js +52 -0
  236. package/dist/services/release-archive.js +143 -0
  237. package/dist/services/release-cache.js +142 -0
  238. package/dist/services/release-compatibility.js +18 -0
  239. package/dist/services/release-download.js +67 -0
  240. package/dist/services/release-envelope.js +104 -0
  241. package/dist/services/release-manifest.js +45 -0
  242. package/dist/services/release-payload.js +114 -0
  243. package/dist/services/release-producer.js +63 -0
  244. package/dist/services/release-provenance.js +160 -0
  245. package/dist/services/release-registry.js +66 -0
  246. package/dist/services/release-source.js +194 -0
  247. package/dist/services/repair-intents.js +1 -1
  248. package/dist/services/review-copy.js +10 -2
  249. package/dist/services/run-control.js +80 -9
  250. package/dist/services/run-controller-adapter.js +1 -1
  251. package/dist/services/run-controller-capture.js +10 -3
  252. package/dist/services/run-controller-process.js +21 -0
  253. package/dist/services/run-controller-recovery.js +5 -5
  254. package/dist/services/run-controller-state.js +1 -1
  255. package/dist/services/run-controller.js +158 -64
  256. package/dist/services/run-recovery-guard.js +5 -3
  257. package/dist/services/run-recovery-runtime.js +3 -3
  258. package/dist/services/run-recovery.js +145 -5
  259. package/dist/services/run-start-custody.js +177 -0
  260. package/dist/services/runs.js +413 -30
  261. package/dist/services/setup.js +377 -0
  262. package/dist/services/stage-context.js +9 -1
  263. package/dist/services/stage-handlers.js +74 -0
  264. package/dist/services/stage-report-renderer.js +63 -0
  265. package/dist/services/stage-work-graph.js +17 -0
  266. package/dist/services/vnext-code-review.js +1 -1
  267. package/dist/services/vnext-code.js +6 -6
  268. package/dist/services/vnext-execution-profile.js +2 -2
  269. package/dist/services/vnext-fanout.js +73 -11
  270. package/dist/services/vnext-merge.js +8 -3
  271. package/dist/services/vnext-plan-review.js +7 -5
  272. package/dist/services/vnext-plan.js +3 -3
  273. package/dist/services/vnext-protocolize.js +20 -6
  274. package/dist/services/vnext-workspace-policy.js +7 -2
  275. package/dist/services/work-registry.js +616 -71
  276. package/dist/services/workspace-files.js +1 -1
  277. package/dist/storage/database.js +84 -6
  278. package/dist/storage/writer-contract.js +11 -7
  279. package/package.json +13 -3
  280. package/tools/create-canon-release.mjs +33 -0
  281. package/tools/produce-hybrid-release.mjs +41 -0
  282. package/dist/harness-runtime/lib/codex-capacity-policy.d.mts +0 -10
  283. package/dist/harness-runtime/lib/codex-capacity-policy.mjs +0 -102
@@ -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) {
@@ -1,11 +1,14 @@
1
1
  import { setTimeout as delay } from "node:timers/promises";
2
2
  import crypto from "node:crypto";
3
- import { CAPACITY_POLICY, capacityBackoff, nativeItemsSettled, normalizeCodexFailure, overloadBurst, parseCapacityOperation, refusalObservation, terminalCodexOverload } from "../harness-runtime/lib/codex-capacity-policy.mjs";
3
+ import { CAPACITY_POLICY, MAX_OVERLOAD_RETRIES, capacityBackoff, nativeItemsSettled, normalizeProviderFailure, overloadExhausted, parseCapacityOperation, refusalObservation, terminalProviderOverload } from "../harness-runtime/lib/provider-overload-policy.mjs";
4
4
  import { AppError } from "../shared/errors.js";
5
5
  import { isObservationLoss } from "../harness-runtime/lib/operation-errors.mjs";
6
- export function eligibleCodexChildOverload(child, parentSessionId) {
6
+ export function providerChildFailure(child) {
7
+ return child.provider_failure ?? { code: child.native_source ? "provider_capacity_transient" : "turn_interrupted", details: { native_source: child.native_source, provider_session_id: child.session_id, turn_id: child.terminal_turn_id, native_turn_id: child.terminal_turn_id, terminal_status: child.status, observed_at: child.terminal_observed_at ?? null, provider_error: child.provider_error, native_turn_items: child.native_turn_items } };
8
+ }
9
+ export function eligibleProviderChildOverload(child, parentSessionId) {
7
10
  return typeof child.session_id === "string" && child.session_id !== parentSessionId && child.parent_session_id === parentSessionId && child.provenance !== "parent_mismatch" && child.status === "failed" && typeof child.terminal_turn_id === "string" && nativeItemsSettled(child.native_turn_items)
8
- && terminalCodexOverload({ code: "turn_interrupted", details: { provider_session_id: child.session_id, turn_id: child.terminal_turn_id, native_turn_id: child.terminal_turn_id, terminal_status: "failed", provider_error: child.provider_error } }, child.session_id, child.terminal_turn_id);
11
+ && terminalProviderOverload(providerChildFailure(child), child.session_id, child.terminal_turn_id);
9
12
  }
10
13
  /** A terminal root is not a settled tree. Only the daemon's exact owned tree
11
14
  * with a settled root and positively live descendants permits waiting. */
@@ -37,7 +40,7 @@ export function assertCapacityOwnerAdmission(context, record, operationId, bindi
37
40
  return;
38
41
  const root = parseCapacityOperation(operationId).root;
39
42
  const childOrdinal = /:child:[a-f0-9]+:followup:(\d+)$/.exec(root)?.[1];
40
- if (childOrdinal && (!Number.isSafeInteger(Number(childOrdinal)) || Number(childOrdinal) < 1 || String(Number(childOrdinal)) !== childOrdinal))
43
+ if (childOrdinal && (!Number.isSafeInteger(Number(childOrdinal)) || Number(childOrdinal) < 1 || Number(childOrdinal) > MAX_OVERLOAD_RETRIES || String(Number(childOrdinal)) !== childOrdinal))
41
44
  throw new AppError("capacity_continuation_conflict", "Invalid native child continuation ordinal", 1);
42
45
  if (root === operationId && !childOrdinal)
43
46
  return;
@@ -71,7 +74,9 @@ export function assertCapacityOwnerAdmission(context, record, operationId, bindi
71
74
  if (intent)
72
75
  break;
73
76
  }
74
- if (!intent || intent.policy !== CAPACITY_POLICY || intent.root_operation_id !== root || intent.ordinal !== parsed.ordinal
77
+ if (intent?.policy && intent.policy !== CAPACITY_POLICY)
78
+ throw new AppError("capacity_policy_mismatch", "Continuation is pinned to another retry policy", 1);
79
+ if (!intent || parsed.ordinal > MAX_OVERLOAD_RETRIES || intent.policy !== CAPACITY_POLICY || intent.root_operation_id !== root || intent.ordinal !== parsed.ordinal
75
80
  || intent.predecessor !== predecessor || intent.operation_id !== operationId || intent.refusal?.turn_id !== intent.turn_id
76
81
  || intent.refusal?.ordinal !== parsed.ordinal - 1 || intent.refusal?.session_id !== intent.session_id
77
82
  || !Number.isFinite(Date.parse(intent.not_before)) || Date.parse(intent.not_before) > Date.parse(context.now())
@@ -115,9 +120,13 @@ export function capacityDelay(error, ordinal, now = Date.now()) {
115
120
  return capacityBackoff(error, ordinal, now);
116
121
  }
117
122
  /** Continue a confirmed terminal Turn, never replay its completed effects. */
118
- export async function continueCodexCapacity(input) {
123
+ export async function continueProviderCapacity(input) {
119
124
  let { error, operationId } = input;
120
125
  const history = [...(input.retainedIntents?.() ?? [])];
126
+ if (history.some(intent => intent.policy !== CAPACITY_POLICY) || input.retainedFailures?.().some(refusal => refusal.policy !== CAPACITY_POLICY))
127
+ throw new AppError("capacity_policy_mismatch", "Retained continuation uses another retry policy", 1);
128
+ if (history.length > MAX_OVERLOAD_RETRIES)
129
+ throw new AppError("capacity_continuation_conflict", "Retained episode exceeds its retry budget", 1);
121
130
  const parsed = parseCapacityOperation(input.operationId);
122
131
  const rootId = parsed.root;
123
132
  const text = input.continuationText ?? "Continue the same assigned task in this Session. Check current durable RUN, Work and Stage state first. Never repeat an observed lifecycle command, accepted HITL answer, check or merge apply; use only an exact issued successor. Stop at the assigned boundary.";
@@ -217,9 +226,9 @@ export async function continueCodexCapacity(input) {
217
226
  throw new AppError("capacity_continuation_blocked", "Retained completion cannot be reconciled with its owner boundary", 1);
218
227
  return boundary === "paused" || boundary === "completed" ? { provider_session_id: input.sessionId, capacity_decision: boundary } : observed.result;
219
228
  }
220
- if (observed.state === "failed" && observed.error && !terminalCodexOverload(observed.error, input.sessionId))
229
+ if (observed.state === "failed" && observed.error && !terminalProviderOverload(observed.error, input.sessionId))
221
230
  throw observed.error;
222
- if (observed.state !== "failed" || !terminalCodexOverload(observed.error, input.sessionId))
231
+ if (observed.state !== "failed" || !terminalProviderOverload(observed.error, input.sessionId))
223
232
  throw new AppError("operation_observation_lost", "Retained successor outcome is not proven; do not resend", 1, { operation_id: intent.operation_id, primary_error: error, observed });
224
233
  operationId = intent.operation_id;
225
234
  error = observed.error;
@@ -240,7 +249,7 @@ export async function continueCodexCapacity(input) {
240
249
  for (;; ordinal += 1) {
241
250
  if (!Number.isSafeInteger(ordinal))
242
251
  conflict("Continuation ordinal overflow");
243
- if (!terminalCodexOverload(error, input.sessionId)) {
252
+ if (!terminalProviderOverload(error, input.sessionId)) {
244
253
  if (ordinal === 1)
245
254
  return null;
246
255
  throw error;
@@ -250,14 +259,14 @@ export async function continueCodexCapacity(input) {
250
259
  return completed;
251
260
  const prior = await observe(() => input.inspectOperation(operationId));
252
261
  const failure = prior.error;
253
- const confirmed = normalizeCodexFailure(failure, input.sessionId);
262
+ const confirmed = normalizeProviderFailure(failure, input.sessionId);
254
263
  const turnId = confirmed?.turn_id;
255
264
  input.signal?.throwIfAborted();
256
265
  input.assertCurrent();
257
266
  if (prior.primary_error)
258
267
  throw prior.primary_error;
259
268
  if (prior.operation_id !== operationId || prior.operation !== "session.prompt" || prior.session_id !== input.sessionId
260
- || prior.state !== "failed" || prior.primary_error || !turnId || !terminalCodexOverload(failure, input.sessionId, turnId)) {
269
+ || prior.state !== "failed" || prior.primary_error || !turnId || !terminalProviderOverload(failure, input.sessionId, turnId)) {
261
270
  throw blocked("Retained predecessor does not prove the exact terminal overloaded Turn");
262
271
  }
263
272
  const readiness = async () => {
@@ -272,7 +281,7 @@ export async function continueCodexCapacity(input) {
272
281
  const ready = await readiness();
273
282
  if (ready === "unproven")
274
283
  throw blocked("Native predecessor readiness is unproven; do not cancel or replay to manufacture proof");
275
- const native = normalizeCodexFailure(failure, input.sessionId, turnId);
284
+ const native = normalizeProviderFailure(failure, input.sessionId, turnId);
276
285
  let refusal = refusals.find(item => item.ordinal === ordinal - 1);
277
286
  if (refusal && (refusal.turn_id !== turnId || refusal.session_id !== input.sessionId || refusal.policy !== CAPACITY_POLICY))
278
287
  conflict("Failure ordinal identifies a different native Turn");
@@ -293,17 +302,26 @@ export async function continueCodexCapacity(input) {
293
302
  input.retainFailure?.(operationId, refusal);
294
303
  refusals.push(refusal);
295
304
  }
296
- const burst = overloadBurst(refusals);
297
- if (burst) {
305
+ const exhausted = overloadExhausted(refusals);
306
+ if (exhausted) {
298
307
  if (input.atomicIntent)
299
308
  input.retainFailure?.(operationId, refusal);
300
- throw new AppError("provider_overload_burst", "Two consecutive continuation Turns were overloaded within two minutes", 1, { ...burst, root_operation_id: rootId, provider_session_id: input.sessionId, continuation_count: ordinal - 1, primary_error: failure });
309
+ throw new AppError("provider_unavailable", "Provider overload retry episode exhausted", 1, { ...exhausted, root_operation_id: rootId, provider_session_id: input.sessionId, continuation_count: ordinal - 1, primary_error: refusals.length ? input.error : failure, last_error: failure });
301
310
  }
302
311
  const nextId = `${rootId}:capacity:${ordinal}:session.prompt`;
303
312
  const existing = history.find(item => item.operation_id === nextId);
304
313
  if (existing && (existing.predecessor !== operationId || existing.turn_id !== turnId || existing.prompt_sha256 !== promptSha || existing.ordinal !== ordinal))
305
314
  throw error;
306
- const wait = existing ? Math.max(0, Date.parse(existing.not_before) - Date.now()) : capacityDelay(failure, ordinal);
315
+ let wait;
316
+ try {
317
+ wait = existing ? Math.max(0, Date.parse(existing.not_before) - Date.now()) : capacityDelay(failure, ordinal);
318
+ }
319
+ catch (cause) {
320
+ if (input.atomicIntent)
321
+ input.retainFailure?.(operationId, refusal);
322
+ const metadata = cause;
323
+ throw new AppError(metadata.code ?? "capacity_retry_metadata_invalid", metadata.message ?? "Invalid provider retry metadata", 1, { ...metadata.details, policy: CAPACITY_POLICY, attempts: refusals.length, root_operation_id: rootId, primary_error: input.error, last_error: failure });
324
+ }
307
325
  if (!Number.isFinite(wait) || Date.now() + wait > 8.64e15 || input.deadline !== undefined && Date.now() + wait >= input.deadline) {
308
326
  if (input.atomicIntent)
309
327
  input.retainFailure?.(operationId, refusal);