Complete 3axis tutorial native and Web/WASM parity tools
This commit is contained in:
188
scripts/generate_release_authorized_gate.py
Normal file
188
scripts/generate_release_authorized_gate.py
Normal file
@@ -0,0 +1,188 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user