@hunter-harness/workflow-harness 0.2.32 → 0.2.34

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 (267) hide show
  1. package/README.md +25 -25
  2. package/harness/bundles/general/claude-code/.harness-build.json +1 -1
  3. package/harness/bundles/general/claude-code/contracts/workflow-policy.json +1 -0
  4. package/harness/bundles/general/claude-code/harness-archive/SKILL.md +1 -1
  5. package/harness/bundles/general/claude-code/harness-archive/templates/render-summary.mjs +215 -106
  6. package/harness/bundles/general/claude-code/harness-archive/templates/summary-data-template.json +8 -0
  7. package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +1 -1
  8. package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  9. package/harness/bundles/general/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  10. package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
  11. package/harness/bundles/general/claude-code/harness-plan/SKILL.md +4 -4
  12. package/harness/bundles/general/claude-code/harness-plan/checklist.md +2 -0
  13. package/harness/bundles/general/claude-code/harness-plan/reference.md +6 -0
  14. package/harness/bundles/general/claude-code/harness-review/SKILL.md +2 -2
  15. package/harness/bundles/general/claude-code/harness-run/SKILL.md +4 -4
  16. package/harness/bundles/general/claude-code/harness-submit/SKILL.md +2 -2
  17. package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
  18. package/harness/bundles/general/claude-code/harness-test/SKILL.md +4 -4
  19. package/harness/bundles/general/claude-code/harness-test/checklist.md +1 -0
  20. package/harness/bundles/general/claude-code/harness-test/reference.md +2 -1
  21. package/harness/bundles/general/claude-code/protocols/ledger-protocol.md +2 -2
  22. package/harness/bundles/general/claude-code/scripts/harness_archive.py +404 -12
  23. package/harness/bundles/general/claude-code/scripts/harness_context.py +717 -0
  24. package/harness/bundles/general/claude-code/scripts/harness_gate.py +1 -0
  25. package/harness/bundles/general/claude-code/scripts/harness_ledger.py +77 -9
  26. package/harness/bundles/general/claude-code/scripts/harness_phase.py +1 -0
  27. package/harness/bundles/general/claude-code/scripts/harness_plan_aggregate.py +249 -0
  28. package/harness/bundles/general/claude-code/scripts/harness_plan_finalize.py +43 -11
  29. package/harness/bundles/general/claude-code/scripts/harness_profile.py +272 -15
  30. package/harness/bundles/general/claude-code/scripts/harness_report_model.py +420 -0
  31. package/harness/bundles/general/claude-code/scripts/harness_review.py +16 -1
  32. package/harness/bundles/general/claude-code/scripts/harness_verification.py +114 -0
  33. package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
  34. package/harness/bundles/general/codebuddy/contracts/workflow-policy.json +1 -0
  35. package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +1 -1
  36. package/harness/bundles/general/codebuddy/harness-archive/templates/render-summary.mjs +215 -106
  37. package/harness/bundles/general/codebuddy/harness-archive/templates/summary-data-template.json +8 -0
  38. package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
  39. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  40. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  41. package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  42. package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +4 -4
  43. package/harness/bundles/general/codebuddy/harness-plan/checklist.md +2 -0
  44. package/harness/bundles/general/codebuddy/harness-plan/reference.md +6 -0
  45. package/harness/bundles/general/codebuddy/harness-review/SKILL.md +2 -2
  46. package/harness/bundles/general/codebuddy/harness-run/SKILL.md +4 -4
  47. package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +2 -2
  48. package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
  49. package/harness/bundles/general/codebuddy/harness-test/SKILL.md +4 -4
  50. package/harness/bundles/general/codebuddy/harness-test/checklist.md +1 -0
  51. package/harness/bundles/general/codebuddy/harness-test/reference.md +2 -1
  52. package/harness/bundles/general/codebuddy/protocols/ledger-protocol.md +2 -2
  53. package/harness/bundles/general/codebuddy/scripts/harness_archive.py +404 -12
  54. package/harness/bundles/general/codebuddy/scripts/harness_context.py +717 -0
  55. package/harness/bundles/general/codebuddy/scripts/harness_gate.py +1 -0
  56. package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +77 -9
  57. package/harness/bundles/general/codebuddy/scripts/harness_phase.py +1 -0
  58. package/harness/bundles/general/codebuddy/scripts/harness_plan_aggregate.py +249 -0
  59. package/harness/bundles/general/codebuddy/scripts/harness_plan_finalize.py +43 -11
  60. package/harness/bundles/general/codebuddy/scripts/harness_profile.py +272 -15
  61. package/harness/bundles/general/codebuddy/scripts/harness_report_model.py +420 -0
  62. package/harness/bundles/general/codebuddy/scripts/harness_review.py +16 -1
  63. package/harness/bundles/general/codebuddy/scripts/harness_verification.py +114 -0
  64. package/harness/bundles/general/codex/.harness-build.json +1 -1
  65. package/harness/bundles/general/codex/contracts/workflow-policy.json +1 -0
  66. package/harness/bundles/general/codex/harness-archive/SKILL.md +1 -1
  67. package/harness/bundles/general/codex/harness-archive/templates/render-summary.mjs +215 -106
  68. package/harness/bundles/general/codex/harness-archive/templates/summary-data-template.json +8 -0
  69. package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
  70. package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +1 -1
  71. package/harness/bundles/general/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  72. package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
  73. package/harness/bundles/general/codex/harness-plan/SKILL.md +4 -4
  74. package/harness/bundles/general/codex/harness-plan/checklist.md +2 -0
  75. package/harness/bundles/general/codex/harness-plan/reference.md +6 -0
  76. package/harness/bundles/general/codex/harness-review/SKILL.md +2 -2
  77. package/harness/bundles/general/codex/harness-run/SKILL.md +4 -4
  78. package/harness/bundles/general/codex/harness-submit/SKILL.md +2 -2
  79. package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
  80. package/harness/bundles/general/codex/harness-test/SKILL.md +4 -4
  81. package/harness/bundles/general/codex/harness-test/checklist.md +1 -0
  82. package/harness/bundles/general/codex/harness-test/reference.md +2 -1
  83. package/harness/bundles/general/codex/protocols/ledger-protocol.md +2 -2
  84. package/harness/bundles/general/codex/scripts/harness_archive.py +404 -12
  85. package/harness/bundles/general/codex/scripts/harness_context.py +717 -0
  86. package/harness/bundles/general/codex/scripts/harness_gate.py +1 -0
  87. package/harness/bundles/general/codex/scripts/harness_ledger.py +77 -9
  88. package/harness/bundles/general/codex/scripts/harness_phase.py +1 -0
  89. package/harness/bundles/general/codex/scripts/harness_plan_aggregate.py +249 -0
  90. package/harness/bundles/general/codex/scripts/harness_plan_finalize.py +43 -11
  91. package/harness/bundles/general/codex/scripts/harness_profile.py +272 -15
  92. package/harness/bundles/general/codex/scripts/harness_report_model.py +420 -0
  93. package/harness/bundles/general/codex/scripts/harness_review.py +16 -1
  94. package/harness/bundles/general/codex/scripts/harness_verification.py +114 -0
  95. package/harness/bundles/general/cursor/.harness-build.json +1 -1
  96. package/harness/bundles/general/cursor/contracts/workflow-policy.json +1 -0
  97. package/harness/bundles/general/cursor/harness-archive/SKILL.md +1 -1
  98. package/harness/bundles/general/cursor/harness-archive/templates/render-summary.mjs +215 -106
  99. package/harness/bundles/general/cursor/harness-archive/templates/summary-data-template.json +8 -0
  100. package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
  101. package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  102. package/harness/bundles/general/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  103. package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
  104. package/harness/bundles/general/cursor/harness-plan/SKILL.md +4 -4
  105. package/harness/bundles/general/cursor/harness-plan/checklist.md +2 -0
  106. package/harness/bundles/general/cursor/harness-plan/reference.md +6 -0
  107. package/harness/bundles/general/cursor/harness-review/SKILL.md +2 -2
  108. package/harness/bundles/general/cursor/harness-run/SKILL.md +4 -4
  109. package/harness/bundles/general/cursor/harness-submit/SKILL.md +2 -2
  110. package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
  111. package/harness/bundles/general/cursor/harness-test/SKILL.md +4 -4
  112. package/harness/bundles/general/cursor/harness-test/checklist.md +1 -0
  113. package/harness/bundles/general/cursor/harness-test/reference.md +2 -1
  114. package/harness/bundles/general/cursor/protocols/ledger-protocol.md +2 -2
  115. package/harness/bundles/general/cursor/scripts/harness_archive.py +404 -12
  116. package/harness/bundles/general/cursor/scripts/harness_context.py +717 -0
  117. package/harness/bundles/general/cursor/scripts/harness_gate.py +1 -0
  118. package/harness/bundles/general/cursor/scripts/harness_ledger.py +77 -9
  119. package/harness/bundles/general/cursor/scripts/harness_phase.py +1 -0
  120. package/harness/bundles/general/cursor/scripts/harness_plan_aggregate.py +249 -0
  121. package/harness/bundles/general/cursor/scripts/harness_plan_finalize.py +43 -11
  122. package/harness/bundles/general/cursor/scripts/harness_profile.py +272 -15
  123. package/harness/bundles/general/cursor/scripts/harness_report_model.py +420 -0
  124. package/harness/bundles/general/cursor/scripts/harness_review.py +16 -1
  125. package/harness/bundles/general/cursor/scripts/harness_verification.py +114 -0
  126. package/harness/bundles/java/claude-code/.harness-build.json +1 -1
  127. package/harness/bundles/java/claude-code/contracts/workflow-policy.json +1 -0
  128. package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
  129. package/harness/bundles/java/claude-code/harness-archive/SKILL.md +1 -1
  130. package/harness/bundles/java/claude-code/harness-archive/templates/render-summary.mjs +215 -106
  131. package/harness/bundles/java/claude-code/harness-archive/templates/summary-data-template.json +8 -0
  132. package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
  133. package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  134. package/harness/bundles/java/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  135. package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
  136. package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
  137. package/harness/bundles/java/claude-code/harness-plan/SKILL.md +4 -4
  138. package/harness/bundles/java/claude-code/harness-plan/checklist.md +2 -0
  139. package/harness/bundles/java/claude-code/harness-plan/reference.md +6 -0
  140. package/harness/bundles/java/claude-code/harness-review/SKILL.md +2 -2
  141. package/harness/bundles/java/claude-code/harness-run/SKILL.md +4 -4
  142. package/harness/bundles/java/claude-code/harness-submit/SKILL.md +2 -2
  143. package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
  144. package/harness/bundles/java/claude-code/harness-test/SKILL.md +4 -4
  145. package/harness/bundles/java/claude-code/harness-test/checklist.md +1 -0
  146. package/harness/bundles/java/claude-code/harness-test/reference.md +2 -0
  147. package/harness/bundles/java/claude-code/protocols/ledger-protocol.md +2 -2
  148. package/harness/bundles/java/claude-code/scripts/harness_archive.py +404 -12
  149. package/harness/bundles/java/claude-code/scripts/harness_context.py +717 -0
  150. package/harness/bundles/java/claude-code/scripts/harness_gate.py +1 -0
  151. package/harness/bundles/java/claude-code/scripts/harness_ledger.py +77 -9
  152. package/harness/bundles/java/claude-code/scripts/harness_phase.py +1 -0
  153. package/harness/bundles/java/claude-code/scripts/harness_plan_aggregate.py +249 -0
  154. package/harness/bundles/java/claude-code/scripts/harness_plan_finalize.py +43 -11
  155. package/harness/bundles/java/claude-code/scripts/harness_profile.py +272 -15
  156. package/harness/bundles/java/claude-code/scripts/harness_report_model.py +420 -0
  157. package/harness/bundles/java/claude-code/scripts/harness_review.py +16 -1
  158. package/harness/bundles/java/claude-code/scripts/harness_verification.py +114 -0
  159. package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
  160. package/harness/bundles/java/codebuddy/contracts/workflow-policy.json +1 -0
  161. package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
  162. package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +1 -1
  163. package/harness/bundles/java/codebuddy/harness-archive/templates/render-summary.mjs +215 -106
  164. package/harness/bundles/java/codebuddy/harness-archive/templates/summary-data-template.json +8 -0
  165. package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
  166. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  167. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  168. package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  169. package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
  170. package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +4 -4
  171. package/harness/bundles/java/codebuddy/harness-plan/checklist.md +2 -0
  172. package/harness/bundles/java/codebuddy/harness-plan/reference.md +6 -0
  173. package/harness/bundles/java/codebuddy/harness-review/SKILL.md +2 -2
  174. package/harness/bundles/java/codebuddy/harness-run/SKILL.md +4 -4
  175. package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +2 -2
  176. package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
  177. package/harness/bundles/java/codebuddy/harness-test/SKILL.md +4 -4
  178. package/harness/bundles/java/codebuddy/harness-test/checklist.md +1 -0
  179. package/harness/bundles/java/codebuddy/harness-test/reference.md +2 -0
  180. package/harness/bundles/java/codebuddy/protocols/ledger-protocol.md +2 -2
  181. package/harness/bundles/java/codebuddy/scripts/harness_archive.py +404 -12
  182. package/harness/bundles/java/codebuddy/scripts/harness_context.py +717 -0
  183. package/harness/bundles/java/codebuddy/scripts/harness_gate.py +1 -0
  184. package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +77 -9
  185. package/harness/bundles/java/codebuddy/scripts/harness_phase.py +1 -0
  186. package/harness/bundles/java/codebuddy/scripts/harness_plan_aggregate.py +249 -0
  187. package/harness/bundles/java/codebuddy/scripts/harness_plan_finalize.py +43 -11
  188. package/harness/bundles/java/codebuddy/scripts/harness_profile.py +272 -15
  189. package/harness/bundles/java/codebuddy/scripts/harness_report_model.py +420 -0
  190. package/harness/bundles/java/codebuddy/scripts/harness_review.py +16 -1
  191. package/harness/bundles/java/codebuddy/scripts/harness_verification.py +114 -0
  192. package/harness/bundles/java/codex/.harness-build.json +1 -1
  193. package/harness/bundles/java/codex/contracts/workflow-policy.json +1 -0
  194. package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
  195. package/harness/bundles/java/codex/harness-archive/SKILL.md +1 -1
  196. package/harness/bundles/java/codex/harness-archive/templates/render-summary.mjs +215 -106
  197. package/harness/bundles/java/codex/harness-archive/templates/summary-data-template.json +8 -0
  198. package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
  199. package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
  200. package/harness/bundles/java/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  201. package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
  202. package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
  203. package/harness/bundles/java/codex/harness-plan/SKILL.md +4 -4
  204. package/harness/bundles/java/codex/harness-plan/checklist.md +2 -0
  205. package/harness/bundles/java/codex/harness-plan/reference.md +6 -0
  206. package/harness/bundles/java/codex/harness-review/SKILL.md +2 -2
  207. package/harness/bundles/java/codex/harness-run/SKILL.md +4 -4
  208. package/harness/bundles/java/codex/harness-submit/SKILL.md +2 -2
  209. package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
  210. package/harness/bundles/java/codex/harness-test/SKILL.md +4 -4
  211. package/harness/bundles/java/codex/harness-test/checklist.md +1 -0
  212. package/harness/bundles/java/codex/harness-test/reference.md +2 -0
  213. package/harness/bundles/java/codex/protocols/ledger-protocol.md +2 -2
  214. package/harness/bundles/java/codex/scripts/harness_archive.py +404 -12
  215. package/harness/bundles/java/codex/scripts/harness_context.py +717 -0
  216. package/harness/bundles/java/codex/scripts/harness_gate.py +1 -0
  217. package/harness/bundles/java/codex/scripts/harness_ledger.py +77 -9
  218. package/harness/bundles/java/codex/scripts/harness_phase.py +1 -0
  219. package/harness/bundles/java/codex/scripts/harness_plan_aggregate.py +249 -0
  220. package/harness/bundles/java/codex/scripts/harness_plan_finalize.py +43 -11
  221. package/harness/bundles/java/codex/scripts/harness_profile.py +272 -15
  222. package/harness/bundles/java/codex/scripts/harness_report_model.py +420 -0
  223. package/harness/bundles/java/codex/scripts/harness_review.py +16 -1
  224. package/harness/bundles/java/codex/scripts/harness_verification.py +114 -0
  225. package/harness/bundles/java/cursor/.harness-build.json +1 -1
  226. package/harness/bundles/java/cursor/contracts/workflow-policy.json +1 -0
  227. package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
  228. package/harness/bundles/java/cursor/harness-archive/SKILL.md +1 -1
  229. package/harness/bundles/java/cursor/harness-archive/templates/render-summary.mjs +215 -106
  230. package/harness/bundles/java/cursor/harness-archive/templates/summary-data-template.json +8 -0
  231. package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
  232. package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  233. package/harness/bundles/java/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  234. package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
  235. package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
  236. package/harness/bundles/java/cursor/harness-plan/SKILL.md +4 -4
  237. package/harness/bundles/java/cursor/harness-plan/checklist.md +2 -0
  238. package/harness/bundles/java/cursor/harness-plan/reference.md +6 -0
  239. package/harness/bundles/java/cursor/harness-review/SKILL.md +2 -2
  240. package/harness/bundles/java/cursor/harness-run/SKILL.md +4 -4
  241. package/harness/bundles/java/cursor/harness-submit/SKILL.md +2 -2
  242. package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
  243. package/harness/bundles/java/cursor/harness-test/SKILL.md +4 -4
  244. package/harness/bundles/java/cursor/harness-test/checklist.md +1 -0
  245. package/harness/bundles/java/cursor/harness-test/reference.md +2 -0
  246. package/harness/bundles/java/cursor/protocols/ledger-protocol.md +2 -2
  247. package/harness/bundles/java/cursor/scripts/harness_archive.py +404 -12
  248. package/harness/bundles/java/cursor/scripts/harness_context.py +717 -0
  249. package/harness/bundles/java/cursor/scripts/harness_gate.py +1 -0
  250. package/harness/bundles/java/cursor/scripts/harness_ledger.py +77 -9
  251. package/harness/bundles/java/cursor/scripts/harness_phase.py +1 -0
  252. package/harness/bundles/java/cursor/scripts/harness_plan_aggregate.py +249 -0
  253. package/harness/bundles/java/cursor/scripts/harness_plan_finalize.py +43 -11
  254. package/harness/bundles/java/cursor/scripts/harness_profile.py +272 -15
  255. package/harness/bundles/java/cursor/scripts/harness_report_model.py +420 -0
  256. package/harness/bundles/java/cursor/scripts/harness_review.py +16 -1
  257. package/harness/bundles/java/cursor/scripts/harness_verification.py +114 -0
  258. package/harness/manifests/general/claude-code.json +45 -29
  259. package/harness/manifests/general/codebuddy.json +45 -29
  260. package/harness/manifests/general/codex.json +45 -29
  261. package/harness/manifests/general/cursor.json +45 -29
  262. package/harness/manifests/java/claude-code.json +47 -31
  263. package/harness/manifests/java/codebuddy.json +47 -31
  264. package/harness/manifests/java/codex.json +47 -31
  265. package/harness/manifests/java/cursor.json +47 -31
  266. package/hunter-workflow-family.json +4 -4
  267. package/package.json +1 -1
