406 lines
14 KiB
Python
406 lines
14 KiB
Python
#!/usr/bin/env python3
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
"""Thirty host-only guardrails for the Phase-0.9D readback design."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import copy
|
|
import importlib.util
|
|
from pathlib import Path
|
|
import sys
|
|
from types import ModuleType
|
|
from typing import Callable
|
|
|
|
|
|
def load_module(name: str, path: Path) -> ModuleType:
|
|
spec = importlib.util.spec_from_file_location(name, path)
|
|
if spec is None or spec.loader is None:
|
|
raise RuntimeError(f"could not load {path}")
|
|
module = importlib.util.module_from_spec(spec)
|
|
sys.modules[name] = module
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
def require(condition: bool, message: str) -> None:
|
|
if not condition:
|
|
raise RuntimeError(message)
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--root", type=Path, required=True)
|
|
args = parser.parse_args()
|
|
root = args.root.resolve()
|
|
validator = load_module(
|
|
"phase09d_validator", root / "tools/validate_phase09d_readback.py"
|
|
)
|
|
manifest = validator.load_json(
|
|
root / "manifests/runtime/phase-0.9d-existing-stack-readback.json"
|
|
)
|
|
schema = validator.load_json(
|
|
root
|
|
/ "manifests/runtime/phase-0.9d-existing-stack-readback.schema.json"
|
|
)
|
|
defaults = manifest["endpoint_defaults"]
|
|
cases: list[tuple[str, Callable[[], None]]] = []
|
|
|
|
def case(name: str) -> Callable[[Callable[[], None]], Callable[[], None]]:
|
|
def register(function: Callable[[], None]) -> Callable[[], None]:
|
|
cases.append((name, function))
|
|
return function
|
|
|
|
return register
|
|
|
|
def candidate(**changes: object) -> dict[str, object]:
|
|
route: dict[str, object] = {
|
|
"readback_candidate": True,
|
|
"open_flags": ["O_RDONLY", "O_NOFOLLOW"],
|
|
"writes_bytes": False,
|
|
"creates_file": False,
|
|
"removes_file": False,
|
|
"renames_file": False,
|
|
"modifies_configuration": False,
|
|
"launches_payload": False,
|
|
"process_or_service_action": False,
|
|
"writes_autoload_triggered": False,
|
|
"binary_safe_file_response": True,
|
|
"exact_returned_byte_count": True,
|
|
"partial_result_rejected": True,
|
|
"short_read_behavior": "DETECTED_AND_INVALID",
|
|
"automatic_retry": False,
|
|
"automatic_resume": False,
|
|
}
|
|
route.update(changes)
|
|
return route
|
|
|
|
def invalid_manifest(changed: dict[str, object], message: str) -> None:
|
|
require(bool(validator.validate_manifest(changed)), message)
|
|
|
|
@case("01 device write can never be a readback candidate")
|
|
def _() -> None:
|
|
errors = validator.route_readback_errors(
|
|
candidate(writes_bytes=True), defaults
|
|
)
|
|
require(bool(errors), "device-write candidate passed")
|
|
|
|
@case("02 rename unlink create and truncate are rejected")
|
|
def _() -> None:
|
|
mutations = (
|
|
{"renames_file": True},
|
|
{"removes_file": True},
|
|
{"creates_file": True},
|
|
{"open_flags": ["fopen(wb)"]},
|
|
)
|
|
for mutation in mutations:
|
|
require(
|
|
bool(
|
|
validator.route_readback_errors(
|
|
candidate(**mutation), defaults
|
|
)
|
|
),
|
|
f"mutating route passed: {mutation}",
|
|
)
|
|
|
|
@case("03 payload launch is rejected")
|
|
def _() -> None:
|
|
require(
|
|
bool(
|
|
validator.route_readback_errors(
|
|
candidate(launches_payload=True), defaults
|
|
)
|
|
),
|
|
"launch route passed",
|
|
)
|
|
|
|
@case("04 autoload_triggered routes are excluded")
|
|
def _() -> None:
|
|
require(
|
|
bool(
|
|
validator.route_readback_errors(
|
|
candidate(writes_autoload_triggered=True), defaults
|
|
)
|
|
),
|
|
"autoload mutation passed",
|
|
)
|
|
require(
|
|
manifest["flag_semantics"]["autoload_triggered"]["excluded_windows"]
|
|
== [1, 2],
|
|
"autoload route is not excluded from Windows 1 and 2",
|
|
)
|
|
|
|
@case("05 server_active alone is not anti-brick critical")
|
|
def _() -> None:
|
|
semantics = manifest["flag_semantics"]["server_active_flag"]
|
|
require(
|
|
semantics["classification"] == "LOW_VOLATILE",
|
|
"server_active is overclassified",
|
|
)
|
|
require(
|
|
validator.server_active_observation_status(semantics) == "PARTIAL",
|
|
"server_active route should remain partial",
|
|
)
|
|
|
|
@case("06 server_active semantics must be complete")
|
|
def _() -> None:
|
|
changed = copy.deepcopy(manifest)
|
|
changed["flag_semantics"]["server_active_flag"]["fully_documented"] = False
|
|
invalid_manifest(changed, "incomplete server_active semantics passed")
|
|
|
|
@case("07 missing reset semantics blocks observation")
|
|
def _() -> None:
|
|
semantics = copy.deepcopy(
|
|
manifest["flag_semantics"]["server_active_flag"]
|
|
)
|
|
semantics["reset_path"] = "UNPROVEN"
|
|
require(
|
|
validator.server_active_observation_status(semantics) == "BLOCKED",
|
|
"unknown reset semantics did not block",
|
|
)
|
|
|
|
@case("08 file read without binary framing is invalid")
|
|
def _() -> None:
|
|
require(
|
|
bool(
|
|
validator.route_readback_errors(
|
|
candidate(binary_safe_file_response=False), defaults
|
|
)
|
|
),
|
|
"text-framed file route passed",
|
|
)
|
|
|
|
@case("09 file read without short-read detection is invalid")
|
|
def _() -> None:
|
|
require(
|
|
bool(
|
|
validator.route_readback_errors(
|
|
candidate(short_read_behavior="UNPROVEN"), defaults
|
|
)
|
|
),
|
|
"short-read-unsafe route passed",
|
|
)
|
|
|
|
@case("10 partial host transfer is invalid")
|
|
def _() -> None:
|
|
record = {
|
|
"status": "TRANSFER_INCOMPLETE",
|
|
"transfer_complete": False,
|
|
"automatic_resume": False,
|
|
"automatic_retry": False,
|
|
"recovery_proven": False,
|
|
}
|
|
require(
|
|
bool(validator.backup_record_errors(record)),
|
|
"partial transfer was not invalidated",
|
|
)
|
|
|
|
@case("11 automatic resume is forbidden")
|
|
def _() -> None:
|
|
record = {
|
|
"status": "INVALID",
|
|
"automatic_resume": True,
|
|
"automatic_retry": False,
|
|
"recovery_proven": False,
|
|
}
|
|
require(
|
|
bool(validator.backup_record_errors(record)),
|
|
"automatic resume passed",
|
|
)
|
|
|
|
@case("12 automatic retry is forbidden")
|
|
def _() -> None:
|
|
record = {
|
|
"status": "INVALID",
|
|
"automatic_resume": False,
|
|
"automatic_retry": True,
|
|
"recovery_proven": False,
|
|
}
|
|
require(
|
|
bool(validator.backup_record_errors(record)),
|
|
"automatic retry passed",
|
|
)
|
|
|
|
@case("13 two readbacks must match size hash and bytes")
|
|
def _() -> None:
|
|
record = {
|
|
"status": "COPIES_MATCH",
|
|
"transfer_complete": True,
|
|
"automatic_resume": False,
|
|
"automatic_retry": False,
|
|
"closed_and_reopened": True,
|
|
"exact_byte_count": 8,
|
|
"sha256": "0" * 64,
|
|
"sizes_match": True,
|
|
"hashes_match": True,
|
|
"bytes_match": False,
|
|
"recovery_proven": False,
|
|
}
|
|
require(
|
|
bool(validator.backup_record_errors(record)),
|
|
"byte mismatch passed",
|
|
)
|
|
|
|
@case("14 hash without exact byte count is insufficient")
|
|
def _() -> None:
|
|
record = {
|
|
"status": "HOST_COPY_HASHED",
|
|
"transfer_complete": True,
|
|
"automatic_resume": False,
|
|
"automatic_retry": False,
|
|
"closed_and_reopened": True,
|
|
"sha256": "0" * 64,
|
|
"recovery_proven": False,
|
|
}
|
|
require(
|
|
bool(validator.backup_record_errors(record)),
|
|
"hash without count passed",
|
|
)
|
|
|
|
@case("15 host backup is not recovery proof")
|
|
def _() -> None:
|
|
record = {
|
|
"status": "COPIES_MATCH",
|
|
"transfer_complete": True,
|
|
"automatic_resume": False,
|
|
"automatic_retry": False,
|
|
"closed_and_reopened": True,
|
|
"exact_byte_count": 8,
|
|
"sha256": "0" * 64,
|
|
"sizes_match": True,
|
|
"hashes_match": True,
|
|
"bytes_match": True,
|
|
"recovery_proven": True,
|
|
}
|
|
require(
|
|
bool(validator.backup_record_errors(record)),
|
|
"backup incorrectly proved recovery",
|
|
)
|
|
|
|
@case("16 replacement-dependent recovery is self-dependent")
|
|
def _() -> None:
|
|
require(
|
|
validator.recovery_dependency_classification(
|
|
"payload_manager", "payload_manager"
|
|
)
|
|
== "SELF_DEPENDENT",
|
|
"self-dependence was not detected",
|
|
)
|
|
|
|
@case("17 package path cannot become live")
|
|
def _() -> None:
|
|
changed = copy.deepcopy(manifest)
|
|
changed["path_inventory"][0]["live"] = True
|
|
changed["runtime_observed_live_paths"] = [
|
|
changed["path_inventory"][0]["path"]
|
|
]
|
|
invalid_manifest(changed, "offline path was promoted to live")
|
|
|
|
@case("18 conflicting paths retain PATH_CONFLICT")
|
|
def _() -> None:
|
|
changed = copy.deepcopy(manifest)
|
|
changed["path_classification"] = "RESOLVED"
|
|
invalid_manifest(changed, "path conflict was silently resolved")
|
|
|
|
@case("19 Window 1 contains no file transfer")
|
|
def _() -> None:
|
|
changed = copy.deepcopy(manifest)
|
|
changed["operational_windows"][0]["file_transfer"] = True
|
|
invalid_manifest(changed, "Window 1 transfer passed")
|
|
|
|
@case("20 Window 2 contains no device write")
|
|
def _() -> None:
|
|
changed = copy.deepcopy(manifest)
|
|
changed["operational_windows"][1]["device_write"] = True
|
|
invalid_manifest(changed, "Window 2 write passed")
|
|
|
|
@case("21 Window 2 contains no launch")
|
|
def _() -> None:
|
|
changed = copy.deepcopy(manifest)
|
|
changed["operational_windows"][1]["payload_launch"] = True
|
|
invalid_manifest(changed, "Window 2 launch passed")
|
|
|
|
@case("22 Window 2 excludes autoload_status")
|
|
def _() -> None:
|
|
changed = copy.deepcopy(manifest)
|
|
changed["operational_windows"][1]["autoload_status_route"] = True
|
|
invalid_manifest(changed, "Window 2 autoload_status passed")
|
|
|
|
@case("23 Window 3 has no automatic third attempt")
|
|
def _() -> None:
|
|
changed = copy.deepcopy(manifest)
|
|
changed["operational_windows"][2]["automatic_third_attempt"] = True
|
|
invalid_manifest(changed, "automatic third attempt passed")
|
|
|
|
@case("24 components have separate windows")
|
|
def _() -> None:
|
|
changed = copy.deepcopy(manifest)
|
|
changed["operational_windows"][3]["component_session_separate"] = False
|
|
invalid_manifest(changed, "combined component window passed")
|
|
|
|
@case("25 side-by-side does not authorize installation")
|
|
def _() -> None:
|
|
changed = copy.deepcopy(manifest)
|
|
changed["side_by_side"]["grants_installation_authorization"] = True
|
|
invalid_manifest(changed, "side-by-side authorized installation")
|
|
|
|
@case("26 no observer artifact appears")
|
|
def _() -> None:
|
|
require(
|
|
manifest["actions"]["observer_created"] is False
|
|
and manifest["actions"]["target_artifact_created"] is False,
|
|
"observer artifact recorded",
|
|
)
|
|
require(validator.collect_errors(root) == [], "offline audit invalid")
|
|
|
|
@case("27 no target code appears")
|
|
def _() -> None:
|
|
forbidden = {".c", ".cc", ".cpp", ".s", ".asm", ".ld", ".elf"}
|
|
for relative in validator._phase09d_paths(root):
|
|
require(
|
|
Path(relative).suffix.lower() not in forbidden,
|
|
f"target code/artifact appeared: {relative}",
|
|
)
|
|
|
|
@case("28 host tests are not hardware evidence")
|
|
def _() -> None:
|
|
changed = copy.deepcopy(manifest)
|
|
changed["final_decision"]["hardware_evidence_claimed"] = True
|
|
invalid_manifest(changed, "host test became hardware proof")
|
|
|
|
@case("29 every authorization field stays false")
|
|
def _() -> None:
|
|
for field in validator.AUTHORIZATION_FIELDS:
|
|
changed = copy.deepcopy(manifest)
|
|
changed["authorization"][field] = True
|
|
invalid_manifest(changed, f"authorization passed: {field}")
|
|
|
|
@case("30 Manager backup remains installation blocker")
|
|
def _() -> None:
|
|
changed = copy.deepcopy(manifest)
|
|
changed["canonical_state"]["payload_manager_backup"] = "COMPLETE"
|
|
invalid_manifest(changed, "Manager backup blocker was removed")
|
|
|
|
require(len(cases) == 30, f"expected 30 guardrails, found {len(cases)}")
|
|
require(
|
|
validator.validate_schema_instance(schema, manifest) == [],
|
|
"manifest does not satisfy schema",
|
|
)
|
|
|
|
failures: list[str] = []
|
|
for name, function in cases:
|
|
try:
|
|
function()
|
|
except Exception as error: # noqa: BLE001 - standalone test harness
|
|
failures.append(f"{name}: {error}")
|
|
if failures:
|
|
for failure in failures:
|
|
print(f"FAIL: {failure}")
|
|
return 1
|
|
print("Phase-0.9D readback guardrails: 30/30 PASS")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|