This commit is contained in:
@@ -0,0 +1,253 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Validate the inactive Phase-1.0G host one-shot runner boundary."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
from typing import Any
|
||||
|
||||
|
||||
PHASE = "PHASE_1_0G_INTERVAL_ONE_SHOT_RUNNER"
|
||||
STATUS = "OFFLINE_RUNNER_PREPARED_NO_DEVICE_AUTHORIZATION"
|
||||
RUNNER_COMMIT = "55e873df12f4dd099c082d4a26b225644b8e0567"
|
||||
TARGET_SOURCE_COMMIT = "0eaf68d6de4dc9757d85cc7ad5c714b1d151c8f8"
|
||||
ARTIFACT_NAME = "retroarch_ps5_interval_diag.elf"
|
||||
ARTIFACT_SIZE = 1845152
|
||||
ARTIFACT_SHA256 = "e8bfc01c61bfb14b5814280a6e5442f1a5ad05ace5439d1c09e7e5ee00cd0055"
|
||||
SOURCE_HASHES = {
|
||||
"tools/ps5_diag_duplex.py": "df72af4e738ab5969b78c42cf71d376c2a95b43e26b3d445cd7be9d88cadbf19",
|
||||
"tests/test_ps5_phase10e_tools.py": "1d21072d9bae6d67e38e3908737326bc5b9eceefd90a659ae3d601833f543944",
|
||||
"pkg/ps5/validate_port.py": "c65373df687328243d20184ddb65e589b1f0d2e6460058062054a9dbc8e3c185",
|
||||
}
|
||||
AUTHORIZATION_FIELDS = (
|
||||
"ps5_connection_authorized",
|
||||
"device_transfer_authorized",
|
||||
"device_execution_authorized",
|
||||
"result_receive_authorized",
|
||||
"installation_authorized",
|
||||
"autoload_authorized",
|
||||
"device_write_authorized",
|
||||
"automatic_retry",
|
||||
)
|
||||
ACTION_FIELDS = (
|
||||
"ps5_connected",
|
||||
"device_request_performed",
|
||||
"files_transferred",
|
||||
"device_write_performed",
|
||||
"target_execution_performed",
|
||||
"result_received_from_device",
|
||||
"installation_performed",
|
||||
"autoload_performed",
|
||||
)
|
||||
APPROVAL_FALSE_FIELDS = (
|
||||
"authorized",
|
||||
"installation_authorized",
|
||||
"autoload_authorized",
|
||||
"device_write_authorized",
|
||||
"automatic_retry",
|
||||
"reconnect_authorized",
|
||||
"resume_authorized",
|
||||
"automatic_reboot_authorized",
|
||||
)
|
||||
DELIVERABLES = (
|
||||
"docs/retroarch/phase-1.0g-one-shot-runner.md",
|
||||
"docs/approvals/phase-1.0g-one-shot-runner-template.md",
|
||||
"manifests/retroarch/phase-1.0g-one-shot-runner.json",
|
||||
"manifests/retroarch/phase-1.0g-one-shot-approval-template.json",
|
||||
"tools/validate_retroarch_phase10g.py",
|
||||
"tests/test_retroarch_phase10g.py",
|
||||
"packaging/retroarch/phase10g/SHA256SUMS.txt",
|
||||
)
|
||||
|
||||
|
||||
def load_json(path: Path) -> dict[str, Any]:
|
||||
value = json.loads(path.read_text(encoding="utf-8"))
|
||||
if not isinstance(value, dict):
|
||||
raise ValueError(f"{path} is not a JSON object")
|
||||
return value
|
||||
|
||||
|
||||
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 failed")
|
||||
return result.stdout.strip()
|
||||
|
||||
|
||||
def sha256_text(value: str) -> str:
|
||||
return hashlib.sha256(value.encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
def all_false(record: dict[str, Any], fields: tuple[str, ...]) -> bool:
|
||||
return all(record.get(field) is False for field in fields)
|
||||
|
||||
|
||||
def artifact_is_inactive(record: dict[str, Any], phase10f: dict[str, Any]) -> bool:
|
||||
previous = phase10f.get("artifact", {})
|
||||
return (
|
||||
record.get("name") == ARTIFACT_NAME
|
||||
and record.get("size") == ARTIFACT_SIZE == previous.get("size")
|
||||
and record.get("sha256") == ARTIFACT_SHA256 == previous.get("sha256")
|
||||
and record.get("profile") == previous.get("profile") == "interval-diag"
|
||||
and record.get("execution_eligible") is False
|
||||
and record.get("transfer_eligible") is False
|
||||
and record.get("installation_eligible") is False
|
||||
and record.get("device_action_performed") is False
|
||||
and record.get("tracked") is False
|
||||
and record.get("changed_from_phase10f") is False
|
||||
)
|
||||
|
||||
|
||||
def protocol_is_exact(record: dict[str, Any]) -> bool:
|
||||
return (
|
||||
record.get("magic") == "CHD10F01"
|
||||
and record.get("version") == 1
|
||||
and record.get("frame_size") == 64
|
||||
and record.get("interval_stages") == [f"I{index:02d}" for index in range(15)]
|
||||
and record.get("d_stages") == [f"D{index:02d}" for index in range(13)]
|
||||
)
|
||||
|
||||
|
||||
def runner_is_inactive_and_bounded(record: dict[str, Any]) -> bool:
|
||||
return (
|
||||
record.get("implementation_available") is True
|
||||
and record.get("protocol_selection") == "MANIFEST_ONLY"
|
||||
and record.get("protocol_activation_authorized") is False
|
||||
and record.get("run_id") is None
|
||||
and record.get("tracked_target") is None
|
||||
and record.get("tracked_port") is None
|
||||
and record.get("active_manifest_required") is True
|
||||
and record.get("local_approval_required") is True
|
||||
and record.get("actual_artifact_rehashed_before_receipt") is True
|
||||
and record.get("actual_artifact_rehashed_before_socket") is True
|
||||
and record.get("attempt_receipt_required") is True
|
||||
and record.get("attempt_receipt_exclusive_create") is True
|
||||
and record.get("attempt_receipt_durable_fsync") is True
|
||||
and record.get("attempt_receipt_written_before_connect") is True
|
||||
and all(record.get(field) == 1 for field in (
|
||||
"maximum_connections", "maximum_transfers", "maximum_executions",
|
||||
"maximum_result_receives"
|
||||
))
|
||||
and record.get("receive_limit_bytes") == 65536
|
||||
and record.get("retry") is False
|
||||
and record.get("reconnect") is False
|
||||
and record.get("resume") is False
|
||||
and record.get("trace_exclusive_create") is True
|
||||
and record.get("trace_overwrite") is False
|
||||
)
|
||||
|
||||
|
||||
def approval_template_is_inactive(record: dict[str, Any]) -> bool:
|
||||
return (
|
||||
record.get("phase") == PHASE
|
||||
and all_false(record, APPROVAL_FALSE_FIELDS)
|
||||
and record.get("consumed") is False
|
||||
and record.get("authorization_scope") == "EXACT_ONE_SHOT_PHASE_1_0G"
|
||||
and record.get("authorized_by") is None
|
||||
and record.get("approval_reference") is None
|
||||
and record.get("protocol_magic") == "CHD10F01"
|
||||
and record.get("protocol_version") == 1
|
||||
and record.get("protocol_frame_size") == 64
|
||||
and record.get("run_id") is None
|
||||
and record.get("target") is None
|
||||
and record.get("port") is None
|
||||
and record.get("firmware") == "9.60"
|
||||
and record.get("artifact_name") == ARTIFACT_NAME
|
||||
and record.get("artifact_size") == ARTIFACT_SIZE
|
||||
and record.get("artifact_sha256") == ARTIFACT_SHA256
|
||||
and record.get("timeout_seconds") == 75
|
||||
and all(record.get(field) == 1 for field in (
|
||||
"maximum_connections", "maximum_transfers", "maximum_executions",
|
||||
"maximum_result_receives"
|
||||
))
|
||||
)
|
||||
|
||||
|
||||
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:
|
||||
record = load_json(root / "manifests/retroarch/phase-1.0g-one-shot-runner.json")
|
||||
approval = load_json(root / "manifests/retroarch/phase-1.0g-one-shot-approval-template.json")
|
||||
phase10f = load_json(root / "manifests/retroarch/phase-1.0f-startup-interval.json")
|
||||
except (OSError, ValueError, json.JSONDecodeError) as error:
|
||||
return errors + [str(error)]
|
||||
|
||||
if record.get("phase") != PHASE or record.get("status") != STATUS:
|
||||
errors.append("phase/status mismatch")
|
||||
if record.get("runner_commit") != RUNNER_COMMIT:
|
||||
errors.append("runner commit mismatch")
|
||||
if record.get("target_source_commit") != TARGET_SOURCE_COMMIT:
|
||||
errors.append("target source commit mismatch")
|
||||
if not all_false(record.get("authorizations", {}), AUTHORIZATION_FIELDS):
|
||||
errors.append("all Phase-1.0G authorizations must remain false")
|
||||
if not all_false(record.get("phase_actions", {}), ACTION_FIELDS):
|
||||
errors.append("Phase-1.0G must record no device action")
|
||||
if not artifact_is_inactive(record.get("artifact", {}), phase10f):
|
||||
errors.append("artifact identity changed or became eligible")
|
||||
if not protocol_is_exact(record.get("result_protocol", {})):
|
||||
errors.append("result protocol mismatch")
|
||||
if not runner_is_inactive_and_bounded(record.get("runner", {})):
|
||||
errors.append("runner is active or lacks one-shot guardrails")
|
||||
if not approval_template_is_inactive(approval):
|
||||
errors.append("tracked approval template grants authority or is incomplete")
|
||||
activation = record.get("activation_requirements", {})
|
||||
if activation.get("current_requirements_satisfied") is not False:
|
||||
errors.append("activation requirements are incorrectly satisfied")
|
||||
if record.get("tests", {}).get("hardware_evidence_from_phase10g") is not False:
|
||||
errors.append("host tests are mislabeled as hardware evidence")
|
||||
|
||||
tracked = git(root, "ls-files").splitlines()
|
||||
if any(path.lower().endswith((".elf", ".self", ".sprx", ".pkg", ".map")) for path in tracked):
|
||||
errors.append("target artifact or linker map is tracked")
|
||||
if any("phase10g" in path.lower() and "execution" in path.lower() for path in tracked):
|
||||
errors.append("Phase-1.0G execution package is tracked")
|
||||
sums = (root / "packaging/retroarch/phase10g/SHA256SUMS.txt").read_text(encoding="utf-8")
|
||||
if ARTIFACT_SHA256 not in sums or not all(value in sums for value in SOURCE_HASHES.values()):
|
||||
errors.append("hash-only record is incomplete")
|
||||
|
||||
if retroarch_root is not None:
|
||||
try:
|
||||
git(retroarch_root, "cat-file", "-e", f"{RUNNER_COMMIT}^{{commit}}")
|
||||
git(retroarch_root, "cat-file", "-e", f"{TARGET_SOURCE_COMMIT}^{{commit}}")
|
||||
for relative, expected in SOURCE_HASHES.items():
|
||||
content = git(retroarch_root, "show", f"{RUNNER_COMMIT}:{relative}")
|
||||
if sha256_text(content + "\n") != expected:
|
||||
errors.append(f"runner source hash mismatch: {relative}")
|
||||
runner = git(retroarch_root, "show", f"{RUNNER_COMMIT}:tools/ps5_diag_duplex.py")
|
||||
if "ONE_SHOT_ATTEMPT_CONSUMED_BEFORE_CONNECT" not in runner:
|
||||
errors.append("runner lacks pre-connect attempt receipt")
|
||||
if 'open("xb")' not in runner or "os.fsync" not in runner:
|
||||
errors.append("runner receipt is not exclusive and durable")
|
||||
if "protocol_selection" not in runner or "MANIFEST_ONLY" not in runner:
|
||||
errors.append("runner protocol is not manifest-selected")
|
||||
except RuntimeError as error:
|
||||
errors.append(str(error))
|
||||
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)
|
||||
if errors:
|
||||
for error in errors:
|
||||
print(f"ERROR: {error}")
|
||||
return 1
|
||||
print("Phase-1.0G offline one-shot runner validation passed")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user