@hunter-harness/workflow-harness 0.2.86 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/harness/bundles/general/claude-code/.harness-build.json +1 -1
  2. package/harness/bundles/general/claude-code/contracts/workflow-policy.json +46 -17
  3. package/harness/bundles/general/claude-code/harness-archive/SKILL.md +2 -2
  4. package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +2 -2
  5. package/harness/bundles/general/claude-code/harness-execute/SKILL.md +189 -0
  6. package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +2 -2
  7. package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +2 -2
  8. package/harness/bundles/general/claude-code/harness-plan/SKILL.md +10 -10
  9. package/harness/bundles/general/claude-code/harness-plan/checklist.md +2 -13
  10. package/harness/bundles/general/claude-code/harness-plan/reference.md +43 -81
  11. package/harness/bundles/general/claude-code/harness-pull/SKILL.md +1 -1
  12. package/harness/bundles/general/claude-code/harness-push/SKILL.md +1 -1
  13. package/harness/bundles/general/claude-code/harness-review/SKILL.md +3 -3
  14. package/harness/bundles/general/claude-code/harness-review/protocols.md +1 -1
  15. package/harness/bundles/general/claude-code/harness-run/SKILL.md +8 -2
  16. package/harness/bundles/general/claude-code/harness-submit/SKILL.md +2 -2
  17. package/harness/bundles/general/claude-code/harness-submit/checklist.md +1 -1
  18. package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
  19. package/harness/bundles/general/claude-code/harness-test/SKILL.md +11 -2
  20. package/harness/bundles/general/claude-code/scripts/harness_adoption_metrics.py +391 -0
  21. package/harness/bundles/general/claude-code/scripts/harness_archive.py +23 -14
  22. package/harness/bundles/general/claude-code/scripts/harness_context.py +94 -43
  23. package/harness/bundles/general/claude-code/scripts/harness_events.py +1 -1
  24. package/harness/bundles/general/claude-code/scripts/harness_events_sync.py +1 -0
  25. package/harness/bundles/general/claude-code/scripts/harness_fixback.py +10 -10
  26. package/harness/bundles/general/claude-code/scripts/harness_gate.py +92 -25
  27. package/harness/bundles/general/claude-code/scripts/harness_ledger.py +4 -2
  28. package/harness/bundles/general/claude-code/scripts/harness_paths.py +120 -5
  29. package/harness/bundles/general/claude-code/scripts/harness_phase.py +41 -17
  30. package/harness/bundles/general/claude-code/scripts/harness_plan_finalize.py +16 -873
  31. package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
  32. package/harness/bundles/general/codebuddy/contracts/workflow-policy.json +46 -17
  33. package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +2 -2
  34. package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +2 -2
  35. package/harness/bundles/general/codebuddy/harness-execute/SKILL.md +170 -0
  36. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +2 -2
  37. package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +2 -2
  38. package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +10 -10
  39. package/harness/bundles/general/codebuddy/harness-plan/checklist.md +2 -13
  40. package/harness/bundles/general/codebuddy/harness-plan/reference.md +43 -81
  41. package/harness/bundles/general/codebuddy/harness-pull/SKILL.md +1 -1
  42. package/harness/bundles/general/codebuddy/harness-push/SKILL.md +1 -1
  43. package/harness/bundles/general/codebuddy/harness-review/SKILL.md +3 -3
  44. package/harness/bundles/general/codebuddy/harness-review/protocols.md +1 -1
  45. package/harness/bundles/general/codebuddy/harness-run/SKILL.md +8 -2
  46. package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +2 -2
  47. package/harness/bundles/general/codebuddy/harness-submit/checklist.md +1 -1
  48. package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
  49. package/harness/bundles/general/codebuddy/harness-test/SKILL.md +11 -2
  50. package/harness/bundles/general/codebuddy/scripts/harness_adoption_metrics.py +391 -0
  51. package/harness/bundles/general/codebuddy/scripts/harness_archive.py +23 -14
  52. package/harness/bundles/general/codebuddy/scripts/harness_context.py +94 -43
  53. package/harness/bundles/general/codebuddy/scripts/harness_events.py +1 -1
  54. package/harness/bundles/general/codebuddy/scripts/harness_events_sync.py +1 -0
  55. package/harness/bundles/general/codebuddy/scripts/harness_fixback.py +10 -10
  56. package/harness/bundles/general/codebuddy/scripts/harness_gate.py +92 -25
  57. package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +4 -2
  58. package/harness/bundles/general/codebuddy/scripts/harness_paths.py +120 -5
  59. package/harness/bundles/general/codebuddy/scripts/harness_phase.py +41 -17
  60. package/harness/bundles/general/codebuddy/scripts/harness_plan_finalize.py +16 -873
  61. package/harness/bundles/general/codex/.harness-build.json +1 -1
  62. package/harness/bundles/general/codex/contracts/workflow-policy.json +46 -17
  63. package/harness/bundles/general/codex/harness-archive/SKILL.md +2 -2
  64. package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +2 -2
  65. package/harness/bundles/general/codex/harness-execute/SKILL.md +170 -0
  66. package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +2 -2
  67. package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +2 -2
  68. package/harness/bundles/general/codex/harness-plan/SKILL.md +10 -10
  69. package/harness/bundles/general/codex/harness-plan/checklist.md +2 -13
  70. package/harness/bundles/general/codex/harness-plan/reference.md +43 -81
  71. package/harness/bundles/general/codex/harness-pull/SKILL.md +1 -1
  72. package/harness/bundles/general/codex/harness-push/SKILL.md +1 -1
  73. package/harness/bundles/general/codex/harness-review/SKILL.md +3 -3
  74. package/harness/bundles/general/codex/harness-review/protocols.md +1 -1
  75. package/harness/bundles/general/codex/harness-run/SKILL.md +8 -2
  76. package/harness/bundles/general/codex/harness-submit/SKILL.md +2 -2
  77. package/harness/bundles/general/codex/harness-submit/checklist.md +1 -1
  78. package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
  79. package/harness/bundles/general/codex/harness-test/SKILL.md +11 -2
  80. package/harness/bundles/general/codex/scripts/harness_adoption_metrics.py +391 -0
  81. package/harness/bundles/general/codex/scripts/harness_archive.py +23 -14
  82. package/harness/bundles/general/codex/scripts/harness_context.py +94 -43
  83. package/harness/bundles/general/codex/scripts/harness_events.py +1 -1
  84. package/harness/bundles/general/codex/scripts/harness_events_sync.py +1 -0
  85. package/harness/bundles/general/codex/scripts/harness_fixback.py +10 -10
  86. package/harness/bundles/general/codex/scripts/harness_gate.py +92 -25
  87. package/harness/bundles/general/codex/scripts/harness_ledger.py +4 -2
  88. package/harness/bundles/general/codex/scripts/harness_paths.py +120 -5
  89. package/harness/bundles/general/codex/scripts/harness_phase.py +41 -17
  90. package/harness/bundles/general/codex/scripts/harness_plan_finalize.py +16 -873
  91. package/harness/bundles/general/cursor/.harness-build.json +1 -1
  92. package/harness/bundles/general/cursor/contracts/workflow-policy.json +46 -17
  93. package/harness/bundles/general/cursor/harness-archive/SKILL.md +2 -2
  94. package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +2 -2
  95. package/harness/bundles/general/cursor/harness-execute/SKILL.md +170 -0
  96. package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +2 -2
  97. package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +2 -2
  98. package/harness/bundles/general/cursor/harness-plan/SKILL.md +10 -10
  99. package/harness/bundles/general/cursor/harness-plan/checklist.md +2 -13
  100. package/harness/bundles/general/cursor/harness-plan/reference.md +43 -81
  101. package/harness/bundles/general/cursor/harness-pull/SKILL.md +1 -1
  102. package/harness/bundles/general/cursor/harness-push/SKILL.md +1 -1
  103. package/harness/bundles/general/cursor/harness-review/SKILL.md +3 -3
  104. package/harness/bundles/general/cursor/harness-review/protocols.md +1 -1
  105. package/harness/bundles/general/cursor/harness-run/SKILL.md +8 -2
  106. package/harness/bundles/general/cursor/harness-submit/SKILL.md +2 -2
  107. package/harness/bundles/general/cursor/harness-submit/checklist.md +1 -1
  108. package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
  109. package/harness/bundles/general/cursor/harness-test/SKILL.md +11 -2
  110. package/harness/bundles/general/cursor/scripts/harness_adoption_metrics.py +391 -0
  111. package/harness/bundles/general/cursor/scripts/harness_archive.py +23 -14
  112. package/harness/bundles/general/cursor/scripts/harness_context.py +94 -43
  113. package/harness/bundles/general/cursor/scripts/harness_events.py +1 -1
  114. package/harness/bundles/general/cursor/scripts/harness_events_sync.py +1 -0
  115. package/harness/bundles/general/cursor/scripts/harness_fixback.py +10 -10
  116. package/harness/bundles/general/cursor/scripts/harness_gate.py +92 -25
  117. package/harness/bundles/general/cursor/scripts/harness_ledger.py +4 -2
  118. package/harness/bundles/general/cursor/scripts/harness_paths.py +120 -5
  119. package/harness/bundles/general/cursor/scripts/harness_phase.py +41 -17
  120. package/harness/bundles/general/cursor/scripts/harness_plan_finalize.py +16 -873
  121. package/harness/bundles/java/claude-code/.harness-build.json +1 -1
  122. package/harness/bundles/java/claude-code/contracts/workflow-policy.json +46 -17
  123. package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +2 -2
  124. package/harness/bundles/java/claude-code/harness-archive/SKILL.md +2 -2
  125. package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +2 -2
  126. package/harness/bundles/java/claude-code/harness-execute/SKILL.md +189 -0
  127. package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +2 -2
  128. package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +2 -2
  129. package/harness/bundles/java/claude-code/harness-package/SKILL.md +2 -2
  130. package/harness/bundles/java/claude-code/harness-plan/SKILL.md +10 -10
  131. package/harness/bundles/java/claude-code/harness-plan/checklist.md +2 -13
  132. package/harness/bundles/java/claude-code/harness-plan/reference.md +43 -81
  133. package/harness/bundles/java/claude-code/harness-pull/SKILL.md +1 -1
  134. package/harness/bundles/java/claude-code/harness-push/SKILL.md +1 -1
  135. package/harness/bundles/java/claude-code/harness-review/SKILL.md +3 -3
  136. package/harness/bundles/java/claude-code/harness-review/protocols.md +1 -1
  137. package/harness/bundles/java/claude-code/harness-run/SKILL.md +8 -2
  138. package/harness/bundles/java/claude-code/harness-submit/SKILL.md +2 -2
  139. package/harness/bundles/java/claude-code/harness-submit/checklist.md +1 -1
  140. package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
  141. package/harness/bundles/java/claude-code/harness-test/SKILL.md +11 -2
  142. package/harness/bundles/java/claude-code/scripts/harness_adoption_metrics.py +391 -0
  143. package/harness/bundles/java/claude-code/scripts/harness_archive.py +23 -14
  144. package/harness/bundles/java/claude-code/scripts/harness_context.py +94 -43
  145. package/harness/bundles/java/claude-code/scripts/harness_events.py +1 -1
  146. package/harness/bundles/java/claude-code/scripts/harness_events_sync.py +1 -0
  147. package/harness/bundles/java/claude-code/scripts/harness_fixback.py +10 -10
  148. package/harness/bundles/java/claude-code/scripts/harness_gate.py +92 -25
  149. package/harness/bundles/java/claude-code/scripts/harness_ledger.py +4 -2
  150. package/harness/bundles/java/claude-code/scripts/harness_paths.py +120 -5
  151. package/harness/bundles/java/claude-code/scripts/harness_phase.py +41 -17
  152. package/harness/bundles/java/claude-code/scripts/harness_plan_finalize.py +16 -873
  153. package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
  154. package/harness/bundles/java/codebuddy/contracts/workflow-policy.json +46 -17
  155. package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +2 -2
  156. package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +2 -2
  157. package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +2 -2
  158. package/harness/bundles/java/codebuddy/harness-execute/SKILL.md +170 -0
  159. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +2 -2
  160. package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +2 -2
  161. package/harness/bundles/java/codebuddy/harness-package/SKILL.md +2 -2
  162. package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +10 -10
  163. package/harness/bundles/java/codebuddy/harness-plan/checklist.md +2 -13
  164. package/harness/bundles/java/codebuddy/harness-plan/reference.md +43 -81
  165. package/harness/bundles/java/codebuddy/harness-pull/SKILL.md +1 -1
  166. package/harness/bundles/java/codebuddy/harness-push/SKILL.md +1 -1
  167. package/harness/bundles/java/codebuddy/harness-review/SKILL.md +3 -3
  168. package/harness/bundles/java/codebuddy/harness-review/protocols.md +1 -1
  169. package/harness/bundles/java/codebuddy/harness-run/SKILL.md +8 -2
  170. package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +2 -2
  171. package/harness/bundles/java/codebuddy/harness-submit/checklist.md +1 -1
  172. package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
  173. package/harness/bundles/java/codebuddy/harness-test/SKILL.md +11 -2
  174. package/harness/bundles/java/codebuddy/scripts/harness_adoption_metrics.py +391 -0
  175. package/harness/bundles/java/codebuddy/scripts/harness_archive.py +23 -14
  176. package/harness/bundles/java/codebuddy/scripts/harness_context.py +94 -43
  177. package/harness/bundles/java/codebuddy/scripts/harness_events.py +1 -1
  178. package/harness/bundles/java/codebuddy/scripts/harness_events_sync.py +1 -0
  179. package/harness/bundles/java/codebuddy/scripts/harness_fixback.py +10 -10
  180. package/harness/bundles/java/codebuddy/scripts/harness_gate.py +92 -25
  181. package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +4 -2
  182. package/harness/bundles/java/codebuddy/scripts/harness_paths.py +120 -5
  183. package/harness/bundles/java/codebuddy/scripts/harness_phase.py +41 -17
  184. package/harness/bundles/java/codebuddy/scripts/harness_plan_finalize.py +16 -873
  185. package/harness/bundles/java/codex/.harness-build.json +1 -1
  186. package/harness/bundles/java/codex/contracts/workflow-policy.json +46 -17
  187. package/harness/bundles/java/codex/harness-apidoc/SKILL.md +2 -2
  188. package/harness/bundles/java/codex/harness-archive/SKILL.md +2 -2
  189. package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +2 -2
  190. package/harness/bundles/java/codex/harness-execute/SKILL.md +170 -0
  191. package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +2 -2
  192. package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +2 -2
  193. package/harness/bundles/java/codex/harness-package/SKILL.md +2 -2
  194. package/harness/bundles/java/codex/harness-plan/SKILL.md +10 -10
  195. package/harness/bundles/java/codex/harness-plan/checklist.md +2 -13
  196. package/harness/bundles/java/codex/harness-plan/reference.md +43 -81
  197. package/harness/bundles/java/codex/harness-pull/SKILL.md +1 -1
  198. package/harness/bundles/java/codex/harness-push/SKILL.md +1 -1
  199. package/harness/bundles/java/codex/harness-review/SKILL.md +3 -3
  200. package/harness/bundles/java/codex/harness-review/protocols.md +1 -1
  201. package/harness/bundles/java/codex/harness-run/SKILL.md +8 -2
  202. package/harness/bundles/java/codex/harness-submit/SKILL.md +2 -2
  203. package/harness/bundles/java/codex/harness-submit/checklist.md +1 -1
  204. package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
  205. package/harness/bundles/java/codex/harness-test/SKILL.md +11 -2
  206. package/harness/bundles/java/codex/scripts/harness_adoption_metrics.py +391 -0
  207. package/harness/bundles/java/codex/scripts/harness_archive.py +23 -14
  208. package/harness/bundles/java/codex/scripts/harness_context.py +94 -43
  209. package/harness/bundles/java/codex/scripts/harness_events.py +1 -1
  210. package/harness/bundles/java/codex/scripts/harness_events_sync.py +1 -0
  211. package/harness/bundles/java/codex/scripts/harness_fixback.py +10 -10
  212. package/harness/bundles/java/codex/scripts/harness_gate.py +92 -25
  213. package/harness/bundles/java/codex/scripts/harness_ledger.py +4 -2
  214. package/harness/bundles/java/codex/scripts/harness_paths.py +120 -5
  215. package/harness/bundles/java/codex/scripts/harness_phase.py +41 -17
  216. package/harness/bundles/java/codex/scripts/harness_plan_finalize.py +16 -873
  217. package/harness/bundles/java/cursor/.harness-build.json +1 -1
  218. package/harness/bundles/java/cursor/contracts/workflow-policy.json +46 -17
  219. package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +2 -2
  220. package/harness/bundles/java/cursor/harness-archive/SKILL.md +2 -2
  221. package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +2 -2
  222. package/harness/bundles/java/cursor/harness-execute/SKILL.md +170 -0
  223. package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +2 -2
  224. package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +2 -2
  225. package/harness/bundles/java/cursor/harness-package/SKILL.md +2 -2
  226. package/harness/bundles/java/cursor/harness-plan/SKILL.md +10 -10
  227. package/harness/bundles/java/cursor/harness-plan/checklist.md +2 -13
  228. package/harness/bundles/java/cursor/harness-plan/reference.md +43 -81
  229. package/harness/bundles/java/cursor/harness-pull/SKILL.md +1 -1
  230. package/harness/bundles/java/cursor/harness-push/SKILL.md +1 -1
  231. package/harness/bundles/java/cursor/harness-review/SKILL.md +3 -3
  232. package/harness/bundles/java/cursor/harness-review/protocols.md +1 -1
  233. package/harness/bundles/java/cursor/harness-run/SKILL.md +8 -2
  234. package/harness/bundles/java/cursor/harness-submit/SKILL.md +2 -2
  235. package/harness/bundles/java/cursor/harness-submit/checklist.md +1 -1
  236. package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
  237. package/harness/bundles/java/cursor/harness-test/SKILL.md +11 -2
  238. package/harness/bundles/java/cursor/scripts/harness_adoption_metrics.py +391 -0
  239. package/harness/bundles/java/cursor/scripts/harness_archive.py +23 -14
  240. package/harness/bundles/java/cursor/scripts/harness_context.py +94 -43
  241. package/harness/bundles/java/cursor/scripts/harness_events.py +1 -1
  242. package/harness/bundles/java/cursor/scripts/harness_events_sync.py +1 -0
  243. package/harness/bundles/java/cursor/scripts/harness_fixback.py +10 -10
  244. package/harness/bundles/java/cursor/scripts/harness_gate.py +92 -25
  245. package/harness/bundles/java/cursor/scripts/harness_ledger.py +4 -2
  246. package/harness/bundles/java/cursor/scripts/harness_paths.py +120 -5
  247. package/harness/bundles/java/cursor/scripts/harness_phase.py +41 -17
  248. package/harness/bundles/java/cursor/scripts/harness_plan_finalize.py +16 -873
  249. package/harness/manifests/general/claude-code.json +36 -28
  250. package/harness/manifests/general/codebuddy.json +36 -28
  251. package/harness/manifests/general/codex.json +36 -28
  252. package/harness/manifests/general/cursor.json +36 -28
  253. package/harness/manifests/java/claude-code.json +38 -30
  254. package/harness/manifests/java/codebuddy.json +38 -30
  255. package/harness/manifests/java/codex.json +38 -30
  256. package/harness/manifests/java/cursor.json +38 -30
  257. package/hunter-workflow-family.json +2 -2
  258. package/package.json +1 -1
