sdd-agent-platform 0.4.1 → 0.5.0

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 (698) hide show
  1. package/README.md +24 -28
  2. package/node_modules/@sdd-agent-platform/core/dist/ai-tools.js +84 -103
  3. package/node_modules/@sdd-agent-platform/core/dist/ai-tools.js.map +1 -1
  4. package/node_modules/@sdd-agent-platform/core/dist/config/init-project.d.ts +10 -6
  5. package/node_modules/@sdd-agent-platform/core/dist/config/init-project.js +7 -8
  6. package/node_modules/@sdd-agent-platform/core/dist/config/init-project.js.map +1 -1
  7. package/node_modules/@sdd-agent-platform/core/dist/config/project-config.d.ts +3 -1
  8. package/node_modules/@sdd-agent-platform/core/dist/config/project-config.js +7 -3
  9. package/node_modules/@sdd-agent-platform/core/dist/config/project-config.js.map +1 -1
  10. package/node_modules/@sdd-agent-platform/core/dist/config/starter-documents.d.ts +0 -1
  11. package/node_modules/@sdd-agent-platform/core/dist/config/starter-documents.js +374 -421
  12. package/node_modules/@sdd-agent-platform/core/dist/config/starter-documents.js.map +1 -1
  13. package/node_modules/@sdd-agent-platform/core/dist/context/build-package.d.ts +1 -1
  14. package/node_modules/@sdd-agent-platform/core/dist/context/build-package.js +7 -19
  15. package/node_modules/@sdd-agent-platform/core/dist/context/build-package.js.map +1 -1
  16. package/node_modules/@sdd-agent-platform/core/dist/contracts.d.ts +7 -1
  17. package/node_modules/@sdd-agent-platform/core/dist/contracts.js +6 -0
  18. package/node_modules/@sdd-agent-platform/core/dist/contracts.js.map +1 -1
  19. package/node_modules/@sdd-agent-platform/core/dist/doctor/checks/document-chain.js +2 -12
  20. package/node_modules/@sdd-agent-platform/core/dist/doctor/checks/document-chain.js.map +1 -1
  21. package/node_modules/@sdd-agent-platform/core/dist/doctor/doctor.js +1 -18
  22. package/node_modules/@sdd-agent-platform/core/dist/doctor/doctor.js.map +1 -1
  23. package/node_modules/@sdd-agent-platform/core/dist/evidence/lookup.d.ts +1 -1
  24. package/node_modules/@sdd-agent-platform/core/dist/evidence/lookup.js +1 -1
  25. package/node_modules/@sdd-agent-platform/core/dist/evidence/lookup.js.map +1 -1
  26. package/node_modules/@sdd-agent-platform/core/dist/evidence-runtime/contracts.d.ts +0 -1
  27. package/node_modules/@sdd-agent-platform/core/dist/evidence-runtime/coordination.js +110 -0
  28. package/node_modules/@sdd-agent-platform/core/dist/evidence-runtime/coordination.js.map +1 -0
  29. package/node_modules/@sdd-agent-platform/core/dist/execution/host-invocation.js +83 -83
  30. package/node_modules/@sdd-agent-platform/core/dist/instructions.d.ts +1 -1
  31. package/node_modules/@sdd-agent-platform/core/dist/instructions.js +37 -80
  32. package/node_modules/@sdd-agent-platform/core/dist/instructions.js.map +1 -1
  33. package/node_modules/@sdd-agent-platform/core/dist/lifecycle/ship.js +58 -68
  34. package/node_modules/@sdd-agent-platform/core/dist/lifecycle/ship.js.map +1 -1
  35. package/node_modules/@sdd-agent-platform/core/dist/lifecycle-graph/contracts.d.ts +159 -0
  36. package/node_modules/@sdd-agent-platform/core/dist/lifecycle-graph/contracts.js +7 -0
  37. package/node_modules/@sdd-agent-platform/core/dist/lifecycle-graph/contracts.js.map +1 -0
  38. package/node_modules/@sdd-agent-platform/core/dist/lifecycle-graph/kernel.d.ts +16 -0
  39. package/node_modules/@sdd-agent-platform/core/dist/lifecycle-graph/kernel.js +461 -0
  40. package/node_modules/@sdd-agent-platform/core/dist/lifecycle-graph/kernel.js.map +1 -0
  41. package/node_modules/@sdd-agent-platform/core/dist/lifecycle-graph.d.ts +2 -0
  42. package/node_modules/@sdd-agent-platform/core/dist/lifecycle-graph.js +3 -0
  43. package/node_modules/@sdd-agent-platform/core/dist/lifecycle-graph.js.map +1 -0
  44. package/node_modules/@sdd-agent-platform/core/dist/orchestration/contracts.d.ts +1 -1
  45. package/node_modules/@sdd-agent-platform/core/dist/orchestration/runtime.js +21 -28
  46. package/node_modules/@sdd-agent-platform/core/dist/orchestration/runtime.js.map +1 -1
  47. package/node_modules/@sdd-agent-platform/core/dist/registries/agent-registry.js +124 -40
  48. package/node_modules/@sdd-agent-platform/core/dist/registries/agent-registry.js.map +1 -1
  49. package/node_modules/@sdd-agent-platform/core/dist/registries/command-team-runtime.d.ts +1 -1
  50. package/node_modules/@sdd-agent-platform/core/dist/registries/command-team-runtime.js +6 -13
  51. package/node_modules/@sdd-agent-platform/core/dist/registries/command-team-runtime.js.map +1 -1
  52. package/node_modules/@sdd-agent-platform/core/dist/registries/plan-scout-domains.d.ts +13 -0
  53. package/node_modules/@sdd-agent-platform/core/dist/registries/plan-scout-domains.js +76 -0
  54. package/node_modules/@sdd-agent-platform/core/dist/registries/plan-scout-domains.js.map +1 -0
  55. package/node_modules/@sdd-agent-platform/core/dist/registries/skill-capabilities.js +7 -7
  56. package/node_modules/@sdd-agent-platform/core/dist/registries/skill-capabilities.js.map +1 -1
  57. package/node_modules/@sdd-agent-platform/core/dist/registries/tool-capabilities.js +6 -6
  58. package/node_modules/@sdd-agent-platform/core/dist/registries/tool-capabilities.js.map +1 -1
  59. package/node_modules/@sdd-agent-platform/core/dist/registries/workflow-gates.d.ts +1 -1
  60. package/node_modules/@sdd-agent-platform/core/dist/registries/workflow-gates.js +18 -18
  61. package/node_modules/@sdd-agent-platform/core/dist/registries/workflow-gates.js.map +1 -1
  62. package/node_modules/@sdd-agent-platform/core/dist/risk/consumer-diagnostics.js +2 -1
  63. package/node_modules/@sdd-agent-platform/core/dist/risk/consumer-diagnostics.js.map +1 -1
  64. package/node_modules/@sdd-agent-platform/core/dist/risk/contracts.d.ts +2 -2
  65. package/node_modules/@sdd-agent-platform/core/dist/risk/kernel.js +7 -7
  66. package/node_modules/@sdd-agent-platform/core/dist/risk/kernel.js.map +1 -1
  67. package/node_modules/@sdd-agent-platform/core/dist/risk/legacy-adapters.js +12 -27
  68. package/node_modules/@sdd-agent-platform/core/dist/risk/legacy-adapters.js.map +1 -1
  69. package/node_modules/@sdd-agent-platform/core/dist/risk/workflow-gates.js +6 -6
  70. package/node_modules/@sdd-agent-platform/core/dist/risk/workflow-gates.js.map +1 -1
  71. package/node_modules/@sdd-agent-platform/core/dist/router/agent-runtime-config.js +1 -1
  72. package/node_modules/@sdd-agent-platform/core/dist/router/agent-runtime-config.js.map +1 -1
  73. package/node_modules/@sdd-agent-platform/core/dist/router/routing.js +2 -4
  74. package/node_modules/@sdd-agent-platform/core/dist/router/routing.js.map +1 -1
  75. package/node_modules/@sdd-agent-platform/core/dist/router/runtime-import.d.ts +28 -0
  76. package/node_modules/@sdd-agent-platform/core/dist/router/runtime-import.js +383 -0
  77. package/node_modules/@sdd-agent-platform/core/dist/router/runtime-import.js.map +1 -0
  78. package/node_modules/@sdd-agent-platform/core/dist/router/stage-route-binding.d.ts +37 -0
  79. package/node_modules/@sdd-agent-platform/core/dist/router/stage-route-binding.js +227 -0
  80. package/node_modules/@sdd-agent-platform/core/dist/router/stage-route-binding.js.map +1 -0
  81. package/node_modules/@sdd-agent-platform/core/dist/router.d.ts +1 -0
  82. package/node_modules/@sdd-agent-platform/core/dist/router.js +1 -0
  83. package/node_modules/@sdd-agent-platform/core/dist/router.js.map +1 -1
  84. package/node_modules/@sdd-agent-platform/core/dist/run-state/artifacts.d.ts +16 -0
  85. package/node_modules/@sdd-agent-platform/core/dist/run-state/artifacts.js +6 -0
  86. package/node_modules/@sdd-agent-platform/core/dist/run-state/artifacts.js.map +1 -1
  87. package/node_modules/@sdd-agent-platform/core/dist/run-state/model.d.ts +20 -0
  88. package/node_modules/@sdd-agent-platform/core/dist/run-state/run-state.js +7 -7
  89. package/node_modules/@sdd-agent-platform/core/dist/run-state/run-state.js.map +1 -1
  90. package/node_modules/@sdd-agent-platform/core/dist/run-state/task-evidence.d.ts +1 -2
  91. package/node_modules/@sdd-agent-platform/core/dist/run-state/task-evidence.js +2 -9
  92. package/node_modules/@sdd-agent-platform/core/dist/run-state/task-evidence.js.map +1 -1
  93. package/node_modules/@sdd-agent-platform/core/dist/run-state/timing.d.ts +8 -0
  94. package/node_modules/@sdd-agent-platform/core/dist/run-state/timing.js +131 -0
  95. package/node_modules/@sdd-agent-platform/core/dist/run-state/timing.js.map +1 -0
  96. package/node_modules/@sdd-agent-platform/core/dist/runtime-analysis/build.js +1 -4
  97. package/node_modules/@sdd-agent-platform/core/dist/runtime-analysis/build.js.map +1 -1
  98. package/node_modules/@sdd-agent-platform/core/dist/runtime-analysis/findings.js +0 -39
  99. package/node_modules/@sdd-agent-platform/core/dist/runtime-analysis/findings.js.map +1 -1
  100. package/node_modules/@sdd-agent-platform/core/dist/runtime-analysis/model.d.ts +1 -17
  101. package/node_modules/@sdd-agent-platform/core/dist/runtime-paths.d.ts +10 -0
  102. package/node_modules/@sdd-agent-platform/core/dist/runtime-paths.js +65 -0
  103. package/node_modules/@sdd-agent-platform/core/dist/runtime-paths.js.map +1 -1
  104. package/node_modules/@sdd-agent-platform/core/dist/runtime-projection-p0.d.ts +64 -0
  105. package/node_modules/@sdd-agent-platform/core/dist/runtime-projection-p0.js +211 -0
  106. package/node_modules/@sdd-agent-platform/core/dist/runtime-projection-p0.js.map +1 -0
  107. package/node_modules/@sdd-agent-platform/core/dist/sdd-docs/artifact-depth.d.ts +14 -0
  108. package/node_modules/@sdd-agent-platform/core/dist/sdd-docs/artifact-depth.js +179 -0
  109. package/node_modules/@sdd-agent-platform/core/dist/sdd-docs/artifact-depth.js.map +1 -0
  110. package/node_modules/@sdd-agent-platform/core/dist/sdd-docs/task-parser.d.ts +5 -1
  111. package/node_modules/@sdd-agent-platform/core/dist/sdd-docs/task-parser.js +60 -22
  112. package/node_modules/@sdd-agent-platform/core/dist/sdd-docs/task-parser.js.map +1 -1
  113. package/node_modules/@sdd-agent-platform/core/dist/sdd-docs/task-rendering.js +2 -2
  114. package/node_modules/@sdd-agent-platform/core/dist/sdd-docs/task-rendering.js.map +1 -1
  115. package/node_modules/@sdd-agent-platform/core/dist/spec-entry.js +40 -0
  116. package/node_modules/@sdd-agent-platform/core/dist/spec-entry.js.map +1 -0
  117. package/node_modules/@sdd-agent-platform/core/dist/spec-manager-contracts.d.ts +12 -0
  118. package/node_modules/@sdd-agent-platform/core/dist/spec-manager-contracts.js +2 -0
  119. package/node_modules/@sdd-agent-platform/core/dist/spec-manager-contracts.js.map +1 -0
  120. package/node_modules/@sdd-agent-platform/core/dist/stage-artifacts.d.ts +55 -0
  121. package/node_modules/@sdd-agent-platform/core/dist/stage-artifacts.js +315 -0
  122. package/node_modules/@sdd-agent-platform/core/dist/stage-artifacts.js.map +1 -0
  123. package/node_modules/@sdd-agent-platform/core/dist/stage-collaboration-contracts.d.ts +55 -0
  124. package/node_modules/@sdd-agent-platform/core/dist/stage-collaboration-contracts.js +238 -0
  125. package/node_modules/@sdd-agent-platform/core/dist/stage-collaboration-contracts.js.map +1 -0
  126. package/node_modules/@sdd-agent-platform/core/dist/stage-collaboration.d.ts +736 -0
  127. package/node_modules/@sdd-agent-platform/core/dist/stage-collaboration.js +4018 -0
  128. package/node_modules/@sdd-agent-platform/core/dist/stage-collaboration.js.map +1 -0
  129. package/node_modules/@sdd-agent-platform/core/dist/stage-runtime/runtime.js +8 -1
  130. package/node_modules/@sdd-agent-platform/core/dist/stage-runtime/runtime.js.map +1 -1
  131. package/node_modules/@sdd-agent-platform/core/dist/status/project-status.js +25 -1
  132. package/node_modules/@sdd-agent-platform/core/dist/status/project-status.js.map +1 -1
  133. package/node_modules/@sdd-agent-platform/core/dist/storage/runtime-store.d.ts +170 -18
  134. package/node_modules/@sdd-agent-platform/core/dist/storage/runtime-store.js +597 -85
  135. package/node_modules/@sdd-agent-platform/core/dist/storage/runtime-store.js.map +1 -1
  136. package/node_modules/@sdd-agent-platform/core/dist/sync-back/apply.d.ts +1 -17
  137. package/node_modules/@sdd-agent-platform/core/dist/sync-back/apply.js +1 -242
  138. package/node_modules/@sdd-agent-platform/core/dist/sync-back/apply.js.map +1 -1
  139. package/node_modules/@sdd-agent-platform/core/dist/sync-back/inspect.d.ts +1 -110
  140. package/node_modules/@sdd-agent-platform/core/dist/sync-back/inspect.js +1 -496
  141. package/node_modules/@sdd-agent-platform/core/dist/sync-back/inspect.js.map +1 -1
  142. package/node_modules/@sdd-agent-platform/core/dist/sync-back.d.ts +1 -2
  143. package/node_modules/@sdd-agent-platform/core/dist/sync-back.js +1 -2
  144. package/node_modules/@sdd-agent-platform/core/dist/sync-back.js.map +1 -1
  145. package/node_modules/@sdd-agent-platform/core/dist/task-execution-contract.d.ts +167 -0
  146. package/node_modules/@sdd-agent-platform/core/dist/task-execution-contract.js +377 -0
  147. package/node_modules/@sdd-agent-platform/core/dist/task-execution-contract.js.map +1 -0
  148. package/node_modules/@sdd-agent-platform/core/dist/test-support/fixtures.js +329 -314
  149. package/node_modules/@sdd-agent-platform/core/dist/test-support/fixtures.js.map +1 -1
  150. package/node_modules/@sdd-agent-platform/core/dist/test-support/run-state.d.ts +1 -0
  151. package/node_modules/@sdd-agent-platform/core/dist/test-support/run-state.js +31 -0
  152. package/node_modules/@sdd-agent-platform/core/dist/test-support/run-state.js.map +1 -1
  153. package/node_modules/@sdd-agent-platform/core/dist/truth-reconciliation.d.ts +44 -0
  154. package/node_modules/@sdd-agent-platform/core/dist/truth-reconciliation.js +135 -0
  155. package/node_modules/@sdd-agent-platform/core/dist/truth-reconciliation.js.map +1 -0
  156. package/node_modules/@sdd-agent-platform/core/dist/tsconfig.tsbuildinfo +1 -1
  157. package/node_modules/@sdd-agent-platform/core/dist/verification/goal-verify.d.ts +0 -49
  158. package/node_modules/@sdd-agent-platform/core/dist/verification/goal-verify.js +1 -545
  159. package/node_modules/@sdd-agent-platform/core/dist/verification/goal-verify.js.map +1 -1
  160. package/node_modules/@sdd-agent-platform/core/dist/verification/rendering.d.ts +5 -7
  161. package/node_modules/@sdd-agent-platform/core/dist/verification/rendering.js +15 -55
  162. package/node_modules/@sdd-agent-platform/core/dist/verification/rendering.js.map +1 -1
  163. package/node_modules/@sdd-agent-platform/core/dist/verification/single-task-loop.js +1 -40
  164. package/node_modules/@sdd-agent-platform/core/dist/verification/single-task-loop.js.map +1 -1
  165. package/node_modules/@sdd-agent-platform/core/dist/verification/task-evidence-judgment.d.ts +49 -0
  166. package/node_modules/@sdd-agent-platform/core/dist/verification/task-evidence-judgment.js +521 -0
  167. package/node_modules/@sdd-agent-platform/core/dist/verification/task-evidence-judgment.js.map +1 -0
  168. package/node_modules/@sdd-agent-platform/core/dist/verification/test-runtime.d.ts +12 -2
  169. package/node_modules/@sdd-agent-platform/core/dist/verification/test-runtime.js +247 -112
  170. package/node_modules/@sdd-agent-platform/core/dist/verification/test-runtime.js.map +1 -1
  171. package/node_modules/@sdd-agent-platform/core/dist/verification/validation-cache.d.ts +26 -0
  172. package/node_modules/@sdd-agent-platform/core/dist/verification/validation-cache.js +73 -0
  173. package/node_modules/@sdd-agent-platform/core/dist/verification/validation-cache.js.map +1 -0
  174. package/node_modules/@sdd-agent-platform/core/dist/verification/verify-contract.d.ts +1 -1
  175. package/node_modules/@sdd-agent-platform/core/dist/verification/verify-contract.js +49 -72
  176. package/node_modules/@sdd-agent-platform/core/dist/verification/verify-contract.js.map +1 -1
  177. package/node_modules/@sdd-agent-platform/core/dist/verification.d.ts +3 -3
  178. package/node_modules/@sdd-agent-platform/core/dist/verification.js +2 -2
  179. package/node_modules/@sdd-agent-platform/core/dist/verification.js.map +1 -1
  180. package/node_modules/@sdd-agent-platform/core/dist/workflow-gate/evidence-packet.js +2 -7
  181. package/node_modules/@sdd-agent-platform/core/dist/workflow-gate/evidence-packet.js.map +1 -1
  182. package/node_modules/@sdd-agent-platform/core/dist/workflow-gate/hard-checks.js +0 -7
  183. package/node_modules/@sdd-agent-platform/core/dist/workflow-gate/hard-checks.js.map +1 -1
  184. package/node_modules/@sdd-agent-platform/core/dist/workflow-gate/policy.js +2 -4
  185. package/node_modules/@sdd-agent-platform/core/dist/workflow-gate/policy.js.map +1 -1
  186. package/node_modules/@sdd-agent-platform/core/dist/workflow-gate/types.d.ts +3 -5
  187. package/node_modules/@sdd-agent-platform/core/dist/workflow-state/latest-eligible-run.js +30 -4
  188. package/node_modules/@sdd-agent-platform/core/dist/workflow-state/latest-eligible-run.js.map +1 -1
  189. package/node_modules/@sdd-agent-platform/core/dist/workflow-state/migration-recovery.d.ts +40 -0
  190. package/node_modules/@sdd-agent-platform/core/dist/workflow-state/migration-recovery.js +110 -0
  191. package/node_modules/@sdd-agent-platform/core/dist/workflow-state/migration-recovery.js.map +1 -0
  192. package/node_modules/@sdd-agent-platform/core/dist/workflow-state/repair-contract.d.ts +12 -0
  193. package/node_modules/@sdd-agent-platform/core/dist/workflow-state/repair-contract.js +63 -0
  194. package/node_modules/@sdd-agent-platform/core/dist/workflow-state/repair-contract.js.map +1 -0
  195. package/node_modules/@sdd-agent-platform/core/dist/workflow-state/resolve-task-run.d.ts +21 -0
  196. package/node_modules/@sdd-agent-platform/core/dist/workflow-state/resolve-task-run.js +95 -0
  197. package/node_modules/@sdd-agent-platform/core/dist/workflow-state/resolve-task-run.js.map +1 -0
  198. package/node_modules/@sdd-agent-platform/core/dist/workflow-state/resolve.d.ts +55 -5
  199. package/node_modules/@sdd-agent-platform/core/dist/workflow-state/resolve.js +518 -36
  200. package/node_modules/@sdd-agent-platform/core/dist/workflow-state/resolve.js.map +1 -1
  201. package/node_modules/@sdd-agent-platform/core/dist/workflow-state/runtime-projections.d.ts +228 -0
  202. package/node_modules/@sdd-agent-platform/core/dist/workflow-state/runtime-projections.js +452 -0
  203. package/node_modules/@sdd-agent-platform/core/dist/workflow-state/runtime-projections.js.map +1 -0
  204. package/node_modules/@sdd-agent-platform/core/package.json +6 -3
  205. package/node_modules/@sdd-agent-platform/core/src/ai-tools.test.ts +238 -137
  206. package/node_modules/@sdd-agent-platform/core/src/ai-tools.ts +84 -103
  207. package/node_modules/@sdd-agent-platform/core/src/artifacts/ingestion.test.ts +189 -189
  208. package/node_modules/@sdd-agent-platform/core/src/artifacts/ingestion.ts +222 -222
  209. package/node_modules/@sdd-agent-platform/core/src/artifacts/sdd-evidence.test.ts +28 -28
  210. package/node_modules/@sdd-agent-platform/core/src/artifacts/sdd-evidence.ts +302 -302
  211. package/node_modules/@sdd-agent-platform/core/src/artifacts/sdd-result.test.ts +181 -181
  212. package/node_modules/@sdd-agent-platform/core/src/artifacts/sdd-result.ts +231 -231
  213. package/node_modules/@sdd-agent-platform/core/src/artifacts/templates.ts +99 -99
  214. package/node_modules/@sdd-agent-platform/core/src/artifacts.ts +4 -4
  215. package/node_modules/@sdd-agent-platform/core/src/coding-facts/contracts.ts +79 -79
  216. package/node_modules/@sdd-agent-platform/core/src/coding-facts.ts +1 -1
  217. package/node_modules/@sdd-agent-platform/core/src/config/init-project.test.ts +314 -306
  218. package/node_modules/@sdd-agent-platform/core/src/config/init-project.ts +128 -120
  219. package/node_modules/@sdd-agent-platform/core/src/config/project-config.ts +265 -259
  220. package/node_modules/@sdd-agent-platform/core/src/config/project-detection.ts +147 -147
  221. package/node_modules/@sdd-agent-platform/core/src/config/starter-documents.ts +400 -445
  222. package/node_modules/@sdd-agent-platform/core/src/context/budget.ts +30 -30
  223. package/node_modules/@sdd-agent-platform/core/src/context/build-package.ts +305 -317
  224. package/node_modules/@sdd-agent-platform/core/src/context/command-summary.ts +45 -45
  225. package/node_modules/@sdd-agent-platform/core/src/context/context-build.test.ts +188 -188
  226. package/node_modules/@sdd-agent-platform/core/src/context/evidence-summary.ts +144 -144
  227. package/node_modules/@sdd-agent-platform/core/src/context/log-worker.ts +48 -48
  228. package/node_modules/@sdd-agent-platform/core/src/context/source-refs.ts +41 -41
  229. package/node_modules/@sdd-agent-platform/core/src/context-offload/contracts.ts +47 -47
  230. package/node_modules/@sdd-agent-platform/core/src/context-offload/runtime.test.ts +71 -71
  231. package/node_modules/@sdd-agent-platform/core/src/context-offload/runtime.ts +178 -178
  232. package/node_modules/@sdd-agent-platform/core/src/context-offload.ts +2 -2
  233. package/node_modules/@sdd-agent-platform/core/src/context.ts +6 -6
  234. package/node_modules/@sdd-agent-platform/core/src/contracts/issues.ts +13 -13
  235. package/node_modules/@sdd-agent-platform/core/src/contracts.test.ts +9 -9
  236. package/node_modules/@sdd-agent-platform/core/src/contracts.ts +121 -115
  237. package/node_modules/@sdd-agent-platform/core/src/delegation/delegation.test.ts +183 -183
  238. package/node_modules/@sdd-agent-platform/core/src/delegation/model.ts +23 -23
  239. package/node_modules/@sdd-agent-platform/core/src/delegation/queue.ts +58 -58
  240. package/node_modules/@sdd-agent-platform/core/src/delegation/run-state.ts +14 -14
  241. package/node_modules/@sdd-agent-platform/core/src/delegation/state-machine.ts +90 -90
  242. package/node_modules/@sdd-agent-platform/core/src/delegation/validation.ts +124 -124
  243. package/node_modules/@sdd-agent-platform/core/src/delegation.ts +26 -26
  244. package/node_modules/@sdd-agent-platform/core/src/doctor/checks/ai-entries.ts +28 -28
  245. package/node_modules/@sdd-agent-platform/core/src/doctor/checks/document-chain.ts +104 -112
  246. package/node_modules/@sdd-agent-platform/core/src/doctor/checks/local-run-index.ts +27 -27
  247. package/node_modules/@sdd-agent-platform/core/src/doctor/checks/project.ts +84 -84
  248. package/node_modules/@sdd-agent-platform/core/src/doctor/checks/registries.ts +252 -252
  249. package/node_modules/@sdd-agent-platform/core/src/doctor/checks/run-evidence.ts +330 -330
  250. package/node_modules/@sdd-agent-platform/core/src/doctor/checks/run-records.ts +79 -79
  251. package/node_modules/@sdd-agent-platform/core/src/doctor/checks/run-trust.ts +128 -128
  252. package/node_modules/@sdd-agent-platform/core/src/doctor/checks/runtime-contracts.ts +300 -300
  253. package/node_modules/@sdd-agent-platform/core/src/doctor/doctor.test.ts +627 -657
  254. package/node_modules/@sdd-agent-platform/core/src/doctor/doctor.ts +301 -318
  255. package/node_modules/@sdd-agent-platform/core/src/doctor/model.ts +13 -13
  256. package/node_modules/@sdd-agent-platform/core/src/doctor/summary.ts +11 -11
  257. package/node_modules/@sdd-agent-platform/core/src/doctor.ts +2 -2
  258. package/node_modules/@sdd-agent-platform/core/src/evidence/lookup.ts +80 -80
  259. package/node_modules/@sdd-agent-platform/core/src/evidence-runtime/contracts.ts +48 -49
  260. package/node_modules/@sdd-agent-platform/core/src/evidence-runtime.ts +1 -1
  261. package/node_modules/@sdd-agent-platform/core/src/execution/agent-execution-records.ts +195 -195
  262. package/node_modules/@sdd-agent-platform/core/src/execution/background-executor.test.ts +187 -187
  263. package/node_modules/@sdd-agent-platform/core/src/execution/background-executor.ts +305 -305
  264. package/node_modules/@sdd-agent-platform/core/src/execution/foreground-subagents.test.ts +97 -97
  265. package/node_modules/@sdd-agent-platform/core/src/execution/foreground-subagents.ts +453 -453
  266. package/node_modules/@sdd-agent-platform/core/src/execution/host-invocation.ts +225 -225
  267. package/node_modules/@sdd-agent-platform/core/src/execution/resident-worker.test.ts +132 -132
  268. package/node_modules/@sdd-agent-platform/core/src/execution/resident-worker.ts +436 -436
  269. package/node_modules/@sdd-agent-platform/core/src/execution/stage-team-runtime.test.ts +102 -102
  270. package/node_modules/@sdd-agent-platform/core/src/execution/stage-team-runtime.ts +271 -271
  271. package/node_modules/@sdd-agent-platform/core/src/execution/wave-executor.test.ts +111 -111
  272. package/node_modules/@sdd-agent-platform/core/src/execution/wave-executor.ts +231 -231
  273. package/node_modules/@sdd-agent-platform/core/src/execution.ts +5 -5
  274. package/node_modules/@sdd-agent-platform/core/src/governance/policy.test.ts +57 -57
  275. package/node_modules/@sdd-agent-platform/core/src/governance/policy.ts +175 -175
  276. package/node_modules/@sdd-agent-platform/core/src/governance.ts +1 -1
  277. package/node_modules/@sdd-agent-platform/core/src/instructions.test.ts +80 -49
  278. package/node_modules/@sdd-agent-platform/core/src/instructions.ts +38 -81
  279. package/node_modules/@sdd-agent-platform/core/src/lifecycle/decision-gate.test.ts +174 -174
  280. package/node_modules/@sdd-agent-platform/core/src/lifecycle/decision-gate.ts +373 -373
  281. package/node_modules/@sdd-agent-platform/core/src/lifecycle/rendering.ts +29 -29
  282. package/node_modules/@sdd-agent-platform/core/src/lifecycle/risk-signals.ts +146 -146
  283. package/node_modules/@sdd-agent-platform/core/src/lifecycle/ship.test.ts +47 -0
  284. package/node_modules/@sdd-agent-platform/core/src/lifecycle/ship.ts +255 -263
  285. package/node_modules/@sdd-agent-platform/core/src/lifecycle-graph/contracts.ts +179 -0
  286. package/node_modules/@sdd-agent-platform/core/src/lifecycle-graph/kernel.ts +522 -0
  287. package/node_modules/@sdd-agent-platform/core/src/lifecycle-graph.ts +2 -0
  288. package/node_modules/@sdd-agent-platform/core/src/lifecycle.ts +4 -4
  289. package/node_modules/@sdd-agent-platform/core/src/orchestration/contracts.ts +50 -50
  290. package/node_modules/@sdd-agent-platform/core/src/orchestration/index.ts +2 -2
  291. package/node_modules/@sdd-agent-platform/core/src/orchestration/runtime.ts +331 -342
  292. package/node_modules/@sdd-agent-platform/core/src/path-safety.test.ts +22 -22
  293. package/node_modules/@sdd-agent-platform/core/src/phase8-contracts.test.ts +243 -243
  294. package/node_modules/@sdd-agent-platform/core/src/phase8-projection-compat.test.ts +152 -153
  295. package/node_modules/@sdd-agent-platform/core/src/phase8-risk-kernel.test.ts +277 -277
  296. package/node_modules/@sdd-agent-platform/core/src/phase9-lifecycle-graph.test.ts +103 -0
  297. package/node_modules/@sdd-agent-platform/core/src/planning/task-graph.test.ts +88 -88
  298. package/node_modules/@sdd-agent-platform/core/src/planning/task-graph.ts +222 -222
  299. package/node_modules/@sdd-agent-platform/core/src/planning/wave-plan.test.ts +79 -79
  300. package/node_modules/@sdd-agent-platform/core/src/planning/wave-plan.ts +160 -160
  301. package/node_modules/@sdd-agent-platform/core/src/planning.ts +2 -2
  302. package/node_modules/@sdd-agent-platform/core/src/registries/agent-capability-catalog.ts +426 -426
  303. package/node_modules/@sdd-agent-platform/core/src/registries/agent-registry.ts +230 -146
  304. package/node_modules/@sdd-agent-platform/core/src/registries/agent-runtime-static.ts +142 -142
  305. package/node_modules/@sdd-agent-platform/core/src/registries/capability-sources.ts +253 -253
  306. package/node_modules/@sdd-agent-platform/core/src/registries/command-team-runtime.ts +302 -309
  307. package/node_modules/@sdd-agent-platform/core/src/registries/eval-learning-context.ts +246 -246
  308. package/node_modules/@sdd-agent-platform/core/src/registries/plan-scout-domains.ts +89 -0
  309. package/node_modules/@sdd-agent-platform/core/src/registries/query-status.ts +119 -119
  310. package/node_modules/@sdd-agent-platform/core/src/registries/registries.test.ts +454 -429
  311. package/node_modules/@sdd-agent-platform/core/src/registries/skill-capabilities.ts +37 -37
  312. package/node_modules/@sdd-agent-platform/core/src/registries/tool-capabilities.ts +135 -135
  313. package/node_modules/@sdd-agent-platform/core/src/registries/tool-plugins.ts +132 -132
  314. package/node_modules/@sdd-agent-platform/core/src/registries/worker-adapters.ts +144 -144
  315. package/node_modules/@sdd-agent-platform/core/src/registries/workflow-gates.ts +111 -111
  316. package/node_modules/@sdd-agent-platform/core/src/registries.ts +42 -42
  317. package/node_modules/@sdd-agent-platform/core/src/risk/consumer-diagnostics.ts +98 -97
  318. package/node_modules/@sdd-agent-platform/core/src/risk/contracts.ts +63 -63
  319. package/node_modules/@sdd-agent-platform/core/src/risk/kernel.ts +233 -233
  320. package/node_modules/@sdd-agent-platform/core/src/risk/legacy-adapters.ts +251 -266
  321. package/node_modules/@sdd-agent-platform/core/src/risk/workflow-gates.ts +203 -203
  322. package/node_modules/@sdd-agent-platform/core/src/risk.ts +5 -5
  323. package/node_modules/@sdd-agent-platform/core/src/router/agent-runtime-config.ts +327 -327
  324. package/node_modules/@sdd-agent-platform/core/src/router/agent-runtime.ts +388 -388
  325. package/node_modules/@sdd-agent-platform/core/src/router/profile-resolution.ts +154 -154
  326. package/node_modules/@sdd-agent-platform/core/src/router/risk-policy.ts +33 -33
  327. package/node_modules/@sdd-agent-platform/core/src/router/route-cache.ts +100 -100
  328. package/node_modules/@sdd-agent-platform/core/src/router/route-projection.ts +356 -356
  329. package/node_modules/@sdd-agent-platform/core/src/router/route-sdd-task.test.ts +428 -428
  330. package/node_modules/@sdd-agent-platform/core/src/router/route-sdd-task.ts +2 -2
  331. package/node_modules/@sdd-agent-platform/core/src/router/routing-rules.ts +73 -73
  332. package/node_modules/@sdd-agent-platform/core/src/router/routing.ts +189 -191
  333. package/node_modules/@sdd-agent-platform/core/src/router/runtime-import.ts +464 -0
  334. package/node_modules/@sdd-agent-platform/core/src/router/runtime-inspection.ts +124 -124
  335. package/node_modules/@sdd-agent-platform/core/src/router/runtime-registry.ts +123 -123
  336. package/node_modules/@sdd-agent-platform/core/src/router/runtime-validation.ts +277 -277
  337. package/node_modules/@sdd-agent-platform/core/src/router/stage-route-binding.ts +273 -0
  338. package/node_modules/@sdd-agent-platform/core/src/router/team-mode.ts +170 -170
  339. package/node_modules/@sdd-agent-platform/core/src/router.ts +5 -4
  340. package/node_modules/@sdd-agent-platform/core/src/run-state/artifacts.ts +126 -118
  341. package/node_modules/@sdd-agent-platform/core/src/run-state/events.ts +27 -27
  342. package/node_modules/@sdd-agent-platform/core/src/run-state/inspect-run.ts +172 -172
  343. package/node_modules/@sdd-agent-platform/core/src/run-state/invocation-ledger.ts +109 -109
  344. package/node_modules/@sdd-agent-platform/core/src/run-state/model.ts +252 -230
  345. package/node_modules/@sdd-agent-platform/core/src/run-state/run-index.test.ts +52 -52
  346. package/node_modules/@sdd-agent-platform/core/src/run-state/run-index.ts +356 -356
  347. package/node_modules/@sdd-agent-platform/core/src/run-state/run-state.test.ts +70 -70
  348. package/node_modules/@sdd-agent-platform/core/src/run-state/run-state.ts +406 -406
  349. package/node_modules/@sdd-agent-platform/core/src/run-state/task-evidence.ts +198 -206
  350. package/node_modules/@sdd-agent-platform/core/src/run-state/timing.ts +146 -0
  351. package/node_modules/@sdd-agent-platform/core/src/run-state.ts +8 -8
  352. package/node_modules/@sdd-agent-platform/core/src/runtime-analysis/build.ts +60 -63
  353. package/node_modules/@sdd-agent-platform/core/src/runtime-analysis/findings.ts +257 -296
  354. package/node_modules/@sdd-agent-platform/core/src/runtime-analysis/model.ts +140 -152
  355. package/node_modules/@sdd-agent-platform/core/src/runtime-analysis.test.ts +66 -68
  356. package/node_modules/@sdd-agent-platform/core/src/runtime-analysis.ts +2 -2
  357. package/node_modules/@sdd-agent-platform/core/src/runtime-paths.ts +253 -176
  358. package/node_modules/@sdd-agent-platform/core/src/runtime-projection-p0.test.ts +101 -0
  359. package/node_modules/@sdd-agent-platform/core/src/runtime-projection-p0.ts +314 -0
  360. package/node_modules/@sdd-agent-platform/core/src/sdd-docs/artifact-depth.test.ts +380 -0
  361. package/node_modules/@sdd-agent-platform/core/src/sdd-docs/artifact-depth.ts +207 -0
  362. package/node_modules/@sdd-agent-platform/core/src/sdd-docs/context.ts +111 -111
  363. package/node_modules/@sdd-agent-platform/core/src/sdd-docs/document-hashes.ts +207 -207
  364. package/node_modules/@sdd-agent-platform/core/src/sdd-docs/run-binding.ts +95 -95
  365. package/node_modules/@sdd-agent-platform/core/src/sdd-docs/task-inspection.ts +39 -39
  366. package/node_modules/@sdd-agent-platform/core/src/sdd-docs/task-parser.test.ts +467 -401
  367. package/node_modules/@sdd-agent-platform/core/src/sdd-docs/task-parser.ts +738 -694
  368. package/node_modules/@sdd-agent-platform/core/src/sdd-docs/task-rendering.ts +81 -81
  369. package/node_modules/@sdd-agent-platform/core/src/sdd-docs.ts +5 -5
  370. package/node_modules/@sdd-agent-platform/core/src/spec-manager-contracts.ts +13 -0
  371. package/node_modules/@sdd-agent-platform/core/src/stage-artifacts.ts +435 -0
  372. package/node_modules/@sdd-agent-platform/core/src/stage-collaboration-contracts.ts +316 -0
  373. package/node_modules/@sdd-agent-platform/core/src/stage-collaboration.test.ts +2964 -0
  374. package/node_modules/@sdd-agent-platform/core/src/stage-collaboration.ts +5856 -0
  375. package/node_modules/@sdd-agent-platform/core/src/stage-runtime/contracts.ts +40 -40
  376. package/node_modules/@sdd-agent-platform/core/src/stage-runtime/runtime.test.ts +209 -209
  377. package/node_modules/@sdd-agent-platform/core/src/stage-runtime/runtime.ts +360 -352
  378. package/node_modules/@sdd-agent-platform/core/src/stage-runtime.ts +2 -2
  379. package/node_modules/@sdd-agent-platform/core/src/status/project-status.test.ts +288 -288
  380. package/node_modules/@sdd-agent-platform/core/src/status/project-status.ts +651 -625
  381. package/node_modules/@sdd-agent-platform/core/src/status.ts +2 -2
  382. package/node_modules/@sdd-agent-platform/core/src/storage/json-io.ts +10 -10
  383. package/node_modules/@sdd-agent-platform/core/src/storage/runtime-store.test.ts +489 -489
  384. package/node_modules/@sdd-agent-platform/core/src/storage/runtime-store.ts +1981 -1175
  385. package/node_modules/@sdd-agent-platform/core/src/subagents/contracts.ts +45 -45
  386. package/node_modules/@sdd-agent-platform/core/src/subagents/runtime.test.ts +232 -232
  387. package/node_modules/@sdd-agent-platform/core/src/subagents/runtime.ts +307 -307
  388. package/node_modules/@sdd-agent-platform/core/src/subagents.ts +2 -2
  389. package/node_modules/@sdd-agent-platform/core/src/task-execution-contract.test.ts +141 -0
  390. package/node_modules/@sdd-agent-platform/core/src/task-execution-contract.ts +566 -0
  391. package/node_modules/@sdd-agent-platform/core/src/task-risk-profile.ts +193 -193
  392. package/node_modules/@sdd-agent-platform/core/src/test-support/fixtures.ts +413 -398
  393. package/node_modules/@sdd-agent-platform/core/src/test-support/run-state.ts +102 -70
  394. package/node_modules/@sdd-agent-platform/core/src/test-support.ts +2 -2
  395. package/node_modules/@sdd-agent-platform/core/src/truth-reconciliation.test.ts +72 -0
  396. package/node_modules/@sdd-agent-platform/core/src/truth-reconciliation.ts +174 -0
  397. package/node_modules/@sdd-agent-platform/core/src/verification/rendering.ts +137 -181
  398. package/node_modules/@sdd-agent-platform/core/src/verification/review-gate.test.ts +77 -77
  399. package/node_modules/@sdd-agent-platform/core/src/verification/review-gate.ts +77 -77
  400. package/node_modules/@sdd-agent-platform/core/src/verification/single-task-loop.ts +455 -494
  401. package/node_modules/@sdd-agent-platform/core/src/verification/{goal-verify.test.ts → task-evidence-judgment.test.ts} +261 -335
  402. package/node_modules/@sdd-agent-platform/core/src/verification/{goal-verify.ts → task-evidence-judgment.ts} +619 -648
  403. package/node_modules/@sdd-agent-platform/core/src/verification/test-runtime.ts +1190 -1032
  404. package/node_modules/@sdd-agent-platform/core/src/verification/validation-cache.ts +106 -0
  405. package/node_modules/@sdd-agent-platform/core/src/verification/validation-wave.ts +513 -513
  406. package/node_modules/@sdd-agent-platform/core/src/verification/verify-contract.ts +334 -358
  407. package/node_modules/@sdd-agent-platform/core/src/verification.ts +8 -8
  408. package/node_modules/@sdd-agent-platform/core/src/work-units/contracts.ts +26 -26
  409. package/node_modules/@sdd-agent-platform/core/src/work-units/runtime.test.ts +88 -88
  410. package/node_modules/@sdd-agent-platform/core/src/work-units/runtime.ts +112 -112
  411. package/node_modules/@sdd-agent-platform/core/src/work-units.ts +2 -2
  412. package/node_modules/@sdd-agent-platform/core/src/workflow-gate/evidence-packet.ts +190 -196
  413. package/node_modules/@sdd-agent-platform/core/src/workflow-gate/hard-checks.test.ts +169 -171
  414. package/node_modules/@sdd-agent-platform/core/src/workflow-gate/hard-checks.ts +136 -143
  415. package/node_modules/@sdd-agent-platform/core/src/workflow-gate/policy.test.ts +135 -137
  416. package/node_modules/@sdd-agent-platform/core/src/workflow-gate/policy.ts +153 -155
  417. package/node_modules/@sdd-agent-platform/core/src/workflow-gate/types.ts +111 -114
  418. package/node_modules/@sdd-agent-platform/core/src/workflow-state/affected-file-conflicts.ts +95 -95
  419. package/node_modules/@sdd-agent-platform/core/src/workflow-state/dependencies.test.ts +32 -32
  420. package/node_modules/@sdd-agent-platform/core/src/workflow-state/dependencies.ts +114 -114
  421. package/node_modules/@sdd-agent-platform/core/src/workflow-state/latest-eligible-run.ts +184 -156
  422. package/node_modules/@sdd-agent-platform/core/src/workflow-state/migration-recovery.ts +158 -0
  423. package/node_modules/@sdd-agent-platform/core/src/workflow-state/repair-contract.ts +77 -0
  424. package/node_modules/@sdd-agent-platform/core/src/workflow-state/resolve-task-run.ts +114 -0
  425. package/node_modules/@sdd-agent-platform/core/src/workflow-state/resolve.test.ts +970 -464
  426. package/node_modules/@sdd-agent-platform/core/src/workflow-state/resolve.ts +967 -363
  427. package/node_modules/@sdd-agent-platform/core/src/workflow-state/runtime-projections.ts +712 -0
  428. package/node_modules/@sdd-agent-platform/core/src/workflow-state.ts +2 -2
  429. package/node_modules/@sdd-agent-platform/core/src/worktree/isolation.ts +130 -130
  430. package/node_modules/@sdd-agent-platform/core/src/worktree/lifecycle.ts +269 -269
  431. package/node_modules/@sdd-agent-platform/core/src/worktree/worktree.test.ts +150 -150
  432. package/node_modules/@sdd-agent-platform/core/src/worktree.ts +2 -2
  433. package/node_modules/@sdd-agent-platform/core/tsconfig.json +15 -15
  434. package/package.json +2 -2
  435. package/packages/cli/dist/args.js +1 -1
  436. package/packages/cli/dist/args.js.map +1 -1
  437. package/packages/cli/dist/commands/context.js +1 -1
  438. package/packages/cli/dist/commands/context.js.map +1 -1
  439. package/packages/cli/dist/commands/evidence.js.map +1 -0
  440. package/packages/cli/dist/commands/execution.js +126 -0
  441. package/packages/cli/dist/commands/execution.js.map +1 -1
  442. package/packages/cli/dist/commands/instructions.d.ts +1 -1
  443. package/packages/cli/dist/commands/instructions.js +15 -1
  444. package/packages/cli/dist/commands/instructions.js.map +1 -1
  445. package/packages/cli/dist/commands/registry/runtime.js +70 -1
  446. package/packages/cli/dist/commands/registry/runtime.js.map +1 -1
  447. package/packages/cli/dist/commands/run.js +12 -1
  448. package/packages/cli/dist/commands/run.js.map +1 -1
  449. package/packages/cli/dist/commands/stage-close.d.ts +66 -0
  450. package/packages/cli/dist/commands/stage-close.js +524 -0
  451. package/packages/cli/dist/commands/stage-close.js.map +1 -0
  452. package/packages/cli/dist/commands/status.js +8 -1
  453. package/packages/cli/dist/commands/status.js.map +1 -1
  454. package/packages/cli/dist/commands/tasks.js.map +1 -1
  455. package/packages/cli/dist/dispatch.js +6 -31
  456. package/packages/cli/dist/dispatch.js.map +1 -1
  457. package/packages/cli/dist/help.js +153 -158
  458. package/packages/cli/dist/help.js.map +1 -1
  459. package/packages/cli/dist/renderers/workflow.d.ts +51 -2
  460. package/packages/cli/dist/renderers/workflow.js.map +1 -1
  461. package/packages/cli/dist/skill-import-args.d.ts +10 -0
  462. package/packages/cli/dist/skill-import-args.js +47 -0
  463. package/packages/cli/dist/skill-import-args.js.map +1 -0
  464. package/packages/cli/dist/tsconfig.tsbuildinfo +1 -1
  465. package/packages/cli/package.json +2 -2
  466. package/packages/core/dist/ai-tools.js +84 -103
  467. package/packages/core/dist/ai-tools.js.map +1 -1
  468. package/packages/core/dist/config/init-project.d.ts +10 -6
  469. package/packages/core/dist/config/init-project.js +7 -8
  470. package/packages/core/dist/config/init-project.js.map +1 -1
  471. package/packages/core/dist/config/project-config.d.ts +3 -1
  472. package/packages/core/dist/config/project-config.js +7 -3
  473. package/packages/core/dist/config/project-config.js.map +1 -1
  474. package/packages/core/dist/config/starter-documents.d.ts +0 -1
  475. package/packages/core/dist/config/starter-documents.js +374 -421
  476. package/packages/core/dist/config/starter-documents.js.map +1 -1
  477. package/packages/core/dist/context/build-package.d.ts +1 -1
  478. package/packages/core/dist/context/build-package.js +7 -19
  479. package/packages/core/dist/context/build-package.js.map +1 -1
  480. package/packages/core/dist/contracts.d.ts +7 -1
  481. package/packages/core/dist/contracts.js +6 -0
  482. package/packages/core/dist/contracts.js.map +1 -1
  483. package/packages/core/dist/doctor/checks/document-chain.js +2 -12
  484. package/packages/core/dist/doctor/checks/document-chain.js.map +1 -1
  485. package/packages/core/dist/doctor/doctor.js +1 -18
  486. package/packages/core/dist/doctor/doctor.js.map +1 -1
  487. package/packages/core/dist/evidence/lookup.d.ts +1 -1
  488. package/packages/core/dist/evidence/lookup.js +1 -1
  489. package/packages/core/dist/evidence/lookup.js.map +1 -1
  490. package/packages/core/dist/evidence-runtime/contracts.d.ts +0 -1
  491. package/packages/core/dist/evidence-runtime/coordination.js +110 -0
  492. package/packages/core/dist/evidence-runtime/coordination.js.map +1 -0
  493. package/packages/core/dist/execution/host-invocation.js +83 -83
  494. package/packages/core/dist/instructions.d.ts +1 -1
  495. package/packages/core/dist/instructions.js +37 -80
  496. package/packages/core/dist/instructions.js.map +1 -1
  497. package/packages/core/dist/lifecycle/ship.js +58 -68
  498. package/packages/core/dist/lifecycle/ship.js.map +1 -1
  499. package/packages/core/dist/lifecycle-graph/contracts.d.ts +159 -0
  500. package/packages/core/dist/lifecycle-graph/contracts.js +7 -0
  501. package/packages/core/dist/lifecycle-graph/contracts.js.map +1 -0
  502. package/packages/core/dist/lifecycle-graph/kernel.d.ts +16 -0
  503. package/packages/core/dist/lifecycle-graph/kernel.js +461 -0
  504. package/packages/core/dist/lifecycle-graph/kernel.js.map +1 -0
  505. package/packages/core/dist/lifecycle-graph.d.ts +2 -0
  506. package/packages/core/dist/lifecycle-graph.js +3 -0
  507. package/packages/core/dist/lifecycle-graph.js.map +1 -0
  508. package/packages/core/dist/orchestration/contracts.d.ts +1 -1
  509. package/packages/core/dist/orchestration/runtime.js +21 -28
  510. package/packages/core/dist/orchestration/runtime.js.map +1 -1
  511. package/packages/core/dist/registries/agent-registry.js +124 -40
  512. package/packages/core/dist/registries/agent-registry.js.map +1 -1
  513. package/packages/core/dist/registries/command-team-runtime.d.ts +1 -1
  514. package/packages/core/dist/registries/command-team-runtime.js +6 -13
  515. package/packages/core/dist/registries/command-team-runtime.js.map +1 -1
  516. package/packages/core/dist/registries/plan-scout-domains.d.ts +13 -0
  517. package/packages/core/dist/registries/plan-scout-domains.js +76 -0
  518. package/packages/core/dist/registries/plan-scout-domains.js.map +1 -0
  519. package/packages/core/dist/registries/skill-capabilities.js +7 -7
  520. package/packages/core/dist/registries/skill-capabilities.js.map +1 -1
  521. package/packages/core/dist/registries/tool-capabilities.js +6 -6
  522. package/packages/core/dist/registries/tool-capabilities.js.map +1 -1
  523. package/packages/core/dist/registries/workflow-gates.d.ts +1 -1
  524. package/packages/core/dist/registries/workflow-gates.js +18 -18
  525. package/packages/core/dist/registries/workflow-gates.js.map +1 -1
  526. package/packages/core/dist/risk/consumer-diagnostics.js +2 -1
  527. package/packages/core/dist/risk/consumer-diagnostics.js.map +1 -1
  528. package/packages/core/dist/risk/contracts.d.ts +2 -2
  529. package/packages/core/dist/risk/kernel.js +7 -7
  530. package/packages/core/dist/risk/kernel.js.map +1 -1
  531. package/packages/core/dist/risk/legacy-adapters.js +12 -27
  532. package/packages/core/dist/risk/legacy-adapters.js.map +1 -1
  533. package/packages/core/dist/risk/workflow-gates.js +6 -6
  534. package/packages/core/dist/risk/workflow-gates.js.map +1 -1
  535. package/packages/core/dist/router/agent-runtime-config.js +1 -1
  536. package/packages/core/dist/router/agent-runtime-config.js.map +1 -1
  537. package/packages/core/dist/router/routing.js +2 -4
  538. package/packages/core/dist/router/routing.js.map +1 -1
  539. package/packages/core/dist/router/runtime-import.d.ts +28 -0
  540. package/packages/core/dist/router/runtime-import.js +383 -0
  541. package/packages/core/dist/router/runtime-import.js.map +1 -0
  542. package/packages/core/dist/router/stage-route-binding.d.ts +37 -0
  543. package/packages/core/dist/router/stage-route-binding.js +227 -0
  544. package/packages/core/dist/router/stage-route-binding.js.map +1 -0
  545. package/packages/core/dist/router.d.ts +1 -0
  546. package/packages/core/dist/router.js +1 -0
  547. package/packages/core/dist/router.js.map +1 -1
  548. package/packages/core/dist/run-state/artifacts.d.ts +16 -0
  549. package/packages/core/dist/run-state/artifacts.js +6 -0
  550. package/packages/core/dist/run-state/artifacts.js.map +1 -1
  551. package/packages/core/dist/run-state/model.d.ts +20 -0
  552. package/packages/core/dist/run-state/run-state.js +7 -7
  553. package/packages/core/dist/run-state/run-state.js.map +1 -1
  554. package/packages/core/dist/run-state/task-evidence.d.ts +1 -2
  555. package/packages/core/dist/run-state/task-evidence.js +2 -9
  556. package/packages/core/dist/run-state/task-evidence.js.map +1 -1
  557. package/packages/core/dist/run-state/timing.d.ts +8 -0
  558. package/packages/core/dist/run-state/timing.js +131 -0
  559. package/packages/core/dist/run-state/timing.js.map +1 -0
  560. package/packages/core/dist/runtime-analysis/build.js +1 -4
  561. package/packages/core/dist/runtime-analysis/build.js.map +1 -1
  562. package/packages/core/dist/runtime-analysis/findings.js +0 -39
  563. package/packages/core/dist/runtime-analysis/findings.js.map +1 -1
  564. package/packages/core/dist/runtime-analysis/model.d.ts +1 -17
  565. package/packages/core/dist/runtime-paths.d.ts +10 -0
  566. package/packages/core/dist/runtime-paths.js +65 -0
  567. package/packages/core/dist/runtime-paths.js.map +1 -1
  568. package/packages/core/dist/runtime-projection-p0.d.ts +64 -0
  569. package/packages/core/dist/runtime-projection-p0.js +211 -0
  570. package/packages/core/dist/runtime-projection-p0.js.map +1 -0
  571. package/packages/core/dist/sdd-docs/artifact-depth.d.ts +14 -0
  572. package/packages/core/dist/sdd-docs/artifact-depth.js +179 -0
  573. package/packages/core/dist/sdd-docs/artifact-depth.js.map +1 -0
  574. package/packages/core/dist/sdd-docs/task-parser.d.ts +5 -1
  575. package/packages/core/dist/sdd-docs/task-parser.js +60 -22
  576. package/packages/core/dist/sdd-docs/task-parser.js.map +1 -1
  577. package/packages/core/dist/sdd-docs/task-rendering.js +2 -2
  578. package/packages/core/dist/sdd-docs/task-rendering.js.map +1 -1
  579. package/packages/core/dist/spec-entry.js +40 -0
  580. package/packages/core/dist/spec-entry.js.map +1 -0
  581. package/packages/core/dist/spec-manager-contracts.d.ts +12 -0
  582. package/packages/core/dist/spec-manager-contracts.js +2 -0
  583. package/packages/core/dist/spec-manager-contracts.js.map +1 -0
  584. package/packages/core/dist/stage-artifacts.d.ts +55 -0
  585. package/packages/core/dist/stage-artifacts.js +315 -0
  586. package/packages/core/dist/stage-artifacts.js.map +1 -0
  587. package/packages/core/dist/stage-collaboration-contracts.d.ts +55 -0
  588. package/packages/core/dist/stage-collaboration-contracts.js +238 -0
  589. package/packages/core/dist/stage-collaboration-contracts.js.map +1 -0
  590. package/packages/core/dist/stage-collaboration.d.ts +736 -0
  591. package/packages/core/dist/stage-collaboration.js +4018 -0
  592. package/packages/core/dist/stage-collaboration.js.map +1 -0
  593. package/packages/core/dist/stage-runtime/runtime.js +8 -1
  594. package/packages/core/dist/stage-runtime/runtime.js.map +1 -1
  595. package/packages/core/dist/status/project-status.js +25 -1
  596. package/packages/core/dist/status/project-status.js.map +1 -1
  597. package/packages/core/dist/storage/runtime-store.d.ts +170 -18
  598. package/packages/core/dist/storage/runtime-store.js +597 -85
  599. package/packages/core/dist/storage/runtime-store.js.map +1 -1
  600. package/packages/core/dist/sync-back/apply.d.ts +1 -17
  601. package/packages/core/dist/sync-back/apply.js +1 -242
  602. package/packages/core/dist/sync-back/apply.js.map +1 -1
  603. package/packages/core/dist/sync-back/inspect.d.ts +1 -110
  604. package/packages/core/dist/sync-back/inspect.js +1 -496
  605. package/packages/core/dist/sync-back/inspect.js.map +1 -1
  606. package/packages/core/dist/sync-back.d.ts +1 -2
  607. package/packages/core/dist/sync-back.js +1 -2
  608. package/packages/core/dist/sync-back.js.map +1 -1
  609. package/packages/core/dist/task-execution-contract.d.ts +167 -0
  610. package/packages/core/dist/task-execution-contract.js +377 -0
  611. package/packages/core/dist/task-execution-contract.js.map +1 -0
  612. package/packages/core/dist/test-support/fixtures.js +329 -314
  613. package/packages/core/dist/test-support/fixtures.js.map +1 -1
  614. package/packages/core/dist/test-support/run-state.d.ts +1 -0
  615. package/packages/core/dist/test-support/run-state.js +31 -0
  616. package/packages/core/dist/test-support/run-state.js.map +1 -1
  617. package/packages/core/dist/truth-reconciliation.d.ts +44 -0
  618. package/packages/core/dist/truth-reconciliation.js +135 -0
  619. package/packages/core/dist/truth-reconciliation.js.map +1 -0
  620. package/packages/core/dist/tsconfig.tsbuildinfo +1 -1
  621. package/packages/core/dist/verification/goal-verify.d.ts +0 -49
  622. package/packages/core/dist/verification/goal-verify.js +1 -545
  623. package/packages/core/dist/verification/goal-verify.js.map +1 -1
  624. package/packages/core/dist/verification/rendering.d.ts +5 -7
  625. package/packages/core/dist/verification/rendering.js +15 -55
  626. package/packages/core/dist/verification/rendering.js.map +1 -1
  627. package/packages/core/dist/verification/single-task-loop.js +1 -40
  628. package/packages/core/dist/verification/single-task-loop.js.map +1 -1
  629. package/packages/core/dist/verification/task-evidence-judgment.d.ts +49 -0
  630. package/packages/core/dist/verification/task-evidence-judgment.js +521 -0
  631. package/packages/core/dist/verification/task-evidence-judgment.js.map +1 -0
  632. package/packages/core/dist/verification/test-runtime.d.ts +12 -2
  633. package/packages/core/dist/verification/test-runtime.js +247 -112
  634. package/packages/core/dist/verification/test-runtime.js.map +1 -1
  635. package/packages/core/dist/verification/validation-cache.d.ts +26 -0
  636. package/packages/core/dist/verification/validation-cache.js +73 -0
  637. package/packages/core/dist/verification/validation-cache.js.map +1 -0
  638. package/packages/core/dist/verification/verify-contract.d.ts +1 -1
  639. package/packages/core/dist/verification/verify-contract.js +49 -72
  640. package/packages/core/dist/verification/verify-contract.js.map +1 -1
  641. package/packages/core/dist/verification.d.ts +3 -3
  642. package/packages/core/dist/verification.js +2 -2
  643. package/packages/core/dist/verification.js.map +1 -1
  644. package/packages/core/dist/workflow-gate/evidence-packet.js +2 -7
  645. package/packages/core/dist/workflow-gate/evidence-packet.js.map +1 -1
  646. package/packages/core/dist/workflow-gate/hard-checks.js +0 -7
  647. package/packages/core/dist/workflow-gate/hard-checks.js.map +1 -1
  648. package/packages/core/dist/workflow-gate/policy.js +2 -4
  649. package/packages/core/dist/workflow-gate/policy.js.map +1 -1
  650. package/packages/core/dist/workflow-gate/types.d.ts +3 -5
  651. package/packages/core/dist/workflow-state/latest-eligible-run.js +30 -4
  652. package/packages/core/dist/workflow-state/latest-eligible-run.js.map +1 -1
  653. package/packages/core/dist/workflow-state/migration-recovery.d.ts +40 -0
  654. package/packages/core/dist/workflow-state/migration-recovery.js +110 -0
  655. package/packages/core/dist/workflow-state/migration-recovery.js.map +1 -0
  656. package/packages/core/dist/workflow-state/repair-contract.d.ts +12 -0
  657. package/packages/core/dist/workflow-state/repair-contract.js +63 -0
  658. package/packages/core/dist/workflow-state/repair-contract.js.map +1 -0
  659. package/packages/core/dist/workflow-state/resolve-task-run.d.ts +21 -0
  660. package/packages/core/dist/workflow-state/resolve-task-run.js +95 -0
  661. package/packages/core/dist/workflow-state/resolve-task-run.js.map +1 -0
  662. package/packages/core/dist/workflow-state/resolve.d.ts +55 -5
  663. package/packages/core/dist/workflow-state/resolve.js +518 -36
  664. package/packages/core/dist/workflow-state/resolve.js.map +1 -1
  665. package/packages/core/dist/workflow-state/runtime-projections.d.ts +228 -0
  666. package/packages/core/dist/workflow-state/runtime-projections.js +452 -0
  667. package/packages/core/dist/workflow-state/runtime-projections.js.map +1 -0
  668. package/packages/core/package.json +6 -3
  669. package/tsconfig.build.json +6 -7
  670. package/node_modules/@sdd-agent-platform/core/dist/doctor/render.d.ts +0 -2
  671. package/node_modules/@sdd-agent-platform/core/dist/doctor/render.js +0 -44
  672. package/node_modules/@sdd-agent-platform/core/dist/doctor/render.js.map +0 -1
  673. package/node_modules/@sdd-agent-platform/core/src/sync-back/apply.ts +0 -270
  674. package/node_modules/@sdd-agent-platform/core/src/sync-back/inspect.ts +0 -655
  675. package/node_modules/@sdd-agent-platform/core/src/sync-back/sync-back.test.ts +0 -569
  676. package/node_modules/@sdd-agent-platform/core/src/sync-back.ts +0 -2
  677. package/node_modules/@sdd-agent-platform/core/src/verification/single-task-loop.test.ts +0 -255
  678. package/node_modules/@sdd-agent-platform/core/src/verification/test-runtime.test.ts +0 -439
  679. package/node_modules/@sdd-agent-platform/core/src/verification/validation-wave.test.ts +0 -341
  680. package/node_modules/@sdd-agent-platform/core/src/verification/verify-contract.test.ts +0 -204
  681. package/packages/cli/dist/commands/lifecycle.d.ts +0 -6
  682. package/packages/cli/dist/commands/lifecycle.js +0 -112
  683. package/packages/cli/dist/commands/lifecycle.js.map +0 -1
  684. package/packages/cli/dist/commands/sync-back.d.ts +0 -6
  685. package/packages/cli/dist/commands/sync-back.js +0 -82
  686. package/packages/cli/dist/commands/sync-back.js.map +0 -1
  687. package/packages/cli/dist/commands/test.d.ts +0 -6
  688. package/packages/cli/dist/commands/test.js +0 -195
  689. package/packages/cli/dist/commands/test.js.map +0 -1
  690. package/packages/cli/dist/commands/verifies.d.ts +0 -6
  691. package/packages/cli/dist/commands/verifies.js +0 -85
  692. package/packages/cli/dist/commands/verifies.js.map +0 -1
  693. package/packages/cli/dist/commands/verify.d.ts +0 -6
  694. package/packages/cli/dist/commands/verify.js +0 -134
  695. package/packages/cli/dist/commands/verify.js.map +0 -1
  696. package/packages/core/dist/doctor/render.d.ts +0 -2
  697. package/packages/core/dist/doctor/render.js +0 -44
  698. package/packages/core/dist/doctor/render.js.map +0 -1
