@hunter-harness/workflow-harness 0.2.55 → 0.2.56
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/README.md +3 -3
- package/harness/bundles/general/claude-code/contracts/workflow-policy.json +1 -1
- package/harness/bundles/general/claude-code/harness-archive/SKILL.md +21 -33
- package/harness/bundles/general/claude-code/harness-archive/checklist.md +6 -10
- package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-plan/SKILL.md +7 -3
- package/harness/bundles/general/claude-code/harness-review/SKILL.md +6 -4
- package/harness/bundles/general/claude-code/harness-run/SKILL.md +7 -5
- package/harness/bundles/general/claude-code/harness-run/checklist.md +1 -1
- package/harness/bundles/general/claude-code/harness-run/reference.md +2 -2
- package/harness/bundles/general/claude-code/harness-submit/SKILL.md +6 -6
- 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 +6 -6
- package/harness/bundles/general/claude-code/harness-test/checklist.md +1 -1
- package/harness/bundles/general/claude-code/harness-test/reference.md +2 -2
- package/harness/bundles/general/claude-code/protocols/ledger-protocol.md +3 -3
- package/harness/bundles/general/claude-code/scripts/harness_archive.py +276 -67
- package/harness/bundles/general/claude-code/scripts/harness_context.py +165 -41
- package/harness/bundles/general/claude-code/scripts/harness_events.py +46 -4
- package/harness/bundles/general/claude-code/scripts/harness_events_sync.py +108 -3
- package/harness/bundles/general/claude-code/scripts/harness_fixback.py +189 -1
- package/harness/bundles/general/claude-code/scripts/harness_gate.py +188 -2
- package/harness/bundles/general/claude-code/scripts/harness_ledger.py +193 -11
- package/harness/bundles/general/claude-code/scripts/harness_plan_finalize.py +80 -0
- package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/general/codebuddy/README.md +3 -3
- package/harness/bundles/general/codebuddy/contracts/workflow-policy.json +1 -1
- package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +21 -33
- package/harness/bundles/general/codebuddy/harness-archive/checklist.md +6 -10
- package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +7 -3
- package/harness/bundles/general/codebuddy/harness-review/SKILL.md +6 -4
- package/harness/bundles/general/codebuddy/harness-run/SKILL.md +7 -5
- package/harness/bundles/general/codebuddy/harness-run/checklist.md +1 -1
- package/harness/bundles/general/codebuddy/harness-run/reference.md +2 -2
- package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +6 -6
- 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 +6 -6
- package/harness/bundles/general/codebuddy/harness-test/checklist.md +1 -1
- package/harness/bundles/general/codebuddy/harness-test/reference.md +2 -2
- package/harness/bundles/general/codebuddy/protocols/ledger-protocol.md +3 -3
- package/harness/bundles/general/codebuddy/scripts/harness_archive.py +276 -67
- package/harness/bundles/general/codebuddy/scripts/harness_context.py +165 -41
- package/harness/bundles/general/codebuddy/scripts/harness_events.py +46 -4
- package/harness/bundles/general/codebuddy/scripts/harness_events_sync.py +108 -3
- package/harness/bundles/general/codebuddy/scripts/harness_fixback.py +189 -1
- package/harness/bundles/general/codebuddy/scripts/harness_gate.py +188 -2
- package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +193 -11
- package/harness/bundles/general/codebuddy/scripts/harness_plan_finalize.py +80 -0
- package/harness/bundles/general/codex/.harness-build.json +1 -1
- package/harness/bundles/general/codex/README.md +3 -3
- package/harness/bundles/general/codex/contracts/workflow-policy.json +1 -1
- package/harness/bundles/general/codex/harness-archive/SKILL.md +21 -33
- package/harness/bundles/general/codex/harness-archive/checklist.md +6 -10
- package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-plan/SKILL.md +7 -3
- package/harness/bundles/general/codex/harness-review/SKILL.md +4 -4
- package/harness/bundles/general/codex/harness-run/SKILL.md +7 -5
- package/harness/bundles/general/codex/harness-run/checklist.md +1 -1
- package/harness/bundles/general/codex/harness-run/reference.md +2 -2
- package/harness/bundles/general/codex/harness-submit/SKILL.md +6 -6
- 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 +6 -6
- package/harness/bundles/general/codex/harness-test/checklist.md +1 -1
- package/harness/bundles/general/codex/harness-test/reference.md +2 -2
- package/harness/bundles/general/codex/protocols/ledger-protocol.md +3 -3
- package/harness/bundles/general/codex/scripts/harness_archive.py +276 -67
- package/harness/bundles/general/codex/scripts/harness_context.py +165 -41
- package/harness/bundles/general/codex/scripts/harness_events.py +46 -4
- package/harness/bundles/general/codex/scripts/harness_events_sync.py +108 -3
- package/harness/bundles/general/codex/scripts/harness_fixback.py +189 -1
- package/harness/bundles/general/codex/scripts/harness_gate.py +188 -2
- package/harness/bundles/general/codex/scripts/harness_ledger.py +193 -11
- package/harness/bundles/general/codex/scripts/harness_plan_finalize.py +80 -0
- package/harness/bundles/general/cursor/.harness-build.json +1 -1
- package/harness/bundles/general/cursor/README.md +3 -3
- package/harness/bundles/general/cursor/contracts/workflow-policy.json +1 -1
- package/harness/bundles/general/cursor/harness-archive/SKILL.md +21 -33
- package/harness/bundles/general/cursor/harness-archive/checklist.md +6 -10
- package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-plan/SKILL.md +7 -3
- package/harness/bundles/general/cursor/harness-review/SKILL.md +4 -4
- package/harness/bundles/general/cursor/harness-run/SKILL.md +7 -5
- package/harness/bundles/general/cursor/harness-run/checklist.md +1 -1
- package/harness/bundles/general/cursor/harness-run/reference.md +2 -2
- package/harness/bundles/general/cursor/harness-submit/SKILL.md +6 -6
- 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 +6 -6
- package/harness/bundles/general/cursor/harness-test/checklist.md +1 -1
- package/harness/bundles/general/cursor/harness-test/reference.md +2 -2
- package/harness/bundles/general/cursor/protocols/ledger-protocol.md +3 -3
- package/harness/bundles/general/cursor/scripts/harness_archive.py +276 -67
- package/harness/bundles/general/cursor/scripts/harness_context.py +165 -41
- package/harness/bundles/general/cursor/scripts/harness_events.py +46 -4
- package/harness/bundles/general/cursor/scripts/harness_events_sync.py +108 -3
- package/harness/bundles/general/cursor/scripts/harness_fixback.py +189 -1
- package/harness/bundles/general/cursor/scripts/harness_gate.py +188 -2
- package/harness/bundles/general/cursor/scripts/harness_ledger.py +193 -11
- package/harness/bundles/general/cursor/scripts/harness_plan_finalize.py +80 -0
- package/harness/bundles/java/claude-code/.harness-build.json +1 -1
- package/harness/bundles/java/claude-code/README.md +3 -3
- package/harness/bundles/java/claude-code/contracts/workflow-policy.json +1 -1
- package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-archive/SKILL.md +21 -33
- package/harness/bundles/java/claude-code/harness-archive/checklist.md +6 -10
- package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-package/checklist.md +1 -1
- package/harness/bundles/java/claude-code/harness-plan/SKILL.md +7 -3
- package/harness/bundles/java/claude-code/harness-review/SKILL.md +6 -4
- package/harness/bundles/java/claude-code/harness-run/SKILL.md +7 -5
- package/harness/bundles/java/claude-code/harness-run/checklist.md +1 -1
- package/harness/bundles/java/claude-code/harness-run/reference.md +2 -2
- package/harness/bundles/java/claude-code/harness-submit/SKILL.md +6 -6
- 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 +6 -6
- package/harness/bundles/java/claude-code/harness-test/checklist.md +1 -1
- package/harness/bundles/java/claude-code/harness-test/reference.md +1 -1
- package/harness/bundles/java/claude-code/protocols/ledger-protocol.md +3 -3
- package/harness/bundles/java/claude-code/scripts/harness_archive.py +276 -67
- package/harness/bundles/java/claude-code/scripts/harness_context.py +165 -41
- package/harness/bundles/java/claude-code/scripts/harness_events.py +46 -4
- package/harness/bundles/java/claude-code/scripts/harness_events_sync.py +108 -3
- package/harness/bundles/java/claude-code/scripts/harness_fixback.py +189 -1
- package/harness/bundles/java/claude-code/scripts/harness_gate.py +188 -2
- package/harness/bundles/java/claude-code/scripts/harness_ledger.py +193 -11
- package/harness/bundles/java/claude-code/scripts/harness_plan_finalize.py +80 -0
- package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/java/codebuddy/README.md +3 -3
- package/harness/bundles/java/codebuddy/contracts/workflow-policy.json +1 -1
- package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +21 -33
- package/harness/bundles/java/codebuddy/harness-archive/checklist.md +6 -10
- package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-package/checklist.md +1 -1
- package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +7 -3
- package/harness/bundles/java/codebuddy/harness-review/SKILL.md +6 -4
- package/harness/bundles/java/codebuddy/harness-run/SKILL.md +7 -5
- package/harness/bundles/java/codebuddy/harness-run/checklist.md +1 -1
- package/harness/bundles/java/codebuddy/harness-run/reference.md +2 -2
- package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +6 -6
- 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 +6 -6
- package/harness/bundles/java/codebuddy/harness-test/checklist.md +1 -1
- package/harness/bundles/java/codebuddy/harness-test/reference.md +1 -1
- package/harness/bundles/java/codebuddy/protocols/ledger-protocol.md +3 -3
- package/harness/bundles/java/codebuddy/scripts/harness_archive.py +276 -67
- package/harness/bundles/java/codebuddy/scripts/harness_context.py +165 -41
- package/harness/bundles/java/codebuddy/scripts/harness_events.py +46 -4
- package/harness/bundles/java/codebuddy/scripts/harness_events_sync.py +108 -3
- package/harness/bundles/java/codebuddy/scripts/harness_fixback.py +189 -1
- package/harness/bundles/java/codebuddy/scripts/harness_gate.py +188 -2
- package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +193 -11
- package/harness/bundles/java/codebuddy/scripts/harness_plan_finalize.py +80 -0
- package/harness/bundles/java/codex/.harness-build.json +1 -1
- package/harness/bundles/java/codex/README.md +3 -3
- package/harness/bundles/java/codex/contracts/workflow-policy.json +1 -1
- package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-archive/SKILL.md +21 -33
- package/harness/bundles/java/codex/harness-archive/checklist.md +6 -10
- package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-package/checklist.md +1 -1
- package/harness/bundles/java/codex/harness-plan/SKILL.md +7 -3
- package/harness/bundles/java/codex/harness-review/SKILL.md +4 -4
- package/harness/bundles/java/codex/harness-run/SKILL.md +7 -5
- package/harness/bundles/java/codex/harness-run/checklist.md +1 -1
- package/harness/bundles/java/codex/harness-run/reference.md +2 -2
- package/harness/bundles/java/codex/harness-submit/SKILL.md +6 -6
- 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 +6 -6
- package/harness/bundles/java/codex/harness-test/checklist.md +1 -1
- package/harness/bundles/java/codex/harness-test/reference.md +1 -1
- package/harness/bundles/java/codex/protocols/ledger-protocol.md +3 -3
- package/harness/bundles/java/codex/scripts/harness_archive.py +276 -67
- package/harness/bundles/java/codex/scripts/harness_context.py +165 -41
- package/harness/bundles/java/codex/scripts/harness_events.py +46 -4
- package/harness/bundles/java/codex/scripts/harness_events_sync.py +108 -3
- package/harness/bundles/java/codex/scripts/harness_fixback.py +189 -1
- package/harness/bundles/java/codex/scripts/harness_gate.py +188 -2
- package/harness/bundles/java/codex/scripts/harness_ledger.py +193 -11
- package/harness/bundles/java/codex/scripts/harness_plan_finalize.py +80 -0
- package/harness/bundles/java/cursor/.harness-build.json +1 -1
- package/harness/bundles/java/cursor/README.md +3 -3
- package/harness/bundles/java/cursor/contracts/workflow-policy.json +1 -1
- package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-archive/SKILL.md +21 -33
- package/harness/bundles/java/cursor/harness-archive/checklist.md +6 -10
- package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-package/checklist.md +1 -1
- package/harness/bundles/java/cursor/harness-plan/SKILL.md +7 -3
- package/harness/bundles/java/cursor/harness-review/SKILL.md +4 -4
- package/harness/bundles/java/cursor/harness-run/SKILL.md +7 -5
- package/harness/bundles/java/cursor/harness-run/checklist.md +1 -1
- package/harness/bundles/java/cursor/harness-run/reference.md +2 -2
- package/harness/bundles/java/cursor/harness-submit/SKILL.md +6 -6
- 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 +6 -6
- package/harness/bundles/java/cursor/harness-test/checklist.md +1 -1
- package/harness/bundles/java/cursor/harness-test/reference.md +1 -1
- package/harness/bundles/java/cursor/protocols/ledger-protocol.md +3 -3
- package/harness/bundles/java/cursor/scripts/harness_archive.py +276 -67
- package/harness/bundles/java/cursor/scripts/harness_context.py +165 -41
- package/harness/bundles/java/cursor/scripts/harness_events.py +46 -4
- package/harness/bundles/java/cursor/scripts/harness_events_sync.py +108 -3
- package/harness/bundles/java/cursor/scripts/harness_fixback.py +189 -1
- package/harness/bundles/java/cursor/scripts/harness_gate.py +188 -2
- package/harness/bundles/java/cursor/scripts/harness_ledger.py +193 -11
- package/harness/bundles/java/cursor/scripts/harness_plan_finalize.py +80 -0
- package/harness/manifests/general/claude-code.json +30 -30
- package/harness/manifests/general/codebuddy.json +30 -30
- package/harness/manifests/general/codex.json +30 -30
- package/harness/manifests/general/cursor.json +30 -30
- package/harness/manifests/java/claude-code.json +33 -33
- package/harness/manifests/java/codebuddy.json +33 -33
- package/harness/manifests/java/codex.json +33 -33
- package/harness/manifests/java/cursor.json +33 -33
- package/hunter-workflow-family.json +5 -5
- package/package.json +1 -1
|
@@ -160,6 +160,39 @@ def infer_change_name(dir_path: Path) -> str:
|
|
|
160
160
|
return name
|
|
161
161
|
|
|
162
162
|
|
|
163
|
+
def resolve_archive_state_root(change_dir: Path) -> Path:
|
|
164
|
+
"""Resolve the dynamic state view shared by status, certify and finalize."""
|
|
165
|
+
change_dir = change_dir.resolve()
|
|
166
|
+
return hp.resolve_state_dir_for_contract(
|
|
167
|
+
change_dir,
|
|
168
|
+
find_project_root(change_dir),
|
|
169
|
+
).resolve()
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def archive_read_roots(change_dir: Path) -> list[Path]:
|
|
173
|
+
"""Return dynamic state first and the immutable contract as fallback."""
|
|
174
|
+
contract = change_dir.resolve()
|
|
175
|
+
state = resolve_archive_state_root(contract)
|
|
176
|
+
return [state] if state == contract else [state, contract]
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _first_archive_path(change_dir: Path, relative: str) -> Path | None:
|
|
180
|
+
for root in archive_read_roots(change_dir):
|
|
181
|
+
candidate = root / relative
|
|
182
|
+
if candidate.is_file():
|
|
183
|
+
return candidate
|
|
184
|
+
return None
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _archive_path_label(change_dir: Path, path: Path) -> str:
|
|
188
|
+
for root in archive_read_roots(change_dir):
|
|
189
|
+
try:
|
|
190
|
+
return path.resolve().relative_to(root).as_posix()
|
|
191
|
+
except ValueError:
|
|
192
|
+
continue
|
|
193
|
+
return path.name
|
|
194
|
+
|
|
195
|
+
|
|
163
196
|
def load_template() -> dict[str, Any]:
|
|
164
197
|
if SUMMARY_TEMPLATE.is_file():
|
|
165
198
|
data = read_json(SUMMARY_TEMPLATE)
|
|
@@ -218,6 +251,8 @@ def generate_manifest(root: Path, output_path: Path) -> dict[str, Any]:
|
|
|
218
251
|
for path in sorted(root.rglob("*")):
|
|
219
252
|
if not path.is_file():
|
|
220
253
|
continue
|
|
254
|
+
if path.name.endswith(".lock"):
|
|
255
|
+
continue
|
|
221
256
|
if exclude is not None and path.resolve() == exclude:
|
|
222
257
|
continue
|
|
223
258
|
rel = path.relative_to(root).as_posix()
|
|
@@ -314,12 +349,14 @@ def compare_manifests(
|
|
|
314
349
|
|
|
315
350
|
|
|
316
351
|
def load_ledger(change_dir: Path) -> dict[str, Any] | None:
|
|
317
|
-
for
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
352
|
+
for root in archive_read_roots(change_dir):
|
|
353
|
+
for rel in (
|
|
354
|
+
"evidence/verification-ledger.json",
|
|
355
|
+
"verification-ledger.json",
|
|
356
|
+
):
|
|
357
|
+
path = root / rel
|
|
358
|
+
if not path.is_file():
|
|
359
|
+
continue
|
|
323
360
|
try:
|
|
324
361
|
data = read_json(path)
|
|
325
362
|
return data if isinstance(data, dict) else None
|
|
@@ -330,14 +367,15 @@ def load_ledger(change_dir: Path) -> dict[str, Any] | None:
|
|
|
330
367
|
|
|
331
368
|
def load_ci_metrics(change_dir: Path) -> tuple[dict[str, Any] | None, str | None]:
|
|
332
369
|
"""Load schema-versioned runner metrics without parsing human CI logs."""
|
|
333
|
-
for
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
370
|
+
for root in archive_read_roots(change_dir):
|
|
371
|
+
for relative in ("evidence/ci-metrics.json", "runtime/ci-metrics.json"):
|
|
372
|
+
path = root / relative
|
|
373
|
+
if not path.is_file():
|
|
374
|
+
continue
|
|
375
|
+
value = read_json(path)
|
|
376
|
+
if value.get("schemaVersion") != 1:
|
|
377
|
+
raise ValueError(f"unsupported ci-metrics schema: {path}")
|
|
378
|
+
return value, relative
|
|
341
379
|
return None, None
|
|
342
380
|
|
|
343
381
|
|
|
@@ -451,8 +489,8 @@ def load_product_candidate_ci(change_dir: Path) -> dict[str, Any] | None:
|
|
|
451
489
|
"meta/product-candidate-verification.json",
|
|
452
490
|
"runtime/product-candidate-verification.json",
|
|
453
491
|
):
|
|
454
|
-
path = change_dir
|
|
455
|
-
if
|
|
492
|
+
path = _first_archive_path(change_dir, relative)
|
|
493
|
+
if path is None:
|
|
456
494
|
continue
|
|
457
495
|
try:
|
|
458
496
|
data = read_json(path)
|
|
@@ -857,14 +895,21 @@ def _candidate_value_hash(value: Any) -> str:
|
|
|
857
895
|
def _candidate_evidence_hash(change_dir: Path, value: Any) -> str:
|
|
858
896
|
if isinstance(value, str) and value.strip():
|
|
859
897
|
raw = Path(value.strip())
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
898
|
+
candidates = [raw] if raw.is_absolute() else [
|
|
899
|
+
root / raw for root in archive_read_roots(change_dir)
|
|
900
|
+
]
|
|
901
|
+
for candidate in candidates:
|
|
902
|
+
try:
|
|
903
|
+
resolved = candidate.resolve()
|
|
904
|
+
if not any(
|
|
905
|
+
resolved.is_relative_to(root.resolve())
|
|
906
|
+
for root in archive_read_roots(change_dir)
|
|
907
|
+
):
|
|
908
|
+
continue
|
|
909
|
+
except OSError:
|
|
910
|
+
continue
|
|
911
|
+
if resolved.is_file():
|
|
912
|
+
return f"sha256:{sha256_file(resolved)}"
|
|
868
913
|
return _candidate_value_hash(value)
|
|
869
914
|
|
|
870
915
|
|
|
@@ -980,8 +1025,6 @@ def certify_local_candidate(
|
|
|
980
1025
|
"command",
|
|
981
1026
|
"evidence",
|
|
982
1027
|
"inputsHash",
|
|
983
|
-
"toolchainHash",
|
|
984
|
-
"environmentHash",
|
|
985
1028
|
):
|
|
986
1029
|
if entry.get(field) in (None, "", [], {}):
|
|
987
1030
|
missing.append(field)
|
|
@@ -994,25 +1037,24 @@ def certify_local_candidate(
|
|
|
994
1037
|
"name": name,
|
|
995
1038
|
"command": entry.get("command"),
|
|
996
1039
|
"inputsHash": entry.get("inputsHash"),
|
|
997
|
-
"toolchainHash":
|
|
998
|
-
|
|
1040
|
+
"toolchainHash": (
|
|
1041
|
+
entry.get("toolchainHash")
|
|
1042
|
+
or hl.default_toolchain_hash(project_root)
|
|
1043
|
+
),
|
|
1044
|
+
"environmentHash": (
|
|
1045
|
+
entry.get("environmentHash")
|
|
1046
|
+
or hl.default_environment_hash(project_root)
|
|
1047
|
+
),
|
|
1048
|
+
"inputsFiles": list(entry.get("inputsFiles") or []),
|
|
999
1049
|
"logHash": _candidate_evidence_hash(
|
|
1000
1050
|
change_dir, entry.get("evidence")
|
|
1001
1051
|
),
|
|
1002
1052
|
}
|
|
1003
1053
|
)
|
|
1004
1054
|
|
|
1005
|
-
ledger_path =
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
for relative in (
|
|
1009
|
-
"evidence/verification-ledger.json",
|
|
1010
|
-
"verification-ledger.json",
|
|
1011
|
-
)
|
|
1012
|
-
if (change_dir / relative).is_file()
|
|
1013
|
-
),
|
|
1014
|
-
None,
|
|
1015
|
-
)
|
|
1055
|
+
ledger_path = _first_archive_path(
|
|
1056
|
+
change_dir, "evidence/verification-ledger.json"
|
|
1057
|
+
) or _first_archive_path(change_dir, "verification-ledger.json")
|
|
1016
1058
|
if ledger_path is None:
|
|
1017
1059
|
raise ValueError("verification ledger path missing")
|
|
1018
1060
|
|
|
@@ -1023,15 +1065,29 @@ def certify_local_candidate(
|
|
|
1023
1065
|
or ""
|
|
1024
1066
|
).strip()
|
|
1025
1067
|
rc, current_head, _stderr = git_run(project_root, "rev-parse", "HEAD")
|
|
1068
|
+
rebound_from_commit: str | None = None
|
|
1026
1069
|
if rc == 0 and current_head:
|
|
1027
1070
|
if product_commit and not (
|
|
1028
1071
|
current_head.startswith(product_commit)
|
|
1029
1072
|
or product_commit.startswith(current_head)
|
|
1030
1073
|
):
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1074
|
+
for item in selected:
|
|
1075
|
+
input_files = [str(path) for path in item.get("inputsFiles") or []]
|
|
1076
|
+
if not input_files:
|
|
1077
|
+
raise ValueError(
|
|
1078
|
+
"product commit is stale and validation inputs are unavailable: "
|
|
1079
|
+
f"ledger={product_commit} current={current_head}"
|
|
1080
|
+
)
|
|
1081
|
+
current_inputs_hash, _paths = hl.compute_inputs_hash(
|
|
1082
|
+
hl.resolve_input_files(input_files, project_root),
|
|
1083
|
+
project_root=project_root,
|
|
1084
|
+
)
|
|
1085
|
+
if current_inputs_hash != item.get("inputsHash"):
|
|
1086
|
+
raise ValueError(
|
|
1087
|
+
"product commit is stale and validated inputs changed: "
|
|
1088
|
+
f"ledger={product_commit} current={current_head}"
|
|
1089
|
+
)
|
|
1090
|
+
rebound_from_commit = product_commit
|
|
1035
1091
|
product_commit = current_head
|
|
1036
1092
|
if not product_commit:
|
|
1037
1093
|
raise ValueError("product commit unavailable")
|
|
@@ -1045,7 +1101,7 @@ def certify_local_candidate(
|
|
|
1045
1101
|
)
|
|
1046
1102
|
current_tree_hash = f"sha256:{detail['hash']}"
|
|
1047
1103
|
recorded_tree_hash = str(ledger.get("productTreeHash") or "").strip()
|
|
1048
|
-
if recorded_tree_hash and recorded_tree_hash.removeprefix(
|
|
1104
|
+
if recorded_tree_hash and rebound_from_commit is None and recorded_tree_hash.removeprefix(
|
|
1049
1105
|
"sha256:"
|
|
1050
1106
|
) != current_tree_hash.removeprefix("sha256:"):
|
|
1051
1107
|
raise ValueError(
|
|
@@ -1090,8 +1146,13 @@ def certify_local_candidate(
|
|
|
1090
1146
|
"logHashes": sorted({str(item["logHash"]) for item in selected}),
|
|
1091
1147
|
},
|
|
1092
1148
|
}
|
|
1149
|
+
if rebound_from_commit is not None:
|
|
1150
|
+
receipt["subject"]["reboundFromCommit"] = rebound_from_commit
|
|
1151
|
+
receipt["verification"]["commitReboundWithoutRerun"] = True
|
|
1093
1152
|
write_json(
|
|
1094
|
-
change_dir
|
|
1153
|
+
resolve_archive_state_root(change_dir)
|
|
1154
|
+
/ "evidence"
|
|
1155
|
+
/ "product-candidate-verification.json",
|
|
1095
1156
|
receipt,
|
|
1096
1157
|
)
|
|
1097
1158
|
return receipt
|
|
@@ -1811,9 +1872,11 @@ def verify_manifest_byte_coverage(
|
|
|
1811
1872
|
}
|
|
1812
1873
|
|
|
1813
1874
|
def load_execution_log(change_dir: Path) -> str:
|
|
1814
|
-
for
|
|
1815
|
-
|
|
1816
|
-
|
|
1875
|
+
for root in archive_read_roots(change_dir):
|
|
1876
|
+
for rel in ("logs/execution-log.md", "execution-log.md"):
|
|
1877
|
+
path = root / rel
|
|
1878
|
+
if not path.is_file():
|
|
1879
|
+
continue
|
|
1817
1880
|
try:
|
|
1818
1881
|
return path.read_text(encoding="utf-8-sig")
|
|
1819
1882
|
except OSError:
|
|
@@ -1822,12 +1885,14 @@ def load_execution_log(change_dir: Path) -> str:
|
|
|
1822
1885
|
|
|
1823
1886
|
|
|
1824
1887
|
def load_existing_summary(change_dir: Path) -> dict[str, Any] | None:
|
|
1825
|
-
for
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1888
|
+
for root in archive_read_roots(change_dir):
|
|
1889
|
+
for rel in (
|
|
1890
|
+
"reports/final/summary-data.json",
|
|
1891
|
+
"summary-data.json",
|
|
1892
|
+
):
|
|
1893
|
+
path = root / rel
|
|
1894
|
+
if not path.is_file():
|
|
1895
|
+
continue
|
|
1831
1896
|
try:
|
|
1832
1897
|
data = read_json(path)
|
|
1833
1898
|
return data if isinstance(data, dict) else None
|
|
@@ -2074,15 +2139,36 @@ def check_status(
|
|
|
2074
2139
|
*,
|
|
2075
2140
|
allow_missing_review: bool = False,
|
|
2076
2141
|
archive_intent: str = "release-candidate",
|
|
2142
|
+
closure_disposition: str = "completed",
|
|
2143
|
+
closure_reason: str = "",
|
|
2077
2144
|
) -> dict[str, Any]:
|
|
2078
2145
|
"""Read-only archive preconditions. Never mutates."""
|
|
2079
2146
|
if archive_intent not in {"release-candidate", "record-only"}:
|
|
2080
2147
|
raise ValueError(
|
|
2081
2148
|
"archive_intent must be release-candidate or record-only"
|
|
2082
2149
|
)
|
|
2150
|
+
if closure_disposition not in {"completed", "abandoned", "superseded"}:
|
|
2151
|
+
raise ValueError(
|
|
2152
|
+
"closure_disposition must be completed, abandoned, or superseded"
|
|
2153
|
+
)
|
|
2154
|
+
closure_reason = closure_reason.strip()
|
|
2083
2155
|
blockers: list[dict[str, str]] = []
|
|
2084
2156
|
warnings: list[dict[str, str]] = []
|
|
2085
2157
|
checks: dict[str, Any] = {}
|
|
2158
|
+
if closure_disposition != "completed" and not closure_reason:
|
|
2159
|
+
blockers.append(
|
|
2160
|
+
{
|
|
2161
|
+
"code": "closure-reason-required",
|
|
2162
|
+
"message": "ending an unfinished change requires a closure reason",
|
|
2163
|
+
}
|
|
2164
|
+
)
|
|
2165
|
+
if closure_disposition != "completed" and archive_intent == "release-candidate":
|
|
2166
|
+
blockers.append(
|
|
2167
|
+
{
|
|
2168
|
+
"code": "closure-not-release-candidate",
|
|
2169
|
+
"message": "abandoned or superseded changes can be archived but cannot be release candidates",
|
|
2170
|
+
}
|
|
2171
|
+
)
|
|
2086
2172
|
|
|
2087
2173
|
if not change_dir.is_dir():
|
|
2088
2174
|
return {
|
|
@@ -2097,6 +2183,8 @@ def check_status(
|
|
|
2097
2183
|
|
|
2098
2184
|
project = find_project_root(change_dir)
|
|
2099
2185
|
checks["project_root"] = str(project)
|
|
2186
|
+
state_root = resolve_archive_state_root(change_dir)
|
|
2187
|
+
checks["state_root"] = str(state_root)
|
|
2100
2188
|
|
|
2101
2189
|
exact_byte = check_archive_exact_byte_policy(project)
|
|
2102
2190
|
checks["archive_exact_byte"] = exact_byte
|
|
@@ -2138,7 +2226,7 @@ def check_status(
|
|
|
2138
2226
|
}
|
|
2139
2227
|
)
|
|
2140
2228
|
|
|
2141
|
-
events_path =
|
|
2229
|
+
events_path = state_root / "events.ndjson"
|
|
2142
2230
|
events_present = events_path.is_file() and events_path.stat().st_size > 0
|
|
2143
2231
|
checks["events_ndjson"] = events_present
|
|
2144
2232
|
if not events_present:
|
|
@@ -2152,10 +2240,19 @@ def check_status(
|
|
|
2152
2240
|
ledger = load_ledger(change_dir)
|
|
2153
2241
|
checks["verification_ledger"] = ledger is not None
|
|
2154
2242
|
if ledger is None:
|
|
2155
|
-
blockers
|
|
2243
|
+
target = warnings if closure_disposition != "completed" else blockers
|
|
2244
|
+
target.append(
|
|
2156
2245
|
{
|
|
2157
|
-
"code":
|
|
2158
|
-
|
|
2246
|
+
"code": (
|
|
2247
|
+
"closure-ledger-not-required"
|
|
2248
|
+
if closure_disposition != "completed"
|
|
2249
|
+
else "missing-verification-ledger"
|
|
2250
|
+
),
|
|
2251
|
+
"message": (
|
|
2252
|
+
"verification was not completed; the closure records it as NOT_RUN"
|
|
2253
|
+
if closure_disposition != "completed"
|
|
2254
|
+
else "evidence/verification-ledger.json is required before archive"
|
|
2255
|
+
),
|
|
2159
2256
|
}
|
|
2160
2257
|
)
|
|
2161
2258
|
|
|
@@ -2425,6 +2522,8 @@ def check_status(
|
|
|
2425
2522
|
change_dir, write=False, for_replay=False
|
|
2426
2523
|
)
|
|
2427
2524
|
release_summary["archiveIntent"] = archive_intent
|
|
2525
|
+
release_summary["closureDisposition"] = closure_disposition
|
|
2526
|
+
release_summary["closureReason"] = closure_reason
|
|
2428
2527
|
report_adequacy = validate_report_adequacy(release_summary)
|
|
2429
2528
|
release_decision = evaluate_release_eligibility(
|
|
2430
2529
|
change_dir,
|
|
@@ -2448,12 +2547,19 @@ def check_status(
|
|
|
2448
2547
|
"ok": True,
|
|
2449
2548
|
"archivable": archivable,
|
|
2450
2549
|
"archiveIntent": archive_intent,
|
|
2550
|
+
"releaseIntent": "candidate" if archive_intent == "release-candidate" else "none",
|
|
2551
|
+
"closureDisposition": closure_disposition,
|
|
2552
|
+
"closureReason": closure_reason or None,
|
|
2451
2553
|
"archiveIntegrity": archive_integrity,
|
|
2452
2554
|
"candidateVerification": release_decision["checks"][
|
|
2453
2555
|
"candidateVerification"
|
|
2454
2556
|
],
|
|
2455
2557
|
"releaseDecision": release_decision,
|
|
2456
|
-
"releaseEligible":
|
|
2558
|
+
"releaseEligible": (
|
|
2559
|
+
release_decision["releaseEligible"]
|
|
2560
|
+
if closure_disposition == "completed"
|
|
2561
|
+
else False
|
|
2562
|
+
),
|
|
2457
2563
|
"change_dir": str(change_dir),
|
|
2458
2564
|
"change_name": infer_change_name(change_dir),
|
|
2459
2565
|
"blockers": blockers,
|
|
@@ -2484,7 +2590,10 @@ def archive_auto_gate(
|
|
|
2484
2590
|
"nextAction": "Resolve archive status blockers, then run `harness_archive.py auto-gate` again.",
|
|
2485
2591
|
}
|
|
2486
2592
|
|
|
2487
|
-
|
|
2593
|
+
state_root = resolve_archive_state_root(change_dir)
|
|
2594
|
+
snapshot_path = state_root / "meta" / "state-snapshot.json"
|
|
2595
|
+
if not snapshot_path.is_file():
|
|
2596
|
+
snapshot_path = change_dir / "meta" / "state-snapshot.json"
|
|
2488
2597
|
try:
|
|
2489
2598
|
snapshot = read_json(snapshot_path)
|
|
2490
2599
|
except (OSError, json.JSONDecodeError, TypeError):
|
|
@@ -2496,7 +2605,7 @@ def archive_auto_gate(
|
|
|
2496
2605
|
"autoArchiveAllowed": False,
|
|
2497
2606
|
"reasonCode": "ARCHIVE_BOUNDARY_SNAPSHOT_MISSING",
|
|
2498
2607
|
"status": status,
|
|
2499
|
-
"nextAction": "Capture the
|
|
2608
|
+
"nextAction": "Capture the archive-boundary state snapshot before auto-archiving.",
|
|
2500
2609
|
}
|
|
2501
2610
|
|
|
2502
2611
|
git_state = snapshot.get("git")
|
|
@@ -2505,33 +2614,65 @@ def archive_auto_gate(
|
|
|
2505
2614
|
if isinstance(git_state, dict)
|
|
2506
2615
|
else snapshot.get("base") or snapshot.get("baseCommit")
|
|
2507
2616
|
)
|
|
2508
|
-
|
|
2617
|
+
content_state = snapshot.get("content")
|
|
2618
|
+
content_hash = (
|
|
2619
|
+
content_state.get("productTreeHash")
|
|
2620
|
+
if isinstance(content_state, dict)
|
|
2621
|
+
else snapshot.get("productTreeHash")
|
|
2622
|
+
)
|
|
2623
|
+
content_boundary_valid = (
|
|
2624
|
+
isinstance(content_hash, str)
|
|
2625
|
+
and re.fullmatch(r"sha256:[0-9a-f]{64}", content_hash.strip()) is not None
|
|
2626
|
+
)
|
|
2627
|
+
git_boundary_valid = isinstance(snapshot_base, str) and bool(snapshot_base.strip())
|
|
2628
|
+
if not git_boundary_valid and not content_boundary_valid:
|
|
2509
2629
|
return {
|
|
2510
2630
|
"ok": False,
|
|
2511
2631
|
"action": "auto-gate",
|
|
2512
2632
|
"autoArchiveAllowed": False,
|
|
2513
2633
|
"reasonCode": "ARCHIVE_BOUNDARY_SNAPSHOT_INVALID",
|
|
2514
2634
|
"status": status,
|
|
2515
|
-
"nextAction":
|
|
2635
|
+
"nextAction": (
|
|
2636
|
+
"Capture a state snapshot containing either a Git boundary or "
|
|
2637
|
+
"a deterministic productTreeHash content boundary."
|
|
2638
|
+
),
|
|
2516
2639
|
}
|
|
2517
2640
|
|
|
2518
|
-
events_path =
|
|
2641
|
+
events_path = state_root / "events.ndjson"
|
|
2519
2642
|
events = he.load_events(events_path) if events_path.is_file() else []
|
|
2643
|
+
policy = load_gate_policy(change_dir) or {}
|
|
2644
|
+
raw_planned = policy.get("plannedPhases")
|
|
2645
|
+
if not isinstance(raw_planned, list) or not raw_planned:
|
|
2646
|
+
raw_planned = policy.get("defaultPhases")
|
|
2647
|
+
planned_phases = (
|
|
2648
|
+
[str(item).strip() for item in raw_planned if str(item).strip()]
|
|
2649
|
+
if isinstance(raw_planned, list)
|
|
2650
|
+
else []
|
|
2651
|
+
)
|
|
2652
|
+
completed_phase: str | None = None
|
|
2653
|
+
if "archive" in planned_phases:
|
|
2654
|
+
archive_index = planned_phases.index("archive")
|
|
2655
|
+
if archive_index > 0:
|
|
2656
|
+
completed_phase = planned_phases[archive_index - 1]
|
|
2657
|
+
expected_phases = {completed_phase} if completed_phase else {"submit", "merge"}
|
|
2520
2658
|
completed = any(
|
|
2521
|
-
event.get("phase") in
|
|
2659
|
+
event.get("phase") in expected_phases
|
|
2522
2660
|
and event.get("type") == "phase.end"
|
|
2523
2661
|
and str(event.get("status") or "").upper() in {"OK", "WARN"}
|
|
2524
2662
|
for event in events
|
|
2525
2663
|
if isinstance(event, dict)
|
|
2526
2664
|
)
|
|
2527
2665
|
if not completed:
|
|
2666
|
+
phase_label = completed_phase or "Submit or Merge"
|
|
2528
2667
|
return {
|
|
2529
2668
|
"ok": False,
|
|
2530
2669
|
"action": "auto-gate",
|
|
2531
2670
|
"autoArchiveAllowed": False,
|
|
2532
|
-
"reasonCode": "
|
|
2671
|
+
"reasonCode": "PLANNED_PREREQUISITE_NOT_COMPLETED",
|
|
2533
2672
|
"status": status,
|
|
2534
|
-
"
|
|
2673
|
+
"plannedPhases": planned_phases or None,
|
|
2674
|
+
"completedPrerequisitePhase": completed_phase,
|
|
2675
|
+
"nextAction": f"Complete {phase_label} and record its terminal phase event before auto-archiving.",
|
|
2535
2676
|
}
|
|
2536
2677
|
|
|
2537
2678
|
return {
|
|
@@ -2539,6 +2680,8 @@ def archive_auto_gate(
|
|
|
2539
2680
|
"action": "auto-gate",
|
|
2540
2681
|
"autoArchiveAllowed": True,
|
|
2541
2682
|
"reasonCode": "ARCHIVE_AUTO_GATE_SATISFIED",
|
|
2683
|
+
"plannedPhases": planned_phases or None,
|
|
2684
|
+
"completedPrerequisitePhase": completed_phase,
|
|
2542
2685
|
"status": status,
|
|
2543
2686
|
"snapshotPath": str(snapshot_path),
|
|
2544
2687
|
"snapshotBase": snapshot_base,
|
|
@@ -4026,6 +4169,9 @@ def collect_summary_data(
|
|
|
4026
4169
|
"knownRisks",
|
|
4027
4170
|
"manualActions",
|
|
4028
4171
|
"archiveIntent",
|
|
4172
|
+
"releaseIntent",
|
|
4173
|
+
"closureDisposition",
|
|
4174
|
+
"closureReason",
|
|
4029
4175
|
"archiveIntegrity",
|
|
4030
4176
|
"candidateVerification",
|
|
4031
4177
|
"releaseDecision",
|
|
@@ -4164,6 +4310,19 @@ def collect_summary_data(
|
|
|
4164
4310
|
data["archiveIntent"] = str(
|
|
4165
4311
|
intent_data.get("intent") or "release-candidate"
|
|
4166
4312
|
)
|
|
4313
|
+
data["releaseIntent"] = str(
|
|
4314
|
+
intent_data.get("releaseIntent")
|
|
4315
|
+
or ("candidate" if data["archiveIntent"] == "release-candidate" else "none")
|
|
4316
|
+
)
|
|
4317
|
+
data["closureDisposition"] = str(
|
|
4318
|
+
intent_data.get("closureDisposition") or "completed"
|
|
4319
|
+
)
|
|
4320
|
+
raw_closure_reason = intent_data.get("closureReason")
|
|
4321
|
+
data["closureReason"] = (
|
|
4322
|
+
str(raw_closure_reason).strip()
|
|
4323
|
+
if isinstance(raw_closure_reason, str) and raw_closure_reason.strip()
|
|
4324
|
+
else None
|
|
4325
|
+
)
|
|
4167
4326
|
data["archiveIntegrity"] = {"ok": True}
|
|
4168
4327
|
data["candidateVerification"] = candidate_gate
|
|
4169
4328
|
|
|
@@ -5199,6 +5358,9 @@ def _freeze_evidence_cutoff(work_dir: Path) -> dict[str, Any]:
|
|
|
5199
5358
|
"path": "events.ndjson",
|
|
5200
5359
|
}
|
|
5201
5360
|
write_json(work_dir / "evidence" / "evidence-cutoff.json", cutoff)
|
|
5361
|
+
# The staged archive is immutable after this point. The event lock is a
|
|
5362
|
+
# runtime coordination file, not evidence, and must not survive publish.
|
|
5363
|
+
events_file.with_name(events_file.name + ".lock").unlink(missing_ok=True)
|
|
5202
5364
|
return cutoff
|
|
5203
5365
|
|
|
5204
5366
|
|
|
@@ -7302,6 +7464,8 @@ def cmd_finalize(
|
|
|
7302
7464
|
skip_ingest: bool = False,
|
|
7303
7465
|
allow_missing_review: bool = False,
|
|
7304
7466
|
archive_intent: str = "release-candidate",
|
|
7467
|
+
closure_disposition: str = "completed",
|
|
7468
|
+
closure_reason: str = "",
|
|
7305
7469
|
) -> tuple[int, dict[str, Any]]:
|
|
7306
7470
|
"""Execute the 9-step finalize pipeline. Returns (exit_code, payload)."""
|
|
7307
7471
|
if archive_intent not in {"release-candidate", "record-only"}:
|
|
@@ -7310,6 +7474,25 @@ def cmd_finalize(
|
|
|
7310
7474
|
"action": "finalize",
|
|
7311
7475
|
"error": "archive_intent must be release-candidate or record-only",
|
|
7312
7476
|
}
|
|
7477
|
+
if closure_disposition not in {"completed", "abandoned", "superseded"}:
|
|
7478
|
+
return 1, {
|
|
7479
|
+
"ok": False,
|
|
7480
|
+
"action": "finalize",
|
|
7481
|
+
"error": "invalid closure disposition",
|
|
7482
|
+
}
|
|
7483
|
+
closure_reason = closure_reason.strip()
|
|
7484
|
+
if closure_disposition != "completed" and not closure_reason:
|
|
7485
|
+
return 1, {
|
|
7486
|
+
"ok": False,
|
|
7487
|
+
"action": "finalize",
|
|
7488
|
+
"error": "closure reason is required for abandoned or superseded changes",
|
|
7489
|
+
}
|
|
7490
|
+
if closure_disposition != "completed" and archive_intent == "release-candidate":
|
|
7491
|
+
return 1, {
|
|
7492
|
+
"ok": False,
|
|
7493
|
+
"action": "finalize",
|
|
7494
|
+
"error": "abandoned or superseded changes cannot be release candidates",
|
|
7495
|
+
}
|
|
7313
7496
|
warnings: list[str] = []
|
|
7314
7497
|
original_change_dir = change_dir.resolve()
|
|
7315
7498
|
change_name = original_change_dir.name
|
|
@@ -7396,6 +7579,9 @@ def cmd_finalize(
|
|
|
7396
7579
|
"operationTempDir": str(operation_temp_dir),
|
|
7397
7580
|
"operationRecord": str(operation_record),
|
|
7398
7581
|
"archiveIntent": archive_intent,
|
|
7582
|
+
"releaseIntent": "candidate" if archive_intent == "release-candidate" else "none",
|
|
7583
|
+
"closureDisposition": closure_disposition,
|
|
7584
|
+
"closureReason": closure_reason or None,
|
|
7399
7585
|
"releaseEligible": False,
|
|
7400
7586
|
"warnings": warnings,
|
|
7401
7587
|
"steps": {},
|
|
@@ -7787,6 +7973,11 @@ def cmd_finalize(
|
|
|
7787
7973
|
{
|
|
7788
7974
|
"schemaVersion": 1,
|
|
7789
7975
|
"intent": archive_intent,
|
|
7976
|
+
"releaseIntent": (
|
|
7977
|
+
"candidate" if archive_intent == "release-candidate" else "none"
|
|
7978
|
+
),
|
|
7979
|
+
"closureDisposition": closure_disposition,
|
|
7980
|
+
"closureReason": closure_reason or None,
|
|
7790
7981
|
"candidateVerificationCode": ci_gate.get("code"),
|
|
7791
7982
|
"releaseEligible": False,
|
|
7792
7983
|
"releaseDecisionCode": "PENDING_IDENTITY_AND_REPORT_GATES",
|
|
@@ -9219,6 +9410,8 @@ def cmd_status_cli(args: argparse.Namespace) -> int:
|
|
|
9219
9410
|
change_dir,
|
|
9220
9411
|
allow_missing_review=bool(getattr(args, "allow_missing_review", False)),
|
|
9221
9412
|
archive_intent=str(getattr(args, "intent", "release-candidate")),
|
|
9413
|
+
closure_disposition=str(getattr(args, "closure", "completed")),
|
|
9414
|
+
closure_reason=str(getattr(args, "closure_reason", "")),
|
|
9222
9415
|
)
|
|
9223
9416
|
emit_json(result)
|
|
9224
9417
|
# Checks completed → exit 0; archivable flag conveys the gate result.
|
|
@@ -9289,6 +9482,8 @@ def cmd_finalize_cli(args: argparse.Namespace) -> int:
|
|
|
9289
9482
|
skip_ingest=bool(args.skip_ingest),
|
|
9290
9483
|
allow_missing_review=bool(getattr(args, "allow_missing_review", False)),
|
|
9291
9484
|
archive_intent=str(getattr(args, "intent", "release-candidate")),
|
|
9485
|
+
closure_disposition=str(getattr(args, "closure", "completed")),
|
|
9486
|
+
closure_reason=str(getattr(args, "closure_reason", "")),
|
|
9292
9487
|
)
|
|
9293
9488
|
emit_json(payload)
|
|
9294
9489
|
return code
|
|
@@ -9497,6 +9692,13 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
9497
9692
|
action="store_true",
|
|
9498
9693
|
help="downgrade full-tier missing review from blocker to warning",
|
|
9499
9694
|
)
|
|
9695
|
+
p_status.add_argument(
|
|
9696
|
+
"--closure",
|
|
9697
|
+
choices=("completed", "abandoned", "superseded"),
|
|
9698
|
+
default="completed",
|
|
9699
|
+
help="lifecycle outcome, independent from release intent",
|
|
9700
|
+
)
|
|
9701
|
+
p_status.add_argument("--closure-reason", default="")
|
|
9500
9702
|
p_status.add_argument(
|
|
9501
9703
|
"--intent",
|
|
9502
9704
|
choices=("release-candidate", "record-only"),
|
|
@@ -9543,6 +9745,13 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
9543
9745
|
"the project and is verified before source deletion"
|
|
9544
9746
|
),
|
|
9545
9747
|
)
|
|
9748
|
+
p_fin.add_argument(
|
|
9749
|
+
"--closure",
|
|
9750
|
+
choices=("completed", "abandoned", "superseded"),
|
|
9751
|
+
default="completed",
|
|
9752
|
+
help="lifecycle outcome, independent from release intent",
|
|
9753
|
+
)
|
|
9754
|
+
p_fin.add_argument("--closure-reason", default="")
|
|
9546
9755
|
p_fin.add_argument(
|
|
9547
9756
|
"--retention-policy",
|
|
9548
9757
|
default="unspecified",
|