sdd-agent-platform 0.4.0 → 0.4.1

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 (417) hide show
  1. package/README.md +18 -23
  2. package/node_modules/@sdd-agent-platform/core/dist/ai-tools.js +31 -28
  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.js +3 -2
  5. package/node_modules/@sdd-agent-platform/core/dist/config/init-project.js.map +1 -1
  6. package/node_modules/@sdd-agent-platform/core/dist/config/starter-documents.d.ts +1 -1
  7. package/node_modules/@sdd-agent-platform/core/dist/config/starter-documents.js +14 -5
  8. package/node_modules/@sdd-agent-platform/core/dist/config/starter-documents.js.map +1 -1
  9. package/node_modules/@sdd-agent-platform/core/dist/contracts.d.ts +2 -0
  10. package/node_modules/@sdd-agent-platform/core/dist/contracts.js +2 -0
  11. package/node_modules/@sdd-agent-platform/core/dist/contracts.js.map +1 -1
  12. package/node_modules/@sdd-agent-platform/core/dist/doctor/checks/run-evidence.js +3 -3
  13. package/node_modules/@sdd-agent-platform/core/dist/doctor/checks/run-evidence.js.map +1 -1
  14. package/node_modules/@sdd-agent-platform/core/dist/doctor/doctor.js +155 -1
  15. package/node_modules/@sdd-agent-platform/core/dist/doctor/doctor.js.map +1 -1
  16. package/node_modules/@sdd-agent-platform/core/dist/evidence/lookup.d.ts +23 -0
  17. package/node_modules/@sdd-agent-platform/core/dist/evidence/lookup.js +54 -0
  18. package/node_modules/@sdd-agent-platform/core/dist/evidence/lookup.js.map +1 -0
  19. package/node_modules/@sdd-agent-platform/core/dist/evidence-runtime/contracts.d.ts +11 -0
  20. package/node_modules/@sdd-agent-platform/core/dist/execution/agent-execution-records.js +15 -8
  21. package/node_modules/@sdd-agent-platform/core/dist/execution/agent-execution-records.js.map +1 -1
  22. package/node_modules/@sdd-agent-platform/core/dist/execution/resident-worker.js +14 -6
  23. package/node_modules/@sdd-agent-platform/core/dist/execution/resident-worker.js.map +1 -1
  24. package/node_modules/@sdd-agent-platform/core/dist/execution/stage-team-runtime.d.ts +112 -0
  25. package/node_modules/@sdd-agent-platform/core/dist/execution/stage-team-runtime.js +145 -0
  26. package/node_modules/@sdd-agent-platform/core/dist/execution/stage-team-runtime.js.map +1 -0
  27. package/node_modules/@sdd-agent-platform/core/dist/instructions.js +36 -36
  28. package/node_modules/@sdd-agent-platform/core/dist/instructions.js.map +1 -1
  29. package/node_modules/@sdd-agent-platform/core/dist/lifecycle/ship.d.ts +2 -0
  30. package/node_modules/@sdd-agent-platform/core/dist/lifecycle/ship.js +37 -17
  31. package/node_modules/@sdd-agent-platform/core/dist/lifecycle/ship.js.map +1 -1
  32. package/node_modules/@sdd-agent-platform/core/dist/registries/agent-capability-catalog.d.ts +16 -1
  33. package/node_modules/@sdd-agent-platform/core/dist/registries/agent-capability-catalog.js +174 -16
  34. package/node_modules/@sdd-agent-platform/core/dist/registries/agent-capability-catalog.js.map +1 -1
  35. package/node_modules/@sdd-agent-platform/core/dist/registries/agent-registry.js +2 -2
  36. package/node_modules/@sdd-agent-platform/core/dist/registries/agent-registry.js.map +1 -1
  37. package/node_modules/@sdd-agent-platform/core/dist/registries/agent-runtime-static.d.ts +10 -0
  38. package/node_modules/@sdd-agent-platform/core/dist/registries/agent-runtime-static.js +31 -1
  39. package/node_modules/@sdd-agent-platform/core/dist/registries/agent-runtime-static.js.map +1 -1
  40. package/node_modules/@sdd-agent-platform/core/dist/registries/capability-sources.d.ts +2 -17
  41. package/node_modules/@sdd-agent-platform/core/dist/registries/capability-sources.js +222 -10
  42. package/node_modules/@sdd-agent-platform/core/dist/registries/capability-sources.js.map +1 -1
  43. package/node_modules/@sdd-agent-platform/core/dist/registries/workflow-gates.js +5 -5
  44. package/node_modules/@sdd-agent-platform/core/dist/registries/workflow-gates.js.map +1 -1
  45. package/node_modules/@sdd-agent-platform/core/dist/router/agent-runtime-config.js +27 -12
  46. package/node_modules/@sdd-agent-platform/core/dist/router/agent-runtime-config.js.map +1 -1
  47. package/node_modules/@sdd-agent-platform/core/dist/router/agent-runtime.d.ts +59 -1
  48. package/node_modules/@sdd-agent-platform/core/dist/router/route-projection.d.ts +3 -1
  49. package/node_modules/@sdd-agent-platform/core/dist/router/route-projection.js +191 -0
  50. package/node_modules/@sdd-agent-platform/core/dist/router/route-projection.js.map +1 -1
  51. package/node_modules/@sdd-agent-platform/core/dist/router/routing.js +32 -6
  52. package/node_modules/@sdd-agent-platform/core/dist/router/routing.js.map +1 -1
  53. package/node_modules/@sdd-agent-platform/core/dist/router/runtime-inspection.js +11 -4
  54. package/node_modules/@sdd-agent-platform/core/dist/router/runtime-inspection.js.map +1 -1
  55. package/node_modules/@sdd-agent-platform/core/dist/router/runtime-validation.js +31 -3
  56. package/node_modules/@sdd-agent-platform/core/dist/router/runtime-validation.js.map +1 -1
  57. package/node_modules/@sdd-agent-platform/core/dist/run-state/artifacts.js +48 -15
  58. package/node_modules/@sdd-agent-platform/core/dist/run-state/artifacts.js.map +1 -1
  59. package/node_modules/@sdd-agent-platform/core/dist/run-state/events.js +2 -2
  60. package/node_modules/@sdd-agent-platform/core/dist/run-state/events.js.map +1 -1
  61. package/node_modules/@sdd-agent-platform/core/dist/run-state/inspect-run.d.ts +3 -1
  62. package/node_modules/@sdd-agent-platform/core/dist/run-state/inspect-run.js +15 -49
  63. package/node_modules/@sdd-agent-platform/core/dist/run-state/inspect-run.js.map +1 -1
  64. package/node_modules/@sdd-agent-platform/core/dist/run-state/invocation-ledger.js +2 -2
  65. package/node_modules/@sdd-agent-platform/core/dist/run-state/invocation-ledger.js.map +1 -1
  66. package/node_modules/@sdd-agent-platform/core/dist/run-state/model.d.ts +25 -1
  67. package/node_modules/@sdd-agent-platform/core/dist/run-state/run-state.js +21 -14
  68. package/node_modules/@sdd-agent-platform/core/dist/run-state/run-state.js.map +1 -1
  69. package/node_modules/@sdd-agent-platform/core/dist/run-state/task-evidence.d.ts +62 -0
  70. package/node_modules/@sdd-agent-platform/core/dist/run-state/task-evidence.js +130 -0
  71. package/node_modules/@sdd-agent-platform/core/dist/run-state/task-evidence.js.map +1 -0
  72. package/node_modules/@sdd-agent-platform/core/dist/run-state.d.ts +1 -0
  73. package/node_modules/@sdd-agent-platform/core/dist/run-state.js +1 -0
  74. package/node_modules/@sdd-agent-platform/core/dist/run-state.js.map +1 -1
  75. package/node_modules/@sdd-agent-platform/core/dist/runtime-paths.d.ts +10 -0
  76. package/node_modules/@sdd-agent-platform/core/dist/runtime-paths.js +44 -14
  77. package/node_modules/@sdd-agent-platform/core/dist/runtime-paths.js.map +1 -1
  78. package/node_modules/@sdd-agent-platform/core/dist/sdd-docs/context.js +1 -1
  79. package/node_modules/@sdd-agent-platform/core/dist/sdd-docs/context.js.map +1 -1
  80. package/node_modules/@sdd-agent-platform/core/dist/sdd-docs/document-hashes.d.ts +4 -0
  81. package/node_modules/@sdd-agent-platform/core/dist/sdd-docs/document-hashes.js +189 -0
  82. package/node_modules/@sdd-agent-platform/core/dist/sdd-docs/document-hashes.js.map +1 -0
  83. package/node_modules/@sdd-agent-platform/core/dist/sdd-docs/run-binding.js +12 -3
  84. package/node_modules/@sdd-agent-platform/core/dist/sdd-docs/run-binding.js.map +1 -1
  85. package/node_modules/@sdd-agent-platform/core/dist/sdd-docs/task-parser.d.ts +20 -0
  86. package/node_modules/@sdd-agent-platform/core/dist/sdd-docs/task-parser.js +101 -21
  87. package/node_modules/@sdd-agent-platform/core/dist/sdd-docs/task-parser.js.map +1 -1
  88. package/node_modules/@sdd-agent-platform/core/dist/status/project-status.d.ts +62 -1
  89. package/node_modules/@sdd-agent-platform/core/dist/status/project-status.js +192 -4
  90. package/node_modules/@sdd-agent-platform/core/dist/status/project-status.js.map +1 -1
  91. package/node_modules/@sdd-agent-platform/core/dist/storage/runtime-store.d.ts +195 -2
  92. package/node_modules/@sdd-agent-platform/core/dist/storage/runtime-store.js +499 -2
  93. package/node_modules/@sdd-agent-platform/core/dist/storage/runtime-store.js.map +1 -1
  94. package/node_modules/@sdd-agent-platform/core/dist/sync-back/apply.js +23 -1
  95. package/node_modules/@sdd-agent-platform/core/dist/sync-back/apply.js.map +1 -1
  96. package/node_modules/@sdd-agent-platform/core/dist/sync-back/inspect.d.ts +19 -0
  97. package/node_modules/@sdd-agent-platform/core/dist/sync-back/inspect.js +114 -12
  98. package/node_modules/@sdd-agent-platform/core/dist/sync-back/inspect.js.map +1 -1
  99. package/node_modules/@sdd-agent-platform/core/dist/test-support/fixtures.js +21 -0
  100. package/node_modules/@sdd-agent-platform/core/dist/test-support/fixtures.js.map +1 -1
  101. package/node_modules/@sdd-agent-platform/core/dist/test-support/run-state.js +16 -2
  102. package/node_modules/@sdd-agent-platform/core/dist/test-support/run-state.js.map +1 -1
  103. package/node_modules/@sdd-agent-platform/core/dist/tsconfig.tsbuildinfo +1 -1
  104. package/node_modules/@sdd-agent-platform/core/dist/verification/goal-verify.js +34 -2
  105. package/node_modules/@sdd-agent-platform/core/dist/verification/goal-verify.js.map +1 -1
  106. package/node_modules/@sdd-agent-platform/core/dist/verification/rendering.js +15 -5
  107. package/node_modules/@sdd-agent-platform/core/dist/verification/rendering.js.map +1 -1
  108. package/node_modules/@sdd-agent-platform/core/dist/verification/review-gate.d.ts +22 -0
  109. package/node_modules/@sdd-agent-platform/core/dist/verification/review-gate.js +53 -0
  110. package/node_modules/@sdd-agent-platform/core/dist/verification/review-gate.js.map +1 -0
  111. package/node_modules/@sdd-agent-platform/core/dist/verification/single-task-loop.js +102 -9
  112. package/node_modules/@sdd-agent-platform/core/dist/verification/single-task-loop.js.map +1 -1
  113. package/node_modules/@sdd-agent-platform/core/dist/verification/test-runtime.d.ts +16 -1
  114. package/node_modules/@sdd-agent-platform/core/dist/verification/test-runtime.js +355 -69
  115. package/node_modules/@sdd-agent-platform/core/dist/verification/test-runtime.js.map +1 -1
  116. package/node_modules/@sdd-agent-platform/core/dist/verification/validation-wave.d.ts +58 -0
  117. package/node_modules/@sdd-agent-platform/core/dist/verification/validation-wave.js +428 -0
  118. package/node_modules/@sdd-agent-platform/core/dist/verification/validation-wave.js.map +1 -0
  119. package/node_modules/@sdd-agent-platform/core/dist/verification/verify-contract.d.ts +2 -0
  120. package/node_modules/@sdd-agent-platform/core/dist/verification/verify-contract.js +116 -18
  121. package/node_modules/@sdd-agent-platform/core/dist/verification/verify-contract.js.map +1 -1
  122. package/node_modules/@sdd-agent-platform/core/dist/verification.d.ts +2 -0
  123. package/node_modules/@sdd-agent-platform/core/dist/verification.js +2 -0
  124. package/node_modules/@sdd-agent-platform/core/dist/verification.js.map +1 -1
  125. package/node_modules/@sdd-agent-platform/core/dist/workflow-gate/evidence-packet.d.ts +24 -0
  126. package/node_modules/@sdd-agent-platform/core/dist/workflow-gate/evidence-packet.js +182 -0
  127. package/node_modules/@sdd-agent-platform/core/dist/workflow-gate/evidence-packet.js.map +1 -0
  128. package/node_modules/@sdd-agent-platform/core/dist/workflow-gate/hard-checks.d.ts +4 -0
  129. package/node_modules/@sdd-agent-platform/core/dist/workflow-gate/hard-checks.js +130 -0
  130. package/node_modules/@sdd-agent-platform/core/dist/workflow-gate/hard-checks.js.map +1 -0
  131. package/node_modules/@sdd-agent-platform/core/dist/workflow-gate/policy.d.ts +4 -0
  132. package/node_modules/@sdd-agent-platform/core/dist/workflow-gate/policy.js +146 -0
  133. package/node_modules/@sdd-agent-platform/core/dist/workflow-gate/policy.js.map +1 -0
  134. package/node_modules/@sdd-agent-platform/core/dist/workflow-gate/types.d.ts +89 -0
  135. package/node_modules/@sdd-agent-platform/core/dist/workflow-gate/types.js +2 -0
  136. package/node_modules/@sdd-agent-platform/core/dist/workflow-gate/types.js.map +1 -0
  137. package/node_modules/@sdd-agent-platform/core/dist/workflow-state/affected-file-conflicts.d.ts +1 -0
  138. package/node_modules/@sdd-agent-platform/core/dist/workflow-state/affected-file-conflicts.js +16 -1
  139. package/node_modules/@sdd-agent-platform/core/dist/workflow-state/affected-file-conflicts.js.map +1 -1
  140. package/node_modules/@sdd-agent-platform/core/dist/workflow-state/dependencies.d.ts +8 -4
  141. package/node_modules/@sdd-agent-platform/core/dist/workflow-state/dependencies.js +25 -11
  142. package/node_modules/@sdd-agent-platform/core/dist/workflow-state/dependencies.js.map +1 -1
  143. package/node_modules/@sdd-agent-platform/core/dist/workflow-state/latest-eligible-run.d.ts +38 -0
  144. package/node_modules/@sdd-agent-platform/core/dist/workflow-state/latest-eligible-run.js +122 -0
  145. package/node_modules/@sdd-agent-platform/core/dist/workflow-state/latest-eligible-run.js.map +1 -0
  146. package/node_modules/@sdd-agent-platform/core/dist/workflow-state/resolve.d.ts +27 -0
  147. package/node_modules/@sdd-agent-platform/core/dist/workflow-state/resolve.js +166 -37
  148. package/node_modules/@sdd-agent-platform/core/dist/workflow-state/resolve.js.map +1 -1
  149. package/node_modules/@sdd-agent-platform/core/dist/workflow-state.d.ts +1 -0
  150. package/node_modules/@sdd-agent-platform/core/dist/workflow-state.js +1 -0
  151. package/node_modules/@sdd-agent-platform/core/dist/workflow-state.js.map +1 -1
  152. package/node_modules/@sdd-agent-platform/core/package.json +1 -1
  153. package/node_modules/@sdd-agent-platform/core/src/ai-tools.ts +31 -28
  154. package/node_modules/@sdd-agent-platform/core/src/artifacts/sdd-result.test.ts +50 -4
  155. package/node_modules/@sdd-agent-platform/core/src/config/init-project.test.ts +13 -10
  156. package/node_modules/@sdd-agent-platform/core/src/config/init-project.ts +3 -2
  157. package/node_modules/@sdd-agent-platform/core/src/config/starter-documents.ts +15 -5
  158. package/node_modules/@sdd-agent-platform/core/src/contracts.ts +2 -0
  159. package/node_modules/@sdd-agent-platform/core/src/doctor/checks/run-evidence.ts +3 -3
  160. package/node_modules/@sdd-agent-platform/core/src/doctor/doctor.test.ts +117 -5
  161. package/node_modules/@sdd-agent-platform/core/src/doctor/doctor.ts +164 -1
  162. package/node_modules/@sdd-agent-platform/core/src/evidence/lookup.ts +80 -0
  163. package/node_modules/@sdd-agent-platform/core/src/evidence-runtime/contracts.ts +12 -0
  164. package/node_modules/@sdd-agent-platform/core/src/execution/agent-execution-records.ts +16 -11
  165. package/node_modules/@sdd-agent-platform/core/src/execution/background-executor.test.ts +7 -0
  166. package/node_modules/@sdd-agent-platform/core/src/execution/resident-worker.test.ts +5 -0
  167. package/node_modules/@sdd-agent-platform/core/src/execution/resident-worker.ts +14 -6
  168. package/node_modules/@sdd-agent-platform/core/src/execution/stage-team-runtime.test.ts +102 -0
  169. package/node_modules/@sdd-agent-platform/core/src/execution/stage-team-runtime.ts +271 -0
  170. package/node_modules/@sdd-agent-platform/core/src/execution/wave-executor.test.ts +4 -0
  171. package/node_modules/@sdd-agent-platform/core/src/governance/policy.test.ts +2 -0
  172. package/node_modules/@sdd-agent-platform/core/src/instructions.test.ts +11 -5
  173. package/node_modules/@sdd-agent-platform/core/src/instructions.ts +36 -36
  174. package/node_modules/@sdd-agent-platform/core/src/lifecycle/ship.ts +39 -17
  175. package/node_modules/@sdd-agent-platform/core/src/phase8-contracts.test.ts +3 -2
  176. package/node_modules/@sdd-agent-platform/core/src/phase8-risk-kernel.test.ts +5 -0
  177. package/node_modules/@sdd-agent-platform/core/src/planning/task-graph.test.ts +2 -0
  178. package/node_modules/@sdd-agent-platform/core/src/planning/wave-plan.test.ts +3 -0
  179. package/node_modules/@sdd-agent-platform/core/src/registries/agent-capability-catalog.ts +269 -17
  180. package/node_modules/@sdd-agent-platform/core/src/registries/agent-registry.ts +2 -2
  181. package/node_modules/@sdd-agent-platform/core/src/registries/agent-runtime-static.ts +41 -1
  182. package/node_modules/@sdd-agent-platform/core/src/registries/capability-sources.ts +238 -15
  183. package/node_modules/@sdd-agent-platform/core/src/registries/registries.test.ts +27 -2
  184. package/node_modules/@sdd-agent-platform/core/src/registries/workflow-gates.ts +5 -5
  185. package/node_modules/@sdd-agent-platform/core/src/router/agent-runtime-config.ts +31 -12
  186. package/node_modules/@sdd-agent-platform/core/src/router/agent-runtime.ts +66 -1
  187. package/node_modules/@sdd-agent-platform/core/src/router/route-projection.ts +211 -0
  188. package/node_modules/@sdd-agent-platform/core/src/router/route-sdd-task.test.ts +151 -3
  189. package/node_modules/@sdd-agent-platform/core/src/router/routing.ts +35 -6
  190. package/node_modules/@sdd-agent-platform/core/src/router/runtime-inspection.ts +11 -4
  191. package/node_modules/@sdd-agent-platform/core/src/router/runtime-validation.ts +32 -3
  192. package/node_modules/@sdd-agent-platform/core/src/run-state/artifacts.ts +48 -15
  193. package/node_modules/@sdd-agent-platform/core/src/run-state/events.ts +2 -2
  194. package/node_modules/@sdd-agent-platform/core/src/run-state/inspect-run.ts +17 -52
  195. package/node_modules/@sdd-agent-platform/core/src/run-state/invocation-ledger.ts +2 -2
  196. package/node_modules/@sdd-agent-platform/core/src/run-state/model.ts +28 -1
  197. package/node_modules/@sdd-agent-platform/core/src/run-state/run-state.test.ts +3 -0
  198. package/node_modules/@sdd-agent-platform/core/src/run-state/run-state.ts +22 -18
  199. package/node_modules/@sdd-agent-platform/core/src/run-state/task-evidence.ts +206 -0
  200. package/node_modules/@sdd-agent-platform/core/src/run-state.ts +1 -0
  201. package/node_modules/@sdd-agent-platform/core/src/runtime-paths.ts +54 -14
  202. package/node_modules/@sdd-agent-platform/core/src/sdd-docs/context.ts +1 -1
  203. package/node_modules/@sdd-agent-platform/core/src/sdd-docs/document-hashes.ts +207 -0
  204. package/node_modules/@sdd-agent-platform/core/src/sdd-docs/run-binding.ts +12 -3
  205. package/node_modules/@sdd-agent-platform/core/src/sdd-docs/task-parser.test.ts +139 -0
  206. package/node_modules/@sdd-agent-platform/core/src/sdd-docs/task-parser.ts +137 -24
  207. package/node_modules/@sdd-agent-platform/core/src/status/project-status.ts +268 -5
  208. package/node_modules/@sdd-agent-platform/core/src/storage/runtime-store.test.ts +368 -4
  209. package/node_modules/@sdd-agent-platform/core/src/storage/runtime-store.ts +697 -2
  210. package/node_modules/@sdd-agent-platform/core/src/sync-back/apply.ts +23 -1
  211. package/node_modules/@sdd-agent-platform/core/src/sync-back/inspect.ts +145 -12
  212. package/node_modules/@sdd-agent-platform/core/src/sync-back/sync-back.test.ts +132 -9
  213. package/node_modules/@sdd-agent-platform/core/src/test-support/fixtures.ts +21 -0
  214. package/node_modules/@sdd-agent-platform/core/src/test-support/run-state.ts +16 -2
  215. package/node_modules/@sdd-agent-platform/core/src/verification/goal-verify.test.ts +1 -1
  216. package/node_modules/@sdd-agent-platform/core/src/verification/goal-verify.ts +38 -5
  217. package/node_modules/@sdd-agent-platform/core/src/verification/rendering.ts +15 -5
  218. package/node_modules/@sdd-agent-platform/core/src/verification/review-gate.test.ts +77 -0
  219. package/node_modules/@sdd-agent-platform/core/src/verification/review-gate.ts +77 -0
  220. package/node_modules/@sdd-agent-platform/core/src/verification/single-task-loop.test.ts +64 -4
  221. package/node_modules/@sdd-agent-platform/core/src/verification/single-task-loop.ts +110 -12
  222. package/node_modules/@sdd-agent-platform/core/src/verification/test-runtime.test.ts +72 -25
  223. package/node_modules/@sdd-agent-platform/core/src/verification/test-runtime.ts +402 -77
  224. package/node_modules/@sdd-agent-platform/core/src/verification/validation-wave.test.ts +341 -0
  225. package/node_modules/@sdd-agent-platform/core/src/verification/validation-wave.ts +513 -0
  226. package/node_modules/@sdd-agent-platform/core/src/verification/verify-contract.test.ts +144 -5
  227. package/node_modules/@sdd-agent-platform/core/src/verification/verify-contract.ts +129 -18
  228. package/node_modules/@sdd-agent-platform/core/src/verification.ts +2 -0
  229. package/node_modules/@sdd-agent-platform/core/src/workflow-gate/evidence-packet.ts +196 -0
  230. package/node_modules/@sdd-agent-platform/core/src/workflow-gate/hard-checks.test.ts +171 -0
  231. package/node_modules/@sdd-agent-platform/core/src/workflow-gate/hard-checks.ts +143 -0
  232. package/node_modules/@sdd-agent-platform/core/src/workflow-gate/policy.test.ts +137 -0
  233. package/node_modules/@sdd-agent-platform/core/src/workflow-gate/policy.ts +155 -0
  234. package/node_modules/@sdd-agent-platform/core/src/workflow-gate/types.ts +114 -0
  235. package/node_modules/@sdd-agent-platform/core/src/workflow-state/affected-file-conflicts.ts +18 -1
  236. package/node_modules/@sdd-agent-platform/core/src/workflow-state/dependencies.test.ts +1 -1
  237. package/node_modules/@sdd-agent-platform/core/src/workflow-state/dependencies.ts +33 -11
  238. package/node_modules/@sdd-agent-platform/core/src/workflow-state/latest-eligible-run.ts +156 -0
  239. package/node_modules/@sdd-agent-platform/core/src/workflow-state/resolve.test.ts +351 -2
  240. package/node_modules/@sdd-agent-platform/core/src/workflow-state/resolve.ts +227 -39
  241. package/node_modules/@sdd-agent-platform/core/src/workflow-state.ts +1 -0
  242. package/package.json +1 -1
  243. package/packages/cli/dist/commands/status.js +2 -2
  244. package/packages/cli/dist/commands/status.js.map +1 -1
  245. package/packages/cli/dist/commands/sync-back.js +1 -1
  246. package/packages/cli/dist/commands/sync-back.js.map +1 -1
  247. package/packages/cli/dist/commands/tasks.js +4 -4
  248. package/packages/cli/dist/commands/tasks.js.map +1 -1
  249. package/packages/cli/dist/commands/test.js +94 -5
  250. package/packages/cli/dist/commands/test.js.map +1 -1
  251. package/packages/cli/dist/commands/verifies.js +5 -3
  252. package/packages/cli/dist/commands/verifies.js.map +1 -1
  253. package/packages/cli/dist/commands/verify.js +48 -7
  254. package/packages/cli/dist/commands/verify.js.map +1 -1
  255. package/packages/cli/dist/help.js +32 -18
  256. package/packages/cli/dist/help.js.map +1 -1
  257. package/packages/cli/dist/renderers/artifacts.js +1 -1
  258. package/packages/cli/dist/renderers/artifacts.js.map +1 -1
  259. package/packages/cli/dist/renderers/registry-runtime.js +7 -2
  260. package/packages/cli/dist/renderers/registry-runtime.js.map +1 -1
  261. package/packages/cli/dist/renderers/router.js +4 -2
  262. package/packages/cli/dist/renderers/router.js.map +1 -1
  263. package/packages/cli/dist/renderers/workflow.js +33 -12
  264. package/packages/cli/dist/renderers/workflow.js.map +1 -1
  265. package/packages/cli/dist/tsconfig.tsbuildinfo +1 -1
  266. package/packages/cli/package.json +2 -2
  267. package/packages/core/dist/ai-tools.js +31 -28
  268. package/packages/core/dist/ai-tools.js.map +1 -1
  269. package/packages/core/dist/config/init-project.js +3 -2
  270. package/packages/core/dist/config/init-project.js.map +1 -1
  271. package/packages/core/dist/config/starter-documents.d.ts +1 -1
  272. package/packages/core/dist/config/starter-documents.js +14 -5
  273. package/packages/core/dist/config/starter-documents.js.map +1 -1
  274. package/packages/core/dist/contracts.d.ts +2 -0
  275. package/packages/core/dist/contracts.js +2 -0
  276. package/packages/core/dist/contracts.js.map +1 -1
  277. package/packages/core/dist/doctor/checks/run-evidence.js +3 -3
  278. package/packages/core/dist/doctor/checks/run-evidence.js.map +1 -1
  279. package/packages/core/dist/doctor/doctor.js +155 -1
  280. package/packages/core/dist/doctor/doctor.js.map +1 -1
  281. package/packages/core/dist/evidence/lookup.d.ts +23 -0
  282. package/packages/core/dist/evidence/lookup.js +54 -0
  283. package/packages/core/dist/evidence/lookup.js.map +1 -0
  284. package/packages/core/dist/evidence-runtime/contracts.d.ts +11 -0
  285. package/packages/core/dist/execution/agent-execution-records.js +15 -8
  286. package/packages/core/dist/execution/agent-execution-records.js.map +1 -1
  287. package/packages/core/dist/execution/resident-worker.js +14 -6
  288. package/packages/core/dist/execution/resident-worker.js.map +1 -1
  289. package/packages/core/dist/execution/stage-team-runtime.d.ts +112 -0
  290. package/packages/core/dist/execution/stage-team-runtime.js +145 -0
  291. package/packages/core/dist/execution/stage-team-runtime.js.map +1 -0
  292. package/packages/core/dist/instructions.js +36 -36
  293. package/packages/core/dist/instructions.js.map +1 -1
  294. package/packages/core/dist/lifecycle/ship.d.ts +2 -0
  295. package/packages/core/dist/lifecycle/ship.js +37 -17
  296. package/packages/core/dist/lifecycle/ship.js.map +1 -1
  297. package/packages/core/dist/registries/agent-capability-catalog.d.ts +16 -1
  298. package/packages/core/dist/registries/agent-capability-catalog.js +174 -16
  299. package/packages/core/dist/registries/agent-capability-catalog.js.map +1 -1
  300. package/packages/core/dist/registries/agent-registry.js +2 -2
  301. package/packages/core/dist/registries/agent-registry.js.map +1 -1
  302. package/packages/core/dist/registries/agent-runtime-static.d.ts +10 -0
  303. package/packages/core/dist/registries/agent-runtime-static.js +31 -1
  304. package/packages/core/dist/registries/agent-runtime-static.js.map +1 -1
  305. package/packages/core/dist/registries/capability-sources.d.ts +2 -17
  306. package/packages/core/dist/registries/capability-sources.js +222 -10
  307. package/packages/core/dist/registries/capability-sources.js.map +1 -1
  308. package/packages/core/dist/registries/workflow-gates.js +5 -5
  309. package/packages/core/dist/registries/workflow-gates.js.map +1 -1
  310. package/packages/core/dist/router/agent-runtime-config.js +27 -12
  311. package/packages/core/dist/router/agent-runtime-config.js.map +1 -1
  312. package/packages/core/dist/router/agent-runtime.d.ts +59 -1
  313. package/packages/core/dist/router/route-projection.d.ts +3 -1
  314. package/packages/core/dist/router/route-projection.js +191 -0
  315. package/packages/core/dist/router/route-projection.js.map +1 -1
  316. package/packages/core/dist/router/routing.js +32 -6
  317. package/packages/core/dist/router/routing.js.map +1 -1
  318. package/packages/core/dist/router/runtime-inspection.js +11 -4
  319. package/packages/core/dist/router/runtime-inspection.js.map +1 -1
  320. package/packages/core/dist/router/runtime-validation.js +31 -3
  321. package/packages/core/dist/router/runtime-validation.js.map +1 -1
  322. package/packages/core/dist/run-state/artifacts.js +48 -15
  323. package/packages/core/dist/run-state/artifacts.js.map +1 -1
  324. package/packages/core/dist/run-state/events.js +2 -2
  325. package/packages/core/dist/run-state/events.js.map +1 -1
  326. package/packages/core/dist/run-state/inspect-run.d.ts +3 -1
  327. package/packages/core/dist/run-state/inspect-run.js +15 -49
  328. package/packages/core/dist/run-state/inspect-run.js.map +1 -1
  329. package/packages/core/dist/run-state/invocation-ledger.js +2 -2
  330. package/packages/core/dist/run-state/invocation-ledger.js.map +1 -1
  331. package/packages/core/dist/run-state/model.d.ts +25 -1
  332. package/packages/core/dist/run-state/run-state.js +21 -14
  333. package/packages/core/dist/run-state/run-state.js.map +1 -1
  334. package/packages/core/dist/run-state/task-evidence.d.ts +62 -0
  335. package/packages/core/dist/run-state/task-evidence.js +130 -0
  336. package/packages/core/dist/run-state/task-evidence.js.map +1 -0
  337. package/packages/core/dist/run-state.d.ts +1 -0
  338. package/packages/core/dist/run-state.js +1 -0
  339. package/packages/core/dist/run-state.js.map +1 -1
  340. package/packages/core/dist/runtime-paths.d.ts +10 -0
  341. package/packages/core/dist/runtime-paths.js +44 -14
  342. package/packages/core/dist/runtime-paths.js.map +1 -1
  343. package/packages/core/dist/sdd-docs/context.js +1 -1
  344. package/packages/core/dist/sdd-docs/context.js.map +1 -1
  345. package/packages/core/dist/sdd-docs/document-hashes.d.ts +4 -0
  346. package/packages/core/dist/sdd-docs/document-hashes.js +189 -0
  347. package/packages/core/dist/sdd-docs/document-hashes.js.map +1 -0
  348. package/packages/core/dist/sdd-docs/run-binding.js +12 -3
  349. package/packages/core/dist/sdd-docs/run-binding.js.map +1 -1
  350. package/packages/core/dist/sdd-docs/task-parser.d.ts +20 -0
  351. package/packages/core/dist/sdd-docs/task-parser.js +101 -21
  352. package/packages/core/dist/sdd-docs/task-parser.js.map +1 -1
  353. package/packages/core/dist/status/project-status.d.ts +62 -1
  354. package/packages/core/dist/status/project-status.js +192 -4
  355. package/packages/core/dist/status/project-status.js.map +1 -1
  356. package/packages/core/dist/storage/runtime-store.d.ts +195 -2
  357. package/packages/core/dist/storage/runtime-store.js +499 -2
  358. package/packages/core/dist/storage/runtime-store.js.map +1 -1
  359. package/packages/core/dist/sync-back/apply.js +23 -1
  360. package/packages/core/dist/sync-back/apply.js.map +1 -1
  361. package/packages/core/dist/sync-back/inspect.d.ts +19 -0
  362. package/packages/core/dist/sync-back/inspect.js +114 -12
  363. package/packages/core/dist/sync-back/inspect.js.map +1 -1
  364. package/packages/core/dist/test-support/fixtures.js +21 -0
  365. package/packages/core/dist/test-support/fixtures.js.map +1 -1
  366. package/packages/core/dist/test-support/run-state.js +16 -2
  367. package/packages/core/dist/test-support/run-state.js.map +1 -1
  368. package/packages/core/dist/tsconfig.tsbuildinfo +1 -1
  369. package/packages/core/dist/verification/goal-verify.js +34 -2
  370. package/packages/core/dist/verification/goal-verify.js.map +1 -1
  371. package/packages/core/dist/verification/rendering.js +15 -5
  372. package/packages/core/dist/verification/rendering.js.map +1 -1
  373. package/packages/core/dist/verification/review-gate.d.ts +22 -0
  374. package/packages/core/dist/verification/review-gate.js +53 -0
  375. package/packages/core/dist/verification/review-gate.js.map +1 -0
  376. package/packages/core/dist/verification/single-task-loop.js +102 -9
  377. package/packages/core/dist/verification/single-task-loop.js.map +1 -1
  378. package/packages/core/dist/verification/test-runtime.d.ts +16 -1
  379. package/packages/core/dist/verification/test-runtime.js +355 -69
  380. package/packages/core/dist/verification/test-runtime.js.map +1 -1
  381. package/packages/core/dist/verification/validation-wave.d.ts +58 -0
  382. package/packages/core/dist/verification/validation-wave.js +428 -0
  383. package/packages/core/dist/verification/validation-wave.js.map +1 -0
  384. package/packages/core/dist/verification/verify-contract.d.ts +2 -0
  385. package/packages/core/dist/verification/verify-contract.js +116 -18
  386. package/packages/core/dist/verification/verify-contract.js.map +1 -1
  387. package/packages/core/dist/verification.d.ts +2 -0
  388. package/packages/core/dist/verification.js +2 -0
  389. package/packages/core/dist/verification.js.map +1 -1
  390. package/packages/core/dist/workflow-gate/evidence-packet.d.ts +24 -0
  391. package/packages/core/dist/workflow-gate/evidence-packet.js +182 -0
  392. package/packages/core/dist/workflow-gate/evidence-packet.js.map +1 -0
  393. package/packages/core/dist/workflow-gate/hard-checks.d.ts +4 -0
  394. package/packages/core/dist/workflow-gate/hard-checks.js +130 -0
  395. package/packages/core/dist/workflow-gate/hard-checks.js.map +1 -0
  396. package/packages/core/dist/workflow-gate/policy.d.ts +4 -0
  397. package/packages/core/dist/workflow-gate/policy.js +146 -0
  398. package/packages/core/dist/workflow-gate/policy.js.map +1 -0
  399. package/packages/core/dist/workflow-gate/types.d.ts +89 -0
  400. package/packages/core/dist/workflow-gate/types.js +2 -0
  401. package/packages/core/dist/workflow-gate/types.js.map +1 -0
  402. package/packages/core/dist/workflow-state/affected-file-conflicts.d.ts +1 -0
  403. package/packages/core/dist/workflow-state/affected-file-conflicts.js +16 -1
  404. package/packages/core/dist/workflow-state/affected-file-conflicts.js.map +1 -1
  405. package/packages/core/dist/workflow-state/dependencies.d.ts +8 -4
  406. package/packages/core/dist/workflow-state/dependencies.js +25 -11
  407. package/packages/core/dist/workflow-state/dependencies.js.map +1 -1
  408. package/packages/core/dist/workflow-state/latest-eligible-run.d.ts +38 -0
  409. package/packages/core/dist/workflow-state/latest-eligible-run.js +122 -0
  410. package/packages/core/dist/workflow-state/latest-eligible-run.js.map +1 -0
  411. package/packages/core/dist/workflow-state/resolve.d.ts +27 -0
  412. package/packages/core/dist/workflow-state/resolve.js +166 -37
  413. package/packages/core/dist/workflow-state/resolve.js.map +1 -1
  414. package/packages/core/dist/workflow-state.d.ts +1 -0
  415. package/packages/core/dist/workflow-state.js +1 -0
  416. package/packages/core/dist/workflow-state.js.map +1 -1
  417. package/packages/core/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  import test from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
