Files
chimera-gfx-Public/tools/validate_phase09d_readback.py
Chimera GFX release export a6037502d7
phase0-ci / build-and-audit (push) Successful in 2m14s
Publish Chimera GFX source
2026-09-03 03:27:14 +02:00

760 lines
29 KiB
Python

#!/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())