#!/usr/bin/env python3 # SPDX-License-Identifier: GPL-3.0-or-later """Validate the offline, fail-closed Phase-0.8R evidence contract.""" from __future__ import annotations import argparse import hashlib import json import subprocess from pathlib import Path from typing import Any STATUS = "READ_ONLY_PREFLIGHT_BLOCKED" SOURCE_COMMIT = "2c944d6d65a08f7e1c02f518721cde061b999329" PAYLOAD_MANAGER_COMMIT = "cfbc70f30f419b09bf2b52283f7409e2d3117ee1" DENYLIST_SHA256 = ( "4be1c17b4964f2b68c39b5145bc4af4619c32512d60269ecf5c39728b390fa63" ) STOCK_ELFLDR_SHA256 = ( "092d16ee0ede0c494947efd38d1a17bbd7cc4b022d3858ea898833c188c703e8" ) STOCK_PAYLOAD_MANAGER_SHA256 = ( "518740adbacccb9094fadb07dd424c53ee290f38306449ccc9d6957fdf813c0b" ) IMMUTABLE_EVIDENCE = { "docs/runtime/phase-0.8-read-only-preflight.md": { "role": "historical_human_readable_preflight_record", "sha256": "3fbe086175a6048176075f447ec1482074928e3b5282db97ea2169395fe1d508", }, "manifests/runtime/phase-0.8-read-only-preflight.json": { "role": "historical_machine_readable_preflight_record", "sha256": "47d7f452f8799979fe99b3e6d56859f03544112725bf7e5b349eba5ed81b3322", }, "tests/test_phase08_preflight.py": { "role": "historical_fail_closed_regression_test", "sha256": "8a4ad7c70de28ffe3148fd3fd1f68c36a872c53c691c9068e1ff163970863c48", }, } AUTHORIZATION_FIELDS = ( "authorized", "installation_authorized", "lifecycle_authorized", "execution_authorized", "transfer_authorized", "automatic_retry", ) ACTIVITY_FIELDS = ( "hardware_observed", "ps5_connected", "files_transferred", "target_execution_performed", "target_mutation_performed", "target_artifact_created", "collector_implemented", "target_build_performed", ) REQUIRED_BLOCKERS = { "exact_permission_quote_absent": "STOP-RO", "collector_identity_absent": "STOP-RO", "collector_side_effect_contract_absent": "STOP-RO", "two_current_firmware_sources_absent": "STOP-GATE", "live_object_identities_absent": "STOP-GATE", "listeners_absent": "STOP-GATE", "autoload_status_absent": "STOP-GATE", "rollback_backups_absent": "STOP-GATE", "payload_manager_backup_not_byte_exact_on_device": "HARD_STOP-GATE", "unknown_result_is_stop": "STOP", "timeout_is_stop": "STOP", "deviation_is_stop": "STOP", "automatic_retry_forbidden": "STOP", } REQUIRED_FINDINGS = { "options_any_endpoint", "get_version", "get_log", "get_autoload_status", "get_config", "get_list_payloads", "get_processes_list", "get_sources_list", "get_ip", } REQUIRED_OBSERVATIONS = { "firmware", "live_paths", "object_identities", "file_sizes", "sha256", "processes_services", "listeners", "autoload", "rollback_files", "storage_precondition", } REQUIRED_PROHIBITED_ACTIONS = { "connect_to_ps5", "probe_ip_port_or_device_interface", "use_usb_or_removable_media", "transfer_ps5_file", "package_for_ps5_deployment", "install_or_replace_target_component", "execute_elf_or_payload", "build_target_elf", "start_cross_compiler", "implement_or_build_collector", "modify_payload_manager_production_code", "modify_elfldr_production_code", "modify_lifecycle_code", "activate_or_modify_autoload", "activate_retry", "change_target_configuration", "start_stop_or_signal_target_service_or_process", "implement_gnm_videoout_sdl_audio_input_shaders_cores_or_retroarch", "download_or_install_packages", "contact_internet_gitea_or_other_remote", "commit_or_push", } TEMPLATE_REQUIRED_FIELDS = { "exact_user_statement", "authorization_date", "expiration_time", "device_identity", "exact_purpose", "exact_observations", "method_or_collector_id", "source_commit", "collector_file_size", "collector_sha256", "firmware_gate", "maximum_runtime_ms", "maximum_execution_count", "maximum_transfer_count", "network_behavior", "output_channel", "allowed_volatile_effects", "prohibited_persistent_effects", "stop_criteria", "cleanup_requirements", "reporting_requirements", "explicit_installation_exclusion", "explicit_lifecycle_probe_exclusion", "explicit_autoload_and_retry_exclusion", "explicit_graphics_and_retroarch_exclusion", "revocation_method", "manual_confirmation_template_does_not_authorize", } TARGET_SUFFIXES = { ".elf", ".self", ".sprx", ".pkg", ".bin", ".payload", ".zip", ".tar", ".tgz", ".7z", } def sha256(path: Path) -> str: digest = hashlib.sha256() with path.open("rb") as stream: for block in iter(lambda: stream.read(1024 * 1024), b""): digest.update(block) return digest.hexdigest() def load_json(path: Path) -> dict[str, Any]: document = json.loads(path.read_text(encoding="utf-8")) if not isinstance(document, dict): raise ValueError(f"{path}: expected a JSON object") return document def extract_json_contract(path: Path, name: str) -> dict[str, Any]: text = path.read_text(encoding="utf-8") begin = f"" end = f"" if text.count(begin) != 1 or text.count(end) != 1: raise ValueError(f"{path}: expected exactly one {name} contract") block = text.split(begin, 1)[1].split(end, 1)[0].strip() if not block.startswith("```json\n") or not block.endswith("\n```"): raise ValueError(f"{path}: {name} must be one fenced JSON object") document = json.loads(block[len("```json\n") : -len("\n```")]) if not isinstance(document, dict): raise ValueError(f"{path}: {name} must be a JSON object") return document def positive_status_values(value: Any) -> list[str]: errors: list[str] = [] positive = { "READY", "COMPLETE", "COMPLETED", "AUTHORIZED", "PASS", "PASSED", "READ_ONLY_PREFLIGHT_DATA_COMPLETE", "READY_FOR_HARDENED_RUNTIME_DEPLOYMENT", } if isinstance(value, dict): for key, child in value.items(): if key == "historical_validation_report": continue errors.extend(positive_status_values(child)) elif isinstance(value, list): for child in value: errors.extend(positive_status_values(child)) elif isinstance(value, str) and value.upper() in positive: errors.append(value) return errors def validate_manifest( manifest: dict[str, Any], denylist: dict[str, Any] ) -> list[str]: errors: list[str] = [] if manifest.get("schema_version") != 1: errors.append("unsupported remediation schema version") schema_contract = manifest.get("schema_contract", {}) if schema_contract.get("id") != "chimera-gfx-phase-0.8-remediation-v1": errors.append("remediation schema-contract ID changed") if schema_contract.get("shared_schema_available") is not False: errors.append("remediation incorrectly claims a shared schema") if schema_contract.get("validator") != "tools/validate_phase08_remediation.py": errors.append("remediation validator binding changed") if manifest.get("phase_id") != "0.8R": errors.append("remediation phase ID changed") if manifest.get("phase") != "offline_remediation": errors.append("remediation phase widened beyond offline") if manifest.get("status") != STATUS: errors.append("Phase-0.8 status is not fail-closed") if positive_status_values(manifest): errors.append("manifest contains an overriding positive status") historical = manifest.get("historical_identity", {}) if historical.get("source_commit") != SOURCE_COMMIT: errors.append("historical source commit changed") if historical.get("status") != STATUS: errors.append("historical Phase-0.8 status changed") historical_report = historical.get("historical_validation_report", {}) if ( historical_report.get("classification") != "historical_report_not_current_hardware_evidence" ): errors.append("historical tests were promoted to hardware evidence") immutable = { item.get("path"): { "role": item.get("role"), "sha256": item.get("sha256"), } for item in manifest.get("immutable_evidence", []) } if immutable != IMMUTABLE_EVIDENCE: errors.append("immutable Phase-0.8 evidence binding changed") authorization = manifest.get("authorization", {}) if any(authorization.get(field) is not False for field in AUTHORIZATION_FIELDS): errors.append("an authorization or retry field is not false") activity = manifest.get("activity", {}) if any(activity.get(field) is not False for field in ACTIVITY_FIELDS): errors.append("manifest claims a prohibited target activity") if manifest.get("firmware_runtime_behavior") != "UNPROVEN": errors.append("firmware runtime behavior was promoted") claim_boundaries = manifest.get("claim_boundaries", {}) required_false_claims = { "hardware_safety_proven", "firmware_behavior_proven", "absence_of_volatile_effects_proven", "no_persistent_write_found_equals_side_effect_free", "host_tests_are_hardware_evidence", "missing_observation_means_safe_absence", } if any(claim_boundaries.get(field) is not False for field in required_false_claims): errors.append("a prohibited safety or evidence claim was enabled") stock = manifest.get("stock_identities", {}) if ( stock.get("classification") != "reference_only" or stock.get("current_device_observed") is not False ): errors.append("stock identities were promoted from reference-only") elfldr = stock.get("elfldr", {}) if ( elfldr.get("size") != 397000 or elfldr.get("sha256") != STOCK_ELFLDR_SHA256 or elfldr.get("current_device_match") != "UNPROVEN" ): errors.append("stock elfldr reference changed or was promoted") manager = stock.get("payload_manager", {}) if ( manager.get("size") != 2050320 or manager.get("sha256") != STOCK_PAYLOAD_MANAGER_SHA256 or manager.get("current_device_match") != "UNPROVEN" ): errors.append("stock Payload Manager reference changed or was promoted") backup = manifest.get("payload_manager_backup", {}) if backup != { "classification": "hard_blocker", "on_device_proven": False, "byte_exact_proven": False, "creation_allowed_in_strict_read_only_phase": False, "result": "HARD_STOP-GATE", }: errors.append("Payload Manager backup hard blocker changed") entries = denylist.get("entries", []) if ( denylist.get("fail_closed") is not True or len(entries) != 1 or entries[0].get("sha256") != DENYLIST_SHA256 or entries[0].get("status") != "BLOCKED" or entries[0].get("permanent") is not True or entries[0].get("execution_eligible") is not False ): errors.append("permanent denylist binding changed") blockers = { item.get("id"): item.get("severity") for item in manifest.get("blockers", []) } if blockers != REQUIRED_BLOCKERS: errors.append("remediation blocker set changed") findings = { item.get("id"): item for item in manifest.get("side_effect_findings", []) } if set(findings) != REQUIRED_FINDINGS: errors.append("Payload Manager side-effect finding set changed") for finding_id, finding in findings.items(): if finding.get("strict_read_only_preflight_suitable") is not False: errors.append(f"{finding_id}: incorrectly marked strict-read-only suitable") references = finding.get("source_references") if not isinstance(references, list) or not references: errors.append(f"{finding_id}: source references are absent") if finding_id != "options_any_endpoint": if finding.get("http_method") == "OPTIONS": errors.append(f"{finding_id}: non-OPTIONS finding mislabeled") if finding.get("writes_server_active_flag") is not True: errors.append(f"{finding_id}: server_active_flag mutation hidden") options = findings.get("options_any_endpoint", {}) if ( options.get("http_method") != "OPTIONS" or options.get("writes_server_active_flag") is not False or options.get("strict_read_only_preflight_suitable") is not False ): errors.append("OPTIONS route classification changed") autoload = findings.get("get_autoload_status", {}) if ( autoload.get("endpoint") != "/autoload_status" or autoload.get("writes_autoload_triggered") is not True or autoload.get("reads_filesystem_or_configuration") is not True ): errors.append("/autoload_status mutations or reads were hidden") source = manifest.get("payload_manager_source", {}) if ( source.get("commit") != PAYLOAD_MANAGER_COMMIT or source.get("release") != "v0.3.1" ): errors.append("Payload Manager source identity changed") source_files = source.get("files") if not isinstance(source_files, list) or len(source_files) < 3: errors.append("Payload Manager source-file evidence is incomplete") observations = { item.get("id"): item for item in manifest.get("evidence_contract", []) } if set(observations) != REQUIRED_OBSERVATIONS: errors.append("future evidence-contract observation set changed") for observation_id, observation in observations.items(): if observation.get("confidence") != "UNPROVEN": errors.append(f"{observation_id}: confidence was promoted") if observation.get("timeout_ms") is not None: errors.append(f"{observation_id}: timeout invented before tool review") if observation.get("fail_closed_result") != "STOP": errors.append(f"{observation_id}: fail-closed result changed") identity = observation.get("required_collector_identity") output = observation.get("reviewer_output") if not isinstance(identity, list) or not identity: errors.append(f"{observation_id}: collector identity contract absent") if not isinstance(output, list) or not output: errors.append(f"{observation_id}: reviewer output contract absent") prohibited = set(manifest.get("prohibited_actions", [])) if prohibited != REQUIRED_PROHIBITED_ACTIONS: errors.append("prohibited-action set changed") future = manifest.get("future_activity", {}) if ( future.get("mode") != "design_only" or future.get("bounded_observation_implemented") is not False or future.get("collector_selected") is not False or future.get("transfer_method_selected") is not False or future.get("execution_method_selected") is not False or future.get("new_explicit_authorization_required") is not True ): errors.append("future bounded observation was promoted beyond design") retroarch = manifest.get("retroarch", {}) if retroarch != { "goal": "long_term_goal", "active_phase": False, "work_started": False, "dependency_chain_only": True, }: errors.append("RetroArch was promoted into the active phase") return errors def validate_template(template: dict[str, Any]) -> list[str]: errors: list[str] = [] if template.get("template_only") is not True: errors.append("bounded-observation template is not template-only") for field in ( "authorized", "execution_authorized", "transfer_authorized", "installation_authorized", "lifecycle_authorized", "automatic_retry", ): if template.get(field) is not False: errors.append(f"template field {field} is not false") required = template.get("required_fields", {}) if set(required) != TEMPLATE_REQUIRED_FIELDS: errors.append("bounded-observation required-field set changed") elif any(value is not None for value in required.values()): errors.append("bounded-observation template contains prefilled request data") exclusions = template.get("fixed_exclusions", {}) expected_exclusions = { "installation", "lifecycle_probe", "autoload", "automatic_retry", "gnm", "videoout", "sdl", "retroarch", } if set(exclusions) != expected_exclusions or any( value is not True for value in exclusions.values() ): errors.append("bounded-observation fixed exclusions changed") return errors def validate_doc_contract( contract: dict[str, Any], manifest: dict[str, Any] ) -> list[str]: errors: list[str] = [] if contract.get("status") != manifest.get("status"): errors.append("documentation/manifest status mismatch") if contract.get("authorization") != manifest.get("authorization"): errors.append("documentation/manifest authorization mismatch") blocker_ids = [item.get("id") for item in manifest.get("blockers", [])] if contract.get("blockers") != blocker_ids: errors.append("documentation/manifest blocker mismatch") if contract.get("firmware_runtime_behavior") != "UNPROVEN": errors.append("documentation promoted firmware behavior") if contract.get("stock_identity_classification") != "reference_only": errors.append("documentation promoted stock identities") if contract.get("payload_manager_backup_classification") != "hard_blocker": errors.append("documentation weakened the manager backup blocker") if contract.get("retroarch_active_phase") is not False: errors.append("documentation promoted RetroArch into the active phase") return errors def validate_immutable_evidence(root: Path) -> list[str]: errors: list[str] = [] for relative, expected in IMMUTABLE_EVIDENCE.items(): path = root / relative if not path.is_file(): errors.append(f"immutable evidence missing: {relative}") elif sha256(path) != expected["sha256"]: errors.append(f"immutable evidence hash mismatch: {relative}") return errors def changed_paths(root: Path) -> list[Path]: result = subprocess.run( ["git", "status", "--porcelain=v1", "--untracked-files=all"], cwd=root, check=True, capture_output=True, text=True, ) paths: list[Path] = [] for line in result.stdout.splitlines(): value = line[3:] for candidate in value.split(" -> "): candidate = candidate.strip('"') paths.append(Path(candidate)) return paths def validate_changed_files(root: Path) -> list[str]: errors: list[str] = [] for path in changed_paths(root): if path.suffix.lower() in TARGET_SUFFIXES: errors.append(f"target artifact appears in change set: {path}") normalized = path.as_posix() if normalized.startswith( ("src/", "include/", "samples/", "adapters/", "work/upstream/") ): errors.append(f"production/runtime source changed in remediation: {path}") return errors def validate_local_payload_manager_source( root: Path, manifest: dict[str, Any] ) -> list[str]: errors: list[str] = [] source_root = root / "work/upstream/pldmgr-v0.3.1" if not source_root.is_dir(): return ["required local Payload Manager source checkout is absent"] head = subprocess.run( ["git", "rev-parse", "HEAD"], cwd=source_root, check=True, capture_output=True, text=True, ).stdout.strip() if head != PAYLOAD_MANAGER_COMMIT: errors.append("local Payload Manager source commit changed") status = subprocess.run( ["git", "status", "--porcelain"], cwd=source_root, check=True, capture_output=True, text=True, ).stdout.strip() if status: errors.append("local Payload Manager source checkout is dirty") for item in manifest["payload_manager_source"]["files"]: path = source_root / item["path"] if not path.is_file(): errors.append(f"Payload Manager source file missing: {item['path']}") elif sha256(path) != item["sha256"]: errors.append(f"Payload Manager source hash mismatch: {item['path']}") return errors def collect_errors(root: Path, require_local_source: bool = False) -> list[str]: manifest = load_json(root / "manifests/runtime/phase-0.8-remediation.json") denylist = load_json(root / "manifests/artifact-denylist.json") doc_contract = extract_json_contract( root / "docs/runtime/phase-0.8-remediation.md", "PHASE08R_CONTRACT" ) template = extract_json_contract( root / "docs/approvals/phase-0.8-bounded-observation-template.md", "PHASE08_BOUNDED_OBSERVATION_TEMPLATE", ) errors = validate_immutable_evidence(root) errors.extend(validate_manifest(manifest, denylist)) errors.extend(validate_doc_contract(doc_contract, manifest)) errors.extend(validate_template(template)) errors.extend(validate_changed_files(root)) if require_local_source: errors.extend(validate_local_payload_manager_source(root, manifest)) return errors def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--root", type=Path, required=True) parser.add_argument( "--require-local-source", action="store_true", help="also require and rehash the ignored pinned Payload Manager checkout", ) args = parser.parse_args() root = args.root.resolve() errors = collect_errors(root, args.require_local_source) if errors: for error in errors: print(f"Phase-0.8R validation failed: {error}") return 1 print( "Phase-0.8R remediation validation passed: " f"{len(IMMUTABLE_EVIDENCE)} immutable files, " f"{len(REQUIRED_FINDINGS)} side-effect findings, " f"{len(REQUIRED_OBSERVATIONS)} observation contracts, " f"{len(REQUIRED_BLOCKERS)} blockers; hardware evidence not claimed" ) return 0 if __name__ == "__main__": try: raise SystemExit(main()) except ( KeyError, OSError, subprocess.CalledProcessError, TypeError, ValueError, json.JSONDecodeError, ) as error: print(f"Phase-0.8R validation failed: {error}") raise SystemExit(1) from error