@hunter-harness/workflow-harness 0.2.86 → 0.3.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.
- package/harness/bundles/general/claude-code/.harness-build.json +1 -1
- package/harness/bundles/general/claude-code/contracts/workflow-policy.json +46 -17
- package/harness/bundles/general/claude-code/harness-archive/SKILL.md +2 -2
- package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +2 -2
- package/harness/bundles/general/claude-code/harness-execute/SKILL.md +189 -0
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +2 -2
- package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +2 -2
- package/harness/bundles/general/claude-code/harness-plan/SKILL.md +10 -10
- package/harness/bundles/general/claude-code/harness-plan/checklist.md +2 -13
- package/harness/bundles/general/claude-code/harness-plan/reference.md +43 -81
- package/harness/bundles/general/claude-code/harness-pull/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-push/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-review/SKILL.md +3 -3
- package/harness/bundles/general/claude-code/harness-review/protocols.md +1 -1
- package/harness/bundles/general/claude-code/harness-run/SKILL.md +8 -2
- package/harness/bundles/general/claude-code/harness-submit/SKILL.md +2 -2
- package/harness/bundles/general/claude-code/harness-submit/checklist.md +1 -1
- package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-test/SKILL.md +11 -2
- package/harness/bundles/general/claude-code/scripts/harness_adoption_metrics.py +391 -0
- package/harness/bundles/general/claude-code/scripts/harness_archive.py +46 -15
- package/harness/bundles/general/claude-code/scripts/harness_context.py +94 -43
- package/harness/bundles/general/claude-code/scripts/harness_events.py +1 -1
- package/harness/bundles/general/claude-code/scripts/harness_events_sync.py +1 -0
- package/harness/bundles/general/claude-code/scripts/harness_fixback.py +10 -10
- package/harness/bundles/general/claude-code/scripts/harness_gate.py +92 -25
- package/harness/bundles/general/claude-code/scripts/harness_ledger.py +4 -2
- package/harness/bundles/general/claude-code/scripts/harness_paths.py +120 -5
- package/harness/bundles/general/claude-code/scripts/harness_phase.py +41 -17
- package/harness/bundles/general/claude-code/scripts/harness_plan_finalize.py +16 -873
- package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/general/codebuddy/contracts/workflow-policy.json +46 -17
- package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +2 -2
- package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +2 -2
- package/harness/bundles/general/codebuddy/harness-execute/SKILL.md +170 -0
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +2 -2
- package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +2 -2
- package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +10 -10
- package/harness/bundles/general/codebuddy/harness-plan/checklist.md +2 -13
- package/harness/bundles/general/codebuddy/harness-plan/reference.md +43 -81
- package/harness/bundles/general/codebuddy/harness-pull/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-push/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-review/SKILL.md +3 -3
- package/harness/bundles/general/codebuddy/harness-review/protocols.md +1 -1
- package/harness/bundles/general/codebuddy/harness-run/SKILL.md +8 -2
- package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +2 -2
- package/harness/bundles/general/codebuddy/harness-submit/checklist.md +1 -1
- package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-test/SKILL.md +11 -2
- package/harness/bundles/general/codebuddy/scripts/harness_adoption_metrics.py +391 -0
- package/harness/bundles/general/codebuddy/scripts/harness_archive.py +46 -15
- package/harness/bundles/general/codebuddy/scripts/harness_context.py +94 -43
- package/harness/bundles/general/codebuddy/scripts/harness_events.py +1 -1
- package/harness/bundles/general/codebuddy/scripts/harness_events_sync.py +1 -0
- package/harness/bundles/general/codebuddy/scripts/harness_fixback.py +10 -10
- package/harness/bundles/general/codebuddy/scripts/harness_gate.py +92 -25
- package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +4 -2
- package/harness/bundles/general/codebuddy/scripts/harness_paths.py +120 -5
- package/harness/bundles/general/codebuddy/scripts/harness_phase.py +41 -17
- package/harness/bundles/general/codebuddy/scripts/harness_plan_finalize.py +16 -873
- package/harness/bundles/general/codex/.harness-build.json +1 -1
- package/harness/bundles/general/codex/contracts/workflow-policy.json +46 -17
- package/harness/bundles/general/codex/harness-archive/SKILL.md +2 -2
- package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +2 -2
- package/harness/bundles/general/codex/harness-execute/SKILL.md +170 -0
- package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +2 -2
- package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +2 -2
- package/harness/bundles/general/codex/harness-plan/SKILL.md +10 -10
- package/harness/bundles/general/codex/harness-plan/checklist.md +2 -13
- package/harness/bundles/general/codex/harness-plan/reference.md +43 -81
- package/harness/bundles/general/codex/harness-pull/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-push/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-review/SKILL.md +3 -3
- package/harness/bundles/general/codex/harness-review/protocols.md +1 -1
- package/harness/bundles/general/codex/harness-run/SKILL.md +8 -2
- package/harness/bundles/general/codex/harness-submit/SKILL.md +2 -2
- package/harness/bundles/general/codex/harness-submit/checklist.md +1 -1
- package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-test/SKILL.md +11 -2
- package/harness/bundles/general/codex/scripts/harness_adoption_metrics.py +391 -0
- package/harness/bundles/general/codex/scripts/harness_archive.py +46 -15
- package/harness/bundles/general/codex/scripts/harness_context.py +94 -43
- package/harness/bundles/general/codex/scripts/harness_events.py +1 -1
- package/harness/bundles/general/codex/scripts/harness_events_sync.py +1 -0
- package/harness/bundles/general/codex/scripts/harness_fixback.py +10 -10
- package/harness/bundles/general/codex/scripts/harness_gate.py +92 -25
- package/harness/bundles/general/codex/scripts/harness_ledger.py +4 -2
- package/harness/bundles/general/codex/scripts/harness_paths.py +120 -5
- package/harness/bundles/general/codex/scripts/harness_phase.py +41 -17
- package/harness/bundles/general/codex/scripts/harness_plan_finalize.py +16 -873
- package/harness/bundles/general/cursor/.harness-build.json +1 -1
- package/harness/bundles/general/cursor/contracts/workflow-policy.json +46 -17
- package/harness/bundles/general/cursor/harness-archive/SKILL.md +2 -2
- package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +2 -2
- package/harness/bundles/general/cursor/harness-execute/SKILL.md +170 -0
- package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +2 -2
- package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +2 -2
- package/harness/bundles/general/cursor/harness-plan/SKILL.md +10 -10
- package/harness/bundles/general/cursor/harness-plan/checklist.md +2 -13
- package/harness/bundles/general/cursor/harness-plan/reference.md +43 -81
- package/harness/bundles/general/cursor/harness-pull/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-push/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-review/SKILL.md +3 -3
- package/harness/bundles/general/cursor/harness-review/protocols.md +1 -1
- package/harness/bundles/general/cursor/harness-run/SKILL.md +8 -2
- package/harness/bundles/general/cursor/harness-submit/SKILL.md +2 -2
- package/harness/bundles/general/cursor/harness-submit/checklist.md +1 -1
- package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-test/SKILL.md +11 -2
- package/harness/bundles/general/cursor/scripts/harness_adoption_metrics.py +391 -0
- package/harness/bundles/general/cursor/scripts/harness_archive.py +46 -15
- package/harness/bundles/general/cursor/scripts/harness_context.py +94 -43
- package/harness/bundles/general/cursor/scripts/harness_events.py +1 -1
- package/harness/bundles/general/cursor/scripts/harness_events_sync.py +1 -0
- package/harness/bundles/general/cursor/scripts/harness_fixback.py +10 -10
- package/harness/bundles/general/cursor/scripts/harness_gate.py +92 -25
- package/harness/bundles/general/cursor/scripts/harness_ledger.py +4 -2
- package/harness/bundles/general/cursor/scripts/harness_paths.py +120 -5
- package/harness/bundles/general/cursor/scripts/harness_phase.py +41 -17
- package/harness/bundles/general/cursor/scripts/harness_plan_finalize.py +16 -873
- package/harness/bundles/java/claude-code/.harness-build.json +1 -1
- package/harness/bundles/java/claude-code/contracts/workflow-policy.json +46 -17
- package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +2 -2
- package/harness/bundles/java/claude-code/harness-archive/SKILL.md +2 -2
- package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +2 -2
- package/harness/bundles/java/claude-code/harness-execute/SKILL.md +189 -0
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +2 -2
- package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +2 -2
- package/harness/bundles/java/claude-code/harness-package/SKILL.md +2 -2
- package/harness/bundles/java/claude-code/harness-plan/SKILL.md +10 -10
- package/harness/bundles/java/claude-code/harness-plan/checklist.md +2 -13
- package/harness/bundles/java/claude-code/harness-plan/reference.md +43 -81
- package/harness/bundles/java/claude-code/harness-pull/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-push/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-review/SKILL.md +3 -3
- package/harness/bundles/java/claude-code/harness-review/protocols.md +1 -1
- package/harness/bundles/java/claude-code/harness-run/SKILL.md +8 -2
- package/harness/bundles/java/claude-code/harness-submit/SKILL.md +2 -2
- package/harness/bundles/java/claude-code/harness-submit/checklist.md +1 -1
- package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-test/SKILL.md +11 -2
- package/harness/bundles/java/claude-code/scripts/harness_adoption_metrics.py +391 -0
- package/harness/bundles/java/claude-code/scripts/harness_archive.py +46 -15
- package/harness/bundles/java/claude-code/scripts/harness_context.py +94 -43
- package/harness/bundles/java/claude-code/scripts/harness_events.py +1 -1
- package/harness/bundles/java/claude-code/scripts/harness_events_sync.py +1 -0
- package/harness/bundles/java/claude-code/scripts/harness_fixback.py +10 -10
- package/harness/bundles/java/claude-code/scripts/harness_gate.py +92 -25
- package/harness/bundles/java/claude-code/scripts/harness_ledger.py +4 -2
- package/harness/bundles/java/claude-code/scripts/harness_paths.py +120 -5
- package/harness/bundles/java/claude-code/scripts/harness_phase.py +41 -17
- package/harness/bundles/java/claude-code/scripts/harness_plan_finalize.py +16 -873
- package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/java/codebuddy/contracts/workflow-policy.json +46 -17
- package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +2 -2
- package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +2 -2
- package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +2 -2
- package/harness/bundles/java/codebuddy/harness-execute/SKILL.md +170 -0
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +2 -2
- package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +2 -2
- package/harness/bundles/java/codebuddy/harness-package/SKILL.md +2 -2
- package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +10 -10
- package/harness/bundles/java/codebuddy/harness-plan/checklist.md +2 -13
- package/harness/bundles/java/codebuddy/harness-plan/reference.md +43 -81
- package/harness/bundles/java/codebuddy/harness-pull/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-push/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-review/SKILL.md +3 -3
- package/harness/bundles/java/codebuddy/harness-review/protocols.md +1 -1
- package/harness/bundles/java/codebuddy/harness-run/SKILL.md +8 -2
- package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +2 -2
- package/harness/bundles/java/codebuddy/harness-submit/checklist.md +1 -1
- package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-test/SKILL.md +11 -2
- package/harness/bundles/java/codebuddy/scripts/harness_adoption_metrics.py +391 -0
- package/harness/bundles/java/codebuddy/scripts/harness_archive.py +46 -15
- package/harness/bundles/java/codebuddy/scripts/harness_context.py +94 -43
- package/harness/bundles/java/codebuddy/scripts/harness_events.py +1 -1
- package/harness/bundles/java/codebuddy/scripts/harness_events_sync.py +1 -0
- package/harness/bundles/java/codebuddy/scripts/harness_fixback.py +10 -10
- package/harness/bundles/java/codebuddy/scripts/harness_gate.py +92 -25
- package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +4 -2
- package/harness/bundles/java/codebuddy/scripts/harness_paths.py +120 -5
- package/harness/bundles/java/codebuddy/scripts/harness_phase.py +41 -17
- package/harness/bundles/java/codebuddy/scripts/harness_plan_finalize.py +16 -873
- package/harness/bundles/java/codex/.harness-build.json +1 -1
- package/harness/bundles/java/codex/contracts/workflow-policy.json +46 -17
- package/harness/bundles/java/codex/harness-apidoc/SKILL.md +2 -2
- package/harness/bundles/java/codex/harness-archive/SKILL.md +2 -2
- package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +2 -2
- package/harness/bundles/java/codex/harness-execute/SKILL.md +170 -0
- package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +2 -2
- package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +2 -2
- package/harness/bundles/java/codex/harness-package/SKILL.md +2 -2
- package/harness/bundles/java/codex/harness-plan/SKILL.md +10 -10
- package/harness/bundles/java/codex/harness-plan/checklist.md +2 -13
- package/harness/bundles/java/codex/harness-plan/reference.md +43 -81
- package/harness/bundles/java/codex/harness-pull/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-push/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-review/SKILL.md +3 -3
- package/harness/bundles/java/codex/harness-review/protocols.md +1 -1
- package/harness/bundles/java/codex/harness-run/SKILL.md +8 -2
- package/harness/bundles/java/codex/harness-submit/SKILL.md +2 -2
- package/harness/bundles/java/codex/harness-submit/checklist.md +1 -1
- package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-test/SKILL.md +11 -2
- package/harness/bundles/java/codex/scripts/harness_adoption_metrics.py +391 -0
- package/harness/bundles/java/codex/scripts/harness_archive.py +46 -15
- package/harness/bundles/java/codex/scripts/harness_context.py +94 -43
- package/harness/bundles/java/codex/scripts/harness_events.py +1 -1
- package/harness/bundles/java/codex/scripts/harness_events_sync.py +1 -0
- package/harness/bundles/java/codex/scripts/harness_fixback.py +10 -10
- package/harness/bundles/java/codex/scripts/harness_gate.py +92 -25
- package/harness/bundles/java/codex/scripts/harness_ledger.py +4 -2
- package/harness/bundles/java/codex/scripts/harness_paths.py +120 -5
- package/harness/bundles/java/codex/scripts/harness_phase.py +41 -17
- package/harness/bundles/java/codex/scripts/harness_plan_finalize.py +16 -873
- package/harness/bundles/java/cursor/.harness-build.json +1 -1
- package/harness/bundles/java/cursor/contracts/workflow-policy.json +46 -17
- package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +2 -2
- package/harness/bundles/java/cursor/harness-archive/SKILL.md +2 -2
- package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +2 -2
- package/harness/bundles/java/cursor/harness-execute/SKILL.md +170 -0
- package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +2 -2
- package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +2 -2
- package/harness/bundles/java/cursor/harness-package/SKILL.md +2 -2
- package/harness/bundles/java/cursor/harness-plan/SKILL.md +10 -10
- package/harness/bundles/java/cursor/harness-plan/checklist.md +2 -13
- package/harness/bundles/java/cursor/harness-plan/reference.md +43 -81
- package/harness/bundles/java/cursor/harness-pull/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-push/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-review/SKILL.md +3 -3
- package/harness/bundles/java/cursor/harness-review/protocols.md +1 -1
- package/harness/bundles/java/cursor/harness-run/SKILL.md +8 -2
- package/harness/bundles/java/cursor/harness-submit/SKILL.md +2 -2
- package/harness/bundles/java/cursor/harness-submit/checklist.md +1 -1
- package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-test/SKILL.md +11 -2
- package/harness/bundles/java/cursor/scripts/harness_adoption_metrics.py +391 -0
- package/harness/bundles/java/cursor/scripts/harness_archive.py +46 -15
- package/harness/bundles/java/cursor/scripts/harness_context.py +94 -43
- package/harness/bundles/java/cursor/scripts/harness_events.py +1 -1
- package/harness/bundles/java/cursor/scripts/harness_events_sync.py +1 -0
- package/harness/bundles/java/cursor/scripts/harness_fixback.py +10 -10
- package/harness/bundles/java/cursor/scripts/harness_gate.py +92 -25
- package/harness/bundles/java/cursor/scripts/harness_ledger.py +4 -2
- package/harness/bundles/java/cursor/scripts/harness_paths.py +120 -5
- package/harness/bundles/java/cursor/scripts/harness_phase.py +41 -17
- package/harness/bundles/java/cursor/scripts/harness_plan_finalize.py +16 -873
- package/harness/manifests/general/claude-code.json +36 -28
- package/harness/manifests/general/codebuddy.json +36 -28
- package/harness/manifests/general/codex.json +36 -28
- package/harness/manifests/general/cursor.json +36 -28
- package/harness/manifests/java/claude-code.json +38 -30
- package/harness/manifests/java/codebuddy.json +38 -30
- package/harness/manifests/java/codex.json +38 -30
- package/harness/manifests/java/cursor.json +38 -30
- package/hunter-workflow-family.json +2 -2
- 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", "
|
|
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
|
-
# 五百行的函数,想知道"
|
|
165
|
-
#
|
|
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
|
-
"
|
|
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
|
-
|
|
485
|
-
|
|
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
|
-
|
|
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
|
-
|
|
918
|
-
|
|
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)
|
|
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→
|
|
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 == "
|
|
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
|
|
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=
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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") == "
|
|
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
|
-
"
|
|
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
|
|
72
|
-
#
|
|
73
|
-
|
|
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": "
|
|
41
|
-
"unitTest": "
|
|
42
|
-
"unitTestFull": "
|
|
43
|
-
"apiTest": "
|
|
44
|
-
"browserTest": "
|
|
45
|
-
"dbCompatibility": "
|
|
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
|
-
|
|
80
|
-
|
|
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"{
|
|
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
|
-
|
|
817
|
-
|
|
818
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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="
|
|
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
|
|