@deksden-com/dd-flow-cli 0.9.0-beta.15 → 0.9.0-beta.150

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 (557) hide show
  1. package/CHANGELOG.md +895 -0
  2. package/README.md +140 -6
  3. package/dist/build-info.json +8 -8
  4. package/dist/cli/argument-values.js +38 -0
  5. package/dist/cli/command-inputs.js +386 -0
  6. package/dist/cli/command-metadata.js +232 -0
  7. package/dist/cli/command-option-descriptions.js +192 -0
  8. package/dist/cli/completion-cli.js +70 -0
  9. package/dist/cli/completion.js +57 -0
  10. package/dist/cli/diagnostic-invocation.js +46 -0
  11. package/dist/cli/diagnostic.js +120 -0
  12. package/dist/cli/error-output.js +52 -0
  13. package/dist/cli/executor-onboarding.js +76 -0
  14. package/dist/cli/flows.js +29 -0
  15. package/dist/cli/harnesses.js +84 -0
  16. package/dist/cli/help.js +396 -19
  17. package/dist/cli/hook-ingress.js +102 -0
  18. package/dist/cli/input-preparation.js +165 -0
  19. package/dist/cli/memory-flow-admission.js +50 -0
  20. package/dist/cli/memory-init-invocation.js +85 -0
  21. package/dist/cli/memory-init.js +249 -0
  22. package/dist/cli/memory-storage-admission.js +54 -0
  23. package/dist/cli/memory-storage-qualification.js +105 -0
  24. package/dist/cli/memory-storage-transition.js +123 -0
  25. package/dist/cli/memory-upgrade.js +189 -0
  26. package/dist/cli/models.js +39 -0
  27. package/dist/cli/native-hook-ingress.js +90 -0
  28. package/dist/cli/native-hook-preparation.js +55 -0
  29. package/dist/cli/operator-profiles.js +61 -0
  30. package/dist/cli/output-options.js +35 -0
  31. package/dist/cli/response-file.js +72 -0
  32. package/dist/cli/run-cli.js +1734 -550
  33. package/dist/cli/runtime-maintenance-diagnostics.js +46 -0
  34. package/dist/cli/runtime-maintenance-ingress.js +52 -0
  35. package/dist/cli/runtime-process.js +153 -0
  36. package/dist/cli/setup.js +150 -0
  37. package/dist/cli.js +89 -2
  38. package/dist/domain/diagnostic-aspects.js +103 -0
  39. package/dist/domain/diagnostic-flows.js +8 -0
  40. package/dist/domain/stage-catalog.js +1 -1
  41. package/dist/domain/stage-handler-identities.js +28 -0
  42. package/dist/harness-runtime/DD-ZCODE.md +112 -0
  43. package/dist/harness-runtime/OPENCODE-LICENSE.txt +24 -0
  44. package/dist/harness-runtime/bin/dd-agy.mjs +56 -0
  45. package/dist/harness-runtime/bin/dd-codex.mjs +27 -0
  46. package/dist/harness-runtime/bin/dd-droid.mjs +34 -0
  47. package/dist/harness-runtime/bin/dd-grok.mjs +40 -0
  48. package/dist/harness-runtime/bin/dd-opencode.mjs +22 -0
  49. package/dist/harness-runtime/bin/dd-zcode.mjs +87 -0
  50. package/dist/harness-runtime/lib/adapter-timeouts.d.mts +5 -0
  51. package/dist/harness-runtime/lib/adapter-timeouts.mjs +34 -0
  52. package/dist/harness-runtime/lib/agy-error-boundary.mjs +58 -0
  53. package/dist/harness-runtime/lib/agy-hooks.d.mts +1 -0
  54. package/dist/harness-runtime/lib/agy-hooks.mjs +9 -0
  55. package/dist/harness-runtime/lib/agy-native-inspection.mjs +216 -0
  56. package/dist/harness-runtime/lib/agy-native_pb.js +117 -0
  57. package/dist/harness-runtime/lib/daemon-observation.d.mts +2 -0
  58. package/dist/harness-runtime/lib/daemon-observation.mjs +163 -0
  59. package/dist/harness-runtime/lib/daemon-operations.d.mts +2 -0
  60. package/dist/harness-runtime/lib/daemon-operations.mjs +462 -0
  61. package/dist/harness-runtime/lib/dd-agy-daemon.d.mts +6 -0
  62. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +1096 -0
  63. package/dist/harness-runtime/lib/dd-agy.mjs +86 -0
  64. package/dist/harness-runtime/lib/dd-codex-daemon.d.mts +1 -0
  65. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +231 -0
  66. package/dist/harness-runtime/lib/dd-codex.d.mts +1 -0
  67. package/dist/harness-runtime/lib/dd-codex.mjs +954 -0
  68. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +141 -0
  69. package/dist/harness-runtime/lib/dd-droid.mjs +626 -0
  70. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +555 -0
  71. package/dist/harness-runtime/lib/dd-grok.mjs +180 -0
  72. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +622 -0
  73. package/dist/harness-runtime/lib/dd-opencode.mjs +390 -0
  74. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +752 -0
  75. package/dist/harness-runtime/lib/dd-zcode.mjs +1175 -0
  76. package/dist/harness-runtime/lib/delegation-instructions.d.mts +14 -0
  77. package/dist/harness-runtime/lib/delegation-instructions.mjs +157 -0
  78. package/dist/harness-runtime/lib/dispatch-fence.mjs +14 -0
  79. package/dist/harness-runtime/lib/driver-recovery.mjs +140 -0
  80. package/dist/harness-runtime/lib/droid-observation.mjs +66 -0
  81. package/dist/harness-runtime/lib/entity-references.d.mts +2 -0
  82. package/dist/harness-runtime/lib/entity-references.mjs +9 -0
  83. package/dist/harness-runtime/lib/lease-renewal.d.mts +8 -0
  84. package/dist/harness-runtime/lib/lease-renewal.mjs +37 -0
  85. package/dist/harness-runtime/lib/lifecycle-command.d.mts +20 -0
  86. package/dist/harness-runtime/lib/lifecycle-command.mjs +273 -0
  87. package/dist/harness-runtime/lib/lifecycle-contract.d.mts +10 -0
  88. package/dist/harness-runtime/lib/lifecycle-contract.mjs +19 -0
  89. package/dist/harness-runtime/lib/managed-daemon.d.mts +16 -0
  90. package/dist/harness-runtime/lib/managed-daemon.mjs +698 -0
  91. package/dist/harness-runtime/lib/managed-flow-env.mjs +15 -0
  92. package/dist/harness-runtime/lib/model-observations.mjs +101 -0
  93. package/dist/harness-runtime/lib/native-children.d.mts +23 -0
  94. package/dist/harness-runtime/lib/native-children.mjs +219 -0
  95. package/dist/harness-runtime/lib/native-hook-command.d.mts +7 -0
  96. package/dist/harness-runtime/lib/native-hook-command.mjs +251 -0
  97. package/dist/harness-runtime/lib/native-request-proof.d.mts +2 -0
  98. package/dist/harness-runtime/lib/native-request-proof.mjs +65 -0
  99. package/dist/harness-runtime/lib/native-streams.mjs +45 -0
  100. package/dist/harness-runtime/lib/observation-clock.d.mts +18 -0
  101. package/dist/harness-runtime/lib/observation-clock.mjs +95 -0
  102. package/dist/harness-runtime/lib/opencode-plugin.mjs +85 -0
  103. package/dist/harness-runtime/lib/operation-context.mjs +5 -0
  104. package/dist/harness-runtime/lib/operation-errors.d.mts +16 -0
  105. package/dist/harness-runtime/lib/operation-errors.mjs +127 -0
  106. package/dist/harness-runtime/lib/operation-progress.d.mts +7 -0
  107. package/dist/harness-runtime/lib/operation-progress.mjs +114 -0
  108. package/dist/harness-runtime/lib/process-json.d.mts +5 -0
  109. package/dist/harness-runtime/lib/process-json.mjs +201 -0
  110. package/dist/harness-runtime/lib/process-snapshot.mjs +10 -0
  111. package/dist/harness-runtime/lib/provider-overload-policy.d.mts +15 -0
  112. package/dist/harness-runtime/lib/provider-overload-policy.mjs +151 -0
  113. package/dist/harness-runtime/lib/runner-events.mjs +202 -0
  114. package/dist/harness-runtime/lib/runner-lock.mjs +42 -0
  115. package/dist/harness-runtime/lib/session-settlement.mjs +30 -0
  116. package/dist/harness-runtime/lib/setup-progress.d.mts +9 -0
  117. package/dist/harness-runtime/lib/setup-progress.mjs +53 -0
  118. package/dist/harness-runtime/lib/tool-observations.d.mts +21 -0
  119. package/dist/harness-runtime/lib/tool-observations.mjs +228 -0
  120. package/dist/harness-runtime/lib/zcode-usage.mjs +55 -0
  121. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-1y4a697q.js +81 -0
  122. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-2ny4vxct.js +92 -0
  123. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-6mptkzbc.js +0 -0
  124. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-7x67cf1q.js +52 -0
  125. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-9rqn6x4v.js +16 -0
  126. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-9vdz0zwn.js +1661 -0
  127. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ecs5ftka.js +1466 -0
  128. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-frbwqjmf.js +145 -0
  129. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-gne7h7mq.js +50 -0
  130. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-gx9wnkzr.js +105 -0
  131. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-m2eywc8w.js +0 -0
  132. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-nwcpakzx.js +12 -0
  133. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-q4g3afnw.js +623 -0
  134. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-qav6mfdz.js +0 -0
  135. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-sxt88q69.js +12 -0
  136. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-t1xa31vm.js +39 -0
  137. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ve4aws36.js +103 -0
  138. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vmytr4q8.js +4 -0
  139. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vt9jbch8.js +36 -0
  140. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vyas3vgt.js +0 -0
  141. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-wjyeq25y.js +30 -0
  142. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-xgnj2w4v.js +20 -0
  143. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-y2etg5bs.js +9 -0
  144. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ydhz1zrh.js +238 -0
  145. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-z6qdwcsf.js +62 -0
  146. package/dist/harness-runtime/vendor/opencode-client/contract.d.ts +1 -0
  147. package/dist/harness-runtime/vendor/opencode-client/contract.js +12 -0
  148. package/dist/harness-runtime/vendor/opencode-client/effect/api/api.d.ts +2806 -0
  149. package/dist/harness-runtime/vendor/opencode-client/effect/api/api.js +0 -0
  150. package/dist/harness-runtime/vendor/opencode-client/effect/api.d.ts +4 -0
  151. package/dist/harness-runtime/vendor/opencode-client/effect/api.js +0 -0
  152. package/dist/harness-runtime/vendor/opencode-client/effect/client.d.ts +2530 -0
  153. package/dist/harness-runtime/vendor/opencode-client/effect/client.js +15 -0
  154. package/dist/harness-runtime/vendor/opencode-client/effect/generated/client-error.d.ts +7 -0
  155. package/dist/harness-runtime/vendor/opencode-client/effect/generated/client-error.js +6 -0
  156. package/dist/harness-runtime/vendor/opencode-client/effect/generated/client.d.ts +2529 -0
  157. package/dist/harness-runtime/vendor/opencode-client/effect/generated/client.js +8 -0
  158. package/dist/harness-runtime/vendor/opencode-client/effect/generated/index.d.ts +2 -0
  159. package/dist/harness-runtime/vendor/opencode-client/effect/generated/index.js +12 -0
  160. package/dist/harness-runtime/vendor/opencode-client/effect/index.d.ts +35 -0
  161. package/dist/harness-runtime/vendor/opencode-client/effect/index.js +75 -0
  162. package/dist/harness-runtime/vendor/opencode-client/effect/rpc.d.ts +20 -0
  163. package/dist/harness-runtime/vendor/opencode-client/effect/rpc.js +11 -0
  164. package/dist/harness-runtime/vendor/opencode-client/effect/service.d.ts +90 -0
  165. package/dist/harness-runtime/vendor/opencode-client/effect/service.js +187 -0
  166. package/dist/harness-runtime/vendor/opencode-client/package.json +84 -0
  167. package/dist/harness-runtime/vendor/opencode-client/promise/api.d.ts +21 -0
  168. package/dist/harness-runtime/vendor/opencode-client/promise/api.js +0 -0
  169. package/dist/harness-runtime/vendor/opencode-client/promise/client.d.ts +252 -0
  170. package/dist/harness-runtime/vendor/opencode-client/promise/client.js +14 -0
  171. package/dist/harness-runtime/vendor/opencode-client/promise/generated/client-error.d.ts +8 -0
  172. package/dist/harness-runtime/vendor/opencode-client/promise/generated/client-error.js +6 -0
  173. package/dist/harness-runtime/vendor/opencode-client/promise/generated/client.d.ts +259 -0
  174. package/dist/harness-runtime/vendor/opencode-client/promise/generated/client.js +7 -0
  175. package/dist/harness-runtime/vendor/opencode-client/promise/generated/index.d.ts +3 -0
  176. package/dist/harness-runtime/vendor/opencode-client/promise/generated/index.js +79 -0
  177. package/dist/harness-runtime/vendor/opencode-client/promise/generated/types.d.ts +8932 -0
  178. package/dist/harness-runtime/vendor/opencode-client/promise/generated/types.js +70 -0
  179. package/dist/harness-runtime/vendor/opencode-client/promise/index.d.ts +6 -0
  180. package/dist/harness-runtime/vendor/opencode-client/promise/index.js +83 -0
  181. package/dist/harness-runtime/vendor/opencode-client/promise/rpc.d.ts +32 -0
  182. package/dist/harness-runtime/vendor/opencode-client/promise/rpc.js +7 -0
  183. package/dist/harness-runtime/vendor/opencode-client/promise/service.d.ts +24 -0
  184. package/dist/harness-runtime/vendor/opencode-client/promise/service.js +187 -0
  185. package/dist/harness-runtime/vendor/opencode-client/pty-handoff.d.ts +12 -0
  186. package/dist/harness-runtime/vendor/opencode-client/pty-handoff.js +14 -0
  187. package/dist/harness-runtime/vendor/opencode-client/rpc-runtime.d.ts +20 -0
  188. package/dist/harness-runtime/vendor/opencode-client/rpc-runtime.js +14 -0
  189. package/dist/harness-runtime/vendor/opencode-client/service-contender.d.ts +37 -0
  190. package/dist/harness-runtime/vendor/opencode-client/service-contender.js +10 -0
  191. package/dist/harness-runtime/vendor/opencode-client/service-probe.d.ts +70 -0
  192. package/dist/harness-runtime/vendor/opencode-client/service-probe.js +16 -0
  193. package/dist/harness-runtime/vendor/opencode-client/service-timing.d.ts +12 -0
  194. package/dist/harness-runtime/vendor/opencode-client/service-timing.js +10 -0
  195. package/dist/harness-runtime/vendor/opencode-client/service-version.d.ts +2 -0
  196. package/dist/harness-runtime/vendor/opencode-client/service-version.js +6 -0
  197. package/dist/harness-runtime/vendor/opencode-client/service.d.ts +52 -0
  198. package/dist/harness-runtime/vendor/opencode-client/service.js +1 -0
  199. package/dist/harness-runtime/vendor/opencode-client/shared-events.d.ts +11 -0
  200. package/dist/harness-runtime/vendor/opencode-client/shared-events.js +8 -0
  201. package/dist/harness-runtime/vendor/opencode-client/solid/connection.d.ts +37 -0
  202. package/dist/harness-runtime/vendor/opencode-client/solid/connection.js +10 -0
  203. package/dist/harness-runtime/vendor/opencode-client/solid/data.d.ts +211 -0
  204. package/dist/harness-runtime/vendor/opencode-client/solid/data.js +18 -0
  205. package/dist/harness-runtime/vendor/opencode-client/solid/index.d.ts +3 -0
  206. package/dist/harness-runtime/vendor/opencode-client/solid/index.js +32 -0
  207. package/dist/harness-runtime/vendor/opencode-client/solid/pty.d.ts +22 -0
  208. package/dist/harness-runtime/vendor/opencode-client/solid/pty.js +8 -0
  209. package/dist/harness-runtime/vendor/opencode-plugin.mjs +3 -0
  210. package/dist/protocol/local-files.js +18 -9
  211. package/dist/runtime/context.js +8 -4
  212. package/dist/schemas/agent-profile.schema.json +1 -1
  213. package/dist/schemas/code-review-decision.schema.json +9 -0
  214. package/dist/schemas/code-review-result.schema.json +1 -1
  215. package/dist/schemas/code-verification.schema.json +7 -5
  216. package/dist/schemas/code-work-batch.schema.json +2 -2
  217. package/dist/schemas/diagnostic-stage-result.schema.json +354 -0
  218. package/dist/schemas/diagnostic-work-result.schema.json +296 -0
  219. package/dist/schemas/discovery-result.schema.json +184 -0
  220. package/dist/schemas/execution-bundle.schema.json +211 -0
  221. package/dist/schemas/execution-settings.schema.json +162 -0
  222. package/dist/schemas/flow-descriptor.schema.json +54 -0
  223. package/dist/schemas/flow-run.schema.json +29 -0
  224. package/dist/schemas/harness-catalog.schema.json +80 -0
  225. package/dist/schemas/mb-init-compose-result.schema.json +457 -0
  226. package/dist/schemas/mb-init-discover-result.schema.json +500 -0
  227. package/dist/schemas/mb-init-finish-result.schema.json +422 -0
  228. package/dist/schemas/mb-init-preflight-result.schema.json +367 -0
  229. package/dist/schemas/mb-init-review-result.schema.json +396 -0
  230. package/dist/schemas/mb-init-verify-result.schema.json +391 -0
  231. package/dist/schemas/mb-upgrade-stage-result.schema.json +863 -0
  232. package/dist/schemas/memory-bootstrap-receipt-v2.schema.json +67 -0
  233. package/dist/schemas/memory-bootstrap-receipt.schema.json +354 -0
  234. package/dist/schemas/memory-composition-work-result.schema.json +38 -0
  235. package/dist/schemas/memory-repair-work-result.schema.json +56 -0
  236. package/dist/schemas/memory-review-correction-plan.schema.json +30 -0
  237. package/dist/schemas/memory-review-work-result.schema.json +43 -0
  238. package/dist/schemas/model-catalog.schema.json +153 -0
  239. package/dist/schemas/plan-review-decision.schema.json +12 -3
  240. package/dist/schemas/plan-review-result.schema.json +2 -2
  241. package/dist/schemas/profile-settings.schema.json +122 -0
  242. package/dist/schemas/project-flow-pack-manifest.schema.json +1 -1
  243. package/dist/schemas/release-impact.schema.json +3 -3
  244. package/dist/schemas/release-manifest.schema.json +291 -0
  245. package/dist/schemas/run-control-receipt.schema.json +118 -0
  246. package/dist/schemas/run-control-request.schema.json +36 -0
  247. package/dist/schemas/run-engine-binding.schema.json +1 -1
  248. package/dist/schemas/stage-start-response.schema.json +4 -0
  249. package/dist/schemas/vnext-protocol-plan.schema.json +2 -2
  250. package/dist/services/accepted-intent.js +139 -0
  251. package/dist/services/branch-context.js +3 -3
  252. package/dist/services/canon.js +54 -15
  253. package/dist/services/check-environment.js +21 -0
  254. package/dist/services/check-runner-evidence.js +562 -0
  255. package/dist/services/cleanup.js +133 -40
  256. package/dist/services/cli-operation-classifier.js +26 -63
  257. package/dist/services/cli-upgrade-allowlist.js +27 -0
  258. package/dist/services/code-checks.js +1398 -119
  259. package/dist/services/codex-hook-delivery.js +32 -0
  260. package/dist/services/command-context.js +80 -0
  261. package/dist/services/compatibility-preflight.js +16 -3
  262. package/dist/services/config.js +11 -8
  263. package/dist/services/continuation-outcome.js +20 -0
  264. package/dist/services/controller-child-capacity.js +13 -0
  265. package/dist/services/controller-child-observer.js +115 -0
  266. package/dist/services/controller-execution.js +94 -0
  267. package/dist/services/controller-fanout.js +226 -0
  268. package/dist/services/controller-memory-task.js +45 -0
  269. package/dist/services/controller-memory-worker.js +49 -0
  270. package/dist/services/dashboard.js +143 -58
  271. package/dist/services/descriptor-coordinator-session.js +35 -0
  272. package/dist/services/descriptor-root-work.js +53 -0
  273. package/dist/services/descriptor-run-settlement.js +107 -0
  274. package/dist/services/descriptor-run-start.js +475 -0
  275. package/dist/services/descriptor-stage-coordinator.js +250 -0
  276. package/dist/services/descriptor-stage-settlement.js +223 -0
  277. package/dist/services/descriptor-stage-start.js +175 -0
  278. package/dist/services/diagnostic-evidence.js +73 -0
  279. package/dist/services/diagnostic-report-publication.js +72 -0
  280. package/dist/services/diagnostic-report.js +67 -0
  281. package/dist/services/diagnostic-run-bundle.js +74 -0
  282. package/dist/services/diagnostic-snapshot.js +159 -0
  283. package/dist/services/diagnostic-stage-handlers.js +108 -0
  284. package/dist/services/diagnostic-stage-results.js +132 -0
  285. package/dist/services/diagnostic-start.js +84 -0
  286. package/dist/services/diagnostic-work-batch.js +34 -0
  287. package/dist/services/diagnostic-work-collection.js +97 -0
  288. package/dist/services/diagnostic-work-registration.js +101 -0
  289. package/dist/services/diagnostic-work-results.js +93 -0
  290. package/dist/services/discovery/agy-discovery.js +2 -0
  291. package/dist/services/discovery/codex-discovery.js +2 -0
  292. package/dist/services/discovery/droid-discovery.js +2 -0
  293. package/dist/services/discovery/grok-discovery.js +2 -0
  294. package/dist/services/discovery/opencode-discovery.js +2 -0
  295. package/dist/services/discovery/static.js +31 -0
  296. package/dist/services/discovery/zcode-discovery.js +2 -0
  297. package/dist/services/engines.js +354 -163
  298. package/dist/services/eval-snapshots.js +1316 -150
  299. package/dist/services/execution-bundles.js +275 -0
  300. package/dist/services/execution-policy.js +185 -0
  301. package/dist/services/executor-onboarding.js +56 -0
  302. package/dist/services/external-work-launch.js +310 -0
  303. package/dist/services/flow-catalog.js +30 -0
  304. package/dist/services/flow-descriptors.js +82 -0
  305. package/dist/services/frozen-engine.js +387 -0
  306. package/dist/services/generic-execution.js +164 -0
  307. package/dist/services/harness-adapter.js +355 -36
  308. package/dist/services/harness-config.js +2 -0
  309. package/dist/services/harness-discovery.js +29 -0
  310. package/dist/services/harness-doctor.js +75 -0
  311. package/dist/services/harness-probe-effects.js +51 -0
  312. package/dist/services/harness-version-probe.js +100 -0
  313. package/dist/services/hook-memory-routing.js +188 -0
  314. package/dist/services/hook-recovery-guard.js +14 -0
  315. package/dist/services/hook-session-observation.js +24 -0
  316. package/dist/services/hooks.js +791 -326
  317. package/dist/services/hybrid-contracts.js +474 -0
  318. package/dist/services/hybrid-digest.js +14 -0
  319. package/dist/services/ids.js +29 -1
  320. package/dist/services/installation-lock.js +95 -0
  321. package/dist/services/installed-flow-source.js +226 -0
  322. package/dist/services/lanes.js +78 -65
  323. package/dist/services/lifecycle-command.js +1 -288
  324. package/dist/services/lifecycle-contract.js +1 -0
  325. package/dist/services/lifecycle-invocations.js +2148 -0
  326. package/dist/services/managed-daemon-binding.js +38 -0
  327. package/dist/services/managed-lease-monitor.js +133 -0
  328. package/dist/services/managed-processes.js +449 -69
  329. package/dist/services/memory-accepted-composition.js +78 -0
  330. package/dist/services/memory-baseline-preservation.js +65 -0
  331. package/dist/services/memory-completed-correction-wave.js +328 -0
  332. package/dist/services/memory-compose-handler.js +400 -0
  333. package/dist/services/memory-composition-acceptance.js +375 -0
  334. package/dist/services/memory-composition-graph.js +165 -0
  335. package/dist/services/memory-composition-registration.js +161 -0
  336. package/dist/services/memory-composition-work-custody.js +156 -0
  337. package/dist/services/memory-composition-work-results.js +108 -0
  338. package/dist/services/memory-corrected-review-acceptance.js +129 -0
  339. package/dist/services/memory-corrected-review-batch.js +37 -0
  340. package/dist/services/memory-corrected-review-findings.js +108 -0
  341. package/dist/services/memory-corrected-review-inputs.js +215 -0
  342. package/dist/services/memory-corrected-review-projection.js +78 -0
  343. package/dist/services/memory-corrected-review-receipt.js +178 -0
  344. package/dist/services/memory-corrected-review-registration.js +192 -0
  345. package/dist/services/memory-correction-check-receipt.js +235 -0
  346. package/dist/services/memory-correction-input-owner.js +6 -0
  347. package/dist/services/memory-custody-snapshot.js +36 -0
  348. package/dist/services/memory-discover-handler.js +104 -0
  349. package/dist/services/memory-finish-evidence.js +457 -0
  350. package/dist/services/memory-finish-handler.js +229 -0
  351. package/dist/services/memory-finish-integration.js +450 -0
  352. package/dist/services/memory-finish-readiness.js +169 -0
  353. package/dist/services/memory-git-integration.js +220 -0
  354. package/dist/services/memory-historical-corrected-review.js +212 -0
  355. package/dist/services/memory-historical-original-review.js +143 -0
  356. package/dist/services/memory-historical-repair-registration.js +105 -0
  357. package/dist/services/memory-historical-second-review.js +147 -0
  358. package/dist/services/memory-init-bootstrap-readiness.js +210 -0
  359. package/dist/services/memory-init-contexts.js +131 -0
  360. package/dist/services/memory-init-controller-start.js +103 -0
  361. package/dist/services/memory-init-engine-release.js +223 -0
  362. package/dist/services/memory-init-git-policy.js +165 -0
  363. package/dist/services/memory-init-lint-capability.js +203 -0
  364. package/dist/services/memory-init-native-capacity.js +309 -0
  365. package/dist/services/memory-init-preflight.js +334 -0
  366. package/dist/services/memory-init-project-bootstrap.js +599 -0
  367. package/dist/services/memory-init-request-candidate.js +213 -0
  368. package/dist/services/memory-init-request-input.js +82 -0
  369. package/dist/services/memory-init-resource-storage.js +125 -0
  370. package/dist/services/memory-init-run-allocation.js +81 -0
  371. package/dist/services/memory-init-run-bundle.js +135 -0
  372. package/dist/services/memory-init-start.js +181 -0
  373. package/dist/services/memory-init-startup-custody.js +80 -0
  374. package/dist/services/memory-init-startup-input.js +240 -0
  375. package/dist/services/memory-init-storage.js +91 -0
  376. package/dist/services/memory-init-stores.js +359 -0
  377. package/dist/services/memory-init-worktrunk-capability.js +174 -0
  378. package/dist/services/memory-layer-publication.js +611 -0
  379. package/dist/services/memory-layer.js +221 -0
  380. package/dist/services/memory-lint-config.js +194 -0
  381. package/dist/services/memory-native-review-copy.js +103 -0
  382. package/dist/services/memory-normal-flow-entry.js +87 -0
  383. package/dist/services/memory-ordinary-defaults.js +129 -0
  384. package/dist/services/memory-preflight-bootstrap-custody.js +115 -0
  385. package/dist/services/memory-preflight-handler.js +274 -0
  386. package/dist/services/memory-repair-current-view.js +299 -0
  387. package/dist/services/memory-repair-document-delta.js +488 -0
  388. package/dist/services/memory-repair-history.js +496 -0
  389. package/dist/services/memory-repair-physical-binding.js +194 -0
  390. package/dist/services/memory-repair-publication-files.js +152 -0
  391. package/dist/services/memory-repair-publication-intent.js +27 -0
  392. package/dist/services/memory-repair-publication-plan.js +162 -0
  393. package/dist/services/memory-repair-publication-source.js +225 -0
  394. package/dist/services/memory-repair-publication.js +795 -0
  395. package/dist/services/memory-repair-registration.js +240 -0
  396. package/dist/services/memory-repair-work-baseline.js +415 -0
  397. package/dist/services/memory-repair-work-completion.js +117 -0
  398. package/dist/services/memory-repair-work-custody.js +73 -0
  399. package/dist/services/memory-repair-work-results.js +108 -0
  400. package/dist/services/memory-repair-workspace.js +261 -0
  401. package/dist/services/memory-repair-writer-copy.js +736 -0
  402. package/dist/services/memory-result-binding.js +239 -0
  403. package/dist/services/memory-retained-review-authority.js +138 -0
  404. package/dist/services/memory-retained-review-results.js +70 -0
  405. package/dist/services/memory-retained-second-baseline.js +102 -0
  406. package/dist/services/memory-retained-second-repairs.js +162 -0
  407. package/dist/services/memory-review-acceptance.js +214 -0
  408. package/dist/services/memory-review-correction-plan.js +206 -0
  409. package/dist/services/memory-review-handler.js +585 -0
  410. package/dist/services/memory-review-inputs.js +246 -0
  411. package/dist/services/memory-review-limits.js +10 -0
  412. package/dist/services/memory-review-receipt.js +180 -0
  413. package/dist/services/memory-review-registration.js +117 -0
  414. package/dist/services/memory-review-work-custody.js +126 -0
  415. package/dist/services/memory-review-work-input.js +91 -0
  416. package/dist/services/memory-review-work-results.js +95 -0
  417. package/dist/services/memory-second-corrected-review-inputs.js +167 -0
  418. package/dist/services/memory-second-correction-plan.js +77 -0
  419. package/dist/services/memory-second-repair-baseline.js +91 -0
  420. package/dist/services/memory-second-repair-launch.js +614 -0
  421. package/dist/services/memory-second-repair-registration.js +88 -0
  422. package/dist/services/memory-second-repair-source.js +187 -0
  423. package/dist/services/memory-second-review-acceptance.js +115 -0
  424. package/dist/services/memory-source-observations.js +117 -0
  425. package/dist/services/memory-stage-dispatch.js +65 -0
  426. package/dist/services/memory-stage-results.js +290 -0
  427. package/dist/services/memory-tool-probe.js +25 -0
  428. package/dist/services/memory-upgrade-analysis-handler.js +243 -0
  429. package/dist/services/memory-upgrade-bootstrap-readiness.js +251 -0
  430. package/dist/services/memory-upgrade-evidence.js +100 -0
  431. package/dist/services/memory-upgrade-inventory.js +442 -0
  432. package/dist/services/memory-upgrade-publication-custody.js +73 -0
  433. package/dist/services/memory-upgrade-publication.js +413 -0
  434. package/dist/services/memory-upgrade-selection.js +430 -0
  435. package/dist/services/memory-upgrade-start.js +60 -0
  436. package/dist/services/memory-upgrade-storage-readiness.js +340 -0
  437. package/dist/services/memory-verification-acceptance.js +156 -0
  438. package/dist/services/memory-verification-checks.js +317 -0
  439. package/dist/services/memory-verification-receipt.js +160 -0
  440. package/dist/services/memory-verify-handler.js +137 -0
  441. package/dist/services/memory-work-result-json.js +12 -0
  442. package/dist/services/memory-work-result-preparation.js +64 -0
  443. package/dist/services/merge-queue.js +228 -149
  444. package/dist/services/merge-server.js +163 -35
  445. package/dist/services/migrations.js +164 -114
  446. package/dist/services/model-catalog.js +109 -0
  447. package/dist/services/model-discovery.js +46 -0
  448. package/dist/services/native-daemon-history.js +50 -0
  449. package/dist/services/native-session-control.js +39 -0
  450. package/dist/services/operator-profiles.js +236 -0
  451. package/dist/services/physical-startup.js +45 -0
  452. package/dist/services/plan-runtime.js +26 -11
  453. package/dist/services/plans.js +28 -25
  454. package/dist/services/project-registry.js +97 -0
  455. package/dist/services/project-summary.js +1 -1
  456. package/dist/services/projects.js +17 -87
  457. package/dist/services/prompt-delivery.js +71 -0
  458. package/dist/services/prompts.js +28 -17
  459. package/dist/services/protocols.js +163 -87
  460. package/dist/services/provider-capacity.js +455 -0
  461. package/dist/services/recovery-observation-budget.js +121 -0
  462. package/dist/services/recovery-snapshot-database.js +588 -0
  463. package/dist/services/release-archive-producer.js +52 -0
  464. package/dist/services/release-archive.js +143 -0
  465. package/dist/services/release-cache.js +150 -0
  466. package/dist/services/release-compatibility.js +18 -0
  467. package/dist/services/release-download.js +67 -0
  468. package/dist/services/release-envelope.js +104 -0
  469. package/dist/services/release-manifest.js +45 -0
  470. package/dist/services/release-payload.js +114 -0
  471. package/dist/services/release-producer.js +63 -0
  472. package/dist/services/release-provenance.js +160 -0
  473. package/dist/services/release-registry.js +66 -0
  474. package/dist/services/release-source.js +194 -0
  475. package/dist/services/repair-intents.js +31 -0
  476. package/dist/services/repair-receipt.js +13 -0
  477. package/dist/services/review-copy.js +165 -0
  478. package/dist/services/review-deferral.js +23 -0
  479. package/dist/services/run-control-receipt.js +56 -0
  480. package/dist/services/run-control-worker.js +428 -0
  481. package/dist/services/run-control.js +1092 -0
  482. package/dist/services/run-controller-adapter.js +400 -0
  483. package/dist/services/run-controller-capture.js +186 -0
  484. package/dist/services/run-controller-process.js +205 -0
  485. package/dist/services/run-controller-recovery.js +295 -0
  486. package/dist/services/run-controller-state.js +97 -0
  487. package/dist/services/run-controller.js +1326 -0
  488. package/dist/services/run-engine-bindings.js +1 -1
  489. package/dist/services/run-fork.js +147 -0
  490. package/dist/services/run-health.js +149 -0
  491. package/dist/services/run-observations.js +152 -0
  492. package/dist/services/run-projection.js +22 -64
  493. package/dist/services/run-recovery-guard.js +107 -0
  494. package/dist/services/run-recovery-runtime.js +112 -0
  495. package/dist/services/run-recovery.js +456 -0
  496. package/dist/services/run-start-custody.js +189 -0
  497. package/dist/services/runs.js +1023 -246
  498. package/dist/services/runtime-budget.js +446 -0
  499. package/dist/services/runtime-command.js +43 -0
  500. package/dist/services/runtime-scope-capture.js +52 -0
  501. package/dist/services/runtime-scope-control.js +468 -0
  502. package/dist/services/runtime-scope-resume.js +605 -0
  503. package/dist/services/runtime-scope-stop.js +211 -0
  504. package/dist/services/runtime-scope-worker.js +314 -0
  505. package/dist/services/runtime-service.js +19 -14
  506. package/dist/services/schema-validation.js +10 -8
  507. package/dist/services/scope-writer-cleanup.js +27 -0
  508. package/dist/services/sessions.js +36 -91
  509. package/dist/services/setup.js +427 -0
  510. package/dist/services/stage-blocker.js +17 -7
  511. package/dist/services/stage-context.js +193 -19
  512. package/dist/services/stage-handlers.js +89 -0
  513. package/dist/services/stage-lifecycle.js +152 -116
  514. package/dist/services/stage-pause.js +202 -91
  515. package/dist/services/stage-report-renderer.js +234 -6
  516. package/dist/services/stage-work-graph.js +52 -0
  517. package/dist/services/tooling.js +41 -30
  518. package/dist/services/usage.js +49 -101
  519. package/dist/services/verification-matrix.js +393 -0
  520. package/dist/services/version-status.js +1 -1
  521. package/dist/services/vnext-code-review.js +235 -88
  522. package/dist/services/vnext-code.js +327 -226
  523. package/dist/services/vnext-contracts.js +10 -1
  524. package/dist/services/vnext-execution-profile.js +80 -5
  525. package/dist/services/vnext-fanout.js +355 -21
  526. package/dist/services/vnext-merge.js +332 -111
  527. package/dist/services/vnext-plan-review.js +287 -132
  528. package/dist/services/vnext-plan.js +228 -104
  529. package/dist/services/vnext-protocolize.js +533 -95
  530. package/dist/services/vnext-specify.js +123 -81
  531. package/dist/services/vnext-workspace-policy.js +7 -2
  532. package/dist/services/work-registry.js +1706 -200
  533. package/dist/services/workspace-bootstrap.js +53 -15
  534. package/dist/services/workspace-files.js +85 -0
  535. package/dist/services/worktrees.js +105 -48
  536. package/dist/shared/entity-references.js +1 -0
  537. package/dist/shared/errors.js +27 -0
  538. package/dist/storage/agent-output.js +26 -0
  539. package/dist/storage/data-preservation.js +65 -0
  540. package/dist/storage/database.js +930 -40
  541. package/dist/storage/foreign-key-transaction.js +39 -0
  542. package/dist/storage/immutable-input.js +45 -0
  543. package/dist/storage/import-guard.js +159 -0
  544. package/dist/storage/memory-upgrade-schema-bootstrap.js +38 -0
  545. package/dist/storage/native-cleanup.js +52 -0
  546. package/dist/storage/paired-writer-migration.js +424 -0
  547. package/dist/storage/paths.js +17 -4
  548. package/dist/storage/projection-lock.js +73 -0
  549. package/dist/storage/work-references.js +23 -0
  550. package/dist/storage/writer-contract.js +96 -0
  551. package/dist/storage/writer-migration.js +346 -0
  552. package/dist/storage/writer-restore.js +217 -0
  553. package/package.json +50 -13
  554. package/tools/create-canon-release.mjs +33 -0
  555. package/tools/probe-codex-history.mjs +37 -0
  556. package/tools/produce-hybrid-release.mjs +41 -0
  557. package/tools/repair-paused-run-status.mjs +84 -0
