Files
chimera-gfx-Public/tools/validate_phase09e_bootstrap.py
T
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

799 lines
29 KiB
Python

#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-3.0-or-later
"""Validate the offline-only Phase-0.9E bootstrap provenance audit."""
from __future__ import annotations
import argparse
import hashlib
import json
from pathlib import Path
import re
import subprocess
from typing import Any
import zipfile
EXPECTED_BASELINE = "e5304e394efb0790e1939bef87404f19b58caa43"
EXPECTED_BRANCH = "codex/chimera-gfx-phase09e-bootstrap-rescue-provenance"
EXPECTED_PHASE = "PHASE_0_9E_BOOTSTRAP_RESCUE_PROVENANCE"
EXPECTED_STATUS = "DESIGN_ONLY"
EXPECTED_CLASSIFICATION = "BOOTSTRAP_IMPLEMENTATION_MISSING"
BLOCKED_HASH = (
"4be1c17b4964f2b68c39b5145bc4af4619c32512d60269ecf5c39728b390fa63"
)
DENYLIST_HASH = (
"e9603b0e3792781ad5b511afb22ef61e3d4fd4c5a16bf928f8609193bcd97783"
)
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"
)
ZIP_ENTRIES = (
"PS5/",
"PS5/EXPORT/",
"PS5/EXPORT/BACKUP/",
"PS5/EXPORT/BACKUP/202606102126_00/",
INNER_NAME,
)
AUTHORIZATION_FIELDS = (
"device_action_authorized",
"execution_authorized",
"installation_authorized",
"lifecycle_authorized",
"autoload_authorized",
"device_write_authorized",
"transfer_authorized",
"automatic_retry",
"target_build_authorized",
"rescue_payload_design_authorized",
)
ACTION_FIELDS = (
"internet_used",
"fetch_performed",
"clone_performed",
"package_installed",
"ps5_connected",
"device_request_performed",
"files_transferred",
"device_write_performed",
"target_execution_performed",
"target_build_performed",
"target_artifact_created",
"rescue_payload_created",
"readback_payload_created",
"device_client_created",
"backup_created",
"staging_performed",
)
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",
)
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/elfldr-ps5.elf": (
397000,
"63e810982471eb40cae3a20aa9df9a0a02892f420e429874fae4e99aa400b561",
),
"../chimera-elfldr/elfldr-ps5.elf.map": (
142905,
"2ea5ff73299db6e61d5fd998c358ee910bc4e8fa1e6e7c73dbaa842bc813124f",
),
"../chimera-elfldr/bootstrap.elf.map": (
143081,
"ec4ae17abeb8270948d8a7d4196593ba94cbdb69376b8b9f7f009b25ab5b3323",
),
"../chimera-elfldr/socksrv.elf.map": (
145072,
"bf74db6ee68a60652426936e7037bb5a143078f44cc7774fa04c9bc4bc5447c1",
),
"../chimera-elfldr/Makefile": (
1902,
"3df93b48fc61ec67907b136e44ea2085baacf08347864d32befc072a7a783384",
),
"../chimera-elfldr/README.md": (
2821,
"372aeb28dc971b2bd98093a47fdaf77c32f75bbdc3b3d7e8678900744b91eadb",
),
"../chimera-elfldr/main.c": (
2473,
"876389a26999073994e63ca29926982280d9594a1ee941244205b54f57e2b4d1",
),
"../chimera-elfldr/bootstrap.c": (
2081,
"5a8072ec0d6db919cb3a81a7028dc91fd8e2c3a0d69b0fa7e84836fa93b45381",
),
"../chimera-elfldr/socksrv.c": (
11556,
"d642ced3e9b4a296dd15e355050ebe956f53a6dfdaa6ac10109cd067a3bba3d7",
),
"work/upstream/release-assets/elfldr-ps5-v0.23.elf": (
397000,
"092d16ee0ede0c494947efd38d1a17bbd7cc4b022d3858ea898833c188c703e8",
),
"../chimera-ps5-payload-manager/pldmgr-controlled.elf": (
99560,
"8fecf8241a46246eddbd21e8bb4d875f5d76f1f4f4c6a11384df1f131aa5e5b1",
),
}
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"
),
}
DELIVERABLES = (
"docs/runtime/phase-0.9e-bootstrap-provenance.md",
"docs/runtime/phase-0.9e-independent-rescue-chain.md",
"docs/runtime/phase-0.9e-loader-9020-protocol.md",
"docs/runtime/phase-0.9e-reboot-and-crash-model.md",
"docs/runtime/phase-0.9e-future-rescue-payload-contract.md",
"docs/runtime/phase-0.9e-output-architecture-options.md",
"manifests/runtime/phase-0.9e-bootstrap-provenance.json",
"manifests/runtime/phase-0.9e-bootstrap-provenance.schema.json",
"manifests/runtime/phase-0.9e-loader-protocol.json",
"manifests/runtime/phase-0.9e-loader-protocol.schema.json",
"tools/validate_phase09e_bootstrap.py",
"tests/test_phase09e_bootstrap.py",
"packaging/phase09e/SHA256SUMS.txt",
)
FORBIDDEN_NEW_SUFFIXES = {
".c",
".cc",
".cpp",
".s",
".asm",
".ld",
".elf",
".self",
".sprx",
".pkg",
".bin",
}
FORBIDDEN_PRODUCTION_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 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 candidate_archive_path(root: Path) -> Path:
"""Resolve the same Windows user Downloads path from Windows or WSL."""
windows_home_candidate = Path.home() / "Downloads" / OUTER_NAME
if windows_home_candidate.is_file():
return windows_home_candidate
for ancestor in (root, *root.parents):
if ancestor.parent.name.lower() == "users":
return ancestor / "Downloads" / OUTER_NAME
return windows_home_candidate
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 provenance_errors(artifact: dict[str, Any]) -> list[str]:
errors: list[str] = []
for field in PROVENANCE_FIELDS:
if field not in artifact:
errors.append(f"missing provenance field {field}")
confidence = artifact.get("confidence")
if confidence not in {
"EXACT_USED",
"STRONG_MATCH",
"POSSIBLE",
"REFERENCE_ONLY",
"UNKNOWN",
}:
errors.append("invalid confidence")
obtained = str(artifact.get("obtained_from", "")).lower()
repository = str(artifact.get("source_repository") or "").lower()
if (
"public" in obtained or "upstream" in obtained or "upstream" in repository
) and confidence == "EXACT_USED":
errors.append("public upstream cannot be exact-used without provenance")
if (
artifact.get("source_repository") is None
and artifact.get("build_identity") is None
and artifact.get("source_commit") is not None
):
errors.append("opaque binary cannot carry an invented source commit")
if not re.fullmatch(r"[0-9a-f]{64}", str(artifact.get("sha256", ""))):
errors.append("invalid SHA-256")
if not isinstance(artifact.get("size"), int) or artifact.get("size", -1) < 0:
errors.append("invalid artifact size")
return errors
def rescue_classification(
*,
actual_package_available: bool,
requires_elfldr: bool = False,
requires_payload_manager: bool = False,
replaces_live_component: bool = False,
all_required_properties_proven: bool = False,
) -> str:
if not actual_package_available:
return "BOOTSTRAP_IMPLEMENTATION_MISSING"
if requires_elfldr or requires_payload_manager:
return "SELF_OR_CROSS_DEPENDENT"
if replaces_live_component:
return "NO_INDEPENDENT_RESCUE_PATH"
if all_required_properties_proven:
return "INDEPENDENT_RESCUE_EXECUTOR_CANDIDATE"
return "PARTIAL_RESCUE_EXECUTOR"
def host_to_memory_classification(
*,
receive_code: bool,
mapping_code: bool,
entrypoint_code: bool,
device_file_only: bool = False,
) -> str:
if device_file_only:
return "DEVICE_FILE_ONLY"
if receive_code and mapping_code and entrypoint_code:
return "PROVEN_FROM_SOURCE"
if receive_code or mapping_code or entrypoint_code:
return "PARTIAL"
return "UNKNOWN"
def protocol_model_complete(protocol: dict[str, Any]) -> bool:
required = (
"maximum_payload_size",
"headers",
"length_fields",
"short_read_detection",
"short_write_detection",
"bounds_checks",
)
return all(protocol.get(field) not in {None, "", "UNKNOWN"} for field in required)
def risk_classification(
*,
temporary_socket: bool = False,
live_filesystem_write: bool = False,
autoload_activation: bool = False,
) -> str:
if live_filesystem_write or autoload_activation:
return "BRICK_RELEVANT"
if temporary_socket:
return "LOW_TECHNICAL"
return "UNKNOWN"
def reboot_classification(
*,
exact_package: bool,
source_design_restartable: bool,
hardware_observed: bool,
) -> str:
if exact_package and source_design_restartable and hardware_observed:
return "REBOOT_RECOVERY_SUPPORTED_BY_DESIGN"
if exact_package and source_design_restartable:
return "REBOOT_RECOVERY_PLAUSIBLE"
return "REBOOT_RECOVERY_UNPROVEN"
def phase09f_design_allowed(
*,
actual_package_available: bool,
independent_from_elfldr: bool,
independent_from_payload_manager: bool,
no_live_replacement: bool,
) -> bool:
return all(
(
actual_package_available,
independent_from_elfldr,
independent_from_payload_manager,
no_live_replacement,
)
)
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 phase."""
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")
pattern = schema.get("pattern")
if pattern and isinstance(instance, str) and not re.fullmatch(pattern, instance):
errors.append(f"{path}: pattern mismatch")
if isinstance(instance, dict):
for key in schema.get("required", []):
if key not in instance:
errors.append(f"{path}: missing {key}")
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 "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] = []
expected_top = {
"phase": EXPECTED_PHASE,
"status": EXPECTED_STATUS,
"baseline_commit": EXPECTED_BASELINE,
"branch": EXPECTED_BRANCH,
}
for field, expected in expected_top.items():
if manifest.get(field) != expected:
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")
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 = {
"phase09d_decision": "BLOCKED_NO_READBACK_PATH",
"phase09c_new_observer": "BLOCKED_MULTIPLE_FOUNDATIONAL_CONTRACTS",
"payload_manager_backup": "HARD_BLOCKER_FOR_INSTALLATION",
"independent_recovery": "UNPROVEN",
"firmware_runtime_behavior": "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")
decisions = manifest.get("decisions", {})
expected_decisions = {
"actual_bootstrap_package_available": False,
"actual_bootstrap_identity": None,
"bootstrap_provenance": "POSSIBLE",
"independent_from_elfldr": "unproven",
"independent_from_payload_manager": "unproven",
"restartable_after_reboot": "unproven",
"host_to_memory": "UNKNOWN",
"output_channel_for_future_rescue": "UNKNOWN",
"independent_rescue_classification": EXPECTED_CLASSIFICATION,
"phase09f_rescue_payload_design_allowed": False,
"device_action_authorized": False,
"execution_authorized": False,
"installation_authorized": False,
"automatic_retry": False,
}
if decisions != expected_decisions:
errors.append("decision matrix changed")
if rescue_classification(actual_package_available=False) != (
EXPECTED_CLASSIFICATION
):
errors.append("missing implementation is misclassified")
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 not recorded clean: {name}")
audit = manifest.get("candidate_archive_audit", {})
expected_audit = {
"outer_size": OUTER_SIZE,
"outer_sha256": OUTER_SHA256,
"zip_entry_count": len(ZIP_ENTRIES),
"inner_path": INNER_NAME,
"inner_size": INNER_SIZE,
"inner_sha256": INNER_SHA256,
"inner_magic_ascii": "SIECAF",
"acceptable_local_parser_present": False,
"decryption_attempted": False,
"source_identity_proven": False,
"deployed_use_proven": False,
"classification": "POSSIBLE",
}
for field, expected in expected_audit.items():
if audit.get(field) != expected:
errors.append(f"candidate_archive_audit.{field} changed")
artifacts = manifest.get("artifacts", [])
if len(artifacts) != 13:
errors.append("artifact inventory must contain 13 records")
seen: set[str] = set()
for artifact in artifacts:
name = str(artifact.get("logical_name"))
if name in seen:
errors.append(f"duplicate artifact: {name}")
seen.add(name)
errors.extend(f"{name}: {error}" for error in provenance_errors(artifact))
if artifact.get("confidence") == "EXACT_USED":
errors.append(f"{name}: no exact-used artifact is proven")
if manifest.get("exact_used_artifacts") != []:
errors.append("exact_used_artifacts must remain empty")
host_memory = manifest.get("host_to_memory_analysis", {})
if (
host_memory.get("classification") != "UNKNOWN"
or host_memory.get("receive_code") != "MISSING"
or host_memory.get("mapping_code") != "MISSING"
or host_memory.get("entrypoint_selection") != "UNKNOWN"
):
errors.append("host-to-memory is overclaimed")
reboot = manifest.get("reboot_and_crash", {})
if (
reboot.get("classification") != "REBOOT_RECOVERY_UNPROVEN"
or reboot.get("automatic_retry") is not False
):
errors.append("reboot recovery is overclaimed or retry enabled")
finding_classes = {
item.get("id"): item.get("classification")
for item in manifest.get("security_findings", [])
}
if finding_classes.get("unknown_live_filesystem_effect") != "BRICK_RELEVANT":
errors.append("unknown live filesystem effect is not brick relevant")
if finding_classes.get("unknown_autoload_effect") != "BRICK_RELEVANT":
errors.append("unknown autoload effect is not brick relevant")
if finding_classes.get(
"temporary_socket_not_automatically_brick_relevant"
) == "BRICK_RELEVANT":
errors.append("temporary socket is automatically overclassified")
contract = manifest.get("future_rescue_contract", {})
if (
contract.get("requirements_total") != 20
or contract.get("bootstrap_supported_now") != []
or contract.get("target_source_allowed") is not False
):
errors.append("future rescue contract is not fully blocked")
if len(manifest.get("output_options", [])) != 5:
errors.append("output architecture options are incomplete")
if len(manifest.get("missing_actual_files", [])) < 5:
errors.append("missing actual bootstrap inputs are incomplete")
final = manifest.get("final_decision", {})
if final.get("classification") != EXPECTED_CLASSIFICATION:
errors.append("final decision changed")
return errors
def validate_protocol(protocol_manifest: dict[str, Any]) -> list[str]:
errors: list[str] = []
expected = {
"phase": "PHASE_0_9E_LOADER_9020_PROTOCOL",
"status": "UNPROVEN_IMPLEMENTATION_MISSING",
"exact_server_source_present": False,
"exact_server_binary_present": False,
"exact_host_client_present": False,
"protocol_identity": None,
"classification": "CONCEPTUAL_9020_DESCRIPTION_IS_NOT_PROTOCOL_PROOF",
}
for field, value in expected.items():
if protocol_manifest.get(field) != value:
errors.append(f"protocol {field} changed")
protocol = protocol_manifest.get("protocol", {})
if protocol.get("port") != 9020:
errors.append("protocol port changed")
if protocol_model_complete(protocol):
errors.append("missing implementation cannot yield a complete protocol")
for field in (
"handshake",
"length_fields",
"maximum_payload_size",
"timeout",
"retry",
"response_direction",
"parser",
"mappings",
"entrypoint_validation",
"filesystem_staging",
"cleanup",
):
if protocol.get(field) not in {"UNKNOWN", "MISSING"}:
errors.append(f"protocol.{field} is overclaimed")
host_model = protocol_manifest.get("host_model", {})
for field in (
"allowed",
"created",
"network_port_opened",
"device_connection_performed",
"payload_sent",
"binary_executed",
):
if host_model.get(field) is not False:
errors.append(f"host_model.{field} must be false")
return errors
def _changed_paths(root: Path) -> set[str]:
paths = {
path
for path in git(root, "diff", "--name-only", EXPECTED_BASELINE).splitlines()
if path
}
status = git(root, "status", "--porcelain=v1", "--untracked-files=all")
for line in status.splitlines():
if not line:
continue
path = line[3:]
if " -> " in path:
path = path.split(" -> ", 1)[1]
paths.add(path.replace("\\", "/"))
return paths
def phase09e_path_errors(paths: set[str]) -> list[str]:
errors: list[str] = []
for path in paths:
normalized = path.replace("\\", "/")
suffix = Path(normalized).suffix.lower()
if normalized.startswith(FORBIDDEN_PRODUCTION_PREFIXES):
errors.append(f"production/target path changed: {normalized}")
if suffix in FORBIDDEN_NEW_SUFFIXES:
errors.append(f"forbidden target artifact/source added: {normalized}")
lowered = normalized.lower()
if any(
token in lowered
for token in ("rescue.elf", "readback.elf", "executionpackage", "installpackage")
):
errors.append(f"forbidden Phase-0.9E output added: {normalized}")
if "tests/host/phase09e_protocol_model.py" in paths:
errors.append("protocol model created without exact protocol source")
return errors
def _validate_checksum_file(root: Path) -> list[str]:
path = root / "packaging/phase09e/SHA256SUMS.txt"
if not path.is_file():
return ["missing packaging/phase09e/SHA256SUMS.txt"]
errors: list[str] = []
for line in 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, label = parts
if label == f"~/Downloads/{OUTER_NAME}":
actual = sha256_file(candidate_archive_path(root))
elif label == f"~/Downloads/{OUTER_NAME}::{INNER_NAME}":
with zipfile.ZipFile(candidate_archive_path(root)) as archive:
digest = hashlib.sha256()
with archive.open(INNER_NAME) as stream:
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(chunk)
actual = digest.hexdigest()
else:
candidate = root / label
if not candidate.is_file():
errors.append(f"checksum target missing: {label}")
continue
actual = sha256_file(candidate)
if actual != expected:
errors.append(f"checksum mismatch: {label}")
return errors
def collect_errors(root: Path) -> list[str]:
errors: list[str] = []
manifest_path = (
root / "manifests/runtime/phase-0.9e-bootstrap-provenance.json"
)
protocol_path = root / "manifests/runtime/phase-0.9e-loader-protocol.json"
schema_path = (
root / "manifests/runtime/phase-0.9e-bootstrap-provenance.schema.json"
)
protocol_schema_path = (
root / "manifests/runtime/phase-0.9e-loader-protocol.schema.json"
)
try:
manifest = load_json(manifest_path)
protocol_manifest = load_json(protocol_path)
schema = load_json(schema_path)
protocol_schema = load_json(protocol_schema_path)
except (OSError, ValueError, json.JSONDecodeError) as error:
return [str(error)]
errors.extend(validate_schema_instance(schema, manifest))
errors.extend(validate_schema_instance(protocol_schema, protocol_manifest))
errors.extend(validate_manifest(manifest))
errors.extend(validate_protocol(protocol_manifest))
for relative in DELIVERABLES:
if not (root / relative).is_file():
errors.append(f"missing deliverable: {relative}")
if git(root, "rev-parse", "--abbrev-ref", "HEAD") != EXPECTED_BRANCH:
errors.append("current branch is not the Phase-0.9E branch")
if git(root, "merge-base", EXPECTED_BASELINE, "HEAD") != EXPECTED_BASELINE:
errors.append("Phase-0.9E branch no longer descends from baseline")
if sha256_file(root / "manifests/artifact-denylist.json") != DENYLIST_HASH:
errors.append("permanent 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}")
for name, (relative, expected) in SOURCE_COMMITS.items():
source_root = (root / relative).resolve()
if git(source_root, "rev-parse", "HEAD") != expected:
errors.append(f"source HEAD changed: {name}")
if git(source_root, "status", "--porcelain"):
errors.append(f"source tree dirty: {name}")
for relative, (expected_size, expected_hash) in SOURCE_HASHES.items():
path = (root / relative).resolve()
if not path.is_file():
errors.append(f"source artifact missing: {relative}")
continue
if path.stat().st_size != expected_size or sha256_file(path) != expected_hash:
errors.append(f"source artifact identity changed: {relative}")
outer = candidate_archive_path(root)
if not outer.is_file():
errors.append(f"candidate archive missing: {outer}")
else:
if outer.stat().st_size != OUTER_SIZE or sha256_file(outer) != OUTER_SHA256:
errors.append("candidate outer archive identity changed")
try:
with zipfile.ZipFile(outer) as archive:
if tuple(item.filename for item in archive.infolist()) != ZIP_ENTRIES:
errors.append("candidate ZIP inventory changed")
info = archive.getinfo(INNER_NAME)
if info.file_size != INNER_SIZE:
errors.append("candidate inner size changed")
digest = hashlib.sha256()
magic = b""
with archive.open(INNER_NAME) as stream:
while True:
chunk = stream.read(1024 * 1024)
if not chunk:
break
if not magic:
magic = chunk[:6]
digest.update(chunk)
if magic != b"SIECAF" or digest.hexdigest() != INNER_SHA256:
errors.append("candidate inner identity changed")
except (OSError, KeyError, zipfile.BadZipFile) as error:
errors.append(f"candidate ZIP audit failed: {error}")
errors.extend(phase09e_path_errors(_changed_paths(root)))
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 bootstrap provenance validation: PASS")
print(f"Decision: {EXPECTED_CLASSIFICATION}")
print("Device actions: NONE")
return 0
if __name__ == "__main__":
raise SystemExit(main())