#!/usr/bin/env python3 # SPDX-License-Identifier: GPL-3.0-or-later """Validate the offline-only Phase-0.9D readback and recovery audit.""" from __future__ import annotations import argparse import hashlib import json from pathlib import Path import subprocess from typing import Any EXPECTED_BASELINE = "e0e68829ab76977c845e7106ef93e6c01fbc966e" EXPECTED_BRANCH = "codex/chimera-gfx-phase09d-existing-stack-readback" EXPECTED_PHASE = "PHASE_0_9D_EXISTING_STACK_READBACK" EXPECTED_STATUS = "DESIGN_ONLY" BLOCKED_HASH = ( "4be1c17b4964f2b68c39b5145bc4af4619c32512d60269ecf5c39728b390fa63" ) DENYLIST_HASH = ( "e9603b0e3792781ad5b511afb22ef61e3d4fd4c5a16bf928f8609193bcd97783" ) AUTHORIZATION_FIELDS = ( "installation_authorized", "execution_authorized", "lifecycle_authorized", "automatic_retry", "autoload_authorized", "device_write_authorized", "transfer_authorized", "observer_build_authorized", "backup_creation_authorized", ) ACTION_FIELDS = ( "ps5_connected", "device_request_performed", "files_transferred", "device_write_performed", "target_execution_performed", "target_artifact_created", "target_build_performed", "observer_created", "device_client_created", "backup_created", "staging_performed", ) 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/README.md": ( "372aeb28dc971b2bd98093a47fdaf77c32f75bbdc3b3d7e8678900744b91eadb" ), "../chimera-elfldr/main.c": ( "876389a26999073994e63ca29926982280d9594a1ee941244205b54f57e2b4d1" ), "../chimera-elfldr/bootstrap.c": ( "5a8072ec0d6db919cb3a81a7028dc91fd8e2c3a0d69b0fa7e84836fa93b45381" ), "../chimera-elfldr/socksrv.c": ( "d642ced3e9b4a296dd15e355050ebe956f53a6dfdaa6ac10109cd067a3bba3d7" ), "../chimera-ps5-payload-manager/README.md": ( "a00277252da46c701326ef66e5ca0d13cadffd50b3c8adf6da89cd1948c97718" ), "../chimera-ps5-payload-manager/DEVELOPMENT.md": ( "0c17bed71b07c9aadf31c47625bbe1ccb42e670e594fb64a1e07aa782d5eec31" ), "../chimera-ps5-payload-manager/deploy.sh": ( "2facdc1ca70db57ba258265c07a8ae1d30d3d2fb429d6718d5d5088300788e52" ), "../chimera-ps5-payload-manager/include/pldmgr.h": ( "8603b8338364cea60ffaf94f985f112fb2ddfda2f44b1ccc9a2bd7a15d1b229a" ), "../chimera-ps5-payload-manager/src/http_server.c": ( "2c8ff2a4bc1028d71e3cc342839584d425b6e7502e55e18e2762cdbf62c59d40" ), "../chimera-ps5-payload-manager/src/log_server.c": ( "659095f43df1bbe8eb24acb165f027edc277af1e60aabb26ba9e3920b233d6f1" ), "../chimera-ps5-payload-manager/src/autoload.c": ( "7051cab3ee1a3e0b9f6498000565eb9e160b9c63efa1771f250e98ec3aa4ae67" ), "../chimera-ps5-payload-manager/src/main.c": ( "b2374e8fb101587b15c8261c58cb4f0573d88c490214365051eb9facd60f6eed" ), "../chimera-ps5-payload-manager/src/controlled_manager.c": ( "042b55b2cece32effed636529249fd18061a2fe3c5e70c7f755f2817cfb84b99" ), "../chimera-ps5-payload-manager/src/verified_launcher.c": ( "066100ca4917c7acc560e2e85666ca136cd7ccfd9094417377048f41106dd56e" ), "../chimera-ps5-payload-manager/src/ps5_launcher.c": ( "29c1a5fd01784a59e88b3698940f120cb03020071bc2b7d74a1da1a51524ef59" ), "../chimera-ps5-payload-manager/src/payload_mgr.c": ( "d67e9ba33edc8ca3a45aae07923d4c4790348b5f8570307e581e16780abafcba" ), "../chimera-ps5-payload-manager/src/repository.c": ( "8ba694ae6d4813573752acd82ce2dbfb715b51d4ec6155f783904d76dd115adb" ), "../chimera-ps5-payload-manager/src/sources.c": ( "a7a4a5cafccfba74902d6ed21ba001e6a9f62d4dff840ac38882d04827162c96" ), "work/upstream/elfldr-v0.23/socksrv.c": ( "500d3c7df7ed5eac1adc925c89344d75c469651b71143716fdb77bfb2209a40c" ), "work/upstream/pldmgr-v0.3.1/include/pldmgr.h": ( "01c693a3248dce7a663dd4ed9c73ce5f3a4443b5f2bd210746d94993dee27b91" ), "work/upstream/pldmgr-v0.3.1/src/http_server.c": ( "35cf5d8f0dd44cf64ceab5e4b0ecc09413c82d7e9946ba9de2ca4b1898631fdd" ), "samples/lifecycle_probe/main.c": ( "1ae7df1fe921ccab2a252f77975d3d441ef7725e34535b024580c0d4a242d766" ), } 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" ), } DELIVERABLES = ( "docs/runtime/phase-0.9d-bootstrap-recovery-chain.md", "docs/runtime/phase-0.9d-existing-stack-endpoint-matrix.md", "docs/runtime/phase-0.9d-readback-feasibility.md", "docs/runtime/phase-0.9d-off-device-backup-contract.md", "docs/runtime/phase-0.9d-independent-recovery-analysis.md", "docs/runtime/phase-0.9d-operational-windows.md", "manifests/runtime/phase-0.9d-existing-stack-readback.json", "manifests/runtime/phase-0.9d-existing-stack-readback.schema.json", "tools/validate_phase09d_readback.py", "tests/test_phase09d_readback.py", ) EXPECTED_DECISIONS = { "new_observer_feasibility": "BLOCKED", "existing_stack_manual_fact_collection": "PARTIAL", "existing_stack_single_readback": "BLOCKED_NO_READBACK_PATH", "existing_stack_repeat_readback": "BLOCKED", "elfldr_independent_recovery": "PARTIAL", "payload_manager_independent_recovery": "PARTIAL", "side_by_side_feasibility": "BLOCKED", "device_write": "NOT_AUTHORIZED", "target_execution": "NOT_AUTHORIZED", "installation": "NOT_AUTHORIZED", } REQUIRED_EFFECTIVE_ROUTE_FIELDS = ( "method", "endpoint", "handler", "source", "lines", "parameters", "authentication", "response", "open_flags", "reads_bytes", "writes_bytes", "creates_file", "removes_file", "renames_file", "reads_directory", "reads_metadata", "calculates_hash", "modifies_configuration", "writes_server_active_flag", "writes_autoload_triggered", "writes_log_ring", "launches_payload", "process_or_service_action", "network_behavior", "timeout_behavior", "retry_behavior", "maximum_size", "short_read_behavior", "error_behavior", "effect_class", "readback_candidate", "observation_candidate", "binary_safe_file_response", "exact_returned_byte_count", "partial_result_rejected", "forbidden_reason", ) BACKUP_STATES = ( "TRANSFER_NOT_STARTED", "TRANSFER_INCOMPLETE", "HOST_COPY_RECEIVED", "HOST_COPY_REOPENED", "HOST_COPY_HASHED", "SECOND_COPY_CREATED", "SECOND_COPY_REOPENED", "SECOND_COPY_HASHED", "COPIES_MATCH", "SOURCE_MAPPING_PARTIAL", "SOURCE_MAPPING_VERIFIED", "INVALID", ) 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 an 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 effective_route( route: dict[str, Any], defaults: dict[str, Any] ) -> dict[str, Any]: merged = dict(defaults) merged.update(route) return merged def route_readback_errors( route: dict[str, Any], defaults: dict[str, Any] | None = None ) -> list[str]: merged = effective_route(route, defaults or {}) errors: list[str] = [] flags = {str(flag).lower() for flag in merged.get("open_flags", [])} mutating_open = any( token in flag for flag in flags for token in ("o_wronly", "o_rdwr", "o_creat", "o_append", "wb", "(w)") ) mutating = any( bool(merged.get(field)) for field in ( "writes_bytes", "creates_file", "removes_file", "renames_file", "modifies_configuration", ) ) or mutating_open if merged.get("readback_candidate"): if mutating: errors.append("readback candidate mutates a device file or configuration") if merged.get("launches_payload"): errors.append("readback candidate launches a payload") if merged.get("process_or_service_action"): errors.append("readback candidate performs a process/service action") if merged.get("writes_autoload_triggered"): errors.append("readback candidate writes autoload_triggered") if not merged.get("binary_safe_file_response"): errors.append("readback candidate lacks binary-safe framing") if not merged.get("exact_returned_byte_count"): errors.append("readback candidate lacks an exact byte count") if not merged.get("partial_result_rejected"): errors.append("readback candidate does not reject partial output") if str(merged.get("short_read_behavior", "")).upper() in { "", "ABSENT", "UNPROVEN", "NO_HOST_FILE_READBACK_CONTRACT", "NO_FILE_RESPONSE", }: errors.append("readback candidate lacks short-read detection") if merged.get("automatic_retry") is True: errors.append("readback candidate enables automatic retry") if merged.get("automatic_resume") is True: errors.append("readback candidate enables automatic resume") return errors def backup_record_errors(record: dict[str, Any]) -> list[str]: errors: list[str] = [] status = record.get("status") if status not in BACKUP_STATES: errors.append("unknown backup status") if record.get("automatic_resume"): errors.append("automatic resume is forbidden") if record.get("automatic_retry"): errors.append("automatic retry is forbidden") if status in {"HOST_COPY_HASHED", "SECOND_COPY_HASHED", "COPIES_MATCH"}: if not isinstance(record.get("exact_byte_count"), int): errors.append("a hash requires an exact byte count") if not record.get("closed_and_reopened"): errors.append("hash requires close and reopen") if not record.get("sha256"): errors.append("hashed state requires SHA-256") if status == "COPIES_MATCH": if not all( record.get(field) for field in ("sizes_match", "hashes_match", "bytes_match") ): errors.append("COPIES_MATCH requires size, hash, and byte equality") if record.get("transfer_complete") is False and status != "INVALID": errors.append("partial transfer must be INVALID") if record.get("recovery_proven"): errors.append("host backup cannot prove recovery") return errors def server_active_observation_status(semantics: dict[str, Any]) -> str: if ( not semantics.get("fully_documented") or semantics.get("reset_path") in {None, "UNPROVEN"} ): return "BLOCKED" if semantics.get("reset_path") == "NONE_IN_PROCESS": return "PARTIAL" return "READY_FOR_REVIEW" def recovery_dependency_classification( component: str, recovery_executor: str ) -> str: if component == recovery_executor: return "SELF_DEPENDENT" if recovery_executor in {"ABSENT", "UNPROVEN", ""}: return recovery_executor return "CROSS_DEPENDENT" def _type_matches(value: Any, expected: str) -> bool: return { "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), "number": isinstance(value, (int, float)) and not isinstance(value, bool), "null": value is None, }.get(expected, True) def validate_schema_instance( schema: dict[str, Any], instance: Any, path: str = "$" ) -> list[str]: """Small offline validator for the schema features used by this record.""" errors: list[str] = [] expected_type = schema.get("type") if expected_type and not _type_matches(instance, expected_type): return [f"{path}: expected {expected_type}"] if "const" in schema and instance != schema["const"]: errors.append(f"{path}: expected constant {schema['const']!r}") if "enum" in schema and instance not in schema["enum"]: errors.append(f"{path}: value is outside enum") if isinstance(instance, dict): required = schema.get("required", []) for key in required: if key not in instance: errors.append(f"{path}: missing {key}") if len(instance) < schema.get("minProperties", 0): errors.append(f"{path}: too few properties") 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 schema.get("additionalProperties") is False: errors.append(f"{path}: unexpected property {key}") elif isinstance(schema.get("additionalProperties"), dict): errors.extend( validate_schema_instance( schema["additionalProperties"], value, f"{path}.{key}" ) ) if isinstance(instance, list): if len(instance) < schema.get("minItems", 0): errors.append(f"{path}: too few items") if "maxItems" in schema and len(instance) > schema["maxItems"]: errors.append(f"{path}: too many items") item_schema = schema.get("items") if isinstance(item_schema, dict): for index, item in enumerate(instance): errors.extend( validate_schema_instance(item_schema, item, f"{path}[{index}]") ) return errors def validate_manifest(manifest: dict[str, Any]) -> list[str]: errors: list[str] = [] if manifest.get("phase") != EXPECTED_PHASE: errors.append("wrong phase") if manifest.get("status") != EXPECTED_STATUS: errors.append("status must remain DESIGN_ONLY") if manifest.get("baseline_commit") != EXPECTED_BASELINE: errors.append("wrong baseline") if manifest.get("branch") != EXPECTED_BRANCH: errors.append("wrong branch") for field in AUTHORIZATION_FIELDS: if manifest.get("authorization", {}).get(field) is not False: errors.append(f"authorization.{field} must be false") for field in ACTION_FIELDS: if manifest.get("actions", {}).get(field) is not False: errors.append(f"actions.{field} must be false") canonical = manifest.get("canonical_state", {}) expected_canonical = { "phase08_status": "READ_ONLY_PREFLIGHT_BLOCKED", "phase09a_status": "DESIGN_ONLY", "phase09b_status": "BLOCKED", "phase09c_classification": "BLOCKED_MULTIPLE_FOUNDATIONAL_CONTRACTS", "firmware_runtime_behavior": "UNPROVEN", "stock_identities": "reference_only", "payload_manager_backup": "HARD_BLOCKER_FOR_INSTALLATION", "independent_recovery": "UNPROVEN", "permanent_denylist_sha256": DENYLIST_HASH, "permanently_blocked_artifact_sha256": BLOCKED_HASH, } for field, expected in expected_canonical.items(): if canonical.get(field) != expected: errors.append(f"canonical_state.{field} changed") if manifest.get("decisions") != EXPECTED_DECISIONS: errors.append("decision matrix changed") if manifest.get("source_commits", {}).get("lifecycle_source") != ( "fe08300339a13f899fb78ea404ada381a5cba87c" ): errors.append("lifecycle source binding changed") for name, (_, expected) in SOURCE_COMMITS.items(): if manifest.get("source_commits", {}).get(name) != expected: errors.append(f"source commit changed: {name}") if manifest.get("source_tree_status", {}).get(name) != "clean": errors.append(f"source tree is not recorded clean: {name}") routes = manifest.get("endpoint_matrix", []) if len(routes) != 40: errors.append("endpoint matrix must contain all 40 audited route records") defaults = manifest.get("endpoint_defaults", {}) identities: set[tuple[str, str]] = set() for route in routes: effective = effective_route(route, defaults) identity = (str(route.get("profile")), str(route.get("endpoint"))) if identity in identities: errors.append(f"duplicate route identity: {identity}") identities.add(identity) for field in REQUIRED_EFFECTIVE_ROUTE_FIELDS: if field not in effective: errors.append(f"{identity}: missing effective field {field}") errors.extend( f"{identity}: {error}" for error in route_readback_errors(route, defaults) ) if effective.get("readback_candidate") is not False: errors.append(f"{identity}: no audited route may be a readback candidate") autoload = next( ( effective_route(route, defaults) for route in routes if route.get("profile") == "full" and route.get("endpoint") == "/autoload_status" ), None, ) if not autoload or not autoload.get("writes_autoload_triggered"): errors.append("/autoload_status mutation is not recorded") flags = manifest.get("flag_semantics", {}) server_active = flags.get("server_active_flag", {}) if server_active.get("classification") == "ANTI_BRICK_CRITICAL": errors.append("server_active_flag is incorrectly anti-brick critical") if ( not server_active.get("fully_documented") or server_active.get("reset_path") != "NONE_IN_PROCESS" or server_active.get("lifetime") != "PROCESS_LOCAL" ): errors.append("server_active_flag semantics are incomplete") autoload_flag = flags.get("autoload_triggered", {}) if autoload_flag.get("excluded_windows") != [1, 2]: errors.append("autoload_triggered must be excluded from Windows 1 and 2") if len(manifest.get("readback_routes", [])) != 7: errors.append("readback route search is incomplete") for route in manifest.get("readback_routes", []): if route.get("usable_once") or route.get("usable_twice"): errors.append("a rejected readback route is marked usable") if manifest.get("path_classification") != "PATH_CONFLICT": errors.append("path conflict was removed") if manifest.get("runtime_observed_live_paths") != []: errors.append("offline package/config paths cannot become live paths") contract = manifest.get("host_backup_contract", {}) if contract.get("statuses") != list(BACKUP_STATES): errors.append("backup status vocabulary changed") required_true = ( "one_component_per_session", "new_exclusive_local_output", "binary_mode", "exact_received_byte_count_required", "close_reopen_before_hash", "sha256_required", "size_required", "second_independent_connection", "second_new_output", "compare_size", "compare_sha256", "compare_every_byte", "capture_raw_protocol_metadata", "capture_literal_source_path", "capture_device_and_session", "capture_client_commit", "off_device_backup_valid_requires_copies_match", ) for field in required_true: if contract.get(field) is not True: errors.append(f"host_backup_contract.{field} must be true") required_false = ( "overwrite_existing_output", "automatic_resume", "automatic_retry", "contains_device_write_command", "recovery_proven_allowed", ) for field in required_false: if contract.get(field) is not False: errors.append(f"host_backup_contract.{field} must be false") if contract.get("partial_transfer_status") != "INVALID": errors.append("partial host transfer must be INVALID") recovery = manifest.get("recovery_dependencies", {}) if recovery.get("elfldr", {}).get("classification") != "PARTIAL": errors.append("elfldr recovery must remain PARTIAL") manager = recovery.get("payload_manager", {}) if manager.get("classification") != "PARTIAL": errors.append("Payload Manager recovery must remain PARTIAL") if "CROSS_DEPENDENT" not in manager.get("detailed", []): errors.append("Payload Manager cross-dependence is missing") if manifest.get("side_by_side", {}).get("classification") != "BLOCKED": errors.append("side-by-side must remain blocked") if manifest.get("side_by_side", {}).get( "grants_installation_authorization" ): errors.append("side-by-side cannot authorize installation") windows = manifest.get("operational_windows", []) if [window.get("window") for window in windows] != list(range(1, 8)): errors.append("operational windows are incomplete or reordered") for window in windows: if window.get("device_write") is not False: errors.append(f"Window {window.get('window')} permits device write") if window.get("payload_launch") is not False: errors.append(f"Window {window.get('window')} permits payload launch") if window.get("autoload_status_route") is not False: errors.append(f"Window {window.get('window')} permits /autoload_status") if window.get("automatic_retry") is not False: errors.append(f"Window {window.get('window')} permits retry") if windows and windows[0].get("file_transfer") is not False: errors.append("Window 1 contains file transfer") if len(windows) >= 3 and windows[2].get("automatic_third_attempt") is not False: errors.append("Window 3 permits an automatic third attempt") if len(windows) >= 4 and windows[3].get("component_session_separate") is not True: errors.append("components do not have separate windows") final = manifest.get("final_decision", {}) if ( final.get("classification") != "BLOCKED" or final.get("hardware_evidence_claimed") is not False or final.get("device_action_authorized") is not False ): errors.append("final decision must remain an offline-only block") return errors def _phase09d_paths(root: Path) -> list[str]: paths: set[str] = set() for candidate in git(root, "ls-files").splitlines(): normalized = candidate.replace("\\", "/") lowered = normalized.lower() if "phase-0.9d" in lowered or "phase09d" in lowered: paths.add(normalized) output = git(root, "status", "--porcelain=v1", "--untracked-files=all") for line in output.splitlines(): if len(line) < 4: continue candidate = line[3:].replace("\\", "/") if " -> " in candidate: candidate = candidate.rsplit(" -> ", 1)[1] lowered = candidate.lower() if "phase-0.9d" in lowered or "phase09d" in lowered: paths.add(candidate) return sorted(paths) def collect_errors(root: Path) -> list[str]: errors: list[str] = [] manifest_path = ( root / "manifests/runtime/phase-0.9d-existing-stack-readback.json" ) schema_path = ( root / "manifests/runtime/phase-0.9d-existing-stack-readback.schema.json" ) try: manifest = load_json(manifest_path) schema = load_json(schema_path) except (OSError, ValueError, json.JSONDecodeError) as error: return [str(error)] errors.extend(validate_manifest(manifest)) errors.extend(validate_schema_instance(schema, manifest)) for relative in DELIVERABLES: if not (root / relative).is_file(): errors.append(f"missing deliverable: {relative}") for relative, expected in SOURCE_HASHES.items(): path = (root / relative).resolve() if not path.is_file(): errors.append(f"missing source evidence: {relative}") elif sha256_file(path) != expected: errors.append(f"source evidence changed: {relative}") if manifest.get("source_evidence", {}).get(relative) != expected: errors.append(f"manifest source hash mismatch: {relative}") 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}") if manifest.get("immutable_evidence", {}).get(relative) != expected: errors.append(f"manifest immutable hash mismatch: {relative}") denylist = root / "manifests/artifact-denylist.json" if not denylist.is_file() or sha256_file(denylist) != DENYLIST_HASH: errors.append("permanent denylist changed") else: denylist_data = load_json(denylist) entries = json.dumps(denylist_data, sort_keys=True) if BLOCKED_HASH not in entries: errors.append("permanently blocked artifact is absent from denylist") for name, (relative, expected) in SOURCE_COMMITS.items(): source_root = (root / relative).resolve() try: if git(source_root, "rev-parse", "HEAD") != expected: errors.append(f"source HEAD changed: {name}") if git(source_root, "status", "--porcelain=v1"): errors.append(f"source tree is dirty: {name}") except RuntimeError as error: errors.append(f"{name}: {error}") try: if git(root, "branch", "--show-current") != EXPECTED_BRANCH: errors.append("current branch is not the Phase-0.9D branch") except RuntimeError as error: errors.append(str(error)) allowed_suffixes = {".md", ".json", ".py"} forbidden_roots = ("samples/", "src/", "include/", "packaging/", "adapters/") for relative in _phase09d_paths(root): lowered = relative.lower() if Path(relative).suffix.lower() not in allowed_suffixes: errors.append(f"forbidden Phase-0.9D artifact/source suffix: {relative}") if lowered.startswith(forbidden_roots): errors.append(f"forbidden Phase-0.9D target/product path: {relative}") if lowered.endswith((".elf", ".o", ".a", ".so", ".map", ".s", ".asm", ".ld")): errors.append(f"forbidden Phase-0.9D target artifact: {relative}") docs = "\n".join( (root / relative).read_text(encoding="utf-8") for relative in DELIVERABLES if relative.endswith(".md") and (root / relative).is_file() ) for marker in ( "BLOCKED_NO_READBACK_PATH", "PATH_CONFLICT", "server_active_flag", "autoload_triggered", "OFF_DEVICE_BACKUP_VALID", "RECOVERY_PROVEN", "No PS5 was contacted", ): if marker not in docs: errors.append(f"documentation marker missing: {marker}") 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.9D existing-stack readback audit: PASS") print("Decision: BLOCKED_NO_READBACK_PATH") print("Device contact/write/execution/artifact creation: false") return 0 if __name__ == "__main__": raise SystemExit(main())