@@ -155,14 +155,14 @@ GATE_WARNINGS_REL = Path("evidence") / "gate-warnings.ndjson"
155
155
  # Lifecycle order used to scope C9 scenario coverage by scenario ownerPhase.
156
156
  # A scenario owned by a later phase is deferred, not missing, at an earlier
157
157
  # phase close. Must stay a superset-ordering of hpf.VALID_OWNER_PHASES.
158
- SCENARIO_OWNER_PHASE_ORDER = ("plan", "run", "test", "review", "submit")
158
+ SCENARIO_OWNER_PHASE_ORDER = ("plan", "execute", "review", "submit")
159
159
 
160
160
  # ---------------------------------------------------------------------------
161
161
  # 阶段能力表:每个阶段在 begin/close 里额外做什么,集中在这里声明。
162
162
  #
163
163
  # 这些开关以前是 cmd_begin / cmd_close 里散落的 `if args.phase == ...` —— 两个
164
- # 五百行的函数,想知道"test 阶段关门时到底跑哪几项"要通读全文。更要紧的是
165
- # run/test/review 的差异被摊平在控制流里,看不出它们其实共用同一套生命周期。
164
+ # 五百行的函数,想知道"execute 阶段关门时到底跑哪几项"要通读全文。更要紧的是
165
+ # execute/review 的差异被摊平在控制流里,看不出它们其实共用同一套生命周期。
166
166
  #
