Files
chimera-gfx-Public/tools/validate_retroarch_phase10a.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

327 lines
12 KiB
Python

#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-3.0-or-later
"""Validate the offline Phase-1.0A RetroArch PS5 port evidence."""
from __future__ import annotations
import argparse
import hashlib
import json
from pathlib import Path
import re
import subprocess
import sys
from typing import Any
PHASE = "PHASE_1_0A_RETROARCH_PS5_PORT_BOOTSTRAP"
STATUS = "RETROARCH_PS5_SOFTWARE_PORT_BUILT"
BASELINE = "4c086f84ab2e7c53e6750b66b3b3e1f591ff6a06"
BRANCH = "codex/chimera-gfx-phase10a-retroarch-port-bootstrap"
RETROARCH_BRANCH = "codex/ps5-port-bootstrap"
RETROARCH_TAG = "v1.22.2"
RETROARCH_COMMIT = "69a4f0ea1e8aaf442ae4858f2e7f2b31a1776576"
RETROARCH_TREE = "33babf9eb7699b5d571a3063ea21c3e488c159fe"
RETROARCH_ARCHIVE_SHA256 = (
"245ef18c8fa8fbd9fbb5eb25cf43e17c6aace2f95c1ed99873cbd794012bb232"
)
SDK_COMMIT = "d2e2e585740362976a39fdd5ccf390f199a7bc37"
SDL_COMMIT = "0baf4ac49382b537ba449901b5b6d0d189bb1fbb"
PACBREW_COMMIT = "c2abcfcb60f569128abd0e8e70ad03a67bee5ea7"
DENYLIST_SHA256 = "e9603b0e3792781ad5b511afb22ef61e3d4fd4c5a16bf928f8609193bcd97783"
OFFLINE_AUTHORIZATION_FIELDS = (
"offline_source_acquisition_authorized",
"offline_source_modification_authorized",
"offline_target_build_authorized",
"offline_artifact_analysis_authorized",
"private_gitea_push_authorized",
)
DEVICE_AUTHORIZATION_FIELDS = (
"ps5_connection_authorized",
"device_transfer_authorized",
"device_execution_authorized",
"installation_authorized",
"lifecycle_authorized",
"autoload_authorized",
"device_write_authorized",
"automatic_retry",
)
DELIVERABLES = (
"docs/retroarch/phase-1.0a-port-plan.md",
"docs/retroarch/phase-1.0a-upstream-analysis.md",
"docs/retroarch/phase-1.0a-ps4-reference-delta.md",
"docs/retroarch/phase-1.0a-pacbrew-sdl-analysis.md",
"docs/retroarch/phase-1.0a-build-results.md",
"docs/retroarch/phase-1.0a-driver-status.md",
"docs/retroarch/phase-1.0a-next-device-smoke-test.md",
"manifests/retroarch/upstreams.json",
"manifests/retroarch/phase-1.0a-build.json",
"manifests/retroarch/phase-1.0a-artifacts.json",
"tests/test_retroarch_phase10a.py",
"tools/validate_retroarch_phase10a.py",
"packaging/retroarch/phase10a/SHA256SUMS.txt",
)
REQUIRED_REAL_SYMBOLS = {
"rarch_main",
"retroarch_main_init",
"runloop_iterate",
"retro_init",
"retro_deinit",
"retro_run",
"frontend_ctx_ps5",
}
DISABLED_FEATURES = {
"networking",
"online_updater",
"dynamic_cores",
"autoload",
"installation",
"gnm",
"opengl",
"vulkan",
}
FORBIDDEN_MARKERS = (
"chimera_lifecycle_probe",
"prospero-deploy",
"payload_sender",
"sceSystemServiceLoadExec",
)
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} is not 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 artifact_execution_allowed(record: dict[str, Any]) -> bool:
return bool(record.get("execution_eligible", False))
def route_is_forbidden(text: str) -> bool:
lowered = text.lower()
return any(marker.lower() in lowered for marker in FORBIDDEN_MARKERS)
def profile_is_closed(profile: dict[str, Any]) -> bool:
disabled = set(profile.get("disabled_features", []))
return (
DISABLED_FEATURES.issubset(disabled)
and profile.get("automatic_retry") is False
and profile.get("filesystem_writes_on_default_path") is False
)
def is_real_retroarch_artifact(record: dict[str, Any]) -> bool:
return REQUIRED_REAL_SYMBOLS.issubset(set(record.get("required_symbols", [])))
def has_personal_path(strings: list[str]) -> bool:
patterns = (
re.compile(r"[A-Za-z]:\\Users\\", re.IGNORECASE),
re.compile(r"/(?:mnt/[a-z]/)?Users/[^/]+/", re.IGNORECASE),
re.compile(r"/home/[^/]+/", re.IGNORECASE),
)
return any(pattern.search(value) for value in strings for pattern in patterns)
def has_ip_address(strings: list[str]) -> bool:
pattern = re.compile(r"(?<!\d)(?:\d{1,3}\.){3}\d{1,3}(?!\d)")
return any(pattern.search(value) for value in strings)
def validate_artifact(record: dict[str, Any]) -> list[str]:
errors: list[str] = []
name = record.get("name", "<unnamed>")
if artifact_execution_allowed(record):
errors.append(f"{name}: execution_eligible must be false")
if record.get("target_execution_performed") is not False:
errors.append(f"{name}: target execution state is not false")
if record.get("reproducibility") != "BYTE_IDENTICAL_TWO_CLEAN_BUILDS":
errors.append(f"{name}: reproducibility is not byte exact")
if not is_real_retroarch_artifact(record):
errors.append(f"{name}: real RetroArch symbols are incomplete")
if len(record.get("sha256", "")) != 64 or record.get("size", 0) <= 0:
errors.append(f"{name}: invalid size/hash identity")
if not record.get("linker_map_sha256"):
errors.append(f"{name}: linker map identity is missing")
if not isinstance(record.get("undefined_symbols"), list):
errors.append(f"{name}: undefined-symbol inventory is missing")
if record.get("embedded_personal_paths") is not False:
errors.append(f"{name}: personal path audit did not pass")
if not isinstance(record.get("embedded_ip_addresses"), list):
errors.append(f"{name}: IP-address inventory is missing")
if record.get("embedded_device_ip_addresses") is not False:
errors.append(f"{name}: a device/private address is embedded")
if record.get("forbidden_markers_found"):
errors.append(f"{name}: forbidden marker found")
return errors
def git(root: Path, *args: str) -> str:
result = subprocess.run(
["git", *args],
cwd=root,
capture_output=True,
text=True,
check=False,
)
if result.returncode:
raise RuntimeError(result.stderr.strip() or "git command failed")
return result.stdout.strip()
def validate(root: Path, retroarch_root: Path | None = None) -> list[str]:
errors: list[str] = []
for relative in DELIVERABLES:
if not (root / relative).is_file():
errors.append(f"missing deliverable: {relative}")
try:
build = load_json(root / "manifests/retroarch/phase-1.0a-build.json")
artifacts_doc = load_json(
root / "manifests/retroarch/phase-1.0a-artifacts.json"
)
upstreams = load_json(root / "manifests/retroarch/upstreams.json")
except (OSError, ValueError, json.JSONDecodeError) as error:
return errors + [str(error)]
if build.get("phase") != PHASE or build.get("status") != STATUS:
errors.append("phase/status mismatch")
if build.get("baseline_commit") != BASELINE:
errors.append("baseline mismatch")
if build.get("branch") != BRANCH:
errors.append("branch mismatch")
for field in OFFLINE_AUTHORIZATION_FIELDS:
if build.get(field) is not True:
errors.append(f"{field} must record the explicit offline grant")
for field in DEVICE_AUTHORIZATION_FIELDS:
if build.get(field) is not False:
errors.append(f"{field} must remain false")
for field in (
"ps5_connected",
"device_request_performed",
"files_transferred",
"device_write_performed",
"target_execution_performed",
"install_package_created",
"execution_package_created",
):
if build.get(field) is not False:
errors.append(f"{field} must remain false")
fork = build.get("chimera_retroarch", {})
if fork.get("branch") != RETROARCH_BRANCH or len(fork.get("commit", "")) != 40:
errors.append("private fork identity is incomplete")
if fork.get("working_tree_clean") is not True:
errors.append("private fork was not clean at final build")
profiles = build.get("profiles", {})
for name in (
"ps5-headless-smokecore",
"ps5-software-rgui-smokecore",
"host-smokecore-integration",
):
if name not in profiles or not profile_is_closed(profiles[name]):
errors.append(f"profile is not closed: {name}")
host = build.get("host_tests", {})
if (
host.get("frames") != 600
or host.get("video_fnv1a64") != "43f920496eb5f435"
or host.get("audio_fnv1a64") != "a48f47dc08c56625"
or host.get("asan_ubsan") != "PASS"
or host.get("input_mapping") != "PASS"
or host.get("clean_shutdown") != "PASS"
):
errors.append("host smoke-core evidence mismatch")
expected_upstreams = {
"retroarch": RETROARCH_COMMIT,
"ps5_payload_sdk": SDK_COMMIT,
"ps5_sdl": SDL_COMMIT,
"pacbrew": PACBREW_COMMIT,
}
for name, commit in expected_upstreams.items():
if upstreams.get("sources", {}).get(name, {}).get("commit") != commit:
errors.append(f"upstream mismatch: {name}")
retroarch = upstreams.get("sources", {}).get("retroarch", {})
if (
retroarch.get("tag") != RETROARCH_TAG
or retroarch.get("tree") != RETROARCH_TREE
or retroarch.get("source_archive_sha256") != RETROARCH_ARCHIVE_SHA256
):
errors.append("RetroArch release provenance mismatch")
records = artifacts_doc.get("artifacts", [])
if {item.get("name") for item in records} != {
"retroarch_ps5_headless.elf",
"retroarch_ps5_software.elf",
}:
errors.append("artifact set mismatch")
for record in records:
errors.extend(validate_artifact(record))
denylist = root / "manifests/artifact-denylist.json"
if not denylist.is_file() or sha256_file(denylist) != DENYLIST_SHA256:
errors.append("permanent denylist changed")
tracked = git(root, "ls-files").splitlines()
forbidden_suffixes = (".elf", ".self", ".sprx", ".pkg")
for relative in tracked:
if relative.lower().endswith(forbidden_suffixes):
errors.append(f"tracked target artifact: {relative}")
new_text = "\n".join(
(root / relative).read_text(encoding="utf-8", errors="replace")
for relative in DELIVERABLES
if (root / relative).is_file()
)
unfinished_template = "{" * 2
unfinished_label = "".join(("T", "B", "D"))
if unfinished_template in new_text or unfinished_label in new_text:
errors.append("unfinished placeholder in deliverables")
if retroarch_root is not None:
for record in records:
path = retroarch_root / record["local_relative_path"]
if not path.is_file():
errors.append(f"local artifact missing: {path}")
continue
if path.stat().st_size != record["size"] or sha256_file(path) != record[
"sha256"
]:
errors.append(f"local artifact identity mismatch: {path.name}")
return errors
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--root", type=Path, required=True)
parser.add_argument("--retroarch-root", type=Path)
args = parser.parse_args()
errors = validate(
args.root.resolve(),
args.retroarch_root.resolve() if args.retroarch_root else None,
)
if errors:
for error in errors:
print(error, file=sys.stderr)
return 1
print("Phase 1.0A RetroArch evidence validated")
return 0
if __name__ == "__main__":
raise SystemExit(main())