@@ -1 +1,10 @@
1
- export {};
1
+ import { isControllerManagedInvocation, managedLifecycleCommand } from "./lifecycle-invocations.js";
2
+ /** Successor authority belongs to the controller, not the finishing Session.
3
+ * Manual flows still need an executable next step. Current-stage finish and
4
+ * paused/child Work commands do not pass through this boundary. */
5
+ export function stageSuccessor(context, input) {
6
+ const next = { ...input.metadata, kind: "start_stage", stage: input.stage };
7
+ return isControllerManagedInvocation(context, input.projectRoot, input.runId)
8
+ ? { ...next, controller_owned: true, stop_turn: true, instruction: "This Stage is complete. End this Turn now and await the controller's next Stage assignment. Do not start the successor Stage or prepare its command." }
9
+ : { ...next, command: managedLifecycleCommand(context, input.command) };
10
+ }
@@ -1,19 +1,91 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ import crypto from "node:crypto";
4
+ import { isDeepStrictEqual } from "node:util";
3
5
  import { AppError } from "../shared/errors.js";
6
+ import { canonicalPath } from "../storage/paths.js";
7
+ import { isExecutionRouting } from "./execution-policy.js";
8
+ export const RUN_OPERATIONAL_DECISION_CONTRACT = "run-operational-decision@1";
9
+ const object = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
10
+ const text = (value) => typeof value === "string" && Boolean(value.trim());
11
+ const keys = (value, allowed) => object(value) && Object.keys(value).every(key => allowed.includes(key));
12
+ /** Structural validation only: acceptance is supplied by the caller, never inferred from settings. */
13
+ export function validateRunOperationalDecision(value, settings) {
14
+ if (!object(value) || value.schema_id !== RUN_OPERATIONAL_DECISION_CONTRACT || !text(value.id)
15
+ || !object(value.scope) || !text(value.scope.project_root) || !path.isAbsolute(value.scope.project_root)
16
+ || [value.scope.eval_id, value.scope.execution_id].some(item => item !== undefined && !text(item))
17
+ || !object(value.settings) || !object(value.settings.merge_delivery) || value.settings.merge_delivery.strategy !== settings.merge_delivery.strategy
18
+ || !object(value.settings.merge_cleanup) || value.settings.merge_cleanup.source !== settings.merge_cleanup.source
19
+ || !object(value.policy) || !text(value.policy.path) || !text(value.policy.sha256) || !/^[a-f0-9]{64}$/.test(value.policy.sha256)
20
+ || !Array.isArray(value.policy.clauses) || !value.policy.clauses.length || !value.policy.clauses.every(text)
21
+ || !object(value.acceptance) || !text(value.acceptance.source) || !text(value.acceptance.reference)
22
+ || value.exception !== undefined && (!object(value.exception) || !text(value.exception.reason) || !text(value.exception.owner)
23
+ || !text(value.exception.review_at) || !Number.isFinite(Date.parse(value.exception.review_at))
24
+ || value.exception.expires_at !== undefined && (!text(value.exception.expires_at) || !Number.isFinite(Date.parse(value.exception.expires_at))))) {
25
+ throw new AppError("operational_decision_invalid", "Operational decision requires explicit acceptance, scoped authority and matching settings", 2);
26
+ }
27
+ if (!keys(value, ["schema_id", "id", "scope", "settings", "policy", "acceptance", "exception", "declaration"])
28
+ || !keys(value.scope, ["project_root", "eval_id", "execution_id"])
29
+ || !keys(value.settings, ["merge_delivery", "merge_cleanup"])
30
+ || !keys(value.settings.merge_delivery, ["strategy"]) || !keys(value.settings.merge_cleanup, ["source"])
31
+ || !keys(value.policy, ["path", "sha256", "clauses"]) || !keys(value.acceptance, ["source", "reference"])
32
+ || value.exception !== undefined && !keys(value.exception, ["reason", "owner", "review_at", "expires_at"])
33
+ || value.declaration !== undefined && !keys(value.declaration, ["bytes", "sha256"]))
34
+ throw new AppError("operational_decision_invalid", "Operational decision contains unsupported authority fields", 2);
35
+ if (value.declaration !== undefined) {
36
+ if (!object(value.declaration) || !text(value.declaration.bytes) || !text(value.declaration.sha256)
37
+ || crypto.createHash("sha256").update(value.declaration.bytes).digest("hex") !== value.declaration.sha256)
38
+ throw new AppError("operational_decision_invalid", "Original operational declaration checksum does not match", 2);
39
+ let declaration;
40
+ try {
41
+ declaration = JSON.parse(value.declaration.bytes);
42
+ }
43
+ catch {
44
+ throw new AppError("operational_decision_invalid", "Original operational declaration must be JSON", 2);
45
+ }
46
+ if (!object(declaration) || !keys(declaration, ["schema_id", "id", "scope", "settings", "policy", "acceptance", "exception"])
47
+ || declaration.scope !== undefined && !keys(declaration.scope, ["project_root", "eval_id", "execution_id"])
48
+ || ["schema_id", "id", "settings", "policy", "acceptance", "exception"].some(key => !isDeepStrictEqual(declaration[key], value[key])))
49
+ throw new AppError("operational_decision_invalid", "Materialized decision changed the accepted declaration", 2);
50
+ }
51
+ return value;
52
+ }
53
+ /** Machine validation of declared policy bytes, not an NLP interpretation of Markdown. */
54
+ export function assertOperationalDecisionSource(decision, projectRoot, now) {
55
+ if (canonicalPath(decision.scope.project_root, false) !== canonicalPath(projectRoot, false))
56
+ throw new AppError("operational_decision_scope_mismatch", "Operational decision belongs to another project", 2);
57
+ const file = path.resolve(projectRoot, decision.policy.path);
58
+ const relative = path.relative(fs.realpathSync(projectRoot), fs.existsSync(file) ? fs.realpathSync(file) : file);
59
+ if (relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative) || relative === ".." || !fs.existsSync(file) || crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex") !== decision.policy.sha256)
60
+ throw new AppError("operational_decision_policy_changed", "Operational decision policy source checksum does not match an owned file", 2, { file });
61
+ if (decision.exception?.expires_at && Date.parse(decision.exception.expires_at) <= Date.parse(now))
62
+ throw new AppError("operational_decision_expired", "Explicit operational decision expiration has passed", 2);
63
+ }
4
64
  const relativePath = path.join(".memory-bank", "dd-flow", "project-execution.json");