- import { mkdtemp, rm, writeFile } from 'node:fs/promises';
3
+ import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
4
4
  import { tmpdir } from 'node:os';
5
5
  import path from 'node:path';
6
6
 
@@ -8,17 +8,17 @@ import { validTaskMarkdown, writeBranchDocs } from '../test-support/fixtures.js'
8
8
  import { inspectVerifyContract, writeVerifyContract } from './verify-contract.js';
9
9
 
10
10
 
11
- test('inspectVerifyContract warns when verify document is missing', async () => {
11
+ test('inspectVerifyContract blocks when verify document is missing', async () => {
12
12
  const root = await mkdtemp(path.join(tmpdir(), 'sdd-verify-contract-missing-'));
13
13
  try {
14
14
  await writeBranchDocs(root, 'feature', validTaskMarkdown('T1', []));
15
15
 
16
16
  const inspection = await inspectVerifyContract(root, { branch: 'feature', branchSource: 'cli_option' });
17
17
 
18
- assert.equal(inspection.status, 'WARN');
18
+ assert.equal(inspection.status, 'BLOCKED');
19
19
  assert.equal(inspection.exists, false);
20
20
  assert.equal(inspection.taskCount, 1);
21
- assert.equal(inspection.issues.some((issue) => issue.field === 'verify.md' && issue.level === 'WARN'), true);
21
+ assert.equal(inspection.issues.some((issue) => issue.field === 'verify.md' && issue.level === 'FAIL'), true);
22
22
  } finally {
23
23
  await rm(root, { recursive: true, force: true });
24
24
  }
@@ -34,6 +34,9 @@ test('writeVerifyContract creates task-derived verify contract', async () => {
34
34
 
35
35
  assert.equal(written.status, 'created');
36
36
  assert.match(written.content, /contract: sdd-verify-doc-v1/);
37
+ assert.match(written.content, /verification_batches:/);
38
+ assert.match(written.content, /id: \"task-T1\"/);
39
+ assert.match(written.content, /failure_policy:\n repair_flow: deferred/);
37
40
  assert.match(written.content, /\| T1 \|/);
38
41
  assert.match(written.content, /author_role: verification-designer/);
39
42
  assert.match(written.content, /independent_from_roles:\n - task-planner\n - implementer/);
@@ -47,6 +50,87 @@ test('writeVerifyContract creates task-derived verify contract', async () => {
47
50
  }
48
51
  });
49
52
 
53
+ test('writeVerifyContract groups tasks by validation_batch', async () => {
54
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-verify-contract-batch-'));
55
+ try {
56
+ await writeBranchDocs(root, 'feature', `# Tasks
57
+
58
+ ### T1: UI copy
59
+
60
+ \`\`\`sdd-task
61
+ id: T1
62
+ status: pending
63
+ wave: 1
64
+ depends_on: []
65
+ acceptance_refs:
66
+ - AC-T1-copy
67
+ affected_files:
68
+ - app/src/main/webapp/index.jsp
69
+ change_surface: frontend_only
70
+ implementation_wave: ui-wave-1
71
+ validation_batch: ui-render-wave-1
72
+ validation_timing: batch_end
73
+ requires_verify_before_next: false
74
+ validation:
75
+ - browser/manual check copy => AC-T1-copy
76
+ required_artifacts:
77
+ - artifacts/ui-render-wave-1.md
78
+ risk: []
79
+ \`\`\`
80
+
81
+ #### Boundary
82
+
83
+ Only UI copy.
84
+
85
+ #### Acceptance
86
+
87
+ - Copy renders.
88
+
89
+ ### T2: UI image
90
+
91
+ \`\`\`sdd-task
92
+ id: T2
93
+ status: pending
94
+ wave: 1
95
+ depends_on: []
96
+ acceptance_refs:
97
+ - AC-T2-image
98
+ affected_files:
99
+ - app/src/main/webapp/assets/banner.svg
100
+ change_surface: frontend_only
101
+ implementation_wave: ui-wave-1
102
+ validation_batch: ui-render-wave-1
103
+ validation_timing: batch_end
104
+ requires_verify_before_next: false
105
+ validation:
106
+ - browser/manual check image => AC-T2-image
107
+ required_artifacts:
108
+ - artifacts/ui-render-wave-1.md
109
+ risk: []
110
+ \`\`\`
111
+
112
+ #### Boundary
113
+
114
+ Only UI image.
115
+
116
+ #### Acceptance
117
+
118
+ - Image renders.
119
+ `);
120
+
121
+ const written = await writeVerifyContract(root, { branch: 'feature', branchSource: 'cli_option' });
122
+
123
+ assert.match(written.content, /id: \"ui-render-wave-1\"/);
124
+ assert.match(written.content, /timing: batch_end/);
125
+ assert.match(written.content, /change_surface: frontend_only/);
126
+ assert.match(written.content, /forbidden_validation:\n - \"maven_compile\"\n - \"gradle_build\"/);
127
+ assert.match(written.content, /T1:\n - \"AC-T1-copy\"/);
128
+ assert.match(written.content, /T2:\n - \"AC-T2-image\"/);
129
+ } finally {
130
+ await rm(root, { recursive: true, force: true });
131
+ }
132
+ });
133
+
50
134
  test('inspectVerifyContract warns when verify document is stale', async () => {
51
135
  const root = await mkdtemp(path.join(tmpdir(), 'sdd-verify-contract-stale-'));
52
136
  try {
@@ -57,9 +141,64 @@ test('inspectVerifyContract warns when verify document is stale', async () => {
57
141
  const inspection = await inspectVerifyContract(root, { branch: 'feature', branchSource: 'cli_option' });
58
142
 
59
143
  assert.equal(inspection.status, 'WARN');
60
- assert.equal(inspection.issues.some((issue) => issue.field === 'based_on_tasks_hash'), true);
144
+ assert.equal(inspection.issues.some((issue) => issue.field === 'based_on_tasks_contract_hash'), true);
61
145
  assert.equal(inspection.issues.some((issue) => issue.field === 'tasks' && /T2/.test(issue.message)), true);
62
146
  } finally {
63
147
  await rm(root, { recursive: true, force: true });
64
148
  }
65
149
  });
150
+
151
+ test('inspectVerifyContract blocks legacy verify documents without tasks contract hash', async () => {
152
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-verify-contract-legacy-'));
153
+ try {
154
+ await writeBranchDocs(root, 'feature', validTaskMarkdown('T1', []));
155
+ const written = await writeVerifyContract(root, { branch: 'feature', branchSource: 'cli_option' });
156
+ await writeFile(path.join(root, 'specs', 'feature', 'verify.md'), written.content.replace(/^based_on_tasks_contract_hash: .+\n/m, ''), 'utf8');
157
+
158
+ const inspection = await inspectVerifyContract(root, { branch: 'feature', branchSource: 'cli_option' });
159
+
160
+ assert.equal(inspection.status, 'BLOCKED');
161
+ assert.equal(inspection.issues.some((issue) => issue.field === 'based_on_tasks_contract_hash' && issue.level === 'FAIL'), true);
162
+ } finally {
163
+ await rm(root, { recursive: true, force: true });
164
+ }
165
+ });
166
+
167
+ test('writeVerifyContract upgrades legacy verify documents without force', async () => {
168
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-verify-contract-upgrade-'));
169
+ try {
170
+ await writeBranchDocs(root, 'feature', validTaskMarkdown('T1', []));
171
+ const written = await writeVerifyContract(root, { branch: 'feature', branchSource: 'cli_option' });
172
+ await writeFile(path.join(root, 'specs', 'feature', 'verify.md'), written.content.replace(/^based_on_tasks_contract_hash: .+\n/m, ''), 'utf8');
173
+
174
+ const upgraded = await writeVerifyContract(root, { branch: 'feature', branchSource: 'cli_option' });
175
+ const inspection = await inspectVerifyContract(root, { branch: 'feature', branchSource: 'cli_option' });
176
+
177
+ assert.equal(upgraded.status, 'updated');
178
+ assert.match(upgraded.content, /based_on_tasks_contract_hash: /);
179
+ assert.equal(inspection.status, 'PASS');
180
+ } finally {
181
+ await rm(root, { recursive: true, force: true });
182
+ }
183
+ });
184
+
185
+ test('writeVerifyContract preserves stale bound verify documents without force', async () => {
186
+ const root = await mkdtemp(path.join(tmpdir(), 'sdd-verify-contract-preserve-stale-'));
187
+ try {
188
+ await writeBranchDocs(root, 'feature', validTaskMarkdown('T1', []));
189
+ const written = await writeVerifyContract(root, { branch: 'feature', branchSource: 'cli_option' });
190
+ const verifyPath = path.join(root, 'specs', 'feature', 'verify.md');
191
+ const initial = await readFile(verifyPath, 'utf8');
192
+ await writeFile(path.join(root, 'specs', 'feature', 'tasks.md'), validTaskMarkdown('T2', []), 'utf8');
193
+
194
+ const preserved = await writeVerifyContract(root, { branch: 'feature', branchSource: 'cli_option' });
195
+ const inspection = await inspectVerifyContract(root, { branch: 'feature', branchSource: 'cli_option' });
196
+
197
+ assert.equal(written.status, 'created');
198
+ assert.equal(preserved.status, 'unchanged');
199
+ assert.equal(preserved.content, initial);
200
+ assert.equal(inspection.status, 'WARN');
201
+ } finally {
202
+ await rm(root, { recursive: true, force: true });
203
+ }
204
+ });
@@ -27,6 +27,8 @@ export interface VerifyContractInspection {
27
27
  status: VerifyContractStatus;
28
28
  basedOnTasksHash: string | null;
29
29
  currentTasksHash: string | null;
30
+ basedOnTasksContractHash: string | null;
31
+ currentTasksContractHash: string | null;
30
32
  issues: VerifyContractIssue[];
31
33
  taskCount: number;
32
34
  authorRole: VerifyContractAuthorRole | null;
@@ -58,10 +60,10 @@ export async function inspectVerifyContract(projectRoot: string, options: { bran
58
60
 
59
61
  if (!model.documents.verifyExists || raw === null) {
60
62
  issues.push({
61
- level: 'WARN',
63
+ level: 'FAIL',
62
64
  field: 'verify.md',
63
65
  message: 'Verification contract document is missing.',
64
- action: `Run sdd verifies write --branch ${context.partition} to create specs/${context.partition}/verify.md.`
66
+ action: `Run /sdd:tasks or sdd verifies write --branch ${context.partition} to create specs/${context.partition}/verify.md before /sdd:test.`
65
67
  });
66
68
  } else {
67
69
  if (!raw.includes(`contract: ${VERIFY_DOCUMENT_CONTRACT_VERSION}`)) {
@@ -72,12 +74,23 @@ export async function inspectVerifyContract(projectRoot: string, options: { bran
72
74
  action: 'Regenerate verify.md with sdd verifies write or update the frontmatter contract.'
73
75
  });
74
76
  }
77
+ if (!model.documents.verifyBasedOnTasksContractHash) {
78
+ issues.push({
79
+ level: 'FAIL',
80
+ field: 'based_on_tasks_contract_hash',
81
+ message: 'verify.md does not bind to the frozen tasks contract hash.',
82
+ action: `Run /sdd:tasks or sdd verifies write --branch ${context.partition} --force to regenerate verify.md from the current tasks contract.`
83
+ });
84
+ }
85
+
75
86
  if (model.documents.verifyStale) {
76
87
  issues.push({
77
- level: 'WARN',
78
- field: 'based_on_tasks_hash',
79
- message: `verify.md is stale for current tasks hash ${model.documents.tasksHash}.`,
80
- action: `Run sdd verifies write --branch ${context.partition} --force after reviewing task contract changes.`
88
+ level: model.documents.verifyBasedOnTasksContractHash ? 'WARN' : 'FAIL',
89
+ field: 'based_on_tasks_contract_hash',
90
+ message: model.documents.verifyBasedOnTasksContractHash
91
+ ? `verify.md is stale for current tasks contract hash ${model.documents.tasksContractHash}.`
92
+ : 'verify.md cannot be proven current because based_on_tasks_contract_hash is missing.',
93
+ action: `Run /sdd:tasks or sdd verifies write --branch ${context.partition} --force after reviewing task contract changes.`
81
94
  });
82
95
  }
83
96
  const authorRole = readDocumentScalar(raw, 'author_role');
@@ -86,7 +99,7 @@ export async function inspectVerifyContract(projectRoot: string, options: { bran
86
99
  level: 'WARN',
87
100
  field: 'author_role',
88
101
  message: `verify.md must be owned by ${VERIFY_CONTRACT_AUTHOR_ROLE}, not ${authorRole ?? 'missing'}.`,
89
- action: 'Regenerate verify.md with a verification-designer role before running /sdd:test.'
102
+ action: 'Regenerate verify.md with a verification-designer role before running sdd test task.'
90
103
  });
91
104
  }
92
105
  const independentFromRoles = readDocumentList(raw, 'independent_from_roles');
@@ -100,6 +113,14 @@ export async function inspectVerifyContract(projectRoot: string, options: { bran
100
113
  });
101
114
  }
102
115
  }
116
+ if (!/^verification_batches:\s*$/m.test(raw)) {
117
+ issues.push({
118
+ level: 'FAIL',
119
+ field: 'verification_batches',
120
+ message: 'verify.md does not declare verification_batches from the frozen tasks contract.',
121
+ action: `Run /sdd:tasks or sdd verifies write --branch ${context.partition} --force to regenerate verify.md as a batch/wave validation plan.`
122
+ });
123
+ }
103
124
  for (const task of model.tasks) {
104
125
  if (!new RegExp(`\\b${escapeRegex(task.id)}\\b`).test(raw)) {
105
126
  issues.push({
@@ -120,6 +141,8 @@ export async function inspectVerifyContract(projectRoot: string, options: { bran
120
141
  status: issues.some((issue) => issue.level === 'FAIL') ? 'BLOCKED' : issues.length > 0 ? 'WARN' : 'PASS',
121
142
  basedOnTasksHash: model.documents.verifyBasedOnTasksHash ?? null,
122
143
  currentTasksHash: model.documents.tasksHash ?? null,
144
+ basedOnTasksContractHash: model.documents.verifyBasedOnTasksContractHash ?? null,
145
+ currentTasksContractHash: model.documents.tasksContractHash ?? null,
123
146
  issues,
124
147
  taskCount: model.tasks.length,
125
148
  authorRole: raw ? readDocumentScalar(raw, 'author_role') as VerifyContractAuthorRole | null : null,
@@ -134,13 +157,18 @@ export async function writeVerifyContract(projectRoot: string, options: { branch
134
157
  const existed = await exists(model.verifyPath);
135
158
  if (existed && !options.force) {
136
159
  const current = await readFile(model.verifyPath, 'utf8');
137
- return {
138
- branch: context.partition,
139
- verifyPath: model.verifyPath,
140
- relativePath: `specs/${context.partition}/verify.md`,
141
- status: current === content ? 'unchanged' : 'unchanged',
142
- content: current
143
- };
160
+ const currentTasksContractHash = readDocumentScalar(current, 'based_on_tasks_contract_hash');
161
+ const hasCurrentBatchPlan = /^verification_batches:\s*$/m.test(current);
162
+ const hasContractBinding = Boolean(currentTasksContractHash && currentTasksContractHash !== 'missing');
163
+ if (current === content || (hasContractBinding && hasCurrentBatchPlan)) {
164
+ return {
165
+ branch: context.partition,
166
+ verifyPath: model.verifyPath,
167
+ relativePath: `specs/${context.partition}/verify.md`,
168
+ status: 'unchanged',
169
+ content: current
170
+ };
171
+ }
144
172
  }
145
173
  await mkdir(path.dirname(model.verifyPath), { recursive: true });
146
174
  await writeFile(model.verifyPath, content, 'utf8');
@@ -158,6 +186,7 @@ export function renderVerifyContractDocument(model: SddTaskModel, timestamp: str
158
186
  contract: ${VERIFY_DOCUMENT_CONTRACT_VERSION}
159
187
  version: 1.0.0
160
188
  branch: ${model.branch}
189
+ based_on_tasks_contract_hash: ${model.documents.tasksContractHash ?? 'missing'}
161
190
  based_on_tasks_hash: ${model.documents.tasksHash ?? 'missing'}
162
191
  author_role: ${VERIFY_CONTRACT_AUTHOR_ROLE}
163
192
  independent_from_roles:
@@ -172,13 +201,18 @@ updated_at: ${timestamp}
172
201
 
173
202
  This document maps executable SDD tasks to verification expectations. It is derived from specs/${model.branch}/tasks.md and is not runtime evidence. It is owned by the verification-designer role and must remain independent from task planning and implementation authority.
174
203
 
175
- ## 2. Task Verification Matrix
204
+ ## 2. Verification Batches
205
+
206
+ verification_batches:
207
+ ${model.tasks.length > 0 ? renderVerificationBatches(model.tasks) : ' []'}
208
+
209
+ ## 3. Task Verification Matrix
176
210
 
177
211
  | Task | Acceptance refs | Validation commands | Required artifacts | Verification availability |
178
212
  |---|---|---|---|---|
179
213
  ${model.tasks.length > 0 ? model.tasks.map(renderTaskMatrixRow).join('\n') : '| none | none | none | none | none |'}
180
214
 
181
- ## 3. Verification Rules
215
+ ## 4. Verification Rules
182
216
 
183
217
  - The agent that creates tasks.md must not be the same authority that owns verify.md.
184
218
  - The implementer must not own verify.md or perform authoritative goal verification.
@@ -186,15 +220,92 @@ ${model.tasks.length > 0 ? model.tasks.map(renderTaskMatrixRow).join('\n') : '|
186
220
  - Physical evidence files live under branch evidence .sdd/runs/<branchSlug>/evidence/artifacts/.
187
221
  - Goal-level verify must resolve the latest eligible run by branch and task unless --run is explicitly supplied for replay.
188
222
  - PASS requires policy-backed acceptance evidence, not mention-only acceptance text.
189
- - Sync-back must inspect the generated proposal before applying task status changes.
223
+ - \`/sdd:test\` consumes this contract and must not create or refresh it.
224
+ - Low-level sync-back may inspect or replay the generated proposal for diagnostic/recovery use, but normal workflow completion proceeds through test judgment and ship readiness.
190
225
 
191
- ## 4. Out of Scope
226
+ ## 5. Out of Scope
192
227
 
193
228
  - This document does not replace runtime.sqlite, branch evidence artifacts, validator reports, or sync-back proposals.
194
229
  - This document does not authorize publish, push, tag, release, or source changes outside the selected task boundary.
195
230
  `;
196
231
  }
197
232
 
233
+ interface VerificationBatch {
234
+ id: string;
235
+ tasks: SddTask[];
236
+ }
237
+
238
+ function renderVerificationBatches(tasks: SddTask[]): string {
239
+ const batches = new Map<string, VerificationBatch>();
240
+ for (const task of tasks) {
241
+ const id = task.validationBatch ?? `task-${task.id}`;
242
+ const existing = batches.get(id);
243
+ if (existing) {
244
+ existing.tasks.push(task);
245
+ } else {
246
+ batches.set(id, { id, tasks: [task] });
247
+ }
248
+ }
249
+ return Array.from(batches.values()).map(renderVerificationBatch).join('\n');
250
+ }
251
+
252
+ function renderVerificationBatch(batch: VerificationBatch): string {
253
+ const timing = batch.tasks.some((task) => task.validationTiming === 'wave_end') ? 'wave_end' : batch.tasks.some((task) => task.validationTiming === 'batch_end') ? 'batch_end' : 'task_end';
254
+ const changeSurface = unique(batch.tasks.map((task) => task.changeSurface)).length === 1 ? batch.tasks[0].changeSurface : 'mixed';
255
+ const requiredArtifacts = unique(batch.tasks.flatMap((task) => task.requiredArtifacts));
256
+ const forbiddenValidation = changeSurface === 'frontend_only' ? ['maven_compile', 'gradle_build'] : [];
257
+ return [
258
+ ` - id: ${quoteYaml(batch.id)}`,
259
+ ` timing: ${timing}`,
260
+ ` tasks:`,
261
+ ...batch.tasks.map((task) => ` - ${quoteYaml(task.id)}`),
262
+ ` change_surface: ${changeSurface}`,
263
+ ` checks:`,
264
+ ...renderBatchChecks(batch.tasks),
265
+ ` forbidden_validation:${renderInlineOrNestedList(forbiddenValidation, 6)}`,
266
+ ` required_artifacts:${renderInlineOrNestedList(requiredArtifacts, 6)}`,
267
+ ` acceptance_mapping:`,
268
+ ...renderAcceptanceMapping(batch.tasks),
269
+ ` failure_policy:`,
270
+ ` repair_flow: deferred`,
271
+ ` rerun_scope: pending_design`
272
+ ].join('\n');
273
+ }
274
+
275
+ function renderBatchChecks(tasks: SddTask[]): string[] {
276
+ const checks = unique(tasks.flatMap((task) => task.validation));
277
+ return checks.length > 0 ? checks.map((check) => ` - ${quoteYaml(check)}`) : [' []'];
278
+ }
279
+
280
+ function renderAcceptanceMapping(tasks: SddTask[]): string[] {
281
+ const lines: string[] = [];
282
+ for (const task of tasks) {
283
+ lines.push(` ${task.id}:`);
284
+ if (task.acceptanceRefs.length > 0) {
285
+ lines.push(...task.acceptanceRefs.map((ref) => ` - ${quoteYaml(ref)}`));
286
+ } else {
287
+ lines.push(' []');
288
+ }
289
+ }
290
+ return lines;
291
+ }
292
+
293
+ function renderInlineOrNestedList(values: string[], indent: number): string {
294
+ if (values.length === 0) {
295
+ return ' []';
296
+ }
297
+ const spaces = ' '.repeat(indent);
298
+ return `\n${values.map((value) => `${spaces}- ${quoteYaml(value)}`).join('\n')}`;
299
+ }
300
+
301
+ function unique(values: string[]): string[] {
302
+ return Array.from(new Set(values.filter(Boolean)));
303
+ }
304
+
305
+ function quoteYaml(value: string): string {
306
+ return JSON.stringify(value);
307
+ }
308
+
198
309
  function renderTaskMatrixRow(task: SddTask): string {
199
310
  return `| ${task.id} | ${cell(task.acceptanceRefs)} | ${cell(task.validation)} | ${cell(task.requiredArtifacts)} | ${cell(task.verificationAvailability)} |`;
200
311
  }
@@ -4,3 +4,5 @@ export type { GoalVerifyResultLike, SingleTaskLoopResultLike } from './verificat
4
4
  export * from './verification/single-task-loop.js';
5
5
  export * from './verification/verify-contract.js';
6
6
  export * from './verification/test-runtime.js';
7
+ export * from './verification/validation-wave.js';
8
+ export * from './verification/review-gate.js';
@@ -0,0 +1,196 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { WORKFLOW_GATE_RUNTIME_CONTRACT_VERSION } from '../contracts.js';
4
+ import { listRuntimeArtifactPayloads, listRuntimeDurableGaps, listRuntimeFanInEvidenceSets, listRuntimeLlmAdvisorAssessments, listRuntimeSyncBackDecisions, recordRuntimeWorkflowGateDecision, runtimeScopedId, type RuntimeArtifactPayloadRecord } from '../storage/runtime-store.js';
5
+ import { getEvidenceAttachmentPath } from '../runtime-paths.js';
6
+ import { readAllRunStates } from '../run-state/run-state.js';
7
+ import { affectedFileConflictsForSelectedRuns } from '../workflow-state/affected-file-conflicts.js';
8
+ import { resolveWorkflowState } from '../workflow-state/resolve.js';
9
+ import { evaluateWorkflowGateHardChecks } from './hard-checks.js';
10
+ import { synthesizeWorkflowGateDecision } from './policy.js';
11
+ import type { WorkflowGateDecision, WorkflowGateDecisionKind, WorkflowGateEvidencePacket } from './types.js';
12
+
13
+ export async function buildWorkflowGateEvidencePacket(projectRoot: string, input: { branch?: string | null; taskId?: string | null; runId?: string | null; decisionKind: WorkflowGateDecisionKind; waveRunId?: string | null; taskIds?: string[]; runIds?: string[]; evidenceRefs?: string[] }): Promise<WorkflowGateEvidencePacket> {
14
+ const workflow = await resolveWorkflowState(projectRoot, { branch: input.branch, branchSource: input.branch ? 'cli_option' : undefined, taskId: input.taskId ?? null });
15
+ const taskId = input.taskId ?? workflow.latestRun?.taskId ?? workflow.model.tasks[0]?.id ?? null;
16
+ const selection = taskId ? workflow.latestEligibleRunsByTask.find((candidate) => candidate.taskId === taskId) ?? null : null;
17
+ const runId = input.runId ?? selection?.selected?.runId ?? workflow.latestRun?.runId ?? null;
18
+ const scopedTaskIds = sortedUnique(input.taskIds ?? (taskId ? [taskId] : []));
19
+ const scopedLatestEligibleSelections = scopedTaskIds.length > 0
20
+ ? scopedTaskIds.map((scopedTaskId) => workflow.latestEligibleRunsByTask.find((candidate) => candidate.taskId === scopedTaskId) ?? emptyLatestEligibleSelection(workflow.branch, scopedTaskId))
21
+ : (selection ? [selection] : []);
22
+ const scopedRunIds = sortedUnique(input.runIds ?? scopedLatestEligibleSelections.flatMap((candidate) => candidate.selected ? [candidate.selected.runId] : []));
23
+ const workflowScope = {
24
+ waveRunId: input.waveRunId ?? null,
25
+ taskIds: scopedTaskIds,
26
+ runIds: scopedRunIds.length > 0 ? scopedRunIds : sortedUnique(runId ? [runId] : []),
27
+ evidenceRefs: sortedUnique(input.evidenceRefs ?? [])
28
+ };
29
+ const scopedTasks = workflowScope.taskIds.length > 0
30
+ ? workflow.model.tasks.filter((candidate) => workflowScope.taskIds.includes(candidate.id))
31
+ : workflow.model.tasks.filter((candidate) => candidate.id === taskId);
32
+ const runtimeInputs = await loadRuntimeGateInputs(projectRoot, workflow.branch, workflowScope.taskIds, workflowScope.runIds);
33
+ const scopedAffectedFileConflicts = await loadScopedAffectedFileConflicts(projectRoot, workflow.affectedFileConflicts, workflowScope.runIds);
34
+ const packetBase = {
35
+ packetId: runtimeScopedId(WORKFLOW_GATE_RUNTIME_CONTRACT_VERSION, workflow.branch, taskId ?? 'none', runId ?? 'none', input.decisionKind),
36
+ generatedAt: new Date().toISOString(),
37
+ decisionKind: input.decisionKind,
38
+ partition: workflow.branch,
39
+ taskId,
40
+ runId,
41
+ documents: workflow.documents,
42
+ latestEligibleSelection: selection,
43
+ latestEligibleSelections: scopedLatestEligibleSelections,
44
+ workflowScope,
45
+ runtimeHealth: runtimeInputs.runtimeHealth,
46
+ durableGaps: runtimeInputs.durableGaps,
47
+ dependencyBlockers: workflow.dependencyBlockers,
48
+ affectedFileConflicts: scopedAffectedFileConflicts,
49
+ syncBackDecision: runtimeInputs.syncBackDecisions[0] ?? null,
50
+ syncBackDecisions: runtimeInputs.syncBackDecisions,
51
+ fanInEvidenceSets: runtimeInputs.fanInEvidenceSets,
52
+ advisorAssessments: runtimeInputs.advisorAssessments,
53
+ canonicalEvidenceRefs: sortedUnique([...workflowScope.evidenceRefs, ...runtimeInputs.payloads.filter((payload) => payload.status === 'active').map((payload) => payload.logicalRef)]),
54
+ requiredArtifactRefs: sortedUnique(scopedTasks.flatMap((task) => task.requiredArtifacts)),
55
+ artifactPayloadHealth: await inspectArtifactPayloadHealth(projectRoot, runtimeInputs.payloads.filter((payload) => payload.status === 'active'))
56
+ };
57
+ return {
58
+ ...packetBase,
59
+ hardChecks: evaluateWorkflowGateHardChecks(packetBase)
60
+ };
61
+ }
62
+
63
+ function emptyLatestEligibleSelection(partition: string, taskId: string): WorkflowGateEvidencePacket['latestEligibleSelections'][number] {
64
+ return {
65
+ partition,
66
+ taskId,
67
+ selected: null,
68
+ candidates: [],
69
+ rejected: []
70
+ };
71
+ }
72
+
73
+ async function loadScopedAffectedFileConflicts(projectRoot: string, fallback: WorkflowGateEvidencePacket['affectedFileConflicts'], runIds: string[]): Promise<WorkflowGateEvidencePacket['affectedFileConflicts']> {
74
+ if (runIds.length === 0) {
75
+ return fallback;
76
+ }
77
+ const states = await readAllRunStates(projectRoot);
78
+ const selectedRuns = states.filter((state) => runIds.includes(state.runId));
79
+ return affectedFileConflictsForSelectedRuns(states, selectedRuns);
80
+ }
81
+
82
+ async function loadRuntimeGateInputs(projectRoot: string, partition: string, taskIds: string[], runIds: string[]): Promise<{
83
+ runtimeHealth: WorkflowGateEvidencePacket['runtimeHealth'];
84
+ durableGaps: WorkflowGateEvidencePacket['durableGaps'];
85
+ syncBackDecisions: NonNullable<WorkflowGateEvidencePacket['syncBackDecision']>[];
86
+ fanInEvidenceSets: WorkflowGateEvidencePacket['fanInEvidenceSets'];
87
+ advisorAssessments: WorkflowGateEvidencePacket['advisorAssessments'];
88
+ payloads: RuntimeArtifactPayloadRecord[];
89
+ }> {
90
+ try {
91
+ const runtimeScopes = scopedRuntimeQueries(taskIds, runIds);
92
+ const [durableGapResults, syncBackDecisionResults, fanInEvidenceSetResults, advisorAssessmentResults, payloadResults] = await Promise.all([
93
+ Promise.all(runtimeScopes.map((scope) => listRuntimeDurableGaps(projectRoot, { partition, taskId: scope.taskId, runId: scope.runId, status: 'open_terminal' }))),
94
+ Promise.all(runIds.map((scopedRunId) => listRuntimeSyncBackDecisions(projectRoot, scopedRunId))),
95
+ Promise.all(runtimeScopes.map((scope) => listRuntimeFanInEvidenceSets(projectRoot, { partition, taskId: scope.taskId, runId: scope.runId }))),
96
+ Promise.all(runtimeScopes.map((scope) => listRuntimeLlmAdvisorAssessments(projectRoot, { partition, taskId: scope.taskId, runId: scope.runId }))),
97
+ Promise.all(runIds.map((scopedRunId) => listRuntimeArtifactPayloads(projectRoot, { runId: scopedRunId })))
98
+ ]);
99
+ return {
100
+ runtimeHealth: { status: 'ok', issues: [] },
101
+ durableGaps: uniqueBy(durableGapResults.flat(), (gap) => gap.gapId),
102
+ syncBackDecisions: uniqueBy(syncBackDecisionResults.flat(), (decision) => decision.decisionId),
103
+ fanInEvidenceSets: uniqueBy(fanInEvidenceSetResults.flat(), (evidenceSet) => evidenceSet.fanInId),
104
+ advisorAssessments: uniqueBy(advisorAssessmentResults.flat(), (assessment) => assessment.assessmentId),
105
+ payloads: uniqueBy(payloadResults.flat(), (payload) => payload.payloadId)
106
+ };
107
+ } catch (error) {
108
+ return {
109
+ runtimeHealth: { status: 'unavailable', issues: [messageFromError(error)] },
110
+ durableGaps: [],
111
+ syncBackDecisions: [],
112
+ fanInEvidenceSets: [],
113
+ advisorAssessments: [],
114
+ payloads: []
115
+ };
116
+ }
117
+ }
118
+
119
+ function scopedRuntimeQueries(taskIds: string[], runIds: string[]): Array<{ taskId: string | null; runId: string | null }> {
120
+ if (taskIds.length === 0 && runIds.length === 0) {
121
+ return [{ taskId: null, runId: null }];
122
+ }
123
+ if (taskIds.length === 0) {
124
+ return runIds.map((scopedRunId) => ({ taskId: null, runId: scopedRunId }));
125
+ }
126
+ if (runIds.length === 0) {
127
+ return taskIds.map((scopedTaskId) => ({ taskId: scopedTaskId, runId: null }));
128
+ }
129
+ return uniqueBy([
130
+ ...taskIds.flatMap((scopedTaskId) => runIds.map((scopedRunId) => ({ taskId: scopedTaskId, runId: scopedRunId }))),
131
+ ...taskIds.map((scopedTaskId) => ({ taskId: scopedTaskId, runId: null })),
132
+ ...runIds.map((scopedRunId) => ({ taskId: null, runId: scopedRunId }))
133
+ ], (scope) => `${scope.taskId ?? 'none'}:${scope.runId ?? 'none'}`);
134
+ }
135
+
136
+ function uniqueBy<T>(items: T[], keyOf: (item: T) => string): T[] {
137
+ const seen = new Set<string>();
138
+ return items.filter((item) => {
139
+ const key = keyOf(item);
140
+ if (seen.has(key)) {
141
+ return false;
142
+ }
143
+ seen.add(key);
144
+ return true;
145
+ });
146
+ }
147
+
148
+ async function inspectArtifactPayloadHealth(projectRoot: string, payloads: RuntimeArtifactPayloadRecord[]): Promise<WorkflowGateEvidencePacket['artifactPayloadHealth']> {
149
+ return Promise.all(payloads.map(async (payload) => {
150
+ try {
151
+ const raw = await readFile(getEvidenceAttachmentPath(projectRoot, payload.branchSlug, payload.physicalPayloadPath), 'utf8');
152
+ const actualDigest = hashDocumentContent(raw);
153
+ return {
154
+ payloadId: payload.payloadId,
155
+ logicalRef: payload.logicalRef,
156
+ physicalPayloadPath: payload.physicalPayloadPath,
157
+ expectedDigest: payload.digest,
158
+ actualDigest,
159
+ status: actualDigest === payload.digest ? 'ok' as const : 'digest_drift' as const
160
+ };
161
+ } catch {
162
+ return {
163
+ payloadId: payload.payloadId,
164
+ logicalRef: payload.logicalRef,
165
+ physicalPayloadPath: payload.physicalPayloadPath,
166
+ expectedDigest: payload.digest,
167
+ actualDigest: null,
168
+ status: 'missing' as const
169
+ };
170
+ }
171
+ }));
172
+ }
173
+
174
+ function hashDocumentContent(raw: string): string {
175
+ return createHash('sha256').update(raw.replace(/\r\n/g, '\n'), 'utf8').digest('hex');
176
+ }
177
+
178
+ export async function evaluateAndRecordWorkflowGateDecision(projectRoot: string, input: { branch?: string | null; taskId?: string | null; runId?: string | null; decisionKind: WorkflowGateDecisionKind; waveRunId?: string | null; taskIds?: string[]; runIds?: string[]; evidenceRefs?: string[] }): Promise<{ packet: WorkflowGateEvidencePacket; decision: WorkflowGateDecision }> {
179
+ const packet = await buildWorkflowGateEvidencePacket(projectRoot, input);
180
+ const decision = synthesizeWorkflowGateDecision(packet);
181
+ try {
182
+ await recordRuntimeWorkflowGateDecision(projectRoot, decision);
183
+ } catch (error) {
184
+ if (packet.runtimeHealth.status === 'ok') {
185
+ throw error;
186
+ }
187
+ }
188
+ return { packet, decision };
189
+ }
190
+ function sortedUnique(values: string[]): string[] {
191
+ return [...new Set(values.filter(Boolean))].sort();
192
+ }
193
+
194
+ function messageFromError(error: unknown): string {
195
+ return error instanceof Error ? error.message : String(error);
196
+ }