#!/usr/bin/env python3 # SPDX-License-Identifier: GPL-3.0-or-later """Validate the blocked, host-only Phase-0.9C feasibility closure.""" from __future__ import annotations import argparse import hashlib import json from pathlib import Path import re import subprocess from typing import Any EXPECTED_CLASSIFICATION = "BLOCKED_MULTIPLE_FOUNDATIONAL_CONTRACTS" EXPECTED_BASELINE = "3ddc213ea67bb286256ae42e52c65e00488608ca" EXPECTED_BRANCH = "codex/chimera-gfx-phase09c-execution-feasibility" BLOCKED_HASH = ( "4be1c17b4964f2b68c39b5145bc4af4619c32512d60269ecf5c39728b390fa63" ) AUTHORIZATION_FIELDS = ( "authorized", "transfer_authorized", "execution_authorized", "installation_authorized", "lifecycle_authorized", "autoload_authorized", "backup_creation_authorized", "observer_build_authorized", "automatic_retry", ) SOURCE_COMMITS = { "hardened_elfldr": ( "../chimera-elfldr", "197623058f509eddde18868dafcb92fdcac66464", ), "controlled_payload_manager": ( "../chimera-ps5-payload-manager", "e23d94ff91233aa770e2342800c1467875bdef44", ), "elfldr_public_base": ( "work/upstream/elfldr-v0.23", "699e8bcff03e91e8d6ca6eba281af25c5a58d8c2", ), "payload_manager_public_base": ( "work/upstream/pldmgr-v0.3.1", "cfbc70f30f419b09bf2b52283f7409e2d3117ee1", ), "ps5_payload_sdk_v0_41": ( "work/upstream/sdk", "d2e2e585740362976a39fdd5ccf390f199a7bc37", ), } SOURCE_HASHES = { "../chimera-elfldr/elfldr.c": ( "9949f8e4037984d10f1f5aa498e4665593d1fac8a33614d7f2141349839bb803" ), "../chimera-elfldr/socksrv.c": ( "d642ced3e9b4a296dd15e355050ebe956f53a6dfdaa6ac10109cd067a3bba3d7" ), "../chimera-elfldr/controlled_runtime.c": ( "10145f3bbb3b54e3d715b1667e45c6f12d3c7f52a04342ab104ec8dd49e384c9" ), "../chimera-elfldr/ps5_controlled.c": ( "68717ef1cc31c483743c5af325c59e73a90f66e93b880b989e349a4ddc748772" ), "../chimera-ps5-payload-manager/src/verified_launcher.c": ( "066100ca4917c7acc560e2e85666ca136cd7ccfd9094417377048f41106dd56e" ), "../chimera-ps5-payload-manager/src/ps5_launcher.c": ( "29c1a5fd01784a59e88b3698940f120cb03020071bc2b7d74a1da1a51524ef59" ), "work/upstream/sdk/crt/crt.c": ( "3875f4739ec40b33f1f4967a1acbb585a527d2c281a360153e1f69c8b945932a" ), "work/upstream/sdk/crt/patch.c": ( "4f76a677bba54f4641e1cf2755768c29afb7464a35b2f21e85db2aa2785eceac" ), "work/upstream/sdk/crt/Makefile": ( "6a62f777f32ab05cbe7bff81c00cfd6639ba8bf440aba611a40ff0b62732568b" ), "work/upstream/sdk/host/bin/prospero-clang": ( "0cf49ae43d6110a7606c0ee4d702fc4b5d5e1c3ae9a722945a48b80294e295ba" ), "work/upstream/sdk/host/elf_x86_64.x": ( "169b80d01da601ef96bbc584986608dec1d9c01397eae81eccd4e1a66b0a6c6a" ), "work/upstream/sdk/crt/kernel.c": ( "ac1c375aae8d3cb1be5fb8bad2f4e6492b6ec1f1450977b534202f46b5b70321" ), "work/upstream/sdk/sce_stubs/libkernel_web.c": ( "dca70757a0680ede52502fe7db10060fe30506f7ed0adfc43ce00700145ab4ff" ), "samples/lifecycle_probe/main.c": ( "1ae7df1fe921ccab2a252f77975d3d441ef7725e34535b024580c0d4a242d766" ), } IMMUTABLE_HASHES = { "docs/runtime/phase-0.8-read-only-preflight.md": ( "3fbe086175a6048176075f447ec1482074928e3b5282db97ea2169395fe1d508" ), "manifests/runtime/phase-0.8-read-only-preflight.json": ( "47d7f452f8799979fe99b3e6d56859f03544112725bf7e5b349eba5ed81b3322" ), "tests/test_phase08_preflight.py": ( "8a4ad7c70de28ffe3148fd3fd1f68c36a872c53c691c9068e1ff163970863c48" ), "manifests/runtime/phase-0.9-anti-brick-design.json": ( "39fd7c70cae998d9d74e7caf1ff3c19f9f76f7de2e5a50bc09baede4ba53e9e9" ), "manifests/runtime/phase-0.9b-observer.json": ( "104c4a667ad17f9827fc7276852c6faeef96effb8e3561a5ebc19a62c7d51634" ), "manifests/runtime/phase-0.9b-observation-plan.schema.json": ( "efcea3b0001ef5b2da65c372ceb93ee2fec09c9331b2e4cbb6008212504c0918" ), } CHECKSUM_FILES = ( "docs/runtime/phase-0.9c-capability-closure.md", "docs/runtime/phase-0.9c-output-channel-feasibility.md", "docs/runtime/phase-0.9c-side-effect-model.md", "docs/runtime/phase-0.9c-startup-exit-feasibility.md", "docs/runtime/phase-0.9c-static-audit.md", "manifests/runtime/phase-0.9c-feasibility.json", "manifests/runtime/phase-0.9c-feasibility.schema.json", "tests/phase09c_feasibility_model.py", "tests/test_phase09c_feasibility.py", "tests/test_phase09c_protocol.py", "tools/validate_phase09c_feasibility.py", ) CAPABILITIES = { "runtime_self_identity", "firmware_source_1", "firmware_source_2", "mount_query", "metadata", "object_id", "size", "sha256", "processes", "services", "listeners", "autoload", "rollback_objects", "monotonic_time", "startup", "output", "normal_exit", "error_exit", "timeout", "cleanup", "recovery_independence", } OUTPUT_ARCHITECTURES = ( ( "D1_CALLER_OWNED_BOUNDED_BUFFER", "CONCEPT_FEASIBLE_REQUIRES_LOADER_CHANGE_AND_EXIT_PROOF", ), ("D2_EXISTING_REQUEST_RESPONSE", "REJECTED_SEND_ONLY_NO_RESULT_RECEIVE"), ( "D3_LOADER_OWNED_STATUS_RECORD", "UNPROVEN_REQUIRES_LOADER_STATE_AND_PROPAGATION_CHANGE", ), ( "D4_PROCESS_EXIT_STATUS", "REJECTED_WAIT_STATUS_DISCARDED_AND_AMBIGUOUS", ), ) FORBIDDEN_PHASE09C_SUFFIXES = { ".c", ".cc", ".cpp", ".cxx", ".s", ".asm", ".o", ".obj", ".elf", ".self", ".sprx", ".map", ".pkg", ".zip", ".tar", ".gz", } def load_json(path: Path) -> dict[str, Any]: with path.open("r", encoding="utf-8") as handle: value = json.load(handle) if not isinstance(value, dict): raise ValueError(f"{path} must contain a JSON object") return value def sha256_file(path: Path) -> str: digest = hashlib.sha256() with path.open("rb") as handle: for chunk in iter(lambda: handle.read(1024 * 1024), b""): digest.update(chunk) return digest.hexdigest() def git_output(root: Path, *args: str) -> str: return subprocess.check_output( ["git", *args], cwd=root, text=True, encoding="utf-8" ).strip() def _json_type_matches(expected: str, value: Any) -> bool: if expected == "object": return isinstance(value, dict) if expected == "array": return isinstance(value, list) if expected == "string": return isinstance(value, str) if expected == "integer": return isinstance(value, int) and not isinstance(value, bool) if expected == "boolean": return isinstance(value, bool) if expected == "null": return value is None return True def validate_schema_instance( schema: dict[str, Any], value: Any, path: str = "$" ) -> list[str]: """Validate the JSON-Schema features used by the Phase-0.9C schema.""" errors: list[str] = [] if "const" in schema and value != schema["const"]: errors.append(f"{path} differs from schema const") return errors expected_type = schema.get("type") if isinstance(expected_type, str) and not _json_type_matches(expected_type, value): errors.append(f"{path} is not {expected_type}") return errors if isinstance(value, dict): required = schema.get("required", []) for key in required: if key not in value: errors.append(f"{path}.{key} is required") properties = schema.get("properties", {}) if schema.get("additionalProperties") is False: for key in value: if key not in properties: errors.append(f"{path}.{key} is not allowed") for key, child_schema in properties.items(): if key in value: errors.extend( validate_schema_instance(child_schema, value[key], f"{path}.{key}") ) if isinstance(value, list): minimum = schema.get("minItems") maximum = schema.get("maxItems") if isinstance(minimum, int) and len(value) < minimum: errors.append(f"{path} has fewer than {minimum} items") if isinstance(maximum, int) and len(value) > maximum: errors.append(f"{path} has more than {maximum} items") if schema.get("uniqueItems") is True: normalized = [json.dumps(item, sort_keys=True) for item in value] if len(normalized) != len(set(normalized)): errors.append(f"{path} contains duplicate items") item_schema = schema.get("items") if isinstance(item_schema, dict): for index, item in enumerate(value): errors.extend( validate_schema_instance(item_schema, item, f"{path}[{index}]") ) return errors def validate_authorizations(value: dict[str, Any]) -> list[str]: errors: list[str] = [] for field in AUTHORIZATION_FIELDS: if value.get(field) is not False: errors.append(f"authorization.{field} must be false") return errors def validate_manifest(manifest: dict[str, Any]) -> list[str]: errors: list[str] = [] if manifest.get("schema_version") != 1 or manifest.get("phase") != "0.9C": errors.append("Phase-0.9C manifest identity changed") if manifest.get("status") != "BLOCKED": errors.append("Phase-0.9C status must remain BLOCKED") if manifest.get("classification") != EXPECTED_CLASSIFICATION: errors.append("Phase-0.9C classification changed") if manifest.get("baseline_commit") != EXPECTED_BASELINE: errors.append("Phase-0.9C baseline changed") if manifest.get("branch") != EXPECTED_BRANCH: errors.append("Phase-0.9C branch changed") errors.extend(validate_authorizations(manifest.get("authorization", {}))) canonical = manifest.get("canonical_state_preserved", {}) expected_canonical = { "historical_phase08_status": "READ_ONLY_PREFLIGHT_BLOCKED", "phase09a_status": "DESIGN_ONLY", "phase09b_status": "BLOCKED", "firmware_runtime_behavior": "UNPROVEN", "stock_hashes": "reference_only", "payload_manager_backup": "HARD_BLOCKER", "device_contact_performed": False, "device_transfer_performed": False, "device_execution_performed": False, "target_build_performed": False, } if canonical != expected_canonical: errors.append("canonical Phase-0.8/0.9 state changed") deny = manifest.get("permanent_denylist_binding", {}) if deny != { "sha256": BLOCKED_HASH, "status": "BLOCKED", "permanent": True, "execution_eligible": False, }: errors.append("permanent artifact denial changed") source_commits = manifest.get("source_commits", {}) for key, (_, expected_commit) in SOURCE_COMMITS.items(): if source_commits.get(key) != expected_commit: errors.append(f"source commit changed: {key}") if source_commits.get("lifecycle_probe_source") != ( "fe08300339a13f899fb78ea404ada381a5cba87c" ): errors.append("lifecycle source commit changed") if manifest.get("source_tree_status") != { key: "clean" for key in SOURCE_COMMITS }: errors.append("source tree status record changed") evidence = { item.get("path"): item.get("sha256") for item in manifest.get("source_evidence", []) if isinstance(item, dict) } if evidence != SOURCE_HASHES: errors.append("source evidence path/hash inventory changed") immutable = manifest.get("immutable_evidence", {}) if immutable.get("phase08", {}).get("files") != { key: IMMUTABLE_HASHES[key] for key in ( "docs/runtime/phase-0.8-read-only-preflight.md", "manifests/runtime/phase-0.8-read-only-preflight.json", "tests/test_phase08_preflight.py", ) }: errors.append("Phase-0.8 immutable manifest bindings changed") if immutable.get("phase09a_manifest_sha256") != IMMUTABLE_HASHES[ "manifests/runtime/phase-0.9-anti-brick-design.json" ]: errors.append("Phase-0.9A manifest binding changed") if immutable.get("phase09b_manifest_sha256") != IMMUTABLE_HASHES[ "manifests/runtime/phase-0.9b-observer.json" ]: errors.append("Phase-0.9B manifest binding changed") if immutable.get("phase09b_schema_sha256") != IMMUTABLE_HASHES[ "manifests/runtime/phase-0.9b-observation-plan.schema.json" ]: errors.append("Phase-0.9B schema binding changed") startup = manifest.get("startup_exit", {}) false_startup_fields = ( "normal_sdk_kernelwrite_free", "freestanding_dependency_closure_proven", "stack_alignment_proven", "complete_relocation_and_bss_tls_contract_proven", "callable_read_and_time_abi_proven", "safe_return_proven", "safe_process_exit_proven", "error_exit_proven", "timeout_safe_exit_proven", "complete_cleanup_proven", ) for field in false_startup_fields: if startup.get(field) is not False: errors.append(f"startup_exit.{field} must be false") if startup.get("normal_sdk_status") != "PROVEN_SIDE_EFFECTING": errors.append("normal SDK startup side effects were hidden") if not startup.get("blockers"): errors.append("startup/exit blockers are absent") architectures = manifest.get("output_architectures", []) observed_architectures = tuple( (item.get("id"), item.get("status")) for item in architectures if isinstance(item, dict) ) if observed_architectures != OUTPUT_ARCHITECTURES: errors.append("output architecture decisions changed or reordered") if any(item.get("current_implementation") is not False for item in architectures): errors.append("an output architecture claims current implementation") protocol = manifest.get("host_protocol", {}) expected_protocol = { "model": "tests/phase09c_feasibility_model.py", "host_only": True, "target_implemented": False, "magic": "CHG09C01", "version": 1, "header_size": 256, "maximum_output_size": 4096, "maximum_body_size": 3840, "integer_encoding": "unsigned_big_endian", "execution_nonce_bytes": 16, "request_id_bytes": 16, "firmware_field_bytes": 8, "artifact_hash_algorithm": "sha256", "body_checksum_algorithm": "sha256", "result_checksum_algorithm": "sha256", "completion_marker": "COMPLETE", "pointers_present": False, "dynamic_growth": False, } for field, expected in expected_protocol.items(): if protocol.get(field) != expected: errors.append(f"host protocol field changed: {field}") if len(protocol.get("required_fields", [])) < 16: errors.append("host protocol required fields are incomplete") if len(protocol.get("fail_closed_conditions", [])) < 10: errors.append("host protocol fail-closed cases are incomplete") firmware = manifest.get("firmware", {}) source_two = firmware.get("source_two", {}) if firmware.get("expected") != "9.60": errors.append("exact firmware gate changed") if source_two != { "identity": None, "status": "ABSENT", "export_name_candidate_accepted": False, "nonce_bound_runtime_result_present": False, }: errors.append("firmware source two was fabricated or promoted") if firmware.get("agreement_proven") is not False: errors.append("firmware agreement was claimed") if firmware.get("gate") != "BLOCKED_FIRMWARE_SOURCE_INCOMPLETE": errors.append("firmware gate was promoted") side_effects = manifest.get("side_effect_model", {}) for field in ( "no_persistent_content_write_is_side_effect_free", "read_only_flag_is_side_effect_free", "all_planned_observations_proven_side_effect_free", ): if side_effects.get(field) is not False: errors.append(f"side-effect claim must remain false: {field}") if side_effects.get("gate") != "BLOCKED_OBSERVATION_SIDE_EFFECTS_UNBOUNDED": errors.append("side-effect gate changed") required_dimensions = { "content", "metadata", "atime", "audit", "cache", "counters", "service_state", "security_monitoring", "open_bookkeeping", "process_accounting", "object_lifetime", "races", } if set(side_effects.get("dimensions", [])) != required_dimensions: errors.append("side-effect dimensions are incomplete") capabilities = manifest.get("capability_closure", []) if {item.get("id") for item in capabilities if isinstance(item, dict)} != CAPABILITIES: errors.append("capability closure inventory changed") for item in capabilities: if not isinstance(item, dict): errors.append("capability entry is not an object") continue if item.get("implementation_allowed") is not False: errors.append(f"capability implementation enabled: {item.get('id')}") if item.get("execution_allowed") is not False: errors.append(f"capability execution enabled: {item.get('id')}") if item.get("target_evidence") in (None, "", "PROVEN"): errors.append(f"capability target evidence invalid: {item.get('id')}") if not item.get("blocker"): errors.append(f"capability blocker absent: {item.get('id')}") implementation = manifest.get("implementation", {}) if not implementation or any(value is not False for value in implementation.values()): errors.append("target/runtime implementation state must be entirely false") if manifest.get("artifact") != { "present": False, "path": None, "sha256": None, "size": None, "execution_eligible": False, "execution_authorized": False, }: errors.append("Phase-0.9C artifact must not exist") static = manifest.get("static_audit", {}) if static.get("status") != "NOT_APPLICABLE_NO_TARGET_SOURCE_OR_ARTIFACT": errors.append("static target audit was falsely promoted") if static.get("host_model_only") is not True: errors.append("static audit is not explicitly host-only") decision = manifest.get("final_decision", {}) if decision.get("positive_classification_allowed") is not False: errors.append("positive classification was enabled") if decision.get("classification") != EXPECTED_CLASSIFICATION: errors.append("final decision differs from top-level classification") expected_blockers = { "BLOCKED_STARTUP_ABI_UNPROVEN", "BLOCKED_EXIT_CLEANUP_UNPROVEN", "BLOCKED_NO_BOUNDED_OUTPUT_CHANNEL", "BLOCKED_FIRMWARE_SOURCE_INCOMPLETE", "BLOCKED_OBSERVATION_SIDE_EFFECTS_UNBOUNDED", } if set(decision.get("foundational_blockers", [])) != expected_blockers: errors.append("foundational blocker set changed") if decision.get("next_phase_automatic") is not False: errors.append("automatic next phase was enabled") return errors def validate_source_trees(root: Path) -> list[str]: errors: list[str] = [] for key, (relative, expected_commit) in SOURCE_COMMITS.items(): source_root = (root / relative).resolve() if not source_root.is_dir(): errors.append(f"source tree missing: {key}") continue try: commit = git_output(source_root, "rev-parse", "HEAD") status = git_output(source_root, "status", "--short") except subprocess.CalledProcessError: errors.append(f"source tree is not readable Git: {key}") continue if commit != expected_commit: errors.append(f"source tree commit mismatch: {key}") if status: errors.append(f"source tree is dirty: {key}") for relative, expected_hash in SOURCE_HASHES.items(): path = (root / relative).resolve() if not path.is_file(): errors.append(f"source evidence missing: {relative}") elif sha256_file(path) != expected_hash: errors.append(f"source evidence hash mismatch: {relative}") return errors def validate_immutable_evidence(root: Path) -> list[str]: errors: list[str] = [] for relative, expected_hash in IMMUTABLE_HASHES.items(): path = root / relative if not path.is_file(): errors.append(f"immutable evidence missing: {relative}") elif sha256_file(path) != expected_hash: errors.append(f"immutable evidence changed: {relative}") return errors def forbidden_repository_path(relative: str) -> bool: normalized = relative.replace("\\", "/").lower() path = Path(normalized) phase_marker = "phase09c" in normalized or "phase-0.9c" in normalized if phase_marker and path.suffix in FORBIDDEN_PHASE09C_SUFFIXES: return True if normalized.startswith( ( "samples/phase09c", "samples/phase-0.9c", "src/backends/ps5/phase09c", "src/backends/ps5/observer", ) ): return True if normalized.startswith("packaging/phase09c/"): return normalized != "packaging/phase09c/sha256sums.txt" return False def validate_repository_boundary(root: Path) -> list[str]: errors: list[str] = [] listed = git_output( root, "ls-files", "--cached", "--others", "--exclude-standard" ).splitlines() for relative in listed: if forbidden_repository_path(relative): errors.append(f"forbidden Phase-0.9C target/package path: {relative}") cmake = (root / "CMakeLists.txt").read_text(encoding="utf-8") if re.search( r"add_(?:executable|library)\s*\([^)]*phase[-_]?0?9c", cmake, flags=re.IGNORECASE | re.DOTALL, ): errors.append("CMake declares a Phase-0.9C target") forbidden_directories = ( root / "samples/phase09c_observer", root / "samples/phase-0.9c-observer", root / "packaging/phase09c/lifecycle", root / "packaging/phase09c/install", root / "packaging/phase09c/autoload", ) for path in forbidden_directories: if path.exists(): errors.append(f"forbidden Phase-0.9C path exists: {path.relative_to(root)}") return errors def validate_checksums(root: Path) -> list[str]: errors: list[str] = [] checksum_path = root / "packaging/phase09c/SHA256SUMS.txt" if not checksum_path.is_file(): return ["Phase-0.9C checksum file is missing"] observed: dict[str, str] = {} for line in checksum_path.read_text(encoding="utf-8").splitlines(): parts = line.split(" ", 1) if len(parts) != 2 or not re.fullmatch(r"[0-9a-f]{64}", parts[0]): errors.append("malformed Phase-0.9C checksum line") continue observed[parts[1]] = parts[0] if tuple(observed) != CHECKSUM_FILES: errors.append("Phase-0.9C checksum inventory or order changed") for relative in CHECKSUM_FILES: path = root / relative if not path.is_file(): errors.append(f"Phase-0.9C checksummed file missing: {relative}") elif observed.get(relative) != sha256_file(path): errors.append(f"Phase-0.9C checksum mismatch: {relative}") return errors def collect_errors(root: Path) -> list[str]: manifest_path = root / "manifests/runtime/phase-0.9c-feasibility.json" schema_path = root / "manifests/runtime/phase-0.9c-feasibility.schema.json" manifest = load_json(manifest_path) schema = load_json(schema_path) errors = validate_manifest(manifest) errors.extend(validate_schema_instance(schema, manifest)) errors.extend(validate_source_trees(root)) errors.extend(validate_immutable_evidence(root)) errors.extend(validate_repository_boundary(root)) errors.extend(validate_checksums(root)) denylist = load_json(root / "manifests/artifact-denylist.json") entries = denylist.get("entries", []) if ( len(entries) != 1 or entries[0].get("sha256") != BLOCKED_HASH 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 no longer blocks the legacy artifact") return errors def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--root", type=Path, required=True) args = parser.parse_args() root = args.root.resolve() errors = collect_errors(root) if errors: for error in errors: print(f"Phase-0.9C validation failed: {error}") return 1 print( "Phase-0.9C feasibility manifest, schema, sources, immutable evidence, " "denylist, checksums, and no-target boundary: PASS" ) return 0 if __name__ == "__main__": raise SystemExit(main())