167
167
  # 键的含义:
168
168
  # plan_handoff begin 时校验 plan 已 finalize(只有进入实现的第一个阶段需要)
@@ -174,11 +174,10 @@ SCENARIO_OWNER_PHASE_ORDER = ("plan", "run", "test", "review", "submit")
174
174
  # projection_drift close 时 projection receipt 变化即硬失败(外发边界)
175
175
  PHASE_GATE_RULES: dict[str, frozenset[str]] = {
176
176
  "plan": frozenset(),
177
- "run": frozenset({
177
+ "execute": frozenset({
178
178
  "plan_handoff", "test_guard", "scenario_coverage",
179
179
  "ledger_blocking", "head_may_advance",
180
180
  }),
181
- "test": frozenset({"test_guard", "scenario_coverage", "ledger_blocking"}),
182
181
  "review": frozenset({"review_outputs"}),
183
182
  "package": frozenset({"ledger_blocking"}),
184
183
  "apidoc": frozenset(),
@@ -481,8 +480,20 @@ def _phase_capsule_path(change_dir: Path, phase: str, run_id: str) -> Path:
481
480
  def load_phase_capsule(
482
481
  change_dir: Path, phase: str, run_id: str
483
482
  ) -> dict[str, Any] | None:
484
- path = _phase_capsule_path(change_dir, phase, run_id)
485
- if not path.is_file():
483
+ # 在途 change capsule 可能按旧名(run/test)写入:先归一入参,canonical
484
+ # 查不到时对所有解析为该 canonical 的旧名各查一次。新写一律 canonical。
485
+ phase = hp.resolve_phase_name(phase) or phase
486
+ candidates = [phase] + [
487
+ legacy for legacy, canonical in hp.LEGACY_PHASE_ALIASES.items()
488
+ if canonical == phase
489
+ ]
490
+ path: Path | None = None
491
+ for name in candidates:
492
+ candidate = _phase_capsule_path(change_dir, name, run_id)
493
+ if candidate.is_file():
494
+ path = candidate
495
+ break
496
+ if path is None:
486
497
  return None
487
498
  try:
488
499
  data = _read_json(path)
@@ -719,7 +730,16 @@ def validate_ledger_for_phase_close(
719
730
  individual validation status to be FAIL/NOT_RUN, so a failing phase can
720
731
  close honestly without polluting evidence or blocking lease release.
721
732
  """
722
- required = policy.get("requiredValidations", {}).get(phase, [])
733
+ resolved_phase = hp.resolve_phase_name(phase) or phase
734
+ # requiredValidations 的键可能还是合并前的旧名(在途 change 的旧契约),
735
+ # 入参也可能是旧名(手工 --phase run)——两侧都归一后再匹配;旧契约的
736
+ # run/test 两个键都映射到 execute,取并集。
737
+ required: list[str] = []
738
+ for key, value in (policy.get("requiredValidations") or {}).items():
739
+ if (hp.resolve_phase_name(key) or str(key)) == resolved_phase:
740
+ for item in value or []:
741
+ if item not in required:
742
+ required.append(item)
723
743
  if not required:
724
744
  return {"ok": True, "code": "LEDGER_NOT_REQUIRED", "phase": phase}
725
745
 
@@ -913,11 +933,17 @@ def validate_ledger_for_phase_close(
913
933
  def effective_workflow_policy(
914
934
  workflow: dict[str, Any], change_dir: Path
915
935
  ) -> dict[str, Any]:
916
- """Overlay a classified change's per-phase gate requirements."""
917
- path = change_dir / "meta" / "gate-policy.json"
918
- if not path.is_file():
936
+ """Overlay a classified change's per-phase gate requirements.
937
+
938
+ 权威切换(2026-08):经 ``harness_paths.load_change_gate_policy`` 读取——
939
+ v2 发布快照(meta/plan-profile.json)优先,classify 工作副本回退。
940
+ 返回值保持与历史一致的纯净 workflow 形状;source/drift 由需要记录的
941
+ 调用方另行调用 ``load_change_gate_policy`` 获取(见 gate begin 路径)。
942
+ """
943
+ loaded = hp.load_change_gate_policy(change_dir)
944
+ document = loaded.get("policy")
945
+ if document is None:
919
946
  return workflow
920
- document = json.loads(path.read_text(encoding="utf-8"))
921
947
  if not isinstance(document, dict) or document.get("schemaVersion") != 1:
922
948
  raise ValueError("gate-policy.json must be a schemaVersion 1 object")
923
949
  by_phase = document.get("requiredValidationsByPhase")
@@ -1334,7 +1360,7 @@ def _apply_required_gate_contract(
1334
1360
  if stage_name == "package":
1335
1361
  dependencies = [
1336
1362
  f"validation:{item}" for item in required
1337
- if validation_phases.get(item) in {"run", "test"}
1363
+ if hp.resolve_phase_name(validation_phases.get(item)) == "execute"
1338
1364
  ]
1339
1365
  elif stage_name == "apidoc" and "apiTest" in required_set:
1340
1366
  dependencies = ["validation:apiTest"]
@@ -1364,7 +1390,7 @@ def classify_risk(
1364
1390
  workflow: dict[str, Any] | None = None,
1365
1391
  ) -> dict[str, Any]:
1366
1392
  # 无风险信号时的起步档。曾经是 full,于是每个普通变更都默认背上
1367
- # plan→runtest→review→submit→archive 六阶段和 apiTest;风险信号推断只在
1393
+ # plan→execute→review→submit→archive 五阶段和 apiTest;风险信号推断只在
1368
1394
  # --stage post-run 下跑,而生产流程没有任何地方调用它,起步值实际就是终值。
1369
1395
  # standard 保留 compile/unitTest/unitTestFull 的证据闭环,只去掉默认的
1370
1396
  # review 阶段与 apiTest。下面的单调升级逻辑不变,有信号照样升到 full。
@@ -1803,7 +1829,7 @@ def append_phase_event(
1803
1829
  trigger=("fixback" if fixback or "fixback" in note.lower() else None),
1804
1830
  from_phase=(
1805
1831
  "review"
1806
- if phase == "run" and (fixback or "fixback" in note.lower())
1832
+ if phase == "execute" and (fixback or "fixback" in note.lower())
1807
1833
  else None
1808
1834
  ),
1809
1835
  result_status=None,
@@ -1940,7 +1966,7 @@ def validate_context_for_gate_begin(
1940
1966
  if not isinstance(current, dict):
1941
1967
  return {"ok": True, "code": "CONTEXT_LEGACY_COMPATIBLE"}
1942
1968
  current_phase = str(current.get("phase") or view.get("currentPhase") or "")
1943
- if current_phase != phase:
1969
+ if hp.resolve_phase_name(current_phase) != phase:
1944
1970
  return {
1945
1971
  "ok": False,
1946
1972
  "code": "CONTEXT_HANDOFF_REQUIRED",
@@ -1954,11 +1980,11 @@ def validate_context_for_gate_begin(
1954
1980
  begin_items = begins if isinstance(begins, list) else []
1955
1981
  acknowledged = any(
1956
1982
  isinstance(item, dict)
1957
- and item.get("phase") == phase
1983
+ and hp.resolve_phase_name(item.get("phase")) == phase
1958
1984
  and item.get("receiptHash") == receipt_hash
1959
1985
  for item in begin_items
1960
1986
  )
1961
- if latest.get("toPhase") != phase or not acknowledged:
1987
+ if hp.resolve_phase_name(latest.get("toPhase")) != phase or not acknowledged:
1962
1988
  return {
1963
1989
  "ok": False,
1964
1990
  "code": "CONTEXT_BEGIN_REQUIRED",
@@ -1970,7 +1996,7 @@ def validate_context_for_gate_begin(
1970
1996
 
1971
1997
  def _phase_event_exists(change_dir: Path, phase: str, type_: str, run_id: str) -> bool:
1972
1998
  return any(
1973
- event.get("phase") == phase
1999
+ hp.resolve_phase_name(event.get("phase")) == phase
1974
2000
  and event.get("type") == type_
1975
2001
  and event.get("run_id") == run_id
1976
2002
  for event in he.load_events(he.events_path(change_dir))
@@ -1983,7 +2009,10 @@ def _latest_phase_end(
1983
2009
  run_id: str | None = None,
1984
2010
  ) -> dict[str, Any] | None:
1985
2011
  for event in reversed(he.load_events(he.events_path(change_dir))):
1986
- if event.get("phase") != phase or event.get("type") != "phase.end":
2012
+ if (
2013
+ hp.resolve_phase_name(event.get("phase")) != phase
2014
+ or event.get("type") != "phase.end"
2015
+ ):
1987
2016
  continue
1988
2017
  if run_id and event.get("run_id") != run_id:
1989
2018
  continue
@@ -2001,7 +2030,7 @@ def _latest_open_run_id(change_dir: Path, phase: str) -> str | None:
2001
2030
  started: list[str] = []
2002
2031
  ended: set[str] = set()
2003
2032
  for event in he.load_events(he.events_path(change_dir)):
2004
- if event.get("phase") != phase:
2033
+ if hp.resolve_phase_name(event.get("phase")) != phase:
2005
2034
  continue
2006
2035
  run_id = str(event.get("run_id") or "")
2007
2036
  if not run_id:
@@ -2047,12 +2076,15 @@ def _close_context_handoff(
2047
2076
  to_phase = getattr(args, "to_phase", None)
2048
2077
  if not to_phase:
2049
2078
  return None
2079
+ # 写边界归一:旧名(--to-phase test)落 canonical;未知名交给
2080
+ # close_transition 的 PHASE_UNKNOWN 报错,不在此吞掉。
2081
+ to_phase = hp.resolve_phase_name(to_phase) or str(to_phase)
2050
2082
  try:
2051
2083
  return hctx.close_transition(
2052
2084
  project,
2053
2085
  change_id,
2054
2086
  from_phase=args.phase,
2055
- to_phase=str(to_phase),
2087
+ to_phase=to_phase,
2056
2088
  executor=getattr(args, "executor", None),
2057
2089
  artifacts=list(getattr(args, "artifact", None) or []),
2058
2090
  status=status,
@@ -2081,6 +2113,10 @@ def _scenario_owner_phase_rank(owner_phase: str | None) -> int | None:
2081
2113
  if not owner_phase:
2082
2114
  return None
2083
2115
  normalized = str(owner_phase).strip().lower()
2116
+ # 旧 manifest 的 ownerPhase=run/test 经别名表归一到 execute 后再定秩。
2117
+ resolved = hp.resolve_phase_name(normalized)
2118
+ if resolved is not None:
2119
+ normalized = resolved
2084
2120
  if normalized not in hpf.VALID_OWNER_PHASES:
2085
2121
  return None
2086
2122
  try:
@@ -2514,8 +2550,32 @@ def validate_review_outputs_for_close(
2514
2550
  }
2515
2551
 
2516
2552
 
2553
+ def _normalize_gate_phase(args: argparse.Namespace, as_json: bool) -> int | None:
2554
+ """cmd_begin/cmd_close 入口归一:旧名(run/test)映射到现阶段名。
2555
+
2556
+ 归一后 args.phase 全链路只流动 canonical 名;未知名报错并列出合法名与别名。
2557
+ """
2558
+ resolved = hp.resolve_phase_name(getattr(args, "phase", None))
2559
+ if resolved is not None:
2560
+ args.phase = resolved
2561
+ return None
2562
+ return emit_error(
2563
+ "PHASE_UNKNOWN",
2564
+ f"未知阶段名 {getattr(args, 'phase', None)!r};合法阶段 "
2565
+ f"{list(hp.WORKFLOW_PHASES)},旧名别名 {hp.LEGACY_PHASE_ALIASES}。",
2566
+ as_json=as_json,
2567
+ extra={
2568
+ "allowedPhases": list(hp.WORKFLOW_PHASES),
2569
+ "legacyAliases": dict(hp.LEGACY_PHASE_ALIASES),
2570
+ },
2571
+ )
2572
+
2573
+
2517
2574
  def cmd_begin(args: argparse.Namespace) -> int:
2518
2575
  as_json = bool(args.json)
2576
+ normalized = _normalize_gate_phase(args, as_json)
2577
+ if normalized is not None:
2578
+ return normalized
2519
2579
  project = hc.resolve_main_project_root()
2520
2580
  resolved = hc.resolve_change(project, args.change)
2521
2581
  if not resolved.get("ok"):
@@ -2646,7 +2706,9 @@ def cmd_begin(args: argparse.Namespace) -> int:
2646
2706
  current_lease = hc.inspect_lease(project, resolved["changeId"])
2647
2707
  run_id = explicit_run_id or (
2648
2708
  str(current_lease.get("runId"))
2649
- if isinstance(current_lease, dict) and current_lease.get("phase") == args.phase
2709
+ if isinstance(current_lease, dict) and (
2710
+ hp.resolve_phase_name(current_lease.get("phase")) == args.phase
2711
+ )
2650
2712
  else "run-" + uuid.uuid4().hex
2651
2713
  )
2652
2714
  try:
@@ -2870,6 +2932,9 @@ def cmd_begin(args: argparse.Namespace) -> int:
2870
2932
 
2871
2933
  def cmd_close(args: argparse.Namespace) -> int:
2872
2934
  as_json = bool(args.json)
2935
+ normalized = _normalize_gate_phase(args, as_json)
2936
+ if normalized is not None:
2937
+ return normalized
2873
2938
  project = hc.resolve_main_project_root()
2874
2939
  resolved = hc.resolve_change(project, args.change)
2875
2940
  if not resolved.get("ok"):
@@ -2940,7 +3005,7 @@ def cmd_close(args: argparse.Namespace) -> int:
2940
3005
  if (
2941
3006
  expired_run_id
2942
3007
  and expired_run_id == expected_run_id
2943
- and str(expired_lease.get("phase") or "") == args.phase
3008
+ and hp.resolve_phase_name(expired_lease.get("phase")) == args.phase
2944
3009
  ):
2945
3010
  reacquired = hc.claim_lease(
2946
3011
  project,
@@ -3042,7 +3107,9 @@ def cmd_close(args: argparse.Namespace) -> int:
3042
3107
  },
3043
3108
  )
3044
3109
  run_id = explicit_run_id or str(current_lease.get("runId") or "")
3045
- if str(current_lease.get("runId")) != run_id or str(current_lease.get("phase")) != args.phase:
3110
+ if str(current_lease.get("runId")) != run_id or (
3111
+ hp.resolve_phase_name(current_lease.get("phase")) != args.phase
3112
+ ):
3046
3113
  return emit_error(
3047
3114
  "LEASE_OWNER_MISMATCH",
3048
3115
  "active lease does not match close phase/run-id",
@@ -202,14 +202,16 @@ def frozen_ownership_check(change_dir: Path) -> dict[str, Any]:
202
202
  if not capsule_root.is_dir():
203
203
  return {"ok": True, "code": "OWNERSHIP_FREEZE_NOT_APPLICABLE"}
204
204
  capsules: list[dict[str, Any]] = []
205
- for path in capsule_root.glob("run-*.json"):
205
+ # capsule 文件名是 <phase>-<hash>.json;execute 合并(run/test→execute)后
206
+ # 新旧两批名字都可能躺在在途 change 里,按内容归一判定而不是按文件名。
207
+ for path in capsule_root.glob("*.json"):
206
208
  try:
207
209
  item = json.loads(path.read_text(encoding="utf-8-sig"))
208
210
  except (OSError, json.JSONDecodeError):
209
211
  continue
210
212
  if (
211
213
  isinstance(item, dict)
212
- and item.get("phase") == "run"
214
+ and harness_paths.resolve_phase_name(item.get("phase")) == "execute"
213
215
  and not item.get("closedAt")
214
216
  and _nonempty_str(item.get("ownershipHash"))
215
217
  ):
@@ -42,8 +42,7 @@ CHANGE_CONTEXT_REL = Path("meta") / "change-context.json"
42
42
  # "unsupported reconcile target phase"。同一份事实不该有两个副本。
43
43
  WORKFLOW_PHASES = (
44
44
  "plan",
45
- "run",
46
- "test",
45
+ "execute",
47
46
  "review",
48
47
  "package",
49
48
  "apidoc",
@@ -68,9 +67,11 @@ KNOWN_NON_WORKFLOW_PHASES = frozenset({
68
67
  })
69
68
 
70
69
  # 旧阶段名 → 现阶段名。阶段合并/改名时在这里登记,读时映射即可让历史
71
- # gate-policy.json plannedPhases 继续可读,不必迁移已落盘的 change。
72
- # 目前为空:还没有发生过阶段改名。
73
- LEGACY_PHASE_ALIASES: dict[str, str] = {}
70
+ # gate-policy.json、plannedPhases 与已落盘 transitions 继续可读,不必迁移
71
+ # 已落盘的 change。注意:映射只能用在哈希校验之后的业务比较层——对已哈希
72
+ # receipt 先映射再重算会断 _payload_hash 链。
73
+ # 2026-08 run+test 合并为 execute(方案 c,review 保留独立阶段)。
74
+ LEGACY_PHASE_ALIASES: dict[str, str] = {"run": "execute", "test": "execute"}
74
75
 
75
76
 
76
77
  def resolve_phase_name(phase: Any) -> str | None:
@@ -230,6 +231,120 @@ def load_change_contract(contract_dir: Path) -> dict[str, Any]:
230
231
  return data
231
232
 
232
233
 
234
+ def _unpack_v2_gate_policy(wrapper: Any) -> dict[str, Any] | None:
235
+ """meta/plan-profile.json(v2 包装体)→ Python schemaVersion:1 形状。
236
+
237
+ 门禁权威所需的四个字段(mode/planned_phases/required_gate_dag/
238
+ required_validations_by_phase)缺一即视为不完整快照(0.2.92-era 的发布
239
+ 没有门禁 overlay),调用方回退工作副本。返回 None 表示不可用作权威。
240
+ """
241
+ if not isinstance(wrapper, dict):
242
+ return None
243
+ if wrapper.get("artifact_type") != "gate_policy":
244
+ return None
245
+ content = wrapper.get("content")
246
+ if not isinstance(content, dict):
247
+ return None
248
+ planned = content.get("planned_phases")
249
+ dag = content.get("required_gate_dag")
250
+ by_phase = content.get("required_validations_by_phase")
251
+ if (
252
+ not isinstance(content.get("mode"), str)
253
+ or not (isinstance(planned, list) and all(isinstance(p, str) for p in planned))
254
+ or not isinstance(dag, dict)
255
+ or not isinstance(by_phase, dict)
256
+ ):
257
+ return None
258
+ policy: dict[str, Any] = {
259
+ "schemaVersion": 1,
260
+ "tier": content.get("tier"),
261
+ "source": content.get("source"),
262
+ "plannedPhases": list(planned),
263
+ "requiredValidations": list(content.get("required_validations") or []),
264
+ "requiredValidationsByPhase": dict(by_phase),
265
+ "requiredGateDag": dict(dag),
266
+ }
267
+ return policy
268
+
269
+
270
+ def load_change_gate_policy(change_dir: Path) -> dict[str, Any]:
271
+ """v2-first 的每 change 门禁策略加载(权威切换 2026-08)。
272
+
273
+ meta/plan-profile.json(v2 发布快照,门禁字段已哈希绑定)优先于
274
+ meta/gate-policy.json(classify 在 0.5 写的工作副本)。v2 快照不完整
275
+ 或缺失 → 回退工作副本。两者并存且 plannedPhases(canonical 归一去重
276
+ 后)不一致 → drift 报告,策略以 v2 为准(已审批定稿 > 工作副本;
277
+ 发布后改写工作副本本身就是异常)。
278
+
279
+ 返回 ``{"policy": <schemaVersion:1 dict 或 None>, "source":
280
+ "v2-plan-profile" | "gate-policy-json" | None, "drift": dict | None,
281
+ "working_error": bool}``——``working_error`` 区分"工作副本不存在"与
282
+ "存在但不可读"(`_phase_plan` 的 legacy:policy-unreadable 语义靠它)。
283
+ """
284
+ change_dir = Path(change_dir)
285
+ v2_policy: dict[str, Any] | None = None
286
+ v2_path = change_dir / "meta" / "plan-profile.json"
287
+ if v2_path.is_file():
288
+ try:
289
+ v2_policy = _unpack_v2_gate_policy(
290
+ json.loads(v2_path.read_text(encoding="utf-8-sig"))
291
+ )
292
+ except (OSError, ValueError, json.JSONDecodeError):
293
+ v2_policy = None
294
+
295
+ working: dict[str, Any] | None = None
296
+ working_error = False
297
+ working_path = change_dir / "meta" / "gate-policy.json"
298
+ if working_path.is_file():
299
+ try:
300
+ candidate = json.loads(working_path.read_text(encoding="utf-8-sig"))
301
+ # 工作副本保持与历史一致的宽松接受:任何 dict 都交给下游各自的
302
+ # 形状校验(effective_workflow_policy 查 schemaVersion,reconcile
303
+ # 查 requiredGateDag),加载器不提前收紧。
304
+ if isinstance(candidate, dict):
305
+ working = candidate
306
+ else:
307
+ working_error = True
308
+ except (OSError, ValueError, json.JSONDecodeError):
309
+ working_error = True
310
+
311
+ drift: dict[str, Any] | None = None
312
+ if v2_policy is not None and working is not None:
313
+ canonical = lambda names: sorted(
314
+ {name for raw in (names or []) if (name := resolve_phase_name(raw))}
315
+ )
316
+ v2_phases = canonical(v2_policy.get("plannedPhases"))
317
+ working_phases = canonical(working.get("plannedPhases"))
318
+ if v2_phases != working_phases:
319
+ drift = {
320
+ "kind": "plannedPhases",
321
+ "v2": v2_phases,
322
+ "gate_policy_json": working_phases,
323
+ "resolution": "v2-plan-profile wins (approved publication snapshot)",
324
+ }
325
+
326
+ if v2_policy is not None:
327
+ return {
328
+ "policy": v2_policy,
329
+ "source": "v2-plan-profile",
330
+ "drift": drift,
331
+ "working_error": working_error,
332
+ }
333
+ if working is not None:
334
+ return {
335
+ "policy": working,
336
+ "source": "gate-policy-json",
337
+ "drift": None,
338
+ "working_error": False,
339
+ }
340
+ return {
341
+ "policy": None,
342
+ "source": None,
343
+ "drift": None,
344
+ "working_error": working_error,
345
+ }
346
+
347
+
233
348
  def contract_layout_kind(contract: dict[str, Any]) -> str:
234
349
  """``split-v1`` when the contract declares a separate runtime root."""
235
350
  ownership = contract.get("stateOwnership")
@@ -37,12 +37,12 @@ CI_METRICS_SCHEMA_VERSION = 1
37
37
  # 一份拷贝,少了 merge,worktree 变更走到 merge 就在 target_required_dag 里硬 raise。
38
38
  PHASE_ORDER = hpaths.WORKFLOW_PHASES
39
39
  VALIDATION_PHASES = {
40
- "compile": "run",
41
- "unitTest": "run",
42
- "unitTestFull": "test",
43
- "apiTest": "test",
44
- "browserTest": "test",
45
- "dbCompatibility": "test",
40
+ "compile": "execute",
41
+ "unitTest": "execute",
42
+ "unitTestFull": "execute",
43
+ "apiTest": "execute",
44
+ "browserTest": "execute",
45
+ "dbCompatibility": "execute",
46
46
  "package": "package",
47
47
  }
48
48
  FINAL_SEQUENCE_RECEIPTS_REL = (
@@ -76,10 +76,11 @@ def _write_json(path: Path, value: Any) -> None:
76
76
 
77
77
 
78
78
  def _final_sequence_contract(change_dir: Path) -> dict[str, Any] | None:
79
- policy_path = change_dir / "meta" / "gate-policy.json"
80
- if not policy_path.is_file():
79
+ # 权威切换(2026-08):v2 发布快照优先,classify 工作副本回退。
80
+ loaded = hpaths.load_change_gate_policy(change_dir)
81
+ policy = loaded.get("policy")
82
+ if policy is None:
81
83
  return None
82
- policy = _read_json(policy_path)
83
84
  declared = policy.get("finalSequence")
84
85
  if isinstance(declared, dict) and isinstance(declared.get("nodeIds"), list):
85
86
  return declared
@@ -786,6 +787,13 @@ def select_phase_capsule(
786
787
  "message": f"invalid phase selector: {phase}",
787
788
  "remediation": "pass the phase name emitted by harness_gate.py begin",
788
789
  }
790
+ # 在途 change 的 capsule 可能按旧名(run/test)写入:归一后按 canonical
791
+ # 优先、旧名兜底查找。新写一律 canonical。
792
+ phase = hpaths.resolve_phase_name(phase) or phase
793
+ phase_names = [phase] + [
794
+ legacy for legacy, canonical in hpaths.LEGACY_PHASE_ALIASES.items()
795
+ if canonical == phase
796
+ ]
789
797
  state_dir = hpaths.resolve_state_dir_for_contract(change_dir, project_root)
790
798
  capsule_dir = state_dir / "runtime" / "phase-context"
791
799
  candidates = (
@@ -793,7 +801,7 @@ def select_phase_capsule(
793
801
  path
794
802
  for path in capsule_dir.iterdir()
795
803
  if path.is_file()
796
- and path.name.startswith(f"{phase}-")
804
+ and any(path.name.startswith(f"{name}-") for name in phase_names)
797
805
  and path.suffix == ".json"
798
806
  ]
799
807
  if capsule_dir.is_dir()
@@ -813,14 +821,20 @@ def select_phase_capsule(
813
821
  "message": f"{len(candidates)} capsule(s) exist for phase {phase}",
814
822
  "remediation": f"rerun reconcile with --phase {phase} --run-id <run-id>",
815
823
  }
816
- key = hashlib.sha256(f"{phase}\0{run_id}".encode("utf-8")).hexdigest()[:20]
817
- path = capsule_dir / f"{phase}-{key}.json"
818
- if not path.is_file():
824
+ path: Path | None = None
825
+ for name in phase_names:
826
+ key = hashlib.sha256(f"{name}\0{run_id}".encode("utf-8")).hexdigest()[:20]
827
+ candidate = capsule_dir / f"{name}-{key}.json"
828
+ if candidate.is_file():
829
+ path = candidate
830
+ break
831
+ if path is None:
832
+ key = hashlib.sha256(f"{phase}\0{run_id}".encode("utf-8")).hexdigest()[:20]
819
833
  return {
820
834
  "ok": False,
821
835
  "code": "PHASE_CAPSULE_NOT_FOUND",
822
836
  "message": f"no capsule for phase {phase} and run-id {run_id}",
823
- "expectedPath": str(path),
837
+ "expectedPath": str(capsule_dir / f"{phase}-{key}.json"),
824
838
  "remediation": f"begin/resume the phase or verify --phase {phase} --run-id {run_id}",
825
839
  }
826
840
  try:
@@ -833,7 +847,10 @@ def select_phase_capsule(
833
847
  "path": str(path),
834
848
  "remediation": f"repair or resume the capsule for --phase {phase} --run-id {run_id}",
835
849
  }
836
- if capsule.get("phase") != phase or capsule.get("runId") != run_id:
850
+ if (
851
+ hpaths.resolve_phase_name(capsule.get("phase")) != phase
852
+ or capsule.get("runId") != run_id
853
+ ):
837
854
  return {
838
855
  "ok": False,
839
856
  "code": "PHASE_CAPSULE_IDENTITY_MISMATCH",
@@ -1368,7 +1385,14 @@ def reconcile(
1368
1385
  ) -> dict[str, Any]:
1369
1386
  change_dir = change_dir.resolve()
1370
1387
  project_root = project_root.resolve()
1371
- policy = _read_json(change_dir / "meta" / "gate-policy.json")
1388
+ # 权威切换(2026-08):v2 发布快照优先,classify 工作副本回退。
1389
+ loaded = hpaths.load_change_gate_policy(change_dir)
1390
+ policy = loaded.get("policy")
1391
+ if policy is None:
1392
+ raise ValueError(
1393
+ "gate policy missing; run harness_gate.py classify "
1394
+ "or publish via plan finalize"
1395
+ )
1372
1396
  dag = (
1373
1397
  target_required_dag(policy, target_phase)
1374
1398
  if target_phase is not None
@@ -1633,7 +1657,7 @@ def build_parser() -> argparse.ArgumentParser:
1633
1657
  reconcile_parser.add_argument("--json", action="store_true")
1634
1658
  reconcile_parser.add_argument("--output")
1635
1659
  reconcile_parser.add_argument("--close", action="store_true")
1636
- reconcile_parser.add_argument("--phase", default="run")
1660
+ reconcile_parser.add_argument("--phase", default="execute")
1637
1661
  reconcile_parser.add_argument("--run-id")
1638
1662
  reconcile_parser.set_defaults(func=_cmd_reconcile)
1639
1663