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
@@ -0,0 +1,2964 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { access, mkdir, mkdtemp, readFile, readdir, rm, writeFile } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
5
+ import path from 'node:path';
6
+
7
+ import { LIFECYCLE_RISK_DECISION_CONTRACT_VERSION, STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION, VERIFY_DOCUMENT_CONTRACT_VERSION, type RuntimeRef, type RuntimeRefKind, type SddStage } from './contracts.js';
8
+ import type { LifecycleRiskDecision } from './risk/contracts.js';
9
+ import { initProject } from './config/init-project.js';
10
+ import {
11
+ adjudicateSpecStageClosureRequest,
12
+ buildPlanStageWorkOrder,
13
+ buildShipStageWorkOrder,
14
+ buildSpecStageWorkOrder,
15
+ buildTasksStageWorkOrder,
16
+ buildExecuteStageWorkOrder,
17
+ deriveSpecCollaborationProfile,
18
+ inspectFullStageChain,
19
+ inspectSpecCollaborationHealth,
20
+ reconcileExecuteCollaborationClosure,
21
+ reconcileShipCollaborationClosure,
22
+ reconcilePlanCollaborationClosure,
23
+ reconcileTasksCollaborationClosure,
24
+ reconcileSpecCollaborationClosure,
25
+ readTruthAlignmentProjection,
26
+ recordSpecCollaborationAdjudicationProjection,
27
+ type CapabilityFinding,
28
+ type SpecDocumentCandidate,
29
+ type SpecManagerCoordinationRecord,
30
+ type SpecProposalItemKind,
31
+ type SpecReviewResult,
32
+ type StageClosureRequest,
33
+ type StageCollaborationProfile
34
+ } from './stage-collaboration.js';
35
+ import { inspectWorkflowStageHandoff, readStageRunProjection, readWorkflowHandoffProjection, recordStageRunProjection, recordWorkflowHandoffProjection } from './stage-runtime/runtime.js';
36
+ import { hashDocumentContent } from './sdd-docs/document-hashes.js';
37
+ import { recordTaskExecutionProjectionBundleFromAcceptedTasks } from './task-execution-contract.js';
38
+ import { listRuntimeStageArtifacts, listRuntimeStageCollaborationContracts, recordRuntimeStageArtifact } from './storage/runtime-store.js';
39
+ import { expectedStageArtifactContracts, readMarkdownArtifact, validateStageArtifactFrontmatter } from './stage-artifacts.js';
40
+
41
+ const generatedAt = '2026-01-01T00:00:00.000Z';
42
+
43
+ test('direct lifecycle keeps spec collaboration as runtime no-op', () => {
44
+ const profile = deriveSpecCollaborationProfile(decision('direct', ['execute', 'ship']), generatedAt);
45
+ const result = adjudicateSpecStageClosureRequest({ profile, generatedAt });
46
+
47
+ assert.equal(profile.contract, STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION);
48
+ assert.equal(profile.intensity, 'noop');
49
+ assert.equal(profile.required, false);
50
+ assert.equal(profile.requiresStageClosure, false);
51
+ assert.equal(result.health, 'no-op');
52
+ assert.equal(result.stageDecision?.status, 'skipped');
53
+ assert.equal(result.handoffPacket, null);
54
+ });
55
+
56
+ test('full lifecycle creates spec-manager work order with agent-team proposal-only authority', () => {
57
+ const profile = deriveSpecCollaborationProfile(decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']), generatedAt);
58
+ const workOrder = buildSpecStageWorkOrder(profile, ref('projection', 'profile/spec'), generatedAt);
59
+
60
+ assert.equal(profile.intensity, 'team-required');
61
+ assert.equal(profile.required, true);
62
+ assert.equal(profile.requiresStageClosure, true);
63
+ assert.equal(workOrder.authorityCeiling, 'proposal_input');
64
+ assert.deepEqual(workOrder.forbiddenActions, ['stage_pass', 'risk_decision', 'gate_pass', 'ship_ready', 'truth_alignment_approved']);
65
+ assert.equal(workOrder.stageManager, 'spec-manager');
66
+ assert.deepEqual(workOrder.agentTeam, ['scout', 'spec-reviewer']);
67
+ assert.deepEqual(workOrder.requiredOutputKinds, ['scout_context', 'spec_review', 'manager_closure_request']);
68
+ assert.deepEqual(workOrder.requiredCapabilities, ['norm_discovery', 'uncertainty_resolution']);
69
+ assert.equal(workOrder.collaborationPlan.topology, 'team-required');
70
+ assert.equal(workOrder.collaborationPlan.fanIn, 'runtime_adjudication');
71
+ assert.equal(workOrder.collaborationPlan.participants.some((participant) => participant.kind === 'agent' && participant.id === 'spec-manager'), true);
72
+ assert.equal(workOrder.collaborationPlan.participants.some((participant) => participant.kind === 'subagent' && participant.id === 'spec-reviewer' && participant.role === 'spec-document-reviewer'), true);
73
+ assert.equal(workOrder.collaborationPlan.participants.some((participant) => participant.kind === 'subagent' && participant.id === 'scout' && participant.parallelGroup === 'discovery'), true);
74
+ assert.equal(workOrder.collaborationPlan.participants.some((participant) => participant.kind === 'skill' && participant.id === 'cap.norm_discovery'), true);
75
+ assert.equal(workOrder.collaborationPlan.participants.some((participant) => participant.kind === 'material-pack' && participant.id === 'project-norms'), true);
76
+ });
77
+
78
+ test('stage run projection keeps completed stage from being downgraded by later rejected diagnostics', async () => {
79
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-stage-run-terminal-'));
80
+ try {
81
+ await initProject(root);
82
+ const scope = { branch: 'feature' };
83
+ const completed = testStageRun('execute-completed', scope, 'completed', '2026-01-01T00:00:00.000Z');
84
+ const rejected = testStageRun('execute-rejected', scope, 'failed', '2026-01-01T00:01:00.000Z');
85
+
86
+ await recordStageRunProjection(root, completed);
87
+ const result = await recordStageRunProjection(root, rejected);
88
+ const stored = await readStageRunProjection(root, scope, 'execute');
89
+
90
+ assert.equal(result.status, 'unchanged');
91
+ assert.equal(stored?.payload.id, completed.id);
92
+ assert.equal(stored?.payload.status, 'completed');
93
+ } finally {
94
+ await rm(root, { recursive: true, force: true });
95
+ }
96
+ });
97
+
98
+ test('blocking ambiguity produces clarification gate instead of handoff', () => {
99
+ const profile = deriveSpecCollaborationProfile(decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']), generatedAt);
100
+ const team = stageClosure(profile, { items: [candidateItem('amb-1', 'blocking_ambiguity')] });
101
+ const result = adjudicateSpecStageClosureRequest({ profile, closureRequest: team.closureRequest, generatedAt });
102
+
103
+ assert.equal(result.health, 'needs_clarification');
104
+ assert.equal(result.clarificationGate?.status, 'needs_clarification');
105
+ assert.deepEqual(result.clarificationGate?.blockingItemIds, ['amb-1']);
106
+ assert.equal(result.stageDecision, null);
107
+ assert.equal(result.handoffPacket, null);
108
+ assert.equal(profile.collaborationPlan.topology, 'team-required');
109
+ });
110
+
111
+ test('answered clarification becomes spec decision and spec to plan handoff', () => {
112
+ const profile = deriveSpecCollaborationProfile(decision('research', ['spec', 'plan', 'tasks']), generatedAt);
113
+ const team = stageClosure(profile, { items: [candidateItem('amb-1', 'blocking_ambiguity'), candidateItem('find-1', 'advisory_finding')] });
114
+ const result = adjudicateSpecStageClosureRequest({
115
+ profile,
116
+ closureRequest: team.closureRequest,
117
+ answerRefs: [ref('external', 'answer/spec-ambiguity')],
118
+ generatedAt
119
+ });
120
+
121
+ assert.equal(result.health, 'ready_for_plan');
122
+ assert.equal(result.specDecisionRecord?.source, 'clarification-answer');
123
+ assert.deepEqual(result.specDecisionRecord?.acceptedItemIds, ['amb-1', 'find-1']);
124
+ assert.equal(result.stageDecision?.status, 'completed');
125
+ assert.equal(result.handoffPacket?.fromStage, 'spec');
126
+ assert.equal(result.handoffPacket?.toStage, 'plan');
127
+ assert.deepEqual(result.handoffPacket?.recommendedNextStageRoles, ['role.norm-scout', 'role.uncertainty-reviewer']);
128
+ assert.equal(profile.collaborationPlan.topology, 'parallel-research');
129
+ });
130
+
131
+ test('valid stage closure is accepted into runtime-owned stage decision and handoff', () => {
132
+ const profile = deriveSpecCollaborationProfile(decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']), generatedAt);
133
+ const team = stageClosure(profile);
134
+ const result = adjudicateSpecStageClosureRequest({ profile, closureRequest: team.closureRequest, generatedAt });
135
+
136
+ assert.equal(result.health, 'ready_for_plan');
137
+ assert.deepEqual(result.acceptedItemIds, ['find-1', 'cap-1', 'handoff-1']);
138
+ assert.equal(result.specDecisionRecord?.source, 'stage-closure-request');
139
+ assert.equal(result.stageDecision?.health, 'ready_for_plan');
140
+ assert.equal(result.handoffPacket?.status, 'proposed');
141
+ assert.deepEqual(result.handoffPacket?.recommendedNextStageRoles, ['role.norm-scout', 'role.performance-planner', 'role.verification-designer']);
142
+ });
143
+
144
+ test('authority-violating closure request is rejected with actionable metadata', () => {
145
+ const profile = deriveSpecCollaborationProfile(decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']), generatedAt);
146
+ const team = stageClosure(profile, { authorityAttempts: ['stage_pass'] });
147
+ const result = adjudicateSpecStageClosureRequest({ profile, closureRequest: team.closureRequest, generatedAt, retryBudgetRemaining: 2 });
148
+
149
+ assert.equal(result.health, 'rejected');
150
+ assert.equal(result.rejection?.reasonCode, 'authority_violation');
151
+ assert.equal(result.rejection?.retryAllowed, true);
152
+ assert.equal(result.rejection?.retryBudgetRemaining, 2);
153
+ assert.equal(result.rejection?.fallbackRoute, 'revise-proposal');
154
+ assert.match(result.rejection?.requiredNextAction ?? '', /Remove workflow-authority/);
155
+ });
156
+
157
+ test('candidate items must carry refs', () => {
158
+ const profile = deriveSpecCollaborationProfile(decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']), generatedAt);
159
+ const team = stageClosure(profile, { items: [candidateItem('find-1', 'advisory_finding', [])] });
160
+ const result = adjudicateSpecStageClosureRequest({ profile, closureRequest: team.closureRequest, generatedAt });
161
+
162
+ assert.equal(result.health, 'rejected');
163
+ assert.equal(result.rejection?.reasonCode, 'missing_refs');
164
+ });
165
+
166
+ test('spec closure does not require spec-reviewer approval as runtime truth', () => {
167
+ const profile = deriveSpecCollaborationProfile(decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']), generatedAt);
168
+ const team = stageClosure(profile);
169
+ const closureRequest = { ...team.closureRequest, reviewRefs: [], reviewResults: [] };
170
+ const result = adjudicateSpecStageClosureRequest({ profile, closureRequest, generatedAt });
171
+
172
+ assert.equal(result.health, 'ready_for_plan');
173
+ assert.equal(result.rejection, null);
174
+ assert.equal(result.nextActions[0]?.reasonCode, 'stage_ready');
175
+ });
176
+
177
+ test('spec closure requires runtime close boundary facts for close-stage recommendation', () => {
178
+ const profile = deriveSpecCollaborationProfile(decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']), generatedAt);
179
+ const team = stageClosure(profile);
180
+ const closureRequest = { ...team.closureRequest, runtimeCloseBoundaryFacts: null };
181
+ const result = adjudicateSpecStageClosureRequest({ profile, closureRequest, generatedAt });
182
+
183
+ assert.equal(result.health, 'rejected');
184
+ assert.equal(result.rejection?.reasonCode, 'missing_refs');
185
+ });
186
+
187
+ test('spec collaboration adjudication projection exposes compact health', async () => {
188
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-spec-collaboration-'));
189
+ try {
190
+ await initProject(root);
191
+ const profile = deriveSpecCollaborationProfile(decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']), generatedAt);
192
+ const team = stageClosure(profile);
193
+ const result = adjudicateSpecStageClosureRequest({ profile, closureRequest: team.closureRequest, generatedAt });
194
+
195
+ await recordSpecCollaborationAdjudicationProjection(root, result);
196
+ const health = await inspectSpecCollaborationHealth(root, 'master');
197
+
198
+ assert.equal(health.status, 'ready_for_plan');
199
+ assert.equal(health.projectionCount, 1);
200
+ assert.equal(health.latestHandoffId, result.handoffPacket?.handoffId);
201
+ assert.equal(health.latestClarificationGateId, null);
202
+ assert.equal(health.latestRejectionReason, null);
203
+ assert.match(health.reasons.join(' '), /ready for plan handoff/);
204
+ } finally {
205
+ await rm(root, { recursive: true, force: true });
206
+ }
207
+ });
208
+
209
+ test('full-stage chain diagnostic reports missing projections before stage closure', async () => {
210
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-full-chain-missing-'));
211
+ try {
212
+ await initProject(root);
213
+ const chain = await inspectFullStageChain(root, 'master');
214
+
215
+ assert.equal(chain.contract, 'sdd-full-stage-chain-diagnostic-v1');
216
+ assert.equal(chain.status, 'missing');
217
+ assert.equal(chain.stages.length, 5);
218
+ assert.equal(chain.projectionCounts.adjudications, 0);
219
+ assert.equal(chain.projectionCounts.completedStages, 0);
220
+ assert.equal(chain.projectionCounts.validatedCollaborationContracts, 0);
221
+ assert.equal(chain.finalHealth, null);
222
+ assert.equal(chain.stages.every((stage) => stage.projectionRef === null), true);
223
+ } finally {
224
+ await rm(root, { recursive: true, force: true });
225
+ }
226
+ });
227
+
228
+ test('ready closure accepts final spec document without spec-stage side evidence', async () => {
229
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-spec-closure-ready-'));
230
+ try {
231
+ await initProject(root);
232
+ const reviewedSpecContent = reviewedSpec();
233
+ const specHash = hashDocumentContent(reviewedSpecContent);
234
+ await writeFile(path.join(root, 'specs', 'master', 'spec.md'), reviewedSpecContent, 'utf8');
235
+ const riskDecision = decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']);
236
+
237
+ const closure = await reconcileSpecCollaborationClosure(root, {
238
+ decision: riskDecision,
239
+ generatedAt,
240
+ runId: 'run-ready-closure',
241
+ outputCloseRequest: outputCloseRequest()
242
+ });
243
+ const specContent = await readFile(path.join(root, 'specs', 'master', 'spec.md'), 'utf8');
244
+ const health = await inspectSpecCollaborationHealth(root, 'master');
245
+ const handoff = await inspectWorkflowStageHandoff(root, 'master');
246
+ const registered = await listRuntimeStageArtifacts(root, { branchSlug: 'master', stage: 'spec' });
247
+ const registeredContracts = await listRuntimeStageCollaborationContracts(root, { branchSlug: 'master', stage: 'spec' });
248
+
249
+ assert.equal(closure.adjudication.closureRefs?.specAcceptanceStatus, 'accepted');
250
+ assert.equal(closure.acceptedSpecRef?.ref, 'specs/master/spec.md');
251
+ assert.equal(closure.acceptedSpecRef?.hash, specHash);
252
+ assert.equal(specContent, reviewedSpecContent);
253
+ assert.deepEqual(closure.artifactRefs, []);
254
+ assert.equal(registered.length, 0);
255
+ assert.equal(registeredContracts.length, 0);
256
+ assert.equal(closure.registeredCollaborationContracts.length, 0);
257
+ assert.equal(closure.adjudication.closureRefs?.collaborationContractRef, null);
258
+ assert.equal(health.status, 'ready_for_plan');
259
+ assert.equal(health.latest?.closureRefs?.runRef.ref, 'run-ready-closure');
260
+ assert.equal(health.latest?.closureRefs?.acceptedSpecRef?.ref, 'specs/master/spec.md');
261
+ assert.equal(handoff.status, 'fresh');
262
+ assert.equal(handoff.latestStageRun?.status, 'completed');
263
+ assert.equal(handoff.latestHandoff?.requiredInputRefs[0]?.ref, 'specs/master/spec.md');
264
+ assert.equal(handoff.latestHandoff?.requiredInputRefs[0]?.hash, specHash);
265
+ await assert.rejects(access(path.join(root, '.sdd', 'runs', 'master', 'evidence', 'artifacts', 'phase9.1')));
266
+ } finally {
267
+ await rm(root, { recursive: true, force: true });
268
+ }
269
+ });
270
+
271
+ test('spec closure rejects structurally complete but shallow spec document', async () => {
272
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-spec-closure-shallow-'));
273
+ try {
274
+ await initProject(root);
275
+ await writeFile(path.join(root, 'specs', 'master', 'spec.md'), shallowSpec(), 'utf8');
276
+ const riskDecision = decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']);
277
+
278
+ const closure = await reconcileSpecCollaborationClosure(root, {
279
+ decision: riskDecision,
280
+ generatedAt,
281
+ runId: 'run-shallow-spec-closure',
282
+ outputCloseRequest: outputCloseRequest()
283
+ });
284
+
285
+ assert.equal(closure.adjudication.health, 'rejected');
286
+ assert.equal(closure.adjudication.closureRefs?.specAcceptanceStatus, 'not_accepted_wrong_ref');
287
+ assert.match(closure.adjudication.rejection?.explanation ?? '', /blocker:spec\.downstream_guesswork\.plan_would_rediscover_requirements/);
288
+ } finally {
289
+ await rm(root, { recursive: true, force: true });
290
+ }
291
+ });
292
+
293
+ test('ready structural closure does not require collaboration contract for spec handoff', async () => {
294
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-spec-closure-missing-contract-'));
295
+ try {
296
+ await initProject(root);
297
+ const reviewedSpecContent = reviewedSpec();
298
+ const specHash = hashDocumentContent(reviewedSpecContent);
299
+ await writeFile(path.join(root, 'specs', 'master', 'spec.md'), reviewedSpecContent, 'utf8');
300
+ const riskDecision = decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']);
301
+
302
+ const closure = await reconcileSpecCollaborationClosure(root, {
303
+ decision: riskDecision,
304
+ generatedAt,
305
+ runId: 'run-missing-contract-closure',
306
+ outputCloseRequest: outputCloseRequest()
307
+ });
308
+ const handoff = await inspectWorkflowStageHandoff(root, 'master');
309
+ const registeredContracts = await listRuntimeStageCollaborationContracts(root, { branchSlug: 'master', stage: 'spec' });
310
+
311
+ assert.equal(closure.adjudication.health, 'ready_for_plan');
312
+ assert.equal(closure.adjudication.closureRefs?.specAcceptanceStatus, 'accepted');
313
+ assert.equal(closure.acceptedSpecRef?.ref, 'specs/master/spec.md');
314
+ assert.equal(closure.acceptedSpecRef?.hash, specHash);
315
+ assert.equal(registeredContracts.length, 0);
316
+ assert.equal(handoff.status, 'fresh');
317
+ } finally {
318
+ await rm(root, { recursive: true, force: true });
319
+ }
320
+ });
321
+
322
+ test('stage artifact registration supports plan-stage review and manager artifacts', async () => {
323
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-plan-artifact-registration-'));
324
+ try {
325
+ await initProject(root);
326
+ const planContent = reviewedPlan();
327
+ const planHash = hashDocumentContent(planContent);
328
+ await mkdir(path.join(root, 'specs', 'master'), { recursive: true });
329
+ await writeFile(path.join(root, 'specs', 'master', 'plan.md'), planContent, 'utf8');
330
+ await writePlanStageArtifacts(root, { planHash });
331
+
332
+ const reviewArtifact = await readMarkdownArtifact(root, '.sdd/runs/master/plan/plan-review-v1.md');
333
+ const reviewRecord = validateStageArtifactFrontmatter({
334
+ branch: 'master',
335
+ stage: 'plan',
336
+ ref: reviewArtifact.ref,
337
+ hash: reviewArtifact.hash,
338
+ frontmatter: reviewArtifact.frontmatter,
339
+ registeredAt: generatedAt
340
+ });
341
+ const managerArtifact = await readMarkdownArtifact(root, '.sdd/runs/master/plan/plan-manager-v1.md');
342
+ const managerRecord = validateStageArtifactFrontmatter({
343
+ branch: 'master',
344
+ stage: 'plan',
345
+ ref: managerArtifact.ref,
346
+ hash: managerArtifact.hash,
347
+ frontmatter: managerArtifact.frontmatter,
348
+ registeredAt: generatedAt
349
+ });
350
+ await recordRuntimeStageArtifact(root, reviewRecord);
351
+ await recordRuntimeStageArtifact(root, managerRecord);
352
+
353
+ const registered = await listRuntimeStageArtifacts(root, { branchSlug: 'master', stage: 'plan' });
354
+
355
+ assert.deepEqual(registered.map((artifact) => artifact.kind).sort(), ['manager_closure_request', 'plan_review']);
356
+ assert.equal(reviewRecord.targetRef?.ref, 'specs/master/plan.md');
357
+ assert.equal(reviewRecord.targetHash, planHash);
358
+ assert.equal(managerRecord.reviewRef?.ref, '.sdd/runs/master/plan/plan-review-v1.md');
359
+ assert.equal(managerRecord.reviewHash, reviewArtifact.hash);
360
+ assert.throws(() => validateStageArtifactFrontmatter({
361
+ branch: 'master',
362
+ stage: 'plan',
363
+ ref: reviewArtifact.ref,
364
+ hash: reviewArtifact.hash,
365
+ frontmatter: { ...reviewArtifact.frontmatter, producer: 'spec-reviewer' },
366
+ registeredAt: generatedAt
367
+ }), /must be produced by plan-review-agent/);
368
+ } finally {
369
+ await rm(root, { recursive: true, force: true });
370
+ }
371
+ });
372
+
373
+ test('stage artifact registration supports review and manager pairs through ship', async () => {
374
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-stage-artifact-registration-'));
375
+ try {
376
+ await initProject(root);
377
+ for (const stageCase of stageArtifactPairCases) {
378
+ await writeStageArtifactPair(root, stageCase);
379
+ const reviewArtifact = await readMarkdownArtifact(root, `.sdd/runs/master/${stageCase.stage}/${stageCase.reviewFile}`);
380
+ const reviewRecord = validateStageArtifactFrontmatter({
381
+ branch: 'master',
382
+ stage: stageCase.stage,
383
+ ref: reviewArtifact.ref,
384
+ hash: reviewArtifact.hash,
385
+ frontmatter: reviewArtifact.frontmatter,
386
+ registeredAt: generatedAt
387
+ });
388
+ const managerArtifact = await readMarkdownArtifact(root, `.sdd/runs/master/${stageCase.stage}/${stageCase.managerFile}`);
389
+ const managerRecord = validateStageArtifactFrontmatter({
390
+ branch: 'master',
391
+ stage: stageCase.stage,
392
+ ref: managerArtifact.ref,
393
+ hash: managerArtifact.hash,
394
+ frontmatter: managerArtifact.frontmatter,
395
+ registeredAt: generatedAt
396
+ });
397
+ await recordRuntimeStageArtifact(root, reviewRecord);
398
+ await recordRuntimeStageArtifact(root, managerRecord);
399
+
400
+ const registered = await listRuntimeStageArtifacts(root, { branchSlug: 'master', stage: stageCase.stage });
401
+ const registeredKinds = registered.map((artifact) => artifact.kind);
402
+
403
+ assert.equal(registeredKinds.includes(stageCase.reviewKind), true);
404
+ assert.equal(registeredKinds.includes('manager_closure_request'), true);
405
+ assert.equal(reviewRecord.producer, stageCase.reviewProducer);
406
+ assert.equal(reviewRecord.targetRef?.ref, stageCase.targetRef);
407
+ assert.equal(managerRecord.producer, stageCase.managerProducer);
408
+ assert.equal(managerRecord.reviewRef?.ref, `.sdd/runs/master/${stageCase.stage}/${stageCase.reviewFile}`);
409
+ assert.equal(managerRecord.reviewHash, reviewArtifact.hash);
410
+ }
411
+ } finally {
412
+ await rm(root, { recursive: true, force: true });
413
+ }
414
+ });
415
+
416
+ test('clarification closure records agent-authored evidence without accepting spec or handoff', async () => {
417
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-spec-closure-clarification-'));
418
+ try {
419
+ await initProject(root);
420
+ const reviewedSpecContent = reviewedSpec();
421
+ const specHash = hashDocumentContent(reviewedSpecContent);
422
+ await writeFile(path.join(root, 'specs', 'master', 'spec.md'), reviewedSpecContent, 'utf8');
423
+ await writeSpecStageArtifacts(root, { specHash, verdict: 'blocked', blockingCount: 1, recommendation: 'request_clarification' });
424
+ const riskDecision = decision('research', ['spec', 'plan', 'tasks']);
425
+ const profile = deriveSpecCollaborationProfile(riskDecision, generatedAt);
426
+ const blockingItem = candidateItem('before-plan-question', 'blocking_ambiguity');
427
+ const closureRequest = stageClosure(profile, { items: [blockingItem] }).closureRequest;
428
+
429
+ const closure = await reconcileSpecCollaborationClosure(root, {
430
+ decision: riskDecision,
431
+ generatedAt,
432
+ runId: 'run-clarification-closure',
433
+ artifactRefs: ['.sdd/runs/master/spec/scout.md', '.sdd/runs/master/spec/spec-review-v1.md', '.sdd/runs/master/spec/spec-manager-v1.md'],
434
+ closureRequest,
435
+ });
436
+ const health = await inspectSpecCollaborationHealth(root, 'master');
437
+ const handoff = await inspectWorkflowStageHandoff(root, 'master');
438
+
439
+ assert.equal(closure.adjudication.health, 'needs_clarification');
440
+ assert.equal(closure.adjudication.closureRefs?.specAcceptanceStatus, 'not_accepted_needs_clarification');
441
+ assert.equal(closure.acceptedSpecRef, null);
442
+ assert.equal(closure.artifactRefs.every((artifact) => artifact.ref.startsWith('.sdd/runs/master/spec/')), true);
443
+ assert.equal(health.status, 'needs_clarification');
444
+ assert.equal(handoff.status, 'missing');
445
+ await assert.rejects(access(path.join(root, '.sdd', 'runs', 'master', 'evidence', 'artifacts', 'phase9.1')));
446
+ } finally {
447
+ await rm(root, { recursive: true, force: true });
448
+ }
449
+ });
450
+
451
+ test('ready structural closure rejects mismatched canonical spec hash without handoff', async () => {
452
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-spec-closure-mismatch-'));
453
+ try {
454
+ await initProject(root);
455
+ const reviewedSpecContent = reviewedSpec();
456
+ await writeFile(path.join(root, 'specs', 'master', 'spec.md'), reviewedSpecContent, 'utf8');
457
+ const riskDecision = decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']);
458
+ const profile = deriveSpecCollaborationProfile(riskDecision, generatedAt);
459
+ const staleClosureRequest = stageClosure(profile).closureRequest;
460
+
461
+ const closure = await reconcileSpecCollaborationClosure(root, {
462
+ decision: riskDecision,
463
+ generatedAt,
464
+ runId: 'run-hash-mismatch-closure',
465
+ closureRequest: staleClosureRequest
466
+ });
467
+ const handoff = await inspectWorkflowStageHandoff(root, 'master');
468
+
469
+ assert.equal(closure.adjudication.health, 'rejected');
470
+ assert.equal(closure.adjudication.rejection?.reasonCode, 'invalid_proposal');
471
+ assert.equal(closure.adjudication.closureRefs?.specAcceptanceStatus, 'not_accepted_hash_mismatch');
472
+ assert.equal(closure.acceptedSpecRef, null);
473
+ assert.equal(handoff.status, 'missing');
474
+ } finally {
475
+ await rm(root, { recursive: true, force: true });
476
+ }
477
+ });
478
+
479
+ test('ready plan closure accepts reviewed plan document and records plan to tasks handoff', async () => {
480
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-plan-closure-ready-'));
481
+ try {
482
+ await initProject(root);
483
+ const riskDecision = decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']);
484
+ await writeAcceptedSpecClosure(root, riskDecision, 'run-spec-ready-for-plan');
485
+ const planContent = reviewedPlan();
486
+ const planHash = hashDocumentContent(planContent);
487
+ await writeFile(path.join(root, 'specs', 'master', 'plan.md'), planContent, 'utf8');
488
+
489
+
490
+ const closure = await reconcilePlanCollaborationClosure(root, {
491
+ decision: riskDecision,
492
+ generatedAt,
493
+ runId: 'run-plan-ready-closure',
494
+ outputCloseRequest: planOutputCloseRequest()
495
+ });
496
+ const planContentAfterClosure = await readFile(path.join(root, 'specs', 'master', 'plan.md'), 'utf8');
497
+ const registered = await listRuntimeStageArtifacts(root, { branchSlug: 'master', stage: 'plan' });
498
+ const handoff = await readWorkflowHandoffProjection(root, riskDecision.scope, 'plan', 'tasks');
499
+
500
+ assert.equal(closure.adjudication.health, 'ready_for_tasks');
501
+ assert.equal(closure.adjudication.closureRefs.planAcceptanceStatus, 'accepted');
502
+ assert.equal(closure.acceptedPlanRef?.ref, 'specs/master/plan.md');
503
+ assert.equal(closure.acceptedPlanRef?.hash, planHash);
504
+ assert.equal(planContentAfterClosure, planContent);
505
+ assert.deepEqual(registered.map((artifact) => artifact.kind).sort(), []);
506
+ assert.equal(handoff?.payload.fromStage, 'plan');
507
+ assert.equal(handoff?.payload.toStage, 'tasks');
508
+ const acceptedPlanInputRef = handoff?.payload.requiredInputRefs.find((inputRef) => inputRef.ref === 'specs/master/plan.md');
509
+ assert.equal(acceptedPlanInputRef?.ref, 'specs/master/plan.md');
510
+ assert.equal(acceptedPlanInputRef?.hash, planHash);
511
+ await assert.rejects(access(path.join(root, '.sdd', 'runs', 'master', 'evidence', 'artifacts', 'phase9.2')));
512
+ } finally {
513
+ await rm(root, { recursive: true, force: true });
514
+ }
515
+ });
516
+
517
+ test('plan closure accepts warning-only artifact-depth diagnostics without close trap', async () => {
518
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-plan-closure-warning-only-'));
519
+ try {
520
+ await initProject(root);
521
+ const riskDecision = decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']);
522
+ await writeAcceptedSpecClosure(root, riskDecision, 'run-spec-ready-for-plan-warning-only');
523
+ const planContent = reviewedPlan()
524
+ .replace("Plan close accepts final plan.md V3 ref/hash and 'PlanRuntimeCloseBoundaryFacts'. No HTTP endpoint exists; the API surface is the TypeScript function contract plus runtime-store projection schema.", "The API shape uses the existing report endpoint. SQL uses existing joins and source fields selected by the accepted plan.")
525
+ .replace("No persisted business data is changed, but runtime projection state must remain consistent across close, rejection, and handoff publication.", "The database query reads report tables and aggregates rows.");
526
+ const planHash = hashDocumentContent(planContent);
527
+ await writeFile(path.join(root, 'specs', 'master', 'plan.md'), planContent, 'utf8');
528
+
529
+ const closure = await reconcilePlanCollaborationClosure(root, {
530
+ decision: riskDecision,
531
+ generatedAt,
532
+ runId: 'run-plan-warning-only-closure',
533
+ outputCloseRequest: planOutputCloseRequest()
534
+ });
535
+ const handoff = await readWorkflowHandoffProjection(root, riskDecision.scope, 'plan', 'tasks');
536
+
537
+ assert.equal(closure.adjudication.health, 'ready_for_tasks');
538
+ assert.equal(closure.acceptedPlanRef?.hash, planHash);
539
+ assert.equal(handoff?.payload.toStage, 'tasks');
540
+ } finally {
541
+ await rm(root, { recursive: true, force: true });
542
+ }
543
+ });
544
+
545
+ test('plan closure rejects explicit implementation-time design decisions as blocker', async () => {
546
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-plan-closure-design-leak-'));
547
+ try {
548
+ await initProject(root);
549
+ const riskDecision = decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']);
550
+ await writeAcceptedSpecClosure(root, riskDecision, 'run-spec-ready-for-plan-design-leak');
551
+ const planContent = reviewedPlan().replace("Plan close accepts final plan.md V3 ref/hash and 'PlanRuntimeCloseBoundaryFacts'. No HTTP endpoint exists; the API surface is the TypeScript function contract plus runtime-store projection schema.", 'Implementation will determine the final SQL join and response fields.');
552
+ await writeFile(path.join(root, 'specs', 'master', 'plan.md'), planContent, 'utf8');
553
+
554
+ const closure = await reconcilePlanCollaborationClosure(root, {
555
+ decision: riskDecision,
556
+ generatedAt,
557
+ runId: 'run-plan-design-leak-closure',
558
+ outputCloseRequest: planOutputCloseRequest()
559
+ });
560
+ const handoff = await readWorkflowHandoffProjection(root, riskDecision.scope, 'plan', 'tasks');
561
+
562
+ assert.equal(closure.adjudication.health, 'rejected');
563
+ assert.match(closure.adjudication.rejection?.explanation ?? '', /blocker:plan\.downstream_guesswork\.tasks_or_execute_would_redesign/);
564
+ assert.equal(handoff, null);
565
+ } finally {
566
+ await rm(root, { recursive: true, force: true });
567
+ }
568
+ });
569
+
570
+ test('plan closure accepts final plan document without plan-review side evidence', async () => {
571
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-plan-closure-without-review-'));
572
+ try {
573
+ await initProject(root);
574
+ const riskDecision = decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']);
575
+ await writeAcceptedSpecClosure(root, riskDecision, 'run-spec-ready-for-plan-without-review');
576
+ const planContent = reviewedPlan();
577
+ const planHash = hashDocumentContent(planContent);
578
+ await writeFile(path.join(root, 'specs', 'master', 'plan.md'), planContent, 'utf8');
579
+
580
+ const closure = await reconcilePlanCollaborationClosure(root, {
581
+ decision: riskDecision,
582
+ generatedAt,
583
+ runId: 'run-plan-without-review-closure',
584
+ outputCloseRequest: planOutputCloseRequest()
585
+ });
586
+ const registered = await listRuntimeStageArtifacts(root, { branchSlug: 'master', stage: 'plan' });
587
+ const handoff = await readWorkflowHandoffProjection(root, riskDecision.scope, 'plan', 'tasks');
588
+
589
+ assert.equal(closure.adjudication.health, 'ready_for_tasks');
590
+ assert.equal(closure.adjudication.closureRefs.planAcceptanceStatus, 'accepted');
591
+ assert.equal(closure.acceptedPlanRef?.hash, planHash);
592
+ assert.deepEqual(registered.map((artifact) => artifact.kind).sort(), []);
593
+ assert.equal(handoff?.payload.requiredInputRefs.find((inputRef) => inputRef.ref === 'specs/master/plan.md')?.hash, planHash);
594
+ } finally {
595
+ await rm(root, { recursive: true, force: true });
596
+ }
597
+ });
598
+
599
+ test('plan closure keeps side artifacts out of plan to tasks handoff', async () => {
600
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-plan-closure-side-artifacts-'));
601
+ try {
602
+ await initProject(root);
603
+ const riskDecision = decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']);
604
+ await writeAcceptedSpecClosure(root, riskDecision, 'run-spec-ready-for-plan-side-artifacts');
605
+ const planContent = reviewedPlan();
606
+ const planHash = hashDocumentContent(planContent);
607
+ await writeFile(path.join(root, 'specs', 'master', 'plan.md'), planContent, 'utf8');
608
+ await writePlanStageArtifacts(root, { planHash });
609
+
610
+ const closure = await reconcilePlanCollaborationClosure(root, {
611
+ decision: riskDecision,
612
+ generatedAt,
613
+ runId: 'run-plan-side-artifacts-closure',
614
+ artifactRefs: ['.sdd/runs/master/plan/plan-review-v1.md', '.sdd/runs/master/plan/plan-manager-v1.md'],
615
+ outputCloseRequest: planOutputCloseRequest()
616
+ });
617
+ const handoff = await readWorkflowHandoffProjection(root, riskDecision.scope, 'plan', 'tasks');
618
+
619
+ assert.equal(closure.adjudication.health, 'ready_for_tasks');
620
+ assert.deepEqual(handoff?.payload.outputRefs.map((outputRef) => outputRef.ref), ['specs/master/plan.md']);
621
+ assert.deepEqual(handoff?.payload.evidenceRefs, []);
622
+ assert.equal(handoff?.payload.requiredInputRefs.find((inputRef) => inputRef.ref === 'specs/master/spec.md')?.kind, 'document');
623
+ assert.equal(handoff?.payload.requiredInputRefs.find((inputRef) => inputRef.ref === 'specs/master/plan.md')?.hash, planHash);
624
+ } finally {
625
+ await rm(root, { recursive: true, force: true });
626
+ }
627
+ });
628
+
629
+ test('plan closure rejects numbered forbidden task sections', async () => {
630
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-plan-closure-numbered-task-section-'));
631
+ try {
632
+ await initProject(root);
633
+ const riskDecision = decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']);
634
+ await writeAcceptedSpecClosure(root, riskDecision, 'run-spec-ready-for-plan-numbered-task-section');
635
+ const planContent = `${reviewedPlan()}\n## 18. Task Units\n\nT1 should not be owned by plan.\n`;
636
+ await writeFile(path.join(root, 'specs', 'master', 'plan.md'), planContent, 'utf8');
637
+
638
+ const closure = await reconcilePlanCollaborationClosure(root, {
639
+ decision: riskDecision,
640
+ generatedAt,
641
+ runId: 'run-plan-numbered-task-section-closure',
642
+ outputCloseRequest: planOutputCloseRequest()
643
+ });
644
+
645
+ assert.equal(closure.adjudication.health, 'rejected');
646
+ assert.equal(closure.adjudication.closureRefs.planAcceptanceStatus, 'not_accepted_wrong_ref');
647
+ assert.match(closure.adjudication.closureRefs.reasons.join(' '), /forbidden task/);
648
+ } finally {
649
+ await rm(root, { recursive: true, force: true });
650
+ }
651
+ });
652
+
653
+ test('plan closure rejects task unit leakage inside allowed sections', async () => {
654
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-plan-closure-task-leakage-'));
655
+ try {
656
+ await initProject(root);
657
+ const riskDecision = decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']);
658
+ await writeAcceptedSpecClosure(root, riskDecision, 'run-spec-ready-for-plan-task-leakage');
659
+ const planContent = reviewedPlan().replace('Tasks must consume accepted spec and accepted plan refs only; task-stage owns task IDs, execution waves, dependency graph, and per-task acceptance.', 'Tasks must consume accepted spec and accepted plan refs only; task-stage owns task IDs, execution waves, dependency graph, and per-task acceptance.\n\n### T1: leaked task\nsourceRequirements:\n - REQ-1');
660
+ await writeFile(path.join(root, 'specs', 'master', 'plan.md'), planContent, 'utf8');
661
+
662
+ const closure = await reconcilePlanCollaborationClosure(root, {
663
+ decision: riskDecision,
664
+ generatedAt,
665
+ runId: 'run-plan-task-leakage-closure',
666
+ outputCloseRequest: planOutputCloseRequest()
667
+ });
668
+
669
+ assert.equal(closure.adjudication.health, 'rejected');
670
+ assert.equal(closure.adjudication.closureRefs.planAcceptanceStatus, 'not_accepted_wrong_ref');
671
+ assert.match(closure.adjudication.closureRefs.reasons.join(' '), /forbidden task/);
672
+ } finally {
673
+ await rm(root, { recursive: true, force: true });
674
+ }
675
+ });
676
+
677
+ test('plan closure rejects checkpoint that is not ready for tasks', async () => {
678
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-plan-closure-checkpoint-not-ready-'));
679
+ try {
680
+ await initProject(root);
681
+ const riskDecision = decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']);
682
+ await writeAcceptedSpecClosure(root, riskDecision, 'run-spec-ready-for-plan-checkpoint-not-ready');
683
+ const planContent = reviewedPlan().replace('- ready_for_tasks: true', '- ready_for_tasks: false');
684
+ await writeFile(path.join(root, 'specs', 'master', 'plan.md'), planContent, 'utf8');
685
+
686
+ const closure = await reconcilePlanCollaborationClosure(root, {
687
+ decision: riskDecision,
688
+ generatedAt,
689
+ runId: 'run-plan-checkpoint-not-ready-closure',
690
+ outputCloseRequest: planOutputCloseRequest()
691
+ });
692
+
693
+ assert.equal(closure.adjudication.health, 'rejected');
694
+ assert.equal(closure.adjudication.closureRefs.planAcceptanceStatus, 'not_accepted_wrong_ref');
695
+ assert.match(closure.adjudication.closureRefs.reasons.join(' '), /ready_for_tasks/);
696
+ } finally {
697
+ await rm(root, { recursive: true, force: true });
698
+ }
699
+ });
700
+
701
+ test('plan closure rejects missing accepted spec handoff without recording handoff', async () => {
702
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-plan-closure-missing-upstream-'));
703
+ try {
704
+ await initProject(root);
705
+ const riskDecision = decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']);
706
+ const planContent = reviewedPlan();
707
+
708
+ await writeFile(path.join(root, 'specs', 'master', 'spec.md'), reviewedSpec(), 'utf8');
709
+ await writeFile(path.join(root, 'specs', 'master', 'plan.md'), planContent, 'utf8');
710
+
711
+
712
+ const closure = await reconcilePlanCollaborationClosure(root, {
713
+ decision: riskDecision,
714
+ generatedAt,
715
+ runId: 'run-plan-missing-upstream-closure',
716
+ outputCloseRequest: planOutputCloseRequest()
717
+ });
718
+ const handoff = await readWorkflowHandoffProjection(root, riskDecision.scope, 'plan', 'tasks');
719
+
720
+ assert.equal(closure.adjudication.health, 'rejected');
721
+ assert.equal(closure.adjudication.closureRefs.planAcceptanceStatus, 'not_accepted_missing_upstream');
722
+ assert.equal(closure.acceptedPlanRef, null);
723
+ assert.equal(handoff, null);
724
+ } finally {
725
+ await rm(root, { recursive: true, force: true });
726
+ }
727
+ });
728
+
729
+ test('plan closure rejects stale accepted spec handoff without recording handoff', async () => {
730
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-plan-closure-stale-upstream-'));
731
+ try {
732
+ await initProject(root);
733
+ const riskDecision = decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']);
734
+ await writeAcceptedSpecClosure(root, riskDecision, 'run-spec-ready-for-plan-stale');
735
+ await writeFile(path.join(root, 'specs', 'master', 'spec.md'), `${reviewedSpec()}\nStale after handoff.\n`, 'utf8');
736
+ const planContent = reviewedPlan();
737
+
738
+ await writeFile(path.join(root, 'specs', 'master', 'plan.md'), planContent, 'utf8');
739
+
740
+
741
+ const closure = await reconcilePlanCollaborationClosure(root, {
742
+ decision: riskDecision,
743
+ generatedAt,
744
+ runId: 'run-plan-stale-upstream-closure',
745
+ outputCloseRequest: planOutputCloseRequest()
746
+ });
747
+ const handoff = await readWorkflowHandoffProjection(root, riskDecision.scope, 'plan', 'tasks');
748
+
749
+ assert.equal(closure.adjudication.health, 'rejected');
750
+ assert.equal(closure.adjudication.closureRefs.planAcceptanceStatus, 'not_accepted_missing_upstream');
751
+ assert.match(closure.adjudication.closureRefs.reasons.join(' '), /stale/);
752
+ assert.equal(closure.acceptedPlanRef, null);
753
+ assert.equal(handoff, null);
754
+ } finally {
755
+ await rm(root, { recursive: true, force: true });
756
+ }
757
+ });
758
+
759
+ test('plan closure rejects missing output close request without recording handoff', async () => {
760
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-plan-closure-missing-output-request-'));
761
+ try {
762
+ await initProject(root);
763
+ const riskDecision = decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']);
764
+ await writeAcceptedSpecClosure(root, riskDecision, 'run-spec-ready-for-plan-missing-output-request');
765
+ await writeFile(path.join(root, 'specs', 'master', 'plan.md'), reviewedPlan(), 'utf8');
766
+
767
+ const closure = await reconcilePlanCollaborationClosure(root, {
768
+ decision: riskDecision,
769
+ generatedAt,
770
+ runId: 'run-plan-missing-output-request-closure'
771
+ });
772
+ const handoff = await readWorkflowHandoffProjection(root, riskDecision.scope, 'plan', 'tasks');
773
+
774
+ assert.equal(closure.adjudication.health, 'rejected');
775
+ assert.equal(closure.adjudication.rejection?.reasonCode, 'invalid_proposal');
776
+ assert.equal(closure.adjudication.closureRefs.planAcceptanceStatus, 'not_accepted_wrong_ref');
777
+ assert.equal(closure.acceptedPlanRef, null);
778
+ assert.equal(handoff, null);
779
+ } finally {
780
+ await rm(root, { recursive: true, force: true });
781
+ }
782
+ });
783
+
784
+ test('ready tasks closure accepts final tasks document and records tasks to execute handoff', async () => {
785
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-tasks-closure-ready-'));
786
+ try {
787
+ await initProject(root);
788
+ const riskDecision = decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']);
789
+ await writeAcceptedPlanClosure(root, riskDecision, 'run-plan-ready-for-tasks');
790
+ const tasksContent = reviewedTasks();
791
+ const tasksHash = hashDocumentContent(tasksContent);
792
+ await writeFile(path.join(root, 'specs', 'master', 'tasks.md'), tasksContent, 'utf8');
793
+
794
+ const closure = await reconcileTasksCollaborationClosure(root, {
795
+ decision: riskDecision,
796
+ generatedAt,
797
+ runId: 'run-tasks-ready-closure',
798
+ outputCloseRequest: tasksOutputCloseRequest()
799
+ });
800
+ const tasksContentAfterClosure = await readFile(path.join(root, 'specs', 'master', 'tasks.md'), 'utf8');
801
+ const registered = await listRuntimeStageArtifacts(root, { branchSlug: 'master', stage: 'tasks' });
802
+ const handoff = await readWorkflowHandoffProjection(root, riskDecision.scope, 'tasks', 'execute');
803
+
804
+ assert.equal(closure.adjudication.health, 'ready_for_execute');
805
+ assert.equal(closure.adjudication.closureRefs.tasksAcceptanceStatus, 'accepted');
806
+ assert.equal(closure.acceptedTasksRef?.ref, 'specs/master/tasks.md');
807
+ assert.equal(closure.acceptedTasksRef?.hash, tasksHash);
808
+ assert.equal(tasksContentAfterClosure, tasksContent);
809
+ assert.deepEqual(registered.map((artifact) => artifact.kind).sort(), []);
810
+ assert.equal(handoff?.payload.fromStage, 'tasks');
811
+ assert.equal(handoff?.payload.toStage, 'execute');
812
+ assert.equal(handoff?.payload.requiredInputRefs[0]?.ref, 'specs/master/tasks.md');
813
+ assert.equal(handoff?.payload.requiredInputRefs[0]?.hash, tasksHash);
814
+ await assert.rejects(access(path.join(root, '.sdd', 'runs', 'master', 'evidence', 'artifacts', 'phase9.3')));
815
+ } finally {
816
+ await rm(root, { recursive: true, force: true });
817
+ }
818
+ });
819
+
820
+ test('tasks closure accepts warning-only artifact-depth diagnostics without close trap', async () => {
821
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-tasks-closure-warning-only-'));
822
+ try {
823
+ await initProject(root);
824
+ const riskDecision = decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']);
825
+ await writeAcceptedPlanClosure(root, riskDecision, 'run-plan-ready-for-tasks-warning-only');
826
+ const tasksContent = warningOnlyTasks();
827
+ const tasksHash = hashDocumentContent(tasksContent);
828
+ await writeFile(path.join(root, 'specs', 'master', 'tasks.md'), tasksContent, 'utf8');
829
+
830
+ const closure = await reconcileTasksCollaborationClosure(root, {
831
+ decision: riskDecision,
832
+ generatedAt,
833
+ runId: 'run-tasks-warning-only-closure',
834
+ outputCloseRequest: tasksOutputCloseRequest()
835
+ });
836
+ const handoff = await readWorkflowHandoffProjection(root, riskDecision.scope, 'tasks', 'execute');
837
+
838
+ assert.equal(closure.adjudication.health, 'ready_for_execute');
839
+ assert.equal(closure.acceptedTasksRef?.hash, tasksHash);
840
+ assert.equal(handoff?.payload.toStage, 'execute');
841
+ } finally {
842
+ await rm(root, { recursive: true, force: true });
843
+ }
844
+ });
845
+
846
+ test('tasks closure accepts semantic section aliases without fixed-heading close trap', async () => {
847
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-tasks-closure-section-aliases-'));
848
+ try {
849
+ await initProject(root);
850
+ const riskDecision = decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']);
851
+ await writeAcceptedPlanClosure(root, riskDecision, 'run-plan-ready-for-tasks-section-aliases');
852
+ const tasksContent = reviewedTasks()
853
+ .replace('## 1. Split Basis', '## 1. Work Unit Split Basis')
854
+ .replace('## 3. Implementation Tasks', '## 3. Implementation Work Units')
855
+ .replace('## 4. Validation Tasks', '## 4. Validation Work Units')
856
+ .replace('## 5. Task Dependencies and Handoff', '## 5. Dependencies and Validation Handoff')
857
+ .replace('## 7. Open Execution Questions', '## 7. Open Questions')
858
+ .replace('## 8. Tasks Close Quality Evidence', '## 8. Close Quality Evidence');
859
+ const tasksHash = hashDocumentContent(tasksContent);
860
+ await writeFile(path.join(root, 'specs', 'master', 'tasks.md'), tasksContent, 'utf8');
861
+
862
+ const closure = await reconcileTasksCollaborationClosure(root, {
863
+ decision: riskDecision,
864
+ generatedAt,
865
+ runId: 'run-tasks-section-aliases-closure',
866
+ outputCloseRequest: tasksOutputCloseRequest()
867
+ });
868
+
869
+ assert.equal(closure.adjudication.health, 'ready_for_execute');
870
+ assert.equal(closure.acceptedTasksRef?.hash, tasksHash);
871
+ } finally {
872
+ await rm(root, { recursive: true, force: true });
873
+ }
874
+ });
875
+
876
+ test('tasks closure rejects ambiguous many-to-one validation mapping', async () => {
877
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-tasks-closure-ambiguous-validation-'));
878
+ try {
879
+ await initProject(root);
880
+ const riskDecision = decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']);
881
+ await writeAcceptedPlanClosure(root, riskDecision, 'run-plan-ready-for-tasks-ambiguous-validation');
882
+ const tasksContent = reviewedTasks().replace('validationHandoff:\n - T4', 'validationHandoff:\n - T3');
883
+ await writeFile(path.join(root, 'specs', 'master', 'tasks.md'), tasksContent, 'utf8');
884
+
885
+ const closure = await reconcileTasksCollaborationClosure(root, {
886
+ decision: riskDecision,
887
+ generatedAt,
888
+ runId: 'run-tasks-ambiguous-validation-closure',
889
+ outputCloseRequest: tasksOutputCloseRequest()
890
+ });
891
+ const handoff = await readWorkflowHandoffProjection(root, riskDecision.scope, 'tasks', 'execute');
892
+
893
+ assert.equal(closure.adjudication.health, 'rejected');
894
+ assert.match(closure.adjudication.rejection?.explanation ?? '', /must validate exactly|must hand off only/);
895
+ assert.equal(handoff, null);
896
+ } finally {
897
+ await rm(root, { recursive: true, force: true });
898
+ }
899
+ });
900
+
901
+ test('tasks closure accepts broad execution slice after task-review reconciliation', async () => {
902
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-tasks-closure-reviewed-broad-slice-'));
903
+ try {
904
+ await initProject(root);
905
+ const riskDecision = decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']);
906
+ await writeAcceptedPlanClosure(root, riskDecision, 'run-plan-ready-for-tasks-reviewed-broad-slice');
907
+ const tasksContent = oversizedCoarseTasks();
908
+ const tasksHash = hashDocumentContent(tasksContent);
909
+ await writeFile(path.join(root, 'specs', 'master', 'tasks.md'), tasksContent, 'utf8');
910
+
911
+ const closure = await reconcileTasksCollaborationClosure(root, {
912
+ decision: riskDecision,
913
+ generatedAt,
914
+ runId: 'run-tasks-reviewed-broad-slice-closure',
915
+ outputCloseRequest: tasksOutputCloseRequest()
916
+ });
917
+ const handoff = await readWorkflowHandoffProjection(root, riskDecision.scope, 'tasks', 'execute');
918
+
919
+ assert.equal(closure.adjudication.health, 'ready_for_execute');
920
+ assert.equal(closure.acceptedTasksRef?.hash, tasksHash);
921
+ assert.equal(handoff?.payload.toStage, 'execute');
922
+ } finally {
923
+ await rm(root, { recursive: true, force: true });
924
+ }
925
+ });
926
+
927
+ test('tasks closure rejects missing task-review reconciliation evidence', async () => {
928
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-tasks-closure-missing-review-reconciliation-'));
929
+ try {
930
+ await initProject(root);
931
+ const riskDecision = decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']);
932
+ await writeAcceptedPlanClosure(root, riskDecision, 'run-plan-ready-for-tasks-missing-review-reconciliation');
933
+ const tasksContent = reviewedTasks()
934
+ .replace('- task-review-agent verdict: approved\n', '')
935
+ .replace('- review_reconciliation: blocking findings resolved; review_blockers_remaining: []\n', '');
936
+ await writeFile(path.join(root, 'specs', 'master', 'tasks.md'), tasksContent, 'utf8');
937
+
938
+ const closure = await reconcileTasksCollaborationClosure(root, {
939
+ decision: riskDecision,
940
+ generatedAt,
941
+ runId: 'run-tasks-missing-review-reconciliation-closure',
942
+ outputCloseRequest: tasksOutputCloseRequest()
943
+ });
944
+ const handoff = await readWorkflowHandoffProjection(root, riskDecision.scope, 'tasks', 'execute');
945
+
946
+ assert.equal(closure.adjudication.health, 'rejected');
947
+ assert.match(closure.adjudication.rejection?.explanation ?? '', /task-review-agent verdict and tasks-manager review reconciliation/);
948
+ assert.equal(handoff, null);
949
+ } finally {
950
+ await rm(root, { recursive: true, force: true });
951
+ }
952
+ });
953
+
954
+ test('tasks closure rejects manager-declared execution guesswork before execute', async () => {
955
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-tasks-closure-execution-guesswork-'));
956
+ try {
957
+ await initProject(root);
958
+ const riskDecision = decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']);
959
+ await writeAcceptedPlanClosure(root, riskDecision, 'run-plan-ready-for-tasks-execution-guesswork');
960
+ const tasksContent = warningOnlyTasks().replace('- downstream_execution_guesswork_remaining: []', '- downstream_execution_guesswork_remaining: ["SQL join and response fields unresolved"]');
961
+ await writeFile(path.join(root, 'specs', 'master', 'tasks.md'), tasksContent, 'utf8');
962
+
963
+ const closure = await reconcileTasksCollaborationClosure(root, {
964
+ decision: riskDecision,
965
+ generatedAt,
966
+ runId: 'run-tasks-execution-guesswork-closure',
967
+ outputCloseRequest: tasksOutputCloseRequest()
968
+ });
969
+ const handoff = await readWorkflowHandoffProjection(root, riskDecision.scope, 'tasks', 'execute');
970
+
971
+ assert.equal(closure.adjudication.health, 'rejected');
972
+ assert.match(closure.adjudication.rejection?.explanation ?? '', /downstream_execution_guesswork_remaining/);
973
+ assert.equal(handoff, null);
974
+ } finally {
975
+ await rm(root, { recursive: true, force: true });
976
+ }
977
+ });
978
+
979
+ test('tasks closure accepts final tasks document without tasks-review side evidence', async () => {
980
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-tasks-closure-without-review-'));
981
+ try {
982
+ await initProject(root);
983
+ const riskDecision = decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']);
984
+ await writeAcceptedPlanClosure(root, riskDecision, 'run-plan-ready-for-tasks-without-review');
985
+ const tasksContent = reviewedTasks();
986
+ const tasksHash = hashDocumentContent(tasksContent);
987
+ await writeFile(path.join(root, 'specs', 'master', 'tasks.md'), tasksContent, 'utf8');
988
+
989
+ const closure = await reconcileTasksCollaborationClosure(root, {
990
+ decision: riskDecision,
991
+ generatedAt,
992
+ runId: 'run-tasks-without-review-closure',
993
+ outputCloseRequest: tasksOutputCloseRequest()
994
+ });
995
+ const handoff = await readWorkflowHandoffProjection(root, riskDecision.scope, 'tasks', 'execute');
996
+ const registered = await listRuntimeStageArtifacts(root, { branchSlug: 'master', stage: 'tasks' });
997
+
998
+ assert.equal(closure.adjudication.health, 'ready_for_execute');
999
+ assert.equal(closure.adjudication.closureRefs.tasksAcceptanceStatus, 'accepted');
1000
+ assert.equal(closure.acceptedTasksRef?.hash, tasksHash);
1001
+ assert.deepEqual(registered.map((artifact) => artifact.kind).sort(), []);
1002
+ assert.equal(handoff?.payload.toStage, 'execute');
1003
+ } finally {
1004
+ await rm(root, { recursive: true, force: true });
1005
+ }
1006
+ });
1007
+
1008
+ test('tasks closure rejects stale accepted plan handoff without recording handoff', async () => {
1009
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-tasks-closure-stale-upstream-'));
1010
+ try {
1011
+ await initProject(root);
1012
+ const riskDecision = decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']);
1013
+ await writeAcceptedPlanClosure(root, riskDecision, 'run-plan-ready-for-tasks-stale');
1014
+ await writeFile(path.join(root, 'specs', 'master', 'plan.md'), `${reviewedPlan()}\nStale after handoff.\n`, 'utf8');
1015
+ const tasksContent = reviewedTasks();
1016
+ await writeFile(path.join(root, 'specs', 'master', 'tasks.md'), tasksContent, 'utf8');
1017
+
1018
+ const closure = await reconcileTasksCollaborationClosure(root, {
1019
+ decision: riskDecision,
1020
+ generatedAt,
1021
+ runId: 'run-tasks-stale-upstream-closure',
1022
+ outputCloseRequest: tasksOutputCloseRequest()
1023
+ });
1024
+ const handoff = await readWorkflowHandoffProjection(root, riskDecision.scope, 'tasks', 'execute');
1025
+
1026
+ assert.equal(closure.adjudication.health, 'rejected');
1027
+ assert.equal(closure.adjudication.closureRefs.tasksAcceptanceStatus, 'not_accepted_missing_upstream');
1028
+ assert.match(closure.adjudication.closureRefs.reasons.join(' '), /stale/);
1029
+ assert.equal(closure.acceptedTasksRef, null);
1030
+ assert.equal(handoff, null);
1031
+ } finally {
1032
+ await rm(root, { recursive: true, force: true });
1033
+ }
1034
+ });
1035
+
1036
+ test('tasks closure rejects stale accepted spec from plan handoff', async () => {
1037
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-tasks-closure-stale-spec-upstream-'));
1038
+ try {
1039
+ await initProject(root);
1040
+ const riskDecision = decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']);
1041
+ await writeAcceptedPlanClosure(root, riskDecision, 'run-plan-ready-for-tasks-stale-spec');
1042
+ await writeFile(path.join(root, 'specs', 'master', 'spec.md'), `${reviewedSpec()}\nStale spec after plan handoff.\n`, 'utf8');
1043
+ const tasksContent = reviewedTasks();
1044
+ await writeFile(path.join(root, 'specs', 'master', 'tasks.md'), tasksContent, 'utf8');
1045
+
1046
+ const closure = await reconcileTasksCollaborationClosure(root, {
1047
+ decision: riskDecision,
1048
+ generatedAt,
1049
+ runId: 'run-tasks-stale-spec-upstream-closure',
1050
+ outputCloseRequest: tasksOutputCloseRequest()
1051
+ });
1052
+ const handoff = await readWorkflowHandoffProjection(root, riskDecision.scope, 'tasks', 'execute');
1053
+
1054
+ assert.equal(closure.adjudication.health, 'rejected');
1055
+ assert.equal(closure.adjudication.closureRefs.tasksAcceptanceStatus, 'not_accepted_missing_upstream');
1056
+ assert.match(closure.adjudication.closureRefs.reasons.join(' '), /spec hash is stale/);
1057
+ assert.equal(closure.acceptedTasksRef, null);
1058
+ assert.equal(handoff, null);
1059
+ } finally {
1060
+ await rm(root, { recursive: true, force: true });
1061
+ }
1062
+ });
1063
+
1064
+ test('tasks closure rejects duplicate task ids without recording handoff', async () => {
1065
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-tasks-closure-duplicate-'));
1066
+ try {
1067
+ await initProject(root);
1068
+ const riskDecision = decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']);
1069
+ await writeAcceptedPlanClosure(root, riskDecision, 'run-plan-ready-for-tasks-duplicate');
1070
+ const tasksContent = reviewedTasks({ duplicate: true });
1071
+ await writeFile(path.join(root, 'specs', 'master', 'tasks.md'), tasksContent, 'utf8');
1072
+
1073
+ const closure = await reconcileTasksCollaborationClosure(root, {
1074
+ decision: riskDecision,
1075
+ generatedAt,
1076
+ runId: 'run-tasks-duplicate-closure',
1077
+ outputCloseRequest: tasksOutputCloseRequest()
1078
+ });
1079
+ const handoff = await readWorkflowHandoffProjection(root, riskDecision.scope, 'tasks', 'execute');
1080
+
1081
+ assert.equal(closure.adjudication.health, 'rejected');
1082
+ assert.equal(closure.adjudication.rejection?.reasonCode, 'invalid_proposal');
1083
+ assert.equal(closure.adjudication.closureRefs.tasksAcceptanceStatus, 'not_accepted_duplicate_task_ids');
1084
+ assert.equal(closure.acceptedTasksRef, null);
1085
+ assert.equal(handoff, null);
1086
+ } finally {
1087
+ await rm(root, { recursive: true, force: true });
1088
+ }
1089
+ });
1090
+
1091
+ test('execute closure accepts completed task and validation projections without execute side evidence', async () => {
1092
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-execute-closure-projection-ready-'));
1093
+ try {
1094
+ await initProject(root);
1095
+ const riskDecision = decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']);
1096
+ await writeAcceptedPlanClosure(root, riskDecision, 'run-execute-projection-plan-ready');
1097
+ const tasksContent = reviewedTasks({ completed: true, includeValidation: true });
1098
+ const tasksHash = hashDocumentContent(tasksContent);
1099
+ await writeFile(path.join(root, 'specs', 'master', 'tasks.md'), tasksContent, 'utf8');
1100
+ const tasksClosure = await reconcileTasksCollaborationClosure(root, {
1101
+ decision: riskDecision,
1102
+ generatedAt,
1103
+ runId: 'run-execute-projection-tasks-ready',
1104
+ outputCloseRequest: tasksOutputCloseRequest()
1105
+ });
1106
+ assert.equal(tasksClosure.acceptedTasksRef?.hash, tasksHash);
1107
+ await recordTaskExecutionProjectionBundleFromAcceptedTasks(root, {
1108
+ branch: 'master',
1109
+ acceptedTasksRef: tasksClosure.acceptedTasksRef as RuntimeRef,
1110
+ acceptedTasksHash: tasksHash,
1111
+ generatedAt
1112
+ });
1113
+
1114
+ const executeClosure = await reconcileExecuteCollaborationClosure(root, {
1115
+ decision: riskDecision,
1116
+ generatedAt,
1117
+ runId: 'run-execute-projection-ready'
1118
+ });
1119
+ const handoff = await readWorkflowHandoffProjection(root, riskDecision.scope, 'execute', 'ship');
1120
+ const registered = await listRuntimeStageArtifacts(root, { branchSlug: 'master', stage: 'execute' });
1121
+
1122
+ assert.equal(executeClosure.adjudication.health, 'ready_for_ship');
1123
+ assert.equal(executeClosure.adjudication.closureRefs.executeAcceptanceStatus, 'accepted');
1124
+ assert.equal(executeClosure.acceptedExecuteRef?.ref, 'phase10_3_task_dependency_graph_projection:master');
1125
+ assert.deepEqual(registered.map((artifact) => artifact.kind).sort(), []);
1126
+ assert.equal(handoff?.payload.fromStage, 'execute');
1127
+ assert.equal(handoff?.payload.toStage, 'ship');
1128
+ } finally {
1129
+ await rm(root, { recursive: true, force: true });
1130
+ }
1131
+ });
1132
+
1133
+
1134
+
1135
+ test('ready ship closure accepts reviewed readiness evidence and records final ship-ready projection', async () => {
1136
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-stage-ship-ready-'));
1137
+ try {
1138
+ await initProject(root);
1139
+ const riskDecision = decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']);
1140
+ await writeAcceptedExecuteClosure(root, riskDecision, 'run-execute-ready-for-ship');
1141
+ await writeShipStageArtifacts(root, {});
1142
+
1143
+ const closure = await reconcileShipCollaborationClosure(root, {
1144
+ decision: riskDecision,
1145
+ generatedAt,
1146
+ runId: 'run-ship-ready-closure'
1147
+ });
1148
+ const registered = await listRuntimeStageArtifacts(root, { branchSlug: 'master', stage: 'ship' });
1149
+ const shipFiles = await readdir(path.join(root, '.sdd', 'runs', 'master', 'ship'));
1150
+
1151
+ assert.equal(closure.adjudication.health, 'ship_ready');
1152
+ assert.equal(closure.adjudication.stageDecision.status, 'completed');
1153
+ assert.equal(closure.adjudication.handoffPacket, null);
1154
+ assert.equal(closure.adjudication.closureRefs.shipReadinessStatus, 'accepted');
1155
+ assert.equal(closure.acceptedShipReadinessRef?.ref, '.sdd/runs/master/ship/ship-readiness-v1.md');
1156
+ assert.equal(closure.releaseDocumentRef, null);
1157
+ assert.deepEqual(registered.map((artifact) => artifact.kind).sort(), ['manager_closure_request', 'release_review', 'ship_readiness']);
1158
+ assert.deepEqual(shipFiles, ['release-review-v1.md', 'ship-collaboration-contract-v1.md', 'ship-manager-v1.md', 'ship-readiness-v1.md']);
1159
+ await assert.rejects(access(path.join(root, '.sdd', 'runs', 'master', 'evidence', 'artifacts', 'phase9.9')));
1160
+ } finally {
1161
+ await rm(root, { recursive: true, force: true });
1162
+ }
1163
+ });
1164
+
1165
+ test('ship required release review is satisfied by registered release-review artifact', async () => {
1166
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-stage-ship-required-review-'));
1167
+ try {
1168
+ await initProject(root);
1169
+ const riskDecision: LifecycleRiskDecision = {
1170
+ ...decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']),
1171
+ requiredEvidence: [{
1172
+ id: 'phase8-risk:command-evidence',
1173
+ acceptanceRef: 'phase8-risk-decision',
1174
+ kind: 'command',
1175
+ requiredBefore: 'ship',
1176
+ refs: [ref('task', 'T1'), ref('task', 'T2')],
1177
+ reasons: ['Task declares high-risk tags: evidence-semantics.']
1178
+ }],
1179
+ requiredReviews: [{
1180
+ id: 'phase8-risk:release-review',
1181
+ kind: 'release',
1182
+ requiredBefore: 'ship',
1183
+ reasons: ['Phase 8 risk signal requires release review.']
1184
+ }]
1185
+ };
1186
+ await writeAcceptedExecuteClosure(root, riskDecision, 'run-execute-ready-for-ship-required-review');
1187
+ await writeShipStageArtifacts(root, {});
1188
+
1189
+ const closure = await reconcileShipCollaborationClosure(root, {
1190
+ decision: riskDecision,
1191
+ generatedAt,
1192
+ runId: 'run-ship-required-review-closure'
1193
+ });
1194
+ const registered = await listRuntimeStageArtifacts(root, { branchSlug: 'master', stage: 'ship' });
1195
+
1196
+ assert.equal(closure.adjudication.health, 'ship_ready');
1197
+ assert.equal(closure.adjudication.closureRefs.shipReadinessStatus, 'accepted');
1198
+ assert.equal(closure.acceptedShipReadinessRef?.ref, '.sdd/runs/master/ship/ship-readiness-v1.md');
1199
+ assert.equal(registered.some((artifact) => artifact.kind === 'release_review'), true);
1200
+ } finally {
1201
+ await rm(root, { recursive: true, force: true });
1202
+ }
1203
+ });
1204
+
1205
+ test('full-stage chain diagnostic summarizes ready chain from runtime projections', async () => {
1206
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-full-chain-ready-'));
1207
+ try {
1208
+ await initProject(root);
1209
+ const riskDecision = decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']);
1210
+ await writeAcceptedTasksClosure(root, riskDecision, 'run-full-chain-ready-for-execute');
1211
+ const chain = await inspectFullStageChain(root, 'master');
1212
+
1213
+ assert.equal(chain.status, 'partial');
1214
+ assert.equal(chain.finalHealth, 'ready_for_execute');
1215
+ assert.equal(chain.projectionCounts.adjudications, 3);
1216
+ assert.equal(chain.projectionCounts.completedStages, 3);
1217
+ assert.equal(chain.projectionCounts.validatedCollaborationContracts, 0);
1218
+ assert.equal(chain.projectionCounts.handoffs, 3);
1219
+ assert.deepEqual(chain.stages.map((stage) => stage.stage), ['spec', 'plan', 'tasks', 'execute', 'ship']);
1220
+ const acceptedRefs = chain.stages.map((stage) => stage.acceptedRef).filter((ref): ref is RuntimeRef => ref !== null);
1221
+ assert.equal(acceptedRefs.every((ref) => !ref.ref.startsWith('runs/')), true);
1222
+ assert.equal(acceptedRefs.every((ref) => ref.ref.includes('evidence/artifacts') === false), true);
1223
+ assert.equal(acceptedRefs.every((ref) => ref.ref.startsWith('artifacts/') === false), true);
1224
+ } finally {
1225
+ await rm(root, { recursive: true, force: true });
1226
+ }
1227
+ });
1228
+
1229
+ test('runtime close replay does not create or mutate Markdown files', async () => {
1230
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-full-chain-markdown-immutable-'));
1231
+ try {
1232
+ await initProject(root);
1233
+ const riskDecision = decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']);
1234
+ await writeAcceptedExecuteClosure(root, riskDecision, 'run-markdown-immutable-execute-ready-for-ship');
1235
+ await writeShipStageArtifacts(root, {});
1236
+ await reconcileShipCollaborationClosure(root, {
1237
+ decision: riskDecision,
1238
+ generatedAt,
1239
+ runId: 'run-markdown-immutable-ship-ready'
1240
+ });
1241
+ const before = await markdownSnapshot(root);
1242
+
1243
+ await reconcileSpecCollaborationClosure(root, { decision: riskDecision, generatedAt, runId: 'run-markdown-immutable-replay-spec', outputCloseRequest: outputCloseRequest() });
1244
+ await reconcilePlanCollaborationClosure(root, { decision: riskDecision, generatedAt, runId: 'run-markdown-immutable-replay-plan' });
1245
+ await reconcileTasksCollaborationClosure(root, { decision: riskDecision, generatedAt, runId: 'run-markdown-immutable-replay-tasks' });
1246
+ await reconcileExecuteCollaborationClosure(root, { decision: riskDecision, generatedAt, runId: 'run-markdown-immutable-replay-execute' });
1247
+ await reconcileShipCollaborationClosure(root, { decision: riskDecision, generatedAt, runId: 'run-markdown-immutable-replay-ship' });
1248
+ const after = await markdownSnapshot(root);
1249
+
1250
+ assert.deepEqual(after, before);
1251
+ assert.equal([...after.keys()].some((file) => file.startsWith('runs/')), false);
1252
+ } finally {
1253
+ await rm(root, { recursive: true, force: true });
1254
+ }
1255
+ });
1256
+
1257
+ test('ship closure rejects missing release review without final readiness', async () => {
1258
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-stage-ship-missing-review-'));
1259
+ try {
1260
+ await initProject(root);
1261
+ const riskDecision = decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']);
1262
+ await writeAcceptedExecuteClosure(root, riskDecision, 'run-execute-ready-for-ship-missing-review');
1263
+ await writeShipStageArtifacts(root, { omitReview: true });
1264
+
1265
+ const closure = await reconcileShipCollaborationClosure(root, {
1266
+ decision: riskDecision,
1267
+ generatedAt,
1268
+ runId: 'run-ship-missing-review-closure'
1269
+ });
1270
+
1271
+ assert.equal(closure.adjudication.health, 'rejected');
1272
+ assert.equal(closure.adjudication.closureRefs.shipReadinessStatus, 'not_accepted_missing_review');
1273
+ assert.equal(closure.acceptedShipReadinessRef, null);
1274
+ } finally {
1275
+ await rm(root, { recursive: true, force: true });
1276
+ }
1277
+ });
1278
+
1279
+ test('ship closure rejects stale truthAlignment reality refs without final readiness', async () => {
1280
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-stage-ship-stale-truth-alignment-'));
1281
+ try {
1282
+ await initProject(root);
1283
+ const riskDecision = decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']);
1284
+ await writeAcceptedExecuteClosure(root, riskDecision, 'run-execute-ready-for-ship-stale');
1285
+ await writeFile(path.join(root, '.sdd', 'runs', 'master', 'execute', 'evidence-judgment-v1.md'), `${evidenceJudgmentArtifact('passed', true, 0, 0)}\nDrift after truthAlignment.\n`, 'utf8');
1286
+ await writeShipStageArtifacts(root, {});
1287
+
1288
+ const closure = await reconcileShipCollaborationClosure(root, {
1289
+ decision: riskDecision,
1290
+ generatedAt,
1291
+ runId: 'run-ship-stale-truth-alignment-closure'
1292
+ });
1293
+
1294
+ assert.equal(closure.adjudication.health, 'rejected');
1295
+ assert.equal(closure.adjudication.closureRefs.shipReadinessStatus, 'not_accepted_missing_upstream');
1296
+ assert.match(closure.adjudication.closureRefs.reasons.join(' '), /truthAlignment|stale/);
1297
+ assert.equal(closure.acceptedShipReadinessRef, null);
1298
+ } finally {
1299
+ await rm(root, { recursive: true, force: true });
1300
+ }
1301
+ });
1302
+
1303
+ test('ship closure rejects open readiness blockers without final readiness', async () => {
1304
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-stage-ship-open-blocker-'));
1305
+ try {
1306
+ await initProject(root);
1307
+ const riskDecision = decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']);
1308
+ await writeAcceptedExecuteClosure(root, riskDecision, 'run-execute-ready-for-ship-open-blocker');
1309
+ await writeShipStageArtifacts(root, { doctorBlockerCount: 1 });
1310
+
1311
+ const closure = await reconcileShipCollaborationClosure(root, {
1312
+ decision: riskDecision,
1313
+ generatedAt,
1314
+ runId: 'run-ship-open-blocker-closure'
1315
+ });
1316
+
1317
+ assert.equal(closure.adjudication.health, 'rejected');
1318
+ assert.equal(closure.adjudication.closureRefs.shipReadinessStatus, 'not_accepted_open_blocker');
1319
+ assert.equal(closure.acceptedShipReadinessRef, null);
1320
+ } finally {
1321
+ await rm(root, { recursive: true, force: true });
1322
+ }
1323
+ });
1324
+
1325
+ test('ship closure rejects release document hash drift when release doc is declared', async () => {
1326
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-stage-ship-release-drift-'));
1327
+ try {
1328
+ await initProject(root);
1329
+ const riskDecision = decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']);
1330
+ await writeAcceptedExecuteClosure(root, riskDecision, 'run-execute-ready-for-ship-release-drift');
1331
+ const releaseContent = '# Release Notes\n\nReady to ship.\n';
1332
+ await writeFile(path.join(root, 'specs', 'master', 'release.md'), releaseContent, 'utf8');
1333
+ await writeShipStageArtifacts(root, { release: { ref: 'specs/master/release.md', hash: hashDocumentContent(releaseContent) } });
1334
+ await writeFile(path.join(root, 'specs', 'master', 'release.md'), `${releaseContent}\nDrift after readiness.\n`, 'utf8');
1335
+
1336
+ const closure = await reconcileShipCollaborationClosure(root, {
1337
+ decision: riskDecision,
1338
+ generatedAt,
1339
+ runId: 'run-ship-release-drift-closure'
1340
+ });
1341
+
1342
+ assert.equal(closure.adjudication.health, 'rejected');
1343
+ assert.equal(closure.adjudication.closureRefs.shipReadinessStatus, 'not_accepted_release_drift');
1344
+ assert.equal(closure.acceptedShipReadinessRef, null);
1345
+ assert.equal(closure.releaseDocumentRef, null);
1346
+ } finally {
1347
+ await rm(root, { recursive: true, force: true });
1348
+ }
1349
+ });
1350
+
1351
+ test('ship closure rejects authority-violating readiness evidence', async () => {
1352
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-stage-ship-authority-'));
1353
+ try {
1354
+ await initProject(root);
1355
+ const riskDecision = decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']);
1356
+ await writeAcceptedExecuteClosure(root, riskDecision, 'run-execute-ready-for-ship-authority');
1357
+ await writeShipStageArtifacts(root, { authorityAttempts: ['ship_ready'] });
1358
+
1359
+ const closure = await reconcileShipCollaborationClosure(root, {
1360
+ decision: riskDecision,
1361
+ generatedAt,
1362
+ runId: 'run-ship-authority-closure'
1363
+ });
1364
+
1365
+ assert.equal(closure.adjudication.health, 'rejected');
1366
+ assert.equal(closure.adjudication.closureRefs.shipReadinessStatus, 'not_accepted_authority_violation');
1367
+ assert.equal(closure.acceptedShipReadinessRef, null);
1368
+ } finally {
1369
+ await rm(root, { recursive: true, force: true });
1370
+ }
1371
+ });
1372
+
1373
+ function decision(profile: LifecycleRiskDecision['profile'], requiredStages: LifecycleRiskDecision['requiredStages']): LifecycleRiskDecision {
1374
+ return {
1375
+ contract: LIFECYCLE_RISK_DECISION_CONTRACT_VERSION,
1376
+ scope: { branch: 'master' },
1377
+ profile,
1378
+ requiredStages,
1379
+ skippedStages: [],
1380
+ blockedStages: profile === 'blocked' ? ['spec', 'plan', 'tasks', 'execute', 'ship'] : [],
1381
+ requiredEvidence: [],
1382
+ requiredReviews: [],
1383
+ humanCheckpointRequired: profile === 'blocked',
1384
+ approvalPolicy: profile === 'blocked' ? 'blocked' : profile === 'direct' ? 'auto-allow' : 'review-required',
1385
+ inputRefs: [ref('document', 'specs/master/phase9.1-spec.md')],
1386
+ signalRefs: [],
1387
+ policyVersion: 'test-policy',
1388
+ inputHash: 'input-hash',
1389
+ confidence: 'high',
1390
+ reasons: [`${profile} test decision`],
1391
+ generatedAt
1392
+ };
1393
+ }
1394
+
1395
+ function stageClosure(profile: StageCollaborationProfile, overrides: { items?: CandidateItem[]; authorityAttempts?: StageClosureRequest['authorityAttempts']; canonicalSpecRef?: ReturnType<typeof ref> } = {}): { coordination: SpecManagerCoordinationRecord; closureRequest: StageClosureRequest } {
1396
+ const workOrder = buildSpecStageWorkOrder(profile, ref('projection', 'profile/spec'), generatedAt);
1397
+ const workOrderRef = ref('projection', `spec-work-order/${profile.scope.branch}`);
1398
+ const candidateRef = overrides.canonicalSpecRef ?? ref('document', 'specs/master/spec.md');
1399
+ const reviewRef = ref('artifact', '.sdd/runs/master/spec/spec-review-v1.md');
1400
+ const items = overrides.items ?? [candidateItem('find-1', 'advisory_finding'), candidateItem('cap-1', 'capability_suggestion'), candidateItem('handoff-1', 'handoff_proposal')];
1401
+ const unresolvedAmbiguityIds = items.filter((item) => item.kind === 'blocking_ambiguity').map((item) => item.id);
1402
+ const candidate: SpecDocumentCandidate = {
1403
+ contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
1404
+ candidateId: `candidate-${profile.scope.branch}`,
1405
+ stage: 'spec',
1406
+ scope: profile.scope,
1407
+ producedBy: 'spec-manager',
1408
+ inputRefs: workOrder.inputRefs,
1409
+ evidenceRefs: items.flatMap((item) => item.refs),
1410
+ acceptedItems: items,
1411
+ generatedAt
1412
+ };
1413
+ const capabilityFindings = items
1414
+ .filter((item) => item.kind === 'capability_suggestion')
1415
+ .map((item): CapabilityFinding => ({
1416
+ contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
1417
+ findingId: `finding-${item.id}`,
1418
+ stage: 'spec',
1419
+ scope: profile.scope,
1420
+ capabilityDomain: 'solution-design',
1421
+ summary: item.summary,
1422
+ refs: item.refs,
1423
+ generatedAt
1424
+ }));
1425
+ const capabilityFindingRefs = capabilityFindings.map((finding) => ref('artifact', `.sdd/runs/master/spec/spec-capability-finding-${finding.findingId}.md`));
1426
+ const review: SpecReviewResult = {
1427
+ contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
1428
+ reviewId: `review-${profile.scope.branch}`,
1429
+ stage: 'spec',
1430
+ scope: profile.scope,
1431
+ reviewer: 'spec-reviewer',
1432
+ candidateRef,
1433
+ verdict: unresolvedAmbiguityIds.length > 0 ? 'blocked' : 'approved',
1434
+ findings: items.map((item) => item.summary),
1435
+ evidenceRefs: items.flatMap((item) => item.refs),
1436
+ generatedAt
1437
+ };
1438
+ const agentTeamRefs = [candidateRef, reviewRef, ...capabilityFindingRefs];
1439
+ const coordination: SpecManagerCoordinationRecord = {
1440
+ contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
1441
+ coordinationId: `coordination-${profile.scope.branch}`,
1442
+ stage: 'spec',
1443
+ scope: profile.scope,
1444
+ stageManager: 'spec-manager',
1445
+ workOrderRef,
1446
+ agentTeamRefs,
1447
+ recommendation: unresolvedAmbiguityIds.length > 0 ? 'request_clarification' : 'close_stage',
1448
+ generatedAt
1449
+ };
1450
+ const closureRequest: StageClosureRequest = {
1451
+ contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
1452
+ closureRequestId: `closure-${profile.scope.branch}`,
1453
+ stage: 'spec',
1454
+ scope: profile.scope,
1455
+ submittedBy: 'spec-manager',
1456
+ workOrderRef,
1457
+ coordinationRef: ref('artifact', '.sdd/runs/master/spec/spec-manager-v1.md'),
1458
+ candidateRef,
1459
+ reviewRefs: [reviewRef],
1460
+ capabilityFindingRefs,
1461
+ evidenceRefs: [workOrderRef, ...agentTeamRefs, ...items.flatMap((item) => item.refs)],
1462
+ candidate,
1463
+ reviewResults: [review],
1464
+ capabilityFindings,
1465
+ unresolvedAmbiguityIds,
1466
+ authorityAttempts: overrides.authorityAttempts ?? [],
1467
+ managerRecommendation: unresolvedAmbiguityIds.length > 0 ? 'request_clarification' : 'close_stage',
1468
+ runtimeCloseBoundaryFacts: unresolvedAmbiguityIds.length > 0 ? null : {
1469
+ contract: 'sdd-spec-runtime-close-boundary-facts-v1',
1470
+ scope: profile.scope,
1471
+ finalSpecRef: { kind: 'document', ref: 'specs/master/spec.md', hash: 'test-spec-hash' },
1472
+ finalSpecHash: 'test-spec-hash',
1473
+ templateContract: 'sdd-spec-doc-v3',
1474
+ blockingBeforePlanCount: 0,
1475
+ sectionCloseDeclaration: 'present',
1476
+ reviewSignal: 'present',
1477
+ runtimeTruthOwner: 'Runtime Kernel'
1478
+ },
1479
+ generatedAt
1480
+ };
1481
+ return { coordination, closureRequest };
1482
+ }
1483
+
1484
+ type CandidateItem = { id: string; kind: SpecProposalItemKind; summary: string; refs: ReturnType<typeof ref>[] };
1485
+
1486
+ function candidateItem(id: string, kind: SpecProposalItemKind, refs = [ref('document', `specs/master/${id}.md`)]): CandidateItem {
1487
+ return { id, kind, summary: `${kind} ${id}`, refs };
1488
+ }
1489
+
1490
+ async function writeAcceptedSpecClosure(root: string, riskDecision: LifecycleRiskDecision, runId: string): Promise<void> {
1491
+ const specContent = reviewedSpec();
1492
+ const specHash = hashDocumentContent(specContent);
1493
+ await writeFile(path.join(root, 'specs', 'master', 'spec.md'), specContent, 'utf8');
1494
+ const closure = await reconcileSpecCollaborationClosure(root, {
1495
+ decision: riskDecision,
1496
+ generatedAt,
1497
+ runId,
1498
+ outputCloseRequest: outputCloseRequest()
1499
+ });
1500
+ assert.equal(closure.adjudication.closureRefs?.specAcceptanceStatus, 'accepted');
1501
+ assert.equal(closure.acceptedSpecRef?.hash, specHash);
1502
+ }
1503
+
1504
+ async function writeAcceptedPlanClosure(root: string, riskDecision: LifecycleRiskDecision, runId: string): Promise<void> {
1505
+ await writeAcceptedSpecClosure(root, riskDecision, `${runId}-spec`);
1506
+ const planContent = reviewedPlan();
1507
+ const planHash = hashDocumentContent(planContent);
1508
+ await writeFile(path.join(root, 'specs', 'master', 'plan.md'), planContent, 'utf8');
1509
+
1510
+ const closure = await reconcilePlanCollaborationClosure(root, {
1511
+ decision: riskDecision,
1512
+ generatedAt,
1513
+ runId,
1514
+ outputCloseRequest: planOutputCloseRequest()
1515
+ });
1516
+ assert.equal(closure.adjudication.closureRefs.planAcceptanceStatus, 'accepted');
1517
+ assert.equal(closure.acceptedPlanRef?.hash, planHash);
1518
+ }
1519
+
1520
+ async function writeAcceptedTasksClosure(root: string, riskDecision: LifecycleRiskDecision, runId: string): Promise<void> {
1521
+ await writeAcceptedPlanClosure(root, riskDecision, `${runId}-plan`);
1522
+ const tasksContent = reviewedTasks();
1523
+ const tasksHash = hashDocumentContent(tasksContent);
1524
+ await writeFile(path.join(root, 'specs', 'master', 'tasks.md'), tasksContent, 'utf8');
1525
+ const closure = await reconcileTasksCollaborationClosure(root, {
1526
+ decision: riskDecision,
1527
+ generatedAt,
1528
+ runId,
1529
+ outputCloseRequest: tasksOutputCloseRequest()
1530
+ });
1531
+ assert.equal(closure.adjudication.closureRefs.tasksAcceptanceStatus, 'accepted');
1532
+ assert.equal(closure.acceptedTasksRef?.hash, tasksHash);
1533
+ }
1534
+
1535
+
1536
+
1537
+ async function writeAcceptedExecuteClosure(root: string, riskDecision: LifecycleRiskDecision, runId: string): Promise<{ acceptedExecuteRef: RuntimeRef }> {
1538
+ await writeAcceptedPlanClosure(root, riskDecision, `${runId}-plan`);
1539
+ const tasksContent = reviewedTasks({ completed: true, includeValidation: true });
1540
+ const tasksHash = hashDocumentContent(tasksContent);
1541
+ await writeFile(path.join(root, 'specs', 'master', 'tasks.md'), tasksContent, 'utf8');
1542
+ const tasksClosure = await reconcileTasksCollaborationClosure(root, {
1543
+ decision: riskDecision,
1544
+ generatedAt,
1545
+ runId: `${runId}-tasks`,
1546
+ outputCloseRequest: tasksOutputCloseRequest()
1547
+ });
1548
+ assert.equal(tasksClosure.adjudication.closureRefs.tasksAcceptanceStatus, 'accepted');
1549
+ assert.equal(tasksClosure.acceptedTasksRef?.hash, tasksHash);
1550
+ await recordTaskExecutionProjectionBundleFromAcceptedTasks(root, {
1551
+ branch: 'master',
1552
+ acceptedTasksRef: tasksClosure.acceptedTasksRef as RuntimeRef,
1553
+ acceptedTasksHash: tasksHash,
1554
+ generatedAt
1555
+ });
1556
+ await writeExecuteEvidenceJudgmentArtifacts(root, {});
1557
+ const closure = await reconcileExecuteCollaborationClosure(root, {
1558
+ decision: riskDecision,
1559
+ generatedAt,
1560
+ runId
1561
+ });
1562
+ assert.equal(closure.adjudication.health, 'ready_for_ship');
1563
+ assert.equal(closure.adjudication.closureRefs.executeAcceptanceStatus, 'accepted');
1564
+ assert.equal(closure.adjudication.closureRefs.truthAlignmentProjectionRef?.kind, 'projection');
1565
+ assert.ok(closure.acceptedExecuteRef);
1566
+ return { acceptedExecuteRef: closure.acceptedExecuteRef };
1567
+ }
1568
+
1569
+
1570
+ async function writeWorkspaceFile(root: string, fileRef: string, content: string): Promise<void> {
1571
+ await mkdir(path.dirname(path.join(root, fileRef)), { recursive: true });
1572
+ await writeFile(path.join(root, fileRef), content, 'utf8');
1573
+ }
1574
+
1575
+ async function writeSpecStageArtifacts(root: string, options: { specHash: string; verdict?: SpecReviewResult['verdict']; blockingCount?: number; recommendation?: SpecManagerCoordinationRecord['recommendation']; omitContract?: boolean }): Promise<void> {
1576
+ const dir = path.join(root, '.sdd', 'runs', 'master', 'spec');
1577
+ await mkdir(dir, { recursive: true });
1578
+ if (!options.omitContract) {
1579
+ await writeFile(path.join(dir, 'spec-collaboration-contract-v1.md'), specCollaborationContractArtifact(specWorkOrderId()), 'utf8');
1580
+ }
1581
+ await writeFile(path.join(dir, 'scout.md'), specScoutArtifact(), 'utf8');
1582
+ const review = specReviewArtifact(options.specHash, options.verdict ?? 'approved', options.blockingCount ?? 0);
1583
+ const reviewHash = hashDocumentContent(review);
1584
+ await writeFile(path.join(dir, 'spec-review-v1.md'), review, 'utf8');
1585
+ await writeFile(path.join(dir, 'spec-manager-v1.md'), specManagerArtifact(options.specHash, reviewHash, options.recommendation ?? 'close_stage'), 'utf8');
1586
+ }
1587
+
1588
+ function specWorkOrderId(): string {
1589
+ const profile = deriveSpecCollaborationProfile(decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']), generatedAt);
1590
+ return buildSpecStageWorkOrder(profile, ref('projection', 'profile/spec'), generatedAt).workOrderId;
1591
+ }
1592
+
1593
+ function specCollaborationContractArtifact(workOrderId: string): string {
1594
+ return `---
1595
+ contract: sdd-stage-collaboration-contract-v1
1596
+ branch: master
1597
+ stage: spec
1598
+ kind: stage_collaboration_contract
1599
+ producer: spec-manager
1600
+ workOrderId: ${workOrderId}
1601
+ selectedAgents:
1602
+ - scout
1603
+ - spec-reviewer
1604
+ selectedSkills:
1605
+ - cap.norm_discovery
1606
+ - cap.uncertainty_resolution
1607
+ selectedMaterialPacks:
1608
+ - project-norms
1609
+ - uncertainty-map
1610
+ evidenceContracts:
1611
+ - sdd-spec-scout-artifact-v1
1612
+ - sdd-spec-review-artifact-v1
1613
+ - sdd-spec-manager-artifact-v1
1614
+ checkpointPlan:
1615
+ - scout_context
1616
+ - review_before_closure
1617
+ writableRefs:
1618
+ - specs/master/spec.md
1619
+ - .sdd/runs/master/spec/scout.md
1620
+ - .sdd/runs/master/spec/spec-review-v1.md
1621
+ - .sdd/runs/master/spec/spec-manager-v1.md
1622
+ authorityAttempts: []
1623
+ maxReworkAttempts: 2
1624
+ allowAutoWithinStage: true
1625
+ allowAutoNextStage: false
1626
+ authorityCeiling: proposal_input
1627
+ ---
1628
+
1629
+ # Spec Collaboration Contract
1630
+
1631
+ Spec-manager selected the spec-stage team and evidence strategy within runtime constraints.
1632
+ `;
1633
+ }
1634
+
1635
+ interface StageContractFixtureWorkOrder {
1636
+ workOrderId: string;
1637
+ stage: SddStage;
1638
+ stageManager: string;
1639
+ agentTeam: readonly string[];
1640
+ requiredCapabilities: readonly string[];
1641
+ materialPackIds: readonly string[];
1642
+ requiredOutputKinds: readonly string[];
1643
+ authorityCeiling: string;
1644
+ }
1645
+
1646
+ async function writeStageCollaborationContract(root: string, stage: SddStage, writableRefs: readonly string[]): Promise<void> {
1647
+ const dir = path.join(root, '.sdd', 'runs', 'master', stage);
1648
+ const workOrder = stageContractFixtureWorkOrder(stage);
1649
+ await writeFile(path.join(dir, `${stage}-collaboration-contract-v1.md`), stageCollaborationContractArtifact(workOrder, writableRefs), 'utf8');
1650
+ }
1651
+
1652
+ function stageContractFixtureWorkOrder(stage: SddStage): StageContractFixtureWorkOrder {
1653
+ const scope = decision('full', ['spec', 'plan', 'tasks', 'execute', 'ship']).scope;
1654
+ const profileRef = ref('projection', `profile/${stage}`);
1655
+ const inputRefs = [ref('document', 'specs/master/phase9.1-spec.md')];
1656
+ if (stage === 'plan') return buildPlanStageWorkOrder(scope, profileRef, inputRefs, generatedAt);
1657
+ if (stage === 'tasks') return buildTasksStageWorkOrder(scope, profileRef, inputRefs, generatedAt);
1658
+ if (stage === 'execute') return buildExecuteStageWorkOrder(scope, profileRef, inputRefs, generatedAt);
1659
+ if (stage === 'ship') return buildShipStageWorkOrder(scope, profileRef, inputRefs, generatedAt);
1660
+ throw new Error(`Unsupported stage contract fixture ${stage}`);
1661
+ }
1662
+
1663
+ function stageCollaborationContractArtifact(workOrder: StageContractFixtureWorkOrder, writableRefs: readonly string[]): string {
1664
+ return `---
1665
+ contract: sdd-stage-collaboration-contract-v1
1666
+ branch: master
1667
+ stage: ${workOrder.stage}
1668
+ kind: stage_collaboration_contract
1669
+ producer: ${workOrder.stageManager}
1670
+ workOrderId: ${workOrder.workOrderId}
1671
+ selectedAgents:
1672
+ ${yamlList(workOrder.agentTeam)}
1673
+ selectedSkills:
1674
+ ${yamlList(workOrder.requiredCapabilities.map((capability) => `cap.${capability}`))}
1675
+ selectedMaterialPacks:
1676
+ ${yamlList(workOrder.materialPackIds)}
1677
+ evidenceContracts:
1678
+ ${yamlList(expectedStageArtifactContracts(workOrder.stage, workOrder.requiredOutputKinds))}
1679
+ checkpointPlan:
1680
+ ${yamlList(workOrder.requiredOutputKinds)}
1681
+ writableRefs:
1682
+ ${yamlList(writableRefs)}
1683
+ authorityAttempts: []
1684
+ maxReworkAttempts: 2
1685
+ allowAutoWithinStage: true
1686
+ allowAutoNextStage: false
1687
+ authorityCeiling: ${workOrder.authorityCeiling}
1688
+ ---
1689
+
1690
+ # ${workOrder.stage} Collaboration Contract
1691
+
1692
+ ${workOrder.stageManager} selected the stage team and evidence strategy within runtime constraints.
1693
+ `;
1694
+ }
1695
+
1696
+ function yamlList(values: readonly string[]): string {
1697
+ return values.map((value) => ` - ${value}`).join('\n');
1698
+ }
1699
+
1700
+ function specScoutArtifact(): string {
1701
+ return `---
1702
+ contract: sdd-spec-scout-artifact-v1
1703
+ branch: master
1704
+ stage: spec
1705
+ kind: scout_context
1706
+ producer: scout
1707
+ ---
1708
+
1709
+ # Scout Context
1710
+
1711
+ The scout captured project norms and constraints for the spec drafter and reviewer.
1712
+ `;
1713
+ }
1714
+
1715
+ function specReviewArtifact(specHash: string, verdict: SpecReviewResult['verdict'], blockingCount: number): string {
1716
+ return `---
1717
+ contract: sdd-spec-review-artifact-v1
1718
+ branch: master
1719
+ stage: spec
1720
+ kind: spec_review
1721
+ producer: spec-reviewer
1722
+ targetRef: specs/master/spec.md
1723
+ targetHash: ${specHash}
1724
+ verdict: ${verdict}
1725
+ findingCount: ${blockingCount}
1726
+ blockingCount: ${blockingCount}
1727
+ ---
1728
+
1729
+ # Spec Review
1730
+
1731
+ Verdict: ${verdict}.
1732
+ `;
1733
+ }
1734
+
1735
+ function specManagerArtifact(specHash: string, reviewHash: string, recommendation: SpecManagerCoordinationRecord['recommendation']): string {
1736
+ return `---
1737
+ contract: sdd-spec-manager-artifact-v1
1738
+ branch: master
1739
+ stage: spec
1740
+ kind: manager_closure_request
1741
+ producer: spec-manager
1742
+ targetRef: specs/master/spec.md
1743
+ targetHash: ${specHash}
1744
+ reviewRef: .sdd/runs/master/spec/spec-review-v1.md
1745
+ reviewHash: ${reviewHash}
1746
+ recommendation: ${recommendation}
1747
+ finalSpecRef: specs/master/spec.md
1748
+ finalSpecHash: ${specHash}
1749
+ templateContract: sdd-spec-doc-v3
1750
+ blockingBeforePlanCount: 0
1751
+ sectionCloseDeclaration: present
1752
+ reviewSignal: present
1753
+ runtimeTruthOwner: Runtime Kernel
1754
+ ---
1755
+
1756
+ # Spec Manager Coordination
1757
+
1758
+ Recommendation: ${recommendation}.
1759
+ `;
1760
+ }
1761
+
1762
+ async function writePlanStageArtifacts(root: string, options: { planHash: string; omitContract?: boolean; omitReview?: boolean; verdict?: SpecReviewResult['verdict']; blockingCount?: number; recommendation?: SpecManagerCoordinationRecord['recommendation'] }): Promise<void> {
1763
+ const dir = path.join(root, '.sdd', 'runs', 'master', 'plan');
1764
+ await mkdir(dir, { recursive: true });
1765
+ if (!options.omitContract) {
1766
+ await writeStageCollaborationContract(root, 'plan', ['specs/master/plan.md', '.sdd/runs/master/plan/plan-review-v1.md', '.sdd/runs/master/plan/plan-manager-v1.md']);
1767
+ }
1768
+ const review = planReviewArtifact(options.planHash, options.verdict ?? 'approved', options.blockingCount ?? 0);
1769
+ const reviewHash = hashDocumentContent(review);
1770
+ if (!options.omitReview) {
1771
+ await writeFile(path.join(dir, 'plan-review-v1.md'), review, 'utf8');
1772
+ }
1773
+ await writeFile(path.join(dir, 'plan-manager-v1.md'), planManagerArtifact(options.planHash, reviewHash, options.recommendation ?? 'close_stage'), 'utf8');
1774
+ }
1775
+
1776
+ function planReviewArtifact(planHash: string, verdict: SpecReviewResult['verdict'], blockingCount: number): string {
1777
+ return `---
1778
+ contract: sdd-plan-review-artifact-v1
1779
+ branch: master
1780
+ stage: plan
1781
+ kind: plan_review
1782
+ producer: plan-review-agent
1783
+ targetRef: specs/master/plan.md
1784
+ targetHash: ${planHash}
1785
+ verdict: ${verdict}
1786
+ findingCount: ${blockingCount}
1787
+ blockingCount: ${blockingCount}
1788
+ ---
1789
+
1790
+ # Plan Review
1791
+
1792
+ Verdict: ${verdict}.
1793
+ `;
1794
+ }
1795
+
1796
+ function planManagerArtifact(planHash: string, reviewHash: string, recommendation: SpecManagerCoordinationRecord['recommendation']): string {
1797
+ return `---
1798
+ contract: sdd-plan-manager-artifact-v1
1799
+ branch: master
1800
+ stage: plan
1801
+ kind: manager_closure_request
1802
+ producer: plan-manager
1803
+ targetRef: specs/master/plan.md
1804
+ targetHash: ${planHash}
1805
+ reviewRef: .sdd/runs/master/plan/plan-review-v1.md
1806
+ reviewHash: ${reviewHash}
1807
+ recommendation: ${recommendation}
1808
+ ---
1809
+
1810
+ # Plan Manager Coordination
1811
+
1812
+ Recommendation: ${recommendation}.
1813
+ `;
1814
+ }
1815
+
1816
+ async function writeTasksStageArtifacts(root: string, options: { tasksHash: string; omitContract?: boolean; omitReview?: boolean; verdict?: SpecReviewResult['verdict']; blockingCount?: number; recommendation?: SpecManagerCoordinationRecord['recommendation'] }): Promise<void> {
1817
+ const dir = path.join(root, '.sdd', 'runs', 'master', 'tasks');
1818
+ await mkdir(dir, { recursive: true });
1819
+ if (!options.omitContract) {
1820
+ await writeStageCollaborationContract(root, 'tasks', ['specs/master/tasks.md', '.sdd/runs/master/tasks/tasks-review-v1.md', '.sdd/runs/master/tasks/tasks-manager-v1.md']);
1821
+ }
1822
+ const review = tasksReviewArtifact(options.tasksHash, options.verdict ?? 'approved', options.blockingCount ?? 0);
1823
+ const reviewHash = hashDocumentContent(review);
1824
+ if (!options.omitReview) {
1825
+ await writeFile(path.join(dir, 'tasks-review-v1.md'), review, 'utf8');
1826
+ }
1827
+ await writeFile(path.join(dir, 'tasks-manager-v1.md'), tasksManagerArtifact(options.tasksHash, reviewHash, options.recommendation ?? 'close_stage'), 'utf8');
1828
+ }
1829
+
1830
+ function tasksReviewArtifact(tasksHash: string, verdict: SpecReviewResult['verdict'], blockingCount: number): string {
1831
+ return `---
1832
+ contract: sdd-tasks-review-artifact-v1
1833
+ branch: master
1834
+ stage: tasks
1835
+ kind: tasks_review
1836
+ producer: task-review-agent
1837
+ targetRef: specs/master/tasks.md
1838
+ targetHash: ${tasksHash}
1839
+ verdict: ${verdict}
1840
+ findingCount: ${blockingCount}
1841
+ blockingCount: ${blockingCount}
1842
+ ---
1843
+
1844
+ # Tasks Review
1845
+
1846
+ Verdict: ${verdict}.
1847
+ `;
1848
+ }
1849
+
1850
+ function tasksManagerArtifact(tasksHash: string, reviewHash: string, recommendation: SpecManagerCoordinationRecord['recommendation']): string {
1851
+ return `---
1852
+ contract: sdd-tasks-manager-artifact-v1
1853
+ branch: master
1854
+ stage: tasks
1855
+ kind: manager_closure_request
1856
+ producer: tasks-manager
1857
+ targetRef: specs/master/tasks.md
1858
+ targetHash: ${tasksHash}
1859
+ reviewRef: .sdd/runs/master/tasks/tasks-review-v1.md
1860
+ reviewHash: ${reviewHash}
1861
+ recommendation: ${recommendation}
1862
+ ---
1863
+
1864
+ # Tasks Manager Coordination
1865
+
1866
+ Recommendation: ${recommendation}.
1867
+ `;
1868
+ }
1869
+
1870
+
1871
+
1872
+
1873
+
1874
+ async function writeExecuteEvidenceJudgmentArtifacts(root: string, options: { omitContract?: boolean; omitVerification?: boolean; status?: 'passed' | 'failed'; coverageComplete?: boolean; durableGapCount?: number; openGapCount?: number; omitReview?: boolean; verdict?: SpecReviewResult['verdict']; blockingCount?: number; recommendation?: SpecManagerCoordinationRecord['recommendation'] }): Promise<void> {
1875
+ const dir = path.join(root, '.sdd', 'runs', 'master', 'execute');
1876
+ await mkdir(dir, { recursive: true });
1877
+ if (!options.omitContract) {
1878
+ await writeStageCollaborationContract(root, 'execute', ['.sdd/runs/master/execute/evidence-judgment-v1.md', '.sdd/runs/master/execute/evidence-judgment-review-v1.md', '.sdd/runs/master/execute/evidence-judgment-manager-v1.md']);
1879
+ }
1880
+ const verification = evidenceJudgmentArtifact(options.status ?? 'passed', options.coverageComplete ?? true, options.durableGapCount ?? 0, options.openGapCount ?? 0);
1881
+ const verificationHash = hashDocumentContent(verification);
1882
+ if (!options.omitVerification) {
1883
+ await writeFile(path.join(dir, 'evidence-judgment-v1.md'), verification, 'utf8');
1884
+ }
1885
+ const review = evidenceReviewArtifact(verificationHash, options.verdict ?? 'approved', options.blockingCount ?? 0);
1886
+ const reviewHash = hashDocumentContent(review);
1887
+ if (!options.omitReview) {
1888
+ await writeFile(path.join(dir, 'evidence-judgment-review-v1.md'), review, 'utf8');
1889
+ }
1890
+ await writeFile(path.join(dir, 'evidence-judgment-manager-v1.md'), evidenceJudgmentManagerArtifact(verificationHash, reviewHash, options.recommendation ?? 'close_stage'), 'utf8');
1891
+ }
1892
+
1893
+ function evidenceJudgmentArtifact(status: 'passed' | 'failed', coverageComplete: boolean, durableGapCount: number, openGapCount: number): string {
1894
+ return `---
1895
+ contract: sdd-execute-evidence-judgment-artifact-v1
1896
+ branch: master
1897
+ stage: execute
1898
+ kind: evidence_judgment
1899
+ producer: evidence-validator
1900
+ status: ${status}
1901
+ coverageComplete: ${coverageComplete}
1902
+ durableGapCount: ${durableGapCount}
1903
+ openGapCount: ${openGapCount}
1904
+ ---
1905
+
1906
+ # Evidence Judgment
1907
+
1908
+ Coverage complete: ${coverageComplete}.
1909
+ `;
1910
+ }
1911
+
1912
+ function evidenceReviewArtifact(verificationHash: string, verdict: SpecReviewResult['verdict'], blockingCount: number): string {
1913
+ return `---
1914
+ contract: sdd-execute-evidence-judgment-review-artifact-v1
1915
+ branch: master
1916
+ stage: execute
1917
+ kind: evidence_review
1918
+ producer: evidence-reviewer
1919
+ targetRef: .sdd/runs/master/execute/evidence-judgment-v1.md
1920
+ targetHash: ${verificationHash}
1921
+ verdict: ${verdict}
1922
+ findingCount: ${blockingCount}
1923
+ blockingCount: ${blockingCount}
1924
+ ---
1925
+
1926
+ # Goal Review
1927
+
1928
+ Verdict: ${verdict}.
1929
+ `;
1930
+ }
1931
+
1932
+ function evidenceJudgmentManagerArtifact(verificationHash: string, reviewHash: string, recommendation: SpecManagerCoordinationRecord['recommendation']): string {
1933
+ return `---
1934
+ contract: sdd-execute-manager-artifact-v1
1935
+ branch: master
1936
+ stage: execute
1937
+ kind: manager_closure_request
1938
+ producer: execute-manager
1939
+ targetRef: .sdd/runs/master/execute/evidence-judgment-v1.md
1940
+ targetHash: ${verificationHash}
1941
+ reviewRef: .sdd/runs/master/execute/evidence-judgment-review-v1.md
1942
+ reviewHash: ${reviewHash}
1943
+ recommendation: ${recommendation}
1944
+ ---
1945
+
1946
+ # Execute Evidence Judgment Manager Coordination
1947
+
1948
+ Recommendation: ${recommendation}.
1949
+ `;
1950
+ }
1951
+
1952
+
1953
+ async function writeShipStageArtifacts(root: string, options: { omitContract?: boolean; omitReadiness?: boolean; omitReview?: boolean; status?: 'ready' | 'blocked'; doctorBlockerCount?: number; capabilityBlockerCount?: number; repairBlockerCount?: number; gapBlockerCount?: number; migrationBlockerCount?: number; release?: { ref: string; hash: string }; authorityAttempts?: string[]; verdict?: SpecReviewResult['verdict']; blockingCount?: number; recommendation?: SpecManagerCoordinationRecord['recommendation'] }): Promise<void> {
1954
+ const dir = path.join(root, '.sdd', 'runs', 'master', 'ship');
1955
+ await mkdir(dir, { recursive: true });
1956
+ if (!options.omitContract) {
1957
+ await writeStageCollaborationContract(root, 'ship', ['.sdd/runs/master/ship/ship-readiness-v1.md', '.sdd/runs/master/ship/release-review-v1.md', '.sdd/runs/master/ship/ship-manager-v1.md', 'specs/master/release.md']);
1958
+ }
1959
+ const readiness = shipReadinessArtifact({
1960
+ status: options.status ?? 'ready',
1961
+ doctorBlockerCount: options.doctorBlockerCount ?? 0,
1962
+ capabilityBlockerCount: options.capabilityBlockerCount ?? 0,
1963
+ repairBlockerCount: options.repairBlockerCount ?? 0,
1964
+ gapBlockerCount: options.gapBlockerCount ?? 0,
1965
+ migrationBlockerCount: options.migrationBlockerCount ?? 0,
1966
+ release: options.release,
1967
+ authorityAttempts: options.authorityAttempts ?? []
1968
+ });
1969
+ const readinessHash = hashDocumentContent(readiness);
1970
+ if (!options.omitReadiness) {
1971
+ await writeFile(path.join(dir, 'ship-readiness-v1.md'), readiness, 'utf8');
1972
+ }
1973
+ const review = shipReleaseReviewArtifact(readinessHash, options.verdict ?? 'approved', options.blockingCount ?? 0);
1974
+ const reviewHash = hashDocumentContent(review);
1975
+ if (!options.omitReview) {
1976
+ await writeFile(path.join(dir, 'release-review-v1.md'), review, 'utf8');
1977
+ }
1978
+ await writeFile(path.join(dir, 'ship-manager-v1.md'), shipManagerArtifact(readinessHash, reviewHash, options.recommendation ?? 'close_stage'), 'utf8');
1979
+ }
1980
+
1981
+ function shipReadinessArtifact(options: { status: 'ready' | 'blocked'; doctorBlockerCount: number; capabilityBlockerCount: number; repairBlockerCount: number; gapBlockerCount: number; migrationBlockerCount: number; release?: { ref: string; hash: string }; authorityAttempts: string[] }): string {
1982
+ const releaseLines = options.release ? `releaseRef: ${options.release.ref}\nreleaseHash: ${options.release.hash}\n` : '';
1983
+ const authorityLines = options.authorityAttempts.length > 0
1984
+ ? `authorityAttempts:\n${options.authorityAttempts.map((attempt) => ` - ${attempt}`).join('\n')}\n`
1985
+ : '';
1986
+ return `---
1987
+ contract: sdd-ship-readiness-artifact-v1
1988
+ branch: master
1989
+ stage: ship
1990
+ kind: ship_readiness
1991
+ producer: ship-validator
1992
+ status: ${options.status}
1993
+ doctorBlockerCount: ${options.doctorBlockerCount}
1994
+ capabilityBlockerCount: ${options.capabilityBlockerCount}
1995
+ repairBlockerCount: ${options.repairBlockerCount}
1996
+ gapBlockerCount: ${options.gapBlockerCount}
1997
+ migrationBlockerCount: ${options.migrationBlockerCount}
1998
+ ${releaseLines}${authorityLines}---
1999
+
2000
+ # Ship Readiness
2001
+
2002
+ Status: ${options.status}.
2003
+ `;
2004
+ }
2005
+
2006
+ function shipReleaseReviewArtifact(readinessHash: string, verdict: SpecReviewResult['verdict'], blockingCount: number): string {
2007
+ return `---
2008
+ contract: sdd-ship-release-review-artifact-v1
2009
+ branch: master
2010
+ stage: ship
2011
+ kind: release_review
2012
+ producer: release-reviewer
2013
+ targetRef: .sdd/runs/master/ship/ship-readiness-v1.md
2014
+ targetHash: ${readinessHash}
2015
+ verdict: ${verdict}
2016
+ findingCount: ${blockingCount}
2017
+ blockingCount: ${blockingCount}
2018
+ ---
2019
+
2020
+ # Release Review
2021
+
2022
+ Verdict: ${verdict}.
2023
+ `;
2024
+ }
2025
+
2026
+ function shipManagerArtifact(readinessHash: string, reviewHash: string, recommendation: SpecManagerCoordinationRecord['recommendation']): string {
2027
+ return `---
2028
+ contract: sdd-ship-manager-artifact-v1
2029
+ branch: master
2030
+ stage: ship
2031
+ kind: manager_closure_request
2032
+ producer: ship-manager
2033
+ targetRef: .sdd/runs/master/ship/ship-readiness-v1.md
2034
+ targetHash: ${readinessHash}
2035
+ reviewRef: .sdd/runs/master/ship/release-review-v1.md
2036
+ reviewHash: ${reviewHash}
2037
+ recommendation: ${recommendation}
2038
+ ---
2039
+
2040
+ # Ship Manager Coordination
2041
+
2042
+ Recommendation: ${recommendation}.
2043
+ `;
2044
+ }
2045
+
2046
+ function reviewedSpec(): string {
2047
+ return `---
2048
+ contract: sdd-spec-doc-v3
2049
+ title: Reviewed test spec
2050
+ ---
2051
+
2052
+ # Reviewed Test Spec
2053
+
2054
+ ## Problem Reframing / Intent Discovery
2055
+
2056
+ Surface request, reframed problem, inferred real intent, and observable success are explicit for the reviewed fixture.
2057
+
2058
+ ## Change Delta
2059
+
2060
+ Current behavior, desired behavior, delta, unchanged behavior, and non-goals are explicit.
2061
+
2062
+ ## Scope
2063
+
2064
+ In Scope: reviewed spec closure. Out of Scope: implementation planning.
2065
+
2066
+ ## Requirements
2067
+
2068
+ | ID | Requirement | Priority | Source |
2069
+ |---|---|---|---|
2070
+ | REQ-1 | Runtime close accepts the reviewed spec. | Must | test |
2071
+
2072
+ ## Acceptance Criteria / Evidence Targets
2073
+
2074
+ | ID | Acceptance | Covers | Evidence Target | Priority |
2075
+ |---|---|---|---|---|
2076
+ | AC-1 | Runtime accepts the final spec ref and hash. | REQ-1 | stage close result | Must |
2077
+
2078
+ ## Definitions / Rules
2079
+
2080
+ The final spec is the only primary runtime spec artifact.
2081
+
2082
+ ## Planning Constraints / Signals
2083
+
2084
+ Plan consumes the accepted spec ref/hash only.
2085
+
2086
+ ## Open Questions / Ambiguity Ledger
2087
+
2088
+ No blocking user decisions remain. Researchable uncertainty is resolved by scout evidence or explicitly logged. Safe assumptions are listed when used, and deferred questions are routed to plan, tasks, or execute verification.
2089
+
2090
+ ## Close Quality Evidence
2091
+
2092
+ - requirement_review_sufficient: true
2093
+ - problem_reframed: true
2094
+ - domain_rules_confirmed_or_routed: true
2095
+ - acceptance_evidence_targets_defined: true
2096
+ - ambiguity_routed: true
2097
+ - downstream_business_guesswork_remaining: []
2098
+ - ready_for_plan: true
2099
+ `;
2100
+ }
2101
+
2102
+ function shallowSpec(): string {
2103
+ return `---
2104
+ contract: sdd-spec-doc-v3
2105
+ title: Shallow test spec
2106
+ ---
2107
+
2108
+ # Shallow Test Spec
2109
+
2110
+ ## Problem Reframing / Intent Discovery
2111
+
2112
+ Do the requested change.
2113
+
2114
+ ## Change Delta
2115
+
2116
+ Some behavior changes.
2117
+
2118
+ ## Scope
2119
+
2120
+ In scope: change.
2121
+
2122
+ ## Requirements
2123
+
2124
+ | ID | Requirement | Priority | Source |
2125
+ |---|---|---|---|
2126
+ | REQ-1 | Change the behavior. | Must | user |
2127
+
2128
+ ## Acceptance Criteria / Evidence Targets
2129
+
2130
+ | ID | Acceptance | Covers | Priority |
2131
+ |---|---|---|---|
2132
+ | AC-1 | It works. | REQ-1 | Must |
2133
+
2134
+ ## Definitions / Rules
2135
+
2136
+ None.
2137
+
2138
+ ## Planning Constraints / Signals
2139
+
2140
+ None.
2141
+
2142
+ ## Open Questions / Ambiguity Ledger
2143
+
2144
+ None.
2145
+
2146
+ ## Close Quality Evidence
2147
+
2148
+ - problem_reframed: true
2149
+ `;
2150
+ }
2151
+
2152
+ function outputCloseRequest(branch = 'master') {
2153
+ return {
2154
+ finalSpecRef: `specs/${branch}/spec.md`,
2155
+ sectionCloseDeclaration: 'present',
2156
+ reviewSignal: 'present',
2157
+ blockingBeforePlanCount: 0
2158
+ };
2159
+ }
2160
+
2161
+ function planOutputCloseRequest(branch = 'master') {
2162
+ return {
2163
+ finalPlanRef: `specs/${branch}/plan.md`,
2164
+ planCloseQualityEvidence: 'present',
2165
+ reviewSignal: 'present',
2166
+ blockingBeforeTasksCount: 0
2167
+ };
2168
+ }
2169
+
2170
+ function tasksOutputCloseRequest(branch = 'master') {
2171
+ return {
2172
+ finalTasksRef: `specs/${branch}/tasks.md`,
2173
+ dependencyGraphStatus: 'present',
2174
+ validationSignalsStatus: 'present',
2175
+ reviewSignal: 'present',
2176
+ blockingBeforeExecuteCount: 0
2177
+ };
2178
+ }
2179
+
2180
+ function reviewedPlan(): string {
2181
+ return `---
2182
+ contract: sdd-plan-doc-v3
2183
+ title: Reviewed test plan
2184
+ ---
2185
+
2186
+ # Reviewed Test Plan
2187
+
2188
+ ## 0. Metadata
2189
+
2190
+ - spec_id: test-spec
2191
+ - plan_id: test-plan
2192
+ - branch: master
2193
+ - lifecycle_profile: compact
2194
+ - risk_level: low
2195
+ - owner: plan-manager
2196
+ - reviewers: []
2197
+ - scout_domains: []
2198
+ - created_at: 2026-06-11T00:00:00.000Z
2199
+ - updated_at: 2026-06-11T00:00:00.000Z
2200
+
2201
+ ## 1. Upstream Spec Trace
2202
+
2203
+ | Spec Item | Plan Section | Strategy Response |
2204
+ |---|---|---|
2205
+ | AC-1 | §4 Target Design Overview | Use accepted upstream truth for plan closure. |
2206
+
2207
+ ## 2. Planning Problem / Strategy Framing
2208
+
2209
+ This fixture verifies plan close against an accepted spec handoff without making side evidence authoritative.
2210
+
2211
+ ## 3. Current Implementation Map
2212
+
2213
+ Observed evidence: 'packages/core/src/stage-collaboration.ts' owns 'reconcilePlanCollaborationClosure', 'validateAcceptedPlanArtifact', 'PlanRuntimeCloseBoundaryFacts', and runtime projection writes. 'packages/core/src/storage/runtime-store.ts' owns projection persistence. The plan reuses runtime ref/hash conventions and changes only plan close validation depth; it does not reuse side evidence as runtime truth. Scout gap: none for this fixture; code surfaces are grounded by direct test fixtures.
2214
+ ## 4. Target Design Overview
2215
+
2216
+ Use the accepted spec ref/hash as upstream truth and keep plan-stage side traces out of downstream consumption.
2217
+
2218
+ ## 5. Change Topology / Responsibility Boundaries
2219
+
2220
+ Plan close remains output-centered around final plan.md V3. Tasks-stage owns decomposition and execution topology.
2221
+
2222
+ ## 6. Interface / API / Schema Design
2223
+
2224
+ Plan close accepts final plan.md V3 ref/hash and 'PlanRuntimeCloseBoundaryFacts'. No HTTP endpoint exists; the API surface is the TypeScript function contract plus runtime-store projection schema.
2225
+
2226
+ | Source table / input field | Runtime field / output mapping | Owner | Rule |
2227
+ |---|---|---|---|
2228
+ | 'PlanRuntimeCloseBoundaryFacts.branch' | 'branch' / 'scopeKey' | 'reconcilePlanCollaborationClosure' | Selects the workflow partition. |
2229
+ | 'acceptedSpecRef' | 'upstreamSpecRef' | 'validateAcceptedPlanArtifact' | Must match the accepted spec document ref. |
2230
+ | 'acceptedSpecHash' | 'upstreamSpecHash' | 'validateAcceptedPlanArtifact' | Must match the stored accepted spec hash. |
2231
+ | 'finalPlanRef' / 'acceptedPlanRef' | 'acceptedPlanRef' | runtime-store projection schema | Must point to 'specs/master/plan.md'. |
2232
+ | 'finalPlanHash' / 'acceptedPlanHash' | 'acceptedPlanHash' and 'contentHash' | runtime-store projection schema | Must match canonical file hash before handoff. |
2233
+ | 'blockingBeforeTasksCount' | 'readyForTasks' status | plan adjudication projection | Zero blockers maps to 'ready_for_tasks'. |
2234
+ ## 7. State / Data / Concurrency Design
2235
+
2236
+ No persisted business data is changed, but runtime projection state must remain consistent across close, rejection, and handoff publication.
2237
+
2238
+ | State / data key | Rule | Failure / rollback behavior |
2239
+ |---|---|---|
2240
+ | 'partitionKey' and 'scopeKey' | Branch-scoped runtime state isolates 'master' from other partitions. | Wrong key leaves existing handoff stale and does not publish a new handoff. |
2241
+ | 'projectionType' and 'ownerStage' | plan adjudication and workflow handoff stay separate records. | Partial failure must not publish plan->tasks without 'acceptedPlanRef'. |
2242
+ | 'acceptedPlanRef' and 'contentHash' | Hash equality is required before runtime accepts the plan artifact. | Hash mismatch rejects close and preserves the previous projection state. |
2243
+ | 'handoffStatus' and 'readyForTasks' | 'ready_for_tasks' is only derived after zero blockers and valid plan evidence. | Blocking status routes back to plan repair instead of tasks. |
2244
+ | 'supersededAt' and 'rollbackState' | Superseding or deleting plan-stage projections is the rollback path. | Downstream tasks projections are not edited as plan rollback. |
2245
+ ## 8. Key Design Decisions
2246
+
2247
+ | Decision | Reason | Tradeoff | Rejected alternatives |
2248
+ |---|---|---|---|
2249
+ | Validate final plan.md only | Keep side findings non-authoritative | Requires dense plan document | Required plan side evidence |
2250
+
2251
+ ## 9. Alternatives Considered
2252
+
2253
+ | Alternative | Why rejected |
2254
+ |---|---|
2255
+ | Close from side evidence | Would make side logs authoritative again. |
2256
+
2257
+ ## 10. Risk Controls
2258
+
2259
+ | Risk | Impact | Design Control | Validation Hook | Task-stage Constraint |
2260
+ |---|---|---|---|---|
2261
+ | Thin plan fixture | Tasks lose design guidance and execute guesses runtime behavior. | Require concrete surfaces, mappings, state/failure rules, and scenario validation before close. | stage-collaboration tests assert accepted and rejected plan closures. | tasks-stage must consume accepted plan ref/hash and must not derive from side logs. |
2262
+
2263
+ ## 11. Validation Strategy
2264
+
2265
+ | Scenario | Input Data / User Action | Expected API / UI / SQL Result | Command / Evidence | Proves | Does Not Prove |
2266
+ |---|---|---|---|---|---|
2267
+ | Accepted plan close | 'finalPlanRef=specs/master/plan.md', matching 'finalPlanHash', zero blockers | Runtime returns 'ready_for_tasks' and records plan->tasks handoff | stage-collaboration focused test | Proves ref/hash and close evidence are accepted. | Does not prove application feature behavior. |
2268
+ | Stale plan close | 'finalPlanHash' differs from canonical plan content | Runtime rejects close and does not publish a fresh handoff | stage-collaboration stale hash test | Proves failure routing and rollback preservation. | Does not prove all storage failures. |
2269
+
2270
+ ## 12. Rollout / Rollback / Compatibility
2271
+
2272
+ This fixture has no rollout surface beyond regression validation.
2273
+
2274
+ ## 13. Task-stage Constraints
2275
+
2276
+ Tasks must consume accepted spec and accepted plan refs only; task-stage owns task IDs, execution waves, dependency graph, and per-task acceptance.
2277
+
2278
+ Task slicing boundary: keep runtime artifact validation, projection write, and handoff inspection in one rollback unit unless a later task explicitly proves independent rollback safety. Dependency rationale: tasks that consume plan close output must wait for accepted 'acceptedPlanRef' and 'contentHash' evidence. Validation coverage must include accepted and rejected close scenarios. Risky seam: never let side evidence replace the canonical plan ref/hash.
2279
+
2280
+ Forbidden: task IDs, task lists, execution waves, dependency graph, taskClass, sdd-task blocks, implementation checklist.
2281
+
2282
+ ## 14. Open Questions / User Decisions
2283
+
2284
+ | ID | Question / Decision | Owner | Blocks |
2285
+ |---|---|---|---|
2286
+
2287
+ ## 15. Plan Close Quality Evidence
2288
+
2289
+ - technical_design_sufficient: true
2290
+ - code_surfaces_grounded: true
2291
+ - field_api_sql_mapping_complete: true
2292
+ - state_failure_rollback_model_complete: true
2293
+ - scenario_validation_matrix_complete: true
2294
+ - risk_controls_tied_to_validation: true
2295
+ - downstream_design_guesswork_remaining: []
2296
+ - task_list_leakage_checked: true
2297
+ - ready_for_tasks: true
2298
+ `;
2299
+ }
2300
+
2301
+ function reviewedTasks(options: { duplicate?: boolean; completed?: boolean; includeValidation?: boolean } = {}): string {
2302
+ const secondId = options.duplicate ? 'T1' : 'T2';
2303
+ const status = options.completed ? 'completed' : 'pending';
2304
+ const validationTasks = `${reviewedValidationTask(status, 'T1', 'T3', 'packages/core/src/example.ts', 'REQ-1', 'AC-1', 'PLAN-1')}${reviewedValidationTask(status, secondId, 'T4', 'packages/core/src/example-2.ts', 'REQ-2', 'AC-2', 'PLAN-2')}`;
2305
+ return `---
2306
+ contract: sdd-tasks-doc-v2
2307
+ title: Reviewed test tasks
2308
+ ---
2309
+
2310
+ # Reviewed Test Tasks
2311
+
2312
+ ## 1. Split Basis
2313
+
2314
+ The tasks are split from the accepted spec and plan into implementation work first, followed by a separate validation task. Split chain: PLAN-1 / PLAN-2 plan decisions -> work-unit boundary -> dependency and rollback rationale -> completion evidence -> failure route.
2315
+
2316
+ ## 2. Execution Order Overview
2317
+
2318
+ | Wave | Tasks | Type | Why now |
2319
+ |---|---|---|---|
2320
+ | 1 | T1, ${secondId} | implementation | Implement the accepted plan boundaries before validation. |
2321
+ | 2 | T3, T4 | validation | Validate each accepted implementation rollback unit one-to-one after T1 and ${secondId}. |
2322
+
2323
+ ## 3. Implementation Tasks
2324
+
2325
+ ### T1 — Implement first reviewed task
2326
+
2327
+ **Type**: implementation / core
2328
+ **Execution order**: Wave 1
2329
+ **Dependencies**: none
2330
+ **Routing hint**: runtime, backend
2331
+
2332
+ **Why this task exists**
2333
+
2334
+ T1 implements the first accepted plan decision and risk boundary without running the final validation. This work-unit context preserves the design part from PLAN-1 and execute must not reinterpret the upstream scope.
2335
+
2336
+ **Spec context**
2337
+
2338
+ Covers REQ-1 and contributes to AC-1.
2339
+
2340
+ **Plan context**
2341
+
2342
+ Keep the implementation inside PLAN-1 and hand the result to T3 for validation.
2343
+
2344
+ **Implementation scope**
2345
+
2346
+ - Allowed code changes: packages/core/src/example.ts only.
2347
+
2348
+ - Forbidden files: packages/core/src/example-2.ts and unrelated runtime storage files.
2349
+ **Do not do**
2350
+
2351
+ - Do not run full validation in this implementation task.
2352
+ - Do not change unrelated files.
2353
+
2354
+ **Completion evidence**
2355
+
2356
+ - Code review confirms the T1 boundary and basic code sanity passes.
2357
+ - Observable completion means the affected area is updated and ready for T3; what does not count as completion is running only the final validation without implementation evidence.
2358
+
2359
+ **Handoff**
2360
+
2361
+ T1 hands off to T3 for validation.
2362
+
2363
+ **Rollback unit**
2364
+
2365
+ T1 rollback unit is packages/core/src/example.ts; it is reverted together with T3 evidence if validation shows this boundary caused failure.
2366
+
2367
+ \`\`\`sdd-task
2368
+ id: T1
2369
+ status: ${status}
2370
+ taskClass: implementation
2371
+ unitType: core
2372
+ wave: 1
2373
+ dependencies: []
2374
+ validationHandoff:
2375
+ - T3
2376
+ routingHints:
2377
+ - runtime
2378
+ - backend
2379
+ affectedAreas:
2380
+ - packages/core/src/example.ts
2381
+ sourceRequirements:
2382
+ - REQ-1
2383
+ sourceAcceptanceCriteria:
2384
+ - AC-1
2385
+ planRefs:
2386
+ - PLAN-1
2387
+ change_surface: backend_only
2388
+ depends_on: []
2389
+ affected_files:
2390
+ - packages/core/src/example.ts
2391
+ validation:
2392
+ - code review
2393
+ risk: []
2394
+ acceptance_refs:
2395
+ - AC-1
2396
+ plan_refs:
2397
+ - PLAN-1
2398
+ \`\`\`
2399
+
2400
+ #### Boundary
2401
+
2402
+ Implement only the reviewed T1 boundary with allowed files and forbidden scope preserved.
2403
+
2404
+ #### Acceptance
2405
+
2406
+ - AC-1 is handed off to T3 for validation.
2407
+
2408
+ ### ${secondId} — Implement second reviewed task
2409
+
2410
+ **Type**: implementation / core
2411
+ **Execution order**: Wave 1
2412
+ **Dependencies**: none
2413
+ **Routing hint**: runtime, backend
2414
+
2415
+ **Why this task exists**
2416
+
2417
+ ${secondId} implements the second accepted plan decision and risk boundary without running the final validation. This work-unit context preserves the design part from PLAN-2 and execute must not reinterpret the upstream scope.
2418
+
2419
+ **Spec context**
2420
+
2421
+ Covers REQ-2 and contributes to AC-2.
2422
+
2423
+ **Plan context**
2424
+
2425
+ Keep the implementation inside PLAN-2 and hand the result to T4 for validation.
2426
+
2427
+ **Implementation scope**
2428
+
2429
+ - Allowed code changes: packages/core/src/example-2.ts only.
2430
+
2431
+ - Forbidden files: packages/core/src/example.ts and unrelated runtime storage files.
2432
+ **Do not do**
2433
+
2434
+ - Do not run full validation in this implementation task.
2435
+ - Do not change unrelated files.
2436
+
2437
+ **Completion evidence**
2438
+
2439
+ - Code review confirms the ${secondId} boundary and basic code sanity passes.
2440
+ - Observable completion means the affected area is updated and ready for T4; what does not count as completion is running only the final validation without implementation evidence.
2441
+
2442
+ **Handoff**
2443
+
2444
+ ${secondId} hands off to T4 for validation.
2445
+
2446
+ **Rollback unit**
2447
+
2448
+ ${secondId} rollback unit is packages/core/src/example-2.ts; it is reverted together with T4 evidence if validation shows this boundary caused failure.
2449
+
2450
+ \`\`\`sdd-task
2451
+ id: ${secondId}
2452
+ status: ${status}
2453
+ taskClass: implementation
2454
+ unitType: core
2455
+ wave: 1
2456
+ dependencies: []
2457
+ validationHandoff:
2458
+ - T4
2459
+ routingHints:
2460
+ - runtime
2461
+ - backend
2462
+ affectedAreas:
2463
+ - packages/core/src/example-2.ts
2464
+ sourceRequirements:
2465
+ - REQ-2
2466
+ sourceAcceptanceCriteria:
2467
+ - AC-2
2468
+ planRefs:
2469
+ - PLAN-2
2470
+ change_surface: backend_only
2471
+ depends_on: []
2472
+ affected_files:
2473
+ - packages/core/src/example-2.ts
2474
+ validation:
2475
+ - code review
2476
+ risk: []
2477
+ acceptance_refs:
2478
+ - AC-2
2479
+ plan_refs:
2480
+ - PLAN-2
2481
+ \`\`\`
2482
+
2483
+ #### Boundary
2484
+
2485
+ Implement only the reviewed ${secondId} boundary with allowed files and forbidden scope preserved.
2486
+
2487
+ #### Acceptance
2488
+
2489
+ - AC-2 is handed off to T4 for validation.
2490
+
2491
+ ## 4. Validation Tasks
2492
+
2493
+ ${validationTasks}## 5. Task Dependencies and Handoff
2494
+
2495
+ T1 and ${secondId} run in Wave 1 because they unlock independent implementation surfaces. T3 depends on T1, and T4 depends on ${secondId}, because validation must map one-to-one to each implementation rollback unit. Parallel implementation is safe only while the two rollback units stay separate.
2496
+
2497
+ ## 6. Coverage Check
2498
+
2499
+ | Source | Covered by | Work-unit rationale | Completion / validation evidence |
2500
+ |---|---|---|---|
2501
+ | REQ-1 / AC-1 | T1, T3 | PLAN-1 implementation plus independent validation | T1 completion evidence and T3 expected result |
2502
+ | REQ-2 / AC-2 | ${secondId}, T4 | PLAN-2 implementation plus independent validation | ${secondId} completion evidence and T4 expected result |
2503
+ | PLAN-1 / PLAN-2 | T1, ${secondId}, T3, T4 | plan decisions mapped to rollback-separated tasks | validation handoff verifies each work unit one-to-one |
2504
+
2505
+ ## 7. Open Execution Questions
2506
+
2507
+ No blocking execution questions before execute. Failures route to implementation task, tasks boundary repair, plan repair, spec/user decision, or environment diagnostic route according to the failure routing above.
2508
+
2509
+ ## 8. Tasks Close Quality Evidence
2510
+
2511
+ - task_decomposition_sufficient: true
2512
+ - work_units_have_context: true
2513
+ - upstream_trace_complete: true
2514
+ - boundaries_and_rollback_units_defined: true
2515
+ - dependency_rationale_complete: true
2516
+ - completion_evidence_defined: true
2517
+ - validation_expected_results_defined: true
2518
+ - failure_routes_defined: true
2519
+ - task-review-agent verdict: approved
2520
+ - review_reconciliation: blocking findings resolved; review_blockers_remaining: []
2521
+ - downstream_execution_guesswork_remaining: []
2522
+ - ready_for_execute: true
2523
+ `;
2524
+ }
2525
+
2526
+ function warningOnlyTasks(): string {
2527
+ return [
2528
+ '---',
2529
+ 'contract: sdd-tasks-doc-v2',
2530
+ 'title: Warning-only tasks',
2531
+ '---',
2532
+ '',
2533
+ '# Warning-only Tasks',
2534
+ '',
2535
+ '## Split Basis',
2536
+ '',
2537
+ 'Split accepted plan into one implementation and one validation task.',
2538
+ '',
2539
+ '## Execution Order Overview',
2540
+ '',
2541
+ 'Wave 1 runs T1. Wave 2 runs T2.',
2542
+ '',
2543
+ '## Implementation Tasks',
2544
+ '',
2545
+ '### T1 — Implement minimal report path',
2546
+ '',
2547
+ 'Change only packages/core/src/example.ts.',
2548
+ '',
2549
+ '```sdd-task',
2550
+ 'id: T1',
2551
+ 'status: pending',
2552
+ 'taskClass: implementation',
2553
+ 'unitType: core',
2554
+ 'wave: 1',
2555
+ 'dependencies: []',
2556
+ 'validationHandoff:',
2557
+ ' - T2',
2558
+ 'affectedAreas:',
2559
+ ' - packages/core/src/example.ts',
2560
+ 'affected_files:',
2561
+ ' - packages/core/src/example.ts',
2562
+ 'validation:',
2563
+ ' - code review',
2564
+ '```',
2565
+ '',
2566
+ '#### Boundary',
2567
+ '',
2568
+ 'Start from the existing minimal report path. Change only packages/core/src/example.ts and do not modify unrelated runtime storage.',
2569
+ '',
2570
+ '#### Acceptance',
2571
+ '',
2572
+ '- Completion evidence: code review confirms the T1 boundary and T2 validation remains the proof task.',
2573
+ '',
2574
+ '## Validation Tasks',
2575
+ '',
2576
+ '### T2 — Validate minimal report path',
2577
+ '',
2578
+ 'Run the selected check and route failures to tasks repair, plan repair, spec/user decision, or environment diagnostic.',
2579
+ '',
2580
+ '```sdd-task',
2581
+ 'id: T2',
2582
+ 'status: pending',
2583
+ 'taskClass: validation',
2584
+ 'unitType: validation',
2585
+ 'wave: 2',
2586
+ 'dependencies:',
2587
+ ' - T1',
2588
+ 'validatesImplementationTasks:',
2589
+ ' - T1',
2590
+ 'affectedAreas:',
2591
+ ' - packages/core/src/example.ts',
2592
+ 'affected_files:',
2593
+ ' - packages/core/src/example.ts',
2594
+ 'validation:',
2595
+ ' - npm test',
2596
+ '```',
2597
+ '',
2598
+ '#### Boundary',
2599
+ '',
2600
+ 'Validate only T1 output and do not expand implementation scope.',
2601
+ '',
2602
+ '#### Acceptance',
2603
+ '',
2604
+ '- Expected result: selected validation check passes for T1 output; this does not prove release readiness.',
2605
+ '',
2606
+ '## Task Dependencies and Handoff',
2607
+ '',
2608
+ 'T2 depends on T1.',
2609
+ '',
2610
+ '## Coverage Check',
2611
+ '',
2612
+ 'T1 and T2 cover the accepted minimal report path.',
2613
+ '',
2614
+ '## Open Execution Questions',
2615
+ '',
2616
+ 'No blocking execution questions. Failure route: tasks repair, plan repair, spec/user decision, or environment diagnostic.',
2617
+ '',
2618
+ '## Tasks Close Quality Evidence',
2619
+ '',
2620
+ '- task-review-agent verdict: approved',
2621
+ '- review_reconciliation: blocking findings resolved; review_blockers_remaining: []',
2622
+ '- downstream_execution_guesswork_remaining: []',
2623
+ ''
2624
+ ].join('\n');
2625
+ }
2626
+
2627
+ function oversizedCoarseTasks(): string {
2628
+ return warningOnlyTasks()
2629
+ .replace(
2630
+ 'Change only packages/core/src/example.ts.',
2631
+ 'Change Controller, Service, Mapper, DTO, VO, mapper XML, SQL, JSP, and UI configuration in one task. Affected surfaces: ReportController, ReportService, ReportMapper, ReportDTO, ReportVO, mapper XML, permission SQL, JSP, and UI route.'
2632
+ )
2633
+ .replace(
2634
+ 'Start from the existing minimal report path. Change only packages/core/src/example.ts and do not modify unrelated runtime storage.',
2635
+ 'Start from the existing report path. Change backend API, service logic, mapper SQL, XML wiring, permission SQL, JSP, and UI route in this one task. Do not modify unrelated runtime storage.'
2636
+ );
2637
+ }
2638
+
2639
+ function reviewedValidationTask(status: string, implementationId: string, validationId: string, affectedFile: string, sourceRequirement: string, acceptanceRef: string, planRef: string): string {
2640
+ return `### ${validationId} — Validate ${implementationId} output
2641
+
2642
+ **Type**: validation<br>
2643
+ **Execution order**: Wave 2<br>
2644
+ **Dependencies**: ${implementationId}
2645
+
2646
+ **Validates implementation task**
2647
+
2648
+ - ${implementationId}
2649
+
2650
+ **Validation target**
2651
+
2652
+ Validate ${acceptanceRef} against the implementation output from ${implementationId}; expected result is that targeted tests pass and acceptance behavior matches the accepted plan.
2653
+
2654
+ **Validation method**
2655
+
2656
+ - npm test
2657
+
2658
+ **Pass criteria**
2659
+
2660
+ The targeted validation passes, no blocking acceptance gap remains, and evidence is enough to accept ${implementationId}; it does not prove unrelated release readiness.
2661
+
2662
+ **Failure routing**
2663
+
2664
+ - Runtime behavior failures return to ${implementationId}.
2665
+ - Boundary errors route to tasks repair; missing technical design routes to plan repair; unclear business rule routes to spec/user decision; environment failures route to diagnostic handling.
2666
+
2667
+ \`\`\`sdd-task
2668
+ id: ${validationId}
2669
+ status: ${status}
2670
+ taskClass: validation
2671
+ unitType: validation
2672
+ wave: 2
2673
+ dependencies:
2674
+ - ${implementationId}
2675
+ validatesImplementationTasks:
2676
+ - ${implementationId}
2677
+ routingHints:
2678
+ - validation
2679
+ affectedAreas:
2680
+ - ${affectedFile}
2681
+ sourceRequirements:
2682
+ - ${sourceRequirement}
2683
+ sourceAcceptanceCriteria:
2684
+ - ${acceptanceRef}
2685
+ planRefs:
2686
+ - ${planRef}
2687
+ change_surface: backend_only
2688
+ depends_on:
2689
+ - ${implementationId}
2690
+ affected_files:
2691
+ - ${affectedFile}
2692
+ validation:
2693
+ - npm test
2694
+ risk: []
2695
+ acceptance_refs:
2696
+ - ${acceptanceRef}
2697
+ plan_refs:
2698
+ - ${planRef}
2699
+ \`\`\`
2700
+
2701
+ #### Boundary
2702
+
2703
+ Validate only ${implementationId} output.
2704
+
2705
+ #### Acceptance
2706
+
2707
+ - ${acceptanceRef} is validated.
2708
+
2709
+ `;
2710
+ }
2711
+
2712
+ function reviewedVerify(tasksContent: string, options: { omitTaskId?: string } = {}): string {
2713
+ const tasks = reviewedVerifyTasks(tasksContent).filter((task) => task.id !== options.omitTaskId);
2714
+ const taskRows = tasks
2715
+ .map((task) => `| ${task.id} | ${task.acceptanceRefs.length > 0 ? task.acceptanceRefs.join('<br>') : 'none'} | ${task.validationCommands.length > 0 ? task.validationCommands.join('<br>') : 'npm test'} | validation-report.md | available |`)
2716
+ .join('\n');
2717
+ const batches = tasks
2718
+ .map((task) => ` - task: ${task.id}`)
2719
+ .join('\n');
2720
+ return `---
2721
+ contract: ${VERIFY_DOCUMENT_CONTRACT_VERSION}
2722
+ version: 1.0.0
2723
+ branch: master
2724
+ author_role: verification-designer
2725
+ independent_from_roles:
2726
+ - task-planner
2727
+ - implementer
2728
+ ---
2729
+
2730
+ # Verify Contract: master
2731
+
2732
+ ## 1. Purpose
2733
+
2734
+ Reviewed verification contract produced by the tasks-stage verification design fixture.
2735
+
2736
+ ## 2. Verification Batches
2737
+
2738
+ verification_batches:
2739
+ ${batches}
2740
+
2741
+ ## 3. Task Verification Matrix
2742
+
2743
+ | Task | Acceptance refs | Validation commands | Required artifacts | Verification availability |
2744
+ |---|---|---|---|---|
2745
+ ${taskRows}
2746
+
2747
+ ## 4. Verification Rules
2748
+
2749
+ - PASS requires policy-backed acceptance evidence.
2750
+
2751
+ ## 5. Out of Scope
2752
+
2753
+ - This document does not authorize publish, push, tag, or release actions.
2754
+ `;
2755
+ }
2756
+
2757
+ function reviewedVerifyTasks(tasksContent: string): Array<{ id: string; acceptanceRefs: string[]; validationCommands: string[] }> {
2758
+ return Array.from(tasksContent.matchAll(/```sdd-task\s*\r?\n([\s\S]*?)```/g))
2759
+ .map((match) => match[1])
2760
+ .map((block) => ({
2761
+ id: blockScalarValue(block, 'id'),
2762
+ acceptanceRefs: blockListValue(block, ['sourceAcceptanceCriteria', 'source_acceptance_criteria', 'acceptance_refs']),
2763
+ validationCommands: blockListValue(block, ['validation'])
2764
+ }))
2765
+ .filter((task): task is { id: string; acceptanceRefs: string[]; validationCommands: string[] } => Boolean(task.id));
2766
+ }
2767
+
2768
+ function blockScalarValue(block: string, key: string): string | null {
2769
+ const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
2770
+ const match = block.match(new RegExp(`^\\s*${escapedKey}:\\s*(.+?)\\s*$`, 'm'));
2771
+ return match ? cleanBlockValue(match[1]) : null;
2772
+ }
2773
+
2774
+ function blockListValue(block: string, keys: string[]): string[] {
2775
+ const lines = block.split(/\r?\n/);
2776
+ for (const key of keys) {
2777
+ const start = lines.findIndex((line) => new RegExp(`^\\s*${key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}:`).test(line));
2778
+ if (start === -1) {
2779
+ continue;
2780
+ }
2781
+ const inline = lines[start].split(':').slice(1).join(':').trim();
2782
+ if (inline && inline !== '[]') {
2783
+ return inline.startsWith('[') && inline.endsWith(']')
2784
+ ? inline.slice(1, -1).split(',').map(cleanBlockValue).filter(Boolean)
2785
+ : [cleanBlockValue(inline)];
2786
+ }
2787
+ const values: string[] = [];
2788
+ for (const line of lines.slice(start + 1)) {
2789
+ if (/^\s*[A-Za-z_][\w]*:\s*/.test(line)) {
2790
+ break;
2791
+ }
2792
+ const item = line.trim().match(/^-\s+(.+)$/)?.[1];
2793
+ if (item) {
2794
+ values.push(cleanBlockValue(item));
2795
+ }
2796
+ }
2797
+ return values;
2798
+ }
2799
+ return [];
2800
+ }
2801
+
2802
+ function cleanBlockValue(value: string): string {
2803
+ return value.trim().replace(/^["'`]|["'`]$/g, '');
2804
+ }
2805
+
2806
+ interface StageArtifactPairCase {
2807
+ stage: SddStage;
2808
+ reviewFile: string;
2809
+ managerFile: string;
2810
+ reviewKind: string;
2811
+ reviewProducer: string;
2812
+ managerProducer: string;
2813
+ reviewContract: string;
2814
+ managerContract: string;
2815
+ targetRef: string;
2816
+ }
2817
+
2818
+ const stageArtifactPairCases: StageArtifactPairCase[] = [
2819
+ {
2820
+ stage: 'tasks',
2821
+ reviewFile: 'tasks-review-v1.md',
2822
+ managerFile: 'tasks-manager-v1.md',
2823
+ reviewKind: 'tasks_review',
2824
+ reviewProducer: 'task-review-agent',
2825
+ managerProducer: 'tasks-manager',
2826
+ reviewContract: 'sdd-tasks-review-artifact-v1',
2827
+ managerContract: 'sdd-tasks-manager-artifact-v1',
2828
+ targetRef: 'specs/master/tasks.md'
2829
+ },
2830
+ {
2831
+ stage: 'execute',
2832
+ reviewFile: 'code-review-v1.md',
2833
+ managerFile: 'execute-manager-v1.md',
2834
+ reviewKind: 'code_review',
2835
+ reviewProducer: 'code-reviewer',
2836
+ managerProducer: 'execute-manager',
2837
+ reviewContract: 'sdd-execute-code-review-artifact-v1',
2838
+ managerContract: 'sdd-execute-manager-artifact-v1',
2839
+ targetRef: '.sdd/runs/master/execute/implementation-v1.md'
2840
+ },
2841
+ {
2842
+ stage: 'execute',
2843
+ reviewFile: 'test-review-v1.md',
2844
+ managerFile: 'execute-manager-v1.md',
2845
+ reviewKind: 'test_review',
2846
+ reviewProducer: 'test-reviewer',
2847
+ managerProducer: 'execute-manager',
2848
+ reviewContract: 'sdd-execute-test-review-artifact-v1',
2849
+ managerContract: 'sdd-execute-manager-artifact-v1',
2850
+ targetRef: '.sdd/runs/master/execute/test-execution-v1.md'
2851
+ },
2852
+ {
2853
+ stage: 'execute',
2854
+ reviewFile: 'evidence-judgment-review-v1.md',
2855
+ managerFile: 'evidence-judgment-manager-v1.md',
2856
+ reviewKind: 'evidence_review',
2857
+ reviewProducer: 'evidence-reviewer',
2858
+ managerProducer: 'execute-manager',
2859
+ reviewContract: 'sdd-execute-evidence-judgment-review-artifact-v1',
2860
+ managerContract: 'sdd-execute-manager-artifact-v1',
2861
+ targetRef: '.sdd/runs/master/execute/evidence-judgment-v1.md'
2862
+ },
2863
+ {
2864
+ stage: 'ship',
2865
+ reviewFile: 'release-review-v1.md',
2866
+ managerFile: 'ship-manager-v1.md',
2867
+ reviewKind: 'release_review',
2868
+ reviewProducer: 'release-reviewer',
2869
+ managerProducer: 'ship-manager',
2870
+ reviewContract: 'sdd-ship-release-review-artifact-v1',
2871
+ managerContract: 'sdd-ship-manager-artifact-v1',
2872
+ targetRef: '.sdd/runs/master/ship/ship-readiness-v1.md'
2873
+ }
2874
+ ];
2875
+
2876
+ async function writeStageArtifactPair(root: string, stageCase: StageArtifactPairCase): Promise<void> {
2877
+ const dir = path.join(root, '.sdd', 'runs', 'master', stageCase.stage);
2878
+ await mkdir(dir, { recursive: true });
2879
+ const targetHash = `target-hash-${stageCase.stage}`;
2880
+ const review = stageReviewArtifact(stageCase, targetHash);
2881
+ const reviewHash = hashDocumentContent(review);
2882
+ await writeFile(path.join(dir, stageCase.reviewFile), review, 'utf8');
2883
+ await writeFile(path.join(dir, stageCase.managerFile), stageManagerArtifact(stageCase, targetHash, reviewHash), 'utf8');
2884
+ }
2885
+
2886
+ function stageReviewArtifact(stageCase: StageArtifactPairCase, targetHash: string): string {
2887
+ return `---
2888
+ contract: ${stageCase.reviewContract}
2889
+ branch: master
2890
+ stage: ${stageCase.stage}
2891
+ kind: ${stageCase.reviewKind}
2892
+ producer: ${stageCase.reviewProducer}
2893
+ targetRef: ${stageCase.targetRef}
2894
+ targetHash: ${targetHash}
2895
+ verdict: approved
2896
+ findingCount: 0
2897
+ blockingCount: 0
2898
+ ---
2899
+
2900
+ # ${stageCase.stage} Review
2901
+
2902
+ Verdict: approved.
2903
+ `;
2904
+ }
2905
+
2906
+ function stageManagerArtifact(stageCase: StageArtifactPairCase, targetHash: string, reviewHash: string): string {
2907
+ return `---
2908
+ contract: ${stageCase.managerContract}
2909
+ branch: master
2910
+ stage: ${stageCase.stage}
2911
+ kind: manager_closure_request
2912
+ producer: ${stageCase.managerProducer}
2913
+ targetRef: ${stageCase.targetRef}
2914
+ targetHash: ${targetHash}
2915
+ reviewRef: .sdd/runs/master/${stageCase.stage}/${stageCase.reviewFile}
2916
+ reviewHash: ${reviewHash}
2917
+ recommendation: close_stage
2918
+ ---
2919
+
2920
+ # ${stageCase.stage} Manager Coordination
2921
+
2922
+ Recommendation: close_stage.
2923
+ `;
2924
+ }
2925
+
2926
+ function testStageRun(id: string, scope: { branch: string }, status: 'completed' | 'failed', timestamp: string) {
2927
+ return {
2928
+ contract: 'sdd-stage-run-v1' as const,
2929
+ id,
2930
+ scope,
2931
+ stage: 'execute' as const,
2932
+ ownerAgent: 'execute-manager',
2933
+ coMainAgents: ['implementer', 'code-reviewer'],
2934
+ status,
2935
+ inputRefs: [],
2936
+ outputRefs: status === 'completed' ? [ref('artifact', '.sdd/runs/feature/execute/implementation-v1.md', 'hash')] : [],
2937
+ decisionRefs: [],
2938
+ blockingReasons: status === 'failed' ? ['Later diagnostic rejection must not downgrade completed stage.'] : [],
2939
+ createdAt: timestamp,
2940
+ updatedAt: timestamp
2941
+ };
2942
+ }
2943
+
2944
+ function ref(kind: RuntimeRefKind, value: string, hash?: string) {
2945
+ return hash ? { kind, ref: value, hash } : { kind, ref: value };
2946
+ }
2947
+
2948
+ async function markdownSnapshot(root: string): Promise<Map<string, string>> {
2949
+ const entries = new Map<string, string>();
2950
+ await collectMarkdownSnapshot(root, root, entries);
2951
+ return entries;
2952
+ }
2953
+
2954
+ async function collectMarkdownSnapshot(root: string, dir: string, entries: Map<string, string>): Promise<void> {
2955
+ for (const entry of await readdir(dir, { withFileTypes: true })) {
2956
+ const absolute = path.join(dir, entry.name);
2957
+ const relative = path.relative(root, absolute).replace(/\\/g, '/');
2958
+ if (entry.isDirectory()) {
2959
+ await collectMarkdownSnapshot(root, absolute, entries);
2960
+ } else if (entry.isFile() && entry.name.endsWith('.md')) {
2961
+ entries.set(relative, hashDocumentContent(await readFile(absolute, 'utf8')));
2962
+ }
2963
+ }
2964
+ }