@@ -1,1032 +1,1190 @@
1
- import { createHash } from 'node:crypto';
2
- import { spawn } from 'node:child_process';
3
- import { appendEvent } from '../run-state/events.js';
4
- import { appendInvocationLedgerEntry } from '../run-state/invocation-ledger.js';
5
- import type { RunState, RunStateTaskRuntime } from '../run-state/model.js';
6
- import { createRun, readRunState, writeRunState } from '../run-state/run-state.js';
7
- import { writeArtifact } from '../run-state/artifacts.js';
8
- import { resolveSddContext } from '../sdd-docs/context.js';
9
- import { bindRunStateToTask } from '../sdd-docs/run-binding.js';
10
- import { parseSddBranch, type SddTask } from '../sdd-docs/task-parser.js';
11
- import { inspectSddTask } from '../sdd-docs/task-inspection.js';
12
- import { recordRuntimeAcceptanceEvidenceMap, recordRuntimeDurableGap, recordRuntimeProjection, recordRuntimeTestRun, recordRuntimeTestStep, recordRuntimeValidationEnvironmentSession, recordRuntimeValidationWaveRun, runtimeScopedId, updateRuntimeDurableGapStatus } from '../storage/runtime-store.js';
13
- import { ACCEPTANCE_POLICY_RULESET_VERSION, SDD_EVIDENCE_CONTRACT, SDD_EVIDENCE_VERSION, SDD_RESULT_CONTRACT, SDD_RESULT_VERSION, TEST_EVIDENCE_RUN_CONTRACT_VERSION, WORKFLOW_HANDOFF_CONTRACT_VERSION } from '../contracts.js';
14
- import type { LifecycleRiskDecision } from '../risk/contracts.js';
15
- import { inspectVerifyContract, type VerifyContractInspection } from './verify-contract.js';
16
- import type { AcceptanceEvidenceCoverage, CapabilityEvidenceClassification, EvidenceCoverageStatus, TestEvidenceStatus, UnifiedTestEvidenceRun } from '../evidence-runtime.js';
17
- import { ensureTaskOrchestration, inspectOrchestrationGate } from '../orchestration/runtime.js';
18
- import { recordStageRunProjection, recordWorkflowHandoffProjection, validateWorkflowHandoff } from '../stage-runtime/runtime.js';
19
- import type { StageRun, WorkflowHandoff } from '../stage-runtime/contracts.js';
20
- import { evaluateTaskWorkflowGate, verifyContractBlockedGate, type ApprovalPolicy, type LifecycleRiskProfile, type LifecycleWorkflowGate } from '../risk.js';
21
- import { validateSddResultArtifact } from '../artifacts/sdd-result.js';
22
- import { dependencyBlockingReasonsForTask } from '../workflow-state/dependencies.js';
23
- import { routeSddTask } from '../router/route-sdd-task.js';
24
- import type { AgentCapabilityRouteDecision } from '../router/agent-runtime.js';
25
- import { evaluateAndRecordWorkflowGateDecision } from '../workflow-gate/evidence-packet.js';
26
- import type { WorkflowGateDecision, WorkflowGateStatus } from '../workflow-gate/types.js';
27
-
28
- const DEFAULT_TEST_TIMEOUT_MS = 120_000;
29
- const MAX_CAPTURE_BYTES = 256 * 1024;
30
-
31
- export type SddTestStatus = 'PASS' | 'FAIL' | 'BLOCKED';
32
- export type SddTestStepStatus = 'pass' | 'fail' | 'blocked';
33
-
34
- export interface SddTestCommandInput {
35
- command?: string;
36
- argv?: string[];
37
- }
38
-
39
- export interface SddTestCommandStep {
40
- stepId: string;
41
- command: string;
42
- argv: string[] | null;
43
- shell: boolean;
44
- acceptanceRefs: string[];
45
- status: SddTestStepStatus;
46
- exitCode: number | null;
47
- signal: string | null;
48
- durationMs: number;
49
- outputArtifact: string;
50
- stdoutBytes: number;
51
- stderrBytes: number;
52
- truncated: boolean;
53
- }
54
-
55
- interface NormalizedSddTestCommand {
56
- command: string;
57
- argv: string[] | null;
58
- shell: boolean;
59
- }
60
-
61
- type VerifyContractAction = 'none' | 'created' | 'refreshed' | 'blocked';
62
-
63
- type RuntimeTestJudgment = WorkflowGateStatus;
64
-
65
- export interface SddTestResult {
66
- contract: 'sdd-test-runtime-v1';
67
- runId: string;
68
- testRunId: string;
69
- validationWaveRunId: string;
70
- validationEnvironmentSessionId: string;
71
- branch: string;
72
- taskId: string;
73
- status: SddTestStatus;
74
- validationStatus: SddTestStatus;
75
- workflowGateStatus: WorkflowGateStatus;
76
- runtimeJudgment: RuntimeTestJudgment;
77
- workflowGateDecision: WorkflowGateDecision;
78
- verifyContractStatus: string;
79
- verifyContractAction: VerifyContractAction;
80
- lifecycleGate: LifecycleWorkflowGate;
81
- lifecycleProfile: LifecycleRiskProfile | null;
82
- approvalPolicy: ApprovalPolicy | null;
83
- requiredStages: string[];
84
- primaryReason: string;
85
- commandStatus: TestEvidenceStatus;
86
- evidenceCoverage: EvidenceCoverageStatus;
87
- policyJudgment: TestEvidenceStatus;
88
- acceptanceCoverage: AcceptanceEvidenceCoverage[];
89
- capabilityEvidence: CapabilityEvidenceClassification[];
90
- syncBackReady: boolean;
91
- commands: string[];
92
- steps: SddTestCommandStep[];
93
- validationArtifact: string | null;
94
- indexArtifact: string | null;
95
- gaps: string[];
96
- next: string;
97
- }
98
-
99
- export interface RunSddTestOptions {
100
- taskId: string;
101
- branch?: string | null;
102
- runId?: string | null;
103
- commands?: string[];
104
- commandInputs?: SddTestCommandInput[];
105
- timeoutMs?: number;
106
- approved?: boolean;
107
- validationWave?: { waveRunId: string; environmentSessionId: string; taskIds: string[]; acceptanceRefsByTask?: Record<string, string[]> };
108
- }
109
-
110
- export async function runSddTest(projectRoot: string, options: RunSddTestOptions): Promise<SddTestResult> {
111
- const context = await resolveSddContext(projectRoot, { branch: options.branch ?? undefined, branchSource: options.branch ? 'cli_option' : undefined });
112
- const model = await parseSddBranch(projectRoot, context.partition);
113
- const inspected = inspectSddTask(model, options.taskId);
114
- const task = inspected.task;
115
- const verifyContract = await ensureVerifyContractForTest(projectRoot, context.partition);
116
- const verifyInspection = verifyContract.inspection;
117
- const initialState = options.runId ? await readRunState(projectRoot, options.runId) : await createRun(projectRoot);
118
- const state = await bindRunStateToTask(projectRoot, initialState, context, model, task, options.taskId);
119
- const testRunId = runtimeScopedId(state.runId, options.taskId, new Date().toISOString(), 'sdd-test');
120
- const commandInputs = normalizeTestCommandInputs(options.commandInputs, options.commands, task?.validation ?? []);
121
- const commands = commandInputs.map((input) => input.command);
122
- const gaps: string[] = [];
123
- const startedAt = new Date().toISOString();
124
- const ownsValidationWave = !options.validationWave;
125
- const validationWaveRunId = options.validationWave?.waveRunId ?? runtimeScopedId(context.partition, options.taskId, state.runId, testRunId, 'validation-wave');
126
- const validationEnvironmentSessionId = options.validationWave?.environmentSessionId ?? runtimeScopedId(context.partition, validationWaveRunId, 'validation-env');
127
- const validationWaveTaskIds = options.validationWave?.taskIds ?? [options.taskId];
128
- const validationWaveAcceptanceRefs = options.validationWave?.acceptanceRefsByTask?.[options.taskId];
129
- if (ownsValidationWave) {
130
- await recordRuntimeValidationEnvironmentSession(projectRoot, {
131
- sessionId: validationEnvironmentSessionId,
132
- partition: context.partition,
133
- runId: state.runId,
134
- waveRunId: validationWaveRunId,
135
- status: 'active',
136
- reuseKey: `${context.partition}:${options.taskId}`,
137
- createdAt: startedAt,
138
- updatedAt: startedAt,
139
- payload: { contract: 'phase-8.17-validation-wave-runtime-v1', mode: 'single-task' }
140
- });
141
- await recordRuntimeValidationWaveRun(projectRoot, {
142
- waveRunId: validationWaveRunId,
143
- partition: context.partition,
144
- runId: state.runId,
145
- taskIds: validationWaveTaskIds,
146
- status: 'RUNNING',
147
- environmentSessionId: validationEnvironmentSessionId,
148
- startedAt,
149
- completedAt: startedAt,
150
- payload: { contract: 'phase-8.17-validation-wave-runtime-v1', mode: 'single-task', taskId: options.taskId }
151
- });
152
- }
153
-
154
- await appendEvent(projectRoot, state.runId, {
155
- event: 'test_runtime_started',
156
- runId: state.runId,
157
- summary: `SDD test runtime started for ${options.taskId}`,
158
- data: { taskId: options.taskId, branch: context.partition, testRunId, commands }
159
- });
160
-
161
- if (!task) {
162
- gaps.push(`Task ${options.taskId} was not found in specs/${context.partition}/tasks.md.`);
163
- }
164
- if (task) {
165
- gaps.push(...inspected.gaps.filter((gap) => gap.severity === 'blocking').map((gap) => `${gap.field}: ${gap.message}`));
166
- gaps.push(...dependencyBlockingReasonsForTask(model, options.taskId));
167
- }
168
- if (verifyContract.action === 'blocked') {
169
- gaps.push(verifyContractBlocker(verifyInspection));
170
- }
171
- if (commands.length === 0) {
172
- gaps.push(`Task ${options.taskId} has no validation commands.`);
173
- }
174
- const orchestration = await ensureTaskOrchestration(projectRoot, model, task, {
175
- branch: context.partition,
176
- runId: state.runId,
177
- taskId: options.taskId,
178
- agent: 'validator',
179
- stage: 'test',
180
- status: 'active'
181
- });
182
- const orchestrationGate = await inspectOrchestrationGate(projectRoot, {
183
- branch: context.partition,
184
- runId: state.runId,
185
- taskId: options.taskId,
186
- target: 'test',
187
- riskDecision: orchestration.riskDecision,
188
- stageRun: orchestration.stageRun,
189
- contextLoadSignal: orchestration.contextLoadSignal,
190
- contextOffloadDecision: orchestration.contextOffloadDecision
191
- });
192
- const reviewerCheckpointSatisfied = await hasReviewerCheckpoint(projectRoot, state, options.taskId);
193
- const workflowGate = verifyContract.action === 'blocked'
194
- ? verifyContractBlockedGate(options.taskId)
195
- : evaluateTaskWorkflowGate({ task, taskId: options.taskId, riskDecision: orchestration.riskDecision, approved: options.approved, reviewerCheckpointSatisfied });
196
- if (workflowGate.blocksTest) {
197
- gaps.push(workflowGate.primaryReason);
198
- }
199
- gaps.push(...orchestrationGate.blockingReasons);
200
-
201
- await recordRuntimeTestRun(projectRoot, {
202
- testRunId,
203
- runId: state.runId,
204
- partition: context.partition,
205
- taskId: options.taskId,
206
- status: 'RUNNING',
207
- startedAt,
208
- completedAt: startedAt,
209
- payload: { verifyContractStatus: verifyInspection.status, verifyContractAction: verifyContract.action, lifecycleGate: workflowGate.lifecycleGate, lifecycleProfile: workflowGate.lifecycleProfile, approvalPolicy: workflowGate.approvalPolicy, requiredStages: workflowGate.requiredStages, primaryReason: workflowGate.primaryReason, commands, commandInputs, evidence: [], gaps }
210
- });
211
-
212
- const steps: SddTestCommandStep[] = [];
213
- if (gaps.length === 0) {
214
- for (const [index, commandInput] of commandInputs.entries()) {
215
- const step = await runCommandStep(projectRoot, state.runId, context.partition, options.taskId, testRunId, index + 1, commandInput, acceptanceRefsForCommand(task, commandInput.command, validationWaveAcceptanceRefs), options.timeoutMs ?? DEFAULT_TEST_TIMEOUT_MS);
216
- steps.push(step);
217
- await appendEvent(projectRoot, state.runId, {
218
- event: 'test_step_completed',
219
- runId: state.runId,
220
- summary: `SDD test step ${step.status}: ${step.command}`,
221
- data: { taskId: options.taskId, testRunId, step }
222
- });
223
- }
224
- }
225
-
226
- const commandStatus = deriveCommandStatus(gaps, steps);
227
- const acceptanceCoverage = buildAcceptanceCoverage(task, steps, commandStatus, validationWaveAcceptanceRefs);
228
- const evidenceCoverage = summarizeEvidenceCoverage(acceptanceCoverage);
229
- const policyJudgment = derivePolicyJudgment(commandStatus, evidenceCoverage);
230
- await recordAcceptanceEvidenceMaps(projectRoot, validationWaveRunId, testRunId, context.partition, state.runId, options.taskId, acceptanceCoverage);
231
- const validationStatus = policyJudgment;
232
- const syncBackReady = false;
233
- const capabilityRoute = task ? await routeSddTask(projectRoot, { taskId: options.taskId, branch: context.partition, approved: options.approved }) : null;
234
- const capabilityEvidence = buildCapabilityEvidenceClassification(capabilityRoute?.capabilityDecision ?? null, steps);
235
- const validationArtifact = task ? await writeValidationArtifact(projectRoot, state.runId, task, validationStatus, steps, gaps, capabilityEvidence) : null;
236
- const evidenceBeforeIndex = [validationArtifact?.runRelativePath, ...steps.map((step) => step.outputArtifact)].filter((item): item is string => Boolean(item));
237
- await persistTestRunState(projectRoot, state, options.taskId, validationStatus, commands, evidenceBeforeIndex, validationArtifact?.runRelativePath ?? null);
238
- await resolveTestRuntimeDurableGap(projectRoot, context.partition, state.runId, options.taskId, validationStatus, gaps);
239
-
240
- const gateDecision = (await evaluateAndRecordWorkflowGateDecision(projectRoot, {
241
- branch: context.partition,
242
- taskId: options.taskId,
243
- runId: state.runId,
244
- decisionKind: 'test'
245
- })).decision;
246
- const runtimeJudgment = gateDecision.status;
247
- const status = finalStatusForTest(validationStatus, runtimeJudgment);
248
- await recordTestRuntimeDurableGap(projectRoot, context.partition, state.runId, options.taskId, status, validationStatus, runtimeJudgment, gaps, evidenceBeforeIndex);
249
- const unifiedEvidence = buildUnifiedTestEvidenceRun(testRunId, context.partition, state.runId, options.taskId, commandStatus, evidenceCoverage, policyJudgment, status, runtimeJudgment, steps, acceptanceCoverage, capabilityEvidence, syncBackReady, gaps, workflowGate.nextAction, gateDecision);
250
- const indexArtifact = await writeIndexArtifact(projectRoot, state.runId, {
251
- testRunId,
252
- validationWaveRunId,
253
- validationEnvironmentSessionId,
254
- branch: context.partition,
255
- taskId: options.taskId,
256
- status,
257
- validationStatus,
258
- workflowGateStatus: gateDecision.status,
259
- runtimeJudgment,
260
- workflowGateDecision: gateDecision,
261
- verifyContractStatus: verifyInspection.status,
262
- verifyContractAction: verifyContract.action,
263
- lifecycleGate: workflowGate.lifecycleGate,
264
- lifecycleProfile: workflowGate.lifecycleProfile,
265
- approvalPolicy: workflowGate.approvalPolicy,
266
- requiredStages: workflowGate.requiredStages,
267
- primaryReason: workflowGate.primaryReason,
268
- commandStatus,
269
- evidenceCoverage,
270
- policyJudgment,
271
- acceptanceCoverage,
272
- capabilityEvidence,
273
- syncBackReady,
274
- commands,
275
- steps,
276
- validationArtifact: validationArtifact?.runRelativePath ?? null,
277
- gaps
278
- });
279
- const completedAt = new Date().toISOString();
280
- const evidence = [validationArtifact?.runRelativePath, indexArtifact.runRelativePath, ...steps.map((step) => step.outputArtifact)].filter((item): item is string => Boolean(item));
281
-
282
- await recordRuntimeTestRun(projectRoot, {
283
- testRunId,
284
- runId: state.runId,
285
- partition: context.partition,
286
- taskId: options.taskId,
287
- status,
288
- startedAt,
289
- completedAt,
290
- payload: { verifyContractStatus: verifyInspection.status, verifyContractAction: verifyContract.action, lifecycleGate: workflowGate.lifecycleGate, lifecycleProfile: workflowGate.lifecycleProfile, approvalPolicy: workflowGate.approvalPolicy, requiredStages: workflowGate.requiredStages, primaryReason: workflowGate.primaryReason, commandStatus, evidenceCoverage, policyJudgment, validationStatus, workflowGateStatus: gateDecision.status, runtimeJudgment, workflowGateDecision: gateDecision, acceptanceCoverage, capabilityEvidence, syncBackReady, commands, commandInputs, evidence, gaps }
291
- });
292
- await recordRuntimeProjection(projectRoot, 'test_runtime', `${context.partition}:${options.taskId}:${state.runId}`, {
293
- contract: 'sdd-test-runtime-v1',
294
- testRunId,
295
- runId: state.runId,
296
- taskId: options.taskId,
297
- status,
298
- validationStatus,
299
- workflowGateStatus: gateDecision.status,
300
- runtimeJudgment,
301
- lifecycleGate: workflowGate.lifecycleGate,
302
- primaryReason: workflowGate.primaryReason,
303
- evidence,
304
- gaps
305
- });
306
- await recordRuntimeProjection(projectRoot, 'test_evidence_run', `${context.partition}:${options.taskId}:${state.runId}`, unifiedEvidence);
307
- await recordTestWorkflowProjection(projectRoot, {
308
- taskId: options.taskId,
309
- stageRun: orchestration.stageRun,
310
- status,
311
- completedAt,
312
- evidence,
313
- gaps,
314
- riskDecision: orchestration.riskDecision
315
- });
316
- await persistTestGateOutcome(projectRoot, state.runId, options.taskId, status, validationStatus, commands, evidence, validationArtifact?.runRelativePath ?? null, gateDecision);
317
- if (ownsValidationWave) {
318
- await recordRuntimeValidationWaveRun(projectRoot, {
319
- waveRunId: validationWaveRunId,
320
- partition: context.partition,
321
- runId: state.runId,
322
- taskIds: validationWaveTaskIds,
323
- status,
324
- environmentSessionId: validationEnvironmentSessionId,
325
- startedAt,
326
- completedAt,
327
- payload: { contract: 'phase-8.17-validation-wave-runtime-v1', mode: 'single-task', taskId: options.taskId, testRunId, evidence, gaps, workflowGateDecision: gateDecision }
328
- });
329
- await recordRuntimeValidationEnvironmentSession(projectRoot, {
330
- sessionId: validationEnvironmentSessionId,
331
- partition: context.partition,
332
- runId: state.runId,
333
- waveRunId: validationWaveRunId,
334
- status: status === 'PASS' ? 'completed' : status === 'FAIL' ? 'failed' : 'blocked',
335
- reuseKey: `${context.partition}:${options.taskId}`,
336
- createdAt: startedAt,
337
- updatedAt: completedAt,
338
- payload: { contract: 'phase-8.17-validation-wave-runtime-v1', mode: 'single-task', taskId: options.taskId, validationStatus, workflowGateStatus: gateDecision.status, status }
339
- });
340
- }
341
-
342
- await appendEvent(projectRoot, state.runId, {
343
- event: status === 'PASS' ? 'test_runtime_passed' : 'test_runtime_blocked',
344
- runId: state.runId,
345
- summary: `SDD test runtime ${status} for ${options.taskId}`,
346
- data: { taskId: options.taskId, testRunId, status, validationStatus, evidence, gaps, gateDecisionId: gateDecision.decisionId, gateStatus: gateDecision.status }
347
- });
348
-
349
- return {
350
- contract: 'sdd-test-runtime-v1',
351
- runId: state.runId,
352
- testRunId,
353
- validationWaveRunId,
354
- validationEnvironmentSessionId,
355
- branch: context.partition,
356
- taskId: options.taskId,
357
- status,
358
- validationStatus,
359
- workflowGateStatus: gateDecision.status,
360
- runtimeJudgment,
361
- workflowGateDecision: gateDecision,
362
- verifyContractStatus: verifyInspection.status,
363
- verifyContractAction: verifyContract.action,
364
- lifecycleGate: workflowGate.lifecycleGate,
365
- lifecycleProfile: workflowGate.lifecycleProfile,
366
- approvalPolicy: workflowGate.approvalPolicy,
367
- requiredStages: workflowGate.requiredStages,
368
- primaryReason: workflowGate.primaryReason,
369
- commandStatus,
370
- evidenceCoverage,
371
- policyJudgment,
372
- acceptanceCoverage,
373
- capabilityEvidence,
374
- syncBackReady,
375
- commands,
376
- steps,
377
- validationArtifact: validationArtifact?.runRelativePath ?? null,
378
- indexArtifact: indexArtifact.runRelativePath,
379
- gaps,
380
- next: nextForTestResult(status, runtimeJudgment, context.partition, options.taskId, state.runId, indexArtifact.runRelativePath, workflowGate.nextAction, gateDecision)
381
- };
382
- }
383
-
384
- async function recordAcceptanceEvidenceMaps(projectRoot: string, waveRunId: string, testRunId: string, partition: string, runId: string, taskId: string, acceptanceCoverage: AcceptanceEvidenceCoverage[]): Promise<void> {
385
- const createdAt = new Date().toISOString();
386
- for (const coverage of acceptanceCoverage) {
387
- await recordRuntimeAcceptanceEvidenceMap(projectRoot, {
388
- mapId: runtimeScopedId(waveRunId, testRunId, taskId, coverage.acceptanceRef),
389
- waveRunId,
390
- testRunId,
391
- partition,
392
- runId,
393
- taskId,
394
- acceptanceRef: coverage.acceptanceRef,
395
- status: coverage.status,
396
- evidenceRefs: coverage.evidenceRefs.map((ref) => ref.ref),
397
- gaps: coverage.gaps,
398
- createdAt,
399
- payload: coverage
400
- });
401
- }
402
- }
403
-
404
- async function recordTestWorkflowProjection(projectRoot: string, input: { taskId: string; stageRun: StageRun; status: SddTestStatus; completedAt: string; evidence: string[]; gaps: string[]; riskDecision: LifecycleRiskDecision }): Promise<void> {
405
- const outputRefs = input.evidence.map((ref) => ({ kind: 'artifact' as const, ref }));
406
- const completedStage: StageRun = {
407
- ...input.stageRun,
408
- status: input.status === 'PASS' ? 'completed' : input.status === 'FAIL' ? 'failed' : 'blocked',
409
- outputRefs,
410
- blockingReasons: input.status === 'PASS' ? [] : input.gaps.length > 0 ? input.gaps : [`SDD test ${input.status}.`],
411
- updatedAt: input.completedAt
412
- };
413
- await recordStageRunProjection(projectRoot, completedStage);
414
-
415
- const handoff: WorkflowHandoff = {
416
- contract: WORKFLOW_HANDOFF_CONTRACT_VERSION,
417
- id: `${completedStage.id}:handoff:goal-verify`,
418
- scope: completedStage.scope,
419
- fromStage: 'test',
420
- toStage: 'goal-verify',
421
- fromAgent: 'validator',
422
- toAgent: 'verifier',
423
- status: input.status === 'PASS' ? 'proposed' : 'blocked',
424
- outputRefs,
425
- requiredInputRefs: [{ kind: 'task', ref: input.taskId }],
426
- riskDecisionRef: input.stageRun.decisionRefs[0] ?? { kind: 'task', ref: input.taskId },
427
- evidenceRefs: outputRefs,
428
- openQuestions: [],
429
- blockingGaps: input.status === 'PASS' ? [] : completedStage.blockingReasons,
430
- createdAt: input.completedAt,
431
- decidedAt: input.completedAt
432
- };
433
- const validation = validateWorkflowHandoff({ handoff, sourceStageRun: completedStage, lifecycleRiskDecision: input.riskDecision });
434
- await recordWorkflowHandoffProjection(projectRoot, validation.valid ? handoff : { ...handoff, status: 'blocked', blockingGaps: validation.issues, decidedAt: input.completedAt });
435
- }
436
-
437
-
438
- async function ensureVerifyContractForTest(projectRoot: string, branch: string): Promise<{ inspection: VerifyContractInspection; action: VerifyContractAction }> {
439
- const inspection = await inspectVerifyContract(projectRoot, { branch, branchSource: 'cli_option' });
440
- return { inspection, action: inspection.status === 'PASS' ? 'none' : 'blocked' };
441
- }
442
-
443
- function verifyContractBlocker(inspection: VerifyContractInspection): string {
444
- const issueSummary = inspection.issues.map((issue) => `${issue.field}: ${issue.message}`).join(' ');
445
- return `verify.md contract is ${inspection.status}; ${issueSummary || 'inspect verify.md before executing tests.'}`;
446
- }
447
-
448
- async function hasReviewerCheckpoint(projectRoot: string, state: RunState, taskId: string): Promise<boolean> {
449
- const artifactPaths = new Set([
450
- ...state.artifacts
451
- .filter((artifact) => artifact.task === taskId && (artifact.agent === 'reviewer' || artifact.kind === 'review'))
452
- .map((artifact) => artifact.path),
453
- `artifacts/review-${taskId}.md`
454
- ]);
455
-
456
- for (const artifactPath of artifactPaths) {
457
- const report = await validateSddResultArtifact(projectRoot, state.runId, artifactPath, { expectedTask: taskId, expectedAgent: 'reviewer' });
458
- if (report.valid && report.result?.status === 'PASS') {
459
- return true;
460
- }
461
- }
462
-
463
- return false;
464
- }
465
-
466
- export function renderSddTestResult(result: SddTestResult): string {
467
- return [
468
- `SDD test ${result.taskId}`,
469
- '',
470
- resultSentenceForTest(result),
471
- '',
472
- 'Decision:',
473
- `- validation_status=${result.validationStatus}`,
474
- `- workflow_gate_status=${result.workflowGateStatus}`,
475
- `- workflow_gate_decision=${result.workflowGateDecision.decisionId}`,
476
- '',
477
- 'Why:',
478
- `- ${result.primaryReason}`,
479
- `- capability_evidence=${capabilityEvidenceSummary(result.capabilityEvidence)}`,
480
- '',
481
- 'Next:',
482
- `- ${result.next}`
483
- ].join('\n');
484
- }
485
-
486
-
487
- async function runCommandStep(projectRoot: string, runId: string, branch: string, taskId: string, testRunId: string, sequence: number, commandInput: NormalizedSddTestCommand, acceptanceRefs: string[], timeoutMs: number): Promise<SddTestCommandStep> {
488
- const started = Date.now();
489
- const executed = await executeCommand(projectRoot, commandInput, timeoutMs);
490
- const durationMs = Date.now() - started;
491
- const status: SddTestStepStatus = executed.timedOut || executed.error ? 'blocked' : executed.exitCode === 0 ? 'pass' : 'fail';
492
- const stepId = `${testRunId}-${String(sequence).padStart(3, '0')}`;
493
- const output = renderCommandOutput(commandInput, status, executed, durationMs);
494
- const outputArtifact = await writeArtifact(projectRoot, runId, `test-${taskId}-${String(sequence).padStart(3, '0')}.log`, output);
495
- await appendInvocationLedgerEntry(projectRoot, {
496
- runId,
497
- taskId,
498
- branch,
499
- kind: 'command',
500
- ref: commandInput.command,
501
- status,
502
- artifactPath: outputArtifact.runRelativePath,
503
- outputHash: hashDocumentContent(output),
504
- materialRefs: [outputArtifact.runRelativePath],
505
- metadata: {
506
- source: 'sdd-test',
507
- exitCode: executed.exitCode,
508
- durationMs,
509
- stdoutBytes: executed.stdoutBytes,
510
- stderrBytes: executed.stderrBytes,
511
- truncated: executed.truncated,
512
- acceptanceRefs: acceptanceRefs.join(','),
513
- shell: commandInput.shell,
514
- argv: commandInput.argv ? JSON.stringify(commandInput.argv) : null
515
- }
516
- });
517
- const step: SddTestCommandStep = {
518
- stepId,
519
- command: commandInput.command,
520
- argv: commandInput.argv,
521
- shell: commandInput.shell,
522
- acceptanceRefs,
523
- status,
524
- exitCode: executed.exitCode,
525
- signal: executed.signal,
526
- durationMs,
527
- outputArtifact: outputArtifact.runRelativePath,
528
- stdoutBytes: executed.stdoutBytes,
529
- stderrBytes: executed.stderrBytes,
530
- truncated: executed.truncated
531
- };
532
- await recordRuntimeTestStep(projectRoot, {
533
- stepId,
534
- testRunId,
535
- runId,
536
- taskId,
537
- command: commandInput.command,
538
- status,
539
- exitCode: executed.exitCode,
540
- durationMs,
541
- outputArtifact: outputArtifact.runRelativePath,
542
- payload: step
543
- });
544
- return step;
545
- }
546
-
547
- function executeCommand(projectRoot: string, commandInput: NormalizedSddTestCommand, timeoutMs: number): Promise<{ exitCode: number | null; signal: string | null; stdout: string; stderr: string; stdoutBytes: number; stderrBytes: number; truncated: boolean; timedOut: boolean; error: string | null }> {
548
- return new Promise((resolve) => {
549
- const child = commandInput.argv
550
- ? spawn(commandInput.argv[0], commandInput.argv.slice(1), { cwd: projectRoot, shell: false, windowsHide: true, env: process.env })
551
- : spawn(commandInput.command, { cwd: projectRoot, shell: true, windowsHide: true, env: process.env });
552
- let stdout = '';
553
- let stderr = '';
554
- let stdoutBytes = 0;
555
- let stderrBytes = 0;
556
- let truncated = false;
557
- let settled = false;
558
- let timedOut = false;
559
- const timer = setTimeout(() => {
560
- timedOut = true;
561
- child.kill();
562
- }, timeoutMs);
563
- const finish = (result: { exitCode: number | null; signal: string | null; error: string | null }) => {
564
- if (settled) {
565
- return;
566
- }
567
- settled = true;
568
- clearTimeout(timer);
569
- resolve({ ...result, stdout, stderr, stdoutBytes, stderrBytes, truncated, timedOut });
570
- };
571
- child.stdout?.on('data', (chunk: Buffer) => {
572
- stdoutBytes += chunk.length;
573
- const next = chunk.toString('utf8');
574
- if (Buffer.byteLength(stdout, 'utf8') < MAX_CAPTURE_BYTES) {
575
- stdout += next;
576
- } else {
577
- truncated = true;
578
- }
579
- });
580
- child.stderr?.on('data', (chunk: Buffer) => {
581
- stderrBytes += chunk.length;
582
- const next = chunk.toString('utf8');
583
- if (Buffer.byteLength(stderr, 'utf8') < MAX_CAPTURE_BYTES) {
584
- stderr += next;
585
- } else {
586
- truncated = true;
587
- }
588
- });
589
- child.on('error', (error) => finish({ exitCode: null, signal: null, error: error.message }));
590
- child.on('close', (code, signal) => finish({ exitCode: code, signal, error: null }));
591
- });
592
- }
593
-
594
- async function writeValidationArtifact(projectRoot: string, runId: string, task: SddTask, status: SddTestStatus, steps: SddTestCommandStep[], gaps: string[], capabilityEvidence: CapabilityEvidenceClassification[]): Promise<{ absolutePath: string; runRelativePath: string }> {
595
- const artifactPath = `test-validation-${task.id}.md`;
596
- const runRelativePath = `artifacts/${artifactPath}`;
597
- const resultStatus = status === 'PASS' ? 'PASS' : status === 'FAIL' ? 'FAIL' : 'BLOCKED';
598
- const content = `# Test Validation ${task.id}\n\n\`\`\`sdd-result\ncontract: ${SDD_RESULT_CONTRACT}\nversion: ${SDD_RESULT_VERSION}\nagent: validator\ntask: ${task.id}\nstatus: ${resultStatus}\nartifacts:\n - ${runRelativePath}\n\`\`\`\n\n## Test Runtime\n\n- status: ${status}\n- commands:\n${steps.length > 0 ? steps.map((step) => ` - [${step.status}] ${step.command}`).join('\n') : ' - none'}\n- gaps:\n${gaps.length > 0 ? gaps.map((gap) => ` - ${gap}`).join('\n') : ' - none'}\n\n## Capability Evidence Classification\n\n${renderCapabilityEvidenceClassification(capabilityEvidence)}\n\n## Acceptance Evidence\n\n${renderEvidenceBlocks(task, status, runRelativePath, steps)}\n`;
599
- return writeArtifact(projectRoot, runId, artifactPath, content);
600
- }
601
-
602
-
603
- async function writeIndexArtifact(projectRoot: string, runId: string, payload: Omit<SddTestResult, 'contract' | 'runId' | 'indexArtifact' | 'next'>): Promise<{ absolutePath: string; runRelativePath: string }> {
604
- return writeArtifact(projectRoot, runId, `test-index-${payload.taskId}.json`, `${JSON.stringify({ contract: 'sdd-test-runtime-v1', runId, ...payload }, null, 2)}\n`);
605
- }
606
-
607
- async function persistTestRunState(projectRoot: string, state: RunState, taskId: string, validationStatus: SddTestStatus, commands: string[], evidence: string[], validationArtifact: string | null): Promise<void> {
608
- const latest = await readRunState(projectRoot, state.runId);
609
- const knownArtifacts = new Set(latest.artifacts.map((artifact) => artifact.path));
610
- const now = new Date().toISOString();
611
- const nextArtifacts = evidence
612
- .filter((artifactPath) => !knownArtifacts.has(artifactPath))
613
- .map((artifactPath) => ({ path: artifactPath, kind: testArtifactKind(artifactPath), task: taskId, agent: 'test-runtime', createdAt: now }));
614
- const existingTaskState = latest.tasks[taskId];
615
- await writeRunState(projectRoot, {
616
- ...latest,
617
- status: validationStatus === 'PASS' ? 'running' : validationStatus === 'FAIL' ? 'failed' : 'blocked',
618
- phase: 'test',
619
- currentTask: taskId,
620
- tasks: {
621
- ...latest.tasks,
622
- [taskId]: {
623
- ...baseRuntimeTaskState(existingTaskState),
624
- status: validationStatus === 'PASS' ? 'validation_passed_pending_gate' : validationStatus === 'FAIL' ? 'validation_failed' : 'validation_blocked',
625
- implementationStatus: existingTaskState?.implementationStatus ?? 'implemented',
626
- verificationStatus: verificationStatusFromTest(validationStatus),
627
- testStatus: validationStatus,
628
- evidence
629
- }
630
- },
631
- artifacts: [...latest.artifacts, ...nextArtifacts],
632
- validation: {
633
- status: validationStatus === 'PASS' ? 'pass' : validationStatus === 'FAIL' ? 'fail' : 'blocked',
634
- commands,
635
- evidence
636
- },
637
- syncBack: latest.syncBack
638
- });
639
- }
640
-
641
- async function persistTestGateOutcome(projectRoot: string, runId: string, taskId: string, status: SddTestStatus, validationStatus: SddTestStatus, commands: string[], evidence: string[], validationArtifact: string | null, gateDecision: WorkflowGateDecision): Promise<void> {
642
- const latest = await readRunState(projectRoot, runId);
643
- const knownArtifacts = new Set(latest.artifacts.map((artifact) => artifact.path));
644
- const now = new Date().toISOString();
645
- const nextArtifacts = evidence
646
- .filter((artifactPath) => !knownArtifacts.has(artifactPath))
647
- .map((artifactPath) => ({ path: artifactPath, kind: testArtifactKind(artifactPath), task: taskId, agent: 'test-runtime', createdAt: now }));
648
- const existingTaskState = latest.tasks[taskId];
649
- await writeRunState(projectRoot, {
650
- ...latest,
651
- status: status === 'PASS' ? 'completed' : status === 'FAIL' ? 'failed' : 'blocked',
652
- phase: 'test',
653
- currentTask: taskId,
654
- tasks: {
655
- ...latest.tasks,
656
- [taskId]: {
657
- ...baseRuntimeTaskState(existingTaskState),
658
- status: runtimeTaskStatusAfterGate(status, validationStatus),
659
- implementationStatus: existingTaskState?.implementationStatus ?? 'implemented',
660
- verificationStatus: verificationStatusFromTest(validationStatus),
661
- testStatus: status,
662
- validationStatus,
663
- workflowGateStatus: gateDecision.status,
664
- workflowGateDecisionId: gateDecision.decisionId,
665
- evidence
666
- }
667
- },
668
- artifacts: [...latest.artifacts, ...nextArtifacts],
669
- validation: {
670
- status: validationStatus === 'PASS' ? 'pass' : validationStatus === 'FAIL' ? 'fail' : 'blocked',
671
- commands,
672
- evidence
673
- },
674
- syncBack: latest.syncBack
675
- });
676
- }
677
-
678
- function testArtifactKind(artifactPath: string): string {
679
- const fileName = artifactPath.split('/').pop() ?? artifactPath;
680
- if (fileName.startsWith('test-validation-')) {
681
- return 'test-validation';
682
- }
683
- if (fileName.startsWith('test-index-')) {
684
- return 'test-index';
685
- }
686
- return 'test';
687
- }
688
-
689
- function baseRuntimeTaskState(existing: RunStateTaskRuntime | undefined): RunStateTaskRuntime {
690
- return {
691
- status: existing?.status ?? 'not_started',
692
- implementationStatus: existing?.implementationStatus ?? 'not_started',
693
- verificationStatus: existing?.verificationStatus ?? 'not_run',
694
- validationBatch: existing?.validationBatch ?? null,
695
- validationTiming: existing?.validationTiming ?? 'task_end',
696
- requiresVerifyBeforeNext: existing?.requiresVerifyBeforeNext ?? true,
697
- gaps: existing?.gaps,
698
- artifacts: existing?.artifacts,
699
- testStatus: existing?.testStatus,
700
- workflowGateStatus: existing?.workflowGateStatus,
701
- workflowGateDecisionId: existing?.workflowGateDecisionId,
702
- evidence: existing?.evidence
703
- };
704
- }
705
-
706
- function verificationStatusFromTest(validationStatus: SddTestStatus): RunStateTaskRuntime['verificationStatus'] {
707
- if (validationStatus === 'PASS') {
708
- return 'pass';
709
- }
710
- return validationStatus === 'FAIL' ? 'failed' : 'blocked';
711
- }
712
-
713
- function runtimeTaskStatusAfterGate(status: SddTestStatus, validationStatus: SddTestStatus): string {
714
- if (status === 'PASS') {
715
- return 'implemented_verified';
716
- }
717
- if (validationStatus === 'PASS') {
718
- return 'workflow_gate_blocked';
719
- }
720
- return validationStatus === 'FAIL' ? 'validation_failed' : 'validation_blocked';
721
- }
722
-
723
- function deriveCommandStatus(gaps: string[], steps: SddTestCommandStep[]): TestEvidenceStatus {
724
- if (gaps.length > 0 || steps.some((step) => step.status === 'blocked')) {
725
- return 'BLOCKED';
726
- }
727
- if (steps.some((step) => step.status === 'fail')) {
728
- return 'FAIL';
729
- }
730
- return 'PASS';
731
- }
732
-
733
- function buildAcceptanceCoverage(task: SddTask | null, steps: SddTestCommandStep[], commandStatus: TestEvidenceStatus, acceptanceRefsOverride: string[] | undefined): AcceptanceEvidenceCoverage[] {
734
- const acceptanceRefs = acceptanceRefsOverride && acceptanceRefsOverride.length > 0 ? [...new Set(acceptanceRefsOverride)] : task ? taskAcceptanceRefs(task) : [];
735
- return acceptanceRefs.map((acceptanceRef) => {
736
- const mappedSteps = steps.filter((step) => step.acceptanceRefs.includes(acceptanceRef));
737
- const hasPassingEvidence = mappedSteps.some((step) => step.status === 'pass');
738
- const hasFailingEvidence = mappedSteps.some((step) => step.status === 'fail' || step.status === 'blocked');
739
- const status: EvidenceCoverageStatus = hasPassingEvidence && !hasFailingEvidence && commandStatus === 'PASS'
740
- ? 'complete'
741
- : mappedSteps.length > 0
742
- ? 'partial'
743
- : 'missing';
744
- return {
745
- acceptanceRef,
746
- status,
747
- evidenceRefs: mappedSteps.map((step) => ({ kind: 'artifact', ref: step.outputArtifact })),
748
- gaps: status === 'complete' ? [] : [`Acceptance ${acceptanceRef} has no complete non-stale evidence from sdd test task.`]
749
- };
750
- });
751
- }
752
-
753
- function summarizeEvidenceCoverage(acceptanceCoverage: AcceptanceEvidenceCoverage[]): EvidenceCoverageStatus {
754
- if (acceptanceCoverage.length === 0) {
755
- return 'missing';
756
- }
757
- if (acceptanceCoverage.every((coverage) => coverage.status === 'complete')) {
758
- return 'complete';
759
- }
760
- if (acceptanceCoverage.some((coverage) => coverage.status === 'complete' || coverage.status === 'partial')) {
761
- return 'partial';
762
- }
763
- return 'missing';
764
- }
765
-
766
- function derivePolicyJudgment(commandStatus: TestEvidenceStatus, evidenceCoverage: EvidenceCoverageStatus): SddTestStatus {
767
- if (commandStatus === 'FAIL') {
768
- return 'FAIL';
769
- }
770
- if (commandStatus === 'BLOCKED' || evidenceCoverage !== 'complete') {
771
- return 'BLOCKED';
772
- }
773
- return 'PASS';
774
- }
775
-
776
- async function resolveTestRuntimeDurableGap(
777
- projectRoot: string,
778
- branch: string,
779
- runId: string,
780
- taskId: string,
781
- validationStatus: SddTestStatus,
782
- gaps: string[]
783
- ): Promise<void> {
784
- if (validationStatus !== 'PASS' || gaps.length > 0) {
785
- return;
786
- }
787
- await updateRuntimeDurableGapStatus(projectRoot, {
788
- gapId: testRuntimeGapId(branch, runId, taskId),
789
- status: 'resolved',
790
- source: 'gate_policy',
791
- payload: { validationStatus, gaps }
792
- });
793
- }
794
-
795
- async function recordTestRuntimeDurableGap(
796
- projectRoot: string,
797
- branch: string,
798
- runId: string,
799
- taskId: string,
800
- status: SddTestStatus,
801
- validationStatus: SddTestStatus,
802
- runtimeJudgment: WorkflowGateStatus,
803
- gaps: string[],
804
- evidenceRefs: string[]
805
- ): Promise<void> {
806
- if (status === 'PASS' && gaps.length === 0) {
807
- return;
808
- }
809
- const message = gaps[0] ?? (runtimeJudgment === 'PASS' ? `Validation status is ${validationStatus}.` : `Workflow gate status is ${runtimeJudgment}.`);
810
- await recordRuntimeDurableGap(projectRoot, {
811
- gapId: testRuntimeGapId(branch, runId, taskId),
812
- partition: branch,
813
- taskId,
814
- runId,
815
- stage: 'test',
816
- gate: 'test',
817
- source: 'runtime',
818
- category: runtimeJudgment === 'PASS' ? 'validation' : 'workflow_gate',
819
- severity: 'blocking',
820
- status: 'open',
821
- message,
822
- recommendation: `Resolve test runtime gaps for ${taskId}, then rerun sdd test task ${taskId} --branch ${branch}.`,
823
- evidenceRefs,
824
- proposalRefs: [],
825
- sourceRefs: [],
826
- payload: { status, validationStatus, runtimeJudgment, gaps }
827
- });
828
- }
829
-
830
- function testRuntimeGapId(branch: string, _runId: string, taskId: string): string {
831
- return runtimeScopedId(branch, taskId, 'test-runtime-gap');
832
- }
833
-
834
- function finalStatusForTest(validationStatus: SddTestStatus, runtimeJudgment: WorkflowGateStatus): SddTestStatus {
835
- if (validationStatus !== 'PASS') {
836
- return validationStatus;
837
- }
838
- return runtimeJudgment === 'PASS' ? 'PASS' : 'BLOCKED';
839
- }
840
-
841
- function buildUnifiedTestEvidenceRun(id: string, branch: string, runId: string, taskId: string, commandStatus: TestEvidenceStatus, evidenceCoverage: EvidenceCoverageStatus, policyJudgment: TestEvidenceStatus, status: SddTestStatus, runtimeJudgment: WorkflowGateStatus, steps: SddTestCommandStep[], acceptanceCoverage: AcceptanceEvidenceCoverage[], capabilityEvidence: CapabilityEvidenceClassification[], syncBackReady: boolean, gaps: string[], gateNextAction: string | null, gateDecision: WorkflowGateDecision | null): UnifiedTestEvidenceRun {
842
- return {
843
- contract: TEST_EVIDENCE_RUN_CONTRACT_VERSION,
844
- id,
845
- scope: { branch, taskId, runId },
846
- commandStatus,
847
- evidenceCoverage,
848
- policyJudgment,
849
- commands: steps.map((step) => ({
850
- command: step.command,
851
- status: step.status === 'pass' ? 'PASS' : step.status === 'fail' ? 'FAIL' : 'BLOCKED',
852
- outputRef: { kind: 'artifact', ref: step.outputArtifact },
853
- evidenceRefs: [{ kind: 'artifact', ref: step.outputArtifact }],
854
- acceptanceRefs: step.acceptanceRefs,
855
- startedAt: new Date(Date.now() - step.durationMs).toISOString(),
856
- completedAt: new Date().toISOString()
857
- })),
858
- acceptanceCoverage,
859
- capabilityEvidence,
860
- syncBackReady,
861
- gaps: [...gaps, ...acceptanceCoverage.flatMap((coverage) => coverage.gaps)],
862
- next: nextForTestResult(status, runtimeJudgment, branch, taskId, runId, `artifacts/test-index-${taskId}.json`, gateNextAction, gateDecision),
863
- generatedAt: new Date().toISOString()
864
- };
865
- }
866
-
867
- function buildCapabilityEvidenceClassification(decision: AgentCapabilityRouteDecision | null, steps: SddTestCommandStep[]): CapabilityEvidenceClassification[] {
868
- if (!decision) {
869
- return [{
870
- class: 'diagnostic',
871
- source: 'runtime_diagnostic',
872
- domainOrSourceId: 'capability-routing',
873
- evidenceRefs: [],
874
- acceptanceRefs: [],
875
- provenanceRefs: [],
876
- reason: 'Capability routing did not run; no capability output is accepted as test evidence.'
877
- }];
878
- }
879
- const acceptanceRefs = [...new Set(steps.flatMap((step) => step.acceptanceRefs))];
880
- const professionalEvidence = decision.selectedDomains.map((domain) => ({
881
- class: 'candidate' as const,
882
- source: 'professional_capability' as const,
883
- domainOrSourceId: domain.domain,
884
- evidenceRefs: [],
885
- acceptanceRefs,
886
- provenanceRefs: [{ kind: 'projection' as const, ref: `capability:${domain.capabilityId}` }],
887
- reason: `${domain.reason}; capability output is advisory candidate evidence until accepted by command evidence and policy refs.`
888
- }));
889
- const externalEvidence = decision.rejectedExternalSources.map((source) => ({
890
- class: capabilityClassForRejectedSource(source.quarantineStatus) as CapabilityEvidenceClassification['class'],
891
- source: 'external_source' as const,
892
- domainOrSourceId: source.sourceId,
893
- evidenceRefs: [],
894
- acceptanceRefs: [],
895
- provenanceRefs: [{ kind: 'external' as const, ref: source.sourceId }],
896
- reason: source.reason
897
- }));
898
- return [...professionalEvidence, ...externalEvidence];
899
- }
900
-
901
- function capabilityClassForRejectedSource(status: AgentCapabilityRouteDecision['rejectedExternalSources'][number]['quarantineStatus']): CapabilityEvidenceClassification['class'] {
902
- if (status === 'denied') {
903
- return 'blocked';
904
- }
905
- if (status === 'required' || status === 'quarantined') {
906
- return 'quarantined';
907
- }
908
- return 'diagnostic';
909
- }
910
-
911
- function renderCapabilityEvidenceClassification(items: CapabilityEvidenceClassification[]): string {
912
- if (items.length === 0) {
913
- return '- none';
914
- }
915
- return items.map((item) => `- [${item.class}] ${item.source}:${item.domainOrSourceId} — ${item.reason}`).join('\n');
916
- }
917
-
918
- function capabilityEvidenceSummary(items: CapabilityEvidenceClassification[]): string {
919
- if (items.length === 0) {
920
- return 'none';
921
- }
922
- const counts = new Map<CapabilityEvidenceClassification['class'], number>();
923
- for (const item of items) {
924
- counts.set(item.class, (counts.get(item.class) ?? 0) + 1);
925
- }
926
- return [...counts.entries()].map(([kind, count]) => `${kind}:${count}`).join(',');
927
- }
928
-
929
- function nextForTestResult(status: SddTestStatus, runtimeJudgment: WorkflowGateStatus, branch: string, taskId: string, _runId: string, indexArtifact: string, gateNextAction: string | null, gateDecision: WorkflowGateDecision | null): string {
930
- if (status === 'PASS') {
931
- return `sdd ship --branch ${branch} --dry-run`;
932
- }
933
- if (gateNextAction) {
934
- return gateNextAction;
935
- }
936
- if (runtimeJudgment === 'HUMAN_REQUIRED') {
937
- return gateDecision ? `Create a decision card for workflow gate ${gateDecision.decisionId}, then rerun sdd test task ${taskId} --branch ${branch}.` : `Create a decision card, then rerun sdd test task ${taskId} --branch ${branch}.`;
938
- }
939
- if (runtimeJudgment === 'WARN') {
940
- return gateDecision ? `Review workflow gate ${gateDecision.decisionId} warnings, then rerun sdd test task ${taskId} --branch ${branch} or proceed only with explicit review.` : `Review workflow gate warnings, then rerun sdd test task ${taskId} --branch ${branch}.`;
941
- }
942
- if (runtimeJudgment === 'ADVISORY_ONLY') {
943
- return `Inspect advisor assessments for ${taskId}; advisory output cannot satisfy the test gate.`;
944
- }
945
- if (status === 'FAIL') {
946
- return `Inspect ${indexArtifact}, fix failing validation commands, then rerun sdd test task ${taskId} --branch ${branch}.`;
947
- }
948
- return gateDecision ? `Inspect ${indexArtifact} and workflow gate ${gateDecision.decisionId}, resolve blockers, then rerun sdd test task ${taskId} --branch ${branch}.` : `Inspect ${indexArtifact}, fix command/evidence gaps, then rerun sdd test task ${taskId} --branch ${branch}.`;
949
- }
950
-
951
- function resultSentenceForTest(result: SddTestResult): string {
952
- if (result.status === 'PASS') {
953
- return 'Validation and workflow gate passed; proceed to release readiness.';
954
- }
955
- if (result.validationStatus === 'PASS' && result.workflowGateStatus !== 'PASS') {
956
- return `Validation passed, but workflow gate returned ${result.workflowGateStatus}.`;
957
- }
958
- if (result.commandStatus === 'BLOCKED') {
959
- return 'Blocked before validation commands ran.';
960
- }
961
- return result.status === 'FAIL' ? 'Validation failed.' : 'Validation did not produce complete evidence.';
962
- }
963
-
964
- function renderEvidenceBlocks(task: SddTask, status: SddTestStatus, sourceArtifact: string, steps: SddTestCommandStep[]): string {
965
- const acceptances = task.acceptanceRefs.length > 0 ? task.acceptanceRefs : task.acceptance;
966
- if (acceptances.length === 0) {
967
- return 'No acceptance targets declared.';
968
- }
969
- const mappedEvidence = acceptances
970
- .map((acceptance) => ({ acceptance, steps: steps.filter((step) => step.acceptanceRefs.includes(acceptance)) }))
971
- .filter((item) => item.steps.length > 0);
972
- if (mappedEvidence.length === 0) {
973
- return 'No acceptance evidence emitted; validation commands are not explicitly mapped to acceptance refs.';
974
- }
975
- return mappedEvidence.map(({ acceptance, steps: mappedSteps }) => {
976
- const evidenceStatus = evidenceStatusForMappedSteps(status, mappedSteps);
977
- return `\`\`\`sdd-evidence\ncontract: ${SDD_EVIDENCE_CONTRACT}\nversion: ${SDD_EVIDENCE_VERSION}\ntask: ${task.id}\nacceptance: ${acceptance}\nstatus: ${evidenceStatus}\nclaim: Explicit validation mapping ${mappedSteps.map((step) => step.command).join(' && ')} produced ${evidenceStatus} for ${acceptance}.\nsource_artifact: ${sourceArtifact}\nevidence_refs:\n${mappedSteps.map((step) => ` - command:${step.command}\n - artifact:${step.outputArtifact}`).join('\n')}\nprovenance_refs:\n - artifact:${sourceArtifact}\n${mappedSteps.map((step) => ` - command:${step.command}`).join('\n')}\npolicy_refs:\n - ${ACCEPTANCE_POLICY_RULESET_VERSION}:require-source-evidence\n - ${ACCEPTANCE_POLICY_RULESET_VERSION}:require-provenance\n - ${ACCEPTANCE_POLICY_RULESET_VERSION}:require-policy-rule\n\`\`\``;
978
- }).join('\n\n');
979
- }
980
-
981
- function normalizeTestCommandInputs(commandInputs: SddTestCommandInput[] | undefined, commands: string[] | undefined, taskValidation: string[]): NormalizedSddTestCommand[] {
982
- if (commandInputs && commandInputs.length > 0) {
983
- return commandInputs.map(normalizeTestCommandInput);
984
- }
985
- return (commands && commands.length > 0 ? commands : taskValidation).map((command) => ({ command, argv: null, shell: true }));
986
- }
987
-
988
- function normalizeTestCommandInput(input: SddTestCommandInput): NormalizedSddTestCommand {
989
- if (input.argv) {
990
- const argv = input.argv.filter((item) => item.length > 0);
991
- if (argv.length === 0) {
992
- throw new Error('Command argv input must include an executable.');
993
- }
994
- return { command: argv.join(' '), argv, shell: false };
995
- }
996
- if (input.command) {
997
- return { command: input.command, argv: null, shell: true };
998
- }
999
- throw new Error('Command input must include command or argv.');
1000
- }
1001
-
1002
- function acceptanceRefsForCommand(task: SddTask | null, command: string, acceptanceRefsOverride: string[] | undefined): string[] {
1003
- if (acceptanceRefsOverride && acceptanceRefsOverride.length > 0) {
1004
- return [...new Set(acceptanceRefsOverride)];
1005
- }
1006
- return [...new Set((task?.validationCommands ?? [])
1007
- .filter((entry) => entry.command === command)
1008
- .flatMap((entry) => entry.acceptanceRefs))];
1009
- }
1010
-
1011
- function taskAcceptanceRefs(task: SddTask): string[] {
1012
- const refs = task.acceptanceRefs.length > 0 ? task.acceptanceRefs : task.acceptance;
1013
- return [...new Set(refs)];
1014
- }
1015
-
1016
- function evidenceStatusForMappedSteps(status: SddTestStatus, steps: SddTestCommandStep[]): SddTestStatus {
1017
- if (steps.some((step) => step.status === 'fail')) {
1018
- return 'FAIL';
1019
- }
1020
- if (status === 'BLOCKED' || steps.some((step) => step.status === 'blocked')) {
1021
- return 'BLOCKED';
1022
- }
1023
- return 'PASS';
1024
- }
1025
-
1026
- function renderCommandOutput(commandInput: NormalizedSddTestCommand, status: SddTestStepStatus, executed: { exitCode: number | null; signal: string | null; stdout: string; stderr: string; truncated: boolean; timedOut: boolean; error: string | null }, durationMs: number): string {
1027
- return `# Test Command Output\n\n- command: ${commandInput.command}\n- shell: ${commandInput.shell}\n- argv: ${commandInput.argv ? JSON.stringify(commandInput.argv) : 'none'}\n- status: ${status}\n- exit_code: ${executed.exitCode ?? 'none'}\n- signal: ${executed.signal ?? 'none'}\n- duration_ms: ${durationMs}\n- timed_out: ${executed.timedOut}\n- truncated: ${executed.truncated}\n- error: ${executed.error ?? 'none'}\n\n## stdout\n\n\`\`\`text\n${executed.stdout}\n\`\`\`\n\n## stderr\n\n\`\`\`text\n${executed.stderr}\n\`\`\`\n`;
1028
- }
1029
-
1030
- function hashDocumentContent(raw: string): string {
1031
- return createHash('sha256').update(raw.replace(/\r\n/g, '\n'), 'utf8').digest('hex');
1032
- }
1
+ import { createHash } from 'node:crypto';
2
+ import { spawn } from 'node:child_process';
3
+ import { appendEvent } from '../run-state/events.js';
4
+ import { appendArtifactHashLedgerEntry, appendInvocationLedgerEntry } from '../run-state/invocation-ledger.js';
5
+ import type { RunState, RunStateTaskRuntime } from '../run-state/model.js';
6
+ import { createRun, readAllRunStates, readRunState, writeRunState } from '../run-state/run-state.js';
7
+ import { recordRuntimeOnlyArtifact, recordStageEvidenceArtifact } from '../run-state/artifacts.js';
8
+ import { toBranchStageEvidenceRef } from '../runtime-paths.js';
9
+ import { resolveSddContext } from '../sdd-docs/context.js';
10
+ import { bindRunStateToTask } from '../sdd-docs/run-binding.js';
11
+ import { parseSddBranch, type SddTask, type SddTaskModel } from '../sdd-docs/task-parser.js';
12
+ import { inspectSddTask } from '../sdd-docs/task-inspection.js';
13
+ import { listRuntimeArtifactPayloads, readRuntimeValidationCacheEntry, recordRuntimeAcceptanceEvidenceMap, recordRuntimeDurableGap, recordRuntimeProjection, recordRuntimeTestRun, recordRuntimeTestStep, recordRuntimeValidationCacheEntry, recordRuntimeValidationCacheUse, recordRuntimeValidationEnvironmentSession, recordRuntimeValidationWaveRun, runtimeScopedId, updateRuntimeDurableGapStatus, type RuntimeValidationCacheEntryRecord } from '../storage/runtime-store.js';
14
+ import { ACCEPTANCE_POLICY_RULESET_VERSION, SDD_EVIDENCE_CONTRACT, SDD_EVIDENCE_VERSION, SDD_RESULT_CONTRACT, SDD_RESULT_VERSION, TEST_EVIDENCE_RUN_CONTRACT_VERSION, WORKFLOW_HANDOFF_CONTRACT_VERSION } from '../contracts.js';
15
+ import type { LifecycleRiskDecision } from '../risk/contracts.js';
16
+ import { inspectVerifyContract, type VerifyContractInspection } from './verify-contract.js';
17
+ import type { AcceptanceEvidenceCoverage, CapabilityEvidenceClassification, EvidenceCoverageStatus, TestEvidenceStatus, UnifiedTestEvidenceRun } from '../evidence-runtime.js';
18
+ import { ensureTaskOrchestration, inspectOrchestrationGate } from '../orchestration/runtime.js';
19
+ import { recordStageRunProjection, recordWorkflowHandoffProjection, validateWorkflowHandoff } from '../stage-runtime/runtime.js';
20
+ import type { StageRun, WorkflowHandoff } from '../stage-runtime/contracts.js';
21
+ import { evaluateTaskWorkflowGate, verifyContractBlockedGate, type ApprovalPolicy, type LifecycleRiskProfile, type LifecycleWorkflowGate } from '../risk.js';
22
+ import { validateSddResultArtifact } from '../artifacts/sdd-result.js';
23
+ import { dependencyBlockingReasonsForTask } from '../workflow-state/dependencies.js';
24
+ import { selectLatestEligibleRunsByTask } from '../workflow-state/latest-eligible-run.js';
25
+ import { latestRuntimeTaskStates } from '../workflow-state/resolve.js';
26
+ import { routeSddTask } from '../router/route-sdd-task.js';
27
+ import type { AgentCapabilityRouteDecision } from '../router/agent-runtime.js';
28
+ import { evaluateAndRecordWorkflowGateDecision } from '../workflow-gate/evidence-packet.js';
29
+ import type { WorkflowGateDecision, WorkflowGateStatus } from '../workflow-gate/types.js';
30
+ import { buildValidationCachePlan, type ValidationCacheUnsafeReason } from './validation-cache.js';
31
+
32
+ const DEFAULT_TEST_TIMEOUT_MS = 120_000;
33
+ const MAX_CAPTURE_BYTES = 256 * 1024;
34
+
35
+ export type SddTestStatus = 'PASS' | 'FAIL' | 'BLOCKED';
36
+ export type SddTestStepStatus = 'pass' | 'fail' | 'blocked';
37
+
38
+ export interface SddTestCommandInput {
39
+ command?: string;
40
+ argv?: string[];
41
+ }
42
+
43
+ export interface SddTestCommandStep {
44
+ stepId: string;
45
+ command: string;
46
+ argv: string[] | null;
47
+ shell: boolean;
48
+ acceptanceRefs: string[];
49
+ status: SddTestStepStatus;
50
+ exitCode: number | null;
51
+ signal: string | null;
52
+ durationMs: number;
53
+ outputArtifact: string | null;
54
+ stdoutBytes: number;
55
+ stderrBytes: number;
56
+ truncated: boolean;
57
+ startedAt: string;
58
+ endedAt: string;
59
+ cwd: string;
60
+ stdoutDigest: string;
61
+ stderrDigest: string;
62
+ outputSummary: string;
63
+ cacheStatus: 'hit' | 'miss' | 'unsafe';
64
+ cacheKey: string | null;
65
+ cacheSourceTestRunId: string | null;
66
+ cacheUnsafeReasons: ValidationCacheUnsafeReason[];
67
+ }
68
+
69
+ interface NormalizedSddTestCommand {
70
+ command: string;
71
+ argv: string[] | null;
72
+ shell: boolean;
73
+ }
74
+
75
+ type VerifyContractAction = 'none' | 'created' | 'refreshed' | 'blocked';
76
+
77
+ type RuntimeTestJudgment = WorkflowGateStatus;
78
+
79
+ export interface SddTestResult {
80
+ contract: 'sdd-test-runtime-v1';
81
+ runId: string;
82
+ testRunId: string;
83
+ validationWaveRunId: string;
84
+ validationEnvironmentSessionId: string;
85
+ branch: string;
86
+ taskId: string;
87
+ status: SddTestStatus;
88
+ validationStatus: SddTestStatus;
89
+ workflowGateStatus: WorkflowGateStatus;
90
+ runtimeJudgment: RuntimeTestJudgment;
91
+ workflowGateDecision: WorkflowGateDecision;
92
+ verifyContractStatus: string;
93
+ verifyContractAction: VerifyContractAction;
94
+ lifecycleGate: LifecycleWorkflowGate;
95
+ lifecycleProfile: LifecycleRiskProfile | null;
96
+ approvalPolicy: ApprovalPolicy | null;
97
+ requiredStages: string[];
98
+ primaryReason: string;
99
+ commandStatus: TestEvidenceStatus;
100
+ evidenceCoverage: EvidenceCoverageStatus;
101
+ policyJudgment: TestEvidenceStatus;
102
+ acceptanceCoverage: AcceptanceEvidenceCoverage[];
103
+ capabilityEvidence: CapabilityEvidenceClassification[];
104
+ commands: string[];
105
+ steps: SddTestCommandStep[];
106
+ validationArtifact: string | null;
107
+ indexArtifact: string | null;
108
+ gaps: string[];
109
+ next: string;
110
+ }
111
+
112
+ export interface RunSddTestOptions {
113
+ taskId: string;
114
+ branch?: string | null;
115
+ runId?: string | null;
116
+ commands?: string[];
117
+ commandInputs?: SddTestCommandInput[];
118
+ timeoutMs?: number;
119
+ approved?: boolean;
120
+ validationWave?: { waveRunId: string; environmentSessionId: string; taskIds: string[]; acceptanceRefsByTask?: Record<string, string[]> };
121
+ }
122
+
123
+ export async function runSddTest(projectRoot: string, options: RunSddTestOptions): Promise<SddTestResult> {
124
+ const context = await resolveSddContext(projectRoot, { branch: options.branch ?? undefined, branchSource: options.branch ? 'cli_option' : undefined });
125
+ const model = await parseSddBranch(projectRoot, context.partition);
126
+ const inspected = inspectSddTask(model, options.taskId);
127
+ const task = inspected.task;
128
+ const verifyContract = await ensureVerifyContractForTest(projectRoot, context.partition);
129
+ const verifyInspection = verifyContract.inspection;
130
+ const initialState = options.runId ? await readRunState(projectRoot, options.runId) : await createRun(projectRoot);
131
+ const state = await bindRunStateToTask(projectRoot, initialState, context, model, task, options.taskId);
132
+ const testRunId = runtimeScopedId(state.runId, options.taskId, new Date().toISOString(), 'sdd-test');
133
+ const commandInputs = normalizeTestCommandInputs(options.commandInputs, options.commands, task?.validation ?? []);
134
+ const commands = commandInputs.map((input) => input.command);
135
+ const gaps: string[] = [];
136
+ const startedAt = new Date().toISOString();
137
+ const ownsValidationWave = !options.validationWave;
138
+ const validationWaveRunId = options.validationWave?.waveRunId ?? runtimeScopedId(context.partition, options.taskId, state.runId, testRunId, 'validation-wave');
139
+ const validationEnvironmentSessionId = options.validationWave?.environmentSessionId ?? runtimeScopedId(context.partition, validationWaveRunId, 'validation-env');
140
+ const validationWaveTaskIds = options.validationWave?.taskIds ?? [options.taskId];
141
+ const validationWaveAcceptanceRefs = options.validationWave?.acceptanceRefsByTask?.[options.taskId];
142
+ const validationWaveScopeAcceptanceRefs = [...new Set(Object.values(options.validationWave?.acceptanceRefsByTask ?? { [options.taskId]: task?.acceptanceRefs ?? [] }).flat())].sort();
143
+ if (ownsValidationWave) {
144
+ await recordRuntimeValidationEnvironmentSession(projectRoot, {
145
+ sessionId: validationEnvironmentSessionId,
146
+ partition: context.partition,
147
+ runId: state.runId,
148
+ waveRunId: validationWaveRunId,
149
+ status: 'active',
150
+ reuseKey: `${context.partition}:${options.taskId}`,
151
+ createdAt: startedAt,
152
+ updatedAt: startedAt,
153
+ payload: { contract: 'phase-8.17-validation-wave-runtime-v1', mode: 'single-task' }
154
+ });
155
+ await recordRuntimeValidationWaveRun(projectRoot, {
156
+ waveRunId: validationWaveRunId,
157
+ partition: context.partition,
158
+ runId: state.runId,
159
+ taskIds: validationWaveTaskIds,
160
+ status: 'RUNNING',
161
+ environmentSessionId: validationEnvironmentSessionId,
162
+ startedAt,
163
+ completedAt: startedAt,
164
+ payload: { contract: 'phase-8.17-validation-wave-runtime-v1', mode: 'single-task', taskId: options.taskId }
165
+ });
166
+ }
167
+
168
+ await appendEvent(projectRoot, state.runId, {
169
+ event: 'test_runtime_started',
170
+ runId: state.runId,
171
+ summary: `SDD test runtime started for ${options.taskId}`,
172
+ data: { taskId: options.taskId, branch: context.partition, testRunId, commands }
173
+ });
174
+
175
+ const states = await readAllRunStates(projectRoot);
176
+ const latestEligibleRunsByTask = selectLatestEligibleRunsByTask({ states, model, partition: context.partition, currentGitBranch: context.currentGitBranch });
177
+ const runtimeByTask = latestRuntimeTaskStates(latestEligibleRunsByTask, states);
178
+
179
+ if (!task) {
180
+ gaps.push(`Task ${options.taskId} was not found in specs/${context.partition}/tasks.md.`);
181
+ }
182
+ if (task) {
183
+ gaps.push(...inspected.gaps.filter((gap) => gap.severity === 'blocking').map((gap) => `${gap.field}: ${gap.message}`));
184
+ gaps.push(...dependencyBlockingReasonsForTask(model, options.taskId, { runtimeByTask }));
185
+ }
186
+ if (verifyContract.action === 'blocked') {
187
+ gaps.push(verifyContractBlocker(verifyInspection));
188
+ }
189
+ if (commands.length === 0) {
190
+ gaps.push(`Task ${options.taskId} has no validation commands.`);
191
+ }
192
+ const orchestration = await ensureTaskOrchestration(projectRoot, model, task, {
193
+ branch: context.partition,
194
+ runId: state.runId,
195
+ taskId: options.taskId,
196
+ agent: 'validator',
197
+ stage: 'execute',
198
+ status: 'active'
199
+ });
200
+ const orchestrationGate = await inspectOrchestrationGate(projectRoot, {
201
+ branch: context.partition,
202
+ runId: state.runId,
203
+ taskId: options.taskId,
204
+ target: 'execution',
205
+ riskDecision: orchestration.riskDecision,
206
+ stageRun: orchestration.stageRun,
207
+ contextLoadSignal: orchestration.contextLoadSignal,
208
+ contextOffloadDecision: orchestration.contextOffloadDecision
209
+ });
210
+ const reviewerCheckpointSatisfied = await hasReviewerCheckpoint(projectRoot, state, options.taskId);
211
+ const workflowGate = verifyContract.action === 'blocked'
212
+ ? verifyContractBlockedGate(options.taskId)
213
+ : evaluateTaskWorkflowGate({ task, taskId: options.taskId, riskDecision: orchestration.riskDecision, approved: options.approved, reviewerCheckpointSatisfied });
214
+ if (workflowGate.blocksTest) {
215
+ gaps.push(workflowGate.primaryReason);
216
+ }
217
+ gaps.push(...orchestrationGate.blockingReasons);
218
+
219
+ await recordRuntimeTestRun(projectRoot, {
220
+ testRunId,
221
+ runId: state.runId,
222
+ partition: context.partition,
223
+ taskId: options.taskId,
224
+ status: 'RUNNING',
225
+ startedAt,
226
+ completedAt: startedAt,
227
+ payload: { verifyContractStatus: verifyInspection.status, verifyContractAction: verifyContract.action, lifecycleGate: workflowGate.lifecycleGate, lifecycleProfile: workflowGate.lifecycleProfile, approvalPolicy: workflowGate.approvalPolicy, requiredStages: workflowGate.requiredStages, primaryReason: workflowGate.primaryReason, commands, commandInputs, evidence: [], gaps }
228
+ });
229
+
230
+ const steps: SddTestCommandStep[] = [];
231
+ if (gaps.length === 0) {
232
+ for (const [index, commandInput] of commandInputs.entries()) {
233
+ const step = await runCommandStep(projectRoot, state.runId, context.partition, options.taskId, testRunId, index + 1, commandInput, acceptanceRefsForCommand(task, commandInput.command, validationWaveAcceptanceRefs), options.timeoutMs ?? DEFAULT_TEST_TIMEOUT_MS, model, task!, validationWaveTaskIds, validationWaveScopeAcceptanceRefs);
234
+ steps.push(step);
235
+ await appendEvent(projectRoot, state.runId, {
236
+ event: 'test_step_completed',
237
+ runId: state.runId,
238
+ summary: `SDD test step ${step.status}: ${step.command}`,
239
+ data: { taskId: options.taskId, testRunId, step }
240
+ });
241
+ }
242
+ }
243
+
244
+ const commandStatus = deriveCommandStatus(gaps, steps);
245
+ const acceptanceCoverage = buildAcceptanceCoverage(task, steps, commandStatus, validationWaveAcceptanceRefs);
246
+ const evidenceCoverage = summarizeEvidenceCoverage(acceptanceCoverage);
247
+ const policyJudgment = derivePolicyJudgment(commandStatus, evidenceCoverage);
248
+ await recordAcceptanceEvidenceMaps(projectRoot, validationWaveRunId, testRunId, context.partition, state.runId, options.taskId, acceptanceCoverage);
249
+ const validationStatus = policyJudgment;
250
+ const capabilityRoute = task ? await routeSddTask(projectRoot, { taskId: options.taskId, branch: context.partition, approved: options.approved }) : null;
251
+ const capabilityEvidence = buildCapabilityEvidenceClassification(capabilityRoute?.capabilityDecision ?? null, steps);
252
+ const validationArtifact = task ? await writeValidationArtifact(projectRoot, state.runId, context.partition, task, validationStatus, steps, gaps, capabilityEvidence) : null;
253
+ const evidenceBeforeGate = runtimeEvidenceRefs(validationArtifact?.runRelativePath ?? null, steps);
254
+ await persistTestRunState(projectRoot, state, options.taskId, validationStatus, commands, evidenceBeforeGate, validationArtifact?.runRelativePath ?? null);
255
+ await resolveTestRuntimeDurableGap(projectRoot, context.partition, state.runId, options.taskId, validationStatus, gaps);
256
+
257
+ const gateDecision = (await evaluateAndRecordWorkflowGateDecision(projectRoot, {
258
+ branch: context.partition,
259
+ taskId: options.taskId,
260
+ runId: state.runId,
261
+ decisionKind: 'test'
262
+ })).decision;
263
+ const runtimeJudgment = gateDecision.status;
264
+ const status = finalStatusForTest(validationStatus, runtimeJudgment);
265
+ await recordTestRuntimeDurableGap(projectRoot, context.partition, state.runId, options.taskId, status, validationStatus, runtimeJudgment, gaps, evidenceBeforeGate);
266
+ const unifiedEvidence = buildUnifiedTestEvidenceRun(testRunId, context.partition, state.runId, options.taskId, commandStatus, evidenceCoverage, policyJudgment, status, runtimeJudgment, steps, acceptanceCoverage, capabilityEvidence, gaps, workflowGate.nextAction, gateDecision);
267
+ const completedAt = new Date().toISOString();
268
+ const evidence = runtimeEvidenceRefs(validationArtifact?.runRelativePath ?? null, steps);
269
+
270
+ await recordRuntimeTestRun(projectRoot, {
271
+ testRunId,
272
+ runId: state.runId,
273
+ partition: context.partition,
274
+ taskId: options.taskId,
275
+ status,
276
+ startedAt,
277
+ completedAt,
278
+ payload: { verifyContractStatus: verifyInspection.status, verifyContractAction: verifyContract.action, lifecycleGate: workflowGate.lifecycleGate, lifecycleProfile: workflowGate.lifecycleProfile, approvalPolicy: workflowGate.approvalPolicy, requiredStages: workflowGate.requiredStages, primaryReason: workflowGate.primaryReason, commandStatus, evidenceCoverage, policyJudgment, validationStatus, workflowGateStatus: gateDecision.status, runtimeJudgment, workflowGateDecision: gateDecision, acceptanceCoverage, capabilityEvidence, commands, commandInputs, evidence, gaps }
279
+ });
280
+ await recordRuntimeProjection(projectRoot, 'test_runtime', `${context.partition}:${options.taskId}:${state.runId}`, {
281
+ contract: 'sdd-test-runtime-v1',
282
+ testRunId,
283
+ runId: state.runId,
284
+ taskId: options.taskId,
285
+ status,
286
+ validationStatus,
287
+ workflowGateStatus: gateDecision.status,
288
+ runtimeJudgment,
289
+ lifecycleGate: workflowGate.lifecycleGate,
290
+ primaryReason: workflowGate.primaryReason,
291
+ evidence,
292
+ gaps
293
+ });
294
+ await recordRuntimeProjection(projectRoot, 'test_evidence_run', `${context.partition}:${options.taskId}:${state.runId}`, unifiedEvidence);
295
+ await recordTestWorkflowProjection(projectRoot, {
296
+ taskId: options.taskId,
297
+ stageRun: orchestration.stageRun,
298
+ status,
299
+ completedAt,
300
+ evidence,
301
+ gaps,
302
+ riskDecision: orchestration.riskDecision
303
+ });
304
+ await persistTestGateOutcome(projectRoot, state.runId, options.taskId, status, validationStatus, commands, evidence, validationArtifact?.runRelativePath ?? null, gateDecision);
305
+ if (ownsValidationWave) {
306
+ await recordRuntimeValidationWaveRun(projectRoot, {
307
+ waveRunId: validationWaveRunId,
308
+ partition: context.partition,
309
+ runId: state.runId,
310
+ taskIds: validationWaveTaskIds,
311
+ status,
312
+ environmentSessionId: validationEnvironmentSessionId,
313
+ startedAt,
314
+ completedAt,
315
+ payload: { contract: 'phase-8.17-validation-wave-runtime-v1', mode: 'single-task', taskId: options.taskId, testRunId, evidence, gaps, workflowGateDecision: gateDecision }
316
+ });
317
+ await recordRuntimeValidationEnvironmentSession(projectRoot, {
318
+ sessionId: validationEnvironmentSessionId,
319
+ partition: context.partition,
320
+ runId: state.runId,
321
+ waveRunId: validationWaveRunId,
322
+ status: status === 'PASS' ? 'completed' : status === 'FAIL' ? 'failed' : 'blocked',
323
+ reuseKey: `${context.partition}:${options.taskId}`,
324
+ createdAt: startedAt,
325
+ updatedAt: completedAt,
326
+ payload: { contract: 'phase-8.17-validation-wave-runtime-v1', mode: 'single-task', taskId: options.taskId, validationStatus, workflowGateStatus: gateDecision.status, status }
327
+ });
328
+ }
329
+
330
+ await appendEvent(projectRoot, state.runId, {
331
+ event: status === 'PASS' ? 'test_runtime_passed' : 'test_runtime_blocked',
332
+ runId: state.runId,
333
+ summary: `SDD test runtime ${status} for ${options.taskId}`,
334
+ data: { taskId: options.taskId, testRunId, status, validationStatus, evidence, gaps, gateDecisionId: gateDecision.decisionId, gateStatus: gateDecision.status }
335
+ });
336
+
337
+ return {
338
+ contract: 'sdd-test-runtime-v1',
339
+ runId: state.runId,
340
+ testRunId,
341
+ validationWaveRunId,
342
+ validationEnvironmentSessionId,
343
+ branch: context.partition,
344
+ taskId: options.taskId,
345
+ status,
346
+ validationStatus,
347
+ workflowGateStatus: gateDecision.status,
348
+ runtimeJudgment,
349
+ workflowGateDecision: gateDecision,
350
+ verifyContractStatus: verifyInspection.status,
351
+ verifyContractAction: verifyContract.action,
352
+ lifecycleGate: workflowGate.lifecycleGate,
353
+ lifecycleProfile: workflowGate.lifecycleProfile,
354
+ approvalPolicy: workflowGate.approvalPolicy,
355
+ requiredStages: workflowGate.requiredStages,
356
+ primaryReason: workflowGate.primaryReason,
357
+ commandStatus,
358
+ evidenceCoverage,
359
+ policyJudgment,
360
+ acceptanceCoverage,
361
+ capabilityEvidence,
362
+ commands,
363
+ steps,
364
+ validationArtifact: validationArtifact?.runRelativePath ?? null,
365
+ indexArtifact: null,
366
+ gaps,
367
+ next: nextForTestResult(status, runtimeJudgment, context.partition, options.taskId, workflowGate.nextAction, gateDecision)
368
+ };
369
+ }
370
+
371
+ async function recordAcceptanceEvidenceMaps(projectRoot: string, waveRunId: string, testRunId: string, partition: string, runId: string, taskId: string, acceptanceCoverage: AcceptanceEvidenceCoverage[]): Promise<void> {
372
+ const createdAt = new Date().toISOString();
373
+ for (const coverage of acceptanceCoverage) {
374
+ await recordRuntimeAcceptanceEvidenceMap(projectRoot, {
375
+ mapId: runtimeScopedId(waveRunId, testRunId, taskId, coverage.acceptanceRef),
376
+ waveRunId,
377
+ testRunId,
378
+ partition,
379
+ runId,
380
+ taskId,
381
+ acceptanceRef: coverage.acceptanceRef,
382
+ status: coverage.status,
383
+ evidenceRefs: coverage.evidenceRefs.map((ref) => ref.ref),
384
+ gaps: coverage.gaps,
385
+ createdAt,
386
+ payload: coverage
387
+ });
388
+ }
389
+ }
390
+
391
+ async function recordTestWorkflowProjection(projectRoot: string, input: { taskId: string; stageRun: StageRun; status: SddTestStatus; completedAt: string; evidence: string[]; gaps: string[]; riskDecision: LifecycleRiskDecision }): Promise<void> {
392
+ const outputRefs = input.evidence.map((ref) => ({ kind: 'artifact' as const, ref }));
393
+ const completedStage: StageRun = {
394
+ ...input.stageRun,
395
+ status: input.status === 'PASS' ? 'completed' : input.status === 'FAIL' ? 'failed' : 'blocked',
396
+ outputRefs,
397
+ blockingReasons: input.status === 'PASS' ? [] : input.gaps.length > 0 ? input.gaps : [`SDD test ${input.status}.`],
398
+ updatedAt: input.completedAt
399
+ };
400
+ await recordStageRunProjection(projectRoot, completedStage);
401
+
402
+ const handoff: WorkflowHandoff = {
403
+ contract: WORKFLOW_HANDOFF_CONTRACT_VERSION,
404
+ id: `${completedStage.id}:handoff:ship`,
405
+ scope: completedStage.scope,
406
+ fromStage: 'execute',
407
+ toStage: 'ship',
408
+ fromAgent: 'validator',
409
+ toAgent: 'ship-manager',
410
+ status: input.status === 'PASS' ? 'proposed' : 'blocked',
411
+ outputRefs,
412
+ requiredInputRefs: [{ kind: 'task', ref: input.taskId }],
413
+ riskDecisionRef: input.stageRun.decisionRefs[0] ?? { kind: 'task', ref: input.taskId },
414
+ evidenceRefs: outputRefs,
415
+ openQuestions: [],
416
+ blockingGaps: input.status === 'PASS' ? [] : completedStage.blockingReasons,
417
+ createdAt: input.completedAt,
418
+ decidedAt: input.completedAt
419
+ };
420
+ const validation = validateWorkflowHandoff({ handoff, sourceStageRun: completedStage, lifecycleRiskDecision: input.riskDecision });
421
+ await recordWorkflowHandoffProjection(projectRoot, validation.valid ? handoff : { ...handoff, status: 'blocked', blockingGaps: validation.issues, decidedAt: input.completedAt });
422
+ }
423
+
424
+
425
+ async function ensureVerifyContractForTest(projectRoot: string, branch: string): Promise<{ inspection: VerifyContractInspection; action: VerifyContractAction }> {
426
+ const inspection = await inspectVerifyContract(projectRoot, { branch, branchSource: 'cli_option' });
427
+ return { inspection, action: inspection.status === 'PASS' ? 'none' : 'blocked' };
428
+ }
429
+
430
+ function verifyContractBlocker(inspection: VerifyContractInspection): string {
431
+ const issueSummary = inspection.issues.map((issue) => `${issue.field}: ${issue.message}`).join(' ');
432
+ return `verify.md contract is ${inspection.status}; ${issueSummary || 'inspect verify.md before executing tests.'}`;
433
+ }
434
+
435
+ async function hasReviewerCheckpoint(projectRoot: string, state: RunState, taskId: string): Promise<boolean> {
436
+ const artifactPaths = new Set(state.artifacts
437
+ .filter((artifact) => artifact.task === taskId && (artifact.agent === 'reviewer' || artifact.kind === 'review'))
438
+ .map((artifact) => artifact.path));
439
+ const branch = state.partition ?? state.gitBranch ?? 'unscoped';
440
+ const payloads = await listRuntimeArtifactPayloads(projectRoot, { runId: state.runId, taskId });
441
+ for (const payload of payloads) {
442
+ const fileName = payload.logicalRef.replace(/\\/g, '/').split('/').filter(Boolean).pop();
443
+ if (fileName && (payload.artifactRole === 'review' || /review/i.test(fileName))) {
444
+ artifactPaths.add(toBranchStageEvidenceRef(branch, 'execute', fileName));
445
+ }
446
+ }
447
+
448
+ for (const artifactPath of artifactPaths) {
449
+ const report = await validateSddResultArtifact(projectRoot, state.runId, artifactPath, { expectedTask: taskId, expectedAgent: 'reviewer' });
450
+ if (report.valid && report.result?.status === 'PASS') {
451
+ return true;
452
+ }
453
+ }
454
+
455
+ return false;
456
+ }
457
+
458
+ export function renderSddTestResult(result: SddTestResult): string {
459
+ return [
460
+ `SDD test ${result.taskId}`,
461
+ '',
462
+ resultSentenceForTest(result),
463
+ '',
464
+ 'Decision:',
465
+ `- validation_status=${result.validationStatus}`,
466
+ `- workflow_gate_status=${result.workflowGateStatus}`,
467
+ `- workflow_gate_decision=${result.workflowGateDecision.decisionId}`,
468
+ '',
469
+ 'Why:',
470
+ `- ${result.primaryReason}`,
471
+ `- capability_evidence=${capabilityEvidenceSummary(result.capabilityEvidence)}`,
472
+ '',
473
+ 'Next:',
474
+ `- ${result.next}`
475
+ ].join('\n');
476
+ }
477
+
478
+
479
+ async function runCommandStep(projectRoot: string, runId: string, branch: string, taskId: string, testRunId: string, sequence: number, commandInput: NormalizedSddTestCommand, acceptanceRefs: string[], timeoutMs: number, model: SddTaskModel, task: SddTask, validationScopeTaskIds: string[], validationScopeAcceptanceRefs: string[]): Promise<SddTestCommandStep> {
480
+ const cachePlan = buildValidationCachePlan({ branch, model, task, command: commandInput.command, argv: commandInput.argv, shell: commandInput.shell, validationScopeTaskIds, acceptanceRefs: validationScopeAcceptanceRefs });
481
+ const cached = cachePlan.eligible ? await readRuntimeValidationCacheEntry(projectRoot, { branchSlug: branch, cacheKey: cachePlan.cacheKey }) : null;
482
+ if (cached) {
483
+ return recordCachedCommandStep(projectRoot, runId, branch, taskId, testRunId, sequence, commandInput, acceptanceRefs, cachePlan.cacheKey, cached);
484
+ }
485
+
486
+ const started = Date.now();
487
+ const startedAt = new Date(started).toISOString();
488
+ const executed = await executeCommand(projectRoot, commandInput, timeoutMs);
489
+ const ended = Date.now();
490
+ const endedAt = new Date(ended).toISOString();
491
+ const durationMs = ended - started;
492
+ const status: SddTestStepStatus = executed.timedOut || executed.error ? 'blocked' : executed.exitCode === 0 ? 'pass' : 'fail';
493
+ const stepId = `${testRunId}-${String(sequence).padStart(3, '0')}`;
494
+ const cacheStatus = cachePlan.eligible ? 'miss' : 'unsafe';
495
+ const shouldPersistOutputArtifact = shouldPersistCommandOutputArtifact(status, executed, cacheStatus, cachePlan.unsafeReasons);
496
+ const outputFileName = shouldPersistOutputArtifact ? `test-${taskId}-${String(sequence).padStart(3, '0')}.log` : null;
497
+ const outputRef = outputFileName ? toBranchStageEvidenceRef(branch, 'execute', outputFileName) : null;
498
+ const output = outputFileName ? renderCommandOutput(commandInput, status, executed, durationMs, cacheStatus, cachePlan.cacheKey, null, cachePlan.unsafeReasons) : null;
499
+ if (outputFileName && outputRef && output) {
500
+ await recordRuntimeOnlyArtifact(projectRoot, runId, outputFileName, output, { logicalRef: outputRef, branch, taskId, artifactRole: 'test-command-output' });
501
+ await appendArtifactHashLedgerEntry(projectRoot, {
502
+ runId,
503
+ taskId,
504
+ branch,
505
+ artifactPath: outputRef,
506
+ content: output,
507
+ status: 'recorded'
508
+ });
509
+ }
510
+ const stdoutDigest = hashDocumentContent(executed.stdout);
511
+ const stderrDigest = hashDocumentContent(executed.stderr);
512
+ const outputSummary = summarizeCommandOutput(executed);
513
+ await appendInvocationLedgerEntry(projectRoot, {
514
+ runId,
515
+ taskId,
516
+ branch,
517
+ kind: 'command',
518
+ ref: commandInput.command,
519
+ status,
520
+ artifactPath: outputRef,
521
+ outputHash: output ? hashDocumentContent(output) : null,
522
+ materialRefs: outputRef ? [outputRef] : [],
523
+ metadata: {
524
+ stepId,
525
+ source: 'sdd-test',
526
+ exitCode: executed.exitCode,
527
+ durationMs,
528
+ stdoutBytes: executed.stdoutBytes,
529
+ stderrBytes: executed.stderrBytes,
530
+ truncated: executed.truncated,
531
+ acceptanceRefs: acceptanceRefs.join(','),
532
+ shell: commandInput.shell,
533
+ argv: commandInput.argv ? JSON.stringify(commandInput.argv) : null,
534
+ stdoutDigest,
535
+ stderrDigest,
536
+ cacheKey: cachePlan.cacheKey,
537
+ cacheStatus,
538
+ cacheUnsafeReasons: cachePlan.unsafeReasons.join(',')
539
+ }
540
+ });
541
+ const step: SddTestCommandStep = {
542
+ stepId,
543
+ command: commandInput.command,
544
+ argv: commandInput.argv,
545
+ shell: commandInput.shell,
546
+ acceptanceRefs,
547
+ status,
548
+ exitCode: executed.exitCode,
549
+ signal: executed.signal,
550
+ durationMs,
551
+ outputArtifact: outputRef,
552
+ stdoutBytes: executed.stdoutBytes,
553
+ stderrBytes: executed.stderrBytes,
554
+ truncated: executed.truncated,
555
+ startedAt,
556
+ endedAt,
557
+ cwd: projectRoot,
558
+ stdoutDigest,
559
+ stderrDigest,
560
+ outputSummary,
561
+ cacheStatus,
562
+ cacheKey: cachePlan.cacheKey,
563
+ cacheSourceTestRunId: null,
564
+ cacheUnsafeReasons: cachePlan.unsafeReasons
565
+ };
566
+ await recordRuntimeTestStep(projectRoot, {
567
+ stepId,
568
+ testRunId,
569
+ runId,
570
+ taskId,
571
+ command: commandInput.command,
572
+ status,
573
+ exitCode: executed.exitCode,
574
+ durationMs,
575
+ outputArtifact: outputRef,
576
+ payload: step
577
+ });
578
+ if (status === 'pass' && cachePlan.eligible) {
579
+ const now = new Date().toISOString();
580
+ await recordRuntimeValidationCacheEntry(projectRoot, {
581
+ cacheKey: cachePlan.cacheKey,
582
+ branchSlug: branch,
583
+ command: commandInput.command,
584
+ status: 'valid',
585
+ sourceTestRunId: testRunId,
586
+ sourceRunId: runId,
587
+ sourceEvidenceSetId: null,
588
+ outputArtifact: outputRef,
589
+ stdoutDigest,
590
+ stderrDigest,
591
+ createdAt: now,
592
+ lastUsedAt: now,
593
+ payload: { cachePlan, step }
594
+ });
595
+ }
596
+ return step;
597
+ }
598
+
599
+ async function recordCachedCommandStep(projectRoot: string, runId: string, branch: string, taskId: string, testRunId: string, sequence: number, commandInput: NormalizedSddTestCommand, acceptanceRefs: string[], cacheKey: string, cached: RuntimeValidationCacheEntryRecord): Promise<SddTestCommandStep> {
600
+ const now = new Date().toISOString();
601
+ const stepId = `${testRunId}-${String(sequence).padStart(3, '0')}`;
602
+ const step: SddTestCommandStep = {
603
+ stepId,
604
+ command: commandInput.command,
605
+ argv: commandInput.argv,
606
+ shell: commandInput.shell,
607
+ acceptanceRefs,
608
+ status: 'pass',
609
+ exitCode: 0,
610
+ signal: null,
611
+ durationMs: 0,
612
+ outputArtifact: null,
613
+ stdoutBytes: 0,
614
+ stderrBytes: 0,
615
+ truncated: false,
616
+ startedAt: now,
617
+ endedAt: now,
618
+ cwd: projectRoot,
619
+ stdoutDigest: cached.stdoutDigest,
620
+ stderrDigest: cached.stderrDigest,
621
+ outputSummary: `cache_hit source_test_run=${cached.sourceTestRunId} source_artifact=${cached.outputArtifact ?? 'none'}`,
622
+ cacheStatus: 'hit',
623
+ cacheKey,
624
+ cacheSourceTestRunId: cached.sourceTestRunId,
625
+ cacheUnsafeReasons: []
626
+ };
627
+ await appendInvocationLedgerEntry(projectRoot, {
628
+ runId,
629
+ taskId,
630
+ branch,
631
+ kind: 'command',
632
+ ref: commandInput.command,
633
+ status: 'pass',
634
+ artifactPath: null,
635
+ outputHash: null,
636
+ materialRefs: cached.outputArtifact ? [cached.outputArtifact] : [],
637
+ metadata: { source: 'sdd-test-cache', stepId, cacheKey, sourceTestRunId: cached.sourceTestRunId, sourceEvidenceSetId: cached.sourceEvidenceSetId, acceptanceRefs: acceptanceRefs.join(',') }
638
+ });
639
+ await recordRuntimeTestStep(projectRoot, {
640
+ stepId,
641
+ testRunId,
642
+ runId,
643
+ taskId,
644
+ command: commandInput.command,
645
+ status: 'pass',
646
+ exitCode: 0,
647
+ durationMs: 0,
648
+ outputArtifact: null,
649
+ payload: step
650
+ });
651
+ await recordRuntimeValidationCacheUse(projectRoot, {
652
+ useId: runtimeScopedId(cacheKey, testRunId, taskId, stepId),
653
+ cacheKey,
654
+ branchSlug: branch,
655
+ testRunId,
656
+ runId,
657
+ taskId,
658
+ sourceTestRunId: cached.sourceTestRunId,
659
+ sourceEvidenceSetId: cached.sourceEvidenceSetId,
660
+ reusedAt: now,
661
+ mappedTaskIds: [taskId],
662
+ reason: 'same validation cache key within compatible branch contract scope',
663
+ payload: { sourceRunId: cached.sourceRunId, sourceArtifact: cached.outputArtifact }
664
+ });
665
+ return step;
666
+ }
667
+
668
+ function executeCommand(projectRoot: string, commandInput: NormalizedSddTestCommand, timeoutMs: number): Promise<{ exitCode: number | null; signal: string | null; stdout: string; stderr: string; stdoutBytes: number; stderrBytes: number; truncated: boolean; timedOut: boolean; error: string | null }> {
669
+ return new Promise((resolve) => {
670
+ const child = commandInput.argv
671
+ ? spawn(commandInput.argv[0], commandInput.argv.slice(1), { cwd: projectRoot, shell: false, windowsHide: true, env: process.env })
672
+ : spawn(commandInput.command, { cwd: projectRoot, shell: true, windowsHide: true, env: process.env });
673
+ let stdout = '';
674
+ let stderr = '';
675
+ let stdoutBytes = 0;
676
+ let stderrBytes = 0;
677
+ let truncated = false;
678
+ let settled = false;
679
+ let timedOut = false;
680
+ const timer = setTimeout(() => {
681
+ timedOut = true;
682
+ child.kill();
683
+ }, timeoutMs);
684
+ const finish = (result: { exitCode: number | null; signal: string | null; error: string | null }) => {
685
+ if (settled) {
686
+ return;
687
+ }
688
+ settled = true;
689
+ clearTimeout(timer);
690
+ resolve({ ...result, stdout, stderr, stdoutBytes, stderrBytes, truncated, timedOut });
691
+ };
692
+ child.stdout?.on('data', (chunk: Buffer) => {
693
+ stdoutBytes += chunk.length;
694
+ const next = chunk.toString('utf8');
695
+ if (Buffer.byteLength(stdout, 'utf8') < MAX_CAPTURE_BYTES) {
696
+ stdout += next;
697
+ } else {
698
+ truncated = true;
699
+ }
700
+ });
701
+ child.stderr?.on('data', (chunk: Buffer) => {
702
+ stderrBytes += chunk.length;
703
+ const next = chunk.toString('utf8');
704
+ if (Buffer.byteLength(stderr, 'utf8') < MAX_CAPTURE_BYTES) {
705
+ stderr += next;
706
+ } else {
707
+ truncated = true;
708
+ }
709
+ });
710
+ child.on('error', (error) => finish({ exitCode: null, signal: null, error: error.message }));
711
+ child.on('close', (code, signal) => finish({ exitCode: code, signal, error: null }));
712
+ });
713
+ }
714
+
715
+ async function writeValidationArtifact(projectRoot: string, runId: string, branch: string, task: SddTask, status: SddTestStatus, steps: SddTestCommandStep[], gaps: string[], _capabilityEvidence: CapabilityEvidenceClassification[]): Promise<{ absolutePath: string; runRelativePath: string }> {
716
+ const artifactPath = validationArtifactPath(task);
717
+ const stageEvidenceRef = toBranchStageEvidenceRef(branch, 'execute', artifactPath);
718
+ const resultStatus = status === 'PASS' ? 'PASS' : status === 'FAIL' ? 'FAIL' : 'BLOCKED';
719
+ const content = `# Test Validation ${task.id}\n\n\`\`\`sdd-result\ncontract: ${SDD_RESULT_CONTRACT}\nversion: ${SDD_RESULT_VERSION}\nagent: validator\ntask: ${task.id}\nstatus: ${resultStatus}\nartifacts:\n - ${stageEvidenceRef}\n\`\`\`\n\n## Test Runtime\n\n- status: ${status}\n- commands:\n${steps.length > 0 ? steps.map((step) => ` - [${step.status}] ${step.command}`).join('\n') : ' - none'}\n- gaps:\n${gaps.length > 0 ? gaps.map((gap) => ` - ${gap}`).join('\n') : ' - none'}\n\n## Acceptance Evidence\n\n${renderEvidenceBlocks(task, status, stageEvidenceRef, steps)}\n`;
720
+ const written = await recordStageEvidenceArtifact(projectRoot, runId, stageEvidenceRef, content, { taskId: task.id, artifactRole: 'test-validation' });
721
+ return { ...written, runRelativePath: stageEvidenceRef };
722
+ }
723
+
724
+ function validationArtifactPath(task: SddTask): string {
725
+ return `test-validation-${task.id}.md`;
726
+ }
727
+
728
+
729
+
730
+ async function persistTestRunState(projectRoot: string, state: RunState, taskId: string, validationStatus: SddTestStatus, commands: string[], evidence: string[], validationArtifact: string | null): Promise<void> {
731
+ const latest = await readRunState(projectRoot, state.runId);
732
+ const knownArtifacts = new Set(latest.artifacts.map((artifact) => artifact.path));
733
+ const now = new Date().toISOString();
734
+ const nextArtifacts = evidence
735
+ .filter((artifactPath) => !knownArtifacts.has(artifactPath))
736
+ .map((artifactPath) => ({ path: artifactPath, kind: testArtifactKind(artifactPath), task: taskId, agent: 'test-runtime', createdAt: now }));
737
+ const existingTaskState = latest.tasks[taskId];
738
+ await writeRunState(projectRoot, {
739
+ ...latest,
740
+ status: validationStatus === 'PASS' ? 'running' : validationStatus === 'FAIL' ? 'failed' : 'blocked',
741
+ phase: 'test',
742
+ currentTask: taskId,
743
+ tasks: {
744
+ ...latest.tasks,
745
+ [taskId]: {
746
+ ...baseRuntimeTaskState(existingTaskState),
747
+ status: validationStatus === 'PASS' ? 'validation_passed_pending_gate' : validationStatus === 'FAIL' ? 'validation_failed' : 'validation_blocked',
748
+ implementationStatus: existingTaskState?.implementationStatus ?? 'not_started',
749
+ verificationStatus: verificationStatusFromTest(validationStatus),
750
+ testStatus: validationStatus,
751
+ evidence
752
+ }
753
+ },
754
+ artifacts: [...latest.artifacts, ...nextArtifacts],
755
+ validation: {
756
+ status: validationStatus === 'PASS' ? 'pass' : validationStatus === 'FAIL' ? 'fail' : 'blocked',
757
+ commands,
758
+ evidence
759
+ }
760
+ });
761
+ }
762
+
763
+ async function persistTestGateOutcome(projectRoot: string, runId: string, taskId: string, status: SddTestStatus, validationStatus: SddTestStatus, commands: string[], evidence: string[], validationArtifact: string | null, gateDecision: WorkflowGateDecision): Promise<void> {
764
+ const latest = await readRunState(projectRoot, runId);
765
+ const knownArtifacts = new Set(latest.artifacts.map((artifact) => artifact.path));
766
+ const now = new Date().toISOString();
767
+ const nextArtifacts = evidence
768
+ .filter((artifactPath) => !knownArtifacts.has(artifactPath))
769
+ .map((artifactPath) => ({ path: artifactPath, kind: testArtifactKind(artifactPath), task: taskId, agent: 'test-runtime', createdAt: now }));
770
+ const existingTaskState = latest.tasks[taskId];
771
+ await writeRunState(projectRoot, {
772
+ ...latest,
773
+ status: status === 'PASS' ? 'completed' : status === 'FAIL' ? 'failed' : 'blocked',
774
+ phase: 'test',
775
+ currentTask: taskId,
776
+ tasks: {
777
+ ...latest.tasks,
778
+ [taskId]: {
779
+ ...baseRuntimeTaskState(existingTaskState),
780
+ status: runtimeTaskStatusAfterGate(status, validationStatus),
781
+ implementationStatus: existingTaskState?.implementationStatus ?? 'not_started',
782
+ verificationStatus: verificationStatusFromTest(validationStatus),
783
+ testStatus: status,
784
+ validationStatus,
785
+ workflowGateStatus: gateDecision.status,
786
+ workflowGateDecisionId: gateDecision.decisionId,
787
+ evidence
788
+ }
789
+ },
790
+ artifacts: [...latest.artifacts, ...nextArtifacts],
791
+ validation: {
792
+ status: validationStatus === 'PASS' ? 'pass' : validationStatus === 'FAIL' ? 'fail' : 'blocked',
793
+ commands,
794
+ evidence
795
+ }
796
+ });
797
+ }
798
+
799
+ function testArtifactKind(artifactPath: string): string {
800
+ const fileName = artifactPath.split('/').pop() ?? artifactPath;
801
+ if (fileName.startsWith('test-validation-')) {
802
+ return 'test-validation';
803
+ }
804
+ return 'test';
805
+ }
806
+
807
+ function baseRuntimeTaskState(existing: RunStateTaskRuntime | undefined): RunStateTaskRuntime {
808
+ return {
809
+ status: existing?.status ?? 'not_started',
810
+ implementationStatus: existing?.implementationStatus ?? 'not_started',
811
+ verificationStatus: existing?.verificationStatus ?? 'not_run',
812
+ validationBatch: existing?.validationBatch ?? null,
813
+ validationTiming: existing?.validationTiming ?? 'task_end',
814
+ requiresVerifyBeforeNext: existing?.requiresVerifyBeforeNext ?? true,
815
+ gaps: existing?.gaps,
816
+ artifacts: existing?.artifacts,
817
+ testStatus: existing?.testStatus,
818
+ workflowGateStatus: existing?.workflowGateStatus,
819
+ workflowGateDecisionId: existing?.workflowGateDecisionId,
820
+ evidence: existing?.evidence
821
+ };
822
+ }
823
+
824
+ function verificationStatusFromTest(validationStatus: SddTestStatus): RunStateTaskRuntime['verificationStatus'] {
825
+ if (validationStatus === 'PASS') {
826
+ return 'pass';
827
+ }
828
+ return validationStatus === 'FAIL' ? 'failed' : 'blocked';
829
+ }
830
+
831
+ function runtimeTaskStatusAfterGate(status: SddTestStatus, validationStatus: SddTestStatus): string {
832
+ if (status === 'PASS') {
833
+ return 'implemented_verified';
834
+ }
835
+ if (validationStatus === 'PASS') {
836
+ return 'workflow_gate_blocked';
837
+ }
838
+ return validationStatus === 'FAIL' ? 'validation_failed' : 'validation_blocked';
839
+ }
840
+
841
+ function deriveCommandStatus(gaps: string[], steps: SddTestCommandStep[]): TestEvidenceStatus {
842
+ if (gaps.length > 0 || steps.some((step) => step.status === 'blocked')) {
843
+ return 'BLOCKED';
844
+ }
845
+ if (steps.some((step) => step.status === 'fail')) {
846
+ return 'FAIL';
847
+ }
848
+ return 'PASS';
849
+ }
850
+
851
+ function buildAcceptanceCoverage(task: SddTask | null, steps: SddTestCommandStep[], commandStatus: TestEvidenceStatus, acceptanceRefsOverride: string[] | undefined): AcceptanceEvidenceCoverage[] {
852
+ const acceptanceRefs = acceptanceRefsOverride && acceptanceRefsOverride.length > 0 ? [...new Set(acceptanceRefsOverride)] : task ? taskAcceptanceRefs(task) : [];
853
+ return acceptanceRefs.map((acceptanceRef) => {
854
+ const mappedSteps = steps.filter((step) => step.acceptanceRefs.includes(acceptanceRef));
855
+ const hasPassingEvidence = mappedSteps.some((step) => step.status === 'pass');
856
+ const hasFailingEvidence = mappedSteps.some((step) => step.status === 'fail' || step.status === 'blocked');
857
+ const status: EvidenceCoverageStatus = hasPassingEvidence && !hasFailingEvidence && commandStatus === 'PASS'
858
+ ? 'complete'
859
+ : mappedSteps.length > 0
860
+ ? 'partial'
861
+ : 'missing';
862
+ return {
863
+ acceptanceRef,
864
+ status,
865
+ evidenceRefs: mappedSteps.map(commandStepRuntimeRef),
866
+ gaps: status === 'complete' ? [] : [`Acceptance ${acceptanceRef} has no complete non-stale execute validation evidence.`]
867
+ };
868
+ });
869
+ }
870
+
871
+ function runtimeEvidenceRefs(validationArtifact: string | null, steps: SddTestCommandStep[]): string[] {
872
+ return [validationArtifact, ...steps.map((step) => step.outputArtifact)].filter((item): item is string => Boolean(item));
873
+ }
874
+
875
+ function commandStepRuntimeRef(step: SddTestCommandStep): { kind: 'command'; ref: string } {
876
+ return { kind: 'command', ref: step.stepId };
877
+ }
878
+
879
+ function artifactRuntimeRefs(ref: string | null): Array<{ kind: 'artifact'; ref: string }> {
880
+ return ref ? [{ kind: 'artifact', ref }] : [];
881
+ }
882
+
883
+ function summarizeEvidenceCoverage(acceptanceCoverage: AcceptanceEvidenceCoverage[]): EvidenceCoverageStatus {
884
+ if (acceptanceCoverage.length === 0) {
885
+ return 'missing';
886
+ }
887
+ if (acceptanceCoverage.every((coverage) => coverage.status === 'complete')) {
888
+ return 'complete';
889
+ }
890
+ if (acceptanceCoverage.some((coverage) => coverage.status === 'complete' || coverage.status === 'partial')) {
891
+ return 'partial';
892
+ }
893
+ return 'missing';
894
+ }
895
+
896
+ function derivePolicyJudgment(commandStatus: TestEvidenceStatus, evidenceCoverage: EvidenceCoverageStatus): SddTestStatus {
897
+ if (commandStatus === 'FAIL') {
898
+ return 'FAIL';
899
+ }
900
+ if (commandStatus === 'BLOCKED' || evidenceCoverage !== 'complete') {
901
+ return 'BLOCKED';
902
+ }
903
+ return 'PASS';
904
+ }
905
+
906
+ async function resolveTestRuntimeDurableGap(
907
+ projectRoot: string,
908
+ branch: string,
909
+ runId: string,
910
+ taskId: string,
911
+ validationStatus: SddTestStatus,
912
+ gaps: string[]
913
+ ): Promise<void> {
914
+ if (validationStatus !== 'PASS' || gaps.length > 0) {
915
+ return;
916
+ }
917
+ await updateRuntimeDurableGapStatus(projectRoot, {
918
+ gapId: testRuntimeGapId(branch, runId, taskId),
919
+ status: 'resolved',
920
+ source: 'gate_policy',
921
+ payload: { validationStatus, gaps }
922
+ });
923
+ }
924
+
925
+ async function recordTestRuntimeDurableGap(
926
+ projectRoot: string,
927
+ branch: string,
928
+ runId: string,
929
+ taskId: string,
930
+ status: SddTestStatus,
931
+ validationStatus: SddTestStatus,
932
+ runtimeJudgment: WorkflowGateStatus,
933
+ gaps: string[],
934
+ evidenceRefs: string[]
935
+ ): Promise<void> {
936
+ if (status === 'PASS' && gaps.length === 0) {
937
+ return;
938
+ }
939
+ const message = gaps[0] ?? (runtimeJudgment === 'PASS' ? `Validation status is ${validationStatus}.` : `Workflow gate status is ${runtimeJudgment}.`);
940
+ await recordRuntimeDurableGap(projectRoot, {
941
+ gapId: testRuntimeGapId(branch, runId, taskId),
942
+ partition: branch,
943
+ taskId,
944
+ runId,
945
+ stage: 'execute',
946
+ gate: 'execute',
947
+ source: 'runtime',
948
+ category: runtimeJudgment === 'PASS' ? 'validation' : 'workflow_gate',
949
+ severity: 'blocking',
950
+ status: 'open',
951
+ message,
952
+ recommendation: `Resolve execute runtime gaps for ${taskId}, then rerun ${testValidationUnitCommand(branch)}.`,
953
+ evidenceRefs,
954
+ proposalRefs: [],
955
+ sourceRefs: [],
956
+ payload: { status, validationStatus, runtimeJudgment, gaps }
957
+ });
958
+ }
959
+
960
+ function testRuntimeGapId(branch: string, _runId: string, taskId: string): string {
961
+ return runtimeScopedId(branch, taskId, 'test-runtime-gap');
962
+ }
963
+
964
+ function finalStatusForTest(validationStatus: SddTestStatus, runtimeJudgment: WorkflowGateStatus): SddTestStatus {
965
+ if (validationStatus !== 'PASS') {
966
+ return validationStatus;
967
+ }
968
+ return runtimeJudgment === 'PASS' || runtimeJudgment === 'WARN' ? 'PASS' : 'BLOCKED';
969
+ }
970
+
971
+ function buildUnifiedTestEvidenceRun(id: string, branch: string, runId: string, taskId: string, commandStatus: TestEvidenceStatus, evidenceCoverage: EvidenceCoverageStatus, policyJudgment: TestEvidenceStatus, status: SddTestStatus, runtimeJudgment: WorkflowGateStatus, steps: SddTestCommandStep[], acceptanceCoverage: AcceptanceEvidenceCoverage[], capabilityEvidence: CapabilityEvidenceClassification[], gaps: string[], gateNextAction: string | null, gateDecision: WorkflowGateDecision | null): UnifiedTestEvidenceRun {
972
+ return {
973
+ contract: TEST_EVIDENCE_RUN_CONTRACT_VERSION,
974
+ id,
975
+ scope: { branch, taskId, runId },
976
+ commandStatus,
977
+ evidenceCoverage,
978
+ policyJudgment,
979
+ commands: steps.map((step) => ({
980
+ command: step.command,
981
+ status: step.status === 'pass' ? 'PASS' : step.status === 'fail' ? 'FAIL' : 'BLOCKED',
982
+ outputRef: step.outputArtifact ? { kind: 'artifact', ref: step.outputArtifact } : undefined,
983
+ evidenceRefs: [commandStepRuntimeRef(step), ...artifactRuntimeRefs(step.outputArtifact)],
984
+ acceptanceRefs: step.acceptanceRefs,
985
+ startedAt: new Date(Date.now() - step.durationMs).toISOString(),
986
+ completedAt: new Date().toISOString()
987
+ })),
988
+ acceptanceCoverage,
989
+ capabilityEvidence,
990
+ gaps: [...gaps, ...acceptanceCoverage.flatMap((coverage) => coverage.gaps)],
991
+ next: nextForTestResult(status, runtimeJudgment, branch, taskId, gateNextAction, gateDecision),
992
+ generatedAt: new Date().toISOString()
993
+ };
994
+ }
995
+
996
+ function buildCapabilityEvidenceClassification(decision: AgentCapabilityRouteDecision | null, steps: SddTestCommandStep[]): CapabilityEvidenceClassification[] {
997
+ if (!decision) {
998
+ return [{
999
+ class: 'diagnostic',
1000
+ source: 'runtime_diagnostic',
1001
+ domainOrSourceId: 'capability-routing',
1002
+ evidenceRefs: [],
1003
+ acceptanceRefs: [],
1004
+ provenanceRefs: [],
1005
+ reason: 'Capability routing did not run; no capability output is accepted as test evidence.'
1006
+ }];
1007
+ }
1008
+ const acceptanceRefs = [...new Set(steps.flatMap((step) => step.acceptanceRefs))];
1009
+ const professionalEvidence = decision.selectedDomains.map((domain) => ({
1010
+ class: 'candidate' as const,
1011
+ source: 'professional_capability' as const,
1012
+ domainOrSourceId: domain.domain,
1013
+ evidenceRefs: [],
1014
+ acceptanceRefs,
1015
+ provenanceRefs: [{ kind: 'projection' as const, ref: `capability:${domain.capabilityId}` }],
1016
+ reason: `${domain.reason}; capability output is advisory candidate evidence until accepted by command evidence and policy refs.`
1017
+ }));
1018
+ const externalEvidence = decision.rejectedExternalSources.map((source) => ({
1019
+ class: capabilityClassForRejectedSource(source.quarantineStatus) as CapabilityEvidenceClassification['class'],
1020
+ source: 'external_source' as const,
1021
+ domainOrSourceId: source.sourceId,
1022
+ evidenceRefs: [],
1023
+ acceptanceRefs: [],
1024
+ provenanceRefs: [{ kind: 'external' as const, ref: source.sourceId }],
1025
+ reason: source.reason
1026
+ }));
1027
+ return [...professionalEvidence, ...externalEvidence];
1028
+ }
1029
+
1030
+ function capabilityClassForRejectedSource(status: AgentCapabilityRouteDecision['rejectedExternalSources'][number]['quarantineStatus']): CapabilityEvidenceClassification['class'] {
1031
+ if (status === 'denied') {
1032
+ return 'blocked';
1033
+ }
1034
+ if (status === 'required' || status === 'quarantined') {
1035
+ return 'quarantined';
1036
+ }
1037
+ return 'diagnostic';
1038
+ }
1039
+
1040
+
1041
+ function capabilityEvidenceSummary(items: CapabilityEvidenceClassification[]): string {
1042
+ if (items.length === 0) {
1043
+ return 'none';
1044
+ }
1045
+ const counts = new Map<CapabilityEvidenceClassification['class'], number>();
1046
+ for (const item of items) {
1047
+ counts.set(item.class, (counts.get(item.class) ?? 0) + 1);
1048
+ }
1049
+ return [...counts.entries()].map(([kind, count]) => `${kind}:${count}`).join(',');
1050
+ }
1051
+
1052
+ function nextForTestResult(status: SddTestStatus, runtimeJudgment: WorkflowGateStatus, branch: string, taskId: string, gateNextAction: string | null, gateDecision: WorkflowGateDecision | null): string {
1053
+ const inspectHint = `Inspect runtime test read model for ${taskId}`;
1054
+ if (status === 'PASS') {
1055
+ return `sdd execute close --branch ${branch} --compact-json`;
1056
+ }
1057
+ if (gateNextAction) {
1058
+ return gateNextAction;
1059
+ }
1060
+ if (runtimeJudgment === 'HUMAN_REQUIRED') {
1061
+ return gateDecision ? `Create a decision card for workflow gate ${gateDecision.decisionId}, then rerun ${testValidationUnitCommand(branch)}.` : `Create a decision card, then rerun ${testValidationUnitCommand(branch)}.`;
1062
+ }
1063
+ if (runtimeJudgment === 'WARN') {
1064
+ return gateDecision ? `Review workflow gate ${gateDecision.decisionId} warnings, then rerun ${testValidationUnitCommand(branch)} or proceed only with explicit review.` : `Review workflow gate warnings, then rerun ${testValidationUnitCommand(branch)}.`;
1065
+ }
1066
+ if (runtimeJudgment === 'ADVISORY_ONLY') {
1067
+ return `Inspect advisor assessments for ${taskId}; advisory output cannot satisfy the test gate.`;
1068
+ }
1069
+ if (status === 'FAIL') {
1070
+ return `${inspectHint}, fix failing validation commands, then rerun ${testValidationUnitCommand(branch)}.`;
1071
+ }
1072
+ return gateDecision ? `${inspectHint} and workflow gate ${gateDecision.decisionId}, resolve blockers, then rerun ${testValidationUnitCommand(branch)}.` : `${inspectHint}, fix command/evidence gaps, then rerun ${testValidationUnitCommand(branch)}.`;
1073
+ }
1074
+
1075
+
1076
+ function testValidationUnitCommand(branch: string): string {
1077
+ return `sdd execute --branch ${branch} --json`;
1078
+ }
1079
+
1080
+ function resultSentenceForTest(result: SddTestResult): string {
1081
+ if (result.status === 'PASS') {
1082
+ return 'Validation and workflow gate passed; proceed to execute evidence judgment and truthAlignment before release readiness.';
1083
+ }
1084
+ if (result.validationStatus === 'PASS' && result.workflowGateStatus !== 'PASS') {
1085
+ return `Validation passed, but workflow gate returned ${result.workflowGateStatus}.`;
1086
+ }
1087
+ if (result.commandStatus === 'BLOCKED') {
1088
+ return 'Blocked before validation commands ran.';
1089
+ }
1090
+ return result.status === 'FAIL' ? 'Validation failed.' : 'Validation did not produce complete evidence.';
1091
+ }
1092
+
1093
+ function renderEvidenceBlocks(task: SddTask, status: SddTestStatus, sourceArtifact: string, steps: SddTestCommandStep[]): string {
1094
+ const acceptances = task.acceptanceRefs.length > 0 ? task.acceptanceRefs : task.acceptance;
1095
+ if (acceptances.length === 0) {
1096
+ return 'No acceptance targets declared.';
1097
+ }
1098
+ const mappedEvidence = acceptances
1099
+ .map((acceptance) => ({ acceptance, steps: steps.filter((step) => step.acceptanceRefs.includes(acceptance)) }))
1100
+ .filter((item) => item.steps.length > 0);
1101
+ if (mappedEvidence.length === 0) {
1102
+ return 'No acceptance evidence emitted; validation commands are not explicitly mapped to acceptance refs.';
1103
+ }
1104
+ return mappedEvidence.map(({ acceptance, steps: mappedSteps }) => {
1105
+ const evidenceStatus = evidenceStatusForMappedSteps(status, mappedSteps);
1106
+ return `\`\`\`sdd-evidence\ncontract: ${SDD_EVIDENCE_CONTRACT}\nversion: ${SDD_EVIDENCE_VERSION}\ntask: ${task.id}\nacceptance: ${acceptance}\nstatus: ${evidenceStatus}\nclaim: Explicit validation mapping ${mappedSteps.map((step) => step.command).join(' && ')} produced ${evidenceStatus} for ${acceptance}.\nsource_artifact: ${sourceArtifact}\nevidence_refs:\n${mappedSteps.map(renderStepEvidenceRefs).join('\n')}\nprovenance_refs:\n - artifact:${sourceArtifact}\n${mappedSteps.map((step) => ` - command:${step.command}`).join('\n')}\npolicy_refs:\n - ${ACCEPTANCE_POLICY_RULESET_VERSION}:require-source-evidence\n - ${ACCEPTANCE_POLICY_RULESET_VERSION}:require-provenance\n - ${ACCEPTANCE_POLICY_RULESET_VERSION}:require-policy-rule\n\`\`\``;
1107
+ }).join('\n\n');
1108
+ }
1109
+
1110
+ function normalizeTestCommandInputs(commandInputs: SddTestCommandInput[] | undefined, commands: string[] | undefined, taskValidation: string[]): NormalizedSddTestCommand[] {
1111
+ if (commandInputs && commandInputs.length > 0) {
1112
+ return commandInputs.map(normalizeTestCommandInput);
1113
+ }
1114
+ return (commands && commands.length > 0 ? commands : taskValidation).map((command) => ({ command, argv: null, shell: true }));
1115
+ }
1116
+
1117
+ function normalizeTestCommandInput(input: SddTestCommandInput): NormalizedSddTestCommand {
1118
+ if (input.argv) {
1119
+ const argv = input.argv.filter((item) => item.length > 0);
1120
+ if (argv.length === 0) {
1121
+ throw new Error('Command argv input must include an executable.');
1122
+ }
1123
+ return { command: argv.join(' '), argv, shell: false };
1124
+ }
1125
+ if (input.command) {
1126
+ return { command: input.command, argv: null, shell: true };
1127
+ }
1128
+ throw new Error('Command input must include command or argv.');
1129
+ }
1130
+
1131
+ function acceptanceRefsForCommand(task: SddTask | null, command: string, acceptanceRefsOverride: string[] | undefined): string[] {
1132
+ if (acceptanceRefsOverride && acceptanceRefsOverride.length > 0) {
1133
+ return [...new Set(acceptanceRefsOverride)];
1134
+ }
1135
+ return [...new Set((task?.validationCommands ?? [])
1136
+ .filter((entry) => entry.command === command)
1137
+ .flatMap((entry) => entry.acceptanceRefs))];
1138
+ }
1139
+
1140
+ function taskAcceptanceRefs(task: SddTask): string[] {
1141
+ const refs = task.acceptanceRefs.length > 0 ? task.acceptanceRefs : task.acceptance;
1142
+ return [...new Set(refs)];
1143
+ }
1144
+
1145
+ function evidenceStatusForMappedSteps(status: SddTestStatus, steps: SddTestCommandStep[]): SddTestStatus {
1146
+ if (steps.some((step) => step.status === 'fail')) {
1147
+ return 'FAIL';
1148
+ }
1149
+ if (status === 'BLOCKED' || steps.some((step) => step.status === 'blocked')) {
1150
+ return 'BLOCKED';
1151
+ }
1152
+ return 'PASS';
1153
+ }
1154
+
1155
+ function renderCommandOutput(commandInput: NormalizedSddTestCommand, status: SddTestStepStatus, executed: { exitCode: number | null; signal: string | null; stdout: string; stderr: string; truncated: boolean; timedOut: boolean; error: string | null }, durationMs: number, cacheStatus: 'hit' | 'miss' | 'unsafe' = 'unsafe', cacheKey: string | null = null, cacheSourceTestRunId: string | null = null, cacheUnsafeReasons: ValidationCacheUnsafeReason[] = []): string {
1156
+ return `# Test Command Output\n\n- command: ${commandInput.command}\n- shell: ${commandInput.shell}\n- argv: ${commandInput.argv ? JSON.stringify(commandInput.argv) : 'none'}\n- status: ${status}\n- exit_code: ${executed.exitCode ?? 'none'}\n- signal: ${executed.signal ?? 'none'}\n- duration_ms: ${durationMs}\n- timed_out: ${executed.timedOut}\n- truncated: ${executed.truncated}\n- error: ${executed.error ?? 'none'}\n- cache_status: ${cacheStatus}\n- cache_key: ${cacheKey ?? 'none'}\n- cache_source_test_run: ${cacheSourceTestRunId ?? 'none'}\n- cache_unsafe_reasons: ${cacheUnsafeReasons.join(',') || 'none'}\n\n## stdout\n\n\`\`\`text\n${executed.stdout}\n\`\`\`\n\n## stderr\n\n\`\`\`text\n${executed.stderr}\n\`\`\`\n`;
1157
+ }
1158
+
1159
+ function shouldPersistCommandOutputArtifact(status: SddTestStepStatus, executed: { truncated: boolean; timedOut: boolean; error: string | null }, cacheStatus: 'hit' | 'miss' | 'unsafe', cacheUnsafeReasons: ValidationCacheUnsafeReason[]): boolean {
1160
+ return status !== 'pass' || executed.truncated || executed.timedOut || Boolean(executed.error) || cacheStatus === 'unsafe' || cacheUnsafeReasons.length > 0;
1161
+ }
1162
+
1163
+ function renderStepEvidenceRefs(step: SddTestCommandStep): string {
1164
+ const refs = [` - command:${step.stepId}`];
1165
+ if (step.outputArtifact) {
1166
+ refs.push(` - artifact:${step.outputArtifact}`);
1167
+ }
1168
+ return refs.join('\n');
1169
+ }
1170
+
1171
+
1172
+ function summarizeCommandOutput(executed: { exitCode: number | null; signal: string | null; stdout: string; stderr: string; stdoutBytes: number; stderrBytes: number; truncated: boolean; timedOut: boolean; error: string | null }): string {
1173
+ const parts = [
1174
+ `exit=${executed.exitCode ?? 'none'}`,
1175
+ `signal=${executed.signal ?? 'none'}`,
1176
+ `stdout_bytes=${executed.stdoutBytes}`,
1177
+ `stderr_bytes=${executed.stderrBytes}`,
1178
+ `truncated=${executed.truncated}`,
1179
+ `timed_out=${executed.timedOut}`
1180
+ ];
1181
+ if (executed.error) {
1182
+ parts.push(`error=${executed.error}`);
1183
+ }
1184
+ return parts.join(' ');
1185
+ }
1186
+
1187
+
1188
+ function hashDocumentContent(raw: string): string {
1189
+ return createHash('sha256').update(raw.replace(/\r\n/g, '\n'), 'utf8').digest('hex');
1190
+ }