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

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 (560) hide show
  1. package/CHANGELOG.md +907 -0
  2. package/README.md +139 -10
  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 +79 -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/agy-terminal-envelope.mjs +67 -0
  58. package/dist/harness-runtime/lib/daemon-observation.d.mts +2 -0
  59. package/dist/harness-runtime/lib/daemon-observation.mjs +163 -0
  60. package/dist/harness-runtime/lib/daemon-operations.d.mts +2 -0
  61. package/dist/harness-runtime/lib/daemon-operations.mjs +462 -0
  62. package/dist/harness-runtime/lib/dd-agy-daemon.d.mts +6 -0
  63. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +1092 -0
  64. package/dist/harness-runtime/lib/dd-agy.mjs +86 -0
  65. package/dist/harness-runtime/lib/dd-codex-daemon.d.mts +1 -0
  66. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +231 -0
  67. package/dist/harness-runtime/lib/dd-codex.d.mts +1 -0
  68. package/dist/harness-runtime/lib/dd-codex.mjs +954 -0
  69. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +141 -0
  70. package/dist/harness-runtime/lib/dd-droid.mjs +626 -0
  71. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +555 -0
  72. package/dist/harness-runtime/lib/dd-grok.mjs +180 -0
  73. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +627 -0
  74. package/dist/harness-runtime/lib/dd-opencode.mjs +390 -0
  75. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +752 -0
  76. package/dist/harness-runtime/lib/dd-zcode.mjs +1200 -0
  77. package/dist/harness-runtime/lib/delegation-instructions.d.mts +14 -0
  78. package/dist/harness-runtime/lib/delegation-instructions.mjs +166 -0
  79. package/dist/harness-runtime/lib/dispatch-fence.mjs +14 -0
  80. package/dist/harness-runtime/lib/driver-recovery.mjs +140 -0
  81. package/dist/harness-runtime/lib/droid-observation.mjs +66 -0
  82. package/dist/harness-runtime/lib/entity-references.d.mts +2 -0
  83. package/dist/harness-runtime/lib/entity-references.mjs +9 -0
  84. package/dist/harness-runtime/lib/lease-renewal.d.mts +8 -0
  85. package/dist/harness-runtime/lib/lease-renewal.mjs +37 -0
  86. package/dist/harness-runtime/lib/lifecycle-command.d.mts +20 -0
  87. package/dist/harness-runtime/lib/lifecycle-command.mjs +273 -0
  88. package/dist/harness-runtime/lib/lifecycle-contract.d.mts +10 -0
  89. package/dist/harness-runtime/lib/lifecycle-contract.mjs +19 -0
  90. package/dist/harness-runtime/lib/managed-daemon.d.mts +16 -0
  91. package/dist/harness-runtime/lib/managed-daemon.mjs +698 -0
  92. package/dist/harness-runtime/lib/managed-flow-env.mjs +15 -0
  93. package/dist/harness-runtime/lib/model-observations.mjs +113 -0
  94. package/dist/harness-runtime/lib/native-children.d.mts +34 -0
  95. package/dist/harness-runtime/lib/native-children.mjs +265 -0
  96. package/dist/harness-runtime/lib/native-hook-command.d.mts +7 -0
  97. package/dist/harness-runtime/lib/native-hook-command.mjs +251 -0
  98. package/dist/harness-runtime/lib/native-request-proof.d.mts +2 -0
  99. package/dist/harness-runtime/lib/native-request-proof.mjs +65 -0
  100. package/dist/harness-runtime/lib/native-streams.mjs +45 -0
  101. package/dist/harness-runtime/lib/observation-clock.d.mts +18 -0
  102. package/dist/harness-runtime/lib/observation-clock.mjs +95 -0
  103. package/dist/harness-runtime/lib/opencode-plugin.mjs +85 -0
  104. package/dist/harness-runtime/lib/operation-context.mjs +5 -0
  105. package/dist/harness-runtime/lib/operation-errors.d.mts +16 -0
  106. package/dist/harness-runtime/lib/operation-errors.mjs +127 -0
  107. package/dist/harness-runtime/lib/operation-progress.d.mts +7 -0
  108. package/dist/harness-runtime/lib/operation-progress.mjs +114 -0
  109. package/dist/harness-runtime/lib/process-json.d.mts +5 -0
  110. package/dist/harness-runtime/lib/process-json.mjs +201 -0
  111. package/dist/harness-runtime/lib/process-snapshot.mjs +10 -0
  112. package/dist/harness-runtime/lib/provider-overload-policy.d.mts +23 -0
  113. package/dist/harness-runtime/lib/provider-overload-policy.mjs +242 -0
  114. package/dist/harness-runtime/lib/runner-events.mjs +202 -0
  115. package/dist/harness-runtime/lib/runner-lock.mjs +42 -0
  116. package/dist/harness-runtime/lib/session-settlement.mjs +30 -0
  117. package/dist/harness-runtime/lib/setup-progress.d.mts +9 -0
  118. package/dist/harness-runtime/lib/setup-progress.mjs +53 -0
  119. package/dist/harness-runtime/lib/tool-observations.d.mts +21 -0
  120. package/dist/harness-runtime/lib/tool-observations.mjs +228 -0
  121. package/dist/harness-runtime/lib/zcode-usage.mjs +55 -0
  122. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-1y4a697q.js +81 -0
  123. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-2ny4vxct.js +92 -0
  124. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-6mptkzbc.js +0 -0
  125. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-7x67cf1q.js +52 -0
  126. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-9rqn6x4v.js +16 -0
  127. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-9vdz0zwn.js +1661 -0
  128. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ecs5ftka.js +1466 -0
  129. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-frbwqjmf.js +145 -0
  130. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-gne7h7mq.js +50 -0
  131. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-gx9wnkzr.js +105 -0
  132. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-m2eywc8w.js +0 -0
  133. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-nwcpakzx.js +12 -0
  134. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-q4g3afnw.js +623 -0
  135. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-qav6mfdz.js +0 -0
  136. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-sxt88q69.js +12 -0
  137. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-t1xa31vm.js +39 -0
  138. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ve4aws36.js +103 -0
  139. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vmytr4q8.js +4 -0
  140. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vt9jbch8.js +36 -0
  141. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vyas3vgt.js +0 -0
  142. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-wjyeq25y.js +30 -0
  143. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-xgnj2w4v.js +20 -0
  144. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-y2etg5bs.js +9 -0
  145. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ydhz1zrh.js +238 -0
  146. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-z6qdwcsf.js +62 -0
  147. package/dist/harness-runtime/vendor/opencode-client/contract.d.ts +1 -0
  148. package/dist/harness-runtime/vendor/opencode-client/contract.js +12 -0
  149. package/dist/harness-runtime/vendor/opencode-client/effect/api/api.d.ts +2806 -0
  150. package/dist/harness-runtime/vendor/opencode-client/effect/api/api.js +0 -0
  151. package/dist/harness-runtime/vendor/opencode-client/effect/api.d.ts +4 -0
  152. package/dist/harness-runtime/vendor/opencode-client/effect/api.js +0 -0
  153. package/dist/harness-runtime/vendor/opencode-client/effect/client.d.ts +2530 -0
  154. package/dist/harness-runtime/vendor/opencode-client/effect/client.js +15 -0
  155. package/dist/harness-runtime/vendor/opencode-client/effect/generated/client-error.d.ts +7 -0
  156. package/dist/harness-runtime/vendor/opencode-client/effect/generated/client-error.js +6 -0
  157. package/dist/harness-runtime/vendor/opencode-client/effect/generated/client.d.ts +2529 -0
  158. package/dist/harness-runtime/vendor/opencode-client/effect/generated/client.js +8 -0
  159. package/dist/harness-runtime/vendor/opencode-client/effect/generated/index.d.ts +2 -0
  160. package/dist/harness-runtime/vendor/opencode-client/effect/generated/index.js +12 -0
  161. package/dist/harness-runtime/vendor/opencode-client/effect/index.d.ts +35 -0
  162. package/dist/harness-runtime/vendor/opencode-client/effect/index.js +75 -0
  163. package/dist/harness-runtime/vendor/opencode-client/effect/rpc.d.ts +20 -0
  164. package/dist/harness-runtime/vendor/opencode-client/effect/rpc.js +11 -0
  165. package/dist/harness-runtime/vendor/opencode-client/effect/service.d.ts +90 -0
  166. package/dist/harness-runtime/vendor/opencode-client/effect/service.js +187 -0
  167. package/dist/harness-runtime/vendor/opencode-client/package.json +84 -0
  168. package/dist/harness-runtime/vendor/opencode-client/promise/api.d.ts +21 -0
  169. package/dist/harness-runtime/vendor/opencode-client/promise/api.js +0 -0
  170. package/dist/harness-runtime/vendor/opencode-client/promise/client.d.ts +252 -0
  171. package/dist/harness-runtime/vendor/opencode-client/promise/client.js +14 -0
  172. package/dist/harness-runtime/vendor/opencode-client/promise/generated/client-error.d.ts +8 -0
  173. package/dist/harness-runtime/vendor/opencode-client/promise/generated/client-error.js +6 -0
  174. package/dist/harness-runtime/vendor/opencode-client/promise/generated/client.d.ts +259 -0
  175. package/dist/harness-runtime/vendor/opencode-client/promise/generated/client.js +7 -0
  176. package/dist/harness-runtime/vendor/opencode-client/promise/generated/index.d.ts +3 -0
  177. package/dist/harness-runtime/vendor/opencode-client/promise/generated/index.js +79 -0
  178. package/dist/harness-runtime/vendor/opencode-client/promise/generated/types.d.ts +8932 -0
  179. package/dist/harness-runtime/vendor/opencode-client/promise/generated/types.js +70 -0
  180. package/dist/harness-runtime/vendor/opencode-client/promise/index.d.ts +6 -0
  181. package/dist/harness-runtime/vendor/opencode-client/promise/index.js +83 -0
  182. package/dist/harness-runtime/vendor/opencode-client/promise/rpc.d.ts +32 -0
  183. package/dist/harness-runtime/vendor/opencode-client/promise/rpc.js +7 -0
  184. package/dist/harness-runtime/vendor/opencode-client/promise/service.d.ts +24 -0
  185. package/dist/harness-runtime/vendor/opencode-client/promise/service.js +187 -0
  186. package/dist/harness-runtime/vendor/opencode-client/pty-handoff.d.ts +12 -0
  187. package/dist/harness-runtime/vendor/opencode-client/pty-handoff.js +14 -0
  188. package/dist/harness-runtime/vendor/opencode-client/rpc-runtime.d.ts +20 -0
  189. package/dist/harness-runtime/vendor/opencode-client/rpc-runtime.js +14 -0
  190. package/dist/harness-runtime/vendor/opencode-client/service-contender.d.ts +37 -0
  191. package/dist/harness-runtime/vendor/opencode-client/service-contender.js +10 -0
  192. package/dist/harness-runtime/vendor/opencode-client/service-probe.d.ts +70 -0
  193. package/dist/harness-runtime/vendor/opencode-client/service-probe.js +16 -0
  194. package/dist/harness-runtime/vendor/opencode-client/service-timing.d.ts +12 -0
  195. package/dist/harness-runtime/vendor/opencode-client/service-timing.js +10 -0
  196. package/dist/harness-runtime/vendor/opencode-client/service-version.d.ts +2 -0
  197. package/dist/harness-runtime/vendor/opencode-client/service-version.js +6 -0
  198. package/dist/harness-runtime/vendor/opencode-client/service.d.ts +52 -0
  199. package/dist/harness-runtime/vendor/opencode-client/service.js +1 -0
  200. package/dist/harness-runtime/vendor/opencode-client/shared-events.d.ts +11 -0
  201. package/dist/harness-runtime/vendor/opencode-client/shared-events.js +8 -0
  202. package/dist/harness-runtime/vendor/opencode-client/solid/connection.d.ts +37 -0
  203. package/dist/harness-runtime/vendor/opencode-client/solid/connection.js +10 -0
  204. package/dist/harness-runtime/vendor/opencode-client/solid/data.d.ts +211 -0
  205. package/dist/harness-runtime/vendor/opencode-client/solid/data.js +18 -0
  206. package/dist/harness-runtime/vendor/opencode-client/solid/index.d.ts +3 -0
  207. package/dist/harness-runtime/vendor/opencode-client/solid/index.js +32 -0
  208. package/dist/harness-runtime/vendor/opencode-client/solid/pty.d.ts +22 -0
  209. package/dist/harness-runtime/vendor/opencode-client/solid/pty.js +8 -0
  210. package/dist/harness-runtime/vendor/opencode-plugin.mjs +3 -0
  211. package/dist/protocol/local-files.js +18 -9
  212. package/dist/runtime/context.js +8 -4
  213. package/dist/schemas/agent-profile.schema.json +1 -1
  214. package/dist/schemas/code-review-decision.schema.json +9 -0
  215. package/dist/schemas/code-review-result.schema.json +1 -1
  216. package/dist/schemas/code-verification.schema.json +7 -5
  217. package/dist/schemas/code-work-batch.schema.json +2 -2
  218. package/dist/schemas/diagnostic-stage-result.schema.json +354 -0
  219. package/dist/schemas/diagnostic-work-result.schema.json +296 -0
  220. package/dist/schemas/discovery-result.schema.json +184 -0
  221. package/dist/schemas/execution-bundle.schema.json +211 -0
  222. package/dist/schemas/execution-settings.schema.json +162 -0
  223. package/dist/schemas/flow-descriptor.schema.json +54 -0
  224. package/dist/schemas/flow-run.schema.json +29 -0
  225. package/dist/schemas/harness-catalog.schema.json +80 -0
  226. package/dist/schemas/mb-init-compose-result.schema.json +457 -0
  227. package/dist/schemas/mb-init-discover-result.schema.json +500 -0
  228. package/dist/schemas/mb-init-finish-result.schema.json +422 -0
  229. package/dist/schemas/mb-init-preflight-result.schema.json +367 -0
  230. package/dist/schemas/mb-init-review-result.schema.json +396 -0
  231. package/dist/schemas/mb-init-verify-result.schema.json +391 -0
  232. package/dist/schemas/mb-upgrade-stage-result.schema.json +863 -0
  233. package/dist/schemas/memory-bootstrap-receipt-v2.schema.json +67 -0
  234. package/dist/schemas/memory-bootstrap-receipt.schema.json +354 -0
  235. package/dist/schemas/memory-composition-work-result.schema.json +38 -0
  236. package/dist/schemas/memory-repair-work-result.schema.json +56 -0
  237. package/dist/schemas/memory-review-correction-plan.schema.json +30 -0
  238. package/dist/schemas/memory-review-work-result.schema.json +43 -0
  239. package/dist/schemas/model-catalog.schema.json +153 -0
  240. package/dist/schemas/plan-review-decision.schema.json +12 -3
  241. package/dist/schemas/plan-review-result.schema.json +2 -2
  242. package/dist/schemas/profile-settings.schema.json +122 -0
  243. package/dist/schemas/project-flow-pack-manifest.schema.json +1 -1
  244. package/dist/schemas/release-impact.schema.json +3 -3
  245. package/dist/schemas/release-manifest.schema.json +291 -0
  246. package/dist/schemas/run-control-receipt.schema.json +118 -0
  247. package/dist/schemas/run-control-request.schema.json +36 -0
  248. package/dist/schemas/run-engine-binding.schema.json +1 -1
  249. package/dist/schemas/stage-start-response.schema.json +4 -0
  250. package/dist/schemas/vnext-protocol-plan.schema.json +2 -2
  251. package/dist/services/accepted-intent.js +139 -0
  252. package/dist/services/branch-context.js +3 -3
  253. package/dist/services/canon.js +54 -15
  254. package/dist/services/check-environment.js +21 -0
  255. package/dist/services/check-runner-evidence.js +277 -0
  256. package/dist/services/check-runner-proof.js +305 -0
  257. package/dist/services/cleanup.js +133 -40
  258. package/dist/services/cli-operation-classifier.js +26 -63
  259. package/dist/services/cli-upgrade-allowlist.js +27 -0
  260. package/dist/services/code-checks.js +1401 -119
  261. package/dist/services/codex-hook-delivery.js +32 -0
  262. package/dist/services/command-context.js +80 -0
  263. package/dist/services/compatibility-preflight.js +16 -3
  264. package/dist/services/config.js +11 -8
  265. package/dist/services/continuation-outcome.js +20 -0
  266. package/dist/services/controller-child-capacity.js +71 -0
  267. package/dist/services/controller-child-observer.js +121 -0
  268. package/dist/services/controller-execution.js +94 -0
  269. package/dist/services/controller-fanout.js +226 -0
  270. package/dist/services/controller-memory-task.js +45 -0
  271. package/dist/services/controller-memory-worker.js +49 -0
  272. package/dist/services/dashboard.js +143 -58
  273. package/dist/services/descriptor-coordinator-session.js +35 -0
  274. package/dist/services/descriptor-root-work.js +53 -0
  275. package/dist/services/descriptor-run-settlement.js +107 -0
  276. package/dist/services/descriptor-run-start.js +475 -0
  277. package/dist/services/descriptor-stage-coordinator.js +250 -0
  278. package/dist/services/descriptor-stage-settlement.js +223 -0
  279. package/dist/services/descriptor-stage-start.js +175 -0
  280. package/dist/services/diagnostic-evidence.js +73 -0
  281. package/dist/services/diagnostic-report-publication.js +72 -0
  282. package/dist/services/diagnostic-report.js +67 -0
  283. package/dist/services/diagnostic-run-bundle.js +74 -0
  284. package/dist/services/diagnostic-snapshot.js +159 -0
  285. package/dist/services/diagnostic-stage-handlers.js +108 -0
  286. package/dist/services/diagnostic-stage-results.js +132 -0
  287. package/dist/services/diagnostic-start.js +84 -0
  288. package/dist/services/diagnostic-work-batch.js +34 -0
  289. package/dist/services/diagnostic-work-collection.js +97 -0
  290. package/dist/services/diagnostic-work-registration.js +101 -0
  291. package/dist/services/diagnostic-work-results.js +93 -0
  292. package/dist/services/discovery/agy-discovery.js +2 -0
  293. package/dist/services/discovery/codex-discovery.js +2 -0
  294. package/dist/services/discovery/droid-discovery.js +2 -0
  295. package/dist/services/discovery/grok-discovery.js +2 -0
  296. package/dist/services/discovery/opencode-discovery.js +2 -0
  297. package/dist/services/discovery/static.js +31 -0
  298. package/dist/services/discovery/zcode-discovery.js +2 -0
  299. package/dist/services/engines.js +354 -163
  300. package/dist/services/eval-snapshots.js +1316 -150
  301. package/dist/services/execution-bundles.js +275 -0
  302. package/dist/services/execution-policy.js +185 -0
  303. package/dist/services/executor-onboarding.js +56 -0
  304. package/dist/services/external-work-launch.js +310 -0
  305. package/dist/services/flow-catalog.js +30 -0
  306. package/dist/services/flow-descriptors.js +82 -0
  307. package/dist/services/frozen-engine.js +387 -0
  308. package/dist/services/generic-execution.js +164 -0
  309. package/dist/services/harness-adapter.js +355 -36
  310. package/dist/services/harness-config.js +2 -0
  311. package/dist/services/harness-discovery.js +29 -0
  312. package/dist/services/harness-doctor.js +75 -0
  313. package/dist/services/harness-probe-effects.js +51 -0
  314. package/dist/services/harness-version-probe.js +100 -0
  315. package/dist/services/hook-memory-routing.js +188 -0
  316. package/dist/services/hook-recovery-guard.js +14 -0
  317. package/dist/services/hook-session-observation.js +24 -0
  318. package/dist/services/hooks.js +791 -326
  319. package/dist/services/hybrid-contracts.js +474 -0
  320. package/dist/services/hybrid-digest.js +14 -0
  321. package/dist/services/ids.js +29 -1
  322. package/dist/services/installation-lock.js +95 -0
  323. package/dist/services/installed-flow-source.js +226 -0
  324. package/dist/services/lanes.js +78 -65
  325. package/dist/services/lifecycle-command.js +1 -288
  326. package/dist/services/lifecycle-contract.js +1 -0
  327. package/dist/services/lifecycle-invocations.js +2148 -0
  328. package/dist/services/managed-daemon-binding.js +38 -0
  329. package/dist/services/managed-lease-monitor.js +133 -0
  330. package/dist/services/managed-processes.js +449 -69
  331. package/dist/services/memory-accepted-composition.js +78 -0
  332. package/dist/services/memory-baseline-preservation.js +65 -0
  333. package/dist/services/memory-completed-correction-wave.js +328 -0
  334. package/dist/services/memory-compose-handler.js +400 -0
  335. package/dist/services/memory-composition-acceptance.js +375 -0
  336. package/dist/services/memory-composition-graph.js +165 -0
  337. package/dist/services/memory-composition-registration.js +161 -0
  338. package/dist/services/memory-composition-work-custody.js +156 -0
  339. package/dist/services/memory-composition-work-results.js +108 -0
  340. package/dist/services/memory-corrected-review-acceptance.js +129 -0
  341. package/dist/services/memory-corrected-review-batch.js +37 -0
  342. package/dist/services/memory-corrected-review-findings.js +108 -0
  343. package/dist/services/memory-corrected-review-inputs.js +215 -0
  344. package/dist/services/memory-corrected-review-projection.js +78 -0
  345. package/dist/services/memory-corrected-review-receipt.js +178 -0
  346. package/dist/services/memory-corrected-review-registration.js +192 -0
  347. package/dist/services/memory-correction-check-receipt.js +235 -0
  348. package/dist/services/memory-correction-input-owner.js +6 -0
  349. package/dist/services/memory-custody-snapshot.js +36 -0
  350. package/dist/services/memory-discover-handler.js +104 -0
  351. package/dist/services/memory-finish-evidence.js +457 -0
  352. package/dist/services/memory-finish-handler.js +229 -0
  353. package/dist/services/memory-finish-integration.js +450 -0
  354. package/dist/services/memory-finish-readiness.js +169 -0
  355. package/dist/services/memory-git-integration.js +220 -0
  356. package/dist/services/memory-historical-corrected-review.js +212 -0
  357. package/dist/services/memory-historical-original-review.js +143 -0
  358. package/dist/services/memory-historical-repair-registration.js +105 -0
  359. package/dist/services/memory-historical-second-review.js +147 -0
  360. package/dist/services/memory-init-bootstrap-readiness.js +210 -0
  361. package/dist/services/memory-init-contexts.js +131 -0
  362. package/dist/services/memory-init-controller-start.js +103 -0
  363. package/dist/services/memory-init-engine-release.js +223 -0
  364. package/dist/services/memory-init-git-policy.js +165 -0
  365. package/dist/services/memory-init-lint-capability.js +203 -0
  366. package/dist/services/memory-init-native-capacity.js +309 -0
  367. package/dist/services/memory-init-preflight.js +334 -0
  368. package/dist/services/memory-init-project-bootstrap.js +599 -0
  369. package/dist/services/memory-init-request-candidate.js +213 -0
  370. package/dist/services/memory-init-request-input.js +82 -0
  371. package/dist/services/memory-init-resource-storage.js +125 -0
  372. package/dist/services/memory-init-run-allocation.js +81 -0
  373. package/dist/services/memory-init-run-bundle.js +135 -0
  374. package/dist/services/memory-init-start.js +181 -0
  375. package/dist/services/memory-init-startup-custody.js +80 -0
  376. package/dist/services/memory-init-startup-input.js +240 -0
  377. package/dist/services/memory-init-storage.js +91 -0
  378. package/dist/services/memory-init-stores.js +359 -0
  379. package/dist/services/memory-init-worktrunk-capability.js +174 -0
  380. package/dist/services/memory-layer-publication.js +611 -0
  381. package/dist/services/memory-layer.js +221 -0
  382. package/dist/services/memory-lint-config.js +194 -0
  383. package/dist/services/memory-native-review-copy.js +103 -0
  384. package/dist/services/memory-normal-flow-entry.js +87 -0
  385. package/dist/services/memory-ordinary-defaults.js +129 -0
  386. package/dist/services/memory-preflight-bootstrap-custody.js +115 -0
  387. package/dist/services/memory-preflight-handler.js +274 -0
  388. package/dist/services/memory-repair-current-view.js +299 -0
  389. package/dist/services/memory-repair-document-delta.js +488 -0
  390. package/dist/services/memory-repair-history.js +496 -0
  391. package/dist/services/memory-repair-physical-binding.js +194 -0
  392. package/dist/services/memory-repair-publication-files.js +152 -0
  393. package/dist/services/memory-repair-publication-intent.js +27 -0
  394. package/dist/services/memory-repair-publication-plan.js +162 -0
  395. package/dist/services/memory-repair-publication-source.js +225 -0
  396. package/dist/services/memory-repair-publication.js +795 -0
  397. package/dist/services/memory-repair-registration.js +240 -0
  398. package/dist/services/memory-repair-work-baseline.js +415 -0
  399. package/dist/services/memory-repair-work-completion.js +117 -0
  400. package/dist/services/memory-repair-work-custody.js +73 -0
  401. package/dist/services/memory-repair-work-results.js +108 -0
  402. package/dist/services/memory-repair-workspace.js +261 -0
  403. package/dist/services/memory-repair-writer-copy.js +736 -0
  404. package/dist/services/memory-result-binding.js +239 -0
  405. package/dist/services/memory-retained-review-authority.js +138 -0
  406. package/dist/services/memory-retained-review-results.js +70 -0
  407. package/dist/services/memory-retained-second-baseline.js +102 -0
  408. package/dist/services/memory-retained-second-repairs.js +162 -0
  409. package/dist/services/memory-review-acceptance.js +214 -0
  410. package/dist/services/memory-review-correction-plan.js +206 -0
  411. package/dist/services/memory-review-handler.js +585 -0
  412. package/dist/services/memory-review-inputs.js +246 -0
  413. package/dist/services/memory-review-limits.js +10 -0
  414. package/dist/services/memory-review-receipt.js +180 -0
  415. package/dist/services/memory-review-registration.js +117 -0
  416. package/dist/services/memory-review-work-custody.js +126 -0
  417. package/dist/services/memory-review-work-input.js +91 -0
  418. package/dist/services/memory-review-work-results.js +95 -0
  419. package/dist/services/memory-second-corrected-review-inputs.js +167 -0
  420. package/dist/services/memory-second-correction-plan.js +77 -0
  421. package/dist/services/memory-second-repair-baseline.js +91 -0
  422. package/dist/services/memory-second-repair-launch.js +614 -0
  423. package/dist/services/memory-second-repair-registration.js +88 -0
  424. package/dist/services/memory-second-repair-source.js +187 -0
  425. package/dist/services/memory-second-review-acceptance.js +115 -0
  426. package/dist/services/memory-source-observations.js +117 -0
  427. package/dist/services/memory-stage-dispatch.js +65 -0
  428. package/dist/services/memory-stage-results.js +290 -0
  429. package/dist/services/memory-tool-probe.js +25 -0
  430. package/dist/services/memory-upgrade-analysis-handler.js +243 -0
  431. package/dist/services/memory-upgrade-bootstrap-readiness.js +251 -0
  432. package/dist/services/memory-upgrade-evidence.js +100 -0
  433. package/dist/services/memory-upgrade-inventory.js +442 -0
  434. package/dist/services/memory-upgrade-publication-custody.js +73 -0
  435. package/dist/services/memory-upgrade-publication.js +413 -0
  436. package/dist/services/memory-upgrade-selection.js +430 -0
  437. package/dist/services/memory-upgrade-start.js +60 -0
  438. package/dist/services/memory-upgrade-storage-readiness.js +340 -0
  439. package/dist/services/memory-verification-acceptance.js +156 -0
  440. package/dist/services/memory-verification-checks.js +317 -0
  441. package/dist/services/memory-verification-receipt.js +160 -0
  442. package/dist/services/memory-verify-handler.js +137 -0
  443. package/dist/services/memory-work-result-json.js +12 -0
  444. package/dist/services/memory-work-result-preparation.js +64 -0
  445. package/dist/services/merge-queue.js +228 -149
  446. package/dist/services/merge-server.js +163 -35
  447. package/dist/services/migrations.js +164 -114
  448. package/dist/services/model-catalog.js +109 -0
  449. package/dist/services/model-discovery.js +46 -0
  450. package/dist/services/native-daemon-history.js +50 -0
  451. package/dist/services/native-session-control.js +39 -0
  452. package/dist/services/operator-profiles.js +236 -0
  453. package/dist/services/physical-startup.js +45 -0
  454. package/dist/services/plan-runtime.js +26 -11
  455. package/dist/services/plans.js +28 -25
  456. package/dist/services/project-registry.js +97 -0
  457. package/dist/services/project-summary.js +1 -1
  458. package/dist/services/projects.js +17 -87
  459. package/dist/services/prompt-delivery.js +71 -0
  460. package/dist/services/prompts.js +28 -17
  461. package/dist/services/protocols.js +163 -87
  462. package/dist/services/provider-capacity.js +463 -0
  463. package/dist/services/recovery-observation-budget.js +121 -0
  464. package/dist/services/recovery-snapshot-database.js +588 -0
  465. package/dist/services/release-archive-producer.js +52 -0
  466. package/dist/services/release-archive.js +143 -0
  467. package/dist/services/release-cache.js +150 -0
  468. package/dist/services/release-compatibility.js +18 -0
  469. package/dist/services/release-download.js +67 -0
  470. package/dist/services/release-envelope.js +104 -0
  471. package/dist/services/release-manifest.js +45 -0
  472. package/dist/services/release-payload.js +114 -0
  473. package/dist/services/release-producer.js +63 -0
  474. package/dist/services/release-provenance.js +160 -0
  475. package/dist/services/release-registry.js +66 -0
  476. package/dist/services/release-source.js +194 -0
  477. package/dist/services/repair-intents.js +31 -0
  478. package/dist/services/repair-receipt.js +13 -0
  479. package/dist/services/review-copy.js +165 -0
  480. package/dist/services/review-deferral.js +23 -0
  481. package/dist/services/run-control-receipt.js +56 -0
  482. package/dist/services/run-control-worker.js +428 -0
  483. package/dist/services/run-control.js +1092 -0
  484. package/dist/services/run-controller-adapter.js +400 -0
  485. package/dist/services/run-controller-capture.js +186 -0
  486. package/dist/services/run-controller-process.js +205 -0
  487. package/dist/services/run-controller-recovery.js +295 -0
  488. package/dist/services/run-controller-state.js +97 -0
  489. package/dist/services/run-controller.js +1395 -0
  490. package/dist/services/run-engine-bindings.js +1 -1
  491. package/dist/services/run-fork.js +147 -0
  492. package/dist/services/run-health.js +149 -0
  493. package/dist/services/run-observations.js +152 -0
  494. package/dist/services/run-projection.js +22 -64
  495. package/dist/services/run-recovery-guard.js +107 -0
  496. package/dist/services/run-recovery-runtime.js +112 -0
  497. package/dist/services/run-recovery.js +456 -0
  498. package/dist/services/run-start-custody.js +189 -0
  499. package/dist/services/runs.js +1023 -246
  500. package/dist/services/runtime-budget.js +446 -0
  501. package/dist/services/runtime-command.js +43 -0
  502. package/dist/services/runtime-scope-capture.js +52 -0
  503. package/dist/services/runtime-scope-control.js +468 -0
  504. package/dist/services/runtime-scope-resume.js +605 -0
  505. package/dist/services/runtime-scope-stop.js +211 -0
  506. package/dist/services/runtime-scope-worker.js +314 -0
  507. package/dist/services/runtime-service.js +19 -14
  508. package/dist/services/schema-validation.js +10 -8
  509. package/dist/services/scope-writer-cleanup.js +27 -0
  510. package/dist/services/sessions.js +36 -91
  511. package/dist/services/setup.js +427 -0
  512. package/dist/services/stage-blocker.js +17 -7
  513. package/dist/services/stage-context.js +240 -18
  514. package/dist/services/stage-handlers.js +89 -0
  515. package/dist/services/stage-lifecycle.js +152 -116
  516. package/dist/services/stage-pause.js +202 -91
  517. package/dist/services/stage-report-renderer.js +234 -6
  518. package/dist/services/stage-work-graph.js +52 -0
  519. package/dist/services/tooling.js +41 -30
  520. package/dist/services/usage.js +49 -101
  521. package/dist/services/verification-matrix.js +393 -0
  522. package/dist/services/version-status.js +1 -1
  523. package/dist/services/vnext-code-review.js +235 -88
  524. package/dist/services/vnext-code.js +327 -226
  525. package/dist/services/vnext-contracts.js +10 -1
  526. package/dist/services/vnext-execution-profile.js +80 -5
  527. package/dist/services/vnext-fanout.js +355 -21
  528. package/dist/services/vnext-merge.js +332 -111
  529. package/dist/services/vnext-plan-review.js +287 -132
  530. package/dist/services/vnext-plan.js +228 -104
  531. package/dist/services/vnext-protocolize.js +533 -95
  532. package/dist/services/vnext-specify.js +123 -81
  533. package/dist/services/vnext-workspace-policy.js +7 -2
  534. package/dist/services/work-registry.js +1706 -200
  535. package/dist/services/workspace-bootstrap.js +53 -15
  536. package/dist/services/workspace-files.js +85 -0
  537. package/dist/services/worktrees.js +105 -48
  538. package/dist/shared/entity-references.js +1 -0
  539. package/dist/shared/errors.js +27 -0
  540. package/dist/storage/agent-output.js +26 -0
  541. package/dist/storage/data-preservation.js +65 -0
  542. package/dist/storage/database.js +930 -40
  543. package/dist/storage/foreign-key-transaction.js +39 -0
  544. package/dist/storage/immutable-input.js +45 -0
  545. package/dist/storage/import-guard.js +159 -0
  546. package/dist/storage/memory-upgrade-schema-bootstrap.js +38 -0
  547. package/dist/storage/native-cleanup.js +52 -0
  548. package/dist/storage/paired-writer-migration.js +424 -0
  549. package/dist/storage/paths.js +17 -4
  550. package/dist/storage/projection-lock.js +73 -0
  551. package/dist/storage/work-references.js +23 -0
  552. package/dist/storage/writer-contract.js +96 -0
  553. package/dist/storage/writer-migration.js +346 -0
  554. package/dist/storage/writer-restore.js +217 -0
  555. package/dist/verification-components.json +299 -0
  556. package/package.json +51 -13
  557. package/tools/create-canon-release.mjs +33 -0
  558. package/tools/probe-codex-history.mjs +37 -0
  559. package/tools/produce-hybrid-release.mjs +41 -0
  560. package/tools/repair-paused-run-status.mjs +84 -0
