@deksden-com/dd-flow-cli 0.9.0-beta.14 → 0.9.0-beta.141

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 (493) hide show
  1. package/CHANGELOG.md +740 -0
  2. package/README.md +143 -1
  3. package/dist/build-info.json +8 -8
  4. package/dist/cli/argument-values.js +38 -0
  5. package/dist/cli/command-inputs.js +376 -0
  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 +328 -18
  15. package/dist/cli/hook-ingress.js +78 -0
  16. package/dist/cli/input-preparation.js +165 -0
  17. package/dist/cli/memory-flow-admission.js +44 -0
  18. package/dist/cli/memory-init-invocation.js +78 -0
  19. package/dist/cli/memory-init.js +240 -0
  20. package/dist/cli/memory-storage-admission.js +53 -0
  21. package/dist/cli/models.js +39 -0
  22. package/dist/cli/native-hook-ingress.js +80 -0
  23. package/dist/cli/operator-profiles.js +61 -0
  24. package/dist/cli/output-options.js +35 -0
  25. package/dist/cli/response-file.js +71 -0
  26. package/dist/cli/run-cli.js +1689 -545
  27. package/dist/cli/runtime-maintenance-diagnostics.js +46 -0
  28. package/dist/cli/runtime-maintenance-ingress.js +52 -0
  29. package/dist/cli/runtime-process.js +153 -0
  30. package/dist/cli/setup.js +149 -0
  31. package/dist/cli.js +71 -2
  32. package/dist/domain/stage-catalog.js +1 -1
  33. package/dist/domain/stage-handler-identities.js +20 -0
  34. package/dist/harness-runtime/DD-ZCODE.md +112 -0
  35. package/dist/harness-runtime/OPENCODE-LICENSE.txt +24 -0
  36. package/dist/harness-runtime/bin/dd-agy.mjs +56 -0
  37. package/dist/harness-runtime/bin/dd-codex.mjs +27 -0
  38. package/dist/harness-runtime/bin/dd-droid.mjs +34 -0
  39. package/dist/harness-runtime/bin/dd-grok.mjs +40 -0
  40. package/dist/harness-runtime/bin/dd-opencode.mjs +22 -0
  41. package/dist/harness-runtime/bin/dd-zcode.mjs +87 -0
  42. package/dist/harness-runtime/lib/adapter-timeouts.d.mts +5 -0
  43. package/dist/harness-runtime/lib/adapter-timeouts.mjs +34 -0
  44. package/dist/harness-runtime/lib/agy-hooks.d.mts +1 -0
  45. package/dist/harness-runtime/lib/agy-hooks.mjs +9 -0
  46. package/dist/harness-runtime/lib/daemon-observation.d.mts +2 -0
  47. package/dist/harness-runtime/lib/daemon-observation.mjs +163 -0
  48. package/dist/harness-runtime/lib/daemon-operations.d.mts +1 -0
  49. package/dist/harness-runtime/lib/daemon-operations.mjs +371 -0
  50. package/dist/harness-runtime/lib/dd-agy-daemon.d.mts +6 -0
  51. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +728 -0
  52. package/dist/harness-runtime/lib/dd-agy.mjs +70 -0
  53. package/dist/harness-runtime/lib/dd-codex-daemon.d.mts +1 -0
  54. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +196 -0
  55. package/dist/harness-runtime/lib/dd-codex.d.mts +1 -0
  56. package/dist/harness-runtime/lib/dd-codex.mjs +817 -0
  57. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +140 -0
  58. package/dist/harness-runtime/lib/dd-droid.mjs +584 -0
  59. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +498 -0
  60. package/dist/harness-runtime/lib/dd-grok.mjs +177 -0
  61. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +557 -0
  62. package/dist/harness-runtime/lib/dd-opencode.mjs +363 -0
  63. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +751 -0
  64. package/dist/harness-runtime/lib/dd-zcode.mjs +1145 -0
  65. package/dist/harness-runtime/lib/delegation-instructions.d.mts +14 -0
  66. package/dist/harness-runtime/lib/delegation-instructions.mjs +157 -0
  67. package/dist/harness-runtime/lib/dispatch-fence.mjs +14 -0
  68. package/dist/harness-runtime/lib/driver-recovery.mjs +139 -0
  69. package/dist/harness-runtime/lib/droid-observation.mjs +66 -0
  70. package/dist/harness-runtime/lib/lease-renewal.d.mts +8 -0
  71. package/dist/harness-runtime/lib/lease-renewal.mjs +37 -0
  72. package/dist/harness-runtime/lib/lifecycle-command.d.mts +20 -0
  73. package/dist/harness-runtime/lib/lifecycle-command.mjs +273 -0
  74. package/dist/harness-runtime/lib/lifecycle-contract.d.mts +10 -0
  75. package/dist/harness-runtime/lib/lifecycle-contract.mjs +19 -0
  76. package/dist/harness-runtime/lib/managed-daemon.d.mts +16 -0
  77. package/dist/harness-runtime/lib/managed-daemon.mjs +683 -0
  78. package/dist/harness-runtime/lib/managed-flow-env.mjs +15 -0
  79. package/dist/harness-runtime/lib/model-observations.mjs +101 -0
  80. package/dist/harness-runtime/lib/native-children.d.mts +16 -0
  81. package/dist/harness-runtime/lib/native-children.mjs +113 -0
  82. package/dist/harness-runtime/lib/native-hook-command.d.mts +7 -0
  83. package/dist/harness-runtime/lib/native-hook-command.mjs +251 -0
  84. package/dist/harness-runtime/lib/native-request-proof.d.mts +2 -0
  85. package/dist/harness-runtime/lib/native-request-proof.mjs +65 -0
  86. package/dist/harness-runtime/lib/native-streams.mjs +45 -0
  87. package/dist/harness-runtime/lib/observation-clock.d.mts +18 -0
  88. package/dist/harness-runtime/lib/observation-clock.mjs +92 -0
  89. package/dist/harness-runtime/lib/opencode-plugin.mjs +85 -0
  90. package/dist/harness-runtime/lib/operation-context.mjs +5 -0
  91. package/dist/harness-runtime/lib/operation-errors.d.mts +16 -0
  92. package/dist/harness-runtime/lib/operation-errors.mjs +127 -0
  93. package/dist/harness-runtime/lib/operation-progress.d.mts +7 -0
  94. package/dist/harness-runtime/lib/operation-progress.mjs +114 -0
  95. package/dist/harness-runtime/lib/process-json.d.mts +5 -0
  96. package/dist/harness-runtime/lib/process-json.mjs +201 -0
  97. package/dist/harness-runtime/lib/process-snapshot.mjs +10 -0
  98. package/dist/harness-runtime/lib/provider-overload-policy.d.mts +13 -0
  99. package/dist/harness-runtime/lib/provider-overload-policy.mjs +138 -0
  100. package/dist/harness-runtime/lib/runner-events.mjs +200 -0
  101. package/dist/harness-runtime/lib/runner-lock.mjs +42 -0
  102. package/dist/harness-runtime/lib/session-settlement.mjs +30 -0
  103. package/dist/harness-runtime/lib/setup-progress.d.mts +9 -0
  104. package/dist/harness-runtime/lib/setup-progress.mjs +51 -0
  105. package/dist/harness-runtime/lib/tool-observations.d.mts +21 -0
  106. package/dist/harness-runtime/lib/tool-observations.mjs +228 -0
  107. package/dist/harness-runtime/lib/zcode-usage.mjs +55 -0
  108. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-1y4a697q.js +81 -0
  109. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-2ny4vxct.js +92 -0
  110. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-6mptkzbc.js +0 -0
  111. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-7x67cf1q.js +52 -0
  112. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-9rqn6x4v.js +16 -0
  113. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-9vdz0zwn.js +1661 -0
  114. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ecs5ftka.js +1466 -0
  115. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-frbwqjmf.js +145 -0
  116. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-gne7h7mq.js +50 -0
  117. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-gx9wnkzr.js +105 -0
  118. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-m2eywc8w.js +0 -0
  119. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-nwcpakzx.js +12 -0
  120. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-q4g3afnw.js +623 -0
  121. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-qav6mfdz.js +0 -0
  122. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-sxt88q69.js +12 -0
  123. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-t1xa31vm.js +39 -0
  124. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ve4aws36.js +103 -0
  125. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vmytr4q8.js +4 -0
  126. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vt9jbch8.js +36 -0
  127. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vyas3vgt.js +0 -0
  128. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-wjyeq25y.js +30 -0
  129. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-xgnj2w4v.js +20 -0
  130. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-y2etg5bs.js +9 -0
  131. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ydhz1zrh.js +238 -0
  132. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-z6qdwcsf.js +62 -0
  133. package/dist/harness-runtime/vendor/opencode-client/contract.d.ts +1 -0
  134. package/dist/harness-runtime/vendor/opencode-client/contract.js +12 -0
  135. package/dist/harness-runtime/vendor/opencode-client/effect/api/api.d.ts +2806 -0
  136. package/dist/harness-runtime/vendor/opencode-client/effect/api/api.js +0 -0
  137. package/dist/harness-runtime/vendor/opencode-client/effect/api.d.ts +4 -0
  138. package/dist/harness-runtime/vendor/opencode-client/effect/api.js +0 -0
  139. package/dist/harness-runtime/vendor/opencode-client/effect/client.d.ts +2530 -0
  140. package/dist/harness-runtime/vendor/opencode-client/effect/client.js +15 -0
  141. package/dist/harness-runtime/vendor/opencode-client/effect/generated/client-error.d.ts +7 -0
  142. package/dist/harness-runtime/vendor/opencode-client/effect/generated/client-error.js +6 -0
  143. package/dist/harness-runtime/vendor/opencode-client/effect/generated/client.d.ts +2529 -0
  144. package/dist/harness-runtime/vendor/opencode-client/effect/generated/client.js +8 -0
  145. package/dist/harness-runtime/vendor/opencode-client/effect/generated/index.d.ts +2 -0
  146. package/dist/harness-runtime/vendor/opencode-client/effect/generated/index.js +12 -0
  147. package/dist/harness-runtime/vendor/opencode-client/effect/index.d.ts +35 -0
  148. package/dist/harness-runtime/vendor/opencode-client/effect/index.js +75 -0
  149. package/dist/harness-runtime/vendor/opencode-client/effect/rpc.d.ts +20 -0
  150. package/dist/harness-runtime/vendor/opencode-client/effect/rpc.js +11 -0
  151. package/dist/harness-runtime/vendor/opencode-client/effect/service.d.ts +90 -0
  152. package/dist/harness-runtime/vendor/opencode-client/effect/service.js +187 -0
  153. package/dist/harness-runtime/vendor/opencode-client/package.json +84 -0
  154. package/dist/harness-runtime/vendor/opencode-client/promise/api.d.ts +21 -0
  155. package/dist/harness-runtime/vendor/opencode-client/promise/api.js +0 -0
  156. package/dist/harness-runtime/vendor/opencode-client/promise/client.d.ts +252 -0
  157. package/dist/harness-runtime/vendor/opencode-client/promise/client.js +14 -0
  158. package/dist/harness-runtime/vendor/opencode-client/promise/generated/client-error.d.ts +8 -0
  159. package/dist/harness-runtime/vendor/opencode-client/promise/generated/client-error.js +6 -0
  160. package/dist/harness-runtime/vendor/opencode-client/promise/generated/client.d.ts +259 -0
  161. package/dist/harness-runtime/vendor/opencode-client/promise/generated/client.js +7 -0
  162. package/dist/harness-runtime/vendor/opencode-client/promise/generated/index.d.ts +3 -0
  163. package/dist/harness-runtime/vendor/opencode-client/promise/generated/index.js +79 -0
  164. package/dist/harness-runtime/vendor/opencode-client/promise/generated/types.d.ts +8932 -0
  165. package/dist/harness-runtime/vendor/opencode-client/promise/generated/types.js +70 -0
  166. package/dist/harness-runtime/vendor/opencode-client/promise/index.d.ts +6 -0
  167. package/dist/harness-runtime/vendor/opencode-client/promise/index.js +83 -0
  168. package/dist/harness-runtime/vendor/opencode-client/promise/rpc.d.ts +32 -0
  169. package/dist/harness-runtime/vendor/opencode-client/promise/rpc.js +7 -0
  170. package/dist/harness-runtime/vendor/opencode-client/promise/service.d.ts +24 -0
  171. package/dist/harness-runtime/vendor/opencode-client/promise/service.js +187 -0
  172. package/dist/harness-runtime/vendor/opencode-client/pty-handoff.d.ts +12 -0
  173. package/dist/harness-runtime/vendor/opencode-client/pty-handoff.js +14 -0
  174. package/dist/harness-runtime/vendor/opencode-client/rpc-runtime.d.ts +20 -0
  175. package/dist/harness-runtime/vendor/opencode-client/rpc-runtime.js +14 -0
  176. package/dist/harness-runtime/vendor/opencode-client/service-contender.d.ts +37 -0
  177. package/dist/harness-runtime/vendor/opencode-client/service-contender.js +10 -0
  178. package/dist/harness-runtime/vendor/opencode-client/service-probe.d.ts +70 -0
  179. package/dist/harness-runtime/vendor/opencode-client/service-probe.js +16 -0
  180. package/dist/harness-runtime/vendor/opencode-client/service-timing.d.ts +12 -0
  181. package/dist/harness-runtime/vendor/opencode-client/service-timing.js +10 -0
  182. package/dist/harness-runtime/vendor/opencode-client/service-version.d.ts +2 -0
  183. package/dist/harness-runtime/vendor/opencode-client/service-version.js +6 -0
  184. package/dist/harness-runtime/vendor/opencode-client/service.d.ts +52 -0
  185. package/dist/harness-runtime/vendor/opencode-client/service.js +1 -0
  186. package/dist/harness-runtime/vendor/opencode-client/shared-events.d.ts +11 -0
  187. package/dist/harness-runtime/vendor/opencode-client/shared-events.js +8 -0
  188. package/dist/harness-runtime/vendor/opencode-client/solid/connection.d.ts +37 -0
  189. package/dist/harness-runtime/vendor/opencode-client/solid/connection.js +10 -0
  190. package/dist/harness-runtime/vendor/opencode-client/solid/data.d.ts +211 -0
  191. package/dist/harness-runtime/vendor/opencode-client/solid/data.js +18 -0
  192. package/dist/harness-runtime/vendor/opencode-client/solid/index.d.ts +3 -0
  193. package/dist/harness-runtime/vendor/opencode-client/solid/index.js +32 -0
  194. package/dist/harness-runtime/vendor/opencode-client/solid/pty.d.ts +22 -0
  195. package/dist/harness-runtime/vendor/opencode-client/solid/pty.js +8 -0
  196. package/dist/harness-runtime/vendor/opencode-plugin.mjs +3 -0
  197. package/dist/protocol/local-files.js +18 -9
  198. package/dist/runtime/context.js +8 -4
  199. package/dist/schemas/agent-profile.schema.json +1 -1
  200. package/dist/schemas/code-review-result.schema.json +1 -1
  201. package/dist/schemas/code-verification.schema.json +7 -5
  202. package/dist/schemas/code-work-batch.schema.json +2 -2
  203. package/dist/schemas/discovery-result.schema.json +184 -0
  204. package/dist/schemas/execution-bundle.schema.json +205 -0
  205. package/dist/schemas/execution-settings.schema.json +162 -0
  206. package/dist/schemas/flow-descriptor.schema.json +54 -0
  207. package/dist/schemas/flow-run.schema.json +28 -0
  208. package/dist/schemas/harness-catalog.schema.json +80 -0
  209. package/dist/schemas/mb-init-compose-result.schema.json +457 -0
  210. package/dist/schemas/mb-init-discover-result.schema.json +500 -0
  211. package/dist/schemas/mb-init-finish-result.schema.json +422 -0
  212. package/dist/schemas/mb-init-preflight-result.schema.json +367 -0
  213. package/dist/schemas/mb-init-review-result.schema.json +396 -0
  214. package/dist/schemas/mb-init-verify-result.schema.json +391 -0
  215. package/dist/schemas/memory-bootstrap-receipt-v2.schema.json +67 -0
  216. package/dist/schemas/memory-bootstrap-receipt.schema.json +354 -0
  217. package/dist/schemas/memory-composition-work-result.schema.json +38 -0
  218. package/dist/schemas/memory-repair-work-result.schema.json +49 -0
  219. package/dist/schemas/memory-review-correction-plan.schema.json +30 -0
  220. package/dist/schemas/memory-review-work-result.schema.json +43 -0
  221. package/dist/schemas/model-catalog.schema.json +153 -0
  222. package/dist/schemas/plan-review-decision.schema.json +3 -3
  223. package/dist/schemas/plan-review-result.schema.json +2 -2
  224. package/dist/schemas/profile-settings.schema.json +122 -0
  225. package/dist/schemas/release-manifest.schema.json +291 -0
  226. package/dist/schemas/run-control-receipt.schema.json +118 -0
  227. package/dist/schemas/run-control-request.schema.json +36 -0
  228. package/dist/schemas/run-engine-binding.schema.json +1 -1
  229. package/dist/schemas/vnext-protocol-plan.schema.json +2 -2
  230. package/dist/services/branch-context.js +3 -3
  231. package/dist/services/canon.js +11 -8
  232. package/dist/services/check-runner-evidence.js +250 -0
  233. package/dist/services/cleanup.js +133 -40
  234. package/dist/services/cli-operation-classifier.js +25 -62
  235. package/dist/services/cli-upgrade-allowlist.js +27 -0
  236. package/dist/services/code-checks.js +1168 -107
  237. package/dist/services/codex-hook-delivery.js +32 -0
  238. package/dist/services/command-context.js +80 -0
  239. package/dist/services/compatibility-preflight.js +13 -3
  240. package/dist/services/config.js +11 -8
  241. package/dist/services/continuation-outcome.js +20 -0
  242. package/dist/services/controller-child-capacity.js +13 -0
  243. package/dist/services/controller-child-observer.js +115 -0
  244. package/dist/services/controller-execution.js +88 -0
  245. package/dist/services/controller-fanout.js +202 -0
  246. package/dist/services/dashboard.js +143 -58
  247. package/dist/services/descriptor-coordinator-session.js +35 -0
  248. package/dist/services/descriptor-root-work.js +53 -0
  249. package/dist/services/descriptor-run-settlement.js +105 -0
  250. package/dist/services/descriptor-run-start.js +449 -0
  251. package/dist/services/descriptor-stage-coordinator.js +197 -0
  252. package/dist/services/descriptor-stage-settlement.js +222 -0
  253. package/dist/services/descriptor-stage-start.js +139 -0
  254. package/dist/services/discovery/agy-discovery.js +2 -0
  255. package/dist/services/discovery/codex-discovery.js +2 -0
  256. package/dist/services/discovery/droid-discovery.js +2 -0
  257. package/dist/services/discovery/grok-discovery.js +2 -0
  258. package/dist/services/discovery/opencode-discovery.js +2 -0
  259. package/dist/services/discovery/static.js +31 -0
  260. package/dist/services/discovery/zcode-discovery.js +2 -0
  261. package/dist/services/engines.js +294 -153
  262. package/dist/services/eval-snapshots.js +1215 -116
  263. package/dist/services/execution-bundles.js +263 -0
  264. package/dist/services/execution-policy.js +185 -0
  265. package/dist/services/executor-onboarding.js +56 -0
  266. package/dist/services/external-work-launch.js +308 -0
  267. package/dist/services/flow-catalog.js +21 -0
  268. package/dist/services/flow-descriptors.js +82 -0
  269. package/dist/services/frozen-engine.js +250 -0
  270. package/dist/services/generic-execution.js +164 -0
  271. package/dist/services/harness-adapter.js +355 -36
  272. package/dist/services/harness-config.js +2 -0
  273. package/dist/services/harness-discovery.js +29 -0
  274. package/dist/services/harness-doctor.js +75 -0
  275. package/dist/services/harness-probe-effects.js +51 -0
  276. package/dist/services/harness-version-probe.js +100 -0
  277. package/dist/services/hooks.js +771 -325
  278. package/dist/services/hybrid-contracts.js +483 -0
  279. package/dist/services/ids.js +29 -1
  280. package/dist/services/installation-lock.js +95 -0
  281. package/dist/services/installed-flow-source.js +183 -0
  282. package/dist/services/lanes.js +78 -65
  283. package/dist/services/lifecycle-command.js +1 -288
  284. package/dist/services/lifecycle-contract.js +1 -0
  285. package/dist/services/lifecycle-invocations.js +2007 -0
  286. package/dist/services/managed-daemon-binding.js +38 -0
  287. package/dist/services/managed-lease-monitor.js +133 -0
  288. package/dist/services/managed-processes.js +475 -55
  289. package/dist/services/memory-accepted-composition.js +77 -0
  290. package/dist/services/memory-baseline-preservation.js +65 -0
  291. package/dist/services/memory-completed-correction-wave.js +320 -0
  292. package/dist/services/memory-compose-handler.js +258 -0
  293. package/dist/services/memory-composition-acceptance.js +199 -0
  294. package/dist/services/memory-composition-graph.js +134 -0
  295. package/dist/services/memory-composition-registration.js +146 -0
  296. package/dist/services/memory-composition-work-custody.js +151 -0
  297. package/dist/services/memory-composition-work-results.js +96 -0
  298. package/dist/services/memory-corrected-review-acceptance.js +129 -0
  299. package/dist/services/memory-corrected-review-batch.js +30 -0
  300. package/dist/services/memory-corrected-review-findings.js +106 -0
  301. package/dist/services/memory-corrected-review-inputs.js +210 -0
  302. package/dist/services/memory-corrected-review-projection.js +78 -0
  303. package/dist/services/memory-corrected-review-receipt.js +170 -0
  304. package/dist/services/memory-corrected-review-registration.js +191 -0
  305. package/dist/services/memory-correction-check-receipt.js +219 -0
  306. package/dist/services/memory-correction-input-owner.js +6 -0
  307. package/dist/services/memory-discover-handler.js +104 -0
  308. package/dist/services/memory-finish-evidence.js +435 -0
  309. package/dist/services/memory-finish-handler.js +215 -0
  310. package/dist/services/memory-finish-integration.js +280 -0
  311. package/dist/services/memory-finish-readiness.js +122 -0
  312. package/dist/services/memory-git-integration.js +151 -0
  313. package/dist/services/memory-historical-corrected-review.js +206 -0
  314. package/dist/services/memory-historical-original-review.js +130 -0
  315. package/dist/services/memory-historical-repair-registration.js +99 -0
  316. package/dist/services/memory-historical-second-review.js +128 -0
  317. package/dist/services/memory-init-bootstrap-readiness.js +210 -0
  318. package/dist/services/memory-init-contexts.js +89 -0
  319. package/dist/services/memory-init-controller-start.js +84 -0
  320. package/dist/services/memory-init-engine-release.js +223 -0
  321. package/dist/services/memory-init-git-policy.js +136 -0
  322. package/dist/services/memory-init-lint-capability.js +185 -0
  323. package/dist/services/memory-init-native-capacity.js +296 -0
  324. package/dist/services/memory-init-preflight.js +330 -0
  325. package/dist/services/memory-init-project-bootstrap.js +599 -0
  326. package/dist/services/memory-init-request-candidate.js +157 -0
  327. package/dist/services/memory-init-request-input.js +66 -0
  328. package/dist/services/memory-init-resource-storage.js +125 -0
  329. package/dist/services/memory-init-run-allocation.js +81 -0
  330. package/dist/services/memory-init-run-bundle.js +98 -0
  331. package/dist/services/memory-init-start.js +181 -0
  332. package/dist/services/memory-init-startup-custody.js +80 -0
  333. package/dist/services/memory-init-startup-input.js +240 -0
  334. package/dist/services/memory-init-storage.js +81 -0
  335. package/dist/services/memory-init-stores.js +359 -0
  336. package/dist/services/memory-init-worktrunk-capability.js +178 -0
  337. package/dist/services/memory-layer-publication.js +611 -0
  338. package/dist/services/memory-layer.js +156 -0
  339. package/dist/services/memory-lint-config.js +85 -0
  340. package/dist/services/memory-native-review-copy.js +165 -0
  341. package/dist/services/memory-normal-flow-entry.js +75 -0
  342. package/dist/services/memory-ordinary-defaults.js +112 -0
  343. package/dist/services/memory-preflight-bootstrap-custody.js +115 -0
  344. package/dist/services/memory-preflight-handler.js +259 -0
  345. package/dist/services/memory-repair-current-view.js +299 -0
  346. package/dist/services/memory-repair-document-delta.js +451 -0
  347. package/dist/services/memory-repair-history.js +491 -0
  348. package/dist/services/memory-repair-physical-binding.js +312 -0
  349. package/dist/services/memory-repair-publication-files.js +152 -0
  350. package/dist/services/memory-repair-publication-intent.js +27 -0
  351. package/dist/services/memory-repair-publication-plan.js +160 -0
  352. package/dist/services/memory-repair-publication-source.js +225 -0
  353. package/dist/services/memory-repair-publication.js +794 -0
  354. package/dist/services/memory-repair-registration.js +239 -0
  355. package/dist/services/memory-repair-work-baseline.js +413 -0
  356. package/dist/services/memory-repair-work-completion.js +97 -0
  357. package/dist/services/memory-repair-work-custody.js +73 -0
  358. package/dist/services/memory-repair-work-results.js +89 -0
  359. package/dist/services/memory-repair-workspace.js +252 -0
  360. package/dist/services/memory-repair-writer-copy.js +735 -0
  361. package/dist/services/memory-result-binding.js +173 -0
  362. package/dist/services/memory-retained-review-authority.js +129 -0
  363. package/dist/services/memory-retained-review-results.js +70 -0
  364. package/dist/services/memory-retained-second-baseline.js +102 -0
  365. package/dist/services/memory-retained-second-repairs.js +156 -0
  366. package/dist/services/memory-review-acceptance.js +214 -0
  367. package/dist/services/memory-review-correction-plan.js +171 -0
  368. package/dist/services/memory-review-handler.js +585 -0
  369. package/dist/services/memory-review-inputs.js +200 -0
  370. package/dist/services/memory-review-receipt.js +180 -0
  371. package/dist/services/memory-review-registration.js +108 -0
  372. package/dist/services/memory-review-work-custody.js +124 -0
  373. package/dist/services/memory-review-work-input.js +72 -0
  374. package/dist/services/memory-review-work-results.js +73 -0
  375. package/dist/services/memory-second-corrected-review-inputs.js +165 -0
  376. package/dist/services/memory-second-correction-plan.js +76 -0
  377. package/dist/services/memory-second-repair-baseline.js +91 -0
  378. package/dist/services/memory-second-repair-launch.js +614 -0
  379. package/dist/services/memory-second-repair-registration.js +88 -0
  380. package/dist/services/memory-second-repair-source.js +187 -0
  381. package/dist/services/memory-second-review-acceptance.js +115 -0
  382. package/dist/services/memory-source-observations.js +117 -0
  383. package/dist/services/memory-stage-dispatch.js +28 -0
  384. package/dist/services/memory-stage-results.js +199 -0
  385. package/dist/services/memory-verification-acceptance.js +135 -0
  386. package/dist/services/memory-verification-checks.js +252 -0
  387. package/dist/services/memory-verification-receipt.js +159 -0
  388. package/dist/services/memory-verify-handler.js +135 -0
  389. package/dist/services/memory-work-result-preparation.js +63 -0
  390. package/dist/services/merge-queue.js +228 -149
  391. package/dist/services/merge-server.js +161 -35
  392. package/dist/services/migrations.js +37 -16
  393. package/dist/services/model-catalog.js +109 -0
  394. package/dist/services/model-discovery.js +46 -0
  395. package/dist/services/native-daemon-history.js +49 -0
  396. package/dist/services/native-session-control.js +39 -0
  397. package/dist/services/operator-profiles.js +236 -0
  398. package/dist/services/plan-runtime.js +26 -11
  399. package/dist/services/plans.js +28 -25
  400. package/dist/services/projects.js +37 -17
  401. package/dist/services/prompts.js +28 -17
  402. package/dist/services/protocols.js +163 -87
  403. package/dist/services/provider-capacity.js +385 -0
  404. package/dist/services/recovery-observation-budget.js +121 -0
  405. package/dist/services/recovery-snapshot-database.js +560 -0
  406. package/dist/services/release-archive-producer.js +52 -0
  407. package/dist/services/release-archive.js +143 -0
  408. package/dist/services/release-cache.js +142 -0
  409. package/dist/services/release-compatibility.js +18 -0
  410. package/dist/services/release-download.js +67 -0
  411. package/dist/services/release-envelope.js +104 -0
  412. package/dist/services/release-manifest.js +45 -0
  413. package/dist/services/release-payload.js +114 -0
  414. package/dist/services/release-producer.js +63 -0
  415. package/dist/services/release-provenance.js +160 -0
  416. package/dist/services/release-registry.js +66 -0
  417. package/dist/services/release-source.js +194 -0
  418. package/dist/services/repair-intents.js +42 -0
  419. package/dist/services/review-copy.js +165 -0
  420. package/dist/services/review-deferral.js +23 -0
  421. package/dist/services/run-control-receipt.js +56 -0
  422. package/dist/services/run-control-worker.js +411 -0
  423. package/dist/services/run-control.js +1056 -0
  424. package/dist/services/run-controller-adapter.js +399 -0
  425. package/dist/services/run-controller-capture.js +186 -0
  426. package/dist/services/run-controller-process.js +208 -0
  427. package/dist/services/run-controller-recovery.js +289 -0
  428. package/dist/services/run-controller-state.js +97 -0
  429. package/dist/services/run-controller.js +1205 -0
  430. package/dist/services/run-engine-bindings.js +1 -1
  431. package/dist/services/run-fork.js +147 -0
  432. package/dist/services/run-health.js +149 -0
  433. package/dist/services/run-observations.js +152 -0
  434. package/dist/services/run-projection.js +22 -64
  435. package/dist/services/run-recovery-guard.js +77 -0
  436. package/dist/services/run-recovery-runtime.js +112 -0
  437. package/dist/services/run-recovery.js +489 -0
  438. package/dist/services/run-start-custody.js +177 -0
  439. package/dist/services/runs.js +1019 -247
  440. package/dist/services/runtime-budget.js +430 -0
  441. package/dist/services/runtime-command.js +43 -0
  442. package/dist/services/runtime-scope-capture.js +52 -0
  443. package/dist/services/runtime-scope-control.js +468 -0
  444. package/dist/services/runtime-scope-resume.js +599 -0
  445. package/dist/services/runtime-scope-stop.js +210 -0
  446. package/dist/services/runtime-scope-worker.js +287 -0
  447. package/dist/services/runtime-service.js +96 -0
  448. package/dist/services/schema-validation.js +10 -8
  449. package/dist/services/scope-writer-cleanup.js +27 -0
  450. package/dist/services/sessions.js +35 -69
  451. package/dist/services/setup.js +377 -0
  452. package/dist/services/stage-blocker.js +17 -7
  453. package/dist/services/stage-context.js +193 -19
  454. package/dist/services/stage-handlers.js +74 -0
  455. package/dist/services/stage-lifecycle.js +152 -116
  456. package/dist/services/stage-pause.js +89 -75
  457. package/dist/services/stage-report-renderer.js +234 -6
  458. package/dist/services/stage-work-graph.js +52 -0
  459. package/dist/services/tooling.js +41 -30
  460. package/dist/services/usage.js +49 -101
  461. package/dist/services/verification-matrix.js +375 -0
  462. package/dist/services/vnext-code-review.js +223 -88
  463. package/dist/services/vnext-code.js +327 -226
  464. package/dist/services/vnext-contracts.js +10 -1
  465. package/dist/services/vnext-execution-profile.js +80 -5
  466. package/dist/services/vnext-fanout.js +311 -19
  467. package/dist/services/vnext-merge.js +332 -111
  468. package/dist/services/vnext-plan-review.js +279 -127
  469. package/dist/services/vnext-plan.js +228 -104
  470. package/dist/services/vnext-protocolize.js +531 -95
  471. package/dist/services/vnext-specify.js +119 -81
  472. package/dist/services/vnext-workspace-policy.js +7 -2
  473. package/dist/services/work-registry.js +1627 -199
  474. package/dist/services/workspace-bootstrap.js +52 -15
  475. package/dist/services/workspace-files.js +85 -0
  476. package/dist/services/worktrees.js +105 -48
  477. package/dist/shared/entity-references.js +8 -0
  478. package/dist/shared/errors.js +27 -0
  479. package/dist/storage/agent-output.js +26 -0
  480. package/dist/storage/database.js +891 -39
  481. package/dist/storage/foreign-key-transaction.js +39 -0
  482. package/dist/storage/immutable-input.js +45 -0
  483. package/dist/storage/import-guard.js +102 -0
  484. package/dist/storage/native-cleanup.js +52 -0
  485. package/dist/storage/paths.js +17 -4
  486. package/dist/storage/projection-lock.js +73 -0
  487. package/dist/storage/work-references.js +23 -0
  488. package/dist/storage/writer-contract.js +89 -0
  489. package/dist/storage/writer-migration.js +85 -0
  490. package/package.json +43 -13
  491. package/tools/create-canon-release.mjs +33 -0
  492. package/tools/produce-hybrid-release.mjs +41 -0
  493. package/tools/repair-paused-run-status.mjs +84 -0
