This commit is contained in:
@@ -0,0 +1,618 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Validate the offline, fail-closed Phase-0.8R evidence contract."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
STATUS = "READ_ONLY_PREFLIGHT_BLOCKED"
|
||||
SOURCE_COMMIT = "2c944d6d65a08f7e1c02f518721cde061b999329"
|
||||
PAYLOAD_MANAGER_COMMIT = "cfbc70f30f419b09bf2b52283f7409e2d3117ee1"
|
||||
DENYLIST_SHA256 = (
|
||||
"4be1c17b4964f2b68c39b5145bc4af4619c32512d60269ecf5c39728b390fa63"
|
||||
)
|
||||
STOCK_ELFLDR_SHA256 = (
|
||||
"092d16ee0ede0c494947efd38d1a17bbd7cc4b022d3858ea898833c188c703e8"
|
||||
)
|
||||
STOCK_PAYLOAD_MANAGER_SHA256 = (
|
||||
"518740adbacccb9094fadb07dd424c53ee290f38306449ccc9d6957fdf813c0b"
|
||||
)
|
||||
|
||||
IMMUTABLE_EVIDENCE = {
|
||||
"docs/runtime/phase-0.8-read-only-preflight.md": {
|
||||
"role": "historical_human_readable_preflight_record",
|
||||
"sha256": "3fbe086175a6048176075f447ec1482074928e3b5282db97ea2169395fe1d508",
|
||||
},
|
||||
"manifests/runtime/phase-0.8-read-only-preflight.json": {
|
||||
"role": "historical_machine_readable_preflight_record",
|
||||
"sha256": "47d7f452f8799979fe99b3e6d56859f03544112725bf7e5b349eba5ed81b3322",
|
||||
},
|
||||
"tests/test_phase08_preflight.py": {
|
||||
"role": "historical_fail_closed_regression_test",
|
||||
"sha256": "8a4ad7c70de28ffe3148fd3fd1f68c36a872c53c691c9068e1ff163970863c48",
|
||||
},
|
||||
}
|
||||
|
||||
AUTHORIZATION_FIELDS = (
|
||||
"authorized",
|
||||
"installation_authorized",
|
||||
"lifecycle_authorized",
|
||||
"execution_authorized",
|
||||
"transfer_authorized",
|
||||
"automatic_retry",
|
||||
)
|
||||
|
||||
ACTIVITY_FIELDS = (
|
||||
"hardware_observed",
|
||||
"ps5_connected",
|
||||
"files_transferred",
|
||||
"target_execution_performed",
|
||||
"target_mutation_performed",
|
||||
"target_artifact_created",
|
||||
"collector_implemented",
|
||||
"target_build_performed",
|
||||
)
|
||||
|
||||
REQUIRED_BLOCKERS = {
|
||||
"exact_permission_quote_absent": "STOP-RO",
|
||||
"collector_identity_absent": "STOP-RO",
|
||||
"collector_side_effect_contract_absent": "STOP-RO",
|
||||
"two_current_firmware_sources_absent": "STOP-GATE",
|
||||
"live_object_identities_absent": "STOP-GATE",
|
||||
"listeners_absent": "STOP-GATE",
|
||||
"autoload_status_absent": "STOP-GATE",
|
||||
"rollback_backups_absent": "STOP-GATE",
|
||||
"payload_manager_backup_not_byte_exact_on_device": "HARD_STOP-GATE",
|
||||
"unknown_result_is_stop": "STOP",
|
||||
"timeout_is_stop": "STOP",
|
||||
"deviation_is_stop": "STOP",
|
||||
"automatic_retry_forbidden": "STOP",
|
||||
}
|
||||
|
||||
REQUIRED_FINDINGS = {
|
||||
"options_any_endpoint",
|
||||
"get_version",
|
||||
"get_log",
|
||||
"get_autoload_status",
|
||||
"get_config",
|
||||
"get_list_payloads",
|
||||
"get_processes_list",
|
||||
"get_sources_list",
|
||||
"get_ip",
|
||||
}
|
||||
|
||||
REQUIRED_OBSERVATIONS = {
|
||||
"firmware",
|
||||
"live_paths",
|
||||
"object_identities",
|
||||
"file_sizes",
|
||||
"sha256",
|
||||
"processes_services",
|
||||
"listeners",
|
||||
"autoload",
|
||||
"rollback_files",
|
||||
"storage_precondition",
|
||||
}
|
||||
|
||||
REQUIRED_PROHIBITED_ACTIONS = {
|
||||
"connect_to_ps5",
|
||||
"probe_ip_port_or_device_interface",
|
||||
"use_usb_or_removable_media",
|
||||
"transfer_ps5_file",
|
||||
"package_for_ps5_deployment",
|
||||
"install_or_replace_target_component",
|
||||
"execute_elf_or_payload",
|
||||
"build_target_elf",
|
||||
"start_cross_compiler",
|
||||
"implement_or_build_collector",
|
||||
"modify_payload_manager_production_code",
|
||||
"modify_elfldr_production_code",
|
||||
"modify_lifecycle_code",
|
||||
"activate_or_modify_autoload",
|
||||
"activate_retry",
|
||||
"change_target_configuration",
|
||||
"start_stop_or_signal_target_service_or_process",
|
||||
"implement_gnm_videoout_sdl_audio_input_shaders_cores_or_retroarch",
|
||||
"download_or_install_packages",
|
||||
"contact_internet_gitea_or_other_remote",
|
||||
"commit_or_push",
|
||||
}
|
||||
|
||||
TEMPLATE_REQUIRED_FIELDS = {
|
||||
"exact_user_statement",
|
||||
"authorization_date",
|
||||
"expiration_time",
|
||||
"device_identity",
|
||||
"exact_purpose",
|
||||
"exact_observations",
|
||||
"method_or_collector_id",
|
||||
"source_commit",
|
||||
"collector_file_size",
|
||||
"collector_sha256",
|
||||
"firmware_gate",
|
||||
"maximum_runtime_ms",
|
||||
"maximum_execution_count",
|
||||
"maximum_transfer_count",
|
||||
"network_behavior",
|
||||
"output_channel",
|
||||
"allowed_volatile_effects",
|
||||
"prohibited_persistent_effects",
|
||||
"stop_criteria",
|
||||
"cleanup_requirements",
|
||||
"reporting_requirements",
|
||||
"explicit_installation_exclusion",
|
||||
"explicit_lifecycle_probe_exclusion",
|
||||
"explicit_autoload_and_retry_exclusion",
|
||||
"explicit_graphics_and_retroarch_exclusion",
|
||||
"revocation_method",
|
||||
"manual_confirmation_template_does_not_authorize",
|
||||
}
|
||||
|
||||
TARGET_SUFFIXES = {
|
||||
".elf",
|
||||
".self",
|
||||
".sprx",
|
||||
".pkg",
|
||||
".bin",
|
||||
".payload",
|
||||
".zip",
|
||||
".tar",
|
||||
".tgz",
|
||||
".7z",
|
||||
}
|
||||
|
||||
|
||||
def sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as stream:
|
||||
for block in iter(lambda: stream.read(1024 * 1024), b""):
|
||||
digest.update(block)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def load_json(path: Path) -> dict[str, Any]:
|
||||
document = json.loads(path.read_text(encoding="utf-8"))
|
||||
if not isinstance(document, dict):
|
||||
raise ValueError(f"{path}: expected a JSON object")
|
||||
return document
|
||||
|
||||
|
||||
def extract_json_contract(path: Path, name: str) -> dict[str, Any]:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
begin = f"<!-- BEGIN {name} -->"
|
||||
end = f"<!-- END {name} -->"
|
||||
if text.count(begin) != 1 or text.count(end) != 1:
|
||||
raise ValueError(f"{path}: expected exactly one {name} contract")
|
||||
block = text.split(begin, 1)[1].split(end, 1)[0].strip()
|
||||
if not block.startswith("```json\n") or not block.endswith("\n```"):
|
||||
raise ValueError(f"{path}: {name} must be one fenced JSON object")
|
||||
document = json.loads(block[len("```json\n") : -len("\n```")])
|
||||
if not isinstance(document, dict):
|
||||
raise ValueError(f"{path}: {name} must be a JSON object")
|
||||
return document
|
||||
|
||||
|
||||
def positive_status_values(value: Any) -> list[str]:
|
||||
errors: list[str] = []
|
||||
positive = {
|
||||
"READY",
|
||||
"COMPLETE",
|
||||
"COMPLETED",
|
||||
"AUTHORIZED",
|
||||
"PASS",
|
||||
"PASSED",
|
||||
"READ_ONLY_PREFLIGHT_DATA_COMPLETE",
|
||||
"READY_FOR_HARDENED_RUNTIME_DEPLOYMENT",
|
||||
}
|
||||
if isinstance(value, dict):
|
||||
for key, child in value.items():
|
||||
if key == "historical_validation_report":
|
||||
continue
|
||||
errors.extend(positive_status_values(child))
|
||||
elif isinstance(value, list):
|
||||
for child in value:
|
||||
errors.extend(positive_status_values(child))
|
||||
elif isinstance(value, str) and value.upper() in positive:
|
||||
errors.append(value)
|
||||
return errors
|
||||
|
||||
|
||||
def validate_manifest(
|
||||
manifest: dict[str, Any], denylist: dict[str, Any]
|
||||
) -> list[str]:
|
||||
errors: list[str] = []
|
||||
|
||||
if manifest.get("schema_version") != 1:
|
||||
errors.append("unsupported remediation schema version")
|
||||
schema_contract = manifest.get("schema_contract", {})
|
||||
if schema_contract.get("id") != "chimera-gfx-phase-0.8-remediation-v1":
|
||||
errors.append("remediation schema-contract ID changed")
|
||||
if schema_contract.get("shared_schema_available") is not False:
|
||||
errors.append("remediation incorrectly claims a shared schema")
|
||||
if schema_contract.get("validator") != "tools/validate_phase08_remediation.py":
|
||||
errors.append("remediation validator binding changed")
|
||||
|
||||
if manifest.get("phase_id") != "0.8R":
|
||||
errors.append("remediation phase ID changed")
|
||||
if manifest.get("phase") != "offline_remediation":
|
||||
errors.append("remediation phase widened beyond offline")
|
||||
if manifest.get("status") != STATUS:
|
||||
errors.append("Phase-0.8 status is not fail-closed")
|
||||
if positive_status_values(manifest):
|
||||
errors.append("manifest contains an overriding positive status")
|
||||
|
||||
historical = manifest.get("historical_identity", {})
|
||||
if historical.get("source_commit") != SOURCE_COMMIT:
|
||||
errors.append("historical source commit changed")
|
||||
if historical.get("status") != STATUS:
|
||||
errors.append("historical Phase-0.8 status changed")
|
||||
historical_report = historical.get("historical_validation_report", {})
|
||||
if (
|
||||
historical_report.get("classification")
|
||||
!= "historical_report_not_current_hardware_evidence"
|
||||
):
|
||||
errors.append("historical tests were promoted to hardware evidence")
|
||||
|
||||
immutable = {
|
||||
item.get("path"): {
|
||||
"role": item.get("role"),
|
||||
"sha256": item.get("sha256"),
|
||||
}
|
||||
for item in manifest.get("immutable_evidence", [])
|
||||
}
|
||||
if immutable != IMMUTABLE_EVIDENCE:
|
||||
errors.append("immutable Phase-0.8 evidence binding changed")
|
||||
|
||||
authorization = manifest.get("authorization", {})
|
||||
if any(authorization.get(field) is not False for field in AUTHORIZATION_FIELDS):
|
||||
errors.append("an authorization or retry field is not false")
|
||||
activity = manifest.get("activity", {})
|
||||
if any(activity.get(field) is not False for field in ACTIVITY_FIELDS):
|
||||
errors.append("manifest claims a prohibited target activity")
|
||||
|
||||
if manifest.get("firmware_runtime_behavior") != "UNPROVEN":
|
||||
errors.append("firmware runtime behavior was promoted")
|
||||
claim_boundaries = manifest.get("claim_boundaries", {})
|
||||
required_false_claims = {
|
||||
"hardware_safety_proven",
|
||||
"firmware_behavior_proven",
|
||||
"absence_of_volatile_effects_proven",
|
||||
"no_persistent_write_found_equals_side_effect_free",
|
||||
"host_tests_are_hardware_evidence",
|
||||
"missing_observation_means_safe_absence",
|
||||
}
|
||||
if any(claim_boundaries.get(field) is not False for field in required_false_claims):
|
||||
errors.append("a prohibited safety or evidence claim was enabled")
|
||||
|
||||
stock = manifest.get("stock_identities", {})
|
||||
if (
|
||||
stock.get("classification") != "reference_only"
|
||||
or stock.get("current_device_observed") is not False
|
||||
):
|
||||
errors.append("stock identities were promoted from reference-only")
|
||||
elfldr = stock.get("elfldr", {})
|
||||
if (
|
||||
elfldr.get("size") != 397000
|
||||
or elfldr.get("sha256") != STOCK_ELFLDR_SHA256
|
||||
or elfldr.get("current_device_match") != "UNPROVEN"
|
||||
):
|
||||
errors.append("stock elfldr reference changed or was promoted")
|
||||
manager = stock.get("payload_manager", {})
|
||||
if (
|
||||
manager.get("size") != 2050320
|
||||
or manager.get("sha256") != STOCK_PAYLOAD_MANAGER_SHA256
|
||||
or manager.get("current_device_match") != "UNPROVEN"
|
||||
):
|
||||
errors.append("stock Payload Manager reference changed or was promoted")
|
||||
|
||||
backup = manifest.get("payload_manager_backup", {})
|
||||
if backup != {
|
||||
"classification": "hard_blocker",
|
||||
"on_device_proven": False,
|
||||
"byte_exact_proven": False,
|
||||
"creation_allowed_in_strict_read_only_phase": False,
|
||||
"result": "HARD_STOP-GATE",
|
||||
}:
|
||||
errors.append("Payload Manager backup hard blocker changed")
|
||||
|
||||
entries = denylist.get("entries", [])
|
||||
if (
|
||||
denylist.get("fail_closed") is not True
|
||||
or len(entries) != 1
|
||||
or entries[0].get("sha256") != DENYLIST_SHA256
|
||||
or entries[0].get("status") != "BLOCKED"
|
||||
or entries[0].get("permanent") is not True
|
||||
or entries[0].get("execution_eligible") is not False
|
||||
):
|
||||
errors.append("permanent denylist binding changed")
|
||||
|
||||
blockers = {
|
||||
item.get("id"): item.get("severity") for item in manifest.get("blockers", [])
|
||||
}
|
||||
if blockers != REQUIRED_BLOCKERS:
|
||||
errors.append("remediation blocker set changed")
|
||||
|
||||
findings = {
|
||||
item.get("id"): item for item in manifest.get("side_effect_findings", [])
|
||||
}
|
||||
if set(findings) != REQUIRED_FINDINGS:
|
||||
errors.append("Payload Manager side-effect finding set changed")
|
||||
for finding_id, finding in findings.items():
|
||||
if finding.get("strict_read_only_preflight_suitable") is not False:
|
||||
errors.append(f"{finding_id}: incorrectly marked strict-read-only suitable")
|
||||
references = finding.get("source_references")
|
||||
if not isinstance(references, list) or not references:
|
||||
errors.append(f"{finding_id}: source references are absent")
|
||||
if finding_id != "options_any_endpoint":
|
||||
if finding.get("http_method") == "OPTIONS":
|
||||
errors.append(f"{finding_id}: non-OPTIONS finding mislabeled")
|
||||
if finding.get("writes_server_active_flag") is not True:
|
||||
errors.append(f"{finding_id}: server_active_flag mutation hidden")
|
||||
options = findings.get("options_any_endpoint", {})
|
||||
if (
|
||||
options.get("http_method") != "OPTIONS"
|
||||
or options.get("writes_server_active_flag") is not False
|
||||
or options.get("strict_read_only_preflight_suitable") is not False
|
||||
):
|
||||
errors.append("OPTIONS route classification changed")
|
||||
autoload = findings.get("get_autoload_status", {})
|
||||
if (
|
||||
autoload.get("endpoint") != "/autoload_status"
|
||||
or autoload.get("writes_autoload_triggered") is not True
|
||||
or autoload.get("reads_filesystem_or_configuration") is not True
|
||||
):
|
||||
errors.append("/autoload_status mutations or reads were hidden")
|
||||
|
||||
source = manifest.get("payload_manager_source", {})
|
||||
if (
|
||||
source.get("commit") != PAYLOAD_MANAGER_COMMIT
|
||||
or source.get("release") != "v0.3.1"
|
||||
):
|
||||
errors.append("Payload Manager source identity changed")
|
||||
source_files = source.get("files")
|
||||
if not isinstance(source_files, list) or len(source_files) < 3:
|
||||
errors.append("Payload Manager source-file evidence is incomplete")
|
||||
|
||||
observations = {
|
||||
item.get("id"): item for item in manifest.get("evidence_contract", [])
|
||||
}
|
||||
if set(observations) != REQUIRED_OBSERVATIONS:
|
||||
errors.append("future evidence-contract observation set changed")
|
||||
for observation_id, observation in observations.items():
|
||||
if observation.get("confidence") != "UNPROVEN":
|
||||
errors.append(f"{observation_id}: confidence was promoted")
|
||||
if observation.get("timeout_ms") is not None:
|
||||
errors.append(f"{observation_id}: timeout invented before tool review")
|
||||
if observation.get("fail_closed_result") != "STOP":
|
||||
errors.append(f"{observation_id}: fail-closed result changed")
|
||||
identity = observation.get("required_collector_identity")
|
||||
output = observation.get("reviewer_output")
|
||||
if not isinstance(identity, list) or not identity:
|
||||
errors.append(f"{observation_id}: collector identity contract absent")
|
||||
if not isinstance(output, list) or not output:
|
||||
errors.append(f"{observation_id}: reviewer output contract absent")
|
||||
|
||||
prohibited = set(manifest.get("prohibited_actions", []))
|
||||
if prohibited != REQUIRED_PROHIBITED_ACTIONS:
|
||||
errors.append("prohibited-action set changed")
|
||||
future = manifest.get("future_activity", {})
|
||||
if (
|
||||
future.get("mode") != "design_only"
|
||||
or future.get("bounded_observation_implemented") is not False
|
||||
or future.get("collector_selected") is not False
|
||||
or future.get("transfer_method_selected") is not False
|
||||
or future.get("execution_method_selected") is not False
|
||||
or future.get("new_explicit_authorization_required") is not True
|
||||
):
|
||||
errors.append("future bounded observation was promoted beyond design")
|
||||
retroarch = manifest.get("retroarch", {})
|
||||
if retroarch != {
|
||||
"goal": "long_term_goal",
|
||||
"active_phase": False,
|
||||
"work_started": False,
|
||||
"dependency_chain_only": True,
|
||||
}:
|
||||
errors.append("RetroArch was promoted into the active phase")
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
def validate_template(template: dict[str, Any]) -> list[str]:
|
||||
errors: list[str] = []
|
||||
if template.get("template_only") is not True:
|
||||
errors.append("bounded-observation template is not template-only")
|
||||
for field in (
|
||||
"authorized",
|
||||
"execution_authorized",
|
||||
"transfer_authorized",
|
||||
"installation_authorized",
|
||||
"lifecycle_authorized",
|
||||
"automatic_retry",
|
||||
):
|
||||
if template.get(field) is not False:
|
||||
errors.append(f"template field {field} is not false")
|
||||
required = template.get("required_fields", {})
|
||||
if set(required) != TEMPLATE_REQUIRED_FIELDS:
|
||||
errors.append("bounded-observation required-field set changed")
|
||||
elif any(value is not None for value in required.values()):
|
||||
errors.append("bounded-observation template contains prefilled request data")
|
||||
exclusions = template.get("fixed_exclusions", {})
|
||||
expected_exclusions = {
|
||||
"installation",
|
||||
"lifecycle_probe",
|
||||
"autoload",
|
||||
"automatic_retry",
|
||||
"gnm",
|
||||
"videoout",
|
||||
"sdl",
|
||||
"retroarch",
|
||||
}
|
||||
if set(exclusions) != expected_exclusions or any(
|
||||
value is not True for value in exclusions.values()
|
||||
):
|
||||
errors.append("bounded-observation fixed exclusions changed")
|
||||
return errors
|
||||
|
||||
|
||||
def validate_doc_contract(
|
||||
contract: dict[str, Any], manifest: dict[str, Any]
|
||||
) -> list[str]:
|
||||
errors: list[str] = []
|
||||
if contract.get("status") != manifest.get("status"):
|
||||
errors.append("documentation/manifest status mismatch")
|
||||
if contract.get("authorization") != manifest.get("authorization"):
|
||||
errors.append("documentation/manifest authorization mismatch")
|
||||
blocker_ids = [item.get("id") for item in manifest.get("blockers", [])]
|
||||
if contract.get("blockers") != blocker_ids:
|
||||
errors.append("documentation/manifest blocker mismatch")
|
||||
if contract.get("firmware_runtime_behavior") != "UNPROVEN":
|
||||
errors.append("documentation promoted firmware behavior")
|
||||
if contract.get("stock_identity_classification") != "reference_only":
|
||||
errors.append("documentation promoted stock identities")
|
||||
if contract.get("payload_manager_backup_classification") != "hard_blocker":
|
||||
errors.append("documentation weakened the manager backup blocker")
|
||||
if contract.get("retroarch_active_phase") is not False:
|
||||
errors.append("documentation promoted RetroArch into the active phase")
|
||||
return errors
|
||||
|
||||
|
||||
def validate_immutable_evidence(root: Path) -> list[str]:
|
||||
errors: list[str] = []
|
||||
for relative, expected in IMMUTABLE_EVIDENCE.items():
|
||||
path = root / relative
|
||||
if not path.is_file():
|
||||
errors.append(f"immutable evidence missing: {relative}")
|
||||
elif sha256(path) != expected["sha256"]:
|
||||
errors.append(f"immutable evidence hash mismatch: {relative}")
|
||||
return errors
|
||||
|
||||
|
||||
def changed_paths(root: Path) -> list[Path]:
|
||||
result = subprocess.run(
|
||||
["git", "status", "--porcelain=v1", "--untracked-files=all"],
|
||||
cwd=root,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
paths: list[Path] = []
|
||||
for line in result.stdout.splitlines():
|
||||
value = line[3:]
|
||||
for candidate in value.split(" -> "):
|
||||
candidate = candidate.strip('"')
|
||||
paths.append(Path(candidate))
|
||||
return paths
|
||||
|
||||
|
||||
def validate_changed_files(root: Path) -> list[str]:
|
||||
errors: list[str] = []
|
||||
for path in changed_paths(root):
|
||||
if path.suffix.lower() in TARGET_SUFFIXES:
|
||||
errors.append(f"target artifact appears in change set: {path}")
|
||||
normalized = path.as_posix()
|
||||
if normalized.startswith(
|
||||
("src/", "include/", "samples/", "adapters/", "work/upstream/")
|
||||
):
|
||||
errors.append(f"production/runtime source changed in remediation: {path}")
|
||||
return errors
|
||||
|
||||
|
||||
def validate_local_payload_manager_source(
|
||||
root: Path, manifest: dict[str, Any]
|
||||
) -> list[str]:
|
||||
errors: list[str] = []
|
||||
source_root = root / "work/upstream/pldmgr-v0.3.1"
|
||||
if not source_root.is_dir():
|
||||
return ["required local Payload Manager source checkout is absent"]
|
||||
head = subprocess.run(
|
||||
["git", "rev-parse", "HEAD"],
|
||||
cwd=source_root,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
).stdout.strip()
|
||||
if head != PAYLOAD_MANAGER_COMMIT:
|
||||
errors.append("local Payload Manager source commit changed")
|
||||
status = subprocess.run(
|
||||
["git", "status", "--porcelain"],
|
||||
cwd=source_root,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
).stdout.strip()
|
||||
if status:
|
||||
errors.append("local Payload Manager source checkout is dirty")
|
||||
for item in manifest["payload_manager_source"]["files"]:
|
||||
path = source_root / item["path"]
|
||||
if not path.is_file():
|
||||
errors.append(f"Payload Manager source file missing: {item['path']}")
|
||||
elif sha256(path) != item["sha256"]:
|
||||
errors.append(f"Payload Manager source hash mismatch: {item['path']}")
|
||||
return errors
|
||||
|
||||
|
||||
def collect_errors(root: Path, require_local_source: bool = False) -> list[str]:
|
||||
manifest = load_json(root / "manifests/runtime/phase-0.8-remediation.json")
|
||||
denylist = load_json(root / "manifests/artifact-denylist.json")
|
||||
doc_contract = extract_json_contract(
|
||||
root / "docs/runtime/phase-0.8-remediation.md", "PHASE08R_CONTRACT"
|
||||
)
|
||||
template = extract_json_contract(
|
||||
root / "docs/approvals/phase-0.8-bounded-observation-template.md",
|
||||
"PHASE08_BOUNDED_OBSERVATION_TEMPLATE",
|
||||
)
|
||||
errors = validate_immutable_evidence(root)
|
||||
errors.extend(validate_manifest(manifest, denylist))
|
||||
errors.extend(validate_doc_contract(doc_contract, manifest))
|
||||
errors.extend(validate_template(template))
|
||||
errors.extend(validate_changed_files(root))
|
||||
if require_local_source:
|
||||
errors.extend(validate_local_payload_manager_source(root, manifest))
|
||||
return errors
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
parser.add_argument(
|
||||
"--require-local-source",
|
||||
action="store_true",
|
||||
help="also require and rehash the ignored pinned Payload Manager checkout",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
root = args.root.resolve()
|
||||
errors = collect_errors(root, args.require_local_source)
|
||||
if errors:
|
||||
for error in errors:
|
||||
print(f"Phase-0.8R validation failed: {error}")
|
||||
return 1
|
||||
print(
|
||||
"Phase-0.8R remediation validation passed: "
|
||||
f"{len(IMMUTABLE_EVIDENCE)} immutable files, "
|
||||
f"{len(REQUIRED_FINDINGS)} side-effect findings, "
|
||||
f"{len(REQUIRED_OBSERVATIONS)} observation contracts, "
|
||||
f"{len(REQUIRED_BLOCKERS)} blockers; hardware evidence not claimed"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
raise SystemExit(main())
|
||||
except (
|
||||
KeyError,
|
||||
OSError,
|
||||
subprocess.CalledProcessError,
|
||||
TypeError,
|
||||
ValueError,
|
||||
json.JSONDecodeError,
|
||||
) as error:
|
||||
print(f"Phase-0.8R validation failed: {error}")
|
||||
raise SystemExit(1) from error
|
||||
Reference in New Issue
Block a user