@@ -0,0 +1,175 @@
1
+ import path from "node:path";
2
+ import { createHash } from "node:crypto";
3
+ import { Ajv2020 } from "ajv/dist/2020.js";
4
+ import { Ajv } from "ajv/dist/ajv.js";
5
+ import { AppError } from "../shared/errors.js";
6
+ import { requireProjectByRoot } from "./projects.js";
7
+ import { resolveProjectRoot } from "../storage/paths.js";
8
+ import { readFlowRunExecutionBundle, assertPreparedRunBundleSqlAuthority, prepareFlowRunStageAttachment, attachFlowRunStage } from "./runs.js";
9
+ import { acceptStagePacket, prepareStagePacket, publishStagePacket, readAcceptedStageData } from "./stage-report-renderer.js";
10
+ import { appendAudit } from "./audit.js";
11
+ import { prepareMemoryOrdinaryDefaults } from "./memory-ordinary-defaults.js";
12
+ import { readMemoryUpgradeEvidence, readIssuedMemoryUpgradeTarget } from "./memory-upgrade-evidence.js";
13
+ import { observeMemoryNormalFlowEntryDiagnostic } from "./memory-normal-flow-entry.js";
14
+ import { readDescriptorStageInput } from "./descriptor-stage-coordinator.js";
15
+ import { readMemoryFinishGitSelection } from "./memory-finish-integration.js";
16
+ import { readMemoryInitGitPolicy } from "./memory-init-git-policy.js";
17
+ const hash = (bytes) => createHash("sha256").update(bytes).digest("hex");
18
+ const event = "flow_run.descriptor_stage_start";
19
+ function refuse(reason) { throw new AppError("descriptor_stage_start_invalid", "Stage start lacks exact retained input custody", 1, { reason, effect: "no_effect" }); }
20
+ /** Atomic generic stage entry; Work/Session launch remains the handler's responsibility. */
21
+ export class PreparedDescriptorStageStart {
22
+ #scope;
23
+ #bundle;
24
+ #packet;
25
+ #receipt;
26
+ #attachment;
27
+ #predecessor;
28
+ constructor(scope, bundle, packet, receipt, attachment, predecessor) {
29
+ this.#scope = scope;
30
+ this.#bundle = bundle;
31
+ this.#packet = packet;
32
+ this.#receipt = receipt;
33
+ this.#attachment = attachment;
34
+ this.#predecessor = predecessor;
35
+ Object.freeze(this);
36
+ }
37
+ static prepare(context, input) {
38
+ if (!(input.bytes instanceof Uint8Array) || input.bytes.byteLength > 1024 * 1024)
39
+ refuse("unbounded_input");
40
+ const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
41
+ const bundle = readFlowRunExecutionBundle(context, input);
42
+ if (!bundle)
43
+ refuse("frozen_bundle_required");
44
+ return context.db.readSnapshot(() => {
45
+ const scope = { projectId: project.id, runId: input.runId, stage: input.stage };
46
+ const run = assertPreparedRunBundleSqlAuthority(context, scope, bundle), declared = bundle.descriptor.stage(input.stage);
47
+ if (!run.run_root)
48
+ refuse("run_root_missing");
49
+ const prior = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type=? AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.stage')=? LIMIT 2", [project.id, event, input.runId, input.stage]);
50
+ if (prior.length > 1)
51
+ refuse("ambiguous_start_receipt");
52
+ const retained = prior[0] ? JSON.parse(prior[0].payload_json) : null;
53
+ const attachment = retained ? null : prepareFlowRunStageAttachment(context, { projectRoot: input.projectRoot, runId: input.runId, stage: input.stage, dir: declared.directory, status: "running" });
54
+ const attempt = retained?.attempt ?? attachment.attempt;
55
+ if (typeof attempt !== "string" || !/^try-[0-9]{3,}$/.test(attempt))
56
+ refuse("invalid_attempt");
57
+ const index = JSON.parse(run.index_json), target = index.stage_runs?.find((stage) => stage.stage === input.stage);
58
+ const previous = index.stage_runs?.filter((stage) => stage.status === "done" && (!target || stage.order < target.order))
59
+ .sort((a, b) => b.order - a.order)[0];
60
+ const accepted = previous ? readAcceptedStageData(context, { ...scope, stage: previous.stage }) : null;
61
+ if (accepted) {
62
+ const schema = JSON.parse(bundle.descriptor.stageBlob(previous.stage, "result_schema").toString("utf8"));
63
+ const ajv = schema.$schema?.includes("2020-12") ? new Ajv2020({ strict: false, validateFormats: false }) : new Ajv({ strict: false, validateFormats: false });
64
+ if (schema.$id && accepted.data_schema_id !== schema.$id || !ajv.compile(schema)(JSON.parse(accepted.bytes.toString("utf8"))))
65
+ refuse("invalid_predecessor_result");
66
+ }
67
+ const predecessor = accepted ? { stage: previous.stage, authority: accepted.authority_sha256 } : null;
68
+ const frozen = bundle.execution.snapshot().scope;
69
+ const upgradeEvidence = bundle.descriptor.descriptor.id === "mb-upgrade" ? readMemoryUpgradeEvidence(context, { requestId: frozen.requestId, projectRoot: frozen.projectRoot, workspaceRoot: frozen.workspace }) : null;
70
+ const upgradeTarget = upgradeEvidence ? readIssuedMemoryUpgradeTarget(context, { requestId: frozen.requestId, projectRoot: frozen.projectRoot, workspaceRoot: frozen.workspace }, project.id) : null;
71
+ let ordinaryDefaults;
72
+ if (bundle.descriptor.descriptor.id === "mb-init" && input.stage === "compose"
73
+ || bundle.descriptor.descriptor.id === "mb-upgrade" && ["diff-analysis", "upgrade"].includes(input.stage)) {
74
+ if (retained) {
75
+ const rows = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type='flow_run.stage_packet' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.file')=? LIMIT 2", [project.id, input.runId, retained.file]);
76
+ if (rows.length !== 1)
77
+ refuse("input_packet_custody_missing");
78
+ ordinaryDefaults = JSON.parse(Buffer.from(JSON.parse(rows[0].payload_json).base64, "base64").toString("utf8")).ordinary_defaults;
79
+ }
80
+ else if (bundle.descriptor.descriptor.id === "mb-upgrade" && input.stage === "upgrade") {
81
+ // Accepted analysis owns the missing paths before authoring is allocated.
82
+ ordinaryDefaults = readDescriptorStageInput(context, { ...scope, stage: "diff-analysis" }, bundle, true, true).input.ordinary_defaults;
83
+ }
84
+ else {
85
+ const selected = bundle.execution.snapshot().scope;
86
+ const requestScope = { requestId: selected.requestId, projectRoot: selected.projectRoot, workspaceRoot: selected.workspace };
87
+ const hasInitPolicy = bundle.descriptor.descriptor.id === "mb-init" && readMemoryInitGitPolicy(context, requestScope) !== null;
88
+ const gitSelection = bundle.descriptor.descriptor.id === "mb-upgrade" || hasInitPolicy ? readMemoryFinishGitSelection(context, scope, bundle) : null;
89
+ const targetContract = upgradeTarget?.index.entries.find(entry => entry.path === ".memory-bank/dd-flow/flow-contract.json");
90
+ const contractBytes = targetContract ? upgradeTarget.artifacts.find(file => file.path === targetContract.retained.path) : undefined;
91
+ if (bundle.descriptor.descriptor.id === "mb-upgrade" && !contractBytes)
92
+ refuse("issued_target_contract_missing");
93
+ const targetTemplate = contractBytes ? JSON.parse(Buffer.from(contractBytes.base64, "base64").toString("utf8")).workspace?.feature_branch_template : undefined;
94
+ ordinaryDefaults = prepareMemoryOrdinaryDefaults(context, selected.workspace, bundle.execution.settings.root_profile, requestScope, gitSelection?.selection.integrationBranch, targetTemplate);
95
+ }
96
+ }
97
+ let ordinaryEntry;
98
+ if (upgradeEvidence && input.stage === "verify") {
99
+ if (retained) {
100
+ const rows = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type='flow_run.stage_packet' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.file')=? LIMIT 2", [project.id, input.runId, retained.file]);
101
+ if (rows.length !== 1)
102
+ refuse("input_packet_custody_missing");
103
+ ordinaryEntry = JSON.parse(Buffer.from(JSON.parse(rows[0].payload_json).base64, "base64").toString("utf8")).ordinary_entry;
104
+ }
105
+ else
106
+ ordinaryEntry = observeMemoryNormalFlowEntryDiagnostic(context, frozen.workspace);
107
+ }
108
+ const bytes = Buffer.from(JSON.stringify({ schema_id: "dd-flow/descriptor-stage-input@1", run_id: input.runId, stage: input.stage, attempt,
109
+ bundle_sha256: bundle.sha256, descriptor_sha256: bundle.descriptor.sha256, execution_sha256: bundle.execution.sha256,
110
+ predecessor: accepted ? { stage: previous.stage, result: accepted.result, input_sha256: accepted.input_sha256 ?? null, authority_sha256: accepted.authority_sha256 } : null,
111
+ payload_base64: Buffer.from(input.bytes).toString("base64"), ...(ordinaryDefaults !== undefined ? { ordinary_defaults: ordinaryDefaults } : {}),
112
+ ...(upgradeEvidence ? { upgrade_baseline: upgradeEvidence.reference } : {}),
113
+ ...(ordinaryEntry !== undefined ? { ordinary_entry: ordinaryEntry } : {}),
114
+ ...(upgradeTarget ? { upgrade_target: upgradeTarget.reference } : {}) }));
115
+ if (bytes.length > 1024 * 1024)
116
+ refuse("unbounded_input_packet");
117
+ const file = path.join(run.run_root, declared.directory, "stage-input.json");
118
+ const packet = prepareStagePacket(file, bytes);
119
+ const receipt = { schema_id: "dd-flow/descriptor-stage-start@1", run_id: input.runId, stage: input.stage, attempt, bundle_sha256: bundle.sha256, input_sha256: hash(bytes), file };
120
+ if (retained && JSON.stringify(retained) !== JSON.stringify(receipt))
121
+ refuse("changed_start_request");
122
+ const prepared = new PreparedDescriptorStageStart(scope, bundle, packet, receipt, attachment, predecessor);
123
+ if (retained)
124
+ prepared.assertCustody(context);
125
+ return prepared;
126
+ });
127
+ }
128
+ assertCustody(context) {
129
+ this.assertPredecessor(context);
130
+ const run = assertPreparedRunBundleSqlAuthority(context, this.#scope, this.#bundle), index = JSON.parse(run.index_json);
131
+ const stage = index.stage_runs?.find((value) => value.stage === this.#scope.stage);
132
+ if (stage?.attempt !== this.#receipt.attempt || stage.input_sha256 !== this.#receipt.input_sha256 || stage.dir !== this.#bundle.descriptor.stage(this.#scope.stage).directory)
133
+ refuse("stage_input_changed");
134
+ const packets = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type='flow_run.stage_packet' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.stage')=? AND json_extract(payload_json,'$.file')=? LIMIT 2", [this.#scope.projectId, this.#scope.runId, this.#scope.stage, this.#packet.file]);
135
+ if (packets.length !== 1)
136
+ refuse("input_packet_custody_missing");
137
+ const packet = JSON.parse(packets[0].payload_json);
138
+ if (packet.attempt !== this.#receipt.attempt || packet.sha256 !== this.#packet.sha256 || packet.base64 !== this.#packet.bytes.toString("base64"))
139
+ refuse("input_packet_custody_changed");
140
+ }
141
+ assertPredecessor(context) {
142
+ if (this.#predecessor && readAcceptedStageData(context, { ...this.#scope, stage: this.#predecessor.stage }).authority_sha256 !== this.#predecessor.authority)
143
+ refuse("predecessor_changed");
144
+ }
145
+ apply(context, assertHandlerCustody) {
146
+ const retainedRun = assertPreparedRunBundleSqlAuthority(context, this.#scope, this.#bundle);
147
+ const current = readFlowRunExecutionBundle(context, { projectRoot: retainedRun.project_root, runId: this.#scope.runId });
148
+ if (!current || current.sha256 !== this.#bundle.sha256)
149
+ refuse("physical_bundle_changed");
150
+ const changed = context.db.writeTransaction(() => {
151
+ assertPreparedRunBundleSqlAuthority(context, this.#scope, this.#bundle);
152
+ assertHandlerCustody?.(context);
153
+ this.assertPredecessor(context);
154
+ const prior = context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type=? AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.stage')=? LIMIT 2", [this.#scope.projectId, event, this.#scope.runId, this.#scope.stage]);
155
+ if (prior.length) {
156
+ if (prior.length !== 1 || prior[0].payload_json !== JSON.stringify(this.#receipt))
157
+ refuse("start_receipt_changed");
158
+ this.assertCustody(context);
159
+ return false;
160
+ }
161
+ if (!this.#attachment)
162
+ refuse("start_receipt_missing");
163
+ attachFlowRunStage(context, { projectRoot: this.#attachment.run.project_root, runId: this.#scope.runId, stage: this.#scope.stage,
164
+ dir: this.#bundle.descriptor.stage(this.#scope.stage).directory, status: "running", inputSha256: this.#packet.sha256 }, this.#attachment);
165
+ acceptStagePacket(context, { ...this.#scope, prepared: this.#packet });
166
+ appendAudit(context, { projectId: this.#scope.projectId, eventType: event, payload: this.#receipt });
167
+ this.assertCustody(context);
168
+ return true;
169
+ }, "descriptorStage.start");
170
+ publishStagePacket(context, this.#scope.projectId, this.#scope.runId, this.#scope.stage, this.#packet.file);
171
+ const run = assertPreparedRunBundleSqlAuthority(context, this.#scope, this.#bundle);
172
+ const status = JSON.parse(run.index_json).stage_runs.find((value) => value.stage === this.#scope.stage)?.status;
173
+ return { changed, stage: this.#scope.stage, status: status, run_status: run.status, attempt: this.#receipt.attempt, input_sha256: this.#packet.sha256, input_path: this.#packet.file };
174
+ }
175
+ }
@@ -0,0 +1,73 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { createHash } from "node:crypto";
4
+ import { AppError } from "../shared/errors.js";
5
+ const refuse = (reason, details = {}) => {
6
+ const correctable = ["invalid_source_reference", "source_hash_changed", "source_missing"].includes(reason);
7
+ const message = reason === "source_hash_changed"
8
+ ? "Citation sha256 does not match its assigned snapshot file; correct the result using source_path and actual_source_sha256, then retry. No result was accepted."
9
+ : reason === "source_missing" ? "Citation is absent from the original project snapshot. Cite an existing project source instead; generated .dd-flow/review-evidence observations are not project sources. For a missing link target cite the existing referring document and record the absence as a limitation. Correct the result, then retry; no result was accepted."
10
+ : correctable ? "Use snapshot-relative evidence paths with forward slashes (for example src/index.ts), without absolute paths, dot segments or .git; sha256 must be 64 lowercase hex characters"
11
+ : "Diagnostic evidence does not match the assigned snapshot and coverage";
12
+ throw new AppError("diagnostic_evidence_invalid", message, 2, { ...details, reason, effect: "no_effect", ...(correctable ? { phase: "prepare", recoverable: true } : {}) });
13
+ };
14
+ /** Run after schema validation; schema-shaped hashes are not evidence by themselves. */
15
+ export function verifyDiagnosticSources(root, sources) {
16
+ const canonicalRoot = fs.realpathSync(root), seen = new Map();
17
+ for (const source of sources) {
18
+ if (!source.path || source.path.length > 4096 || source.path.includes("\\") || path.posix.isAbsolute(source.path) || path.win32.isAbsolute(source.path)
19
+ || source.path.split("/").some(part => !part || part === "." || part === ".." || part === ".git")
20
+ || !/^[a-f0-9]{64}$/.test(source.sha256))
21
+ refuse("invalid_source_reference");
22
+ if (seen.has(source.path) && seen.get(source.path) !== source.sha256)
23
+ refuse("conflicting_source_reference");
24
+ seen.set(source.path, source.sha256);
25
+ let bytes;
26
+ try {
27
+ const file = fs.realpathSync(path.join(canonicalRoot, source.path)), relative = path.relative(canonicalRoot, file);
28
+ if (relative === ".." || relative.startsWith(".." + path.sep) || path.isAbsolute(relative))
29
+ refuse("escaping_source_reference");
30
+ const stat = fs.statSync(file);
31
+ if (!stat.isFile() || stat.size > 16 * 1024 * 1024)
32
+ refuse("unbounded_source_reference");
33
+ bytes = fs.readFileSync(file);
34
+ }
35
+ catch (error) {
36
+ if (error instanceof AppError)
37
+ throw error;
38
+ if (["ENOENT", "ENOTDIR"].includes(error.code ?? "")) {
39
+ return refuse("source_missing", { source_path: source.path });
40
+ }
41
+ return refuse("source_unavailable");
42
+ }
43
+ const actual = createHash("sha256").update(bytes).digest("hex");
44
+ if (actual !== source.sha256)
45
+ refuse("source_hash_changed", {
46
+ source_path: source.path, provided_source_sha256: source.sha256, actual_source_sha256: actual
47
+ });
48
+ }
49
+ }
50
+ export function verifyDiagnosticCoverage(root, baseline, coverage) {
51
+ const ids = coverage.map(value => value.id);
52
+ if (new Set(ids).size !== ids.length || ids.length !== baseline.length || baseline.some(id => !ids.includes(id)))
53
+ refuse("incomplete_aspect_coverage");
54
+ for (const value of coverage) {
55
+ if (!value.reason.trim())
56
+ refuse("missing_applicability_reason");
57
+ verifyDiagnosticSources(root, value.evidence);
58
+ }
59
+ }
60
+ /** Each independent critic must account for every candidate, including rejected findings. */
61
+ export function verifyDiagnosticCritic(root, candidates, decisions) {
62
+ const ids = candidates.map(value => value.id), reviewed = decisions.map(value => value.finding_id);
63
+ if (new Set(ids).size !== ids.length || new Set(reviewed).size !== reviewed.length
64
+ || ids.length !== reviewed.length || ids.some(id => !reviewed.includes(id)))
65
+ refuse("incomplete_critic_coverage");
66
+ for (const candidate of candidates) {
67
+ if (!candidate.evidence.length)
68
+ refuse("finding_without_evidence");
69
+ verifyDiagnosticSources(root, candidate.evidence);
70
+ }
71
+ for (const decision of decisions)
72
+ verifyDiagnosticSources(root, decision.evidence);
73
+ }
@@ -0,0 +1,72 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { AppError } from "../shared/errors.js";
4
+ import { hybridDigest } from "./hybrid-contracts.js";
5
+ import { appendAudit } from "./audit.js";
6
+ import { prepareStagePacket, acceptStagePacket } from "./stage-report-renderer.js";
7
+ import { publishImmutableInput } from "../storage/immutable-input.js";
8
+ import { renderDiagnosticReport } from "./diagnostic-report.js";
9
+ function refuse(reason) { throw new AppError("diagnostic_report_publication_invalid", "Diagnostic report publication requires its owned stage and exact bytes", 2, { reason, effect: "no_effect" }); }
10
+ /** Files are immutable candidates until retain is called in the Stage acceptance
11
+ * writer. Interrupted publication can replay before any terminal acceptance. */
12
+ export function prepareDiagnosticReportPublication(context, scope, report) {
13
+ const run = context.db.get("SELECT run_root,index_json FROM runs WHERE project_id=? AND id=?", [scope.projectId, scope.runId]);
14
+ const stage = run ? JSON.parse(run.index_json).stage_runs.find((value) => value.stage === scope.stage) : null;
15
+ if (!run?.run_root || scope.stage !== "report" || stage?.attempt !== scope.attempt || stage.input_sha256 !== scope.inputSha256
16
+ || report.run.id !== scope.runId || !["running", "done"].includes(stage.status))
17
+ refuse("foreign_stage");
18
+ const directory = path.join(run.run_root, stage.dir);
19
+ if (fs.realpathSync(run.run_root) !== run.run_root || fs.realpathSync(directory) !== directory
20
+ || !directory.startsWith(run.run_root + path.sep))
21
+ refuse("unsafe_report_directory");
22
+ const rendered = renderDiagnosticReport(report);
23
+ const packets = Object.entries(rendered).map(([kind, bytes]) => {
24
+ if (bytes.length > 4 * 1024 * 1024)
25
+ refuse("report_exceeds_bound");
26
+ const suffix = kind === "markdown" ? "md" : kind;
27
+ const file = path.join(directory, `diagnostic-report.${suffix}`);
28
+ try {
29
+ if (!fs.lstatSync(file).isFile() || fs.lstatSync(file).isSymbolicLink())
30
+ refuse("unsafe_report_file");
31
+ }
32
+ catch (error) {
33
+ if (error.code !== "ENOENT")
34
+ throw error;
35
+ }
36
+ return prepareStagePacket(file, bytes);
37
+ });
38
+ const manifest = { schema_id: "dd-flow/diagnostic-report-publication@1", run_id: scope.runId, stage: scope.stage, attempt: scope.attempt,
39
+ input_sha256: scope.inputSha256, artifacts: packets.map(packet => ({ path: path.relative(run.run_root, packet.file).split(path.sep).join("/"), sha256: packet.sha256 })) };
40
+ const retained = (writer) => writer.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type='diagnostic.report' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.attempt')=? LIMIT 2", [scope.projectId, scope.runId, scope.attempt]);
41
+ return {
42
+ manifest,
43
+ publish() {
44
+ if (fs.realpathSync(directory) !== directory)
45
+ refuse("report_directory_changed");
46
+ for (const packet of packets)
47
+ publishImmutableInput(packet.file, packet.bytes, "diagnostic_report_conflict");
48
+ },
49
+ /** SQL-only. The existing packet ledger retains recoverable authoritative
50
+ * bytes in the same transaction as Stage completion. */
51
+ retain(writer) {
52
+ const rows = retained(writer);
53
+ if (rows.length) {
54
+ if (rows.length !== 1 || hybridDigest(JSON.parse(rows[0].payload_json)) !== hybridDigest(manifest))
55
+ refuse("report_custody_changed");
56
+ for (const packet of packets) {
57
+ const records = writer.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type='flow_run.stage_packet' AND json_extract(payload_json,'$.run_id')=? AND json_extract(payload_json,'$.stage')=? AND json_extract(payload_json,'$.attempt')=? AND json_extract(payload_json,'$.file')=? LIMIT 2", [scope.projectId, scope.runId, scope.stage, scope.attempt, packet.file]);
58
+ if (records.length !== 1 || Buffer.byteLength(records[0].payload_json) > 6 * 1024 * 1024
59
+ || hybridDigest(JSON.parse(records[0].payload_json)) !== hybridDigest({ run_id: scope.runId, stage: scope.stage,
60
+ attempt: scope.attempt, file: packet.file, sha256: packet.sha256, base64: packet.bytes.toString("base64") }))
61
+ refuse("report_packet_custody_changed");
62
+ }
63
+ return;
64
+ }
65
+ if (stage.status !== "running")
66
+ refuse("report_receipt_missing");
67
+ for (const packet of packets)
68
+ acceptStagePacket(writer, { ...scope, prepared: packet });
69
+ appendAudit(writer, { projectId: scope.projectId, eventType: "diagnostic.report", payload: manifest });
70
+ }
71
+ };
72
+ }
@@ -0,0 +1,67 @@
1
+ import { AppError } from "../shared/errors.js";
2
+ import { hybridDigest } from "./hybrid-contracts.js";
3
+ import { collectDiagnosticCandidates } from "./diagnostic-work-results.js";
4
+ function refuse(reason) { throw new AppError("diagnostic_report_invalid", "Diagnostic report requires the reviewed candidate and critic registers", 2, { reason, effect: "no_effect" }); }
5
+ /** Projection only. The handler separately owns source checks, physical critic
6
+ * independence, SQL custody, publication and terminal RUN settlement. */
7
+ export function prepareDiagnosticReport(result, analysis, critics) {
8
+ if (result.stage !== "report" || !critics.length
9
+ || [...analysis, ...critics].some(work => work.workId !== work.result.work_id || work.result.run_id !== result.run_id || work.result.flow_id !== result.flow_id))
10
+ refuse("foreign_work");
11
+ const candidates = collectDiagnosticCandidates(analysis);
12
+ if (hybridDigest(candidates.findings) !== hybridDigest(result.details.findings))
13
+ refuse("candidate_register_changed");
14
+ for (const critic of critics) {
15
+ const ids = critic.result.decisions.map(value => value.finding_id);
16
+ if (critic.result.stage !== "review" || critic.result.findings.length || new Set(ids).size !== ids.length
17
+ || ids.length !== candidates.findings.length || candidates.findings.some(value => !ids.includes(value.id)))
18
+ refuse("incomplete_critic");
19
+ }
20
+ const decisions = new Map(result.details.decisions.map(value => [value.finding_id, value]));
21
+ if (decisions.size !== candidates.findings.length || result.details.decisions.length !== decisions.size
22
+ || candidates.findings.some(value => !decisions.has(value.id)))
23
+ refuse("incomplete_resolution");
24
+ const findings = candidates.findings.map((finding, index) => ({ ...finding, origin: candidates.origins[index],
25
+ resolution: decisions.get(finding.id),
26
+ critic_decisions: critics.map(work => ({ work_id: work.workId, decision: work.result.decisions.find(value => value.finding_id === finding.id) })) }));
27
+ const incomplete = result.details.coverage.some(value => value.applicability === "blocked" || value.applicability === "out_of_scope")
28
+ || result.details.missing_expected_sources.length > 0 || result.details.limitations.length > 0;
29
+ const accepted = findings.filter(value => !["rejected", "duplicate"].includes(value.resolution.disposition));
30
+ const sourceMap = new Map();
31
+ for (const source of [...result.details.sources, ...result.details.coverage.flatMap(value => value.evidence),
32
+ ...analysis.flatMap(value => value.result.sources), ...critics.flatMap(value => value.result.sources),
33
+ ...findings.flatMap(value => [...value.evidence, ...value.resolution.evidence, ...value.critic_decisions.flatMap(critic => critic.decision.evidence)])]) {
34
+ if (sourceMap.has(source.path) && sourceMap.get(source.path).sha256 !== source.sha256)
35
+ refuse("conflicting_source_versions");
36
+ sourceMap.set(source.path, structuredClone(source));
37
+ }
38
+ return {
39
+ schema_id: "dd-flow/diagnostic-report@1",
40
+ run: { id: result.run_id, flow_id: result.flow_id, snapshot_sha256: result.snapshot_sha256 },
41
+ summary: result.summary, review_focus: structuredClone(result.details.focus), source_map: [...sourceMap.values()],
42
+ aspect_coverage: structuredClone(result.details.coverage),
43
+ critic_pass: critics.map(work => ({ work_id: work.workId, role: work.result.aspect_id, summary: work.result.summary,
44
+ semantic_sha256: hybridDigest(work.result), decisions: structuredClone(work.result.decisions), limitations: [...work.result.limitations] })),
45
+ findings_register: findings,
46
+ conformance_summary: { assessment: incomplete ? "incomplete" : accepted.length ? "findings" : "no_accepted_findings_in_selected_scope",
47
+ accepted_findings: accepted.length, rejected_or_duplicate_findings: findings.length - accepted.length,
48
+ applicable_aspects: result.details.coverage.filter(value => value.applicability === "applicable").length,
49
+ blocked_aspects: result.details.coverage.filter(value => value.applicability === "blocked").length,
50
+ missing_expected_sources: [...result.details.missing_expected_sources] },
51
+ review_fix_recommendation: { authorization: "not_granted", candidate_ids: findings.filter(value => value.resolution.disposition === "review_fix_candidate").map(value => value.id) },
52
+ deferrals: { authorization: "not_granted", candidate_ids: findings.filter(value => value.resolution.disposition === "def_candidate").map(value => value.id),
53
+ observed_sources: [...sourceMap.values()].filter(value => /(?:^|\/)DEF(?:-MBU)?-[^/]+/.test(value.path)) },
54
+ limitations: [...result.details.limitations],
55
+ dashboard_links: critics.map(work => ({ work_id: work.workId, result: `run://${result.run_id}/works/${work.workId}/result.json` }))
56
+ };
57
+ }
58
+ /** HTML embeds exactly the authoritative JSON value; untrusted prose is text,
59
+ * never HTML or executable script. Markdown remains a compact companion. */
60
+ export function renderDiagnosticReport(report) {
61
+ const json = Buffer.from(JSON.stringify(report, null, 2) + "\n");
62
+ const escape = (text) => text.replace(/[&<>"']/g, value => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[value]);
63
+ const embedded = json.toString("utf8").replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
64
+ const markdown = Buffer.from(`# ${report.run.flow_id} · ${report.run.id}\n\n${report.summary}\n\nAssessment: ${report.conformance_summary.assessment}\n\n${report.findings_register.length} findings retained; ${report.critic_pass.length} independent critic passes.\n\nAuthoritative data: diagnostic-report.json\n`);
65
+ const html = Buffer.from(`<!doctype html><html lang="en"><meta charset="utf-8"><title>${escape(report.run.flow_id)} · ${escape(report.run.id)}</title><body><h1>${escape(report.review_focus.title)}</h1><p>${escape(report.summary)}</p><p>${escape(report.conformance_summary.assessment)}</p><h2>Coverage</h2><ul>${report.aspect_coverage.map(value => `<li>${escape(value.id)}: ${escape(value.applicability)} — ${escape(value.reason)}</li>`).join("")}</ul><h2>Findings</h2><pre>${escape(JSON.stringify(report.findings_register, null, 2))}</pre><script id="diagnostic-report-data" type="application/json">${embedded}</script></body></html>\n`);
66
+ return { json, markdown, html };
67
+ }
@@ -0,0 +1,74 @@
1
+ import { createHash } from "node:crypto";
2
+ import { AppError } from "../shared/errors.js";
3
+ import { isDiagnosticFlow } from "../domain/diagnostic-flows.js";
4
+ import { PreparedInstalledFlowSource } from "./installed-flow-source.js";
5
+ import { FrozenGenericExecution } from "./generic-execution.js";
6
+ import { AdmittedFrozenEngine } from "./frozen-engine.js";
7
+ import { RetainedExecutionBundle } from "./execution-bundles.js";
8
+ import { readDiagnosticSnapshot } from "./diagnostic-snapshot.js";
9
+ import { requireProjectByRoot } from "./projects.js";
10
+ import { nextDescriptorRunId } from "./descriptor-run-start.js";
11
+ import { bundledHandlerContext } from "./stage-handlers.js";
12
+ import { hybridDigest } from "./hybrid-contracts.js";
13
+ const bytes = (value) => Buffer.from(JSON.stringify(value));
14
+ const hash = (value) => createHash("sha256").update(value).digest("hex");
15
+ function refuse(reason) { throw new AppError("diagnostic_run_bundle_invalid", "Diagnostic bundle requires its frozen source, execution and snapshot", 2, { reason, effect: "no_effect" }); }
16
+ /** Initial construction only. Retry after allocation restores the retained RUN
17
+ * bundle, never current operator settings/canon. boundAt belongs to the request. */
18
+ export function prepareDiagnosticRunBundle(context, input) {
19
+ if (!(input.source instanceof PreparedInstalledFlowSource) || !(input.execution instanceof FrozenGenericExecution)
20
+ || !(input.engine instanceof AdmittedFrozenEngine) || !Number.isFinite(Date.parse(input.boundAt)))
21
+ refuse("unprepared_selection");
22
+ const frozen = input.execution.snapshot(), flowId = frozen.settings.flow_id;
23
+ if (!isDiagnosticFlow(flowId))
24
+ refuse("diagnostic_flow_required");
25
+ const project = requireProjectByRoot(context, frozen.scope.projectRoot);
26
+ const snapshot = readDiagnosticSnapshot(context, { projectId: project.id, projectRoot: frozen.scope.projectRoot,
27
+ sourceRoot: frozen.scope.projectRoot, requestId: frozen.scope.requestId, flowId });
28
+ if (!snapshot || snapshot.root !== frozen.scope.workspace)
29
+ refuse("snapshot_selection_changed");
30
+ input.source.revalidate();
31
+ const flow = input.source.resolveRegisteredFlow();
32
+ if (flow.descriptor.id !== flowId || flow.sha256 !== frozen.settings.descriptor_sha256)
33
+ refuse("flow_selection_changed");
34
+ input.engine.admitDiagnosticFlow(input.source, flowId);
35
+ const manifest = input.engine.manifest(), checksum = manifest.integrity.checksum;
36
+ if (typeof checksum !== "string")
37
+ refuse("engine_identity_missing");
38
+ const runId = nextDescriptorRunId(context, { projectRoot: frozen.scope.projectRoot, requestId: frozen.scope.requestId, slug: flowId });
39
+ const binding = { schema_id: "dd-flow/run-engine-binding@1", run_id: runId,
40
+ project_root: frozen.scope.projectRoot, bound_at: input.boundAt, source: "run_creation", reason: "Request-bound read-only diagnostic",
41
+ engine: { package_name: manifest.package_name, package_version: manifest.package_version, engine_version: manifest.engine_version,
42
+ integrity_checksum: checksum, snapshot_root: manifest.snapshot_root }, probe: { status: "not_required", command: null } };
43
+ const blobs = input.source.blobs();
44
+ const put = (logical, content) => {
45
+ if (blobs.has(logical))
46
+ refuse("bundle_path_collision");
47
+ blobs.set(logical, content);
48
+ return { path: logical, sha256: hash(content) };
49
+ };
50
+ for (const ref of [frozen.settings.attached_session_receipt, frozen.settings.delegation.native_capacity_receipt]) {
51
+ if (!ref)
52
+ continue;
53
+ const content = input.receiptBytes?.get(ref.sha256);
54
+ if (!content || hash(content) !== ref.sha256 || !input.receipts?.has(ref.sha256))
55
+ refuse("execution_receipt_missing");
56
+ put(ref.path, Buffer.from(content));
57
+ }
58
+ const profiles = { schema_id: "dd-flow/profile-settings@1", profiles: frozen.profiles, aliases: {}, default_profile: null };
59
+ const profile = put("runtime/profiles.json", bytes({ ...profiles, revision: hybridDigest(profiles) }));
60
+ const execution = put("runtime/execution.json", bytes(frozen.settings));
61
+ const engineBinding = put("runtime/engine-binding.json", bytes(binding));
62
+ const engineManifest = put("runtime/engine-manifest.json", input.engine.manifestBytes());
63
+ put("runtime/diagnostic-snapshot.json", bytes(snapshot));
64
+ const provenance = { canon: input.source.canonProvenance(), project_pack: null };
65
+ const bundle = { schema_id: "dd-flow/execution-bundle@1", run_id: runId, flow_id: flowId,
66
+ descriptor: input.source.descriptorReference(), execution_settings: execution, profile_settings: profile,
67
+ engine_binding: engineBinding, engine_manifest: engineManifest, bootstrap_receipts: [], ...provenance,
68
+ inventory: [...blobs].map(([logical, content]) => ({ path: logical, sha256: hash(content) })) };
69
+ const encoded = bytes(bundle);
70
+ return RetainedExecutionBundle.admit(encoded, hash(encoded), blobs, { runId, executionScope: frozen.scope,
71
+ bundleProjectRoot: frozen.scope.projectRoot, descriptorSha256: flow.sha256, engineChecksum: checksum,
72
+ engineBindingSha256: engineBinding.sha256, engineManifestSha256: engineManifest.sha256,
73
+ descriptorContext: bundledHandlerContext(new Map()), bundleProvenance: provenance, receipts: input.receipts ?? new Map() });
74
+ }
@@ -0,0 +1,159 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { execFileSync } from "node:child_process";
4
+ import { AppError } from "../shared/errors.js";
5
+ import { isDiagnosticFlow } from "../domain/diagnostic-flows.js";
6
+ import { hybridDigest } from "./hybrid-contracts.js";
7
+ import { appendAudit } from "./audit.js";
8
+ import { reviewSourceHash, reviewSourceFilter, reviewExecutionCopyHash } from "./review-copy.js";
9
+ import { projectPacketLocators, runContextRelocations } from "./stage-context.js";
10
+ const intentEvent = "diagnostic.snapshot_intent", readyEvent = "diagnostic.snapshot_ready";
11
+ function refuse(reason) { throw new AppError("diagnostic_snapshot_invalid", "Read-only diagnosis requires its retained unchanged snapshot", 1, { reason, effect: "no_effect" }); }
12
+ const inside = (root, file) => { const relative = path.relative(root, file); return relative === "" || !relative.startsWith(".." + path.sep) && relative !== ".." && !path.isAbsolute(relative); };
13
+ function rows(context, input, event) {
14
+ return context.db.all("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type=? AND json_extract(payload_json,'$.requestId')=? LIMIT 2", [input.projectId, event, input.requestId]);
15
+ }
16
+ function destination(context, input) {
17
+ return path.join(context.ddFlowHome, "diagnostic-snapshots", hybridDigest(input), "workspace");
18
+ }
19
+ /** Deterministic destination for freezing execution before the first copy. */
20
+ export function diagnosticSnapshotRoot(context, input) {
21
+ validate(context, input);
22
+ return destination(context, input);
23
+ }
24
+ function physicalSnapshot(context, value, runId) {
25
+ return runId ? JSON.parse(projectPacketLocators(JSON.stringify(value), runContextRelocations(context, value.projectId, runId))) : value;
26
+ }
27
+ /** Only physical locators are projected through existing RUN import receipts.
28
+ * The immutable source selection and SQL custody remain byte-for-byte original. */
29
+ export function diagnosticSnapshotPaths(context, value, runId) {
30
+ const physical = physicalSnapshot(context, value, runId);
31
+ return { root: physical.root, sourceRoot: physical.sourceRoot };
32
+ }
33
+ function validate(context, input, runId) {
34
+ const physical = physicalSnapshot(context, input, runId);
35
+ if (!isDiagnosticFlow(input.flowId) || !input.requestId?.trim() || input.requestId.length > 200
36
+ || !path.isAbsolute(physical.projectRoot) || physical.projectRoot !== physical.sourceRoot
37
+ || fs.realpathSync(physical.sourceRoot) !== physical.sourceRoot)
38
+ refuse("invalid_selection");
39
+ const project = context.db.get("SELECT root,status FROM projects WHERE id=?", [input.projectId]);
40
+ if (project?.root !== physical.projectRoot || project.status !== "active")
41
+ refuse("project_not_active");
42
+ if (inside(physical.sourceRoot, context.ddFlowHome))
43
+ refuse("runtime_inside_source");
44
+ }
45
+ function retained(context, input, runId) {
46
+ const records = rows(context, input, intentEvent);
47
+ if (records.length > 1)
48
+ refuse("ambiguous_snapshot");
49
+ if (!records.length)
50
+ return null;
51
+ if (Buffer.byteLength(records[0].payload_json) > 16 * 1024)
52
+ refuse("unbounded_snapshot");
53
+ const value = JSON.parse(records[0].payload_json);
54
+ if (value.schema_id !== "dd-flow/diagnostic-snapshot@1" || physicalSnapshot(context, value, runId).root !== destination(context, input)
55
+ || hybridDigest({ ...value, ...input }) !== hybridDigest(value) || !/^[a-f0-9]{64}$/.test(value.source_sha256))
56
+ refuse("snapshot_selection_changed");
57
+ return value;
58
+ }
59
+ function verifyFiles(value, requirePrivateGit = true) {
60
+ if (!fs.lstatSync(value.root).isDirectory() || fs.realpathSync(value.root) !== value.root
61
+ || reviewSourceHash(value.sourceRoot) !== value.source_sha256
62
+ || reviewExecutionCopyHash(value.root, value.sourceRoot, false) !== value.source_sha256)
63
+ refuse("source_or_copy_changed");
64
+ if (requirePrivateGit && !fs.lstatSync(path.join(value.root, ".git")).isDirectory())
65
+ refuse("private_git_required");
66
+ }
67
+ function seal(context, input, value) {
68
+ context.db.writeTransaction(() => {
69
+ if (hybridDigest(retained(context, input)) !== hybridDigest(value))
70
+ refuse("snapshot_custody_changed");
71
+ const ready = rows(context, input, readyEvent);
72
+ if (ready.length > 1 || ready.length === 1 && hybridDigest(JSON.parse(ready[0].payload_json)) !== hybridDigest(value))
73
+ refuse("snapshot_custody_changed");
74
+ if (!ready.length)
75
+ appendAudit(context, { projectId: input.projectId, eventType: readyEvent, payload: { ...value } });
76
+ });
77
+ }
78
+ /** SQL-owned selection plus freshly checked physical source/copy; never adopts a caller's claimed path. */
79
+ export function readDiagnosticSnapshot(context, input, runId) {
80
+ validate(context, input, runId);
81
+ const value = readDiagnosticSnapshotCustody(context, input, runId);
82
+ if (!value)
83
+ return null;
84
+ verifyFiles(physicalSnapshot(context, value, runId));
85
+ return value;
86
+ }
87
+ /** SQL-only custody check for an already physically validated selection.
88
+ * Callers must perform readDiagnosticSnapshot before entering a writer. */
89
+ export function readDiagnosticSnapshotCustody(context, input, runId) {
90
+ const value = retained(context, input, runId);
91
+ if (!value)
92
+ return null;
93
+ const ready = rows(context, input, readyEvent);
94
+ if (ready.length !== 1 || hybridDigest(JSON.parse(ready[0].payload_json)) !== hybridDigest(value))
95
+ refuse("snapshot_not_ready");
96
+ return value;
97
+ }
98
+ export function assertDiagnosticSnapshot(context, snapshot) {
99
+ const input = { projectId: snapshot.projectId, projectRoot: snapshot.projectRoot, sourceRoot: snapshot.sourceRoot, requestId: snapshot.requestId, flowId: snapshot.flowId };
100
+ const selected = readDiagnosticSnapshot(context, input);
101
+ if (!selected || hybridDigest(selected) !== hybridDigest(snapshot))
102
+ refuse("snapshot_binding_changed");
103
+ }
104
+ /** Copies only established review-visible inputs, never environment files or Git internals.
105
+ * Existing partial copies are verified, not overwritten. Reports belong outside this workspace. */
106
+ export function prepareDiagnosticSnapshot(context, input) {
107
+ validate(context, input);
108
+ const prior = retained(context, input);
109
+ if (prior) {
110
+ if (rows(context, input, readyEvent).length)
111
+ return readDiagnosticSnapshot(context, input);
112
+ verifyFiles(prior);
113
+ seal(context, input, prior);
114
+ return prior;
115
+ }
116
+ const root = destination(context, input), env = { ...context.env, GIT_OPTIONAL_LOCKS: "0" };
117
+ for (const key of Object.keys(env))
118
+ if (key.startsWith("GIT_") && key !== "GIT_OPTIONAL_LOCKS")
119
+ delete env[key];
120
+ const git = (...args) => {
121
+ try {
122
+ return execFileSync("git", ["-c", "core.fsmonitor=false", "-C", input.sourceRoot, ...args], { env, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], maxBuffer: 1024 * 1024 }).trim();
123
+ }
124
+ catch {
125
+ return null;
126
+ } // Git absence is explicit missing evidence, never a clean verdict.
127
+ };
128
+ const value = { ...input, schema_id: "dd-flow/diagnostic-snapshot@1", root, source_sha256: reviewSourceHash(input.sourceRoot),
129
+ git: { head: git("rev-parse", "HEAD"), branch: git("symbolic-ref", "--quiet", "--short", "HEAD"), status: git("status", "--porcelain=v1") } };
130
+ let current = context.ddFlowHome;
131
+ for (const part of path.relative(context.ddFlowHome, path.dirname(root)).split(path.sep)) {
132
+ current = path.join(current, part);
133
+ if (fs.existsSync(current)) {
134
+ if (!fs.lstatSync(current).isDirectory() || fs.realpathSync(current) !== current)
135
+ refuse("unsafe_snapshot_parent");
136
+ }
137
+ else
138
+ fs.mkdirSync(current, { mode: 0o700 });
139
+ }
140
+ if (fs.existsSync(root))
141
+ refuse("unowned_snapshot_directory");
142
+ context.db.writeTransaction(() => {
143
+ if (rows(context, input, intentEvent).length)
144
+ refuse("snapshot_already_reserved");
145
+ appendAudit(context, { projectId: input.projectId, eventType: intentEvent, payload: { ...value } });
146
+ });
147
+ fs.mkdirSync(root, { mode: 0o700 });
148
+ fs.cpSync(input.sourceRoot, root, { recursive: true, verbatimSymlinks: true, force: false, errorOnExist: true, filter: reviewSourceFilter(input.sourceRoot) });
149
+ verifyFiles(value, false);
150
+ // A private, independent index makes ordinary controller Git observations safe.
151
+ // No project worktree/index, hooks, commits or branch is changed.
152
+ execFileSync("git", ["-c", "init.templateDir=", "init", "--quiet", "--initial-branch=diagnostic-snapshot", root], { env, stdio: "pipe" });
153
+ // A tracked source can also match .gitignore. Keep the selected safe files
154
+ // tracked in the private index so subsequent review copies cannot omit it.
155
+ execFileSync("git", ["-C", root, "add", "--force", "--all", "--", "."], { env, stdio: "pipe" });
156
+ verifyFiles(value);
157
+ seal(context, input, value);
158
+ return value;
159
+ }