#!/usr/bin/env python3 # SPDX-License-Identifier: GPL-3.0-or-later """Regression guardrails for the offline Phase-0.8R remediation contract.""" from __future__ import annotations import argparse import copy import importlib.util from pathlib import Path from types import ModuleType from typing import Any def load_validator(root: Path) -> ModuleType: path = root / "tools/validate_phase08_remediation.py" spec = importlib.util.spec_from_file_location("phase08r_validator", path) if spec is None or spec.loader is None: raise RuntimeError("could not load Phase-0.8R validator") module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module def require_invalid(errors: list[str], scenario: str) -> None: if not errors: raise RuntimeError(f"unsafe mutation passed validation: {scenario}") def validate_manifest_mutation( validator: ModuleType, manifest: dict[str, Any], denylist: dict[str, Any], scenario: str, ) -> None: require_invalid(validator.validate_manifest(manifest, denylist), scenario) def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--root", type=Path, required=True) args = parser.parse_args() root = args.root.resolve() validator = load_validator(root) errors = validator.collect_errors(root, require_local_source=False) if errors: raise RuntimeError("; ".join(errors)) manifest = validator.load_json( root / "manifests/runtime/phase-0.8-remediation.json" ) denylist = validator.load_json(root / "manifests/artifact-denylist.json") doc_contract = validator.extract_json_contract( root / "docs/runtime/phase-0.8-remediation.md", "PHASE08R_CONTRACT" ) template = validator.extract_json_contract( root / "docs/approvals/phase-0.8-bounded-observation-template.md", "PHASE08_BOUNDED_OBSERVATION_TEMPLATE", ) ready = copy.deepcopy(manifest) ready["status"] = "READY" validate_manifest_mutation( validator, ready, denylist, "general READY status overrides blockers" ) authorized = copy.deepcopy(manifest) authorized["authorization"]["execution_authorized"] = True validate_manifest_mutation( validator, authorized, denylist, "execution authorization became true" ) retry = copy.deepcopy(manifest) retry["authorization"]["automatic_retry"] = True validate_manifest_mutation( validator, retry, denylist, "automatic retry became true" ) current_stock = copy.deepcopy(manifest) current_stock["stock_identities"]["classification"] = "current_device_identity" current_stock["stock_identities"]["current_device_observed"] = True validate_manifest_mutation( validator, current_stock, denylist, "reference-only hashes were promoted" ) backup_ready = copy.deepcopy(manifest) backup_ready["payload_manager_backup"]["classification"] = "ready" backup_ready["payload_manager_backup"]["on_device_proven"] = True backup_ready["payload_manager_backup"]["byte_exact_proven"] = True backup_ready["payload_manager_backup"]["result"] = "PASS" validate_manifest_mutation( validator, backup_ready, denylist, "manager backup hard blocker disappeared" ) missing_deny = copy.deepcopy(denylist) missing_deny["entries"] = [] validate_manifest_mutation( validator, manifest, missing_deny, "permanent denylist entry disappeared" ) non_options_read_only = copy.deepcopy(manifest) for finding in non_options_read_only["side_effect_findings"]: if finding["id"] == "get_version": finding["writes_server_active_flag"] = False finding["strict_read_only_preflight_suitable"] = True validate_manifest_mutation( validator, non_options_read_only, denylist, "non-OPTIONS request was called strict read-only", ) autoload_read_only = copy.deepcopy(manifest) for finding in autoload_read_only["side_effect_findings"]: if finding["id"] == "get_autoload_status": finding["writes_autoload_triggered"] = False finding["reads_filesystem_or_configuration"] = False validate_manifest_mutation( validator, autoload_read_only, denylist, "/autoload_status mutations and reads were hidden", ) options_suitable = copy.deepcopy(manifest) for finding in options_suitable["side_effect_findings"]: if finding["id"] == "options_any_endpoint": finding["strict_read_only_preflight_suitable"] = True validate_manifest_mutation( validator, options_suitable, denylist, "OPTIONS was promoted to collector" ) hardware_claim = copy.deepcopy(manifest) hardware_claim["claim_boundaries"]["hardware_safety_proven"] = True hardware_claim["firmware_runtime_behavior"] = "PROVEN_SAFE" validate_manifest_mutation( validator, hardware_claim, denylist, "host evidence became hardware proof" ) retroarch_active = copy.deepcopy(manifest) retroarch_active["retroarch"]["active_phase"] = True retroarch_active["retroarch"]["work_started"] = True validate_manifest_mutation( validator, retroarch_active, denylist, "RetroArch became active work" ) template_authorized = copy.deepcopy(template) template_authorized["authorized"] = True require_invalid( validator.validate_template(template_authorized), "bounded-observation template became authorization", ) template_prefilled = copy.deepcopy(template) template_prefilled["required_fields"]["collector_sha256"] = "0" * 64 require_invalid( validator.validate_template(template_prefilled), "template invented an artifact hash", ) merged_approvals = copy.deepcopy(manifest) del merged_approvals["authorization"]["lifecycle_authorized"] validate_manifest_mutation( validator, merged_approvals, denylist, "installation and lifecycle authorization were merged", ) doc_without_hard_blocker = copy.deepcopy(doc_contract) doc_without_hard_blocker["blockers"].remove( "payload_manager_backup_not_byte_exact_on_device" ) require_invalid( validator.validate_doc_contract(doc_without_hard_blocker, manifest), "documentation omitted manager backup hard blocker", ) if validator.validate_changed_files(root): raise RuntimeError("; ".join(validator.validate_changed_files(root))) print( "Phase-0.8R regression guardrails passed: immutable hashes, blocked " "status, false authorizations, denylist, reference-only stock hashes, " "hard backup gate, route effects, template denial, phase separation, " "RetroArch deferral, and no target artifact" ) return 0 if __name__ == "__main__": raise SystemExit(main())