@@ -0,0 +1,420 @@
1
+ #!/usr/bin/env python3
2
+ """Normalized report facts shared by archive collection and rendering."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import copy
7
+ import datetime as dt
8
+ from typing import Any, Iterable, Mapping
9
+
10
+ REPORT_MODEL_SCHEMA_VERSION = 1
11
+ UNKNOWN_VALUES = frozenset({"", "unknown", "not_available", "n/a", "null"})
12
+ TERMINAL_STATUSES = frozenset(
13
+ {
14
+ "OK",
15
+ "PASS",
16
+ "PASSED",
17
+ "WARN",
18
+ "FAIL",
19
+ "FAILED",
20
+ "ERROR",
21
+ "BLOCKED",
22
+ "BLOCKED_BY_ENV",
23
+ "BLOCKED_BY_DBA",
24
+ "NOT_RUN",
25
+ "SKIPPED",
26
+ "USER_SKIPPED",
27
+ "NOT_APPLICABLE",
28
+ "CANCELLED",
29
+ "TIMED_OUT",
30
+ "TERMINATED",
31
+ }
32
+ )
33
+ IDENTITY_FIELDS = (
34
+ "repositoryId",
35
+ "changeName",
36
+ "baseCommit",
37
+ "productCommit",
38
+ "featureMergeHash",
39
+ "releaseTipHash",
40
+ "productTreeHash",
41
+ "environmentHash",
42
+ "candidateId",
43
+ )
44
+ CONTENT_HASH_FIELDS = frozenset({"productTreeHash", "environmentHash"})
45
+ TARGET_IDENTITY_FIELDS = (
46
+ "target",
47
+ "repositoryId",
48
+ "changeName",
49
+ "productCommit",
50
+ "productTreeHash",
51
+ "environmentHash",
52
+ "profile",
53
+ "candidateId",
54
+ )
55
+ CURRENT_DISPOSITIONS = frozenset(
56
+ {"OPEN", "ACCEPTED_RISK", "DEFERRED", "UNKNOWN"}
57
+ )
58
+
59
+
60
+ def measurement(
61
+ value: Any = None,
62
+ *,
63
+ applicable: bool | None = True,
64
+ reason: str | None = None,
65
+ ) -> dict[str, Any]:
66
+ if isinstance(value, Mapping) and value.get("state") in {
67
+ "unknown",
68
+ "not_applicable",
69
+ "zero",
70
+ "known",
71
+ }:
72
+ return copy.deepcopy(dict(value))
73
+ if applicable is False:
74
+ result = {"state": "not_applicable", "value": None}
75
+ elif value is None or (
76
+ isinstance(value, str) and value.strip().lower() in UNKNOWN_VALUES
77
+ ):
78
+ result = {"state": "unknown", "value": None}
79
+ elif isinstance(value, (int, float)) and not isinstance(value, bool) and value == 0:
80
+ result = {"state": "zero", "value": value}
81
+ else:
82
+ result = {"state": "known", "value": copy.deepcopy(value)}
83
+ if reason:
84
+ result["reason"] = reason
85
+ return result
86
+
87
+
88
+ def canonical_identity(summary: Mapping[str, Any]) -> dict[str, str]:
89
+ nested = summary.get("changeIdentity")
90
+ nested = nested if isinstance(nested, Mapping) else {}
91
+ fallbacks: dict[str, tuple[str, ...]] = {
92
+ "repositoryId": ("repositoryId",),
93
+ "changeName": ("changeName",),
94
+ "baseCommit": ("baseCommit",),
95
+ "productCommit": ("productCommit", "finalCommit"),
96
+ "featureMergeHash": ("featureMergeHash", "productCommit", "finalCommit"),
97
+ "releaseTipHash": ("releaseTipHash", "archiveCommit", "finalCommit"),
98
+ "productTreeHash": ("productTreeHash",),
99
+ "environmentHash": ("environmentHash",),
100
+ "candidateId": ("candidateId",),
101
+ }
102
+ identity: dict[str, str] = {}
103
+ for field in IDENTITY_FIELDS:
104
+ candidates = (nested.get(field),) + tuple(
105
+ summary.get(key) for key in fallbacks[field]
106
+ )
107
+ value = next(
108
+ (
109
+ str(candidate).strip()
110
+ for candidate in candidates
111
+ if candidate is not None and str(candidate).strip()
112
+ ),
113
+ "",
114
+ )
115
+ if field in CONTENT_HASH_FIELDS and value and value != "not_available":
116
+ value = "sha256:" + value.removeprefix("sha256:")
117
+ identity[field] = value
118
+ return identity
119
+
120
+
121
+ def apply_identity_mirrors(
122
+ summary: Mapping[str, Any],
123
+ identity: Mapping[str, Any] | None = None,
124
+ ) -> dict[str, Any]:
125
+ result = copy.deepcopy(dict(summary))
126
+ canonical = dict(identity or canonical_identity(summary))
127
+ result["changeIdentity"] = copy.deepcopy(canonical)
128
+ for field in (
129
+ "productCommit",
130
+ "featureMergeHash",
131
+ "releaseTipHash",
132
+ "productTreeHash",
133
+ "environmentHash",
134
+ ):
135
+ if canonical.get(field):
136
+ result[field] = canonical[field]
137
+ git_facts = result.get("gitFacts")
138
+ git_facts = copy.deepcopy(git_facts) if isinstance(git_facts, Mapping) else {}
139
+ for field in (
140
+ "baseCommit",
141
+ "productCommit",
142
+ "featureMergeHash",
143
+ "releaseTipHash",
144
+ "productTreeHash",
145
+ "environmentHash",
146
+ ):
147
+ if canonical.get(field):
148
+ git_facts[field] = canonical[field]
149
+ result["gitFacts"] = git_facts
150
+ return result
151
+
152
+
153
+ def _identity_value(field: str, value: Any) -> str:
154
+ text = str(value or "").strip()
155
+ if field in CONTENT_HASH_FIELDS and text and text != "not_available":
156
+ return text.removeprefix("sha256:")
157
+ return text
158
+
159
+
160
+ def _record_time(record: Mapping[str, Any]) -> dt.datetime:
161
+ for field in ("finishedAt", "completedAt", "endedAt", "timestamp", "recordedAt"):
162
+ value = str(record.get(field) or "").strip()
163
+ if not value:
164
+ continue
165
+ try:
166
+ return dt.datetime.fromisoformat(value.replace("Z", "+00:00"))
167
+ except ValueError:
168
+ continue
169
+ return dt.datetime.min.replace(tzinfo=dt.timezone.utc)
170
+
171
+
172
+ def select_latest_terminal_verification(
173
+ records: Iterable[Mapping[str, Any]],
174
+ target_identity: Mapping[str, Any],
175
+ ) -> dict[str, Any] | None:
176
+ matches: list[Mapping[str, Any]] = []
177
+ for record in records:
178
+ if str(record.get("status") or "").strip().upper() not in TERMINAL_STATUSES:
179
+ continue
180
+ matches_identity = True
181
+ for field in TARGET_IDENTITY_FIELDS:
182
+ expected = _identity_value(field, target_identity.get(field))
183
+ if not expected:
184
+ continue
185
+ actual = _identity_value(field, record.get(field))
186
+ if actual != expected:
187
+ matches_identity = False
188
+ break
189
+ if matches_identity:
190
+ matches.append(record)
191
+ if not matches:
192
+ return None
193
+ return copy.deepcopy(dict(max(matches, key=_record_time)))
194
+
195
+
196
+ def current_findings(items: Iterable[Mapping[str, Any]]) -> list[dict[str, Any]]:
197
+ current: list[dict[str, Any]] = []
198
+ for item in items:
199
+ disposition = (
200
+ str(item.get("disposition") or "UNKNOWN")
201
+ .strip()
202
+ .upper()
203
+ .replace("-", "_")
204
+ )
205
+ if disposition not in CURRENT_DISPOSITIONS:
206
+ continue
207
+ entry = copy.deepcopy(dict(item))
208
+ entry["disposition"] = disposition
209
+ current.append(entry)
210
+ return current
211
+
212
+
213
+ def latest_terminal_validations(
214
+ ledger: Mapping[str, Any] | None,
215
+ ) -> dict[str, dict[str, Any]]:
216
+ """Project validation history onto the current immutable product target."""
217
+ if not isinstance(ledger, Mapping):
218
+ return {}
219
+ raw_validations = ledger.get("validations")
220
+ raw_validations = (
221
+ raw_validations if isinstance(raw_validations, Mapping) else {}
222
+ )
223
+ global_history: list[Mapping[str, Any]] = []
224
+ dynamic_targets = ledger.get("verificationTargets")
225
+ if isinstance(dynamic_targets, Mapping):
226
+ for key, item in dynamic_targets.items():
227
+ if isinstance(item, Mapping):
228
+ global_history.append(
229
+ {
230
+ **dict(item),
231
+ "target": (
232
+ item.get("target")
233
+ or item.get("verification")
234
+ or str(key)
235
+ ),
236
+ }
237
+ )
238
+ elif isinstance(dynamic_targets, list):
239
+ global_history.extend(
240
+ item for item in dynamic_targets if isinstance(item, Mapping)
241
+ )
242
+ for key in ("verificationHistory", "validationHistory", "validationsHistory"):
243
+ items = ledger.get(key)
244
+ if isinstance(items, list):
245
+ global_history.extend(
246
+ item for item in items if isinstance(item, Mapping)
247
+ )
248
+ projected: dict[str, dict[str, Any]] = {}
249
+ targets = {
250
+ str(key) for key in raw_validations
251
+ } | {
252
+ str(item.get("target") or item.get("verification") or "")
253
+ for item in global_history
254
+ if item.get("target") or item.get("verification")
255
+ }
256
+ base_target = {
257
+ "repositoryId": ledger.get("repositoryId"),
258
+ "changeName": ledger.get("changeName"),
259
+ "productCommit": (
260
+ ledger.get("productCommit")
261
+ or ledger.get("headCommit")
262
+ or ledger.get("currentHead")
263
+ ),
264
+ "productTreeHash": ledger.get("productTreeHash"),
265
+ "environmentHash": ledger.get("environmentHash"),
266
+ "profile": ledger.get("profile"),
267
+ "candidateId": ledger.get("candidateId"),
268
+ }
269
+ for target in sorted(item for item in targets if item):
270
+ direct = raw_validations.get(target)
271
+ candidates: list[Mapping[str, Any]] = [
272
+ {
273
+ **dict(item),
274
+ "target": item.get("target") or item.get("verification") or target,
275
+ }
276
+ for item in global_history
277
+ if str(item.get("target") or item.get("verification") or "") == target
278
+ ]
279
+ if isinstance(direct, Mapping):
280
+ for key in ("history", "attempts"):
281
+ items = direct.get(key)
282
+ if isinstance(items, list):
283
+ candidates.extend(
284
+ {**dict(item), "target": target}
285
+ for item in items
286
+ if isinstance(item, Mapping)
287
+ )
288
+ candidates.append({**dict(direct), "target": target})
289
+ selected = select_latest_terminal_verification(
290
+ candidates,
291
+ {"target": target, **base_target},
292
+ )
293
+ if selected is not None:
294
+ selected.pop("target", None)
295
+ projected[target] = selected
296
+ elif isinstance(direct, Mapping):
297
+ projected[target] = copy.deepcopy(dict(direct))
298
+ return projected
299
+
300
+
301
+ def _measure_record(value: Any) -> Any:
302
+ if not isinstance(value, Mapping):
303
+ return measurement(value)
304
+ return {
305
+ str(key): _measure_record(item)
306
+ for key, item in value.items()
307
+ }
308
+
309
+
310
+ def _mark_uncollected_metrics(
311
+ measured: dict[str, Any],
312
+ *paths: tuple[str, ...],
313
+ ) -> None:
314
+ for path in paths:
315
+ current: Any = measured
316
+ for key in path[:-1]:
317
+ if not isinstance(current, dict):
318
+ current = None
319
+ break
320
+ current = current.get(key)
321
+ if isinstance(current, dict) and path:
322
+ current[path[-1]] = measurement(
323
+ None,
324
+ reason="metric was not collected",
325
+ )
326
+
327
+
328
+ def normalize_report(summary: Mapping[str, Any]) -> dict[str, Any]:
329
+ canonical = canonical_identity(summary)
330
+ review_items = summary.get("reviewFindings")
331
+ review_items = review_items if isinstance(review_items, list) else []
332
+ risks = current_findings(review_items)
333
+ remote_cost = summary.get("remoteCost")
334
+ remote_cost = remote_cost if isinstance(remote_cost, Mapping) else {}
335
+ artifact_storage = summary.get("artifactStorage")
336
+ artifact_storage = (
337
+ artifact_storage if isinstance(artifact_storage, Mapping) else {}
338
+ )
339
+ candidate = summary.get("candidateVerification")
340
+ candidate = copy.deepcopy(candidate) if isinstance(candidate, Mapping) else {}
341
+ current = {
342
+ "status": str(summary.get("finalStatus") or "UNKNOWN"),
343
+ "reasons": copy.deepcopy(list(summary.get("finalStatusReasons") or [])),
344
+ "stages": copy.deepcopy(dict(summary.get("stageStatus") or {})),
345
+ "findings": risks,
346
+ "knownRisks": (
347
+ copy.deepcopy(risks)
348
+ if review_items
349
+ else copy.deepcopy(list(summary.get("knownRisks") or []))
350
+ ),
351
+ }
352
+ history = {
353
+ "timeline": copy.deepcopy(list(summary.get("timeline") or [])),
354
+ "attempts": copy.deepcopy(
355
+ list((summary.get("timing") or {}).get("attempts") or [])
356
+ if isinstance(summary.get("timing"), Mapping)
357
+ else []
358
+ ),
359
+ }
360
+ release_raw = summary.get("releaseDecision")
361
+ release_raw = release_raw if isinstance(release_raw, Mapping) else {}
362
+ release_intent = str(summary.get("archiveIntent") or "")
363
+ release_decision = (
364
+ "NOT_REQUESTED"
365
+ if release_intent == "record-only"
366
+ else str(
367
+ release_raw.get("code")
368
+ or summary.get("releaseDecision")
369
+ or "NOT_DECIDED"
370
+ )
371
+ )
372
+ release = {
373
+ "decision": release_decision,
374
+ "eligible": (
375
+ False
376
+ if release_intent == "record-only"
377
+ else bool(
378
+ release_raw.get("releaseEligible")
379
+ if "releaseEligible" in release_raw
380
+ else summary.get("releaseEligible")
381
+ )
382
+ ),
383
+ "candidate": candidate,
384
+ "intent": release_intent,
385
+ }
386
+ remote_measurements = _measure_record(remote_cost)
387
+ if remote_cost.get("available") is False:
388
+ _mark_uncollected_metrics(
389
+ remote_measurements,
390
+ ("totals", "runCount"),
391
+ ("totals", "runnerMinutes"),
392
+ ("totals", "queueWaitMs"),
393
+ ("totals", "artifactBytes"),
394
+ ("totals", "duplicateRunCount"),
395
+ )
396
+ storage_measurements = _measure_record(artifact_storage)
397
+ if artifact_storage.get("available") is not True:
398
+ _mark_uncollected_metrics(
399
+ storage_measurements,
400
+ ("artifactCount",),
401
+ ("bytesAdded",),
402
+ ("bytesReused",),
403
+ ("bytesPruned",),
404
+ )
405
+ return {
406
+ "schemaVersion": REPORT_MODEL_SCHEMA_VERSION,
407
+ "changeName": str(summary.get("changeName") or ""),
408
+ "identity": canonical,
409
+ "outcomes": {
410
+ "current": current,
411
+ "history": history,
412
+ "release": release,
413
+ },
414
+ "verification": copy.deepcopy(dict(summary.get("verification") or {})),
415
+ "timing": copy.deepcopy(dict(summary.get("timing") or {})),
416
+ "measurements": {
417
+ "remoteCost": remote_measurements,
418
+ "artifactStorage": storage_measurements,
419
+ },
420
+ }
@@ -36,7 +36,15 @@ if hasattr(sys.stderr, "reconfigure"):
36
36
  sys.stderr.reconfigure(encoding="utf-8")
37
37
 
38
38
  SEVERITIES = {"RED", "YELLOW", "OK"}
39
- DISPOSITIONS = {"OPEN", "FIXED", "ACCEPTED_RISK", "DEFERRED", "UNKNOWN"}
39
+ DISPOSITIONS = {
40
+ "OPEN",
41
+ "FIXED",
42
+ "ACCEPTED_RISK",
43
+ "DEFERRED",
44
+ "NOT_APPLICABLE",
45
+ "UNKNOWN",
46
+ }
47
+ CURRENT_RISK_DISPOSITIONS = {"OPEN", "ACCEPTED_RISK", "DEFERRED", "UNKNOWN"}
40
48
  FINDINGS_REL = Path("reports") / "review" / "review-findings.json"
41
49
  DISPOSITIONS_REL = Path("reports") / "review" / "fixback-dispositions.json"
42
50
  _REQUIRED_FINDING_FIELDS = ("dimension", "severity", "path", "line", "title")
@@ -253,6 +261,11 @@ def status(change_dir: Path) -> dict[str, Any]:
253
261
  "disposition": disposition,
254
262
  }
255
263
  )
264
+ current_risks = [
265
+ item for item in items
266
+ if item.get("severity") in {"RED", "YELLOW"}
267
+ and item.get("disposition") in CURRENT_RISK_DISPOSITIONS
268
+ ]
256
269
  return {
257
270
  "ok": True,
258
271
  "code": "STATUS",
@@ -260,6 +273,8 @@ def status(change_dir: Path) -> dict[str, Any]:
260
273
  "counts": counts,
261
274
  "dispositions": disposition_counts,
262
275
  "items": items,
276
+ "currentRiskCount": len(current_risks),
277
+ "currentRisks": current_risks,
263
278
  }
264
279
 
265
280
 
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env python3
2
+ """Select verification targets from a diff and dependency graph."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import fnmatch
8
+ import json
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+
13
+ def select_verifications(payload: dict[str, Any]) -> dict[str, Any]:
14
+ changed = sorted(
15
+ {
16
+ str(path).replace("\\", "/")
17
+ for path in payload.get("changedFiles", [])
18
+ if str(path).strip()
19
+ }
20
+ )
21
+ targets = [
22
+ target for target in payload.get("targets", []) if isinstance(target, dict)
23
+ ]
24
+ selected_ids: set[str] = set()
25
+ reasons: dict[str, list[str]] = {}
26
+ for target in targets:
27
+ target_id = str(target.get("id") or "")
28
+ patterns = [str(pattern) for pattern in target.get("inputs", [])]
29
+ matched = sorted(
30
+ path
31
+ for path in changed
32
+ if any(fnmatch.fnmatch(path, pattern) for pattern in patterns)
33
+ )
34
+ if matched:
35
+ selected_ids.add(target_id)
36
+ reasons[target_id] = ["input-match:" + path for path in matched]
37
+
38
+ changed_closure = True
39
+ while changed_closure:
40
+ changed_closure = False
41
+ for target in targets:
42
+ target_id = str(target.get("id") or "")
43
+ dependencies = {str(item) for item in target.get("dependsOn", [])}
44
+ if target_id not in selected_ids and dependencies & selected_ids:
45
+ selected_ids.add(target_id)
46
+ reasons[target_id] = [
47
+ "consumer-of:" + dependency
48
+ for dependency in sorted(dependencies & selected_ids)
49
+ ]
50
+ changed_closure = True
51
+
52
+ escalation_reasons: list[str] = []
53
+ lockfiles = {
54
+ "package-lock.json",
55
+ "npm-shrinkwrap.json",
56
+ "pnpm-lock.yaml",
57
+ "yarn.lock",
58
+ "pom.xml",
59
+ "Cargo.lock",
60
+ "go.sum",
61
+ }
62
+ if any(path in lockfiles or Path(path).name in lockfiles for path in changed):
63
+ escalation_reasons.append("lockfile-changed")
64
+ if any(
65
+ path.startswith(("packages/contracts/", "contracts/"))
66
+ or "/schema/" in path
67
+ or path.endswith((".schema.json", ".proto"))
68
+ for path in changed
69
+ ):
70
+ escalation_reasons.append("public-contract-changed")
71
+ selected = [
72
+ {**target, "selectionReasons": reasons[str(target.get("id") or "")]}
73
+ for target in targets
74
+ if str(target.get("id") or "") in selected_ids
75
+ ]
76
+ omitted = [
77
+ {**target, "omissionReason": "no impacted input or dependency"}
78
+ for target in targets
79
+ if str(target.get("id") or "") not in selected_ids
80
+ ]
81
+ return {
82
+ "ok": True,
83
+ "code": "VERIFICATION_TARGETS_SELECTED",
84
+ "changedFiles": changed,
85
+ "selected": selected,
86
+ "omitted": omitted,
87
+ "dependencyClosure": [str(item.get("id")) for item in selected],
88
+ "candidateRequired": bool(escalation_reasons),
89
+ "escalationReasons": escalation_reasons,
90
+ }
91
+
92
+
93
+ def main(argv: list[str] | None = None) -> int:
94
+ parser = argparse.ArgumentParser(prog="harness_verification.py")
95
+ parser.add_argument("select", nargs="?")
96
+ parser.add_argument("--input", required=True, type=Path)
97
+ parser.add_argument("--out", type=Path)
98
+ parser.add_argument("--json", action="store_true")
99
+ args = parser.parse_args(argv)
100
+ result = select_verifications(
101
+ json.loads(args.input.read_text(encoding="utf-8-sig"))
102
+ )
103
+ if args.out:
104
+ args.out.parent.mkdir(parents=True, exist_ok=True)
105
+ args.out.write_text(
106
+ json.dumps(result, ensure_ascii=False, indent=2) + "\n",
107
+ encoding="utf-8",
108
+ )
109
+ print(json.dumps(result, ensure_ascii=False, indent=2))
110
+ return 0
111
+
112
+
113
+ if __name__ == "__main__":
114
+ raise SystemExit(main())