@@ -1,11 +1,18 @@
1
+ import { managedInvocationContext, managedLifecycleCommand } from "./lifecycle-invocations.js";
2
+ import { stageLifecycleInstruction } from "../harness-runtime/lib/delegation-instructions.mjs";
3
+ import { continueProviderCapacity } from "./provider-capacity.js";
4
+ import { errorRecord } from "./run-controller-state.js";
1
5
  import crypto from "node:crypto";
2
6
  import fs from "node:fs";
3
7
  import path from "node:path";
4
8
  import { AppError } from "../shared/errors.js";
9
+ import { runtimeBudgetForRun } from "./runtime-budget.js";
10
+ import { managedLeaseMonitor } from "./managed-lease-monitor.js";
5
11
  import { flowCommand } from "./stage-pause.js";
6
- import { validateSchema } from "./schema-validation.js";
7
- import { adapterSessionId, runHarnessAdapter } from "./harness-adapter.js";
12
+ import { adapterSessionId, prepareHarnessFlowExecutable, runHarnessAdapter } from "./harness-adapter.js";
8
13
  import { harnessRuntimeArguments, resolveHarnessCommand } from "./harness-config.js";
14
+ import { assertRunMutationAllowed, recoveryGuard } from "./run-recovery.js";
15
+ import { loadAgentProfile, resolveExecutionPolicy, runtimeHarness } from "./execution-policy.js";
9
16
  /** A deterministic dispatcher; only the launched harness Session performs agent work. */