5
65
  /** The project owns defaults; a RUN snapshots them once and never re-infers them. */
6
66
  export function loadVnextExecutionProfile(projectRoot) {
67
+ return loadVnextExecutionProfileSnapshot(projectRoot).profile;
68
+ }
69
+ /** Parse and identify the same source bytes; a second read cannot bind another declaration. */
70
+ export function loadVnextExecutionProfileSnapshot(projectRoot) {
7
71
  const file = path.join(projectRoot, relativePath);
72
+ let bytes;
8
73
  let value;
9
74
  try {
10
- value = JSON.parse(fs.readFileSync(file, "utf8"));
75
+ bytes = fs.readFileSync(file);
76
+ value = JSON.parse(bytes.toString("utf8"));
11
77
  }
12
78
  catch (error) {
13
79
  throw new AppError("execution_profile_missing", "vNext flow requires .memory-bank/dd-flow/project-execution.json", 1, { file, cause: String(error) });
14
80
  }
81
+ return { profile: validateVnextExecutionProfile(value), bytes };
82
+ }
83
+ /** Pure shared admission contract for project-execution@2 and @3. */
84
+ export function validateVnextExecutionProfile(value) {
85
+ if (!object(value))
86
+ throw new AppError("execution_profile_invalid", "Project execution profile must be an object", 1);
15
87
  const profile = value;
16
- if (profile.schema_id !== "dd-flow/project-execution@2"
88
+ if ((profile.schema_id !== "dd-flow/project-execution@2" && profile.schema_id !== "dd-flow/project-execution@3")
17
89
  || (profile.stage_session_mode !== undefined && profile.stage_session_mode !== "same_session" && profile.stage_session_mode !== "new_session")
18
90
  || !["auto", "off", "standard", "deep"].includes(String(profile.plan_review_mode))
19
91
  || (profile.code_review_mode !== undefined && !["auto", "off", "standard", "deep"].includes(String(profile.code_review_mode)))
@@ -21,10 +93,13 @@ export function loadVnextExecutionProfile(projectRoot) {
21
93
  || profile.merge_delivery?.strategy !== "local"
22
94
  || !["retain", "delete_after_success"].includes(String(profile.merge_cleanup?.source))
23
95
  || !["code_completed", "code_review_completed", "merge_completed"].includes(String(profile.stop_target))
24
- || !profile.code_bootstrap || typeof profile.code_bootstrap.command !== "string" || !profile.code_bootstrap.command.trim()
25
- || typeof profile.code_bootstrap.policy_ref !== "string" || !profile.code_bootstrap.policy_ref.trim()) {
26
- throw new AppError("execution_profile_invalid", "Project execution profile does not match dd-flow/project-execution@2", 1, { file });
96
+ || (profile.code_bootstrap !== undefined && (!profile.code_bootstrap || typeof profile.code_bootstrap.command !== "string" || !profile.code_bootstrap.command.trim()
97
+ || typeof profile.code_bootstrap.policy_ref !== "string" || !profile.code_bootstrap.policy_ref.trim()))
98
+ || (profile.execution === undefined ? profile.schema_id === "dd-flow/project-execution@3" : !isExecutionRouting(profile.execution))) {
99
+ throw new AppError("execution_profile_invalid", "Project execution profile does not match its supported dd-flow schema", 1);
27
100
  }
101
+ if (profile.operational_decision !== undefined)
102
+ validateRunOperationalDecision(profile.operational_decision, profile);
28
103
  return { ...profile, stage_session_mode: profile.stage_session_mode ?? "same_session", code_review_mode: profile.code_review_mode ?? "off" };
29
104
  }
30
105
  export function executionProfilePath(projectRoot) { return path.join(projectRoot, relativePath); }
@@ -3,13 +3,111 @@ import path from "node:path";
3
3
  import { AppError } from "../shared/errors.js";
4
4
  import { resolveProjectRoot } from "../storage/paths.js";
5
5
  import { requireProjectByRoot } from "./projects.js";
6
- import { getFlowRunVariables } from "./runs.js";
7
- import { listWorks } from "./work-registry.js";
6
+ import { getFlowRunVariables, appendFlowRunTimelineEvent, readFlowRunExecutionBundle } from "./runs.js";
7
+ import { resolveExecutionPolicy } from "./execution-policy.js";
8
+ import { listWorks, failWork, cancelWork, readWorkBatchCustody } from "./work-registry.js";
9
+ import { storageSessionId } from "./session-identity.js";
10
+ import { reconcileUnfinishedChecks } from "./code-checks.js";
8
11
  import { vnextStageDirectory } from "../domain/stage-catalog.js";
12
+ import { stageWorkIds, stageGraphScope } from "./stage-work-graph.js";
13
+ import { eligibleProviderChildOverload } from "./provider-capacity.js";
14
+ import { renderNativeChildFollowupInstruction } from "../harness-runtime/lib/delegation-instructions.mjs";
15
+ import { readDescriptorRootWork } from "./descriptor-root-work.js";
16
+ import { selectMemoryCompositionRegistration } from "./memory-composition-registration.js";
17
+ import { safeNativeFailure } from "../harness-runtime/lib/native-children.mjs";
18
+ import { isDiagnosticFlow } from "../domain/diagnostic-flows.js";
19
+ import { readRegisteredDiagnosticGraph } from "./diagnostic-work-registration.js";
9
20
  export const subagentCapacityKey = "runtime.subagent.available_slots";
21
+ /** External concurrency is policy, never evidence of native child qualification. */
22
+ export function getStageFanoutCapacity(context, input) {
23
+ const projectRoot = resolveProjectRoot(input.projectRoot);
24
+ const project = requireProjectByRoot(context, projectRoot);
25
+ const run = context.db.get("SELECT index_json FROM runs WHERE project_id = ? AND id = ?", [project.id, input.runId]);
26
+ if (!run)
27
+ throw new AppError("not_found", "RUN is unavailable", 1);
28
+ const index = JSON.parse(run.index_json);
29
+ const bundle = index.execution_bundle ? readFlowRunExecutionBundle(context, { projectRoot, runId: input.runId }) : null;
30
+ const generic = bundle?.execution.work(input.stage).delegation;
31
+ if (generic?.mode === "external")
32
+ return input.nativeOnly
33
+ ? { run_key: null, available_slots: null, source: "harness_qualification" }
34
+ : { run_key: null, available_slots: generic.max_parallel, source: "external_policy" };
35
+ // Generic native admission already requires a scoped capacity receipt whose
36
+ // observed count covers this frozen limit. Mutable legacy variables cannot
37
+ // replace that receipt or silently prevent its productive dispatch.
38
+ if (generic?.mode === "native")
39
+ return { run_key: null, available_slots: generic.max_parallel, source: "harness_qualification" };
40
+ const frozen = index.execution_profile;
41
+ if (!bundle && frozen?.settings.execution) {
42
+ if (!frozen.agent_profiles)
43
+ throw new AppError("execution_profile_not_frozen", "Fan-out needs the RUN's frozen profiles", 1);
44
+ const resolved = resolveExecutionPolicy({ ddFlowHome: context.ddFlowHome, policy: frozen.settings.execution, profiles: frozen.agent_profiles, stage: input.stage });
45
+ if (resolved.delegation.mode === "external" && !input.nativeOnly)
46
+ return { run_key: null, available_slots: resolved.delegation.max_parallel, source: "external_policy" };
47
+ }
48
+ const variables = getFlowRunVariables(context, { projectRoot, runId: input.runId });
49
+ const available = variables.variables[subagentCapacityKey];
50
+ return { run_key: subagentCapacityKey, available_slots: typeof available === "number" && Number.isInteger(available) && available >= 0 ? Math.min(available, generic?.max_parallel ?? available) : null, source: "harness_qualification" };
51
+ }
52
+ function nativeFailureController(context, projectId, runId, stage, parent) {
53
+ if (!context.db.get("SELECT 1 FROM sqlite_master WHERE type='table' AND name='run_controllers'"))
54
+ return null;
55
+ const rows = context.db.all("SELECT controller_id,generation,state_json FROM run_controllers WHERE project_id=? AND run_id=? AND status NOT IN ('completed','cancelled','superseded')", [projectId, runId]);
56
+ const matching = rows.filter(row => {
57
+ try {
58
+ const state = JSON.parse(row.state_json);
59
+ return state.stage === stage && state.sessions?.[state.current_session]?.id === parent;
60
+ }
61
+ catch {
62
+ return false;
63
+ }
64
+ });
65
+ return matching.length === 1 ? matching[0] : null;
66
+ }
67
+ /** Failure metadata authored by a Work is not native evidence. Require the
68
+ * independently recorded reconciliation and its exact current registry links. */
69
+ export function verifiedNativeWorkFailure(context, input) {
70
+ const b = input.receipt.native_binding;
71
+ if (!b || b.schema_id !== "dd-flow/native-work-failure@1")
72
+ return;
73
+ const project = requireProjectByRoot(context, input.projectRoot);
74
+ const scope = stageGraphScope(context, project.id, input.runId, input.stage);
75
+ const controller = nativeFailureController(context, project.id, input.runId, input.stage, input.parentSessionId);
76
+ if (!controller || b.controller_id !== controller.controller_id || b.generation !== controller.generation
77
+ || b.project_id !== project.id || b.run_id !== input.runId || b.stage !== input.stage
78
+ || b.stage_attempt_id !== scope.attempt || b.stage_cycle !== scope.cycle
79
+ || b.work_id !== input.workId || b.parent_work_id !== input.parentWorkId
80
+ || b.parent_provider_session_id !== input.parentSessionId || b.terminal_turn_id !== input.receipt.terminal_turn_id
81
+ || input.receipt.source !== "native_child_observation" || input.receipt.outcome !== "failed")
82
+ return;
83
+ const link = context.db.get(`SELECT ws.id FROM work_sessions ws JOIN works w ON w.work_id=ws.work_id JOIN sessions s ON s.project_id=w.project_id AND s.session_id=ws.session_id
84
+ WHERE ws.id=? AND w.project_id=? AND w.run_id=? AND w.work_id=? AND w.parent_work_id=? AND ws.status='failed' AND w.status='failed' AND s.parent_session_id=? AND s.provider_session_id=? AND s.harness=?`, [b.work_session_id, project.id, input.runId, input.workId, input.parentWorkId, storageSessionId({ harness_id: b.harness_id, session_id: input.parentSessionId }), b.provider_session_id, b.harness_id]);
85
+ if (!link)
86
+ return;
87
+ const run = context.db.get("SELECT run_root FROM runs WHERE project_id=? AND id=?", [project.id, input.runId]);
88
+ try {
89
+ const events = fs.readFileSync(path.join(run.run_root, "timeline.jsonl"), "utf8").trim().split("\n");
90
+ const proof = events.some(line => {
91
+ const event = JSON.parse(line);
92
+ if (event.type !== "native_child_reconciliation" || event.stage !== input.stage)
93
+ return false;
94
+ const terminal = event.observations?.children?.find((child) => child.session_id === b.provider_session_id && child.terminal_turn_id === b.terminal_turn_id);
95
+ if (event.observations?.parent_session_id !== b.parent_provider_session_id || event.observations?.harness_id !== b.harness_id
96
+ || terminal?.status !== "failed" || terminal.terminal_observed_at !== input.receipt.terminal_observed_at
97
+ || JSON.stringify(safeNativeFailure(terminal.provider_error)) !== JSON.stringify(safeNativeFailure(input.receipt.provider_error)))
98
+ return false;
99
+ return event.reconciled?.some((entry) => entry.outcome === "failed"
100
+ && entry.native_binding && Object.keys(b).every(key => b[key] === entry.native_binding[key]));
101
+ });
102
+ return proof ? b : undefined;
103
+ }
104
+ catch {
105
+ return;
106
+ }
107
+ }
10
108
  export function fanoutState(input) {
11
109
  if (!input.hasWork)
12
- return input.dispatch === "none" ? "coordinator_required" : "dispatch_required";
110
+ return input.registeredEmpty ? "children_settled" : input.dispatch === "none" ? "coordinator_required" : "dispatch_required";
13
111
  if (input.capacityRequired && !input.capacityKnown)
14
112
  return "capacity_required";
15
113
  if (input.created || input.running)
@@ -18,7 +116,7 @@ export function fanoutState(input) {
18
116
  }
19
117
  export function fanoutFile(stageRoot) { return path.join(stageRoot, "fanout.json"); }
20
118
  export function writeFanoutDescriptor(stageRoot, descriptor) {
21
- const value = { schema_id: "dd-flow/work-fanout@1", kind: "work_fanout", ...descriptor };
119
+ const value = { schema_id: descriptor.scope ? "dd-flow/work-fanout@2" : "dd-flow/work-fanout@1", kind: "work_fanout", ...descriptor };
22
120
  fs.writeFileSync(fanoutFile(stageRoot), `${JSON.stringify(value, null, 2)}\n`);
23
121
  return value;
24
122
  }
@@ -27,31 +125,135 @@ export function readFanoutDescriptor(stageRoot) {
27
125
  if (!fs.existsSync(file))
28
126
  return null;
29
127
  const value = JSON.parse(fs.readFileSync(file, "utf8"));
30
- if (value.schema_id !== "dd-flow/work-fanout@1" || value.kind !== "work_fanout" || typeof value.stage !== "string" || typeof value.parent_work_id !== "string" || !["none", "plan-review"].includes(String(value.dispatch)) || typeof value.capacity_required !== "boolean") {
128
+ if (!["dd-flow/work-fanout@1", "dd-flow/work-fanout@2"].includes(String(value.schema_id)) || value.kind !== "work_fanout" || typeof value.stage !== "string" || typeof value.parent_work_id !== "string" || !["none", "plan-review"].includes(String(value.dispatch)) || typeof value.capacity_required !== "boolean" || value.schema_id === "dd-flow/work-fanout@2" && (!value.scope || [value.scope.project_id, value.scope.run_id, value.scope.attempt, value.scope.cycle].some(item => typeof item !== "string"))) {
31
129
  throw new AppError("runtime_missing", "Stage fan-out descriptor is invalid", 1, { file });
32
130
  }
33
131
  return value;
34
132
  }
133
+ /** Memory graphs are SQL-owned; a mutable/missing fanout.json is not their
134
+ * dispatch authority. Domain registrations and Work launch owners still fence
135
+ * productive assignments, including reviewer/correction waves.
136
+ */
137
+ export function memoryStageFanoutDescriptor(context, input) {
138
+ if (!["compose", "upgrade", "review"].includes(input.stage))
139
+ return null;
140
+ const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
141
+ const run = context.db.get("SELECT flow_kind,index_json FROM runs WHERE project_id=? AND id=?", [project.id, input.runId]);
142
+ if (!run || !["mb-init", "mb-upgrade"].includes(run.flow_kind))
143
+ return null;
144
+ const authoringStage = run.flow_kind === "mb-init" ? "compose" : "upgrade";
145
+ if (input.stage !== authoringStage && input.stage !== "review")
146
+ return null;
147
+ const bundle = readFlowRunExecutionBundle(context, input);
148
+ if (!bundle || bundle.descriptor.descriptor.id !== run.flow_kind)
149
+ throw new AppError("stage_graph_compatibility_required", "Memory graph requires its admitted RUN bundle", 1);
150
+ const index = JSON.parse(run.index_json);
151
+ if (index.current_stage !== input.stage || !index.stage_runs.some(stage => stage.stage === input.stage && stage.status === "running"))
152
+ return null;
153
+ const root = readDescriptorRootWork(context, { projectId: project.id, runId: input.runId }, bundle);
154
+ const scope = stageGraphScope(context, project.id, input.runId, input.stage), members = stageWorkIds(context, project.id, input.runId, input.stage);
155
+ if (root.status !== "running" || !members.has(root.workId))
156
+ throw new AppError("stage_graph_conflict", "Memory graph lost its active coordinator", 1);
157
+ for (const id of members) {
158
+ if (id === root.workId)
159
+ continue;
160
+ const work = context.db.get("SELECT parent_work_id,result_schema FROM works WHERE project_id=? AND run_id=? AND work_id=?", [project.id, input.runId, id]);
161
+ const schemas = input.stage === authoringStage ? ["dd-flow/memory-composition-work-result@1"] : ["dd-flow/memory-review-work-result@1", "dd-flow/memory-repair-work-result@1"];
162
+ if (!work || work.parent_work_id !== root.workId || !schemas.includes(work.result_schema ?? ""))
163
+ throw new AppError("stage_graph_conflict", "Memory graph contains an unassigned Work", 1);
164
+ const custody = readWorkBatchCustody(context, id);
165
+ if (custody.scope.project_id !== scope.project_id || custody.scope.run_id !== scope.run_id || custody.scope.attempt !== scope.attempt
166
+ || custody.scope.cycle !== scope.cycle || custody.scope.stage !== input.stage || custody.scope.parent_work_id !== root.workId)
167
+ throw new AppError("stage_graph_conflict", "Memory graph contains foreign allocation custody", 1);
168
+ }
169
+ if (input.stage === authoringStage) {
170
+ const children = context.db.all("SELECT work_id FROM works WHERE project_id=? AND run_id=? AND parent_work_id=?", [project.id, input.runId, root.workId]).filter(work => members.has(work.work_id));
171
+ const registered = context.db.get("SELECT 1 FROM audit_events WHERE project_id=? AND event_type='memory.composition_registration' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.scope.attempt')=? AND json_extract(payload_json,'$.scope.cycle')=? LIMIT 1", [project.id, input.runId, scope.attempt, scope.cycle]);
172
+ if (children.length || registered) {
173
+ const registration = selectMemoryCompositionRegistration(context, { ...scope, stage: authoringStage, parent_work_id: root.workId });
174
+ const ids = new Set(Object.values(registration.work_ids));
175
+ if (ids.size !== children.length || children.some(work => !ids.has(work.work_id)))
176
+ throw new AppError("stage_graph_conflict", "Memory composition differs from its registered graph", 1);
177
+ }
178
+ }
179
+ return { schema_id: "dd-flow/work-fanout@2", kind: "work_fanout", scope, stage: input.stage, parent_work_id: root.workId, dispatch: "none", capacity_required: true };
180
+ }
181
+ /** Diagnostic graphs are registered by the Stage handler, never fanout.json. */
182
+ export function diagnosticStageFanoutDescriptor(context, input) {
183
+ if (!["audit", "analyze", "review"].includes(input.stage))
184
+ return null;
185
+ const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
186
+ const run = context.db.get("SELECT flow_kind,index_json FROM runs WHERE project_id=? AND id=?", [project.id, input.runId]);
187
+ if (!run || !isDiagnosticFlow(run.flow_kind))
188
+ return null;
189
+ const index = JSON.parse(run.index_json);
190
+ if (index.current_stage !== input.stage || !index.stage_runs.some(stage => stage.stage === input.stage && stage.status === "running"))
191
+ return null;
192
+ const bundle = readFlowRunExecutionBundle(context, input);
193
+ if (!bundle || bundle.manifest.flow_id !== run.flow_kind)
194
+ throw new AppError("stage_graph_conflict", "Diagnostic graph requires its frozen bundle", 1);
195
+ const scope = { projectId: project.id, runId: input.runId, stage: input.stage };
196
+ const root = readDescriptorRootWork(context, scope, bundle), graph = readRegisteredDiagnosticGraph(context, scope);
197
+ const members = stageWorkIds(context, project.id, input.runId, input.stage);
198
+ if (!graph || graph.parent_work_id !== root.workId || graph.bundle_sha256 !== bundle.sha256 || root.status !== "running"
199
+ || !members.has(root.workId) || members.size !== Object.keys(graph.work_ids).length + 1
200
+ || Object.values(graph.work_ids).some(id => !members.has(id)))
201
+ throw new AppError("stage_graph_conflict", "Diagnostic graph requires its exact registered Work; replay the issued Stage entry if registration was interrupted", 1);
202
+ return { schema_id: "dd-flow/work-fanout@2", kind: "work_fanout", scope: stageGraphScope(context, project.id, input.runId, input.stage),
203
+ stage: input.stage, parent_work_id: root.workId, dispatch: "none", capacity_required: true };
204
+ }
35
205
  /** Read-only projection for an external orchestrator. dd-flow keeps the graph authority. */
36
206
  export function getVnextFanoutStatus(context, input) {
37
207
  const projectRoot = resolveProjectRoot(input.projectRoot);
38
208
  const project = requireProjectByRoot(context, projectRoot);
39
- const run = context.db.get("SELECT run_root FROM runs WHERE project_id = ? AND id = ?", [project.id, input.runId]);
40
- if (!run?.run_root)
41
- throw new AppError("not_found", "RUN artifact root is unavailable", 1, { run_id: input.runId });
42
- const stageRoot = path.join(run.run_root, vnextStageDirectory(input.stage));
43
- const descriptor = readFanoutDescriptor(stageRoot);
209
+ const run = context.db.get("SELECT run_root, index_json FROM runs WHERE project_id = ? AND id = ?", [project.id, input.runId]);
210
+ if (!run)
211
+ throw new AppError("not_found", "RUN is not registered", 1, { run_id: input.runId });
212
+ if (!run.run_root)
213
+ throw new AppError("runtime_artifact_missing", "RUN artifact root is unavailable", 1, { run_id: input.runId });
214
+ const index = JSON.parse(run.index_json);
215
+ const bundle = index.execution_bundle ? readFlowRunExecutionBundle(context, { projectRoot, runId: input.runId }) : null;
216
+ const stageRoot = path.join(run.run_root, bundle ? bundle.descriptor.stage(input.stage).directory : vnextStageDirectory(input.stage));
217
+ const memoryDescriptor = memoryStageFanoutDescriptor(context, input);
218
+ const diagnosticDescriptor = diagnosticStageFanoutDescriptor(context, input);
219
+ let descriptor = memoryDescriptor ?? diagnosticDescriptor ?? (bundle && isDiagnosticFlow(bundle.manifest.flow_id) ? null : readFanoutDescriptor(stageRoot));
220
+ // Registration is authoritative even if projection publication was interrupted,
221
+ // or an otherwise review-off stage acquired an aggregate repair graph.
222
+ if (!descriptor && context.db.get("SELECT 1 FROM sqlite_master WHERE name = 'repair_intents'")) {
223
+ const members = stageWorkIds(context, project.id, input.runId, input.stage);
224
+ const parents = context.db.all("SELECT w.work_id, w.parent_work_id FROM repair_intents i JOIN works w ON w.work_id = i.work_id WHERE i.project_id = ? AND i.run_id = ? AND i.stage = ?", [project.id, input.runId, input.stage]).filter(work => members.has(work.work_id));
225
+ const roots = [...new Set(parents.map(work => work.parent_work_id))];
226
+ if (roots.length > 1)
227
+ throw new AppError("stage_graph_conflict", "Registered repairs disagree on their stage coordinator", 1);
228
+ if (roots.length === 1)
229
+ descriptor = { schema_id: "dd-flow/work-fanout@2", scope: stageGraphScope(context, project.id, input.runId, input.stage), kind: "work_fanout", stage: input.stage, parent_work_id: roots[0], dispatch: "none", capacity_required: true };
230
+ }
44
231
  if (!descriptor)
45
232
  return { ok: true, run_id: input.runId, stage: input.stage, orchestration: null };
46
233
  if (descriptor.stage !== input.stage)
47
234
  throw new AppError("runtime_missing", "Stage fan-out descriptor belongs to another stage", 1, { requested_stage: input.stage, descriptor_stage: descriptor.stage });
48
- const variables = getFlowRunVariables(context, { projectRoot, runId: input.runId });
49
- const available = variables.variables[subagentCapacityKey];
50
- const all = listWorks(context, { parentWorkId: descriptor.parent_work_id, includeResults: false });
51
- const ready = listWorks(context, { parentWorkId: descriptor.parent_work_id, ready: true, includeResults: false });
235
+ const capacity = getStageFanoutCapacity(context, { projectRoot, runId: input.runId, stage: input.stage });
236
+ const members = stageWorkIds(context, project.id, input.runId, input.stage);
237
+ const currentScope = stageGraphScope(context, project.id, input.runId, input.stage);
238
+ const compatible = descriptor.scope ? Object.entries(currentScope).every(([key, value]) => descriptor.scope[key] === value) : members.size > 0;
239
+ if (!compatible)
240
+ return { ok: true, run_id: input.runId, stage: input.stage, orchestration: { ...descriptor, state: "compatibility_required", compatibility: { code: "stage_graph_compatibility_required", message: "Legacy or stale stage graph has no authoritative current membership; use the pinned engine", expected_scope: currentScope } } };
241
+ const all = listWorks(context, { parentWorkId: descriptor.parent_work_id, includeResults: false, workIds: members });
242
+ const ready = { works: all.works.filter(work => work.ready) };
52
243
  const counts = Object.fromEntries(["created", "running", "completed", "failed", "cancelled"].map((status) => [status, all.works.filter((work) => work.status === status).length]));
53
244
  const hasWork = all.works.length > 0;
54
- const capacityKnown = typeof available === "number" && Number.isInteger(available) && available >= 0;
245
+ // Only SQL-owned memory registration can attest a deliberately empty
246
+ // graph. A mutable fanout.json must never turn missing Work into success.
247
+ let registeredEmpty = false;
248
+ if (diagnosticDescriptor && !hasWork) {
249
+ registeredEmpty = Object.keys(readRegisteredDiagnosticGraph(context, { projectId: project.id, runId: input.runId, stage: input.stage }).work_ids).length === 0;
250
+ }
251
+ if (memoryDescriptor && input.stage === "upgrade" && !hasWork
252
+ && context.db.get("SELECT 1 FROM audit_events WHERE project_id=? AND event_type='memory.composition_registration' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.scope.stage')='upgrade' AND json_extract(payload_json,'$.scope.attempt')=? AND json_extract(payload_json,'$.scope.cycle')=? AND json_extract(payload_json,'$.scope.parent_work_id')=? LIMIT 1", [project.id, input.runId, currentScope.attempt, currentScope.cycle, descriptor.parent_work_id])) {
253
+ const registered = selectMemoryCompositionRegistration(context, { ...currentScope, stage: "upgrade", parent_work_id: descriptor.parent_work_id });
254
+ registeredEmpty = Object.keys(registered.work_ids).length === 0;
255
+ }
256
+ const capacityKnown = capacity.available_slots !== null;
55
257
  return {
56
258
  ok: true,
57
259
  run_id: input.runId,
@@ -61,12 +263,144 @@ export function getVnextFanoutStatus(context, input) {
61
263
  // PLAN-REVIEW has a deterministic dispatcher. CODE and CODE-REVIEW do
62
264
  // not: their coordinator first materializes the Work graph from its
63
265
  // authoritative prompt. An empty graph is therefore not a success.
64
- state: fanoutState({ dispatch: descriptor.dispatch, hasWork, capacityRequired: descriptor.capacity_required, capacityKnown, created: counts.created ?? 0, running: counts.running ?? 0 }),
65
- capacity: {
66
- run_key: subagentCapacityKey,
67
- available_slots: capacityKnown ? available : null
68
- },
69
- works: { ...counts, ready: ready.works.map((work) => ({ work_id: work.work_id, task: work.task, start_command: work.start_command })) }
266
+ state: fanoutState({ dispatch: descriptor.dispatch, hasWork, registeredEmpty, capacityRequired: descriptor.capacity_required, capacityKnown, created: counts.created ?? 0, running: counts.running ?? 0 }),
267
+ capacity,
268
+ graph_revision: all.works.map(work => ({ work_id: work.work_id, status: work.status, depends_on: work.depends_on })),
269
+ works: { ...counts, ready: ready.works.map((work) => ({ work_id: work.work_id, task: work.task, launch_policy: work.launch_policy, start_command: work.start_command })) }
70
270
  }
71
271
  };
72
272
  }
273
+ /** Explicit controller mutation, separate from read-only fanout status.
274
+ * A native child may be mapped only by its registered identity. Reused native
275
+ * Sessions need a provider execution identity: without one we report ambiguity
276
+ * instead of applying a late event to the newest Work attempt. */
277
+ export async function reconcileVnextFanout(context, input) {
278
+ const value = prepareNativeObservations(input.observations);
279
+ const status = getVnextFanoutStatus(context, input);
280
+ if (!status.orchestration)
281
+ return { ok: true, issues: [], reconciled: [] };
282
+ if (status.orchestration.state === "compatibility_required")
283
+ throw new AppError("stage_graph_compatibility_required", "Cannot reconcile a legacy or stale graph", 1);
284
+ const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
285
+ const members = stageWorkIds(context, project.id, input.runId, input.stage);
286
+ const parent = storageSessionId({ harness_id: value.harness_id, session_id: value.parent_session_id });
287
+ const issues = [], reconciled = [];
288
+ for (const child of value.children) {
289
+ if (child.provenance === "parent_mismatch" || child.parent_session_id !== undefined && child.parent_session_id !== value.parent_session_id) {
290
+ issues.push({ code: "native_child_parent_mismatch", session_id: child.session_id, observed_parent_session_id: child.parent_session_id ?? null, expected_parent_session_id: value.parent_session_id, outcome: child.status });
291
+ continue;
292
+ }
293
+ if (child.status === "running" || child.status === "unknown")
294
+ continue;
295
+ const sessionId = storageSessionId({ harness_id: value.harness_id, session_id: String(child.session_id) });
296
+ const links = context.db.all(`SELECT ws.id, ws.work_id, ws.status, w.status AS work_status, w.parent_work_id FROM work_sessions ws JOIN works w ON w.work_id = ws.work_id JOIN sessions s ON s.project_id = w.project_id AND s.session_id = ws.session_id WHERE w.project_id = ? AND w.run_id = ? AND ws.session_id = ? AND s.parent_session_id = ? ORDER BY ws.created_at`, [project.id, input.runId, sessionId, parent]);
297
+ const active = links.filter(link => link.status === "running" && link.work_status === "running" && link.parent_work_id === status.orchestration.parent_work_id && members.has(link.work_id));
298
+ if (!active.length) {
299
+ if (!links.length)
300
+ issues.push({ code: "child_launch_association_missing", session_id: child.session_id, outcome: child.status });
301
+ continue;
302
+ }
303
+ if (links.length !== 1 || active.length !== 1) {
304
+ issues.push({ code: "child_execution_ambiguous", session_id: child.session_id, work_ids: active.map(link => link.work_id) });
305
+ continue;
306
+ }
307
+ const link = active[0];
308
+ if (renderNativeChildFollowupInstruction(String(value.harness_id), String(child.session_id)) && eligibleProviderChildOverload(child, value.parent_session_id)) {
309
+ issues.push({ code: "child_capacity_pending", work_id: link.work_id, session_id: child.session_id });
310
+ continue;
311
+ }
312
+ const pendingCheck = () => context.db.get("SELECT 1 FROM check_receipts WHERE project_id = ? AND run_id = ? AND work_id = ? AND status = 'running' LIMIT 1", [project.id, input.runId, link.work_id]);
313
+ if (pendingCheck()) {
314
+ const run = context.db.get("SELECT workspace_root, run_root FROM runs WHERE project_id = ? AND id = ?", [project.id, input.runId]);
315
+ try {
316
+ reconcileUnfinishedChecks(context, { projectId: project.id, runId: input.runId, workspaceRoot: run.workspace_root, runHome: run.run_root, workId: link.work_id });
317
+ }
318
+ catch (error) {
319
+ if (!(error instanceof AppError) || error.code !== "check_in_progress")
320
+ throw error;
321
+ }
322
+ if (pendingCheck()) {
323
+ issues.push({ code: "child_check_in_progress", work_id: link.work_id });
324
+ continue;
325
+ }
326
+ }
327
+ if (context.db.get("SELECT 1 FROM works WHERE parent_work_id = ? AND status IN ('created','running','paused') LIMIT 1", [link.work_id])) {
328
+ issues.push({ code: "child_descendants_unsettled", work_id: link.work_id });
329
+ continue;
330
+ }
331
+ if (child.status === "settled_by_root") {
332
+ issues.push({ code: "child_execution_outcome_unconfirmed", work_id: link.work_id, session_id: child.session_id });
333
+ continue;
334
+ }
335
+ if (child.status === "completed") {
336
+ issues.push({ code: "execution_ended_without_work_result", work_id: link.work_id, session_id: child.session_id });
337
+ continue;
338
+ }
339
+ const providerError = safeNativeFailure(child.provider_error);
340
+ const controller = nativeFailureController(context, project.id, input.runId, input.stage, value.parent_session_id);
341
+ const scope = stageGraphScope(context, project.id, input.runId, input.stage);
342
+ const terminalTime = typeof child.terminal_observed_at === "string" ? Date.parse(child.terminal_observed_at) : NaN;
343
+ const nativeBinding = child.status === "failed" && controller && typeof child.terminal_turn_id === "string" && child.terminal_turn_id.length > 0 && child.terminal_turn_id.length <= 256 && Number.isFinite(terminalTime)
344
+ ? { schema_id: "dd-flow/native-work-failure@1", project_id: project.id, run_id: input.runId, stage: input.stage, stage_attempt_id: scope.attempt, stage_cycle: scope.cycle,
345
+ parent_work_id: status.orchestration.parent_work_id, work_id: link.work_id, work_session_id: link.id, harness_id: value.harness_id,
346
+ parent_provider_session_id: value.parent_session_id, provider_session_id: String(child.session_id), terminal_turn_id: child.terminal_turn_id,
347
+ controller_id: controller.controller_id, generation: controller.generation } : undefined;
348
+ const reason = JSON.stringify({ source: "native_child_observation", harness_id: value.harness_id, session_id: child.session_id, work_session_id: link.id, outcome: child.status,
349
+ ...(nativeBinding ? { native_binding: nativeBinding } : {}),
350
+ ...(typeof child.terminal_turn_id === "string" ? { terminal_turn_id: child.terminal_turn_id } : {}),
351
+ ...(typeof child.terminal_observed_at === "string" ? { terminal_observed_at: child.terminal_observed_at } : {}),
352
+ ...(providerError ? { provider_error: providerError } : {}) });
353
+ const result = await (child.status === "failed" ? failWork : cancelWork)(context, link.work_id, reason);
354
+ reconciled.push({ work_id: link.work_id, work_session_id: link.id, outcome: child.status, ...(nativeBinding ? { native_binding: nativeBinding } : {}), result });
355
+ }
356
+ appendFlowRunTimelineEvent(context, project.id, input.runId, { type: "native_child_reconciliation", stage: input.stage, observations: value, issues, reconciled });
357
+ return { ok: true, issues, reconciled };
358
+ }
359
+ /**
360
+ * Records every terminal native child at the Stage where it is first observed.
361
+ * This is separate from fan-out settlement: a historical child can be valid
362
+ * evidence for an earlier Work, while an unbound terminal child is retained
363
+ * as an inconsistency instead of being rediscovered in a later review wave.
364
+ */
365
+ export function prepareNativeObservations(observations) {
366
+ const value = observations;
367
+ if (!value || typeof value.harness_id !== "string" || !value.harness_id.trim() || typeof value.parent_session_id !== "string" || !value.parent_session_id.trim() || !Array.isArray(value.children)
368
+ || value.children.some(child => !child || typeof child.session_id !== "string" || !child.session_id.trim() || typeof child.status !== "string" || !["running", "unknown", "completed", "failed", "cancelled", "settled_by_root"].includes(child.status) || child.parent_session_id !== undefined && typeof child.parent_session_id !== "string" || child.provenance !== undefined && !["root_child", "parent_mismatch"].includes(String(child.provenance))))
369
+ throw new AppError("usage", "Expected native observations: harness_id, parent_session_id and children[{session_id,status}]", 2);
370
+ if (new Set(value.children.map(child => child.session_id)).size !== value.children.length)
371
+ throw new AppError("usage", "Native observations must contain each child Session exactly once", 2);
372
+ return { harness_id: value.harness_id, parent_session_id: value.parent_session_id, children: value.children };
373
+ }
374
+ export function observeVnextNativeChildren(context, input) {
375
+ const value = prepareNativeObservations(input.observations);
376
+ const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
377
+ const parent = storageSessionId({ harness_id: value.harness_id, session_id: value.parent_session_id });
378
+ const issues = [], history = [], pending = [];
379
+ for (const child of value.children) {
380
+ if (child.provenance === "parent_mismatch" || child.parent_session_id !== undefined && child.parent_session_id !== value.parent_session_id) {
381
+ issues.push({ code: "native_child_parent_mismatch", session_id: child.session_id, observed_parent_session_id: child.parent_session_id ?? null, expected_parent_session_id: value.parent_session_id, outcome: child.status, detected_at_stage: input.stage });
382
+ continue;
383
+ }
384
+ const sessionId = storageSessionId({ harness_id: value.harness_id, session_id: String(child.session_id) });
385
+ const links = context.db.all(`SELECT ws.id, ws.work_id, ws.status, w.status AS work_status, w.parent_work_id, s.current_stage
386
+ FROM work_sessions ws JOIN works w ON w.work_id = ws.work_id
387
+ JOIN sessions s ON s.project_id = w.project_id AND s.session_id = ws.session_id
388
+ WHERE w.project_id = ? AND w.run_id = ? AND ws.session_id = ? AND s.parent_session_id = ? ORDER BY ws.created_at`, [project.id, input.runId, sessionId, parent]);
389
+ if (child.status === "running" || child.status === "unknown") {
390
+ pending.push({ session_id: child.session_id, outcome: child.status, links: links.map(link => ({ work_id: link.work_id, work_session_id: link.id })) });
391
+ continue;
392
+ }
393
+ if (!links.length) {
394
+ issues.push({ code: "unbound_native_delegation_detected", session_id: child.session_id, outcome: child.status, detected_at_stage: input.stage, origin_stage: "unknown" });
395
+ continue;
396
+ }
397
+ if (links.length !== 1) {
398
+ issues.push({ code: "child_execution_ambiguous", session_id: child.session_id, outcome: child.status, detected_at_stage: input.stage, work_ids: links.map(link => link.work_id) });
399
+ continue;
400
+ }
401
+ const link = links[0];
402
+ history.push({ session_id: child.session_id, outcome: child.status, work_id: link.work_id, work_session_id: link.id, work_status: link.work_status, origin_stage: link.current_stage ?? "unknown", detected_at_stage: input.stage });
403
+ }
404
+ appendFlowRunTimelineEvent(context, project.id, input.runId, { type: "native_child_observation", stage: input.stage, observations: value, issues, history, pending });
405
+ return { ok: true, issues, history, pending };
406
+ }