K8: record blocked owner review and release evidence

This commit is contained in:
2026-08-21 14:27:23 +08:00
parent 2ef2d82db3
commit b1b5a3cc8a
62 changed files with 171781 additions and 19 deletions

View File

@@ -0,0 +1,174 @@
#!/usr/bin/env python3
"""Generate the deterministic K8 milestone closeout record.
Closeout completion means the evidence package was audited and frozen with an
explicit release stop. It does not turn the blocked package into a candidate.
"""
from __future__ import annotations
import argparse
import hashlib
import json
from pathlib import Path
from typing import Any
CONTEXT = Path("context-lock.json")
AUDIT = Path("coverage/post-release-audit.report.json")
GATE = Path("coverage/release-candidate-gate.report.json")
CLOSURE = Path("coverage/release-closure.report.json")
INDEX = Path("coverage/release-evidence-index.json")
OUTPUT = Path("coverage/k8-milestone-closeout.report.json")
def canonical(value: Any) -> str:
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
def digest(value: Any) -> str:
return hashlib.sha256(canonical(value).encode("utf-8")).hexdigest()
def file_digest(path: Path) -> str | None:
if not path.is_file():
return None
handle = hashlib.sha256()
with path.open("rb") as stream:
for block in iter(lambda: stream.read(1024 * 1024), b""):
handle.update(block)
return handle.hexdigest()
def read_json(root: Path, path: Path) -> dict[str, Any]:
value = json.loads((root / path).read_text(encoding="utf-8"))
if not isinstance(value, dict):
raise ValueError(f"{path} must contain an object")
return value
def artifact(root: Path, path: Path, role: str) -> dict[str, Any]:
full = root / path
sha = file_digest(full)
if sha is None:
return {"path": path.as_posix(), "role": role, "status": "missing"}
return {"path": path.as_posix(), "role": role, "status": "present", "bytes": full.stat().st_size, "sha256": sha}
def generate(root: Path, output: Path = OUTPUT) -> dict[str, Any]:
root = root.resolve()
errors: dict[str, str] = {}
documents: dict[Path, dict[str, Any]] = {}
for path in (CONTEXT, AUDIT, GATE, CLOSURE, INDEX):
try:
documents[path] = read_json(root, path)
except (OSError, UnicodeError, json.JSONDecodeError, ValueError) as exc:
documents[path] = {}
errors[path.as_posix()] = str(exc)
lock = documents[CONTEXT]
audit = documents[AUDIT]
gate = documents[GATE]
closure = documents[CLOSURE]
index = documents[INDEX]
context_id = lock.get("context_id")
hash_rows = []
for path in (CONTEXT, AUDIT, GATE, CLOSURE, INDEX):
actual = file_digest(root / path)
hash_rows.append({"path": path.as_posix(), "sha256": actual, "status": "verified" if actual else "blocked"})
hash_ok = all(row["status"] == "verified" for row in hash_rows)
audit_report_hash = audit.get("report_sha256")
audit_report_hash_ok = bool(audit_report_hash) and audit_report_hash == digest({key: value for key, value in audit.items() if key != "report_sha256"})
stop_ok = (
gate.get("release_action") == "STOP_RELEASE"
and gate.get("release_decision") == "blocked_not_release_candidate"
and gate.get("candidate_eligible") is False
and gate.get("blocked_count") == 6
and audit.get("release_action") == "STOP_RELEASE"
and audit.get("candidate_eligible") is False
and audit.get("blocked_count") == 6
)
context_ok = context_id and all(
report.get("context_id") in {None, context_id}
for report in (audit, gate, closure, index)
)
schema_versions = lock.get("schema_versions", {})
schema_ok = bool(schema_versions) and all(value == 1 for value in schema_versions.values())
audit_checks = audit.get("checks", {})
audit_checks_ok = all(value.get("status") == "verified" for value in audit_checks.values())
blocker_ids = {row.get("id") for row in audit.get("blockers", [])}
blocker_ok = len(blocker_ids) == 6 and all(row.get("status") == "blocked" for row in audit.get("blockers", []))
checks = {
"input_hashes": {"status": "verified" if hash_ok else "blocked", "artifacts": hash_rows},
"audit_self_hash": {"status": "verified" if audit_report_hash_ok else "blocked", "declared": audit_report_hash},
"context_consistency": {"status": "verified" if context_ok else "blocked", "context_id": context_id},
"locked_schema_contracts": {"status": "verified" if schema_ok else "blocked", "schema_versions": schema_versions},
"audit_checks": {"status": "verified" if audit_checks_ok else "blocked", "checks": {key: value.get("status") for key, value in audit_checks.items()}},
"stop_decision": {"status": "verified" if stop_ok else "blocked", "blocked_count": gate.get("blocked_count")},
"blocker_preservation": {"status": "verified" if blocker_ok else "blocked", "count": len(audit.get("blockers", [])), "ids": sorted(str(value) for value in blocker_ids)},
}
checks_ok = all(value["status"] == "verified" for value in checks.values()) and not errors
report: dict[str, Any] = {
"schema": "k8-milestone-closeout.v1",
"context_id": context_id,
"milestone": "K8",
"task_id": "K8-REL-06",
"status": "complete" if checks_ok else "blocked",
"milestone_status": "closed_with_release_blockers" if checks_ok else "closeout_blocked",
"release_decision": "blocked_not_release_candidate",
"release_action": "STOP_RELEASE",
"candidate_eligible": False,
"source_baseline": {
"linuxcnc_version": lock.get("linuxcnc_version"),
"linuxcnc_source_sha": lock.get("linuxcnc_source_sha"),
"context_lock": artifact(root, CONTEXT, "context_lock"),
},
"inputs": [
artifact(root, AUDIT, "post_release_audit"),
artifact(root, GATE, "release_candidate_gate"),
artifact(root, CLOSURE, "release_closure"),
artifact(root, INDEX, "release_evidence_index"),
artifact(root, CONTEXT, "context_lock"),
],
"checks": checks,
"blockers": audit.get("blockers", []),
"blocked_count": len(audit.get("blockers", [])),
"rebuild": {
"commands": [
"python3 scripts/generate_release_closure.py",
"python3 scripts/generate_post_release_audit.py",
"python3 scripts/generate_k8_milestone_closeout.py",
"node web/worker/test_replay.mjs",
"node web/workspace/test_recovery.mjs",
"node web/workspace/test_migration.mjs",
"python3 -m unittest discover -s scripts -p 'test_*.py' -v",
],
"deterministic_inputs": [CONTEXT.as_posix(), AUDIT.as_posix(), GATE.as_posix(), CLOSURE.as_posix(), INDEX.as_posix()],
},
"failure_policy": {
"on_missing_input": "STOP_RELEASE",
"on_hash_mismatch": "STOP_RELEASE",
"forbidden": ["delete_samples", "lower_checks", "reclassify_as_REAL-EXCLUDED", "promote_blocked_to_accepted"],
},
"errors": errors,
"report_sha256": None,
}
report["report_sha256"] = digest({key: value for key, value in report.items() if key != "report_sha256"})
destination = output if output.is_absolute() else root / output
destination.parent.mkdir(parents=True, exist_ok=True)
destination.write_text(json.dumps(report, ensure_ascii=False, sort_keys=True, indent=2) + "\n", encoding="utf-8")
return report
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--repo-root", type=Path, default=Path(__file__).resolve().parents[1])
parser.add_argument("--output", type=Path, default=OUTPUT)
args = parser.parse_args()
report = generate(args.repo_root, args.output)
print("k8_closeout=" + report["status"] + " action=" + report["release_action"] + " blockers=" + str(report["blocked_count"]))
return 0
if __name__ == "__main__":
raise SystemExit(main())