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,4018 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readdir, readFile } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { LIFECYCLE_DECISION_CONTRACT, LIFECYCLE_DECISION_VERSION, STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION, STAGE_RUN_CONTRACT_VERSION, WORKFLOW_HANDOFF_CONTRACT_VERSION } from './contracts.js';
5
+ import { LIFECYCLE_RISK_DECISION_PROJECTION_TYPE, lifecycleRiskDecisionScopeKey, recordLifecycleRiskDecisionProjection } from './risk/kernel.js';
6
+ import { createRun } from './run-state/run-state.js';
7
+ import { normalizePortablePath } from './path-safety.js';
8
+ import { getBranchStageEvidenceDir, normalizeBranchStageEvidenceRef, toBranchStageEvidenceRef } from './runtime-paths.js';
9
+ import { resolveSddContext } from './sdd-docs/context.js';
10
+ import { evaluatePlanArtifactDepth, evaluateSpecArtifactDepth, formatArtifactDepthBlockingIssues } from './sdd-docs/artifact-depth.js';
11
+ import { hashDocumentContent, hashSemanticDocument, hashTasksContract } from './sdd-docs/document-hashes.js';
12
+ import { parseSddTasksMarkdown } from './sdd-docs/task-parser.js';
13
+ import { EXECUTION_LANE_PROJECTION_TYPE, TASK_DEPENDENCY_GRAPH_PROJECTION_TYPE, TASK_UNIT_PROJECTION_TYPE, executionLaneScopeKey, readTaskDependencyGraphProjection, taskDependencyGraphScopeKey } from './task-execution-contract.js';
14
+ import { readWorkflowHandoffProjection, recordStageRunProjection, recordWorkflowHandoffProjection, STAGE_RUN_PROJECTION_TYPE, stageRunScopeKey, WORKFLOW_HANDOFF_PROJECTION_TYPE, workflowHandoffScopeKey } from './stage-runtime/runtime.js';
15
+ import { listRuntimeProjections, recordRuntimeArtifactPayload, recordRuntimeProjectionEnvelope, recordRuntimeStageArtifact, recordRuntimeStageCollaborationContract, runtimeScopedId } from './storage/runtime-store.js';
16
+ import { readMarkdownArtifact, validateStageArtifactFrontmatter } from './stage-artifacts.js';
17
+ import { readStageCollaborationContract, validateStageCollaborationContractFrontmatter } from './stage-collaboration-contracts.js';
18
+ export const SPEC_STAGE_MANAGER = 'spec-manager';
19
+ export const SPEC_STAGE_REVIEW_AGENT = 'spec-reviewer';
20
+ export const SPEC_STAGE_SCOUT_AGENT = 'scout';
21
+ export const SPEC_STAGE_AGENT_TEAM = [SPEC_STAGE_SCOUT_AGENT, SPEC_STAGE_REVIEW_AGENT];
22
+ export const SPEC_STAGE_REQUIRED_CAPABILITIES = ['norm_discovery', 'uncertainty_resolution'];
23
+ export const SPEC_STAGE_OPTIONAL_CAPABILITIES = ['context_curation', 'solution-design', 'frontend-engineering', 'security-engineering', 'ui-ux-product-design'];
24
+ export const SPEC_STAGE_MATERIAL_PACKS = ['project-norms', 'uncertainty-map', 'baseline-solution-design', 'baseline-frontend-engineering', 'baseline-security-engineering', 'baseline-ui-ux-product-design'];
25
+ export const STAGE_COLLABORATION_RUNTIME_PRODUCER_VERSION = 'phase9.1-stage-collaboration-runtime-v1';
26
+ export const SPEC_COLLABORATION_ADJUDICATION_PROJECTION_TYPE = 'phase9_1_spec_collaboration_adjudication';
27
+ export const PLAN_COLLABORATION_ADJUDICATION_PROJECTION_TYPE = 'phase9_2_plan_collaboration_adjudication';
28
+ export const PLAN_STAGE_MANAGER = 'plan-manager';
29
+ export const PLAN_STAGE_SCOUT_AGENT = 'plan-scout';
30
+ export const PLAN_STAGE_REVIEW_AGENT = 'plan-review-agent';
31
+ export const PLAN_STAGE_AGENT_TEAM = [PLAN_STAGE_SCOUT_AGENT, PLAN_STAGE_REVIEW_AGENT];
32
+ export const PLAN_SCOUT_DOMAINS = ['architecture-runtime', 'backend-api', 'frontend-ui', 'database-migration', 'security', 'performance', 'testing-validation'];
33
+ export const PLAN_STAGE_REQUIRED_CAPABILITIES = ['plan-strategy', 'plan-pressure-review'];
34
+ export const PLAN_STAGE_OPTIONAL_CAPABILITIES = [...PLAN_SCOUT_DOMAINS];
35
+ export const PLAN_STAGE_MATERIAL_PACKS = ['project-norms', 'plan-section-rubric', 'plan-scout-domain-packs'];
36
+ export const TASKS_COLLABORATION_ADJUDICATION_PROJECTION_TYPE = 'phase9_3_tasks_collaboration_adjudication';
37
+ export const TASKS_STAGE_MANAGER = 'tasks-manager';
38
+ export const TASKS_STAGE_REVIEW_AGENT = 'task-review-agent';
39
+ export const TASKS_STAGE_SLICER_AGENT = 'task-slicer';
40
+ export const EXECUTE_IMPLEMENTER_AGENT = 'implementer';
41
+ export const EXECUTE_CODE_REVIEW_AGENT = 'code-reviewer';
42
+ export const EXECUTE_DEBUGGER_AGENT = 'debugger';
43
+ export const EXECUTE_TEST_RUNNER_AGENT = 'test-runner';
44
+ export const EXECUTE_VALIDATOR_AGENT = 'validator';
45
+ export const EXECUTE_TEST_REVIEW_AGENT = 'test-reviewer';
46
+ export const EXECUTE_COLLABORATION_ADJUDICATION_PROJECTION_TYPE = 'phase10_execute_collaboration_adjudication';
47
+ export const EXECUTE_EVIDENCE_JUDGMENT_VALIDATOR_AGENT = 'evidence-validator';
48
+ export const EXECUTE_EVIDENCE_JUDGMENT_REVIEW_AGENT = 'evidence-reviewer';
49
+ export const TRUTH_ALIGNMENT_PROJECTION_TYPE = 'phase9_12_truth_alignment';
50
+ export const TRUTH_ALIGNMENT_CONTRACT = 'sdd-truth-alignment-v1';
51
+ export const SHIP_COLLABORATION_ADJUDICATION_PROJECTION_TYPE = 'phase9_9_ship_collaboration_adjudication';
52
+ export const SHIP_STAGE_MANAGER = 'ship-manager';
53
+ export const SHIP_STAGE_VALIDATOR_AGENT = 'ship-validator';
54
+ export const SHIP_STAGE_REVIEW_AGENT = 'release-reviewer';
55
+ const TRUTH_ALIGNMENT_STATUSES = ['aligned', 'drift_detected', 'update_required', 'blocked'];
56
+ const TRUTH_ALIGNMENT_SEMANTIC_IMPACTS = ['none', 'bounded', 'material'];
57
+ const TRUTH_ALIGNMENT_OWNER_STAGES = ['spec', 'plan', 'tasks', 'execute', 'ship'];
58
+ export function deriveSpecCollaborationProfile(decision, generatedAt = new Date().toISOString()) {
59
+ const required = decision.requiredStages.includes('spec');
60
+ const intensity = specIntensity(decision, required);
61
+ return {
62
+ contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
63
+ stage: 'spec',
64
+ scope: decision.scope,
65
+ lifecycleProfile: decision.profile,
66
+ intensity,
67
+ required,
68
+ requiresStageClosure: intensity !== 'noop' && intensity !== 'blocked',
69
+ allowedProposalKinds: intensity === 'noop' ? [] : ['advisory_finding', 'blocking_ambiguity', 'capability_suggestion', 'handoff_proposal'],
70
+ stageManager: intensity === 'noop' || intensity === 'blocked' ? null : SPEC_STAGE_MANAGER,
71
+ agentTeam: specMemberAgents(intensity),
72
+ requiredCapabilities: specRequiredCapabilities(intensity),
73
+ optionalCapabilities: specOptionalCapabilities(intensity),
74
+ materialPackIds: specMaterialPackIds(intensity),
75
+ collaborationPlan: specCollaborationPlan(intensity),
76
+ inputRefs: decision.inputRefs,
77
+ reasons: specProfileReasons(decision, required, intensity),
78
+ generatedAt
79
+ };
80
+ }
81
+ export function buildSpecStageWorkOrder(profile, profileRef, generatedAt = new Date().toISOString()) {
82
+ return {
83
+ contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
84
+ workOrderId: stableId('spec-work-order', profile.scope),
85
+ stage: 'spec',
86
+ scope: profile.scope,
87
+ profileRef,
88
+ authorityCeiling: 'proposal_input',
89
+ requiredOutputKinds: specRequiredOutputKinds(profile.intensity),
90
+ forbiddenActions: ['stage_pass', 'risk_decision', 'gate_pass', 'ship_ready', 'truth_alignment_approved'],
91
+ stageManager: SPEC_STAGE_MANAGER,
92
+ agentTeam: profile.agentTeam,
93
+ requiredCapabilities: profile.requiredCapabilities,
94
+ optionalCapabilities: profile.optionalCapabilities,
95
+ materialPackIds: profile.materialPackIds,
96
+ collaborationPlan: profile.collaborationPlan,
97
+ inputRefs: profile.inputRefs,
98
+ generatedAt
99
+ };
100
+ }
101
+ export function planCollaborationScopeKey(scope) {
102
+ return [scope.branch, scope.taskId ?? 'all', scope.runId ?? 'none', scope.changeRef ?? 'none', 'plan'].join(':');
103
+ }
104
+ export function buildPlanStageWorkOrder(scope, profileRef, inputRefs, generatedAt = new Date().toISOString(), intensity = 'scout-first') {
105
+ const collaborationPlan = planCollaborationPlan(intensity);
106
+ return {
107
+ contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
108
+ workOrderId: stableId('plan-work-order', scope),
109
+ stage: 'plan',
110
+ scope,
111
+ profileRef,
112
+ authorityCeiling: 'proposal_input',
113
+ requiredOutputKinds: planRequiredOutputKinds(intensity),
114
+ forbiddenActions: ['stage_pass', 'risk_decision', 'gate_pass', 'ship_ready', 'truth_alignment_approved'],
115
+ inputRefs,
116
+ stageManager: PLAN_STAGE_MANAGER,
117
+ agentTeam: planMemberAgents(intensity),
118
+ planScoutDomains: planScoutDomains(intensity),
119
+ requiredCapabilities: planRequiredCapabilities(intensity),
120
+ optionalCapabilities: planOptionalCapabilities(intensity),
121
+ materialPackIds: planMaterialPackIds(intensity),
122
+ collaborationPlan,
123
+ generatedAt
124
+ };
125
+ }
126
+ export function tasksCollaborationScopeKey(scope) {
127
+ return [scope.branch, scope.taskId ?? 'all', scope.runId ?? 'none', scope.changeRef ?? 'none', 'tasks'].join(':');
128
+ }
129
+ export function buildTasksStageWorkOrder(scope, profileRef, inputRefs, generatedAt = new Date().toISOString()) {
130
+ return {
131
+ contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
132
+ workOrderId: stableId('tasks-work-order', scope),
133
+ stage: 'tasks',
134
+ scope,
135
+ profileRef,
136
+ authorityCeiling: 'proposal_input',
137
+ requiredOutputKinds: ['tasks_context', 'tasks_review', 'manager_closure_request'],
138
+ forbiddenActions: ['stage_pass', 'risk_decision', 'gate_pass', 'ship_ready', 'truth_alignment_approved'],
139
+ inputRefs,
140
+ stageManager: TASKS_STAGE_MANAGER,
141
+ agentTeam: [TASKS_STAGE_SLICER_AGENT, TASKS_STAGE_REVIEW_AGENT],
142
+ requiredCapabilities: ['task-decomposition', 'acceptance-mapping'],
143
+ optionalCapabilities: ['dependency-analysis', 'parallelization-planning'],
144
+ materialPackIds: ['project-norms', 'baseline-task-slicing', 'baseline-acceptance-mapping'],
145
+ generatedAt
146
+ };
147
+ }
148
+ export function executeCollaborationScopeKey(scope) {
149
+ return [scope.branch, scope.taskId ?? 'all', scope.runId ?? 'none', scope.changeRef ?? 'none', 'execute'].join(':');
150
+ }
151
+ export function buildExecuteStageWorkOrder(scope, profileRef, inputRefs, generatedAt = new Date().toISOString()) {
152
+ return {
153
+ contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
154
+ workOrderId: stableId('execute-work-order', scope),
155
+ stage: 'execute',
156
+ scope,
157
+ profileRef,
158
+ authorityCeiling: 'proposal_input',
159
+ requiredOutputKinds: ['implementation_evidence', 'code_review', 'validation_evidence', 'checkpoint_decision', 'evidence_judgment', 'manager_closure_request'],
160
+ forbiddenActions: ['stage_pass', 'risk_decision', 'gate_pass', 'ship_ready', 'truth_alignment_approved'],
161
+ inputRefs,
162
+ stageManager: 'execute-manager',
163
+ agentTeam: [EXECUTE_IMPLEMENTER_AGENT, EXECUTE_CODE_REVIEW_AGENT, EXECUTE_DEBUGGER_AGENT, EXECUTE_TEST_RUNNER_AGENT, EXECUTE_VALIDATOR_AGENT, EXECUTE_TEST_REVIEW_AGENT, EXECUTE_EVIDENCE_JUDGMENT_VALIDATOR_AGENT, EXECUTE_EVIDENCE_JUDGMENT_REVIEW_AGENT],
164
+ requiredCapabilities: ['implementation', 'code-review', 'validation', 'evidence-judgment'],
165
+ optionalCapabilities: ['debugging', 'checkpoint-review', 'frontend-engineering', 'security-engineering'],
166
+ materialPackIds: ['accepted-task-contract', 'execute-lane-contract', 'validation-contract', 'checkpoint-policy', 'evidence-judgment-policy'],
167
+ generatedAt
168
+ };
169
+ }
170
+ export function truthAlignmentScopeKey(scope) {
171
+ return [scope.branch, scope.taskId ?? 'all', scope.runId ?? 'none', scope.changeRef ?? 'none', 'truth-alignment'].join(':');
172
+ }
173
+ function buildTruthAlignmentProjection(scope, input) {
174
+ const acceptedRealityRefs = uniqueRuntimeRefs([input.acceptedEvidenceJudgmentRef, ...input.artifactRefs].filter((ref) => ref !== null));
175
+ return {
176
+ contract: TRUTH_ALIGNMENT_CONTRACT,
177
+ branch: scope.branch,
178
+ sourceStage: 'execute',
179
+ declaredTruthRefs: uniqueRuntimeRefs(input.declaredTruthRefs),
180
+ acceptedRealityRefs,
181
+ status: input.status,
182
+ ownerStage: input.ownerStage,
183
+ semanticImpact: input.semanticImpact,
184
+ staleRefs: uniqueRuntimeRefs(input.staleRefs),
185
+ invalidatesStages: [...new Set(input.invalidatesStages)],
186
+ reasons: input.reasons,
187
+ createdAt: input.generatedAt
188
+ };
189
+ }
190
+ export function shipCollaborationScopeKey(scope) {
191
+ return [scope.branch, scope.taskId ?? 'all', scope.runId ?? 'none', scope.changeRef ?? 'none', 'ship'].join(':');
192
+ }
193
+ export function buildShipStageWorkOrder(scope, profileRef, inputRefs, generatedAt = new Date().toISOString()) {
194
+ return {
195
+ contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
196
+ workOrderId: stableId('ship-work-order', scope),
197
+ stage: 'ship',
198
+ scope,
199
+ profileRef,
200
+ authorityCeiling: 'proposal_input',
201
+ requiredOutputKinds: ['ship_readiness', 'release_review', 'manager_closure_request'],
202
+ forbiddenActions: ['stage_pass', 'risk_decision', 'gate_pass', 'ship_ready', 'truth_alignment_approved'],
203
+ inputRefs,
204
+ stageManager: SHIP_STAGE_MANAGER,
205
+ agentTeam: [SHIP_STAGE_VALIDATOR_AGENT, SHIP_STAGE_REVIEW_AGENT],
206
+ requiredCapabilities: ['ship-readiness', 'release-readiness'],
207
+ optionalCapabilities: ['release-note-review', 'migration-readiness', 'operational-readiness'],
208
+ materialPackIds: ['project-norms', 'baseline-ship-readiness', 'baseline-release-review'],
209
+ generatedAt
210
+ };
211
+ }
212
+ export function specCollaborationScopeKey(scope) {
213
+ return [scope.branch, scope.taskId ?? 'all', scope.runId ?? 'none', scope.changeRef ?? 'none', 'spec'].join(':');
214
+ }
215
+ export async function recordSpecCollaborationAdjudicationProjection(projectRoot, result) {
216
+ return recordRuntimeProjectionEnvelope(projectRoot, {
217
+ projectionType: SPEC_COLLABORATION_ADJUDICATION_PROJECTION_TYPE,
218
+ scopeKey: specCollaborationScopeKey(result.scope),
219
+ inputHash: stableHash(JSON.stringify(result)),
220
+ producer: 'phase9.1-stage-collaboration-runtime',
221
+ producerVersion: STAGE_COLLABORATION_RUNTIME_PRODUCER_VERSION,
222
+ generatedAt: result.createdAt,
223
+ payload: result
224
+ });
225
+ }
226
+ export async function recordPlanCollaborationAdjudicationProjection(projectRoot, result) {
227
+ return recordRuntimeProjectionEnvelope(projectRoot, {
228
+ projectionType: PLAN_COLLABORATION_ADJUDICATION_PROJECTION_TYPE,
229
+ scopeKey: planCollaborationScopeKey(result.scope),
230
+ inputHash: stableHash(JSON.stringify(result)),
231
+ producer: 'phase9.2-plan-stage-collaboration-runtime',
232
+ producerVersion: 'phase9.2-plan-stage-collaboration-runtime-v1',
233
+ generatedAt: result.createdAt,
234
+ payload: result
235
+ });
236
+ }
237
+ export async function recordTasksCollaborationAdjudicationProjection(projectRoot, result) {
238
+ return recordRuntimeProjectionEnvelope(projectRoot, {
239
+ projectionType: TASKS_COLLABORATION_ADJUDICATION_PROJECTION_TYPE,
240
+ scopeKey: tasksCollaborationScopeKey(result.scope),
241
+ inputHash: stableHash(JSON.stringify(result)),
242
+ producer: 'phase9.3-tasks-stage-collaboration-runtime',
243
+ producerVersion: 'phase9.3-tasks-stage-collaboration-runtime-v1',
244
+ generatedAt: result.createdAt,
245
+ payload: result
246
+ });
247
+ }
248
+ export async function recordExecuteCollaborationAdjudicationProjection(projectRoot, result) {
249
+ return recordRuntimeProjectionEnvelope(projectRoot, {
250
+ projectionType: EXECUTE_COLLABORATION_ADJUDICATION_PROJECTION_TYPE,
251
+ scopeKey: executeCollaborationScopeKey(result.scope),
252
+ inputHash: stableHash(JSON.stringify(result)),
253
+ producer: 'phase10-execute-stage-collaboration-runtime',
254
+ producerVersion: 'phase10-execute-stage-collaboration-runtime-v1',
255
+ generatedAt: result.createdAt,
256
+ payload: result
257
+ });
258
+ }
259
+ export async function recordTruthAlignmentProjection(projectRoot, result) {
260
+ return recordRuntimeProjectionEnvelope(projectRoot, {
261
+ projectionType: TRUTH_ALIGNMENT_PROJECTION_TYPE,
262
+ scopeKey: truthAlignmentScopeKey({ branch: result.branch }),
263
+ inputHash: stableHash(JSON.stringify(result)),
264
+ producer: 'phase9.12-truth-alignment-runtime-projection',
265
+ producerVersion: 'phase9.12-truth-alignment-runtime-projection-v1',
266
+ generatedAt: result.createdAt,
267
+ payload: result
268
+ });
269
+ }
270
+ export async function readTruthAlignmentProjection(projectRoot, scope) {
271
+ const scopeKey = truthAlignmentScopeKey({ branch: scope.branch });
272
+ const projections = await listRuntimeProjections(projectRoot, [TRUTH_ALIGNMENT_PROJECTION_TYPE]);
273
+ return projections
274
+ .map((projection) => projection.payload)
275
+ .filter((envelope) => envelope?.projectionType === TRUTH_ALIGNMENT_PROJECTION_TYPE && (envelope.scopeKey === scopeKey || envelope.payload?.branch === scope.branch))
276
+ .sort((left, right) => right.generatedAt.localeCompare(left.generatedAt))[0] ?? null;
277
+ }
278
+ export async function recordShipCollaborationAdjudicationProjection(projectRoot, result) {
279
+ return recordRuntimeProjectionEnvelope(projectRoot, {
280
+ projectionType: SHIP_COLLABORATION_ADJUDICATION_PROJECTION_TYPE,
281
+ scopeKey: shipCollaborationScopeKey(result.scope),
282
+ inputHash: stableHash(JSON.stringify(result)),
283
+ producer: 'phase9.9-ship-stage-collaboration-runtime',
284
+ producerVersion: 'phase9.9-ship-stage-collaboration-runtime-v1',
285
+ generatedAt: result.createdAt,
286
+ payload: result
287
+ });
288
+ }
289
+ export async function reconcileSpecCollaborationClosure(projectRoot, input) {
290
+ const generatedAt = input.generatedAt ?? new Date().toISOString();
291
+ const context = await resolveSddContext(projectRoot, { branch: input.decision.scope.branch, branchSource: 'cli_option' });
292
+ const decision = {
293
+ ...input.decision,
294
+ scope: { ...input.decision.scope, branch: context.rawBranch }
295
+ };
296
+ const run = await createRun(projectRoot, {
297
+ runId: input.runId,
298
+ branch: context.rawBranch,
299
+ lifecycleDecision: runLifecycleDecisionRecord(decision)
300
+ });
301
+ const runRef = { kind: 'run', ref: run.runId };
302
+ const profile = deriveSpecCollaborationProfile(decision, generatedAt);
303
+ const profileRef = { kind: 'projection', ref: `${SPEC_COLLABORATION_ADJUDICATION_PROJECTION_TYPE}:${specCollaborationScopeKey(profile.scope)}:profile` };
304
+ const workOrder = profile.stageManager ? buildSpecStageWorkOrder(profile, profileRef, generatedAt) : null;
305
+ const registeredCollaborationContracts = input.collaborationContractRefs && input.collaborationContractRefs.length > 0
306
+ ? await registerStageCollaborationContracts(projectRoot, context.rawBranch, 'spec', workOrder, input.collaborationContractRefs, generatedAt)
307
+ : [];
308
+ const validatedCollaborationContract = latestValidatedStageCollaborationContract(registeredCollaborationContracts);
309
+ const collaborationContractRef = validatedCollaborationContract ? runtimeRefForStageCollaborationContract(validatedCollaborationContract) : null;
310
+ const registeredArtifacts = await registerSpecStageArtifacts(projectRoot, context.rawBranch, profile, input.artifactRefs, run.runId, generatedAt);
311
+ const artifactRefs = registeredArtifacts.map(runtimeRefForStageArtifact);
312
+ const outputClosure = await buildOutputCenteredSpecClosureRequest(projectRoot, profile, workOrder, context.partition, input.outputCloseRequest, generatedAt);
313
+ const closureRequest = input.closureRequest ?? outputClosure;
314
+ const coordination = input.coordination ?? deriveRegisteredSpecManagerCoordination(profile, workOrder, registeredArtifacts, generatedAt);
315
+ const candidate = closureRequest?.candidate ?? null;
316
+ const reviews = closureRequest?.reviewResults ?? [];
317
+ const capabilityFindings = closureRequest?.capabilityFindings ?? [];
318
+ let adjudication = adjudicateSpecStageClosureRequest({
319
+ profile,
320
+ closureRequest,
321
+ answerRefs: input.answerRefs,
322
+ generatedAt
323
+ });
324
+ const specAcceptance = await validateAcceptedSpecArtifact(projectRoot, {
325
+ partition: context.partition,
326
+ adjudication,
327
+ closureRequest,
328
+ registeredArtifacts,
329
+ validatedCollaborationContract
330
+ });
331
+ if (specAcceptance.rejectionIssue) {
332
+ adjudication = rejected({ profile, closureRequest, answerRefs: input.answerRefs }, specAcceptance.rejectionIssue.reasonCode, specAcceptance.rejectionIssue.explanation, specAcceptance.rejectionIssue.requiredNextAction, specAcceptance.rejectionIssue.fallbackRoute, generatedAt, true);
333
+ }
334
+ const adjudicationProjectionRef = specAdjudicationProjectionRef(adjudication.scope);
335
+ const lifecycleRiskProjectionRef = {
336
+ kind: 'projection',
337
+ ref: `${LIFECYCLE_RISK_DECISION_PROJECTION_TYPE}:${lifecycleRiskDecisionScopeKey(decision.scope)}`
338
+ };
339
+ const outputRefs = [specAcceptance.acceptedSpecRef, adjudicationProjectionRef, collaborationContractRef, ...artifactRefs].filter((ref) => ref !== null);
340
+ const stageRun = buildClosureStageRun(profile, adjudication, {
341
+ runId: run.runId,
342
+ outputRefs,
343
+ decisionRefs: [lifecycleRiskProjectionRef, adjudicationProjectionRef],
344
+ generatedAt
345
+ });
346
+ const stageRunProjectionRef = {
347
+ kind: 'projection',
348
+ ref: `${STAGE_RUN_PROJECTION_TYPE}:${stageRunScopeKey(stageRun.scope, stageRun.stage)}`
349
+ };
350
+ const handoff = specAcceptance.specAcceptanceStatus === 'accepted' && adjudication.health === 'ready_for_plan'
351
+ ? buildClosureWorkflowHandoff(profile, decision, adjudication, {
352
+ outputRefs,
353
+ requiredInputRefs: specAcceptance.acceptedSpecRef ? [specAcceptance.acceptedSpecRef] : [],
354
+ evidenceRefs: artifactRefs,
355
+ riskDecisionRef: lifecycleRiskProjectionRef,
356
+ generatedAt
357
+ })
358
+ : null;
359
+ const workflowHandoffProjectionRef = handoff
360
+ ? { kind: 'projection', ref: `${WORKFLOW_HANDOFF_PROJECTION_TYPE}:${workflowHandoffScopeKey(handoff.scope, handoff.fromStage, handoff.toStage)}` }
361
+ : undefined;
362
+ const closureRefs = {
363
+ runRef,
364
+ acceptedSpecRef: specAcceptance.acceptedSpecRef,
365
+ specAcceptanceStatus: specAcceptance.specAcceptanceStatus,
366
+ specHash: specAcceptance.specHash,
367
+ specContractHash: specAcceptance.specContractHash,
368
+ artifactRefs,
369
+ collaborationContractRef,
370
+ lifecycleRiskProjectionRef,
371
+ adjudicationProjectionRef,
372
+ stageRunProjectionRef,
373
+ workflowHandoffProjectionRef,
374
+ reasons: specAcceptance.reasons
375
+ };
376
+ const enrichedAdjudication = { ...adjudication, closureRefs };
377
+ await recordLifecycleRiskDecisionProjection(projectRoot, decision);
378
+ await recordStageRunProjection(projectRoot, stageRun);
379
+ if (handoff) {
380
+ await recordWorkflowHandoffProjection(projectRoot, handoff);
381
+ }
382
+ const projection = await recordSpecCollaborationAdjudicationProjection(projectRoot, enrichedAdjudication);
383
+ return {
384
+ runId: run.runId,
385
+ branch: context.rawBranch,
386
+ profile,
387
+ workOrder,
388
+ coordination,
389
+ candidate,
390
+ reviews,
391
+ capabilityFindings,
392
+ closureRequest,
393
+ adjudication: enrichedAdjudication,
394
+ projectionRef: { kind: 'projection', ref: `${projection.envelope.projectionType}:${projection.envelope.scopeKey}` },
395
+ artifactRefs,
396
+ registeredArtifacts,
397
+ registeredCollaborationContracts,
398
+ acceptedSpecRef: specAcceptance.acceptedSpecRef
399
+ };
400
+ }
401
+ export async function reconcilePlanCollaborationClosure(projectRoot, input) {
402
+ const generatedAt = input.generatedAt ?? new Date().toISOString();
403
+ const context = await resolveSddContext(projectRoot, { branch: input.decision.scope.branch, branchSource: 'cli_option' });
404
+ const decision = {
405
+ ...input.decision,
406
+ scope: { ...input.decision.scope, branch: context.rawBranch }
407
+ };
408
+ const run = await createRun(projectRoot, {
409
+ runId: input.runId,
410
+ branch: context.rawBranch,
411
+ lifecycleDecision: runLifecycleDecisionRecord(decision)
412
+ });
413
+ const runRef = { kind: 'run', ref: run.runId };
414
+ const planRequired = decision.requiredStages.includes('plan');
415
+ const blocked = decision.profile === 'blocked' || decision.approvalPolicy === 'blocked' || decision.blockedStages.includes('plan');
416
+ const acceptedSpecHandoffEnvelope = await readWorkflowHandoffProjection(projectRoot, decision.scope, 'spec', 'plan');
417
+ const workOrderInputRefs = acceptedSpecHandoffEnvelope?.payload.requiredInputRefs ?? decision.inputRefs;
418
+ const profileRef = { kind: 'projection', ref: `${PLAN_COLLABORATION_ADJUDICATION_PROJECTION_TYPE}:${planCollaborationScopeKey(decision.scope)}:profile` };
419
+ const workOrder = planRequired && !blocked ? buildPlanStageWorkOrder(decision.scope, profileRef, workOrderInputRefs, generatedAt) : null;
420
+ const registeredCollaborationContracts = await registerStageCollaborationContracts(projectRoot, context.rawBranch, 'plan', null, input.collaborationContractRefs, generatedAt);
421
+ const validatedCollaborationContract = latestValidatedStageCollaborationContract(registeredCollaborationContracts);
422
+ const collaborationContractRef = validatedCollaborationContract ? runtimeRefForStageCollaborationContract(validatedCollaborationContract) : null;
423
+ const registeredArtifacts = await registerBranchStageArtifacts(projectRoot, context.rawBranch, 'plan', input.artifactRefs, false, run.runId, generatedAt);
424
+ const closureRequest = await buildOutputCenteredPlanClosureRequest(projectRoot, decision.scope, workOrder, context.partition, acceptedSpecHandoffEnvelope?.payload ?? null, input.outputCloseRequest, generatedAt);
425
+ const artifactRefs = registeredArtifacts.map(runtimeRefForStageArtifact);
426
+ const lifecycleRiskProjectionRef = {
427
+ kind: 'projection',
428
+ ref: `${LIFECYCLE_RISK_DECISION_PROJECTION_TYPE}:${lifecycleRiskDecisionScopeKey(decision.scope)}`
429
+ };
430
+ const adjudicationProjectionRef = planAdjudicationProjectionRef(decision.scope);
431
+ const planAcceptance = await validateAcceptedPlanArtifact(projectRoot, {
432
+ partition: context.partition,
433
+ required: planRequired,
434
+ blocked,
435
+ registeredArtifacts,
436
+ registeredCollaborationContracts,
437
+ acceptedSpecHandoff: acceptedSpecHandoffEnvelope?.payload ?? null,
438
+ closureRequest,
439
+ });
440
+ const health = planAcceptance.planAcceptanceStatus === 'accepted'
441
+ ? 'ready_for_tasks'
442
+ : !planRequired
443
+ ? 'no-op'
444
+ : blocked
445
+ ? 'blocked'
446
+ : 'rejected';
447
+ const outputRefs = [planAcceptance.acceptedPlanRef, adjudicationProjectionRef, collaborationContractRef, ...artifactRefs].filter((ref) => ref !== null);
448
+ const stageRun = buildPlanClosureStageRun(decision.scope, run.runId, workOrder, health, {
449
+ outputRefs,
450
+ decisionRefs: [lifecycleRiskProjectionRef, adjudicationProjectionRef],
451
+ inputRefs: acceptedSpecHandoffEnvelope?.payload.requiredInputRefs ?? decision.inputRefs,
452
+ generatedAt,
453
+ rejectionReason: planAcceptance.rejectionIssue?.explanation ?? null
454
+ });
455
+ const stageRunProjectionRef = {
456
+ kind: 'projection',
457
+ ref: `${STAGE_RUN_PROJECTION_TYPE}:${stageRunScopeKey(stageRun.scope, stageRun.stage)}`
458
+ };
459
+ const acceptedSpecRef = acceptedSpecRefFromHandoff(context.partition, acceptedSpecHandoffEnvelope?.payload ?? null);
460
+ const handoffOutputRefs = [planAcceptance.acceptedPlanRef].filter((ref) => ref !== null);
461
+ const handoffRequiredInputRefs = [acceptedSpecRef, planAcceptance.acceptedPlanRef].filter((ref) => ref !== null);
462
+ const handoff = planAcceptance.planAcceptanceStatus === 'accepted'
463
+ ? buildPlanWorkflowHandoff(decision.scope, decision, {
464
+ outputRefs: handoffOutputRefs,
465
+ requiredInputRefs: handoffRequiredInputRefs,
466
+ evidenceRefs: [],
467
+ riskDecisionRef: lifecycleRiskProjectionRef,
468
+ generatedAt
469
+ })
470
+ : null;
471
+ const workflowHandoffProjectionRef = handoff
472
+ ? { kind: 'projection', ref: `${WORKFLOW_HANDOFF_PROJECTION_TYPE}:${workflowHandoffScopeKey(handoff.scope, handoff.fromStage, handoff.toStage)}` }
473
+ : undefined;
474
+ const rejection = planAcceptance.rejectionIssue
475
+ ? buildStageRejection('plan', decision.scope, null, planAcceptance.rejectionIssue.reasonCode, planAcceptance.rejectionIssue.explanation, planAcceptance.rejectionIssue.requiredNextAction, planAcceptance.rejectionIssue.fallbackRoute, generatedAt, true)
476
+ : null;
477
+ const closureRefs = {
478
+ runRef,
479
+ acceptedPlanRef: planAcceptance.acceptedPlanRef,
480
+ planAcceptanceStatus: planAcceptance.planAcceptanceStatus,
481
+ planHash: planAcceptance.planHash,
482
+ planContractHash: planAcceptance.planContractHash,
483
+ acceptedSpecHandoffRef: acceptedSpecHandoffEnvelope ? { kind: 'projection', ref: `${WORKFLOW_HANDOFF_PROJECTION_TYPE}:${workflowHandoffScopeKey(decision.scope, 'spec', 'plan')}` } : null,
484
+ artifactRefs,
485
+ collaborationContractRef,
486
+ lifecycleRiskProjectionRef,
487
+ adjudicationProjectionRef,
488
+ stageRunProjectionRef,
489
+ workflowHandoffProjectionRef,
490
+ reasons: planAcceptance.reasons
491
+ };
492
+ const adjudication = {
493
+ contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
494
+ adjudicationId: stableId('plan-adjudication', decision.scope, health, generatedAt),
495
+ stage: 'plan',
496
+ scope: decision.scope,
497
+ health,
498
+ stageDecision: {
499
+ contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
500
+ decisionId: stableId('plan-stage-decision', decision.scope, run.runId, generatedAt),
501
+ stage: 'plan',
502
+ scope: decision.scope,
503
+ status: health === 'ready_for_tasks' ? 'completed' : health === 'no-op' ? 'skipped' : health === 'blocked' ? 'blocked' : 'rejected',
504
+ health,
505
+ acceptedDecisionRefs: [planAcceptance.acceptedPlanRef, adjudicationProjectionRef].filter((ref) => ref !== null),
506
+ advisoryRefs: [],
507
+ capabilityRefs: artifactRefs.filter((ref) => ref.ref.includes('context') || ref.ref.includes('capability')),
508
+ blockingReasons: health === 'ready_for_tasks' || health === 'no-op' ? [] : planAcceptance.reasons,
509
+ createdAt: generatedAt
510
+ },
511
+ handoffPacket: handoff,
512
+ rejection,
513
+ nextActions: buildStageRuntimeNextActions('plan', decision.scope, generatedAt, health === 'ready_for_tasks' ? ['stage_ready', 'handoff_ready'] : rejection ? [rejectionReasonToNextActionReason(rejection.reasonCode)] : ['report_only']),
514
+ closureRefs,
515
+ createdAt: generatedAt
516
+ };
517
+ await recordLifecycleRiskDecisionProjection(projectRoot, decision);
518
+ await recordStageRunProjection(projectRoot, stageRun);
519
+ if (handoff) {
520
+ await recordWorkflowHandoffProjection(projectRoot, handoff);
521
+ }
522
+ const projection = await recordPlanCollaborationAdjudicationProjection(projectRoot, adjudication);
523
+ return {
524
+ runId: run.runId,
525
+ branch: context.rawBranch,
526
+ workOrder,
527
+ adjudication,
528
+ projectionRef: { kind: 'projection', ref: `${projection.envelope.projectionType}:${projection.envelope.scopeKey}` },
529
+ artifactRefs,
530
+ registeredArtifacts,
531
+ registeredCollaborationContracts,
532
+ acceptedPlanRef: planAcceptance.acceptedPlanRef
533
+ };
534
+ }
535
+ export async function reconcileTasksCollaborationClosure(projectRoot, input) {
536
+ const generatedAt = input.generatedAt ?? new Date().toISOString();
537
+ const context = await resolveSddContext(projectRoot, { branch: input.decision.scope.branch, branchSource: 'cli_option' });
538
+ const decision = {
539
+ ...input.decision,
540
+ scope: { ...input.decision.scope, branch: context.rawBranch }
541
+ };
542
+ const run = await createRun(projectRoot, {
543
+ runId: input.runId,
544
+ branch: context.rawBranch,
545
+ lifecycleDecision: runLifecycleDecisionRecord(decision)
546
+ });
547
+ const runRef = { kind: 'run', ref: run.runId };
548
+ const tasksRequired = decision.requiredStages.includes('tasks');
549
+ const blocked = decision.profile === 'blocked' || decision.approvalPolicy === 'blocked' || decision.blockedStages.includes('tasks');
550
+ const acceptedPlanHandoffEnvelope = await readWorkflowHandoffProjection(projectRoot, decision.scope, 'plan', 'tasks');
551
+ const workOrderInputRefs = acceptedPlanHandoffEnvelope?.payload.requiredInputRefs ?? decision.inputRefs;
552
+ const profileRef = { kind: 'projection', ref: `${TASKS_COLLABORATION_ADJUDICATION_PROJECTION_TYPE}:${tasksCollaborationScopeKey(decision.scope)}:profile` };
553
+ const workOrder = tasksRequired && !blocked ? buildTasksStageWorkOrder(decision.scope, profileRef, workOrderInputRefs, generatedAt) : null;
554
+ const registeredCollaborationContracts = await registerStageCollaborationContracts(projectRoot, context.rawBranch, 'tasks', null, input.collaborationContractRefs, generatedAt);
555
+ const validatedCollaborationContract = latestValidatedStageCollaborationContract(registeredCollaborationContracts);
556
+ const collaborationContractRef = validatedCollaborationContract ? runtimeRefForStageCollaborationContract(validatedCollaborationContract) : null;
557
+ const registeredArtifacts = await registerBranchStageArtifacts(projectRoot, context.rawBranch, 'tasks', input.artifactRefs, false, run.runId, generatedAt);
558
+ const closureRequest = await buildOutputCenteredTasksClosureRequest(projectRoot, decision.scope, workOrder, context.partition, acceptedPlanHandoffEnvelope?.payload ?? null, input.outputCloseRequest, generatedAt);
559
+ const artifactRefs = registeredArtifacts.map(runtimeRefForStageArtifact);
560
+ const lifecycleRiskProjectionRef = {
561
+ kind: 'projection',
562
+ ref: `${LIFECYCLE_RISK_DECISION_PROJECTION_TYPE}:${lifecycleRiskDecisionScopeKey(decision.scope)}`
563
+ };
564
+ const adjudicationProjectionRef = tasksAdjudicationProjectionRef(decision.scope);
565
+ const tasksAcceptance = await validateAcceptedTasksArtifact(projectRoot, {
566
+ partition: context.partition,
567
+ required: tasksRequired,
568
+ blocked,
569
+ registeredArtifacts,
570
+ registeredCollaborationContracts,
571
+ acceptedPlanHandoff: acceptedPlanHandoffEnvelope?.payload ?? null,
572
+ closureRequest
573
+ });
574
+ const health = tasksAcceptance.tasksAcceptanceStatus === 'accepted'
575
+ ? 'ready_for_execute'
576
+ : !tasksRequired
577
+ ? 'no-op'
578
+ : blocked
579
+ ? 'blocked'
580
+ : 'rejected';
581
+ const outputRefs = [tasksAcceptance.acceptedTasksRef, adjudicationProjectionRef, collaborationContractRef, ...artifactRefs].filter((ref) => ref !== null);
582
+ const stageRun = buildTasksClosureStageRun(decision.scope, run.runId, workOrder, health, {
583
+ outputRefs,
584
+ decisionRefs: [lifecycleRiskProjectionRef, adjudicationProjectionRef],
585
+ inputRefs: acceptedPlanHandoffEnvelope?.payload.requiredInputRefs ?? decision.inputRefs,
586
+ generatedAt,
587
+ rejectionReason: tasksAcceptance.rejectionIssue?.explanation ?? null
588
+ });
589
+ const stageRunProjectionRef = {
590
+ kind: 'projection',
591
+ ref: `${STAGE_RUN_PROJECTION_TYPE}:${stageRunScopeKey(stageRun.scope, stageRun.stage)}`
592
+ };
593
+ const handoff = tasksAcceptance.tasksAcceptanceStatus === 'accepted'
594
+ ? buildTasksWorkflowHandoff(decision.scope, decision, {
595
+ outputRefs,
596
+ requiredInputRefs: tasksAcceptance.acceptedTasksRef ? [tasksAcceptance.acceptedTasksRef] : [],
597
+ evidenceRefs: artifactRefs,
598
+ riskDecisionRef: lifecycleRiskProjectionRef,
599
+ generatedAt
600
+ })
601
+ : null;
602
+ const workflowHandoffProjectionRef = handoff
603
+ ? { kind: 'projection', ref: `${WORKFLOW_HANDOFF_PROJECTION_TYPE}:${workflowHandoffScopeKey(handoff.scope, handoff.fromStage, handoff.toStage)}` }
604
+ : undefined;
605
+ const rejection = tasksAcceptance.rejectionIssue
606
+ ? buildStageRejection('tasks', decision.scope, null, tasksAcceptance.rejectionIssue.reasonCode, tasksAcceptance.rejectionIssue.explanation, tasksAcceptance.rejectionIssue.requiredNextAction, tasksAcceptance.rejectionIssue.fallbackRoute, generatedAt, true)
607
+ : null;
608
+ const closureRefs = {
609
+ runRef,
610
+ acceptedTasksRef: tasksAcceptance.acceptedTasksRef,
611
+ tasksAcceptanceStatus: tasksAcceptance.tasksAcceptanceStatus,
612
+ tasksHash: tasksAcceptance.tasksHash,
613
+ tasksContractHash: tasksAcceptance.tasksContractHash,
614
+ acceptedPlanHandoffRef: acceptedPlanHandoffEnvelope ? { kind: 'projection', ref: `${WORKFLOW_HANDOFF_PROJECTION_TYPE}:${workflowHandoffScopeKey(decision.scope, 'plan', 'tasks')}` } : null,
615
+ artifactRefs,
616
+ collaborationContractRef,
617
+ lifecycleRiskProjectionRef,
618
+ adjudicationProjectionRef,
619
+ stageRunProjectionRef,
620
+ workflowHandoffProjectionRef,
621
+ reasons: tasksAcceptance.reasons
622
+ };
623
+ const adjudication = {
624
+ contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
625
+ adjudicationId: stableId('tasks-adjudication', decision.scope, health, generatedAt),
626
+ stage: 'tasks',
627
+ scope: decision.scope,
628
+ health,
629
+ stageDecision: {
630
+ contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
631
+ decisionId: stableId('tasks-stage-decision', decision.scope, run.runId, generatedAt),
632
+ stage: 'tasks',
633
+ scope: decision.scope,
634
+ status: health === 'ready_for_execute' ? 'completed' : health === 'no-op' ? 'skipped' : health === 'blocked' ? 'blocked' : 'rejected',
635
+ health,
636
+ acceptedDecisionRefs: outputRefs,
637
+ advisoryRefs: [],
638
+ capabilityRefs: artifactRefs.filter((ref) => ref.ref.includes('context') || ref.ref.includes('capability')),
639
+ blockingReasons: health === 'ready_for_execute' || health === 'no-op' ? [] : tasksAcceptance.reasons,
640
+ createdAt: generatedAt
641
+ },
642
+ handoffPacket: handoff,
643
+ rejection,
644
+ nextActions: buildStageRuntimeNextActions('tasks', decision.scope, generatedAt, health === 'ready_for_execute' ? ['stage_ready', 'handoff_ready'] : rejection ? [rejectionReasonToNextActionReason(rejection.reasonCode)] : ['report_only']),
645
+ closureRefs,
646
+ createdAt: generatedAt
647
+ };
648
+ await recordLifecycleRiskDecisionProjection(projectRoot, decision);
649
+ await recordStageRunProjection(projectRoot, stageRun);
650
+ if (handoff) {
651
+ await recordWorkflowHandoffProjection(projectRoot, handoff);
652
+ }
653
+ const projection = await recordTasksCollaborationAdjudicationProjection(projectRoot, adjudication);
654
+ return {
655
+ runId: run.runId,
656
+ branch: context.rawBranch,
657
+ workOrder,
658
+ adjudication,
659
+ projectionRef: { kind: 'projection', ref: `${projection.envelope.projectionType}:${projection.envelope.scopeKey}` },
660
+ artifactRefs,
661
+ registeredArtifacts,
662
+ registeredCollaborationContracts,
663
+ acceptedTasksRef: tasksAcceptance.acceptedTasksRef
664
+ };
665
+ }
666
+ export async function reconcileExecuteCollaborationClosure(projectRoot, input) {
667
+ const generatedAt = input.generatedAt ?? new Date().toISOString();
668
+ const context = await resolveSddContext(projectRoot, { branch: input.decision.scope.branch, branchSource: 'cli_option' });
669
+ const decision = {
670
+ ...input.decision,
671
+ scope: { ...input.decision.scope, branch: context.rawBranch }
672
+ };
673
+ const run = await createRun(projectRoot, {
674
+ runId: input.runId,
675
+ branch: context.rawBranch,
676
+ lifecycleDecision: runLifecycleDecisionRecord(decision)
677
+ });
678
+ const runRef = { kind: 'run', ref: run.runId };
679
+ const executeRequired = decision.requiredStages.includes('execute');
680
+ const blocked = decision.profile === 'blocked' || decision.approvalPolicy === 'blocked' || decision.blockedStages.includes('execute');
681
+ const acceptedTasksHandoffEnvelope = await readWorkflowHandoffProjection(projectRoot, decision.scope, 'tasks', 'execute');
682
+ const workOrderInputRefs = acceptedTasksHandoffEnvelope?.payload.requiredInputRefs ?? decision.inputRefs;
683
+ const profileRef = { kind: 'projection', ref: `${EXECUTE_COLLABORATION_ADJUDICATION_PROJECTION_TYPE}:${executeCollaborationScopeKey(decision.scope)}:profile` };
684
+ const workOrder = executeRequired && !blocked ? buildExecuteStageWorkOrder(decision.scope, profileRef, workOrderInputRefs, generatedAt) : null;
685
+ const registeredCollaborationContracts = await registerStageCollaborationContracts(projectRoot, context.rawBranch, 'execute', workOrder, input.collaborationContractRefs, generatedAt);
686
+ const validatedCollaborationContract = latestValidatedStageCollaborationContract(registeredCollaborationContracts);
687
+ const collaborationContractRef = validatedCollaborationContract ? runtimeRefForStageCollaborationContract(validatedCollaborationContract) : null;
688
+ const registeredArtifacts = await registerBranchStageArtifacts(projectRoot, context.rawBranch, 'execute', input.artifactRefs, Boolean(workOrder), run.runId, generatedAt);
689
+ const artifactRefs = registeredArtifacts.map(runtimeRefForStageArtifact);
690
+ const lifecycleRiskProjectionRef = {
691
+ kind: 'projection',
692
+ ref: `${LIFECYCLE_RISK_DECISION_PROJECTION_TYPE}:${lifecycleRiskDecisionScopeKey(decision.scope)}`
693
+ };
694
+ const adjudicationProjectionRef = executeAdjudicationProjectionRef(decision.scope);
695
+ const executeAcceptance = await validateAcceptedExecuteArtifact(projectRoot, {
696
+ partition: context.partition,
697
+ required: executeRequired,
698
+ blocked,
699
+ registeredArtifacts,
700
+ registeredCollaborationContracts,
701
+ acceptedTasksHandoff: acceptedTasksHandoffEnvelope?.payload ?? null
702
+ });
703
+ const health = executeAcceptance.executeAcceptanceStatus === 'accepted'
704
+ ? 'ready_for_ship'
705
+ : !executeRequired
706
+ ? 'no-op'
707
+ : blocked
708
+ ? 'blocked'
709
+ : 'rejected';
710
+ const acceptedEvidenceJudgmentArtifact = latestStageArtifact(registeredArtifacts, 'evidence_judgment');
711
+ const acceptedEvidenceJudgmentRef = executeAcceptance.executeAcceptanceStatus === 'accepted' && acceptedEvidenceJudgmentArtifact
712
+ ? { kind: 'artifact', ref: acceptedEvidenceJudgmentArtifact.ref, hash: acceptedEvidenceJudgmentArtifact.hash }
713
+ : null;
714
+ const truthAlignmentProjection = acceptedEvidenceJudgmentRef
715
+ ? buildTruthAlignmentProjection(decision.scope, {
716
+ acceptedEvidenceJudgmentRef: acceptedEvidenceJudgmentRef,
717
+ declaredTruthRefs: await collectTruthAlignmentDeclaredTruthRefs(projectRoot, context.rawBranch, acceptedTasksHandoffEnvelope?.payload ?? null),
718
+ artifactRefs,
719
+ status: 'aligned',
720
+ ownerStage: null,
721
+ semanticImpact: 'none',
722
+ staleRefs: [],
723
+ invalidatesStages: [],
724
+ reasons: ['Accepted execute evidence judgment is structurally aligned with declared upstream truth refs.'],
725
+ generatedAt
726
+ })
727
+ : null;
728
+ const truthAlignmentProjectionRef = truthAlignmentProjection
729
+ ? { kind: 'projection', ref: `${TRUTH_ALIGNMENT_PROJECTION_TYPE}:${truthAlignmentScopeKey({ branch: decision.scope.branch })}` }
730
+ : null;
731
+ const outputRefs = [executeAcceptance.acceptedExecuteRef, truthAlignmentProjectionRef, adjudicationProjectionRef, collaborationContractRef, ...executeAcceptance.laneEvidenceRefs, ...artifactRefs].filter((ref) => ref !== null);
732
+ const stageRun = buildExecuteClosureStageRun(decision.scope, run.runId, workOrder, health, {
733
+ outputRefs,
734
+ decisionRefs: [lifecycleRiskProjectionRef, adjudicationProjectionRef],
735
+ inputRefs: acceptedTasksHandoffEnvelope?.payload.requiredInputRefs ?? decision.inputRefs,
736
+ generatedAt,
737
+ rejectionReason: executeAcceptance.rejectionIssue?.explanation ?? null
738
+ });
739
+ const stageRunProjectionRef = {
740
+ kind: 'projection',
741
+ ref: `${STAGE_RUN_PROJECTION_TYPE}:${stageRunScopeKey(stageRun.scope, stageRun.stage)}`
742
+ };
743
+ const handoff = executeAcceptance.executeAcceptanceStatus === 'accepted'
744
+ ? buildExecuteWorkflowHandoff(decision.scope, decision, {
745
+ outputRefs,
746
+ requiredInputRefs: executeAcceptance.acceptedExecuteRef ? [executeAcceptance.acceptedExecuteRef, ...executeAcceptance.laneEvidenceRefs] : executeAcceptance.laneEvidenceRefs,
747
+ evidenceRefs: artifactRefs,
748
+ riskDecisionRef: lifecycleRiskProjectionRef,
749
+ generatedAt
750
+ })
751
+ : null;
752
+ const workflowHandoffProjectionRef = handoff
753
+ ? { kind: 'projection', ref: `${WORKFLOW_HANDOFF_PROJECTION_TYPE}:${workflowHandoffScopeKey(handoff.scope, handoff.fromStage, handoff.toStage)}` }
754
+ : undefined;
755
+ const rejection = executeAcceptance.rejectionIssue
756
+ ? buildStageRejection('execute', decision.scope, null, executeAcceptance.rejectionIssue.reasonCode, executeAcceptance.rejectionIssue.explanation, executeAcceptance.rejectionIssue.requiredNextAction, executeAcceptance.rejectionIssue.fallbackRoute, generatedAt, true)
757
+ : null;
758
+ const closureRefs = {
759
+ runRef,
760
+ acceptedExecuteRef: executeAcceptance.acceptedExecuteRef,
761
+ executeAcceptanceStatus: executeAcceptance.executeAcceptanceStatus,
762
+ acceptedTasksHandoffRef: acceptedTasksHandoffEnvelope ? { kind: 'projection', ref: `${WORKFLOW_HANDOFF_PROJECTION_TYPE}:${workflowHandoffScopeKey(decision.scope, 'tasks', 'execute')}` } : null,
763
+ truthAlignmentProjectionRef,
764
+ laneEvidenceRefs: executeAcceptance.laneEvidenceRefs,
765
+ artifactRefs,
766
+ collaborationContractRef,
767
+ lifecycleRiskProjectionRef,
768
+ adjudicationProjectionRef,
769
+ stageRunProjectionRef,
770
+ workflowHandoffProjectionRef,
771
+ reasons: executeAcceptance.reasons
772
+ };
773
+ const adjudication = {
774
+ contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
775
+ adjudicationId: stableId('execute-adjudication', decision.scope, health, generatedAt),
776
+ stage: 'execute',
777
+ scope: decision.scope,
778
+ health,
779
+ stageDecision: {
780
+ contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
781
+ decisionId: stableId('execute-stage-decision', decision.scope, run.runId, generatedAt),
782
+ stage: 'execute',
783
+ scope: decision.scope,
784
+ status: health === 'ready_for_ship' ? 'completed' : health === 'no-op' ? 'skipped' : health === 'blocked' ? 'blocked' : 'rejected',
785
+ health,
786
+ acceptedDecisionRefs: outputRefs,
787
+ advisoryRefs: [],
788
+ capabilityRefs: executeAcceptance.laneEvidenceRefs,
789
+ blockingReasons: health === 'ready_for_ship' || health === 'no-op' ? [] : executeAcceptance.reasons,
790
+ createdAt: generatedAt
791
+ },
792
+ handoffPacket: handoff,
793
+ rejection,
794
+ nextActions: buildStageRuntimeNextActions('execute', decision.scope, generatedAt, health === 'ready_for_ship' ? ['stage_ready', 'handoff_ready'] : rejection ? [rejectionReasonToNextActionReason(rejection.reasonCode)] : ['report_only']),
795
+ closureRefs,
796
+ createdAt: generatedAt
797
+ };
798
+ await recordLifecycleRiskDecisionProjection(projectRoot, decision);
799
+ await recordStageRunProjection(projectRoot, stageRun);
800
+ if (truthAlignmentProjection) {
801
+ await recordTruthAlignmentProjection(projectRoot, truthAlignmentProjection);
802
+ }
803
+ if (handoff) {
804
+ await recordWorkflowHandoffProjection(projectRoot, handoff);
805
+ }
806
+ const projection = await recordExecuteCollaborationAdjudicationProjection(projectRoot, adjudication);
807
+ return {
808
+ runId: run.runId,
809
+ branch: context.rawBranch,
810
+ workOrder,
811
+ adjudication,
812
+ projectionRef: { kind: 'projection', ref: `${projection.envelope.projectionType}:${projection.envelope.scopeKey}` },
813
+ artifactRefs,
814
+ registeredArtifacts,
815
+ registeredCollaborationContracts,
816
+ acceptedExecuteRef: executeAcceptance.acceptedExecuteRef
817
+ };
818
+ }
819
+ export async function reconcileShipCollaborationClosure(projectRoot, input) {
820
+ const generatedAt = input.generatedAt ?? new Date().toISOString();
821
+ const context = await resolveSddContext(projectRoot, { branch: input.decision.scope.branch, branchSource: 'cli_option' });
822
+ const decision = {
823
+ ...input.decision,
824
+ scope: { ...input.decision.scope, branch: context.rawBranch }
825
+ };
826
+ const run = await createRun(projectRoot, {
827
+ runId: input.runId,
828
+ branch: context.rawBranch,
829
+ lifecycleDecision: runLifecycleDecisionRecord(decision)
830
+ });
831
+ const runRef = { kind: 'run', ref: run.runId };
832
+ const shipRequired = decision.requiredStages.includes('ship');
833
+ const openShipBlockers = shipBlockerCount(decision);
834
+ const blockedReason = openShipBlockers > 0
835
+ ? 'Lifecycle risk decision has open ship blockers.'
836
+ : 'Lifecycle risk decision blocks ship closure.';
837
+ const blocked = decision.profile === 'blocked' || decision.approvalPolicy === 'blocked' || decision.blockedStages.includes('ship') || openShipBlockers > 0;
838
+ const truthAlignmentEnvelope = await readTruthAlignmentProjection(projectRoot, decision.scope);
839
+ const workOrderInputRefs = truthAlignmentEnvelope?.payload.acceptedRealityRefs ?? decision.inputRefs;
840
+ const profileRef = { kind: 'projection', ref: `${SHIP_COLLABORATION_ADJUDICATION_PROJECTION_TYPE}:${shipCollaborationScopeKey(decision.scope)}:profile` };
841
+ const workOrder = shipRequired && !blocked ? buildShipStageWorkOrder(decision.scope, profileRef, workOrderInputRefs, generatedAt) : null;
842
+ const registeredCollaborationContracts = await registerStageCollaborationContracts(projectRoot, context.rawBranch, 'ship', workOrder, input.collaborationContractRefs, generatedAt);
843
+ const validatedCollaborationContract = latestValidatedStageCollaborationContract(registeredCollaborationContracts);
844
+ const collaborationContractRef = validatedCollaborationContract ? runtimeRefForStageCollaborationContract(validatedCollaborationContract) : null;
845
+ const registeredArtifacts = await registerBranchStageArtifacts(projectRoot, context.rawBranch, 'ship', input.artifactRefs, Boolean(workOrder), run.runId, generatedAt);
846
+ const artifactRefs = registeredArtifacts.map(runtimeRefForStageArtifact);
847
+ const lifecycleRiskProjectionRef = {
848
+ kind: 'projection',
849
+ ref: `${LIFECYCLE_RISK_DECISION_PROJECTION_TYPE}:${lifecycleRiskDecisionScopeKey(decision.scope)}`
850
+ };
851
+ const adjudicationProjectionRef = shipAdjudicationProjectionRef(decision.scope);
852
+ const shipReadiness = await validateAcceptedShipReadinessArtifact(projectRoot, {
853
+ partition: context.partition,
854
+ required: shipRequired,
855
+ blocked,
856
+ blockedReason,
857
+ registeredArtifacts,
858
+ registeredCollaborationContracts,
859
+ truthAlignment: truthAlignmentEnvelope?.payload ?? null
860
+ });
861
+ const health = shipReadiness.shipReadinessStatus === 'accepted'
862
+ ? 'ship_ready'
863
+ : !shipRequired
864
+ ? 'no-op'
865
+ : blocked
866
+ ? 'blocked'
867
+ : 'rejected';
868
+ const truthAlignmentProjectionRef = truthAlignmentEnvelope ? { kind: 'projection', ref: `${TRUTH_ALIGNMENT_PROJECTION_TYPE}:${truthAlignmentScopeKey({ branch: decision.scope.branch })}` } : null;
869
+ const outputRefs = [shipReadiness.acceptedShipReadinessRef, shipReadiness.releaseDocumentRef, truthAlignmentProjectionRef, adjudicationProjectionRef, collaborationContractRef, ...artifactRefs].filter((ref) => ref !== null);
870
+ const stageRun = buildShipClosureStageRun(decision.scope, run.runId, workOrder, health, {
871
+ outputRefs,
872
+ decisionRefs: [lifecycleRiskProjectionRef, adjudicationProjectionRef],
873
+ inputRefs: truthAlignmentEnvelope?.payload.acceptedRealityRefs ?? decision.inputRefs,
874
+ generatedAt,
875
+ rejectionReason: shipReadiness.rejectionIssue?.explanation ?? null
876
+ });
877
+ const stageRunProjectionRef = {
878
+ kind: 'projection',
879
+ ref: `${STAGE_RUN_PROJECTION_TYPE}:${stageRunScopeKey(stageRun.scope, stageRun.stage)}`
880
+ };
881
+ const rejection = shipReadiness.rejectionIssue
882
+ ? buildStageRejection('ship', decision.scope, null, shipReadiness.rejectionIssue.reasonCode, shipReadiness.rejectionIssue.explanation, shipReadiness.rejectionIssue.requiredNextAction, shipReadiness.rejectionIssue.fallbackRoute, generatedAt, true)
883
+ : null;
884
+ const closureRefs = {
885
+ runRef,
886
+ acceptedShipReadinessRef: shipReadiness.acceptedShipReadinessRef,
887
+ shipReadinessStatus: shipReadiness.shipReadinessStatus,
888
+ shipReadinessHash: shipReadiness.shipReadinessHash,
889
+ releaseDocumentRef: shipReadiness.releaseDocumentRef,
890
+ truthAlignmentProjectionRef,
891
+ artifactRefs,
892
+ collaborationContractRef,
893
+ lifecycleRiskProjectionRef,
894
+ adjudicationProjectionRef,
895
+ stageRunProjectionRef,
896
+ reasons: shipReadiness.reasons
897
+ };
898
+ const adjudication = {
899
+ contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
900
+ adjudicationId: stableId('ship-adjudication', decision.scope, health, generatedAt),
901
+ stage: 'ship',
902
+ scope: decision.scope,
903
+ health,
904
+ stageDecision: {
905
+ contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
906
+ decisionId: stableId('ship-stage-decision', decision.scope, run.runId, generatedAt),
907
+ stage: 'ship',
908
+ scope: decision.scope,
909
+ status: health === 'ship_ready' ? 'completed' : health === 'no-op' ? 'skipped' : health === 'blocked' ? 'blocked' : 'rejected',
910
+ health,
911
+ acceptedDecisionRefs: outputRefs,
912
+ advisoryRefs: [],
913
+ capabilityRefs: artifactRefs.filter((ref) => ref.ref.includes('ship') || ref.ref.includes('release') || ref.ref.includes('capability')),
914
+ blockingReasons: health === 'ship_ready' || health === 'no-op' ? [] : shipReadiness.reasons,
915
+ createdAt: generatedAt
916
+ },
917
+ handoffPacket: null,
918
+ rejection,
919
+ nextActions: buildStageRuntimeNextActions('ship', decision.scope, generatedAt, health === 'ship_ready' ? ['stage_ready'] : rejection ? [rejectionReasonToNextActionReason(rejection.reasonCode)] : ['report_only']),
920
+ closureRefs,
921
+ createdAt: generatedAt
922
+ };
923
+ await recordLifecycleRiskDecisionProjection(projectRoot, decision);
924
+ await recordStageRunProjection(projectRoot, stageRun);
925
+ const projection = await recordShipCollaborationAdjudicationProjection(projectRoot, adjudication);
926
+ return {
927
+ runId: run.runId,
928
+ branch: context.rawBranch,
929
+ workOrder,
930
+ adjudication,
931
+ projectionRef: { kind: 'projection', ref: `${projection.envelope.projectionType}:${projection.envelope.scopeKey}` },
932
+ artifactRefs,
933
+ registeredArtifacts,
934
+ registeredCollaborationContracts,
935
+ acceptedShipReadinessRef: shipReadiness.acceptedShipReadinessRef,
936
+ releaseDocumentRef: shipReadiness.releaseDocumentRef
937
+ };
938
+ }
939
+ const FULL_STAGE_CHAIN = [
940
+ { stage: 'spec', expectedHealth: 'ready_for_plan', projectionType: SPEC_COLLABORATION_ADJUDICATION_PROJECTION_TYPE },
941
+ { stage: 'plan', expectedHealth: 'ready_for_tasks', projectionType: PLAN_COLLABORATION_ADJUDICATION_PROJECTION_TYPE },
942
+ { stage: 'tasks', expectedHealth: 'ready_for_execute', projectionType: TASKS_COLLABORATION_ADJUDICATION_PROJECTION_TYPE },
943
+ { stage: 'execute', expectedHealth: 'ready_for_ship', projectionType: EXECUTE_COLLABORATION_ADJUDICATION_PROJECTION_TYPE },
944
+ { stage: 'ship', expectedHealth: 'ship_ready', projectionType: SHIP_COLLABORATION_ADJUDICATION_PROJECTION_TYPE }
945
+ ];
946
+ export async function inspectFullStageChain(projectRoot, branch) {
947
+ const projections = await listRuntimeProjections(projectRoot, FULL_STAGE_CHAIN.map((item) => item.projectionType));
948
+ const envelopes = projections
949
+ .map((projection) => projection.payload)
950
+ .filter((envelope) => envelope?.payload?.scope?.branch === branch);
951
+ const stages = FULL_STAGE_CHAIN.map((item) => fullStageChainStage(item, latestStageChainEnvelope(envelopes, item.projectionType, item.stage)));
952
+ const completedStages = stages.filter((stage) => stage.status === 'completed' && stage.health === stage.expectedHealth).length;
953
+ const validatedCollaborationContracts = stages.filter((stage) => stage.collaborationContractStatus === 'validated').length;
954
+ const handoffs = stages.filter((stage) => stage.handoffProjectionRef !== null).length;
955
+ const status = fullStageChainStatus(stages);
956
+ return {
957
+ contract: 'sdd-full-stage-chain-diagnostic-v1',
958
+ branch,
959
+ status,
960
+ stages,
961
+ projectionCounts: {
962
+ adjudications: stages.filter((stage) => stage.projectionRef !== null).length,
963
+ completedStages,
964
+ validatedCollaborationContracts,
965
+ handoffs
966
+ },
967
+ finalHealth: fullStageChainFinalHealth(stages),
968
+ reasons: fullStageChainReasons(status, stages)
969
+ };
970
+ }
971
+ async function collectTruthAlignmentDeclaredTruthRefs(projectRoot, branch, testHandoff) {
972
+ const projectionTypes = FULL_STAGE_CHAIN
973
+ .filter((item) => item.stage !== 'ship')
974
+ .map((item) => item.projectionType);
975
+ const projections = await listRuntimeProjections(projectRoot, projectionTypes);
976
+ const envelopes = projections
977
+ .map((projection) => projection.payload)
978
+ .filter((envelope) => envelope?.payload?.scope?.branch === branch);
979
+ const acceptedRefs = FULL_STAGE_CHAIN
980
+ .filter((item) => item.stage !== 'ship')
981
+ .map((item) => latestStageChainEnvelope(envelopes, item.projectionType, item.stage))
982
+ .map((envelope) => envelope ? acceptedRefFromAdjudication(envelope.payload.stage, envelope.payload) : null)
983
+ .filter((ref) => ref !== null);
984
+ return uniqueRuntimeRefs([...acceptedRefs, ...(testHandoff?.requiredInputRefs ?? [])]);
985
+ }
986
+ function latestStageChainEnvelope(envelopes, projectionType, stage) {
987
+ return envelopes
988
+ .filter((envelope) => envelope.projectionType === projectionType && envelope.payload.stage === stage)
989
+ .sort((left, right) => right.generatedAt.localeCompare(left.generatedAt))[0] ?? null;
990
+ }
991
+ function fullStageChainStage(item, envelope) {
992
+ const adjudication = envelope?.payload ?? null;
993
+ const closureRefs = (adjudication?.closureRefs ?? {});
994
+ const collaborationContractRef = runtimeRefValue(closureRefs.collaborationContractRef);
995
+ const handoff = adjudication?.handoffPacket ? {
996
+ fromStage: adjudication.handoffPacket.fromStage,
997
+ toStage: adjudication.handoffPacket.toStage,
998
+ status: adjudication.handoffPacket.status
999
+ } : null;
1000
+ const acceptedRef = adjudication ? acceptedRefFromAdjudication(item.stage, adjudication) : null;
1001
+ return {
1002
+ stage: item.stage,
1003
+ expectedHealth: item.expectedHealth,
1004
+ projectionType: item.projectionType,
1005
+ projectionRef: envelope ? { kind: 'projection', ref: `${envelope.projectionType}:${envelope.scopeKey}` } : null,
1006
+ health: adjudication?.health ?? null,
1007
+ status: adjudication?.stageDecision?.status ?? null,
1008
+ acceptedRef,
1009
+ acceptedHash: acceptedRef?.hash ?? null,
1010
+ collaborationContractRef,
1011
+ collaborationContractStatus: collaborationContractRef ? 'validated' : 'missing',
1012
+ handoff,
1013
+ handoffProjectionRef: runtimeRefValue(closureRefs.workflowHandoffProjectionRef),
1014
+ reasons: Array.isArray(closureRefs.reasons) ? closureRefs.reasons.filter((reason) => typeof reason === 'string') : adjudication?.rejection ? [adjudication.rejection.explanation] : []
1015
+ };
1016
+ }
1017
+ function acceptedRefFromAdjudication(stage, adjudication) {
1018
+ const closureRefs = (adjudication.closureRefs ?? {});
1019
+ const keyByStage = {
1020
+ spec: 'acceptedSpecRef',
1021
+ plan: 'acceptedPlanRef',
1022
+ tasks: 'acceptedTasksRef',
1023
+ execute: 'acceptedExecuteRef',
1024
+ ship: 'acceptedShipReadinessRef'
1025
+ };
1026
+ return runtimeRefValue(closureRefs[keyByStage[stage]]) ?? adjudication.stageDecision?.acceptedDecisionRefs[0] ?? null;
1027
+ }
1028
+ function runtimeRefValue(value) {
1029
+ if (!value || typeof value !== 'object') {
1030
+ return null;
1031
+ }
1032
+ const candidate = value;
1033
+ if (typeof candidate.kind !== 'string' || typeof candidate.ref !== 'string') {
1034
+ return null;
1035
+ }
1036
+ return {
1037
+ kind: candidate.kind,
1038
+ ref: candidate.ref,
1039
+ hash: typeof candidate.hash === 'string' ? candidate.hash : undefined
1040
+ };
1041
+ }
1042
+ function fullStageChainFinalHealth(stages) {
1043
+ return [...stages]
1044
+ .reverse()
1045
+ .find((stage) => stage.status === 'completed' && stage.health === stage.expectedHealth)?.health ?? null;
1046
+ }
1047
+ function fullStageChainStatus(stages) {
1048
+ if (stages.every((stage) => stage.projectionRef === null)) {
1049
+ return 'missing';
1050
+ }
1051
+ if (stages.some((stage) => stage.status === 'rejected' || stage.health === 'rejected')) {
1052
+ return 'rejected';
1053
+ }
1054
+ if (stages.some((stage) => stage.status === 'blocked' || stage.health === 'blocked')) {
1055
+ return 'blocked';
1056
+ }
1057
+ if (stages.every((stage) => stage.status === 'completed' && stage.health === stage.expectedHealth && stage.collaborationContractStatus === 'validated')) {
1058
+ return 'ready';
1059
+ }
1060
+ return 'partial';
1061
+ }
1062
+ function fullStageChainReasons(status, stages) {
1063
+ if (status === 'missing') {
1064
+ return ['No Phase 9 stage collaboration adjudication projections found.'];
1065
+ }
1066
+ if (status === 'ready') {
1067
+ return ['Active Phase 9 stage chain is closed through ship_ready with validated collaboration contracts.'];
1068
+ }
1069
+ return stages
1070
+ .filter((stage) => stage.status !== 'completed' || stage.health !== stage.expectedHealth || stage.collaborationContractStatus !== 'validated')
1071
+ .map((stage) => `${stage.stage} is ${stage.health ?? 'missing'}; expected ${stage.expectedHealth}; collaboration_contract=${stage.collaborationContractStatus}.`);
1072
+ }
1073
+ export async function inspectSpecCollaborationHealth(projectRoot, branch) {
1074
+ const projections = await listRuntimeProjections(projectRoot, [SPEC_COLLABORATION_ADJUDICATION_PROJECTION_TYPE]);
1075
+ const envelopes = projections
1076
+ .map((projection) => projection.payload)
1077
+ .filter((envelope) => envelope?.payload?.scope?.branch === branch);
1078
+ const latest = envelopes[0]?.payload ?? null;
1079
+ if (!latest) {
1080
+ return {
1081
+ status: 'missing',
1082
+ branch,
1083
+ latest: null,
1084
+ projectionCount: envelopes.length,
1085
+ latestClarificationGateId: null,
1086
+ latestHandoffId: null,
1087
+ latestRejectionReason: null,
1088
+ reasons: ['No spec collaboration adjudication projection found.']
1089
+ };
1090
+ }
1091
+ if (latest.contract !== STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION) {
1092
+ return {
1093
+ status: 'incompatible',
1094
+ branch,
1095
+ latest,
1096
+ projectionCount: envelopes.length,
1097
+ latestClarificationGateId: latest.clarificationGate?.gateId ?? null,
1098
+ latestHandoffId: latest.handoffPacket?.handoffId ?? null,
1099
+ latestRejectionReason: latest.rejection?.reasonCode ?? null,
1100
+ reasons: ['Latest spec collaboration projection has an incompatible contract.']
1101
+ };
1102
+ }
1103
+ return {
1104
+ status: latest.health,
1105
+ branch,
1106
+ latest,
1107
+ projectionCount: envelopes.length,
1108
+ latestClarificationGateId: latest.clarificationGate?.gateId ?? null,
1109
+ latestHandoffId: latest.handoffPacket?.handoffId ?? null,
1110
+ latestRejectionReason: latest.rejection?.reasonCode ?? null,
1111
+ reasons: specCollaborationDiagnosticReasons(latest)
1112
+ };
1113
+ }
1114
+ export function adjudicateSpecStageClosureRequest(input) {
1115
+ const generatedAt = input.generatedAt ?? new Date().toISOString();
1116
+ if (input.profile.intensity === 'blocked') {
1117
+ return rejected(input, 'blocked_lifecycle', 'Lifecycle risk decision blocks spec collaboration.', 'Resolve lifecycle blockers before retrying spec collaboration.', 'runtime-blocked', generatedAt, false);
1118
+ }
1119
+ if (!input.profile.required) {
1120
+ return noOpResult(input.profile, generatedAt);
1121
+ }
1122
+ const closureIssue = validateStageClosureRequest(input);
1123
+ if (closureIssue) {
1124
+ return rejected(input, closureIssue.reasonCode, closureIssue.explanation, closureIssue.requiredNextAction, closureIssue.fallbackRoute, generatedAt, closureIssue.retryAllowed);
1125
+ }
1126
+ const closureRequest = input.closureRequest;
1127
+ const acceptedItems = closureRequest.candidate?.acceptedItems ?? [];
1128
+ const blockingItems = acceptedItems.filter((item) => item.kind === 'blocking_ambiguity' || closureRequest.unresolvedAmbiguityIds.includes(item.id));
1129
+ if (blockingItems.length > 0 && (input.answerRefs ?? []).length === 0) {
1130
+ const clarificationGate = {
1131
+ contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
1132
+ gateId: stableId('spec-clarification', input.profile.scope, closureRequest.closureRequestId, generatedAt),
1133
+ stage: 'spec',
1134
+ scope: input.profile.scope,
1135
+ status: 'needs_clarification',
1136
+ questions: blockingItems.map((item) => item.summary),
1137
+ blockingItemIds: blockingItems.map((item) => item.id),
1138
+ requiredResponder: 'user',
1139
+ createdAt: generatedAt,
1140
+ answerRefs: []
1141
+ };
1142
+ return baseResult(input.profile, generatedAt, {
1143
+ health: 'needs_clarification',
1144
+ acceptedItemIds: acceptedItems.filter((item) => item.kind !== 'blocking_ambiguity').map((item) => item.id),
1145
+ clarificationGate,
1146
+ nextActions: buildRuntimeNextActions(input.profile, generatedAt, ['unresolved_ambiguity'])
1147
+ });
1148
+ }
1149
+ const acceptedItemIds = acceptedItems.map((item) => item.id);
1150
+ const decisionRecord = buildSpecDecisionRecord(input.profile, closureRequest, input.answerRefs ?? [], acceptedItemIds, generatedAt);
1151
+ const stageDecision = buildStageRuntimeDecision(input.profile, closureRequest, decisionRecord, generatedAt);
1152
+ const handoffPacket = buildStageHandoffPacket(input.profile, closureRequest, decisionRecord, generatedAt);
1153
+ return baseResult(input.profile, generatedAt, {
1154
+ health: 'ready_for_plan',
1155
+ acceptedItemIds,
1156
+ specDecisionRecord: decisionRecord,
1157
+ stageDecision,
1158
+ handoffPacket,
1159
+ nextActions: buildRuntimeNextActions(input.profile, generatedAt, ['stage_ready', 'handoff_ready'])
1160
+ });
1161
+ }
1162
+ function planMemberAgents(intensity) {
1163
+ if (intensity === 'noop' || intensity === 'blocked') {
1164
+ return [];
1165
+ }
1166
+ if (intensity === 'lightweight') {
1167
+ return [PLAN_STAGE_REVIEW_AGENT];
1168
+ }
1169
+ return [PLAN_STAGE_SCOUT_AGENT, PLAN_STAGE_REVIEW_AGENT];
1170
+ }
1171
+ function planScoutDomains(intensity) {
1172
+ if (intensity === 'noop' || intensity === 'blocked' || intensity === 'lightweight') {
1173
+ return [];
1174
+ }
1175
+ if (intensity === 'scout-first') {
1176
+ return ['architecture-runtime', 'testing-validation'];
1177
+ }
1178
+ return ['architecture-runtime', 'backend-api', 'database-migration', 'security', 'testing-validation'];
1179
+ }
1180
+ function planRequiredOutputKinds(intensity) {
1181
+ if (intensity === 'noop' || intensity === 'blocked') {
1182
+ return [];
1183
+ }
1184
+ if (intensity === 'lightweight') {
1185
+ return ['plan_review', 'manager_closure_request'];
1186
+ }
1187
+ return ['plan_context', 'plan_review', 'manager_closure_request'];
1188
+ }
1189
+ function planRequiredCapabilities(intensity) {
1190
+ if (intensity === 'noop' || intensity === 'blocked') {
1191
+ return [];
1192
+ }
1193
+ if (intensity === 'lightweight') {
1194
+ return ['plan-strategy', 'plan-pressure-review'];
1195
+ }
1196
+ return ['plan-strategy', 'plan-pressure-review', ...planScoutDomains(intensity)];
1197
+ }
1198
+ function planOptionalCapabilities(intensity) {
1199
+ if (intensity === 'noop' || intensity === 'blocked') {
1200
+ return [];
1201
+ }
1202
+ return PLAN_SCOUT_DOMAINS.filter((domain) => !planScoutDomains(intensity).includes(domain));
1203
+ }
1204
+ function planMaterialPackIds(intensity) {
1205
+ if (intensity === 'noop' || intensity === 'blocked') {
1206
+ return [];
1207
+ }
1208
+ if (intensity === 'lightweight') {
1209
+ return ['project-norms', 'plan-section-rubric'];
1210
+ }
1211
+ return [...PLAN_STAGE_MATERIAL_PACKS];
1212
+ }
1213
+ function planCollaborationPlan(intensity) {
1214
+ if (intensity === 'noop' || intensity === 'blocked') {
1215
+ return { topology: 'none', participants: [], maxParallelism: 0, fanIn: 'runtime_adjudication' };
1216
+ }
1217
+ const requiredCapabilities = planRequiredCapabilities(intensity);
1218
+ const optionalCapabilities = planOptionalCapabilities(intensity);
1219
+ const materialPackIds = planMaterialPackIds(intensity);
1220
+ const participants = [
1221
+ { id: PLAN_STAGE_MANAGER, kind: 'agent', role: 'plan-stage-manager', required: true, capabilityDomain: 'plan-strategy', parallelGroup: null },
1222
+ ...planMemberAgents(intensity).map((agent) => ({
1223
+ id: agent,
1224
+ kind: 'subagent',
1225
+ role: planAgentRole(agent),
1226
+ required: agent === PLAN_STAGE_REVIEW_AGENT,
1227
+ capabilityDomain: planAgentCapability(agent),
1228
+ parallelGroup: planAgentParallelGroup(agent)
1229
+ })),
1230
+ ...requiredCapabilities.map((capability) => ({ id: `cap.${capability}`, kind: 'skill', role: 'required-capability-review', required: true, capabilityDomain: capability, parallelGroup: 'capability-review' })),
1231
+ ...optionalCapabilities.map((capability) => ({ id: `cap.${capability}`, kind: 'skill', role: 'optional-capability-review', required: false, capabilityDomain: capability, parallelGroup: 'capability-review' })),
1232
+ ...materialPackIds.map((packId) => ({ id: packId, kind: 'material-pack', role: 'capability-material', required: false, parallelGroup: 'material-pack' }))
1233
+ ];
1234
+ const topology = intensity === 'lightweight' ? 'team-lite' : intensity === 'scout-first' ? 'parallel-research' : 'team-required';
1235
+ return { topology, participants, maxParallelism: topology === 'team-lite' ? 2 : 3, fanIn: 'runtime_adjudication' };
1236
+ }
1237
+ function planAgentRole(agent) {
1238
+ switch (agent) {
1239
+ case PLAN_STAGE_SCOUT_AGENT:
1240
+ return 'domain-evidence-scout';
1241
+ case PLAN_STAGE_REVIEW_AGENT:
1242
+ return 'plan-pressure-reviewer';
1243
+ }
1244
+ }
1245
+ function planAgentCapability(agent) {
1246
+ switch (agent) {
1247
+ case PLAN_STAGE_SCOUT_AGENT:
1248
+ return 'architecture-runtime';
1249
+ case PLAN_STAGE_REVIEW_AGENT:
1250
+ return 'plan-pressure-review';
1251
+ }
1252
+ }
1253
+ function planAgentParallelGroup(agent) {
1254
+ switch (agent) {
1255
+ case PLAN_STAGE_SCOUT_AGENT:
1256
+ return 'domain-scouting';
1257
+ case PLAN_STAGE_REVIEW_AGENT:
1258
+ return 'review';
1259
+ }
1260
+ }
1261
+ function specIntensity(decision, required) {
1262
+ if (decision.profile === 'blocked' || decision.blockedStages.includes('spec')) {
1263
+ return 'blocked';
1264
+ }
1265
+ if (!required || decision.profile === 'direct') {
1266
+ return 'noop';
1267
+ }
1268
+ if (decision.profile === 'compact') {
1269
+ return 'lightweight';
1270
+ }
1271
+ if (decision.profile === 'research') {
1272
+ return 'scout-first';
1273
+ }
1274
+ return 'team-required';
1275
+ }
1276
+ function specProfileReasons(decision, required, intensity) {
1277
+ if (intensity === 'blocked') {
1278
+ return ['Lifecycle risk decision blocks spec collaboration.', ...decision.reasons];
1279
+ }
1280
+ if (!required) {
1281
+ return ['Spec stage is not required by lifecycle risk decision.', ...decision.reasons];
1282
+ }
1283
+ if (intensity === 'scout-first') {
1284
+ return ['Research profile requires spec-stage uncertainty discovery before plan handoff.', ...decision.reasons];
1285
+ }
1286
+ if (intensity === 'team-required') {
1287
+ return ['Full lifecycle requires runtime-adjudicated spec collaboration before plan handoff.', ...decision.reasons];
1288
+ }
1289
+ return ['Spec collaboration can remain lightweight for this lifecycle decision.', ...decision.reasons];
1290
+ }
1291
+ function specMemberAgents(intensity) {
1292
+ if (intensity === 'noop' || intensity === 'blocked') {
1293
+ return [];
1294
+ }
1295
+ if (intensity === 'lightweight') {
1296
+ return [SPEC_STAGE_REVIEW_AGENT];
1297
+ }
1298
+ return [SPEC_STAGE_SCOUT_AGENT, SPEC_STAGE_REVIEW_AGENT];
1299
+ }
1300
+ function specRequiredOutputKinds(intensity) {
1301
+ if (intensity === 'noop' || intensity === 'blocked') {
1302
+ return [];
1303
+ }
1304
+ if (intensity === 'lightweight') {
1305
+ return ['spec_review', 'manager_closure_request'];
1306
+ }
1307
+ return ['scout_context', 'spec_review', 'manager_closure_request'];
1308
+ }
1309
+ function specRequiredCapabilities(intensity) {
1310
+ if (intensity === 'noop' || intensity === 'blocked') {
1311
+ return [];
1312
+ }
1313
+ if (intensity === 'lightweight') {
1314
+ return ['norm_discovery'];
1315
+ }
1316
+ return SPEC_STAGE_REQUIRED_CAPABILITIES;
1317
+ }
1318
+ function specOptionalCapabilities(intensity) {
1319
+ if (intensity === 'noop' || intensity === 'blocked') {
1320
+ return [];
1321
+ }
1322
+ if (intensity === 'lightweight') {
1323
+ return ['uncertainty_resolution', 'context_curation'];
1324
+ }
1325
+ return SPEC_STAGE_OPTIONAL_CAPABILITIES;
1326
+ }
1327
+ function specMaterialPackIds(intensity) {
1328
+ if (intensity === 'noop' || intensity === 'blocked') {
1329
+ return [];
1330
+ }
1331
+ if (intensity === 'lightweight') {
1332
+ return ['project-norms', 'uncertainty-map'];
1333
+ }
1334
+ return [...SPEC_STAGE_MATERIAL_PACKS];
1335
+ }
1336
+ function specCollaborationPlan(intensity) {
1337
+ if (intensity === 'noop' || intensity === 'blocked') {
1338
+ return { topology: 'none', participants: [], maxParallelism: 0, fanIn: 'runtime_adjudication' };
1339
+ }
1340
+ const requiredCapabilities = specRequiredCapabilities(intensity);
1341
+ const optionalCapabilities = specOptionalCapabilities(intensity);
1342
+ const materialPackIds = specMaterialPackIds(intensity);
1343
+ const participants = [
1344
+ { id: SPEC_STAGE_MANAGER, kind: 'agent', role: 'spec-stage-manager', required: true, capabilityDomain: 'uncertainty_resolution', parallelGroup: null },
1345
+ ...specMemberAgents(intensity).map((agent) => ({
1346
+ id: agent,
1347
+ kind: 'subagent',
1348
+ role: agent === SPEC_STAGE_SCOUT_AGENT ? 'bounded-context-scout' : 'spec-document-reviewer',
1349
+ required: true,
1350
+ capabilityDomain: agent === SPEC_STAGE_SCOUT_AGENT ? 'context_curation' : 'uncertainty_resolution',
1351
+ parallelGroup: agent === SPEC_STAGE_SCOUT_AGENT ? 'discovery' : 'review'
1352
+ })),
1353
+ ...requiredCapabilities.map((capability) => ({ id: `cap.${capability}`, kind: 'skill', role: 'required-capability-review', required: true, capabilityDomain: capability, parallelGroup: 'capability-review' })),
1354
+ ...optionalCapabilities.map((capability) => ({ id: `cap.${capability}`, kind: 'skill', role: 'optional-capability-review', required: false, capabilityDomain: capability, parallelGroup: 'capability-review' })),
1355
+ ...materialPackIds.map((packId) => ({ id: packId, kind: 'material-pack', role: 'capability-material', required: false, parallelGroup: 'material-pack' }))
1356
+ ];
1357
+ const topology = intensity === 'lightweight' ? 'team-lite' : intensity === 'scout-first' ? 'parallel-research' : 'team-required';
1358
+ return { topology, participants, maxParallelism: topology === 'team-lite' ? 2 : 4, fanIn: 'runtime_adjudication' };
1359
+ }
1360
+ function validateStageClosureRequest(input) {
1361
+ const request = input.closureRequest;
1362
+ if (!request) {
1363
+ return { reasonCode: 'invalid_proposal', explanation: 'Spec stage closure requires a StageClosureRequest from spec-manager.', requiredNextAction: 'Submit spec-manager closure request with final spec ref/hash and runtime close boundary facts.', fallbackRoute: 'revise-proposal', retryAllowed: true };
1364
+ }
1365
+ if (request.stage !== 'spec') {
1366
+ return { reasonCode: 'unsupported_stage', explanation: `Spec runtime cannot close ${request.stage} closure requests.`, requiredNextAction: 'Route the closure request to the matching stage runtime.', fallbackRoute: 'revise-proposal', retryAllowed: true };
1367
+ }
1368
+ if (request.authorityAttempts.length > 0) {
1369
+ return { reasonCode: 'authority_violation', explanation: `Spec stage closure attempted workflow authority: ${request.authorityAttempts.join(', ')}.`, requiredNextAction: 'Remove workflow-authority attempts from manager and agent-team outputs.', fallbackRoute: 'revise-proposal', retryAllowed: true };
1370
+ }
1371
+ if (request.candidate && request.candidate.stage !== 'spec') {
1372
+ return { reasonCode: 'unsupported_stage', explanation: `Spec runtime cannot close ${request.candidate.stage} candidates.`, requiredNextAction: 'Route the candidate to the matching stage runtime.', fallbackRoute: 'revise-proposal', retryAllowed: true };
1373
+ }
1374
+ const unsupported = request.candidate?.acceptedItems.find((item) => !input.profile.allowedProposalKinds.includes(item.kind));
1375
+ if (unsupported) {
1376
+ return { reasonCode: 'invalid_proposal', explanation: `Spec candidate item ${unsupported.id} uses unsupported kind ${unsupported.kind}.`, requiredNextAction: 'Submit only allowed spec candidate item kinds for this profile.', fallbackRoute: 'revise-proposal', retryAllowed: true };
1377
+ }
1378
+ const missingItemRef = request.candidate?.acceptedItems.find((item) => item.refs.length === 0);
1379
+ if (missingItemRef) {
1380
+ return { reasonCode: 'missing_refs', explanation: `Spec candidate item ${missingItemRef.id} has no refs.`, requiredNextAction: 'Attach source refs to every candidate item.', fallbackRoute: 'revise-proposal', retryAllowed: true };
1381
+ }
1382
+ if (request.managerRecommendation === 'close_stage' && !request.runtimeCloseBoundaryFacts) {
1383
+ return { reasonCode: 'missing_refs', explanation: 'Spec stage close request is missing runtime close boundary facts for the final spec.md V3.', requiredNextAction: 'Ask spec-manager to submit finalSpecRef/finalSpecHash, section close declaration, review signal presence, and blockingBeforePlanCount=0.', fallbackRoute: 'revise-proposal', retryAllowed: true };
1384
+ }
1385
+ return null;
1386
+ }
1387
+ function noOpResult(profile, generatedAt) {
1388
+ const decisionRecord = {
1389
+ contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
1390
+ decisionId: stableId('spec-decision', profile.scope, 'noop', generatedAt),
1391
+ stage: 'spec',
1392
+ scope: profile.scope,
1393
+ source: 'runtime-noop',
1394
+ acceptedItemIds: [],
1395
+ answerRefs: [],
1396
+ decisionRefs: profile.inputRefs,
1397
+ mustNotAssume: [],
1398
+ createdAt: generatedAt
1399
+ };
1400
+ const stageDecision = {
1401
+ contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
1402
+ decisionId: stableId('spec-stage-decision', profile.scope, 'noop', generatedAt),
1403
+ stage: 'spec',
1404
+ scope: profile.scope,
1405
+ status: 'skipped',
1406
+ health: 'no-op',
1407
+ acceptedDecisionRefs: decisionRecord.decisionRefs,
1408
+ advisoryRefs: [],
1409
+ capabilityRefs: [],
1410
+ blockingReasons: [],
1411
+ createdAt: generatedAt
1412
+ };
1413
+ return baseResult(profile, generatedAt, { health: 'no-op', specDecisionRecord: decisionRecord, stageDecision });
1414
+ }
1415
+ function buildSpecDecisionRecord(profile, closureRequest, answerRefs, acceptedItemIds, generatedAt) {
1416
+ return {
1417
+ contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
1418
+ decisionId: stableId('spec-decision', profile.scope, closureRequest.closureRequestId, generatedAt),
1419
+ stage: 'spec',
1420
+ scope: profile.scope,
1421
+ source: answerRefs.length > 0 ? 'clarification-answer' : 'stage-closure-request',
1422
+ acceptedItemIds,
1423
+ answerRefs,
1424
+ decisionRefs: closureDecisionRefs(closureRequest, answerRefs),
1425
+ mustNotAssume: closureRequest.candidate?.acceptedItems.filter((item) => item.kind === 'blocking_ambiguity').map((item) => item.summary) ?? [],
1426
+ createdAt: generatedAt
1427
+ };
1428
+ }
1429
+ function buildStageRuntimeDecision(profile, closureRequest, decisionRecord, generatedAt) {
1430
+ return {
1431
+ contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
1432
+ decisionId: stableId('spec-stage-decision', profile.scope, closureRequest.closureRequestId, generatedAt),
1433
+ stage: 'spec',
1434
+ scope: profile.scope,
1435
+ status: 'completed',
1436
+ health: 'ready_for_plan',
1437
+ acceptedDecisionRefs: decisionRecord.decisionRefs,
1438
+ advisoryRefs: refsByCandidateKind(closureRequest, 'advisory_finding'),
1439
+ capabilityRefs: refsByCandidateKind(closureRequest, 'capability_suggestion'),
1440
+ blockingReasons: [],
1441
+ createdAt: generatedAt
1442
+ };
1443
+ }
1444
+ function buildStageHandoffPacket(profile, closureRequest, decisionRecord, generatedAt) {
1445
+ return {
1446
+ contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
1447
+ handoffId: stableId('spec-plan-handoff', profile.scope, closureRequest.closureRequestId, generatedAt),
1448
+ scope: profile.scope,
1449
+ fromStage: 'spec',
1450
+ toStage: 'plan',
1451
+ status: 'proposed',
1452
+ acceptedSpecDecisionRefs: decisionRecord.decisionRefs,
1453
+ advisoryRefs: refsByCandidateKind(closureRequest, 'advisory_finding'),
1454
+ capabilityRefs: refsByCandidateKind(closureRequest, 'capability_suggestion'),
1455
+ unresolvedRisks: [],
1456
+ mustNotAssume: decisionRecord.mustNotAssume,
1457
+ recommendedNextStageRoles: recommendedPlanRoles(profile),
1458
+ createdAt: generatedAt
1459
+ };
1460
+ }
1461
+ function refsByCandidateKind(closureRequest, kind) {
1462
+ return closureRequest.candidate?.acceptedItems.filter((item) => item.kind === kind).flatMap((item) => item.refs) ?? [];
1463
+ }
1464
+ function closureDecisionRefs(closureRequest, answerRefs) {
1465
+ return [
1466
+ closureRequest.workOrderRef,
1467
+ closureRequest.coordinationRef,
1468
+ closureRequest.candidateRef,
1469
+ ...closureRequest.reviewRefs,
1470
+ ...closureRequest.capabilityFindingRefs,
1471
+ ...closureRequest.evidenceRefs,
1472
+ ...answerRefs
1473
+ ].filter((ref) => ref !== null);
1474
+ }
1475
+ function recommendedPlanRoles(profile) {
1476
+ if (profile.intensity === 'scout-first') {
1477
+ return ['role.norm-scout', 'role.uncertainty-reviewer'];
1478
+ }
1479
+ if (profile.intensity === 'team-required') {
1480
+ return ['role.norm-scout', 'role.performance-planner', 'role.verification-designer'];
1481
+ }
1482
+ return [];
1483
+ }
1484
+ function rejected(input, reasonCode, explanation, requiredNextAction, fallbackRoute, generatedAt, retryAllowed) {
1485
+ const closureRequestId = input.closureRequest?.closureRequestId ?? null;
1486
+ const rejection = {
1487
+ contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
1488
+ rejectionId: stableId('spec-rejection', input.profile.scope, closureRequestId ?? reasonCode, generatedAt),
1489
+ stage: 'spec',
1490
+ scope: input.profile.scope,
1491
+ closureRequestId,
1492
+ reasonCode,
1493
+ explanation,
1494
+ requiredNextAction,
1495
+ retryAllowed,
1496
+ retryBudgetRemaining: retryAllowed ? input.retryBudgetRemaining ?? 1 : 0,
1497
+ fallbackRoute,
1498
+ createdAt: generatedAt
1499
+ };
1500
+ return baseResult(input.profile, generatedAt, {
1501
+ health: reasonCode === 'blocked_lifecycle' ? 'blocked' : 'rejected',
1502
+ rejection,
1503
+ nextActions: buildRuntimeNextActions(input.profile, generatedAt, [rejectionReasonToNextActionReason(reasonCode)])
1504
+ });
1505
+ }
1506
+ function baseResult(profile, generatedAt, overrides) {
1507
+ return {
1508
+ contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
1509
+ adjudicationId: stableId('spec-adjudication', profile.scope, overrides.health ?? 'unknown', generatedAt),
1510
+ stage: 'spec',
1511
+ scope: profile.scope,
1512
+ health: overrides.health ?? 'rejected',
1513
+ acceptedItemIds: overrides.acceptedItemIds ?? [],
1514
+ clarificationGate: overrides.clarificationGate ?? null,
1515
+ specDecisionRecord: overrides.specDecisionRecord ?? null,
1516
+ stageDecision: overrides.stageDecision ?? null,
1517
+ handoffPacket: overrides.handoffPacket ?? null,
1518
+ rejection: overrides.rejection ?? null,
1519
+ nextActions: overrides.nextActions ?? [],
1520
+ createdAt: generatedAt
1521
+ };
1522
+ }
1523
+ function rejectionReasonToNextActionReason(reasonCode) {
1524
+ if (reasonCode === 'authority_violation') {
1525
+ return 'authority_violation';
1526
+ }
1527
+ if (reasonCode === 'missing_required_review') {
1528
+ return 'missing_required_review';
1529
+ }
1530
+ if (reasonCode === 'missing_refs') {
1531
+ return 'insufficient_evidence';
1532
+ }
1533
+ if (reasonCode === 'blocked_lifecycle') {
1534
+ return 'graph_illegal';
1535
+ }
1536
+ return 'manager_rework_required';
1537
+ }
1538
+ function buildRuntimeNextActions(profile, generatedAt, reasonCodes) {
1539
+ return reasonCodes
1540
+ .map((reasonCode, index) => ({
1541
+ contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
1542
+ actionId: stableId('spec-next-action', profile.scope, reasonCode, generatedAt),
1543
+ stage: 'spec',
1544
+ scope: profile.scope,
1545
+ kind: nextActionKind(reasonCode),
1546
+ owner: nextActionOwner(reasonCode),
1547
+ actionScope: nextActionScope(reasonCode),
1548
+ reasonCode,
1549
+ priority: {
1550
+ reasonRank: reasonCodeRank(reasonCode),
1551
+ upstreamDistance: 0,
1552
+ actionRank: actionKindRank(nextActionKind(reasonCode)),
1553
+ stableOrder: index
1554
+ },
1555
+ requiredInputs: nextActionRequiredInputs(reasonCode),
1556
+ blockedBy: [],
1557
+ createdAt: generatedAt
1558
+ }))
1559
+ .sort(compareRuntimeNextAction);
1560
+ }
1561
+ function compareRuntimeNextAction(left, right) {
1562
+ return left.priority.reasonRank - right.priority.reasonRank
1563
+ || left.priority.upstreamDistance - right.priority.upstreamDistance
1564
+ || left.priority.actionRank - right.priority.actionRank
1565
+ || left.priority.stableOrder - right.priority.stableOrder;
1566
+ }
1567
+ function reasonCodeRank(reasonCode) {
1568
+ const order = ['authority_violation', 'graph_illegal', 'scope_unresolved', 'ambiguous_target', 'missing_canonical_artifact', 'stale_upstream', 'missing_handoff', 'missing_required_review', 'unresolved_conflict', 'unresolved_ambiguity', 'insufficient_evidence', 'manager_rework_required', 'human_required', 'stage_ready', 'handoff_ready', 'report_only'];
1569
+ return order.indexOf(reasonCode);
1570
+ }
1571
+ function nextActionKind(reasonCode) {
1572
+ if (reasonCode === 'authority_violation' || reasonCode === 'graph_illegal') {
1573
+ return 'reject_proposal';
1574
+ }
1575
+ if (reasonCode === 'missing_required_review') {
1576
+ return 'collect_required_review';
1577
+ }
1578
+ if (reasonCode === 'unresolved_ambiguity') {
1579
+ return 'ask_user_clarification';
1580
+ }
1581
+ if (reasonCode === 'handoff_ready') {
1582
+ return 'create_handoff';
1583
+ }
1584
+ if (reasonCode === 'stage_ready') {
1585
+ return 'commit_stage_closure';
1586
+ }
1587
+ if (reasonCode === 'stale_upstream') {
1588
+ return 'refresh_upstream_stage';
1589
+ }
1590
+ if (reasonCode === 'report_only') {
1591
+ return 'report_status';
1592
+ }
1593
+ return 'request_manager_rework';
1594
+ }
1595
+ function nextActionOwner(reasonCode) {
1596
+ if (reasonCode === 'unresolved_ambiguity' || reasonCode === 'human_required') {
1597
+ return 'user';
1598
+ }
1599
+ if (reasonCode === 'stage_ready' || reasonCode === 'handoff_ready' || reasonCode === 'report_only') {
1600
+ return 'runtime';
1601
+ }
1602
+ if (reasonCode === 'scope_unresolved' || reasonCode === 'ambiguous_target') {
1603
+ return 'orchestrator';
1604
+ }
1605
+ return 'stage-manager';
1606
+ }
1607
+ function nextActionScope(reasonCode) {
1608
+ if (reasonCode === 'unresolved_ambiguity' || reasonCode === 'human_required') {
1609
+ return 'human';
1610
+ }
1611
+ if (reasonCode === 'stage_ready' || reasonCode === 'handoff_ready') {
1612
+ return 'runtime';
1613
+ }
1614
+ if (reasonCode === 'scope_unresolved' || reasonCode === 'ambiguous_target') {
1615
+ return 'global';
1616
+ }
1617
+ return 'stage';
1618
+ }
1619
+ function actionKindRank(kind) {
1620
+ const order = ['reject_proposal', 'refresh_upstream_stage', 'collect_required_review', 'resolve_ambiguity', 'request_manager_rework', 'ask_user_clarification', 'commit_stage_closure', 'create_handoff', 'report_status'];
1621
+ return order.indexOf(kind);
1622
+ }
1623
+ function nextActionRequiredInputs(reasonCode) {
1624
+ if (reasonCode === 'missing_required_review') {
1625
+ return ['SpecReviewResult'];
1626
+ }
1627
+ if (reasonCode === 'unresolved_ambiguity') {
1628
+ return ['ClarificationGate answer'];
1629
+ }
1630
+ if (reasonCode === 'insufficient_evidence') {
1631
+ return ['runtime evidence refs'];
1632
+ }
1633
+ return [];
1634
+ }
1635
+ function specCollaborationDiagnosticReasons(result) {
1636
+ if (result.health === 'no-op') {
1637
+ return ['Spec collaboration is not required for this lifecycle decision.'];
1638
+ }
1639
+ if (result.health === 'needs_clarification') {
1640
+ return result.clarificationGate?.questions ?? ['Spec collaboration is waiting for clarification.'];
1641
+ }
1642
+ if (result.health === 'ready_for_plan') {
1643
+ return [`Spec collaboration is ready for plan handoff ${result.handoffPacket?.handoffId ?? 'none'}.`];
1644
+ }
1645
+ if (result.health === 'blocked') {
1646
+ return [result.rejection?.explanation ?? 'Spec collaboration is blocked.'];
1647
+ }
1648
+ return [result.rejection?.explanation ?? 'Spec collaboration proposal was rejected.'];
1649
+ }
1650
+ async function registerStageCollaborationContracts(projectRoot, branch, stage, workOrder, contractRefs, registeredAt) {
1651
+ const refs = await collectStageCollaborationContractRefs(projectRoot, branch, stage, contractRefs, Boolean(workOrder));
1652
+ const records = [];
1653
+ for (const ref of refs) {
1654
+ const artifact = await readStageCollaborationContract(projectRoot, ref);
1655
+ const record = validateStageCollaborationContractFrontmatter({
1656
+ branch,
1657
+ stage,
1658
+ ref: artifact.ref,
1659
+ hash: artifact.hash,
1660
+ frontmatter: artifact.frontmatter,
1661
+ workOrder,
1662
+ registeredAt
1663
+ });
1664
+ records.push(await recordRuntimeStageCollaborationContract(projectRoot, record));
1665
+ }
1666
+ return records;
1667
+ }
1668
+ async function collectStageCollaborationContractRefs(projectRoot, branch, stage, contractRefs, required) {
1669
+ if (contractRefs && contractRefs.length > 0) {
1670
+ return [...new Set(contractRefs.map(normalizeBranchStageEvidenceRef))];
1671
+ }
1672
+ if (!required) {
1673
+ return [];
1674
+ }
1675
+ const dir = getBranchStageEvidenceDir(projectRoot, branch, stage);
1676
+ let files;
1677
+ try {
1678
+ files = await readdir(dir);
1679
+ }
1680
+ catch (error) {
1681
+ if (error.code === 'ENOENT') {
1682
+ return [];
1683
+ }
1684
+ throw error;
1685
+ }
1686
+ return files
1687
+ .filter((fileName) => isStageCollaborationContractFile(stage, fileName))
1688
+ .sort(compareStageEvidenceFileNames)
1689
+ .map((fileName) => toBranchStageEvidenceRef(branch, stage, fileName));
1690
+ }
1691
+ async function registerSpecStageArtifacts(projectRoot, branch, profile, artifactRefs, runId, registeredAt) {
1692
+ const refs = await collectSpecStageEvidenceRefs(projectRoot, branch, profile, artifactRefs);
1693
+ const records = [];
1694
+ for (const ref of refs) {
1695
+ const artifact = await readMarkdownArtifact(projectRoot, ref);
1696
+ const record = validateStageArtifactFrontmatter({
1697
+ branch,
1698
+ stage: 'spec',
1699
+ ref: artifact.ref,
1700
+ hash: artifact.hash,
1701
+ frontmatter: artifact.frontmatter,
1702
+ registeredAt
1703
+ });
1704
+ records.push(await recordRuntimeStageArtifact(projectRoot, record));
1705
+ await recordRegisteredStageArtifactPayload(projectRoot, runId, artifact, record);
1706
+ }
1707
+ return records;
1708
+ }
1709
+ async function collectSpecStageEvidenceRefs(projectRoot, branch, profile, artifactRefs) {
1710
+ void projectRoot;
1711
+ void branch;
1712
+ void profile;
1713
+ if (artifactRefs && artifactRefs.length > 0) {
1714
+ return [...new Set(artifactRefs.map(normalizeBranchStageEvidenceRef))];
1715
+ }
1716
+ return [];
1717
+ }
1718
+ async function registerBranchStageArtifacts(projectRoot, branch, stage, artifactRefs, required, runId, registeredAt) {
1719
+ const refs = await collectBranchStageEvidenceRefs(projectRoot, branch, stage, artifactRefs, required);
1720
+ const records = [];
1721
+ for (const ref of refs) {
1722
+ const artifact = await readMarkdownArtifact(projectRoot, ref);
1723
+ const record = validateStageArtifactFrontmatter({
1724
+ branch,
1725
+ stage,
1726
+ ref: artifact.ref,
1727
+ hash: artifact.hash,
1728
+ frontmatter: artifact.frontmatter,
1729
+ registeredAt
1730
+ });
1731
+ records.push(await recordRuntimeStageArtifact(projectRoot, record));
1732
+ await recordRegisteredStageArtifactPayload(projectRoot, runId, artifact, record);
1733
+ }
1734
+ return records;
1735
+ }
1736
+ async function recordRegisteredStageArtifactPayload(projectRoot, runId, artifact, record) {
1737
+ const physicalPayloadPath = `runtime.sqlite:${runId}:${artifact.ref}`;
1738
+ await recordRuntimeArtifactPayload(projectRoot, {
1739
+ payloadId: runtimeScopedId(runId, artifact.ref, artifact.hash),
1740
+ runId,
1741
+ sourceRunId: runId,
1742
+ branchSlug: record.branch,
1743
+ taskId: null,
1744
+ logicalRef: artifact.ref,
1745
+ physicalPayloadPath,
1746
+ artifactRole: record.kind,
1747
+ digest: artifact.hash,
1748
+ status: 'active',
1749
+ payload: { logicalRef: artifact.ref, physicalPayloadPath, storage: 'runtime.sqlite', source: 'branch_stage_evidence', content: artifact.content }
1750
+ });
1751
+ }
1752
+ async function collectBranchStageEvidenceRefs(projectRoot, branch, stage, artifactRefs, required) {
1753
+ if (artifactRefs && artifactRefs.length > 0) {
1754
+ return [...new Set(artifactRefs.map(normalizeBranchStageEvidenceRef).filter((ref) => !isStageCollaborationContractFile(stage, ref.split('/').pop() ?? ref)))];
1755
+ }
1756
+ if (!required) {
1757
+ return [];
1758
+ }
1759
+ const dir = getBranchStageEvidenceDir(projectRoot, branch, stage);
1760
+ let files;
1761
+ try {
1762
+ files = await readdir(dir);
1763
+ }
1764
+ catch (error) {
1765
+ if (error.code === 'ENOENT') {
1766
+ return [];
1767
+ }
1768
+ throw error;
1769
+ }
1770
+ const candidateRefs = files
1771
+ .filter((fileName) => fileName.endsWith('.md') && !isStageCollaborationContractFile(stage, fileName))
1772
+ .sort(compareStageEvidenceFileNames)
1773
+ .map((fileName) => toBranchStageEvidenceRef(branch, stage, fileName));
1774
+ const checkedRefs = await Promise.all(candidateRefs.map(async (ref) => (await isMarkdownStageArtifactCandidate(projectRoot, ref)) ? ref : null));
1775
+ return checkedRefs.filter((ref) => ref !== null);
1776
+ }
1777
+ async function isMarkdownStageArtifactCandidate(projectRoot, ref) {
1778
+ const content = await readFile(path.join(projectRoot, normalizeBranchStageEvidenceRef(ref)), 'utf8');
1779
+ return /^---\r?\n/.test(content);
1780
+ }
1781
+ function isSpecStageEvidenceFile(fileName) {
1782
+ return fileName === 'scout.md' || /^spec-review-v\d+\.md$/.test(fileName) || /^spec-manager-v\d+\.md$/.test(fileName);
1783
+ }
1784
+ function isStageCollaborationContractFile(stage, fileName) {
1785
+ return fileName === `${stage}-collaboration-contract.md` || new RegExp(`^${escapeRegex(stage)}-collaboration-contract-v\\d+\\.md$`).test(fileName);
1786
+ }
1787
+ function escapeRegex(value) {
1788
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1789
+ }
1790
+ function compareStageEvidenceFileNames(left, right) {
1791
+ return stageEvidenceVersion(left) - stageEvidenceVersion(right) || left.localeCompare(right);
1792
+ }
1793
+ function stageEvidenceVersion(fileNameOrRef) {
1794
+ const fileName = fileNameOrRef.split('/').pop() ?? fileNameOrRef;
1795
+ const match = /-v(\d+)\.md$/.exec(fileName);
1796
+ return match ? Number(match[1]) : 0;
1797
+ }
1798
+ function runtimeRefForStageArtifact(record) {
1799
+ return { kind: 'artifact', ref: record.ref, hash: record.hash };
1800
+ }
1801
+ function runtimeRefForStageCollaborationContract(record) {
1802
+ return { kind: 'artifact', ref: record.ref, hash: record.hash };
1803
+ }
1804
+ async function buildOutputCenteredSpecClosureRequest(projectRoot, profile, workOrder, partition, request, generatedAt) {
1805
+ if (!request) {
1806
+ return null;
1807
+ }
1808
+ const expectedSpecRef = `specs/${partition}/spec.md`;
1809
+ if (request.finalSpecRef && request.finalSpecRef !== expectedSpecRef) {
1810
+ return null;
1811
+ }
1812
+ if (request.sectionCloseDeclaration !== 'present' || request.reviewSignal !== 'present' || request.blockingBeforePlanCount !== 0) {
1813
+ return null;
1814
+ }
1815
+ const content = await readOptionalText(path.join(projectRoot, 'specs', partition, 'spec.md'));
1816
+ if (content === null) {
1817
+ return null;
1818
+ }
1819
+ const specHash = hashDocumentContent(content);
1820
+ const finalSpecRef = { kind: 'document', ref: expectedSpecRef, hash: specHash };
1821
+ const facts = {
1822
+ contract: 'sdd-spec-runtime-close-boundary-facts-v1',
1823
+ scope: profile.scope,
1824
+ finalSpecRef,
1825
+ finalSpecHash: specHash,
1826
+ templateContract: 'sdd-spec-doc-v3',
1827
+ blockingBeforePlanCount: 0,
1828
+ sectionCloseDeclaration: 'present',
1829
+ reviewSignal: 'present',
1830
+ runtimeTruthOwner: 'Runtime Kernel'
1831
+ };
1832
+ const acceptedItems = [{
1833
+ id: 'accepted-spec',
1834
+ kind: 'handoff_proposal',
1835
+ summary: 'Spec-manager submitted final spec.md V3 for runtime ref/hash acceptance.',
1836
+ refs: [finalSpecRef]
1837
+ }];
1838
+ const candidate = {
1839
+ contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
1840
+ candidateId: stableId('spec-document-candidate', profile.scope, expectedSpecRef, specHash, generatedAt),
1841
+ stage: 'spec',
1842
+ scope: profile.scope,
1843
+ producedBy: SPEC_STAGE_MANAGER,
1844
+ inputRefs: profile.inputRefs,
1845
+ evidenceRefs: [],
1846
+ acceptedItems,
1847
+ generatedAt
1848
+ };
1849
+ return {
1850
+ contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
1851
+ closureRequestId: stableId('spec-closure-request', profile.scope, expectedSpecRef, specHash, generatedAt),
1852
+ stage: 'spec',
1853
+ scope: profile.scope,
1854
+ submittedBy: 'host-adapter',
1855
+ workOrderRef: workOrder ? { kind: 'projection', ref: `spec-work-order:${workOrder.workOrderId}` } : { kind: 'projection', ref: `spec-work-order:${specCollaborationScopeKey(profile.scope)}` },
1856
+ coordinationRef: null,
1857
+ candidateRef: finalSpecRef,
1858
+ reviewRefs: [],
1859
+ capabilityFindingRefs: [],
1860
+ evidenceRefs: [],
1861
+ candidate,
1862
+ reviewResults: [],
1863
+ capabilityFindings: [],
1864
+ unresolvedAmbiguityIds: [],
1865
+ authorityAttempts: [],
1866
+ managerRecommendation: 'close_stage',
1867
+ runtimeCloseBoundaryFacts: facts,
1868
+ generatedAt
1869
+ };
1870
+ }
1871
+ async function buildOutputCenteredPlanClosureRequest(projectRoot, scope, workOrder, partition, acceptedSpecHandoff, request, generatedAt) {
1872
+ if (!request) {
1873
+ return null;
1874
+ }
1875
+ const expectedPlanRef = `specs/${partition}/plan.md`;
1876
+ if (request.finalPlanRef && request.finalPlanRef !== expectedPlanRef) {
1877
+ return null;
1878
+ }
1879
+ if (request.planCloseQualityEvidence !== 'present' || request.reviewSignal !== 'present' || request.blockingBeforeTasksCount !== 0) {
1880
+ return null;
1881
+ }
1882
+ const acceptedSpecRef = acceptedSpecRefFromHandoff(partition, acceptedSpecHandoff);
1883
+ if (!acceptedSpecRef) {
1884
+ return null;
1885
+ }
1886
+ const content = await readOptionalText(path.join(projectRoot, 'specs', partition, 'plan.md'));
1887
+ if (content === null) {
1888
+ return null;
1889
+ }
1890
+ const planHash = hashDocumentContent(content);
1891
+ const finalPlanRef = { kind: 'document', ref: expectedPlanRef, hash: planHash };
1892
+ const facts = {
1893
+ contract: 'sdd-plan-runtime-close-boundary-facts-v1',
1894
+ scope,
1895
+ acceptedSpecRef,
1896
+ finalPlanRef,
1897
+ finalPlanHash: planHash,
1898
+ templateContract: 'sdd-plan-doc-v3',
1899
+ blockingBeforeTasksCount: 0,
1900
+ planCloseQualityEvidence: 'present',
1901
+ reviewSignal: 'present',
1902
+ runtimeTruthOwner: 'Runtime Kernel'
1903
+ };
1904
+ return {
1905
+ contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
1906
+ closureRequestId: stableId('plan-closure-request', scope, expectedPlanRef, planHash, generatedAt),
1907
+ stage: 'plan',
1908
+ scope,
1909
+ submittedBy: 'host-adapter',
1910
+ workOrderRef: workOrder ? { kind: 'projection', ref: `plan-work-order:${workOrder.workOrderId}` } : { kind: 'projection', ref: `plan-work-order:${planCollaborationScopeKey(scope)}` },
1911
+ coordinationRef: null,
1912
+ candidateRef: finalPlanRef,
1913
+ reviewRefs: [],
1914
+ capabilityFindingRefs: [],
1915
+ evidenceRefs: [],
1916
+ candidate: null,
1917
+ reviewResults: [],
1918
+ capabilityFindings: [],
1919
+ unresolvedAmbiguityIds: [],
1920
+ authorityAttempts: [],
1921
+ managerRecommendation: 'close_stage',
1922
+ runtimeCloseBoundaryFacts: facts,
1923
+ generatedAt
1924
+ };
1925
+ }
1926
+ async function buildOutputCenteredTasksClosureRequest(projectRoot, scope, workOrder, partition, acceptedPlanHandoff, request, generatedAt) {
1927
+ if (!request) {
1928
+ return null;
1929
+ }
1930
+ const expectedTasksRef = `specs/${partition}/tasks.md`;
1931
+ if (request.finalTasksRef && request.finalTasksRef !== expectedTasksRef) {
1932
+ return null;
1933
+ }
1934
+ if (request.dependencyGraphStatus !== 'present' || request.validationSignalsStatus !== 'present' || request.reviewSignal !== 'present' || request.blockingBeforeExecuteCount !== 0) {
1935
+ return null;
1936
+ }
1937
+ const acceptedPlanRef = acceptedPlanRefFromHandoff(partition, acceptedPlanHandoff);
1938
+ const acceptedSpecRef = acceptedSpecRefFromPlanHandoff(acceptedPlanHandoff);
1939
+ if (!acceptedPlanRef || !acceptedSpecRef) {
1940
+ return null;
1941
+ }
1942
+ const content = await readOptionalText(path.join(projectRoot, 'specs', partition, 'tasks.md'));
1943
+ if (content === null) {
1944
+ return null;
1945
+ }
1946
+ const taskModel = parseSddTasksMarkdown(content, { tasksPath: expectedTasksRef });
1947
+ const tasksHash = hashDocumentContent(content);
1948
+ const finalTasksRef = { kind: 'document', ref: expectedTasksRef, hash: tasksHash };
1949
+ const facts = {
1950
+ contract: 'sdd-tasks-runtime-close-boundary-facts-v1',
1951
+ scope,
1952
+ acceptedSpecRef,
1953
+ acceptedPlanRef,
1954
+ finalTasksRef,
1955
+ finalTasksHash: tasksHash,
1956
+ templateContract: 'sdd-tasks-doc-v2',
1957
+ taskUnitCount: taskModel.tasks.length,
1958
+ blockingBeforeExecuteCount: 0,
1959
+ dependencyGraphStatus: 'present',
1960
+ validationSignalsStatus: 'present',
1961
+ reviewSignal: 'present',
1962
+ runtimeTruthOwner: 'Runtime Kernel'
1963
+ };
1964
+ return {
1965
+ contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
1966
+ closureRequestId: stableId('tasks-closure-request', scope, expectedTasksRef, tasksHash, generatedAt),
1967
+ stage: 'tasks',
1968
+ scope,
1969
+ submittedBy: 'host-adapter',
1970
+ workOrderRef: workOrder ? { kind: 'projection', ref: `tasks-work-order:${workOrder.workOrderId}` } : { kind: 'projection', ref: `tasks-work-order:${tasksCollaborationScopeKey(scope)}` },
1971
+ coordinationRef: null,
1972
+ candidateRef: finalTasksRef,
1973
+ reviewRefs: [],
1974
+ capabilityFindingRefs: [],
1975
+ evidenceRefs: [],
1976
+ candidate: null,
1977
+ reviewResults: [],
1978
+ capabilityFindings: [],
1979
+ unresolvedAmbiguityIds: [],
1980
+ authorityAttempts: [],
1981
+ managerRecommendation: 'close_stage',
1982
+ runtimeCloseBoundaryFacts: facts,
1983
+ generatedAt
1984
+ };
1985
+ }
1986
+ function acceptedSpecRefFromHandoff(partition, handoff) {
1987
+ const expectedSpecRef = `specs/${partition}/spec.md`;
1988
+ return handoff?.requiredInputRefs.find((inputRef) => inputRef.kind === 'document' && inputRef.ref === expectedSpecRef)
1989
+ ?? handoff?.outputRefs.find((outputRef) => outputRef.kind === 'document' && outputRef.ref === expectedSpecRef)
1990
+ ?? null;
1991
+ }
1992
+ function acceptedPlanRefFromHandoff(partition, handoff) {
1993
+ const expectedPlanRef = `specs/${partition}/plan.md`;
1994
+ return handoff?.requiredInputRefs.find((inputRef) => inputRef.kind === 'document' && inputRef.ref === expectedPlanRef)
1995
+ ?? handoff?.outputRefs.find((outputRef) => outputRef.kind === 'document' && outputRef.ref === expectedPlanRef)
1996
+ ?? null;
1997
+ }
1998
+ function acceptedSpecRefFromPlanHandoff(handoff) {
1999
+ return handoff?.requiredInputRefs.find((inputRef) => inputRef.kind === 'document' && inputRef.ref.endsWith('/spec.md'))
2000
+ ?? handoff?.outputRefs.find((outputRef) => outputRef.kind === 'document' && outputRef.ref.endsWith('/spec.md'))
2001
+ ?? null;
2002
+ }
2003
+ function latestValidatedStageCollaborationContract(records) {
2004
+ return records
2005
+ .filter((record) => record.status === 'validated')
2006
+ .sort((left, right) => stageEvidenceVersion(right.ref) - stageEvidenceVersion(left.ref) || right.registeredAt.localeCompare(left.registeredAt))[0] ?? null;
2007
+ }
2008
+ function deriveRegisteredSpecManagerCoordination(profile, workOrder, records, generatedAt) {
2009
+ const manager = latestStageArtifact(records, 'manager_closure_request');
2010
+ if (!manager || !workOrder) {
2011
+ return null;
2012
+ }
2013
+ return {
2014
+ contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
2015
+ coordinationId: stableId('spec-manager-coordination', profile.scope, manager.ref, manager.hash, generatedAt),
2016
+ stage: 'spec',
2017
+ scope: profile.scope,
2018
+ stageManager: SPEC_STAGE_MANAGER,
2019
+ workOrderRef: { kind: 'projection', ref: `spec-work-order:${workOrder.workOrderId}` },
2020
+ agentTeamRefs: records.filter((record) => record.ref !== manager.ref).map(runtimeRefForStageArtifact),
2021
+ recommendation: manager.recommendation ?? 'blocked',
2022
+ generatedAt
2023
+ };
2024
+ }
2025
+ function latestStageArtifact(records, kind) {
2026
+ return records
2027
+ .filter((record) => record.kind === kind)
2028
+ .sort((left, right) => stageEvidenceVersion(right.ref) - stageEvidenceVersion(left.ref) || right.registeredAt.localeCompare(left.registeredAt))[0] ?? null;
2029
+ }
2030
+ async function validateAcceptedSpecArtifact(projectRoot, input) {
2031
+ if (input.adjudication.health !== 'ready_for_plan') {
2032
+ return {
2033
+ acceptedSpecRef: null,
2034
+ specAcceptanceStatus: specArtifactStatusForHealth(input.adjudication.health),
2035
+ specHash: null,
2036
+ specContractHash: null,
2037
+ reasons: [`Spec artifact was not accepted because adjudication health is ${input.adjudication.health}.`]
2038
+ };
2039
+ }
2040
+ const expectedRef = `specs/${input.partition}/spec.md`;
2041
+ const boundaryFacts = input.closureRequest?.runtimeCloseBoundaryFacts ?? null;
2042
+ if (!boundaryFacts || boundaryFacts.contract !== 'sdd-spec-runtime-close-boundary-facts-v1') {
2043
+ return rejectedSpecArtifact('not_accepted_wrong_ref', 'missing_refs', 'Ready spec closure must include runtime close boundary facts for the final spec.md V3.', 'Submit finalSpecRef/finalSpecHash, section close declaration, review signal presence, and blockingBeforePlanCount=0 before closure.');
2044
+ }
2045
+ if (!validSpecRuntimeCloseBoundaryFacts(boundaryFacts, input.partition, expectedRef)) {
2046
+ return rejectedSpecArtifact('not_accepted_wrong_ref', 'invalid_proposal', 'Spec runtime close boundary facts do not match the final spec.md V3 acceptance contract.', 'Refresh spec close facts so finalSpecRef/finalSpecHash, template contract, section close declaration, review signal, and blocking count match runtime requirements.');
2047
+ }
2048
+ const specPath = path.join(projectRoot, 'specs', input.partition, 'spec.md');
2049
+ const content = await readOptionalText(specPath);
2050
+ if (content === null) {
2051
+ return rejectedSpecArtifact('not_accepted_missing_spec', 'missing_refs', `Final spec.md V3 artifact is missing at ${expectedRef}.`, 'Produce specs/<branch>/spec.md before requesting runtime closure.');
2052
+ }
2053
+ if (isManagedStarterSpec(content)) {
2054
+ return rejectedSpecArtifact('not_accepted_starter_spec', 'invalid_proposal', `Canonical spec artifact at ${expectedRef} is still the managed starter spec.`, 'Replace starter spec.md with final semantic spec.md V3 content before closure.');
2055
+ }
2056
+ const specHash = hashDocumentContent(content);
2057
+ const expectedHashes = [boundaryFacts.finalSpecHash, boundaryFacts.finalSpecRef.hash].filter((hash) => Boolean(hash));
2058
+ const mismatchedHash = expectedHashes.find((hash) => hash !== specHash);
2059
+ if (mismatchedHash) {
2060
+ return rejectedSpecArtifact('not_accepted_hash_mismatch', 'invalid_proposal', `Final spec.md V3 hash mismatch for ${expectedRef}: expected ${mismatchedHash}, actual ${specHash}.`, 'Refresh spec close facts with the current spec.md hash before closure.');
2061
+ }
2062
+ const specV3Errors = validateSpecDocumentV3(content);
2063
+ if (specV3Errors.length > 0) {
2064
+ return rejectedSpecArtifact('not_accepted_wrong_ref', 'invalid_proposal', `Final spec.md does not satisfy sdd-spec-doc-v3: ${specV3Errors.join('; ')}.`, 'Rewrite specs/<branch>/spec.md as final spec.md V3 with required semantic sections before closure.');
2065
+ }
2066
+ return {
2067
+ acceptedSpecRef: { kind: 'document', ref: expectedRef, hash: specHash },
2068
+ specAcceptanceStatus: 'accepted',
2069
+ specHash,
2070
+ specContractHash: hashSemanticDocument(content),
2071
+ reasons: [`Runtime accepted final spec.md V3 artifact ${expectedRef}.`]
2072
+ };
2073
+ }
2074
+ function validSpecRuntimeCloseBoundaryFacts(facts, partition, expectedSpecRef) {
2075
+ return facts.contract === 'sdd-spec-runtime-close-boundary-facts-v1'
2076
+ && facts.scope.branch === partition
2077
+ && facts.finalSpecRef.kind === 'document'
2078
+ && facts.finalSpecRef.ref === expectedSpecRef
2079
+ && Boolean(facts.finalSpecRef.hash)
2080
+ && facts.finalSpecHash === facts.finalSpecRef.hash
2081
+ && facts.templateContract === 'sdd-spec-doc-v3'
2082
+ && facts.blockingBeforePlanCount === 0
2083
+ && facts.sectionCloseDeclaration === 'present'
2084
+ && facts.reviewSignal === 'present'
2085
+ && facts.runtimeTruthOwner === 'Runtime Kernel';
2086
+ }
2087
+ function validPlanRuntimeCloseBoundaryFacts(facts, partition, expectedPlanRef, acceptedSpecRef) {
2088
+ return facts.contract === 'sdd-plan-runtime-close-boundary-facts-v1'
2089
+ && facts.scope.branch === partition
2090
+ && facts.acceptedSpecRef.kind === acceptedSpecRef.kind
2091
+ && facts.acceptedSpecRef.ref === acceptedSpecRef.ref
2092
+ && facts.acceptedSpecRef.hash === acceptedSpecRef.hash
2093
+ && facts.finalPlanRef.kind === 'document'
2094
+ && facts.finalPlanRef.ref === expectedPlanRef
2095
+ && Boolean(facts.finalPlanRef.hash)
2096
+ && facts.finalPlanHash === facts.finalPlanRef.hash
2097
+ && facts.templateContract === 'sdd-plan-doc-v3'
2098
+ && facts.blockingBeforeTasksCount === 0
2099
+ && facts.planCloseQualityEvidence === 'present'
2100
+ && facts.reviewSignal === 'present'
2101
+ && facts.runtimeTruthOwner === 'Runtime Kernel';
2102
+ }
2103
+ function validTasksRuntimeCloseBoundaryFacts(facts, partition, expectedTasksRef, acceptedSpecRef, acceptedPlanRef, taskUnitCount) {
2104
+ return facts.contract === 'sdd-tasks-runtime-close-boundary-facts-v1'
2105
+ && facts.scope.branch === partition
2106
+ && facts.acceptedSpecRef.kind === acceptedSpecRef.kind
2107
+ && facts.acceptedSpecRef.ref === acceptedSpecRef.ref
2108
+ && facts.acceptedSpecRef.hash === acceptedSpecRef.hash
2109
+ && facts.acceptedPlanRef.kind === acceptedPlanRef.kind
2110
+ && facts.acceptedPlanRef.ref === acceptedPlanRef.ref
2111
+ && facts.acceptedPlanRef.hash === acceptedPlanRef.hash
2112
+ && facts.finalTasksRef.kind === 'document'
2113
+ && facts.finalTasksRef.ref === expectedTasksRef
2114
+ && Boolean(facts.finalTasksRef.hash)
2115
+ && facts.finalTasksHash === facts.finalTasksRef.hash
2116
+ && facts.templateContract === 'sdd-tasks-doc-v2'
2117
+ && facts.taskUnitCount === taskUnitCount
2118
+ && facts.taskUnitCount > 0
2119
+ && facts.blockingBeforeExecuteCount === 0
2120
+ && facts.dependencyGraphStatus === 'present'
2121
+ && facts.validationSignalsStatus === 'present'
2122
+ && facts.reviewSignal === 'present'
2123
+ && facts.runtimeTruthOwner === 'Runtime Kernel';
2124
+ }
2125
+ function rejectedSpecArtifact(specAcceptanceStatus, reasonCode, explanation, requiredNextAction) {
2126
+ return {
2127
+ acceptedSpecRef: null,
2128
+ specAcceptanceStatus,
2129
+ specHash: null,
2130
+ specContractHash: null,
2131
+ reasons: [explanation],
2132
+ rejectionIssue: {
2133
+ reasonCode,
2134
+ explanation,
2135
+ requiredNextAction,
2136
+ fallbackRoute: 'revise-proposal'
2137
+ }
2138
+ };
2139
+ }
2140
+ async function validateAcceptedPlanArtifact(projectRoot, input) {
2141
+ if (!input.required) {
2142
+ return {
2143
+ acceptedPlanRef: null,
2144
+ planAcceptanceStatus: 'not_required_noop',
2145
+ planHash: null,
2146
+ planContractHash: null,
2147
+ reasons: ['Plan artifact was not required by this lifecycle decision.']
2148
+ };
2149
+ }
2150
+ if (input.blocked) {
2151
+ return rejectedPlanArtifact('not_accepted_blocked', 'blocked_lifecycle', 'Lifecycle risk decision blocks plan closure.', 'Resolve lifecycle blockers before requesting plan-stage closure.', 'runtime-blocked');
2152
+ }
2153
+ const upstreamIssue = await validateAcceptedSpecHandoff(projectRoot, input.partition, input.acceptedSpecHandoff);
2154
+ if (upstreamIssue) {
2155
+ return rejectedPlanArtifact('not_accepted_missing_upstream', 'missing_refs', upstreamIssue, 'Refresh spec-stage closure and provide a fresh spec -> plan handoff before plan closure.');
2156
+ }
2157
+ const expectedRef = `specs/${input.partition}/plan.md`;
2158
+ const acceptedSpecRef = acceptedSpecRefFromHandoff(input.partition, input.acceptedSpecHandoff);
2159
+ if (!acceptedSpecRef) {
2160
+ return rejectedPlanArtifact('not_accepted_missing_upstream', 'missing_refs', 'Plan closure requires accepted spec ref/hash from runtime handoff.', 'Refresh spec-stage closure and provide a fresh spec -> plan handoff before plan closure.');
2161
+ }
2162
+ const boundaryFacts = input.closureRequest?.runtimeCloseBoundaryFacts;
2163
+ if (!boundaryFacts || boundaryFacts.contract !== 'sdd-plan-runtime-close-boundary-facts-v1') {
2164
+ return rejectedPlanArtifact('not_accepted_wrong_ref', 'invalid_proposal', 'Plan close request is missing runtime boundary facts for final plan.md V3.', 'Submit finalPlanRef/finalPlanHash, acceptedSpecRef, plan close quality evidence, review signal, and blockingBeforeTasksCount=0.');
2165
+ }
2166
+ if (!validPlanRuntimeCloseBoundaryFacts(boundaryFacts, input.partition, expectedRef, acceptedSpecRef)) {
2167
+ return rejectedPlanArtifact('not_accepted_wrong_ref', 'invalid_proposal', `Plan close boundary facts must reference accepted spec and final ${expectedRef}.`, 'Refresh the output close request from current runtime handoff and specs/<branch>/plan.md.');
2168
+ }
2169
+ const planPath = path.join(projectRoot, 'specs', input.partition, 'plan.md');
2170
+ const content = await readOptionalText(planPath);
2171
+ if (content === null) {
2172
+ return rejectedPlanArtifact('not_accepted_missing_plan', 'missing_refs', `Canonical plan artifact is missing at ${expectedRef}.`, 'Produce specs/<branch>/plan.md before requesting runtime closure.');
2173
+ }
2174
+ if (isManagedStarterSpec(content)) {
2175
+ return rejectedPlanArtifact('not_accepted_starter_plan', 'invalid_proposal', `Canonical plan artifact at ${expectedRef} is still the managed starter document.`, 'Replace starter plan.md with semantic plan content before closure.');
2176
+ }
2177
+ const documentErrors = validatePlanDocumentV3(content, boundaryFacts);
2178
+ if (documentErrors.length > 0) {
2179
+ return rejectedPlanArtifact('not_accepted_wrong_ref', 'invalid_proposal', `Canonical plan artifact at ${expectedRef} is not a valid sdd-plan-doc-v3: ${documentErrors.join('; ')}.`, 'Rewrite specs/<branch>/plan.md as plan.md V3 before requesting runtime closure.');
2180
+ }
2181
+ const planHash = hashDocumentContent(content);
2182
+ if (boundaryFacts.finalPlanHash !== planHash || boundaryFacts.finalPlanRef.hash !== planHash) {
2183
+ return rejectedPlanArtifact('not_accepted_hash_mismatch', 'invalid_proposal', `Final plan hash mismatch for ${expectedRef}: expected ${boundaryFacts.finalPlanHash}, actual ${planHash}.`, 'Refresh plan close request with the current plan.md hash before closure.');
2184
+ }
2185
+ return {
2186
+ acceptedPlanRef: { kind: 'document', ref: expectedRef, hash: planHash },
2187
+ planAcceptanceStatus: 'accepted',
2188
+ planHash,
2189
+ planContractHash: hashSemanticDocument(content),
2190
+ reasons: [`Runtime accepted reviewed canonical plan artifact ${expectedRef}.`]
2191
+ };
2192
+ }
2193
+ async function validateAcceptedTasksArtifact(projectRoot, input) {
2194
+ if (!input.required) {
2195
+ return {
2196
+ acceptedTasksRef: null,
2197
+ tasksAcceptanceStatus: 'not_required_noop',
2198
+ tasksHash: null,
2199
+ tasksContractHash: null,
2200
+ reasons: ['Tasks artifact was not required by this lifecycle decision.']
2201
+ };
2202
+ }
2203
+ if (input.blocked) {
2204
+ return rejectedTasksArtifact('not_accepted_blocked', 'blocked_lifecycle', 'Lifecycle risk decision blocks tasks closure.', 'Resolve lifecycle blockers before requesting tasks-stage closure.', 'runtime-blocked');
2205
+ }
2206
+ const upstreamIssue = await validateAcceptedPlanHandoff(projectRoot, input.partition, input.acceptedPlanHandoff);
2207
+ if (upstreamIssue) {
2208
+ return rejectedTasksArtifact('not_accepted_missing_upstream', 'missing_refs', upstreamIssue, 'Refresh plan-stage closure and provide a fresh plan -> tasks handoff before tasks closure.');
2209
+ }
2210
+ const expectedRef = `specs/${input.partition}/tasks.md`;
2211
+ const acceptedPlanRef = acceptedPlanRefFromHandoff(input.partition, input.acceptedPlanHandoff);
2212
+ const acceptedSpecRef = acceptedSpecRefFromPlanHandoff(input.acceptedPlanHandoff);
2213
+ if (!acceptedPlanRef || !acceptedSpecRef) {
2214
+ return rejectedTasksArtifact('not_accepted_missing_upstream', 'missing_refs', 'Tasks closure requires accepted spec and plan ref/hash from runtime handoff.', 'Refresh plan-stage closure and provide a fresh plan -> tasks handoff before tasks closure.');
2215
+ }
2216
+ const tasksPath = path.join(projectRoot, 'specs', input.partition, 'tasks.md');
2217
+ const content = await readOptionalText(tasksPath);
2218
+ const boundaryFacts = input.closureRequest?.runtimeCloseBoundaryFacts;
2219
+ if (!boundaryFacts || boundaryFacts.contract !== 'sdd-tasks-runtime-close-boundary-facts-v1') {
2220
+ const closeEvidenceErrors = [];
2221
+ if (content !== null) {
2222
+ validateTasksDepthSignals(content, closeEvidenceErrors);
2223
+ }
2224
+ const closeEvidenceExplanation = closeEvidenceErrors.join('\n');
2225
+ return rejectedTasksArtifact('not_accepted_wrong_ref', 'invalid_proposal', closeEvidenceExplanation || 'Tasks close request is missing runtime boundary facts for final tasks.md V2.', 'Submit finalTasksRef/finalTasksHash, acceptedSpecRef, acceptedPlanRef, taskUnitCount, dependencyGraphStatus, validationSignalsStatus, reviewSignal, and blockingBeforeExecuteCount=0.');
2226
+ }
2227
+ if (content === null) {
2228
+ return rejectedTasksArtifact('not_accepted_missing_tasks', 'missing_refs', `Reviewed canonical tasks artifact is missing at ${expectedRef}.`, 'Produce and review specs/<branch>/tasks.md before requesting runtime closure.');
2229
+ }
2230
+ if (isManagedStarterSpec(content)) {
2231
+ return rejectedTasksArtifact('not_accepted_starter_tasks', 'invalid_proposal', `Canonical tasks artifact at ${expectedRef} is still the managed starter document.`, 'Replace starter tasks.md with reviewed executable task contracts before closure.');
2232
+ }
2233
+ const tasksV2Errors = validateTasksDocumentV2(content, expectedRef);
2234
+ if (tasksV2Errors.length > 0) {
2235
+ return rejectedTasksArtifact('not_accepted_wrong_ref', 'invalid_proposal', `Final tasks.md does not satisfy sdd-tasks-doc-v2: ${tasksV2Errors.join('; ')}.`, 'Rewrite specs/<branch>/tasks.md as final tasks.md V2 with required execution topology sections before closure.');
2236
+ }
2237
+ const taskModel = parseSddTasksMarkdown(content, { tasksPath: expectedRef });
2238
+ const duplicateIdGap = taskModel.gaps.find((gap) => gap.field === 'id' && /Duplicate task id/i.test(gap.message));
2239
+ if (duplicateIdGap) {
2240
+ return rejectedTasksArtifact('not_accepted_duplicate_task_ids', 'invalid_proposal', duplicateIdGap.message, duplicateIdGap.recommendation);
2241
+ }
2242
+ const blockingTaskGap = taskModel.tasks.length === 0
2243
+ ? { message: `Reviewed tasks artifact ${expectedRef} contains no executable sdd-task blocks.`, recommendation: 'Add at least one reviewed sdd-task block before closure.' }
2244
+ : taskModel.gaps.find((gap) => gap.severity === 'blocking') ?? null;
2245
+ if (blockingTaskGap) {
2246
+ return rejectedTasksArtifact('not_accepted_invalid_task_ids', 'invalid_proposal', blockingTaskGap.message, blockingTaskGap.recommendation);
2247
+ }
2248
+ const tasksHash = hashDocumentContent(content);
2249
+ if (!validTasksRuntimeCloseBoundaryFacts(boundaryFacts, input.partition, expectedRef, acceptedSpecRef, acceptedPlanRef, taskModel.tasks.length)) {
2250
+ return rejectedTasksArtifact('not_accepted_wrong_ref', 'invalid_proposal', `Tasks close boundary facts must reference accepted spec, accepted plan, final ${expectedRef}, and parsed task unit count.`, 'Refresh the output close request from current runtime handoff and specs/<branch>/tasks.md.');
2251
+ }
2252
+ if (boundaryFacts.finalTasksHash !== tasksHash || boundaryFacts.finalTasksRef.hash !== tasksHash) {
2253
+ return rejectedTasksArtifact('not_accepted_hash_mismatch', 'invalid_proposal', `Final tasks hash mismatch for ${expectedRef}: expected ${boundaryFacts.finalTasksHash}, actual ${tasksHash}.`, 'Refresh tasks close request with the current tasks.md hash before closure.');
2254
+ }
2255
+ const tasksContractHash = hashTasksContract(content);
2256
+ if (!tasksContractHash) {
2257
+ return rejectedTasksArtifact('not_accepted_invalid_task_ids', 'invalid_proposal', `Reviewed tasks artifact ${expectedRef} does not produce a task contract hash.`, 'Refresh tasks.md with reviewed executable task contracts before closure.');
2258
+ }
2259
+ return {
2260
+ acceptedTasksRef: { kind: 'document', ref: expectedRef, hash: tasksHash },
2261
+ tasksAcceptanceStatus: 'accepted',
2262
+ tasksHash,
2263
+ tasksContractHash,
2264
+ reasons: [`Runtime accepted reviewed canonical tasks artifact ${expectedRef}.`]
2265
+ };
2266
+ }
2267
+ async function validateAcceptedExecuteArtifact(projectRoot, input) {
2268
+ if (!input.required) {
2269
+ return {
2270
+ acceptedExecuteRef: null,
2271
+ executeAcceptanceStatus: 'not_required_noop',
2272
+ laneEvidenceRefs: [],
2273
+ reasons: ['Execute evidence was not required by this lifecycle decision.']
2274
+ };
2275
+ }
2276
+ if (input.blocked) {
2277
+ return rejectedExecuteArtifact('not_accepted_blocked', 'blocked_lifecycle', 'Lifecycle risk decision blocks execute closure.', 'Resolve lifecycle blockers before requesting execute-stage closure.', 'runtime-blocked');
2278
+ }
2279
+ const upstreamIssue = await validateAcceptedExecuteTasksHandoff(projectRoot, input.partition, input.acceptedTasksHandoff);
2280
+ if (upstreamIssue) {
2281
+ return rejectedExecuteArtifact('not_accepted_missing_upstream', 'missing_refs', upstreamIssue, 'Refresh tasks-stage closure and provide a fresh tasks -> execute handoff before execute closure.');
2282
+ }
2283
+ return validateExecuteProjectionReadiness(projectRoot, input.partition, input.acceptedTasksHandoff);
2284
+ }
2285
+ async function validateExecuteProjectionReadiness(projectRoot, partition, acceptedTasksHandoff) {
2286
+ const acceptedTasksRef = acceptedTasksRefFromExecuteHandoff(acceptedTasksHandoff);
2287
+ if (!acceptedTasksRef?.hash) {
2288
+ return rejectedExecuteArtifact('not_accepted_missing_upstream', 'missing_refs', 'Execute close requires a tasks -> execute handoff carrying accepted tasks ref/hash.', 'Refresh tasks-stage closure before requesting execute close.');
2289
+ }
2290
+ const [projectionRecords, dependencyGraphEnvelope] = await Promise.all([
2291
+ listRuntimeProjections(projectRoot, [TASK_UNIT_PROJECTION_TYPE, EXECUTION_LANE_PROJECTION_TYPE]),
2292
+ readTaskDependencyGraphProjection(projectRoot, partition)
2293
+ ]);
2294
+ if (!dependencyGraphEnvelope || dependencyGraphEnvelope.payload.tasksHash !== acceptedTasksRef.hash) {
2295
+ return rejectedExecuteArtifact('not_accepted_hash_mismatch', 'invalid_proposal', `Execute dependency graph must be derived from accepted ${acceptedTasksRef.ref}.`, 'Rebuild execute task projections from the accepted tasks ref/hash before closing execute.');
2296
+ }
2297
+ const taskUnits = projectionRecords
2298
+ .filter((record) => record.projectionType === TASK_UNIT_PROJECTION_TYPE && record.scopeKey.startsWith(`${partition}:`))
2299
+ .map((record) => runtimeProjectionPayload(record.payload))
2300
+ .filter((unit) => Boolean(unit))
2301
+ .filter((unit) => unit.sourceRef.ref === acceptedTasksRef.ref && unit.sourceRef.hash === acceptedTasksRef.hash);
2302
+ if (taskUnits.length === 0) {
2303
+ return rejectedExecuteArtifact('not_accepted_wrong_ref', 'missing_refs', `No TaskUnitProjection records match accepted ${acceptedTasksRef.ref}.`, 'Rebuild execute task projections from the accepted tasks ref/hash before closing execute.');
2304
+ }
2305
+ const executionLanes = projectionRecords
2306
+ .filter((record) => record.projectionType === EXECUTION_LANE_PROJECTION_TYPE && record.scopeKey.startsWith(`${partition}:`))
2307
+ .map((record) => runtimeProjectionPayload(record.payload))
2308
+ .filter((lane) => Boolean(lane));
2309
+ const laneEvidenceRefs = executionLanes.map((lane) => ({ kind: 'projection', ref: `${EXECUTION_LANE_PROJECTION_TYPE}:${executionLaneScopeKey(partition, lane.laneId)}` }));
2310
+ const invalidTaskIds = uniqueStrings([...dependencyGraphEnvelope.payload.invalidTaskIds, ...taskUnits.filter((unit) => unit.dependencyState === 'invalid').map((unit) => unit.taskId)]);
2311
+ if (invalidTaskIds.length > 0) {
2312
+ return rejectedExecuteArtifact('not_accepted_wrong_ref', 'invalid_proposal', `Execute task projections contain invalid task units: ${invalidTaskIds.join(', ')}.`, 'Repair accepted tasks.md and rebuild task projections before closing execute.');
2313
+ }
2314
+ const implementationUnits = taskUnits.filter((unit) => unit.unit.agentRouting.board === 'implementation');
2315
+ const validationUnits = taskUnits.filter((unit) => unit.unit.agentRouting.board === 'validation');
2316
+ if (implementationUnits.length === 0) {
2317
+ return rejectedExecuteArtifact('not_accepted_missing_implementation', 'missing_refs', 'Execute close requires at least one implementation-board task unit from accepted tasks.md.', 'Route accepted implementation tasks before closing execute.');
2318
+ }
2319
+ if (validationUnits.length === 0) {
2320
+ return rejectedExecuteArtifact('not_accepted_missing_validation', 'missing_refs', 'Execute close requires at least one validation-board task unit from accepted tasks.md.', 'Route implementation validation and goal validation tasks before closing execute.');
2321
+ }
2322
+ const incompleteImplementation = implementationUnits.filter((unit) => !isImplementationComplete(unit)).map((unit) => unit.taskId);
2323
+ if (incompleteImplementation.length > 0) {
2324
+ return rejectedExecuteArtifact('not_accepted_missing_implementation', 'missing_refs', `Implementation board is not complete for task units: ${incompleteImplementation.join(', ')}.`, 'Run implementation/review/debug lanes until implementation task units are implemented or validated before execute close.');
2325
+ }
2326
+ const openCheckpointIds = taskUnits.filter((unit) => unit.checkpointState === 'open' || unit.checkpointState === 'locked').map((unit) => `${unit.taskId}:checkpoint`);
2327
+ if (openCheckpointIds.length > 0) {
2328
+ return rejectedExecuteArtifact('not_accepted_missing_checkpoint', 'invalid_proposal', `Execute checkpoints are still open: ${openCheckpointIds.join(', ')}.`, 'Satisfy or resolve execute checkpoints before closing execute.');
2329
+ }
2330
+ const incompleteValidation = validationUnits.filter((unit) => !isValidationComplete(unit)).map((unit) => unit.taskId);
2331
+ if (incompleteValidation.length > 0) {
2332
+ return rejectedExecuteArtifact('not_accepted_missing_validation', 'missing_refs', `Validation board is not complete for task units: ${incompleteValidation.join(', ')}.`, 'Run validation and goal-validation lanes until validation task units are validated before execute close.');
2333
+ }
2334
+ const acceptedExecuteRef = {
2335
+ kind: 'projection',
2336
+ ref: `${TASK_DEPENDENCY_GRAPH_PROJECTION_TYPE}:${taskDependencyGraphScopeKey(partition)}`,
2337
+ hash: dependencyGraphEnvelope.payload.tasksHash
2338
+ };
2339
+ return {
2340
+ acceptedExecuteRef,
2341
+ executeAcceptanceStatus: 'accepted',
2342
+ laneEvidenceRefs,
2343
+ reasons: [`Runtime accepted execute task/lane projections for ${acceptedTasksRef.ref}.`]
2344
+ };
2345
+ }
2346
+ async function validateAcceptedDoArtifact(projectRoot, input) {
2347
+ if (!input.required) {
2348
+ return {
2349
+ acceptedImplementationRef: null,
2350
+ implementationAcceptanceStatus: 'not_required_noop',
2351
+ implementationHash: null,
2352
+ changedFileRefs: [],
2353
+ reasons: ['Implementation artifact was not required by this lifecycle decision.']
2354
+ };
2355
+ }
2356
+ if (input.blocked) {
2357
+ return rejectedDoArtifact('not_accepted_blocked', 'blocked_lifecycle', 'Lifecycle risk decision blocks execute closure.', 'Resolve lifecycle blockers before requesting execute closure.', 'runtime-blocked');
2358
+ }
2359
+ if (!latestValidatedStageCollaborationContract(input.registeredCollaborationContracts)) {
2360
+ return rejectedDoArtifact('not_accepted_wrong_ref', 'missing_refs', 'Ready execute implementation closure must be backed by a validated execute StageCollaborationContract.', 'Ask execute-manager to write .sdd/runs/<branch>/execute/execute-collaboration-contract-vN.md within StageWorkOrder constraints before closure.');
2361
+ }
2362
+ const upstreamIssue = await validateAcceptedTasksExecuteHandoff(projectRoot, input.partition, input.acceptedTasksHandoff);
2363
+ if (upstreamIssue) {
2364
+ return rejectedDoArtifact('not_accepted_missing_upstream', 'missing_refs', upstreamIssue, 'Refresh tasks closure and provide a fresh tasks -> execute handoff before execute closure.');
2365
+ }
2366
+ const implementation = latestStageArtifact(input.registeredArtifacts, 'implementation_evidence');
2367
+ if (!implementation) {
2368
+ return rejectedDoArtifact('not_accepted_missing_implementation', 'missing_refs', 'Ready execute implementation closure must be backed by a registered implementer evidence artifact.', 'Ask implementer to write .sdd/runs/<branch>/execute/implementation-vN.md and register it before closure.');
2369
+ }
2370
+ const manager = latestStageArtifact(input.registeredArtifacts, 'manager_closure_request');
2371
+ if (!manager) {
2372
+ return rejectedDoArtifact('not_accepted_wrong_ref', 'missing_refs', 'Ready execute implementation closure must be backed by a registered execute-manager closure artifact.', 'Ask execute-manager to write .sdd/runs/<branch>/execute/execute-manager-vN.md and register it before closure.');
2373
+ }
2374
+ if (manager.recommendation !== 'close_stage') {
2375
+ return rejectedDoArtifact('not_accepted_rejected', 'invalid_proposal', `Execute-manager recommendation ${manager.recommendation ?? 'none'} cannot accept implementation state for execute validation handoff.`, 'Ask execute-manager to submit a close_stage recommendation after review blockers are resolved.');
2376
+ }
2377
+ const reviewByRef = manager.reviewRef
2378
+ ? input.registeredArtifacts.find((record) => record.kind === 'code_review' && record.ref === manager.reviewRef?.ref) ?? null
2379
+ : null;
2380
+ if (reviewByRef && manager.reviewHash !== reviewByRef.hash) {
2381
+ return rejectedDoArtifact('not_accepted_hash_mismatch', 'invalid_proposal', `Execute-manager review hash mismatch for ${reviewByRef.ref}: expected ${manager.reviewHash ?? 'none'}, actual ${reviewByRef.hash}.`, 'Refresh execute-manager closure artifact so reviewRef/reviewHash point at the registered code review artifact.');
2382
+ }
2383
+ const approvedReview = findStageReviewForManager(input.registeredArtifacts, manager, 'code_review');
2384
+ if (!approvedReview) {
2385
+ return rejectedDoArtifact('not_accepted_missing_review', 'missing_required_review', 'Ready execute implementation closure must include a registered code-reviewer artifact referenced by execute-manager.', 'Ask code-reviewer to write .sdd/runs/<branch>/execute/code-review-vN.md and ask execute-manager to reference that review hash.');
2386
+ }
2387
+ if (approvedReview.verdict !== 'approved' || approvedReview.blockingCount !== 0) {
2388
+ return rejectedDoArtifact('not_accepted_missing_review', 'missing_required_review', `Code-reviewer verdict ${approvedReview.verdict ?? 'none'} with ${approvedReview.blockingCount ?? 0} blockers cannot accept implementation state.`, 'Resolve review blockers and register an approved code-reviewer artifact before closure.');
2389
+ }
2390
+ if (manager.targetRef?.kind !== 'artifact' || manager.targetRef.ref !== implementation.ref || approvedReview.targetRef?.kind !== 'artifact' || approvedReview.targetRef.ref !== implementation.ref) {
2391
+ return rejectedDoArtifact('not_accepted_wrong_ref', 'invalid_proposal', `Ready execute implementation closure must reference reviewed ${implementation.ref} as the implementation evidence artifact.`, 'Refresh code-reviewer and execute-manager artifacts so targetRef points at the implementation evidence artifact.');
2392
+ }
2393
+ const expectedHashes = [manager.targetHash, approvedReview.targetHash, manager.targetRef.hash, approvedReview.targetRef.hash].filter((hash) => Boolean(hash));
2394
+ const mismatchedHash = expectedHashes.find((hash) => hash !== implementation.hash);
2395
+ if (mismatchedHash) {
2396
+ return rejectedDoArtifact('not_accepted_hash_mismatch', 'invalid_proposal', `Reviewed implementation evidence hash mismatch for ${implementation.ref}: expected ${mismatchedHash}, actual ${implementation.hash}.`, 'Refresh code-reviewer and execute-manager artifacts with the current implementation evidence hash before closure.');
2397
+ }
2398
+ const changedFileParse = parseImplementationChangedFileRefs(implementation);
2399
+ if (changedFileParse.issue) {
2400
+ return rejectedDoArtifact('not_accepted_boundary_violation', 'invalid_proposal', changedFileParse.issue, 'Refresh implementation evidence with changedFiles entries that include safe refs and current hashes.');
2401
+ }
2402
+ const changedFileRefs = changedFileParse.refs;
2403
+ const boundaryIssue = validateAllowedChangedFileRefs(changedFileRefs, input.allowedChangedFileRefs);
2404
+ if (boundaryIssue) {
2405
+ return rejectedDoArtifact('not_accepted_boundary_violation', 'invalid_proposal', boundaryIssue, 'Restrict execute implementation evidence to the task boundary or update the allowed changed-file refs.');
2406
+ }
2407
+ const changedFileHashIssue = await validateChangedFileHashes(projectRoot, changedFileRefs);
2408
+ if (changedFileHashIssue) {
2409
+ return rejectedDoArtifact('not_accepted_hash_mismatch', 'invalid_proposal', changedFileHashIssue, 'Refresh implementation, code-reviewer, and execute-manager artifacts with current changed-file hashes before closure.');
2410
+ }
2411
+ return {
2412
+ acceptedImplementationRef: { kind: 'artifact', ref: implementation.ref, hash: implementation.hash },
2413
+ implementationAcceptanceStatus: 'accepted',
2414
+ implementationHash: implementation.hash,
2415
+ changedFileRefs,
2416
+ reasons: [`Runtime accepted reviewed implementation evidence ${implementation.ref}.`]
2417
+ };
2418
+ }
2419
+ async function validateAcceptedTestArtifact(projectRoot, input) {
2420
+ if (!input.required) {
2421
+ return {
2422
+ acceptedTestEvidenceRef: null,
2423
+ testAcceptanceStatus: 'not_required_noop',
2424
+ testEvidenceHash: null,
2425
+ reasons: ['Test evidence was not required by this lifecycle decision.']
2426
+ };
2427
+ }
2428
+ if (input.blocked) {
2429
+ return rejectedTestArtifact('not_accepted_blocked', 'blocked_lifecycle', 'Lifecycle risk decision blocks execute validation closure.', 'Resolve lifecycle blockers before requesting execute validation closure.', 'runtime-blocked');
2430
+ }
2431
+ if (!latestValidatedStageCollaborationContract(input.registeredCollaborationContracts)) {
2432
+ return rejectedTestArtifact('not_accepted_wrong_ref', 'invalid_proposal', 'Ready execute validation closure must be backed by a validated execute StageCollaborationContract.', 'Ask execute-manager to write .sdd/runs/<branch>/execute/validation-collaboration-contract-vN.md within StageWorkOrder constraints before closure.');
2433
+ }
2434
+ const upstreamIssue = await validateAcceptedDoHandoff(projectRoot, input.partition, input.acceptedDoHandoff);
2435
+ if (upstreamIssue) {
2436
+ return rejectedTestArtifact('not_accepted_missing_upstream', 'missing_refs', upstreamIssue, 'Refresh execute implementation closure before execute validation closure.');
2437
+ }
2438
+ const execution = latestStageArtifact(input.registeredArtifacts, 'test_execution');
2439
+ if (!execution) {
2440
+ return rejectedTestArtifact('not_accepted_missing_execution', 'missing_refs', 'Ready execute validation closure must be backed by a registered validation execution artifact.', 'Ask validator to write .sdd/runs/<branch>/execute/test-execution-vN.md and register it before closure.');
2441
+ }
2442
+ const validation = latestStageArtifact(input.registeredArtifacts, 'validation_evidence');
2443
+ if (!validation) {
2444
+ return rejectedTestArtifact('not_accepted_missing_validation', 'missing_refs', 'Ready execute validation closure must be backed by a registered validator evidence artifact.', 'Ask validator to write .sdd/runs/<branch>/execute/validation-vN.md and register it before closure.');
2445
+ }
2446
+ const executionStatus = execution.frontmatter.status;
2447
+ const executionExitCode = execution.frontmatter.exitCode;
2448
+ if (executionStatus !== 'passed' || executionExitCode !== 0) {
2449
+ return rejectedTestArtifact('not_accepted_failed_tests', 'invalid_proposal', `Validation execution ${execution.ref} status ${String(executionStatus)} with exitCode ${String(executionExitCode)} cannot close execute validation.`, 'Rerun failing validation commands and register passing validation execution evidence before closure.');
2450
+ }
2451
+ const validationExecutionRef = executionRefFromValidation(validation);
2452
+ if (validationExecutionRef !== execution.ref) {
2453
+ return rejectedTestArtifact('not_accepted_wrong_ref', 'invalid_proposal', `Validation evidence ${validation.ref} must reference execution evidence ${execution.ref}.`, 'Refresh validation evidence so executionRef points at the registered validation execution artifact.');
2454
+ }
2455
+ const validationExecutionHash = validation.frontmatter.executionHash;
2456
+ if (validationExecutionHash !== execution.hash) {
2457
+ return rejectedTestArtifact('not_accepted_hash_mismatch', 'invalid_proposal', `Validation evidence execution hash mismatch for ${execution.ref}: expected ${String(validationExecutionHash)}, actual ${execution.hash}.`, 'Refresh validation evidence with the current validation execution hash before closure.');
2458
+ }
2459
+ if (validation.frontmatter.status !== 'passed') {
2460
+ return rejectedTestArtifact('not_accepted_failed_tests', 'invalid_proposal', `Validation evidence ${validation.ref} status ${String(validation.frontmatter.status)} cannot close execute validation.`, 'Resolve validation failures and register passing validation evidence before closure.');
2461
+ }
2462
+ if (validation.frontmatter.acceptanceMapped !== true) {
2463
+ return rejectedTestArtifact('not_accepted_unmapped_acceptance', 'invalid_proposal', `Validation evidence ${validation.ref} must declare acceptanceMapped: true.`, 'Map validation evidence to accepted task/verify criteria before requesting execute validation closure.');
2464
+ }
2465
+ const manager = latestStageArtifact(input.registeredArtifacts, 'manager_closure_request');
2466
+ if (!manager) {
2467
+ return rejectedTestArtifact('not_accepted_wrong_ref', 'invalid_proposal', 'Ready execute validation closure must be backed by a registered execute-manager closure artifact.', 'Ask execute-manager to write .sdd/runs/<branch>/execute/validation-manager-vN.md and register it before closure.');
2468
+ }
2469
+ if (manager.recommendation !== 'close_stage') {
2470
+ return rejectedTestArtifact('not_accepted_rejected', 'invalid_proposal', `Execute-manager recommendation ${manager.recommendation ?? 'none'} cannot accept validation evidence for execute evidence judgment.`, 'Ask execute-manager to submit a close_stage recommendation after validation blockers are resolved.');
2471
+ }
2472
+ const reviewByRef = manager.reviewRef
2473
+ ? input.registeredArtifacts.find((record) => record.kind === 'test_review' && record.ref === manager.reviewRef?.ref) ?? null
2474
+ : null;
2475
+ if (reviewByRef && manager.reviewHash !== reviewByRef.hash) {
2476
+ return rejectedTestArtifact('not_accepted_hash_mismatch', 'invalid_proposal', `Execute-manager review hash mismatch for ${reviewByRef.ref}: expected ${manager.reviewHash ?? 'none'}, actual ${reviewByRef.hash}.`, 'Refresh execute-manager closure artifact so reviewRef/reviewHash point at the registered validation review artifact.');
2477
+ }
2478
+ const approvedReview = findStageReviewForManager(input.registeredArtifacts, manager, 'test_review');
2479
+ if (!approvedReview) {
2480
+ return rejectedTestArtifact('not_accepted_missing_review', 'missing_required_review', 'Ready execute validation closure must include a registered validation-reviewer artifact referenced by execute-manager.', 'Ask validation-reviewer to write .sdd/runs/<branch>/execute/validation-review-vN.md and ask execute-manager to reference that review hash.');
2481
+ }
2482
+ if (approvedReview.verdict !== 'approved' || approvedReview.blockingCount !== 0) {
2483
+ return rejectedTestArtifact('not_accepted_missing_review', 'missing_required_review', `Validation-reviewer verdict ${approvedReview.verdict ?? 'none'} with ${approvedReview.blockingCount ?? 0} blockers cannot accept validation evidence.`, 'Resolve review blockers and register an approved validation-reviewer artifact before closure.');
2484
+ }
2485
+ if (manager.targetRef?.kind !== 'artifact' || manager.targetRef.ref !== validation.ref || approvedReview.targetRef?.kind !== 'artifact' || approvedReview.targetRef.ref !== validation.ref) {
2486
+ return rejectedTestArtifact('not_accepted_wrong_ref', 'invalid_proposal', `Ready execute validation closure must reference reviewed ${validation.ref} as the validation evidence artifact.`, 'Refresh validation-reviewer and execute-manager artifacts so targetRef points at the validation evidence artifact.');
2487
+ }
2488
+ const expectedHashes = [manager.targetHash, approvedReview.targetHash, manager.targetRef.hash, approvedReview.targetRef.hash].filter((hash) => Boolean(hash));
2489
+ const mismatchedHash = expectedHashes.find((hash) => hash !== validation.hash);
2490
+ if (mismatchedHash) {
2491
+ return rejectedTestArtifact('not_accepted_hash_mismatch', 'invalid_proposal', `Reviewed validation evidence hash mismatch for ${validation.ref}: expected ${mismatchedHash}, actual ${validation.hash}.`, 'Refresh validation-reviewer and execute-manager artifacts with the current validation evidence hash before closure.');
2492
+ }
2493
+ return {
2494
+ acceptedTestEvidenceRef: { kind: 'artifact', ref: validation.ref, hash: validation.hash },
2495
+ testAcceptanceStatus: 'accepted',
2496
+ testEvidenceHash: validation.hash,
2497
+ reasons: [`Runtime accepted reviewed execute validation evidence ${validation.ref}.`]
2498
+ };
2499
+ }
2500
+ async function validateAcceptedEvidenceJudgmentArtifact(projectRoot, input) {
2501
+ if (!input.required) {
2502
+ return {
2503
+ acceptedEvidenceJudgmentRef: null,
2504
+ evidenceJudgmentAcceptanceStatus: 'not_required_noop',
2505
+ evidenceJudgmentHash: null,
2506
+ reasons: ['Evidence judgment was not required by this lifecycle decision.'],
2507
+ truthAlignment: null
2508
+ };
2509
+ }
2510
+ if (input.blocked) {
2511
+ return rejectedEvidenceJudgmentArtifact('not_accepted_blocked', 'blocked_lifecycle', 'Lifecycle risk decision blocks execute evidence judgment closure.', 'Resolve lifecycle blockers before requesting execute evidence judgment closure.', 'runtime-blocked');
2512
+ }
2513
+ if (!latestValidatedStageCollaborationContract(input.registeredCollaborationContracts)) {
2514
+ return rejectedEvidenceJudgmentArtifact('not_accepted_wrong_ref', 'invalid_proposal', 'Ready execute evidence judgment closure must be backed by a validated execute StageCollaborationContract.', 'Ask execute-manager to write .sdd/runs/<branch>/execute/evidence-judgment-collaboration-contract-vN.md within StageWorkOrder constraints before closure.');
2515
+ }
2516
+ const upstreamIssue = await validateAcceptedTestHandoff(projectRoot, input.partition, input.acceptedTestHandoff);
2517
+ if (upstreamIssue) {
2518
+ return rejectedEvidenceJudgmentArtifact('not_accepted_missing_upstream', 'missing_refs', upstreamIssue, 'Refresh execute validation closure before execute evidence judgment closure.');
2519
+ }
2520
+ const verification = latestStageArtifact(input.registeredArtifacts, 'evidence_judgment');
2521
+ if (!verification) {
2522
+ return rejectedEvidenceJudgmentArtifact('not_accepted_missing_verification', 'missing_refs', 'Ready execute evidence judgment closure must be backed by a registered evidence-judgment artifact.', 'Ask evidence-judgment agent to write .sdd/runs/<branch>/execute/evidence-judgment-vN.md and register it before closure.');
2523
+ }
2524
+ if (verification.frontmatter.status !== 'passed') {
2525
+ return rejectedEvidenceJudgmentArtifact('not_accepted_failed_verification', 'invalid_proposal', `Evidence judgment ${verification.ref} status ${String(verification.frontmatter.status)} cannot close execute evidence judgment.`, 'Resolve evidence judgment failures and register passing evidence judgment evidence before closure.');
2526
+ }
2527
+ if (verification.frontmatter.coverageComplete !== true) {
2528
+ return rejectedEvidenceJudgmentArtifact('not_accepted_incomplete_coverage', 'invalid_proposal', `Evidence judgment ${verification.ref} must declare coverageComplete: true.`, 'Complete acceptance coverage before requesting execute evidence judgment closure.');
2529
+ }
2530
+ const durableGapCount = verification.frontmatter.durableGapCount;
2531
+ const openGapCount = verification.frontmatter.openGapCount;
2532
+ if ((typeof durableGapCount === 'number' && durableGapCount > 0) || (typeof openGapCount === 'number' && openGapCount > 0)) {
2533
+ return rejectedEvidenceJudgmentArtifact('not_accepted_open_gap', 'invalid_proposal', `Evidence judgment ${verification.ref} still declares open durable gaps.`, 'Resolve durable gaps before requesting execute evidence judgment closure.');
2534
+ }
2535
+ const manager = latestStageArtifact(input.registeredArtifacts, 'manager_closure_request');
2536
+ if (!manager) {
2537
+ return rejectedEvidenceJudgmentArtifact('not_accepted_wrong_ref', 'invalid_proposal', 'Ready execute evidence judgment closure must be backed by a registered execute-manager closure artifact.', 'Ask execute-manager to write .sdd/runs/<branch>/execute/evidence-judgment-manager-vN.md and register it before closure.');
2538
+ }
2539
+ if (manager.recommendation !== 'close_stage') {
2540
+ return rejectedEvidenceJudgmentArtifact('not_accepted_rejected', 'invalid_proposal', `Execute-manager recommendation ${manager.recommendation ?? 'none'} cannot accept evidence judgment for truthAlignment and ship readiness.`, 'Ask execute-manager to submit a close_stage recommendation after evidence judgment blockers are resolved.');
2541
+ }
2542
+ const reviewByRef = manager.reviewRef
2543
+ ? input.registeredArtifacts.find((record) => record.kind === 'evidence_review' && record.ref === manager.reviewRef?.ref) ?? null
2544
+ : null;
2545
+ if (reviewByRef && manager.reviewHash !== reviewByRef.hash) {
2546
+ return rejectedEvidenceJudgmentArtifact('not_accepted_hash_mismatch', 'invalid_proposal', `Execute-manager review hash mismatch for ${reviewByRef.ref}: expected ${manager.reviewHash ?? 'none'}, actual ${reviewByRef.hash}.`, 'Refresh execute-manager closure artifact so reviewRef/reviewHash point at the registered evidence review artifact.');
2547
+ }
2548
+ const approvedReview = findStageReviewForManager(input.registeredArtifacts, manager, 'evidence_review');
2549
+ if (!approvedReview) {
2550
+ return rejectedEvidenceJudgmentArtifact('not_accepted_missing_review', 'missing_required_review', 'Ready execute evidence judgment closure must include a registered evidence-reviewer artifact referenced by execute-manager.', 'Ask evidence-reviewer to write .sdd/runs/<branch>/execute/evidence-review-vN.md and ask execute-manager to reference that review hash.');
2551
+ }
2552
+ if (approvedReview.verdict !== 'approved' || approvedReview.blockingCount !== 0) {
2553
+ return rejectedEvidenceJudgmentArtifact('not_accepted_missing_review', 'missing_required_review', `Evidence-reviewer verdict ${approvedReview.verdict ?? 'none'} with ${approvedReview.blockingCount ?? 0} blockers cannot accept evidence judgment.`, 'Resolve review blockers and register an approved evidence-reviewer artifact before closure.');
2554
+ }
2555
+ if (manager.targetRef?.kind !== 'artifact' || manager.targetRef.ref !== verification.ref || approvedReview.targetRef?.kind !== 'artifact' || approvedReview.targetRef.ref !== verification.ref) {
2556
+ return rejectedEvidenceJudgmentArtifact('not_accepted_wrong_ref', 'invalid_proposal', `Ready execute evidence judgment closure must reference reviewed ${verification.ref} as the evidence judgment artifact.`, 'Refresh evidence-reviewer and execute-manager artifacts so targetRef points at the evidence judgment artifact.');
2557
+ }
2558
+ const expectedHashes = [manager.targetHash, approvedReview.targetHash, manager.targetRef.hash, approvedReview.targetRef.hash].filter((hash) => Boolean(hash));
2559
+ const mismatchedHash = expectedHashes.find((hash) => hash !== verification.hash);
2560
+ if (mismatchedHash) {
2561
+ return rejectedEvidenceJudgmentArtifact('not_accepted_hash_mismatch', 'invalid_proposal', `Reviewed evidence judgment hash mismatch for ${verification.ref}: expected ${mismatchedHash}, actual ${verification.hash}.`, 'Refresh evidence-reviewer and execute-manager artifacts with the current evidence judgment hash before closure.');
2562
+ }
2563
+ return {
2564
+ acceptedEvidenceJudgmentRef: { kind: 'artifact', ref: verification.ref, hash: verification.hash },
2565
+ evidenceJudgmentAcceptanceStatus: 'accepted',
2566
+ evidenceJudgmentHash: verification.hash,
2567
+ reasons: [`Runtime accepted reviewed evidence judgment evidence ${verification.ref}.`],
2568
+ truthAlignment: parseEvidenceJudgmentTruthAlignment(verification)
2569
+ };
2570
+ }
2571
+ async function validateAcceptedShipReadinessArtifact(projectRoot, input) {
2572
+ if (!input.required) {
2573
+ return {
2574
+ acceptedShipReadinessRef: null,
2575
+ shipReadinessStatus: 'not_required_noop',
2576
+ shipReadinessHash: null,
2577
+ releaseDocumentRef: null,
2578
+ reasons: ['Ship was not required by this lifecycle decision.']
2579
+ };
2580
+ }
2581
+ if (input.blocked) {
2582
+ return rejectedShipReadinessArtifact('not_accepted_blocked', 'blocked_lifecycle', input.blockedReason ?? 'Lifecycle risk decision blocks ship closure.', 'Resolve ship blockers before requesting ship-stage closure.', 'runtime-blocked');
2583
+ }
2584
+ if (!latestValidatedStageCollaborationContract(input.registeredCollaborationContracts)) {
2585
+ return rejectedShipReadinessArtifact('not_accepted_wrong_ref', 'invalid_proposal', 'Ready ship closure must be backed by a validated ship StageCollaborationContract.', 'Ask ship-manager to write .sdd/runs/<branch>/ship/ship-collaboration-contract-vN.md within StageWorkOrder constraints before closure.');
2586
+ }
2587
+ const truthAlignmentIssue = await validateAcceptedTruthAlignment(projectRoot, input.partition, input.truthAlignment);
2588
+ if (truthAlignmentIssue) {
2589
+ return rejectedShipReadinessArtifact('not_accepted_missing_upstream', 'missing_refs', truthAlignmentIssue, 'Refresh execute evidence judgment closure so runtime records an aligned truthAlignment projection before ship closure.');
2590
+ }
2591
+ const authorityIssue = validateNoRuntimeAuthorityAttempts(input.registeredArtifacts);
2592
+ if (authorityIssue) {
2593
+ return rejectedShipReadinessArtifact('not_accepted_authority_violation', 'authority_violation', authorityIssue, 'Remove workflow-authority attempts from ship evidence before requesting runtime closure.');
2594
+ }
2595
+ const readiness = latestStageArtifact(input.registeredArtifacts, 'ship_readiness');
2596
+ if (!readiness) {
2597
+ return rejectedShipReadinessArtifact('not_accepted_missing_readiness', 'missing_refs', 'Ready ship closure must be backed by a registered ship-validator readiness artifact.', 'Ask ship-validator to write .sdd/runs/<branch>/ship/ship-readiness-vN.md and register it before closure.');
2598
+ }
2599
+ if (readiness.frontmatter.status !== 'ready') {
2600
+ return rejectedShipReadinessArtifact('not_accepted_rejected', 'invalid_proposal', `Ship readiness ${readiness.ref} status ${String(readiness.frontmatter.status)} cannot mark the project ship-ready.`, 'Resolve ship readiness failures and register ready ship evidence before closure.');
2601
+ }
2602
+ const openBlockerCount = readinessBlockerCount(readiness);
2603
+ if (openBlockerCount > 0) {
2604
+ return rejectedShipReadinessArtifact('not_accepted_open_blocker', 'invalid_proposal', `Ship readiness ${readiness.ref} still declares ${openBlockerCount} open blockers.`, 'Resolve doctor, capability, repair, gap, and migration blockers before requesting ship closure.');
2605
+ }
2606
+ const releaseDocument = await parseShipReleaseDocumentRef(projectRoot, input.partition, readiness);
2607
+ if (releaseDocument.issue) {
2608
+ return rejectedShipReadinessArtifact('not_accepted_release_drift', 'invalid_proposal', releaseDocument.issue, 'Refresh release documentation and ship readiness releaseRef/releaseHash before requesting ship closure.');
2609
+ }
2610
+ const manager = latestStageArtifact(input.registeredArtifacts, 'manager_closure_request');
2611
+ if (!manager) {
2612
+ return rejectedShipReadinessArtifact('not_accepted_wrong_ref', 'invalid_proposal', 'Ready ship closure must be backed by a registered ship-manager closure artifact.', 'Ask ship-manager to write .sdd/runs/<branch>/ship/ship-manager-vN.md and register it before closure.');
2613
+ }
2614
+ if (manager.recommendation !== 'close_stage') {
2615
+ return rejectedShipReadinessArtifact('not_accepted_rejected', 'invalid_proposal', `Ship-manager recommendation ${manager.recommendation ?? 'none'} cannot mark the project ship-ready.`, 'Ask ship-manager to submit a close_stage recommendation after ship blockers are resolved.');
2616
+ }
2617
+ const reviewByRef = manager.reviewRef
2618
+ ? input.registeredArtifacts.find((record) => record.kind === 'release_review' && record.ref === manager.reviewRef?.ref) ?? null
2619
+ : null;
2620
+ if (reviewByRef && manager.reviewHash !== reviewByRef.hash) {
2621
+ return rejectedShipReadinessArtifact('not_accepted_hash_mismatch', 'invalid_proposal', `Ship-manager review hash mismatch for ${reviewByRef.ref}: expected ${manager.reviewHash ?? 'none'}, actual ${reviewByRef.hash}.`, 'Refresh ship-manager closure artifact so reviewRef/reviewHash point at the registered release review artifact.');
2622
+ }
2623
+ const approvedReview = findStageReviewForManager(input.registeredArtifacts, manager, 'release_review');
2624
+ if (!approvedReview) {
2625
+ return rejectedShipReadinessArtifact('not_accepted_missing_review', 'missing_required_review', 'Ready ship closure must include a registered release-reviewer artifact referenced by ship-manager.', 'Ask release-reviewer to write .sdd/runs/<branch>/ship/release-review-vN.md and ask ship-manager to reference that review hash.');
2626
+ }
2627
+ if (approvedReview.verdict !== 'approved' || approvedReview.blockingCount !== 0) {
2628
+ return rejectedShipReadinessArtifact('not_accepted_missing_review', 'missing_required_review', `Release-reviewer verdict ${approvedReview.verdict ?? 'none'} with ${approvedReview.blockingCount ?? 0} blockers cannot mark the project ship-ready.`, 'Resolve release review blockers and register an approved release-reviewer artifact before closure.');
2629
+ }
2630
+ if (manager.targetRef?.kind !== 'artifact' || manager.targetRef.ref !== readiness.ref || approvedReview.targetRef?.kind !== 'artifact' || approvedReview.targetRef.ref !== readiness.ref) {
2631
+ return rejectedShipReadinessArtifact('not_accepted_wrong_ref', 'invalid_proposal', `Ready ship closure must reference reviewed ${readiness.ref} as the ship readiness artifact.`, 'Refresh release-reviewer and ship-manager artifacts so targetRef points at the ship readiness artifact.');
2632
+ }
2633
+ const expectedHashes = [manager.targetHash, approvedReview.targetHash, manager.targetRef.hash, approvedReview.targetRef.hash].filter((hash) => Boolean(hash));
2634
+ const mismatchedHash = expectedHashes.find((hash) => hash !== readiness.hash);
2635
+ if (mismatchedHash) {
2636
+ return rejectedShipReadinessArtifact('not_accepted_hash_mismatch', 'invalid_proposal', `Reviewed ship readiness hash mismatch for ${readiness.ref}: expected ${mismatchedHash}, actual ${readiness.hash}.`, 'Refresh release-reviewer and ship-manager artifacts with the current ship readiness hash before closure.');
2637
+ }
2638
+ return {
2639
+ acceptedShipReadinessRef: { kind: 'artifact', ref: readiness.ref, hash: readiness.hash },
2640
+ shipReadinessStatus: 'accepted',
2641
+ shipReadinessHash: readiness.hash,
2642
+ releaseDocumentRef: releaseDocument.ref,
2643
+ reasons: [`Runtime accepted reviewed ship readiness evidence ${readiness.ref}.`]
2644
+ };
2645
+ }
2646
+ async function validateAcceptedPlanHandoff(projectRoot, partition, handoff) {
2647
+ const expectedSpecRef = `specs/${partition}/spec.md`;
2648
+ const expectedPlanRef = `specs/${partition}/plan.md`;
2649
+ if (!handoff) {
2650
+ return 'Tasks closure requires a recorded plan -> tasks handoff.';
2651
+ }
2652
+ if (handoff.fromStage !== 'plan' || handoff.toStage !== 'tasks' || handoff.status === 'blocked' || handoff.status === 'rejected') {
2653
+ return 'Tasks closure requires a non-blocking plan -> tasks handoff.';
2654
+ }
2655
+ const acceptedSpecRef = handoff.requiredInputRefs.find((inputRef) => inputRef.kind === 'document' && inputRef.ref === expectedSpecRef)
2656
+ ?? handoff.outputRefs.find((outputRef) => outputRef.kind === 'document' && outputRef.ref === expectedSpecRef);
2657
+ if (!acceptedSpecRef?.hash) {
2658
+ return `Plan -> tasks handoff must carry accepted ${expectedSpecRef} with a content hash.`;
2659
+ }
2660
+ const specContent = await readOptionalText(path.join(projectRoot, 'specs', partition, 'spec.md'));
2661
+ if (specContent === null) {
2662
+ return `Accepted upstream spec artifact is missing at ${expectedSpecRef}.`;
2663
+ }
2664
+ const currentSpecHash = hashDocumentContent(specContent);
2665
+ if (acceptedSpecRef.hash !== currentSpecHash) {
2666
+ return `Accepted upstream spec hash is stale for ${expectedSpecRef}: expected ${acceptedSpecRef.hash}, actual ${currentSpecHash}.`;
2667
+ }
2668
+ const acceptedPlanRef = handoff.requiredInputRefs.find((inputRef) => inputRef.kind === 'document' && inputRef.ref === expectedPlanRef)
2669
+ ?? handoff.outputRefs.find((outputRef) => outputRef.kind === 'document' && outputRef.ref === expectedPlanRef);
2670
+ if (!acceptedPlanRef?.hash) {
2671
+ return `Plan -> tasks handoff must carry accepted ${expectedPlanRef} with a content hash.`;
2672
+ }
2673
+ const planContent = await readOptionalText(path.join(projectRoot, 'specs', partition, 'plan.md'));
2674
+ if (planContent === null) {
2675
+ return `Accepted upstream plan artifact is missing at ${expectedPlanRef}.`;
2676
+ }
2677
+ const currentPlanHash = hashDocumentContent(planContent);
2678
+ if (acceptedPlanRef.hash !== currentPlanHash) {
2679
+ return `Accepted upstream plan hash is stale for ${expectedPlanRef}: expected ${acceptedPlanRef.hash}, actual ${currentPlanHash}.`;
2680
+ }
2681
+ return null;
2682
+ }
2683
+ async function validateAcceptedExecuteTasksHandoff(projectRoot, partition, handoff) {
2684
+ const expectedTasksRef = `specs/${partition}/tasks.md`;
2685
+ if (!handoff) {
2686
+ return 'Execute closure requires a recorded tasks -> execute handoff.';
2687
+ }
2688
+ if (handoff.fromStage !== 'tasks' || handoff.toStage !== 'execute' || handoff.status === 'blocked' || handoff.status === 'rejected') {
2689
+ return 'Execute closure requires a non-blocking tasks -> execute handoff.';
2690
+ }
2691
+ const acceptedTasksRef = handoff.requiredInputRefs.find((inputRef) => inputRef.kind === 'document' && inputRef.ref === expectedTasksRef)
2692
+ ?? handoff.outputRefs.find((outputRef) => outputRef.kind === 'document' && outputRef.ref === expectedTasksRef);
2693
+ if (!acceptedTasksRef?.hash) {
2694
+ return `Tasks -> execute handoff must carry accepted ${expectedTasksRef} with a content hash.`;
2695
+ }
2696
+ const tasksContent = await readOptionalText(path.join(projectRoot, 'specs', partition, 'tasks.md'));
2697
+ if (tasksContent === null) {
2698
+ return `Accepted upstream tasks artifact is missing at ${expectedTasksRef}.`;
2699
+ }
2700
+ const currentTasksHash = hashDocumentContent(tasksContent);
2701
+ if (acceptedTasksRef.hash !== currentTasksHash) {
2702
+ return `Accepted upstream tasks hash is stale for ${expectedTasksRef}: expected ${acceptedTasksRef.hash}, actual ${currentTasksHash}.`;
2703
+ }
2704
+ return null;
2705
+ }
2706
+ function acceptedTasksRefFromExecuteHandoff(handoff) {
2707
+ if (!handoff) {
2708
+ return null;
2709
+ }
2710
+ return handoff.requiredInputRefs.find((inputRef) => inputRef.kind === 'document' && inputRef.ref.endsWith('/tasks.md'))
2711
+ ?? handoff.outputRefs.find((outputRef) => outputRef.kind === 'document' && outputRef.ref.endsWith('/tasks.md'))
2712
+ ?? null;
2713
+ }
2714
+ function isImplementationComplete(unit) {
2715
+ return unit.executionState === 'implemented' || unit.executionState === 'validated' || unit.evidenceState === 'accepted';
2716
+ }
2717
+ function isValidationComplete(unit) {
2718
+ return unit.executionState === 'validated' || unit.evidenceState === 'accepted';
2719
+ }
2720
+ async function validateAcceptedTasksHandoff(projectRoot, partition, handoff) {
2721
+ const expectedTasksRef = `specs/${partition}/tasks.md`;
2722
+ if (!handoff) {
2723
+ return 'Verification-design closure requires a recorded plan -> tasks handoff.';
2724
+ }
2725
+ if (handoff.fromStage !== 'plan' || handoff.toStage !== 'tasks' || handoff.status === 'blocked' || handoff.status === 'rejected') {
2726
+ return 'Verification-design closure requires a non-blocking plan -> tasks handoff.';
2727
+ }
2728
+ const acceptedTasksRef = handoff.requiredInputRefs.find((inputRef) => inputRef.kind === 'document' && inputRef.ref === expectedTasksRef)
2729
+ ?? handoff.outputRefs.find((outputRef) => outputRef.kind === 'document' && outputRef.ref === expectedTasksRef);
2730
+ if (!acceptedTasksRef?.hash) {
2731
+ return `Plan -> tasks handoff must carry accepted ${expectedTasksRef} with a content hash.`;
2732
+ }
2733
+ const tasksContent = await readOptionalText(path.join(projectRoot, 'specs', partition, 'tasks.md'));
2734
+ if (tasksContent === null) {
2735
+ return `Accepted upstream tasks artifact is missing at ${expectedTasksRef}.`;
2736
+ }
2737
+ const currentTasksHash = hashDocumentContent(tasksContent);
2738
+ if (acceptedTasksRef.hash !== currentTasksHash) {
2739
+ return `Accepted upstream tasks hash is stale for ${expectedTasksRef}: expected ${acceptedTasksRef.hash}, actual ${currentTasksHash}.`;
2740
+ }
2741
+ return null;
2742
+ }
2743
+ async function validateAcceptedTasksExecuteHandoff(projectRoot, partition, handoff) {
2744
+ const expectedTasksRef = `specs/${partition}/tasks.md`;
2745
+ if (!handoff) {
2746
+ return 'Execute closure requires a recorded tasks -> execute handoff.';
2747
+ }
2748
+ if (handoff.fromStage !== 'tasks' || handoff.toStage !== 'execute' || handoff.status === 'blocked' || handoff.status === 'rejected') {
2749
+ return 'Execute closure requires a non-blocking tasks -> execute handoff.';
2750
+ }
2751
+ const acceptedTasksRef = handoff.requiredInputRefs.find((inputRef) => inputRef.kind === 'document' && inputRef.ref === expectedTasksRef)
2752
+ ?? handoff.outputRefs.find((outputRef) => outputRef.kind === 'document' && outputRef.ref === expectedTasksRef);
2753
+ if (!acceptedTasksRef?.hash) {
2754
+ return `Tasks -> execute handoff must carry accepted ${expectedTasksRef} with a content hash.`;
2755
+ }
2756
+ const tasksContent = await readOptionalText(path.join(projectRoot, 'specs', partition, 'tasks.md'));
2757
+ if (tasksContent === null) {
2758
+ return `Accepted upstream tasks artifact is missing at ${expectedTasksRef}.`;
2759
+ }
2760
+ const currentTasksHash = hashDocumentContent(tasksContent);
2761
+ if (acceptedTasksRef.hash !== currentTasksHash) {
2762
+ return `Accepted upstream tasks hash is stale for ${expectedTasksRef}: expected ${acceptedTasksRef.hash}, actual ${currentTasksHash}.`;
2763
+ }
2764
+ return null;
2765
+ }
2766
+ async function validateAcceptedDoHandoff(projectRoot, partition, handoff) {
2767
+ if (!handoff) {
2768
+ return 'Validation lane closure requires a recorded execute -> ship handoff.';
2769
+ }
2770
+ if (handoff.fromStage !== 'execute' || handoff.toStage !== 'ship' || handoff.status === 'blocked' || handoff.status === 'rejected') {
2771
+ return 'Validation lane closure requires a non-blocking execute -> ship handoff.';
2772
+ }
2773
+ const acceptedImplementationRef = handoff.requiredInputRefs.find((inputRef) => inputRef.kind === 'artifact' && inputRef.ref.startsWith(`.sdd/runs/${partition}/execute/implementation-`))
2774
+ ?? handoff.outputRefs.find((outputRef) => outputRef.kind === 'artifact' && outputRef.ref.startsWith(`.sdd/runs/${partition}/execute/implementation-`));
2775
+ if (!acceptedImplementationRef?.hash) {
2776
+ return `Execute -> ship handoff must carry accepted .sdd/runs/${partition}/execute/implementation-vN.md with a content hash.`;
2777
+ }
2778
+ return validateHandoffInputHashes(projectRoot, handoff.requiredInputRefs);
2779
+ }
2780
+ async function validateAcceptedTestHandoff(projectRoot, partition, handoff) {
2781
+ if (!handoff) {
2782
+ return 'Evidence-judgment lane closure requires a recorded execute -> ship handoff.';
2783
+ }
2784
+ if (handoff.fromStage !== 'execute' || handoff.toStage !== 'ship' || handoff.status === 'blocked' || handoff.status === 'rejected') {
2785
+ return 'Evidence-judgment lane closure requires a non-blocking execute -> ship handoff.';
2786
+ }
2787
+ const acceptedTestRef = handoff.requiredInputRefs.find((inputRef) => inputRef.kind === 'artifact' && inputRef.ref.startsWith(`.sdd/runs/${partition}/execute/validation-`))
2788
+ ?? handoff.outputRefs.find((outputRef) => outputRef.kind === 'artifact' && outputRef.ref.startsWith(`.sdd/runs/${partition}/execute/validation-`));
2789
+ if (!acceptedTestRef?.hash) {
2790
+ return `Execute -> ship handoff must carry accepted .sdd/runs/${partition}/execute/validation-vN.md with a content hash.`;
2791
+ }
2792
+ return validateHandoffInputHashes(projectRoot, handoff.requiredInputRefs);
2793
+ }
2794
+ async function validateAcceptedSpecHandoff(projectRoot, partition, handoff) {
2795
+ const expectedSpecRef = `specs/${partition}/spec.md`;
2796
+ if (!handoff) {
2797
+ return 'Plan closure requires a recorded spec -> plan handoff.';
2798
+ }
2799
+ if (handoff.fromStage !== 'spec' || handoff.toStage !== 'plan' || handoff.status === 'blocked' || handoff.status === 'rejected') {
2800
+ return 'Plan closure requires a non-blocking spec -> plan handoff.';
2801
+ }
2802
+ const acceptedSpecRef = handoff.requiredInputRefs.find((inputRef) => inputRef.kind === 'document' && inputRef.ref === expectedSpecRef)
2803
+ ?? handoff.outputRefs.find((outputRef) => outputRef.kind === 'document' && outputRef.ref === expectedSpecRef);
2804
+ if (!acceptedSpecRef?.hash) {
2805
+ return `Spec -> plan handoff must carry accepted ${expectedSpecRef} with a content hash.`;
2806
+ }
2807
+ const specContent = await readOptionalText(path.join(projectRoot, 'specs', partition, 'spec.md'));
2808
+ if (specContent === null) {
2809
+ return `Accepted upstream spec artifact is missing at ${expectedSpecRef}.`;
2810
+ }
2811
+ const currentSpecHash = hashDocumentContent(specContent);
2812
+ if (acceptedSpecRef.hash !== currentSpecHash) {
2813
+ return `Accepted upstream spec hash is stale for ${expectedSpecRef}: expected ${acceptedSpecRef.hash}, actual ${currentSpecHash}.`;
2814
+ }
2815
+ return null;
2816
+ }
2817
+ function rejectedPlanArtifact(planAcceptanceStatus, reasonCode, explanation, requiredNextAction, fallbackRoute = 'revise-proposal') {
2818
+ return {
2819
+ acceptedPlanRef: null,
2820
+ planAcceptanceStatus,
2821
+ planHash: null,
2822
+ planContractHash: null,
2823
+ reasons: [explanation],
2824
+ rejectionIssue: {
2825
+ reasonCode,
2826
+ explanation,
2827
+ requiredNextAction,
2828
+ fallbackRoute
2829
+ }
2830
+ };
2831
+ }
2832
+ function rejectedTasksArtifact(tasksAcceptanceStatus, reasonCode, explanation, requiredNextAction, fallbackRoute = 'revise-proposal') {
2833
+ return {
2834
+ acceptedTasksRef: null,
2835
+ tasksAcceptanceStatus,
2836
+ tasksHash: null,
2837
+ tasksContractHash: null,
2838
+ reasons: [explanation],
2839
+ rejectionIssue: {
2840
+ reasonCode,
2841
+ explanation,
2842
+ requiredNextAction,
2843
+ fallbackRoute
2844
+ }
2845
+ };
2846
+ }
2847
+ function rejectedExecuteArtifact(executeAcceptanceStatus, reasonCode, explanation, requiredNextAction, fallbackRoute = 'revise-proposal') {
2848
+ return {
2849
+ acceptedExecuteRef: null,
2850
+ executeAcceptanceStatus,
2851
+ laneEvidenceRefs: [],
2852
+ reasons: [explanation],
2853
+ rejectionIssue: {
2854
+ reasonCode,
2855
+ explanation,
2856
+ requiredNextAction,
2857
+ fallbackRoute
2858
+ }
2859
+ };
2860
+ }
2861
+ function rejectedDoArtifact(implementationAcceptanceStatus, reasonCode, explanation, requiredNextAction, fallbackRoute = 'revise-proposal') {
2862
+ return {
2863
+ acceptedImplementationRef: null,
2864
+ implementationAcceptanceStatus,
2865
+ implementationHash: null,
2866
+ changedFileRefs: [],
2867
+ reasons: [explanation],
2868
+ rejectionIssue: {
2869
+ reasonCode,
2870
+ explanation,
2871
+ requiredNextAction,
2872
+ fallbackRoute
2873
+ }
2874
+ };
2875
+ }
2876
+ function rejectedTestArtifact(testAcceptanceStatus, reasonCode, explanation, requiredNextAction, fallbackRoute = 'revise-proposal') {
2877
+ return {
2878
+ acceptedTestEvidenceRef: null,
2879
+ testAcceptanceStatus,
2880
+ testEvidenceHash: null,
2881
+ reasons: [explanation],
2882
+ rejectionIssue: {
2883
+ reasonCode,
2884
+ explanation,
2885
+ requiredNextAction,
2886
+ fallbackRoute
2887
+ }
2888
+ };
2889
+ }
2890
+ function rejectedEvidenceJudgmentArtifact(evidenceJudgmentAcceptanceStatus, reasonCode, explanation, requiredNextAction, fallbackRoute = 'revise-proposal') {
2891
+ return {
2892
+ acceptedEvidenceJudgmentRef: null,
2893
+ evidenceJudgmentAcceptanceStatus,
2894
+ evidenceJudgmentHash: null,
2895
+ reasons: [explanation],
2896
+ truthAlignment: null,
2897
+ rejectionIssue: {
2898
+ reasonCode,
2899
+ explanation,
2900
+ requiredNextAction,
2901
+ fallbackRoute
2902
+ }
2903
+ };
2904
+ }
2905
+ function parseEvidenceJudgmentTruthAlignment(verification) {
2906
+ const status = enumFrontmatter(verification.frontmatter, 'truthAlignmentStatus', TRUTH_ALIGNMENT_STATUSES);
2907
+ const semanticImpact = enumFrontmatter(verification.frontmatter, 'semanticImpact', TRUTH_ALIGNMENT_SEMANTIC_IMPACTS);
2908
+ const ownerStage = enumFrontmatter(verification.frontmatter, 'ownerStage', TRUTH_ALIGNMENT_OWNER_STAGES);
2909
+ const invalidatesStages = stageListFrontmatter(verification.frontmatter, 'invalidatesStages');
2910
+ const staleRefs = runtimeRefListFrontmatter(verification.frontmatter, 'staleRefs');
2911
+ const reasons = stringListFrontmatter(verification.frontmatter, 'truthAlignmentReasons');
2912
+ return {
2913
+ status,
2914
+ ownerStage,
2915
+ semanticImpact,
2916
+ staleRefs,
2917
+ invalidatesStages,
2918
+ reasons
2919
+ };
2920
+ }
2921
+ function enumFrontmatter(frontmatter, key, allowed) {
2922
+ const value = frontmatter[key];
2923
+ if (value === undefined || value === null || value === '') {
2924
+ return null;
2925
+ }
2926
+ if (typeof value !== 'string' || !allowed.includes(value)) {
2927
+ return null;
2928
+ }
2929
+ return value;
2930
+ }
2931
+ function stringListFrontmatter(frontmatter, key) {
2932
+ const value = frontmatter[key];
2933
+ if (!Array.isArray(value)) {
2934
+ return [];
2935
+ }
2936
+ return value.filter((item) => typeof item === 'string' && item.length > 0);
2937
+ }
2938
+ function stageListFrontmatter(frontmatter, key) {
2939
+ const values = stringListFrontmatter(frontmatter, key);
2940
+ if (values.length === 0) {
2941
+ return null;
2942
+ }
2943
+ const allowed = new Set(['spec', 'plan', 'tasks', 'execute', 'ship']);
2944
+ return values.filter((value) => allowed.has(value));
2945
+ }
2946
+ function runtimeRefListFrontmatter(frontmatter, key) {
2947
+ const value = frontmatter[key];
2948
+ if (!Array.isArray(value)) {
2949
+ return [];
2950
+ }
2951
+ const refs = [];
2952
+ for (const item of value) {
2953
+ if (!item || typeof item !== 'object' || Array.isArray(item)) {
2954
+ continue;
2955
+ }
2956
+ const entry = item;
2957
+ if (typeof entry.ref !== 'string' || entry.ref.length === 0) {
2958
+ continue;
2959
+ }
2960
+ const kind = typeof entry.kind === 'string' ? entry.kind : entry.ref.startsWith('.sdd/runs/') ? 'artifact' : entry.ref.startsWith('specs/') ? 'document' : 'external';
2961
+ refs.push({ kind, ref: entry.ref, hash: typeof entry.hash === 'string' && entry.hash.length > 0 ? entry.hash : undefined });
2962
+ }
2963
+ return refs;
2964
+ }
2965
+ function rejectedShipReadinessArtifact(shipReadinessStatus, reasonCode, explanation, requiredNextAction, fallbackRoute = 'revise-proposal') {
2966
+ return {
2967
+ acceptedShipReadinessRef: null,
2968
+ shipReadinessStatus,
2969
+ shipReadinessHash: null,
2970
+ releaseDocumentRef: null,
2971
+ reasons: [explanation],
2972
+ rejectionIssue: {
2973
+ reasonCode,
2974
+ explanation,
2975
+ requiredNextAction,
2976
+ fallbackRoute
2977
+ }
2978
+ };
2979
+ }
2980
+ async function parseShipReleaseDocumentRef(projectRoot, partition, readiness) {
2981
+ const rawRef = readiness.frontmatter.releaseRef;
2982
+ const hash = readiness.frontmatter.releaseHash;
2983
+ if (rawRef === undefined || rawRef === null || rawRef === '') {
2984
+ return { ref: null, issue: null };
2985
+ }
2986
+ if (typeof rawRef !== 'string' || typeof hash !== 'string' || hash.length === 0) {
2987
+ return { ref: null, issue: `Ship readiness ${readiness.ref} releaseRef requires a non-empty releaseHash.` };
2988
+ }
2989
+ const normalizedRef = normalizePortablePath(rawRef);
2990
+ if (!normalizedRef || normalizedRef === '.' || normalizedRef === '..' || normalizedRef.includes('..') || path.isAbsolute(rawRef) || !normalizedRef.startsWith(`specs/${partition}/`)) {
2991
+ return { ref: null, issue: `Ship readiness ${readiness.ref} release document ref is unsafe or outside the branch docs: ${rawRef}` };
2992
+ }
2993
+ const filePath = path.resolve(projectRoot, normalizedRef);
2994
+ const projectPath = path.resolve(projectRoot);
2995
+ if (!filePath.startsWith(`${projectPath}${path.sep}`)) {
2996
+ return { ref: null, issue: `Ship readiness ${readiness.ref} release document ref escapes project root: ${rawRef}` };
2997
+ }
2998
+ const content = await readOptionalText(filePath);
2999
+ if (content === null) {
3000
+ return { ref: null, issue: `Release document is missing at ${normalizedRef}.` };
3001
+ }
3002
+ const currentHash = hashDocumentContent(content);
3003
+ if (hash !== currentHash) {
3004
+ return { ref: null, issue: `Release document hash is stale for ${normalizedRef}: expected ${hash}, actual ${currentHash}.` };
3005
+ }
3006
+ return { ref: { kind: 'document', ref: normalizedRef, hash }, issue: null };
3007
+ }
3008
+ async function validateAcceptedTruthAlignment(projectRoot, partition, truthAlignment) {
3009
+ if (!truthAlignment) {
3010
+ return 'Ship closure requires a recorded aligned truthAlignment projection from execute.';
3011
+ }
3012
+ if (truthAlignment.contract !== TRUTH_ALIGNMENT_CONTRACT || truthAlignment.sourceStage !== 'execute') {
3013
+ return 'Ship closure requires a valid execute truthAlignment projection.';
3014
+ }
3015
+ if (truthAlignment.status !== 'aligned') {
3016
+ const reasons = truthAlignment.reasons.length > 0 ? `: ${truthAlignment.reasons.join('; ')}` : '';
3017
+ return `Ship closure requires aligned truthAlignment; current status is ${truthAlignment.status}${reasons}.`;
3018
+ }
3019
+ if (truthAlignment.semanticImpact === 'material') {
3020
+ return 'Ship closure requires material truth drift to be reconciled before ship.';
3021
+ }
3022
+ if (truthAlignment.staleRefs.length > 0) {
3023
+ return `Ship closure requires fresh truthAlignment refs; stale refs: ${truthAlignment.staleRefs.map((ref) => ref.ref).join(', ')}.`;
3024
+ }
3025
+ if (truthAlignment.invalidatesStages.includes('ship')) {
3026
+ return 'Ship closure requires truthAlignment that does not invalidate ship.';
3027
+ }
3028
+ const acceptedEvidenceJudgmentRef = truthAlignment.acceptedRealityRefs.find((ref) => ref.kind === 'artifact' && ref.ref.startsWith(`.sdd/runs/${partition}/execute/evidence-judgment-`));
3029
+ if (!acceptedEvidenceJudgmentRef?.hash) {
3030
+ return `TruthAlignment must carry accepted .sdd/runs/${partition}/execute/evidence-judgment-vN.md with a content hash.`;
3031
+ }
3032
+ return validateTruthAlignmentRefHashes(projectRoot, [...truthAlignment.declaredTruthRefs, ...truthAlignment.acceptedRealityRefs]);
3033
+ }
3034
+ async function validateTruthAlignmentRefHashes(projectRoot, refs) {
3035
+ for (const ref of refs) {
3036
+ if (!ref.hash || ref.kind === 'projection' || ref.kind === 'run') {
3037
+ continue;
3038
+ }
3039
+ const normalized = normalizePortablePath(ref.ref);
3040
+ if (!normalized || normalized === '.' || normalized === '..' || normalized.includes('..') || path.isAbsolute(ref.ref)) {
3041
+ return `TruthAlignment accepted reality ref is unsafe: ${ref.ref}`;
3042
+ }
3043
+ const filePath = path.resolve(projectRoot, normalized);
3044
+ const projectPath = path.resolve(projectRoot);
3045
+ if (!filePath.startsWith(`${projectPath}${path.sep}`)) {
3046
+ return `TruthAlignment accepted reality ref escapes project root: ${ref.ref}`;
3047
+ }
3048
+ const content = await readOptionalText(filePath);
3049
+ if (content === null) {
3050
+ return `TruthAlignment accepted reality ref is missing at ${ref.ref}.`;
3051
+ }
3052
+ const currentHash = hashDocumentContent(content);
3053
+ if (ref.hash !== currentHash) {
3054
+ return `TruthAlignment accepted reality ref hash is stale for ${ref.ref}: expected ${ref.hash}, actual ${currentHash}.`;
3055
+ }
3056
+ }
3057
+ return null;
3058
+ }
3059
+ function validateNoRuntimeAuthorityAttempts(records) {
3060
+ const forbidden = new Set(['stage_pass', 'risk_decision', 'gate_pass', 'ship_ready', 'truth_alignment_approved']);
3061
+ for (const record of records) {
3062
+ const value = record.frontmatter.authorityAttempts;
3063
+ if (!Array.isArray(value)) {
3064
+ continue;
3065
+ }
3066
+ const attempts = value.filter((attempt) => typeof attempt === 'string' && forbidden.has(attempt));
3067
+ if (attempts.length > 0) {
3068
+ return `Ship artifact ${record.ref} attempted workflow authority: ${attempts.join(', ')}.`;
3069
+ }
3070
+ }
3071
+ return null;
3072
+ }
3073
+ function shipBlockerCount(decision) {
3074
+ return decision.requiredEvidence.filter((evidence) => evidence.requiredBefore === 'ship' && evidence.refs.length === 0).length;
3075
+ }
3076
+ function readinessBlockerCount(readiness) {
3077
+ return numericFrontmatter(readiness.frontmatter, 'doctorBlockerCount')
3078
+ + numericFrontmatter(readiness.frontmatter, 'capabilityBlockerCount')
3079
+ + numericFrontmatter(readiness.frontmatter, 'repairBlockerCount')
3080
+ + numericFrontmatter(readiness.frontmatter, 'gapBlockerCount')
3081
+ + numericFrontmatter(readiness.frontmatter, 'migrationBlockerCount')
3082
+ + numericFrontmatter(readiness.frontmatter, 'openBlockerCount');
3083
+ }
3084
+ function numericFrontmatter(frontmatter, key) {
3085
+ const value = frontmatter[key];
3086
+ return typeof value === 'number' && Number.isInteger(value) && value > 0 ? value : 0;
3087
+ }
3088
+ function executionRefFromValidation(validation) {
3089
+ const value = validation.frontmatter.executionRef;
3090
+ if (typeof value !== 'string' || value.length === 0) {
3091
+ return null;
3092
+ }
3093
+ try {
3094
+ return normalizeBranchStageEvidenceRef(value);
3095
+ }
3096
+ catch {
3097
+ return null;
3098
+ }
3099
+ }
3100
+ async function validateHandoffInputHashes(projectRoot, inputRefs) {
3101
+ for (const ref of inputRefs) {
3102
+ if (!ref.hash) {
3103
+ continue;
3104
+ }
3105
+ const normalized = normalizePortablePath(ref.ref);
3106
+ if (!normalized || normalized === '.' || normalized === '..' || normalized.includes('..') || path.isAbsolute(ref.ref)) {
3107
+ return `Handoff input ref is unsafe: ${ref.ref}`;
3108
+ }
3109
+ const filePath = path.resolve(projectRoot, normalized);
3110
+ const projectPath = path.resolve(projectRoot);
3111
+ if (!filePath.startsWith(`${projectPath}${path.sep}`)) {
3112
+ return `Handoff input ref escapes project root: ${ref.ref}`;
3113
+ }
3114
+ const content = await readOptionalText(filePath);
3115
+ if (content === null) {
3116
+ return `Accepted do handoff input is missing at ${ref.ref}.`;
3117
+ }
3118
+ const currentHash = hashDocumentContent(content);
3119
+ if (ref.hash !== currentHash) {
3120
+ return `Accepted do handoff input hash is stale for ${ref.ref}: expected ${ref.hash}, actual ${currentHash}.`;
3121
+ }
3122
+ }
3123
+ return null;
3124
+ }
3125
+ function findStageReviewForManager(records, manager, reviewKind) {
3126
+ if (manager.reviewRef) {
3127
+ return records.find((record) => record.kind === reviewKind && record.ref === manager.reviewRef?.ref && record.hash === manager.reviewHash) ?? null;
3128
+ }
3129
+ return latestStageArtifact(records, reviewKind);
3130
+ }
3131
+ function parseImplementationChangedFileRefs(implementation) {
3132
+ const value = implementation.frontmatter.changedFiles;
3133
+ if (!Array.isArray(value) || value.length === 0) {
3134
+ return { refs: [], issue: `Implementation evidence ${implementation.ref} must declare non-empty changedFiles frontmatter.` };
3135
+ }
3136
+ const refs = [];
3137
+ for (const item of value) {
3138
+ if (!item || typeof item !== 'object' || Array.isArray(item)) {
3139
+ return { refs: [], issue: `Implementation evidence ${implementation.ref} has an invalid changedFiles entry.` };
3140
+ }
3141
+ const entry = item;
3142
+ const rawRef = entry.ref;
3143
+ const hash = entry.hash;
3144
+ if (typeof rawRef !== 'string' || rawRef.length === 0 || typeof hash !== 'string' || hash.length === 0) {
3145
+ return { refs: [], issue: `Implementation evidence ${implementation.ref} changedFiles entries must include ref and hash.` };
3146
+ }
3147
+ const normalizedRef = normalizeChangedFileRef(rawRef);
3148
+ if (!normalizedRef) {
3149
+ return { refs: [], issue: `Implementation evidence ${implementation.ref} changed file ref is unsafe: ${rawRef}` };
3150
+ }
3151
+ refs.push({ kind: 'evidence', ref: normalizedRef, hash });
3152
+ }
3153
+ return { refs, issue: null };
3154
+ }
3155
+ function normalizeChangedFileRef(value) {
3156
+ const normalized = normalizePortablePath(value);
3157
+ if (!normalized || normalized === '.' || normalized === '..' || normalized.includes('..') || path.isAbsolute(value)) {
3158
+ return null;
3159
+ }
3160
+ if (normalized.startsWith('.sdd/runs/') || normalized.startsWith('runs/') || isCanonicalSddDocumentRef(normalized)) {
3161
+ return null;
3162
+ }
3163
+ return normalized;
3164
+ }
3165
+ function validateAllowedChangedFileRefs(changedFileRefs, allowedChangedFileRefs) {
3166
+ if (!allowedChangedFileRefs || allowedChangedFileRefs.length === 0) {
3167
+ return null;
3168
+ }
3169
+ const allowedExact = new Set();
3170
+ const allowedPatterns = [];
3171
+ for (const value of allowedChangedFileRefs) {
3172
+ const normalized = value.includes('*') ? normalizeChangedFilePattern(value) : normalizeChangedFileRef(value);
3173
+ if (!normalized) {
3174
+ return `Allowed changed-file ref is unsafe: ${value}`;
3175
+ }
3176
+ if (normalized.includes('*')) {
3177
+ allowedPatterns.push(changedFilePatternRegex(normalized));
3178
+ }
3179
+ else {
3180
+ allowedExact.add(normalized);
3181
+ }
3182
+ }
3183
+ const outsideBoundary = changedFileRefs.find((ref) => !allowedExact.has(ref.ref) && !allowedPatterns.some((pattern) => pattern.test(ref.ref)));
3184
+ return outsideBoundary ? `Changed file ${outsideBoundary.ref} is outside the allowed execute task boundary.` : null;
3185
+ }
3186
+ function normalizeChangedFilePattern(value) {
3187
+ const normalized = normalizePortablePath(value);
3188
+ if (!normalized || normalized === '.' || normalized === '..' || normalized.includes('..') || path.isAbsolute(value)) {
3189
+ return null;
3190
+ }
3191
+ if (normalized.startsWith('.sdd/runs/') || normalized.startsWith('runs/') || isCanonicalSddDocumentRef(normalized)) {
3192
+ return null;
3193
+ }
3194
+ return normalized;
3195
+ }
3196
+ function isCanonicalSddDocumentRef(normalizedRef) {
3197
+ return /^specs\/[^/]+\/(spec|plan|tasks|verify)\.md$/.test(normalizedRef);
3198
+ }
3199
+ function changedFilePatternRegex(pattern) {
3200
+ return new RegExp(`^${escapeRegex(pattern).replace(/\\\*/g, '[^/]*')}$`);
3201
+ }
3202
+ async function validateChangedFileHashes(projectRoot, changedFileRefs) {
3203
+ for (const ref of changedFileRefs) {
3204
+ const filePath = path.resolve(projectRoot, ref.ref);
3205
+ const projectPath = path.resolve(projectRoot);
3206
+ if (!filePath.startsWith(`${projectPath}${path.sep}`)) {
3207
+ return `Changed file ref escapes project root: ${ref.ref}`;
3208
+ }
3209
+ const content = await readOptionalText(filePath);
3210
+ if (content === null) {
3211
+ return `Changed file ${ref.ref} is missing from the workspace.`;
3212
+ }
3213
+ const currentHash = hashDocumentContent(content);
3214
+ if (ref.hash !== currentHash) {
3215
+ return `Changed file hash is stale for ${ref.ref}: expected ${ref.hash}, actual ${currentHash}.`;
3216
+ }
3217
+ }
3218
+ return null;
3219
+ }
3220
+ function buildPlanClosureStageRun(scope, runId, workOrder, health, input) {
3221
+ const status = health === 'ready_for_tasks'
3222
+ ? 'completed'
3223
+ : health === 'no-op'
3224
+ ? 'skipped'
3225
+ : 'blocked';
3226
+ return {
3227
+ contract: STAGE_RUN_CONTRACT_VERSION,
3228
+ id: stableId('stage-run-plan', scope, runId, input.generatedAt),
3229
+ scope,
3230
+ stage: 'plan',
3231
+ ownerAgent: workOrder?.stageManager ?? 'runtime',
3232
+ coMainAgents: workOrder?.agentTeam ?? [],
3233
+ status,
3234
+ inputRefs: input.inputRefs,
3235
+ outputRefs: input.outputRefs,
3236
+ decisionRefs: input.decisionRefs,
3237
+ blockingReasons: status === 'blocked' ? [input.rejectionReason ?? `Plan adjudication health is ${health}.`] : [],
3238
+ createdAt: input.generatedAt,
3239
+ updatedAt: input.generatedAt
3240
+ };
3241
+ }
3242
+ function buildPlanWorkflowHandoff(scope, decision, input) {
3243
+ return {
3244
+ contract: WORKFLOW_HANDOFF_CONTRACT_VERSION,
3245
+ id: stableId('plan-tasks-handoff', scope, 'closure', input.generatedAt),
3246
+ scope,
3247
+ fromStage: 'plan',
3248
+ toStage: 'tasks',
3249
+ fromAgent: PLAN_STAGE_MANAGER,
3250
+ toAgent: 'tasks-stage-runtime',
3251
+ status: decision.profile === 'blocked' || decision.approvalPolicy === 'blocked' ? 'blocked' : 'proposed',
3252
+ outputRefs: input.outputRefs,
3253
+ requiredInputRefs: input.requiredInputRefs,
3254
+ riskDecisionRef: input.riskDecisionRef,
3255
+ evidenceRefs: input.evidenceRefs,
3256
+ openQuestions: [],
3257
+ blockingGaps: [],
3258
+ createdAt: input.generatedAt
3259
+ };
3260
+ }
3261
+ function buildTasksClosureStageRun(scope, runId, workOrder, health, input) {
3262
+ const status = health === 'ready_for_execute'
3263
+ ? 'completed'
3264
+ : health === 'no-op'
3265
+ ? 'skipped'
3266
+ : 'blocked';
3267
+ return {
3268
+ contract: STAGE_RUN_CONTRACT_VERSION,
3269
+ id: stableId('stage-run-tasks', scope, runId, input.generatedAt),
3270
+ scope,
3271
+ stage: 'tasks',
3272
+ ownerAgent: workOrder?.stageManager ?? 'runtime',
3273
+ coMainAgents: workOrder?.agentTeam ?? [],
3274
+ status,
3275
+ inputRefs: input.inputRefs,
3276
+ outputRefs: input.outputRefs,
3277
+ decisionRefs: input.decisionRefs,
3278
+ blockingReasons: status === 'blocked' ? [input.rejectionReason ?? `Tasks adjudication health is ${health}.`] : [],
3279
+ createdAt: input.generatedAt,
3280
+ updatedAt: input.generatedAt
3281
+ };
3282
+ }
3283
+ function buildTasksWorkflowHandoff(scope, decision, input) {
3284
+ return {
3285
+ contract: WORKFLOW_HANDOFF_CONTRACT_VERSION,
3286
+ id: stableId('tasks-execute-handoff', scope, 'closure', input.generatedAt),
3287
+ scope,
3288
+ fromStage: 'tasks',
3289
+ toStage: 'execute',
3290
+ fromAgent: TASKS_STAGE_MANAGER,
3291
+ toAgent: 'execute-stage-runtime',
3292
+ status: decision.profile === 'blocked' || decision.approvalPolicy === 'blocked' ? 'blocked' : 'proposed',
3293
+ outputRefs: input.outputRefs,
3294
+ requiredInputRefs: input.requiredInputRefs,
3295
+ riskDecisionRef: input.riskDecisionRef,
3296
+ evidenceRefs: input.evidenceRefs,
3297
+ openQuestions: [],
3298
+ blockingGaps: [],
3299
+ createdAt: input.generatedAt
3300
+ };
3301
+ }
3302
+ function buildExecuteClosureStageRun(scope, runId, workOrder, health, input) {
3303
+ const status = health === 'ready_for_ship'
3304
+ ? 'completed'
3305
+ : health === 'no-op'
3306
+ ? 'skipped'
3307
+ : 'blocked';
3308
+ return {
3309
+ contract: STAGE_RUN_CONTRACT_VERSION,
3310
+ id: stableId('stage-run-execute', scope, runId, input.generatedAt),
3311
+ scope,
3312
+ stage: 'execute',
3313
+ ownerAgent: workOrder?.stageManager ?? 'runtime',
3314
+ coMainAgents: workOrder?.agentTeam ?? [],
3315
+ status,
3316
+ inputRefs: input.inputRefs,
3317
+ outputRefs: input.outputRefs,
3318
+ decisionRefs: input.decisionRefs,
3319
+ blockingReasons: status === 'blocked' ? [input.rejectionReason ?? `Execute adjudication health is ${health}.`] : [],
3320
+ createdAt: input.generatedAt,
3321
+ updatedAt: input.generatedAt
3322
+ };
3323
+ }
3324
+ function buildExecuteWorkflowHandoff(scope, decision, input) {
3325
+ return {
3326
+ contract: WORKFLOW_HANDOFF_CONTRACT_VERSION,
3327
+ id: stableId('execute-ship-handoff', scope, 'closure', input.generatedAt),
3328
+ scope,
3329
+ fromStage: 'execute',
3330
+ toStage: 'ship',
3331
+ fromAgent: 'execute-manager',
3332
+ toAgent: 'ship-stage-runtime',
3333
+ status: decision.profile === 'blocked' || decision.approvalPolicy === 'blocked' ? 'blocked' : 'proposed',
3334
+ outputRefs: input.outputRefs,
3335
+ requiredInputRefs: input.requiredInputRefs,
3336
+ riskDecisionRef: input.riskDecisionRef,
3337
+ evidenceRefs: input.evidenceRefs,
3338
+ openQuestions: [],
3339
+ blockingGaps: [],
3340
+ createdAt: input.generatedAt
3341
+ };
3342
+ }
3343
+ function buildShipClosureStageRun(scope, runId, workOrder, health, input) {
3344
+ const status = health === 'ship_ready'
3345
+ ? 'completed'
3346
+ : health === 'no-op'
3347
+ ? 'skipped'
3348
+ : 'blocked';
3349
+ return {
3350
+ contract: STAGE_RUN_CONTRACT_VERSION,
3351
+ id: stableId('stage-run-ship', scope, runId, input.generatedAt),
3352
+ scope,
3353
+ stage: 'ship',
3354
+ ownerAgent: workOrder?.stageManager ?? 'runtime',
3355
+ coMainAgents: workOrder?.agentTeam ?? [],
3356
+ status,
3357
+ inputRefs: input.inputRefs,
3358
+ outputRefs: input.outputRefs,
3359
+ decisionRefs: input.decisionRefs,
3360
+ blockingReasons: status === 'blocked' ? [input.rejectionReason ?? `Ship adjudication health is ${health}.`] : [],
3361
+ createdAt: input.generatedAt,
3362
+ updatedAt: input.generatedAt
3363
+ };
3364
+ }
3365
+ function buildStageRejection(stage, scope, closureRequestId, reasonCode, explanation, requiredNextAction, fallbackRoute, generatedAt, retryAllowed) {
3366
+ return {
3367
+ contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
3368
+ rejectionId: stableId(`${stage}-rejection`, scope, closureRequestId ?? reasonCode, generatedAt),
3369
+ stage,
3370
+ scope,
3371
+ closureRequestId,
3372
+ reasonCode,
3373
+ explanation,
3374
+ requiredNextAction,
3375
+ retryAllowed,
3376
+ retryBudgetRemaining: retryAllowed ? 1 : 0,
3377
+ fallbackRoute,
3378
+ createdAt: generatedAt
3379
+ };
3380
+ }
3381
+ function buildStageRuntimeNextActions(stage, scope, generatedAt, reasonCodes) {
3382
+ return reasonCodes
3383
+ .map((reasonCode, index) => ({
3384
+ contract: STAGE_COLLABORATION_RUNTIME_CONTRACT_VERSION,
3385
+ actionId: stableId(`${stage}-next-action`, scope, reasonCode, generatedAt),
3386
+ stage,
3387
+ scope,
3388
+ kind: nextActionKind(reasonCode),
3389
+ owner: nextActionOwner(reasonCode),
3390
+ actionScope: nextActionScope(reasonCode),
3391
+ reasonCode,
3392
+ priority: {
3393
+ reasonRank: reasonCodeRank(reasonCode),
3394
+ upstreamDistance: stage === 'plan' && (reasonCode === 'missing_handoff' || reasonCode === 'stale_upstream') ? 1 : 0,
3395
+ actionRank: actionKindRank(nextActionKind(reasonCode)),
3396
+ stableOrder: index
3397
+ },
3398
+ requiredInputs: nextActionRequiredInputs(reasonCode),
3399
+ blockedBy: [],
3400
+ createdAt: generatedAt
3401
+ }))
3402
+ .sort(compareRuntimeNextAction);
3403
+ }
3404
+ function buildClosureStageRun(profile, adjudication, input) {
3405
+ const status = adjudication.health === 'ready_for_plan'
3406
+ ? 'completed'
3407
+ : adjudication.health === 'no-op'
3408
+ ? 'skipped'
3409
+ : 'blocked';
3410
+ return {
3411
+ contract: STAGE_RUN_CONTRACT_VERSION,
3412
+ id: stableId('stage-run-spec', profile.scope, input.runId, input.generatedAt),
3413
+ scope: profile.scope,
3414
+ stage: 'spec',
3415
+ ownerAgent: profile.stageManager ?? 'runtime',
3416
+ coMainAgents: profile.agentTeam,
3417
+ status,
3418
+ inputRefs: profile.inputRefs,
3419
+ outputRefs: input.outputRefs,
3420
+ decisionRefs: input.decisionRefs,
3421
+ blockingReasons: status === 'blocked' ? stageBlockingReasons(adjudication) : [],
3422
+ createdAt: input.generatedAt,
3423
+ updatedAt: input.generatedAt
3424
+ };
3425
+ }
3426
+ function buildClosureWorkflowHandoff(profile, decision, adjudication, input) {
3427
+ return {
3428
+ contract: WORKFLOW_HANDOFF_CONTRACT_VERSION,
3429
+ id: adjudication.handoffPacket?.handoffId ?? stableId('spec-plan-handoff', profile.scope, 'closure', input.generatedAt),
3430
+ scope: profile.scope,
3431
+ fromStage: 'spec',
3432
+ toStage: 'plan',
3433
+ fromAgent: profile.stageManager ?? 'runtime',
3434
+ toAgent: recommendedPlanRoles(profile)[0] ?? 'plan-stage-runtime',
3435
+ status: decision.profile === 'blocked' || decision.approvalPolicy === 'blocked' ? 'blocked' : 'proposed',
3436
+ outputRefs: input.outputRefs,
3437
+ requiredInputRefs: input.requiredInputRefs,
3438
+ riskDecisionRef: input.riskDecisionRef,
3439
+ evidenceRefs: input.evidenceRefs,
3440
+ openQuestions: [],
3441
+ blockingGaps: [],
3442
+ createdAt: input.generatedAt
3443
+ };
3444
+ }
3445
+ function runLifecycleDecisionRecord(decision) {
3446
+ return {
3447
+ contract: LIFECYCLE_DECISION_CONTRACT,
3448
+ version: LIFECYCLE_DECISION_VERSION,
3449
+ model_version: decision.policyVersion,
3450
+ input_summary: { scope: decision.scope, inputHash: decision.inputHash },
3451
+ decision: {
3452
+ profile: decision.profile,
3453
+ confidence: decision.confidence,
3454
+ hard_gate_hits: decision.blockedStages,
3455
+ required_stages: decision.requiredStages,
3456
+ skipped_stages: decision.skippedStages,
3457
+ human_checkpoint_required: decision.humanCheckpointRequired
3458
+ },
3459
+ reasons: decision.reasons,
3460
+ escalation_triggers: decision.requiredReviews.map((review) => review.id),
3461
+ downgrade_reason: null,
3462
+ audit: {
3463
+ decided_at: decision.generatedAt,
3464
+ decided_by: 'phase9.1-stage-collaboration-runtime',
3465
+ policy_version: decision.policyVersion,
3466
+ source_artifacts: decision.inputRefs.map((ref) => ref.ref)
3467
+ }
3468
+ };
3469
+ }
3470
+ function stageBlockingReasons(adjudication) {
3471
+ if (adjudication.clarificationGate) {
3472
+ return adjudication.clarificationGate.questions;
3473
+ }
3474
+ if (adjudication.rejection) {
3475
+ return [adjudication.rejection.explanation];
3476
+ }
3477
+ return [`Spec adjudication health is ${adjudication.health}.`];
3478
+ }
3479
+ function specArtifactStatusForHealth(health) {
3480
+ if (health === 'no-op') {
3481
+ return 'not_required_noop';
3482
+ }
3483
+ if (health === 'needs_clarification') {
3484
+ return 'not_accepted_needs_clarification';
3485
+ }
3486
+ if (health === 'blocked') {
3487
+ return 'not_accepted_blocked';
3488
+ }
3489
+ return 'not_accepted_rejected';
3490
+ }
3491
+ function isManagedStarterSpec(content) {
3492
+ return /^sdd_managed_starter:\s*true\s*$/m.test(content);
3493
+ }
3494
+ function validateSpecDocumentV3(content) {
3495
+ const errors = [];
3496
+ if (!/contract:\s*sdd-spec-doc-v3\b/.test(content)) {
3497
+ errors.push('missing contract: sdd-spec-doc-v3');
3498
+ }
3499
+ const requiredSections = [
3500
+ 'Problem Reframing / Intent Discovery',
3501
+ 'Change Delta',
3502
+ 'Scope',
3503
+ 'Requirements',
3504
+ 'Acceptance Criteria / Evidence Targets',
3505
+ 'Definitions / Rules',
3506
+ 'Planning Constraints / Signals',
3507
+ 'Open Questions / Ambiguity Ledger',
3508
+ 'Close Quality Evidence'
3509
+ ];
3510
+ for (const section of requiredSections) {
3511
+ const escaped = escapeRegex(section).replace(/\s+/g, '\\s+');
3512
+ if (!new RegExp(`^##\\s+(?:\\d+\\.\\s*)?${escaped}\\s*$`, 'im').test(content)) {
3513
+ errors.push(`missing section: ${section}`);
3514
+ }
3515
+ }
3516
+ const forbiddenSections = [/^##\s+Implementation Plan\s*$/im, /^##\s+Task List\s*$/im, /^##\s+Validation Plan\s*$/im, /^##\s+Runtime Gate Metadata\s*$/im, /^##\s+Subagent Trace\s*$/im];
3517
+ if (forbiddenSections.some((pattern) => pattern.test(content))) {
3518
+ errors.push('contains forbidden implementation/runtime/subagent section');
3519
+ }
3520
+ errors.push(...formatArtifactDepthBlockingIssues(evaluateSpecArtifactDepth(content)));
3521
+ return errors;
3522
+ }
3523
+ function validateSpecDepthSignals(content, errors) {
3524
+ const problem = extractMarkdownSection(content, 'Problem Reframing / Intent Discovery') ?? '';
3525
+ const changeDelta = extractMarkdownSection(content, 'Change Delta') ?? '';
3526
+ const requirements = extractMarkdownSection(content, 'Requirements') ?? '';
3527
+ const acceptance = extractMarkdownSection(content, 'Acceptance Criteria / Evidence Targets') ?? '';
3528
+ const definitions = extractMarkdownSection(content, 'Definitions / Rules') ?? '';
3529
+ const ambiguity = extractMarkdownSection(content, 'Open Questions / Ambiguity Ledger') ?? '';
3530
+ const closeEvidence = extractMarkdownSection(content, 'Close Quality Evidence') ?? '';
3531
+ if (!hasProblemReframingDepth(problem)) {
3532
+ errors.push('spec lacks problem reframing depth in Problem Reframing / Intent Discovery');
3533
+ }
3534
+ if (!hasChangeDeltaDepth(changeDelta)) {
3535
+ errors.push('spec lacks explicit current/target/delta/unchanged/non-goal depth in Change Delta');
3536
+ }
3537
+ if (!hasRequirementReasoningDepth(requirements, definitions)) {
3538
+ errors.push('spec lacks requirement reasoning or domain rule depth');
3539
+ }
3540
+ if (!hasAcceptanceEvidenceDepth(acceptance)) {
3541
+ errors.push('spec lacks acceptance evidence targets tied to requirements');
3542
+ }
3543
+ if (!hasAmbiguityRoutingDepth(ambiguity)) {
3544
+ errors.push('spec lacks explicit ambiguity routing for blocking decisions, researchable gaps, assumptions, or deferred items');
3545
+ }
3546
+ if (!closeEvidence) {
3547
+ errors.push('missing spec close quality evidence content');
3548
+ return;
3549
+ }
3550
+ const requiredEvidence = [
3551
+ ['requirement_review_sufficient', 'spec close quality evidence must declare requirement_review_sufficient: true'],
3552
+ ['problem_reframed', 'spec close quality evidence must declare problem_reframed: true'],
3553
+ ['domain_rules_confirmed_or_routed', 'spec close quality evidence must declare domain_rules_confirmed_or_routed: true'],
3554
+ ['acceptance_evidence_targets_defined', 'spec close quality evidence must declare acceptance_evidence_targets_defined: true'],
3555
+ ['ambiguity_routed', 'spec close quality evidence must declare ambiguity_routed: true'],
3556
+ ['ready_for_plan', 'spec close quality evidence must declare ready_for_plan: true']
3557
+ ];
3558
+ for (const [key, message] of requiredEvidence) {
3559
+ if (!new RegExp(`${key}\\s*:\\s*` + '`?' + 'true' + '`?', 'i').test(closeEvidence)) {
3560
+ errors.push(message);
3561
+ }
3562
+ }
3563
+ if (!/downstream_business_guesswork_remaining\s*:\s*\[\s*\]/i.test(closeEvidence)) {
3564
+ errors.push('spec close quality evidence must declare no downstream_business_guesswork_remaining');
3565
+ }
3566
+ }
3567
+ function hasProblemReframingDepth(section) {
3568
+ return /surface request/i.test(section)
3569
+ && /reframed problem/i.test(section)
3570
+ && /inferred real intent|real intent/i.test(section)
3571
+ && /observable success/i.test(section);
3572
+ }
3573
+ function hasChangeDeltaDepth(section) {
3574
+ return /current behavior/i.test(section)
3575
+ && /target behavior|desired behavior/i.test(section)
3576
+ && /delta/i.test(section)
3577
+ && /unchanged behavior/i.test(section)
3578
+ && /non-goals?/i.test(section);
3579
+ }
3580
+ function hasRequirementReasoningDepth(requirements, definitions) {
3581
+ const hasRequirement = /\bREQ-\d+\b/i.test(requirements);
3582
+ const hasReasoning = /reasoning|basis|source|constraint|because|why|rule|invariant|domain/i.test(`${requirements}\n${definitions}`);
3583
+ return hasRequirement && hasReasoning;
3584
+ }
3585
+ function hasAcceptanceEvidenceDepth(section) {
3586
+ return /\bAC-\d+\b/i.test(section)
3587
+ && /evidence target|api|ui|sql|test|manual|runtime|stage close/i.test(section)
3588
+ && /REQ-\d+/i.test(section);
3589
+ }
3590
+ function hasAmbiguityRoutingDepth(section) {
3591
+ return /blocking user decisions?|blocking before plan|no blocking/i.test(section)
3592
+ && /researchable|resolved by scout|scout|safe assumptions?|assumptions?|deferred/i.test(section);
3593
+ }
3594
+ function validatePlanDocumentV3(content, boundaryFacts) {
3595
+ const errors = [];
3596
+ if (!/contract:\s*sdd-plan-doc-v3\b/.test(content)) {
3597
+ errors.push('missing contract: sdd-plan-doc-v3');
3598
+ }
3599
+ const requiredSections = [
3600
+ 'Metadata',
3601
+ 'Upstream Spec Trace',
3602
+ 'Planning Problem / Strategy Framing',
3603
+ 'Current Implementation Map',
3604
+ 'Target Design Overview',
3605
+ 'Change Topology / Responsibility Boundaries',
3606
+ 'Interface / API / Schema Design',
3607
+ 'State / Data / Concurrency Design',
3608
+ 'Key Design Decisions',
3609
+ 'Alternatives Considered',
3610
+ 'Risk Controls',
3611
+ 'Validation Strategy',
3612
+ 'Rollout / Rollback / Compatibility',
3613
+ 'Task-stage Constraints',
3614
+ 'Open Questions / User Decisions',
3615
+ 'Plan Close Quality Evidence'
3616
+ ];
3617
+ for (const section of requiredSections) {
3618
+ const escaped = escapeRegex(section).replace(/\s+/g, '\\s+');
3619
+ if (!new RegExp(`^##\\s+(?:\\d+(?:\\.\\d+)*\\.?\\s+)?${escaped}\\s*$`, 'im').test(content)) {
3620
+ errors.push(`missing section: ${section}`);
3621
+ }
3622
+ }
3623
+ const forbiddenSections = ['Task Graph', 'Task Units', 'Task Breakdown Rationale', 'Implementation Diff', 'Validation Results', 'Runtime Gate Metadata', 'Subagent Trace'];
3624
+ const hasForbiddenSection = forbiddenSections.some((section) => {
3625
+ const escaped = escapeRegex(section).replace(/\s+/g, '\\s+');
3626
+ return new RegExp(`^##\\s+(?:\\d+(?:\\.\\d+)*\\.?\\s+)?${escaped}\\s*$`, 'im').test(content);
3627
+ });
3628
+ const taskLeakagePatterns = [/```sdd-task\b/i, /^###\s+T\d+\b/im, /^#{3,}\s+Task\s+Units?\b/im, /^\s*sourceRequirements\s*:/im, /^\s*sourceAcceptanceCriteria\s*:/im, /^\s*taskClass\s*:/im, /^\s*suggestedExecutionLane\s*:/im, /^\s*wave\s*:/im, /^\s*dependencies\s*:/im];
3629
+ if (hasForbiddenSection || taskLeakagePatterns.some((pattern) => pattern.test(content))) {
3630
+ errors.push('contains forbidden task/runtime/subagent section');
3631
+ }
3632
+ const closeEvidence = extractMarkdownSection(content, 'Plan Close Quality Evidence');
3633
+ if (!closeEvidence) {
3634
+ errors.push('missing plan close quality evidence content');
3635
+ }
3636
+ else if (/ready_for_tasks\s*:\s*`?false`?/i.test(closeEvidence) || !/ready_for_tasks\s*:\s*`?true`?/i.test(closeEvidence)) {
3637
+ errors.push('plan close quality evidence must declare ready_for_tasks: true');
3638
+ }
3639
+ validatePlanDepthSignals(content, boundaryFacts, errors);
3640
+ errors.push(...formatArtifactDepthBlockingIssues(evaluatePlanArtifactDepth(content)));
3641
+ return errors;
3642
+ }
3643
+ function validatePlanDepthSignals(_content, boundaryFacts, errors) {
3644
+ if (boundaryFacts?.blockingBeforeTasksCount !== undefined && boundaryFacts.blockingBeforeTasksCount !== 0) {
3645
+ errors.push('plan close quality evidence conflicts with runtime blockingBeforeTasksCount');
3646
+ }
3647
+ }
3648
+ function hasConcreteImplementationSurface(section) {
3649
+ if (/scout gap|needs scout|unresolved gap|not applicable|no existing surface/i.test(section)) {
3650
+ return true;
3651
+ }
3652
+ const namedSurfaces = section.match(/\b[A-Z][A-Za-z0-9_]*(?:Controller|Service|ServiceImpl|Mapper|Repository|Entity|DTO|VO|Request|Response|Component|Page|Config|Test)\b/g) ?? [];
3653
+ const fileRefs = section.match(/`[^`]+\.(?:ts|tsx|js|jsx|java|kt|go|py|rs|xml|sql|jsp|vue|md)`/g) ?? [];
3654
+ return namedSurfaces.length + fileRefs.length >= 2 && /reuse|change|do-not-reuse|do not reuse|owner|ownership|convention|boundary|surface/i.test(section);
3655
+ }
3656
+ function hasFieldApiSqlMapping(section) {
3657
+ if (/not applicable|no interface|no api|no schema/i.test(section) && /because|checked|reason/i.test(section)) {
3658
+ return true;
3659
+ }
3660
+ const backtickFields = section.match(/`[a-zA-Z_][\w.]*`/g) ?? [];
3661
+ const identifierFields = section.match(/\b[A-Za-z_][A-Za-z0-9_]*(?:Id|IDs|Hash|Ref|Refs|Count|Status|Type|Key|Keys|At|Path|Command|Query|DTO|VO|Request|Response|Mapper|Controller|Service)\b/g) ?? [];
3662
+ const hasApiShape = /\b(query|request|response|DTO|VO|endpoint|controller|service|mapper|SQL|MyBatis|分页|权限|错误|响应包装)\b/i.test(section);
3663
+ const hasMapping = /\b(source|table|mapper|SQL|field|column|join|group|aggregate|映射|字段|表|聚合|查询参数|返回字段)\b/i.test(section);
3664
+ return backtickFields.length + identifierFields.length >= 6 && hasApiShape && hasMapping;
3665
+ }
3666
+ function hasStateDataFailureDepth(section) {
3667
+ if (/not applicable|no persisted|no state/i.test(section) && /failure|rollback|stale|dirty|because|reason/i.test(section)) {
3668
+ return true;
3669
+ }
3670
+ const hasDataSources = /\b(table|source|asset|state|flow|idempot|transaction|rollback|failure|partial|dirty|stale|performance|limit|join|dedup|grain|表|状态|流程|失败|回滚|脏数据|性能|去重|粒度|资产池)\b/i.test(section);
3671
+ const identifierRules = section.match(/\b[A-Za-z_][A-Za-z0-9_]*(?:Id|Hash|Ref|Status|Type|Key|At|Path|Projection|Handoff|Stage|State|Scope|Owner|Content)\b/g) ?? [];
3672
+ const hasSpecificRules = (section.match(/`[^`]+`/g) ?? []).length >= 5 || identifierRules.length >= 5 || /@startuml|\|.*\|.*\|/m.test(section);
3673
+ const hasFailureOrLimit = /\b(fail|failure|rollback|partial|dirty|stale|limit|timeout|fallback|异常|失败|回滚|部分|脏|限制|超时|降级)\b/i.test(section);
3674
+ return hasDataSources && hasSpecificRules && hasFailureOrLimit;
3675
+ }
3676
+ function hasScenarioValidationMatrix(validationStrategy, riskControls) {
3677
+ const combined = `${validationStrategy}\n${riskControls}`;
3678
+ const hasScenario = /scenario|input data|user action|expected|proves|does not prove|场景|输入|预期|证明/i.test(combined);
3679
+ const hasEvidence = /command|evidence|manual|test|api|ui|sql|hook|artifact|证据|检查/i.test(combined);
3680
+ const hasRiskLink = /risk|impact|control|validation hook|task-stage constraint|风险|影响|控制/i.test(combined);
3681
+ return hasScenario && hasEvidence && hasRiskLink;
3682
+ }
3683
+ function hasTaskStageConstraintDepth(section) {
3684
+ return /boundary|constraint|rollback|ordering|dependency|validation coverage|risky seam|task-stage|atomic|边界|约束|回滚|依赖|验证/i.test(section)
3685
+ && !/```sdd-task\b/i.test(section);
3686
+ }
3687
+ function hasScoutResolutionSignal(implementationMap, openQuestions, closeEvidence) {
3688
+ return /scout|evidence|observed|grounded|resolved|logged|no existing surface|not applicable/i.test(`${implementationMap}\n${openQuestions}\n${closeEvidence}`);
3689
+ }
3690
+ function extractMarkdownSection(content, section) {
3691
+ for (const sectionName of Array.isArray(section) ? section : [section]) {
3692
+ const escaped = escapeRegex(sectionName).replace(/\s+/g, '\\s+');
3693
+ const match = new RegExp(`^##\\s+(?:\\d+(?:\\.\\d+)*\\.?\\s+)?${escaped}\\s*$`, 'im').exec(content);
3694
+ if (!match) {
3695
+ continue;
3696
+ }
3697
+ const start = match.index + match[0].length;
3698
+ const rest = content.slice(start);
3699
+ const next = /^##\s+/im.exec(rest);
3700
+ return (next ? rest.slice(0, next.index) : rest).trim();
3701
+ }
3702
+ return null;
3703
+ }
3704
+ function validateTasksDocumentV2(content, tasksPath = 'tasks.md') {
3705
+ const errors = [];
3706
+ if (!/contract:\s*sdd-tasks-doc-v2\b/.test(content)) {
3707
+ errors.push('missing contract: sdd-tasks-doc-v2');
3708
+ }
3709
+ const requiredSections = [
3710
+ ['Split Basis', 'Task Split Basis', 'Work Unit Split Basis'],
3711
+ ['Execution Order Overview'],
3712
+ ['Implementation Tasks', 'Implementation Work Units', 'Implementation Task Units'],
3713
+ ['Validation Tasks', 'Validation Work Units', 'Validation Task Units'],
3714
+ ['Task Dependencies and Handoff', 'Dependencies and Validation Handoff', 'Task Dependency and Validation Handoff'],
3715
+ ['Coverage Check'],
3716
+ ['Open Execution Questions', 'Open Questions', 'Execution Questions'],
3717
+ ['Tasks Close Quality Evidence', 'Close Quality Evidence']
3718
+ ];
3719
+ for (const sectionAliases of requiredSections) {
3720
+ if (!hasMarkdownSection(content, sectionAliases)) {
3721
+ errors.push(`missing section: ${sectionAliases[0]}`);
3722
+ }
3723
+ }
3724
+ const forbiddenSections = [/^##\s+(?:\d+(?:\.\d+)*\.?\s+)?Implementation Diff\s*$/im, /^##\s+(?:\d+(?:\.\d+)*\.?\s+)?Validation Results\s*$/im, /^##\s+(?:\d+(?:\.\d+)*\.?\s+)?Runtime Gate Metadata\s*$/im, /^##\s+(?:\d+(?:\.\d+)*\.?\s+)?Subagent Trace\s*$/im, /^##\s+(?:\d+(?:\.\d+)*\.?\s+)?Long-Term Backlog\s*$/im];
3725
+ if (forbiddenSections.some((pattern) => pattern.test(content)) || /\b(?:obligationMatrix|projectionPayload|runtimeGateMetadata)\b/i.test(content)) {
3726
+ errors.push('contains forbidden implementation/runtime/subagent section');
3727
+ }
3728
+ const parsed = parseSddTasksMarkdown(content, { tasksPath });
3729
+ if (parsed.tasks.length === 0) {
3730
+ errors.push('missing executable task units');
3731
+ }
3732
+ const taskIds = new Set(parsed.tasks.map((task) => task.id));
3733
+ const taskById = new Map(parsed.tasks.map((task) => [task.id, task]));
3734
+ let hasImplementationTask = false;
3735
+ let hasValidationTask = false;
3736
+ for (const task of parsed.tasks) {
3737
+ const metadata = task.rawMetadata;
3738
+ const taskClass = task.taskClass;
3739
+ const unitType = task.unitType;
3740
+ const dependencies = task.dependsOn;
3741
+ if (taskClass !== 'implementation' && taskClass !== 'validation') {
3742
+ errors.push(`task ${task.id} missing taskClass implementation|validation`);
3743
+ }
3744
+ if (!unitType) {
3745
+ errors.push(`task ${task.id} missing unitType`);
3746
+ }
3747
+ if (!Number.isInteger(task.wave) || (task.wave ?? 0) <= 0) {
3748
+ errors.push(`task ${task.id} missing execution wave`);
3749
+ }
3750
+ if (task.affectedFiles.length === 0) {
3751
+ errors.push(`task ${task.id} missing affected areas`);
3752
+ }
3753
+ for (const dependency of dependencies) {
3754
+ if (!taskIds.has(dependency)) {
3755
+ errors.push(`task ${task.id} references unknown dependency ${dependency}`);
3756
+ }
3757
+ }
3758
+ if (taskClass === 'implementation') {
3759
+ hasImplementationTask = true;
3760
+ const handoffTasks = task.validationHandoff;
3761
+ if (handoffTasks.length === 0) {
3762
+ errors.push(`task ${task.id} missing validation handoff`);
3763
+ }
3764
+ for (const handoffTaskId of handoffTasks) {
3765
+ const handoffTask = taskById.get(handoffTaskId);
3766
+ if (!handoffTask) {
3767
+ errors.push(`task ${task.id} hands off to unknown validation task ${handoffTaskId}`);
3768
+ }
3769
+ else if (handoffTask.taskClass !== 'validation') {
3770
+ errors.push(`task ${task.id} hands off to non-validation task ${handoffTaskId}`);
3771
+ }
3772
+ else if (Number.isInteger(task.wave) && Number.isInteger(handoffTask.wave) && (handoffTask.wave ?? 0) <= (task.wave ?? 0)) {
3773
+ errors.push(`task ${task.id} validation handoff ${handoffTaskId} must be in a later wave`);
3774
+ }
3775
+ }
3776
+ }
3777
+ if (taskClass === 'validation') {
3778
+ hasValidationTask = true;
3779
+ const validatesTasks = metadataNamedListValue(metadata, ['validatesImplementationTasks', 'validates_implementation_tasks']);
3780
+ if (validatesTasks.length === 0) {
3781
+ errors.push(`task ${task.id} missing validatesImplementationTasks`);
3782
+ }
3783
+ for (const validatedTask of validatesTasks) {
3784
+ if (!taskIds.has(validatedTask)) {
3785
+ errors.push(`task ${task.id} validates unknown implementation task ${validatedTask}`);
3786
+ }
3787
+ const validated = taskById.get(validatedTask);
3788
+ if (validated && validated.taskClass !== 'implementation') {
3789
+ errors.push(`task ${task.id} validates non-implementation task ${validatedTask}`);
3790
+ }
3791
+ if (validated && Number.isInteger(task.wave) && Number.isInteger(validated.wave) && (task.wave ?? 0) <= (validated.wave ?? 0)) {
3792
+ errors.push(`task ${task.id} must run after validated implementation task ${validatedTask}`);
3793
+ }
3794
+ if (!dependencies.includes(validatedTask)) {
3795
+ errors.push(`task ${task.id} must depend on validated implementation task ${validatedTask}`);
3796
+ }
3797
+ }
3798
+ }
3799
+ }
3800
+ if (!hasImplementationTask) {
3801
+ errors.push('missing implementation task');
3802
+ }
3803
+ if (!hasValidationTask) {
3804
+ errors.push('missing validation task');
3805
+ }
3806
+ errors.push(...validateTaskValidationTopology(parsed.tasks));
3807
+ validateTasksDepthSignals(content, errors);
3808
+ errors.push(...taskDependencyCycleErrors(parsed.tasks));
3809
+ return errors;
3810
+ }
3811
+ function validateTasksDepthSignals(content, errors) {
3812
+ const closeEvidence = extractMarkdownSection(content, ['Tasks Close Quality Evidence', 'Close Quality Evidence']) ?? '';
3813
+ if (!hasTaskReviewVerdict(closeEvidence) || !hasTaskReviewReconciliation(closeEvidence)) {
3814
+ errors.push('tasks close quality evidence must include task-review-agent verdict and tasks-manager review reconciliation');
3815
+ }
3816
+ if (!/downstream_execution_guesswork_remaining\s*:\s*\[\s*\]/i.test(closeEvidence)) {
3817
+ errors.push('tasks close quality evidence must declare downstream_execution_guesswork_remaining: []');
3818
+ }
3819
+ }
3820
+ function hasTaskReviewVerdict(section) {
3821
+ return /task-review-agent\s+(?:verdict|review)|review(?:_|\s+)signal|reviewed by task-review-agent/i.test(section);
3822
+ }
3823
+ function hasTaskReviewReconciliation(section) {
3824
+ return /review(?:_|\s+)reconciliation|tasks-manager\s+(?:review\s+)?reconciliation|tasks-manager\s+(?:resolved|adjudicated)|review findings? reconciled|blocking findings resolved|task-review-agent findings resolved/i.test(section);
3825
+ }
3826
+ function hasWorkUnitContextDepth(section) {
3827
+ return /why this task exists|work-unit context|plan decision|design part|must not reinterpret|risk/i.test(section);
3828
+ }
3829
+ function hasUpstreamTraceDepth(section) {
3830
+ return /REQ-\d+/i.test(section) && /AC-\d+/i.test(section) && /planRefs|plan section|plan decision|§\d+/i.test(section);
3831
+ }
3832
+ function hasModificationBoundaryDepth(section) {
3833
+ return /allowed files|allowed code|allowed .*changes|forbidden files|forbidden .*scope|what not to do|modification boundary|scope exclusions/i.test(section);
3834
+ }
3835
+ function hasRollbackUnitDepth(implementationTasks, dependencies) {
3836
+ return /rollback unit|reverted together|rollback boundary|shared rollback/i.test(`${implementationTasks}\n${dependencies}`);
3837
+ }
3838
+ function hasDependencyRationaleDepth(dependencies, implementationTasks) {
3839
+ return /because|why ordering matters|dependency rationale|unlocks|must complete before|parallel/i.test(`${dependencies}\n${implementationTasks}`);
3840
+ }
3841
+ function hasCompletionEvidenceDepth(section) {
3842
+ return /completion evidence|done criteria|observable completion|what does not count|code review|basic code sanity/i.test(section);
3843
+ }
3844
+ function hasValidationExpectedResultDepth(section) {
3845
+ return /expected result|pass criteria|evidence is enough|does not prove|acceptance checks|manual inspection|test/i.test(section);
3846
+ }
3847
+ function hasFailureRouteDepth(section) {
3848
+ return /failure routing|failure route|return to|plan repair|spec\/user decision|environment diagnostic|tasks repair/i.test(section);
3849
+ }
3850
+ function hasMarkdownSection(content, sectionAliases) {
3851
+ return sectionAliases.some((section) => {
3852
+ const escaped = escapeRegex(section).replace(/\s+/g, '\\s+');
3853
+ return new RegExp(`^##\\s+(?:\\d+(?:\\.\\d+)*\\.?\\s+)?${escaped}\\s*$`, 'im').test(content);
3854
+ });
3855
+ }
3856
+ function validateTaskValidationTopology(tasks) {
3857
+ const taskIds = new Set(tasks.map((task) => task.id));
3858
+ if (taskIds.size !== tasks.length) {
3859
+ return [];
3860
+ }
3861
+ const taskById = new Map(tasks.map((task) => [task.id, task]));
3862
+ const errors = [];
3863
+ for (const task of tasks) {
3864
+ if (task.taskClass === 'implementation') {
3865
+ if (task.validationHandoff.length !== 1) {
3866
+ errors.push(`task ${task.id} must hand off to exactly one validation task`);
3867
+ continue;
3868
+ }
3869
+ const validationTask = taskById.get(task.validationHandoff[0]);
3870
+ const validatesTasks = validationTask ? metadataNamedListValue(validationTask.rawMetadata, ['validatesImplementationTasks', 'validates_implementation_tasks']) : [];
3871
+ if (validationTask && (validatesTasks.length !== 1 || validatesTasks[0] !== task.id)) {
3872
+ errors.push(`task ${task.id} validation handoff ${validationTask.id} must validate exactly ${task.id}`);
3873
+ }
3874
+ }
3875
+ if (task.taskClass === 'validation') {
3876
+ const validatesTasks = metadataNamedListValue(task.rawMetadata, ['validatesImplementationTasks', 'validates_implementation_tasks']);
3877
+ if (validatesTasks.length !== 1) {
3878
+ errors.push(`task ${task.id} must validate exactly one implementation task`);
3879
+ continue;
3880
+ }
3881
+ const implementationTask = taskById.get(validatesTasks[0]);
3882
+ if (implementationTask?.taskClass === 'implementation' && (implementationTask.validationHandoff.length !== 1 || implementationTask.validationHandoff[0] !== task.id)) {
3883
+ errors.push(`task ${task.id} validates ${implementationTask.id} but ${implementationTask.id} must hand off only to ${task.id}`);
3884
+ }
3885
+ }
3886
+ }
3887
+ return errors;
3888
+ }
3889
+ function taskDependencyCycleErrors(tasks) {
3890
+ const taskIds = new Set(tasks.map((task) => task.id));
3891
+ const dependencies = new Map(tasks.map((task) => [task.id, task.dependsOn.filter((dependency) => taskIds.has(dependency))]));
3892
+ const visiting = new Set();
3893
+ const visited = new Set();
3894
+ const errors = [];
3895
+ const visit = (taskId, path) => {
3896
+ if (visited.has(taskId)) {
3897
+ return;
3898
+ }
3899
+ if (visiting.has(taskId)) {
3900
+ errors.push(`task dependency cycle: ${[...path, taskId].join(' -> ')}`);
3901
+ return;
3902
+ }
3903
+ visiting.add(taskId);
3904
+ for (const dependency of dependencies.get(taskId) ?? []) {
3905
+ visit(dependency, [...path, taskId]);
3906
+ }
3907
+ visiting.delete(taskId);
3908
+ visited.add(taskId);
3909
+ };
3910
+ for (const task of tasks) {
3911
+ visit(task.id, []);
3912
+ }
3913
+ return Array.from(new Set(errors));
3914
+ }
3915
+ function metadataNamedListValue(metadata, names) {
3916
+ for (const name of names) {
3917
+ const value = metadataListValue(metadata[name]);
3918
+ if (value.length > 0) {
3919
+ return value;
3920
+ }
3921
+ }
3922
+ return [];
3923
+ }
3924
+ function metadataNamedScalarValue(metadata, names) {
3925
+ for (const name of names) {
3926
+ const value = metadataScalarValue(metadata[name]);
3927
+ if (value !== null) {
3928
+ return value;
3929
+ }
3930
+ }
3931
+ return null;
3932
+ }
3933
+ function metadataListValue(value) {
3934
+ if (Array.isArray(value)) {
3935
+ return value.map((item) => String(item).trim()).filter((item) => item.length > 0);
3936
+ }
3937
+ const scalar = metadataScalarValue(value);
3938
+ return scalar ? [scalar] : [];
3939
+ }
3940
+ function metadataScalarValue(value) {
3941
+ if (typeof value === 'string') {
3942
+ const trimmed = value.trim();
3943
+ return trimmed.length > 0 ? trimmed : null;
3944
+ }
3945
+ if (typeof value === 'number' || typeof value === 'boolean') {
3946
+ return String(value);
3947
+ }
3948
+ return null;
3949
+ }
3950
+ async function readOptionalText(filePath) {
3951
+ try {
3952
+ return await readFile(filePath, 'utf8');
3953
+ }
3954
+ catch (error) {
3955
+ if (error.code === 'ENOENT') {
3956
+ return null;
3957
+ }
3958
+ throw error;
3959
+ }
3960
+ }
3961
+ function specAdjudicationProjectionRef(scope) {
3962
+ return {
3963
+ kind: 'projection',
3964
+ ref: `${SPEC_COLLABORATION_ADJUDICATION_PROJECTION_TYPE}:${specCollaborationScopeKey(scope)}`
3965
+ };
3966
+ }
3967
+ function planAdjudicationProjectionRef(scope) {
3968
+ return {
3969
+ kind: 'projection',
3970
+ ref: `${PLAN_COLLABORATION_ADJUDICATION_PROJECTION_TYPE}:${planCollaborationScopeKey(scope)}`
3971
+ };
3972
+ }
3973
+ function tasksAdjudicationProjectionRef(scope) {
3974
+ return {
3975
+ kind: 'projection',
3976
+ ref: `${TASKS_COLLABORATION_ADJUDICATION_PROJECTION_TYPE}:${tasksCollaborationScopeKey(scope)}`
3977
+ };
3978
+ }
3979
+ function executeAdjudicationProjectionRef(scope) {
3980
+ return {
3981
+ kind: 'projection',
3982
+ ref: `${EXECUTE_COLLABORATION_ADJUDICATION_PROJECTION_TYPE}:${executeCollaborationScopeKey(scope)}`
3983
+ };
3984
+ }
3985
+ function shipAdjudicationProjectionRef(scope) {
3986
+ return {
3987
+ kind: 'projection',
3988
+ ref: `${SHIP_COLLABORATION_ADJUDICATION_PROJECTION_TYPE}:${shipCollaborationScopeKey(scope)}`
3989
+ };
3990
+ }
3991
+ function stableHash(value) {
3992
+ return createHash('sha256').update(value, 'utf8').digest('hex');
3993
+ }
3994
+ function uniqueStrings(values) {
3995
+ return Array.from(new Set(values.filter((value) => value.length > 0)));
3996
+ }
3997
+ function uniqueRuntimeRefs(refs) {
3998
+ const seen = new Set();
3999
+ return refs.filter((ref) => {
4000
+ const key = `${ref.kind}:${ref.ref}:${ref.hash ?? ''}`;
4001
+ if (seen.has(key)) {
4002
+ return false;
4003
+ }
4004
+ seen.add(key);
4005
+ return true;
4006
+ });
4007
+ }
4008
+ function runtimeProjectionPayload(payload) {
4009
+ if (!payload || typeof payload !== 'object' || !('payload' in payload)) {
4010
+ return null;
4011
+ }
4012
+ return payload.payload;
4013
+ }
4014
+ function stableId(prefix, scope, ...parts) {
4015
+ const hash = createHash('sha256').update(JSON.stringify([scope, parts]), 'utf8').digest('hex').slice(0, 16);
4016
+ return `${prefix}-${hash}`;
4017
+ }
4018
+ //# sourceMappingURL=stage-collaboration.js.map