10
17
  export async function serveMergeRequests(context, input) {
11
18
  const running = context.db.get("SELECT server_id, pid FROM merge_servers WHERE status IN ('running','stopping') ORDER BY started_at LIMIT 1");
@@ -58,73 +65,192 @@ export async function serveMergeRequests(context, input) {
58
65
  export function mergeServerStatus(context) { return { ok: true, servers: context.db.all("SELECT server_id, profile_id, status, pid, started_at, heartbeat_at, stopped_at, journal_path FROM merge_servers ORDER BY started_at DESC") }; }
59
66
  export function stopMergeServer(context, input) { const changed = context.db.run("UPDATE merge_servers SET status = 'stopping', heartbeat_at = ? WHERE server_id = ? AND status = 'running'", [context.now(), input.serverId]); if (changed.changes !== 1)
60
67
  throw new AppError("not_found", "Running merge-server was not found", 1, { server_id: input.serverId }); return { ok: true, server_id: input.serverId, status: "stopping" }; }
68
+ /** Both dispatchers enter the same project FIFO before creating any provider Session. */
69
+ export function claimQueuedMergeRequest(context, input) {
70
+ return context.db.writeTransaction(() => {
71
+ const request = context.db.get("SELECT merge_request_id, project_id, run_id, executor_work_id, target_workspace, execution_route, status, created_at FROM merge_requests WHERE merge_request_id = ?", [input.requestId]);
72
+ if (!request)
73
+ throw new AppError("not_found", "MERGE request was not found", 1);
74
+ const generation = assertRunMutationAllowed(context, request.project_id, request.run_id, input.expectedGeneration);
75
+ const earlier = context.db.get("SELECT 1 FROM merge_requests WHERE project_id = ? AND status NOT IN ('completed','failed','cancelled','superseded') AND (created_at < ? OR (created_at = ? AND merge_request_id < ?)) LIMIT 1", [request.project_id, request.created_at, request.created_at, request.merge_request_id]);
76
+ if (request.status !== "queued" || request.execution_route !== "server" || earlier)
77
+ return null;
78
+ const token = crypto.randomUUID();
79
+ context.db.run("UPDATE merge_requests SET status = 'dispatching', dispatch_owner = ?, dispatch_lease_token = ?, dispatch_lease_expires_at = ?, updated_at = ? WHERE merge_request_id = ? AND status = 'queued'", [input.ownerId, token, new Date(Date.now() + 120_000).toISOString(), context.now(), request.merge_request_id]);
80
+ return { request, token, generation };
81
+ });
82
+ }
61
83
  async function dispatch(context, input) {
62
- const token = crypto.randomUUID();
63
- const leaseUntil = new Date(Date.now() + 120_000).toISOString();
64
- const claimed = context.db.run("UPDATE merge_requests SET status = 'dispatching', dispatch_owner = ?, dispatch_lease_token = ?, dispatch_lease_expires_at = ?, updated_at = ? WHERE merge_request_id = ? AND status = 'queued' AND execution_route = 'server'", [input.serverId, token, leaseUntil, context.now(), input.request.merge_request_id]);
65
- if (claimed.changes !== 1)
84
+ const run = context.db.get("SELECT index_json, project_root FROM runs WHERE project_id = ? AND id = ?", [input.request.project_id, input.request.run_id]);
85
+ if (!run)
86
+ throw new AppError("not_found", "MERGE RUN was not found", 1, { run_id: input.request.run_id });
87
+ const snapshot = JSON.parse(run.index_json).execution_profile;
88
+ if (snapshot?.settings?.execution) {
89
+ if (!snapshot.agent_profiles)
90
+ throw new AppError("execution_profile_not_frozen", "MERGE dispatch requires frozen RUN agent profiles", 1, { run_id: input.request.run_id });
91
+ const resolved = resolveExecutionPolicy({ ddFlowHome: context.ddFlowHome, policy: snapshot.settings.execution, profiles: snapshot.agent_profiles, stage: "merge" });
92
+ input = { ...input, profile: resolved.coordinator };
93
+ }
94
+ const claim = claimQueuedMergeRequest(context, { requestId: input.request.merge_request_id, ownerId: input.serverId });
95
+ if (!claim)
66
96
  return;
67
- const leaseHeartbeat = setInterval(() => { const now = context.now(); context.db.run("UPDATE merge_requests SET dispatch_lease_expires_at = ?, updated_at = ? WHERE merge_request_id = ? AND status = 'dispatching' AND dispatch_owner = ? AND dispatch_lease_token = ?", [new Date(Date.now() + 120_000).toISOString(), now, input.request.merge_request_id, input.serverId, token]); }, 30_000);
68
- leaseHeartbeat.unref();
97
+ const { generation, token } = claim;
69
98
  const stateDir = path.join(input.root, input.request.merge_request_id);
70
99
  fs.mkdirSync(stateDir, { recursive: true });
71
100
  const promptFile = path.join(stateDir, "launch.md");
72
101
  const adapterJournal = path.join(stateDir, "adapter.events.jsonl");
73
- const stageCommand = `${flowCommand(context)} stage start ${input.request.run_id} --stage merge --project-root ${JSON.stringify(input.request.target_workspace)} --json --progress-jsonl`;
74
- fs.writeFileSync(promptFile, `Start the assigned MERGE stage now. Your first tool call must be this exact standalone command:\n\n${stageCommand}\n\nTrust and follow the complete stage packet it returns. Continue through merge apply and stage finish. Stop only when the stage reaches a terminal result or explicitly requests user/operator action.\n`);
102
+ const flowExecutable = prepareHarnessFlowExecutable(context, stateDir);
103
+ let runtimeContext = { ...context, env: { ...context.env, DD_FLOW_BIN: flowExecutable } };
104
+ const runtimeOwner = { schema_id: "dd-flow/runtime-owner@1", owner_id: `run:${input.request.run_id}:merge:${input.request.merge_request_id}`, role: "merge", project_id: input.request.project_id, run_id: input.request.run_id, work_id: input.request.executor_work_id, operation_id: `${input.request.merge_request_id}:${token}`, state_dir: stateDir, dd_flow_home: context.ddFlowHome, dd_flow_bin: flowExecutable, resource_home: context.env.DD_FLOW_RESOURCE_HOME ?? context.ddFlowHome, budget: runtimeBudgetForRun(context, input.request.project_id, input.request.run_id) };
75
105
  event(input.journal, { type: "launch_intent", server_id: input.serverId, request_id: input.request.merge_request_id, token, profile: input.profile, prompt_file: promptFile, at: context.now() });
76
106
  input.progress?.(`launching ${input.request.merge_request_id} with ${input.profile.id}`);
77
107
  const executable = resolveHarnessCommand(context.ddFlowHome, input.profile.harness, "adapter");
78
- const common = ["--state-dir", stateDir, "--journal", adapterJournal, "--cwd", input.request.target_workspace, "--provider", input.profile.provider, "--model", input.profile.model, "--reasoning", input.profile.reasoning, "--mode", input.profile.mode, "--permission", input.profile.permission, "--project-root", input.request.target_workspace, "--dd-flow-home", context.ddFlowHome, ...harnessRuntimeArguments(context.ddFlowHome, input.profile.harness), "--json"];
108
+ const common = ["--dd-flow-bin", flowExecutable, "--state-dir", stateDir, "--journal", adapterJournal, "--cwd", input.request.target_workspace, "--provider", input.profile.provider, "--model", input.profile.model, "--reasoning", input.profile.reasoning, "--mode", input.profile.mode, "--permission", input.profile.permission, "--project-root", run.project_root, "--dd-flow-home", context.ddFlowHome, ...harnessRuntimeArguments(context.ddFlowHome, input.profile.harness), "--json"];
109
+ const retained = { profile: input.profile, executable, owner: runtimeOwner, session_id: null, state_dir: stateDir, create_operation_id: `${input.request.merge_request_id}:${token}:session.create`, prompt_operation_id: `${input.request.merge_request_id}:${token}:session.prompt` };
110
+ const retain = () => { const changed = context.db.run("UPDATE merge_requests SET adapter_receipt_json = ?, updated_at = ? WHERE merge_request_id = ? AND dispatch_owner = ? AND dispatch_lease_token = ?", [JSON.stringify(retained), context.now(), input.request.merge_request_id, input.serverId, token]); if (changed.changes !== 1)
111
+ throw new AppError("capacity_continuation_blocked", "MERGE dispatch owner changed before durable receipt retention", 1); };
112
+ retain();
113
+ const lease = managedLeaseMonitor(attemptMs => {
114
+ const db = context.db.withDeadline(Date.now() + attemptMs);
115
+ return db.writeTransaction(() => {
116
+ assertRunMutationAllowed({ ...context, db }, input.request.project_id, input.request.run_id, generation);
117
+ const expiry = new Date(Date.now() + 120_000).toISOString();
118
+ const changed = db.run("UPDATE merge_requests SET dispatch_lease_expires_at = ?, updated_at = ? WHERE merge_request_id = ? AND status IN ('dispatching','active') AND dispatch_owner = ? AND dispatch_lease_token = ?", [expiry, context.now(), input.request.merge_request_id, input.serverId, token]);
119
+ return changed.changes === 1 ? { owned: true, lease_expires_at: expiry } : { owned: false };
120
+ });
121
+ });
122
+ const ownership = new AbortController();
123
+ const leaseHeartbeat = setInterval(() => {
124
+ lease.background(() => assertRunMutationAllowed(context, input.request.project_id, input.request.run_id, generation), error => {
125
+ ownership.abort(error);
126
+ try {
127
+ event(input.journal, { type: "dispatch_lease_unconfirmed", request_id: input.request.merge_request_id, error: errorRecord(error), at: context.now() });
128
+ }
129
+ catch { /* No unhandled timer exception; the next boundary sees retained monitor failure. */ }
130
+ });
131
+ }, 30_000);
132
+ leaseHeartbeat.unref();
79
133
  try {
80
- const adapterCall = (args, timeoutMs, progressMessage) => runHarnessAdapter({ executable, args, env: context.env, ...(timeoutMs ? { timeoutMs } : {}), ...(input.progress ? { progress: input.progress } : {}), ...(progressMessage ? { progressMessage } : {}), onEvidence: (value) => event(input.journal, { type: "adapter_call", ...value }) });
134
+ const adapterCall = async (args, timeoutMs, progressMessage, id = `${input.request.merge_request_id}:${token}:${args[0]}.${args[1]}`) => {
135
+ await lease.ensureConfirmed(() => assertRunMutationAllowed(context, input.request.project_id, input.request.run_id, generation));
136
+ try {
137
+ return await runHarnessAdapter({ executable, args, harness: runtimeHarness(input.profile), env: runtimeContext.env, runtimeOwner, operationId: id, signal: ownership.signal, ...(timeoutMs !== undefined ? { timeoutMs } : {}), ...(input.progress ? { progress: input.progress } : {}), ...(progressMessage ? { progressMessage } : {}), onEvidence: (value) => event(input.journal, { type: "adapter_call", ...value }), onNativeOutcome: value => {
138
+ if (args[0] === "session" && args[1] === "create" && adapterSessionId(value)) {
139
+ Object.assign(retained, { session_id: adapterSessionId(value), create: value, settlement_pending: true });
140
+ retain();
141
+ }
142
+ } });
143
+ }
144
+ catch (error) {
145
+ if (ownership.signal.aborted)
146
+ throw ownership.signal.reason;
147
+ throw error;
148
+ }
149
+ };
81
150
  await adapterCall(["daemon", "start", ...common]);
82
151
  const created = await adapterCall(["session", "create", ...common]);
83
152
  const sessionId = adapterSessionId(created);
84
153
  if (!sessionId)
85
154
  throw new AppError("merge_adapter_invalid", "Harness adapter did not return a Session id", 1, { harness: input.profile.harness, response: created });
86
- const prompted = await adapterCall(["session", "prompt", "--session-id", sessionId, "--prompt-file", promptFile, ...common], 45 * 60 * 1000, `MERGE ${input.request.merge_request_id} Session ${sessionId} is still running`);
155
+ Object.assign(retained, { session_id: sessionId, create: created, settlement_pending: false });
156
+ retain();
157
+ runtimeContext = managedInvocationContext(runtimeContext, { harness: runtimeHarness(input.profile), stateDir, sessionId, projectRoot: run.project_root, runId: input.request.run_id, generation });
158
+ const stageCommand = managedLifecycleCommand(runtimeContext, `${flowCommand(runtimeContext)} stage start ${input.request.run_id} --stage merge --project-root ${JSON.stringify(run.project_root)} --json --progress-jsonl`);
159
+ fs.writeFileSync(promptFile, `Start the assigned MERGE stage now. Your first tool call must be this exact standalone command:\n\n${stageCommand}\n\nTrust and follow the complete stage packet it returns. Continue through merge apply and stage finish. Stop only when the stage reaches a terminal result or explicitly requests user/operator action.\n\n${stageLifecycleInstruction}\n`);
160
+ let prompted;
161
+ retained.progress_policy = "native-inactivity@1";
162
+ retain();
163
+ try {
164
+ prompted = await adapterCall(["session", "prompt", "--session-id", sessionId, "--prompt-file", promptFile, ...common], null, `MERGE ${input.request.merge_request_id} Session ${sessionId} is still running`);
165
+ }
166
+ catch (error) {
167
+ const capacityIntents = (retained.capacity_intents ?? []);
168
+ const capacityFailures = (retained.capacity_failures ?? []);
169
+ const resumed = await continueProviderCapacity({ error, sessionId, operationId: String(retained.prompt_operation_id), allowSettledDescendants: true,
170
+ signal: ownership.signal,
171
+ continuationText: `Continue only MERGE ${input.request.merge_request_id}, Work ${input.request.executor_work_id}, RUN ${input.request.run_id} in this same Session and target ${input.request.target_workspace}. First inspect current durable Stage/Work/MERGE receipts. Never repeat Stage start, accepted answer, completed apply or checks. Follow only the current exact issued successor and stop at the original boundary.`,
172
+ retainedIntents: () => capacityIntents,
173
+ retainedFailures: () => capacityFailures,
174
+ retainFailure: (_id, refusal) => { capacityFailures.push(refusal); retained.capacity_failures = capacityFailures; retain(); },
175
+ inspectOperation: id => adapterCall(["daemon", "operation", "--state-dir", stateDir, "--operation-id", id, "--json"], 30_000, undefined, `${id}:capacity-observe`),
176
+ inspectSession: turnId => adapterCall(["session", "inspect", "--session-id", sessionId, ...(turnId ? ["--turn-id", turnId] : []), ...common], 30_000, undefined, `${input.request.merge_request_id}:${token}:capacity-inspect`),
177
+ assertCurrent: () => {
178
+ assertRunMutationAllowed(context, input.request.project_id, input.request.run_id, generation);
179
+ const current = context.db.get("SELECT status, dispatch_owner, dispatch_lease_token, dispatch_lease_expires_at, target_workspace FROM merge_requests WHERE merge_request_id = ?", [input.request.merge_request_id]);
180
+ if (current?.status === "completed" || current?.status === "action_required")
181
+ return;
182
+ if (!current || !["dispatching", "active"].includes(current.status) || current.dispatch_owner !== input.serverId || current.dispatch_lease_token !== token
183
+ || current.dispatch_lease_expires_at <= context.now() || current.target_workspace !== input.request.target_workspace)
184
+ throw new AppError("capacity_continuation_blocked", "MERGE dispatch owner or target changed during capacity wait", 1, { request_id: input.request.merge_request_id });
185
+ if (!["created", "running", "paused", "completed"].includes(context.db.get("SELECT status FROM works WHERE work_id = ?", [input.request.executor_work_id])?.status ?? ""))
186
+ throw new AppError("capacity_continuation_blocked", "MERGE Work has failed or lost its binding", 1);
187
+ },
188
+ decision: () => {
189
+ const status = context.db.get("SELECT status FROM merge_requests WHERE merge_request_id = ?", [input.request.merge_request_id])?.status;
190
+ const workStatus = context.db.get("SELECT status FROM works WHERE work_id = ?", [input.request.executor_work_id])?.status;
191
+ return status === "completed" ? "completed" : status === "action_required" || workStatus === "paused" ? "paused" : "continue";
192
+ },
193
+ retainIntent: intent => { capacityIntents.push(intent); retained.capacity_intents = capacityIntents; retained.capacity_intent = intent; retain(); },
194
+ prompt: (id, text, ordinal) => {
195
+ const file = path.join(stateDir, `capacity-${ordinal}.md`);
196
+ if (fs.existsSync(file)) {
197
+ if (fs.readFileSync(file, "utf8") !== text)
198
+ throw new AppError("capacity_continuation_conflict", "Retained MERGE continuation prompt differs", 1);
199
+ }
200
+ else
201
+ fs.writeFileSync(file, text, { flag: "wx", mode: 0o600 });
202
+ return adapterCall(["session", "prompt", "--session-id", sessionId, "--prompt-file", file, ...common], null, `MERGE ${input.request.merge_request_id} Session ${sessionId} capacity continuation ${ordinal}`, id);
203
+ } });
204
+ if (!resumed)
205
+ throw error;
206
+ prompted = resumed;
207
+ }
208
+ Object.assign(retained, { response: prompted });
209
+ retain();
210
+ assertRunMutationAllowed(context, input.request.project_id, input.request.run_id, generation);
87
211
  const request = context.db.get("SELECT status FROM merge_requests WHERE merge_request_id = ?", [input.request.merge_request_id]);
88
- context.db.run("UPDATE merge_requests SET adapter_receipt_json = ?, updated_at = ? WHERE merge_request_id = ?", [JSON.stringify({ profile: input.profile, executable, session_id: sessionId, response: prompted }), context.now(), input.request.merge_request_id]);
89
212
  event(input.journal, { type: "adapter_settled", request_id: input.request.merge_request_id, session_id: sessionId, request_status: request?.status ?? null, at: context.now() });
90
213
  if (request?.status === "dispatching")
91
- context.db.run("UPDATE merge_requests SET status = 'queued', dispatch_owner = NULL, dispatch_lease_token = NULL, dispatch_lease_expires_at = NULL, last_error_json = ?, updated_at = ? WHERE merge_request_id = ?", [JSON.stringify({ code: "adapter_did_not_start_stage", response: prompted }), context.now(), input.request.merge_request_id]);
214
+ context.db.run("UPDATE merge_requests SET status = 'recovery_required', last_error_json = ?, updated_at = ? WHERE merge_request_id = ? AND status = 'dispatching' AND dispatch_owner = ? AND dispatch_lease_token = ? AND ? = COALESCE((SELECT MAX(generation) FROM run_recovery_guards WHERE project_id = merge_requests.project_id AND run_id = merge_requests.run_id), 0)", [JSON.stringify({ code: "adapter_did_not_start_stage", response: prompted }), context.now(), input.request.merge_request_id, input.serverId, token, generation]);
92
215
  }
93
216
  catch (error) {
217
+ if (error instanceof AppError && ["run_recovery_guarded", "run_recovery_generation_stale"].includes(error.code)) {
218
+ event(input.journal, { type: "recovery_dispatch_blocked", request_id: input.request.merge_request_id, code: error.code, at: context.now() });
219
+ throw error;
220
+ }
94
221
  const request = context.db.get("SELECT status FROM merge_requests WHERE merge_request_id = ?", [input.request.merge_request_id]);
95
- if (request?.status === "dispatching")
96
- context.db.run("UPDATE merge_requests SET status = 'queued', dispatch_owner = NULL, dispatch_lease_token = NULL, dispatch_lease_expires_at = NULL, last_error_json = ?, updated_at = ? WHERE merge_request_id = ?", [JSON.stringify({ code: "adapter_launch_failed", error: String(error) }), context.now(), input.request.merge_request_id]);
97
- else if (request?.status === "active")
98
- context.db.run("UPDATE merge_requests SET status = 'recovery_required', last_error_json = ?, updated_at = ? WHERE merge_request_id = ?", [JSON.stringify({ code: "adapter_lost_after_stage_start", error: String(error) }), context.now(), input.request.merge_request_id]);
222
+ if (request?.status === "dispatching" || request?.status === "active")
223
+ context.db.run("UPDATE merge_requests SET status = 'recovery_required', last_error_json = ?, updated_at = ? WHERE merge_request_id = ? AND status = ? AND dispatch_owner = ? AND dispatch_lease_token = ? AND ? = COALESCE((SELECT MAX(generation) FROM run_recovery_guards WHERE project_id = merge_requests.project_id AND run_id = merge_requests.run_id), 0)", [JSON.stringify({ code: request.status === "dispatching" ? "adapter_launch_failed" : "adapter_lost_after_stage_start", error: errorRecord(error) }), context.now(), input.request.merge_request_id, request.status, input.serverId, token, generation]);
99
224
  throw error;
100
225
  }
101
226
  finally {
227
+ lease.close();
102
228
  clearInterval(leaseHeartbeat);
103
229
  }
104
230
  }
105
- function nextServerRequests(context, limit) { const selected = new Set(); return context.db.all("SELECT merge_request_id, project_id, run_id, executor_work_id, target_workspace, execution_route, status, created_at FROM merge_requests WHERE status = 'queued' AND execution_route = 'server' ORDER BY created_at, merge_request_id").filter((request) => { if (selected.has(request.project_id) || context.db.get("SELECT 1 FROM merge_requests WHERE project_id = ? AND status NOT IN ('completed','failed','cancelled','superseded') AND (created_at < ? OR (created_at = ? AND merge_request_id < ?)) LIMIT 1", [request.project_id, request.created_at, request.created_at, request.merge_request_id]))
231
+ function nextServerRequests(context, limit) { const selected = new Set(); return context.db.all("SELECT merge_request_id, project_id, run_id, executor_work_id, target_workspace, execution_route, status, created_at FROM merge_requests WHERE status = 'queued' AND execution_route = 'server' ORDER BY created_at, merge_request_id").filter((request) => { if (guarded(context, request) || selected.has(request.project_id) || context.db.get("SELECT 1 FROM merge_requests WHERE project_id = ? AND status NOT IN ('completed','failed','cancelled','superseded') AND (created_at < ? OR (created_at = ? AND merge_request_id < ?)) LIMIT 1", [request.project_id, request.created_at, request.created_at, request.merge_request_id]))
106
232
  return false; selected.add(request.project_id); return true; }).slice(0, limit); }
107
- function reconcileExpiredDispatches(context) { const expired = context.db.all("SELECT merge_request_id, executor_work_id FROM merge_requests WHERE status = 'dispatching' AND dispatch_lease_expires_at < ?", [context.now()]); for (const request of expired) {
108
- const work = context.db.get("SELECT status FROM works WHERE work_id = ?", [request.executor_work_id]);
109
- if (work?.status === "created")
110
- context.db.run("UPDATE merge_requests SET status = 'queued', dispatch_owner = NULL, dispatch_lease_token = NULL, dispatch_lease_expires_at = NULL, last_error_json = ?, updated_at = ? WHERE merge_request_id = ?", [JSON.stringify({ code: "expired_dispatch_requeued" }), context.now(), request.merge_request_id]);
111
- else
112
- context.db.run("UPDATE merge_requests SET status = 'recovery_required', last_error_json = ?, updated_at = ? WHERE merge_request_id = ?", [JSON.stringify({ code: "expired_dispatch_with_started_work", work_status: work?.status ?? null }), context.now(), request.merge_request_id]);
113
- } }
114
- function loadProfile(context, id) { const file = path.join(context.ddFlowHome, "agent-profiles", `${id}.json`); let value; try {
115
- value = JSON.parse(fs.readFileSync(file, "utf8"));
233
+ export function reconcileExpiredDispatches(context) {
234
+ context.db.writeTransaction(() => {
235
+ const now = context.now();
236
+ const expired = context.db.all("SELECT merge_request_id, executor_work_id, project_id, run_id, dispatch_owner, dispatch_lease_token, dispatch_lease_expires_at FROM merge_requests WHERE status = 'dispatching' AND dispatch_lease_expires_at < ?", [now]);
237
+ for (const request of expired) {
238
+ if (guarded(context, request))
239
+ continue;
240
+ const work = context.db.get("SELECT status FROM works WHERE work_id = ?", [request.executor_work_id]);
241
+ context.db.run("UPDATE merge_requests SET status = 'recovery_required', last_error_json = ?, updated_at = ? WHERE merge_request_id = ? AND status = 'dispatching' AND dispatch_owner IS ? AND dispatch_lease_token IS ? AND dispatch_lease_expires_at = ? AND dispatch_lease_expires_at < ?", [JSON.stringify({ code: "expired_dispatch_outcome_unknown", work_status: work?.status ?? null }), now, request.merge_request_id, request.dispatch_owner, request.dispatch_lease_token, request.dispatch_lease_expires_at, now]);
242
+ }
243
+ });
116
244
  }
117
- catch (error) {
118
- throw new AppError("agent_profile_missing", "Merge-server agent profile is unavailable", 1, { id, file, cause: String(error) });
119
- } validateSchema({ schemaName: "agent-profile", file }); if (value.id !== id)
120
- throw new AppError("agent_profile_invalid", "Agent profile id does not match its requested filename", 2, { file, expected: id, actual: value.id ?? null }); return value; }
245
+ function loadProfile(context, id) { return loadAgentProfile(context.ddFlowHome, id); }
121
246
  function event(file, value) { fs.appendFileSync(file, `${JSON.stringify(value)}\n`); }
247
+ function guarded(context, request) { const guard = recoveryGuard(context, request.project_id, request.run_id); return Boolean(guard && ["draining", "sealed", "resuming"].includes(guard.status)); }
122
248
  function delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); }
123
249
  function processAlive(pid) { if (!pid)
124
250
  return false; try {
125
251
  process.kill(pid, 0);
126
252
  return true;
127
253
  }
128
- catch {
129
- return false;
254
+ catch (error) {
255
+ return error.code !== "ESRCH";
130
256
  } }
@@ -6,10 +6,12 @@ import { requireProjectByRoot } from "./projects.js";
6
6
  import { getProjectVersionStatus, resolveStatusProjectRoot } from "./version-status.js";
7
7
  import { resolveCanonRoot } from "./canon.js";
8
8
  import { getDatabase } from "../storage/database.js";
9
+ import { cleanupScopeWriters } from "./scope-writer-cleanup.js";
9
10
  import { resolveProjectRoot } from "../storage/paths.js";
10
11
  import { classifyCliOperation } from "./cli-operation-classifier.js";
11
12
  import { compatibilityContexts, selectEngine } from "./engines.js";
12
13
  import { getCliBuildInfo } from "./build-info.js";
14
+ import { prepareJsonFile } from "../cli/input-preparation.js";
13
15
  export function assessMigrationImpact(context, input) {
14
16
  const root = resolveStatusProjectRoot({ requestedRoot: input.projectRoot, cwd: process.cwd() });
15
17
  if (!root.root)
@@ -147,7 +149,7 @@ export function planMigration(context, input) {
147
149
  }
148
150
  };
149
151
  }
150
- export function applyMigration(context, input) {
152
+ export function prepareMigrationApply(context, input) {
151
153
  const classification = input.classification ?? classifyCliOperation(["migration", "apply"], context.env);
152
154
  if (classification.mode !== "mb_upgrade") {
153
155
  throw new AppError("upgrade_authorization_required", "Migration apply requires explicit mb-upgrade authorization", 1, {
@@ -197,7 +199,33 @@ export function applyMigration(context, input) {
197
199
  });
198
200
  }
199
201
  const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
202
+ const current = activeStateSummary(context, project.id);
203
+ if (activeStateBlockers(current).length)
204
+ throw new AppError("migration_active_state", "Migration apply requires a quiescent shared home", 1, { active_state: current });
205
+ return { project, sourceVersion, targetVersion, verifiedBackup, reportEngine, chain };
206
+ }
207
+ export function applyMigration(context, input, prepared = prepareMigrationApply(context, input)) {
200
208
  const writeDb = getDatabase(context.ddFlowHome, "migrate");
209
+ let result, primary, failed = false;
210
+ try {
211
+ result = applyPreparedMigration(context, prepared, writeDb);
212
+ }
213
+ catch (error) {
214
+ primary = error;
215
+ failed = true;
216
+ }
217
+ const cleanup = cleanupScopeWriters([{ name: writeDb.path, db: writeDb, rollback: false, close: "always" }]);
218
+ if (failed) {
219
+ if (primary instanceof AppError && cleanup.length)
220
+ primary.details.migration_cleanup_errors = cleanup;
221
+ throw primary;
222
+ }
223
+ if (cleanup.length)
224
+ throw new AppError("migration_cleanup_failed", "Migration receipt is available but its connection could not be closed", 1, { effect: result?.changed ? "committed" : "no_effect", migration_cleanup_errors: cleanup });
225
+ return result;
226
+ }
227
+ function applyPreparedMigration(context, prepared, writeDb) {
228
+ const { project, sourceVersion, targetVersion, verifiedBackup, reportEngine, chain } = prepared;
201
229
  const already = writeDb.get("SELECT value_json FROM runtime_config WHERE key = ?", [applyKey(project.id, sourceVersion, targetVersion)]);
202
230
  if (already) {
203
231
  return { ok: true, schema_id: "dd-flow/mb-upgrade-migration-apply@1", status: "verified_noop", changed: false, backup: verifiedBackup, recovery: JSON.parse(already.value_json) };
@@ -217,15 +245,14 @@ export function applyMigration(context, input) {
217
245
  chain: chain.steps.map((step) => ({ ...step, status: "applied" }))
218
246
  };
219
247
  try {
220
- writeDb.exec("BEGIN IMMEDIATE");
221
- writeDb.run(`INSERT INTO runtime_config (key, value_json, created_at, updated_at) VALUES (?, ?, ?, ?)`, [applyKey(project.id, sourceVersion, targetVersion), JSON.stringify(result), context.now(), context.now()]);
222
- writeDb.exec("COMMIT");
248
+ writeDb.writeTransaction(() => {
249
+ const current = activeStateSummary({ ...context, db: writeDb }, project.id);
250
+ if (activeStateBlockers(current).length)
251
+ throw new AppError("migration_active_state", "Migration apply requires a quiescent shared home", 1, { active_state: current });
252
+ writeDb.run(`INSERT INTO runtime_config (key, value_json, created_at, updated_at) VALUES (?, ?, ?, ?)`, [applyKey(project.id, sourceVersion, targetVersion), JSON.stringify(result), context.now(), context.now()]);
253
+ });
223
254
  }
224
255
  catch (error) {
225
- try {
226
- writeDb.exec("ROLLBACK");
227
- }
228
- catch { /* preserve the original failure */ }
229
256
  throw new AppError("migration_apply_failed", "Migration apply failed before upgrade RUN creation or canon sync", 1, {
230
257
  recovery: {
231
258
  status: "required",
@@ -235,7 +262,7 @@ export function applyMigration(context, input) {
235
262
  rollback_route: "restore the verified backup before retrying mb-upgrade apply",
236
263
  next_action: "inspect the failed apply and retry only with the same pinned target contract"
237
264
  },
238
- cause: String(error)
265
+ cause: error instanceof AppError ? { code: error.code, message: error.message, details: error.details } : String(error)
239
266
  });
240
267
  }
241
268
  return { ok: true, schema_id: "dd-flow/mb-upgrade-migration-apply@1", status: "applied", changed: true, backup: verifiedBackup, recovery: result };
@@ -513,13 +540,7 @@ function activeStateBlockers(active) {
513
540
  return blockers;
514
541
  }
515
542
  function readJsonObject(filePath) {
516
- let parsed;
517
- try {
518
- parsed = JSON.parse(fs.readFileSync(filePath, "utf8"));
519
- }
520
- catch (error) {
521
- throw new AppError("invalid_json", `Cannot read migration report: ${filePath}`, 2, { cause: String(error) });
522
- }
543
+ const { value: parsed } = prepareJsonFile(filePath, "Migration report");
523
544
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
524
545
  throw new AppError("validation", "Migration report must be a JSON object", 2, { file: filePath });
525
546
  }
@@ -0,0 +1,109 @@
1
+ import { AppError } from "../shared/errors.js";
2
+ import { validateHybridContract } from "./hybrid-contracts.js";
3
+ import { mergeModelListing } from "./model-discovery.js";
4
+ import { readBoundedReleaseFile } from "./release-source.js";
5
+ // Reviewed adapter fallbacks and retained eval configurations, not vendor
6
+ // availability promises or recommended defaults. Missing facts stay unknown;
7
+ // historical eval qualification/capacity claims are deliberately not imported.
8
+ const seed = {
9
+ schema_id: "dd-flow/model-catalog@1", version: "1.1.0",
10
+ entries: [{ harness: "agy", provider: "google", model: "gemini-3.1-pro-high",
11
+ settings: { reasoning: ["unknown"], mode: ["unknown"] },
12
+ provenance: { kind: "reviewed-seed", source: "https://github.com/deksden-com/dd-flow-cli/blob/2ba2ce2364705cf59fdc48bdd5dc5b37fb7cd784/src/harness-runtime/lib/dd-agy.mjs#L7",
13
+ reviewed_at: "2026-10-07T00:00:00Z" }, availability: "not-observed", qualification: { scope: "seed-only", evidence: null } },
14
+ { harness: "grok", provider: "xai", model: "grok-4.6",
15
+ settings: { reasoning: ["high"], mode: ["bypassPermissions"] },
16
+ provenance: { kind: "reviewed-seed", source: "https://github.com/deksden-com/dd-flow-cli/blob/2ba2ce2364705cf59fdc48bdd5dc5b37fb7cd784/src/harness-runtime/lib/dd-grok.mjs#L48",
17
+ reviewed_at: "2026-10-07T00:00:00Z" }, availability: "not-observed", qualification: { scope: "seed-only", evidence: null } },
18
+ { harness: "codex", provider: "unknown", model: "gpt-6.1-sol",
19
+ settings: { reasoning: ["high"], mode: ["unknown"] },
20
+ provenance: { kind: "reviewed-seed", source: "https://github.com/deksden-com/dd-eval/blob/9715970544096fc0288ac8e18f95d4e35997be54/profiles/codex-desktop-gpt-6-1-sol-high.json",
21
+ reviewed_at: "2026-10-07T00:00:00Z" }, availability: "not-observed", qualification: { scope: "seed-only", evidence: null } },
22
+ { harness: "zcode", provider: "builtin:zai-coding-plan", model: "GLM-5.3",
23
+ settings: { reasoning: ["high"], mode: ["yolo"] },
24
+ provenance: { kind: "reviewed-seed", source: "https://github.com/deksden-com/dd-eval/blob/9715970544096fc0288ac8e18f95d4e35997be54/profiles/zcode-acp-zai-glm-5-3-high.json",
25
+ reviewed_at: "2026-10-07T00:00:00Z" }, availability: "not-observed", qualification: { scope: "seed-only", evidence: null } },
26
+ { harness: "opencode", provider: "opencode", model: "big-pickle",
27
+ settings: { reasoning: ["default"], mode: ["build"] },
28
+ provenance: { kind: "reviewed-seed", source: "https://github.com/deksden-com/dd-eval/blob/9715970544096fc0288ac8e18f95d4e35997be54/profiles/opencode-server-opencode-big-pickle-default.json",
29
+ reviewed_at: "2026-10-07T00:00:00Z" }, availability: "not-observed", qualification: { scope: "seed-only", evidence: null } },
30
+ { harness: "droid", provider: "openai", model: "gpt-5.6-sol",
31
+ settings: { reasoning: ["high"], mode: ["auto-high"] },
32
+ provenance: { kind: "reviewed-seed", source: "https://github.com/deksden-com/dd-eval/blob/9715970544096fc0288ac8e18f95d4e35997be54/profiles/droid-cli-openai-gpt-5-6-sol-high.json",
33
+ reviewed_at: "2026-10-07T00:00:00Z" }, availability: "not-observed", qualification: { scope: "seed-only", evidence: null } }]
34
+ };
35
+ const families = ["codex", "zcode", "agy", "opencode", "grok", "droid"];
36
+ const identifier = (value, max = 128) => typeof value === "string" && value.length <= max && /^[A-Za-z0-9][A-Za-z0-9._:/+-]*$/.test(value);
37
+ const providerId = (value) => typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(value);
38
+ function invalid() {
39
+ throw new AppError("model_catalog_input", "Model catalog input requires reviewed seed data and an explicit observation scope; see dd-flow models --help", 2, { effect: "no_effect", help: "models" });
40
+ }
41
+ function readJson(file) {
42
+ let bytes;
43
+ try {
44
+ bytes = readBoundedReleaseFile(file, 4 * 1024 * 1024);
45
+ }
46
+ catch {
47
+ throw new AppError("model_input_unavailable", "Model input must be a bounded readable regular file; see dd-flow models --help", 1, { effect: "no_effect", help: "models" });
48
+ }
49
+ try {
50
+ return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
51
+ }
52
+ catch {
53
+ return invalid();
54
+ }
55
+ }
56
+ export function readModelSeed(file) {
57
+ const input = file === undefined ? structuredClone(seed) : readJson(file);
58
+ if (validateHybridContract("model-catalog", input).length)
59
+ invalid();
60
+ const catalog = input;
61
+ if (catalog.entries.length > 10_000 || catalog.entries.some(entry => {
62
+ let source;
63
+ try {
64
+ source = new URL(entry.provenance.source);
65
+ }
66
+ catch {
67
+ return true;
68
+ }
69
+ const reviewed = entry.provenance.reviewed_at;
70
+ return entry.qualification.scope !== "seed-only" || !providerId(entry.provider) || !identifier(entry.model, 256)
71
+ || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/.test(reviewed) || !Number.isFinite(Date.parse(reviewed))
72
+ || new Date(reviewed).toISOString() !== reviewed.replace(/Z$/, reviewed.includes(".") ? "Z" : ".000Z")
73
+ || source.protocol !== "https:" || source.username !== "" || source.password !== "" || source.search !== ""
74
+ || entry.provenance.source.length > 2048
75
+ || [...entry.settings.reasoning, ...entry.settings.mode].some(value => !identifier(value, 64));
76
+ }))
77
+ invalid();
78
+ return structuredClone(catalog);
79
+ }
80
+ /** Offline reader only. A supplied snapshot is an observation, not account authorization. */
81
+ export function listOperatorModels(input) {
82
+ if (input.harness !== undefined && !families.includes(input.harness) || input.provider !== undefined && !providerId(input.provider)
83
+ || input.model !== undefined && !identifier(input.model, 256)
84
+ || input.accountSha256 !== undefined && !/^[a-f0-9]{64}$/.test(input.accountSha256)
85
+ || (input.accountSha256 !== undefined || input.observationFile !== undefined) && (!input.harness || !input.provider || !input.accountSha256))
86
+ invalid();
87
+ // Validate all selectors before opening operator files.
88
+ const catalog = readModelSeed(input.seedFile);
89
+ let entries, reason, observedAt = null;
90
+ if (input.accountSha256) {
91
+ const scope = { harness: input.harness, provider: input.provider, account_sha256: input.accountSha256 };
92
+ const observation = input.observationFile === undefined ? undefined : readJson(input.observationFile);
93
+ const merged = mergeModelListing(catalog, scope, observation);
94
+ entries = merged.entries.map(entry => ({ harness: scope.harness, provider: scope.provider, ...entry }));
95
+ reason = merged.reason;
96
+ observedAt = merged.observed_at;
97
+ }
98
+ else {
99
+ entries = catalog.entries.filter(entry => (!input.harness || entry.harness === input.harness) && (!input.provider || entry.provider === input.provider))
100
+ .map(entry => ({ harness: entry.harness, provider: entry.provider, model: entry.model, seed: structuredClone(entry),
101
+ availability: "unknown", account_access: "unknown", qualified: "unknown" }));
102
+ reason = "account_scope_not_selected";
103
+ }
104
+ if (input.model)
105
+ entries = entries.filter(entry => entry.model === input.model);
106
+ return { catalog_version: catalog.version, observed_at: observedAt, reason, entries,
107
+ families: families.map(harness => ({ harness, seed_status: catalog.entries.some(entry => entry.harness === harness) ? "reviewed_entries" : "not_curated",
108
+ account_access: "unknown", qualified: "unknown" })) };
109
+ }
@@ -0,0 +1,46 @@
1
+ import { AppError } from "../shared/errors.js";
2
+ import { validateHybridContract } from "./hybrid-contracts.js";
3
+ const invalid = () => new AppError("model_discovery_input", "Model discovery input does not match its scoped contract", 2, { effect: "no_effect" });
4
+ export function validateModelScope(scope) {
5
+ if (!scope || typeof scope !== "object" || Array.isArray(scope)
6
+ || ["harness", "provider", "account_sha256"].some(key => !Object.hasOwn(scope, key))
7
+ || Object.keys(scope).some(key => !["harness", "provider", "account_sha256"].includes(key))
8
+ || !["codex", "zcode", "agy", "opencode", "grok", "droid"].includes(scope.harness)
9
+ || typeof scope.provider !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(scope.provider)
10
+ || typeof scope.account_sha256 !== "string" || !/^[a-f0-9]{64}$/.test(scope.account_sha256))
11
+ throw invalid();
12
+ }
13
+ /** Merge only a matching account/provider snapshot. Listed is not authorized or qualified. */
14
+ export function mergeModelListing(seed, scope, observation) {
15
+ validateModelScope(scope);
16
+ if (validateHybridContract("model-catalog", seed).length)
17
+ throw invalid();
18
+ if (observation !== undefined) {
19
+ if (!observation || typeof observation !== "object" || Array.isArray(observation)
20
+ || ["scope", "observed_at", "status", "complete", "models"].some(key => !Object.hasOwn(observation, key)))
21
+ throw invalid();
22
+ validateModelScope(observation.scope);
23
+ if (Object.keys(observation).some(key => !["scope", "observed_at", "status", "complete", "models"].includes(key))
24
+ || typeof observation.observed_at !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/.test(observation.observed_at)
25
+ || !Number.isFinite(Date.parse(observation.observed_at))
26
+ || new Date(observation.observed_at).toISOString() !== observation.observed_at.replace(/Z$/, observation.observed_at.includes(".") ? "Z" : ".000Z")
27
+ || !["listed", "unavailable"].includes(observation.status)
28
+ || typeof observation.complete !== "boolean" || !Array.isArray(observation.models)
29
+ || observation.models.length > 10_000 || observation.models.some(model => typeof model !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._:/+-]{0,255}$/.test(model))
30
+ || new Set(observation.models).size !== observation.models.length
31
+ || observation.status === "unavailable" && (observation.models.length > 0 || observation.complete))
32
+ throw invalid();
33
+ }
34
+ const matches = observation && observation.scope.harness === scope.harness && observation.scope.provider === scope.provider
35
+ && observation.scope.account_sha256 === scope.account_sha256;
36
+ const listing = matches && observation.status === "listed" ? observation : undefined;
37
+ const entries = seed.entries.filter(entry => entry.harness === scope.harness && entry.provider === scope.provider);
38
+ const seeded = new Map(entries.map(entry => [entry.model, entry]));
39
+ const names = new Set([...seeded.keys(), ...(listing?.models ?? [])]);
40
+ return { scope: { ...scope }, observed_at: matches ? observation.observed_at : null,
41
+ reason: !observation ? "not_observed" : !matches ? "foreign_scope_ignored" : !listing ? "listing_unavailable" : "scoped_listing",
42
+ entries: [...names].sort().map(model => ({ model,
43
+ seed: seeded.has(model) ? structuredClone(seeded.get(model)) : null,
44
+ availability: listing?.models.includes(model) ? "listed" : listing?.complete ? "not_listed" : "unknown",
45
+ account_access: "unknown", qualified: "unknown" })) };
46
+ }
@@ -0,0 +1,49 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { AppError } from "../shared/errors.js";
5
+ import { managedProcessBinding, managedProcessStatus, processTreeIsAlive, resourceHome, retainedResourceHomeMatches } from "./managed-processes.js";
6
+ import { retainedDaemonOperations } from "./run-control.js";
7
+ import { managedProviderBelongsToDaemon } from "./managed-daemon-binding.js";
8
+ /** A replaced daemon is a settled resource, never another Session to resume. */
9
+ export function historicalDaemonSettlement(context, scopeId, record) {
10
+ const metadata = JSON.parse(record.metadata_json);
11
+ if (!record.kind.endsWith("-daemon") || !metadata.state_dir || !path.isAbsolute(metadata.state_dir))
12
+ throw new AppError("runtime_scope_owner_unknown", "Native history requires an absolute daemon directory", 1);
13
+ const current = JSON.parse(fs.readFileSync(path.join(metadata.state_dir, "daemon.json"), "utf8"));
14
+ if (current.resource_process?.id === record.id)
15
+ return null;
16
+ if (record.state !== "stopped" || record.termination_reason !== "daemon_stopped" || !record.pid || !record.pid_started_at || processTreeIsAlive(record))
17
+ throw new AppError("runtime_scope_owner_unknown", "Previous daemon has no physical completion proof", 1);
18
+ const directory = path.join(metadata.state_dir, "daemon-history");
19
+ const matches = fs.readdirSync(directory).filter(name => /^[a-zA-Z0-9-]+\.json$/.test(name)).map(name => {
20
+ const file = path.join(directory, name);
21
+ if (!fs.lstatSync(file).isFile())
22
+ throw new AppError("runtime_scope_owner_unknown", "Daemon history must contain regular receipts", 1);
23
+ const bytes = fs.readFileSync(file, "utf8");
24
+ return { file, bytes, state: JSON.parse(bytes) };
25
+ }).filter(candidate => candidate.state.resource_process?.id === record.id);
26
+ if (matches.length !== 1)
27
+ throw new AppError("runtime_scope_owner_unknown", "Previous daemon has no unique retained receipt", 1);
28
+ const { file, bytes, state } = matches[0], owner = state.config?.runtime_owner;
29
+ if (!state.daemon_id || path.basename(file) !== `${state.daemon_id}.json` || !owner || owner.budget?.scope_id !== scopeId || owner.owner_id !== record.owner_id || owner.role !== metadata.role || owner.state_dir !== metadata.state_dir || owner.adapter_executable !== metadata.adapter_executable || owner.dd_flow_home !== metadata.dd_flow_home || !retainedResourceHomeMatches(context, owner.resource_home) || (owner.project_id ?? null) !== record.project_id || (owner.run_id ?? null) !== record.run_id || (owner.work_id ?? null) !== record.work_id || !record.operation_id?.startsWith(`${owner.operation_id}:`) || state.resource_process?.lease_token !== record.lease_token || state.pid !== record.pid)
30
+ throw new AppError("runtime_scope_owner_unknown", "Archived daemon does not match its registration", 1);
31
+ const closed = state.shutdown_state === "clean" && state.active_tree === false
32
+ ? retainedDaemonOperations(metadata.state_dir).find(operation => operation.daemon_id === state.daemon_id && operation.operation === "daemon.stop" && operation.state === "completed" && operation.result?.stopped === true && operation.result?.clean === true) : undefined;
33
+ const fresh = managedProcessStatus(context).find(item => item.id === record.id);
34
+ if (!closed || !fresh || fresh.lease_token !== record.lease_token || fresh.pid !== record.pid || fresh.pid_started_at !== record.pid_started_at || fresh.metadata_json !== record.metadata_json || fresh.state !== "stopped" || fresh.termination_reason !== "daemon_stopped" || processTreeIsAlive(fresh) || fs.readFileSync(file, "utf8") !== bytes)
35
+ throw new AppError("runtime_scope_outcome_unknown", "Previous daemon lacks stable clean shutdown evidence", 1);
36
+ const providers = managedProcessStatus(context).filter(item => managedProviderBelongsToDaemon(item, fresh, state));
37
+ if (state.provider_process && (providers.length !== 1 || providers[0].state !== "stopped" || providers[0].termination_reason !== "daemon_stopped" || !providers[0].operation_id?.startsWith(`${owner.operation_id}:`) || processTreeIsAlive(providers[0])))
38
+ throw new AppError("runtime_scope_outcome_unknown", "Archived provider lacks its exact physical completion proof", 1);
39
+ return { historical: true, process_id: record.id, role: metadata.role, state_dir: metadata.state_dir, daemon_id: state.daemon_id,
40
+ processes: providers.map(item => ({ process_id: item.id, native_settled: true, binding: managedProcessBinding(item, resourceHome(context)) })),
41
+ settled: true, native_settled: true, operation_id: String(closed.operation_id), owner_operation_id: owner.operation_id,
42
+ inventory_version: crypto.createHash("sha256").update(JSON.stringify([bytes, closed])).digest("hex") };
43
+ }
44
+ export function assertHistoricalDaemonSettlement(context, scopeId, proof) {
45
+ const record = managedProcessStatus(context).find(item => item.id === proof.process_id);
46
+ const fresh = record && historicalDaemonSettlement(context, scopeId, record);
47
+ if (!fresh || proof.historical !== true || proof.native_settled !== true || fresh.inventory_version !== proof.inventory_version)
48
+ throw new AppError("runtime_scope_inventory_changed", "Archived daemon settlement changed after observation", 1);
49
+ }