189 lines
8.5 KiB
Python
189 lines
8.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Generate the explicitly authorized selected-case release gate.
|
|
|
|
This is an additive policy gate. It never rewrites the historical K8 gate or
|
|
turns unresolved technical evidence into verified parity; it records the
|
|
user-authorized risk acceptance for the selected 3axis tutorial bundle.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
AUTHORIZATION = Path("coverage/release-authorization.json")
|
|
HISTORICAL_GATE = Path("coverage/release-candidate-gate.report.json")
|
|
T7_GATE = Path("coverage/wasm-simulation-release-gate-t7.report.json")
|
|
VERIFICATION = Path("coverage/release-verification.report.json")
|
|
OUTPUT = Path("coverage/release-authorized-gate.report.json")
|
|
CONTEXT_ID = "linuxcnc-2.10.0-pre0-wasm-m1-py-c1"
|
|
SELECTED_CASE = "linuxcnc/configs/sim/axis/vismach/3axis-tutorial/3axis-tutorial.ini"
|
|
BLOCKERS = (
|
|
"manifest_seed_unassigned",
|
|
"accepted_entries_missing",
|
|
"official_oracle_missing",
|
|
"configuration_resources_missing",
|
|
"browser_matrix_unverified",
|
|
"license_closure_incomplete",
|
|
)
|
|
|
|
|
|
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:
|
|
return hashlib.sha256(path.read_bytes()).hexdigest()
|
|
|
|
|
|
def artifact(root: Path, path: str, role: str) -> dict[str, Any]:
|
|
relative = Path(path)
|
|
full = root / relative
|
|
if not full.is_file():
|
|
return {"path": relative.as_posix(), "role": role, "status": "missing"}
|
|
return {
|
|
"path": relative.as_posix(),
|
|
"role": role,
|
|
"status": "present",
|
|
"bytes": full.stat().st_size,
|
|
"sha256": file_digest(full),
|
|
}
|
|
|
|
|
|
def load(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 generate(root: Path, output: Path = OUTPUT) -> dict[str, Any]:
|
|
root = root.resolve()
|
|
authorization = load(root, AUTHORIZATION)
|
|
historical = load(root, HISTORICAL_GATE)
|
|
t7 = load(root, T7_GATE)
|
|
verification = load(root, VERIFICATION)
|
|
|
|
waivers = authorization.get("waivers")
|
|
waiver_by_id = {
|
|
row.get("blocker"): row
|
|
for row in waivers
|
|
if isinstance(row, dict)
|
|
} if isinstance(waivers, list) else {}
|
|
waiver_ids = set(waiver_by_id)
|
|
errors: list[dict[str, Any]] = []
|
|
if authorization.get("schema") != "release-authorization.v1":
|
|
errors.append({"code": "authorization_schema_invalid"})
|
|
if authorization.get("context_id") != CONTEXT_ID:
|
|
errors.append({"code": "authorization_context_mismatch", "observed": authorization.get("context_id")})
|
|
if authorization.get("authorized_by") != "workspace-user":
|
|
errors.append({"code": "authorization_owner_invalid"})
|
|
if authorization.get("release_scope") != "selected_case_web_wasm_simulation":
|
|
errors.append({"code": "release_scope_invalid"})
|
|
if authorization.get("decision") != "approve_with_explicit_risk_acceptance":
|
|
errors.append({"code": "authorization_decision_invalid"})
|
|
if authorization.get("candidate_eligible") is not True or authorization.get("release_action") != "ALLOW_RELEASE":
|
|
errors.append({"code": "authorization_release_decision_invalid"})
|
|
if waiver_ids != set(BLOCKERS):
|
|
errors.append({"code": "waiver_identity_invalid", "expected": list(BLOCKERS), "observed": sorted(waiver_ids)})
|
|
if isinstance(waivers, list) and len(waivers) != len(BLOCKERS):
|
|
errors.append({"code": "waiver_count_invalid", "observed": len(waivers)})
|
|
|
|
evidence: list[dict[str, Any]] = []
|
|
for blocker in BLOCKERS:
|
|
row = waiver_by_id.get(blocker)
|
|
if not row or row.get("disposition") != "waived" or not str(row.get("rationale", "")).strip():
|
|
errors.append({"code": "waiver_row_invalid", "blocker": blocker})
|
|
continue
|
|
rows = row.get("evidence")
|
|
if not isinstance(rows, list) or not rows:
|
|
errors.append({"code": "waiver_evidence_missing", "blocker": blocker})
|
|
continue
|
|
for item in rows:
|
|
if not isinstance(item, dict) or not isinstance(item.get("path"), str) or not isinstance(item.get("sha256"), str):
|
|
errors.append({"code": "waiver_evidence_invalid", "blocker": blocker})
|
|
continue
|
|
path = Path(item["path"])
|
|
full = root / path
|
|
if not full.is_file():
|
|
errors.append({"code": "waiver_evidence_missing", "blocker": blocker, "path": path.as_posix()})
|
|
continue
|
|
actual = file_digest(full)
|
|
evidence.append({"blocker": blocker, "path": path.as_posix(), "sha256": actual, "hash_match": actual == item["sha256"]})
|
|
if actual != item["sha256"]:
|
|
errors.append({"code": "waiver_evidence_hash_mismatch", "blocker": blocker, "path": path.as_posix()})
|
|
|
|
historical_blockers = [row.get("id") for row in historical.get("blockers", []) if isinstance(row, dict)]
|
|
if set(historical_blockers) != set(BLOCKERS):
|
|
errors.append({"code": "historical_gate_shape_invalid", "observed": historical_blockers})
|
|
if historical.get("candidate_eligible") is not False or historical.get("release_action") != "STOP_RELEASE":
|
|
errors.append({"code": "historical_gate_not_fail_closed"})
|
|
if t7.get("candidate_eligible") is not False or t7.get("release_action") != "STOP_RELEASE":
|
|
errors.append({"code": "t7_gate_not_fail_closed"})
|
|
if verification.get("schema") != "release-verification.v1" or verification.get("status") != "verified" or verification.get("release_action") != "ALLOW_RELEASE":
|
|
errors.append({"code": "release_verification_not_passed"})
|
|
if verification.get("passed_command_count") != verification.get("required_command_count"):
|
|
errors.append({"code": "release_verification_incomplete"})
|
|
|
|
report: dict[str, Any] = {
|
|
"schema": "release-authorized-gate.v1",
|
|
"status": "authorized" if not errors else "blocked",
|
|
"implementation_status": "verified" if not errors else "blocked",
|
|
"task_id": "RELEASE-AUTHORIZATION-01",
|
|
"context_id": authorization.get("context_id"),
|
|
"release_scope": authorization.get("release_scope"),
|
|
"candidate_eligible": not errors,
|
|
"release_action": "ALLOW_RELEASE" if not errors else "STOP_RELEASE",
|
|
"authorization": {
|
|
"path": AUTHORIZATION.as_posix(),
|
|
"sha256": file_digest(root / AUTHORIZATION),
|
|
"authorization_id": authorization.get("authorization_id"),
|
|
"authorized_by": authorization.get("authorized_by"),
|
|
"decision": authorization.get("decision"),
|
|
},
|
|
"waived_blockers": [] if errors else list(BLOCKERS),
|
|
"remaining_blockers": [],
|
|
"historical_gate": artifact(root, HISTORICAL_GATE.as_posix(), "historical_fail_closed_gate"),
|
|
"t7_gate": artifact(root, T7_GATE.as_posix(), "simulation_fail_closed_gate"),
|
|
"verification": artifact(root, VERIFICATION.as_posix(), "selected_case_release_verification"),
|
|
"evidence": evidence,
|
|
"errors": errors,
|
|
"risk_boundary": {
|
|
"technical_evidence_rewritten": False,
|
|
"full_linuxcnc_parity_claim": False,
|
|
"official_oracle_claim": False,
|
|
"safari_support_claim": False,
|
|
"manifest_ownership_closure_claim": False,
|
|
"license_closure_claim": False,
|
|
},
|
|
"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()
|
|
parser.add_argument("--repo-root", type=Path, default=ROOT)
|
|
parser.add_argument("--output", type=Path, default=OUTPUT)
|
|
args = parser.parse_args()
|
|
report = generate(args.repo_root.resolve(), args.output)
|
|
print(f"release_authorized_gate={report['status']} action={report['release_action']} scope={report['release_scope']}")
|
|
return 0 if report["status"] == "authorized" else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|