#!/usr/bin/env python3 # SPDX-License-Identifier: GPL-3.0-or-later """Validate the host-only Phase-0.9E-R official Y2JB correlation.""" from __future__ import annotations import argparse import hashlib import json from pathlib import Path import re import subprocess from typing import Any import zipfile BASELINE = "3d3151bd12ff0f786c1e1b9af75d7174408e3d2f" BRANCH = "codex/chimera-gfx-phase09e-r-y2jb-correlation" PHASE = "PHASE_0_9E_R_OFFICIAL_Y2JB_CORRELATION" OUTER_NAME = "Y2JB-Autoloader-403-1240.zip" OUTER_SIZE = 504159435 OUTER_SHA256 = "805e3f87f0c371223619ffc7d3a7b3c0d41a1fae8a8b1171d9e2f162659e8291" INNER_NAME = "PS5/EXPORT/BACKUP/202606102126_00/archive.dat" INNER_SIZE = 504365056 INNER_SHA256 = "6439834e8856d45b6d6fe699b74c35ca6985a199ea8ecf3e398c018d37be2d55" DENYLIST_SHA256 = "e9603b0e3792781ad5b511afb22ef61e3d4fd4c5a16bf928f8609193bcd97783" UPSTREAM_COMMIT = "0dbbf4e7e0203af7e5d101a3256c634edf4e3ba2" UPSTREAM_TREE = "c4344f43af7c268337437e6419548dba6f6bc211" UPSTREAM_REMOTE = "https://github.com/Gezine/Y2JB.git" PORT_9020_CLASSIFICATION = "PORT_9020_REFERENCE_ONLY" FINAL_CLASSIFICATION = "LOCAL_BACKUP_NOT_CORRELATED" PROVENANCE_FIELDS = ( "logical_name", "artifact_role", "local_relative_path", "size", "sha256", "file_type", "source_repository", "source_commit", "build_identity", "version", "obtained_from", "evidence_that_it_is_deployed_or_used", "confidence", "immutable", "executable", "persistent_on_device", "transferred_per_session", "required_for_bootstrap", "required_for_recovery", ) AUTHORIZATION_FIELDS = ( "device_action_authorized", "target_build_authorized", "transfer_authorized", "execution_authorized", "installation_authorized", "lifecycle_authorized", "autoload_authorized", "device_write_authorized", "automatic_retry", ) IMMUTABLE_HASHES = { "manifests/runtime/phase-0.8-read-only-preflight.json": "47d7f452f8799979fe99b3e6d56859f03544112725bf7e5b349eba5ed81b3322", "manifests/runtime/phase-0.8-remediation.json": "a9dafed8c83722c43709dcf90ea117c21d6f996ff8fe233810ed08aa20cbe071", "manifests/runtime/phase-0.9-anti-brick-design.json": "39fd7c70cae998d9d74e7caf1ff3c19f9f76f7de2e5a50bc09baede4ba53e9e9", "manifests/runtime/phase-0.9b-observer.json": "104c4a667ad17f9827fc7276852c6faeef96effb8e3561a5ebc19a62c7d51634", "manifests/runtime/phase-0.9c-feasibility.json": "84eb737ff3486d0c5d8b5ecf06809f93bc573497da4919595b4c1817fd34247c", "manifests/runtime/phase-0.9d-existing-stack-readback.json": "86e5aaf034685dbe058b71ffeec645b682f0a8cc7d249e8ac0397155233991de", "manifests/runtime/phase-0.9e-bootstrap-provenance.json": "5dfa9bfe2ae751b2f0ea0e03c60c1a4471a35452389cf471456e6c54eb4601cf", "manifests/runtime/phase-0.9e-loader-protocol.json": "a7ca8b4e8072cb60ad4cd4869f6c508e8014059c8d81ad3b94a8d9e8cd0db4cb", } UPSTREAM_FILE_HASHES = { "README.md": (7500, "16bfdaa624b8b04f4a6a4a7d512ca8473ad2db73e80df39a974267403e34f751"), "payload_sender.py": (1064, "8c87920c41dbdbd66b9f36ca9509f0d6bef9170f351dd97ff831cfb98e642ec6"), "log_server.py": (929, "463114fd46479a7286706de13beb3f52221f36f3cbe37dc5a27bdb6104787a98"), "appinfo_editor.py": (2008, "c1bcb453660f597cbc9026dba76519a4929fc3e967c183deec8a1ca56912e2e8"), "download0/cache/splash_screen/aHR0cHM6Ly93d3cueW91dHViZS5jb20vdHY=/remotejsloader.js": (7132, "30cc6d1535549b2a49b47a9e0c85a3444cf84177be54398691694b7c6505f38e"), "download0/cache/splash_screen/aHR0cHM6Ly93d3cueW91dHViZS5jb20vdHY=/elfldr-ps5-1340.elf": (397000, "30478bcadb6439e1247451c4ac706b6e1385044dd0f12d6486b6d7057929453b"), } DELIVERABLES = ( "docs/runtime/phase-0.9e-r-official-release-correlation.md", "docs/runtime/phase-0.9e-r-release-source-binding.md", "docs/runtime/phase-0.9e-r-port9020-source-audit.md", "docs/runtime/phase-0.9e-r-official-hostsender-audit.md", "docs/runtime/phase-0.9e-r-provenance-gaps.md", "docs/approvals/phase-0.9e-r-y2jb-deployed-use-attestation.md", "manifests/runtime/phase-0.9e-r-release-correlation.json", "manifests/runtime/phase-0.9e-r-release-correlation.schema.json", "manifests/runtime/phase-0.9e-r-port9020-audit.json", "manifests/runtime/phase-0.9e-r-port9020-audit.schema.json", "tools/validate_phase09er_provenance.py", "tests/test_phase09er_provenance.py", "packaging/phase09er/SHA256SUMS.txt", ) FORBIDDEN_SUFFIXES = { ".c", ".cc", ".cpp", ".s", ".asm", ".ld", ".elf", ".self", ".sprx", ".pkg", ".bin", ".wasm", } FORBIDDEN_PREFIXES = ("include/", "src/", "adapters/", "samples/") def load_json(path: Path) -> dict[str, Any]: with path.open("r", encoding="utf-8") as stream: value = json.load(stream) if not isinstance(value, dict): raise ValueError(f"{path} does not contain a JSON object") return value def sha256_file(path: Path) -> str: digest = hashlib.sha256() with path.open("rb") as stream: for chunk in iter(lambda: stream.read(1024 * 1024), b""): digest.update(chunk) return digest.hexdigest() def git(root: Path, *args: str) -> str: result = subprocess.run( ["git", *args], cwd=root, check=False, capture_output=True, text=True, ) if result.returncode != 0: raise RuntimeError(result.stderr.strip() or "git command failed") return result.stdout.strip() def candidate_archive_path(root: Path) -> Path: direct = Path.home() / "Downloads" / OUTER_NAME if direct.is_file(): return direct for ancestor in (root, *root.parents): if ancestor.parent.name.lower() == "users": return ancestor / "Downloads" / OUTER_NAME return direct def official_byte_match( *, official_source: bool, local_name: str, local_size: int, local_sha256: str, asset_name: str, asset_size: int, asset_sha256: str, ) -> bool: """Only exact official name, byte count, and hash establish a match.""" return ( official_source and local_name == asset_name and local_size == asset_size and local_sha256 == asset_sha256 ) def source_binding( *, release_associated: bool, inner_bytes_matched: bool, inner_opaque: bool ) -> str: if inner_bytes_matched: return "REPRODUCIBLE_CONTENT_BINDING" if release_associated and inner_opaque: return "SOURCE_ONLY_ASSOCIATION" if release_associated: return "OFFICIAL_RELEASE_ASSOCIATION" return "NO_LOCAL_RELEASE_ASSOCIATION" def port_implementation_sufficient(classification: str) -> bool: return classification in { "PORT_9020_IMPLEMENTATION_FOUND", "PORT_9020_IMPLEMENTATION_PARTIAL", } def phase09f_design_allowed( *, correlation: str, release_commit_known: bool, upstream_clean: bool, port_classification: str, sender_identified: bool, attestation_available: bool, all_authorizations_false: bool, ) -> bool: return all( ( correlation == "OFFICIAL_RELEASE_BYTE_MATCH", release_commit_known, upstream_clean, port_implementation_sufficient(port_classification), sender_identified, attestation_available, all_authorizations_false, ) ) def sender_duplex(*, sends: bool, receives: bool) -> bool: return sends and receives def sender_short_send_deficiency(*, uses_sendall: bool, explicit_send_loop: bool) -> bool: return not (uses_sendall or explicit_send_loop) def attestation_is_runtime_proof(*, attested: bool, hardware_observed: bool) -> bool: return attested and hardware_observed def _type_matches(value: Any, expected: str | list[str]) -> bool: if isinstance(expected, list): return any(_type_matches(value, item) for item in expected) checks = { "object": isinstance(value, dict), "array": isinstance(value, list), "string": isinstance(value, str), "boolean": isinstance(value, bool), "integer": isinstance(value, int) and not isinstance(value, bool), "null": value is None, } return checks.get(expected, True) def validate_schema_instance( schema: dict[str, Any], instance: Any, path: str = "$" ) -> list[str]: errors: list[str] = [] expected_type = schema.get("type") if expected_type is not None and not _type_matches(instance, expected_type): return [f"{path}: type mismatch"] if "const" in schema and instance != schema["const"]: errors.append(f"{path}: const mismatch") if "enum" in schema and instance not in schema["enum"]: errors.append(f"{path}: outside enum") if isinstance(instance, dict): for required in schema.get("required", []): if required not in instance: errors.append(f"{path}: missing {required}") properties = schema.get("properties", {}) for key, value in instance.items(): if key in properties: errors.extend( validate_schema_instance(properties[key], value, f"{path}.{key}") ) elif isinstance(schema.get("additionalProperties"), dict): errors.extend( validate_schema_instance( schema["additionalProperties"], value, f"{path}.{key}" ) ) if isinstance(instance, list) and len(instance) < schema.get("minItems", 0): errors.append(f"{path}: too few items") return errors def validate_release_manifest(manifest: dict[str, Any]) -> list[str]: errors: list[str] = [] expected = { "phase": PHASE, "status": "DESIGN_ONLY", "baseline_commit": BASELINE, "release_correlation": "OFFICIAL_RELEASE_NO_MATCH", "final_classification": FINAL_CLASSIFICATION, "identified_release": None, "identified_release_asset": None, "inner_content_binding": "SIECAF_OPAQUE_UNBOUND", "phase09f_offline_design_allowed": False, } for field, value in expected.items(): if manifest.get(field) != value: errors.append(f"{field} changed") for field in AUTHORIZATION_FIELDS: if manifest.get("authorization", {}).get(field) is not False: errors.append(f"authorization.{field} must be false") local = manifest.get("local_candidate", {}) if ( local.get("file_name") != OUTER_NAME or local.get("size") != OUTER_SIZE or local.get("sha256") != OUTER_SHA256 ): errors.append("local candidate identity changed") inner = manifest.get("inner_archive", {}) if ( inner.get("size") != INNER_SIZE or inner.get("sha256") != INNER_SHA256 or inner.get("classification") != "OPAQUE_UNBOUND" or inner.get("further_reverse_engineering_performed") is not False ): errors.append("inner SIECAF boundary changed") assets = manifest.get("assets", []) if len(assets) != 7: errors.append("official asset inventory must contain seven assets") for asset in assets: if not re.fullmatch(r"[0-9a-f]{64}", str(asset.get("sha256", ""))): errors.append(f"official asset digest malformed: {asset.get('name')}") if asset.get("classification") != "NO_MATCH" or asset.get("downloaded"): errors.append(f"asset was not metadata-excluded: {asset.get('name')}") if official_byte_match( official_source=True, local_name=OUTER_NAME, local_size=OUTER_SIZE, local_sha256=OUTER_SHA256, asset_name=str(asset.get("name")), asset_size=int(asset.get("size", -1)), asset_sha256=str(asset.get("sha256")), ): errors.append(f"unrecorded official byte match: {asset.get('name')}") tags = manifest.get("tags", []) if len(tags) != 5: errors.append("official tag inventory must contain five tags") for tag in tags: if not re.fullmatch(r"[0-9a-f]{40}", str(tag.get("commit", ""))): errors.append(f"tag commit is not exact: {tag.get('tag')}") if not re.fullmatch(r"[0-9a-f]{64}", str(tag.get("source_archive_sha256", ""))): errors.append(f"source archive hash missing: {tag.get('tag')}") if len(manifest.get("release_inventory", [])) != 6: errors.append("release inventory must cover versions 1.2 through 1.6") provenance = manifest.get("artifact_provenance", []) if len(provenance) < 6: errors.append("artifact provenance inventory is incomplete") for artifact in provenance: missing = [field for field in PROVENANCE_FIELDS if field not in artifact] if missing: errors.append( f"{artifact.get('logical_name')}: missing provenance fields {missing}" ) if artifact.get("confidence") == "EXACT_USED": errors.append( f"{artifact.get('logical_name')}: deployed-use provenance overclaimed" ) if artifact.get("logical_name") == "official_embedded_elfldr_1_6": if artifact.get("source_commit") is not None: errors.append("embedded elfldr received an invented source commit") worktree = manifest.get("upstream_worktree", {}) if ( worktree.get("commit") != UPSTREAM_COMMIT or worktree.get("tree") != UPSTREAM_TREE or worktree.get("clean") is not True or worktree.get("submodules") != 0 or worktree.get("git_lfs_pointers") != 0 ): errors.append("official upstream worktree record changed") sender = manifest.get("official_host_sender", {}) if ( sender.get("response_read") is not False or sender.get("duplex") is not False or sender.get("deployed_use_attested") is not False ): errors.append("official sender was overclaimed") if manifest.get("operator_attestation", {}).get("attested") is not False: errors.append("operator attestation must remain empty") actions = manifest.get("actions", {}) expected_false_actions = ( "downloaded_code_executed", "dependency_installed", "ps5_connected", "ps5_ip_used", "device_request_performed", "files_transferred_to_or_from_ps5", "target_build_performed", "target_code_created", "target_artifact_created", "payload_created", "device_client_created", ) for field in expected_false_actions: if actions.get(field) is not False: errors.append(f"actions.{field} must be false") if ( actions.get("large_release_assets_downloaded") != 0 or actions.get("large_release_asset_bytes_downloaded") != 0 ): errors.append("large release asset download was recorded") if manifest.get("blocking_state", {}).get("payload_manager_backup") != ( "HARD_BLOCKER_FOR_INSTALLATION" ): errors.append("Payload Manager installation blocker changed") if phase09f_design_allowed( correlation=str(manifest.get("release_correlation")), release_commit_known=False, upstream_clean=True, port_classification=PORT_9020_CLASSIFICATION, sender_identified=True, attestation_available=True, all_authorizations_false=True, ): errors.append("Phase 0.9F incorrectly passed") return errors def validate_port_manifest(manifest: dict[str, Any]) -> list[str]: errors: list[str] = [] if manifest.get("phase") != PHASE or manifest.get("status") != "DESIGN_ONLY": errors.append("port manifest phase/status changed") if manifest.get("release_commit") != UPSTREAM_COMMIT: errors.append("port manifest release commit changed") if manifest.get("port_9020_classification") != PORT_9020_CLASSIFICATION: errors.append("port-9020 implementation was overclaimed") if ( manifest.get("port_9020_listener_found") is not False or manifest.get("port_9020_parser_found") is not False or manifest.get("port_9020_mapping_found") is not False ): errors.append("port-9020 source was invented") loader = manifest.get("official_remote_js_loader", {}) if ( loader.get("desired_dynamic_port") != 50000 or loader.get("maximum_receive_bytes") != 512000 or loader.get("framing") != "CONNECTION_EOF_OR_BUFFER_LIMIT" or loader.get("declared_length") is not False or loader.get("timeout") is not False or loader.get("native_elf_mapping") is not False ): errors.append("Remote JS Loader contract changed") if loader.get("automatic_retry_authorized") is not False: errors.append("automatic retry was authorized") relation = manifest.get("port_9021_relation", {}) if relation.get("listener_source_in_y2jb") is not False: errors.append("embedded 9021 listener source was invented") if relation.get("source_commit") is not None: errors.append("embedded elfldr received an invented source commit") sender = manifest.get("official_sender", {}) if sender.get("response_read") is not False or sender.get("duplex") is not False: errors.append("one-way sender was classified duplex") for field in AUTHORIZATION_FIELDS: if manifest.get("authorization", {}).get(field) is not False: errors.append(f"port authorization.{field} must be false") if manifest.get("phase09f_offline_design_allowed") is not False: errors.append("Phase 0.9F was authorized") return errors def changed_paths(root: Path) -> set[str]: paths = set(git(root, "diff", "--name-only", BASELINE).splitlines()) for line in git(root, "status", "--porcelain=v1", "--untracked-files=all").splitlines(): path = line[3:] if " -> " in path: path = path.split(" -> ", 1)[1] paths.add(path.replace("\\", "/")) return {path for path in paths if path} def phase09er_path_errors(paths: set[str]) -> list[str]: errors: list[str] = [] for path in paths: normalized = path.replace("\\", "/") lowered = normalized.lower() if normalized.startswith(FORBIDDEN_PREFIXES): errors.append(f"target/production path changed: {normalized}") if Path(normalized).suffix.lower() in FORBIDDEN_SUFFIXES: errors.append(f"target artifact/source added: {normalized}") if any( token in lowered for token in ( "rescue.elf", "observer.elf", "readback.elf", "executionpackage", "installpackage", "transferpackage", ) ): errors.append(f"forbidden Phase-0.9E-R output: {normalized}") return errors def validate_checksum_file(root: Path) -> list[str]: checksum_path = root / "packaging/phase09er/SHA256SUMS.txt" if not checksum_path.is_file(): return ["missing packaging/phase09er/SHA256SUMS.txt"] errors: list[str] = [] for line in checksum_path.read_text(encoding="utf-8").splitlines(): if not line or line.startswith("#"): continue parts = line.split(" ", 1) if len(parts) != 2 or not re.fullmatch(r"[0-9a-f]{64}", parts[0]): errors.append(f"invalid checksum line: {line}") continue expected, relative = parts path = root / relative if not path.is_file() or sha256_file(path) != expected: errors.append(f"checksum mismatch or missing: {relative}") return errors def collect_errors(root: Path) -> list[str]: errors: list[str] = [] release_path = root / "manifests/runtime/phase-0.9e-r-release-correlation.json" port_path = root / "manifests/runtime/phase-0.9e-r-port9020-audit.json" release_schema_path = ( root / "manifests/runtime/phase-0.9e-r-release-correlation.schema.json" ) port_schema_path = ( root / "manifests/runtime/phase-0.9e-r-port9020-audit.schema.json" ) try: release = load_json(release_path) port = load_json(port_path) release_schema = load_json(release_schema_path) port_schema = load_json(port_schema_path) except (OSError, ValueError, json.JSONDecodeError) as error: return [str(error)] errors.extend(validate_schema_instance(release_schema, release)) errors.extend(validate_schema_instance(port_schema, port)) errors.extend(validate_release_manifest(release)) errors.extend(validate_port_manifest(port)) for relative in DELIVERABLES: if not (root / relative).is_file(): errors.append(f"missing deliverable: {relative}") if git(root, "rev-parse", "--abbrev-ref", "HEAD") != BRANCH: errors.append("current branch is not the Phase-0.9E-R branch") if git(root, "merge-base", BASELINE, "HEAD") != BASELINE: errors.append("branch no longer descends from the canonical baseline") if sha256_file(root / "manifests/artifact-denylist.json") != DENYLIST_SHA256: errors.append("permanent artifact denylist changed") for relative, expected in IMMUTABLE_HASHES.items(): path = root / relative if not path.is_file() or sha256_file(path) != expected: errors.append(f"immutable evidence changed: {relative}") outer = candidate_archive_path(root) if not outer.is_file(): errors.append(f"local Y2JB candidate missing: {outer}") elif outer.stat().st_size != OUTER_SIZE or sha256_file(outer) != OUTER_SHA256: errors.append("local Y2JB outer identity changed") else: try: with zipfile.ZipFile(outer) as archive: info = archive.getinfo(INNER_NAME) if ( len(archive.infolist()) != 5 or archive.comment != b"" or info.file_size != INNER_SIZE or info.compress_size != 504158629 or info.CRC != int("522808c8", 16) ): errors.append("local ZIP metadata changed") digest = hashlib.sha256() magic = b"" with archive.open(info) as stream: for chunk in iter(lambda: stream.read(1024 * 1024), b""): if not magic: magic = chunk[:6] digest.update(chunk) if magic != b"SIECAF" or digest.hexdigest() != INNER_SHA256: errors.append("inner SIECAF identity changed") except (OSError, KeyError, zipfile.BadZipFile) as error: errors.append(f"local ZIP validation failed: {error}") upstream = root / "work/upstream/Y2JB-official" if not upstream.is_dir(): errors.append("official immutable upstream worktree missing") else: if git(upstream, "rev-parse", "HEAD") != UPSTREAM_COMMIT: errors.append("official upstream HEAD changed") if git(upstream, "rev-parse", "HEAD^{tree}") != UPSTREAM_TREE: errors.append("official upstream tree changed") if git(upstream, "status", "--porcelain=v1"): errors.append("official upstream worktree is dirty") remotes = git(upstream, "remote", "-v") if UPSTREAM_REMOTE not in remotes: errors.append("official upstream remote changed") if git(upstream, "submodule", "status"): errors.append("unexpected official upstream submodule") if len(git(upstream, "ls-files").splitlines()) != 19: errors.append("official upstream tracked file inventory changed") for relative, (expected_size, expected_hash) in UPSTREAM_FILE_HASHES.items(): path = upstream / relative if ( not path.is_file() or path.stat().st_size != expected_size or sha256_file(path) != expected_hash ): errors.append(f"official upstream file identity changed: {relative}") errors.extend(phase09er_path_errors(changed_paths(root))) for path in root.rglob("*"): if ( path.is_file() and ".git" not in path.parts and "work" not in path.parts and path.stat().st_size > 50 * 1024 * 1024 ): errors.append(f"large release-like object present in repository: {path}") errors.extend(validate_checksum_file(root)) return errors def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--root", type=Path, required=True) args = parser.parse_args() errors = collect_errors(args.root.resolve()) if errors: for error in errors: print(f"ERROR: {error}") return 1 print("Phase-0.9E-R official Y2JB provenance validation: PASS") print(f"Release correlation: OFFICIAL_RELEASE_NO_MATCH") print(f"Port 9020: {PORT_9020_CLASSIFICATION}") print(f"Decision: {FINAL_CLASSIFICATION}") print("Device actions: NONE") return 0 if __name__ == "__main__": raise SystemExit(main())