This commit is contained in:
@@ -0,0 +1,621 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Validate the host-only Phase-0.9E-R official Y2JB correlation."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
from typing import Any
|
||||
import zipfile
|
||||
|
||||
|
||||
BASELINE = "3d3151bd12ff0f786c1e1b9af75d7174408e3d2f"
|
||||
BRANCH = "codex/chimera-gfx-phase09e-r-y2jb-correlation"
|
||||
PHASE = "PHASE_0_9E_R_OFFICIAL_Y2JB_CORRELATION"
|
||||
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"
|
||||
DENYLIST_SHA256 = "e9603b0e3792781ad5b511afb22ef61e3d4fd4c5a16bf928f8609193bcd97783"
|
||||
UPSTREAM_COMMIT = "0dbbf4e7e0203af7e5d101a3256c634edf4e3ba2"
|
||||
UPSTREAM_TREE = "c4344f43af7c268337437e6419548dba6f6bc211"
|
||||
UPSTREAM_REMOTE = "https://github.com/Gezine/Y2JB.git"
|
||||
PORT_9020_CLASSIFICATION = "PORT_9020_REFERENCE_ONLY"
|
||||
FINAL_CLASSIFICATION = "LOCAL_BACKUP_NOT_CORRELATED"
|
||||
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",
|
||||
)
|
||||
|
||||
AUTHORIZATION_FIELDS = (
|
||||
"device_action_authorized",
|
||||
"target_build_authorized",
|
||||
"transfer_authorized",
|
||||
"execution_authorized",
|
||||
"installation_authorized",
|
||||
"lifecycle_authorized",
|
||||
"autoload_authorized",
|
||||
"device_write_authorized",
|
||||
"automatic_retry",
|
||||
)
|
||||
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",
|
||||
"manifests/runtime/phase-0.9e-bootstrap-provenance.json":
|
||||
"5dfa9bfe2ae751b2f0ea0e03c60c1a4471a35452389cf471456e6c54eb4601cf",
|
||||
"manifests/runtime/phase-0.9e-loader-protocol.json":
|
||||
"a7ca8b4e8072cb60ad4cd4869f6c508e8014059c8d81ad3b94a8d9e8cd0db4cb",
|
||||
}
|
||||
UPSTREAM_FILE_HASHES = {
|
||||
"README.md": (7500, "16bfdaa624b8b04f4a6a4a7d512ca8473ad2db73e80df39a974267403e34f751"),
|
||||
"payload_sender.py": (1064, "8c87920c41dbdbd66b9f36ca9509f0d6bef9170f351dd97ff831cfb98e642ec6"),
|
||||
"log_server.py": (929, "463114fd46479a7286706de13beb3f52221f36f3cbe37dc5a27bdb6104787a98"),
|
||||
"appinfo_editor.py": (2008, "c1bcb453660f597cbc9026dba76519a4929fc3e967c183deec8a1ca56912e2e8"),
|
||||
"download0/cache/splash_screen/aHR0cHM6Ly93d3cueW91dHViZS5jb20vdHY=/remotejsloader.js":
|
||||
(7132, "30cc6d1535549b2a49b47a9e0c85a3444cf84177be54398691694b7c6505f38e"),
|
||||
"download0/cache/splash_screen/aHR0cHM6Ly93d3cueW91dHViZS5jb20vdHY=/elfldr-ps5-1340.elf":
|
||||
(397000, "30478bcadb6439e1247451c4ac706b6e1385044dd0f12d6486b6d7057929453b"),
|
||||
}
|
||||
DELIVERABLES = (
|
||||
"docs/runtime/phase-0.9e-r-official-release-correlation.md",
|
||||
"docs/runtime/phase-0.9e-r-release-source-binding.md",
|
||||
"docs/runtime/phase-0.9e-r-port9020-source-audit.md",
|
||||
"docs/runtime/phase-0.9e-r-official-hostsender-audit.md",
|
||||
"docs/runtime/phase-0.9e-r-provenance-gaps.md",
|
||||
"docs/approvals/phase-0.9e-r-y2jb-deployed-use-attestation.md",
|
||||
"manifests/runtime/phase-0.9e-r-release-correlation.json",
|
||||
"manifests/runtime/phase-0.9e-r-release-correlation.schema.json",
|
||||
"manifests/runtime/phase-0.9e-r-port9020-audit.json",
|
||||
"manifests/runtime/phase-0.9e-r-port9020-audit.schema.json",
|
||||
"tools/validate_phase09er_provenance.py",
|
||||
"tests/test_phase09er_provenance.py",
|
||||
"packaging/phase09er/SHA256SUMS.txt",
|
||||
)
|
||||
FORBIDDEN_SUFFIXES = {
|
||||
".c", ".cc", ".cpp", ".s", ".asm", ".ld", ".elf", ".self", ".sprx",
|
||||
".pkg", ".bin", ".wasm",
|
||||
}
|
||||
FORBIDDEN_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 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 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 candidate_archive_path(root: Path) -> Path:
|
||||
direct = Path.home() / "Downloads" / OUTER_NAME
|
||||
if direct.is_file():
|
||||
return direct
|
||||
for ancestor in (root, *root.parents):
|
||||
if ancestor.parent.name.lower() == "users":
|
||||
return ancestor / "Downloads" / OUTER_NAME
|
||||
return direct
|
||||
|
||||
|
||||
def official_byte_match(
|
||||
*,
|
||||
official_source: bool,
|
||||
local_name: str,
|
||||
local_size: int,
|
||||
local_sha256: str,
|
||||
asset_name: str,
|
||||
asset_size: int,
|
||||
asset_sha256: str,
|
||||
) -> bool:
|
||||
"""Only exact official name, byte count, and hash establish a match."""
|
||||
return (
|
||||
official_source
|
||||
and local_name == asset_name
|
||||
and local_size == asset_size
|
||||
and local_sha256 == asset_sha256
|
||||
)
|
||||
|
||||
|
||||
def source_binding(
|
||||
*, release_associated: bool, inner_bytes_matched: bool, inner_opaque: bool
|
||||
) -> str:
|
||||
if inner_bytes_matched:
|
||||
return "REPRODUCIBLE_CONTENT_BINDING"
|
||||
if release_associated and inner_opaque:
|
||||
return "SOURCE_ONLY_ASSOCIATION"
|
||||
if release_associated:
|
||||
return "OFFICIAL_RELEASE_ASSOCIATION"
|
||||
return "NO_LOCAL_RELEASE_ASSOCIATION"
|
||||
|
||||
|
||||
def port_implementation_sufficient(classification: str) -> bool:
|
||||
return classification in {
|
||||
"PORT_9020_IMPLEMENTATION_FOUND",
|
||||
"PORT_9020_IMPLEMENTATION_PARTIAL",
|
||||
}
|
||||
|
||||
|
||||
def phase09f_design_allowed(
|
||||
*,
|
||||
correlation: str,
|
||||
release_commit_known: bool,
|
||||
upstream_clean: bool,
|
||||
port_classification: str,
|
||||
sender_identified: bool,
|
||||
attestation_available: bool,
|
||||
all_authorizations_false: bool,
|
||||
) -> bool:
|
||||
return all(
|
||||
(
|
||||
correlation == "OFFICIAL_RELEASE_BYTE_MATCH",
|
||||
release_commit_known,
|
||||
upstream_clean,
|
||||
port_implementation_sufficient(port_classification),
|
||||
sender_identified,
|
||||
attestation_available,
|
||||
all_authorizations_false,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def sender_duplex(*, sends: bool, receives: bool) -> bool:
|
||||
return sends and receives
|
||||
|
||||
|
||||
def sender_short_send_deficiency(*, uses_sendall: bool, explicit_send_loop: bool) -> bool:
|
||||
return not (uses_sendall or explicit_send_loop)
|
||||
|
||||
|
||||
def attestation_is_runtime_proof(*, attested: bool, hardware_observed: bool) -> bool:
|
||||
return attested and hardware_observed
|
||||
|
||||
|
||||
def _type_matches(value: Any, expected: str | list[str]) -> bool:
|
||||
if isinstance(expected, list):
|
||||
return any(_type_matches(value, item) for item in expected)
|
||||
checks = {
|
||||
"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),
|
||||
"null": value is None,
|
||||
}
|
||||
return checks.get(expected, True)
|
||||
|
||||
|
||||
def validate_schema_instance(
|
||||
schema: dict[str, Any], instance: Any, path: str = "$"
|
||||
) -> list[str]:
|
||||
errors: list[str] = []
|
||||
expected_type = schema.get("type")
|
||||
if expected_type is not None and not _type_matches(instance, expected_type):
|
||||
return [f"{path}: type mismatch"]
|
||||
if "const" in schema and instance != schema["const"]:
|
||||
errors.append(f"{path}: const mismatch")
|
||||
if "enum" in schema and instance not in schema["enum"]:
|
||||
errors.append(f"{path}: outside enum")
|
||||
if isinstance(instance, dict):
|
||||
for required in schema.get("required", []):
|
||||
if required not in instance:
|
||||
errors.append(f"{path}: missing {required}")
|
||||
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 isinstance(schema.get("additionalProperties"), dict):
|
||||
errors.extend(
|
||||
validate_schema_instance(
|
||||
schema["additionalProperties"], value, f"{path}.{key}"
|
||||
)
|
||||
)
|
||||
if isinstance(instance, list) and len(instance) < schema.get("minItems", 0):
|
||||
errors.append(f"{path}: too few items")
|
||||
return errors
|
||||
|
||||
|
||||
def validate_release_manifest(manifest: dict[str, Any]) -> list[str]:
|
||||
errors: list[str] = []
|
||||
expected = {
|
||||
"phase": PHASE,
|
||||
"status": "DESIGN_ONLY",
|
||||
"baseline_commit": BASELINE,
|
||||
"release_correlation": "OFFICIAL_RELEASE_NO_MATCH",
|
||||
"final_classification": FINAL_CLASSIFICATION,
|
||||
"identified_release": None,
|
||||
"identified_release_asset": None,
|
||||
"inner_content_binding": "SIECAF_OPAQUE_UNBOUND",
|
||||
"phase09f_offline_design_allowed": False,
|
||||
}
|
||||
for field, value in expected.items():
|
||||
if manifest.get(field) != value:
|
||||
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")
|
||||
local = manifest.get("local_candidate", {})
|
||||
if (
|
||||
local.get("file_name") != OUTER_NAME
|
||||
or local.get("size") != OUTER_SIZE
|
||||
or local.get("sha256") != OUTER_SHA256
|
||||
):
|
||||
errors.append("local candidate identity changed")
|
||||
inner = manifest.get("inner_archive", {})
|
||||
if (
|
||||
inner.get("size") != INNER_SIZE
|
||||
or inner.get("sha256") != INNER_SHA256
|
||||
or inner.get("classification") != "OPAQUE_UNBOUND"
|
||||
or inner.get("further_reverse_engineering_performed") is not False
|
||||
):
|
||||
errors.append("inner SIECAF boundary changed")
|
||||
assets = manifest.get("assets", [])
|
||||
if len(assets) != 7:
|
||||
errors.append("official asset inventory must contain seven assets")
|
||||
for asset in assets:
|
||||
if not re.fullmatch(r"[0-9a-f]{64}", str(asset.get("sha256", ""))):
|
||||
errors.append(f"official asset digest malformed: {asset.get('name')}")
|
||||
if asset.get("classification") != "NO_MATCH" or asset.get("downloaded"):
|
||||
errors.append(f"asset was not metadata-excluded: {asset.get('name')}")
|
||||
if official_byte_match(
|
||||
official_source=True,
|
||||
local_name=OUTER_NAME,
|
||||
local_size=OUTER_SIZE,
|
||||
local_sha256=OUTER_SHA256,
|
||||
asset_name=str(asset.get("name")),
|
||||
asset_size=int(asset.get("size", -1)),
|
||||
asset_sha256=str(asset.get("sha256")),
|
||||
):
|
||||
errors.append(f"unrecorded official byte match: {asset.get('name')}")
|
||||
tags = manifest.get("tags", [])
|
||||
if len(tags) != 5:
|
||||
errors.append("official tag inventory must contain five tags")
|
||||
for tag in tags:
|
||||
if not re.fullmatch(r"[0-9a-f]{40}", str(tag.get("commit", ""))):
|
||||
errors.append(f"tag commit is not exact: {tag.get('tag')}")
|
||||
if not re.fullmatch(r"[0-9a-f]{64}", str(tag.get("source_archive_sha256", ""))):
|
||||
errors.append(f"source archive hash missing: {tag.get('tag')}")
|
||||
if len(manifest.get("release_inventory", [])) != 6:
|
||||
errors.append("release inventory must cover versions 1.2 through 1.6")
|
||||
provenance = manifest.get("artifact_provenance", [])
|
||||
if len(provenance) < 6:
|
||||
errors.append("artifact provenance inventory is incomplete")
|
||||
for artifact in provenance:
|
||||
missing = [field for field in PROVENANCE_FIELDS if field not in artifact]
|
||||
if missing:
|
||||
errors.append(
|
||||
f"{artifact.get('logical_name')}: missing provenance fields {missing}"
|
||||
)
|
||||
if artifact.get("confidence") == "EXACT_USED":
|
||||
errors.append(
|
||||
f"{artifact.get('logical_name')}: deployed-use provenance overclaimed"
|
||||
)
|
||||
if artifact.get("logical_name") == "official_embedded_elfldr_1_6":
|
||||
if artifact.get("source_commit") is not None:
|
||||
errors.append("embedded elfldr received an invented source commit")
|
||||
worktree = manifest.get("upstream_worktree", {})
|
||||
if (
|
||||
worktree.get("commit") != UPSTREAM_COMMIT
|
||||
or worktree.get("tree") != UPSTREAM_TREE
|
||||
or worktree.get("clean") is not True
|
||||
or worktree.get("submodules") != 0
|
||||
or worktree.get("git_lfs_pointers") != 0
|
||||
):
|
||||
errors.append("official upstream worktree record changed")
|
||||
sender = manifest.get("official_host_sender", {})
|
||||
if (
|
||||
sender.get("response_read") is not False
|
||||
or sender.get("duplex") is not False
|
||||
or sender.get("deployed_use_attested") is not False
|
||||
):
|
||||
errors.append("official sender was overclaimed")
|
||||
if manifest.get("operator_attestation", {}).get("attested") is not False:
|
||||
errors.append("operator attestation must remain empty")
|
||||
actions = manifest.get("actions", {})
|
||||
expected_false_actions = (
|
||||
"downloaded_code_executed",
|
||||
"dependency_installed",
|
||||
"ps5_connected",
|
||||
"ps5_ip_used",
|
||||
"device_request_performed",
|
||||
"files_transferred_to_or_from_ps5",
|
||||
"target_build_performed",
|
||||
"target_code_created",
|
||||
"target_artifact_created",
|
||||
"payload_created",
|
||||
"device_client_created",
|
||||
)
|
||||
for field in expected_false_actions:
|
||||
if actions.get(field) is not False:
|
||||
errors.append(f"actions.{field} must be false")
|
||||
if (
|
||||
actions.get("large_release_assets_downloaded") != 0
|
||||
or actions.get("large_release_asset_bytes_downloaded") != 0
|
||||
):
|
||||
errors.append("large release asset download was recorded")
|
||||
if manifest.get("blocking_state", {}).get("payload_manager_backup") != (
|
||||
"HARD_BLOCKER_FOR_INSTALLATION"
|
||||
):
|
||||
errors.append("Payload Manager installation blocker changed")
|
||||
if phase09f_design_allowed(
|
||||
correlation=str(manifest.get("release_correlation")),
|
||||
release_commit_known=False,
|
||||
upstream_clean=True,
|
||||
port_classification=PORT_9020_CLASSIFICATION,
|
||||
sender_identified=True,
|
||||
attestation_available=True,
|
||||
all_authorizations_false=True,
|
||||
):
|
||||
errors.append("Phase 0.9F incorrectly passed")
|
||||
return errors
|
||||
|
||||
|
||||
def validate_port_manifest(manifest: dict[str, Any]) -> list[str]:
|
||||
errors: list[str] = []
|
||||
if manifest.get("phase") != PHASE or manifest.get("status") != "DESIGN_ONLY":
|
||||
errors.append("port manifest phase/status changed")
|
||||
if manifest.get("release_commit") != UPSTREAM_COMMIT:
|
||||
errors.append("port manifest release commit changed")
|
||||
if manifest.get("port_9020_classification") != PORT_9020_CLASSIFICATION:
|
||||
errors.append("port-9020 implementation was overclaimed")
|
||||
if (
|
||||
manifest.get("port_9020_listener_found") is not False
|
||||
or manifest.get("port_9020_parser_found") is not False
|
||||
or manifest.get("port_9020_mapping_found") is not False
|
||||
):
|
||||
errors.append("port-9020 source was invented")
|
||||
loader = manifest.get("official_remote_js_loader", {})
|
||||
if (
|
||||
loader.get("desired_dynamic_port") != 50000
|
||||
or loader.get("maximum_receive_bytes") != 512000
|
||||
or loader.get("framing") != "CONNECTION_EOF_OR_BUFFER_LIMIT"
|
||||
or loader.get("declared_length") is not False
|
||||
or loader.get("timeout") is not False
|
||||
or loader.get("native_elf_mapping") is not False
|
||||
):
|
||||
errors.append("Remote JS Loader contract changed")
|
||||
if loader.get("automatic_retry_authorized") is not False:
|
||||
errors.append("automatic retry was authorized")
|
||||
relation = manifest.get("port_9021_relation", {})
|
||||
if relation.get("listener_source_in_y2jb") is not False:
|
||||
errors.append("embedded 9021 listener source was invented")
|
||||
if relation.get("source_commit") is not None:
|
||||
errors.append("embedded elfldr received an invented source commit")
|
||||
sender = manifest.get("official_sender", {})
|
||||
if sender.get("response_read") is not False or sender.get("duplex") is not False:
|
||||
errors.append("one-way sender was classified duplex")
|
||||
for field in AUTHORIZATION_FIELDS:
|
||||
if manifest.get("authorization", {}).get(field) is not False:
|
||||
errors.append(f"port authorization.{field} must be false")
|
||||
if manifest.get("phase09f_offline_design_allowed") is not False:
|
||||
errors.append("Phase 0.9F was authorized")
|
||||
return errors
|
||||
|
||||
|
||||
def changed_paths(root: Path) -> set[str]:
|
||||
paths = set(git(root, "diff", "--name-only", BASELINE).splitlines())
|
||||
for line in git(root, "status", "--porcelain=v1", "--untracked-files=all").splitlines():
|
||||
path = line[3:]
|
||||
if " -> " in path:
|
||||
path = path.split(" -> ", 1)[1]
|
||||
paths.add(path.replace("\\", "/"))
|
||||
return {path for path in paths if path}
|
||||
|
||||
|
||||
def phase09er_path_errors(paths: set[str]) -> list[str]:
|
||||
errors: list[str] = []
|
||||
for path in paths:
|
||||
normalized = path.replace("\\", "/")
|
||||
lowered = normalized.lower()
|
||||
if normalized.startswith(FORBIDDEN_PREFIXES):
|
||||
errors.append(f"target/production path changed: {normalized}")
|
||||
if Path(normalized).suffix.lower() in FORBIDDEN_SUFFIXES:
|
||||
errors.append(f"target artifact/source added: {normalized}")
|
||||
if any(
|
||||
token in lowered
|
||||
for token in (
|
||||
"rescue.elf", "observer.elf", "readback.elf", "executionpackage",
|
||||
"installpackage", "transferpackage",
|
||||
)
|
||||
):
|
||||
errors.append(f"forbidden Phase-0.9E-R output: {normalized}")
|
||||
return errors
|
||||
|
||||
|
||||
def validate_checksum_file(root: Path) -> list[str]:
|
||||
checksum_path = root / "packaging/phase09er/SHA256SUMS.txt"
|
||||
if not checksum_path.is_file():
|
||||
return ["missing packaging/phase09er/SHA256SUMS.txt"]
|
||||
errors: list[str] = []
|
||||
for line in checksum_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, relative = parts
|
||||
path = root / relative
|
||||
if not path.is_file() or sha256_file(path) != expected:
|
||||
errors.append(f"checksum mismatch or missing: {relative}")
|
||||
return errors
|
||||
|
||||
|
||||
def collect_errors(root: Path) -> list[str]:
|
||||
errors: list[str] = []
|
||||
release_path = root / "manifests/runtime/phase-0.9e-r-release-correlation.json"
|
||||
port_path = root / "manifests/runtime/phase-0.9e-r-port9020-audit.json"
|
||||
release_schema_path = (
|
||||
root / "manifests/runtime/phase-0.9e-r-release-correlation.schema.json"
|
||||
)
|
||||
port_schema_path = (
|
||||
root / "manifests/runtime/phase-0.9e-r-port9020-audit.schema.json"
|
||||
)
|
||||
try:
|
||||
release = load_json(release_path)
|
||||
port = load_json(port_path)
|
||||
release_schema = load_json(release_schema_path)
|
||||
port_schema = load_json(port_schema_path)
|
||||
except (OSError, ValueError, json.JSONDecodeError) as error:
|
||||
return [str(error)]
|
||||
errors.extend(validate_schema_instance(release_schema, release))
|
||||
errors.extend(validate_schema_instance(port_schema, port))
|
||||
errors.extend(validate_release_manifest(release))
|
||||
errors.extend(validate_port_manifest(port))
|
||||
|
||||
for relative in DELIVERABLES:
|
||||
if not (root / relative).is_file():
|
||||
errors.append(f"missing deliverable: {relative}")
|
||||
if git(root, "rev-parse", "--abbrev-ref", "HEAD") != BRANCH:
|
||||
errors.append("current branch is not the Phase-0.9E-R branch")
|
||||
if git(root, "merge-base", BASELINE, "HEAD") != BASELINE:
|
||||
errors.append("branch no longer descends from the canonical baseline")
|
||||
if sha256_file(root / "manifests/artifact-denylist.json") != DENYLIST_SHA256:
|
||||
errors.append("permanent artifact 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}")
|
||||
|
||||
outer = candidate_archive_path(root)
|
||||
if not outer.is_file():
|
||||
errors.append(f"local Y2JB candidate missing: {outer}")
|
||||
elif outer.stat().st_size != OUTER_SIZE or sha256_file(outer) != OUTER_SHA256:
|
||||
errors.append("local Y2JB outer identity changed")
|
||||
else:
|
||||
try:
|
||||
with zipfile.ZipFile(outer) as archive:
|
||||
info = archive.getinfo(INNER_NAME)
|
||||
if (
|
||||
len(archive.infolist()) != 5
|
||||
or archive.comment != b""
|
||||
or info.file_size != INNER_SIZE
|
||||
or info.compress_size != 504158629
|
||||
or info.CRC != int("522808c8", 16)
|
||||
):
|
||||
errors.append("local ZIP metadata changed")
|
||||
digest = hashlib.sha256()
|
||||
magic = b""
|
||||
with archive.open(info) as stream:
|
||||
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
|
||||
if not magic:
|
||||
magic = chunk[:6]
|
||||
digest.update(chunk)
|
||||
if magic != b"SIECAF" or digest.hexdigest() != INNER_SHA256:
|
||||
errors.append("inner SIECAF identity changed")
|
||||
except (OSError, KeyError, zipfile.BadZipFile) as error:
|
||||
errors.append(f"local ZIP validation failed: {error}")
|
||||
|
||||
upstream = root / "work/upstream/Y2JB-official"
|
||||
if not upstream.is_dir():
|
||||
errors.append("official immutable upstream worktree missing")
|
||||
else:
|
||||
if git(upstream, "rev-parse", "HEAD") != UPSTREAM_COMMIT:
|
||||
errors.append("official upstream HEAD changed")
|
||||
if git(upstream, "rev-parse", "HEAD^{tree}") != UPSTREAM_TREE:
|
||||
errors.append("official upstream tree changed")
|
||||
if git(upstream, "status", "--porcelain=v1"):
|
||||
errors.append("official upstream worktree is dirty")
|
||||
remotes = git(upstream, "remote", "-v")
|
||||
if UPSTREAM_REMOTE not in remotes:
|
||||
errors.append("official upstream remote changed")
|
||||
if git(upstream, "submodule", "status"):
|
||||
errors.append("unexpected official upstream submodule")
|
||||
if len(git(upstream, "ls-files").splitlines()) != 19:
|
||||
errors.append("official upstream tracked file inventory changed")
|
||||
for relative, (expected_size, expected_hash) in UPSTREAM_FILE_HASHES.items():
|
||||
path = upstream / relative
|
||||
if (
|
||||
not path.is_file()
|
||||
or path.stat().st_size != expected_size
|
||||
or sha256_file(path) != expected_hash
|
||||
):
|
||||
errors.append(f"official upstream file identity changed: {relative}")
|
||||
|
||||
errors.extend(phase09er_path_errors(changed_paths(root)))
|
||||
for path in root.rglob("*"):
|
||||
if (
|
||||
path.is_file()
|
||||
and ".git" not in path.parts
|
||||
and "work" not in path.parts
|
||||
and path.stat().st_size > 50 * 1024 * 1024
|
||||
):
|
||||
errors.append(f"large release-like object present in repository: {path}")
|
||||
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-R official Y2JB provenance validation: PASS")
|
||||
print(f"Release correlation: OFFICIAL_RELEASE_NO_MATCH")
|
||||
print(f"Port 9020: {PORT_9020_CLASSIFICATION}")
|
||||
print(f"Decision: {FINAL_CLASSIFICATION}")
|
||||
print("Device actions: NONE")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user