#!/usr/bin/env python3 """Build deterministic K8 compatibility, license and release evidence reports. The reports intentionally distinguish a verified implementation slice from a release-acceptable result. Missing upstream oracles, source metadata, browser coverage and unassigned manifest seeds stay visible blockers; no derived configuration result is promoted to an accepted golden. """ from __future__ import annotations import argparse import hashlib import json import re from collections import Counter from fnmatch import fnmatchcase from pathlib import Path from typing import Any, Iterable CONTEXT = Path("context-lock.json") SOURCE_INDEX = Path("coverage/source-index.jsonl") MANIFEST = Path("coverage/manifest.jsonl") CONFIGURATION = Path("coverage/configuration-regression.report.json") PYTHON_MANIFEST = Path("python-runtime/manifest.json") PYTHON_REPORT = Path("coverage/python-runtime.report.json") DOWNLOAD_INDEX = Path("vendor/downloads/index.jsonl") FULL_PORT_DOC = Path("docs/linuxcnc-web-wasm-full-port-implementation.md") LICENSE_INDEX = Path("coverage/license-source-index.jsonl") IMPLEMENTATION_LICENSE_DECISION = Path("docs/linuxcnc-web-wasm-license-decision.md") REPORT_PATHS = ( "coverage/configuration-regression.report.json", "coverage/corexy-hal.report.json", "coverage/coupled-kinematics.report.json", "coverage/delta-kinematics.report.json", "coverage/fiveaxis-kinematics.report.json", "coverage/golden-schema.json", "coverage/genhex-kinematics.report.json", "coverage/genser-kinematics.report.json", "coverage/hal-runtime.report.json", "coverage/interpreter-runtime.report.json", "coverage/io-runtime.report.json", "coverage/kinematics-abi-contract.json", "coverage/kinematics-feedback.report.json", "coverage/kinematics-golden-schema.json", "coverage/kinematics-home-limit.report.json", "coverage/kinematics-motion.report.json", "coverage/kinematics-mapping-contract.json", "coverage/kinematics-numeric-policy.json", "coverage/kinematics-oracle.report.json", "coverage/kinematics-registry.report.json", "coverage/kinematics-runtime.report.json", "coverage/kinematics-source-closure.report.json", "coverage/motion-runtime.report.json", "coverage/native-runner.report.json", "coverage/posemath-runtime.report.json", "coverage/robot-kinematics.report.json", "coverage/runtime-mapping.report.json", "coverage/switchkins-kinematics.report.json", "coverage/test-entries.report.json", "coverage/tripod-kinematics.report.json", "coverage/trivkins-integration.report.json", "coverage/trt-kinematics.report.json", "coverage/userkins-kinematics.report.json", "coverage/workspace-recovery.report.json", "coverage/workspace-migration.report.json", "coverage/workspace-rollback.package.json", "coverage/offline-cache.package.json", ) DOMAIN_REPORTS = { "configuration": "coverage/configuration-regression.report.json", "protocol": "coverage/runtime-mapping.report.json", "posemath": "coverage/posemath-runtime.report.json", "interpreter": "coverage/interpreter-runtime.report.json", "motion": "coverage/motion-runtime.report.json", "kinematics": "coverage/kinematics-runtime.report.json", "hal": "coverage/hal-runtime.report.json", "io": "coverage/io-runtime.report.json", "python": "coverage/python-runtime.report.json", "recovery": "coverage/workspace-recovery.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: 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 read_jsonl(root: Path, path: Path) -> list[dict[str, Any]]: rows = [] for number, line in enumerate((root / path).read_text(encoding="utf-8").splitlines(), 1): if not line.strip(): continue value = json.loads(line) if not isinstance(value, dict): raise ValueError(f"{path}:{number} must contain an object") rows.append(value) return rows LICENSE_SPDX = { "BSD-3-Clause": "BSD-3-Clause", "Boost-1.0": "BSL-1.0", "CC-BY-3.0": "CC-BY-3.0", "GFDL-1.1+": "GFDL-1.1-or-later", "GNU-All-Permissive": "FSFAP", "GPL-2": "GPL-2.0-only", "GPL-2+": "GPL-2.0-or-later", "GPL-3+": "GPL-3.0-or-later", "ISC": "ISC", "LGPL-2": "LGPL-2.0-only", "LGPL-2.1+": "LGPL-2.1-or-later", "MIT": "MIT", } def parse_copyright_scopes(text: str) -> list[dict[str, Any]]: """Parse Debian copyright file scopes without interpreting license text.""" scopes: list[dict[str, Any]] = [] for order, paragraph in enumerate(re.split(r"\n\s*\n", text)): fields: dict[str, list[str]] = {} current: str | None = None for line in paragraph.splitlines(): match = re.match(r"^([A-Za-z][A-Za-z-]*):\s*(.*)$", line) if match: current = match.group(1) fields[current] = [match.group(2).strip()] if match.group(2).strip() else [] elif current and line.startswith(" "): value = line.strip() if value: fields[current].append(value) patterns = [pattern for value in fields.get("Files", []) for pattern in value.split()] license_id = fields.get("License", [""])[0].strip() if patterns and license_id: scopes.append({ "order": order, "patterns": patterns, "license": license_id, "spdx": LICENSE_SPDX.get(license_id), }) return scopes def match_copyright_scope(relative_path: str, scopes: list[dict[str, Any]]) -> dict[str, Any] | None: matches: list[tuple[int, int, str, dict[str, Any]]] = [] for scope in scopes: for pattern in scope["patterns"]: if fnmatchcase(relative_path, pattern): specificity = sum(character not in "*?[" for character in pattern) matches.append((specificity, scope["order"], pattern, scope)) if not matches: return None _, _, pattern, scope = max(matches, key=lambda item: (item[0], item[1], item[2])) return {"pattern": pattern, **scope} def write_jsonl(path: Path, rows: Iterable[dict[str, Any]]) -> dict[str, Any]: content = "".join(f"{canonical(row)}\n" for row in rows) path.parent.mkdir(parents=True, exist_ok=True) path.write_text(content, encoding="utf-8") return {"path": path.as_posix(), "bytes": len(content.encode("utf-8")), "sha256": file_digest(path), "status": "present"} def write_report(path: Path, report: dict[str, Any]) -> dict[str, Any]: report["report_sha256"] = digest({key: value for key, value in report.items() if key != "report_sha256"}) path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps(report, ensure_ascii=False, sort_keys=True, indent=2) + "\n", encoding="utf-8") return report def artifact(root: Path, path: Path, role: str) -> dict[str, Any]: full = root / path if not full.is_file(): return {"path": path.as_posix(), "role": role, "status": "missing"} value: dict[str, Any] = { "path": path.as_posix(), "role": role, "status": "present", "bytes": full.stat().st_size, "sha256": file_digest(full), } if full.suffix == ".json": try: parsed = json.loads(full.read_text(encoding="utf-8")) except (OSError, UnicodeError, json.JSONDecodeError): parsed = None if isinstance(parsed, dict): for key in ("schema", "$id", "status", "implementation_status"): if key in parsed: value[key] = parsed[key] if isinstance(parsed.get("oracle"), dict): value["oracle_status"] = parsed["oracle"].get("status") return value def report_status(report: dict[str, Any]) -> str: if report.get("implementation_status") == "verified": return "verified" status = report.get("status") return str(status) if status else "unknown" def nested_report_value(report: dict[str, Any], key: str, nested: str) -> Any: value = report.get(key) return value.get(nested) if isinstance(value, dict) else None def source_observation(root: Path) -> dict[str, Any]: lock = read_json(root, CONTEXT) observed: dict[str, dict[str, Any]] = { "sim_abi": {"value": lock["schema_versions"].get("sim_abi"), "source": "context-lock.json"}, "protocol": {"value": lock["schema_versions"].get("protocol"), "source": "context-lock.json"}, "snapshot": {"value": lock["schema_versions"].get("snapshot"), "source": "context-lock.json"}, "event": {"value": lock["schema_versions"].get("event"), "source": "context-lock.json"}, "workspace": {"value": lock["schema_versions"].get("workspace"), "source": "context-lock.json"}, "manifest": {"value": lock["schema_versions"].get("manifest"), "source": "context-lock.json"}, } checks = ( ("protocol", Path("web/worker/sim-session.mjs"), r"PROTOCOL_SCHEMA\s*=\s*1"), ("event", Path("web/worker/replay.mjs"), r'event-log\.v1'), ("snapshot", Path("web/worker/replay.mjs"), r'snapshot\.v1'), ("workspace", Path("web/workspace/recovery.mjs"), r'simulation-recovery\.v1'), ) for name, path, pattern in checks: text = (root / path).read_text(encoding="utf-8") if (root / path).is_file() else "" observed[name]["source_observed"] = bool(re.search(pattern, text)) observed[name]["source"] = path.as_posix() observed["manifest"]["source_observed"] = (root / MANIFEST).is_file() observed["sim_abi"]["source_observed"] = (root / Path("coverage/kinematics-abi-contract.json")).is_file() for value in observed.values(): value["status"] = "verified" if value.get("value") == 1 and value.get("source_observed") else "blocked" return observed def generate_compatibility(root: Path, out: Path) -> dict[str, Any]: lock = read_json(root, CONTEXT) config = read_json(root, CONFIGURATION) migration = read_json(root, Path("coverage/workspace-migration.report.json")) reports = {name: read_json(root, Path(path)) for name, path in DOMAIN_REPORTS.items() if (root / Path(path)).is_file()} domains = [] for name, path in DOMAIN_REPORTS.items(): report = reports.get(name, {}) domains.append({ "domain": name, "artifact": path, "implementation_status": report_status(report), "runtime_status": report.get("status", "missing"), "oracle_status": report.get("oracle", {}).get("status") if isinstance(report.get("oracle"), dict) else None, "test_evidence": report.get("native_tests") or report.get("native_wasm") or report.get("native_and_wasm") or nested_report_value(report, "runtime", "native_wasm"), "status": "blocked" if nested_report_value(report, "oracle", "status") == "blocked" or report.get("status") in {"partial", "blocked"} else report_status(report), }) browser = lock.get("browser_matrix", {}) browser_ready = browser.get("status") == "verified" and all(browser.get("minimum", {}).values()) contract = source_observation(root) migration_ready = migration.get("status") == "passed" and migration.get("implementation_status") == "verified" and migration.get("migration", {}).get("round_trip_equal") is True and migration.get("rollback", {}).get("status") == "passed" and migration.get("offline", {}).get("status") == "passed" and (root / Path("coverage/workspace-rollback.package.json")).is_file() and (root / Path("coverage/offline-cache.package.json")).is_file() hash_checks = [] for key, path in ( ("configuration_regression_report_sha256", CONFIGURATION), ("python_runtime_report_sha256", PYTHON_REPORT), ("source_index_sha256", SOURCE_INDEX), ("manifest_seed_sha256", MANIFEST), ): expected = lock.get(key) actual = file_digest(root / path) if (root / path).is_file() else None hash_checks.append({"lock_key": key, "path": path.as_posix(), "expected": expected, "actual": actual, "status": "verified" if expected == actual else "blocked"}) report = { "schema": "release-compatibility-matrix.v1", "context_id": lock.get("context_id"), "status": "blocked", "release_decision": "blocked_not_release_candidate", "source_baseline": { "linuxcnc_version": lock.get("linuxcnc_version"), "linuxcnc_source_sha": lock.get("linuxcnc_source_sha"), "linuxcnc_version_sha256": lock.get("linuxcnc_version_sha256"), "context_lock": {"path": CONTEXT.as_posix(), "sha256": file_digest(root / CONTEXT)}, }, "contracts": { "schema_versions": lock.get("schema_versions", {}), "observed": contract, "status": "verified" if all(value["status"] == "verified" for value in contract.values()) else "blocked", }, "hash_checks": hash_checks, "domains": domains, "configuration": { "entries": config.get("counts", {}).get("registered_configuration_entries"), "entrypoints": config.get("counts", {}).get("config_entrypoints"), "startup_preflight": config.get("counts", {}).get("startup_preflight"), "resource_status": config.get("counts", {}).get("resource_status"), "derived_results_are_not_official": config.get("oracle", {}).get("derived_configuration_results_are_not_official"), }, "migration_rollback_offline": { "status": "verified" if migration_ready else "blocked", "artifact": artifact(root, Path("coverage/workspace-migration.report.json"), "migration_rollback_offline"), "rollback_package": artifact(root, Path("coverage/workspace-rollback.package.json"), "rollback_package"), "offline_package": artifact(root, Path("coverage/offline-cache.package.json"), "offline_package"), "format_schema": migration.get("format", {}).get("schema"), "supported_versions": migration.get("format", {}).get("supported_versions", []), "round_trip_equal": migration.get("migration", {}).get("round_trip_equal"), "rollback_recovery": migration.get("rollback", {}).get("recovery_status"), "offline_start": migration.get("offline", {}).get("offline_start"), "no_silent_downgrade": migration.get("recovery_endpoint", {}).get("no_silent_downgrade") is True, }, "browser_matrix": { "status": "verified" if browser_ready else "blocked", "declared_status": browser.get("status"), "minimum": browser.get("minimum", {}), "reason": "minimum Chromium/Firefox/Safari versions are TBD" if not browser_ready else None, }, "oracle": { "status": "blocked", "official_root": "linuxcnc/unit_tests/kinematics", "present": (root / "linuxcnc/unit_tests/kinematics").is_dir(), "reason": "official kinematics and recovery oracle is absent from the locked source", }, "real_excluded": [ {"id": "hardware_driver", "reason": "physical hardware and device access are outside browser simulation"}, {"id": "linux_realtime_kernel", "reason": "hard real-time kernel scheduling is outside WebAssembly"}, {"id": "host_device_access", "reason": "host paths, sockets and devices are denied by the Worker runtime"}, ], "blockers": [ "official_oracle_missing", "browser_matrix_unverified", "configuration_resource_gaps", "manifest_seed_unassigned", "license_metadata_not_per_entry", ] + ([] if migration_ready else ["migration_and_rollback_pending"]), "inputs": [artifact(root, CONTEXT, "context_lock"), artifact(root, SOURCE_INDEX, "source_index"), artifact(root, MANIFEST, "manifest_seed"), artifact(root, CONFIGURATION, "configuration_regression"), artifact(root, PYTHON_MANIFEST, "python_runtime_manifest"), artifact(root, FULL_PORT_DOC, "implementation_contract"), artifact(root, Path("coverage/workspace-migration.report.json"), "migration_rollback_offline"), artifact(root, Path("coverage/workspace-rollback.package.json"), "rollback_package"), artifact(root, Path("coverage/offline-cache.package.json"), "offline_package")], "report_sha256": None, } return write_report(out, report) def generate_license(root: Path, out: Path) -> dict[str, Any]: lock = read_json(root, CONTEXT) sources = read_jsonl(root, SOURCE_INDEX) manifest = read_jsonl(root, MANIFEST) test_report = read_json(root, Path("coverage/test-entries.report.json")) test_rows = read_jsonl(root, Path("coverage/test-entries.jsonl")) copyright_path = Path("linuxcnc/debian/copyright") copyright_text = (root / copyright_path).read_text(encoding="utf-8") declared = sorted(set(re.findall(r"^License:\s*(.+)$", copyright_text, re.MULTILINE))) download_rows = read_jsonl(root, DOWNLOAD_INDEX) python_manifest = read_json(root, PYTHON_MANIFEST) artifact_names = {str(row.get("name")) for row in download_rows} python_artifacts = python_manifest.get("artifacts", []) download_valid = all(str(row.get("name")) in artifact_names and row.get("license") for row in python_artifacts) path_counts = Counter(str(row.get("path", "")).split("/", 1)[0] for row in sources) real_rows = [row for row in test_rows if row.get("portability") == "REAL-EXCLUDED"] source_lookup = {str(row.get("source_id")): row for row in sources} copyright_scopes = parse_copyright_scopes(copyright_text) license_entries: list[dict[str, Any]] = [] for row in sorted(manifest, key=lambda value: str(value.get("id"))): source_id = str(row.get("source")) source_row = source_lookup.get(source_id) source_path = str(source_row.get("path")) if source_row else "" source_root = source_path.split("/", 1)[0] if source_path else "unknown" source_hash = source_row.get("sha256") if source_row else None manifest_hash = row.get("source_sha") source_hash_status = "verified" if source_hash and manifest_hash == source_hash else "blocked" evidence = ["coverage/manifest.jsonl", "coverage/source-index.jsonl"] owner = None owner_source = None license_scope = None license_declared = None license_spdx = None license_status = "blocked_unassigned" closure_status = "blocked" if source_root == "linuxcnc": owner = "upstream-linuxcnc-license-audit" owner_source = "source_root_policy" relative_path = source_path[len("linuxcnc/"):] if source_path.startswith("linuxcnc/") else source_path scope = match_copyright_scope(relative_path, copyright_scopes) if scope and scope.get("spdx"): license_scope = scope["pattern"] license_declared = scope["license"] license_spdx = scope["spdx"] license_status = "scoped_declared" closure_status = "verified_scoped" evidence.append("linuxcnc/debian/copyright") else: license_status = "blocked_scope_unmatched" evidence.append("linuxcnc/debian/copyright") elif source_root in {"wasm-core", "web"}: owner = "web-wasm-license-owner" owner_source = "implementation_license_decision" evidence.append(IMPLEMENTATION_LICENSE_DECISION.as_posix()) else: owner = "source-provenance-license-owner" owner_source = "source_root_policy" license_status = "blocked_scope_unknown" evidence_status = "complete" if source_hash_status == "verified" and all((root / path).is_file() for path in evidence) else "incomplete" license_entries.append({ "id": row.get("id"), "source": source_id, "source_path": source_path or None, "source_root": source_root, "source_kind": row.get("kind"), "source_sha256": source_hash, "source_hash_status": source_hash_status, "owner": owner, "owner_source": owner_source, "license_scope": license_scope, "license_declared": license_declared, "license_spdx": license_spdx, "license_status": license_status, "closure_status": closure_status, "evidence": evidence, "evidence_status": evidence_status, "manifest_mapping_status": row.get("mapping_status"), "manifest_domain": row.get("domain"), }) license_index_path = root / LICENSE_INDEX write_jsonl(license_index_path, license_entries) license_index_artifact = artifact(root, LICENSE_INDEX, "license_source_index") license_status_counts = Counter(entry["license_status"] for entry in license_entries) closure_status_counts = Counter(entry["closure_status"] for entry in license_entries) owner_status_counts = Counter("assigned" if entry["owner"] else "unassigned" for entry in license_entries) source_hash_counts = Counter(entry["source_hash_status"] for entry in license_entries) implementation_entries = [entry for entry in license_entries if entry["source_root"] in {"wasm-core", "web"}] report = { "schema": "release-license-closure.v1", "context_id": lock.get("context_id"), "status": "blocked", "release_decision": "license_closure_incomplete", "source_inventory": { "entries": len(sources), "manifest_entries": len(manifest), "path_roots": dict(sorted(path_counts.items())), "license_field_in_manifest": any("license" in row for row in manifest), "manifest_license_field_missing_entries": len(manifest), "unassigned_license_entries": license_status_counts.get("blocked_unassigned", 0), "status": "partial", "reason": "manifest v1 has no license field; the external deterministic index below records scoped upstream evidence and keeps unassigned implementation licenses blocked", }, "entry_closure": { "index": license_index_artifact, "entries": len(license_entries), "owner_semantics": "license_audit_owner; does not replace manifest feature owner or promote needs_owner entries", "source_hash_status": dict(sorted(source_hash_counts.items())), "owner_status": dict(sorted(owner_status_counts.items())), "license_status": dict(sorted(license_status_counts.items())), "closure_status": dict(sorted(closure_status_counts.items())), "all_entries_have_owner": all(entry["owner"] for entry in license_entries), "all_entries_have_evidence_status": all(entry["evidence_status"] for entry in license_entries), "status": "blocked" if implementation_entries or any(entry["closure_status"] == "blocked" for entry in license_entries) else "verified", }, "upstream_declarations": { "files": [artifact(root, Path("linuxcnc/COPYING"), "license_text"), artifact(root, Path("linuxcnc/COPYING.more"), "license_text"), artifact(root, copyright_path, "scoped_license_declarations")], "declared_spdx_or_legacy_ids": declared, "default_scope": "linuxcnc/* (subject to debian/copyright path exceptions)", "matched_entries": len(license_entries) - len(implementation_entries), "status": "verified_scoped", }, "implementation_license": { "paths": ["wasm-core/**", "web/**"], "status": "blocked", "decision": "unassigned_and_blocked", "reason": "no repository LICENSE/NOTICE/SPDX metadata assigns a license to the Web/WASM implementation files; upstream LinuxCNC declarations are not inherited", "entries": len(implementation_entries), "decision_artifact": artifact(root, IMPLEMENTATION_LICENSE_DECISION, "implementation_license_decision"), }, "download_artifacts": { "status": "verified" if download_valid else "blocked", "index": artifact(root, DOWNLOAD_INDEX, "download_index"), "python_runtime_manifest": artifact(root, PYTHON_MANIFEST, "python_runtime_manifest"), "artifacts": python_artifacts, }, "real_excluded": { "test_entry_count": len(real_rows), "test_report_count": test_report.get("portability", {}).get("REAL-EXCLUDED", 0), "test_source_ids": sorted(str(row.get("test_source_id")) for row in real_rows), "runtime_categories": ["hardware_driver", "linux_realtime_kernel", "host_device_access"], "status": "explicit", }, "blockers": ["wasm_web_license_not_declared"], "report_sha256": None, } return write_report(out, report) def manifest_evidence(root: Path, rows: Iterable[dict[str, Any]]) -> tuple[list[dict[str, Any]], dict[str, int]]: required = ("source", "inputs", "outputs", "test_id", "oracle") entries = [] missing = Counter() for row in rows: if row.get("mapping_status") == "needs_owner": continue owner = row.get("domain") if row.get("domain") not in {None, "", "unassigned"} else None if owner is None: missing["owner"] += 1 entry = { "id": row.get("id"), "source": row.get("source"), "owner": owner, "owner_source": "manifest.domain", "feature_id": row.get("feature_id"), "inputs": row.get("inputs", []), "outputs": row.get("outputs", []), "test_id": row.get("test_id"), "difference": row.get("difference"), "difference_status": "none_recorded" if row.get("difference") is None else "recorded", "oracle_status": row.get("oracle", {}).get("status") if isinstance(row.get("oracle"), dict) else None, "context_id": row.get("context_id"), "mapping_status": row.get("mapping_status"), } for field in required: value = entry["oracle_status"] if field == "oracle" else entry[field] if value is None or value == [] or value == "": missing[field] += 1 entries.append(entry) entries.sort(key=lambda item: str(item["id"])) return entries, dict(sorted(missing.items())) def generate_index(root: Path, out: Path, compatibility: dict[str, Any], license_report: dict[str, Any]) -> dict[str, Any]: lock = read_json(root, CONTEXT) manifest = read_jsonl(root, MANIFEST) mapped, missing = manifest_evidence(root, manifest) artifacts = [artifact(root, Path(path), "coverage_evidence") for path in REPORT_PATHS] artifacts.extend([ artifact(root, Path("coverage/source-index.jsonl"), "source_index"), artifact(root, Path("coverage/manifest.jsonl"), "manifest_seed"), artifact(root, Path("python-runtime/manifest.json"), "python_runtime_manifest"), artifact(root, Path("coverage/compatibility-matrix.report.json"), "compatibility_matrix"), artifact(root, Path("coverage/license-closure.report.json"), "license_closure"), artifact(root, LICENSE_INDEX, "license_source_index"), artifact(root, IMPLEMENTATION_LICENSE_DECISION, "implementation_license_decision"), ]) accepted = sum(1 for row in manifest if row.get("sim_status") == "accepted" or row.get("web_status") == "accepted") unassigned = sum(1 for row in manifest if row.get("mapping_status") == "needs_owner") config = read_json(root, CONFIGURATION) blockers = [ {"id": "manifest_seed_unassigned", "count": unassigned, "status": "blocked"}, {"id": "accepted_entries_missing", "count": accepted, "status": "blocked" if accepted == 0 else "verified"}, {"id": "official_oracle_missing", "count": 1, "status": "blocked"}, {"id": "configuration_resources_missing", "count": config.get("counts", {}).get("resource_missing_rows", 0), "status": "blocked"}, {"id": "browser_matrix_unverified", "count": 1, "status": "blocked"}, {"id": "license_closure_incomplete", "count": len(license_report.get("blockers", [])), "status": "blocked"}, {"id": "migration_rollback_pending", "count": 0 if compatibility.get("migration_rollback_offline", {}).get("status") == "verified" else 1, "status": "verified" if compatibility.get("migration_rollback_offline", {}).get("status") == "verified" else "blocked"}, ] report = { "schema": "release-evidence-index.v1", "context_id": lock.get("context_id"), "status": "blocked", "release_decision": "blocked_not_release_candidate", "context_lock": {"path": CONTEXT.as_posix(), "sha256": file_digest(root / CONTEXT), "schema_versions": lock.get("schema_versions")}, "reports": artifacts, "manifest_summary": { "total": len(manifest), "affected": len(mapped), "needs_owner": unassigned, "accepted": accepted, "required_field_missing": missing, "affected_entries_complete": not missing, "owner_derivation": "manifest.domain; unassigned is never treated as an owner", }, "affected_manifest_entries": mapped, "compatibility_report": {"path": "coverage/compatibility-matrix.report.json", "sha256": compatibility["report_sha256"]}, "license_report": {"path": "coverage/license-closure.report.json", "sha256": license_report["report_sha256"]}, "migration_report": {"path": "coverage/workspace-migration.report.json", "sha256": compatibility.get("migration_rollback_offline", {}).get("artifact", {}).get("sha256")}, "real_excluded": license_report.get("real_excluded"), "blockers": blockers, "integrity": { "no_orphans": config.get("closure", {}).get("graph_orphan_entry_ids") == [] and config.get("closure", {}).get("resource_orphan_entrypoints") == [], "no_silent_downgrade": bool(config.get("oracle", {}).get("derived_configuration_results_are_not_official")) and all(row.get("startup_preflight") for row in config.get("configuration_entries", [])), "derived_configuration_not_accepted": bool(config.get("oracle", {}).get("derived_configuration_results_are_not_official")), "all_affected_entries_have_required_evidence": not missing, "migration_rollback_offline_verified": compatibility.get("migration_rollback_offline", {}).get("status") == "verified", "license_source_index_complete": bool(license_report.get("entry_closure", {}).get("all_entries_have_owner")) and bool(license_report.get("entry_closure", {}).get("all_entries_have_evidence_status")), "implementation_license_blocked_explicitly": license_report.get("implementation_license", {}).get("decision") == "unassigned_and_blocked", }, "report_sha256": None, } return write_report(out, report) def generate_release_gate( root: Path, out: Path, aggregate: dict[str, Any], compatibility: dict[str, Any], license_report: dict[str, Any], index: dict[str, Any], ) -> dict[str, Any]: lock = read_json(root, CONTEXT) recovery = read_json(root, Path("coverage/workspace-recovery.report.json")) migration = read_json(root, Path("coverage/workspace-migration.report.json")) context_id = lock.get("context_id") context_reports = [aggregate, compatibility, license_report, index, recovery, migration] context_ids_match = all(report.get("context_id") in {None, context_id} for report in context_reports) recovery_hash_expected = lock.get("workspace_recovery_report_sha256") recovery_hash_actual = file_digest(root / Path("coverage/workspace-recovery.report.json")) recovery_hash_locked = recovery_hash_expected == recovery_hash_actual context_consistent = context_ids_match and recovery_hash_locked schema_versions = lock.get("schema_versions", {}) schemas_locked = bool(schema_versions) and all(value == 1 for value in schema_versions.values()) and compatibility.get("contracts", {}).get("status") == "verified" evidence_integrity = all(value is True for key, value in aggregate.get("integrity", {}).items() if key not in {"implementation_license_blocked_explicitly"}) license_index_complete = license_report.get("entry_closure", {}).get("all_entries_have_owner") is True and license_report.get("entry_closure", {}).get("all_entries_have_evidence_status") is True migration_verified = compatibility.get("migration_rollback_offline", {}).get("status") == "verified" and migration.get("status") == "passed" blockers = [blocker for blocker in aggregate.get("blockers", []) if blocker.get("status") == "blocked"] reasons = { "manifest_seed_unassigned": "manifest seed entries still have no feature owner", "accepted_entries_missing": "no manifest entry is accepted for release", "official_oracle_missing": "locked source has no official kinematics/recovery oracle root", "configuration_resources_missing": "configuration startup/resource gaps remain", "browser_matrix_unverified": "Chromium/Firefox/Safari minimum versions remain TBD", "license_closure_incomplete": "57 Web/WASM entries have no approved SPDX/NOTICE assignment", } hard_blockers = [ { **blocker, "reason": reasons.get(blocker.get("id"), "release gate prerequisite is not verified"), } for blocker in blockers ] input_paths = ( (CONTEXT, "context_lock"), (Path("coverage/release-closure.report.json"), "release_closure"), (Path("coverage/release-evidence-index.json"), "release_evidence_index"), (Path("coverage/compatibility-matrix.report.json"), "compatibility_matrix"), (Path("coverage/license-closure.report.json"), "license_closure"), (LICENSE_INDEX, "license_source_index"), (Path("coverage/workspace-recovery.report.json"), "workspace_recovery"), (Path("coverage/workspace-migration.report.json"), "workspace_migration"), (Path("coverage/workspace-rollback.package.json"), "rollback_package"), (Path("coverage/offline-cache.package.json"), "offline_package"), (IMPLEMENTATION_LICENSE_DECISION, "implementation_license_decision"), ) report = { "schema": "release-candidate-gate.v1", "context_id": context_id, "status": "blocked" if hard_blockers else "passed", "release_decision": "blocked_not_release_candidate" if hard_blockers else "release_candidate_eligible", "release_action": "STOP_RELEASE" if hard_blockers else "ALLOW_RELEASE_CANDIDATE", "candidate_eligible": not hard_blockers, "source_baseline": { "linuxcnc_version": lock.get("linuxcnc_version"), "linuxcnc_source_sha": lock.get("linuxcnc_source_sha"), "context_lock": artifact(root, CONTEXT, "context_lock"), }, "checks": { "context_consistency": {"status": "verified" if context_consistent else "blocked", "context_ids_match": context_ids_match, "context_id": context_id, "recovery_report_hash_locked": recovery_hash_locked, "recovery_report_hash": recovery_hash_actual}, "locked_schema_contracts": {"status": "verified" if schemas_locked else "blocked", "schema_versions": schema_versions, "compatibility_contract_status": compatibility.get("contracts", {}).get("status")}, "evidence_integrity": {"status": "verified" if evidence_integrity else "blocked", "all_aggregate_integrity_flags": evidence_integrity}, "license_source_index": {"status": "verified" if license_index_complete else "blocked", "entries": license_report.get("entry_closure", {}).get("entries"), "all_entries_have_owner": license_report.get("entry_closure", {}).get("all_entries_have_owner"), "all_entries_have_evidence_status": license_report.get("entry_closure", {}).get("all_entries_have_evidence_status")}, "migration_rollback_offline": {"status": "verified" if migration_verified else "blocked", "compatibility_status": compatibility.get("migration_rollback_offline", {}).get("status"), "migration_status": migration.get("status")}, "release_prerequisites": {"status": "blocked" if hard_blockers else "verified", "blocked_count": len(hard_blockers), "verified_count": sum(1 for blocker in aggregate.get("blockers", []) if blocker.get("status") == "verified")}, }, "blockers": hard_blockers, "blocked_count": len(hard_blockers), "rebuild": { "commands": [ "python3 scripts/generate_release_closure.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": [path.as_posix() for path, _ in input_paths], }, "failure_policy": { "on_missing_input": "STOP_RELEASE", "on_hash_mismatch": "STOP_RELEASE", "on_blocked_prerequisite": "STOP_RELEASE", "forbidden": ["delete_samples", "lower_checks", "reclassify_as_REAL-EXCLUDED", "promote_blocked_to_accepted"], }, "inputs": [artifact(root, path, role) for path, role in input_paths], "report_sha256": None, } return write_report(out, report) def generate(root: Path, output_dir: Path) -> dict[str, dict[str, Any]]: root = root.resolve() output_dir = output_dir if output_dir.is_absolute() else root / output_dir compatibility = generate_compatibility(root, output_dir / "compatibility-matrix.report.json") license_report = generate_license(root, output_dir / "license-closure.report.json") index = generate_index(root, output_dir / "release-evidence-index.json", compatibility, license_report) aggregate = write_report(output_dir / "release-closure.report.json", { "schema": "release-closure-report.v1", "context_id": compatibility["context_id"], "status": "blocked", "release_decision": "blocked_not_release_candidate", "compatibility": {"path": "coverage/compatibility-matrix.report.json", "sha256": compatibility["report_sha256"]}, "license": {"path": "coverage/license-closure.report.json", "sha256": license_report["report_sha256"]}, "migration": {"path": "coverage/workspace-migration.report.json", "sha256": compatibility.get("migration_rollback_offline", {}).get("artifact", {}).get("sha256")}, "evidence_index": {"path": "coverage/release-evidence-index.json", "sha256": index["report_sha256"]}, "blockers": index["blockers"], "integrity": index["integrity"], "report_sha256": None, }) gate = generate_release_gate(root, output_dir / "release-candidate-gate.report.json", aggregate, compatibility, license_report, index) return {"compatibility": compatibility, "license": license_report, "index": index, "aggregate": aggregate, "gate": gate} 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-dir", type=Path, default=Path("coverage")) args = parser.parse_args() reports = generate(args.repo_root, args.output_dir) blocked_count = sum(1 for blocker in reports["index"]["blockers"] if blocker.get("status") == "blocked") print("release_closure=" + reports["index"]["status"] + " gate=" + reports["gate"]["status"] + " action=" + reports["gate"]["release_action"] + " blockers=" + str(blocked_count)) return 0 if __name__ == "__main__": raise SystemExit(main())