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

465 lines
18 KiB
Python

#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-3.0-or-later
"""Validate the blocked, offline-only Phase-0.9B observer audit."""
from __future__ import annotations
import argparse
import hashlib
import json
from pathlib import Path
import re
import subprocess
import sys
from typing import Any
EXPECTED_DECISION = {
"OBSERVER_STARTUP_OR_EXIT_ABI_UNPROVEN",
"NO_PROVEN_NON_PERSISTENT_OUTPUT_CHANNEL",
}
AUTHORIZATION_FIELDS = (
"authorized",
"transfer_authorized",
"execution_authorized",
"installation_authorized",
"lifecycle_authorized",
"autoload_authorized",
"backup_creation_authorized",
"automatic_retry",
)
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",
),
}
def load_json(path: Path) -> dict[str, Any]:
with path.open("r", encoding="utf-8") as handle:
value = json.load(handle)
if not isinstance(value, dict):
raise ValueError(f"{path} must contain a JSON object")
return value
def extract_json_contract(path: Path, marker: str) -> dict[str, Any]:
text = path.read_text(encoding="utf-8")
pattern = (
rf"<!-- BEGIN {re.escape(marker)} -->\s*```json\s*(.*?)\s*```\s*"
rf"<!-- END {re.escape(marker)} -->"
)
match = re.search(pattern, text, flags=re.DOTALL)
if not match:
raise ValueError(f"{path} is missing {marker}")
value = json.loads(match.group(1))
if not isinstance(value, dict):
raise ValueError(f"{marker} must be a JSON object")
return value
def sha256_file(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def git_output(root: Path, *args: str) -> str:
return subprocess.check_output(
["git", *args], cwd=root, text=True, encoding="utf-8"
).strip()
def validate_authorizations(value: dict[str, Any], prefix: str) -> list[str]:
errors: list[str] = []
for field in AUTHORIZATION_FIELDS:
if value.get(field) is not False:
errors.append(f"{prefix}.{field} must be false")
return errors
def validate_schema(schema: dict[str, Any], manifest: dict[str, Any]) -> list[str]:
errors: list[str] = []
properties = schema.get("properties", {})
for field in AUTHORIZATION_FIELDS:
if properties.get(field, {}).get("const") is not False:
errors.append(f"schema {field} must be const false")
if properties.get("device_address", {}).get("const", "missing") is not None:
errors.append("schema device_address must be const null")
if properties.get("maximum_execution_count", {}).get("const") != 0:
errors.append("schema maximum_execution_count must be const zero")
default = schema.get("x-chimera-default-plan")
if default != manifest.get("default_observation_plan"):
errors.append("schema default plan must equal manifest default plan")
if not isinstance(default, dict):
return errors
errors.extend(validate_authorizations(default, "default_plan"))
if default.get("device_address") is not None:
errors.append("default plan contains a device address")
if default.get("device_identity") is not None:
errors.append("default plan contains a device identity")
if default.get("read_paths") != []:
errors.append("default plan contains read paths")
if default.get("allowed_observations") != []:
errors.append("default plan contains observations")
if default.get("output_channel") is not None:
errors.append("default plan contains an output channel")
if default.get("maximum_execution_count") != 0:
errors.append("default plan permits an execution")
return errors
def validate_manifest(root: Path, manifest: dict[str, Any]) -> list[str]:
errors: list[str] = []
if manifest.get("status") != "BLOCKED":
errors.append("Phase-0.9B status must be BLOCKED")
if set(manifest.get("decision", [])) != EXPECTED_DECISION:
errors.append("Phase-0.9B hard-gate decision changed")
errors.extend(
validate_authorizations(manifest.get("authorization", {}), "authorization")
)
canonical = manifest.get("canonical_state_preserved", {})
expected_canonical = {
"historical_phase08_status": "READ_ONLY_PREFLIGHT_BLOCKED",
"phase09a_status": "DESIGN_ONLY",
"firmware_runtime_behavior": "UNPROVEN",
"stock_hashes": "reference_only",
"payload_manager_backup": "HARD_BLOCKER",
"device_contact_performed": False,
"device_transfer_performed": False,
"device_execution_performed": False,
}
for field, expected in expected_canonical.items():
if canonical.get(field) != expected:
errors.append(f"canonical state {field} must be {expected!r}")
deny_binding = manifest.get("permanent_denylist_binding", {})
if deny_binding != {
"sha256": "4be1c17b4964f2b68c39b5145bc4af4619c32512d60269ecf5c39728b390fa63",
"status": "BLOCKED",
"permanent": True,
"execution_eligible": False,
}:
errors.append("permanent denylist binding changed")
commits = manifest.get("source_commits", {})
tree_status = manifest.get("source_tree_status", {})
for key, (relative, expected_commit) in SOURCE_COMMITS.items():
if commits.get(key) != expected_commit:
errors.append(f"manifest source commit mismatch: {key}")
path = (root / relative).resolve()
if not path.exists():
errors.append(f"source tree missing: {relative}")
continue
try:
actual_commit = git_output(path, "rev-parse", "HEAD")
dirty = git_output(path, "status", "--porcelain")
except (OSError, subprocess.CalledProcessError) as exc:
errors.append(f"source tree unreadable: {relative}: {exc}")
continue
if actual_commit != expected_commit:
errors.append(f"source tree commit mismatch: {relative}")
if dirty:
errors.append(f"source tree is dirty: {relative}")
if tree_status.get(key) != "clean":
errors.append(f"manifest does not classify {key} as clean")
for evidence in manifest.get("source_evidence", []):
relative = evidence.get("path")
expected_digest = evidence.get("sha256")
if not isinstance(relative, str) or not isinstance(expected_digest, str):
errors.append("source evidence entry lacks path or SHA-256")
continue
path = (root / relative).resolve()
if not path.is_file():
errors.append(f"source evidence file missing: {relative}")
continue
if sha256_file(path) != expected_digest:
errors.append(f"source evidence digest mismatch: {relative}")
if "size" in evidence and path.stat().st_size != evidence["size"]:
errors.append(f"source evidence size mismatch: {relative}")
matrix = manifest.get("capability_matrix", [])
expected_facts = {
"firmware_source_1",
"firmware_source_2",
"file_metadata",
"object_identity",
"sha256",
"mount_information",
"processes_services",
"listeners",
"autoload_configuration",
"output_channel",
"monotonic_time_deadline",
"process_exit",
}
if {entry.get("needed_fact") for entry in matrix} != expected_facts:
errors.append("capability matrix is incomplete or changed")
if any(entry.get("implement") is not False for entry in matrix):
errors.append("a blocked capability is marked for implementation")
gate = manifest.get("build_gate", {})
for field in (
"startup_and_exit_abi_proven",
"non_persistent_output_channel_proven",
"normal_sdk_crt_kernelwrite_free",
"custom_freestanding_cleanup_proven",
"observer_source_created",
"observer_target_declared",
"target_build_performed",
"double_clean_build_performed",
):
if gate.get(field) is not False:
errors.append(f"build gate {field} must be false")
if gate.get("reason") != "BLOCKED_BEFORE_SOURCE_AND_BUILD":
errors.append("build gate reason changed")
implementation = manifest.get("implementation", {})
if implementation.get("observer_logic_implemented") is not False:
errors.append("observer logic must remain unimplemented")
if implementation.get("observations_implemented") != []:
errors.append("target observations must remain unimplemented")
artifact = manifest.get("artifact", {})
if artifact.get("present") is not False:
errors.append("observer artifact must be absent")
for field in ("path", "sha256", "size"):
if artifact.get(field) is not None:
errors.append(f"artifact {field} must be null")
for field in (
"imports",
"undefined_symbols",
"dynamic_dependencies",
"network_functions",
"filesystem_reads",
):
if artifact.get(field) != []:
errors.append(f"artifact {field} must be empty")
for field in (
"installation_eligible",
"lifecycle_eligible",
"autoload_eligible",
"execution_authorized",
"execution_eligible",
):
if artifact.get(field) is not False:
errors.append(f"artifact {field} must be false")
if (
manifest.get("static_artifact_audit", {}).get("status")
!= "NOT_PERFORMED_BLOCKED_BEFORE_BUILD"
):
errors.append("artifact audit must be recorded as not performed")
reproducibility = manifest.get("reproducibility", {})
if reproducibility.get("status") != "NOT_PERFORMED_BLOCKED_BEFORE_BUILD":
errors.append("reproducibility must be recorded as not performed")
for field in ("build_1_sha256", "build_2_sha256", "byte_identical"):
if reproducibility.get(field) is not None:
errors.append(f"reproducibility {field} must be null")
return errors
def validate_denylist(root: Path) -> list[str]:
denylist = load_json(root / "manifests/artifact-denylist.json")
for entry in denylist.get("entries", []):
if (
entry.get("sha256")
== "4be1c17b4964f2b68c39b5145bc4af4619c32512d60269ecf5c39728b390fa63"
and entry.get("status") == "BLOCKED"
and entry.get("permanent") is True
and entry.get("execution_eligible") is False
):
return []
return ["permanent blocked artifact is missing from the denylist"]
def validate_repository_boundary(root: Path) -> list[str]:
errors: list[str] = []
cmake = (root / "CMakeLists.txt").read_text(encoding="utf-8")
forbidden_cmake = (
"CHIMERA_GFX_BUILD_PS5_OBSERVER",
"chimera-gfx-bounded-observer",
"phase09b-observer.elf",
)
for value in forbidden_cmake:
if value in cmake:
errors.append(f"blocked observer target found in CMake: {value}")
forbidden_paths = (
root / "samples/phase09b_observer",
root / "samples/bounded_observer",
root / "src/observer",
root / "packaging/phase09b/observer.elf",
)
for path in forbidden_paths:
if path.exists():
errors.append(f"blocked observer source/artifact path exists: {path}")
for base in (root / "samples", root / "src", root / "packaging"):
for path in base.rglob("*"):
if not path.is_file():
continue
lowered = path.as_posix().lower()
if path.suffix.lower() in {".c", ".cc", ".cpp", ".s", ".asm"} and (
"phase09b" in lowered or "bounded_observer" in lowered
):
errors.append(f"blocked observer target source exists: {path}")
if path.suffix.lower() in {".elf", ".self", ".sprx", ".pkg", ".map"} and (
"phase09b" in lowered or "observer" in lowered
):
errors.append(f"blocked observer target artifact exists: {path}")
try:
tracked = git_output(root, "ls-files").splitlines()
except (OSError, subprocess.CalledProcessError) as exc:
return [f"could not inspect tracked files: {exc}"]
for relative in tracked:
lowered = relative.lower()
if "phase09b" in lowered or "phase-0.9b" in lowered:
if lowered.endswith((".elf", ".self", ".sprx", ".pkg", ".map")):
errors.append(f"tracked Phase-0.9B target artifact exists: {relative}")
if "phase09b" in lowered and (
"install" in lowered or "lifecycle-package" in lowered
):
errors.append(f"Phase-0.9B install/lifecycle package exists: {relative}")
return errors
def validate_review_checksums(root: Path) -> list[str]:
errors: list[str] = []
expected_paths = {
"docs/approvals/phase-0.9b-observer-execution-template.md",
"docs/runtime/phase-0.9b-bounded-observer-design.md",
"docs/runtime/phase-0.9b-observer-limitations.md",
"docs/runtime/phase-0.9b-observer-result-contract.md",
"docs/runtime/phase-0.9b-observer-static-audit.md",
"manifests/runtime/phase-0.9b-observation-plan.schema.json",
"manifests/runtime/phase-0.9b-observer.json",
"tests/phase09b_observer_model.py",
"tests/test_phase09b_observer_audit.py",
"tools/validate_phase09b_observer_audit.py",
}
checksum_path = root / "packaging/phase09b/SHA256SUMS.txt"
observed: set[str] = set()
for line_number, line in enumerate(
checksum_path.read_text(encoding="utf-8").splitlines(), start=1
):
match = re.fullmatch(r"([0-9a-f]{64}) ([^\r\n]+)", line)
if not match:
errors.append(f"invalid checksum line {line_number}")
continue
expected_digest, relative = match.groups()
if relative in observed:
errors.append(f"duplicate checksum path: {relative}")
continue
observed.add(relative)
path = root / relative
if not path.is_file():
errors.append(f"checksummed file missing: {relative}")
elif sha256_file(path) != expected_digest:
errors.append(f"review checksum mismatch: {relative}")
if observed != expected_paths:
errors.append("Phase-0.9B review checksum inventory is incomplete or expanded")
return errors
def collect_errors(root: Path, require_source_trees: bool = True) -> list[str]:
errors: list[str] = []
manifest_path = root / "manifests/runtime/phase-0.9b-observer.json"
schema_path = root / "manifests/runtime/phase-0.9b-observation-plan.schema.json"
required = [
manifest_path,
schema_path,
root / "docs/runtime/phase-0.9b-bounded-observer-design.md",
root / "docs/runtime/phase-0.9b-observer-static-audit.md",
root / "docs/runtime/phase-0.9b-observer-result-contract.md",
root / "docs/runtime/phase-0.9b-observer-limitations.md",
root / "docs/approvals/phase-0.9b-observer-execution-template.md",
root / "tests/phase09b_observer_model.py",
root / "tests/test_phase09b_observer_audit.py",
root / "packaging/phase09b/SHA256SUMS.txt",
]
for path in required:
if not path.is_file():
errors.append(f"required Phase-0.9B file missing: {path}")
if errors:
return errors
manifest = load_json(manifest_path)
schema = load_json(schema_path)
errors.extend(validate_manifest(root, manifest))
if not require_source_trees:
errors = [
error
for error in errors
if not error.startswith(("source tree missing:", "source tree unreadable:"))
]
errors.extend(validate_schema(schema, manifest))
errors.extend(validate_denylist(root))
errors.extend(validate_repository_boundary(root))
errors.extend(validate_review_checksums(root))
template = extract_json_contract(
root / "docs/approvals/phase-0.9b-observer-execution-template.md",
"PHASE09B_OBSERVER_EXECUTION_TEMPLATE",
)
errors.extend(validate_authorizations(template, "execution_template"))
if template.get("status") != "BLOCKED":
errors.append("execution template must remain BLOCKED")
required_fields = template.get("required_fields", {})
if any(value is not None for value in required_fields.values()):
errors.append("execution template contains prefilled request-specific values")
for relative in (
"docs/runtime/phase-0.9b-bounded-observer-design.md",
"docs/runtime/phase-0.9b-observer-limitations.md",
):
text = (root / relative).read_text(encoding="utf-8")
normalized_text = re.sub(r"[^A-Z0-9]+", " ", text.upper()).strip()
for decision in EXPECTED_DECISION:
if decision.replace("_", " ") not in normalized_text:
errors.append(f"{relative} does not state blocker {decision}")
return errors
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--root", type=Path, required=True)
parser.add_argument(
"--no-source-tree-check",
action="store_true",
help="Skip only missing external source-tree errors for packaged review.",
)
args = parser.parse_args()
root = args.root.resolve()
errors = collect_errors(root, require_source_trees=not args.no_source_tree_check)
if errors:
for error in errors:
print(f"ERROR: {error}", file=sys.stderr)
return 1
print("Phase-0.9B blocked observer audit: PASS")
return 0
if __name__ == "__main__":
raise SystemExit(main())