This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Regression guardrails for the offline Phase-0.8R remediation contract."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import copy
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
from types import ModuleType
|
||||
from typing import Any
|
||||
|
||||
|
||||
def load_validator(root: Path) -> ModuleType:
|
||||
path = root / "tools/validate_phase08_remediation.py"
|
||||
spec = importlib.util.spec_from_file_location("phase08r_validator", path)
|
||||
if spec is None or spec.loader is None:
|
||||
raise RuntimeError("could not load Phase-0.8R validator")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def require_invalid(errors: list[str], scenario: str) -> None:
|
||||
if not errors:
|
||||
raise RuntimeError(f"unsafe mutation passed validation: {scenario}")
|
||||
|
||||
|
||||
def validate_manifest_mutation(
|
||||
validator: ModuleType,
|
||||
manifest: dict[str, Any],
|
||||
denylist: dict[str, Any],
|
||||
scenario: str,
|
||||
) -> None:
|
||||
require_invalid(validator.validate_manifest(manifest, denylist), scenario)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
root = args.root.resolve()
|
||||
validator = load_validator(root)
|
||||
|
||||
errors = validator.collect_errors(root, require_local_source=False)
|
||||
if errors:
|
||||
raise RuntimeError("; ".join(errors))
|
||||
|
||||
manifest = validator.load_json(
|
||||
root / "manifests/runtime/phase-0.8-remediation.json"
|
||||
)
|
||||
denylist = validator.load_json(root / "manifests/artifact-denylist.json")
|
||||
doc_contract = validator.extract_json_contract(
|
||||
root / "docs/runtime/phase-0.8-remediation.md", "PHASE08R_CONTRACT"
|
||||
)
|
||||
template = validator.extract_json_contract(
|
||||
root / "docs/approvals/phase-0.8-bounded-observation-template.md",
|
||||
"PHASE08_BOUNDED_OBSERVATION_TEMPLATE",
|
||||
)
|
||||
|
||||
ready = copy.deepcopy(manifest)
|
||||
ready["status"] = "READY"
|
||||
validate_manifest_mutation(
|
||||
validator, ready, denylist, "general READY status overrides blockers"
|
||||
)
|
||||
|
||||
authorized = copy.deepcopy(manifest)
|
||||
authorized["authorization"]["execution_authorized"] = True
|
||||
validate_manifest_mutation(
|
||||
validator, authorized, denylist, "execution authorization became true"
|
||||
)
|
||||
|
||||
retry = copy.deepcopy(manifest)
|
||||
retry["authorization"]["automatic_retry"] = True
|
||||
validate_manifest_mutation(
|
||||
validator, retry, denylist, "automatic retry became true"
|
||||
)
|
||||
|
||||
current_stock = copy.deepcopy(manifest)
|
||||
current_stock["stock_identities"]["classification"] = "current_device_identity"
|
||||
current_stock["stock_identities"]["current_device_observed"] = True
|
||||
validate_manifest_mutation(
|
||||
validator, current_stock, denylist, "reference-only hashes were promoted"
|
||||
)
|
||||
|
||||
backup_ready = copy.deepcopy(manifest)
|
||||
backup_ready["payload_manager_backup"]["classification"] = "ready"
|
||||
backup_ready["payload_manager_backup"]["on_device_proven"] = True
|
||||
backup_ready["payload_manager_backup"]["byte_exact_proven"] = True
|
||||
backup_ready["payload_manager_backup"]["result"] = "PASS"
|
||||
validate_manifest_mutation(
|
||||
validator, backup_ready, denylist, "manager backup hard blocker disappeared"
|
||||
)
|
||||
|
||||
missing_deny = copy.deepcopy(denylist)
|
||||
missing_deny["entries"] = []
|
||||
validate_manifest_mutation(
|
||||
validator, manifest, missing_deny, "permanent denylist entry disappeared"
|
||||
)
|
||||
|
||||
non_options_read_only = copy.deepcopy(manifest)
|
||||
for finding in non_options_read_only["side_effect_findings"]:
|
||||
if finding["id"] == "get_version":
|
||||
finding["writes_server_active_flag"] = False
|
||||
finding["strict_read_only_preflight_suitable"] = True
|
||||
validate_manifest_mutation(
|
||||
validator,
|
||||
non_options_read_only,
|
||||
denylist,
|
||||
"non-OPTIONS request was called strict read-only",
|
||||
)
|
||||
|
||||
autoload_read_only = copy.deepcopy(manifest)
|
||||
for finding in autoload_read_only["side_effect_findings"]:
|
||||
if finding["id"] == "get_autoload_status":
|
||||
finding["writes_autoload_triggered"] = False
|
||||
finding["reads_filesystem_or_configuration"] = False
|
||||
validate_manifest_mutation(
|
||||
validator,
|
||||
autoload_read_only,
|
||||
denylist,
|
||||
"/autoload_status mutations and reads were hidden",
|
||||
)
|
||||
|
||||
options_suitable = copy.deepcopy(manifest)
|
||||
for finding in options_suitable["side_effect_findings"]:
|
||||
if finding["id"] == "options_any_endpoint":
|
||||
finding["strict_read_only_preflight_suitable"] = True
|
||||
validate_manifest_mutation(
|
||||
validator, options_suitable, denylist, "OPTIONS was promoted to collector"
|
||||
)
|
||||
|
||||
hardware_claim = copy.deepcopy(manifest)
|
||||
hardware_claim["claim_boundaries"]["hardware_safety_proven"] = True
|
||||
hardware_claim["firmware_runtime_behavior"] = "PROVEN_SAFE"
|
||||
validate_manifest_mutation(
|
||||
validator, hardware_claim, denylist, "host evidence became hardware proof"
|
||||
)
|
||||
|
||||
retroarch_active = copy.deepcopy(manifest)
|
||||
retroarch_active["retroarch"]["active_phase"] = True
|
||||
retroarch_active["retroarch"]["work_started"] = True
|
||||
validate_manifest_mutation(
|
||||
validator, retroarch_active, denylist, "RetroArch became active work"
|
||||
)
|
||||
|
||||
template_authorized = copy.deepcopy(template)
|
||||
template_authorized["authorized"] = True
|
||||
require_invalid(
|
||||
validator.validate_template(template_authorized),
|
||||
"bounded-observation template became authorization",
|
||||
)
|
||||
|
||||
template_prefilled = copy.deepcopy(template)
|
||||
template_prefilled["required_fields"]["collector_sha256"] = "0" * 64
|
||||
require_invalid(
|
||||
validator.validate_template(template_prefilled),
|
||||
"template invented an artifact hash",
|
||||
)
|
||||
|
||||
merged_approvals = copy.deepcopy(manifest)
|
||||
del merged_approvals["authorization"]["lifecycle_authorized"]
|
||||
validate_manifest_mutation(
|
||||
validator,
|
||||
merged_approvals,
|
||||
denylist,
|
||||
"installation and lifecycle authorization were merged",
|
||||
)
|
||||
|
||||
doc_without_hard_blocker = copy.deepcopy(doc_contract)
|
||||
doc_without_hard_blocker["blockers"].remove(
|
||||
"payload_manager_backup_not_byte_exact_on_device"
|
||||
)
|
||||
require_invalid(
|
||||
validator.validate_doc_contract(doc_without_hard_blocker, manifest),
|
||||
"documentation omitted manager backup hard blocker",
|
||||
)
|
||||
|
||||
if validator.validate_changed_files(root):
|
||||
raise RuntimeError("; ".join(validator.validate_changed_files(root)))
|
||||
|
||||
print(
|
||||
"Phase-0.8R regression guardrails passed: immutable hashes, blocked "
|
||||
"status, false authorizations, denylist, reference-only stock hashes, "
|
||||
"hard backup gate, route effects, template denial, phase separation, "
|
||||
"RetroArch deferral, and no target artifact"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user