This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Host-only contract model for the blocked Phase-0.9B observer design.
|
||||
|
||||
This module performs no filesystem or network I/O. It is not PS5 observer
|
||||
source and must never be added to a target build.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
import hashlib
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class MockObject:
|
||||
path_known: bool = True
|
||||
path_conflict: bool = False
|
||||
symlink: bool = False
|
||||
object_id_before: str = "dev:1/ino:1"
|
||||
object_id_after: str = "dev:1/ino:1"
|
||||
size_before: int = 0
|
||||
size_after: int = 0
|
||||
chunks: tuple[bytes, ...] = ()
|
||||
read_error: bool = False
|
||||
expected_sha256: str | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class ObserverMock:
|
||||
output_limit: int = 64
|
||||
error_limit: int = 8
|
||||
emitted: list[dict[str, object]] = field(default_factory=list)
|
||||
errors: int = 0
|
||||
retry_count: int = 0
|
||||
persistent_write_count: int = 0
|
||||
service_or_process_mutation_count: int = 0
|
||||
listener_count: int = 0
|
||||
lifecycle_call_count: int = 0
|
||||
installer_call_count: int = 0
|
||||
graphics_or_retroarch_call_count: int = 0
|
||||
exit_reached: bool = False
|
||||
|
||||
def emit(self, category: str, confidence: str, raw_error: str | None) -> bool:
|
||||
if len(self.emitted) >= self.output_limit:
|
||||
self.errors += 1
|
||||
return False
|
||||
if raw_error is not None:
|
||||
self.errors += 1
|
||||
if self.errors > self.error_limit:
|
||||
return False
|
||||
self.emitted.append(
|
||||
{
|
||||
"category": category,
|
||||
"confidence": confidence,
|
||||
"raw_error": raw_error,
|
||||
"persistent_mutation_performed": False,
|
||||
"retry_performed": False,
|
||||
}
|
||||
)
|
||||
return True
|
||||
|
||||
def finish(self) -> None:
|
||||
self.exit_reached = True
|
||||
|
||||
|
||||
def evaluate_firmware(source_one: str | None, source_two: str | None) -> str:
|
||||
if source_one is None or source_two is None:
|
||||
return "UNPROVEN"
|
||||
if source_one != source_two:
|
||||
return "CONFLICT"
|
||||
return "OBSERVED"
|
||||
|
||||
|
||||
def evaluate_object(obj: MockObject) -> tuple[str, str | None]:
|
||||
if not obj.path_known:
|
||||
return "UNPROVEN", "UNKNOWN_PATH"
|
||||
if obj.path_conflict:
|
||||
return "CONFLICT", "PATH_CONFLICT"
|
||||
if obj.symlink:
|
||||
return "UNPROVEN", "PATH_SYMLINK_SAFETY_UNPROVEN"
|
||||
if obj.object_id_before != obj.object_id_after:
|
||||
return "ERROR", "OBJECT_ID_CHANGED"
|
||||
if obj.size_before != obj.size_after:
|
||||
return "ERROR", "SIZE_CHANGED"
|
||||
if obj.read_error:
|
||||
return "ERROR", "READ_ERROR"
|
||||
|
||||
data = b"".join(obj.chunks)
|
||||
if len(data) != obj.size_before:
|
||||
return "ERROR", "SHORT_READ"
|
||||
digest = hashlib.sha256(data).hexdigest()
|
||||
if obj.expected_sha256 is not None and digest != obj.expected_sha256:
|
||||
return "CONFLICT", "HASH_MISMATCH"
|
||||
return "OBSERVED", None
|
||||
|
||||
|
||||
def evaluate_live_backup(live: MockObject, backup: MockObject | None) -> str:
|
||||
if backup is None:
|
||||
return "BACKUP_MISSING"
|
||||
if live.object_id_before == backup.object_id_before:
|
||||
return "SAME_OBJECT"
|
||||
return "SEPARATE_OBJECTS"
|
||||
|
||||
|
||||
def unsupported_query(supported: bool) -> str:
|
||||
return "OBSERVED" if supported else "UNSUPPORTED_OR_UNPROVEN"
|
||||
|
||||
|
||||
def evaluate_autoload(source_present: bool, parse_ok: bool) -> str:
|
||||
if not source_present:
|
||||
return "UNPROVEN"
|
||||
return "OBSERVED" if parse_ok else "ERROR"
|
||||
|
||||
|
||||
def run_terminal_scenario(
|
||||
*,
|
||||
output_channel_ok: bool = True,
|
||||
deadline_reached: bool = False,
|
||||
record_count: int = 1,
|
||||
error_count: int = 0,
|
||||
) -> ObserverMock:
|
||||
observer = ObserverMock()
|
||||
if not output_channel_ok:
|
||||
observer.emit("output", "ERROR", "OUTPUT_CHANNEL_FAILED")
|
||||
observer.finish()
|
||||
return observer
|
||||
if deadline_reached:
|
||||
observer.emit("deadline", "ERROR", "DEADLINE_REACHED")
|
||||
observer.finish()
|
||||
return observer
|
||||
|
||||
for index in range(record_count):
|
||||
if not observer.emit(f"record-{index}", "OBSERVED", None):
|
||||
break
|
||||
for index in range(error_count):
|
||||
if not observer.emit(f"error-{index}", "ERROR", "MOCK_ERROR"):
|
||||
break
|
||||
observer.finish()
|
||||
return observer
|
||||
@@ -0,0 +1,579 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Host-only Phase-0.9C feasibility and result-protocol model.
|
||||
|
||||
This module performs no filesystem, network, compiler, or target operation.
|
||||
It is not observer source and must never be part of a PS5 target.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
import hashlib
|
||||
import struct
|
||||
|
||||
|
||||
PROOF_STATES = {
|
||||
"PROVEN_SAFE",
|
||||
"PROVEN_SIDE_EFFECTING",
|
||||
"UNPROVEN",
|
||||
"NOT_APPLICABLE",
|
||||
}
|
||||
|
||||
STARTUP_PATHS: dict[str, tuple[tuple[str, str], ...]] = {
|
||||
"normal_sdk": (
|
||||
("loader_entry_transfer", "PROVEN_SIDE_EFFECTING"),
|
||||
("bss_clear", "PROVEN_SIDE_EFFECTING"),
|
||||
("syscall_kernel_klog_init", "PROVEN_SIDE_EFFECTING"),
|
||||
("libc_isthreaded_write", "PROVEN_SIDE_EFFECTING"),
|
||||
("__patch_init", "PROVEN_SIDE_EFFECTING"),
|
||||
("rtld_init", "PROVEN_SIDE_EFFECTING"),
|
||||
("constructors", "PROVEN_SIDE_EFFECTING"),
|
||||
("main", "UNPROVEN"),
|
||||
("destructors_and_rtld_close", "UNPROVEN"),
|
||||
("terminate_branch", "UNPROVEN"),
|
||||
),
|
||||
"freestanding_theoretical": (
|
||||
("loader_entry_transfer", "PROVEN_SIDE_EFFECTING"),
|
||||
("stack_alignment", "UNPROVEN"),
|
||||
("bss_initialization", "UNPROVEN"),
|
||||
("relative_relocations_only", "PROVEN_SAFE"),
|
||||
("tls", "UNPROVEN"),
|
||||
("read_abi", "UNPROVEN"),
|
||||
("monotonic_time_abi", "UNPROVEN"),
|
||||
("return_continuation", "UNPROVEN"),
|
||||
("process_exit_abi", "UNPROVEN"),
|
||||
("cleanup", "UNPROVEN"),
|
||||
),
|
||||
"normal_return": (
|
||||
("ret_to_saved_rip", "PROVEN_SIDE_EFFECTING"),
|
||||
("original_process_continuation", "UNPROVEN"),
|
||||
("process_termination", "UNPROVEN"),
|
||||
("loader_cleanup", "UNPROVEN"),
|
||||
),
|
||||
"normal_process_exit": (
|
||||
("exit_import_or_raw_syscall", "UNPROVEN"),
|
||||
("process_teardown", "UNPROVEN"),
|
||||
("waitpid_reap", "PROVEN_SAFE"),
|
||||
("exit_status_delivery", "UNPROVEN"),
|
||||
("result_copyout", "UNPROVEN"),
|
||||
),
|
||||
"error_or_crash": (
|
||||
("partial_runtime_unwind", "UNPROVEN"),
|
||||
("waitpid_status_discarded", "PROVEN_SIDE_EFFECTING"),
|
||||
("fixed_ambiguous_response", "PROVEN_SIDE_EFFECTING"),
|
||||
),
|
||||
"timeout": (
|
||||
("sigterm", "PROVEN_SIDE_EFFECTING"),
|
||||
("sigkill", "PROVEN_SIDE_EFFECTING"),
|
||||
("reap", "PROVEN_SAFE"),
|
||||
("safe_exit", "UNPROVEN"),
|
||||
),
|
||||
}
|
||||
|
||||
PROHIBITED_STARTUP_EFFECTS = {
|
||||
"__patch_init",
|
||||
"kernel_copyin",
|
||||
"kernel_set_ucred_caps",
|
||||
"kernel_set_ucred_attrs",
|
||||
"syscall_permission_bound_write",
|
||||
"dynamic_module_loading",
|
||||
}
|
||||
|
||||
NORMAL_SDK_REACHABLE_PROHIBITED_EFFECTS = {
|
||||
"__patch_init",
|
||||
"kernel_copyin",
|
||||
"kernel_set_ucred_caps",
|
||||
"kernel_set_ucred_attrs",
|
||||
"syscall_permission_bound_write",
|
||||
"dynamic_module_loading",
|
||||
}
|
||||
|
||||
FREESTANDING_DEPENDENCY_CLOSURE = {
|
||||
"entry_address": "PROVEN_SAFE",
|
||||
"rdi_argument": "PROVEN_SAFE",
|
||||
"stack_alignment": "UNPROVEN",
|
||||
"saved_rip_continuation": "UNPROVEN",
|
||||
"bss_zero_fill": "UNPROVEN",
|
||||
"relative_relocation_subset": "PROVEN_SAFE",
|
||||
"complete_relocation_set": "UNPROVEN",
|
||||
"tls": "UNPROVEN",
|
||||
"constructors": "NOT_APPLICABLE",
|
||||
"libc": "NOT_APPLICABLE",
|
||||
"heap": "NOT_APPLICABLE",
|
||||
"callable_read_abi": "UNPROVEN",
|
||||
"callable_monotonic_time_abi": "UNPROVEN",
|
||||
"process_exit_abi": "UNPROVEN",
|
||||
"return_cleanup": "UNPROVEN",
|
||||
"bounded_result_copyout": "UNPROVEN",
|
||||
}
|
||||
|
||||
EXIT_PATHS = {
|
||||
"return": (
|
||||
"ENTRY",
|
||||
"RET_TO_SAVED_RIP",
|
||||
"ORIGINAL_PROCESS_CONTINUATION_UNPROVEN",
|
||||
"NO_SAFE_TERMINAL",
|
||||
),
|
||||
"process_exit": (
|
||||
"ENTRY",
|
||||
"EXIT_ABI_UNPROVEN",
|
||||
"CHILD_REAPED_STATUS_DISCARDED",
|
||||
"NO_RESULT_COPYOUT",
|
||||
),
|
||||
"crash": (
|
||||
"ENTRY",
|
||||
"FAULT",
|
||||
"CHILD_REAPED_STATUS_DISCARDED",
|
||||
"AMBIGUOUS_FIXED_RESPONSE",
|
||||
),
|
||||
"timeout": (
|
||||
"ENTRY",
|
||||
"DEADLINE",
|
||||
"SIGTERM",
|
||||
"SIGKILL",
|
||||
"REAPED",
|
||||
"INADMISSIBLE_TERMINATION",
|
||||
),
|
||||
}
|
||||
|
||||
SAFE_EXIT_TERMINALS: frozenset[str] = frozenset()
|
||||
|
||||
OUTPUT_ARCHITECTURES = {
|
||||
"D1_CALLER_OWNED_BOUNDED_BUFFER": (
|
||||
"CONCEPT_FEASIBLE_REQUIRES_LOADER_CHANGE_AND_EXIT_PROOF"
|
||||
),
|
||||
"D2_EXISTING_REQUEST_RESPONSE": "REJECTED_SEND_ONLY_NO_RESULT_RECEIVE",
|
||||
"D3_LOADER_OWNED_STATUS_RECORD": (
|
||||
"UNPROVEN_REQUIRES_LOADER_STATE_AND_PROPAGATION_CHANGE"
|
||||
),
|
||||
"D4_PROCESS_EXIT_STATUS": "REJECTED_WAIT_STATUS_DISCARDED_AND_AMBIGUOUS",
|
||||
}
|
||||
|
||||
|
||||
def validate_startup_model() -> list[str]:
|
||||
errors: list[str] = []
|
||||
required_paths = {
|
||||
"normal_sdk",
|
||||
"freestanding_theoretical",
|
||||
"normal_return",
|
||||
"normal_process_exit",
|
||||
"error_or_crash",
|
||||
"timeout",
|
||||
}
|
||||
if set(STARTUP_PATHS) != required_paths:
|
||||
errors.append("startup path inventory differs")
|
||||
for path_name, steps in STARTUP_PATHS.items():
|
||||
if not steps:
|
||||
errors.append(f"{path_name} has no steps")
|
||||
for step, classification in steps:
|
||||
if not step or classification not in PROOF_STATES:
|
||||
errors.append(f"{path_name} has invalid step {step}")
|
||||
if not (
|
||||
PROHIBITED_STARTUP_EFFECTS & NORMAL_SDK_REACHABLE_PROHIBITED_EFFECTS
|
||||
):
|
||||
errors.append("normal SDK prohibited effects were hidden")
|
||||
if SAFE_EXIT_TERMINALS:
|
||||
errors.append("an unproven safe exit terminal was added")
|
||||
return errors
|
||||
|
||||
|
||||
def freestanding_blockers() -> tuple[str, ...]:
|
||||
return tuple(
|
||||
key
|
||||
for key, classification in FREESTANDING_DEPENDENCY_CLOSURE.items()
|
||||
if classification == "UNPROVEN"
|
||||
)
|
||||
|
||||
|
||||
def exit_path_is_safe(path: str) -> bool:
|
||||
states = EXIT_PATHS[path]
|
||||
return bool(states and states[-1] in SAFE_EXIT_TERMINALS)
|
||||
|
||||
|
||||
MAGIC = b"CHG09C01"
|
||||
PROTOCOL_VERSION = 1
|
||||
HEADER_SIZE = 256
|
||||
MAX_OUTPUT_SIZE = 4096
|
||||
MAX_BODY_SIZE = MAX_OUTPUT_SIZE - HEADER_SIZE
|
||||
COMPLETION_MARKER = b"COMPLETE"
|
||||
FIRMWARE_FIELD_SIZE = 8
|
||||
NONCE_SIZE = 16
|
||||
REQUEST_ID_SIZE = 16
|
||||
|
||||
STATUS_SUCCESS = 1
|
||||
STATUS_OBSERVER_ERROR = 2
|
||||
STATUS_TIMEOUT = 3
|
||||
STATUS_FIRMWARE_CONFLICT = 4
|
||||
|
||||
FLAG_TRUNCATED = 1 << 0
|
||||
|
||||
CLEANUP_INCOMPLETE = 0
|
||||
CLEANUP_CLEAN = 1
|
||||
CLEANUP_FAILED = 2
|
||||
|
||||
OFFSET_MAGIC = 0
|
||||
OFFSET_VERSION = 8
|
||||
OFFSET_HEADER_SIZE = 10
|
||||
OFFSET_MAX_OUTPUT = 12
|
||||
OFFSET_ACTUAL_OUTPUT = 16
|
||||
OFFSET_OBSERVER_VERSION = 20
|
||||
OFFSET_STATUS = 24
|
||||
OFFSET_FLAGS = 28
|
||||
OFFSET_CLEANUP = 32
|
||||
OFFSET_RESERVED_WORD = 36
|
||||
OFFSET_REQUESTED = 40
|
||||
OFFSET_OBSERVED = 48
|
||||
OFFSET_UNSUPPORTED = 56
|
||||
OFFSET_DEADLINE = 64
|
||||
OFFSET_NONCE = 72
|
||||
OFFSET_REQUEST_ID = 88
|
||||
OFFSET_FIRMWARE_ONE = 104
|
||||
OFFSET_FIRMWARE_TWO = 112
|
||||
OFFSET_ARTIFACT_HASH = 120
|
||||
OFFSET_BODY_CHECKSUM = 152
|
||||
OFFSET_RESULT_CHECKSUM = 184
|
||||
OFFSET_COMPLETION = 216
|
||||
OFFSET_RESERVED = 224
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ResultRequest:
|
||||
execution_nonce: bytes
|
||||
request_id: bytes
|
||||
firmware_source_one: str | None
|
||||
firmware_source_two: str | None
|
||||
observer_version: int
|
||||
requested_capabilities: int
|
||||
artifact_sha256: bytes
|
||||
deadline_monotonic_ns: int
|
||||
|
||||
def validate(self) -> None:
|
||||
if len(self.execution_nonce) != NONCE_SIZE:
|
||||
raise ValueError("execution nonce must be 16 bytes")
|
||||
if len(self.request_id) != REQUEST_ID_SIZE:
|
||||
raise ValueError("request ID must be 16 bytes")
|
||||
if len(self.artifact_sha256) != 32:
|
||||
raise ValueError("artifact SHA-256 must be 32 bytes")
|
||||
if self.observer_version <= 0:
|
||||
raise ValueError("observer version must be positive")
|
||||
if not 0 <= self.requested_capabilities <= 0xFFFFFFFFFFFFFFFF:
|
||||
raise ValueError("requested capability bitmap is out of range")
|
||||
if not 0 < self.deadline_monotonic_ns <= 0xFFFFFFFFFFFFFFFF:
|
||||
raise ValueError("deadline is out of range")
|
||||
_encode_firmware(self.firmware_source_one)
|
||||
_encode_firmware(self.firmware_source_two)
|
||||
|
||||
|
||||
def _encode_firmware(value: str | None) -> bytes:
|
||||
if value is None:
|
||||
return b"\0" * FIRMWARE_FIELD_SIZE
|
||||
encoded = value.encode("ascii")
|
||||
if not encoded or b"\0" in encoded or len(encoded) >= FIRMWARE_FIELD_SIZE:
|
||||
raise ValueError("firmware ID is not canonical")
|
||||
return encoded.ljust(FIRMWARE_FIELD_SIZE, b"\0")
|
||||
|
||||
|
||||
def _decode_firmware(value: bytes) -> str | None:
|
||||
if value == b"\0" * FIRMWARE_FIELD_SIZE:
|
||||
return None
|
||||
first_nul = value.find(b"\0")
|
||||
if first_nul < 1 or any(value[first_nul:]):
|
||||
raise ValueError("firmware ID is not canonically padded")
|
||||
return value[:first_nul].decode("ascii")
|
||||
|
||||
|
||||
def _put_u16(buffer: bytearray, offset: int, value: int) -> None:
|
||||
struct.pack_into(">H", buffer, offset, value)
|
||||
|
||||
|
||||
def _put_u32(buffer: bytearray, offset: int, value: int) -> None:
|
||||
struct.pack_into(">I", buffer, offset, value)
|
||||
|
||||
|
||||
def _put_u64(buffer: bytearray, offset: int, value: int) -> None:
|
||||
struct.pack_into(">Q", buffer, offset, value)
|
||||
|
||||
|
||||
def _get_u16(buffer: bytes, offset: int) -> int:
|
||||
return struct.unpack_from(">H", buffer, offset)[0]
|
||||
|
||||
|
||||
def _get_u32(buffer: bytes, offset: int) -> int:
|
||||
return struct.unpack_from(">I", buffer, offset)[0]
|
||||
|
||||
|
||||
def _get_u64(buffer: bytes, offset: int) -> int:
|
||||
return struct.unpack_from(">Q", buffer, offset)[0]
|
||||
|
||||
|
||||
def _result_checksum(buffer: bytes, actual_output_size: int) -> bytes:
|
||||
candidate = bytearray(buffer[:actual_output_size])
|
||||
candidate[OFFSET_RESULT_CHECKSUM : OFFSET_RESULT_CHECKSUM + 32] = b"\0" * 32
|
||||
candidate[OFFSET_COMPLETION : OFFSET_COMPLETION + 8] = b"\0" * 8
|
||||
return hashlib.sha256(candidate).digest()
|
||||
|
||||
|
||||
def build_result(
|
||||
request: ResultRequest,
|
||||
body: bytes,
|
||||
*,
|
||||
status: int = STATUS_SUCCESS,
|
||||
observed_capabilities: int = 0,
|
||||
unsupported_capabilities: int = 0,
|
||||
cleanup_status: int = CLEANUP_CLEAN,
|
||||
truncate: bool = False,
|
||||
complete: bool = True,
|
||||
protocol_version: int = PROTOCOL_VERSION,
|
||||
) -> bytes:
|
||||
"""Build a deterministic host record as if observer then caller finalized it."""
|
||||
|
||||
request.validate()
|
||||
if not isinstance(body, bytes):
|
||||
raise TypeError("body must be bytes")
|
||||
if truncate and len(body) > MAX_BODY_SIZE:
|
||||
body = body[:MAX_BODY_SIZE]
|
||||
elif len(body) > MAX_BODY_SIZE:
|
||||
raise ValueError("body exceeds fixed result buffer")
|
||||
for value in (observed_capabilities, unsupported_capabilities):
|
||||
if not 0 <= value <= 0xFFFFFFFFFFFFFFFF:
|
||||
raise ValueError("capability bitmap is out of range")
|
||||
|
||||
actual_output_size = HEADER_SIZE + len(body)
|
||||
if actual_output_size < HEADER_SIZE or actual_output_size > MAX_OUTPUT_SIZE:
|
||||
raise ValueError("checked output-size arithmetic failed")
|
||||
|
||||
flags = FLAG_TRUNCATED if truncate else 0
|
||||
buffer = bytearray(MAX_OUTPUT_SIZE)
|
||||
buffer[OFFSET_MAGIC : OFFSET_MAGIC + 8] = MAGIC
|
||||
_put_u16(buffer, OFFSET_VERSION, protocol_version)
|
||||
_put_u16(buffer, OFFSET_HEADER_SIZE, HEADER_SIZE)
|
||||
_put_u32(buffer, OFFSET_MAX_OUTPUT, MAX_OUTPUT_SIZE)
|
||||
_put_u32(buffer, OFFSET_ACTUAL_OUTPUT, actual_output_size)
|
||||
_put_u32(buffer, OFFSET_OBSERVER_VERSION, request.observer_version)
|
||||
_put_u32(buffer, OFFSET_STATUS, status)
|
||||
_put_u32(buffer, OFFSET_FLAGS, flags)
|
||||
_put_u32(buffer, OFFSET_CLEANUP, cleanup_status)
|
||||
_put_u64(buffer, OFFSET_REQUESTED, request.requested_capabilities)
|
||||
_put_u64(buffer, OFFSET_OBSERVED, observed_capabilities)
|
||||
_put_u64(buffer, OFFSET_UNSUPPORTED, unsupported_capabilities)
|
||||
_put_u64(buffer, OFFSET_DEADLINE, request.deadline_monotonic_ns)
|
||||
buffer[OFFSET_NONCE : OFFSET_NONCE + NONCE_SIZE] = request.execution_nonce
|
||||
buffer[OFFSET_REQUEST_ID : OFFSET_REQUEST_ID + REQUEST_ID_SIZE] = (
|
||||
request.request_id
|
||||
)
|
||||
buffer[OFFSET_FIRMWARE_ONE : OFFSET_FIRMWARE_ONE + FIRMWARE_FIELD_SIZE] = (
|
||||
_encode_firmware(request.firmware_source_one)
|
||||
)
|
||||
buffer[OFFSET_FIRMWARE_TWO : OFFSET_FIRMWARE_TWO + FIRMWARE_FIELD_SIZE] = (
|
||||
_encode_firmware(request.firmware_source_two)
|
||||
)
|
||||
buffer[OFFSET_ARTIFACT_HASH : OFFSET_ARTIFACT_HASH + 32] = (
|
||||
request.artifact_sha256
|
||||
)
|
||||
buffer[HEADER_SIZE:actual_output_size] = body
|
||||
buffer[OFFSET_BODY_CHECKSUM : OFFSET_BODY_CHECKSUM + 32] = hashlib.sha256(
|
||||
body
|
||||
).digest()
|
||||
buffer[OFFSET_RESULT_CHECKSUM : OFFSET_RESULT_CHECKSUM + 32] = (
|
||||
_result_checksum(buffer, actual_output_size)
|
||||
)
|
||||
if complete:
|
||||
buffer[OFFSET_COMPLETION : OFFSET_COMPLETION + 8] = COMPLETION_MARKER
|
||||
return bytes(buffer)
|
||||
|
||||
|
||||
def validate_result(
|
||||
record: bytes, request: ResultRequest, *, now_monotonic_ns: int
|
||||
) -> str:
|
||||
"""Validate one fixed caller-owned record and return a fail-closed decision."""
|
||||
|
||||
try:
|
||||
request.validate()
|
||||
except (TypeError, ValueError):
|
||||
return "BLOCKED_INVALID_REQUEST"
|
||||
if len(record) != MAX_OUTPUT_SIZE:
|
||||
return "BLOCKED_WRONG_BUFFER_SIZE"
|
||||
if record[OFFSET_MAGIC : OFFSET_MAGIC + 8] != MAGIC:
|
||||
return "BLOCKED_BAD_MAGIC"
|
||||
if _get_u16(record, OFFSET_VERSION) != PROTOCOL_VERSION:
|
||||
return "BLOCKED_UNKNOWN_VERSION"
|
||||
if _get_u16(record, OFFSET_HEADER_SIZE) != HEADER_SIZE:
|
||||
return "BLOCKED_BAD_HEADER_SIZE"
|
||||
if _get_u32(record, OFFSET_MAX_OUTPUT) != MAX_OUTPUT_SIZE:
|
||||
return "BLOCKED_MAXIMUM_MISMATCH"
|
||||
|
||||
actual_output_size = _get_u32(record, OFFSET_ACTUAL_OUTPUT)
|
||||
if not HEADER_SIZE <= actual_output_size <= MAX_OUTPUT_SIZE:
|
||||
return "BLOCKED_ACTUAL_SIZE"
|
||||
if any(record[actual_output_size:]):
|
||||
return "BLOCKED_NONZERO_UNUSED_BYTES"
|
||||
if _get_u32(record, OFFSET_RESERVED_WORD) != 0 or any(
|
||||
record[OFFSET_RESERVED:HEADER_SIZE]
|
||||
):
|
||||
return "BLOCKED_RESERVED_DATA"
|
||||
if record[OFFSET_COMPLETION : OFFSET_COMPLETION + 8] != COMPLETION_MARKER:
|
||||
return "BLOCKED_INCOMPLETE"
|
||||
if (
|
||||
record[OFFSET_NONCE : OFFSET_NONCE + NONCE_SIZE]
|
||||
!= request.execution_nonce
|
||||
):
|
||||
return "BLOCKED_STALE_NONCE"
|
||||
if (
|
||||
record[OFFSET_REQUEST_ID : OFFSET_REQUEST_ID + REQUEST_ID_SIZE]
|
||||
!= request.request_id
|
||||
):
|
||||
return "BLOCKED_STALE_REQUEST_ID"
|
||||
if _get_u32(record, OFFSET_OBSERVER_VERSION) != request.observer_version:
|
||||
return "BLOCKED_OBSERVER_VERSION"
|
||||
if _get_u64(record, OFFSET_DEADLINE) != request.deadline_monotonic_ns:
|
||||
return "BLOCKED_DEADLINE_BINDING"
|
||||
if now_monotonic_ns > request.deadline_monotonic_ns:
|
||||
return "BLOCKED_TIMEOUT"
|
||||
if (
|
||||
record[OFFSET_ARTIFACT_HASH : OFFSET_ARTIFACT_HASH + 32]
|
||||
!= request.artifact_sha256
|
||||
):
|
||||
return "BLOCKED_ARTIFACT_HASH"
|
||||
|
||||
try:
|
||||
firmware_one = _decode_firmware(
|
||||
record[
|
||||
OFFSET_FIRMWARE_ONE : OFFSET_FIRMWARE_ONE + FIRMWARE_FIELD_SIZE
|
||||
]
|
||||
)
|
||||
firmware_two = _decode_firmware(
|
||||
record[
|
||||
OFFSET_FIRMWARE_TWO : OFFSET_FIRMWARE_TWO + FIRMWARE_FIELD_SIZE
|
||||
]
|
||||
)
|
||||
except (UnicodeDecodeError, ValueError):
|
||||
return "BLOCKED_FIRMWARE_ENCODING"
|
||||
if firmware_one is None:
|
||||
return "BLOCKED_FIRMWARE_SOURCE_1_ABSENT"
|
||||
if firmware_two is None:
|
||||
return "BLOCKED_FIRMWARE_SOURCE_2_ABSENT"
|
||||
if firmware_one != firmware_two:
|
||||
return "BLOCKED_FIRMWARE_CONFLICT"
|
||||
if firmware_one != "9.60":
|
||||
return "BLOCKED_FIRMWARE_MISMATCH"
|
||||
if (
|
||||
firmware_one != request.firmware_source_one
|
||||
or firmware_two != request.firmware_source_two
|
||||
):
|
||||
return "BLOCKED_FIRMWARE_BINDING"
|
||||
|
||||
expected_result_checksum = _result_checksum(record, actual_output_size)
|
||||
if (
|
||||
record[OFFSET_RESULT_CHECKSUM : OFFSET_RESULT_CHECKSUM + 32]
|
||||
!= expected_result_checksum
|
||||
):
|
||||
return "BLOCKED_RESULT_CHECKSUM"
|
||||
body = record[HEADER_SIZE:actual_output_size]
|
||||
if (
|
||||
record[OFFSET_BODY_CHECKSUM : OFFSET_BODY_CHECKSUM + 32]
|
||||
!= hashlib.sha256(body).digest()
|
||||
):
|
||||
return "BLOCKED_BODY_CHECKSUM"
|
||||
if _get_u32(record, OFFSET_FLAGS) & ~FLAG_TRUNCATED:
|
||||
return "BLOCKED_UNKNOWN_FLAGS"
|
||||
if _get_u32(record, OFFSET_FLAGS) & FLAG_TRUNCATED:
|
||||
return "BLOCKED_TRUNCATED"
|
||||
if _get_u32(record, OFFSET_CLEANUP) != CLEANUP_CLEAN:
|
||||
return "BLOCKED_CLEANUP_NOT_PROVEN"
|
||||
if _get_u32(record, OFFSET_STATUS) != STATUS_SUCCESS:
|
||||
return "BLOCKED_OBSERVER_FAILURE"
|
||||
|
||||
requested = _get_u64(record, OFFSET_REQUESTED)
|
||||
observed = _get_u64(record, OFFSET_OBSERVED)
|
||||
unsupported = _get_u64(record, OFFSET_UNSUPPORTED)
|
||||
if requested != request.requested_capabilities:
|
||||
return "BLOCKED_CAPABILITY_REQUEST_BINDING"
|
||||
if observed & unsupported:
|
||||
return "BLOCKED_CAPABILITY_BITMAP_CONFLICT"
|
||||
if (observed | unsupported) & ~requested:
|
||||
return "BLOCKED_UNREQUESTED_CAPABILITY"
|
||||
if (observed | unsupported) != requested:
|
||||
return "BLOCKED_INCOMPLETE_CAPABILITY_RESULT"
|
||||
if unsupported:
|
||||
return "VALID_RECORD_WITH_UNSUPPORTED_CAPABILITIES"
|
||||
return "VALID_COMPLETE_RESULT"
|
||||
|
||||
|
||||
@dataclass
|
||||
class ResultConsumer:
|
||||
consumed: set[tuple[bytes, bytes]] = field(default_factory=set)
|
||||
|
||||
def consume(
|
||||
self, record: bytes, request: ResultRequest, *, now_monotonic_ns: int
|
||||
) -> str:
|
||||
key = (request.execution_nonce, request.request_id)
|
||||
if key in self.consumed:
|
||||
return "BLOCKED_DUPLICATE_RESULT"
|
||||
result = validate_result(
|
||||
record, request, now_monotonic_ns=now_monotonic_ns
|
||||
)
|
||||
if result in {
|
||||
"VALID_COMPLETE_RESULT",
|
||||
"VALID_RECORD_WITH_UNSUPPORTED_CAPABILITIES",
|
||||
}:
|
||||
self.consumed.add(key)
|
||||
return result
|
||||
|
||||
|
||||
SIDE_EFFECT_MODEL = {
|
||||
"runtime_self_identity": {
|
||||
"semantic_readonly",
|
||||
"cache_effect_possible",
|
||||
"audit_effect_possible",
|
||||
},
|
||||
"firmware_query": {
|
||||
"semantic_readonly",
|
||||
"cache_effect_possible",
|
||||
"audit_effect_possible",
|
||||
"service_or_security_counter_effect_possible",
|
||||
},
|
||||
"filesystem_metadata": {
|
||||
"semantic_readonly",
|
||||
"metadata_effect_possible",
|
||||
"cache_effect_possible",
|
||||
"audit_effect_possible",
|
||||
"open_bookkeeping_effect_possible",
|
||||
"object_race_possible",
|
||||
},
|
||||
"filesystem_content_hash": {
|
||||
"semantic_readonly",
|
||||
"metadata_effect_possible",
|
||||
"atime_effect_possible",
|
||||
"cache_effect_possible",
|
||||
"audit_effect_possible",
|
||||
"open_bookkeeping_effect_possible",
|
||||
"object_race_possible",
|
||||
},
|
||||
"process_service_listener_snapshot": {
|
||||
"semantic_readonly",
|
||||
"cache_effect_possible",
|
||||
"audit_effect_possible",
|
||||
"counter_effect_possible",
|
||||
"process_accounting_effect_possible",
|
||||
"object_race_possible",
|
||||
},
|
||||
"autoload_read": {
|
||||
"semantic_readonly",
|
||||
"metadata_effect_possible",
|
||||
"atime_effect_possible",
|
||||
"cache_effect_possible",
|
||||
"audit_effect_possible",
|
||||
"service_state_effect_possible",
|
||||
"object_race_possible",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def classify_side_effects(operation: str) -> frozenset[str]:
|
||||
return frozenset(SIDE_EFFECT_MODEL[operation])
|
||||
|
||||
|
||||
def is_proven_side_effect_free(operation: str) -> bool:
|
||||
_ = SIDE_EFFECT_MODEL[operation]
|
||||
return False
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""In-memory transport double for Phase-1.0W policy tests only."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
class FakeTransportError(RuntimeError):
|
||||
"""Synthetic sequence error."""
|
||||
|
||||
|
||||
class FakeTransport:
|
||||
"""Record one synthetic session without any network primitives."""
|
||||
|
||||
def __init__(self, inbound_chunks: list[bytes]) -> None:
|
||||
self._inbound = list(inbound_chunks)
|
||||
self._plan: Any = None
|
||||
self._command_index = 0
|
||||
self._opened_once = False
|
||||
self._closed = False
|
||||
self.events: list[str] = []
|
||||
|
||||
def open_once(self, plan: Any) -> None:
|
||||
if self._opened_once:
|
||||
raise FakeTransportError("fake transport already opened")
|
||||
self._opened_once = True
|
||||
self._plan = plan
|
||||
self.events.append("OPEN")
|
||||
|
||||
def send_command_token(self, command: str) -> None:
|
||||
if not self._opened_once or self._closed or self._plan is None:
|
||||
raise FakeTransportError("fake transport is not open")
|
||||
if self._command_index >= len(self._plan.commands) or \
|
||||
command != self._plan.commands[self._command_index]:
|
||||
raise FakeTransportError("unexpected synthetic command")
|
||||
self._command_index += 1
|
||||
self.events.append(f"COMMAND_{command.upper()}")
|
||||
|
||||
def receive_chunk(self) -> bytes | None:
|
||||
if not self._opened_once or self._closed:
|
||||
raise FakeTransportError("fake transport is not open")
|
||||
if not self._inbound:
|
||||
return None
|
||||
self.events.append("RECEIVE")
|
||||
return self._inbound.pop(0)
|
||||
|
||||
def close_once(self) -> None:
|
||||
if not self._opened_once or self._closed:
|
||||
raise FakeTransportError("fake transport cannot close")
|
||||
self._closed = True
|
||||
self.events.append("CLOSE")
|
||||
@@ -0,0 +1,62 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include <chimera/gfx/adapters/retroarch.h>
|
||||
#include <chimera/gfx/adapters/sdl2.h>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
static int failures;
|
||||
|
||||
#define CHECK(expression) \
|
||||
do { \
|
||||
if (!(expression)) { \
|
||||
(void)fprintf(stderr, "%s:%d: check failed: %s\n", __FILE__, \
|
||||
__LINE__, #expression); \
|
||||
failures += 1; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
int main(void) {
|
||||
chimera_gfx_config config = CHIMERA_GFX_CONFIG_INIT;
|
||||
chimera_gfx_context *context = NULL;
|
||||
chimera_gfx_retroarch_adapter_info retroarch =
|
||||
CHIMERA_GFX_RETROARCH_ADAPTER_INFO_INIT;
|
||||
chimera_gfx_sdl2_adapter_info sdl2 = CHIMERA_GFX_SDL2_ADAPTER_INFO_INIT;
|
||||
|
||||
CHECK(chimera_gfx_retroarch_query_scaffold(NULL) ==
|
||||
CHIMERA_GFX_STATUS_INVALID_ARGUMENT);
|
||||
retroarch.struct_size = sizeof(retroarch) - 1u;
|
||||
CHECK(chimera_gfx_retroarch_query_scaffold(&retroarch) ==
|
||||
CHIMERA_GFX_STATUS_INVALID_ARGUMENT);
|
||||
retroarch = (chimera_gfx_retroarch_adapter_info)
|
||||
CHIMERA_GFX_RETROARCH_ADAPTER_INFO_INIT;
|
||||
CHECK(chimera_gfx_retroarch_query_scaffold(&retroarch) ==
|
||||
CHIMERA_GFX_STATUS_OK);
|
||||
CHECK(retroarch.supports_rgui == 1u);
|
||||
CHECK(retroarch.supports_hardware_contexts == 0u);
|
||||
CHECK(retroarch.accepts_software_frames == 0u);
|
||||
CHECK(chimera_gfx_sdl2_query_scaffold(NULL) ==
|
||||
CHIMERA_GFX_STATUS_INVALID_ARGUMENT);
|
||||
sdl2.struct_size = sizeof(sdl2) - 1u;
|
||||
CHECK(chimera_gfx_sdl2_query_scaffold(&sdl2) ==
|
||||
CHIMERA_GFX_STATUS_INVALID_ARGUMENT);
|
||||
sdl2 = (chimera_gfx_sdl2_adapter_info)CHIMERA_GFX_SDL2_ADAPTER_INFO_INIT;
|
||||
CHECK(chimera_gfx_sdl2_query_scaffold(&sdl2) == CHIMERA_GFX_STATUS_OK);
|
||||
CHECK(sdl2.accelerated_renderer_available == 0u);
|
||||
CHECK(sdl2.software_fallback_required == 1u);
|
||||
|
||||
CHECK(chimera_gfx_create(&config, &context) == CHIMERA_GFX_STATUS_OK);
|
||||
CHECK(chimera_gfx_retroarch_bind_scaffold(NULL, 0u) ==
|
||||
CHIMERA_GFX_STATUS_INVALID_ARGUMENT);
|
||||
CHECK(chimera_gfx_retroarch_bind_scaffold(context, 0u) ==
|
||||
CHIMERA_GFX_STATUS_UNSUPPORTED);
|
||||
CHECK(chimera_gfx_retroarch_bind_scaffold(context, 1u) ==
|
||||
CHIMERA_GFX_STATUS_SAFETY_POLICY);
|
||||
CHECK(chimera_gfx_sdl2_create_renderer_scaffold(NULL, 0u) ==
|
||||
CHIMERA_GFX_STATUS_INVALID_ARGUMENT);
|
||||
CHECK(chimera_gfx_sdl2_create_renderer_scaffold(context, 0u) ==
|
||||
CHIMERA_GFX_STATUS_UNSUPPORTED);
|
||||
CHECK(chimera_gfx_sdl2_create_renderer_scaffold(context, 1u) ==
|
||||
CHIMERA_GFX_STATUS_SAFETY_POLICY);
|
||||
CHECK(chimera_gfx_context_destroy(context) == CHIMERA_GFX_STATUS_OK);
|
||||
return failures == 0 ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Unit test for strict PS5 ELF import parsing."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
tool_path = args.root.resolve() / "tools/audit_ps5_artifacts.py"
|
||||
specification = importlib.util.spec_from_file_location("artifact_audit", tool_path)
|
||||
if specification is None or specification.loader is None:
|
||||
raise RuntimeError("could not load artifact-audit module")
|
||||
module = importlib.util.module_from_spec(specification)
|
||||
specification.loader.exec_module(module)
|
||||
|
||||
fixture = """
|
||||
U fprintf
|
||||
U sceKernelCreateEqueue
|
||||
U sceVideoOutOpen
|
||||
0000000000001234 T sceVideoOutClose
|
||||
"""
|
||||
imports = module.extract_sce_imports(fixture)
|
||||
if imports != {"sceKernelCreateEqueue", "sceVideoOutOpen"}:
|
||||
raise RuntimeError(f"unexpected parsed imports: {imports}")
|
||||
if len(module.EXPECTED_PHASE1_SCE_IMPORTS) != 15:
|
||||
raise RuntimeError("reviewed Phase-1 import inventory changed")
|
||||
if any(name.startswith("sceGnm") for name in module.EXPECTED_PHASE1_SCE_IMPORTS):
|
||||
raise RuntimeError("reviewed Phase-1 import inventory contains GNM")
|
||||
all_imports = module.extract_undefined_imports(fixture)
|
||||
if all_imports != {"fprintf", "sceKernelCreateEqueue", "sceVideoOutOpen"}:
|
||||
raise RuntimeError(f"unexpected full import inventory: {all_imports}")
|
||||
if module.EXPECTED_PROBE_UNDEFINED_IMPORTS != {
|
||||
"__stderrp",
|
||||
"__stdoutp",
|
||||
"fprintf",
|
||||
"fwrite",
|
||||
"snprintf",
|
||||
"strcmp",
|
||||
}:
|
||||
raise RuntimeError("reviewed probe import inventory changed")
|
||||
dynamic_fixture = """
|
||||
0x0000000000000001 (NEEDED) Shared library: [libkernel_web.sprx]
|
||||
0x0000000000000001 (NEEDED) Shared library: [libSceLibcInternal.sprx]
|
||||
0x0000000000000001 (NEEDED) Shared library: [libSceNet.sprx]
|
||||
"""
|
||||
if module.extract_needed(dynamic_fixture) != module.EXPECTED_PROBE_NEEDED:
|
||||
raise RuntimeError("reviewed probe DT_NEEDED inventory changed")
|
||||
print("PS5 artifact-audit parser passed")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,108 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include <chimera/gfx/chimera_gfx.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static int failures;
|
||||
|
||||
#define CHECK(expression) \
|
||||
do { \
|
||||
if (!(expression)) { \
|
||||
(void)fprintf(stderr, "%s:%d: check failed: %s\n", __FILE__, \
|
||||
__LINE__, #expression); \
|
||||
failures += 1; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static void test_valid_mock_context(void) {
|
||||
chimera_gfx_config config = CHIMERA_GFX_CONFIG_INIT;
|
||||
chimera_gfx_context *context = NULL;
|
||||
chimera_gfx_capabilities capabilities = CHIMERA_GFX_CAPABILITIES_INIT;
|
||||
|
||||
CHECK(chimera_gfx_create(&config, &context) == CHIMERA_GFX_STATUS_OK);
|
||||
CHECK(context != NULL);
|
||||
CHECK(chimera_gfx_get_capabilities(context, &capabilities) ==
|
||||
CHIMERA_GFX_STATUS_OK);
|
||||
CHECK(capabilities.api_version == CHIMERA_GFX_API_VERSION);
|
||||
CHECK(capabilities.backend == CHIMERA_GFX_BACKEND_MOCK);
|
||||
CHECK((capabilities.flags & CHIMERA_GFX_CAPABILITY_BACKEND_AVAILABLE) !=
|
||||
0u);
|
||||
CHECK((capabilities.flags & CHIMERA_GFX_CAPABILITY_NON_RENDERING) != 0u);
|
||||
CHECK((capabilities.flags & CHIMERA_GFX_CAPABILITY_HOST_TEST_ONLY) != 0u);
|
||||
CHECK((capabilities.flags & CHIMERA_GFX_CAPABILITY_PRESENT_MODEL) != 0u);
|
||||
CHECK(capabilities.max_live_surfaces == 16u);
|
||||
CHECK(capabilities.max_live_textures == 16u);
|
||||
CHECK(chimera_gfx_context_destroy(context) == CHIMERA_GFX_STATUS_OK);
|
||||
chimera_gfx_destroy(NULL);
|
||||
}
|
||||
|
||||
static void test_context_validation(void) {
|
||||
chimera_gfx_config config = CHIMERA_GFX_CONFIG_INIT;
|
||||
chimera_gfx_context *context = NULL;
|
||||
|
||||
CHECK(chimera_gfx_create(NULL, &context) ==
|
||||
CHIMERA_GFX_STATUS_INVALID_ARGUMENT);
|
||||
CHECK(context == NULL);
|
||||
CHECK(chimera_gfx_create(&config, NULL) ==
|
||||
CHIMERA_GFX_STATUS_INVALID_ARGUMENT);
|
||||
|
||||
config.struct_size = sizeof(config) - 1u;
|
||||
CHECK(chimera_gfx_create(&config, &context) ==
|
||||
CHIMERA_GFX_STATUS_INVALID_ARGUMENT);
|
||||
config.struct_size = sizeof(config);
|
||||
|
||||
config.api_version += 1u;
|
||||
CHECK(chimera_gfx_create(&config, &context) ==
|
||||
CHIMERA_GFX_STATUS_VERSION_MISMATCH);
|
||||
config.api_version = CHIMERA_GFX_API_VERSION;
|
||||
|
||||
config.flags = CHIMERA_GFX_CONFIG_ALLOW_HARDWARE_RENDERING;
|
||||
CHECK(chimera_gfx_create(&config, &context) ==
|
||||
CHIMERA_GFX_STATUS_SAFETY_POLICY);
|
||||
config.flags = 0u;
|
||||
|
||||
config.backend = CHIMERA_GFX_BACKEND_PS5;
|
||||
CHECK(chimera_gfx_create(&config, &context) ==
|
||||
CHIMERA_GFX_STATUS_SAFETY_POLICY);
|
||||
CHECK(context == NULL);
|
||||
config.backend = 99u;
|
||||
CHECK(chimera_gfx_create(&config, &context) ==
|
||||
CHIMERA_GFX_STATUS_UNSUPPORTED);
|
||||
}
|
||||
|
||||
static void test_capability_validation(void) {
|
||||
chimera_gfx_config config = CHIMERA_GFX_CONFIG_INIT;
|
||||
chimera_gfx_context *context = NULL;
|
||||
chimera_gfx_capabilities capabilities = CHIMERA_GFX_CAPABILITIES_INIT;
|
||||
|
||||
CHECK(chimera_gfx_create(&config, &context) == CHIMERA_GFX_STATUS_OK);
|
||||
CHECK(chimera_gfx_get_capabilities(NULL, &capabilities) ==
|
||||
CHIMERA_GFX_STATUS_INVALID_ARGUMENT);
|
||||
CHECK(chimera_gfx_get_capabilities(context, NULL) ==
|
||||
CHIMERA_GFX_STATUS_INVALID_ARGUMENT);
|
||||
capabilities.struct_size = sizeof(capabilities) - 1u;
|
||||
CHECK(chimera_gfx_get_capabilities(context, &capabilities) ==
|
||||
CHIMERA_GFX_STATUS_INVALID_ARGUMENT);
|
||||
CHECK(chimera_gfx_context_destroy(context) == CHIMERA_GFX_STATUS_OK);
|
||||
}
|
||||
|
||||
static void test_status_strings(void) {
|
||||
chimera_gfx_status status;
|
||||
|
||||
for (status = CHIMERA_GFX_STATUS_OK;
|
||||
status <= CHIMERA_GFX_STATUS_LIMIT_EXCEEDED;
|
||||
status = (chimera_gfx_status)((int)status + 1)) {
|
||||
CHECK(strcmp(chimera_gfx_status_string(status), "unknown status") != 0);
|
||||
}
|
||||
CHECK(strcmp(chimera_gfx_status_string((chimera_gfx_status)999),
|
||||
"unknown status") == 0);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_valid_mock_context();
|
||||
test_context_validation();
|
||||
test_capability_validation();
|
||||
test_status_strings();
|
||||
return failures == 0 ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Exercise the fail-closed artifact execution policy gate."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
BLOCKED_SHA256 = "4be1c17b4964f2b68c39b5145bc4af4619c32512d60269ecf5c39728b390fa63"
|
||||
|
||||
|
||||
def run(command: list[str], expected_decision: str) -> dict[str, object]:
|
||||
result = subprocess.run(command, check=False, capture_output=True, text=True)
|
||||
line = result.stdout.strip().splitlines()[-1]
|
||||
document = json.loads(line)
|
||||
if document.get("decision") != expected_decision:
|
||||
raise RuntimeError(f"unexpected decision: {document}")
|
||||
if expected_decision == "DENY" and result.returncode == 0:
|
||||
raise RuntimeError("denied input returned success")
|
||||
if expected_decision != "DENY" and result.returncode != 0:
|
||||
raise RuntimeError(f"eligible input failed: {result.stderr}")
|
||||
if document.get("execution_authorized") is not False:
|
||||
raise RuntimeError("policy gate must never grant execution authority")
|
||||
return document
|
||||
|
||||
|
||||
def without_option(command: list[str], option: str) -> list[str]:
|
||||
index = command.index(option)
|
||||
return command[:index] + command[index + 2 :]
|
||||
|
||||
|
||||
def write_manifest(path: Path, artifact: Path, eligible: bool) -> str:
|
||||
source_commit = "1" * 40
|
||||
path.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"artifact": {
|
||||
"filename": artifact.name,
|
||||
"id": "execution-policy-test",
|
||||
"sha256": hashlib.sha256(artifact.read_bytes()).hexdigest(),
|
||||
"size": artifact.stat().st_size,
|
||||
"target": "test",
|
||||
"version": "1",
|
||||
},
|
||||
"execution": {
|
||||
"authorized": False,
|
||||
"executed": False,
|
||||
"execution_eligible": eligible,
|
||||
"transferred": False,
|
||||
},
|
||||
"schema_version": 1,
|
||||
"source": {
|
||||
"commit": source_commit,
|
||||
"dirty": False,
|
||||
"repository": "private-gitea-test",
|
||||
},
|
||||
},
|
||||
indent=2,
|
||||
sort_keys=True,
|
||||
)
|
||||
+ "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
return source_commit
|
||||
|
||||
|
||||
def write_runtime_profile(
|
||||
path: Path,
|
||||
artifact: Path,
|
||||
source_commit: str,
|
||||
*,
|
||||
decision: str = "READY_FOR_HARDENED_RUNTIME_DEPLOYMENT",
|
||||
firmware: str = "9.60",
|
||||
effect: str = "PAYLOAD_PROCESS_LOCAL",
|
||||
) -> None:
|
||||
path.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"artifact": {
|
||||
"built": True,
|
||||
"filename": artifact.name,
|
||||
"id": "execution-policy-test",
|
||||
"sha256": hashlib.sha256(artifact.read_bytes()).hexdigest(),
|
||||
"size": artifact.stat().st_size,
|
||||
"source_commit": source_commit,
|
||||
},
|
||||
"budgets": {
|
||||
"automatic_retry": False,
|
||||
"filesystem_write_budget": "controlled_artifact_directory_only",
|
||||
"maximum_runtime_ms": 2000,
|
||||
"payload_network_access": "none",
|
||||
"persistent_write_budget": "controlled_artifact_removable",
|
||||
},
|
||||
"decision": decision,
|
||||
"deployment": {
|
||||
"installed": False,
|
||||
"ready_for_installation": True,
|
||||
"rollback_prepared": True,
|
||||
},
|
||||
"effects": [{"classification": effect, "id": "test_effect"}],
|
||||
"execution_authorized": False,
|
||||
"expected_volatile_effects": (
|
||||
["test_effect"]
|
||||
if effect == "EXPECTED_VOLATILE_RUNTIME_EFFECT"
|
||||
else []
|
||||
),
|
||||
"execution": {
|
||||
"authorized": False,
|
||||
"executed": False,
|
||||
"execution_eligible": True,
|
||||
"transferred": False,
|
||||
},
|
||||
"firmware": {
|
||||
"device_attested": False,
|
||||
"evidence": "jens_explicitly_confirmed_exact_9.60",
|
||||
"exact": firmware,
|
||||
},
|
||||
"hard_blockers": [],
|
||||
"payload_manager": {
|
||||
"base_commit": "cfbc70f30f419b09bf2b52283f7409e2d3117ee1",
|
||||
"hardened_commit": "e23d94ff91233aa770e2342800c1467875bdef44",
|
||||
"installed": False,
|
||||
"release": "v0.3.1-chimera-controlled-phase07",
|
||||
"reproducible": True,
|
||||
"sha256": (
|
||||
"8fecf8241a46246eddbd21e8bb4d875f5d76f1f4f4c6a11384df1f131aa5e5b1"
|
||||
),
|
||||
"size": 99560,
|
||||
},
|
||||
"loader": {
|
||||
"base_commit": "699e8bcff03e91e8d6ca6eba281af25c5a58d8c2",
|
||||
"hardened_commit": "197623058f509eddde18868dafcb92fdcac66464",
|
||||
"installed": False,
|
||||
"release": "v0.23-chimera-phase07",
|
||||
"reproducible": True,
|
||||
"sha256": (
|
||||
"63e810982471eb40cae3a20aa9df9a0a02892f420e429874fae4e99aa400b561"
|
||||
),
|
||||
"size": 397000,
|
||||
},
|
||||
"profile": "controlled-ps5-runtime",
|
||||
"schema_version": 1,
|
||||
"sdk": {
|
||||
"commit": "d2e2e585740362976a39fdd5ccf390f199a7bc37",
|
||||
"release": "v0.41",
|
||||
},
|
||||
},
|
||||
indent=2,
|
||||
sort_keys=True,
|
||||
)
|
||||
+ "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
root = args.root.resolve()
|
||||
denylist_path = root / "manifests/artifact-denylist.json"
|
||||
denylist = json.loads(denylist_path.read_text(encoding="utf-8"))
|
||||
if [entry["sha256"] for entry in denylist["entries"]] != [BLOCKED_SHA256]:
|
||||
raise RuntimeError("permanent denylist hash changed or is absent")
|
||||
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
temporary = Path(directory)
|
||||
artifact = temporary / "test.elf"
|
||||
artifact.write_bytes(b"chimera-execution-policy-test\n")
|
||||
manifest = temporary / "manifest.json"
|
||||
runtime_profile = temporary / "runtime-profile.json"
|
||||
command = [
|
||||
sys.executable,
|
||||
str(root / "tools/check_artifact_execution_policy.py"),
|
||||
"--manifest",
|
||||
str(manifest),
|
||||
"--denylist",
|
||||
str(denylist_path),
|
||||
"--artifact",
|
||||
str(artifact),
|
||||
"--runtime-profile",
|
||||
str(runtime_profile),
|
||||
"--firmware",
|
||||
"9.60",
|
||||
]
|
||||
|
||||
source_commit = write_manifest(manifest, artifact, eligible=False)
|
||||
write_runtime_profile(
|
||||
runtime_profile,
|
||||
artifact,
|
||||
source_commit,
|
||||
decision="BLOCKED_VERSION_OR_UNBOUNDED_EFFECT",
|
||||
)
|
||||
denied = run(command, "DENY")
|
||||
if "MANIFEST_EXECUTION_INELIGIBLE" not in denied["reason_codes"]:
|
||||
raise RuntimeError("execution-ineligible manifest was not refused")
|
||||
|
||||
source_commit = write_manifest(manifest, artifact, eligible=True)
|
||||
write_runtime_profile(runtime_profile, artifact, source_commit)
|
||||
run(command, "PASS_STATIC_DEPLOYMENT_ELIGIBILITY_GATE")
|
||||
|
||||
no_artifact = without_option(command, "--artifact")
|
||||
denied = run(no_artifact, "DENY")
|
||||
if "ARTIFACT_BYTES_NOT_SUPPLIED" not in denied["reason_codes"]:
|
||||
raise RuntimeError("missing artifact bytes did not fail closed")
|
||||
|
||||
no_profile = without_option(command, "--runtime-profile")
|
||||
denied = run(no_profile, "DENY")
|
||||
if "CONTROLLED_RUNTIME_PROFILE_REQUIRED" not in denied["reason_codes"]:
|
||||
raise RuntimeError("missing controlled runtime profile did not fail closed")
|
||||
|
||||
write_runtime_profile(
|
||||
runtime_profile,
|
||||
artifact,
|
||||
source_commit,
|
||||
effect="UNBOUNDED_OR_UNKNOWN",
|
||||
)
|
||||
denied = run(command, "DENY")
|
||||
if "RUNTIME_PROFILE_HARD_EFFECT" not in denied["reason_codes"]:
|
||||
raise RuntimeError("unbounded runtime effect did not fail closed")
|
||||
|
||||
write_runtime_profile(runtime_profile, artifact, source_commit)
|
||||
document = json.loads(runtime_profile.read_text(encoding="utf-8"))
|
||||
document["expected_volatile_effects"] = ["not_the_classified_effect"]
|
||||
runtime_profile.write_text(json.dumps(document), encoding="utf-8")
|
||||
denied = run(command, "DENY")
|
||||
if (
|
||||
"RUNTIME_PROFILE_VOLATILE_EFFECTS_MISMATCH"
|
||||
not in denied["reason_codes"]
|
||||
):
|
||||
raise RuntimeError("volatile-effect mismatch did not fail closed")
|
||||
|
||||
write_runtime_profile(runtime_profile, artifact, source_commit)
|
||||
document = json.loads(runtime_profile.read_text(encoding="utf-8"))
|
||||
del document["expected_volatile_effects"]
|
||||
runtime_profile.write_text(json.dumps(document), encoding="utf-8")
|
||||
denied = run(command, "DENY")
|
||||
if denied["reason_codes"] != ["INVALID_OR_INCOMPLETE_POLICY_INPUT"]:
|
||||
raise RuntimeError("missing volatile-effect declaration did not fail closed")
|
||||
|
||||
write_runtime_profile(runtime_profile, artifact, source_commit)
|
||||
document = json.loads(runtime_profile.read_text(encoding="utf-8"))
|
||||
document["execution_authorized"] = True
|
||||
runtime_profile.write_text(json.dumps(document), encoding="utf-8")
|
||||
denied = run(command, "DENY")
|
||||
if (
|
||||
"RUNTIME_PROFILE_EXECUTION_STATE_INVALID"
|
||||
not in denied["reason_codes"]
|
||||
):
|
||||
raise RuntimeError("runtime authorization widening did not fail closed")
|
||||
|
||||
write_runtime_profile(runtime_profile, artifact, source_commit)
|
||||
document = json.loads(runtime_profile.read_text(encoding="utf-8"))
|
||||
document["payload_manager"]["hardened_commit"] = "0" * 40
|
||||
runtime_profile.write_text(json.dumps(document), encoding="utf-8")
|
||||
denied = run(command, "DENY")
|
||||
if "PAYLOAD_MANAGER_IDENTITY_MISMATCH" not in denied["reason_codes"]:
|
||||
raise RuntimeError("Payload Manager identity mismatch did not fail closed")
|
||||
|
||||
write_runtime_profile(
|
||||
runtime_profile,
|
||||
artifact,
|
||||
source_commit,
|
||||
effect="NOT_A_CLASSIFICATION",
|
||||
)
|
||||
denied = run(command, "DENY")
|
||||
if denied["reason_codes"] != ["INVALID_OR_INCOMPLETE_POLICY_INPUT"]:
|
||||
raise RuntimeError("unknown effect classification did not fail closed")
|
||||
|
||||
write_runtime_profile(runtime_profile, artifact, source_commit)
|
||||
document = json.loads(runtime_profile.read_text(encoding="utf-8"))
|
||||
document["profile"] = "controlled-ps5-lifecycle-v1"
|
||||
runtime_profile.write_text(json.dumps(document), encoding="utf-8")
|
||||
denied = run(command, "DENY")
|
||||
if denied["reason_codes"] != ["INVALID_OR_INCOMPLETE_POLICY_INPUT"]:
|
||||
raise RuntimeError("wrong runtime profile name did not fail closed")
|
||||
|
||||
write_runtime_profile(runtime_profile, artifact, source_commit)
|
||||
document = json.loads(manifest.read_text(encoding="utf-8"))
|
||||
document["execution"]["transferred"] = True
|
||||
manifest.write_text(json.dumps(document), encoding="utf-8")
|
||||
denied = run(command, "DENY")
|
||||
if "MANIFEST_EXECUTION_STATE_INVALID" not in denied["reason_codes"]:
|
||||
raise RuntimeError("manifest transfer claim did not fail closed")
|
||||
|
||||
source_commit = write_manifest(manifest, artifact, eligible=True)
|
||||
write_runtime_profile(runtime_profile, artifact, source_commit)
|
||||
mismatched_firmware = command.copy()
|
||||
mismatched_firmware[-1] = "9.40"
|
||||
denied = run(mismatched_firmware, "DENY")
|
||||
if "FIRMWARE_MISMATCH" not in denied["reason_codes"]:
|
||||
raise RuntimeError("firmware mismatch did not fail closed")
|
||||
|
||||
document = json.loads(manifest.read_text(encoding="utf-8"))
|
||||
document["artifact"]["sha256"] = BLOCKED_SHA256
|
||||
manifest.write_text(json.dumps(document), encoding="utf-8")
|
||||
denied = run(no_artifact, "DENY")
|
||||
if "ARTIFACT_PERMANENTLY_DENYLISTED" not in denied["reason_codes"]:
|
||||
raise RuntimeError("denylisted hash was not refused")
|
||||
|
||||
del document["execution"]["execution_eligible"]
|
||||
manifest.write_text(json.dumps(document), encoding="utf-8")
|
||||
denied = run(no_artifact, "DENY")
|
||||
if denied["reason_codes"] != ["INVALID_OR_INCOMPLETE_POLICY_INPUT"]:
|
||||
raise RuntimeError("missing eligibility did not fail closed")
|
||||
|
||||
artifact.write_bytes(b"changed\n")
|
||||
source_commit = write_manifest(manifest, artifact, eligible=True)
|
||||
write_runtime_profile(runtime_profile, artifact, source_commit)
|
||||
artifact.write_bytes(b"changed-again\n")
|
||||
denied = run(command, "DENY")
|
||||
if "ARTIFACT_DIGEST_MISMATCH" not in denied["reason_codes"]:
|
||||
raise RuntimeError("changed bytes were not refused")
|
||||
|
||||
print("artifact execution policy gate passed all refusal tests")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,27 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include "firmware_gate.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
static int failures;
|
||||
|
||||
#define CHECK(expression) \
|
||||
do { \
|
||||
if (!(expression)) { \
|
||||
(void)fprintf(stderr, "%s:%d: check failed: %s\n", __FILE__, \
|
||||
__LINE__, #expression); \
|
||||
failures += 1; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
int main(void) {
|
||||
CHECK(chimera_gfx_firmware_gate_allows("NONE", "NONE") == 0);
|
||||
CHECK(chimera_gfx_firmware_gate_allows("NONE", "13.40") == 0);
|
||||
CHECK(chimera_gfx_firmware_gate_allows("13.40", "13.40") == 1);
|
||||
CHECK(chimera_gfx_firmware_gate_allows("13.40", "13.4") == 0);
|
||||
CHECK(chimera_gfx_firmware_gate_allows("", "") == 0);
|
||||
CHECK(chimera_gfx_firmware_gate_allows("13.40", "") == 0);
|
||||
CHECK(chimera_gfx_firmware_gate_allows(NULL, "13.40") == 0);
|
||||
CHECK(chimera_gfx_firmware_gate_allows("13.40", NULL) == 0);
|
||||
return failures == 0 ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Integration test for deterministic artifact generation and verification."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def expect_verification_failure(command: list[str], description: str) -> None:
|
||||
result = subprocess.run(command, check=False, capture_output=True, text=True)
|
||||
if result.returncode == 0:
|
||||
raise RuntimeError(f"verification unexpectedly accepted {description}")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
root = args.root.resolve()
|
||||
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
temporary = Path(directory)
|
||||
artifact = temporary / "test.elf"
|
||||
manifest = temporary / "test.json"
|
||||
payload = b"chimera-gfx-test-artifact\n"
|
||||
artifact.write_bytes(payload)
|
||||
subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
str(root / "tools/generate_artifact_manifest.py"),
|
||||
"--artifact",
|
||||
str(artifact),
|
||||
"--output",
|
||||
str(manifest),
|
||||
"--id",
|
||||
"manifest-tool-test",
|
||||
"--version",
|
||||
"1",
|
||||
"--source-repository",
|
||||
"test://chimera-gfx",
|
||||
"--source-commit",
|
||||
"0" * 40,
|
||||
"--target",
|
||||
"test",
|
||||
"--profile",
|
||||
"unit-test",
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
document = json.loads(manifest.read_text(encoding="utf-8"))
|
||||
if document["artifact"]["sha256"] != hashlib.sha256(payload).hexdigest():
|
||||
raise RuntimeError("generated digest differs from expected digest")
|
||||
if document["execution"].get("execution_eligible") is not False:
|
||||
raise RuntimeError("new artifacts must default to execution-ineligible")
|
||||
verify_command = [
|
||||
sys.executable,
|
||||
str(root / "tools/verify_artifact_manifest.py"),
|
||||
"--manifest",
|
||||
str(manifest),
|
||||
"--artifact",
|
||||
str(artifact),
|
||||
]
|
||||
subprocess.run(verify_command, check=True)
|
||||
|
||||
artifact.write_bytes(payload + b"changed")
|
||||
expect_verification_failure(verify_command, "changed artifact bytes")
|
||||
artifact.write_bytes(payload)
|
||||
|
||||
document["execution"]["authorized"] = True
|
||||
manifest.write_text(
|
||||
json.dumps(document, indent=2, sort_keys=True) + "\n", encoding="utf-8"
|
||||
)
|
||||
expect_verification_failure(verify_command, "execution authority")
|
||||
|
||||
document["execution"]["authorized"] = False
|
||||
del document["execution"]["execution_eligible"]
|
||||
manifest.write_text(
|
||||
json.dumps(document, indent=2, sort_keys=True) + "\n", encoding="utf-8"
|
||||
)
|
||||
expect_verification_failure(verify_command, "missing execution eligibility")
|
||||
print("artifact manifest generation and verification passed")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Prove that an unreviewed minimal PS5 startup cannot enter the build graph."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import subprocess
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
parser.add_argument("--cmake", type=Path, required=True)
|
||||
parser.add_argument("--compiler", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
result = subprocess.run(
|
||||
[
|
||||
str(args.cmake),
|
||||
"-S",
|
||||
str(args.root.resolve()),
|
||||
"-B",
|
||||
directory,
|
||||
"-G",
|
||||
"Ninja",
|
||||
f"-DCMAKE_C_COMPILER={args.compiler}",
|
||||
"-DBUILD_TESTING=OFF",
|
||||
"-DCHIMERA_GFX_BUILD_PS5_MINIMAL_STARTUP=ON",
|
||||
],
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
output = result.stdout + result.stderr
|
||||
if result.returncode == 0:
|
||||
raise RuntimeError("minimal startup configure unexpectedly succeeded")
|
||||
required = (
|
||||
"BLOCKED",
|
||||
"no pinned public loader caller",
|
||||
"no minimal PS5 ELF may be built",
|
||||
)
|
||||
if any(snippet not in output for snippet in required):
|
||||
raise RuntimeError(f"configure failed without the policy reason:\n{output}")
|
||||
print("minimal PS5 startup build remains fail-closed")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,169 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include <chimera/gfx/chimera_gfx.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
static int failures;
|
||||
|
||||
#define CHECK(expression) \
|
||||
do { \
|
||||
if (!(expression)) { \
|
||||
(void)fprintf(stderr, "%s:%d: check failed: %s\n", __FILE__, \
|
||||
__LINE__, #expression); \
|
||||
failures += 1; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static void test_complete_lifecycle(void) {
|
||||
static const uint32_t pixels[4] = {
|
||||
UINT32_C(0xff0000ff), UINT32_C(0xff00ff00), UINT32_C(0xffff0000),
|
||||
UINT32_C(0xffffffff)};
|
||||
chimera_gfx_config config = CHIMERA_GFX_CONFIG_INIT;
|
||||
chimera_gfx_context *context = NULL;
|
||||
chimera_gfx_surface *surface = NULL;
|
||||
chimera_gfx_texture *texture = NULL;
|
||||
chimera_gfx_surface_desc surface_desc =
|
||||
CHIMERA_GFX_SURFACE_DESC_INIT(2u, 2u);
|
||||
chimera_gfx_texture_desc texture_desc =
|
||||
CHIMERA_GFX_TEXTURE_DESC_INIT(2u, 2u);
|
||||
chimera_gfx_texture_upload_info upload = CHIMERA_GFX_TEXTURE_UPLOAD_INIT(
|
||||
pixels, 2u * sizeof(uint32_t), sizeof(pixels));
|
||||
chimera_gfx_present_info present_info;
|
||||
chimera_gfx_surface_stats stats = CHIMERA_GFX_SURFACE_STATS_INIT;
|
||||
|
||||
CHECK(chimera_gfx_create(&config, &context) == CHIMERA_GFX_STATUS_OK);
|
||||
CHECK(chimera_gfx_surface_create(context, &surface_desc, &surface) ==
|
||||
CHIMERA_GFX_STATUS_OK);
|
||||
CHECK(chimera_gfx_texture_create(context, &texture_desc, &texture) ==
|
||||
CHIMERA_GFX_STATUS_OK);
|
||||
present_info = (chimera_gfx_present_info)CHIMERA_GFX_PRESENT_INFO_INIT(
|
||||
surface, texture);
|
||||
CHECK(chimera_gfx_present(context, &present_info) ==
|
||||
CHIMERA_GFX_STATUS_INVALID_STATE);
|
||||
CHECK(chimera_gfx_texture_upload(texture, &upload) ==
|
||||
CHIMERA_GFX_STATUS_OK);
|
||||
CHECK(chimera_gfx_present(context, &present_info) == CHIMERA_GFX_STATUS_OK);
|
||||
CHECK(chimera_gfx_present(context, &present_info) == CHIMERA_GFX_STATUS_OK);
|
||||
CHECK(chimera_gfx_surface_get_stats(surface, &stats) ==
|
||||
CHIMERA_GFX_STATUS_OK);
|
||||
CHECK(stats.api_version == CHIMERA_GFX_API_VERSION);
|
||||
CHECK(stats.present_count == 2u);
|
||||
CHECK(stats.last_present_serial == 2u);
|
||||
CHECK(stats.last_texture_hash != 0u);
|
||||
CHECK(chimera_gfx_context_destroy(context) ==
|
||||
CHIMERA_GFX_STATUS_RESOURCE_BUSY);
|
||||
CHECK(chimera_gfx_texture_destroy(texture) == CHIMERA_GFX_STATUS_OK);
|
||||
CHECK(chimera_gfx_surface_destroy(surface) == CHIMERA_GFX_STATUS_OK);
|
||||
CHECK(chimera_gfx_texture_destroy(NULL) == CHIMERA_GFX_STATUS_OK);
|
||||
CHECK(chimera_gfx_surface_destroy(NULL) == CHIMERA_GFX_STATUS_OK);
|
||||
CHECK(chimera_gfx_context_destroy(context) == CHIMERA_GFX_STATUS_OK);
|
||||
}
|
||||
|
||||
static void test_resource_validation(void) {
|
||||
static const uint32_t pixels[4] = {0u, 0u, 0u, 0u};
|
||||
chimera_gfx_config config = CHIMERA_GFX_CONFIG_INIT;
|
||||
chimera_gfx_context *context = NULL;
|
||||
chimera_gfx_surface *surface = NULL;
|
||||
chimera_gfx_texture *texture = NULL;
|
||||
chimera_gfx_texture *invalid_texture = NULL;
|
||||
chimera_gfx_surface_desc surface_desc =
|
||||
CHIMERA_GFX_SURFACE_DESC_INIT(2u, 2u);
|
||||
chimera_gfx_texture_desc texture_desc =
|
||||
CHIMERA_GFX_TEXTURE_DESC_INIT(2u, 2u);
|
||||
chimera_gfx_texture_upload_info upload = CHIMERA_GFX_TEXTURE_UPLOAD_INIT(
|
||||
pixels, sizeof(uint32_t), sizeof(pixels));
|
||||
|
||||
CHECK(chimera_gfx_create(&config, &context) == CHIMERA_GFX_STATUS_OK);
|
||||
CHECK(chimera_gfx_surface_create(context, &surface_desc, &surface) ==
|
||||
CHIMERA_GFX_STATUS_OK);
|
||||
CHECK(chimera_gfx_texture_create(context, &texture_desc, &texture) ==
|
||||
CHIMERA_GFX_STATUS_OK);
|
||||
CHECK(chimera_gfx_texture_upload(texture, &upload) ==
|
||||
CHIMERA_GFX_STATUS_INVALID_ARGUMENT);
|
||||
texture_desc.api_version += 1u;
|
||||
CHECK(
|
||||
chimera_gfx_texture_create(context, &texture_desc, &invalid_texture) ==
|
||||
CHIMERA_GFX_STATUS_VERSION_MISMATCH);
|
||||
CHECK(invalid_texture == NULL);
|
||||
CHECK(chimera_gfx_surface_destroy(surface) == CHIMERA_GFX_STATUS_OK);
|
||||
CHECK(chimera_gfx_texture_destroy(texture) == CHIMERA_GFX_STATUS_OK);
|
||||
CHECK(chimera_gfx_context_destroy(context) == CHIMERA_GFX_STATUS_OK);
|
||||
}
|
||||
|
||||
static void test_ownership_dimensions_and_limits(void) {
|
||||
static const uint32_t pixels[2] = {0u, 0u};
|
||||
chimera_gfx_config config = CHIMERA_GFX_CONFIG_INIT;
|
||||
chimera_gfx_context *first_context = NULL;
|
||||
chimera_gfx_context *second_context = NULL;
|
||||
chimera_gfx_surface *surface = NULL;
|
||||
chimera_gfx_surface *surfaces[17] = {NULL};
|
||||
chimera_gfx_texture *texture = NULL;
|
||||
chimera_gfx_surface_desc surface_desc =
|
||||
CHIMERA_GFX_SURFACE_DESC_INIT(2u, 2u);
|
||||
chimera_gfx_texture_desc texture_desc =
|
||||
CHIMERA_GFX_TEXTURE_DESC_INIT(1u, 2u);
|
||||
chimera_gfx_texture_upload_info upload = CHIMERA_GFX_TEXTURE_UPLOAD_INIT(
|
||||
pixels, sizeof(uint32_t), sizeof(pixels));
|
||||
chimera_gfx_present_info present_info;
|
||||
chimera_gfx_surface_stats stats = CHIMERA_GFX_SURFACE_STATS_INIT;
|
||||
size_t index;
|
||||
|
||||
CHECK(chimera_gfx_create(&config, &first_context) == CHIMERA_GFX_STATUS_OK);
|
||||
CHECK(chimera_gfx_create(&config, &second_context) ==
|
||||
CHIMERA_GFX_STATUS_OK);
|
||||
CHECK(chimera_gfx_surface_create(first_context, &surface_desc, &surface) ==
|
||||
CHIMERA_GFX_STATUS_OK);
|
||||
CHECK(chimera_gfx_texture_create(second_context, &texture_desc, &texture) ==
|
||||
CHIMERA_GFX_STATUS_OK);
|
||||
CHECK(chimera_gfx_texture_upload(texture, &upload) ==
|
||||
CHIMERA_GFX_STATUS_OK);
|
||||
present_info = (chimera_gfx_present_info)CHIMERA_GFX_PRESENT_INFO_INIT(
|
||||
surface, texture);
|
||||
CHECK(chimera_gfx_present(first_context, &present_info) ==
|
||||
CHIMERA_GFX_STATUS_INVALID_ARGUMENT);
|
||||
CHECK(chimera_gfx_texture_destroy(texture) == CHIMERA_GFX_STATUS_OK);
|
||||
CHECK(chimera_gfx_context_destroy(second_context) == CHIMERA_GFX_STATUS_OK);
|
||||
texture = NULL;
|
||||
CHECK(chimera_gfx_texture_create(first_context, &texture_desc, &texture) ==
|
||||
CHIMERA_GFX_STATUS_OK);
|
||||
CHECK(chimera_gfx_texture_upload(texture, &upload) ==
|
||||
CHIMERA_GFX_STATUS_OK);
|
||||
present_info = (chimera_gfx_present_info)CHIMERA_GFX_PRESENT_INFO_INIT(
|
||||
surface, texture);
|
||||
CHECK(chimera_gfx_present(first_context, &present_info) ==
|
||||
CHIMERA_GFX_STATUS_INVALID_ARGUMENT);
|
||||
CHECK(chimera_gfx_texture_destroy(texture) == CHIMERA_GFX_STATUS_OK);
|
||||
stats.struct_size = sizeof(stats) - 1u;
|
||||
CHECK(chimera_gfx_surface_get_stats(surface, &stats) ==
|
||||
CHIMERA_GFX_STATUS_INVALID_ARGUMENT);
|
||||
CHECK(chimera_gfx_surface_destroy(surface) == CHIMERA_GFX_STATUS_OK);
|
||||
|
||||
surface_desc =
|
||||
(chimera_gfx_surface_desc)CHIMERA_GFX_SURFACE_DESC_INIT(1u, 1u);
|
||||
for (index = 0u; index < 16u; ++index) {
|
||||
CHECK(chimera_gfx_surface_create(first_context, &surface_desc,
|
||||
&surfaces[index]) ==
|
||||
CHIMERA_GFX_STATUS_OK);
|
||||
}
|
||||
CHECK(chimera_gfx_surface_create(first_context, &surface_desc,
|
||||
&surfaces[16]) ==
|
||||
CHIMERA_GFX_STATUS_LIMIT_EXCEEDED);
|
||||
CHECK(surfaces[16] == NULL);
|
||||
for (index = 0u; index < 16u; ++index) {
|
||||
CHECK(chimera_gfx_surface_destroy(surfaces[index]) ==
|
||||
CHIMERA_GFX_STATUS_OK);
|
||||
}
|
||||
surface_desc.width = 4097u;
|
||||
CHECK(chimera_gfx_surface_create(first_context, &surface_desc, &surface) ==
|
||||
CHIMERA_GFX_STATUS_LIMIT_EXCEEDED);
|
||||
CHECK(surface == NULL);
|
||||
CHECK(chimera_gfx_context_destroy(first_context) == CHIMERA_GFX_STATUS_OK);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_complete_lifecycle();
|
||||
test_resource_validation();
|
||||
test_ownership_dimensions_and_limits();
|
||||
return failures == 0 ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Validate the fail-closed Phase-0.5 machine-readable decision records."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
BLOCKED_SHA256 = "4be1c17b4964f2b68c39b5145bc4af4619c32512d60269ecf5c39728b390fa63"
|
||||
|
||||
|
||||
def load(path: Path) -> dict[str, object]:
|
||||
document = json.loads(path.read_text(encoding="utf-8"))
|
||||
if not isinstance(document, dict):
|
||||
raise RuntimeError(f"{path}: expected object")
|
||||
return document
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
root = args.root.resolve()
|
||||
|
||||
audit = load(root / "manifests/runtime/phase-0.5-startup-audit.json")
|
||||
decision = load(root / "manifests/runtime/minimal-startup-artifact-decision.json")
|
||||
matrix = load(root / "manifests/runtime/kernelwrite-proof-matrix.json")
|
||||
denylist = load(root / "manifests/artifact-denylist.json")
|
||||
|
||||
if audit.get("decision") != "BLOCKED" or audit["artifact"]["built"] is not False:
|
||||
raise RuntimeError("startup audit no longer blocks artifact construction")
|
||||
if audit["artifact"]["execution_eligible"] is not False:
|
||||
raise RuntimeError("startup audit claims execution eligibility")
|
||||
if audit["loader_evidence"]["caller_source_present"] is not False:
|
||||
raise RuntimeError("audit claims an unreviewed loader caller")
|
||||
required_reachable = {
|
||||
"__patch_init",
|
||||
"kernel_copyin",
|
||||
"kernel_copyout",
|
||||
"kernel_set_ucred_attrs",
|
||||
"kernel_set_ucred_caps",
|
||||
}
|
||||
reachable = set(audit["crt1_static_evidence"]["reachable_prohibited_functions"])
|
||||
if reachable != required_reachable:
|
||||
raise RuntimeError(f"reachable kernel-write inventory changed: {reachable}")
|
||||
|
||||
if decision.get("decision") != "BLOCKED" or decision["artifact"]["built"] is not False:
|
||||
raise RuntimeError("non-build decision changed")
|
||||
if decision["execution"]["execution_eligible"] is not False:
|
||||
raise RuntimeError("non-build record claims execution eligibility")
|
||||
if any(value is not None for value in (
|
||||
decision["artifact"]["filename"],
|
||||
decision["artifact"]["sha256"],
|
||||
decision["artifact"]["size"],
|
||||
)):
|
||||
raise RuntimeError("non-build decision fabricates artifact bytes")
|
||||
|
||||
statuses = {entry["status"] for entry in matrix["entries"]}
|
||||
if statuses != {"SAFE", "UNSAFE", "UNPROVEN"}:
|
||||
raise RuntimeError(f"review matrix lacks a status class: {statuses}")
|
||||
if [entry["sha256"] for entry in denylist["entries"]] != [BLOCKED_SHA256]:
|
||||
raise RuntimeError("permanent blocked hash changed")
|
||||
print("Phase-0.5 machine-readable BLOCKED decision is consistent")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Validate the fail-closed Phase-0.6 exact-loader evidence."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def load(path: Path) -> dict[str, object]:
|
||||
document = json.loads(path.read_text(encoding="utf-8"))
|
||||
if not isinstance(document, dict):
|
||||
raise RuntimeError(f"{path}: expected an object")
|
||||
return document
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
root = args.root.resolve()
|
||||
|
||||
audit = load(root / "manifests/runtime/phase-0.6-loader-runtime-audit.json")
|
||||
denylist = load(root / "manifests/artifact-denylist.json")
|
||||
|
||||
decision = "BLOCKED_VERSION_OR_UNBOUNDED_EFFECT"
|
||||
if audit.get("decision") != decision:
|
||||
raise RuntimeError("Phase-0.6 no longer fails closed")
|
||||
if audit["artifact"]["built"] is not False:
|
||||
raise RuntimeError("Phase-0.6 unexpectedly produced an ELF")
|
||||
if audit["identity"]["elfldr"]["installed_asset_hash_match"] is not True:
|
||||
raise RuntimeError("installed elfldr exact identity is no longer proven")
|
||||
if audit["identity"]["payload_manager"]["installed_asset_hash_match"] is not True:
|
||||
raise RuntimeError("installed Payload Manager exact identity is no longer proven")
|
||||
if audit["identity"]["exact_exploit_autoloader"]["identified"] is not False:
|
||||
raise RuntimeError("unproven exploit identity was promoted without evidence")
|
||||
if any(audit["no_console_actions"].values()):
|
||||
raise RuntimeError("Phase-0.6 records a forbidden console action")
|
||||
|
||||
effects = {item["id"]: item for item in audit["effects"]}
|
||||
for required in (
|
||||
"ptrace_single_step_completion",
|
||||
"payload_runtime_limit",
|
||||
"payload_manager_launch_hash_binding",
|
||||
"payload_manager_upload",
|
||||
):
|
||||
if required not in effects or effects[required]["blocker"] is not True:
|
||||
raise RuntimeError(f"hard blocker disappeared: {required}")
|
||||
if effects["payload_manager_upload"]["classification"] != "PERSISTENT_WRITE":
|
||||
raise RuntimeError("Payload Manager upload write was reclassified")
|
||||
if (
|
||||
effects["ptrace_single_step_completion"]["classification"]
|
||||
!= "UNBOUNDED_OR_UNKNOWN"
|
||||
):
|
||||
raise RuntimeError("elfldr single-step loop was reclassified")
|
||||
|
||||
blocked_hash = (
|
||||
"4be1c17b4964f2b68c39b5145bc4af4619c32512d60269ecf5c39728b390fa63"
|
||||
)
|
||||
if [entry["sha256"] for entry in denylist["entries"]] != [blocked_hash]:
|
||||
raise RuntimeError("permanent denylist changed")
|
||||
|
||||
print("Phase-0.6 exact-loader audit remains fail-closed")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,160 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Validate the committed Phase-0.7 offline deployment evidence."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def load(path: Path) -> dict[str, object]:
|
||||
document = json.loads(path.read_text(encoding="utf-8"))
|
||||
if not isinstance(document, dict):
|
||||
raise RuntimeError(f"{path}: expected an object")
|
||||
return document
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
root = args.root.resolve()
|
||||
audit = load(root / "manifests/runtime/phase-0.7-offline-audit.json")
|
||||
profile = load(root / "manifests/runtime/controlled-ps5-runtime-profile.json")
|
||||
manifest = load(
|
||||
root
|
||||
/ "manifests/artifacts/chimera-gfx-lifecycle-probe-phase07-fw-9.60.json"
|
||||
)
|
||||
loader_manifest = load(
|
||||
root / "manifests/artifacts/chimera-elfldr-phase07-fw-9.60.json"
|
||||
)
|
||||
manager_manifest = load(
|
||||
root
|
||||
/ "manifests/artifacts/chimera-payload-manager-phase07-fw-9.60.json"
|
||||
)
|
||||
proof = load(
|
||||
root / "manifests/runtime/phase-0.7-kernelwrite-proof-matrix.json"
|
||||
)
|
||||
denylist = load(root / "manifests/artifact-denylist.json")
|
||||
|
||||
decision = "READY_FOR_HARDENED_RUNTIME_DEPLOYMENT"
|
||||
if audit.get("decision") != decision or profile.get("decision") != decision:
|
||||
raise RuntimeError("Phase-0.7 deployment decision is inconsistent")
|
||||
if proof.get("decision") != decision:
|
||||
raise RuntimeError("Phase-0.7 proof-matrix decision is inconsistent")
|
||||
if proof.get("kernelwrite_free_claim") is not False:
|
||||
raise RuntimeError("Phase-0.7 incorrectly claims kernelwrite-free startup")
|
||||
if any(audit["ps5_actions"].values()):
|
||||
raise RuntimeError("Phase-0.7 records a forbidden PS5 action")
|
||||
if audit["firmware"] != "9.60":
|
||||
raise RuntimeError("Phase-0.7 firmware gate changed")
|
||||
|
||||
lifecycle = audit["artifacts"]["lifecycle"]
|
||||
if lifecycle["imports"] != ["_exit", "sceKernelSendNotificationRequest"]:
|
||||
raise RuntimeError("lifecycle import inventory changed")
|
||||
if lifecycle["dt_needed"] != [
|
||||
"libSceLibcInternal.sprx",
|
||||
"libkernel_web.sprx",
|
||||
]:
|
||||
raise RuntimeError("lifecycle DT_NEEDED inventory changed")
|
||||
if lifecycle["byte_identical_clean_builds"] is not True:
|
||||
raise RuntimeError("lifecycle reproducibility evidence is absent")
|
||||
sensitive = lifecycle["sensitive_static_inventory"]
|
||||
if sensitive["direct_call_reachability_available"] is not True:
|
||||
raise RuntimeError("lifecycle direct reachability evidence is absent")
|
||||
kernel_runtime = sensitive["categories"]["kernel_runtime_write"]
|
||||
for required in (
|
||||
"__patch_init",
|
||||
"kernel_copyin",
|
||||
"kernel_copyout",
|
||||
"kernel_set_ucred_attrs",
|
||||
"kernel_set_ucred_caps",
|
||||
):
|
||||
if required not in kernel_runtime["directly_reachable_from_entrypoint"]:
|
||||
raise RuntimeError(f"lifecycle hides reachable CRT symbol {required}")
|
||||
dynamic_loading = sensitive["categories"]["dynamic_loading"]["linked"]
|
||||
for required in ("sceKernelLoadStartModule", "sceKernelStopUnloadModule"):
|
||||
if required not in dynamic_loading:
|
||||
raise RuntimeError(f"lifecycle hides linked rtld symbol {required}")
|
||||
if sensitive["categories"]["graphics_or_display"]["linked"]:
|
||||
raise RuntimeError("lifecycle links a graphics/display-sensitive symbol")
|
||||
if lifecycle["sha256"] != manifest["artifact"]["sha256"]:
|
||||
raise RuntimeError("lifecycle audit/manifest hash mismatch")
|
||||
if lifecycle["size"] != manifest["artifact"]["size"]:
|
||||
raise RuntimeError("lifecycle audit/manifest size mismatch")
|
||||
loader = audit["artifacts"]["loader"]
|
||||
if (
|
||||
loader["sha256"] != loader_manifest["artifact"]["sha256"]
|
||||
or loader["size"] != loader_manifest["artifact"]["size"]
|
||||
):
|
||||
raise RuntimeError("loader audit/manifest identity mismatch")
|
||||
manager = audit["artifacts"]["manager"]
|
||||
if (
|
||||
manager["sha256"] != manager_manifest["artifact"]["sha256"]
|
||||
or manager["size"] != manager_manifest["artifact"]["size"]
|
||||
):
|
||||
raise RuntimeError("manager audit/manifest identity mismatch")
|
||||
for reviewed_manifest in (loader_manifest, manager_manifest, manifest):
|
||||
if reviewed_manifest["execution"]["execution_eligible"] is not True:
|
||||
raise RuntimeError("Phase-0.7 exact artifact is not statically eligible")
|
||||
if any(
|
||||
reviewed_manifest["execution"][key] is not False
|
||||
for key in ("authorized", "transferred", "executed")
|
||||
):
|
||||
raise RuntimeError("Phase-0.7 manifest claims a forbidden action")
|
||||
for stripped in (loader, manager):
|
||||
inventory = stripped["sensitive_static_inventory"]
|
||||
if inventory["direct_call_reachability_available"] is not False:
|
||||
raise RuntimeError("stripped binary reachability is overstated")
|
||||
if inventory["categories"]["graphics_or_display"]["linked"]:
|
||||
raise RuntimeError("runtime links a graphics/display-sensitive symbol")
|
||||
if manifest["execution"]["execution_eligible"] is not True:
|
||||
raise RuntimeError("new lifecycle artifact is not statically eligible")
|
||||
if any(
|
||||
manifest["execution"][key] is not False
|
||||
for key in ("authorized", "transferred", "executed")
|
||||
):
|
||||
raise RuntimeError("lifecycle manifest claims a forbidden action")
|
||||
|
||||
if profile["hard_blockers"]:
|
||||
raise RuntimeError("Phase-0.7 profile still has hard blockers")
|
||||
classifications = {
|
||||
item["classification"] for item in profile["effects"]
|
||||
}
|
||||
if {"PERSISTENT_WRITE", "UNBOUNDED_OR_UNKNOWN"} & classifications:
|
||||
raise RuntimeError("Phase-0.7 profile still has a hard effect")
|
||||
if profile["firmware"]["evidence"] != "jens_explicitly_confirmed_exact_9.60":
|
||||
raise RuntimeError("explicit firmware confirmation is absent")
|
||||
if profile["deployment"] != {
|
||||
"installed": False,
|
||||
"ready_for_installation": True,
|
||||
"rollback_prepared": True,
|
||||
}:
|
||||
raise RuntimeError("Phase-0.7 deployment state changed")
|
||||
|
||||
blocked = (
|
||||
"4be1c17b4964f2b68c39b5145bc4af4619c32512d60269ecf5c39728b390fa63"
|
||||
)
|
||||
if [entry["sha256"] for entry in denylist["entries"]] != [blocked]:
|
||||
raise RuntimeError("permanent denylist changed")
|
||||
proof_status = {
|
||||
item["component"]: item["status"] for item in proof["reviews"]
|
||||
}
|
||||
if proof_status.get("sdk_patch_init") != "UNSAFE":
|
||||
raise RuntimeError("normal CRT process-local write is hidden")
|
||||
if proof_status.get("firmware_9_60_runtime_behavior") != "UNPROVEN":
|
||||
raise RuntimeError("offline audit claims hardware evidence")
|
||||
if any(
|
||||
proof["no_ps5_actions"][key] is not False
|
||||
for key in ("connected", "installed", "transferred", "executed")
|
||||
):
|
||||
raise RuntimeError("proof matrix claims a forbidden PS5 action")
|
||||
|
||||
print("Phase-0.7 offline audit is deployment-ready and execution-unauthorized")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Validate the fail-closed Phase-0.8 preflight record."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def load(path: Path) -> dict[str, object]:
|
||||
document = json.loads(path.read_text(encoding="utf-8"))
|
||||
if not isinstance(document, dict):
|
||||
raise RuntimeError(f"{path}: expected an object")
|
||||
return document
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
root = args.root.resolve()
|
||||
|
||||
audit = load(root / "manifests/runtime/phase-0.8-read-only-preflight.json")
|
||||
profile = load(root / "manifests/runtime/controlled-ps5-runtime-profile.json")
|
||||
locks = load(root / "manifests/upstreams.lock.json")
|
||||
|
||||
if audit.get("decision") != "READ_ONLY_PREFLIGHT_BLOCKED":
|
||||
raise RuntimeError("Phase-0.8 no longer fails closed")
|
||||
if audit.get("scope") != "offline_admissibility_audit_only":
|
||||
raise RuntimeError("Phase-0.8 scope was widened")
|
||||
if audit.get("on_device_session_started") is not False:
|
||||
raise RuntimeError("Phase-0.8 incorrectly claims an on-device session")
|
||||
if audit.get("dataset_complete") is not False:
|
||||
raise RuntimeError("Phase-0.8 incorrectly claims a complete dataset")
|
||||
if audit.get("open_stop_ro") is not True or audit.get("open_stop_gate") is not True:
|
||||
raise RuntimeError("Phase-0.8 stop state is incomplete")
|
||||
|
||||
authorization = audit["authorization"]
|
||||
false_authorizations = (
|
||||
"explicit_read_only_preflight_permission_recorded",
|
||||
"connection_authorized",
|
||||
"installation_authorized",
|
||||
"lifecycle_authorized",
|
||||
"execution_authorized",
|
||||
"automatic_retry",
|
||||
)
|
||||
if any(authorization[key] is not False for key in false_authorizations):
|
||||
raise RuntimeError("Phase-0.8 records unauthorized authority")
|
||||
if (
|
||||
authorization["permission_reference"] is not None
|
||||
or authorization["permission_exact_text"] is not None
|
||||
):
|
||||
raise RuntimeError("Phase-0.8 invents a permission record")
|
||||
|
||||
if any(audit["ps5_actions"].values()):
|
||||
raise RuntimeError("Phase-0.8 records an on-device action")
|
||||
|
||||
collector = audit["collector_assessment"]
|
||||
if collector["selected_collector"] is not None:
|
||||
raise RuntimeError("Phase-0.8 selected an inadmissible collector")
|
||||
if collector["can_prove_no_atime_audit_cache_or_metadata_change"] is not False:
|
||||
raise RuntimeError("Phase-0.8 overstates collector side-effect proof")
|
||||
candidates = {item["id"]: item for item in collector["candidates"]}
|
||||
payload_manager = candidates["payload_manager_v0_3_1_http"]
|
||||
if payload_manager["usable"] is not False or payload_manager["result"] != "STOP-RO":
|
||||
raise RuntimeError("stock Payload Manager HTTP was promoted")
|
||||
|
||||
manager_lock = locks["sources"]["ps5_payload_manager"]
|
||||
if payload_manager["source_commit"] != manager_lock["commit"]:
|
||||
raise RuntimeError("Payload Manager preflight source is not pinned")
|
||||
|
||||
required_blockers = {
|
||||
"explicit_permission_record_absent": "STOP-RO",
|
||||
"collector_side_effect_freedom_unproven": "STOP-RO",
|
||||
"payload_manager_http_mutates_runtime_state": "STOP-RO",
|
||||
"two_source_firmware_attestation_absent": "STOP-GATE",
|
||||
"current_live_identity_and_topology_absent": "STOP-GATE",
|
||||
"autoload_startup_retry_state_absent": "STOP-GATE",
|
||||
"stock_elfldr_backup_unproven": "STOP-GATE",
|
||||
"stock_payload_manager_backup_unproven": "HARD_STOP-GATE",
|
||||
}
|
||||
blockers = {item["id"]: item["severity"] for item in audit["blockers"]}
|
||||
if blockers != required_blockers:
|
||||
raise RuntimeError("Phase-0.8 blocker set changed")
|
||||
|
||||
if audit["firmware"]["two_current_sources_agree"] != "UNPROVEN":
|
||||
raise RuntimeError("Phase-0.8 invents a second firmware source")
|
||||
if (
|
||||
audit["rollback_preconditions"]["stock_payload_manager_backup"]["result"]
|
||||
!= "HARD_STOP-GATE"
|
||||
):
|
||||
raise RuntimeError("Payload Manager rollback hard gate disappeared")
|
||||
if audit["payload_manager_backup_exactly_present"] != "UNPROVEN":
|
||||
raise RuntimeError("Phase-0.8 invents an exact manager backup")
|
||||
if audit["historical_evidence_is_not_current_preflight_evidence"] is not True:
|
||||
raise RuntimeError("historical observations were promoted to current evidence")
|
||||
|
||||
if profile["deployment"]["installed"] is not False:
|
||||
raise RuntimeError("Phase-0.8 incorrectly marks the hardened runtime installed")
|
||||
if profile["execution_authorized"] is not False:
|
||||
raise RuntimeError("Phase-0.8 widened execution authority")
|
||||
|
||||
print("Phase-0.8 read-only preflight remains blocked without PS5 contact")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -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())
|
||||
@@ -0,0 +1,763 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Host-only regression tests for the Phase-0.9A anti-brick design."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from types import ModuleType
|
||||
from typing import Any, Callable
|
||||
|
||||
|
||||
IMMUTABLE = {
|
||||
"docs/runtime/phase-0.8-read-only-preflight.md": (
|
||||
"3fbe086175a6048176075f447ec1482074928e3b5282db97ea2169395fe1d508"
|
||||
),
|
||||
"manifests/runtime/phase-0.8-read-only-preflight.json": (
|
||||
"47d7f452f8799979fe99b3e6d56859f03544112725bf7e5b349eba5ed81b3322"
|
||||
),
|
||||
"tests/test_phase08_preflight.py": (
|
||||
"8a4ad7c70de28ffe3148fd3fd1f68c36a872c53c691c9068e1ff163970863c48"
|
||||
),
|
||||
}
|
||||
|
||||
DENIED_SHA256 = (
|
||||
"4be1c17b4964f2b68c39b5145bc4af4619c32512d60269ecf5c39728b390fa63"
|
||||
)
|
||||
|
||||
TEMPLATES = {
|
||||
"docs/approvals/phase-0.9-observation-template.md": (
|
||||
"PHASE09_OBSERVATION_TEMPLATE",
|
||||
"observation",
|
||||
),
|
||||
"docs/approvals/phase-0.9-backup-creation-template.md": (
|
||||
"PHASE09_BACKUP_CREATION_TEMPLATE",
|
||||
"backup_creation",
|
||||
),
|
||||
"docs/approvals/phase-0.9-staging-template.md": (
|
||||
"PHASE09_STAGING_TEMPLATE",
|
||||
"staging",
|
||||
),
|
||||
"docs/approvals/phase-0.9-switch-template.md": (
|
||||
"PHASE09_SWITCH_TEMPLATE",
|
||||
"live_switch",
|
||||
),
|
||||
"docs/approvals/phase-0.9-one-shot-execution-template.md": (
|
||||
"PHASE09_ONE_SHOT_EXECUTION_TEMPLATE",
|
||||
"one_shot_execution",
|
||||
),
|
||||
}
|
||||
|
||||
FALSE_AUTH_FIELDS = (
|
||||
"authorized",
|
||||
"installation_authorized",
|
||||
"execution_authorized",
|
||||
"transfer_authorized",
|
||||
"lifecycle_authorized",
|
||||
"automatic_retry",
|
||||
)
|
||||
|
||||
|
||||
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]:
|
||||
result = json.loads(path.read_text(encoding="utf-8"))
|
||||
if not isinstance(result, dict):
|
||||
raise RuntimeError(f"{path}: expected JSON object")
|
||||
return result
|
||||
|
||||
|
||||
def extract_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 RuntimeError(f"{path}: expected one {name} contract")
|
||||
fenced = text.split(begin, 1)[1].split(end, 1)[0].strip()
|
||||
if not fenced.startswith("```json\n") or not fenced.endswith("\n```"):
|
||||
raise RuntimeError(f"{path}: malformed JSON fence")
|
||||
result = json.loads(fenced[len("```json\n") : -len("\n```")])
|
||||
if not isinstance(result, dict):
|
||||
raise RuntimeError(f"{path}: expected object in contract")
|
||||
return result
|
||||
|
||||
|
||||
def load_simulator(root: Path) -> ModuleType:
|
||||
path = root / "tools/simulate_phase09_transaction.py"
|
||||
spec = importlib.util.spec_from_file_location("phase09_simulator", path)
|
||||
if spec is None or spec.loader is None:
|
||||
raise RuntimeError("could not load Phase-0.9A simulator")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[spec.name] = module
|
||||
previous = sys.dont_write_bytecode
|
||||
sys.dont_write_bytecode = True
|
||||
try:
|
||||
spec.loader.exec_module(module)
|
||||
finally:
|
||||
sys.dont_write_bytecode = previous
|
||||
return module
|
||||
|
||||
|
||||
def require(condition: bool, message: str) -> None:
|
||||
if not condition:
|
||||
raise RuntimeError(message)
|
||||
|
||||
|
||||
def require_blocked(report: dict[str, Any], scenario: str) -> None:
|
||||
require(
|
||||
report["state"] in {"BLOCKED", "ROLLBACK_REQUIRED"},
|
||||
f"{scenario}: state did not fail closed: {report['state']}",
|
||||
)
|
||||
require(
|
||||
report["target_execution_performed"] is False,
|
||||
f"{scenario}: target execution was claimed",
|
||||
)
|
||||
require(
|
||||
report["automatic_retry"] is False,
|
||||
f"{scenario}: automatic retry was enabled",
|
||||
)
|
||||
require(
|
||||
report["crash_invariant"]
|
||||
in {
|
||||
"A_OLD_LIVE_COMPLETE",
|
||||
"B_NEW_LIVE_COMPLETE_AND_BACKUP_INTACT",
|
||||
"C_REJECTED_UNSAFE_OR_UNPROVEN",
|
||||
},
|
||||
f"{scenario}: crash invariant missing",
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
root = args.root.resolve()
|
||||
manifest = load_json(
|
||||
root / "manifests/runtime/phase-0.9-anti-brick-design.json"
|
||||
)
|
||||
denylist = load_json(root / "manifests/artifact-denylist.json")
|
||||
simulator = load_simulator(root)
|
||||
passed: list[str] = []
|
||||
|
||||
def check(name: str, test: Callable[[], None]) -> None:
|
||||
test()
|
||||
passed.append(name)
|
||||
|
||||
def immutable_evidence() -> None:
|
||||
for relative, expected in IMMUTABLE.items():
|
||||
require(sha256(root / relative) == expected, f"immutable drift: {relative}")
|
||||
|
||||
check("immutable Phase-0.8 evidence", immutable_evidence)
|
||||
|
||||
check(
|
||||
"design-only top-level state",
|
||||
lambda: require(
|
||||
manifest["status"] == "DESIGN_ONLY"
|
||||
and manifest["phase"] == "PHASE_0_9A_OFFLINE_ANTI_BRICK"
|
||||
and manifest["historical_status"] == "READ_ONLY_PREFLIGHT_BLOCKED",
|
||||
"design or historical status changed",
|
||||
),
|
||||
)
|
||||
|
||||
def false_activity_and_authority() -> None:
|
||||
for field in (
|
||||
"hardware_observed",
|
||||
"ps5_connected",
|
||||
"device_write_performed",
|
||||
"files_transferred",
|
||||
"target_execution_performed",
|
||||
"target_artifact_created",
|
||||
"installation_authorized",
|
||||
"lifecycle_authorized",
|
||||
"execution_authorized",
|
||||
"transfer_authorized",
|
||||
"automatic_retry",
|
||||
"lifecycle_probe_in_scope",
|
||||
"graphics_in_scope",
|
||||
"retroarch_in_scope",
|
||||
"host_simulation_is_hardware_evidence",
|
||||
):
|
||||
require(manifest.get(field) is False, f"{field} is not false")
|
||||
|
||||
check("all activity and authorization fields false", false_activity_and_authority)
|
||||
|
||||
def nested_authorizations_false() -> None:
|
||||
controlled = {
|
||||
"authorized",
|
||||
"installation_authorized",
|
||||
"execution_authorized",
|
||||
"transfer_authorized",
|
||||
"lifecycle_authorized",
|
||||
"automatic_retry",
|
||||
}
|
||||
|
||||
def visit(value: Any, location: str) -> None:
|
||||
if isinstance(value, dict):
|
||||
for key, child in value.items():
|
||||
if key in controlled:
|
||||
require(child is False, f"{location}.{key} is not false")
|
||||
visit(child, f"{location}.{key}")
|
||||
elif isinstance(value, list):
|
||||
for index, child in enumerate(value):
|
||||
visit(child, f"{location}[{index}]")
|
||||
|
||||
visit(manifest, "manifest")
|
||||
|
||||
check("nested authorization and retry fields false", nested_authorizations_false)
|
||||
|
||||
def permanent_denylist() -> None:
|
||||
entries = denylist["entries"]
|
||||
require(denylist["fail_closed"] is True, "denylist is not fail closed")
|
||||
require(len(entries) == 1, "denylist entry count changed")
|
||||
entry = entries[0]
|
||||
require(entry["sha256"] == DENIED_SHA256, "denied hash changed")
|
||||
require(entry["status"] == "BLOCKED", "denied hash not blocked")
|
||||
require(entry["permanent"] is True, "denied hash not permanent")
|
||||
require(entry["execution_eligible"] is False, "denied hash eligible")
|
||||
require(
|
||||
manifest["permanent_denylist_binding"]
|
||||
== {
|
||||
"sha256": DENIED_SHA256,
|
||||
"status": "BLOCKED",
|
||||
"permanent": True,
|
||||
"execution_eligible": False,
|
||||
},
|
||||
"manifest denylist binding changed",
|
||||
)
|
||||
|
||||
check("permanent denylist remains exact", permanent_denylist)
|
||||
|
||||
check(
|
||||
"firmware and stock identities remain unproven",
|
||||
lambda: require(
|
||||
manifest["firmware_runtime_behavior"] == "UNPROVEN"
|
||||
and manifest["stock_identification"] == "reference_only",
|
||||
"firmware or stock evidence was promoted",
|
||||
),
|
||||
)
|
||||
check(
|
||||
"Payload Manager backup remains hard blocker",
|
||||
lambda: require(
|
||||
manifest["payload_manager_backup"] == "HARD_BLOCKER",
|
||||
"Payload Manager backup hard blocker changed",
|
||||
),
|
||||
)
|
||||
|
||||
def threat_model_complete() -> None:
|
||||
items = manifest["threat_model_items"]
|
||||
require(len(items) == 56, f"expected 56 threat items, got {len(items)}")
|
||||
ids = {item["id"] for item in items}
|
||||
require(len(ids) == len(items), "duplicate threat item ID")
|
||||
require(
|
||||
{item["profile"] for item in items}
|
||||
== {
|
||||
"A_WRONG_TARGET",
|
||||
"B_WRONG_PREIMAGE",
|
||||
"C_BACKUP_FAILURE",
|
||||
"D_WRITE_POWER_LOSS",
|
||||
"E_PROCESS_LIFECYCLE",
|
||||
"F_ROLLBACK_FAILURE",
|
||||
"G_OPERATOR_ERROR",
|
||||
},
|
||||
"threat profiles incomplete",
|
||||
)
|
||||
require(
|
||||
all(
|
||||
item["severity"] in {"CATASTROPHIC", "HIGH", "MEDIUM", "LOW"}
|
||||
and item["reason"]
|
||||
for item in items
|
||||
),
|
||||
"threat severity or reason missing",
|
||||
)
|
||||
|
||||
check("complete classified threat model", threat_model_complete)
|
||||
|
||||
def invariants_complete() -> None:
|
||||
invariants = manifest["anti_brick_invariants"]
|
||||
require(len(invariants) == 20, "anti-brick invariant count changed")
|
||||
require(
|
||||
[item["id"] for item in invariants]
|
||||
== [f"AB-{number:03d}" for number in range(1, 21)],
|
||||
"anti-brick invariant IDs changed",
|
||||
)
|
||||
|
||||
check("AB-001 through AB-020 present", invariants_complete)
|
||||
check(
|
||||
"transaction state set exact",
|
||||
lambda: require(
|
||||
tuple(manifest["transaction_states"]) == simulator.STATES,
|
||||
"manifest and simulator state sets differ",
|
||||
),
|
||||
)
|
||||
|
||||
def transition_skip_rejected() -> None:
|
||||
machine = simulator.StateMachine()
|
||||
try:
|
||||
machine.transition("LIVE_OBJECTS_VERIFIED")
|
||||
except simulator.TransitionError:
|
||||
return
|
||||
raise RuntimeError("state machine accepted an approval-gate skip")
|
||||
|
||||
check("authorization states cannot be skipped", transition_skip_rejected)
|
||||
check(
|
||||
"no retry, autoload or combined transition",
|
||||
lambda: require(
|
||||
{
|
||||
"automatic_retry",
|
||||
"autoload",
|
||||
"combined_component_installation",
|
||||
"lifecycle_transition",
|
||||
"graphics_transition",
|
||||
"retroarch_transition",
|
||||
}.issubset(set(manifest["forbidden_transitions"])),
|
||||
"forbidden transition set incomplete",
|
||||
),
|
||||
)
|
||||
|
||||
def templates_fail_closed() -> None:
|
||||
for relative, (marker, action) in TEMPLATES.items():
|
||||
template = extract_contract(root / relative, marker)
|
||||
require(template["template_only"] is True, f"{relative}: not template")
|
||||
require(
|
||||
template["template_action"] == action,
|
||||
f"{relative}: wrong action binding",
|
||||
)
|
||||
for field in FALSE_AUTH_FIELDS:
|
||||
require(template[field] is False, f"{relative}: {field} not false")
|
||||
required = template["required_fields"]
|
||||
require(required, f"{relative}: required fields absent")
|
||||
require(
|
||||
all(value is None for value in required.values()),
|
||||
f"{relative}: request data was prefilled",
|
||||
)
|
||||
require(
|
||||
template["fixed_exclusions"]["automatic_retry"] is True,
|
||||
f"{relative}: retry exclusion missing",
|
||||
)
|
||||
require(
|
||||
template["fixed_exclusions"]["autoload"] is True,
|
||||
f"{relative}: autoload exclusion missing",
|
||||
)
|
||||
require(
|
||||
template["fixed_exclusions"]["lifecycle_probe"] is True,
|
||||
f"{relative}: lifecycle exclusion missing",
|
||||
)
|
||||
|
||||
check("all five templates are false and unfilled", templates_fail_closed)
|
||||
|
||||
def exact_live_identity_required() -> None:
|
||||
for fault in (
|
||||
"missing_live_path",
|
||||
"missing_mount_id",
|
||||
"missing_object_id",
|
||||
"wrong_size",
|
||||
"wrong_preimage_hash",
|
||||
):
|
||||
report = simulator.simulate_transaction(
|
||||
"hardened_elfldr", [fault], prove_virtual_switch_model=True
|
||||
)
|
||||
require_blocked(report, fault)
|
||||
require(report["virtual_writes"] == [], f"{fault}: write occurred")
|
||||
|
||||
check("no write without exact full live identity", exact_live_identity_required)
|
||||
|
||||
check(
|
||||
"reference-only stock hash never authorizes write",
|
||||
lambda: require_blocked(
|
||||
simulator.simulate_transaction(
|
||||
"hardened_elfldr",
|
||||
["reference_only_preimage"],
|
||||
prove_virtual_switch_model=True,
|
||||
),
|
||||
"reference_only_preimage",
|
||||
),
|
||||
)
|
||||
|
||||
def staging_needs_backup() -> None:
|
||||
for fault in (
|
||||
"backup_same_object_as_live",
|
||||
"short_backup_write",
|
||||
"backup_hash_mismatch",
|
||||
"backup_not_reopened",
|
||||
):
|
||||
report = simulator.simulate_transaction(
|
||||
"controlled_payload_manager",
|
||||
[fault],
|
||||
prove_virtual_switch_model=True,
|
||||
)
|
||||
require_blocked(report, fault)
|
||||
require(
|
||||
"virtual_candidate_stage" not in report["virtual_writes"],
|
||||
f"{fault}: staging occurred without verified backup",
|
||||
)
|
||||
|
||||
check("no staging without separate reopened backup", staging_needs_backup)
|
||||
|
||||
def separate_approvals() -> None:
|
||||
for fault in (
|
||||
"authorization_missing",
|
||||
"authorization_wrong_hash",
|
||||
"authorization_expired",
|
||||
):
|
||||
require_blocked(
|
||||
simulator.simulate_transaction(
|
||||
"hardened_elfldr", [fault], prove_virtual_switch_model=True
|
||||
),
|
||||
fault,
|
||||
)
|
||||
|
||||
check("missing mismatched or expired approval stops", separate_approvals)
|
||||
|
||||
check(
|
||||
"no switch while atomicity is unproven",
|
||||
lambda: require_blocked(
|
||||
simulator.simulate_transaction("hardened_elfldr"),
|
||||
"default unproven platform",
|
||||
),
|
||||
)
|
||||
check(
|
||||
"directory durability unknown stops",
|
||||
lambda: require_blocked(
|
||||
simulator.simulate_transaction(
|
||||
"hardened_elfldr",
|
||||
["directory_durability_unknown"],
|
||||
prove_virtual_switch_model=True,
|
||||
),
|
||||
"directory_durability_unknown",
|
||||
),
|
||||
)
|
||||
|
||||
def synthetic_happy_path_stops_before_execution() -> None:
|
||||
report = simulator.simulate_transaction(
|
||||
"hardened_elfldr", prove_virtual_switch_model=True
|
||||
)
|
||||
require(
|
||||
report["status"] == "DESIGN_MODEL_STOP_BEFORE_EXECUTION",
|
||||
"synthetic model did not stop before execution",
|
||||
)
|
||||
require(
|
||||
report["state"] == "MANUAL_EXECUTION_NOT_AUTHORIZED",
|
||||
"execution approval gate was bypassed",
|
||||
)
|
||||
require(
|
||||
report["crash_invariant"]
|
||||
== "B_NEW_LIVE_COMPLETE_AND_BACKUP_INTACT",
|
||||
"synthetic old/new invariant failed",
|
||||
)
|
||||
require(report["target_execution_performed"] is False, "execution claimed")
|
||||
|
||||
check("post-switch model stops before execution", synthetic_happy_path_stops_before_execution)
|
||||
|
||||
def no_retry_or_autoload() -> None:
|
||||
for fault in ("retry_active", "autoload_active", "timeout"):
|
||||
report = simulator.simulate_transaction(
|
||||
"hardened_elfldr", [fault], prove_virtual_switch_model=True
|
||||
)
|
||||
require_blocked(report, fault)
|
||||
require(report["automatic_retry"] is False, f"{fault}: retry enabled")
|
||||
require(report["autoload"] is False, f"{fault}: autoload enabled")
|
||||
|
||||
check("autoload retry and timeout always stop", no_retry_or_autoload)
|
||||
|
||||
check(
|
||||
"component transactions cannot be combined",
|
||||
lambda: require(
|
||||
set(simulator.COMPONENTS)
|
||||
== {"hardened_elfldr", "controlled_payload_manager"}
|
||||
and manifest["component_order"]["combined_install_all"] is False,
|
||||
"combined transaction surface exists",
|
||||
),
|
||||
)
|
||||
check(
|
||||
"second component waits for acceptance or rollback",
|
||||
lambda: require_blocked(
|
||||
simulator.simulate_transaction(
|
||||
"controlled_payload_manager",
|
||||
["second_component_before_first_accepted"],
|
||||
prove_virtual_switch_model=True,
|
||||
),
|
||||
"second_component_before_first_accepted",
|
||||
),
|
||||
)
|
||||
check(
|
||||
"lifecycle probe excluded from candidate set",
|
||||
lambda: require_blocked(
|
||||
simulator.simulate_transaction(
|
||||
"hardened_elfldr",
|
||||
["lifecycle_probe_candidate"],
|
||||
prove_virtual_switch_model=True,
|
||||
),
|
||||
"lifecycle_probe_candidate",
|
||||
),
|
||||
)
|
||||
check(
|
||||
"graphics SDL and RetroArch inactive",
|
||||
lambda: require(
|
||||
manifest["graphics_in_scope"] is False
|
||||
and manifest["retroarch_in_scope"] is False
|
||||
and manifest["lifecycle_probe_in_scope"] is False,
|
||||
"later-phase scope became active",
|
||||
),
|
||||
)
|
||||
|
||||
def power_loss_contract_complete() -> None:
|
||||
boundaries = manifest["power_loss_boundaries"]
|
||||
require(len(boundaries) == 14, "power-loss boundary count changed")
|
||||
require(
|
||||
[item["id"] for item in boundaries]
|
||||
== list(simulator.POWER_LOSS_BOUNDARIES),
|
||||
"power-loss boundary order or identity changed",
|
||||
)
|
||||
require(
|
||||
all(item["result"] == "UNPROVEN" for item in boundaries),
|
||||
"a PS5 power-loss boundary was promoted",
|
||||
)
|
||||
require(
|
||||
any(item["result"] in {"UNSAFE", "UNPROVEN"} for item in boundaries),
|
||||
"unproven boundaries no longer block",
|
||||
)
|
||||
|
||||
check("all power-loss boundaries explicit and blocking", power_loss_contract_complete)
|
||||
|
||||
def virtual_power_loss_invariant() -> None:
|
||||
for boundary in simulator.POWER_LOSS_BOUNDARIES:
|
||||
report = simulator.simulate_power_loss_boundary(
|
||||
"hardened_elfldr",
|
||||
boundary,
|
||||
prove_virtual_switch_model=True,
|
||||
)
|
||||
require(
|
||||
report["crash_invariant"]
|
||||
in {
|
||||
"A_OLD_LIVE_COMPLETE",
|
||||
"B_NEW_LIVE_COMPLETE_AND_BACKUP_INTACT",
|
||||
},
|
||||
f"{boundary}: virtual model lost old/new invariant",
|
||||
)
|
||||
require(report["automatic_start"] is False, f"{boundary}: auto start")
|
||||
require(report["automatic_retry"] is False, f"{boundary}: retry")
|
||||
|
||||
check("virtual power-loss old-or-new invariant", virtual_power_loss_invariant)
|
||||
|
||||
check(
|
||||
"in-place overwrite forbidden",
|
||||
lambda: require_blocked(
|
||||
simulator.simulate_transaction(
|
||||
"hardened_elfldr",
|
||||
["in_place_overwrite"],
|
||||
prove_virtual_switch_model=True,
|
||||
),
|
||||
"in_place_overwrite",
|
||||
),
|
||||
)
|
||||
check(
|
||||
"two-step rename gap forbidden",
|
||||
lambda: require_blocked(
|
||||
simulator.simulate_transaction(
|
||||
"hardened_elfldr",
|
||||
["two_step_rename_gap"],
|
||||
prove_virtual_switch_model=True,
|
||||
),
|
||||
"two_step_rename_gap",
|
||||
),
|
||||
)
|
||||
|
||||
def identity_race_rejected() -> None:
|
||||
for fault in ("symlink_substitution", "object_swap_after_preflight"):
|
||||
require_blocked(
|
||||
simulator.simulate_transaction(
|
||||
"hardened_elfldr", [fault], prove_virtual_switch_model=True
|
||||
),
|
||||
fault,
|
||||
)
|
||||
|
||||
check("symlink and object-swap races rejected", identity_race_rejected)
|
||||
check(
|
||||
"active target rejected",
|
||||
lambda: require_blocked(
|
||||
simulator.simulate_transaction(
|
||||
"hardened_elfldr",
|
||||
["target_process_active"],
|
||||
prove_virtual_switch_model=True,
|
||||
),
|
||||
"target_process_active",
|
||||
),
|
||||
)
|
||||
check(
|
||||
"candidate mismatch rejected",
|
||||
lambda: require_blocked(
|
||||
simulator.simulate_transaction(
|
||||
"controlled_payload_manager",
|
||||
["candidate_hash_mismatch"],
|
||||
prove_virtual_switch_model=True,
|
||||
),
|
||||
"candidate_hash_mismatch",
|
||||
),
|
||||
)
|
||||
check(
|
||||
"wrong component mapping rejected",
|
||||
lambda: require_blocked(
|
||||
simulator.simulate_transaction(
|
||||
"controlled_payload_manager",
|
||||
["wrong_component_artifact_mapping"],
|
||||
prove_virtual_switch_model=True,
|
||||
),
|
||||
"wrong_component_artifact_mapping",
|
||||
),
|
||||
)
|
||||
check(
|
||||
"unknown firmware rejected",
|
||||
lambda: require_blocked(
|
||||
simulator.simulate_transaction(
|
||||
"hardened_elfldr",
|
||||
["unknown_firmware"],
|
||||
prove_virtual_switch_model=True,
|
||||
),
|
||||
"unknown_firmware",
|
||||
),
|
||||
)
|
||||
check(
|
||||
"live hash mismatch never promotes backup",
|
||||
lambda: require(
|
||||
simulator.simulate_transaction(
|
||||
"hardened_elfldr",
|
||||
["wrong_preimage_hash"],
|
||||
prove_virtual_switch_model=True,
|
||||
)["virtual_writes"]
|
||||
== [],
|
||||
"mismatched live hash reached backup creation",
|
||||
),
|
||||
)
|
||||
check(
|
||||
"recovery dependency on replaced component rejected",
|
||||
lambda: require_blocked(
|
||||
simulator.simulate_transaction(
|
||||
"controlled_payload_manager",
|
||||
["recovery_depends_on_replaced_component"],
|
||||
prove_virtual_switch_model=True,
|
||||
),
|
||||
"recovery_depends_on_replaced_component",
|
||||
),
|
||||
)
|
||||
|
||||
def failed_post_switch_goes_to_rollback_required() -> None:
|
||||
report = simulator.simulate_transaction(
|
||||
"hardened_elfldr",
|
||||
["live_verification_failure"],
|
||||
prove_virtual_switch_model=True,
|
||||
)
|
||||
require(
|
||||
report["state"] == "ROLLBACK_REQUIRED",
|
||||
"failed live verification did not require rollback",
|
||||
)
|
||||
require(report["target_execution_performed"] is False, "execution claimed")
|
||||
|
||||
check("post-switch failure requires rollback", failed_post_switch_goes_to_rollback_required)
|
||||
|
||||
def rollback_mismatch_catastrophic() -> None:
|
||||
report = simulator.simulate_transaction(
|
||||
"hardened_elfldr",
|
||||
["rollback_hash_mismatch"],
|
||||
prove_virtual_switch_model=True,
|
||||
)
|
||||
require(report["state"] == "BLOCKED", "rollback mismatch not blocked")
|
||||
require(report["risk"] == "CATASTROPHIC", "rollback risk not catastrophic")
|
||||
require(
|
||||
report["blockers"][0]["code"] == "ROLLBACK_VERIFY_FAILED",
|
||||
"rollback mismatch blocker changed",
|
||||
)
|
||||
|
||||
check("rollback hash mismatch catastrophic blocked", rollback_mismatch_catastrophic)
|
||||
|
||||
def complete_fault_suite() -> None:
|
||||
for component in simulator.COMPONENTS:
|
||||
suite = simulator.run_fault_suite(component)
|
||||
require(
|
||||
len(suite["fault_results"]) == len(simulator.FAULTS),
|
||||
f"{component}: fault suite incomplete",
|
||||
)
|
||||
require(
|
||||
len(suite["power_loss_results"])
|
||||
== len(simulator.POWER_LOSS_BOUNDARIES),
|
||||
f"{component}: power-loss suite incomplete",
|
||||
)
|
||||
require(suite["hardware_evidence"] is False, "hardware proof claimed")
|
||||
require(suite["device_write_performed"] is False, "device write claimed")
|
||||
require(
|
||||
suite["target_execution_performed"] is False,
|
||||
"target execution claimed",
|
||||
)
|
||||
require(suite["automatic_retry"] is False, "suite retry enabled")
|
||||
|
||||
check("all declared faults injected for both components", complete_fault_suite)
|
||||
|
||||
def simulator_has_no_io_or_target_surface() -> None:
|
||||
source = (
|
||||
root / "tools/simulate_phase09_transaction.py"
|
||||
).read_text(encoding="utf-8")
|
||||
prohibited = (
|
||||
"import socket",
|
||||
"import subprocess",
|
||||
"import requests",
|
||||
"urllib",
|
||||
"ctypes",
|
||||
"os.system",
|
||||
".write_text(",
|
||||
".write_bytes(",
|
||||
"open(",
|
||||
"9021",
|
||||
"8084",
|
||||
"8085",
|
||||
)
|
||||
for token in prohibited:
|
||||
require(token not in source, f"simulator contains prohibited surface: {token}")
|
||||
|
||||
check("simulator cannot perform filesystem network or target I/O", simulator_has_no_io_or_target_surface)
|
||||
|
||||
def docs_keep_platform_unproven() -> None:
|
||||
docs = "\n".join(
|
||||
(root / relative).read_text(encoding="utf-8")
|
||||
for relative in (
|
||||
"docs/runtime/phase-0.9-anti-brick-threat-model.md",
|
||||
"docs/runtime/phase-0.9-installation-transaction-design.md",
|
||||
"docs/runtime/phase-0.9-recovery-and-rollback-contract.md",
|
||||
)
|
||||
)
|
||||
require(
|
||||
"BLOCKER: NO PROVEN POWER-LOSS-SAFE SWITCH" in docs,
|
||||
"power-loss-safe switch blocker missing",
|
||||
)
|
||||
require(
|
||||
"Host simulation is not hardware evidence" in docs
|
||||
or "Host simulation is not hardware" in docs,
|
||||
"host/hardware evidence boundary missing",
|
||||
)
|
||||
for forbidden in ("READY_FOR_INSTALLATION", "DEPLOYMENT_READY"):
|
||||
require(forbidden not in docs, f"forbidden positive status: {forbidden}")
|
||||
|
||||
check("documentation never promotes host model to PS5 proof", docs_keep_platform_unproven)
|
||||
|
||||
require(len(passed) >= 28, "too few Phase-0.9A guardrail tests")
|
||||
print(
|
||||
"Phase-0.9A host-only anti-brick tests passed: "
|
||||
f"{len(passed)} guardrails, {len(simulator.FAULTS)} fault types x "
|
||||
f"{len(simulator.COMPONENTS)} components, "
|
||||
f"{len(simulator.POWER_LOSS_BOUNDARIES)} power-loss boundaries; "
|
||||
"hardware evidence not claimed"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,302 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Host guardrails for the blocked Phase-0.9B observer audit."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import copy
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from types import ModuleType
|
||||
from typing import Any
|
||||
|
||||
|
||||
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 require_invalid(errors: list[str], scenario: str) -> None:
|
||||
require(bool(errors), f"unsafe Phase-0.9B mutation passed: {scenario}")
|
||||
|
||||
|
||||
def synthetic_artifact_findings(
|
||||
*,
|
||||
imports: list[str],
|
||||
strings: list[str],
|
||||
open_flags: list[str],
|
||||
reachable_calls: list[str],
|
||||
) -> list[str]:
|
||||
denied = (
|
||||
"kernel_copyin",
|
||||
"kernel_copyout",
|
||||
"kernel_set_ucred",
|
||||
"ptrace",
|
||||
"dlopen",
|
||||
"dlsym",
|
||||
"sceKernelLoadStartModule",
|
||||
"socket",
|
||||
"connect",
|
||||
"bind",
|
||||
"listen",
|
||||
"accept",
|
||||
"kill",
|
||||
"exec",
|
||||
"spawn",
|
||||
"rename",
|
||||
"unlink",
|
||||
"chmod",
|
||||
"chown",
|
||||
"mkdir",
|
||||
"mount",
|
||||
"reboot",
|
||||
"videoout",
|
||||
"gnm",
|
||||
"sdl",
|
||||
"retroarch",
|
||||
)
|
||||
findings: list[str] = []
|
||||
for category, values in (
|
||||
("import", imports),
|
||||
("string", strings),
|
||||
("reachable", reachable_calls),
|
||||
):
|
||||
for value in values:
|
||||
lowered = value.lower()
|
||||
if any(token.lower() in lowered for token in denied):
|
||||
findings.append(f"{category}:{value}")
|
||||
for value in open_flags:
|
||||
if value in {"O_WRONLY", "O_RDWR", "O_CREAT", "O_TRUNC", "O_APPEND"}:
|
||||
findings.append(f"open_flag:{value}")
|
||||
return findings
|
||||
|
||||
|
||||
def test_host_model(model: ModuleType) -> None:
|
||||
require(model.evaluate_firmware("9.60", "9.60") == "OBSERVED", "equal firmware")
|
||||
require(model.evaluate_firmware("9.60", "9.61") == "CONFLICT", "firmware conflict")
|
||||
require(model.evaluate_firmware("9.60", None) == "UNPROVEN", "missing firmware")
|
||||
|
||||
empty_digest = hashlib.sha256(b"").hexdigest()
|
||||
regular = model.MockObject(expected_sha256=empty_digest)
|
||||
require(model.evaluate_object(regular) == ("OBSERVED", None), "regular object")
|
||||
require(
|
||||
model.evaluate_object(model.MockObject(symlink=True))[1]
|
||||
== "PATH_SYMLINK_SAFETY_UNPROVEN",
|
||||
"symlink",
|
||||
)
|
||||
require(
|
||||
model.evaluate_object(model.MockObject(object_id_after="dev:1/ino:2"))[1]
|
||||
== "OBJECT_ID_CHANGED",
|
||||
"object ID change",
|
||||
)
|
||||
require(
|
||||
model.evaluate_object(model.MockObject(size_after=1))[1] == "SIZE_CHANGED",
|
||||
"size change",
|
||||
)
|
||||
require(
|
||||
model.evaluate_object(model.MockObject(size_before=1, size_after=1))[1]
|
||||
== "SHORT_READ",
|
||||
"short read",
|
||||
)
|
||||
require(
|
||||
model.evaluate_object(model.MockObject(read_error=True))[1] == "READ_ERROR",
|
||||
"read error",
|
||||
)
|
||||
require(
|
||||
model.evaluate_object(model.MockObject(expected_sha256="0" * 64))[1]
|
||||
== "HASH_MISMATCH",
|
||||
"hash mismatch",
|
||||
)
|
||||
require(
|
||||
model.evaluate_object(model.MockObject(path_known=False))[1] == "UNKNOWN_PATH",
|
||||
"unknown path",
|
||||
)
|
||||
require(
|
||||
model.evaluate_object(model.MockObject(path_conflict=True))[1] == "PATH_CONFLICT",
|
||||
"path conflict",
|
||||
)
|
||||
|
||||
backup = model.MockObject(object_id_before="dev:1/ino:2")
|
||||
require(model.evaluate_live_backup(regular, backup) == "SEPARATE_OBJECTS", "backup")
|
||||
require(
|
||||
model.evaluate_live_backup(regular, regular) == "SAME_OBJECT",
|
||||
"same live and backup object",
|
||||
)
|
||||
require(
|
||||
model.evaluate_live_backup(regular, None) == "BACKUP_MISSING",
|
||||
"backup missing",
|
||||
)
|
||||
|
||||
for category in ("mount", "process", "listener"):
|
||||
require(
|
||||
model.unsupported_query(False) == "UNSUPPORTED_OR_UNPROVEN",
|
||||
f"unsupported {category} query",
|
||||
)
|
||||
require(model.evaluate_autoload(False, False) == "UNPROVEN", "autoload missing")
|
||||
require(model.evaluate_autoload(True, False) == "ERROR", "autoload parse")
|
||||
|
||||
output_limited = model.run_terminal_scenario(record_count=100)
|
||||
require(len(output_limited.emitted) == 64, "output limit")
|
||||
require(output_limited.exit_reached, "exit after output limit")
|
||||
|
||||
error_limited = model.run_terminal_scenario(error_count=20)
|
||||
require(error_limited.errors == 9, "error limit")
|
||||
require(error_limited.exit_reached, "exit after error limit")
|
||||
|
||||
deadline = model.run_terminal_scenario(deadline_reached=True)
|
||||
require(deadline.emitted[0]["raw_error"] == "DEADLINE_REACHED", "deadline")
|
||||
require(deadline.exit_reached, "exit after deadline")
|
||||
|
||||
output_failure = model.run_terminal_scenario(output_channel_ok=False)
|
||||
require(
|
||||
output_failure.emitted[0]["raw_error"] == "OUTPUT_CHANNEL_FAILED",
|
||||
"output failure",
|
||||
)
|
||||
require(output_failure.exit_reached, "exit after output failure")
|
||||
|
||||
for observer in (output_limited, error_limited, deadline, output_failure):
|
||||
require(observer.retry_count == 0, "retry occurred")
|
||||
require(observer.persistent_write_count == 0, "persistent write occurred")
|
||||
require(
|
||||
observer.service_or_process_mutation_count == 0,
|
||||
"service/process mutation occurred",
|
||||
)
|
||||
require(observer.listener_count == 0, "listener occurred")
|
||||
require(observer.lifecycle_call_count == 0, "lifecycle call occurred")
|
||||
require(observer.installer_call_count == 0, "installer call occurred")
|
||||
require(
|
||||
observer.graphics_or_retroarch_call_count == 0,
|
||||
"graphics/RetroArch call occurred",
|
||||
)
|
||||
|
||||
|
||||
def test_negative_policy(
|
||||
validator: ModuleType, root: Path, manifest: dict[str, Any]
|
||||
) -> None:
|
||||
ready = copy.deepcopy(manifest)
|
||||
ready["status"] = "READY"
|
||||
require_invalid(validator.validate_manifest(root, ready), "READY status")
|
||||
|
||||
for field in validator.AUTHORIZATION_FIELDS:
|
||||
changed = copy.deepcopy(manifest)
|
||||
changed["authorization"][field] = True
|
||||
require_invalid(
|
||||
validator.validate_manifest(root, changed), f"authorization {field}=true"
|
||||
)
|
||||
|
||||
built = copy.deepcopy(manifest)
|
||||
built["build_gate"]["observer_source_created"] = True
|
||||
built["build_gate"]["observer_target_declared"] = True
|
||||
built["build_gate"]["target_build_performed"] = True
|
||||
built["artifact"]["present"] = True
|
||||
built["artifact"]["path"] = "observer.elf"
|
||||
built["artifact"]["sha256"] = "1" * 64
|
||||
built["artifact"]["size"] = 1
|
||||
require_invalid(validator.validate_manifest(root, built), "artifact appeared")
|
||||
|
||||
startup_claim = copy.deepcopy(manifest)
|
||||
startup_claim["build_gate"]["startup_and_exit_abi_proven"] = True
|
||||
require_invalid(validator.validate_manifest(root, startup_claim), "startup proof")
|
||||
|
||||
output_claim = copy.deepcopy(manifest)
|
||||
output_claim["build_gate"]["non_persistent_output_channel_proven"] = True
|
||||
require_invalid(validator.validate_manifest(root, output_claim), "output proof")
|
||||
|
||||
implementation = copy.deepcopy(manifest)
|
||||
implementation["implementation"]["observer_logic_implemented"] = True
|
||||
implementation["implementation"]["observations_implemented"] = ["firmware"]
|
||||
require_invalid(
|
||||
validator.validate_manifest(root, implementation), "target implementation"
|
||||
)
|
||||
|
||||
reproducible = copy.deepcopy(manifest)
|
||||
reproducible["reproducibility"]["status"] = "PASSED"
|
||||
reproducible["reproducibility"]["build_1_sha256"] = "1" * 64
|
||||
reproducible["reproducibility"]["build_2_sha256"] = "1" * 64
|
||||
reproducible["reproducibility"]["byte_identical"] = True
|
||||
require_invalid(
|
||||
validator.validate_manifest(root, reproducible),
|
||||
"unperformed build became reproducible",
|
||||
)
|
||||
|
||||
|
||||
def test_artifact_audit_guardrails(manifest: dict[str, Any]) -> None:
|
||||
require(manifest["artifact"]["present"] is False, "blocked artifact exists")
|
||||
require(manifest["artifact"]["path"] is None, "blocked artifact path exists")
|
||||
require(
|
||||
manifest["static_artifact_audit"]["status"]
|
||||
== "NOT_PERFORMED_BLOCKED_BEFORE_BUILD",
|
||||
"missing-artifact audit was promoted",
|
||||
)
|
||||
|
||||
findings = synthetic_artifact_findings(
|
||||
imports=["kernel_copyin", "bind", "sceKernelLoadStartModule"],
|
||||
strings=["/autoload_status", "RetroArch", "VideoOut"],
|
||||
open_flags=["O_RDONLY", "O_WRONLY", "O_CREAT"],
|
||||
reachable_calls=["_start->ptrace", "observer->rename", "observer->kill"],
|
||||
)
|
||||
require(len(findings) >= 10, "denied-capability scanner missed synthetic cases")
|
||||
require(
|
||||
synthetic_artifact_findings(
|
||||
imports=[],
|
||||
strings=[],
|
||||
open_flags=["O_RDONLY", "O_NOFOLLOW", "O_CLOEXEC"],
|
||||
reachable_calls=[],
|
||||
)
|
||||
== [],
|
||||
"read-only synthetic audit produced a false positive",
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
root = args.root.resolve()
|
||||
model = load_module(
|
||||
"phase09b_observer_model", root / "tests/phase09b_observer_model.py"
|
||||
)
|
||||
validator = load_module(
|
||||
"phase09b_validator", root / "tools/validate_phase09b_observer_audit.py"
|
||||
)
|
||||
manifest = validator.load_json(
|
||||
root / "manifests/runtime/phase-0.9b-observer.json"
|
||||
)
|
||||
|
||||
errors = validator.collect_errors(root)
|
||||
if errors:
|
||||
raise RuntimeError("; ".join(errors))
|
||||
test_host_model(model)
|
||||
test_negative_policy(validator, root, manifest)
|
||||
test_artifact_audit_guardrails(manifest)
|
||||
|
||||
schema = validator.load_json(
|
||||
root / "manifests/runtime/phase-0.9b-observation-plan.schema.json"
|
||||
)
|
||||
default = schema["x-chimera-default-plan"]
|
||||
require(default["read_paths"] == [], "default plan contains paths")
|
||||
require(default["device_address"] is None, "default plan contains address")
|
||||
require(default["maximum_execution_count"] == 0, "default plan permits execution")
|
||||
for field in validator.AUTHORIZATION_FIELDS:
|
||||
require(default[field] is False, f"default plan {field} is not false")
|
||||
|
||||
print("Phase-0.9B observer host and artifact guardrails: PASS")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,199 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Host guardrails for the blocked Phase-0.9C feasibility closure."""
|
||||
|
||||
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 require_invalid(errors: list[str], scenario: str) -> None:
|
||||
require(bool(errors), f"unsafe Phase-0.9C mutation passed: {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_module(
|
||||
"phase09c_validator", root / "tools/validate_phase09c_feasibility.py"
|
||||
)
|
||||
manifest = validator.load_json(
|
||||
root / "manifests/runtime/phase-0.9c-feasibility.json"
|
||||
)
|
||||
schema = validator.load_json(
|
||||
root / "manifests/runtime/phase-0.9c-feasibility.schema.json"
|
||||
)
|
||||
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
|
||||
|
||||
@case("complete offline validator")
|
||||
def _() -> None:
|
||||
require(validator.collect_errors(root) == [], "current audit does not validate")
|
||||
|
||||
@case("manifest and schema")
|
||||
def _() -> None:
|
||||
require(validator.validate_manifest(manifest) == [], "manifest invalid")
|
||||
require(
|
||||
validator.validate_schema_instance(schema, manifest) == [],
|
||||
"schema rejected manifest",
|
||||
)
|
||||
|
||||
@case("authorization remains false")
|
||||
def _() -> None:
|
||||
for field in validator.AUTHORIZATION_FIELDS:
|
||||
changed = copy.deepcopy(manifest)
|
||||
changed["authorization"][field] = True
|
||||
require_invalid(validator.validate_manifest(changed), field)
|
||||
|
||||
@case("positive classification rejected")
|
||||
def _() -> None:
|
||||
changed = copy.deepcopy(manifest)
|
||||
changed["status"] = "READY"
|
||||
changed["classification"] = (
|
||||
"FEASIBILITY_CONTRACT_PROVEN_NO_TARGET_IMPLEMENTATION"
|
||||
)
|
||||
changed["final_decision"]["positive_classification_allowed"] = True
|
||||
changed["final_decision"]["classification"] = changed["classification"]
|
||||
require_invalid(validator.validate_manifest(changed), "positive decision")
|
||||
|
||||
@case("startup and cleanup promotion rejected")
|
||||
def _() -> None:
|
||||
for field in (
|
||||
"normal_sdk_kernelwrite_free",
|
||||
"freestanding_dependency_closure_proven",
|
||||
"safe_return_proven",
|
||||
"safe_process_exit_proven",
|
||||
"error_exit_proven",
|
||||
"timeout_safe_exit_proven",
|
||||
"complete_cleanup_proven",
|
||||
):
|
||||
changed = copy.deepcopy(manifest)
|
||||
changed["startup_exit"][field] = True
|
||||
require_invalid(validator.validate_manifest(changed), field)
|
||||
|
||||
@case("fabricated firmware source rejected")
|
||||
def _() -> None:
|
||||
changed = copy.deepcopy(manifest)
|
||||
changed["firmware"]["source_two"]["identity"] = "invented"
|
||||
changed["firmware"]["source_two"]["status"] = "PROVEN"
|
||||
changed["firmware"]["agreement_proven"] = True
|
||||
require_invalid(validator.validate_manifest(changed), "firmware source two")
|
||||
|
||||
@case("output implementation promotion rejected")
|
||||
def _() -> None:
|
||||
changed = copy.deepcopy(manifest)
|
||||
changed["output_architectures"][0]["current_implementation"] = True
|
||||
changed["host_protocol"]["target_implemented"] = True
|
||||
require_invalid(validator.validate_manifest(changed), "output implementation")
|
||||
|
||||
@case("capability implementation and execution rejected")
|
||||
def _() -> None:
|
||||
changed = copy.deepcopy(manifest)
|
||||
changed["capability_closure"][0]["implementation_allowed"] = True
|
||||
changed["capability_closure"][0]["execution_allowed"] = True
|
||||
changed["capability_closure"][0]["target_evidence"] = "PROVEN"
|
||||
require_invalid(validator.validate_manifest(changed), "capability promotion")
|
||||
|
||||
@case("artifact and package rejected")
|
||||
def _() -> None:
|
||||
changed = copy.deepcopy(manifest)
|
||||
changed["artifact"] = {
|
||||
"present": True,
|
||||
"path": "phase09c-observer.elf",
|
||||
"sha256": "1" * 64,
|
||||
"size": 1,
|
||||
"execution_eligible": False,
|
||||
"execution_authorized": False,
|
||||
}
|
||||
changed["implementation"]["target_elf_present"] = True
|
||||
require_invalid(validator.validate_manifest(changed), "artifact")
|
||||
for path in (
|
||||
"samples/phase09c_observer/main.c",
|
||||
"outputs/phase09c-observer.elf",
|
||||
"outputs/phase-0.9c-observer.map",
|
||||
"packaging/phase09c/install.zip",
|
||||
"packaging/phase09c/lifecycle.pkg",
|
||||
"packaging/phase09c/autoload.json",
|
||||
):
|
||||
require(validator.forbidden_repository_path(path), path)
|
||||
|
||||
@case("host files remain permitted")
|
||||
def _() -> None:
|
||||
for path in (
|
||||
"docs/runtime/phase-0.9c-static-audit.md",
|
||||
"tests/phase09c_feasibility_model.py",
|
||||
"tests/test_phase09c_protocol.py",
|
||||
"tools/validate_phase09c_feasibility.py",
|
||||
"packaging/phase09c/SHA256SUMS.txt",
|
||||
):
|
||||
require(not validator.forbidden_repository_path(path), path)
|
||||
|
||||
@case("side-effect false promotion rejected")
|
||||
def _() -> None:
|
||||
for field in (
|
||||
"no_persistent_content_write_is_side_effect_free",
|
||||
"read_only_flag_is_side_effect_free",
|
||||
"all_planned_observations_proven_side_effect_free",
|
||||
):
|
||||
changed = copy.deepcopy(manifest)
|
||||
changed["side_effect_model"][field] = True
|
||||
require_invalid(validator.validate_manifest(changed), field)
|
||||
|
||||
@case("permanent denylist and immutable evidence")
|
||||
def _() -> None:
|
||||
require(
|
||||
validator.validate_immutable_evidence(root) == [],
|
||||
"immutable evidence changed",
|
||||
)
|
||||
denylist = validator.load_json(root / "manifests/artifact-denylist.json")
|
||||
entry = denylist["entries"][0]
|
||||
require(entry["sha256"] == validator.BLOCKED_HASH, "denylist hash changed")
|
||||
require(entry["permanent"] is True, "denylist is not permanent")
|
||||
require(
|
||||
entry["execution_eligible"] is False,
|
||||
"denylisted artifact became eligible",
|
||||
)
|
||||
|
||||
for name, function in cases:
|
||||
try:
|
||||
function()
|
||||
except Exception as error:
|
||||
raise RuntimeError(
|
||||
f"Phase-0.9C feasibility case failed: {name}: {error}"
|
||||
) from error
|
||||
|
||||
print(f"Phase-0.9C feasibility host tests: {len(cases)}/{len(cases)} PASS")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,362 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Deterministic, networkless tests for the Phase-0.9C host protocol model."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from types import ModuleType
|
||||
from typing import Callable
|
||||
|
||||
|
||||
def load_module(path: Path) -> ModuleType:
|
||||
spec = importlib.util.spec_from_file_location("phase09c_model", 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[spec.name] = module
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def require(condition: bool, message: str) -> None:
|
||||
if not condition:
|
||||
raise RuntimeError(message)
|
||||
|
||||
|
||||
def request(
|
||||
model: ModuleType,
|
||||
*,
|
||||
firmware_one: str | None = "9.60",
|
||||
firmware_two: str | None = "9.60",
|
||||
requested: int = 0b11,
|
||||
) -> object:
|
||||
return model.ResultRequest(
|
||||
execution_nonce=b"N" * 16,
|
||||
request_id=b"R" * 16,
|
||||
firmware_source_one=firmware_one,
|
||||
firmware_source_two=firmware_two,
|
||||
observer_version=1,
|
||||
requested_capabilities=requested,
|
||||
artifact_sha256=bytes.fromhex("11" * 32),
|
||||
deadline_monotonic_ns=10_000,
|
||||
)
|
||||
|
||||
|
||||
def mutate(record: bytes, offset: int, value: int) -> bytes:
|
||||
changed = bytearray(record)
|
||||
changed[offset] ^= value
|
||||
return bytes(changed)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
root = args.root.resolve()
|
||||
model = load_module(root / "tests/phase09c_feasibility_model.py")
|
||||
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
|
||||
|
||||
@case("startup callgraph")
|
||||
def _() -> None:
|
||||
require(model.validate_startup_model() == [], "startup model invalid")
|
||||
|
||||
@case("prohibited startup effects")
|
||||
def _() -> None:
|
||||
require(
|
||||
model.PROHIBITED_STARTUP_EFFECTS
|
||||
<= model.NORMAL_SDK_REACHABLE_PROHIBITED_EFFECTS,
|
||||
"normal SDK effects hidden",
|
||||
)
|
||||
|
||||
@case("freestanding dependency closure")
|
||||
def _() -> None:
|
||||
blockers = set(model.freestanding_blockers())
|
||||
require(
|
||||
{
|
||||
"stack_alignment",
|
||||
"callable_read_abi",
|
||||
"callable_monotonic_time_abi",
|
||||
"process_exit_abi",
|
||||
"return_cleanup",
|
||||
"bounded_result_copyout",
|
||||
}
|
||||
<= blockers,
|
||||
"freestanding blockers missing",
|
||||
)
|
||||
|
||||
@case("exit state machine")
|
||||
def _() -> None:
|
||||
for path in model.EXIT_PATHS:
|
||||
require(not model.exit_path_is_safe(path), f"{path} became safe")
|
||||
require("SIGKILL" in model.EXIT_PATHS["timeout"], "timeout kill hidden")
|
||||
|
||||
@case("output framing")
|
||||
def _() -> None:
|
||||
expected = request(model)
|
||||
record = model.build_result(
|
||||
expected, b"ok", observed_capabilities=0b11
|
||||
)
|
||||
require(len(record) == 4096, "record is not fixed-size")
|
||||
require(
|
||||
model.validate_result(record, expected, now_monotonic_ns=1)
|
||||
== "VALID_COMPLETE_RESULT",
|
||||
"valid result rejected",
|
||||
)
|
||||
|
||||
@case("maximum lengths")
|
||||
def _() -> None:
|
||||
expected = request(model, requested=0)
|
||||
record = model.build_result(expected, b"x" * model.MAX_BODY_SIZE)
|
||||
require(
|
||||
model.validate_result(record, expected, now_monotonic_ns=1)
|
||||
== "VALID_COMPLETE_RESULT",
|
||||
"maximum body rejected",
|
||||
)
|
||||
try:
|
||||
model.build_result(expected, b"x" * (model.MAX_BODY_SIZE + 1))
|
||||
except ValueError:
|
||||
return
|
||||
raise RuntimeError("oversized body accepted")
|
||||
|
||||
@case("truncation")
|
||||
def _() -> None:
|
||||
expected = request(model, requested=0)
|
||||
record = model.build_result(
|
||||
expected, b"x" * (model.MAX_BODY_SIZE + 1), truncate=True
|
||||
)
|
||||
require(
|
||||
model.validate_result(record, expected, now_monotonic_ns=1)
|
||||
== "BLOCKED_TRUNCATED",
|
||||
"truncation accepted",
|
||||
)
|
||||
|
||||
@case("stale nonce")
|
||||
def _() -> None:
|
||||
expected = request(model)
|
||||
record = model.build_result(
|
||||
expected, b"", observed_capabilities=0b11
|
||||
)
|
||||
stale = model.ResultRequest(
|
||||
execution_nonce=b"S" * 16,
|
||||
request_id=expected.request_id,
|
||||
firmware_source_one="9.60",
|
||||
firmware_source_two="9.60",
|
||||
observer_version=1,
|
||||
requested_capabilities=0b11,
|
||||
artifact_sha256=expected.artifact_sha256,
|
||||
deadline_monotonic_ns=10_000,
|
||||
)
|
||||
require(
|
||||
model.validate_result(record, stale, now_monotonic_ns=1)
|
||||
== "BLOCKED_STALE_NONCE",
|
||||
"stale nonce accepted",
|
||||
)
|
||||
|
||||
@case("duplicate result")
|
||||
def _() -> None:
|
||||
expected = request(model)
|
||||
record = model.build_result(
|
||||
expected, b"", observed_capabilities=0b11
|
||||
)
|
||||
consumer = model.ResultConsumer()
|
||||
require(
|
||||
consumer.consume(record, expected, now_monotonic_ns=1)
|
||||
== "VALID_COMPLETE_RESULT",
|
||||
"first result rejected",
|
||||
)
|
||||
require(
|
||||
consumer.consume(record, expected, now_monotonic_ns=1)
|
||||
== "BLOCKED_DUPLICATE_RESULT",
|
||||
"duplicate accepted",
|
||||
)
|
||||
|
||||
@case("result checksum failure")
|
||||
def _() -> None:
|
||||
expected = request(model)
|
||||
record = model.build_result(
|
||||
expected, b"body", observed_capabilities=0b11
|
||||
)
|
||||
require(
|
||||
model.validate_result(
|
||||
mutate(record, model.HEADER_SIZE, 1),
|
||||
expected,
|
||||
now_monotonic_ns=1,
|
||||
)
|
||||
== "BLOCKED_RESULT_CHECKSUM",
|
||||
"checksum corruption accepted",
|
||||
)
|
||||
|
||||
@case("timeout")
|
||||
def _() -> None:
|
||||
expected = request(model)
|
||||
record = model.build_result(
|
||||
expected, b"", observed_capabilities=0b11
|
||||
)
|
||||
require(
|
||||
model.validate_result(record, expected, now_monotonic_ns=10_001)
|
||||
== "BLOCKED_TIMEOUT",
|
||||
"expired result accepted",
|
||||
)
|
||||
|
||||
@case("incomplete completion marker")
|
||||
def _() -> None:
|
||||
expected = request(model)
|
||||
record = model.build_result(
|
||||
expected, b"", observed_capabilities=0b11, complete=False
|
||||
)
|
||||
require(
|
||||
model.validate_result(record, expected, now_monotonic_ns=1)
|
||||
== "BLOCKED_INCOMPLETE",
|
||||
"incomplete record accepted",
|
||||
)
|
||||
|
||||
@case("unsupported capability")
|
||||
def _() -> None:
|
||||
expected = request(model)
|
||||
record = model.build_result(
|
||||
expected,
|
||||
b"",
|
||||
observed_capabilities=0b01,
|
||||
unsupported_capabilities=0b10,
|
||||
)
|
||||
require(
|
||||
model.validate_result(record, expected, now_monotonic_ns=1)
|
||||
== "VALID_RECORD_WITH_UNSUPPORTED_CAPABILITIES",
|
||||
"explicit unsupported result lost",
|
||||
)
|
||||
|
||||
@case("error versus empty success")
|
||||
def _() -> None:
|
||||
expected = request(model, requested=0)
|
||||
error = model.build_result(
|
||||
expected, b"", status=model.STATUS_OBSERVER_ERROR
|
||||
)
|
||||
empty = model.build_result(expected, b"")
|
||||
require(
|
||||
model.validate_result(error, expected, now_monotonic_ns=1)
|
||||
== "BLOCKED_OBSERVER_FAILURE",
|
||||
"empty failure became success",
|
||||
)
|
||||
require(
|
||||
model.validate_result(empty, expected, now_monotonic_ns=1)
|
||||
== "VALID_COMPLETE_RESULT",
|
||||
"valid no-capability empty result rejected",
|
||||
)
|
||||
|
||||
@case("cleanup status")
|
||||
def _() -> None:
|
||||
expected = request(model)
|
||||
record = model.build_result(
|
||||
expected,
|
||||
b"",
|
||||
observed_capabilities=0b11,
|
||||
cleanup_status=model.CLEANUP_FAILED,
|
||||
)
|
||||
require(
|
||||
model.validate_result(record, expected, now_monotonic_ns=1)
|
||||
== "BLOCKED_CLEANUP_NOT_PROVEN",
|
||||
"failed cleanup accepted",
|
||||
)
|
||||
|
||||
@case("conflicting firmware sources")
|
||||
def _() -> None:
|
||||
expected = request(
|
||||
model, firmware_one="9.60", firmware_two="9.61"
|
||||
)
|
||||
record = model.build_result(
|
||||
expected, b"", observed_capabilities=0b11
|
||||
)
|
||||
require(
|
||||
model.validate_result(record, expected, now_monotonic_ns=1)
|
||||
== "BLOCKED_FIRMWARE_CONFLICT",
|
||||
"firmware conflict accepted",
|
||||
)
|
||||
|
||||
@case("absent firmware source two")
|
||||
def _() -> None:
|
||||
expected = request(model, firmware_two=None)
|
||||
record = model.build_result(
|
||||
expected, b"", observed_capabilities=0b11
|
||||
)
|
||||
require(
|
||||
model.validate_result(record, expected, now_monotonic_ns=1)
|
||||
== "BLOCKED_FIRMWARE_SOURCE_2_ABSENT",
|
||||
"missing firmware source accepted",
|
||||
)
|
||||
|
||||
@case("unknown protocol version")
|
||||
def _() -> None:
|
||||
expected = request(model)
|
||||
record = model.build_result(
|
||||
expected,
|
||||
b"",
|
||||
observed_capabilities=0b11,
|
||||
protocol_version=2,
|
||||
)
|
||||
require(
|
||||
model.validate_result(record, expected, now_monotonic_ns=1)
|
||||
== "BLOCKED_UNKNOWN_VERSION",
|
||||
"unknown version accepted",
|
||||
)
|
||||
|
||||
@case("capability completeness")
|
||||
def _() -> None:
|
||||
expected = request(model)
|
||||
record = model.build_result(
|
||||
expected, b"", observed_capabilities=0b01
|
||||
)
|
||||
require(
|
||||
model.validate_result(record, expected, now_monotonic_ns=1)
|
||||
== "BLOCKED_INCOMPLETE_CAPABILITY_RESULT",
|
||||
"partial capability bitmap accepted",
|
||||
)
|
||||
|
||||
@case("side-effect classification")
|
||||
def _() -> None:
|
||||
effects = model.classify_side_effects("filesystem_content_hash")
|
||||
require("semantic_readonly" in effects, "semantic read missing")
|
||||
require("atime_effect_possible" in effects, "atime risk hidden")
|
||||
require("audit_effect_possible" in effects, "audit risk hidden")
|
||||
require("cache_effect_possible" in effects, "cache risk hidden")
|
||||
require("object_race_possible" in effects, "race risk hidden")
|
||||
require(
|
||||
not model.is_proven_side_effect_free("filesystem_content_hash"),
|
||||
"read was promoted to side-effect-free",
|
||||
)
|
||||
|
||||
@case("output architecture ordering")
|
||||
def _() -> None:
|
||||
require(
|
||||
list(model.OUTPUT_ARCHITECTURES)
|
||||
== [
|
||||
"D1_CALLER_OWNED_BOUNDED_BUFFER",
|
||||
"D2_EXISTING_REQUEST_RESPONSE",
|
||||
"D3_LOADER_OWNED_STATUS_RECORD",
|
||||
"D4_PROCESS_EXIT_STATUS",
|
||||
],
|
||||
"output architectures reordered",
|
||||
)
|
||||
|
||||
for name, function in cases:
|
||||
try:
|
||||
function()
|
||||
except Exception as error:
|
||||
raise RuntimeError(f"Phase-0.9C protocol case failed: {name}: {error}") from error
|
||||
|
||||
print(f"Phase-0.9C protocol host tests: {len(cases)}/{len(cases)} PASS")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,405 @@
|
||||
#!/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())
|
||||
@@ -0,0 +1,302 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Twenty host-only guardrails for the Phase-0.9E provenance audit."""
|
||||
|
||||
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(
|
||||
"phase09e_validator", root / "tools/validate_phase09e_bootstrap.py"
|
||||
)
|
||||
manifest = validator.load_json(
|
||||
root / "manifests/runtime/phase-0.9e-bootstrap-provenance.json"
|
||||
)
|
||||
protocol_manifest = validator.load_json(
|
||||
root / "manifests/runtime/phase-0.9e-loader-protocol.json"
|
||||
)
|
||||
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
|
||||
|
||||
@case("01 public upstream is not exact-used without provenance")
|
||||
def _() -> None:
|
||||
artifact = copy.deepcopy(manifest["artifacts"][11])
|
||||
artifact["confidence"] = "EXACT_USED"
|
||||
require(
|
||||
bool(validator.provenance_errors(artifact)),
|
||||
"public upstream was promoted to exact-used",
|
||||
)
|
||||
|
||||
@case("02 opaque binary gets no invented source commit")
|
||||
def _() -> None:
|
||||
artifact = copy.deepcopy(manifest["artifacts"][1])
|
||||
artifact["source_commit"] = "0" * 40
|
||||
require(
|
||||
bool(validator.provenance_errors(artifact)),
|
||||
"opaque binary accepted an invented source commit",
|
||||
)
|
||||
|
||||
@case("03 missing bootstrap implementation is classified")
|
||||
def _() -> None:
|
||||
require(
|
||||
validator.rescue_classification(actual_package_available=False)
|
||||
== "BOOTSTRAP_IMPLEMENTATION_MISSING",
|
||||
"missing implementation was not blocked",
|
||||
)
|
||||
|
||||
@case("04 an elfldr dependency is not independent")
|
||||
def _() -> None:
|
||||
require(
|
||||
validator.rescue_classification(
|
||||
actual_package_available=True, requires_elfldr=True
|
||||
)
|
||||
== "SELF_OR_CROSS_DEPENDENT",
|
||||
"elfldr-dependent bootstrap was marked independent",
|
||||
)
|
||||
|
||||
@case("05 a Payload Manager dependency is not independent")
|
||||
def _() -> None:
|
||||
require(
|
||||
validator.rescue_classification(
|
||||
actual_package_available=True, requires_payload_manager=True
|
||||
)
|
||||
== "SELF_OR_CROSS_DEPENDENT",
|
||||
"manager-dependent bootstrap was marked independent",
|
||||
)
|
||||
|
||||
@case("06 live replacement cannot be a safe rescue executor")
|
||||
def _() -> None:
|
||||
require(
|
||||
validator.rescue_classification(
|
||||
actual_package_available=True, replaces_live_component=True
|
||||
)
|
||||
== "NO_INDEPENDENT_RESCUE_PATH",
|
||||
"live-replacement bootstrap was accepted",
|
||||
)
|
||||
|
||||
@case("07 host-to-memory needs receive mapping and entrypoint code")
|
||||
def _() -> None:
|
||||
partials = (
|
||||
(True, False, False),
|
||||
(False, True, False),
|
||||
(False, False, True),
|
||||
(True, True, False),
|
||||
)
|
||||
for receive, mapping, entrypoint in partials:
|
||||
require(
|
||||
validator.host_to_memory_classification(
|
||||
receive_code=receive,
|
||||
mapping_code=mapping,
|
||||
entrypoint_code=entrypoint,
|
||||
)
|
||||
!= "PROVEN_FROM_SOURCE",
|
||||
"incomplete host-to-memory evidence passed",
|
||||
)
|
||||
|
||||
@case("08 conceptual port-9020 text is not protocol proof")
|
||||
def _() -> None:
|
||||
require(
|
||||
bool(validator.validate_protocol(protocol_manifest)) is False,
|
||||
"canonical unknown protocol does not validate",
|
||||
)
|
||||
require(
|
||||
protocol_manifest["classification"]
|
||||
== "CONCEPTUAL_9020_DESCRIPTION_IS_NOT_PROTOCOL_PROOF",
|
||||
"conceptual protocol was promoted",
|
||||
)
|
||||
|
||||
@case("09 full protocol model requires framing length and partial I/O")
|
||||
def _() -> None:
|
||||
protocol = copy.deepcopy(protocol_manifest["protocol"])
|
||||
protocol["maximum_payload_size"] = 1024
|
||||
protocol["headers"] = "FIXED"
|
||||
protocol["length_fields"] = "U32"
|
||||
protocol["bounds_checks"] = "PRESENT"
|
||||
protocol["short_read_detection"] = "PRESENT"
|
||||
require(
|
||||
validator.protocol_model_complete(protocol) is False,
|
||||
"protocol without short-write handling passed",
|
||||
)
|
||||
protocol["short_write_detection"] = "PRESENT"
|
||||
require(
|
||||
validator.protocol_model_complete(protocol) is True,
|
||||
"complete synthetic protocol shape was rejected",
|
||||
)
|
||||
|
||||
@case("10 a temporary socket is not automatically brick relevant")
|
||||
def _() -> None:
|
||||
require(
|
||||
validator.risk_classification(temporary_socket=True)
|
||||
!= "BRICK_RELEVANT",
|
||||
"temporary socket was overclassified",
|
||||
)
|
||||
|
||||
@case("11 live filesystem write is brick relevant")
|
||||
def _() -> None:
|
||||
require(
|
||||
validator.risk_classification(live_filesystem_write=True)
|
||||
== "BRICK_RELEVANT",
|
||||
"live filesystem write was underclassified",
|
||||
)
|
||||
|
||||
@case("12 autoload activation is brick relevant")
|
||||
def _() -> None:
|
||||
require(
|
||||
validator.risk_classification(autoload_activation=True)
|
||||
== "BRICK_RELEVANT",
|
||||
"autoload activation was underclassified",
|
||||
)
|
||||
|
||||
@case("13 automatic retry remains forbidden")
|
||||
def _() -> None:
|
||||
changed = copy.deepcopy(manifest)
|
||||
changed["authorization"]["automatic_retry"] = True
|
||||
changed["decisions"]["automatic_retry"] = True
|
||||
require(
|
||||
bool(validator.validate_manifest(changed)),
|
||||
"automatic retry was accepted",
|
||||
)
|
||||
|
||||
@case("14 general expectation cannot prove reboot recovery")
|
||||
def _() -> None:
|
||||
require(
|
||||
validator.reboot_classification(
|
||||
exact_package=False,
|
||||
source_design_restartable=False,
|
||||
hardware_observed=False,
|
||||
)
|
||||
== "REBOOT_RECOVERY_UNPROVEN",
|
||||
"general reboot expectation was promoted to proof",
|
||||
)
|
||||
|
||||
@case("15 restartable description without exact package is at most partial")
|
||||
def _() -> None:
|
||||
require(
|
||||
validator.rescue_classification(
|
||||
actual_package_available=False,
|
||||
all_required_properties_proven=True,
|
||||
)
|
||||
!= "INDEPENDENT_RESCUE_EXECUTOR_CANDIDATE",
|
||||
"description-only chain was marked independent",
|
||||
)
|
||||
|
||||
@case("16 Phase 0.9F is blocked when implementation is missing")
|
||||
def _() -> None:
|
||||
require(
|
||||
validator.phase09f_design_allowed(
|
||||
actual_package_available=False,
|
||||
independent_from_elfldr=True,
|
||||
independent_from_payload_manager=True,
|
||||
no_live_replacement=True,
|
||||
)
|
||||
is False,
|
||||
"Phase 0.9F was allowed without implementation",
|
||||
)
|
||||
|
||||
@case("17 Phase 0.9F is blocked by component dependency")
|
||||
def _() -> None:
|
||||
require(
|
||||
validator.phase09f_design_allowed(
|
||||
actual_package_available=True,
|
||||
independent_from_elfldr=False,
|
||||
independent_from_payload_manager=True,
|
||||
no_live_replacement=True,
|
||||
)
|
||||
is False,
|
||||
"Phase 0.9F was allowed with elfldr dependency",
|
||||
)
|
||||
|
||||
@case("18 no target source is added")
|
||||
def _() -> None:
|
||||
errors = validator.phase09e_path_errors(
|
||||
{"docs/runtime/phase-0.9e-note.md", "src/backends/ps5/rescue.c"}
|
||||
)
|
||||
require(
|
||||
any("target path" in error or "target artifact/source" in error for error in errors),
|
||||
"target source guard did not fire",
|
||||
)
|
||||
require(
|
||||
not validator.phase09e_path_errors(
|
||||
{"docs/runtime/phase-0.9e-note.md"}
|
||||
),
|
||||
"documentation was rejected as target source",
|
||||
)
|
||||
|
||||
@case("19 no target artifact is added")
|
||||
def _() -> None:
|
||||
require(
|
||||
bool(
|
||||
validator.phase09e_path_errors(
|
||||
{"packaging/phase09e/rescue.elf"}
|
||||
)
|
||||
),
|
||||
"target artifact guard did not fire",
|
||||
)
|
||||
|
||||
@case("20 every authorization field remains false")
|
||||
def _() -> None:
|
||||
require(
|
||||
all(
|
||||
manifest["authorization"].get(field) is False
|
||||
for field in validator.AUTHORIZATION_FIELDS
|
||||
),
|
||||
"authorization field became true",
|
||||
)
|
||||
changed = copy.deepcopy(manifest)
|
||||
changed["authorization"]["transfer_authorized"] = True
|
||||
require(
|
||||
bool(validator.validate_manifest(changed)),
|
||||
"true authorization was accepted",
|
||||
)
|
||||
|
||||
failures: list[str] = []
|
||||
for name, function in cases:
|
||||
try:
|
||||
function()
|
||||
except Exception as error: # noqa: BLE001 - test harness reports all cases.
|
||||
failures.append(f"{name}: {error}")
|
||||
if len(cases) != 20:
|
||||
failures.append(f"expected 20 cases, found {len(cases)}")
|
||||
if failures:
|
||||
for failure in failures:
|
||||
print(f"FAIL: {failure}")
|
||||
return 1
|
||||
print("Phase-0.9E bootstrap guardrails: 20/20 PASS")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,410 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Twenty-four host-only guardrails for Phase-0.9E-R2."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from io import BytesIO
|
||||
import hashlib
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import tempfile
|
||||
from types import ModuleType
|
||||
from typing import Callable
|
||||
import zipfile
|
||||
|
||||
|
||||
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(
|
||||
"phase09er2_validator",
|
||||
root / "tools/validate_phase09er2_correlation.py",
|
||||
)
|
||||
inspector = load_module(
|
||||
"phase09er2_inspector", root / "tools/inspect_siecaf_header.py"
|
||||
)
|
||||
manifest = validator.load_json(
|
||||
root / "manifests/runtime/phase-0.9e-r2-inner-correlation.json"
|
||||
)
|
||||
fingerprints = validator.load_json(
|
||||
root / "manifests/runtime/phase-0.9e-r2-siecaf-fingerprints.json"
|
||||
)
|
||||
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 build_archive(
|
||||
*,
|
||||
count: int = 1,
|
||||
metadata_ids: list[int] | None = None,
|
||||
offsets: list[int] | None = None,
|
||||
payload_byte: int = 0xA5,
|
||||
) -> bytes:
|
||||
alignment = inspector.ALIGNMENT
|
||||
file_offset = alignment
|
||||
file_size = count * alignment
|
||||
metadata_ids = metadata_ids or list(range(10000, 10000 + count))
|
||||
offsets = offsets or [file_offset + index * alignment for index in range(count)]
|
||||
header = inspector.HEADER.pack(
|
||||
inspector.MAGIC,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
bytes(range(16)),
|
||||
bytes(range(12)),
|
||||
0,
|
||||
count,
|
||||
file_offset,
|
||||
file_size,
|
||||
)
|
||||
metadata = bytearray()
|
||||
hashes = bytearray()
|
||||
for index in range(count):
|
||||
metadata.extend(
|
||||
inspector.SEGMENT_META.pack(
|
||||
metadata_ids[index],
|
||||
0,
|
||||
0,
|
||||
offsets[index],
|
||||
alignment,
|
||||
3,
|
||||
1,
|
||||
b"\x00" * 12,
|
||||
0,
|
||||
123,
|
||||
)
|
||||
)
|
||||
hashes.extend(
|
||||
inspector.SECTION_HASH.pack(
|
||||
index,
|
||||
0,
|
||||
bytes([index + 1]) * 16,
|
||||
b"\x00" * 24,
|
||||
)
|
||||
)
|
||||
tables = header + metadata + hashes
|
||||
return (
|
||||
tables
|
||||
+ b"\x00" * (file_offset - len(tables))
|
||||
+ bytes([payload_byte]) * file_size
|
||||
)
|
||||
|
||||
@case("01 outer mismatch does not exclude an inner match")
|
||||
def _() -> None:
|
||||
require(
|
||||
"a" * 64 != "b" * 64
|
||||
and validator.inner_byte_match(
|
||||
left_size=10,
|
||||
right_size=10,
|
||||
left_sha256="c" * 64,
|
||||
right_sha256="c" * 64,
|
||||
full_byte_equal=True,
|
||||
),
|
||||
"outer mismatch incorrectly excluded an independent inner match",
|
||||
)
|
||||
|
||||
@case("02 outer filename is not inner provenance")
|
||||
def _() -> None:
|
||||
require(
|
||||
not validator.inner_byte_match(
|
||||
left_size=10,
|
||||
right_size=10,
|
||||
left_sha256="a" * 64,
|
||||
right_sha256="b" * 64,
|
||||
full_byte_equal=False,
|
||||
),
|
||||
"filename-equivalent containers established inner provenance",
|
||||
)
|
||||
|
||||
@case("03 recompression changes outer bytes without changing inner")
|
||||
def _() -> None:
|
||||
payload = b"same-inner-content" * 128
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
first = Path(directory) / "first.zip"
|
||||
second = Path(directory) / "second.zip"
|
||||
with zipfile.ZipFile(first, "w", compression=zipfile.ZIP_STORED) as out:
|
||||
out.writestr("archive.dat", payload)
|
||||
with zipfile.ZipFile(second, "w", compression=zipfile.ZIP_DEFLATED) as out:
|
||||
out.writestr("archive.dat", payload)
|
||||
require(
|
||||
hashlib.sha256(first.read_bytes()).digest()
|
||||
!= hashlib.sha256(second.read_bytes()).digest(),
|
||||
"different ZIP encodings unexpectedly matched",
|
||||
)
|
||||
with zipfile.ZipFile(first) as left, zipfile.ZipFile(second) as right:
|
||||
require(
|
||||
left.read("archive.dat") == right.read("archive.dat"),
|
||||
"recompression changed the inner bytes",
|
||||
)
|
||||
|
||||
@case("04 inner byte match requires size hash and complete bytes")
|
||||
def _() -> None:
|
||||
require(
|
||||
validator.inner_byte_match(
|
||||
left_size=1,
|
||||
right_size=1,
|
||||
left_sha256="a" * 64,
|
||||
right_sha256="a" * 64,
|
||||
full_byte_equal=True,
|
||||
)
|
||||
and not validator.inner_byte_match(
|
||||
left_size=1,
|
||||
right_size=1,
|
||||
left_sha256="a" * 64,
|
||||
right_sha256="a" * 64,
|
||||
full_byte_equal=False,
|
||||
),
|
||||
"complete-byte requirement failed",
|
||||
)
|
||||
|
||||
@case("05 size-only match is not a byte match")
|
||||
def _() -> None:
|
||||
require(
|
||||
validator.classify_inner_comparison(
|
||||
download_valid=True,
|
||||
inner_present=True,
|
||||
left_size=10,
|
||||
right_size=10,
|
||||
left_sha256="a" * 64,
|
||||
right_sha256="b" * 64,
|
||||
full_byte_equal=False,
|
||||
)
|
||||
== "INNER_ARCHIVE_SIZE_ONLY_MATCH",
|
||||
"size-only result was promoted",
|
||||
)
|
||||
|
||||
@case("06 structural equality is not content equality")
|
||||
def _() -> None:
|
||||
left = build_archive(payload_byte=0x11)
|
||||
right = build_archive(payload_byte=0x22)
|
||||
left_result = inspector.inspect_siecaf(BytesIO(left), len(left))
|
||||
right_result = inspector.inspect_siecaf(BytesIO(right), len(right))
|
||||
require(
|
||||
inspector.compare_structures(left_result, right_result)["classification"]
|
||||
== "SIECAF_STRUCTURAL_EXACT"
|
||||
and hashlib.sha256(left).digest() != hashlib.sha256(right).digest(),
|
||||
"structure was treated as decrypted/content identity",
|
||||
)
|
||||
|
||||
@case("07 malformed segment table is rejected")
|
||||
def _() -> None:
|
||||
malformed = build_archive()[: inspector.HEADER.size + 5]
|
||||
result = inspector.inspect_siecaf(BytesIO(malformed), len(malformed))
|
||||
require(result["classification"] == "SIECAF_MALFORMED", str(result))
|
||||
|
||||
@case("08 integer overflow is rejected")
|
||||
def _() -> None:
|
||||
header = inspector.HEADER.pack(
|
||||
inspector.MAGIC,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
b"\x00" * 16,
|
||||
b"\x00" * 12,
|
||||
0,
|
||||
inspector.UINT64_MAX,
|
||||
inspector.ALIGNMENT,
|
||||
inspector.ALIGNMENT,
|
||||
)
|
||||
result = inspector.inspect_siecaf(BytesIO(header), len(header))
|
||||
require(result["classification"] == "SIECAF_MALFORMED", str(result))
|
||||
|
||||
@case("09 out-of-range offset is rejected")
|
||||
def _() -> None:
|
||||
value = build_archive(offsets=[inspector.ALIGNMENT * 2])
|
||||
result = inspector.inspect_siecaf(BytesIO(value), len(value))
|
||||
require(result["classification"] == "SIECAF_MALFORMED", str(result))
|
||||
|
||||
@case("10 overlapping ranges are reported")
|
||||
def _() -> None:
|
||||
value = build_archive(
|
||||
count=2, offsets=[inspector.ALIGNMENT, inspector.ALIGNMENT]
|
||||
)
|
||||
result = inspector.inspect_siecaf(BytesIO(value), len(value))
|
||||
require(
|
||||
result["classification"] == "SIECAF_MALFORMED" and result["overlaps"],
|
||||
str(result),
|
||||
)
|
||||
|
||||
@case("11 duplicate section identity is reported")
|
||||
def _() -> None:
|
||||
value = build_archive(count=2, metadata_ids=[10000, 10000])
|
||||
result = inspector.inspect_siecaf(BytesIO(value), len(value))
|
||||
require(
|
||||
result["classification"] == "SIECAF_MALFORMED"
|
||||
and result["duplicate_metadata_section_keys"],
|
||||
str(result),
|
||||
)
|
||||
|
||||
@case("12 official inner match would not make MediaFire outer official")
|
||||
def _() -> None:
|
||||
require(
|
||||
manifest["outer_zip_official"] is False,
|
||||
"outer MediaFire ZIP was promoted to official",
|
||||
)
|
||||
|
||||
@case("13 community source family is not an official community asset")
|
||||
def _() -> None:
|
||||
itsplk = manifest["community_families"][0]
|
||||
require(
|
||||
itsplk["classification"] == "THIRD_PARTY_BUILD_FROM_PUBLIC_SOURCE_POSSIBLE"
|
||||
and itsplk["system_backup_distributed_by_project"] is False,
|
||||
"third-party build possibility became an official release",
|
||||
)
|
||||
|
||||
@case("14 browser history is not fully exported")
|
||||
def _() -> None:
|
||||
browser = manifest["local_download_provenance"]["browser_history"]
|
||||
require(
|
||||
browser["full_history_exported"] is False
|
||||
and browser["matching_records"] == 0,
|
||||
"browser-history boundary changed",
|
||||
)
|
||||
|
||||
@case("15 signed URL material is redacted")
|
||||
def _() -> None:
|
||||
host = manifest["local_download_provenance"]["zone_identifier"]["host_url"]
|
||||
require(
|
||||
"<redacted-signed-segment>" in host["path_redacted"]
|
||||
and len(host["full_value_sha256"]) == 64,
|
||||
"signed path was not safely represented",
|
||||
)
|
||||
|
||||
@case("16 no large backup is tracked")
|
||||
def _() -> None:
|
||||
for relative in validator.git(root, "ls-files").splitlines():
|
||||
path = root / relative
|
||||
require(
|
||||
not path.is_file()
|
||||
or path.stat().st_size <= validator.MAX_TRACKED_FILE_SIZE,
|
||||
f"large tracked file: {relative}",
|
||||
)
|
||||
|
||||
@case("17 no downloaded file was executed")
|
||||
def _() -> None:
|
||||
require(
|
||||
manifest["actions"]["downloaded_file_executed"] is False,
|
||||
"download execution was recorded",
|
||||
)
|
||||
|
||||
@case("18 ps5-bar-tool was not executed")
|
||||
def _() -> None:
|
||||
require(
|
||||
manifest["actions"]["ps5_bar_tool_executed"] is False
|
||||
and fingerprints["parser_evidence"]["ps5_bar_tool_executed"] is False,
|
||||
"ps5-bar-tool execution was recorded",
|
||||
)
|
||||
|
||||
@case("19 no PS5 hostname or IP was used")
|
||||
def _() -> None:
|
||||
require(
|
||||
manifest["actions"]["ps5_connected"] is False
|
||||
and manifest["actions"]["ps5_ip_used"] is False,
|
||||
"PS5 network use was recorded",
|
||||
)
|
||||
|
||||
@case("20 no target source appears")
|
||||
def _() -> None:
|
||||
try:
|
||||
validator.git(root, "cat-file", "-e", f"{validator.BASELINE}^{{commit}}")
|
||||
except RuntimeError:
|
||||
# A parentless public release intentionally has no private history.
|
||||
# The historical delta remains enforced in the canonical repository.
|
||||
return
|
||||
changed = set(
|
||||
filter(
|
||||
None,
|
||||
validator.git(
|
||||
root,
|
||||
"diff",
|
||||
"--name-only",
|
||||
"--diff-filter=ACMR",
|
||||
validator.BASELINE,
|
||||
).splitlines(),
|
||||
)
|
||||
)
|
||||
for relative in changed:
|
||||
normalized = relative.replace("\\", "/")
|
||||
require(
|
||||
not normalized.startswith(validator.FORBIDDEN_PREFIXES)
|
||||
and Path(normalized).suffix.lower() not in validator.FORBIDDEN_SUFFIXES,
|
||||
f"target/binary material appeared: {normalized}",
|
||||
)
|
||||
|
||||
@case("21 every authorization remains false")
|
||||
def _() -> None:
|
||||
for value in (manifest, fingerprints):
|
||||
for field in validator.AUTHORIZATION_FIELDS:
|
||||
require(
|
||||
value["authorization"][field] is False,
|
||||
f"authorization changed: {field}",
|
||||
)
|
||||
|
||||
@case("22 automatic retry remains false")
|
||||
def _() -> None:
|
||||
require(
|
||||
manifest["authorization"]["automatic_retry"] is False
|
||||
and manifest["actions"]["automatic_retry_used"] is False,
|
||||
"automatic retry was enabled or used",
|
||||
)
|
||||
|
||||
@case("23 outer metadata alone cannot open Phase 0.9F")
|
||||
def _() -> None:
|
||||
require(
|
||||
not validator.phase09f_reconsideration_allowed(
|
||||
inner_source_bound=False,
|
||||
mediafire_maker_source_bound=False,
|
||||
auditable_bootstrap_closure=True,
|
||||
),
|
||||
"outer metadata opened Phase 0.9F",
|
||||
)
|
||||
|
||||
@case("24 runtime deployment remains unproven")
|
||||
def _() -> None:
|
||||
require(
|
||||
manifest["runtime_deployment_verified"] is False
|
||||
and manifest["current_device_contents_verified"] is False
|
||||
and manifest["runtime_firmware_9_60"] == "UNPROVEN",
|
||||
"host-only evidence became runtime proof",
|
||||
)
|
||||
|
||||
for name, function in cases:
|
||||
try:
|
||||
function()
|
||||
except Exception as error:
|
||||
raise RuntimeError(f"{name}: {error}") from error
|
||||
print(f"Phase-0.9E-R2 guardrails: {len(cases)}/{len(cases)} PASS")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,323 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Twenty-two host-only guardrails for Phase 0.9E-R."""
|
||||
|
||||
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(
|
||||
"phase09er_validator", root / "tools/validate_phase09er_provenance.py"
|
||||
)
|
||||
manifest = validator.load_json(
|
||||
root / "manifests/runtime/phase-0.9e-r-release-correlation.json"
|
||||
)
|
||||
port_manifest = validator.load_json(
|
||||
root / "manifests/runtime/phase-0.9e-r-port9020-audit.json"
|
||||
)
|
||||
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
|
||||
|
||||
@case("01 name match alone is not a byte match")
|
||||
def _() -> None:
|
||||
require(
|
||||
not validator.official_byte_match(
|
||||
official_source=True,
|
||||
local_name="same.zip",
|
||||
local_size=1,
|
||||
local_sha256="a" * 64,
|
||||
asset_name="same.zip",
|
||||
asset_size=2,
|
||||
asset_sha256="b" * 64,
|
||||
),
|
||||
"name-only candidate became a byte match",
|
||||
)
|
||||
|
||||
@case("02 size match alone is not a byte match")
|
||||
def _() -> None:
|
||||
require(
|
||||
not validator.official_byte_match(
|
||||
official_source=True,
|
||||
local_name="local.zip",
|
||||
local_size=10,
|
||||
local_sha256="a" * 64,
|
||||
asset_name="asset.zip",
|
||||
asset_size=10,
|
||||
asset_sha256="b" * 64,
|
||||
),
|
||||
"size-only candidate became a byte match",
|
||||
)
|
||||
|
||||
@case("03 hash match also requires exact byte count")
|
||||
def _() -> None:
|
||||
require(
|
||||
not validator.official_byte_match(
|
||||
official_source=True,
|
||||
local_name="same.zip",
|
||||
local_size=10,
|
||||
local_sha256="a" * 64,
|
||||
asset_name="same.zip",
|
||||
asset_size=11,
|
||||
asset_sha256="a" * 64,
|
||||
),
|
||||
"hash with a different byte count became a match",
|
||||
)
|
||||
|
||||
@case("04 a mirror cannot establish an official byte match")
|
||||
def _() -> None:
|
||||
require(
|
||||
not validator.official_byte_match(
|
||||
official_source=False,
|
||||
local_name="same.zip",
|
||||
local_size=10,
|
||||
local_sha256="a" * 64,
|
||||
asset_name="same.zip",
|
||||
asset_size=10,
|
||||
asset_sha256="a" * 64,
|
||||
),
|
||||
"non-official mirror established an official match",
|
||||
)
|
||||
|
||||
@case("05 release association is not inner-content binding")
|
||||
def _() -> None:
|
||||
require(
|
||||
validator.source_binding(
|
||||
release_associated=True,
|
||||
inner_bytes_matched=False,
|
||||
inner_opaque=True,
|
||||
)
|
||||
== "SOURCE_ONLY_ASSOCIATION",
|
||||
"release association was promoted to reproducible content",
|
||||
)
|
||||
|
||||
@case("06 opaque SIECAF receives no invented provenance")
|
||||
def _() -> None:
|
||||
require(
|
||||
manifest["inner_archive"]["classification"] == "OPAQUE_UNBOUND"
|
||||
and manifest["inner_archive"]["further_reverse_engineering_performed"]
|
||||
is False,
|
||||
"opaque inner archive provenance was overclaimed",
|
||||
)
|
||||
|
||||
@case("07 public release commits are exact")
|
||||
def _() -> None:
|
||||
for tag in manifest["tags"]:
|
||||
require(
|
||||
len(tag["commit"]) == 40
|
||||
and all(character in "0123456789abcdef" for character in tag["commit"]),
|
||||
f"release commit is not exact: {tag['tag']}",
|
||||
)
|
||||
|
||||
@case("08 each source archive is hashed")
|
||||
def _() -> None:
|
||||
for tag in manifest["tags"]:
|
||||
require(
|
||||
len(tag["source_archive_sha256"]) == 64,
|
||||
f"source archive hash missing: {tag['tag']}",
|
||||
)
|
||||
|
||||
@case("09 upstream worktree must be clean")
|
||||
def _() -> None:
|
||||
changed = copy.deepcopy(manifest)
|
||||
changed["upstream_worktree"]["clean"] = False
|
||||
require(
|
||||
bool(validator.validate_release_manifest(changed)),
|
||||
"dirty upstream worktree passed",
|
||||
)
|
||||
|
||||
@case("10 port reference is not an implementation")
|
||||
def _() -> None:
|
||||
require(
|
||||
not validator.port_implementation_sufficient("PORT_9020_REFERENCE_ONLY"),
|
||||
"reference-only text was accepted as implementation",
|
||||
)
|
||||
|
||||
@case("11 embedded bytes receive no invented source commit")
|
||||
def _() -> None:
|
||||
changed = copy.deepcopy(port_manifest)
|
||||
changed["port_9021_relation"]["source_commit"] = "0" * 40
|
||||
require(
|
||||
bool(validator.validate_port_manifest(changed)),
|
||||
"embedded loader bytes accepted an invented source commit",
|
||||
)
|
||||
|
||||
@case("12 a sender without receive is not duplex")
|
||||
def _() -> None:
|
||||
require(
|
||||
not validator.sender_duplex(sends=True, receives=False),
|
||||
"one-way sender became duplex",
|
||||
)
|
||||
|
||||
@case("13 missing short-send handling is a deficiency")
|
||||
def _() -> None:
|
||||
require(
|
||||
validator.sender_short_send_deficiency(
|
||||
uses_sendall=False, explicit_send_loop=False
|
||||
),
|
||||
"missing short-send handling was accepted",
|
||||
)
|
||||
require(
|
||||
not validator.sender_short_send_deficiency(
|
||||
uses_sendall=True, explicit_send_loop=False
|
||||
),
|
||||
"sendall was incorrectly marked as lacking short-send handling",
|
||||
)
|
||||
|
||||
@case("14 operator attestation is not runtime evidence")
|
||||
def _() -> None:
|
||||
require(
|
||||
not validator.attestation_is_runtime_proof(
|
||||
attested=True, hardware_observed=False
|
||||
),
|
||||
"operator attestation became runtime proof",
|
||||
)
|
||||
|
||||
@case("15 empty attestation authorizes nothing")
|
||||
def _() -> None:
|
||||
text = (
|
||||
root
|
||||
/ "docs/approvals/phase-0.9e-r-y2jb-deployed-use-attestation.md"
|
||||
).read_text(encoding="utf-8")
|
||||
require("attested: false" in text, "template became attested")
|
||||
for field in validator.AUTHORIZATION_FIELDS:
|
||||
require(f"{field}: false" in text, f"template omits false {field}")
|
||||
|
||||
@case("16 Phase 0.9F blocks without official match")
|
||||
def _() -> None:
|
||||
require(
|
||||
not validator.phase09f_design_allowed(
|
||||
correlation="OFFICIAL_RELEASE_NO_MATCH",
|
||||
release_commit_known=True,
|
||||
upstream_clean=True,
|
||||
port_classification="PORT_9020_IMPLEMENTATION_FOUND",
|
||||
sender_identified=True,
|
||||
attestation_available=True,
|
||||
all_authorizations_false=True,
|
||||
),
|
||||
"Phase 0.9F passed without an official asset match",
|
||||
)
|
||||
|
||||
@case("17 Phase 0.9F blocks without found or partial loader")
|
||||
def _() -> None:
|
||||
require(
|
||||
not validator.phase09f_design_allowed(
|
||||
correlation="OFFICIAL_RELEASE_BYTE_MATCH",
|
||||
release_commit_known=True,
|
||||
upstream_clean=True,
|
||||
port_classification="PORT_9020_REFERENCE_ONLY",
|
||||
sender_identified=True,
|
||||
attestation_available=True,
|
||||
all_authorizations_false=True,
|
||||
),
|
||||
"Phase 0.9F passed with a reference-only loader",
|
||||
)
|
||||
|
||||
@case("18 no target source appears")
|
||||
def _() -> None:
|
||||
errors = validator.phase09er_path_errors(
|
||||
{"docs/runtime/phase-0.9e-r-note.md", "src/backends/ps5/rescue.c"}
|
||||
)
|
||||
require(any("target" in error for error in errors), "target source guard failed")
|
||||
require(
|
||||
not validator.phase09er_path_errors(
|
||||
{"docs/runtime/phase-0.9e-r-note.md"}
|
||||
),
|
||||
"documentation was rejected as target source",
|
||||
)
|
||||
|
||||
@case("19 no target artifact appears")
|
||||
def _() -> None:
|
||||
require(
|
||||
bool(validator.phase09er_path_errors({"packaging/rescue.elf"})),
|
||||
"target artifact guard failed",
|
||||
)
|
||||
|
||||
@case("20 all authorization fields remain false")
|
||||
def _() -> None:
|
||||
changed = copy.deepcopy(manifest)
|
||||
changed["authorization"]["execution_authorized"] = True
|
||||
require(
|
||||
bool(validator.validate_release_manifest(changed)),
|
||||
"true authorization passed",
|
||||
)
|
||||
|
||||
@case("21 automatic retry remains false")
|
||||
def _() -> None:
|
||||
require(
|
||||
manifest["authorization"]["automatic_retry"] is False
|
||||
and port_manifest["authorization"]["automatic_retry"] is False
|
||||
and port_manifest["official_remote_js_loader"][
|
||||
"automatic_retry_authorized"
|
||||
]
|
||||
is False,
|
||||
"automatic retry was authorized",
|
||||
)
|
||||
|
||||
@case("22 no large release asset enters Git")
|
||||
def _() -> None:
|
||||
actions = manifest["actions"]
|
||||
require(
|
||||
actions["large_release_assets_downloaded"] == 0
|
||||
and actions["large_release_asset_bytes_downloaded"] == 0,
|
||||
"a large official release asset was recorded as downloaded",
|
||||
)
|
||||
for path in root.rglob("*"):
|
||||
if (
|
||||
path.is_file()
|
||||
and ".git" not in path.parts
|
||||
and "work" not in path.parts
|
||||
):
|
||||
require(
|
||||
path.stat().st_size <= 50 * 1024 * 1024,
|
||||
f"large release-like file entered repository: {path}",
|
||||
)
|
||||
|
||||
failures: list[str] = []
|
||||
for name, function in cases:
|
||||
try:
|
||||
function()
|
||||
except Exception as error: # guardrail harness reports all failures
|
||||
failures.append(f"{name}: {error}")
|
||||
if failures:
|
||||
for failure in failures:
|
||||
print(f"FAIL: {failure}")
|
||||
return 1
|
||||
print(f"Phase-0.9E-R provenance guardrails: {len(cases)}/{len(cases)} PASS")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,226 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Host-only integration tests for the Phase-1.0AA exact fake adapter."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from datetime import datetime, timezone
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
|
||||
CURRENT_COMMANDS = "authid browse cat cd chgrp chmod chown chroot cmp cp df echo env exec exit export file find grep hbdbg hbldr hexdump http2_get id kill launch ln ls mkdir mknod mount mv notify pkg_install procstat ps pwd reptyr rm rmdir sfocreate sfoinfo sleep stat sum suspend sync sysctl touch umount".split()
|
||||
|
||||
|
||||
def load(path: Path):
|
||||
spec = importlib.util.spec_from_file_location("phase10aa_fake", path)
|
||||
assert spec and spec.loader
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[spec.name] = module
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def require(value: bool, message: str) -> None:
|
||||
if not value:
|
||||
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()
|
||||
sys.path.insert(0, str(root / "tools"))
|
||||
module = load(root / "tools/phase10aa_offline_fake_batch.py")
|
||||
from phase10w_shsrv_client_policy import SessionPlan
|
||||
|
||||
def plan(window="T2_GREETING_AND_HELP", path=None):
|
||||
commands = ("help",) if window == "T2_GREETING_AND_HELP" else ("stat", "sum")
|
||||
return SessionPlan("synthetic_aa", "must-not-persist.invalid", 2323,
|
||||
window, path, commands, 10,
|
||||
datetime(2030, 1, 1, tzinfo=timezone.utc))
|
||||
|
||||
def greeting(extra="", eol="\n"):
|
||||
text = ("Welcome to shsrv.elf running on pid 1, compiled Jul 22 2026 at 12:34:56\n"
|
||||
"Model: synthetic\nS/N: SECRET-SERIAL\nS/W: 9.60\n"
|
||||
"SoC temp: 40 C\nCPU temp: 41 C\nCPU freq: 3500 MHz\n" + extra)
|
||||
return text.replace("\n", eol).encode("ascii")
|
||||
|
||||
def help_text():
|
||||
return "Builtin commands:\n" + "".join(
|
||||
f" {command} - synthetic\n" for command in CURRENT_COMMANDS) + "\n"
|
||||
|
||||
def events(data, data_advance=1.0, deadline_advance=9.0):
|
||||
return (module.FakeReceiveEvent(module.EVENT_DATA, data, data_advance),
|
||||
module.FakeReceiveEvent(module.EVENT_HARD_DEADLINE, b"", deadline_advance))
|
||||
|
||||
def execute(directory, session_plan=None, scripted=None):
|
||||
p = session_plan or plan(); clock = module.OfflineFakeClock()
|
||||
adapter = module.OfflineFakeBatchAdapter(clock, scripted or events(greeting(help_text())))
|
||||
store = module.OfflineFakeEvidenceStore(Path(directory))
|
||||
return module.run_offline_fake_batch(p, adapter, clock, store), adapter
|
||||
|
||||
def expect_failure(function):
|
||||
try: function()
|
||||
except (module.OfflineIntegrationError, module.FakeAdapterError): return
|
||||
raise RuntimeError("invalid fake integration was accepted")
|
||||
|
||||
cases = []
|
||||
def case(name):
|
||||
def register(function): cases.append((name, function)); return function
|
||||
return register
|
||||
|
||||
@case("01 exact fake help integration succeeds")
|
||||
def _():
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
outcome, adapter = execute(d)
|
||||
require(outcome.classification == "SOURCE_FAMILY_FINGERPRINT_ONLY" and adapter.send_count == 1, "help integration failed")
|
||||
|
||||
@case("02 exact path integration sends one batch")
|
||||
def _():
|
||||
path = "/data/a.elf"; data = greeting(f"filename: {path}\nsize: 123\n12345 {path}\n")
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
outcome, adapter = execute(d, plan("T3_ONE_EXACT_PATH", path), events(data))
|
||||
require(outcome.classification == "WEAK_FILE_CORRELATION_ONLY" and adapter.send_count == 1, "path integration failed")
|
||||
|
||||
@case("03 receipt precedes fake open")
|
||||
def _():
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
outcome, _ = execute(d)
|
||||
require(outcome.trace[:3] == ("RECEIPT_CREATED", "FAKE_OPEN", "FAKE_SEND_ONE_BATCH"), "ordering mismatch")
|
||||
|
||||
@case("04 deadline is the only seal event")
|
||||
def _():
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
outcome, _ = execute(d)
|
||||
output = json.loads(outcome.output.path.read_text())
|
||||
require(output["result"]["passive_batch_contract"]["sealed_by_synthetic_hard_deadline"] is True, "deadline seal missing")
|
||||
|
||||
@case("05 early deadline fails")
|
||||
def _():
|
||||
scripted = (module.FakeReceiveEvent(module.EVENT_DATA, greeting(help_text()), 1), module.FakeReceiveEvent(module.EVENT_HARD_DEADLINE, b"", 1))
|
||||
with tempfile.TemporaryDirectory() as d: expect_failure(lambda: execute(d, scripted=scripted))
|
||||
|
||||
@case("06 remote EOF fails")
|
||||
def _():
|
||||
scripted = (module.FakeReceiveEvent(module.EVENT_DATA, greeting(help_text()), 1), module.FakeReceiveEvent(module.EVENT_REMOTE_EOF, b"", 1))
|
||||
with tempfile.TemporaryDirectory() as d: expect_failure(lambda: execute(d, scripted=scripted))
|
||||
|
||||
@case("07 blocked fake receive fails")
|
||||
def _():
|
||||
scripted = (module.FakeReceiveEvent(module.EVENT_BLOCKED, b"", 10),)
|
||||
with tempfile.TemporaryDirectory() as d: expect_failure(lambda: execute(d, scripted=scripted))
|
||||
|
||||
@case("08 data at deadline fails")
|
||||
def _():
|
||||
scripted = (module.FakeReceiveEvent(module.EVENT_DATA, greeting(help_text()), 10),)
|
||||
with tempfile.TemporaryDirectory() as d: expect_failure(lambda: execute(d, scripted=scripted))
|
||||
|
||||
@case("09 missing deadline event fails")
|
||||
def _():
|
||||
scripted = (module.FakeReceiveEvent(module.EVENT_DATA, greeting(help_text()), 1),)
|
||||
with tempfile.TemporaryDirectory() as d: expect_failure(lambda: execute(d, scripted=scripted))
|
||||
|
||||
@case("10 incoming IAC fails")
|
||||
def _():
|
||||
with tempfile.TemporaryDirectory() as d: expect_failure(lambda: execute(d, scripted=events(b"x\xffy")))
|
||||
|
||||
@case("11 partial help fails at deadline")
|
||||
def _():
|
||||
with tempfile.TemporaryDirectory() as d: expect_failure(lambda: execute(d, scripted=events(greeting("Builtin commands:\n help - partial\n\n"))))
|
||||
|
||||
@case("12 CRLF transcript succeeds")
|
||||
def _():
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
outcome, _ = execute(d, scripted=events(greeting(help_text(), "\r\n")))
|
||||
require(outcome.exact_identity is False, "CRLF result promoted")
|
||||
|
||||
@case("13 close occurs after success")
|
||||
def _():
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
_, adapter = execute(d); require(adapter.close_count == 1 and adapter.state == "CLOSED", "success not closed")
|
||||
|
||||
@case("14 close occurs after failure")
|
||||
def _():
|
||||
clock = module.OfflineFakeClock(); adapter = module.OfflineFakeBatchAdapter(clock, (module.FakeReceiveEvent(module.EVENT_REMOTE_EOF),))
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
store = module.OfflineFakeEvidenceStore(Path(d)); expect_failure(lambda: module.run_offline_fake_batch(plan(), adapter, clock, store))
|
||||
require(adapter.close_count == 1 and adapter.state == "CLOSED", "failure not closed")
|
||||
|
||||
@case("15 second fake send is impossible")
|
||||
def _():
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
_, adapter = execute(d); expect_failure(lambda: adapter.send_one_batch(None))
|
||||
|
||||
@case("16 exclusive receipt collision fails")
|
||||
def _():
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
execute(d); expect_failure(lambda: execute(d))
|
||||
|
||||
@case("17 target is absent from evidence")
|
||||
def _():
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
outcome, _ = execute(d); combined = outcome.receipt.path.read_text() + outcome.output.path.read_text()
|
||||
require("must-not-persist" not in combined and "target_address" not in combined, "target retained")
|
||||
|
||||
@case("18 serial and raw transcript are absent from output")
|
||||
def _():
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
outcome, _ = execute(d); value = outcome.output.path.read_text()
|
||||
require("SECRET-SERIAL" not in value and "Welcome to shsrv" not in value and '"raw_transcript_persisted":false' in value, "sensitive input retained")
|
||||
|
||||
@case("19 receipt binds batch hash and size")
|
||||
def _():
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
outcome, _ = execute(d); receipt = json.loads(outcome.receipt.path.read_text())
|
||||
require(receipt["batch_sha256"] == outcome.batch_sha256 and receipt["batch_size"] == 5, "batch binding mismatch")
|
||||
|
||||
@case("20 failure leaves consumed receipt and no output")
|
||||
def _():
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
expect_failure(lambda: execute(d, scripted=(module.FakeReceiveEvent(module.EVENT_REMOTE_EOF),)))
|
||||
files = sorted(path.name for path in Path(d).iterdir())
|
||||
require(files == ["synthetic_aa.aa-consumed.json"], "failure evidence mismatch")
|
||||
|
||||
@case("21 fake event buffer is logically discarded")
|
||||
def _():
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
outcome, adapter = execute(d); output = json.loads(outcome.output.path.read_text())
|
||||
require(adapter.logical_event_buffer_discarded and output["result"]["phase10aa_fake_integration"]["physical_memory_erasure_proven"] is False, "erasure promoted")
|
||||
|
||||
@case("22 exact identity and device proof stay false")
|
||||
def _():
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
outcome, _ = execute(d); require(outcome.exact_identity is False and outcome.device_behavior_proven is False, "proof promoted")
|
||||
|
||||
@case("23 custom adapter is rejected")
|
||||
def _():
|
||||
class Custom: pass
|
||||
clock = module.OfflineFakeClock()
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
store = module.OfflineFakeEvidenceStore(Path(d)); expect_failure(lambda: module.run_offline_fake_batch(plan(), Custom(), clock, store))
|
||||
|
||||
@case("24 fake event validation is bounded")
|
||||
def _():
|
||||
expect_failure(lambda: module.FakeReceiveEvent(module.EVENT_DATA, b"x", float("inf")))
|
||||
|
||||
@case("25 no live API or address exists")
|
||||
def _():
|
||||
names = set(dir(module)); require(not ({"connect", "send", "recv", "main"} & names), "live API exists")
|
||||
|
||||
failures = []
|
||||
for name, function in cases:
|
||||
try: function(); print(f"PASS {name}")
|
||||
except Exception as error: failures.append(f"{name}: {error}"); print(f"FAIL {name}: {error}")
|
||||
if failures: return 1
|
||||
print(f"Phase-1.0AA offline fake-batch tests passed: {len(cases)}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Synthetic tests for Phase-1.0AB nonblocking lifecycle traces."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
|
||||
def load(path: Path):
|
||||
spec = importlib.util.spec_from_file_location("phase10ab_trace", path)
|
||||
assert spec and spec.loader
|
||||
module = importlib.util.module_from_spec(spec); sys.modules[spec.name] = module
|
||||
spec.loader.exec_module(module); return module
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(); parser.add_argument("--root", type=Path, required=True)
|
||||
args = parser.parse_args(); module = load(args.root.resolve() / "tools/phase10ab_nonblocking_trace_model.py")
|
||||
|
||||
def event(op, at, value=0): return module.TraceEvent(op, at, value)
|
||||
def valid(pending=True, sends=(2, 3), waits=()):
|
||||
values = [event(module.RECEIPT_CREATED, 0), event(module.SOCKET_CREATED, 0), event(module.SET_NONBLOCKING, 0)]
|
||||
if pending:
|
||||
values += [event(module.CONNECT_PENDING, .1), event(module.READY_WRITE, .2), event(module.SO_ERROR_ZERO, .2)]
|
||||
else: values += [event(module.CONNECT_IMMEDIATE, .1)]
|
||||
at = .3
|
||||
for count in sends:
|
||||
values += [event(module.READY_WRITE, at), event(module.SEND_BYTES, at, count)]; at += .1
|
||||
for op in waits: values.append(event(op, at)); at += .1
|
||||
values += [event(module.READY_READ, at), event(module.RECV_BYTES, at, 100), event(module.DEADLINE_REACHED, 10), event(module.SANITIZER_ACCEPTED, 10), event(module.LOCAL_CLOSE, 10), event(module.OUTPUT_CREATED, 10)]
|
||||
return tuple(values)
|
||||
def expect_failure(function):
|
||||
try: function()
|
||||
except module.TraceModelError: return
|
||||
raise RuntimeError("invalid trace accepted")
|
||||
cases = []
|
||||
def case(name):
|
||||
def register(function): cases.append((name, function)); return function
|
||||
return register
|
||||
|
||||
@case("01 pending connect complete trace succeeds")
|
||||
def _():
|
||||
result = module.assess_nonblocking_trace(5, 10, valid()); assert result.batch_bytes_sent == 5
|
||||
@case("02 immediate connect complete trace succeeds")
|
||||
def _(): assert module.assess_nonblocking_trace(5, 10, valid(False)).classification == "OFFLINE_NONBLOCKING_SEQUENCE_FEASIBLE"
|
||||
@case("03 partial sends require repeated readiness")
|
||||
def _(): assert module.assess_nonblocking_trace(5, 10, valid(sends=(1, 1, 3))).complete_send_loop
|
||||
@case("04 interrupted waits are recomputed")
|
||||
def _(): assert module.assess_nonblocking_trace(5, 10, valid(waits=(module.WAIT_INTERRUPTED,))).deadline_only_completion
|
||||
@case("05 timeout before deadline is not completion")
|
||||
def _(): assert module.assess_nonblocking_trace(5, 10, valid(waits=(module.WAIT_TIMEOUT,))).local_close_observed
|
||||
@case("06 receipt must precede creation")
|
||||
def _(): expect_failure(lambda: module.assess_nonblocking_trace(5, 10, valid()[1:]))
|
||||
@case("07 nonblocking must precede connect")
|
||||
def _():
|
||||
trace = tuple(e for e in valid() if e.operation != module.SET_NONBLOCKING); expect_failure(lambda: module.assess_nonblocking_trace(5, 10, trace))
|
||||
@case("08 pending connect requires readiness")
|
||||
def _():
|
||||
trace = tuple(e for e in valid() if e.operation != module.READY_WRITE or e.at_seconds != .2); expect_failure(lambda: module.assess_nonblocking_trace(5, 10, trace))
|
||||
@case("09 pending connect requires SO_ERROR")
|
||||
def _():
|
||||
trace = tuple(e for e in valid() if e.operation != module.SO_ERROR_ZERO); expect_failure(lambda: module.assess_nonblocking_trace(5, 10, trace))
|
||||
@case("10 send requires write readiness")
|
||||
def _():
|
||||
trace = tuple(e for e in valid() if not (e.operation == module.READY_WRITE and e.at_seconds == .3)); expect_failure(lambda: module.assess_nonblocking_trace(5, 10, trace))
|
||||
@case("11 send cannot exceed exact batch")
|
||||
def _(): expect_failure(lambda: module.assess_nonblocking_trace(5, 10, valid(sends=(6,))))
|
||||
@case("12 incomplete send fails")
|
||||
def _(): expect_failure(lambda: module.assess_nonblocking_trace(5, 10, valid(sends=(2, 2))))
|
||||
@case("13 receive requires read readiness")
|
||||
def _():
|
||||
trace = tuple(e for e in valid() if e.operation != module.READY_READ); expect_failure(lambda: module.assess_nonblocking_trace(5, 10, trace))
|
||||
@case("14 receive bound is enforced")
|
||||
def _():
|
||||
trace = list(valid()); index = next(i for i,e in enumerate(trace) if e.operation == module.RECV_BYTES); trace[index] = event(module.RECV_BYTES, trace[index].at_seconds, 65537); expect_failure(lambda: module.assess_nonblocking_trace(5, 10, tuple(trace)))
|
||||
@case("15 EOF never completes")
|
||||
def _():
|
||||
trace = list(valid()); index = next(i for i,e in enumerate(trace) if e.operation == module.RECV_BYTES); trace[index] = event(module.RECV_EOF, trace[index].at_seconds); expect_failure(lambda: module.assess_nonblocking_trace(5, 10, tuple(trace)))
|
||||
@case("16 early deadline fails")
|
||||
def _():
|
||||
trace = tuple(event(e.operation, 9 if e.operation in {module.DEADLINE_REACHED,module.SANITIZER_ACCEPTED,module.LOCAL_CLOSE,module.OUTPUT_CREATED} else e.at_seconds, e.value) for e in valid()); expect_failure(lambda: module.assess_nonblocking_trace(5, 10, trace))
|
||||
@case("17 data at deadline fails")
|
||||
def _():
|
||||
trace = list(valid()); index = next(i for i,e in enumerate(trace) if e.operation == module.RECV_BYTES); trace[index] = event(module.RECV_BYTES, 10, 100); expect_failure(lambda: module.assess_nonblocking_trace(5, 10, tuple(trace)))
|
||||
@case("18 deadline requires received data")
|
||||
def _():
|
||||
trace = tuple(e for e in valid() if e.operation not in {module.READY_READ,module.RECV_BYTES}); expect_failure(lambda: module.assess_nonblocking_trace(5, 10, trace))
|
||||
@case("19 sanitizer must follow deadline")
|
||||
def _():
|
||||
trace = tuple(e for e in valid() if e.operation != module.DEADLINE_REACHED); expect_failure(lambda: module.assess_nonblocking_trace(5, 10, trace))
|
||||
@case("20 close must follow sanitizer")
|
||||
def _():
|
||||
trace = tuple(e for e in valid() if e.operation != module.SANITIZER_ACCEPTED); expect_failure(lambda: module.assess_nonblocking_trace(5, 10, trace))
|
||||
@case("21 output must follow close")
|
||||
def _():
|
||||
trace = tuple(e for e in valid() if e.operation != module.LOCAL_CLOSE); expect_failure(lambda: module.assess_nonblocking_trace(5, 10, trace))
|
||||
@case("22 backward time fails")
|
||||
def _():
|
||||
trace = list(valid()); trace[2] = event(module.SET_NONBLOCKING, -.0); trace[1] = event(module.SOCKET_CREATED, .1); expect_failure(lambda: module.assess_nonblocking_trace(5, 10, tuple(trace)))
|
||||
@case("23 trace event bound is enforced")
|
||||
def _(): expect_failure(lambda: module.assess_nonblocking_trace(5, 10, tuple(event(module.RECEIPT_CREATED, 0) for _ in range(513))))
|
||||
@case("24 result never claims device proof")
|
||||
def _():
|
||||
result = module.assess_nonblocking_trace(5, 10, valid()); assert not result.device_behavior_proven and not result.live_transport_present
|
||||
@case("25 no live API exists")
|
||||
def _(): assert not ({"connect", "send", "recv", "main"} & set(dir(module)))
|
||||
|
||||
failures=[]
|
||||
for name,function in cases:
|
||||
try: function(); print(f"PASS {name}")
|
||||
except Exception as error: failures.append(f"{name}: {error}"); print(f"FAIL {name}: {error}")
|
||||
if failures: return 1
|
||||
print(f"Phase-1.0AB nonblocking-trace tests passed: {len(cases)}"); return 0
|
||||
|
||||
|
||||
if __name__ == "__main__": raise SystemExit(main())
|
||||
@@ -0,0 +1,267 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Host-only tests for the Phase-1.0AC dormant fake-syscall adapter."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from datetime import datetime, timezone
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
|
||||
CURRENT_COMMANDS = "authid browse cat cd chgrp chmod chown chroot cmp cp df echo env exec exit export file find grep hbdbg hbldr hexdump http2_get id kill launch ln ls mkdir mknod mount mv notify pkg_install procstat ps pwd reptyr rm rmdir sfocreate sfoinfo sleep stat sum suspend sync sysctl touch umount".split()
|
||||
|
||||
|
||||
def load(path: Path):
|
||||
spec = importlib.util.spec_from_file_location("phase10ac_dormant", path)
|
||||
assert spec and spec.loader
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[spec.name] = module
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
root = args.root.resolve()
|
||||
sys.path.insert(0, str(root / "tools"))
|
||||
module = load(root / "tools/phase10ac_dormant_adapter.py")
|
||||
from phase10w_shsrv_client_policy import SessionPlan
|
||||
from phase10z_passive_batch_contract import build_passive_batch
|
||||
|
||||
def plan(window="T2_GREETING_AND_HELP", path=None):
|
||||
commands = ("help",) if window == "T2_GREETING_AND_HELP" else ("stat", "sum")
|
||||
return SessionPlan("synthetic_ac", "not-retained.invalid", 2323,
|
||||
window, path, commands, 10,
|
||||
datetime(2030, 1, 1, tzinfo=timezone.utc))
|
||||
|
||||
def greeting(extra=""):
|
||||
return ("Welcome to shsrv.elf running on pid 1, compiled Jul 22 2026 at 12:34:56\n"
|
||||
"Model: synthetic\nS/N: SECRET-SERIAL\nS/W: 9.60\n"
|
||||
"SoC temp: 40 C\nCPU temp: 41 C\nCPU freq: 3500 MHz\n" + extra).encode("ascii")
|
||||
|
||||
def help_text():
|
||||
return "Builtin commands:\n" + "".join(
|
||||
f" {command} - synthetic\n" for command in CURRENT_COMMANDS) + "\n"
|
||||
|
||||
def step(op, result, value=0, data=b"", advance=0.0):
|
||||
return module.FakeSyscallStep(op, result, value, data, advance)
|
||||
|
||||
def valid_steps(data=None, pending=True, writes=(2, 3), extra=()):
|
||||
payload = data or greeting(help_text())
|
||||
values = [step(module.CREATE_STREAM, module.OK),
|
||||
step(module.SET_NONBLOCKING, module.OK)]
|
||||
if pending:
|
||||
values += [step(module.START_CONNECT, module.PENDING),
|
||||
step(module.WAIT_WRITE, module.READY, advance=.1),
|
||||
step(module.GET_SO_ERROR, module.ZERO)]
|
||||
else:
|
||||
values += [step(module.START_CONNECT, module.IMMEDIATE, advance=.1)]
|
||||
for count in writes:
|
||||
values += [step(module.WAIT_WRITE, module.READY, advance=.1),
|
||||
step(module.WRITE_BYTES, module.PROGRESS, count)]
|
||||
values += list(extra)
|
||||
values += [step(module.WAIT_READ, module.READY, advance=.1),
|
||||
step(module.READ_BYTES, module.PROGRESS, len(payload), payload),
|
||||
step(module.WAIT_READ, module.TIMEOUT, advance=9.6)]
|
||||
return tuple(values)
|
||||
|
||||
def execute(steps=None, batch=None, close_result=module.OK, receipt=True):
|
||||
clock = module.OfflineFakeClock()
|
||||
actual_batch = batch or build_passive_batch(plan())
|
||||
facade = module.OfflineFakeSyscallFacade(
|
||||
clock, steps or valid_steps(writes=(2, 3)), close_result)
|
||||
return module.run_dormant_adapter(
|
||||
actual_batch, facade, clock, receipt), facade
|
||||
|
||||
def expect_failure(function):
|
||||
try:
|
||||
function()
|
||||
except (module.DormantAdapterError, module.FakeSyscallError):
|
||||
return
|
||||
raise RuntimeError("invalid dormant adapter scenario was accepted")
|
||||
|
||||
cases = []
|
||||
def case(name):
|
||||
def register(function):
|
||||
cases.append((name, function)); return function
|
||||
return register
|
||||
|
||||
@case("01 pending connect lifecycle succeeds")
|
||||
def _():
|
||||
outcome, facade = execute()
|
||||
assert outcome.classification == "OFFLINE_DORMANT_FAKE_SYSCALL_ADAPTER_COMPLETE" and facade.close_count == 1
|
||||
|
||||
@case("02 immediate connect lifecycle succeeds")
|
||||
def _():
|
||||
outcome, _ = execute(valid_steps(pending=False))
|
||||
assert outcome.batch_bytes_sent == 5
|
||||
|
||||
@case("03 partial writes complete exact batch")
|
||||
def _():
|
||||
outcome, _ = execute(valid_steps(writes=(1, 1, 3)))
|
||||
assert outcome.write_calls == 3 and outcome.batch_bytes_sent == 5
|
||||
|
||||
@case("04 interrupted connect wait is bounded")
|
||||
def _():
|
||||
prefix = (step(module.CREATE_STREAM, module.OK), step(module.SET_NONBLOCKING, module.OK),
|
||||
step(module.START_CONNECT, module.PENDING), step(module.WAIT_WRITE, module.INTERRUPTED, advance=.1),
|
||||
step(module.WAIT_WRITE, module.READY, advance=.1), step(module.GET_SO_ERROR, module.ZERO))
|
||||
suffix = valid_steps(pending=False)[3:]
|
||||
outcome, _ = execute(prefix + suffix)
|
||||
assert outcome.interrupted_waits == 1
|
||||
|
||||
@case("05 interrupted read wait is bounded")
|
||||
def _():
|
||||
extra = (step(module.WAIT_READ, module.INTERRUPTED, advance=.1),)
|
||||
outcome, _ = execute(valid_steps(extra=extra))
|
||||
assert outcome.interrupted_waits == 1
|
||||
|
||||
@case("06 timeout only seals at deadline")
|
||||
def _():
|
||||
outcome, _ = execute()
|
||||
assert outcome.timed_out_waits == 1 and outcome.result_classification == "SOURCE_FAMILY_FINGERPRINT_ONLY"
|
||||
|
||||
@case("07 receipt is required before create")
|
||||
def _(): expect_failure(lambda: execute(receipt=False))
|
||||
|
||||
@case("08 exact fake facade type is required")
|
||||
def _():
|
||||
batch = build_passive_batch(plan()); clock = module.OfflineFakeClock()
|
||||
expect_failure(lambda: module.run_dormant_adapter(batch, object(), clock, True))
|
||||
|
||||
@case("09 exact fake clock type is required")
|
||||
def _():
|
||||
class Derived(module.OfflineFakeClock): pass
|
||||
expect_failure(lambda: module.OfflineFakeSyscallFacade(Derived(), valid_steps()))
|
||||
|
||||
@case("10 create failure does not close an unowned descriptor")
|
||||
def _():
|
||||
steps = (step(module.CREATE_STREAM, module.ERROR),)
|
||||
clock = module.OfflineFakeClock(); facade = module.OfflineFakeSyscallFacade(clock, steps)
|
||||
expect_failure(lambda: module.run_dormant_adapter(build_passive_batch(plan()), facade, clock, True))
|
||||
assert facade.close_count == 0
|
||||
|
||||
@case("11 nonblocking failure closes once")
|
||||
def _():
|
||||
steps = (step(module.CREATE_STREAM, module.OK), step(module.SET_NONBLOCKING, module.ERROR))
|
||||
_, facade = None, module.OfflineFakeSyscallFacade(module.OfflineFakeClock(), steps)
|
||||
expect_failure(lambda: module.run_dormant_adapter(build_passive_batch(plan()), facade, facade.clock, True))
|
||||
assert facade.close_count == 1
|
||||
|
||||
@case("12 pending connect requires write readiness")
|
||||
def _():
|
||||
values = list(valid_steps()); values[3] = step(module.GET_SO_ERROR, module.ZERO)
|
||||
expect_failure(lambda: execute(tuple(values)))
|
||||
|
||||
@case("13 pending connect requires zero SO_ERROR")
|
||||
def _():
|
||||
values = list(valid_steps()); values[4] = step(module.GET_SO_ERROR, module.NONZERO)
|
||||
expect_failure(lambda: execute(tuple(values)))
|
||||
|
||||
@case("14 connect error fails")
|
||||
def _():
|
||||
values = list(valid_steps()); values[2] = step(module.START_CONNECT, module.ERROR)
|
||||
expect_failure(lambda: execute(tuple(values)))
|
||||
|
||||
@case("15 write requires readiness")
|
||||
def _():
|
||||
values = list(valid_steps()); del values[5]
|
||||
expect_failure(lambda: execute(tuple(values)))
|
||||
|
||||
@case("16 zero write fails")
|
||||
def _():
|
||||
values = list(valid_steps()); values[6] = step(module.WRITE_BYTES, module.ZERO)
|
||||
expect_failure(lambda: execute(tuple(values)))
|
||||
|
||||
@case("17 excess write count fails")
|
||||
def _():
|
||||
values = list(valid_steps(writes=(6,))); expect_failure(lambda: execute(tuple(values)))
|
||||
|
||||
@case("18 incomplete write script fails")
|
||||
def _():
|
||||
values = valid_steps(writes=(2, 2)); expect_failure(lambda: execute(values))
|
||||
|
||||
@case("19 read requires readiness")
|
||||
def _():
|
||||
values = list(valid_steps()); index = next(i for i, item in enumerate(values) if item.operation == module.WAIT_READ); del values[index]
|
||||
expect_failure(lambda: execute(tuple(values)))
|
||||
|
||||
@case("20 EOF is never completion")
|
||||
def _():
|
||||
values = list(valid_steps()); index = next(i for i, item in enumerate(values) if item.operation == module.READ_BYTES); values[index] = step(module.READ_BYTES, module.EOF)
|
||||
expect_failure(lambda: execute(tuple(values)))
|
||||
|
||||
@case("21 receive bound is enforced")
|
||||
def _():
|
||||
data = b"x" * 65537; expect_failure(lambda: execute(valid_steps(data=data)))
|
||||
|
||||
@case("22 data at deadline fails")
|
||||
def _():
|
||||
values = list(valid_steps()); index = next(i for i, item in enumerate(values) if item.operation == module.READ_BYTES); item = values[index]; values[index] = step(item.operation, item.result, item.value, item.data, 9.6)
|
||||
expect_failure(lambda: execute(tuple(values)))
|
||||
|
||||
@case("23 deadline wins readiness race")
|
||||
def _():
|
||||
values = list(valid_steps()); values[-1] = step(module.WAIT_READ, module.READY, advance=9.6)
|
||||
expect_failure(lambda: execute(tuple(values)))
|
||||
|
||||
@case("24 early script exhaustion fails")
|
||||
def _(): expect_failure(lambda: execute(valid_steps()[:-1]))
|
||||
|
||||
@case("25 close failure invalidates success")
|
||||
def _(): expect_failure(lambda: execute(close_result=module.ERROR))
|
||||
|
||||
@case("26 unused post-deadline steps are discarded")
|
||||
def _():
|
||||
values = valid_steps() + (step(module.READ_BYTES, module.ERROR),)
|
||||
outcome, facade = execute(values)
|
||||
assert outcome.discarded_steps_after_close == 1 and facade.discarded_steps == 1
|
||||
|
||||
@case("27 malformed result fails at deadline")
|
||||
def _(): expect_failure(lambda: execute(valid_steps(data=greeting("partial\n"))))
|
||||
|
||||
@case("28 path result is sanitized")
|
||||
def _():
|
||||
path = "/data/a.elf"
|
||||
data = greeting(f"filename: {path}\nsize: 123\n12345 {path}\n")
|
||||
batch = build_passive_batch(plan("T3_ONE_EXACT_PATH", path))
|
||||
outcome, _ = execute(valid_steps(data=data, writes=(10, 10, len(batch.payload) - 20)), batch)
|
||||
assert outcome.result_classification == "WEAK_FILE_CORRELATION_ONLY" and not outcome.exact_identity
|
||||
|
||||
@case("29 outcome retains no target and proves no device")
|
||||
def _():
|
||||
outcome, _ = execute()
|
||||
assert not outcome.target_retained and not outcome.live_transport_present and not outcome.device_behavior_proven
|
||||
|
||||
@case("30 no live API exists")
|
||||
def _():
|
||||
names = set(dir(module))
|
||||
assert not ({"connect", "send", "recv", "main"} & names)
|
||||
|
||||
@case("31 fake step validation rejects hidden data")
|
||||
def _(): expect_failure(lambda: step(module.CREATE_STREAM, module.OK, data=b"x"))
|
||||
|
||||
@case("32 fake script bound is enforced")
|
||||
def _():
|
||||
clock = module.OfflineFakeClock(); one = step(module.CREATE_STREAM, module.OK)
|
||||
expect_failure(lambda: module.OfflineFakeSyscallFacade(clock, tuple(one for _ in range(1025))))
|
||||
|
||||
failures = []
|
||||
for name, function in cases:
|
||||
try:
|
||||
function(); print(f"PASS {name}")
|
||||
except Exception as error:
|
||||
failures.append(f"{name}: {error}"); print(f"FAIL {name}: {error}")
|
||||
if failures:
|
||||
return 1
|
||||
print(f"Phase-1.0AC dormant-adapter tests passed: {len(cases)}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Host tests for the data-only Phase-1.0AD activation contract."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
ARGS = parse_args()
|
||||
sys.path.insert(0, str(ARGS.root / "tools"))
|
||||
|
||||
from phase10ad_activation_contract import ( # noqa: E402
|
||||
ActivationContractError,
|
||||
ActivationRecord,
|
||||
PHASE,
|
||||
validate_candidate,
|
||||
validate_inactive,
|
||||
validate_numeric_target,
|
||||
)
|
||||
|
||||
|
||||
def inactive() -> ActivationRecord:
|
||||
return ActivationRecord(PHASE, False, None, None, None, None, None, None,
|
||||
None, None, True, False, False, False, False,
|
||||
False, False)
|
||||
|
||||
|
||||
def candidate() -> ActivationRecord:
|
||||
return ActivationRecord(
|
||||
PHASE, True, "192.168.1.50", 2323, "CHIMERA_AD_001",
|
||||
"2026-07-29T12:00:00Z", "2026-07-29T12:05:00Z", "a" * 64,
|
||||
"b" * 64, "c" * 64, True, False, False, False, False, False, False)
|
||||
|
||||
|
||||
class ActivationContractTests(unittest.TestCase):
|
||||
def test_tracked_record_is_inactive(self) -> None:
|
||||
validate_inactive(inactive())
|
||||
|
||||
def test_inactive_record_rejects_every_binding(self) -> None:
|
||||
for field, value in (("target_address", "192.168.1.50"),
|
||||
("target_port", 2323), ("run_id", "RUN_ID_001"),
|
||||
("launcher_sha256", "a" * 64)):
|
||||
with self.subTest(field=field), self.assertRaises(ActivationContractError):
|
||||
validate_inactive(replace(inactive(), **{field: value}))
|
||||
|
||||
def test_private_canonical_ipv4(self) -> None:
|
||||
for address in ("10.0.0.2", "172.16.1.2", "192.168.1.50"):
|
||||
self.assertEqual(validate_numeric_target(address), address)
|
||||
|
||||
def test_rejects_names_public_and_special_addresses(self) -> None:
|
||||
for address in ("ps5", "8.8.8.8", "127.0.0.1", "169.254.1.1",
|
||||
"0.0.0.0", "192.168.001.050", "::1"):
|
||||
with self.subTest(address=address), self.assertRaises(ActivationContractError):
|
||||
validate_numeric_target(address)
|
||||
|
||||
def test_complete_candidate_is_data_valid(self) -> None:
|
||||
validate_candidate(candidate())
|
||||
|
||||
def test_candidate_rejects_wrong_port(self) -> None:
|
||||
with self.assertRaises(ActivationContractError):
|
||||
validate_candidate(replace(candidate(), target_port=9021))
|
||||
|
||||
def test_candidate_rejects_long_or_reversed_window(self) -> None:
|
||||
for end in ("2026-07-29T12:05:01Z", "2026-07-29T11:59:59Z"):
|
||||
with self.subTest(end=end), self.assertRaises(ActivationContractError):
|
||||
validate_candidate(replace(candidate(), expires_at=end))
|
||||
|
||||
def test_candidate_rejects_missing_or_bad_hashes(self) -> None:
|
||||
for field, value in (("launcher_sha256", None),
|
||||
("payload_sha256", "A" * 64),
|
||||
("approval_sha256", "0" * 63)):
|
||||
with self.subTest(field=field), self.assertRaises(ActivationContractError):
|
||||
validate_candidate(replace(candidate(), **{field: value}))
|
||||
|
||||
def test_candidate_rejects_retry_and_effects(self) -> None:
|
||||
for field in ("automatic_retry", "reconnect", "resume",
|
||||
"device_write_authorized", "app_termination_authorized",
|
||||
"system_remount_authorized"):
|
||||
with self.subTest(field=field), self.assertRaises(ActivationContractError):
|
||||
validate_candidate(replace(candidate(), **{field: True}))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[sys.argv[0]])
|
||||
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Failure-injection tests for the Phase-1.0AF lifecycle model."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
|
||||
PARSER = argparse.ArgumentParser()
|
||||
PARSER.add_argument("--root", type=Path, required=True)
|
||||
ROOT = PARSER.parse_args().root
|
||||
sys.path.insert(0, str(ROOT / "tools"))
|
||||
|
||||
from phase10af_bigapp_lifecycle_model import * # noqa: E402,F403
|
||||
|
||||
|
||||
def happy() -> list[FakeEvent]:
|
||||
return [FakeEvent(CHECK_NO_BIGAPP, NONE), FakeEvent(ATTACH_PARENT, OK),
|
||||
FakeEvent(ARM_FORK, OK), FakeEvent(CONTINUE_PARENT, OK),
|
||||
FakeEvent(LAUNCH_FIXED_TITLE, OK),
|
||||
FakeEvent(AWAIT_UNIQUE_CHILD, CHILD, child_id=4242),
|
||||
FakeEvent(DETACH_PARENT, OK), FakeEvent(ARM_EXEC, OK),
|
||||
FakeEvent(CONTINUE_CHILD, OK), FakeEvent(AWAIT_EXEC, OK),
|
||||
FakeEvent(REPLACE_EXACT_PAYLOAD, OK),
|
||||
FakeEvent(RESTORE_MUTATIONS, OK), FakeEvent(DETACH_CHILD, OK),
|
||||
FakeEvent(EMIT_RESULT, OK)]
|
||||
|
||||
|
||||
class LifecycleTests(unittest.TestCase):
|
||||
def test_success(self) -> None:
|
||||
result = run_lifecycle(FakeLifecycleFacade(tuple(happy())))
|
||||
self.assertTrue(result.success)
|
||||
self.assertEqual(result.child_id, 4242)
|
||||
self.assertTrue(result.parent_detached and result.child_detached)
|
||||
self.assertTrue(result.mutations_restored)
|
||||
self.assertFalse(result.child_terminated or result.existing_bigapp_killed)
|
||||
|
||||
def test_existing_bigapp_fails_without_kill(self) -> None:
|
||||
result = run_lifecycle(FakeLifecycleFacade((FakeEvent(CHECK_NO_BIGAPP, EXISTS),)))
|
||||
self.assertFalse(result.success)
|
||||
self.assertFalse(result.existing_bigapp_killed)
|
||||
self.assertEqual(result.child_id, 0)
|
||||
|
||||
def test_parent_stage_failures_detach(self) -> None:
|
||||
for index in range(2, 5):
|
||||
events = happy()[:index + 1]
|
||||
events[index] = FakeEvent(events[index].operation, ERROR)
|
||||
events = events[:index + 1] + [FakeEvent(DETACH_PARENT, OK)]
|
||||
with self.subTest(index=index):
|
||||
result = run_lifecycle(FakeLifecycleFacade(tuple(events)))
|
||||
self.assertFalse(result.success)
|
||||
self.assertTrue(result.parent_detached)
|
||||
|
||||
def test_child_stage_failures_terminate_only_child(self) -> None:
|
||||
for index in range(7, 10):
|
||||
events = happy()[:index + 1]
|
||||
events[index] = FakeEvent(events[index].operation, ERROR)
|
||||
events = events[:index + 1] + [FakeEvent(TERMINATE_CHILD, OK)]
|
||||
with self.subTest(index=index):
|
||||
result = run_lifecycle(FakeLifecycleFacade(tuple(events)))
|
||||
self.assertFalse(result.success)
|
||||
self.assertEqual(result.child_id, 4242)
|
||||
self.assertTrue(result.child_terminated)
|
||||
|
||||
def test_replace_failure_restores_then_terminates(self) -> None:
|
||||
events = happy()[:11]
|
||||
events[10] = FakeEvent(REPLACE_EXACT_PAYLOAD, ERROR)
|
||||
events += [FakeEvent(RESTORE_MUTATIONS, OK), FakeEvent(TERMINATE_CHILD, OK)]
|
||||
result = run_lifecycle(FakeLifecycleFacade(tuple(events)))
|
||||
self.assertFalse(result.success)
|
||||
self.assertTrue(result.mutations_restored and result.child_terminated)
|
||||
self.assertLess(result.trace.index("RESTORE_MUTATIONS:OK"),
|
||||
result.trace.index("TERMINATE_CHILD:OK"))
|
||||
|
||||
def test_timeout_is_failure(self) -> None:
|
||||
events = happy()[:6]
|
||||
events[5] = FakeEvent(AWAIT_UNIQUE_CHILD, TIMEOUT)
|
||||
events += [FakeEvent(DETACH_PARENT, OK)]
|
||||
result = run_lifecycle(FakeLifecycleFacade(tuple(events)))
|
||||
self.assertFalse(result.success)
|
||||
|
||||
def test_main_tick_budget(self) -> None:
|
||||
events = happy()
|
||||
events[0] = FakeEvent(CHECK_NO_BIGAPP, NONE, ticks=64)
|
||||
events = events[:2] + [FakeEvent(DETACH_PARENT, OK)]
|
||||
with self.assertRaises(LifecycleModelError):
|
||||
run_lifecycle(FakeLifecycleFacade(tuple(events)))
|
||||
|
||||
def test_cleanup_failure_is_hard_error(self) -> None:
|
||||
events = happy()[:3]
|
||||
events[2] = FakeEvent(ARM_FORK, ERROR)
|
||||
events += [FakeEvent(DETACH_PARENT, ERROR)]
|
||||
with self.assertRaises(LifecycleModelError):
|
||||
run_lifecycle(FakeLifecycleFacade(tuple(events)))
|
||||
|
||||
def test_unused_or_wrong_order_is_rejected(self) -> None:
|
||||
result = run_lifecycle(FakeLifecycleFacade((FakeEvent(ATTACH_PARENT, OK),)))
|
||||
self.assertFalse(result.success)
|
||||
with self.assertRaises(LifecycleModelError):
|
||||
run_lifecycle(FakeLifecycleFacade(tuple(happy() + [FakeEvent(EMIT_RESULT, OK)])))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Synthetic byte tests for the Phase-1.0AG ELF admission contract."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
import struct
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
|
||||
PARSER = argparse.ArgumentParser()
|
||||
PARSER.add_argument("--root", type=Path, required=True)
|
||||
ROOT = PARSER.parse_args().root
|
||||
sys.path.insert(0, str(ROOT / "tools"))
|
||||
|
||||
from phase10ag_bounded_elf import * # noqa: E402,F403
|
||||
|
||||
|
||||
def make_elf(headers: list[tuple[int, int, int, int, int, int, int, int]] | None = None,
|
||||
entry: int = 0x1000, elf_type: int = ET_DYN,
|
||||
machine: int = EM_X86_64) -> bytes:
|
||||
if headers is None:
|
||||
headers = [(PT_LOAD, PF_R | PF_X, 0x1000, 0x1000, 0, 0x100, 0x100, 0x1000),
|
||||
(PT_LOAD, PF_R | PF_W, 0x2000, 0x3000, 0, 0x80, 0x180, 0x1000)]
|
||||
size = max([0x3000] + [item[2] + item[5] for item in headers])
|
||||
payload = bytearray(size)
|
||||
ident = bytearray(16)
|
||||
ident[:7] = b"\x7fELF\x02\x01\x01"
|
||||
ELF_HEADER.pack_into(payload, 0, bytes(ident), elf_type, machine, 1, entry,
|
||||
64, 0, 0, 64, 56, len(headers), 64, 0, 0)
|
||||
for index, header in enumerate(headers):
|
||||
PROGRAM_HEADER.pack_into(payload, 64 + index * 56, *header)
|
||||
return bytes(payload)
|
||||
|
||||
|
||||
def assess(payload: bytes):
|
||||
return assess_elf(payload, hashlib.sha256(payload).hexdigest())
|
||||
|
||||
|
||||
class BoundedElfTests(unittest.TestCase):
|
||||
def test_valid_pie(self) -> None:
|
||||
result = assess(make_elf())
|
||||
self.assertEqual(len(result.load_segments), 2)
|
||||
self.assertFalse(result.writable_executable_segment)
|
||||
self.assertFalse(result.execution_performed)
|
||||
|
||||
def test_hash_must_match(self) -> None:
|
||||
with self.assertRaises(ElfContractError):
|
||||
assess_elf(make_elf(), "0" * 64)
|
||||
|
||||
def test_header_identity_type_and_machine(self) -> None:
|
||||
for payload in (b"not-elf" + b"\0" * 100, make_elf(elf_type=2),
|
||||
make_elf(machine=3)):
|
||||
with self.subTest(), self.assertRaises(ElfContractError):
|
||||
assess(payload)
|
||||
|
||||
def test_rejects_interpreter(self) -> None:
|
||||
headers = [(PT_INTERP, PF_R, 0x300, 0x300, 0, 8, 8, 1),
|
||||
(PT_LOAD, PF_R | PF_X, 0x1000, 0x1000, 0, 0x100, 0x100, 0x1000)]
|
||||
with self.assertRaises(ElfContractError):
|
||||
assess(make_elf(headers))
|
||||
|
||||
def test_rejects_writable_executable(self) -> None:
|
||||
headers = [(PT_LOAD, PF_R | PF_W | PF_X, 0x1000, 0x1000, 0,
|
||||
0x100, 0x100, 0x1000)]
|
||||
with self.assertRaises(ElfContractError):
|
||||
assess(make_elf(headers))
|
||||
|
||||
def test_rejects_file_and_memory_bounds(self) -> None:
|
||||
cases = [
|
||||
[(PT_LOAD, PF_R | PF_X, 0x2f80, 0x1000, 0, 0x100, 0x100, 0x1000)],
|
||||
[(PT_LOAD, PF_R | PF_X, 0x1000, 0x1000, 0, 0x200, 0x100, 0x1000)],
|
||||
[(PT_LOAD, PF_R | PF_X, 0x1000, 0x1000, 0, 0x100,
|
||||
MAX_TOTAL_LOAD_MEMORY + 1, 0x1000)],
|
||||
]
|
||||
for headers in cases:
|
||||
with self.subTest(headers=headers), self.assertRaises(ElfContractError):
|
||||
assess(make_elf(headers))
|
||||
|
||||
def test_rejects_bad_alignment(self) -> None:
|
||||
for alignment in (0, 3, MAX_ALIGNMENT * 2):
|
||||
headers = [(PT_LOAD, PF_R | PF_X, 0x1000, 0x1000, 0,
|
||||
0x100, 0x100, alignment)]
|
||||
with self.subTest(alignment=alignment), self.assertRaises(ElfContractError):
|
||||
assess(make_elf(headers))
|
||||
|
||||
def test_rejects_overlapping_virtual_ranges(self) -> None:
|
||||
headers = [(PT_LOAD, PF_R | PF_X, 0x1000, 0x1000, 0, 0x100, 0x1800, 0x1000),
|
||||
(PT_LOAD, PF_R | PF_W, 0x2000, 0x2000, 0, 0x100, 0x100, 0x1000)]
|
||||
with self.assertRaises(ElfContractError):
|
||||
assess(make_elf(headers))
|
||||
|
||||
def test_entry_must_be_executable(self) -> None:
|
||||
with self.assertRaises(ElfContractError):
|
||||
assess(make_elf(entry=0x3000))
|
||||
|
||||
def test_program_header_count_is_bounded(self) -> None:
|
||||
headers = [(PT_LOAD, PF_R | PF_X, 0x1000, 0x1000, 0, 1, 1, 0x1000)] * 33
|
||||
with self.assertRaises(ElfContractError):
|
||||
assess(make_elf(headers))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Synthetic tests for the Phase-1.0AH dynamic/relocation contract."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
|
||||
PARSER = argparse.ArgumentParser()
|
||||
PARSER.add_argument("--root", type=Path, required=True)
|
||||
ROOT = PARSER.parse_args().root
|
||||
sys.path.insert(0, str(ROOT / "tools"))
|
||||
|
||||
from phase10ag_bounded_elf import * # noqa: E402,F403
|
||||
from phase10ah_dynamic_contract import * # noqa: E402,F403
|
||||
|
||||
|
||||
NEEDED = ("libSceLibcInternal.sprx", "libkernel_web.sprx")
|
||||
|
||||
|
||||
def make_dynamic(relocations=None, needed=NEEDED, dynamic_terminated=True) -> bytes:
|
||||
strings = b"\0" + b"".join(name.encode("ascii") + b"\0" for name in needed)
|
||||
offsets = []
|
||||
position = 1
|
||||
for name in needed:
|
||||
offsets.append(position)
|
||||
position += len(name) + 1
|
||||
dynamic = b"".join(DYNAMIC_ENTRY.pack(DT_NEEDED, offset) for offset in offsets)
|
||||
if dynamic_terminated:
|
||||
dynamic += DYNAMIC_ENTRY.pack(DT_NULL, 0)
|
||||
if relocations is None:
|
||||
relocations = [(0x2100, R_X86_64_RELATIVE, 0, 0x100),
|
||||
(0x2108, R_X86_64_GLOB_DAT, 1, 0)]
|
||||
rela = b"".join(RELA_ENTRY.pack(target, symbol << 32 | kind, addend)
|
||||
for target, kind, symbol, addend in relocations)
|
||||
payload = bytearray(0x3400)
|
||||
ident = bytearray(16)
|
||||
ident[:7] = b"\x7fELF\x02\x01\x01"
|
||||
ELF_HEADER.pack_into(payload, 0, bytes(ident), ET_DYN, EM_X86_64, 1, 0x100,
|
||||
64, 0x3000, 0, 64, 56, 2, 64, 4, 0)
|
||||
PROGRAM_HEADER.pack_into(payload, 64, PT_LOAD, PF_R | PF_X, 0, 0, 0,
|
||||
0x1000, 0x1000, 0x1000)
|
||||
PROGRAM_HEADER.pack_into(payload, 120, PT_LOAD, PF_R | PF_W, 0x1000,
|
||||
0x2000, 0, 0x1000, 0x2000, 0x1000)
|
||||
payload[0x1800:0x1800 + len(strings)] = strings
|
||||
payload[0x1900:0x1900 + len(dynamic)] = dynamic
|
||||
payload[0x1a00:0x1a00 + len(rela)] = rela
|
||||
sections = [
|
||||
(0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
(0, SHT_STRTAB, 0, 0, 0x1800, len(strings), 0, 0, 1, 0),
|
||||
(0, SHT_DYNAMIC, 0, 0, 0x1900, len(dynamic), 1, 0, 8, 16),
|
||||
(0, SHT_RELA, 0, 0, 0x1a00, len(rela), 0, 0, 8, 24),
|
||||
]
|
||||
for index, section in enumerate(sections):
|
||||
SECTION_HEADER.pack_into(payload, 0x3000 + index * 64, *section)
|
||||
return bytes(payload)
|
||||
|
||||
|
||||
def assess(payload: bytes, needed=NEEDED):
|
||||
return assess_dynamic(payload, hashlib.sha256(payload).hexdigest(), needed)
|
||||
|
||||
|
||||
class DynamicContractTests(unittest.TestCase):
|
||||
def test_valid_split(self) -> None:
|
||||
result = assess(make_dynamic())
|
||||
self.assertEqual(result.relative_count, 1)
|
||||
self.assertEqual(result.glob_dat_count, 1)
|
||||
self.assertEqual(result.loader_applied_types, (R_X86_64_RELATIVE,))
|
||||
self.assertEqual(result.crt_applied_types, (R_X86_64_GLOB_DAT,))
|
||||
self.assertFalse(result.target_mapping_performed)
|
||||
|
||||
def test_needed_inventory_is_exact_and_allowlisted(self) -> None:
|
||||
payload = make_dynamic()
|
||||
with self.assertRaises(DynamicContractError):
|
||||
assess(payload, tuple(reversed(NEEDED)))
|
||||
with self.assertRaises(DynamicContractError):
|
||||
assess(make_dynamic(needed=("evil.sprx",)), ("evil.sprx",))
|
||||
|
||||
def test_dynamic_table_must_terminate(self) -> None:
|
||||
with self.assertRaises(DynamicContractError):
|
||||
assess(make_dynamic(dynamic_terminated=False))
|
||||
|
||||
def test_unknown_relocation_type_is_rejected(self) -> None:
|
||||
with self.assertRaises(DynamicContractError):
|
||||
assess(make_dynamic([(0x2100, 7, 0, 0x100)]))
|
||||
|
||||
def test_targets_must_be_aligned_and_writable(self) -> None:
|
||||
for target in (0x100, 0x2101, 0x5000):
|
||||
with self.subTest(target=target), self.assertRaises(DynamicContractError):
|
||||
assess(make_dynamic([(target, R_X86_64_RELATIVE, 0, 0x100)]))
|
||||
|
||||
def test_relative_requires_zero_symbol_and_mapped_addend(self) -> None:
|
||||
for symbol, addend in ((1, 0x100), (0, -1), (0, 0x9000)):
|
||||
with self.subTest(), self.assertRaises(DynamicContractError):
|
||||
assess(make_dynamic([(0x2100, R_X86_64_RELATIVE, symbol, addend)]))
|
||||
|
||||
def test_glob_dat_requires_symbol_and_zero_addend(self) -> None:
|
||||
for symbol, addend in ((0, 0), (1, 1)):
|
||||
relocs = [(0x2100, R_X86_64_RELATIVE, 0, 0x100),
|
||||
(0x2108, R_X86_64_GLOB_DAT, symbol, addend)]
|
||||
with self.subTest(), self.assertRaises(DynamicContractError):
|
||||
assess(make_dynamic(relocs))
|
||||
|
||||
def test_relative_closure_is_required(self) -> None:
|
||||
with self.assertRaises(DynamicContractError):
|
||||
assess(make_dynamic([(0x2108, R_X86_64_GLOB_DAT, 1, 0)]))
|
||||
|
||||
def test_base_elf_hash_and_shape_remain_enforced(self) -> None:
|
||||
payload = make_dynamic()
|
||||
with self.assertRaises(DynamicContractError):
|
||||
assess_dynamic(payload, "0" * 64, NEEDED)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,133 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Failure-injection tests for Phase-1.0AI mapping transactions."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
|
||||
PARSER = argparse.ArgumentParser()
|
||||
PARSER.add_argument("--root", type=Path, required=True)
|
||||
ROOT = PARSER.parse_args().root
|
||||
sys.path.insert(0, str(ROOT / "tools"))
|
||||
|
||||
from phase10ag_bounded_elf import LoadSegment, PF_R, PF_W, PF_X # noqa: E402
|
||||
from phase10ai_mapping_model import * # noqa: E402,F403
|
||||
|
||||
|
||||
def plan() -> MappingPlan:
|
||||
return MappingPlan((
|
||||
LoadSegment(PF_R | PF_X, 0x4000, 0, 0x1800, 0x1800, 0x4000),
|
||||
LoadSegment(PF_R, 0x8000, 0x8000, 0x1000, 0x1000, 0x4000),
|
||||
LoadSegment(PF_R | PF_W, 0xc000, 0xc000, 0x800, 0x1800, 0x4000),
|
||||
), 7)
|
||||
|
||||
|
||||
def happy(mapping: MappingPlan | None = None) -> list[FakeMappingEvent]:
|
||||
mapping = mapping or plan()
|
||||
events = [FakeMappingEvent(RESERVE_CHILD, OK, value=mapping.region_size),
|
||||
FakeMappingEvent(CREATE_MIRROR, OK, value=mapping.region_size)]
|
||||
for index, segment in enumerate(mapping.segments):
|
||||
if segment.file_size:
|
||||
events.append(FakeMappingEvent(COPY_FILE_BYTES, OK, index,
|
||||
segment.file_size))
|
||||
if segment.memory_size > segment.file_size:
|
||||
events.append(FakeMappingEvent(ZERO_BSS, OK, index,
|
||||
segment.memory_size - segment.file_size))
|
||||
events.append(FakeMappingEvent(APPLY_RELATIVE, OK,
|
||||
value=mapping.relative_relocations))
|
||||
events.append(FakeMappingEvent(COPY_MIRROR_TO_CHILD, OK,
|
||||
value=mapping.region_size))
|
||||
for index, segment in enumerate(mapping.segments):
|
||||
events.append(FakeMappingEvent(SET_FINAL_PROTECTION, OK, index,
|
||||
segment.flags))
|
||||
events += [FakeMappingEvent(SYNC_IMAGE, OK, value=mapping.region_size),
|
||||
FakeMappingEvent(RELEASE_MIRROR, OK)]
|
||||
return events
|
||||
|
||||
|
||||
class MappingModelTests(unittest.TestCase):
|
||||
def test_success(self) -> None:
|
||||
mapping = plan()
|
||||
result = run_mapping(mapping, FakeMappingFacade(tuple(happy(mapping))))
|
||||
self.assertTrue(result.success and result.child_region_retained)
|
||||
self.assertTrue(result.mirror_released)
|
||||
self.assertFalse(result.child_region_unmapped or result.target_mapping_performed)
|
||||
self.assertEqual(result.zeroed_bss_bytes, 0x1000)
|
||||
|
||||
def test_reserve_failure_has_no_cleanup(self) -> None:
|
||||
mapping = plan()
|
||||
events = (FakeMappingEvent(RESERVE_CHILD, ERROR, value=mapping.region_size),)
|
||||
result = run_mapping(mapping, FakeMappingFacade(events))
|
||||
self.assertFalse(result.success or result.child_region_unmapped)
|
||||
|
||||
def test_mirror_creation_failure_unmaps_child(self) -> None:
|
||||
mapping = plan()
|
||||
events = (FakeMappingEvent(RESERVE_CHILD, OK, value=mapping.region_size),
|
||||
FakeMappingEvent(CREATE_MIRROR, ERROR, value=mapping.region_size),
|
||||
FakeMappingEvent(UNMAP_CHILD, OK, value=mapping.region_size))
|
||||
result = run_mapping(mapping, FakeMappingFacade(events))
|
||||
self.assertFalse(result.success)
|
||||
self.assertTrue(result.child_region_unmapped)
|
||||
|
||||
def test_every_post_mirror_failure_releases_and_unmaps(self) -> None:
|
||||
mapping = plan()
|
||||
baseline = happy(mapping)
|
||||
for index in range(2, len(baseline)):
|
||||
failed = baseline[:index + 1]
|
||||
event = failed[index]
|
||||
failed[index] = FakeMappingEvent(event.operation, ERROR,
|
||||
event.segment_index, event.value)
|
||||
failed += [FakeMappingEvent(RELEASE_MIRROR, OK),
|
||||
FakeMappingEvent(UNMAP_CHILD, OK, value=mapping.region_size)]
|
||||
with self.subTest(operation=event.operation, index=index):
|
||||
result = run_mapping(mapping, FakeMappingFacade(tuple(failed)))
|
||||
self.assertFalse(result.success)
|
||||
self.assertTrue(result.mirror_released)
|
||||
self.assertTrue(result.child_region_unmapped)
|
||||
|
||||
def test_cleanup_failure_is_hard_error(self) -> None:
|
||||
mapping = plan()
|
||||
events = happy(mapping)[:3]
|
||||
event = events[-1]
|
||||
events[-1] = FakeMappingEvent(event.operation, ERROR,
|
||||
event.segment_index, event.value)
|
||||
events.append(FakeMappingEvent(RELEASE_MIRROR, ERROR))
|
||||
with self.assertRaises(MappingModelError):
|
||||
run_mapping(mapping, FakeMappingFacade(tuple(events)))
|
||||
|
||||
def test_tick_budget_is_enforced_and_rolled_back(self) -> None:
|
||||
mapping = plan()
|
||||
events = happy(mapping)[:2]
|
||||
first = events[0]
|
||||
events[0] = FakeMappingEvent(first.operation, OK, value=first.value,
|
||||
ticks=128)
|
||||
events += [FakeMappingEvent(UNMAP_CHILD, OK, value=mapping.region_size)]
|
||||
result = run_mapping(mapping, FakeMappingFacade(tuple(events)))
|
||||
self.assertFalse(result.success)
|
||||
self.assertTrue(result.child_region_unmapped)
|
||||
|
||||
def test_plan_rejects_wx_and_page_protection_overlap(self) -> None:
|
||||
with self.assertRaises(MappingModelError):
|
||||
MappingPlan((LoadSegment(PF_R | PF_W | PF_X, 0, 0, 1, 1, 0x4000),), 1)
|
||||
with self.assertRaises(MappingModelError):
|
||||
MappingPlan((LoadSegment(PF_R | PF_X, 0, 0, 1, 0x3000, 0x4000),
|
||||
LoadSegment(PF_R | PF_W, 0x3000, 0x3000, 1, 1, 0x4000)), 1)
|
||||
|
||||
def test_wrong_binding_and_unused_events_are_rejected(self) -> None:
|
||||
mapping = plan()
|
||||
events = happy(mapping)
|
||||
events[2] = FakeMappingEvent(COPY_FILE_BYTES, OK, 0, 1)
|
||||
with self.assertRaises(MappingModelError):
|
||||
run_mapping(mapping, FakeMappingFacade(tuple(events)))
|
||||
with self.assertRaises(MappingModelError):
|
||||
run_mapping(mapping, FakeMappingFacade(tuple(
|
||||
happy(mapping) + [FakeMappingEvent(RELEASE_MIRROR, OK)])))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Exhaustive ownership tests for Phase-1.0AK."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
|
||||
PARSER = argparse.ArgumentParser()
|
||||
PARSER.add_argument("--root", type=Path, required=True)
|
||||
ROOT = PARSER.parse_args().root
|
||||
sys.path.insert(0, str(ROOT / "tools"))
|
||||
|
||||
from phase10ak_hybrid_composition import * # noqa: E402,F403
|
||||
|
||||
|
||||
def happy(count: int = 2) -> list[FakeEvent]:
|
||||
events = [FakeEvent(CREATE_CHILD, OK), FakeEvent(RESERVE_REGION, OK),
|
||||
FakeEvent(CREATE_MIRROR, OK)]
|
||||
for segment in range(count):
|
||||
events += [FakeEvent(CREATE_JIT_MASTER, OK, segment),
|
||||
FakeEvent(MAP_EXECUTABLE, OK, segment),
|
||||
FakeEvent(CREATE_JIT_ALIAS, OK, segment),
|
||||
FakeEvent(MAP_HOST_ALIAS, OK, segment),
|
||||
FakeEvent(MAP_REMOTE_ALIAS, OK, segment),
|
||||
FakeEvent(COPY_ALIAS, OK, segment),
|
||||
FakeEvent(UNMAP_REMOTE_ALIAS, OK, segment),
|
||||
FakeEvent(UNMAP_HOST_ALIAS, OK, segment),
|
||||
FakeEvent(CLOSE_JIT_ALIAS, OK, segment),
|
||||
FakeEvent(CLOSE_JIT_MASTER, OK, segment)]
|
||||
return events + [FakeEvent(FINALIZE_IMAGE, OK), FakeEvent(RELEASE_MIRROR, OK)]
|
||||
|
||||
|
||||
def cleanup_for(prefix: list[FakeEvent]) -> list[FakeEvent]:
|
||||
acquired = {name: set() for name in ("masters", "aliases", "host", "remote")}
|
||||
child = region = mirror = False
|
||||
for event in prefix:
|
||||
if event.result != OK:
|
||||
break
|
||||
if event.operation == CREATE_CHILD: child = True
|
||||
elif event.operation == RESERVE_REGION: region = True
|
||||
elif event.operation == CREATE_MIRROR: mirror = True
|
||||
elif event.operation == CREATE_JIT_MASTER: acquired["masters"].add(event.segment)
|
||||
elif event.operation == CREATE_JIT_ALIAS: acquired["aliases"].add(event.segment)
|
||||
elif event.operation == MAP_HOST_ALIAS: acquired["host"].add(event.segment)
|
||||
elif event.operation == MAP_REMOTE_ALIAS: acquired["remote"].add(event.segment)
|
||||
elif event.operation == UNMAP_REMOTE_ALIAS: acquired["remote"].remove(event.segment)
|
||||
elif event.operation == UNMAP_HOST_ALIAS: acquired["host"].remove(event.segment)
|
||||
elif event.operation == CLOSE_JIT_ALIAS: acquired["aliases"].remove(event.segment)
|
||||
elif event.operation == CLOSE_JIT_MASTER: acquired["masters"].remove(event.segment)
|
||||
elif event.operation == RELEASE_MIRROR: mirror = False
|
||||
result: list[FakeEvent] = []
|
||||
for key, operation in (("remote", UNMAP_REMOTE_ALIAS),
|
||||
("host", UNMAP_HOST_ALIAS),
|
||||
("aliases", CLOSE_JIT_ALIAS),
|
||||
("masters", CLOSE_JIT_MASTER)):
|
||||
result += [FakeEvent(operation, OK, item)
|
||||
for item in sorted(acquired[key], reverse=True)]
|
||||
if mirror: result.append(FakeEvent(RELEASE_MIRROR, OK))
|
||||
if region: result.append(FakeEvent(UNMAP_REGION, OK))
|
||||
if child: result.append(FakeEvent(KILL_AND_REAP_CHILD, OK))
|
||||
return result
|
||||
|
||||
|
||||
class HybridCompositionTests(unittest.TestCase):
|
||||
def test_success_releases_every_temporary_resource(self) -> None:
|
||||
outcome = run_composition(CompositionPlan(2), FakeFacade(tuple(happy())))
|
||||
self.assertTrue(outcome.success and outcome.child_alive and outcome.image_retained)
|
||||
self.assertEqual(outcome.resources_open, 0)
|
||||
self.assertFalse(outcome.target_action_performed or outcome.firmware_behavior_proven)
|
||||
|
||||
def test_every_forward_failure_terminates_fail_closed(self) -> None:
|
||||
baseline = happy()
|
||||
for index, original in enumerate(baseline):
|
||||
failed = baseline[:index] + [FakeEvent(original.operation, ERROR,
|
||||
original.segment)]
|
||||
failed += cleanup_for(failed)
|
||||
with self.subTest(index=index, operation=original.operation):
|
||||
outcome = run_composition(CompositionPlan(2), FakeFacade(tuple(failed)))
|
||||
self.assertFalse(outcome.success or outcome.child_alive)
|
||||
self.assertEqual(outcome.fail_closed_termination, index > 0)
|
||||
self.assertEqual(outcome.resources_open, 0)
|
||||
|
||||
def test_cleanup_failure_is_contained_by_child_termination(self) -> None:
|
||||
failed = happy()[:9]
|
||||
original = failed[-1]
|
||||
failed[-1] = FakeEvent(original.operation, ERROR, original.segment)
|
||||
cleanup = cleanup_for(failed)
|
||||
cleanup[0] = FakeEvent(cleanup[0].operation, ERROR, cleanup[0].segment)
|
||||
outcome = run_composition(CompositionPlan(2), FakeFacade(tuple(failed + cleanup)))
|
||||
self.assertEqual(outcome.classification,
|
||||
"OFFLINE_FAIL_CLOSED_AFTER_CLEANUP_FAILURE")
|
||||
self.assertFalse(outcome.child_alive)
|
||||
|
||||
def test_failed_termination_is_a_hard_error(self) -> None:
|
||||
events = [FakeEvent(CREATE_CHILD, OK), FakeEvent(RESERVE_REGION, ERROR),
|
||||
FakeEvent(KILL_AND_REAP_CHILD, ERROR)]
|
||||
with self.assertRaises(CompositionError):
|
||||
run_composition(CompositionPlan(1), FakeFacade(tuple(events)))
|
||||
|
||||
def test_bounds_wrong_order_deadline_and_unused_events(self) -> None:
|
||||
for value in (0, 9, True):
|
||||
with self.subTest(value=value), self.assertRaises(CompositionError):
|
||||
CompositionPlan(value)
|
||||
events = happy(1)
|
||||
events[0] = FakeEvent(CREATE_CHILD, OK, ticks=256)
|
||||
events = events[:2] + [FakeEvent(KILL_AND_REAP_CHILD, OK)]
|
||||
outcome = run_composition(CompositionPlan(1), FakeFacade(tuple(events)))
|
||||
self.assertFalse(outcome.success)
|
||||
with self.assertRaises(CompositionError):
|
||||
run_composition(CompositionPlan(1), FakeFacade(tuple(
|
||||
happy(1) + [FakeEvent(KILL_AND_REAP_CHILD, OK)])))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Failure-injection tests for Phase-1.0AM bounded copy/restore."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
|
||||
PARSER = argparse.ArgumentParser()
|
||||
PARSER.add_argument("--root", type=Path, required=True)
|
||||
ROOT = PARSER.parse_args().root
|
||||
sys.path.insert(0, str(ROOT / "tools"))
|
||||
|
||||
from phase10am_bounded_copy_model import * # noqa: E402,F403
|
||||
|
||||
|
||||
def prefix() -> list[FakeCopyEvent]:
|
||||
return [FakeCopyEvent(BACKUP_AUTHID, OK), FakeCopyEvent(BACKUP_CAPS, OK),
|
||||
FakeCopyEvent(SET_PRIV_AUTHID, OK), FakeCopyEvent(SET_PRIV_CAPS, OK)]
|
||||
|
||||
|
||||
class BoundedCopyTests(unittest.TestCase):
|
||||
def test_exact_multichunk_success_restores_both_fields(self) -> None:
|
||||
events = prefix() + [FakeCopyEvent(COPY_CHUNK, OK, 4, MORE),
|
||||
FakeCopyEvent(COPY_CHUNK, OK, 6, COMPLETE),
|
||||
FakeCopyEvent(RESTORE_CAPS, OK),
|
||||
FakeCopyEvent(RESTORE_AUTHID, OK)]
|
||||
result = run_copy(CopyPlan(100, 200, 10), FakeCopyFacade(tuple(events)))
|
||||
self.assertTrue(result.success)
|
||||
self.assertEqual(result.copied, 10)
|
||||
self.assertTrue(result.child_alive)
|
||||
self.assertFalse(result.target_copy_performed)
|
||||
self.assertTrue(result.service_available)
|
||||
|
||||
def test_partial_error_is_reported_and_child_is_reaped(self) -> None:
|
||||
events = prefix() + [FakeCopyEvent(COPY_CHUNK, OK, 4, MORE),
|
||||
FakeCopyEvent(COPY_CHUNK, ERROR, 0, MORE),
|
||||
FakeCopyEvent(RESTORE_CAPS, OK),
|
||||
FakeCopyEvent(RESTORE_AUTHID, OK),
|
||||
FakeCopyEvent(KILL_AND_REAP_CHILD, OK)]
|
||||
result = run_copy(CopyPlan(0, 32, 10), FakeCopyFacade(tuple(events)))
|
||||
self.assertEqual(result.classification, "OFFLINE_PARTIAL_COPY_CONTAINED")
|
||||
self.assertEqual(result.copied, 4)
|
||||
self.assertFalse(result.child_alive)
|
||||
|
||||
def test_zero_and_oversized_progress_never_complete(self) -> None:
|
||||
for progress in (0, 11):
|
||||
events = prefix() + [FakeCopyEvent(COPY_CHUNK, OK, progress, MORE),
|
||||
FakeCopyEvent(RESTORE_CAPS, OK),
|
||||
FakeCopyEvent(RESTORE_AUTHID, OK)]
|
||||
with self.subTest(progress=progress):
|
||||
result = run_copy(CopyPlan(0, 32, 10), FakeCopyFacade(tuple(events)))
|
||||
self.assertFalse(result.success)
|
||||
self.assertEqual(result.copied, 0)
|
||||
|
||||
def test_both_restores_are_attempted_and_failure_contains_service(self) -> None:
|
||||
events = prefix() + [FakeCopyEvent(COPY_CHUNK, ERROR, 0, MORE),
|
||||
FakeCopyEvent(RESTORE_CAPS, ERROR),
|
||||
FakeCopyEvent(RESTORE_AUTHID, ERROR),
|
||||
FakeCopyEvent(KILL_AND_REAP_CHILD, OK),
|
||||
FakeCopyEvent(TERMINATE_SERVICE, OK)]
|
||||
result = run_copy(CopyPlan(0, 32, 10), FakeCopyFacade(tuple(events)))
|
||||
self.assertEqual(result.restore_failure_bits, 3)
|
||||
self.assertFalse(result.child_alive or result.service_available)
|
||||
|
||||
def test_caps_set_failure_still_restores_authid(self) -> None:
|
||||
events = prefix()[:3] + [FakeCopyEvent(SET_PRIV_CAPS, ERROR),
|
||||
FakeCopyEvent(RESTORE_AUTHID, OK)]
|
||||
result = run_copy(CopyPlan(0, 32, 10), FakeCopyFacade(tuple(events)))
|
||||
self.assertEqual(result.classification,
|
||||
"OFFLINE_COPY_REJECTED_BEFORE_MUTATION")
|
||||
self.assertTrue(result.child_alive and result.service_available)
|
||||
|
||||
def test_restore_or_child_termination_failure_is_hard(self) -> None:
|
||||
events = prefix() + [FakeCopyEvent(COPY_CHUNK, OK, 1, MORE),
|
||||
FakeCopyEvent(RESTORE_CAPS, OK),
|
||||
FakeCopyEvent(RESTORE_AUTHID, OK),
|
||||
FakeCopyEvent(KILL_AND_REAP_CHILD, ERROR)]
|
||||
with self.assertRaises(CopyModelError):
|
||||
run_copy(CopyPlan(0, 32, 10), FakeCopyFacade(tuple(events)))
|
||||
|
||||
def test_bounds_deadline_status_and_arithmetic(self) -> None:
|
||||
for args in ((0, 0, 0), (MAX_U64, 0, 2), (0, MAX_U64, 2)):
|
||||
with self.subTest(args=args), self.assertRaises(CopyModelError):
|
||||
CopyPlan(*args)
|
||||
events = prefix()
|
||||
events[0] = FakeCopyEvent(BACKUP_AUTHID, OK, ticks=MAX_TICKS)
|
||||
result = run_copy(CopyPlan(0, 32, 10), FakeCopyFacade(tuple(events[:2])))
|
||||
self.assertFalse(result.success)
|
||||
incomplete = prefix() + [FakeCopyEvent(COPY_CHUNK, OK, 9, COMPLETE),
|
||||
FakeCopyEvent(RESTORE_CAPS, OK),
|
||||
FakeCopyEvent(RESTORE_AUTHID, OK),
|
||||
FakeCopyEvent(KILL_AND_REAP_CHILD, OK)]
|
||||
result = run_copy(CopyPlan(0, 32, 10), FakeCopyFacade(tuple(incomplete)))
|
||||
self.assertFalse(result.success)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Failure tests for Phase-1.0AO worker supervision."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
|
||||
PARSER = argparse.ArgumentParser()
|
||||
PARSER.add_argument("--root", type=Path, required=True)
|
||||
ROOT = PARSER.parse_args().root
|
||||
sys.path.insert(0, str(ROOT / "tools"))
|
||||
|
||||
from phase10ao_worker_supervisor_model import * # noqa: E402,F403
|
||||
|
||||
|
||||
PLAN = WorkerPlan(101, 202, 4096)
|
||||
|
||||
|
||||
class WorkerSupervisorTests(unittest.TestCase):
|
||||
def test_exact_success_reaps_worker_and_retains_child(self) -> None:
|
||||
result = WorkerResult(101, SUCCESS, 4096, 0)
|
||||
events = (FakeSupervisorEvent(CREATE_WORKER, OK),
|
||||
FakeSupervisorEvent(VERIFY_WORKER, OK),
|
||||
FakeSupervisorEvent(START_COPY, OK),
|
||||
FakeSupervisorEvent(RECEIVE_RESULT, OK, result),
|
||||
FakeSupervisorEvent(REAP_WORKER, OK))
|
||||
outcome = run_supervisor(PLAN, FakeSupervisorFacade(events))
|
||||
self.assertTrue(outcome.success and outcome.service_alive and outcome.child_alive)
|
||||
self.assertFalse(outcome.worker_alive or outcome.automatic_restart)
|
||||
self.assertFalse(outcome.target_action_performed)
|
||||
|
||||
def test_deadline_terminates_worker_and_child(self) -> None:
|
||||
events = (FakeSupervisorEvent(CREATE_WORKER, OK),
|
||||
FakeSupervisorEvent(VERIFY_WORKER, OK),
|
||||
FakeSupervisorEvent(START_COPY, OK),
|
||||
FakeSupervisorEvent(DEADLINE, OK),
|
||||
FakeSupervisorEvent(TERMINATE_WORKER, OK),
|
||||
FakeSupervisorEvent(REAP_WORKER, OK),
|
||||
FakeSupervisorEvent(TERMINATE_CHILD, OK),
|
||||
FakeSupervisorEvent(REAP_CHILD, OK))
|
||||
outcome = run_supervisor(PLAN, FakeSupervisorFacade(events), True)
|
||||
self.assertEqual(outcome.classification, "OFFLINE_DEADLINE_CONTAINED")
|
||||
self.assertTrue(outcome.service_alive)
|
||||
self.assertFalse(outcome.worker_alive or outcome.child_alive)
|
||||
|
||||
def test_wrong_identity_partial_and_restore_failure_are_contained(self) -> None:
|
||||
results = (WorkerResult(999, SUCCESS, 4096, 0),
|
||||
WorkerResult(101, COPY_ERROR, 7, 0),
|
||||
WorkerResult(101, RESTORE_ERROR, 4096, 1))
|
||||
for result in results:
|
||||
events = (FakeSupervisorEvent(CREATE_WORKER, OK),
|
||||
FakeSupervisorEvent(VERIFY_WORKER, OK),
|
||||
FakeSupervisorEvent(START_COPY, OK),
|
||||
FakeSupervisorEvent(RECEIVE_RESULT, OK, result),
|
||||
FakeSupervisorEvent(TERMINATE_WORKER, OK),
|
||||
FakeSupervisorEvent(REAP_WORKER, OK),
|
||||
FakeSupervisorEvent(TERMINATE_CHILD, OK),
|
||||
FakeSupervisorEvent(REAP_CHILD, OK))
|
||||
with self.subTest(result=result):
|
||||
outcome = run_supervisor(PLAN, FakeSupervisorFacade(events))
|
||||
self.assertFalse(outcome.success or outcome.child_alive)
|
||||
|
||||
def test_start_ambiguity_contains_both_processes(self) -> None:
|
||||
events = (FakeSupervisorEvent(CREATE_WORKER, OK),
|
||||
FakeSupervisorEvent(VERIFY_WORKER, OK),
|
||||
FakeSupervisorEvent(START_COPY, ERROR),
|
||||
FakeSupervisorEvent(TERMINATE_WORKER, OK),
|
||||
FakeSupervisorEvent(REAP_WORKER, OK),
|
||||
FakeSupervisorEvent(TERMINATE_CHILD, OK),
|
||||
FakeSupervisorEvent(REAP_CHILD, OK))
|
||||
outcome = run_supervisor(PLAN, FakeSupervisorFacade(events))
|
||||
self.assertFalse(outcome.child_alive)
|
||||
|
||||
def test_pre_start_failure_does_not_kill_untouched_child(self) -> None:
|
||||
events = (FakeSupervisorEvent(CREATE_WORKER, OK),
|
||||
FakeSupervisorEvent(VERIFY_WORKER, ERROR),
|
||||
FakeSupervisorEvent(TERMINATE_WORKER, OK),
|
||||
FakeSupervisorEvent(REAP_WORKER, OK))
|
||||
outcome = run_supervisor(PLAN, FakeSupervisorFacade(events))
|
||||
self.assertTrue(outcome.child_alive)
|
||||
|
||||
def test_any_terminal_failure_is_hard(self) -> None:
|
||||
events = (FakeSupervisorEvent(CREATE_WORKER, OK),
|
||||
FakeSupervisorEvent(VERIFY_WORKER, ERROR),
|
||||
FakeSupervisorEvent(TERMINATE_WORKER, ERROR))
|
||||
with self.assertRaises(SupervisorError):
|
||||
run_supervisor(PLAN, FakeSupervisorFacade(events))
|
||||
|
||||
def test_bounds_and_unused_events_are_rejected(self) -> None:
|
||||
for values in ((0, 2, 1), (1, 1, 1), (1, 2, MAX_COPY_SIZE + 1)):
|
||||
with self.subTest(values=values), self.assertRaises(SupervisorError):
|
||||
WorkerPlan(*values)
|
||||
events = (FakeSupervisorEvent(CREATE_WORKER, ERROR),
|
||||
FakeSupervisorEvent(REAP_WORKER, OK))
|
||||
with self.assertRaises(SupervisorError):
|
||||
run_supervisor(PLAN, FakeSupervisorFacade(events))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Byte mutation tests for Phase-1.0AQ worker records."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
import struct
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
|
||||
PARSER = argparse.ArgumentParser()
|
||||
PARSER.add_argument("--root", type=Path, required=True)
|
||||
ROOT = PARSER.parse_args().root
|
||||
sys.path.insert(0, str(ROOT / "tools"))
|
||||
|
||||
from phase10aq_worker_result_record import * # noqa: E402,F403
|
||||
|
||||
|
||||
def precommit() -> WorkerPrecommit:
|
||||
return WorkerPrecommit(bytes.fromhex("00112233445566778899aabbccddeeff"),
|
||||
bytes.fromhex("ffeeddccbbaa99887766554433221100"),
|
||||
101, 202, 7, 4096)
|
||||
|
||||
|
||||
class WorkerRecordTests(unittest.TestCase):
|
||||
def test_exact_success_roundtrip(self) -> None:
|
||||
expected = precommit()
|
||||
raw = encode_record(expected, STATUS_SUCCESS, 4096)
|
||||
self.assertEqual(len(raw), RECORD_SIZE)
|
||||
record = parse_record(raw, expected)
|
||||
self.assertTrue(record.successful)
|
||||
self.assertEqual(record.generation, 7)
|
||||
|
||||
def test_every_byte_mutation_is_rejected(self) -> None:
|
||||
expected = precommit()
|
||||
raw = encode_record(expected, STATUS_SUCCESS, 4096)
|
||||
for index in range(len(raw)):
|
||||
mutated = bytearray(raw)
|
||||
mutated[index] ^= 1
|
||||
with self.subTest(index=index), self.assertRaises(WorkerRecordError):
|
||||
parse_record(bytes(mutated), expected)
|
||||
|
||||
def test_truncation_extension_and_nonbytes_are_rejected(self) -> None:
|
||||
expected = precommit()
|
||||
raw = encode_record(expected, STATUS_SUCCESS, 4096)
|
||||
for candidate in (raw[:-1], raw + b"\0", bytearray(raw)):
|
||||
with self.subTest(length=len(candidate)), self.assertRaises(WorkerRecordError):
|
||||
parse_record(candidate, expected) # type: ignore[arg-type]
|
||||
|
||||
def test_pid_nonce_generation_and_attempt_must_all_match(self) -> None:
|
||||
expected = precommit()
|
||||
raw = encode_record(expected, STATUS_SUCCESS, 4096)
|
||||
variants = (
|
||||
WorkerPrecommit(expected.attempt_id, expected.nonce, 102, 202, 7, 4096),
|
||||
WorkerPrecommit(expected.attempt_id, expected.nonce, 101, 202, 8, 4096),
|
||||
WorkerPrecommit(expected.attempt_id, b"x" * 16, 101, 202, 7, 4096),
|
||||
WorkerPrecommit(b"y" * 16, expected.nonce, 101, 202, 7, 4096),
|
||||
)
|
||||
for variant in variants:
|
||||
with self.subTest(variant=variant), self.assertRaises(WorkerRecordError):
|
||||
parse_record(raw, variant)
|
||||
|
||||
def test_failure_records_preserve_exact_progress_and_restore_bits(self) -> None:
|
||||
expected = precommit()
|
||||
copy_error = parse_record(
|
||||
encode_record(expected, STATUS_COPY_ERROR, 123), expected)
|
||||
self.assertFalse(copy_error.successful)
|
||||
self.assertEqual(copy_error.copied, 123)
|
||||
restore_error = parse_record(
|
||||
encode_record(expected, STATUS_RESTORE_ERROR, 4096, 3), expected)
|
||||
self.assertEqual(restore_error.restore_failure_bits, 3)
|
||||
|
||||
def test_forged_rehashed_invalid_fields_are_rejected(self) -> None:
|
||||
expected = precommit()
|
||||
raw = bytearray(encode_record(expected, STATUS_COPY_ERROR, 1))
|
||||
raw[88] = 1
|
||||
raw[HASHED_SIZE:] = hashlib.sha256(raw[:HASHED_SIZE]).digest()
|
||||
with self.assertRaises(WorkerRecordError):
|
||||
parse_record(bytes(raw), expected)
|
||||
raw = bytearray(encode_record(expected, STATUS_COPY_ERROR, 1))
|
||||
struct.pack_into("<I", raw, 12, STATUS_RESTORE_ERROR)
|
||||
struct.pack_into("<I", raw, 48, 0)
|
||||
raw[HASHED_SIZE:] = hashlib.sha256(raw[:HASHED_SIZE]).digest()
|
||||
with self.assertRaises(WorkerRecordError):
|
||||
parse_record(bytes(raw), expected)
|
||||
|
||||
def test_precommit_and_encoding_bounds(self) -> None:
|
||||
with self.assertRaises(WorkerRecordError):
|
||||
WorkerPrecommit(bytes(16), b"x" * 16, 1, 2, 1, 1)
|
||||
with self.assertRaises(WorkerRecordError):
|
||||
WorkerPrecommit(b"a" * 16, b"b" * 16, 1, 1, 1, 1)
|
||||
with self.assertRaises(WorkerRecordError):
|
||||
encode_record(precommit(), STATUS_SUCCESS, 4095)
|
||||
with self.assertRaises(WorkerRecordError):
|
||||
encode_record(precommit(), STATUS_COPY_ERROR, 4097)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Partial-read and deadline tests for Phase-1.0AR."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
|
||||
PARSER = argparse.ArgumentParser()
|
||||
PARSER.add_argument("--root", type=Path, required=True)
|
||||
ROOT = PARSER.parse_args().root
|
||||
sys.path.insert(0, str(ROOT / "tools"))
|
||||
|
||||
from phase10aq_worker_result_record import * # noqa: E402,F403
|
||||
from phase10ar_result_channel_model import * # noqa: E402,F403
|
||||
|
||||
|
||||
def setup() -> tuple[WorkerPrecommit, bytes, ChannelPlan]:
|
||||
precommit = WorkerPrecommit(b"a" * 16, b"b" * 16, 101, 202, 7, 4096)
|
||||
raw = encode_record(precommit, STATUS_SUCCESS, 4096)
|
||||
return precommit, raw, ChannelPlan(precommit, 101, 7, b"b" * 16)
|
||||
|
||||
|
||||
class ResultChannelTests(unittest.TestCase):
|
||||
def test_every_split_point_assembles_exact_record(self) -> None:
|
||||
_, raw, plan = setup()
|
||||
for split in range(1, RECORD_SIZE):
|
||||
events = (FakeReadEvent(DATA, raw[:split]),
|
||||
FakeReadEvent(DATA, raw[split:]))
|
||||
with self.subTest(split=split):
|
||||
outcome = receive_record(plan, events)
|
||||
self.assertTrue(outcome.accepted)
|
||||
self.assertFalse(outcome.eof_is_success or outcome.live_transport_present)
|
||||
|
||||
def test_byte_at_a_time_is_bounded_and_accepted(self) -> None:
|
||||
_, raw, plan = setup()
|
||||
events = tuple(FakeReadEvent(DATA, bytes((value,))) for value in raw)
|
||||
outcome = receive_record(plan, events)
|
||||
self.assertTrue(outcome.accepted)
|
||||
self.assertEqual(outcome.buffered, RECORD_SIZE)
|
||||
|
||||
def test_eof_deadline_and_silent_incomplete_are_never_success(self) -> None:
|
||||
_, raw, plan = setup()
|
||||
scripts = ((FakeReadEvent(DATA, raw[:50]), FakeReadEvent(EOF)),
|
||||
(FakeReadEvent(DATA, raw[:50]), FakeReadEvent(DEADLINE)),
|
||||
(FakeReadEvent(DATA, raw[:50]),))
|
||||
for events in scripts:
|
||||
with self.subTest(events=events):
|
||||
outcome = receive_record(plan, events)
|
||||
self.assertFalse(outcome.accepted or outcome.eof_is_success)
|
||||
self.assertTrue(outcome.containment_required)
|
||||
|
||||
def test_overflow_and_digest_or_identity_mismatch_require_containment(self) -> None:
|
||||
precommit, raw, plan = setup()
|
||||
overflow = receive_record(plan, (FakeReadEvent(DATA, raw[:100]),
|
||||
FakeReadEvent(DATA, raw[100:] + b"x"),))
|
||||
self.assertEqual(overflow.classification, "OFFLINE_CHANNEL_OVERFLOW")
|
||||
damaged = bytearray(raw)
|
||||
damaged[10] ^= 1
|
||||
rejected = receive_record(plan, (FakeReadEvent(DATA, bytes(damaged)),))
|
||||
self.assertEqual(rejected.classification, "OFFLINE_RECORD_REJECTED")
|
||||
other = WorkerPrecommit(precommit.attempt_id, b"c" * 16,
|
||||
101, 202, 7, 4096)
|
||||
other_plan = ChannelPlan(other, 101, 7, b"c" * 16)
|
||||
rejected = receive_record(other_plan, (FakeReadEvent(DATA, raw),))
|
||||
self.assertFalse(rejected.accepted)
|
||||
|
||||
def test_writer_precommit_must_be_exclusive_and_exact(self) -> None:
|
||||
precommit, _, _ = setup()
|
||||
with self.assertRaises(ChannelModelError):
|
||||
ChannelPlan(precommit, 102, 7, b"b" * 16)
|
||||
with self.assertRaises(ChannelModelError):
|
||||
ChannelPlan(precommit, 101, 7, b"b" * 16, False)
|
||||
|
||||
def test_deadline_preempts_crossing_read(self) -> None:
|
||||
_, raw, plan = setup()
|
||||
events = (FakeReadEvent(DATA, raw[:64], ticks=256),
|
||||
FakeReadEvent(DATA, raw[64:]))
|
||||
outcome = receive_record(plan, events)
|
||||
self.assertEqual(outcome.classification, "OFFLINE_CHANNEL_DEADLINE")
|
||||
self.assertEqual(outcome.buffered, 64)
|
||||
|
||||
def test_extra_event_after_boundary_and_invalid_event_are_rejected(self) -> None:
|
||||
_, raw, plan = setup()
|
||||
with self.assertRaises(ChannelModelError):
|
||||
receive_record(plan, (FakeReadEvent(DATA, raw), FakeReadEvent(EOF)))
|
||||
with self.assertRaises(ChannelModelError):
|
||||
FakeReadEvent(DATA, b"")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Failure injection for Phase-1.0AT descriptor ownership."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
|
||||
PARSER = argparse.ArgumentParser()
|
||||
PARSER.add_argument("--root", type=Path, required=True)
|
||||
ROOT = PARSER.parse_args().root
|
||||
sys.path.insert(0, str(ROOT / "tools"))
|
||||
|
||||
from phase10aq_worker_result_record import * # noqa: E402,F403
|
||||
from phase10at_fd_deadline_model import * # noqa: E402,F403
|
||||
|
||||
|
||||
PRECOMMIT = WorkerPrecommit(b"a" * 16, b"b" * 16, 101, 202, 7, 4096)
|
||||
RAW = encode_record(PRECOMMIT, STATUS_SUCCESS, 4096)
|
||||
|
||||
|
||||
def success_events() -> list[FakeFdEvent]:
|
||||
return [FakeFdEvent(CREATE_PIPE, OK),
|
||||
FakeFdEvent(SET_PARENT_READ_NONBLOCK, OK),
|
||||
FakeFdEvent(SPAWN_RFFDG, OK),
|
||||
FakeFdEvent(PARENT_CLOSE_WRITE, OK),
|
||||
FakeFdEvent(CHILD_CLOSE_READ, OK),
|
||||
FakeFdEvent(CHILD_CLOSE_WRITE, OK),
|
||||
FakeFdEvent(REAP_CHILD, OK),
|
||||
FakeFdEvent(PARENT_CLOSE_READ, OK)]
|
||||
|
||||
|
||||
class FdDeadlineTests(unittest.TestCase):
|
||||
def test_success_with_partial_reads_eintr_and_would_block(self) -> None:
|
||||
reads = (FakeRead(EINTR), FakeRead(WOULD_BLOCK),
|
||||
FakeRead(DATA, RAW[:40]), FakeRead(EINTR),
|
||||
FakeRead(DATA, RAW[40:]))
|
||||
result = run_fd_transaction(
|
||||
PRECOMMIT, FakeFdFacade(tuple(success_events())), reads)
|
||||
self.assertTrue(result.success and result.rffdg_used)
|
||||
self.assertEqual(result.eintr_count, 2)
|
||||
self.assertEqual(result.parent_fds_open + result.child_fds_open, 0)
|
||||
self.assertFalse(result.worker_alive or result.live_fd_present)
|
||||
|
||||
def test_every_setup_failure_closes_only_acquired_resources(self) -> None:
|
||||
expected_cleanup = {
|
||||
0: [],
|
||||
1: [PARENT_CLOSE_WRITE, PARENT_CLOSE_READ],
|
||||
2: [PARENT_CLOSE_WRITE, PARENT_CLOSE_READ],
|
||||
3: [TERMINATE_CHILD, CHILD_CLOSE_READ, CHILD_CLOSE_WRITE,
|
||||
REAP_CHILD, PARENT_CLOSE_WRITE, PARENT_CLOSE_READ],
|
||||
4: [TERMINATE_CHILD, CHILD_CLOSE_READ, CHILD_CLOSE_WRITE,
|
||||
REAP_CHILD, PARENT_CLOSE_READ],
|
||||
}
|
||||
baseline = success_events()[:5]
|
||||
for index, event in enumerate(baseline):
|
||||
prefix = baseline[:index] + [FakeFdEvent(event.operation, ERROR)]
|
||||
prefix += [FakeFdEvent(operation, OK)
|
||||
for operation in expected_cleanup[index]]
|
||||
with self.subTest(index=index):
|
||||
result = run_fd_transaction(
|
||||
PRECOMMIT, FakeFdFacade(tuple(prefix)),
|
||||
(FakeRead(DATA, RAW),))
|
||||
self.assertFalse(result.success)
|
||||
self.assertEqual(result.parent_fds_open + result.child_fds_open, 0)
|
||||
|
||||
def test_deadline_eof_overflow_and_bad_record_contain_worker(self) -> None:
|
||||
scripts = (
|
||||
(FakeRead(DATA, RAW[:10], ticks=252), FakeRead(DATA, RAW[10:])),
|
||||
(FakeRead(DATA, RAW[:10]), FakeRead(EOF)),
|
||||
(FakeRead(DATA, RAW[:100]), FakeRead(DATA, RAW[100:] + b"x")),
|
||||
(FakeRead(DATA, RAW), FakeRead(WOULD_BLOCK)),
|
||||
(FakeRead(DATA, bytes(bytearray(RAW[:1]) + RAW[1:])),),
|
||||
)
|
||||
# Make the final script genuinely invalid.
|
||||
damaged = bytearray(RAW)
|
||||
damaged[0] ^= 1
|
||||
scripts = scripts[:-1] + ((FakeRead(DATA, bytes(damaged)),),)
|
||||
cleanup = [FakeFdEvent(TERMINATE_CHILD, OK),
|
||||
FakeFdEvent(CHILD_CLOSE_WRITE, OK),
|
||||
FakeFdEvent(REAP_CHILD, OK),
|
||||
FakeFdEvent(PARENT_CLOSE_READ, OK)]
|
||||
for reads in scripts:
|
||||
events = success_events()[:5] + cleanup
|
||||
with self.subTest(reads=reads):
|
||||
result = run_fd_transaction(
|
||||
PRECOMMIT, FakeFdFacade(tuple(events)), reads)
|
||||
self.assertFalse(result.success or result.worker_alive)
|
||||
self.assertTrue(result.containment_required)
|
||||
|
||||
def test_cleanup_failure_and_unused_operations_are_hard_errors(self) -> None:
|
||||
events = [FakeFdEvent(CREATE_PIPE, OK),
|
||||
FakeFdEvent(SET_PARENT_READ_NONBLOCK, ERROR),
|
||||
FakeFdEvent(PARENT_CLOSE_WRITE, ERROR)]
|
||||
with self.assertRaises(FdModelError):
|
||||
run_fd_transaction(PRECOMMIT, FakeFdFacade(tuple(events)),
|
||||
(FakeRead(DATA, RAW),))
|
||||
with self.assertRaises(FdModelError):
|
||||
run_fd_transaction(
|
||||
PRECOMMIT,
|
||||
FakeFdFacade(tuple(success_events() +
|
||||
[FakeFdEvent(PARENT_CLOSE_READ, OK)])),
|
||||
(FakeRead(DATA, RAW),))
|
||||
|
||||
def test_invalid_read_and_boundary_are_rejected(self) -> None:
|
||||
with self.assertRaises(FdModelError):
|
||||
FakeRead(DATA, b"")
|
||||
with self.assertRaises(FdModelError):
|
||||
run_fd_transaction(PRECOMMIT, FakeFdFacade(tuple(success_events())), ())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Host tests for the target-free Phase-1.0AV canary contract."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
|
||||
PARSER = argparse.ArgumentParser()
|
||||
PARSER.add_argument("--root", type=Path, required=True)
|
||||
ROOT = PARSER.parse_args().root
|
||||
sys.path.insert(0, str(ROOT / "tools"))
|
||||
|
||||
from phase10av_launch_context_canary import * # noqa: E402,F403
|
||||
|
||||
|
||||
def plan() -> CanaryPairPlan:
|
||||
return CanaryPairPlan(
|
||||
PHASE, FIRMWARE, PROTOCOL_MAGIC, "a" * 64,
|
||||
(CanaryArm(RAW_ELFLDR, "b" * 64, "CHIMERA_AV_RAW_0001", "c" * 64),
|
||||
CanaryArm(BIGAPP_CANDIDATE, "d" * 64,
|
||||
"CHIMERA_AV_BIGAPP_01", "e" * 64)),
|
||||
True, False, False, False, False, False, False, False, False, False)
|
||||
|
||||
|
||||
def observation(arm: CanaryArm, submit_result: int) -> CanaryObservation:
|
||||
return CanaryObservation(
|
||||
arm.kind, arm.launcher_sha256, "a" * 64, arm.run_id, PROTOCOL_MAGIC,
|
||||
True, -1, 20, True, submit_result, 0, 10, True, 30, True, False, 0, 0)
|
||||
|
||||
|
||||
class LaunchContextCanaryTests(unittest.TestCase):
|
||||
def test_complete_plan_and_equal_results(self) -> None:
|
||||
value = plan()
|
||||
validate_plan(value)
|
||||
result = classify_pair(
|
||||
value, tuple(observation(arm, -1) for arm in value.arms))
|
||||
self.assertTrue(result.pair_comparable)
|
||||
self.assertEqual(result.status, "NO_SUBMIT_RETURN_DIFFERENCE")
|
||||
self.assertFalse(result.root_cause_proven or result.visible_output_proven)
|
||||
|
||||
def test_changed_return_is_candidate_only(self) -> None:
|
||||
value = plan()
|
||||
result = classify_pair(
|
||||
value, (observation(value.arms[0], -1),
|
||||
observation(value.arms[1], 0)))
|
||||
self.assertTrue(result.launch_context_candidate)
|
||||
self.assertFalse(result.root_cause_proven)
|
||||
self.assertFalse(result.visible_output_proven)
|
||||
self.assertFalse(result.firmware_behavior_proven)
|
||||
self.assertFalse(result.device_action_authorized)
|
||||
|
||||
def test_plan_requires_distinct_arms_and_approvals(self) -> None:
|
||||
value = plan()
|
||||
for field, replacement in (
|
||||
("launcher_sha256", value.arms[0].launcher_sha256),
|
||||
("run_id", value.arms[0].run_id),
|
||||
("approval_sha256", value.arms[0].approval_sha256)):
|
||||
arms = (value.arms[0], replace(value.arms[1], **{field: replacement}))
|
||||
with self.subTest(field=field), self.assertRaises(CanaryContractError):
|
||||
validate_plan(replace(value, arms=arms))
|
||||
|
||||
def test_plan_rejects_every_authority_and_retry(self) -> None:
|
||||
for field in ("automatic_retry", "reconnect", "resume", "installation",
|
||||
"autoload", "device_write_authorized",
|
||||
"app_termination_authorized", "result_reception_authorized",
|
||||
"activation_authorized"):
|
||||
with self.subTest(field=field), self.assertRaises(CanaryContractError):
|
||||
validate_plan(replace(plan(), **{field: True}))
|
||||
|
||||
def test_missing_or_early_terminal_is_incomplete(self) -> None:
|
||||
value = plan()
|
||||
baseline = observation(value.arms[0], -1)
|
||||
for candidate in (
|
||||
replace(observation(value.arms[1], 0), terminal_seen=False),
|
||||
replace(observation(value.arms[1], 0), cleanup_complete=False),
|
||||
replace(observation(value.arms[1], 0), terminal_sequence=20),
|
||||
replace(observation(value.arms[1], 0), submit_sequence=20),
|
||||
replace(observation(value.arms[1], 0), d04_seen=False),
|
||||
replace(observation(value.arms[1], 0), submit_seen=False)):
|
||||
with self.subTest(candidate=candidate):
|
||||
result = classify_pair(value, (baseline, candidate))
|
||||
self.assertEqual(result.status, "INCOMPLETE_NO_CAUSAL_COMPARISON")
|
||||
|
||||
def test_identity_drift_is_hard_failure(self) -> None:
|
||||
value = plan()
|
||||
baseline = observation(value.arms[0], -1)
|
||||
candidate = observation(value.arms[1], 0)
|
||||
for field, replacement in (("payload_sha256", "f" * 64),
|
||||
("run_id", "CHIMERA_AV_WRONG_001"),
|
||||
("protocol_magic", "CHD10OLD"),
|
||||
("launcher_sha256", "f" * 64)):
|
||||
with self.subTest(field=field), self.assertRaises(CanaryContractError):
|
||||
classify_pair(value, (baseline, replace(candidate, **{field: replacement})))
|
||||
|
||||
def test_forbidden_result_claims_are_hard_failure(self) -> None:
|
||||
value = plan()
|
||||
baseline = observation(value.arms[0], -1)
|
||||
candidate = observation(value.arms[1], 0)
|
||||
for field, replacement in (("retry_count", 1),
|
||||
("persistent_write_count", 1),
|
||||
("visible_output_observed", True)):
|
||||
with self.subTest(field=field), self.assertRaises(CanaryContractError):
|
||||
classify_pair(value, (baseline, replace(candidate, **{field: replacement})))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Failure injection for the host-only Phase-1.0AX AV protocol."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
|
||||
PARSER = argparse.ArgumentParser()
|
||||
PARSER.add_argument("--root", type=Path, required=True)
|
||||
ROOT = PARSER.parse_args().root
|
||||
sys.path.insert(0, str(ROOT / "tools"))
|
||||
|
||||
from phase10ax_canary_protocol_model import * # noqa: E402,F403
|
||||
|
||||
|
||||
def frame(sequence: int, stage: int, value: int) -> bytes:
|
||||
return encode_frame(Frame(sequence, stage, KIND_RAW, RAW0_VALID,
|
||||
value, 0, 0, 0, 0))
|
||||
|
||||
|
||||
def snapshot() -> CleanupSnapshot:
|
||||
return CleanupSnapshot(True, True, S15_COMPLETE, 0, 0x2f, 0, 0, 1)
|
||||
|
||||
|
||||
def trace() -> tuple[bytes, ...]:
|
||||
terminal = build_cleanup_terminal(4, snapshot())
|
||||
assert terminal is not None
|
||||
return (frame(1, D07, -1), frame(2, D12, 5),
|
||||
frame(3, D04, -1), terminal)
|
||||
|
||||
|
||||
class CanaryProtocolTests(unittest.TestCase):
|
||||
def test_exact_trace_is_complete_without_visibility_claim(self) -> None:
|
||||
result = validate_trace(trace())
|
||||
self.assertTrue(result.complete)
|
||||
self.assertEqual((result.submit_result, result.sdl_result), (-1, -1))
|
||||
self.assertEqual(result.cleaned_mask, 0x2f)
|
||||
self.assertFalse(result.visible_output_proven)
|
||||
self.assertFalse(result.firmware_behavior_proven)
|
||||
self.assertFalse(result.device_action_authorized)
|
||||
|
||||
def test_every_single_byte_mutation_breaks_frame(self) -> None:
|
||||
raw = frame(1, D07, -1)
|
||||
for index in range(FRAME_SIZE):
|
||||
damaged = bytearray(raw)
|
||||
damaged[index] ^= 1
|
||||
with self.subTest(index=index), self.assertRaises(CanaryProtocolError):
|
||||
parse_frame(bytes(damaged))
|
||||
|
||||
def test_d12_cannot_be_terminal(self) -> None:
|
||||
with self.assertRaises(CanaryProtocolError):
|
||||
encode_frame(Frame(2, D12, KIND_PAIR,
|
||||
RAW0_VALID | RAW1_VALID | TERMINAL,
|
||||
5, 104, 0, 0, 0))
|
||||
|
||||
def test_only_exact_cleanup_state_emits_d14(self) -> None:
|
||||
base = snapshot()
|
||||
self.assertIsNotNone(build_cleanup_terminal(1, base))
|
||||
cases = (("rarch_main_returned", False), ("d04_emitted", False),
|
||||
("phase", 14), ("initialized_mask", 1),
|
||||
("cleanup_order_errors", 1), ("cleanup_failure_count", 1))
|
||||
for field, value in cases:
|
||||
with self.subTest(field=field):
|
||||
self.assertIsNone(build_cleanup_terminal(
|
||||
1, replace(base, **{field: value})))
|
||||
|
||||
def test_cleanup_failure_is_independent_of_first_runtime_error(self) -> None:
|
||||
base = replace(snapshot(), rarch_main_result=-1,
|
||||
cleanup_failure_count=1)
|
||||
self.assertIsNone(build_cleanup_terminal(1, base))
|
||||
|
||||
def test_trace_rejects_order_duplicates_and_post_terminal_data(self) -> None:
|
||||
base = trace()
|
||||
cases = (base[:-1], (base[2], base[0], base[3]),
|
||||
(base[0], base[0], base[2], base[3]),
|
||||
base + (frame(5, 1, 0),),
|
||||
(frame(2, D07, -1), frame(1, D04, -1), base[-1]))
|
||||
for candidate in cases:
|
||||
with self.subTest(candidate=candidate), self.assertRaises(CanaryProtocolError):
|
||||
validate_trace(candidate)
|
||||
|
||||
def test_recomputed_crc_cannot_hide_cleanup_claim(self) -> None:
|
||||
bad = encode_frame(Frame(4, D14, KIND_PAIR,
|
||||
RAW0_VALID | RAW1_VALID | TERMINAL,
|
||||
1, 0x2f, 1, 0, 0))
|
||||
candidate = trace()[:-1] + (bad,)
|
||||
with self.assertRaises(CanaryProtocolError):
|
||||
validate_trace(candidate)
|
||||
|
||||
def test_valid_crc_with_wrong_stage_semantics_fails(self) -> None:
|
||||
base = trace()
|
||||
wrong_submit = encode_frame(Frame(1, D07, KIND_PAIR,
|
||||
RAW0_VALID | RAW1_VALID,
|
||||
-1, 0, 0, 0, 0))
|
||||
terminal = build_cleanup_terminal(5, snapshot())
|
||||
assert terminal is not None
|
||||
duplicate_d12 = (frame(1, D07, -1), frame(2, D12, 5),
|
||||
frame(3, D12, 5), frame(4, D04, -1), terminal)
|
||||
for candidate in ((wrong_submit,) + base[1:], duplicate_d12):
|
||||
with self.subTest(candidate=candidate), self.assertRaises(CanaryProtocolError):
|
||||
validate_trace(candidate)
|
||||
|
||||
def test_invalid_numeric_and_boolean_boundaries_fail(self) -> None:
|
||||
with self.assertRaises(CanaryProtocolError):
|
||||
encode_frame(Frame(0, D07, KIND_RAW, RAW0_VALID, 0, 0, 0, 0, 0))
|
||||
with self.assertRaises(CanaryProtocolError):
|
||||
build_cleanup_terminal(1, replace(snapshot(), d04_emitted=1))
|
||||
with self.assertRaises(CanaryProtocolError):
|
||||
build_cleanup_terminal(1, replace(snapshot(), cleaned_mask=-1))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Host tests for the pure Phase-1.0DC BigApp gate."""
|
||||
from __future__ import annotations
|
||||
import argparse
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
import sys, unittest
|
||||
|
||||
P=argparse.ArgumentParser();P.add_argument("--root",type=Path,required=True);ROOT=P.parse_args().root
|
||||
sys.path.insert(0,str(ROOT/"tools"))
|
||||
from phase10dc_bigapp_gate_contract import (BigAppGateError,BigAppGateRecord,PHASE,validate_candidate,validate_inactive) # noqa:E402
|
||||
|
||||
def inactive():
|
||||
return BigAppGateRecord(PHASE,False,*([None]*14),*([False]*13))
|
||||
|
||||
def candidate():
|
||||
return BigAppGateRecord(PHASE,True,"9.60","PPSA01659","CHIMERA_DC_001","2030-01-01T00:00:00Z","2030-01-01T00:05:00Z","chimera_bigapp_canary_launcher.elf",65536,"a"*64,"retroarch_ps5_launch_canary.elf",1845240,"8dadce9d9faaef21ea129a3d216c768eea9a3ca9bf8ecb8d852e376b58a9bf95","b"*64,"CHD10AV1","D14",True,True,False,False,False,False,False,False,False,False,True,True,True)
|
||||
|
||||
class Tests(unittest.TestCase):
|
||||
def test_inactive(self):validate_inactive(inactive())
|
||||
def test_candidate_data(self):validate_candidate(candidate())
|
||||
def test_exact_payload(self):
|
||||
with self.assertRaises(BigAppGateError):validate_candidate(replace(candidate(),payload_sha256="c"*64))
|
||||
def test_no_existing_bigapp(self):
|
||||
with self.assertRaises(BigAppGateError):validate_candidate(replace(candidate(),no_running_bigapp_attested=False))
|
||||
def test_effect_acceptance(self):
|
||||
with self.assertRaises(BigAppGateError):validate_candidate(replace(candidate(),kernel_ptrace_effects_accepted=False))
|
||||
def test_forbidden_effects(self):
|
||||
for field in ("app_termination_authorized","persistent_write_authorized","system_remount_authorized","installation_authorized","autoload_authorized","automatic_retry","reconnect","fallback_title"):
|
||||
with self.subTest(field=field),self.assertRaises(BigAppGateError):validate_candidate(replace(candidate(),**{field:True}))
|
||||
def test_every_bounded_proof(self):
|
||||
for field in ("bounded_parent_detach_proven","bounded_child_cleanup_proven","bounded_result_channel_proven"):
|
||||
with self.subTest(field=field),self.assertRaises(BigAppGateError):validate_candidate(replace(candidate(),**{field:False}))
|
||||
def test_window_and_title(self):
|
||||
with self.assertRaises(BigAppGateError):validate_candidate(replace(candidate(),expires_at="2030-01-01T00:05:01Z"))
|
||||
with self.assertRaises(BigAppGateError):validate_candidate(replace(candidate(),title_id="FAKE00000"))
|
||||
|
||||
if __name__=="__main__":unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from __future__ import annotations
|
||||
import argparse
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
import sys,unittest
|
||||
P=argparse.ArgumentParser();P.add_argument("--root",type=Path,required=True);ROOT=P.parse_args().root;sys.path.insert(0,str(ROOT/"tools"))
|
||||
from phase10df_title_observer_contract import ObserverError,ObserverPlan,Outcome,PHASE,SyntheticResult,classify_synthetic,validate_candidate,validate_inactive # noqa:E402
|
||||
def inactive():return ObserverPlan(PHASE,False,*([None]*8),*([False]*9))
|
||||
def query():return ObserverPlan(PHASE,True,"9.60","PPSA01659","SOURCE_BOUND_QUERY","a"*64,None,None,"b"*64,4096,True,True,False,False,False,False,False,False,False)
|
||||
def result(**changes):
|
||||
value=SyntheticResult("9.60","PPSA01659","SOURCE_BOUND_QUERY","b"*64,True,64,True,False,None)
|
||||
return replace(value,**changes)
|
||||
class Tests(unittest.TestCase):
|
||||
def test_inactive(self):validate_inactive(inactive())
|
||||
def test_source_query_candidate(self):validate_candidate(query())
|
||||
def test_path_requires_provenance(self):
|
||||
plan=replace(query(),method="EXACT_PATH_METADATA",exact_literal_path="/fixed/path",path_provenance_sha256="c"*64);validate_candidate(plan)
|
||||
with self.assertRaises(ObserverError):validate_candidate(replace(plan,path_provenance_sha256=None))
|
||||
def test_no_shell_or_enumeration(self):
|
||||
for field in ("shell_present","directory_enumeration","title_launch","app_termination","device_write","retry","reconnect"):
|
||||
with self.subTest(field=field),self.assertRaises(ObserverError):validate_candidate(replace(query(),**{field:True}))
|
||||
def test_explicit_results(self):
|
||||
self.assertEqual(classify_synthetic(query(),result()),Outcome.PRESENT)
|
||||
self.assertEqual(classify_synthetic(query(),result(explicit_present=False,explicit_absent=True)),Outcome.ABSENT)
|
||||
def test_error_and_incomplete_are_unknown(self):
|
||||
self.assertEqual(classify_synthetic(query(),result(error_code=-1)),Outcome.UNKNOWN)
|
||||
self.assertEqual(classify_synthetic(query(),result(complete=False)),Outcome.UNKNOWN)
|
||||
def test_ambiguous_and_oversize_rejected(self):
|
||||
with self.assertRaises(ObserverError):classify_synthetic(query(),result(explicit_absent=True))
|
||||
with self.assertRaises(ObserverError):classify_synthetic(query(),result(result_bytes=4097))
|
||||
def test_binding_mismatch_rejected(self):
|
||||
with self.assertRaises(ObserverError):classify_synthetic(query(),result(title_id="OTHER"))
|
||||
if __name__=="__main__":unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from __future__ import annotations
|
||||
import argparse,hashlib,sqlite3,sys,tempfile,unittest
|
||||
from pathlib import Path
|
||||
P=argparse.ArgumentParser();P.add_argument("--root",type=Path,required=True);ROOT=P.parse_args().root;sys.path.insert(0,str(ROOT/"tools"))
|
||||
from phase10dh_snapshot_query import Outcome,PHASE,SnapshotBinding,SnapshotError,query_snapshot # noqa:E402
|
||||
def binding(path):data=path.read_bytes();return SnapshotBinding(PHASE,"PPSA01659",len(data),hashlib.sha256(data).hexdigest())
|
||||
class Phase10DH(unittest.TestCase):
|
||||
def make(self,rows=(),schema=True):
|
||||
temp=tempfile.TemporaryDirectory();path=Path(temp.name)/"snapshot.db";db=sqlite3.connect(path)
|
||||
if schema:
|
||||
db.execute("CREATE TABLE tbl_appinfo (titleId TEXT, key TEXT, val TEXT)");db.executemany("INSERT INTO tbl_appinfo VALUES (?, 'K', 'V')",[(x,) for x in rows])
|
||||
else:db.execute("CREATE TABLE other (value TEXT)")
|
||||
db.commit();db.close();return temp,path
|
||||
def test_present_and_no_sidecars(self):
|
||||
temp,path=self.make(["PPSA01659"])
|
||||
with temp:self.assertEqual(query_snapshot(path,binding(path)),Outcome.PRESENT);self.assertEqual([p.name for p in Path(temp.name).iterdir()],["snapshot.db"])
|
||||
def test_absent(self):
|
||||
temp,path=self.make(["PPSA01650"])
|
||||
with temp:self.assertEqual(query_snapshot(path,binding(path)),Outcome.ABSENT)
|
||||
def test_schema_mismatch_unknown(self):
|
||||
temp,path=self.make(schema=False)
|
||||
with temp:self.assertEqual(query_snapshot(path,binding(path)),Outcome.UNKNOWN)
|
||||
def test_duplicate_is_unknown(self):
|
||||
temp,path=self.make(["PPSA01659","PPSA01659"])
|
||||
with temp:self.assertEqual(query_snapshot(path,binding(path)),Outcome.UNKNOWN)
|
||||
def test_hash_mismatch_rejected(self):
|
||||
temp,path=self.make()
|
||||
with temp:
|
||||
bad=binding(path);bad=SnapshotBinding(bad.phase,bad.title_id,bad.size,"0"*64)
|
||||
with self.assertRaises(SnapshotError):query_snapshot(path,bad)
|
||||
def test_non_database_rejected(self):
|
||||
with tempfile.TemporaryDirectory() as name:
|
||||
path=Path(name)/"x";path.write_bytes(b"not sqlite")
|
||||
with self.assertRaises(SnapshotError):query_snapshot(path,binding(path))
|
||||
if __name__=="__main__":unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from __future__ import annotations
|
||||
import argparse,struct,sys,unittest
|
||||
from pathlib import Path
|
||||
P=argparse.ArgumentParser();P.add_argument("--root",type=Path,required=True);R=P.parse_args().root;sys.path.insert(0,str(R/"tools"))
|
||||
from phase10dm_snapshot_protocol import ProtocolError,parse_stream # noqa:E402
|
||||
F=struct.Struct("<8sIIiiQQQQq")
|
||||
def frame(kind,status=0,size=32,sent=0,dev=1,ino=2,mtime=3):return F.pack(b"CHS10DM1",1,kind,status,0,size,sent,dev,ino,mtime)
|
||||
def valid():
|
||||
data=b"SQLite format 3\x00"+b"x"*16
|
||||
return frame(1,size=len(data))+data+frame(2,size=len(data),sent=len(data))
|
||||
class Tests(unittest.TestCase):
|
||||
def test_valid(self):self.assertEqual(parse_stream(valid()).size,32)
|
||||
def test_truncated(self):
|
||||
with self.assertRaises(ProtocolError):parse_stream(valid()[:-1])
|
||||
def test_error(self):
|
||||
with self.assertRaises(ProtocolError):parse_stream(frame(3,status=1)+b"x"*64)
|
||||
def test_metadata_change(self):
|
||||
raw=valid();raw=raw[:-64]+frame(2,size=32,sent=32,ino=9)
|
||||
with self.assertRaises(ProtocolError):parse_stream(raw)
|
||||
def test_non_sqlite(self):
|
||||
raw=frame(1)+b"z"*32+frame(2,sent=32)
|
||||
with self.assertRaises(ProtocolError):parse_stream(raw)
|
||||
if __name__=="__main__":unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from __future__ import annotations
|
||||
import argparse,struct,sys,tempfile,unittest
|
||||
from pathlib import Path
|
||||
P=argparse.ArgumentParser();P.add_argument("--root",type=Path,required=True);R=P.parse_args().root;sys.path.insert(0,str(R/"tools"))
|
||||
from phase10dm_snapshot_protocol import ProtocolError # noqa:E402
|
||||
from phase10dn_snapshot_receiver import SnapshotReceiver # noqa:E402
|
||||
F=struct.Struct("<8sIIiiQQQQq");DATA=b"SQLite format 3\x00"+b"x"*16
|
||||
def frame(k,ino=2):return F.pack(b"CHS10DM1",1,k,0,0,len(DATA),len(DATA) if k==2 else 0,1,ino,3)
|
||||
class Tests(unittest.TestCase):
|
||||
def test_chunked_success(self):
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
p=Path(d)/"snapshot.db";r=SnapshotReceiver(p);raw=frame(1)+DATA+frame(2)
|
||||
for byte in raw:r.feed(bytes([byte]))
|
||||
self.assertEqual(r.finish().size,len(DATA));self.assertEqual(p.read_bytes(),DATA)
|
||||
def test_existing_rejected(self):
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
p=Path(d)/"x";p.write_bytes(b"x")
|
||||
with self.assertRaises(ProtocolError):SnapshotReceiver(p)
|
||||
def test_truncated_aborts(self):
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
r=SnapshotReceiver(Path(d)/"x");r.feed(frame(1)+DATA)
|
||||
with self.assertRaises(ProtocolError):r.finish()
|
||||
self.assertEqual(r.state,"ABORTED")
|
||||
def test_metadata_mismatch(self):
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
r=SnapshotReceiver(Path(d)/"x");r.feed(frame(1)+DATA+frame(2,ino=9))
|
||||
with self.assertRaises(ProtocolError):r.finish()
|
||||
def test_trailing_rejected(self):
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
r=SnapshotReceiver(Path(d)/"x")
|
||||
with self.assertRaises(ProtocolError):r.feed(frame(1)+DATA+frame(2)+b"x")
|
||||
if __name__=="__main__":unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from __future__ import annotations
|
||||
import argparse,json,sys,tempfile,unittest
|
||||
from pathlib import Path
|
||||
P=argparse.ArgumentParser();P.add_argument("--root",type=Path,required=True);R=P.parse_args().root;sys.path.insert(0,str(R/"tools"))
|
||||
import phase10do_one_shot_snapshot_runner as runner # noqa:E402
|
||||
|
||||
def record(base:Path):
|
||||
return {"active":True,"run_id":"DM_TEST","target":"192.0.2.1","port":9021,"artifact_size":runner.ARTIFACT_SIZE,"artifact_sha256":runner.ARTIFACT_SHA256,"snapshot_path":str((base/"snapshot.db").resolve()),"receipt_path":str((base/"consumed.json").resolve()),"not_before":99.0,"not_after":101.0,"one_connection":True,"one_transfer":True,"one_execution":True,"result_receive":True,"target_file_read":True,"device_write":False,"installation":False,"autoload":False,"retry":False,"reconnect":False}
|
||||
class Tests(unittest.TestCase):
|
||||
def test_inactive_tracked_manifest_rejected(self):
|
||||
manifest=json.loads((R/"manifests/retroarch/phase-1.0do-inactive-snapshot-runner.json").read_text())
|
||||
with self.assertRaises(runner.RunnerError):runner.validate_records(manifest,manifest,100.0)
|
||||
def test_exact_active_pair_validates(self):
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
value=record(Path(d));self.assertEqual(runner.validate_records(value,dict(value),100.0)["run_id"],"DM_TEST")
|
||||
def test_mismatch_and_forbidden_authority_rejected(self):
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
value=record(Path(d));other=dict(value);other["run_id"]="OTHER"
|
||||
with self.assertRaises(runner.RunnerError):runner.validate_records(value,other,100.0)
|
||||
value["device_write"]=True
|
||||
with self.assertRaises(runner.RunnerError):runner.validate_records(value,value,100.0)
|
||||
def test_receipt_is_exclusive(self):
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
value=record(Path(d));runner._consume(value)
|
||||
with self.assertRaises(FileExistsError):runner._consume(value)
|
||||
if __name__=="__main__":unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from __future__ import annotations
|
||||
import argparse,struct,sys,unittest
|
||||
from pathlib import Path
|
||||
P=argparse.ArgumentParser();P.add_argument("--root",type=Path,required=True);R=P.parse_args().root;sys.path.insert(0,str(R/"tools"))
|
||||
from phase10dq_inventory_protocol import ProtocolError,parse_stream # noqa:E402
|
||||
F=struct.Struct("<8sIIiiIIQ24s");E=struct.Struct("<256sIIIIQQq24s")
|
||||
def frame(kind,count=0,transferred=0,status=0):return F.pack(b"CHI10DQ1",1,kind,status,0,count,0,transferred,bytes(24))
|
||||
def entry(name="eboot.bin"):
|
||||
raw=name.encode();return E.pack(raw+bytes(256-len(raw)),len(raw),8,0o100555,0,123,9,10,bytes(24))
|
||||
class Tests(unittest.TestCase):
|
||||
def test_success(self):
|
||||
value=parse_stream(frame(1)+entry()+frame(2,1,320));self.assertEqual(value[0].name,"eboot.bin");self.assertEqual(value[0].size,123)
|
||||
def test_truncated(self):
|
||||
with self.assertRaises(ProtocolError):parse_stream(frame(1)+entry())
|
||||
def test_target_error(self):
|
||||
with self.assertRaisesRegex(ProtocolError,"target error"):parse_stream(frame(3,status=1)+frame(2))
|
||||
def test_traversal_rejected(self):
|
||||
with self.assertRaises(ProtocolError):parse_stream(frame(1)+entry("../x")+frame(2,1,320))
|
||||
def test_terminal_count_rejected(self):
|
||||
with self.assertRaises(ProtocolError):parse_stream(frame(1)+entry()+frame(2,2,320))
|
||||
if __name__=="__main__":unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from __future__ import annotations
|
||||
import argparse,json,sys,tempfile,unittest
|
||||
from pathlib import Path
|
||||
P=argparse.ArgumentParser();P.add_argument("--root",type=Path,required=True);R=P.parse_args().root;sys.path.insert(0,str(R/"tools"))
|
||||
import phase10dr_inventory_runner as runner # noqa:E402
|
||||
def record(root):return {"active":True,"run_id":"TEST","target":"192.0.2.1","port":9021,"artifact_size":runner.ARTIFACT_SIZE,"artifact_sha256":runner.ARTIFACT_SHA256,"output_path":str((root/"out.json").resolve()),"receipt_path":str((root/"receipt.json").resolve()),"not_before":1.0,"not_after":3.0,"one_connection":True,"one_transfer":True,"one_execution":True,"result_receive":True,"directory_inventory":True,"possible_atime_effect_acknowledged":True,"device_file_content_read":False,"persistent_device_write":False,"installation":False,"autoload":False,"retry":False,"reconnect":False}
|
||||
class Tests(unittest.TestCase):
|
||||
def test_tracked_inactive(self):
|
||||
value=json.loads((R/"manifests/retroarch/phase-1.0dr-inactive-inventory-runner.json").read_text())
|
||||
with self.assertRaises(runner.RunnerError):runner.validate_records(value,value,2.0)
|
||||
def test_exact_pair(self):
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
value=record(Path(d));self.assertEqual(runner.validate_records(value,dict(value),2.0)["run_id"],"TEST")
|
||||
def test_atime_ack_required(self):
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
value=record(Path(d));value["possible_atime_effect_acknowledged"]=False
|
||||
with self.assertRaises(runner.RunnerError):runner.validate_records(value,value,2.0)
|
||||
def test_forbidden_content_read(self):
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
value=record(Path(d));value["device_file_content_read"]=True
|
||||
with self.assertRaises(runner.RunnerError):runner.validate_records(value,value,2.0)
|
||||
if __name__=="__main__":unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
import argparse,struct,sys,unittest
|
||||
from pathlib import Path
|
||||
P=argparse.ArgumentParser();P.add_argument("--root",type=Path,required=True);R=P.parse_args().root;sys.path.insert(0,str(R/"tools"))
|
||||
from phase10ds_metadata_protocol import ProtocolError,parse_stream # noqa:E402
|
||||
F=struct.Struct("<8sIIiiIIQ24s");DATA=[b"a",b"{}",b"<x/>",b"crc"]
|
||||
def frame(k,index=0,length=0,total=0,status=0):return F.pack(b"CHM10DS1",1,k,status,0,index,length,total,bytes(24))
|
||||
def stream():
|
||||
raw=frame(1,4);total=0
|
||||
for i,data in enumerate(DATA):raw+=frame(2,i,len(data),total)+data;total+=len(data)
|
||||
return raw+frame(3,4,0,total)
|
||||
class Tests(unittest.TestCase):
|
||||
def test_success(self):self.assertEqual(parse_stream(stream())["app.json"],b"{}")
|
||||
def test_truncated(self):
|
||||
with self.assertRaises(ProtocolError):parse_stream(stream()[:-1])
|
||||
def test_wrong_index(self):
|
||||
raw=bytearray(stream());raw[64+24:64+28]=(3).to_bytes(4,"little")
|
||||
with self.assertRaises(ProtocolError):parse_stream(bytes(raw))
|
||||
def test_target_error(self):
|
||||
with self.assertRaisesRegex(ProtocolError,"target error"):parse_stream(frame(4,status=1)+bytes(64))
|
||||
if __name__=="__main__":unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
import argparse,json,sys,tempfile,unittest
|
||||
from pathlib import Path
|
||||
P=argparse.ArgumentParser();P.add_argument("--root",type=Path,required=True);R=P.parse_args().root;sys.path.insert(0,str(R/"tools"))
|
||||
import phase10dt_metadata_runner as m # noqa:E402
|
||||
def rec(d):return {"active":True,"run_id":"T","target":"192.0.2.1","port":9021,"artifact_size":m.ARTIFACT_SIZE,"artifact_sha256":m.ARTIFACT_SHA256,"output_path":str((d/"o").resolve()),"receipt_path":str((d/"r").resolve()),"not_before":1,"not_after":3,"one_connection":True,"one_transfer":True,"one_execution":True,"result_receive":True,"four_exact_metadata_reads":True,"possible_atime_effect_acknowledged":True,"app_pkg_read":False,"backup_read":False,"persistent_device_write":False,"installation":False,"autoload":False,"retry":False,"reconnect":False}
|
||||
class Tests(unittest.TestCase):
|
||||
def test_inactive(self):
|
||||
v=json.loads((R/"manifests/retroarch/phase-1.0dt-inactive-metadata-runner.json").read_text())
|
||||
with self.assertRaises(m.RunnerError):m.validate(v,v,2)
|
||||
def test_exact(self):
|
||||
with tempfile.TemporaryDirectory() as x:v=rec(Path(x));self.assertEqual(m.validate(v,dict(v),2)["run_id"],"T")
|
||||
def test_package_forbidden(self):
|
||||
with tempfile.TemporaryDirectory() as x:
|
||||
v=rec(Path(x));v["app_pkg_read"]=True
|
||||
with self.assertRaises(m.RunnerError):m.validate(v,v,2)
|
||||
if __name__=="__main__":unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
import argparse,json,sys,unittest
|
||||
from pathlib import Path
|
||||
P=argparse.ArgumentParser();P.add_argument("--root",type=Path,required=True);R=P.parse_args().root;sys.path.insert(0,str(R/"tools"))
|
||||
import phase10dv_package_stat_runner as m # noqa:E402
|
||||
class Tests(unittest.TestCase):
|
||||
def test_tracked_record_is_inactive(self):
|
||||
v=json.loads((R/"manifests/retroarch/phase-1.0dv-inactive-package-stat-runner.json").read_text())
|
||||
with self.assertRaises(m.Error):m.validate(v,v,0)
|
||||
def test_record_size(self):self.assertEqual(m.REC.size,72)
|
||||
if __name__=="__main__":unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
import argparse,json,sys,unittest
|
||||
from pathlib import Path
|
||||
P=argparse.ArgumentParser();P.add_argument("--root",type=Path,required=True);R=P.parse_args().root;sys.path.insert(0,str(R/"tools"))
|
||||
import phase10dx_package_readback_runner as m # noqa:E402
|
||||
class Tests(unittest.TestCase):
|
||||
def test_inactive_manifest_rejected(self):
|
||||
v=json.loads((R/"manifests/retroarch/phase-1.0dx-inactive-package-readback-runner.json").read_text())
|
||||
with self.assertRaises(m.Error):m.validate(v,v,0)
|
||||
def test_protocol_constants(self):self.assertEqual(m.FRAME.size,64);self.assertEqual(m.MAX_WIRE,18153600)
|
||||
if __name__=="__main__":unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Offline synthetic-transcript tests for Phase-1.0T sanitization."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
|
||||
def load(path: Path):
|
||||
spec = importlib.util.spec_from_file_location("phase10t_transcript", path)
|
||||
assert spec and spec.loader
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[spec.name] = module
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def require(value: bool, message: str) -> None:
|
||||
if not value:
|
||||
raise RuntimeError(message)
|
||||
|
||||
|
||||
def greeting(extra: str = "") -> str:
|
||||
return (
|
||||
"Welcome to shsrv.elf running on pid 123, compiled Jul 22 2026 at 12:34:56\n"
|
||||
"Model: synthetic-model\n"
|
||||
"S/N: SYNTHETIC-SERIAL-DO-NOT-RETAIN\n"
|
||||
"S/W: 9.60\n"
|
||||
"SoC temp: 40 C\n"
|
||||
"CPU temp: 41 C\n"
|
||||
"CPU freq: 3500 MHz\n" + extra)
|
||||
|
||||
|
||||
def help_text(commands: list[str]) -> str:
|
||||
return "Builtin commands:\n" + "".join(
|
||||
f" {command} - synthetic\n" for command in commands) + "\n"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
module = load(args.root.resolve() / "tools/phase10t_shsrv_transcript.py")
|
||||
current_commands = "authid browse cat cd chgrp chmod chown chroot cmp cp df echo env exec exit export file find grep hbdbg hbldr hexdump http2_get id kill launch ln ls mkdir mknod mount mv notify pkg_install procstat ps pwd reptyr rm rmdir sfocreate sfoinfo sleep stat sum suspend sync sysctl touch umount".split()
|
||||
v07_commands = "browse cat cd chgrp chmod chown chroot cmp cp echo env exec exit export file find grep hbldr help hexdump http2_get id kill launch ln ls mkdir mknod mount mv notify ps pwd rm rmdir sfocreate sfoinfo sleep stat sum sync sysctl touch umount".split()
|
||||
cases = []
|
||||
|
||||
def case(name):
|
||||
def register(function):
|
||||
cases.append((name, function))
|
||||
return function
|
||||
return register
|
||||
|
||||
@case("01 empty transcript is invalid and non-exact")
|
||||
def _():
|
||||
result = module.parse_transcript("")
|
||||
require(result["classification"] == "INVALID_OR_INCOMPLETE" and result["exact_identity"] is False, "empty input accepted")
|
||||
|
||||
@case("02 greeting is compile metadata only")
|
||||
def _(): require(module.parse_transcript(greeting())["classification"] == "COMPILE_METADATA_ONLY", "greeting promoted")
|
||||
|
||||
@case("03 serial value is discarded")
|
||||
def _():
|
||||
result = module.parse_transcript(greeting())
|
||||
require(result["sensitive_input"]["serial_line_seen"] is True and "SYNTHETIC-SERIAL" not in json.dumps(result), "serial retained")
|
||||
|
||||
@case("04 telemetry values are discarded")
|
||||
def _():
|
||||
result = module.parse_transcript(greeting())
|
||||
require(result["sensitive_input"]["telemetry_line_seen"] is True and "3500" not in json.dumps(result), "telemetry retained")
|
||||
|
||||
@case("05 firmware metadata is retained")
|
||||
def _(): require(module.parse_transcript(greeting())["compile_metadata"]["firmware"] == "9.60", "firmware lost")
|
||||
|
||||
@case("06 v0.19 help matches only a source family")
|
||||
def _():
|
||||
result = module.parse_transcript(greeting(help_text(current_commands)))
|
||||
require(result["command_fingerprint"]["sha256"] == module.CURRENT_COMMAND_HASH and result["command_fingerprint"]["source_family_match"] == "OFFICIAL_V019_SOURCE_FAMILY_CANDIDATE" and result["command_fingerprint"]["proves_exact_binary"] is False, "v0.19 fingerprint mismatch")
|
||||
|
||||
@case("07 v0.7 help matches only a source family")
|
||||
def _():
|
||||
result = module.parse_transcript(greeting(help_text(v07_commands)))
|
||||
require(result["command_fingerprint"]["sha256"] == module.V07_COMMAND_HASH and result["command_fingerprint"]["source_family_match"] == "OFFICIAL_V07_SOURCE_FAMILY_CANDIDATE", "v0.7 fingerprint mismatch")
|
||||
|
||||
@case("08 altered help stays unresolved")
|
||||
def _(): require(module.parse_transcript(greeting(help_text(["help", "unknown"])))["command_fingerprint"]["source_family_match"] == "UNRESOLVED", "unknown family promoted")
|
||||
|
||||
@case("09 unknown stat path is discarded")
|
||||
def _(): require(module.parse_transcript(greeting("filename: /unknown\nsize: 123\n"), {"/approved"})["file_observations"] == [], "unknown path retained")
|
||||
|
||||
@case("10 approved stat path is retained")
|
||||
def _():
|
||||
result = module.parse_transcript(greeting("filename: /approved\nsize: 123\nmtime: 456\n"), {"/approved"})
|
||||
require(result["file_observations"] == [{"path": "/approved", "metadata_seen": True, "size": 123, "mtime": 456, "proves_exact_binary": False}], "approved metadata mismatch")
|
||||
|
||||
@case("11 sum is labeled weak and non-cryptographic")
|
||||
def _():
|
||||
result = module.parse_transcript(greeting("12345 /approved\n"), {"/approved"})
|
||||
observation = result["file_observations"][0]
|
||||
require(observation["weak_checksum_algorithm"] == "BSD_ROTATE_16" and observation["cryptographic_checksum"] is False, "weak sum promoted")
|
||||
|
||||
@case("12 all combined metadata remains non-exact")
|
||||
def _():
|
||||
text = greeting(help_text(current_commands) + "filename: /approved\nsize: 123\n12345 /approved\n")
|
||||
result = module.parse_transcript(text, {"/approved"})
|
||||
require(result["exact_identity"] is False and all(not item["proves_exact_binary"] for item in result["file_observations"]), "combined metadata promoted")
|
||||
|
||||
failures = []
|
||||
for name, function in cases:
|
||||
try:
|
||||
function()
|
||||
print(f"PASS {name}")
|
||||
except Exception as error: # noqa: BLE001 - test harness
|
||||
failures.append(f"{name}: {error}")
|
||||
print(f"FAIL {name}: {error}")
|
||||
if failures:
|
||||
return 1
|
||||
print(f"Phase-1.0T transcript tests passed: {len(cases)}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,202 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Synthetic offline tests for the Phase-1.0V collector model."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
|
||||
def load(path: Path):
|
||||
spec = importlib.util.spec_from_file_location("phase10v_model", path)
|
||||
assert spec and spec.loader
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[spec.name] = module
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def require(value: bool, message: str) -> None:
|
||||
if not value:
|
||||
raise RuntimeError(message)
|
||||
|
||||
|
||||
def greeting(extra: str = "") -> bytes:
|
||||
return (
|
||||
"Welcome to shsrv.elf running on pid 123, compiled Jul 22 2026 at 12:34:56\n"
|
||||
"Model: synthetic-model\n"
|
||||
"S/N: SYNTHETIC-SERIAL-NEVER-RETAIN\n"
|
||||
"S/W: 9.60\n"
|
||||
"SoC temp: 40 C\n"
|
||||
"CPU temp: 41 C\n"
|
||||
"CPU freq: 3500 MHz\n" + extra).encode("utf-8")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
root = args.root.resolve()
|
||||
sys.path.insert(0, str(root / "tools"))
|
||||
module = load(root / "tools/phase10v_shsrv_collector_model.py")
|
||||
cases = []
|
||||
|
||||
def case(name):
|
||||
def register(function):
|
||||
cases.append((name, function))
|
||||
return function
|
||||
return register
|
||||
|
||||
@case("01 plain greeting is parsed offline")
|
||||
def _():
|
||||
collector = module.OfflineCollector(); collector.feed(greeting())
|
||||
require(collector.finalize()["classification"] == "COMPILE_METADATA_ONLY", "greeting rejected")
|
||||
|
||||
@case("02 Telnet negotiation is removed")
|
||||
def _():
|
||||
collector = module.OfflineCollector(); collector.feed(bytes([255, 251, 1]) + greeting())
|
||||
require(collector.finalize()["compile_metadata"]["firmware"] == "9.60", "negotiation leaked")
|
||||
|
||||
@case("03 fragmented Telnet negotiation is handled")
|
||||
def _():
|
||||
collector = module.OfflineCollector(); collector.feed(bytes([255])); collector.feed(bytes([253])); collector.feed(bytes([3]) + greeting())
|
||||
require(collector.finalize()["classification"] == "COMPILE_METADATA_ONLY", "fragmentation failed")
|
||||
|
||||
@case("04 Telnet subnegotiation is removed")
|
||||
def _():
|
||||
control = bytes([255, 250, 24, 1, 2, 3, 255, 240])
|
||||
collector = module.OfflineCollector(); collector.feed(control + greeting())
|
||||
require(collector.finalize()["classification"] == "COMPILE_METADATA_ONLY", "subnegotiation leaked")
|
||||
|
||||
@case("04b doubled IAC stays inside subnegotiation")
|
||||
def _():
|
||||
control = bytes([255, 250, 24, 255, 255, 1, 255, 240])
|
||||
collector = module.OfflineCollector(); collector.feed(control + greeting())
|
||||
require(collector.finalize()["classification"] == "COMPILE_METADATA_ONLY", "doubled IAC state failed")
|
||||
|
||||
@case("05 serial is absent from output")
|
||||
def _():
|
||||
collector = module.OfflineCollector(); collector.feed(greeting()); result = collector.finalize()
|
||||
require("SYNTHETIC-SERIAL" not in json.dumps(result), "serial retained")
|
||||
|
||||
@case("06 telemetry values are absent from output")
|
||||
def _():
|
||||
collector = module.OfflineCollector(); collector.feed(greeting()); result = collector.finalize()
|
||||
require("3500" not in json.dumps(result), "telemetry retained")
|
||||
|
||||
@case("07 exact identity always remains false")
|
||||
def _():
|
||||
collector = module.OfflineCollector(); collector.feed(greeting())
|
||||
require(collector.finalize()["exact_identity"] is False, "identity promoted")
|
||||
|
||||
@case("08 raw byte limit is enforced")
|
||||
def _():
|
||||
collector = module.OfflineCollector()
|
||||
try: collector.feed(b"x" * (module.MAX_RAW_BYTES + 1))
|
||||
except module.CollectorError: return
|
||||
raise RuntimeError("oversized input accepted")
|
||||
|
||||
@case("09 chunk limit is enforced")
|
||||
def _():
|
||||
collector = module.OfflineCollector()
|
||||
try:
|
||||
for _index in range(module.MAX_CHUNKS + 1): collector.feed(b"x")
|
||||
except module.CollectorError: return
|
||||
raise RuntimeError("excess chunks accepted")
|
||||
|
||||
@case("09b empty chunks do not consume the limit")
|
||||
def _():
|
||||
collector = module.OfflineCollector()
|
||||
for _index in range(module.MAX_CHUNKS + 1): collector.feed(b"")
|
||||
require(collector.chunk_count == 0, "empty chunks counted")
|
||||
|
||||
@case("10 incomplete Telnet sequence is rejected")
|
||||
def _():
|
||||
collector = module.OfflineCollector(); collector.feed(bytes([255]))
|
||||
try: collector.finalize()
|
||||
except module.CollectorError: return
|
||||
raise RuntimeError("incomplete control accepted")
|
||||
|
||||
@case("11 invalid UTF-8 is rejected")
|
||||
def _():
|
||||
collector = module.OfflineCollector(); collector.feed(bytes([0xC3, 0x28]))
|
||||
try: collector.finalize()
|
||||
except module.CollectorError: return
|
||||
raise RuntimeError("invalid UTF-8 accepted")
|
||||
|
||||
@case("12 collector finalizes only once")
|
||||
def _():
|
||||
collector = module.OfflineCollector(); collector.feed(greeting()); collector.finalize()
|
||||
try: collector.finalize()
|
||||
except module.CollectorError: return
|
||||
raise RuntimeError("second finalize accepted")
|
||||
|
||||
@case("13 feed after finalization is rejected")
|
||||
def _():
|
||||
collector = module.OfflineCollector(); collector.finalize()
|
||||
try: collector.feed(b"later")
|
||||
except module.CollectorError: return
|
||||
raise RuntimeError("post-finalize feed accepted")
|
||||
|
||||
@case("14 abort prevents output")
|
||||
def _():
|
||||
collector = module.OfflineCollector(); collector.feed(greeting()); collector.abort()
|
||||
try: collector.finalize()
|
||||
except module.CollectorError: return
|
||||
raise RuntimeError("aborted collector finalized")
|
||||
|
||||
@case("15 approved literal-path metadata is retained")
|
||||
def _():
|
||||
collector = module.OfflineCollector(); collector.feed(greeting("filename: /approved\nsize: 123\n"))
|
||||
result = collector.finalize({"/approved"})
|
||||
require(result["file_observations"][0]["size"] == 123, "approved metadata lost")
|
||||
|
||||
@case("16 unknown path is discarded and memory erasure unproven")
|
||||
def _():
|
||||
collector = module.OfflineCollector(); collector.feed(greeting("filename: /unknown\nsize: 123\n"))
|
||||
result = collector.finalize({"/approved"})
|
||||
require(result["file_observations"] == [] and result["collector_model"]["physical_memory_erasure_proven"] is False, "boundary promoted")
|
||||
|
||||
@case("17 unsafe or non-normalized expected paths are rejected")
|
||||
def _():
|
||||
for path in ("relative", "/safe/../escape", "/wild*card", "/line\nfeed"):
|
||||
collector = module.OfflineCollector(); collector.feed(greeting())
|
||||
try: collector.finalize({path})
|
||||
except module.CollectorError: continue
|
||||
raise RuntimeError(f"unsafe path accepted: {path!r}")
|
||||
|
||||
@case("18 unexpected firmware metadata is rejected")
|
||||
def _():
|
||||
collector = module.OfflineCollector(); collector.feed(greeting().replace(b"S/W: 9.60", b"S/W: secret"))
|
||||
try: collector.finalize()
|
||||
except module.CollectorError: return
|
||||
raise RuntimeError("unexpected firmware accepted")
|
||||
|
||||
@case("19 malformed compile metadata is rejected")
|
||||
def _():
|
||||
data = greeting().replace(b"compiled Jul 22 2026", b"compiled SERIAL-IN-DATE")
|
||||
collector = module.OfflineCollector(); collector.feed(data)
|
||||
try: collector.finalize()
|
||||
except module.CollectorError: return
|
||||
raise RuntimeError("malformed compile metadata accepted")
|
||||
|
||||
failures = []
|
||||
for name, function in cases:
|
||||
try:
|
||||
function()
|
||||
print(f"PASS {name}")
|
||||
except Exception as error: # noqa: BLE001 - synthetic harness
|
||||
failures.append(f"{name}: {error}")
|
||||
print(f"FAIL {name}: {error}")
|
||||
if failures:
|
||||
return 1
|
||||
print(f"Phase-1.0V collector-model tests passed: {len(cases)}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,269 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Host-only policy tests for the inactive Phase-1.0W architecture."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from datetime import datetime, timezone
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
|
||||
def load(path: Path):
|
||||
spec = importlib.util.spec_from_file_location("phase10w_policy", path)
|
||||
assert spec and spec.loader
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[spec.name] = module
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def require(value: bool, message: str) -> None:
|
||||
if not value:
|
||||
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()
|
||||
sys.path.insert(0, str(root / "tools"))
|
||||
sys.path.insert(0, str(root / "tests"))
|
||||
module = load(root / "tools/phase10w_shsrv_client_policy.py")
|
||||
from phase10w_fake_transport import FakeTransport, FakeTransportError
|
||||
now = datetime(2026, 7, 22, 12, 0, tzinfo=timezone.utc)
|
||||
|
||||
def records(window="T2_GREETING_AND_HELP"):
|
||||
path = None if window == "T2_GREETING_AND_HELP" else "/data/exact.elf"
|
||||
commands = list(module.WINDOW_COMMANDS[window])
|
||||
common = {
|
||||
"active": True,
|
||||
"policy_sha256": "a" * 64,
|
||||
"collector_sha256": module.COLLECTOR_SHA256,
|
||||
"run_id": "synthetic_run_001",
|
||||
"target_address": "device.invalid",
|
||||
"target_port": 2323,
|
||||
"window": window,
|
||||
"exact_literal_path": path,
|
||||
"commands": commands,
|
||||
"deadline_seconds": 10,
|
||||
"expires_at": "2026-07-22T12:10:00Z",
|
||||
}
|
||||
approval = dict(common)
|
||||
approval.update({
|
||||
"attested": True,
|
||||
"listener_already_running_attested": True,
|
||||
"ps5_connection_authorized": True,
|
||||
"device_request_authorized": True,
|
||||
"result_receive_authorized": True,
|
||||
"spawned_shell_effects_accepted": True,
|
||||
"automatic_serial_query_accepted": True,
|
||||
"automatic_telemetry_query_accepted": True,
|
||||
"sanitized_output_only_accepted": True,
|
||||
"physical_memory_erasure_unproven_accepted": True,
|
||||
"target_build_authorized": False,
|
||||
"device_transfer_authorized": False,
|
||||
"device_execution_authorized": False,
|
||||
"installation_authorized": False,
|
||||
"autoload_authorized": False,
|
||||
"device_write_authorized": False,
|
||||
"automatic_retry": False,
|
||||
"reconnect_authorized": False,
|
||||
"resume_authorized": False,
|
||||
"fallback_authorized": False,
|
||||
})
|
||||
return common, approval
|
||||
|
||||
cases = []
|
||||
|
||||
def case(name):
|
||||
def register(function):
|
||||
cases.append((name, function))
|
||||
return function
|
||||
return register
|
||||
|
||||
@case("01 tracked inactive shape is inert")
|
||||
def _(): require(module.inactive_record_is_inert({"active": False, "policy_sha256": None, "collector_sha256": None, "run_id": None, "target_address": None, "target_port": None, "window": None, "exact_literal_path": None, "commands": [], "deadline_seconds": None, "expires_at": None}), "inactive shape rejected")
|
||||
|
||||
@case("02 exact help window produces immutable plan")
|
||||
def _():
|
||||
activation, approval = records(); plan = module.build_session_plan(activation, approval, now)
|
||||
require(plan.commands == ("help",) and plan.target_port == 2323, "help plan mismatch")
|
||||
|
||||
@case("03 exact-path window validates path")
|
||||
def _():
|
||||
activation, approval = records("T3_ONE_EXACT_PATH"); plan = module.build_session_plan(activation, approval, now)
|
||||
require(plan.exact_literal_path == "/data/exact.elf", "path lost")
|
||||
|
||||
@case("04 inactive record is rejected")
|
||||
def _():
|
||||
activation, approval = records(); activation["active"] = False
|
||||
try: module.build_session_plan(activation, approval, now)
|
||||
except module.PolicyError: return
|
||||
raise RuntimeError("inactive activation accepted")
|
||||
|
||||
@case("05 missing attestation is rejected")
|
||||
def _():
|
||||
activation, approval = records(); approval["attested"] = False
|
||||
try: module.build_session_plan(activation, approval, now)
|
||||
except module.PolicyError: return
|
||||
raise RuntimeError("missing attestation accepted")
|
||||
|
||||
@case("06 record mismatch is rejected")
|
||||
def _():
|
||||
activation, approval = records(); approval["run_id"] = "different_run"
|
||||
try: module.build_session_plan(activation, approval, now)
|
||||
except module.PolicyError: return
|
||||
raise RuntimeError("mismatch accepted")
|
||||
|
||||
@case("07 collector hash mismatch is rejected")
|
||||
def _():
|
||||
activation, approval = records(); activation["collector_sha256"] = approval["collector_sha256"] = "0" * 64
|
||||
try: module.build_session_plan(activation, approval, now)
|
||||
except module.PolicyError: return
|
||||
raise RuntimeError("wrong hash accepted")
|
||||
|
||||
@case("08 retry is rejected")
|
||||
def _():
|
||||
activation, approval = records(); approval["automatic_retry"] = True
|
||||
try: module.build_session_plan(activation, approval, now)
|
||||
except module.PolicyError: return
|
||||
raise RuntimeError("retry accepted")
|
||||
|
||||
@case("09 execution authority is rejected")
|
||||
def _():
|
||||
activation, approval = records(); approval["device_execution_authorized"] = True
|
||||
try: module.build_session_plan(activation, approval, now)
|
||||
except module.PolicyError: return
|
||||
raise RuntimeError("execution authority accepted")
|
||||
|
||||
@case("10 missing side-effect acceptance is rejected")
|
||||
def _():
|
||||
activation, approval = records(); approval["automatic_serial_query_accepted"] = False
|
||||
try: module.build_session_plan(activation, approval, now)
|
||||
except module.PolicyError: return
|
||||
raise RuntimeError("serial effect hidden")
|
||||
|
||||
@case("11 command injection is rejected")
|
||||
def _():
|
||||
activation, approval = records(); activation["commands"] = approval["commands"] = ["help; hbldr"]
|
||||
try: module.build_session_plan(activation, approval, now)
|
||||
except module.PolicyError: return
|
||||
raise RuntimeError("command injection accepted")
|
||||
|
||||
@case("12 target syntax injection is rejected")
|
||||
def _():
|
||||
activation, approval = records(); activation["target_address"] = approval["target_address"] = "device.invalid\nother"
|
||||
try: module.build_session_plan(activation, approval, now)
|
||||
except module.PolicyError: return
|
||||
raise RuntimeError("target injection accepted")
|
||||
|
||||
@case("13 wrong port is rejected")
|
||||
def _():
|
||||
activation, approval = records(); activation["target_port"] = approval["target_port"] = 9999
|
||||
try: module.build_session_plan(activation, approval, now)
|
||||
except module.PolicyError: return
|
||||
raise RuntimeError("wrong port accepted")
|
||||
|
||||
@case("14 unsafe path is rejected")
|
||||
def _():
|
||||
activation, approval = records("T3_ONE_EXACT_PATH"); activation["exact_literal_path"] = approval["exact_literal_path"] = "/data/../escape"
|
||||
try: module.build_session_plan(activation, approval, now)
|
||||
except (module.PolicyError, RuntimeError): return
|
||||
raise RuntimeError("unsafe path accepted")
|
||||
|
||||
@case("15 excessive deadline is rejected")
|
||||
def _():
|
||||
activation, approval = records(); activation["deadline_seconds"] = approval["deadline_seconds"] = 11
|
||||
try: module.build_session_plan(activation, approval, now)
|
||||
except module.PolicyError: return
|
||||
raise RuntimeError("deadline relaxation accepted")
|
||||
|
||||
@case("16 expired approval is rejected")
|
||||
def _():
|
||||
activation, approval = records(); activation["expires_at"] = approval["expires_at"] = "2026-07-22T11:59:00Z"
|
||||
try: module.build_session_plan(activation, approval, now)
|
||||
except module.PolicyError: return
|
||||
raise RuntimeError("expired approval accepted")
|
||||
|
||||
@case("17 overlong approval lifetime is rejected")
|
||||
def _():
|
||||
activation, approval = records(); activation["expires_at"] = approval["expires_at"] = "2026-07-22T12:16:00Z"
|
||||
try: module.build_session_plan(activation, approval, now)
|
||||
except module.PolicyError: return
|
||||
raise RuntimeError("long approval accepted")
|
||||
|
||||
@case("18 policy plan exposes no transport method")
|
||||
def _():
|
||||
activation, approval = records(); plan = module.build_session_plan(activation, approval, now)
|
||||
require(not any(hasattr(plan, name) for name in ("connect", "send", "recv", "open")), "transport method present")
|
||||
|
||||
@case("18b unknown approval field is rejected")
|
||||
def _():
|
||||
activation, approval = records(); approval["unexpected_authority"] = True
|
||||
try: module.build_session_plan(activation, approval, now)
|
||||
except module.PolicyError: return
|
||||
raise RuntimeError("unknown approval field accepted")
|
||||
|
||||
@case("18c missing listener attestation is rejected")
|
||||
def _():
|
||||
activation, approval = records(); approval["listener_already_running_attested"] = False
|
||||
try: module.build_session_plan(activation, approval, now)
|
||||
except module.PolicyError: return
|
||||
raise RuntimeError("missing listener attestation accepted")
|
||||
|
||||
@case("18d policy hash mismatch is rejected")
|
||||
def _():
|
||||
activation, approval = records(); approval["policy_sha256"] = "b" * 64
|
||||
try: module.build_session_plan(activation, approval, now)
|
||||
except module.PolicyError: return
|
||||
raise RuntimeError("policy hash mismatch accepted")
|
||||
|
||||
@case("19 fake transport models exactly one session")
|
||||
def _():
|
||||
activation, approval = records(); plan = module.build_session_plan(activation, approval, now)
|
||||
transport = FakeTransport([b"synthetic"]); transport.open_once(plan)
|
||||
transport.send_command_token("help"); require(transport.receive_chunk() == b"synthetic", "fake input lost")
|
||||
require(transport.receive_chunk() is None, "fake EOF missing"); transport.close_once()
|
||||
require(transport.events == ["OPEN", "COMMAND_HELP", "RECEIVE", "CLOSE"], "event sequence mismatch")
|
||||
|
||||
@case("20 fake second open is rejected")
|
||||
def _():
|
||||
activation, approval = records(); plan = module.build_session_plan(activation, approval, now)
|
||||
transport = FakeTransport([]); transport.open_once(plan)
|
||||
try: transport.open_once(plan)
|
||||
except FakeTransportError: return
|
||||
raise RuntimeError("second fake open accepted")
|
||||
|
||||
@case("21 fake unexpected command is rejected")
|
||||
def _():
|
||||
activation, approval = records(); plan = module.build_session_plan(activation, approval, now)
|
||||
transport = FakeTransport([]); transport.open_once(plan)
|
||||
try: transport.send_command_token("hbldr")
|
||||
except FakeTransportError: return
|
||||
raise RuntimeError("unexpected fake command accepted")
|
||||
|
||||
@case("22 fake transport has no retry or reconnect API")
|
||||
def _():
|
||||
transport = FakeTransport([])
|
||||
require(not any(hasattr(transport, name) for name in ("retry", "reconnect", "resume")), "retry API present")
|
||||
|
||||
failures = []
|
||||
for name, function in cases:
|
||||
try:
|
||||
function()
|
||||
print(f"PASS {name}")
|
||||
except Exception as error: # noqa: BLE001 - synthetic harness
|
||||
failures.append(f"{name}: {error}")
|
||||
print(f"FAIL {name}: {error}")
|
||||
if failures:
|
||||
return 1
|
||||
print(f"Phase-1.0W client-policy tests passed: {len(cases)}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,284 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Fault-injection tests for the inactive Phase-1.0X transport layer."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from datetime import datetime, timezone
|
||||
import importlib.util
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
|
||||
def load(path: Path, name: str):
|
||||
spec = importlib.util.spec_from_file_location(name, path)
|
||||
assert spec and spec.loader
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[spec.name] = module
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def require(value: bool, message: str) -> None:
|
||||
if not value:
|
||||
raise RuntimeError(message)
|
||||
|
||||
|
||||
class FakeClock:
|
||||
def __init__(self) -> None:
|
||||
self.value = 100.0
|
||||
|
||||
def monotonic(self) -> float:
|
||||
return self.value
|
||||
|
||||
|
||||
class FakeAdapter:
|
||||
def __init__(self, boundaries, events, clock, advance=0.0, fail=None):
|
||||
self.boundaries = dict(boundaries)
|
||||
self.events = events
|
||||
self.clock = clock
|
||||
self.advance = advance
|
||||
self.fail = fail
|
||||
self.open_count = 0
|
||||
self.closed = False
|
||||
|
||||
def open_once(self, _plan, _remaining):
|
||||
self.events.append("OPEN")
|
||||
self.open_count += 1
|
||||
if self.fail == "OPEN": raise RuntimeError("SENSITIVE RAW OPEN ERROR")
|
||||
|
||||
def receive_boundary(self, boundary, _remaining):
|
||||
self.events.append(boundary)
|
||||
self.clock.value += self.advance
|
||||
if self.fail == boundary: raise RuntimeError("SENSITIVE RAW RECEIVE ERROR")
|
||||
return self.boundaries.get(boundary, [])
|
||||
|
||||
def send_command_token(self, command, _path, _remaining):
|
||||
self.events.append(f"SEND_{command.upper()}")
|
||||
if self.fail == f"SEND_{command.upper()}":
|
||||
raise RuntimeError("SENSITIVE RAW SEND ERROR")
|
||||
|
||||
def close_once(self):
|
||||
self.events.append("CLOSE")
|
||||
self.closed = True
|
||||
if self.fail == "CLOSE": raise RuntimeError("SENSITIVE RAW CLOSE ERROR")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
root = args.root.resolve()
|
||||
sys.path.insert(0, str(root / "tools"))
|
||||
policy = load(root / "tools/phase10w_shsrv_client_policy.py", "phase10w_policy_x")
|
||||
transport = load(root / "tools/phase10x_inactive_transport.py", "phase10x_transport")
|
||||
now = datetime(2026, 7, 22, 12, 0, tzinfo=timezone.utc)
|
||||
|
||||
def plan(window="T2_GREETING_AND_HELP", run_id="synthetic_run_x"):
|
||||
path = None if window == "T2_GREETING_AND_HELP" else "/data/exact.elf"
|
||||
common = {
|
||||
"active": True, "policy_sha256": "a" * 64,
|
||||
"collector_sha256": policy.COLLECTOR_SHA256, "run_id": run_id,
|
||||
"target_address": "device.invalid", "target_port": 2323,
|
||||
"window": window, "exact_literal_path": path,
|
||||
"commands": list(policy.WINDOW_COMMANDS[window]),
|
||||
"deadline_seconds": 10, "expires_at": "2026-07-22T12:10:00Z",
|
||||
}
|
||||
approval = dict(common)
|
||||
approval.update({
|
||||
"attested": True, "listener_already_running_attested": True,
|
||||
"ps5_connection_authorized": True, "device_request_authorized": True,
|
||||
"result_receive_authorized": True, "spawned_shell_effects_accepted": True,
|
||||
"automatic_serial_query_accepted": True,
|
||||
"automatic_telemetry_query_accepted": True,
|
||||
"sanitized_output_only_accepted": True,
|
||||
"physical_memory_erasure_unproven_accepted": True,
|
||||
"target_build_authorized": False, "device_transfer_authorized": False,
|
||||
"device_execution_authorized": False, "installation_authorized": False,
|
||||
"autoload_authorized": False, "device_write_authorized": False,
|
||||
"automatic_retry": False, "reconnect_authorized": False,
|
||||
"resume_authorized": False, "fallback_authorized": False,
|
||||
})
|
||||
return policy.build_session_plan(common, approval, now)
|
||||
|
||||
greeting = (
|
||||
b"Welcome to shsrv.elf running on pid 1, compiled Jul 22 2026 at 12:34:56\n"
|
||||
b"S/N: SENSITIVE-SERIAL\nS/W: 9.60\nCPU freq: 3500 MHz\n")
|
||||
help_output = b"Builtin commands:\n help\n\n"
|
||||
cases = []
|
||||
|
||||
def case(name):
|
||||
def register(function):
|
||||
cases.append((name, function))
|
||||
return function
|
||||
return register
|
||||
|
||||
def execute(directory, session_plan=None, adapter=None, clock=None):
|
||||
session_plan = session_plan or plan()
|
||||
clock = clock or FakeClock()
|
||||
events = []
|
||||
adapter = adapter or FakeAdapter({"INITIAL_PROMPT": [greeting], "AFTER_HELP": [help_output]}, events, clock)
|
||||
store = transport.ExclusiveEvidenceStore(Path(directory))
|
||||
return transport.run_injected_session(session_plan, adapter, clock, store), events
|
||||
|
||||
@case("01 receipt exists before fake open")
|
||||
def _():
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
session_plan = plan(); clock = FakeClock(); events = []
|
||||
class InspectAdapter(FakeAdapter):
|
||||
def open_once(self, p, remaining):
|
||||
require((Path(directory) / f"{p.run_id}.consumed.json").exists(), "receipt missing before open")
|
||||
super().open_once(p, remaining)
|
||||
adapter = InspectAdapter({"INITIAL_PROMPT": [greeting], "AFTER_HELP": [help_output]}, events, clock)
|
||||
execute(directory, session_plan, adapter, clock)
|
||||
|
||||
@case("02 help flow is one shot")
|
||||
def _():
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
outcome, events = execute(directory)
|
||||
require(events == ["OPEN", "INITIAL_PROMPT", "SEND_HELP", "AFTER_HELP", "CLOSE"] and outcome.exact_identity is False, "help sequence mismatch")
|
||||
|
||||
@case("03 exact-path flow preserves command order")
|
||||
def _():
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
p = plan("T3_ONE_EXACT_PATH"); clock = FakeClock(); events = []
|
||||
adapter = FakeAdapter({"INITIAL_PROMPT": [greeting], "AFTER_STAT": [b"filename: /data/exact.elf\nsize: 123\n"], "AFTER_SUM": [b"12345 /data/exact.elf\n"]}, events, clock)
|
||||
outcome, _ = execute(directory, p, adapter, clock)
|
||||
require(events == ["OPEN", "INITIAL_PROMPT", "SEND_STAT", "AFTER_STAT", "SEND_SUM", "AFTER_SUM", "CLOSE"] and outcome.classification == "WEAK_FILE_CORRELATION_ONLY", "path sequence mismatch")
|
||||
|
||||
@case("04 sanitized output excludes serial and telemetry")
|
||||
def _():
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
outcome, _ = execute(directory); data = outcome.output.path.read_text(encoding="ascii")
|
||||
require("SENSITIVE-SERIAL" not in data and "3500" not in data, "sensitive output retained")
|
||||
|
||||
@case("05 receipt excludes target")
|
||||
def _():
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
outcome, _ = execute(directory); data = outcome.receipt.path.read_text(encoding="ascii")
|
||||
require("device.invalid" not in data and '"target_retained":false' in data, "target retained")
|
||||
|
||||
@case("06 second run is blocked before fake open")
|
||||
def _():
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
execute(directory); clock = FakeClock(); events = []
|
||||
adapter = FakeAdapter({}, events, clock)
|
||||
try: execute(directory, plan(), adapter, clock)
|
||||
except transport.EvidenceFailure: require(events == [], "adapter opened after consumed receipt"); return
|
||||
raise RuntimeError("second run accepted")
|
||||
|
||||
@case("07 existing output cannot be overwritten")
|
||||
def _():
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
p = plan(); store = transport.ExclusiveEvidenceStore(Path(directory)); receipt = store.create_consumed_receipt(p, 100.0)
|
||||
store.create_sanitized_output(p, receipt, {"classification": "x", "exact_identity": False})
|
||||
try: store.create_sanitized_output(p, receipt, {"classification": "y"})
|
||||
except transport.EvidenceFailure: return
|
||||
raise RuntimeError("output overwrite accepted")
|
||||
|
||||
@case("08 deadline expiry fails and closes")
|
||||
def _():
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
p = plan(); clock = FakeClock(); events = []
|
||||
adapter = FakeAdapter({"INITIAL_PROMPT": [greeting]}, events, clock, advance=11.0)
|
||||
try: execute(directory, p, adapter, clock)
|
||||
except transport.SessionFailure: require(adapter.closed, "adapter not closed"); return
|
||||
raise RuntimeError("expired session accepted")
|
||||
|
||||
@case("09 adapter open failure is normalized and closed")
|
||||
def _():
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
p = plan(); clock = FakeClock(); events = []
|
||||
adapter = FakeAdapter({}, events, clock, fail="OPEN")
|
||||
try: execute(directory, p, adapter, clock)
|
||||
except transport.SessionFailure as error:
|
||||
require(
|
||||
"SENSITIVE" not in str(error)
|
||||
and error.__cause__ is None
|
||||
and error.__context__ is None
|
||||
and adapter.closed,
|
||||
"adapter detail/cause leaked or close skipped")
|
||||
return
|
||||
raise RuntimeError("adapter failure accepted")
|
||||
|
||||
@case("10 adapter closes after send failure")
|
||||
def _():
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
p = plan(); clock = FakeClock(); events = []
|
||||
adapter = FakeAdapter({"INITIAL_PROMPT": [greeting]}, events, clock, fail="SEND_HELP")
|
||||
try: execute(directory, p, adapter, clock)
|
||||
except transport.SessionFailure: require(adapter.closed and adapter.open_count == 1, "cleanup/retry mismatch"); return
|
||||
raise RuntimeError("send failure accepted")
|
||||
|
||||
@case("11 excessive boundary chunks are rejected")
|
||||
def _():
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
p = plan(); clock = FakeClock(); events = []
|
||||
adapter = FakeAdapter({"INITIAL_PROMPT": [b"x"] * 65}, events, clock)
|
||||
try: execute(directory, p, adapter, clock)
|
||||
except transport.SessionFailure: return
|
||||
raise RuntimeError("excess chunks accepted")
|
||||
|
||||
@case("12 malformed transcript creates no output")
|
||||
def _():
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
p = plan(); clock = FakeClock(); events = []
|
||||
adapter = FakeAdapter({"INITIAL_PROMPT": [b"\xff"]}, events, clock)
|
||||
try: execute(directory, p, adapter, clock)
|
||||
except transport.SessionFailure:
|
||||
require(not (Path(directory) / f"{p.run_id}.sanitized.json").exists(), "invalid output created"); return
|
||||
raise RuntimeError("malformed transcript accepted")
|
||||
|
||||
@case("13 output reopens as valid JSON")
|
||||
def _():
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
outcome, _ = execute(directory)
|
||||
receipt = json.loads(outcome.receipt.path.read_text(encoding="ascii"))
|
||||
value = json.loads(outcome.output.path.read_text(encoding="ascii"))
|
||||
require(
|
||||
receipt["status"] == "CONSUMED_BEFORE_ADAPTER_OPEN"
|
||||
and value["status"] == "SANITIZED_OUTPUT_COMPLETE"
|
||||
and value["receipt_sha256"] == outcome.receipt.sha256,
|
||||
"output binding mismatch")
|
||||
|
||||
@case("14 output byte count and hash match reopen")
|
||||
def _():
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
outcome, _ = execute(directory); payload = outcome.output.path.read_bytes()
|
||||
import hashlib
|
||||
require(len(payload) == outcome.output.size and hashlib.sha256(payload).hexdigest() == outcome.output.sha256, "reopen identity mismatch")
|
||||
|
||||
@case("15 evidence store exposes no cleanup")
|
||||
def _():
|
||||
require(not any(hasattr(transport.ExclusiveEvidenceStore, name) for name in ("delete", "cleanup", "overwrite")), "cleanup API present")
|
||||
|
||||
@case("16 transport module exposes no live CLI")
|
||||
def _(): require(not hasattr(transport, "main"), "live CLI present")
|
||||
|
||||
@case("17 transport module imports no socket")
|
||||
def _():
|
||||
source = (root / "tools/phase10x_inactive_transport.py").read_text(encoding="utf-8")
|
||||
require("import socket" not in source and "from socket" not in source, "socket import present")
|
||||
|
||||
@case("18 host-only result is not exact identity")
|
||||
def _():
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
outcome, _ = execute(directory); require(outcome.exact_identity is False, "identity promoted")
|
||||
|
||||
failures = []
|
||||
for name, function in cases:
|
||||
try:
|
||||
function(); print(f"PASS {name}")
|
||||
except Exception as error: # noqa: BLE001 - fault harness
|
||||
failures.append(f"{name}: {error}"); print(f"FAIL {name}: {error}")
|
||||
if failures:
|
||||
return 1
|
||||
print(f"Phase-1.0X inactive-transport tests passed: {len(cases)}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Host-only tests for the Phase-1.0Y shsrv framing model."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
|
||||
def load(path: Path):
|
||||
spec = importlib.util.spec_from_file_location("phase10y_framing", path)
|
||||
assert spec and spec.loader
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[spec.name] = module
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def require(value: bool, message: str) -> None:
|
||||
if not value:
|
||||
raise RuntimeError(message)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
model = load(args.root.resolve() / "tools/phase10y_shsrv_framing_model.py")
|
||||
cases = []
|
||||
|
||||
def case(name):
|
||||
def register(function):
|
||||
cases.append((name, function))
|
||||
return function
|
||||
return register
|
||||
|
||||
def decode(family, chunks):
|
||||
decoder = model.ClientWireDecoder(family)
|
||||
for chunk in chunks:
|
||||
decoder.feed(chunk)
|
||||
return decoder.finalize()
|
||||
|
||||
@case("01 legacy family passes raw bytes")
|
||||
def _(): require(decode(model.LEGACY_RAW, [b"help\r\n"]).application == b"help\r\n", "legacy transformed")
|
||||
|
||||
@case("02 legacy family passes Telnet controls to shell")
|
||||
def _():
|
||||
raw = bytes((model.IAC, model.WILL, 1))
|
||||
value = decode(model.LEGACY_RAW, [raw])
|
||||
require(value.application == raw and not value.negotiation_replies, "legacy negotiated")
|
||||
|
||||
@case("03 current family maps CRLF to LF")
|
||||
def _(): require(decode(model.LIBTELNET_NVT, [b"help\r\n"]).application == b"help\n", "CRLF mismatch")
|
||||
|
||||
@case("04 current family maps CRNUL to CR")
|
||||
def _(): require(decode(model.LIBTELNET_NVT, [b"x\r\x00"]).application == b"x\r", "CRNUL mismatch")
|
||||
|
||||
@case("05 current family preserves CR followed by data")
|
||||
def _(): require(decode(model.LIBTELNET_NVT, [b"x\ry"]).application == b"x\ry", "CR data mismatch")
|
||||
|
||||
@case("06 doubled IAC becomes one application byte")
|
||||
def _():
|
||||
value = decode(model.LIBTELNET_NVT, [bytes((model.IAC, model.IAC))])
|
||||
require(value.application == bytes((model.IAC,)), "IAC escape mismatch")
|
||||
|
||||
@case("07 WILL receives DONT")
|
||||
def _():
|
||||
value = decode(model.LIBTELNET_NVT, [bytes((model.IAC, model.WILL, 1))])
|
||||
require(value.negotiation_replies == (bytes((model.IAC, model.DONT, 1)),), "WILL reply mismatch")
|
||||
|
||||
@case("08 DO receives WONT")
|
||||
def _():
|
||||
value = decode(model.LIBTELNET_NVT, [bytes((model.IAC, model.DO, 3))])
|
||||
require(value.negotiation_replies == (bytes((model.IAC, model.WONT, 3)),), "DO reply mismatch")
|
||||
|
||||
@case("09 initial WONT produces no reply")
|
||||
def _(): require(not decode(model.LIBTELNET_NVT, [bytes((model.IAC, model.WONT, 1))]).negotiation_replies, "WONT replied")
|
||||
|
||||
@case("10 initial DONT produces no reply")
|
||||
def _(): require(not decode(model.LIBTELNET_NVT, [bytes((model.IAC, model.DONT, 1))]).negotiation_replies, "DONT replied")
|
||||
|
||||
@case("11 fragmented negotiation is modeled")
|
||||
def _():
|
||||
value = decode(model.LIBTELNET_NVT, [bytes((model.IAC,)), bytes((model.WILL,)), b"\x01"])
|
||||
require(value.negotiation_replies == (bytes((model.IAC, model.DONT, 1)),), "fragment reply mismatch")
|
||||
|
||||
@case("12 subnegotiation is removed")
|
||||
def _():
|
||||
frame = bytes((model.IAC, model.SB, 31, 0, 80, model.IAC, model.SE))
|
||||
require(decode(model.LIBTELNET_NVT, [frame[:2], frame[2:]]).application == b"", "subnegotiation leaked")
|
||||
|
||||
@case("13 incomplete control fails closed")
|
||||
def _():
|
||||
decoder = model.ClientWireDecoder(model.LIBTELNET_NVT); decoder.feed(bytes((model.IAC,)))
|
||||
try: decoder.finalize()
|
||||
except model.FramingError: return
|
||||
raise RuntimeError("incomplete control accepted")
|
||||
|
||||
@case("14 outgoing newline becomes CRLF only in current family")
|
||||
def _():
|
||||
require(model.encode_server_text(model.LIBTELNET_NVT, b"x\n") == b"x\r\n" and model.encode_server_text(model.LEGACY_RAW, b"x\n") == b"x\n", "newline encoding mismatch")
|
||||
|
||||
@case("15 outgoing CR becomes CRNUL")
|
||||
def _(): require(model.encode_server_text(model.LIBTELNET_NVT, b"x\r") == b"x\r\x00", "CR encoding mismatch")
|
||||
|
||||
@case("16 outgoing IAC is doubled")
|
||||
def _(): require(model.encode_server_text(model.LIBTELNET_NVT, bytes((model.IAC,))) == bytes((model.IAC, model.IAC)), "outgoing IAC mismatch")
|
||||
|
||||
@case("17 neither family proactively negotiates")
|
||||
def _(): require(all(model.initial_server_bytes(family) == b"" for family in model.SOURCE_FAMILIES), "proactive bytes invented")
|
||||
|
||||
@case("18 source-shaped prompt remains candidate only")
|
||||
def _():
|
||||
value = model.assess_prompt_candidates(b"greeting\r\n/$ ")
|
||||
require(value.classification == "SOURCE_SHAPE_CANDIDATE_ONLY" and value.exact_completion_proven is False, "prompt promoted")
|
||||
|
||||
@case("19 embedded prompt shape is ambiguous")
|
||||
def _():
|
||||
value = model.assess_prompt_candidates(b"/tmp/$ embedded$ ")
|
||||
require(value.classification == "AMBIGUOUS_PROMPT_CANDIDATES" and len(value.candidate_offsets) == 2, "ambiguity missed")
|
||||
|
||||
@case("20 nonterminal prompt shape is incomplete")
|
||||
def _():
|
||||
value = model.assess_prompt_candidates(b"/$ output")
|
||||
require(value.classification == "NO_TERMINAL_PROMPT_CANDIDATE", "nonterminal candidate accepted")
|
||||
|
||||
@case("21 model byte bound fails closed")
|
||||
def _():
|
||||
decoder = model.ClientWireDecoder(model.LEGACY_RAW)
|
||||
try: decoder.feed(b"x" * (model.MAX_MODEL_BYTES + 1))
|
||||
except model.FramingError: return
|
||||
raise RuntimeError("oversize wire accepted")
|
||||
|
||||
@case("22 IAC command is recorded, not application data")
|
||||
def _():
|
||||
value = decode(model.LIBTELNET_NVT, [bytes((model.IAC, 244))])
|
||||
require(value.application == b"" and value.iac_commands == (244,), "IAC command mismatch")
|
||||
|
||||
failures = []
|
||||
for name, function in cases:
|
||||
try:
|
||||
function()
|
||||
print(f"PASS {name}")
|
||||
except Exception as error: # noqa: BLE001 - test harness
|
||||
failures.append(f"{name}: {error}")
|
||||
print(f"FAIL {name}: {error}")
|
||||
if failures:
|
||||
return 1
|
||||
print(f"Phase-1.0Y framing-model tests passed: {len(cases)}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,206 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Synthetic host-only tests for the Phase-1.0Z passive batch contract."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from dataclasses import asdict, replace
|
||||
from datetime import datetime, timezone
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
|
||||
CURRENT_COMMANDS = "authid browse cat cd chgrp chmod chown chroot cmp cp df echo env exec exit export file find grep hbdbg hbldr hexdump http2_get id kill launch ln ls mkdir mknod mount mv notify pkg_install procstat ps pwd reptyr rm rmdir sfocreate sfoinfo sleep stat sum suspend sync sysctl touch umount".split()
|
||||
|
||||
|
||||
def load(path: Path):
|
||||
spec = importlib.util.spec_from_file_location("phase10z_contract", path)
|
||||
assert spec and spec.loader
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[spec.name] = module
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def require(value: bool, message: str) -> None:
|
||||
if not value:
|
||||
raise RuntimeError(message)
|
||||
|
||||
|
||||
def greeting(extra: str = "", eol: str = "\n") -> bytes:
|
||||
text = (
|
||||
"Welcome to shsrv.elf running on pid 123, compiled Jul 22 2026 at 12:34:56\n"
|
||||
"Model: synthetic-model\nS/N: SYNTHETIC-SERIAL-NEVER-RETAIN\n"
|
||||
"S/W: 9.60\nSoC temp: 40 C\nCPU temp: 41 C\n"
|
||||
"CPU freq: 3500 MHz\n" + extra)
|
||||
return text.replace("\n", eol).encode("ascii")
|
||||
|
||||
|
||||
def help_output() -> str:
|
||||
return "Builtin commands:\n" + "".join(
|
||||
f" {command} - synthetic\n" for command in CURRENT_COMMANDS) + "\n"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
root = args.root.resolve()
|
||||
sys.path.insert(0, str(root / "tools"))
|
||||
module = load(root / "tools/phase10z_passive_batch_contract.py")
|
||||
from phase10w_shsrv_client_policy import SessionPlan
|
||||
|
||||
def plan(window="T2_GREETING_AND_HELP", path=None):
|
||||
commands = ("help",) if window == "T2_GREETING_AND_HELP" else ("stat", "sum")
|
||||
return SessionPlan("synthetic_run", "not-retained.invalid", 2323,
|
||||
window, path, commands, 10,
|
||||
datetime(2030, 1, 1, tzinfo=timezone.utc))
|
||||
|
||||
def expect_failure(function):
|
||||
try:
|
||||
function()
|
||||
except module.PassiveContractError:
|
||||
return
|
||||
raise RuntimeError("invalid input was accepted")
|
||||
|
||||
cases = []
|
||||
|
||||
def case(name):
|
||||
def register(function):
|
||||
cases.append((name, function)); return function
|
||||
return register
|
||||
|
||||
@case("01 help batch is exact plain LF")
|
||||
def _(): require(module.build_passive_batch(plan()).payload == b"help\n", "help bytes differ")
|
||||
|
||||
@case("02 stat and sum form one ordered batch")
|
||||
def _(): require(module.build_passive_batch(plan("T3_ONE_EXACT_PATH", "/data/a.elf")).payload == b"stat /data/a.elf\nsum /data/a.elf\n", "path batch differs")
|
||||
|
||||
@case("03 maximum safe path reaches exact batch bound")
|
||||
def _():
|
||||
batch = module.build_passive_batch(plan("T3_ONE_EXACT_PATH", "/" + "a" * 511))
|
||||
require(len(batch.payload) == module.MAX_BATCH_BYTES == 1035, "batch bound differs")
|
||||
|
||||
@case("04 batch contains no CR NUL IAC or shell separators")
|
||||
def _():
|
||||
payload = module.build_passive_batch(plan("T3_ONE_EXACT_PATH", "/data/a.elf")).payload
|
||||
require(not any(value in payload for value in (0, 13, 255, ord(";"), ord("|"), ord("&"))), "forbidden byte present")
|
||||
|
||||
@case("05 target is not retained")
|
||||
def _(): require("target" not in asdict(module.build_passive_batch(plan())) and "not-retained" not in repr(module.build_passive_batch(plan())), "target retained")
|
||||
|
||||
@case("06 wrong command sequence is rejected")
|
||||
def _(): expect_failure(lambda: module.build_passive_batch(replace(plan(), commands=("stat",))))
|
||||
|
||||
@case("07 help path is rejected")
|
||||
def _(): expect_failure(lambda: module.build_passive_batch(replace(plan(), exact_literal_path="/x")))
|
||||
|
||||
@case("08 missing exact path is rejected")
|
||||
def _(): expect_failure(lambda: module.build_passive_batch(plan("T3_ONE_EXACT_PATH")))
|
||||
|
||||
@case("09 path injection and non ASCII are rejected")
|
||||
def _():
|
||||
for path in ("/x;id", "/x y", "/x\nhelp", "/café"):
|
||||
expect_failure(lambda path=path: module.build_passive_batch(plan("T3_ONE_EXACT_PATH", path)))
|
||||
|
||||
@case("10 incoming IAC fails closed")
|
||||
def _():
|
||||
value = module.PassiveResultAccumulator(module.build_passive_batch(plan()))
|
||||
expect_failure(lambda: value.feed_supplied_chunk(b"x\xffy"))
|
||||
|
||||
@case("10b forged direct batch is rejected")
|
||||
def _():
|
||||
forged = module.PassiveBatch("T3_ONE_EXACT_PATH", b"help\n", (), 2, 10)
|
||||
expect_failure(lambda: module.PassiveResultAccumulator(forged))
|
||||
|
||||
@case("10c resumed direct batch is rejected")
|
||||
def _():
|
||||
batch = module.build_passive_batch(plan())
|
||||
expect_failure(lambda: module.PassiveResultAccumulator(replace(batch, resume_allowed=True)))
|
||||
|
||||
@case("11 legacy LF help transcript seals at deadline")
|
||||
def _():
|
||||
value = module.PassiveResultAccumulator(module.build_passive_batch(plan()))
|
||||
value.feed_supplied_chunk(greeting(help_output()))
|
||||
require(value.seal_at_hard_deadline(True)["classification"] == "SOURCE_FAMILY_FINGERPRINT_ONLY", "legacy transcript failed")
|
||||
|
||||
@case("12 current CRLF help transcript seals at deadline")
|
||||
def _():
|
||||
value = module.PassiveResultAccumulator(module.build_passive_batch(plan()))
|
||||
value.feed_supplied_chunk(greeting(help_output(), "\r\n"))
|
||||
require(value.seal_at_hard_deadline(True)["passive_batch_contract"]["source_family_selected"] is False, "current transcript failed")
|
||||
|
||||
@case("13 exact path transcript requires stat and sum")
|
||||
def _():
|
||||
path = "/data/a.elf"; value = module.PassiveResultAccumulator(module.build_passive_batch(plan("T3_ONE_EXACT_PATH", path)))
|
||||
value.feed_supplied_chunk(greeting(f"filename: {path}\nsize: 123\nmtime: 456\n12345 {path}\n"))
|
||||
result = value.seal_at_hard_deadline(True)
|
||||
require(result["file_observations"][0]["size"] == 123 and result["exact_identity"] is False, "path result promoted or lost")
|
||||
|
||||
@case("14 partial help fails closed")
|
||||
def _():
|
||||
value = module.PassiveResultAccumulator(module.build_passive_batch(plan())); value.feed_supplied_chunk(greeting("Builtin commands:\n help - partial\n\n"))
|
||||
expect_failure(lambda: value.seal_at_hard_deadline(True))
|
||||
|
||||
@case("15 stat without sum fails closed")
|
||||
def _():
|
||||
path = "/data/a.elf"; value = module.PassiveResultAccumulator(module.build_passive_batch(plan("T3_ONE_EXACT_PATH", path)))
|
||||
value.feed_supplied_chunk(greeting(f"filename: {path}\nsize: 123\n")); expect_failure(lambda: value.seal_at_hard_deadline(True))
|
||||
|
||||
@case("16 sum without stat fails closed")
|
||||
def _():
|
||||
path = "/data/a.elf"; value = module.PassiveResultAccumulator(module.build_passive_batch(plan("T3_ONE_EXACT_PATH", path)))
|
||||
value.feed_supplied_chunk(greeting(f"12345 {path}\n")); expect_failure(lambda: value.seal_at_hard_deadline(True))
|
||||
|
||||
@case("16b extra fields cannot mask missing size")
|
||||
def _():
|
||||
path = "/data/a.elf"; value = module.PassiveResultAccumulator(module.build_passive_batch(plan("T3_ONE_EXACT_PATH", path)))
|
||||
observation = {"path": path, "metadata_seen": True, "weak_checksum": "12345", "weak_checksum_algorithm": "BSD_ROTATE_16", "cryptographic_checksum": False, "proves_exact_binary": False, "extra": 1}
|
||||
expect_failure(lambda: value._validate_complete_result({"classification": "WEAK_FILE_CORRELATION_ONLY", "file_observations": [observation]}))
|
||||
|
||||
@case("17 prompt is not an early completion event")
|
||||
def _():
|
||||
value = module.PassiveResultAccumulator(module.build_passive_batch(plan())); value.feed_supplied_chunk(b"/$ ")
|
||||
require(value.state == "RECEIVING" and not hasattr(value, "seal_at_prompt"), "prompt completion exists")
|
||||
|
||||
@case("18 absent deadline event fails closed")
|
||||
def _():
|
||||
value = module.PassiveResultAccumulator(module.build_passive_batch(plan())); value.feed_supplied_chunk(greeting(help_output()))
|
||||
expect_failure(lambda: value.seal_at_hard_deadline(False))
|
||||
|
||||
@case("19 seal is one shot")
|
||||
def _():
|
||||
value = module.PassiveResultAccumulator(module.build_passive_batch(plan())); value.feed_supplied_chunk(greeting(help_output())); value.seal_at_hard_deadline(True)
|
||||
expect_failure(lambda: value.seal_at_hard_deadline(True))
|
||||
|
||||
@case("20 feed after seal fails")
|
||||
def _():
|
||||
value = module.PassiveResultAccumulator(module.build_passive_batch(plan())); value.feed_supplied_chunk(greeting(help_output())); value.seal_at_hard_deadline(True)
|
||||
expect_failure(lambda: value.feed_supplied_chunk(b"later"))
|
||||
|
||||
@case("21 abort cannot produce a result")
|
||||
def _():
|
||||
value = module.PassiveResultAccumulator(module.build_passive_batch(plan())); value.abort()
|
||||
expect_failure(lambda: value.seal_at_hard_deadline(True))
|
||||
|
||||
@case("22 no EOF prompt transport CLI or exact proof API exists")
|
||||
def _():
|
||||
names = set(dir(module.PassiveResultAccumulator)) | set(dir(module))
|
||||
require(not ({"seal_at_eof", "seal_at_prompt", "connect", "main"} & names), "forbidden API exists")
|
||||
|
||||
failures = []
|
||||
for name, function in cases:
|
||||
try:
|
||||
function(); print(f"PASS {name}")
|
||||
except Exception as error: # noqa: BLE001 - synthetic harness
|
||||
failures.append(f"{name}: {error}"); print(f"FAIL {name}: {error}")
|
||||
if failures:
|
||||
return 1
|
||||
print(f"Phase-1.0Z passive-batch tests passed: {len(cases)}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,121 @@
|
||||
/* SPDX-License-Identifier: GPL-3.0-or-later */
|
||||
#include "probe.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct fake_probe {
|
||||
size_t resolve_calls;
|
||||
size_t close_calls;
|
||||
size_t log_calls;
|
||||
int fail_open;
|
||||
int fail_lookup;
|
||||
int fail_close;
|
||||
int saw_address_text;
|
||||
} fake_probe;
|
||||
|
||||
static int failures;
|
||||
|
||||
#define CHECK(expression) \
|
||||
do { \
|
||||
if (!(expression)) { \
|
||||
(void)fprintf(stderr, "%s:%d: check failed: %s\n", __FILE__, \
|
||||
__LINE__, #expression); \
|
||||
failures += 1; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static int fake_open(void *userdata, const char *module_name) {
|
||||
fake_probe *probe = (fake_probe *)userdata;
|
||||
CHECK(strcmp(module_name, "libSceGnmDriver.sprx") == 0);
|
||||
return probe->fail_open != 0 ? -1 : 0;
|
||||
}
|
||||
|
||||
static int fake_resolve(void *userdata, const char *symbol_name, int *present) {
|
||||
fake_probe *probe = (fake_probe *)userdata;
|
||||
|
||||
probe->resolve_calls += 1u;
|
||||
if (probe->fail_lookup != 0) {
|
||||
return -1;
|
||||
}
|
||||
*present = strcmp(symbol_name, "sceGnmSubmitCommandBuffers") != 0 ? 1 : 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fake_close(void *userdata) {
|
||||
fake_probe *probe = (fake_probe *)userdata;
|
||||
probe->close_calls += 1u;
|
||||
return probe->fail_close != 0 ? -1 : 0;
|
||||
}
|
||||
|
||||
static void fake_log(void *userdata, const char *line) {
|
||||
fake_probe *probe = (fake_probe *)userdata;
|
||||
probe->log_calls += 1u;
|
||||
if (strstr(line, "0x") != NULL) {
|
||||
probe->saw_address_text = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static chimera_gfx_probe_ops make_ops(fake_probe *probe) {
|
||||
chimera_gfx_probe_ops ops;
|
||||
ops.userdata = probe;
|
||||
ops.open_module = fake_open;
|
||||
ops.resolve_symbol = fake_resolve;
|
||||
ops.close_module = fake_close;
|
||||
ops.log_line = fake_log;
|
||||
return ops;
|
||||
}
|
||||
|
||||
static void test_partial_success(void) {
|
||||
fake_probe probe = {0};
|
||||
chimera_gfx_probe_ops ops = make_ops(&probe);
|
||||
chimera_gfx_probe_report report;
|
||||
|
||||
CHECK(chimera_gfx_ps5_probe_symbols(&ops, &report) ==
|
||||
CHIMERA_GFX_STATUS_OK);
|
||||
CHECK(report.symbol_count == 21u);
|
||||
CHECK(report.resolved_count == 20u);
|
||||
CHECK(report.module_opened == 0);
|
||||
CHECK(probe.resolve_calls == report.symbol_count);
|
||||
CHECK(probe.close_calls == 1u);
|
||||
CHECK(probe.log_calls == report.symbol_count + 3u);
|
||||
CHECK(probe.saw_address_text == 0);
|
||||
}
|
||||
|
||||
static void test_fail_closed_paths(void) {
|
||||
fake_probe probe = {0};
|
||||
chimera_gfx_probe_ops ops = make_ops(&probe);
|
||||
chimera_gfx_probe_report report;
|
||||
|
||||
probe.fail_open = 1;
|
||||
CHECK(chimera_gfx_ps5_probe_symbols(&ops, &report) ==
|
||||
CHIMERA_GFX_STATUS_BACKEND_UNAVAILABLE);
|
||||
CHECK(probe.resolve_calls == 0u);
|
||||
CHECK(probe.close_calls == 0u);
|
||||
|
||||
probe = (fake_probe){0};
|
||||
probe.fail_lookup = 1;
|
||||
ops = make_ops(&probe);
|
||||
CHECK(chimera_gfx_ps5_probe_symbols(&ops, &report) ==
|
||||
CHIMERA_GFX_STATUS_INTERNAL_ERROR);
|
||||
CHECK(probe.resolve_calls == 1u);
|
||||
CHECK(probe.close_calls == 1u);
|
||||
|
||||
probe = (fake_probe){0};
|
||||
probe.fail_close = 1;
|
||||
ops = make_ops(&probe);
|
||||
CHECK(chimera_gfx_ps5_probe_symbols(&ops, &report) ==
|
||||
CHIMERA_GFX_STATUS_INTERNAL_ERROR);
|
||||
CHECK(report.module_opened == 1);
|
||||
CHECK(probe.resolve_calls == report.symbol_count);
|
||||
CHECK(probe.close_calls == 1u);
|
||||
|
||||
CHECK(chimera_gfx_ps5_probe_symbols(NULL, &report) ==
|
||||
CHIMERA_GFX_STATUS_INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_partial_success();
|
||||
test_fail_closed_paths();
|
||||
return failures == 0 ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Twenty-five host-only guardrails for Phase 1.0A."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
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(
|
||||
"phase10a_validator", root / "tools/validate_retroarch_phase10a.py"
|
||||
)
|
||||
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 artifact(**changes: object) -> dict[str, object]:
|
||||
value: dict[str, object] = {
|
||||
"name": "retroarch_ps5_headless.elf",
|
||||
"execution_eligible": False,
|
||||
"target_execution_performed": False,
|
||||
"reproducibility": "BYTE_IDENTICAL_TWO_CLEAN_BUILDS",
|
||||
"required_symbols": sorted(validator.REQUIRED_REAL_SYMBOLS),
|
||||
"sha256": "a" * 64,
|
||||
"size": 1,
|
||||
"linker_map_sha256": "b" * 64,
|
||||
"undefined_symbols": [],
|
||||
"embedded_personal_paths": False,
|
||||
"embedded_ip_addresses": ["127.0.0.1"],
|
||||
"embedded_device_ip_addresses": False,
|
||||
"forbidden_markers_found": [],
|
||||
}
|
||||
value.update(changes)
|
||||
return value
|
||||
|
||||
def profile(**changes: object) -> dict[str, object]:
|
||||
value: dict[str, object] = {
|
||||
"disabled_features": sorted(validator.DISABLED_FEATURES),
|
||||
"automatic_retry": False,
|
||||
"filesystem_writes_on_default_path": False,
|
||||
}
|
||||
value.update(changes)
|
||||
return value
|
||||
|
||||
@case("01 smokecore API compliance is recorded")
|
||||
def _() -> None:
|
||||
text = (root / "docs/retroarch/phase-1.0a-driver-status.md").read_text()
|
||||
require("libretro" in text.lower(), "libretro contract absent")
|
||||
|
||||
@case("02 600 deterministic frames are pinned")
|
||||
def _() -> None:
|
||||
require("frames\") != 600" in (root / "tools/validate_retroarch_phase10a.py").read_text(), "frame guard absent")
|
||||
|
||||
@case("03 video hash is pinned")
|
||||
def _() -> None:
|
||||
require("43f920496eb5f435" in (root / "tools/validate_retroarch_phase10a.py").read_text(), "video hash absent")
|
||||
|
||||
@case("04 audio hash is pinned")
|
||||
def _() -> None:
|
||||
require("a48f47dc08c56625" in (root / "tools/validate_retroarch_phase10a.py").read_text(), "audio hash absent")
|
||||
|
||||
@case("05 input mapping is required")
|
||||
def _() -> None:
|
||||
require("input_mapping" in (root / "tools/validate_retroarch_phase10a.py").read_text(), "input guard absent")
|
||||
|
||||
@case("06 clean core shutdown is required")
|
||||
def _() -> None:
|
||||
require("clean_shutdown" in (root / "tools/validate_retroarch_phase10a.py").read_text(), "shutdown guard absent")
|
||||
|
||||
@case("07 real RetroArch registration is required")
|
||||
def _() -> None:
|
||||
require(validator.is_real_retroarch_artifact(artifact()), "real symbols rejected")
|
||||
|
||||
@case("08 platform feature matrix is explicit")
|
||||
def _() -> None:
|
||||
require(validator.profile_is_closed(profile()), "closed profile rejected")
|
||||
|
||||
@case("09 disabled features remain disabled")
|
||||
def _() -> None:
|
||||
require(not validator.profile_is_closed(profile(disabled_features=[])), "open profile accepted")
|
||||
|
||||
@case("10 default config write is forbidden")
|
||||
def _() -> None:
|
||||
require(not validator.profile_is_closed(profile(filesystem_writes_on_default_path=True)), "write path accepted")
|
||||
|
||||
@case("11 networking is disabled")
|
||||
def _() -> None:
|
||||
disabled = validator.DISABLED_FEATURES - {"networking"}
|
||||
require(not validator.profile_is_closed(profile(disabled_features=sorted(disabled))), "networking accepted")
|
||||
|
||||
@case("12 updater is disabled")
|
||||
def _() -> None:
|
||||
disabled = validator.DISABLED_FEATURES - {"online_updater"}
|
||||
require(not validator.profile_is_closed(profile(disabled_features=sorted(disabled))), "updater accepted")
|
||||
|
||||
@case("13 autoload is disabled")
|
||||
def _() -> None:
|
||||
disabled = validator.DISABLED_FEATURES - {"autoload"}
|
||||
require(not validator.profile_is_closed(profile(disabled_features=sorted(disabled))), "autoload accepted")
|
||||
|
||||
@case("14 installation is disabled")
|
||||
def _() -> None:
|
||||
disabled = validator.DISABLED_FEATURES - {"installation"}
|
||||
require(not validator.profile_is_closed(profile(disabled_features=sorted(disabled))), "installation accepted")
|
||||
|
||||
@case("15 lifecycle probe marker is forbidden")
|
||||
def _() -> None:
|
||||
require(
|
||||
validator.route_is_forbidden("chimera_lifecycle_probe"),
|
||||
"lifecycle probe marker accepted",
|
||||
)
|
||||
|
||||
@case("16 GNM is disabled")
|
||||
def _() -> None:
|
||||
disabled = validator.DISABLED_FEATURES - {"gnm"}
|
||||
require(not validator.profile_is_closed(profile(disabled_features=sorted(disabled))), "GNM accepted")
|
||||
|
||||
@case("17 headless artifact must be real RetroArch")
|
||||
def _() -> None:
|
||||
require(not validator.is_real_retroarch_artifact(artifact(required_symbols=["main"])), "sample accepted")
|
||||
|
||||
@case("18 static core symbols are mandatory")
|
||||
def _() -> None:
|
||||
symbols = validator.REQUIRED_REAL_SYMBOLS - {"retro_run"}
|
||||
require(not validator.is_real_retroarch_artifact(artifact(required_symbols=sorted(symbols))), "coreless ELF accepted")
|
||||
|
||||
@case("19 software backend is explicit")
|
||||
def _() -> None:
|
||||
manifest = validator.load_json(root / "manifests/retroarch/phase-1.0a-build.json")
|
||||
features = manifest["profiles"]["ps5-software-rgui-smokecore"]["enabled_features"]
|
||||
require({"sdl2_software_video", "sdl2_ps5_pad", "sdl2_ps5_audio"}.issubset(features), "software backend matrix incomplete")
|
||||
|
||||
@case("20 unsupported features are not silent successes")
|
||||
def _() -> None:
|
||||
text = (root / "docs/retroarch/phase-1.0a-driver-status.md").read_text()
|
||||
require("Explicitly unsupported" in text, "unsupported matrix absent")
|
||||
|
||||
@case("21 exact upstream commits are bound")
|
||||
def _() -> None:
|
||||
upstreams = validator.load_json(root / "manifests/retroarch/upstreams.json")
|
||||
require(upstreams["sources"]["retroarch"]["commit"] == validator.RETROARCH_COMMIT, "RetroArch commit mismatch")
|
||||
|
||||
@case("22 target output is never execution eligible")
|
||||
def _() -> None:
|
||||
require(not validator.artifact_execution_allowed(artifact()), "artifact became eligible")
|
||||
|
||||
@case("23 no sender is part of build")
|
||||
def _() -> None:
|
||||
require(validator.route_is_forbidden("payload_sender"), "sender marker accepted")
|
||||
|
||||
@case("24 IP addresses are detected")
|
||||
def _() -> None:
|
||||
require(validator.has_ip_address(["connect 192.0.2.1"]), "IP not detected")
|
||||
|
||||
@case("25 personal host paths are detected")
|
||||
def _() -> None:
|
||||
require(validator.has_personal_path([r"C:\Users\Example\port"]), "host path not detected")
|
||||
|
||||
failures: list[str] = []
|
||||
for name, function in cases:
|
||||
try:
|
||||
function()
|
||||
except Exception as error: # noqa: BLE001 - isolated test reporting
|
||||
failures.append(f"{name}: {error}")
|
||||
if len(cases) != 25:
|
||||
failures.append(f"expected 25 cases, found {len(cases)}")
|
||||
if failures:
|
||||
print("\n".join(failures), file=sys.stderr)
|
||||
return 1
|
||||
print("25 Phase 1.0A guardrails passed")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Mutation guardrails for Phase-1.0AA offline fake integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from copy import deepcopy
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
|
||||
def load(path: Path):
|
||||
spec = importlib.util.spec_from_file_location("phase10aa_validator", path)
|
||||
assert spec and spec.loader
|
||||
module = importlib.util.module_from_spec(spec); sys.modules[spec.name] = module
|
||||
spec.loader.exec_module(module); return module
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(); parser.add_argument("--root", type=Path, required=True)
|
||||
args = parser.parse_args(); root = args.root.resolve()
|
||||
validator = load(root / "tools/validate_retroarch_phase10aa.py")
|
||||
original = validator.load_json(root / "manifests/retroarch/phase-1.0aa-offline-fake-adapter.json")
|
||||
cases = []
|
||||
def mutation(name, function): cases.append((name, function))
|
||||
|
||||
mutation("01 activation rejected", lambda r: r["activation"].update(active=True))
|
||||
mutation("02 target rejected", lambda r: r["activation"].update(target_address="device.invalid"))
|
||||
mutation("03 connection authority rejected", lambda r: r["authorizations"].update(ps5_connection_authorized=True))
|
||||
mutation("04 request authority rejected", lambda r: r["authorizations"].update(device_request_authorized=True))
|
||||
mutation("05 result authority rejected", lambda r: r["authorizations"].update(result_receive_authorized=True))
|
||||
mutation("06 retry rejected", lambda r: r["authorizations"].update(automatic_retry=True))
|
||||
mutation("07 reconnect rejected", lambda r: r["authorizations"].update(reconnect_authorized=True))
|
||||
mutation("08 resume rejected", lambda r: r["authorizations"].update(resume_authorized=True))
|
||||
mutation("09 adapter subclass rejected", lambda r: r["fake_boundary"].update(adapter_subclasses_allowed=True))
|
||||
mutation("10 live protocol rejected", lambda r: r["fake_boundary"].update(live_adapter_protocol_present=True))
|
||||
mutation("11 network import rejected", lambda r: r["fake_boundary"].update(network_import_present=True))
|
||||
mutation("12 real clock rejected", lambda r: r["fake_boundary"].update(real_clock_present=True))
|
||||
mutation("13 second send rejected", lambda r: r["ordering_contract"].update(complete_batch_send_count=2))
|
||||
mutation("14 EOF completion rejected", lambda r: r["ordering_contract"].update(remote_eof="COMPLETE"))
|
||||
mutation("15 early deadline rejected", lambda r: r["ordering_contract"].update(early_deadline="COMPLETE"))
|
||||
mutation("16 target retention rejected", lambda r: r["evidence_contract"].update(target_retained=True))
|
||||
mutation("17 physical erasure proof rejected", lambda r: r["evidence_contract"].update(physical_memory_erasure_proven=True))
|
||||
mutation("18 live adapter decision rejected", lambda r: r["decision"].update(live_adapter_allowed=True))
|
||||
mutation("19 device action rejected", lambda r: r["performed_actions"].update(ps5_connected=True))
|
||||
mutation("20 hardware proof rejected", lambda r: r["tests"].update(hardware_claim_from_host_test=True))
|
||||
|
||||
failures = []
|
||||
for name, mutate in cases:
|
||||
value = deepcopy(original); mutate(value)
|
||||
if validator.validate_record(value): print(f"PASS {name}")
|
||||
else: failures.append(name); print(f"FAIL {name}: mutation accepted")
|
||||
if failures: return 1
|
||||
print(f"Phase-1.0AA guardrails passed: {len(cases)}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Mutation guardrails for Phase-1.0AB feasibility evidence."""
|
||||
|
||||
from __future__ import annotations
|
||||
import argparse
|
||||
from copy import deepcopy
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
def load(path):
|
||||
spec=importlib.util.spec_from_file_location("phase10ab_validator",path);assert spec and spec.loader
|
||||
module=importlib.util.module_from_spec(spec);sys.modules[spec.name]=module;spec.loader.exec_module(module);return module
|
||||
|
||||
def main():
|
||||
parser=argparse.ArgumentParser();parser.add_argument("--root",type=Path,required=True);args=parser.parse_args();root=args.root.resolve()
|
||||
validator=load(root / "tools/validate_retroarch_phase10ab.py");original=validator.load_json(root / "manifests/retroarch/phase-1.0ab-live-adapter-feasibility.json")
|
||||
cases=[]
|
||||
def mutation(name,function): cases.append((name,function))
|
||||
mutation("01 activation rejected",lambda r:r["activation"].update(active=True))
|
||||
mutation("02 target rejected",lambda r:r["activation"].update(target_address="device.invalid"))
|
||||
mutation("03 connection authority rejected",lambda r:r["authorizations"].update(ps5_connection_authorized=True))
|
||||
mutation("04 request authority rejected",lambda r:r["authorizations"].update(device_request_authorized=True))
|
||||
mutation("05 result authority rejected",lambda r:r["authorizations"].update(result_receive_authorized=True))
|
||||
mutation("06 retry rejected",lambda r:r["authorizations"].update(automatic_retry=True))
|
||||
mutation("07 reconnect rejected",lambda r:r["authorizations"].update(reconnect_authorized=True))
|
||||
mutation("08 resume rejected",lambda r:r["authorizations"].update(resume_authorized=True))
|
||||
mutation("09 runtime identity change rejected",lambda r:r["local_runtime"].update(python_version="latest"))
|
||||
mutation("10 connect promotion rejected",lambda r:r["feasibility"].update(pending_connect="PROVEN"))
|
||||
mutation("11 deadline promotion rejected",lambda r:r["feasibility"].update(hard_wall_clock_deadline="PROVEN"))
|
||||
mutation("12 remote cleanup promotion rejected",lambda r:r["feasibility"].update(remote_shell_cleanup="PROVEN"))
|
||||
mutation("13 network import rejected",lambda r:r["trace_model"].update(network_import_present=True))
|
||||
mutation("14 real clock rejected",lambda r:r["trace_model"].update(real_clock_present=True))
|
||||
mutation("15 live implementation stop required",lambda r:r["hard_stops"].update(live_adapter_implementation=False))
|
||||
mutation("16 DNS stop required",lambda r:r["hard_stops"].update(dns=False))
|
||||
mutation("17 live adapter decision rejected",lambda r:r["decision"].update(live_adapter_allowed=True))
|
||||
mutation("18 overall promotion rejected",lambda r:r["decision"].update(overall="PROVEN"))
|
||||
mutation("19 device action rejected",lambda r:r["performed_actions"].update(ps5_connected=True))
|
||||
mutation("20 hardware proof rejected",lambda r:r["tests"].update(hardware_claim_from_host_test=True))
|
||||
failures=[]
|
||||
for name,mutate in cases:
|
||||
value=deepcopy(original);mutate(value)
|
||||
if validator.validate_record(value):print(f"PASS {name}")
|
||||
else:failures.append(name);print(f"FAIL {name}: mutation accepted")
|
||||
if failures:return 1
|
||||
print(f"Phase-1.0AB guardrails passed: {len(cases)}");return 0
|
||||
|
||||
if __name__ == "__main__":raise SystemExit(main())
|
||||
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Mutation guardrails for Phase-1.0AC dormant-adapter evidence."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from copy import deepcopy
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
|
||||
def load(path: Path):
|
||||
spec = importlib.util.spec_from_file_location("phase10ac_validator", path)
|
||||
assert spec and spec.loader
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[spec.name] = module
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
root = args.root.resolve()
|
||||
validator = load(root / "tools/validate_retroarch_phase10ac.py")
|
||||
original = validator.load_json(
|
||||
root / "manifests/retroarch/phase-1.0ac-dormant-adapter.json")
|
||||
cases = []
|
||||
def mutation(name, function): cases.append((name, function))
|
||||
|
||||
mutation("01 activation rejected", lambda r: r["activation"].update(active=True))
|
||||
mutation("02 target rejected", lambda r: r["activation"].update(target_address="device.invalid"))
|
||||
mutation("03 adapter activation hash rejected", lambda r: r["activation"].update(adapter_sha256="00" * 32))
|
||||
mutation("04 connection authority rejected", lambda r: r["authorizations"].update(ps5_connection_authorized=True))
|
||||
mutation("05 request authority rejected", lambda r: r["authorizations"].update(device_request_authorized=True))
|
||||
mutation("06 result authority rejected", lambda r: r["authorizations"].update(result_receive_authorized=True))
|
||||
mutation("07 retry rejected", lambda r: r["authorizations"].update(automatic_retry=True))
|
||||
mutation("08 reconnect rejected", lambda r: r["authorizations"].update(reconnect_authorized=True))
|
||||
mutation("09 live protocol rejected", lambda r: r["adapter"].update(live_adapter_protocol_present=True))
|
||||
mutation("10 network import rejected", lambda r: r["adapter"].update(network_import_present=True))
|
||||
mutation("11 real clock rejected", lambda r: r["adapter"].update(real_clock_present=True))
|
||||
mutation("12 address rejected", lambda r: r["adapter"].update(address_present=True))
|
||||
mutation("13 receipt requirement preserved", lambda r: r["adapter"].update(precommitted_receipt_required=False))
|
||||
mutation("14 EOF rejection preserved", lambda r: r["lifecycle"].update(remote_eof_rejected=False))
|
||||
mutation("15 deadline race rule preserved", lambda r: r["lifecycle"].update(deadline_wins_readiness_race=False))
|
||||
mutation("16 remote cleanup not promoted", lambda r: r["lifecycle"].update(remote_cleanup_proven=True))
|
||||
mutation("17 live implementation stop required", lambda r: r["hard_stops"].update(live_adapter_implementation=False))
|
||||
mutation("18 live adapter decision rejected", lambda r: r["decision"].update(live_adapter_allowed=True))
|
||||
mutation("19 performed device action rejected", lambda r: r["performed_actions"].update(ps5_connected=True))
|
||||
mutation("20 hardware proof rejected", lambda r: r["tests"].update(hardware_claim_from_host_test=True))
|
||||
|
||||
failures = []
|
||||
for name, mutate in cases:
|
||||
value = deepcopy(original); mutate(value)
|
||||
if validator.validate_record(value):
|
||||
print(f"PASS {name}")
|
||||
else:
|
||||
failures.append(name); print(f"FAIL {name}: mutation accepted")
|
||||
if failures:
|
||||
return 1
|
||||
print(f"Phase-1.0AC guardrails passed: {len(cases)}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Guardrails for the tracked Phase-1.0AD boundary."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
|
||||
PARSER = argparse.ArgumentParser()
|
||||
PARSER.add_argument("--root", type=Path, required=True)
|
||||
ROOT = PARSER.parse_args().root
|
||||
|
||||
|
||||
class Phase10ADGuardrails(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.manifest = json.loads((ROOT / "manifests/retroarch/phase-1.0ad-inactive-activation.json").read_text(encoding="utf-8"))
|
||||
|
||||
def test_activation_is_empty(self) -> None:
|
||||
activation = self.manifest["activation"]
|
||||
self.assertFalse(activation["active"])
|
||||
for name in ("target_address", "target_port", "run_id", "not_before",
|
||||
"expires_at", "launcher_sha256", "payload_sha256",
|
||||
"approval_sha256"):
|
||||
self.assertIsNone(activation[name])
|
||||
|
||||
def test_no_authority_exists(self) -> None:
|
||||
self.assertFalse(any(self.manifest["authorizations"].values()))
|
||||
self.assertFalse(self.manifest["decision"]["device_action_allowed"])
|
||||
self.assertFalse(self.manifest["decision"]["bigapp_launcher_implementation_allowed"])
|
||||
|
||||
def test_contract_has_no_live_import(self) -> None:
|
||||
source = (ROOT / "tools/phase10ad_activation_contract.py").read_text(encoding="utf-8")
|
||||
for statement in ("import socket", "import selectors", "import requests",
|
||||
"from socket", "subprocess"):
|
||||
self.assertNotIn(statement, source)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Tracked guardrails for Phase-1.0AE."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
|
||||
PARSER = argparse.ArgumentParser()
|
||||
PARSER.add_argument("--root", type=Path, required=True)
|
||||
ROOT = PARSER.parse_args().root
|
||||
|
||||
|
||||
class Phase10AEGuardrails(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.data = json.loads((ROOT / "manifests/retroarch/phase-1.0ae-launcher-architecture.json").read_text(encoding="utf-8"))
|
||||
|
||||
def test_no_authority_or_target_implementation(self) -> None:
|
||||
self.assertFalse(any(self.data["authorizations"].values()))
|
||||
self.assertFalse(self.data["decision"]["target_implementation_allowed"])
|
||||
self.assertFalse(self.data["decision"]["device_action_allowed"])
|
||||
|
||||
def test_persistent_and_destructive_behavior_is_forbidden(self) -> None:
|
||||
policy = self.data["mandatory_policy"]
|
||||
for key in ("kill_existing_bigapp", "automatic_retry", "fallback_title",
|
||||
"persistent_write", "system_remount", "installation", "autoload"):
|
||||
self.assertFalse(policy[key])
|
||||
|
||||
def test_hard_blockers_remain_explicit(self) -> None:
|
||||
self.assertGreaterEqual(len(self.data["hard_blockers"]), 8)
|
||||
self.assertFalse(self.data["lineage_decision"]["root_cause_proven"])
|
||||
self.assertFalse(self.data["lineage_decision"]["videoout_ownership_proven"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Manifest guardrails for Phase-1.0AF."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
|
||||
PARSER = argparse.ArgumentParser()
|
||||
PARSER.add_argument("--root", type=Path, required=True)
|
||||
ROOT = PARSER.parse_args().root
|
||||
|
||||
|
||||
class Phase10AFGuardrails(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.data = json.loads((ROOT / "manifests/retroarch/phase-1.0af-bigapp-lifecycle-model.json").read_text(encoding="utf-8"))
|
||||
|
||||
def test_no_authority_or_target_claim(self) -> None:
|
||||
self.assertFalse(any(self.data["authorizations"].values()))
|
||||
self.assertFalse(self.data["decision"]["target_implementation_allowed"])
|
||||
self.assertFalse(self.data["decision"]["device_action_allowed"])
|
||||
|
||||
def test_model_is_closed(self) -> None:
|
||||
model = self.data["model"]
|
||||
for key in ("target_code_present", "socket_present", "process_api_present",
|
||||
"syscall_present", "real_clock_present", "filesystem_output_present",
|
||||
"existing_bigapp_killed"):
|
||||
self.assertFalse(model[key])
|
||||
self.assertTrue(model["require_no_existing_bigapp"])
|
||||
self.assertTrue(model["restore_before_child_termination"])
|
||||
|
||||
def test_runtime_blockers_remain(self) -> None:
|
||||
self.assertGreaterEqual(len(self.data["remaining_blockers"]), 7)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Tracked guardrails for Phase-1.0AG."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
|
||||
PARSER = argparse.ArgumentParser()
|
||||
PARSER.add_argument("--root", type=Path, required=True)
|
||||
ROOT = PARSER.parse_args().root
|
||||
|
||||
|
||||
class Phase10AGGuardrails(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.data = json.loads((ROOT / "manifests/retroarch/phase-1.0ag-bounded-elf-contract.json").read_text(encoding="utf-8"))
|
||||
|
||||
def test_parser_does_not_admit_missing_artifact(self) -> None:
|
||||
reference = self.data["historical_reference"]
|
||||
self.assertFalse(reference["bytes_present_in_scanned_workspace"])
|
||||
self.assertFalse(reference["validated_by_phase10ag"])
|
||||
self.assertFalse(self.data["decision"]["historical_artifact_admitted"])
|
||||
|
||||
def test_permissions_and_bounds_fail_closed(self) -> None:
|
||||
admission = self.data["admission"]
|
||||
self.assertFalse(admission["writable_executable_load_allowed"])
|
||||
self.assertFalse(admission["interpreter_allowed"])
|
||||
self.assertFalse(admission["overlapping_load_ranges_allowed"])
|
||||
self.assertFalse(admission["file_interface_present"])
|
||||
self.assertFalse(admission["execution_interface_present"])
|
||||
|
||||
def test_no_authority_or_target_claim(self) -> None:
|
||||
self.assertFalse(any(self.data["authorizations"].values()))
|
||||
self.assertFalse(self.data["decision"]["target_implementation_allowed"])
|
||||
self.assertFalse(self.data["decision"]["device_action_allowed"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Tracked guardrails for Phase-1.0AH."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
|
||||
PARSER = argparse.ArgumentParser()
|
||||
PARSER.add_argument("--root", type=Path, required=True)
|
||||
ROOT = PARSER.parse_args().root
|
||||
|
||||
|
||||
class Phase10AHGuardrails(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.data = json.loads((ROOT / "manifests/retroarch/phase-1.0ah-dynamic-contract.json").read_text(encoding="utf-8"))
|
||||
|
||||
def test_relocation_split_is_exact(self) -> None:
|
||||
contract = self.data["contract"]
|
||||
self.assertEqual(contract["loader_applied_relocation_types"],
|
||||
["R_X86_64_RELATIVE"])
|
||||
self.assertEqual(contract["crt_applied_relocation_types"],
|
||||
["R_X86_64_GLOB_DAT"])
|
||||
self.assertFalse(contract["unknown_relocation_types_allowed"])
|
||||
|
||||
def test_historical_metadata_is_not_byte_evidence(self) -> None:
|
||||
reference = self.data["historical_phase10m_reference"]
|
||||
self.assertFalse(reference["exact_bytes_present"])
|
||||
self.assertFalse(reference["validated_by_phase10ah"])
|
||||
|
||||
def test_no_mapping_or_authority(self) -> None:
|
||||
self.assertFalse(any(self.data["authorizations"].values()))
|
||||
self.assertFalse(self.data["decision"]["target_mapping_allowed"])
|
||||
self.assertFalse(self.data["decision"]["target_implementation_allowed"])
|
||||
self.assertFalse(self.data["decision"]["device_action_allowed"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Tracked guardrails for Phase-1.0AI."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
|
||||
PARSER = argparse.ArgumentParser()
|
||||
PARSER.add_argument("--root", type=Path, required=True)
|
||||
ROOT = PARSER.parse_args().root
|
||||
|
||||
|
||||
class Phase10AIGuardrails(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.data = json.loads((ROOT / "manifests/retroarch/phase-1.0ai-mapping-model.json").read_text(encoding="utf-8"))
|
||||
|
||||
def test_failure_never_retains_partial_mapping(self) -> None:
|
||||
transaction = self.data["transaction"]
|
||||
self.assertTrue(transaction["failure_unmaps_entire_new_child_region"])
|
||||
self.assertTrue(transaction["failure_releases_mirror"])
|
||||
self.assertFalse(transaction["partial_mapping_retained_on_failure"])
|
||||
|
||||
def test_model_has_no_mapping_capability(self) -> None:
|
||||
transaction = self.data["transaction"]
|
||||
self.assertFalse(transaction["target_memory_interface_present"])
|
||||
self.assertFalse(transaction["host_memory_mapping_present"])
|
||||
self.assertFalse(transaction["process_interface_present"])
|
||||
|
||||
def test_no_authority_or_target_claim(self) -> None:
|
||||
self.assertFalse(any(self.data["authorizations"].values()))
|
||||
self.assertFalse(self.data["decision"]["target_mapping_allowed"])
|
||||
self.assertFalse(self.data["decision"]["target_implementation_allowed"])
|
||||
self.assertFalse(self.data["decision"]["device_action_allowed"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Tracked guardrails for Phase-1.0AJ."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
|
||||
PARSER = argparse.ArgumentParser()
|
||||
PARSER.add_argument("--root", type=Path, required=True)
|
||||
ROOT = PARSER.parse_args().root
|
||||
|
||||
|
||||
class Phase10AJGuardrails(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.data = json.loads((ROOT / "manifests/retroarch/phase-1.0aj-primitive-audit.json").read_text(encoding="utf-8"))
|
||||
|
||||
def test_unhardened_loader_is_rejected(self) -> None:
|
||||
matrix = self.data["primitive_matrix"]
|
||||
self.assertEqual(matrix["mirror_and_jit_alias_transaction"],
|
||||
"SHSRV_V07_UNHARDENED_NOT_REUSABLE")
|
||||
self.assertEqual(matrix["complete_bigapp_composition"], "ABSENT")
|
||||
|
||||
def test_gaps_remain_explicit(self) -> None:
|
||||
blockers = self.data["remaining_blockers"]
|
||||
self.assertIn("JIT_FD_ALIAS_AND_HOST_MIRROR_ACQUISITION_NOT_IN_CENTRAL_CLEANUP_STATE", blockers)
|
||||
self.assertIn("CLEANUP_FAILURE_OWNERSHIP_AND_TERMINATION_POLICY_NEEDS_COMPOSITION_MODEL", blockers)
|
||||
|
||||
def test_no_authority_or_target_implementation(self) -> None:
|
||||
self.assertFalse(any(self.data["authorizations"].values()))
|
||||
self.assertFalse(self.data["decision"]["copy_shsrv_loader_code_allowed"])
|
||||
self.assertFalse(self.data["decision"]["target_implementation_allowed"])
|
||||
self.assertFalse(self.data["decision"]["device_action_allowed"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Tracked guardrails for Phase-1.0AK."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
|
||||
PARSER = argparse.ArgumentParser()
|
||||
PARSER.add_argument("--root", type=Path, required=True)
|
||||
ROOT = PARSER.parse_args().root
|
||||
|
||||
|
||||
class Phase10AKGuardrails(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.data = json.loads((ROOT / "manifests/retroarch/phase-1.0ak-hybrid-composition.json").read_text(encoding="utf-8"))
|
||||
|
||||
def test_every_temporary_resource_has_explicit_ownership(self) -> None:
|
||||
ownership = self.data["ownership"]
|
||||
for key in ("host_mirror", "jit_master_descriptors",
|
||||
"jit_alias_descriptors", "host_alias_mappings",
|
||||
"remote_alias_mappings"):
|
||||
self.assertNotEqual(ownership[key], "ABSENT")
|
||||
self.assertTrue(ownership["reverse_cleanup"])
|
||||
|
||||
def test_cleanup_failure_is_fail_closed(self) -> None:
|
||||
ownership = self.data["ownership"]
|
||||
self.assertTrue(ownership["cleanup_failure_requires_child_termination"])
|
||||
self.assertTrue(ownership["failed_child_termination_is_hard_error"])
|
||||
self.assertFalse(ownership["partial_success_allowed"])
|
||||
|
||||
def test_no_target_or_device_authority(self) -> None:
|
||||
self.assertFalse(any(self.data["authorizations"].values()))
|
||||
self.assertFalse(self.data["decision"]["target_implementation_allowed"])
|
||||
self.assertFalse(self.data["decision"]["device_action_allowed"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Tracked guardrails for Phase-1.0AL."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
|
||||
PARSER = argparse.ArgumentParser()
|
||||
PARSER.add_argument("--root", type=Path, required=True)
|
||||
ROOT = PARSER.parse_args().root
|
||||
|
||||
|
||||
class Phase10ALGuardrails(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.data = json.loads((ROOT / "manifests/retroarch/phase-1.0al-mdbg-copy-audit.json").read_text(encoding="utf-8"))
|
||||
|
||||
def test_known_restore_and_progress_gaps_remain_blocking(self) -> None:
|
||||
findings = self.data["findings"]
|
||||
self.assertFalse(findings["caps_set_failure_restores_authid"])
|
||||
self.assertFalse(findings["restore_attempts_all_fields_after_one_failure"])
|
||||
self.assertFalse(findings["partial_progress_exposed_to_caller"])
|
||||
self.assertFalse(findings["return_zero_proves_complete_copy"])
|
||||
|
||||
def test_replacement_contract_is_fail_closed(self) -> None:
|
||||
required = self.data["required_replacement_contract"]
|
||||
self.assertIn("REPORT_EXACT_PARTIAL_BYTE_COUNT", required)
|
||||
self.assertIn("RESTORE_EVERY_CHANGED_FIELD_ON_EVERY_EXIT", required)
|
||||
self.assertIn("KILL_AND_REAP_CHILD_AFTER_PARTIAL_COPY_OR_RESTORE_FAILURE", required)
|
||||
|
||||
def test_no_direct_reuse_or_authority(self) -> None:
|
||||
self.assertFalse(any(self.data["authorizations"].values()))
|
||||
self.assertFalse(self.data["decision"]["direct_sdk_mdbg_copy_reuse_allowed"])
|
||||
self.assertFalse(self.data["decision"]["target_implementation_allowed"])
|
||||
self.assertFalse(self.data["decision"]["device_action_allowed"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Tracked guardrails for Phase-1.0AM."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
|
||||
PARSER = argparse.ArgumentParser()
|
||||
PARSER.add_argument("--root", type=Path, required=True)
|
||||
ROOT = PARSER.parse_args().root
|
||||
|
||||
|
||||
class Phase10AMGuardrails(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.data = json.loads((ROOT / "manifests/retroarch/phase-1.0am-bounded-copy-model.json").read_text(encoding="utf-8"))
|
||||
|
||||
def test_progress_and_restoration_are_exact(self) -> None:
|
||||
contract = self.data["contract"]
|
||||
self.assertTrue(contract["exact_progress_reported"])
|
||||
self.assertTrue(contract["complete_status_requires_exact_length"])
|
||||
self.assertTrue(contract["restore_caps_and_authid_on_every_changed_exit"])
|
||||
self.assertTrue(contract["independent_restore_failure_bits"])
|
||||
|
||||
def test_failure_containment_includes_service(self) -> None:
|
||||
contract = self.data["contract"]
|
||||
self.assertTrue(contract["partial_copy_kills_and_reaps_child"])
|
||||
self.assertTrue(contract["restore_failure_kills_child_and_terminates_service"])
|
||||
self.assertTrue(contract["terminal_cleanup_failure_is_hard_error"])
|
||||
|
||||
def test_no_real_capability_or_authority(self) -> None:
|
||||
contract = self.data["contract"]
|
||||
for key in ("credential_interface_present", "process_interface_present",
|
||||
"memory_interface_present", "clock_interface_present"):
|
||||
self.assertFalse(contract[key])
|
||||
self.assertFalse(any(self.data["authorizations"].values()))
|
||||
self.assertFalse(self.data["decision"]["target_implementation_allowed"])
|
||||
self.assertFalse(self.data["decision"]["device_action_allowed"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Tracked guardrails for Phase-1.0AN."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
|
||||
PARSER = argparse.ArgumentParser()
|
||||
PARSER.add_argument("--root", type=Path, required=True)
|
||||
ROOT = PARSER.parse_args().root
|
||||
|
||||
|
||||
class Phase10ANGuardrails(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.data = json.loads((ROOT / "manifests/retroarch/phase-1.0an-service-lifecycle-audit.json").read_text(encoding="utf-8"))
|
||||
|
||||
def test_fail_stop_is_not_promoted_to_recovery(self) -> None:
|
||||
lifecycle = self.data["service_lifecycle"]
|
||||
self.assertTrue(lifecycle["ptrace_auth_restore_failure_latched"])
|
||||
self.assertTrue(lifecycle["request_handler_process_exits_125"])
|
||||
self.assertFalse(lifecycle["service_restart_owner_present"])
|
||||
self.assertFalse(lifecycle["restart_identity_verification_present"])
|
||||
|
||||
def test_pt_copyin_remains_unproven(self) -> None:
|
||||
copy = self.data["copy_path"]
|
||||
self.assertFalse(copy["ptrace_io_descriptor_progress_checked"])
|
||||
self.assertFalse(copy["hard_deadline_or_preemption_present"])
|
||||
self.assertFalse(copy["safe_replacement_for_mdbg_copy_proven"])
|
||||
|
||||
def test_no_restart_reuse_or_authority(self) -> None:
|
||||
self.assertFalse(any(self.data["authorizations"].values()))
|
||||
self.assertFalse(self.data["decision"]["hardened_pt_copyin_reuse_allowed"])
|
||||
self.assertFalse(self.data["decision"]["automatic_service_restart_allowed"])
|
||||
self.assertFalse(self.data["decision"]["target_implementation_allowed"])
|
||||
self.assertFalse(self.data["decision"]["device_action_allowed"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Tracked guardrails for Phase-1.0AO."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
|
||||
PARSER = argparse.ArgumentParser()
|
||||
PARSER.add_argument("--root", type=Path, required=True)
|
||||
ROOT = PARSER.parse_args().root
|
||||
|
||||
|
||||
class Phase10AOGuardrails(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.data = json.loads((ROOT / "manifests/retroarch/phase-1.0ao-worker-supervisor-model.json").read_text(encoding="utf-8"))
|
||||
|
||||
def test_worker_result_and_containment_are_exact(self) -> None:
|
||||
architecture = self.data["architecture"]
|
||||
self.assertTrue(architecture["worker_identity_must_match"])
|
||||
self.assertTrue(architecture["exact_copy_length_required"])
|
||||
self.assertTrue(architecture["zero_restore_failure_bits_required"])
|
||||
self.assertTrue(architecture["ambiguous_or_partial_attempt_terminates_child"])
|
||||
|
||||
def test_no_restart_retry_or_real_capability(self) -> None:
|
||||
architecture = self.data["architecture"]
|
||||
self.assertFalse(architecture["automatic_restart"])
|
||||
self.assertFalse(architecture["retry"])
|
||||
for key in ("real_process_interface_present", "real_signal_interface_present",
|
||||
"real_clock_present", "real_ipc_present"):
|
||||
self.assertFalse(architecture[key])
|
||||
|
||||
def test_target_feasibility_and_authority_remain_false(self) -> None:
|
||||
self.assertFalse(any(self.data["authorizations"].values()))
|
||||
self.assertFalse(self.data["decision"]["target_architecture_feasible"])
|
||||
self.assertFalse(self.data["decision"]["target_implementation_allowed"])
|
||||
self.assertFalse(self.data["decision"]["device_action_allowed"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Tracked guardrails for Phase-1.0AP."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
|
||||
PARSER = argparse.ArgumentParser()
|
||||
PARSER.add_argument("--root", type=Path, required=True)
|
||||
ROOT = PARSER.parse_args().root
|
||||
|
||||
|
||||
class Phase10APGuardrails(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.data = json.loads((ROOT / "manifests/retroarch/phase-1.0ap-worker-feasibility-audit.json").read_text(encoding="utf-8"))
|
||||
|
||||
def test_candidate_is_not_runtime_proof(self) -> None:
|
||||
worker = self.data["worker_creation"]
|
||||
self.assertTrue(worker["rfork_thread_declared_by_public_sdk"])
|
||||
self.assertTrue(worker["rfork_thread_used_by_official_shsrv"])
|
||||
self.assertFalse(worker["pid_birth_identity_or_generation_token_present"])
|
||||
self.assertFalse(worker["firmware_960_behavior_proven"])
|
||||
|
||||
def test_preemption_and_result_remain_blocked(self) -> None:
|
||||
self.assertFalse(self.data["preemption"]["waitpid_calls_are_bounded"])
|
||||
self.assertFalse(self.data["preemption"]["blocked_mdbg_or_ptrace_io_kill_completion_proven"])
|
||||
self.assertFalse(self.data["result_channel"]["fixed_size_worker_result_record_present"])
|
||||
self.assertFalse(self.data["result_channel"]["bounded_receive_and_deadline_present"])
|
||||
|
||||
def test_no_source_copy_target_or_device_authority(self) -> None:
|
||||
self.assertFalse(any(self.data["authorizations"].values()))
|
||||
self.assertFalse(self.data["decision"]["copy_current_shsrv_code_allowed"])
|
||||
self.assertFalse(self.data["decision"]["target_worker_architecture_feasible"])
|
||||
self.assertFalse(self.data["decision"]["target_implementation_allowed"])
|
||||
self.assertFalse(self.data["decision"]["device_action_allowed"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Tracked guardrails for Phase-1.0AQ."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
|
||||
PARSER = argparse.ArgumentParser()
|
||||
PARSER.add_argument("--root", type=Path, required=True)
|
||||
ROOT = PARSER.parse_args().root
|
||||
|
||||
|
||||
class Phase10AQGuardrails(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.data = json.loads((ROOT / "manifests/retroarch/phase-1.0aq-worker-result-record.json").read_text(encoding="utf-8"))
|
||||
|
||||
def test_identity_is_more_than_pid(self) -> None:
|
||||
identity = self.data["identity"]
|
||||
self.assertFalse(identity["pid_alone_is_identity"])
|
||||
self.assertTrue(identity["monotonic_generation_required"])
|
||||
self.assertTrue(identity["all_identity_fields_precommitted"])
|
||||
self.assertEqual(identity["worker_nonce_bytes"], 16)
|
||||
|
||||
def test_success_and_integrity_are_strict(self) -> None:
|
||||
result = self.data["result"]
|
||||
self.assertTrue(result["success_requires_exact_copy"])
|
||||
self.assertTrue(result["success_requires_zero_restore_bits"])
|
||||
self.assertTrue(result["every_single_byte_mutation_tested"])
|
||||
self.assertFalse(result["digest_is_authentication"])
|
||||
|
||||
def test_no_transport_target_or_authority(self) -> None:
|
||||
self.assertFalse(any(self.data["capabilities"].values()))
|
||||
self.assertFalse(any(self.data["authorizations"].values()))
|
||||
self.assertFalse(self.data["decision"]["transport_implementation_allowed"])
|
||||
self.assertFalse(self.data["decision"]["target_implementation_allowed"])
|
||||
self.assertFalse(self.data["decision"]["device_action_allowed"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Tracked guardrails for Phase-1.0AR."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
|
||||
PARSER = argparse.ArgumentParser()
|
||||
PARSER.add_argument("--root", type=Path, required=True)
|
||||
ROOT = PARSER.parse_args().root
|
||||
|
||||
|
||||
class Phase10ARGuardrails(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.data = json.loads((ROOT / "manifests/retroarch/phase-1.0ar-result-channel-model.json").read_text(encoding="utf-8"))
|
||||
|
||||
def test_completion_not_eof_is_boundary(self) -> None:
|
||||
channel = self.data["channel"]
|
||||
self.assertFalse(channel["eof_is_success"])
|
||||
self.assertFalse(channel["silent_incomplete_is_success"])
|
||||
self.assertTrue(channel["record_completion_is_success_boundary"])
|
||||
self.assertTrue(channel["deadline_preempts_crossing_read"])
|
||||
|
||||
def test_partial_reads_do_not_require_atomic_write(self) -> None:
|
||||
channel = self.data["channel"]
|
||||
self.assertTrue(channel["arbitrary_partial_reads_supported"])
|
||||
self.assertTrue(channel["byte_at_a_time_supported"])
|
||||
self.assertTrue(channel["record_overflow_rejected"])
|
||||
|
||||
def test_no_live_channel_target_or_authority(self) -> None:
|
||||
channel = self.data["channel"]
|
||||
for key in ("live_pipe_present", "live_fd_present", "real_clock_present",
|
||||
"process_interface_present"):
|
||||
self.assertFalse(channel[key])
|
||||
self.assertFalse(any(self.data["authorizations"].values()))
|
||||
self.assertFalse(self.data["decision"]["live_channel_implementation_allowed"])
|
||||
self.assertFalse(self.data["decision"]["target_implementation_allowed"])
|
||||
self.assertFalse(self.data["decision"]["device_action_allowed"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Tracked guardrails for Phase-1.0AS."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
|
||||
PARSER = argparse.ArgumentParser()
|
||||
PARSER.add_argument("--root", type=Path, required=True)
|
||||
ROOT = PARSER.parse_args().root
|
||||
|
||||
|
||||
class Phase10ASGuardrails(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.data = json.loads((ROOT / "manifests/retroarch/phase-1.0as-channel-primitive-audit.json").read_text(encoding="utf-8"))
|
||||
|
||||
def test_candidates_are_not_composition_proof(self) -> None:
|
||||
primitives = self.data["channel_primitives"]
|
||||
self.assertTrue(primitives["pipe_used_by_official_shsrv"])
|
||||
self.assertTrue(primitives["poll_used_by_official_shsrv"])
|
||||
self.assertFalse(primitives["pipe_poll_monotonic_deadline_composition_present"])
|
||||
self.assertFalse(primitives["nonblocking_result_read_callsite_present"])
|
||||
|
||||
def test_current_worker_closes_result_fd(self) -> None:
|
||||
ownership = self.data["fd_ownership"]
|
||||
self.assertTrue(ownership["rfcfdg_documented_as_close_all_fds"])
|
||||
self.assertTrue(ownership["official_shsrv_worker_uses_rfcfdg"])
|
||||
self.assertFalse(ownership["result_fd_inherited_by_worker"])
|
||||
self.assertFalse(ownership["exclusive_parent_child_end_close_order_present"])
|
||||
|
||||
def test_no_live_target_or_authority(self) -> None:
|
||||
self.assertFalse(any(self.data["authorizations"].values()))
|
||||
self.assertFalse(self.data["decision"]["live_channel_architecture_feasible"])
|
||||
self.assertFalse(self.data["decision"]["copy_current_shsrv_code_allowed"])
|
||||
self.assertFalse(self.data["decision"]["target_implementation_allowed"])
|
||||
self.assertFalse(self.data["decision"]["device_action_allowed"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Tracked guardrails for Phase-1.0AT."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
|
||||
PARSER = argparse.ArgumentParser()
|
||||
PARSER.add_argument("--root", type=Path, required=True)
|
||||
ROOT = PARSER.parse_args().root
|
||||
|
||||
|
||||
class Phase10ATGuardrails(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.data = json.loads((ROOT / "manifests/retroarch/phase-1.0at-fd-deadline-model.json").read_text(encoding="utf-8"))
|
||||
|
||||
def test_model_has_no_live_capability(self) -> None:
|
||||
boundary = self.data["model_boundary"]
|
||||
self.assertTrue(boundary["fake_facade_only"])
|
||||
self.assertFalse(boundary["os_imports_present"])
|
||||
self.assertFalse(boundary["process_api_present"])
|
||||
self.assertFalse(boundary["clock_api_present"])
|
||||
self.assertFalse(boundary["network_api_present"])
|
||||
self.assertFalse(boundary["target_address_present"])
|
||||
|
||||
def test_ownership_and_deadline_fail_closed(self) -> None:
|
||||
ownership = self.data["ownership_contract"]
|
||||
deadline = self.data["deadline_contract"]
|
||||
self.assertTrue(ownership["parent_write_end_closed_before_read"])
|
||||
self.assertTrue(ownership["all_acquired_ends_closed_on_failure"])
|
||||
self.assertTrue(ownership["started_worker_terminated_and_reaped_on_failure"])
|
||||
self.assertTrue(deadline["one_absolute_budget_across_setup_and_reads"])
|
||||
self.assertTrue(deadline["trailing_read_event_fails"])
|
||||
|
||||
def test_live_and_device_gates_stay_closed(self) -> None:
|
||||
self.assertFalse(any(self.data["authorizations"].values()))
|
||||
self.assertFalse(self.data["decision"]["live_channel_architecture_feasible"])
|
||||
self.assertFalse(self.data["decision"]["target_implementation_allowed"])
|
||||
self.assertFalse(self.data["decision"]["device_action_allowed"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Tracked guardrails for Phase-1.0AU."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
|
||||
PARSER = argparse.ArgumentParser()
|
||||
PARSER.add_argument("--root", type=Path, required=True)
|
||||
ROOT = PARSER.parse_args().root
|
||||
|
||||
|
||||
class Phase10AUGuardrails(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
path = ROOT / "manifests/retroarch/phase-1.0au-live-channel-feasibility.json"
|
||||
cls.data = json.loads(path.read_text(encoding="utf-8"))
|
||||
|
||||
def test_declarations_do_not_become_runtime_proof(self) -> None:
|
||||
contracts = self.data["public_source_contracts"]
|
||||
self.assertTrue(contracts["source_level_design_inputs_complete"])
|
||||
limits = self.data["evidence_limits"]
|
||||
self.assertFalse(limits["headers_prove_firmware_960_runtime"])
|
||||
self.assertFalse(limits["phase10at_fake_model_proves_live_cleanup"])
|
||||
self.assertFalse(limits["launch_context_fix_proven"])
|
||||
self.assertFalse(limits["visible_flip_proven"])
|
||||
|
||||
def test_current_shsrv_composition_is_rejected(self) -> None:
|
||||
audit = self.data["official_composition_audit"]
|
||||
self.assertFalse(audit["worker_uses_rffdg"])
|
||||
self.assertTrue(audit["worker_uses_rfcfdg_close_all"])
|
||||
self.assertFalse(audit["bounded_kill_and_reap_present"])
|
||||
self.assertTrue(audit["service_has_unbounded_restart_loop"])
|
||||
self.assertFalse(audit["direct_shsrv_reuse_allowed"])
|
||||
|
||||
def test_only_offline_canary_design_opens(self) -> None:
|
||||
decision = self.data["decision"]
|
||||
self.assertFalse(any(self.data["authorizations"].values()))
|
||||
self.assertFalse(decision["live_result_channel_implementation_allowed"])
|
||||
self.assertFalse(decision["target_implementation_allowed"])
|
||||
self.assertFalse(decision["device_action_allowed"])
|
||||
self.assertTrue(decision["offline_canary_contract_design_allowed"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Tracked manifest guardrails for Phase-1.0AV."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
|
||||
PARSER = argparse.ArgumentParser()
|
||||
PARSER.add_argument("--root", type=Path, required=True)
|
||||
ROOT = PARSER.parse_args().root
|
||||
|
||||
|
||||
class Phase10AVGuardrails(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
path = ROOT / "manifests/retroarch/phase-1.0av-launch-context-canary-contract.json"
|
||||
cls.data = json.loads(path.read_text(encoding="utf-8"))
|
||||
|
||||
def test_pair_is_exact_and_independently_authorized(self) -> None:
|
||||
pair = self.data["pair_contract"]
|
||||
self.assertTrue(pair["same_payload_sha256_required"])
|
||||
self.assertTrue(pair["distinct_launcher_sha256_required"])
|
||||
self.assertTrue(pair["distinct_run_id_required"])
|
||||
self.assertTrue(pair["distinct_approval_sha256_required"])
|
||||
self.assertTrue(pair["one_shot_each"])
|
||||
self.assertFalse(pair["automatic_retry"])
|
||||
|
||||
def test_terminal_and_interpretation_fail_closed(self) -> None:
|
||||
result = self.data["result_contract"]
|
||||
limits = self.data["interpretation_limits"]
|
||||
self.assertTrue(result["submit_before_d04_required"])
|
||||
self.assertTrue(result["distinct_terminal_after_d04_required"])
|
||||
self.assertTrue(result["incomplete_arm_is_not_comparable"])
|
||||
self.assertTrue(limits["submit_return_difference_is_candidate_only"])
|
||||
self.assertFalse(limits["submit_zero_means_visible_flip"])
|
||||
self.assertFalse(limits["launch_context_root_cause_proven"])
|
||||
|
||||
def test_no_artifact_target_or_authority(self) -> None:
|
||||
state = self.data["tracked_state"]
|
||||
self.assertFalse(state["target_source_present"])
|
||||
self.assertFalse(state["target_artifact_present"])
|
||||
self.assertFalse(state["execution_eligible"])
|
||||
self.assertFalse(any(self.data["authorizations"].values()))
|
||||
self.assertFalse(self.data["decision"]["target_implementation_allowed"])
|
||||
self.assertFalse(self.data["decision"]["device_action_allowed"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Tracked guardrails for Phase-1.0AW."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
|
||||
PARSER = argparse.ArgumentParser()
|
||||
PARSER.add_argument("--root", type=Path, required=True)
|
||||
ROOT = PARSER.parse_args().root
|
||||
|
||||
|
||||
class Phase10AWGuardrails(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
path = ROOT / "manifests/retroarch/phase-1.0aw-canary-source-delta-audit.json"
|
||||
cls.data = json.loads(path.read_text(encoding="utf-8"))
|
||||
|
||||
def test_old_payload_is_not_promoted(self) -> None:
|
||||
old = self.data["historical_payload"]
|
||||
self.assertFalse(old["bytes_present"])
|
||||
self.assertEqual(old["protocol_magic"], "CHD10J01")
|
||||
self.assertFalse(old["distinct_post_d04_terminal_present"])
|
||||
self.assertFalse(old["reusable_as_av_canary"])
|
||||
|
||||
def test_source_delta_preserves_causal_identity_and_cleanup(self) -> None:
|
||||
delta = self.data["required_payload_source_delta"]
|
||||
self.assertEqual(delta["new_protocol_magic"], "CHD10AV1")
|
||||
self.assertTrue(delta["d12_must_not_be_lifecycle_terminal"])
|
||||
self.assertTrue(delta["d14_emitted_only_after_s15_complete"])
|
||||
self.assertTrue(delta["new_cleanup_failure_counter_required"])
|
||||
self.assertTrue(delta["same_elf_bytes_for_both_arms_required"])
|
||||
self.assertTrue(delta["launcher_identity_compile_define_forbidden"])
|
||||
|
||||
def test_result_candidate_does_not_erase_launcher_effects(self) -> None:
|
||||
result = self.data["bigapp_result_path"]
|
||||
effects = self.data["launcher_effects"]
|
||||
self.assertTrue(result["binary_frame_source_candidate"])
|
||||
self.assertFalse(result["live_result_path_proven"])
|
||||
self.assertTrue(effects["kills_running_bigapp"])
|
||||
self.assertTrue(effects["unbounded_child_discovery_wait"])
|
||||
self.assertFalse(effects["bounded_cleanup_on_every_failure"])
|
||||
self.assertFalse(effects["direct_v07_reuse_allowed"])
|
||||
|
||||
def test_artifact_and_device_gates_remain_closed(self) -> None:
|
||||
self.assertFalse(any(self.data["authorizations"].values()))
|
||||
self.assertFalse(self.data["decision"]["target_artifact_build_allowed"])
|
||||
self.assertFalse(self.data["decision"]["device_action_allowed"])
|
||||
self.assertTrue(self.data["decision"]["host_tested_source_design_allowed"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Tracked manifest guardrails for Phase-1.0AX."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
|
||||
PARSER = argparse.ArgumentParser()
|
||||
PARSER.add_argument("--root", type=Path, required=True)
|
||||
ROOT = PARSER.parse_args().root
|
||||
|
||||
|
||||
class Phase10AXGuardrails(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
path = ROOT / "manifests/retroarch/phase-1.0ax-canary-protocol-model.json"
|
||||
cls.data = json.loads(path.read_text(encoding="utf-8"))
|
||||
|
||||
def test_only_d14_can_complete(self) -> None:
|
||||
frame = self.data["frame_contract"]
|
||||
self.assertEqual(frame["magic"], "CHD10AV1")
|
||||
self.assertTrue(frame["only_d14_terminal"])
|
||||
self.assertTrue(frame["d12_terminal_forbidden"])
|
||||
self.assertEqual(frame["d14_stage_value"], 30)
|
||||
|
||||
def test_cleanup_predicate_is_complete(self) -> None:
|
||||
cleanup = self.data["cleanup_terminal_contract"]
|
||||
for key in ("rarch_main_returned_required", "d04_emitted_required",
|
||||
"s15_complete_required", "initialized_mask_zero_required",
|
||||
"cleanup_order_errors_zero_required",
|
||||
"cleanup_failure_count_zero_required"):
|
||||
self.assertTrue(cleanup[key])
|
||||
|
||||
def test_model_does_not_become_target_or_firmware_evidence(self) -> None:
|
||||
boundary = self.data["model_boundary"]
|
||||
self.assertTrue(boundary["python_bytes_only"])
|
||||
self.assertFalse(boundary["target_source_present"])
|
||||
self.assertFalse(boundary["target_artifact_present"])
|
||||
self.assertFalse(self.data["trace_contract"]["host_trace_means_firmware_behavior"])
|
||||
self.assertFalse(any(self.data["authorizations"].values()))
|
||||
self.assertFalse(self.data["decision"]["target_artifact_build_allowed"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Tracked guardrails for Phase-1.0AY."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
|
||||
PARSER = argparse.ArgumentParser()
|
||||
PARSER.add_argument("--root", type=Path, required=True)
|
||||
ROOT = PARSER.parse_args().root
|
||||
|
||||
|
||||
class Phase10AYGuardrails(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
path = ROOT / "manifests/retroarch/phase-1.0ay-target-source-base.json"
|
||||
cls.data = json.loads(path.read_text(encoding="utf-8"))
|
||||
|
||||
def test_base_preserves_m_and_inactive_n(self) -> None:
|
||||
base = self.data["base_selection"]
|
||||
self.assertTrue(base["selected_commit_descends_from_phase10m_source"])
|
||||
self.assertTrue(base["target_source_blobs_unchanged_from_phase10m"])
|
||||
self.assertTrue(base["selected_commit_contains_inactive_phase10n_runner"])
|
||||
self.assertTrue(base["existing_runner_must_remain_inactive"])
|
||||
self.assertTrue(base["separate_worktree_required"])
|
||||
|
||||
def test_scope_is_host_only(self) -> None:
|
||||
scope = self.data["permitted_patch_scope"]
|
||||
self.assertTrue(scope["host_tests_only"])
|
||||
for key in ("launcher_compile_define", "target_profile", "cross_build",
|
||||
"artifact", "live_runner_activation"):
|
||||
self.assertFalse(scope[key])
|
||||
|
||||
def test_authority_and_artifact_gates_are_closed(self) -> None:
|
||||
self.assertFalse(any(self.data["authorizations"].values()))
|
||||
self.assertFalse(self.data["decision"]["target_profile_allowed"])
|
||||
self.assertFalse(self.data["decision"]["target_artifact_build_allowed"])
|
||||
self.assertFalse(self.data["decision"]["device_action_allowed"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Tracked fail-closed guardrails for Phase-1.0AZ."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
|
||||
PARSER = argparse.ArgumentParser()
|
||||
PARSER.add_argument("--root", type=Path, required=True)
|
||||
ROOT = PARSER.parse_args().root
|
||||
|
||||
|
||||
class Phase10AZGuardrails(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
path = ROOT / "manifests/retroarch/phase-1.0az-host-av-source.json"
|
||||
cls.data = json.loads(path.read_text(encoding="utf-8"))
|
||||
|
||||
def test_terminal_contract_is_strict(self) -> None:
|
||||
protocol = self.data["protocol"]
|
||||
self.assertEqual(protocol["magic"], "CHD10AV1")
|
||||
self.assertEqual(protocol["d14_numeric_stage"], 30)
|
||||
self.assertFalse(protocol["d12_terminal"])
|
||||
for key, value in protocol.items():
|
||||
if key not in ("magic", "d14_numeric_stage", "d12_terminal"):
|
||||
self.assertTrue(value, key)
|
||||
|
||||
def test_scope_remains_host_only(self) -> None:
|
||||
self.assertTrue(self.data["decision"]["host_source_structure_complete"])
|
||||
self.assertFalse(any(self.data["scope"].values()))
|
||||
self.assertFalse(any(self.data["authorizations"].values()))
|
||||
|
||||
def test_remote_and_next_gate_remain_closed(self) -> None:
|
||||
self.assertTrue(self.data["source_bindings"]["remote_push_verified"])
|
||||
decision = self.data["decision"]
|
||||
self.assertTrue(decision["remote_source_binding_complete"])
|
||||
self.assertTrue(decision["target_profile_reassessment_allowed"])
|
||||
self.assertFalse(decision["target_artifact_build_allowed"])
|
||||
self.assertFalse(decision["device_action_allowed"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,216 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Host-only policy tests for the Phase-1.0B smoke candidate."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
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(
|
||||
"phase10b_validator", root / "tools/validate_retroarch_phase10b.py"
|
||||
)
|
||||
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 profile(**changes: object) -> dict[str, object]:
|
||||
value: dict[str, object] = {
|
||||
"disabled_features": sorted(validator.DISABLED_FEATURES),
|
||||
"persistent_writes_allowed": False,
|
||||
"content_required": False,
|
||||
"config_required": False,
|
||||
"networking": False,
|
||||
"autoload": False,
|
||||
"automatic_retry": False,
|
||||
}
|
||||
value.update(changes)
|
||||
return value
|
||||
|
||||
def route(**changes: object) -> dict[str, object]:
|
||||
value: dict[str, object] = {
|
||||
"write": False,
|
||||
"create": False,
|
||||
"append": False,
|
||||
"truncate": False,
|
||||
"rename": False,
|
||||
"unlink": False,
|
||||
"mkdir": False,
|
||||
"retry": False,
|
||||
}
|
||||
value.update(changes)
|
||||
return value
|
||||
|
||||
@case("01 profile is closed")
|
||||
def _() -> None:
|
||||
require(validator.profile_is_closed(profile()), "closed profile rejected")
|
||||
|
||||
@case("02 networking is rejected")
|
||||
def _() -> None:
|
||||
require(not validator.profile_is_closed(profile(networking=True)), "network accepted")
|
||||
|
||||
@case("03 content is not required")
|
||||
def _() -> None:
|
||||
require(not validator.profile_is_closed(profile(content_required=True)), "content accepted")
|
||||
|
||||
@case("04 config is not required")
|
||||
def _() -> None:
|
||||
require(not validator.profile_is_closed(profile(config_required=True)), "config accepted")
|
||||
|
||||
@case("05 automatic retry is rejected")
|
||||
def _() -> None:
|
||||
require(not validator.profile_is_closed(profile(automatic_retry=True)), "retry accepted")
|
||||
|
||||
@case("06 autoload is rejected")
|
||||
def _() -> None:
|
||||
require(not validator.profile_is_closed(profile(autoload=True)), "autoload accepted")
|
||||
|
||||
@case("07 write is rejected")
|
||||
def _() -> None:
|
||||
require(not validator.route_is_read_only(route(write=True)), "write accepted")
|
||||
|
||||
@case("08 create is rejected")
|
||||
def _() -> None:
|
||||
require(not validator.route_is_read_only(route(create=True)), "create accepted")
|
||||
|
||||
@case("09 append is rejected")
|
||||
def _() -> None:
|
||||
require(not validator.route_is_read_only(route(append=True)), "append accepted")
|
||||
|
||||
@case("10 truncate is rejected")
|
||||
def _() -> None:
|
||||
require(not validator.route_is_read_only(route(truncate=True)), "truncate accepted")
|
||||
|
||||
@case("11 rename is rejected")
|
||||
def _() -> None:
|
||||
require(not validator.route_is_read_only(route(rename=True)), "rename accepted")
|
||||
|
||||
@case("12 unlink is rejected")
|
||||
def _() -> None:
|
||||
require(not validator.route_is_read_only(route(unlink=True)), "unlink accepted")
|
||||
|
||||
@case("13 mkdir is rejected")
|
||||
def _() -> None:
|
||||
require(not validator.route_is_read_only(route(mkdir=True)), "mkdir accepted")
|
||||
|
||||
@case("14 route retry is rejected")
|
||||
def _() -> None:
|
||||
require(not validator.route_is_read_only(route(retry=True)), "route retry accepted")
|
||||
|
||||
@case("15 RX/R/RW headers pass")
|
||||
def _() -> None:
|
||||
headers = [
|
||||
{"type": "LOAD", "flags": "R E"},
|
||||
{"type": "LOAD", "flags": "R"},
|
||||
{"type": "LOAD", "flags": "RW"},
|
||||
]
|
||||
require(validator.program_headers_are_wx_closed(headers), "W^X headers rejected")
|
||||
|
||||
@case("16 RWE header fails")
|
||||
def _() -> None:
|
||||
headers = [
|
||||
{"type": "LOAD", "flags": "RWE"},
|
||||
{"type": "LOAD", "flags": "RW"},
|
||||
{"type": "LOAD", "flags": "RW"},
|
||||
]
|
||||
require(not validator.program_headers_are_wx_closed(headers), "RWE accepted")
|
||||
|
||||
@case("17 SceNet import fails")
|
||||
def _() -> None:
|
||||
require(not validator.imports_are_closed(["sceNetSocket"]), "SceNet accepted")
|
||||
|
||||
@case("18 GNM import fails")
|
||||
def _() -> None:
|
||||
require(not validator.imports_are_closed(["sceGnmSubmitCommandBuffers"]), "GNM accepted")
|
||||
|
||||
@case("19 module loading fails")
|
||||
def _() -> None:
|
||||
require(not validator.imports_are_closed(["sceKernelLoadStartModule"]), "module loader accepted")
|
||||
|
||||
@case("20 rumble fails")
|
||||
def _() -> None:
|
||||
require(not validator.imports_are_closed(["scePadSetVibration"]), "rumble accepted")
|
||||
|
||||
@case("21 lightbar fails")
|
||||
def _() -> None:
|
||||
require(not validator.imports_are_closed(["scePadSetLightBar"]), "lightbar accepted")
|
||||
|
||||
@case("22 socket import fails")
|
||||
def _() -> None:
|
||||
require(not validator.imports_are_closed(["socket"]), "socket accepted")
|
||||
|
||||
@case("23 expected imports pass")
|
||||
def _() -> None:
|
||||
require(
|
||||
validator.imports_are_closed(
|
||||
["sceVideoOutOpen", "scePadOpen", "sceAudioOutOpen", "_Exit"]
|
||||
),
|
||||
"expected imports rejected",
|
||||
)
|
||||
|
||||
@case("24 runtime contract pins both deadlines")
|
||||
def _() -> None:
|
||||
text = (root / "docs/retroarch/phase-1.0b-smoke-candidate-design.md").read_text()
|
||||
require("60,000 ms" in text and "3,600" in text, "deadlines absent")
|
||||
|
||||
@case("25 exit risk remains explicit")
|
||||
def _() -> None:
|
||||
text = (root / "docs/retroarch/phase-1.0b-runtime-and-exit-contract.md").read_text()
|
||||
require("unproven" in text.lower() and "_Exit" in text, "exit risk hidden")
|
||||
|
||||
@case("26 host evidence is not hardware proof")
|
||||
def _() -> None:
|
||||
text = (root / "docs/retroarch/phase-1.0b-smoke-candidate-design.md").read_text()
|
||||
require(
|
||||
"no host test or static audit" in text.lower()
|
||||
and "is hardware evidence" in text.lower(),
|
||||
"hardware disclaimer absent",
|
||||
)
|
||||
|
||||
failures: list[str] = []
|
||||
for name, function in cases:
|
||||
try:
|
||||
function()
|
||||
except Exception as error: # noqa: BLE001 - isolated reporting
|
||||
failures.append(f"{name}: {error}")
|
||||
if len(cases) != 26:
|
||||
failures.append(f"expected 26 cases, found {len(cases)}")
|
||||
if failures:
|
||||
print("\n".join(failures), file=sys.stderr)
|
||||
return 1
|
||||
print("26 Phase 1.0B guardrails passed")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Fail-closed Phase-1.0BA guardrails."""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
root = parser.parse_args().root
|
||||
|
||||
|
||||
class Phase10BAGuardrails(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.data = json.loads((root / "manifests/retroarch/phase-1.0ba-target-profile-callsite-audit.json").read_text(encoding="utf-8"))
|
||||
|
||||
def test_callsite_is_post_cleanup_pre_exit(self) -> None:
|
||||
call = self.data["callsite_contract"]
|
||||
for key in ("rarch_main_return_required", "s15_before_d14",
|
||||
"d14_before_process_exit", "early_return_initialized_mask_rejected"):
|
||||
self.assertTrue(call[key])
|
||||
self.assertFalse(call["terminal_emit_result_changes_exit_code"])
|
||||
|
||||
def test_profile_is_launcher_independent(self) -> None:
|
||||
profile = self.data["profile_delta"]
|
||||
self.assertEqual(profile["profile_name"], "launch-canary")
|
||||
self.assertTrue(profile["inherits_write_diag_behavior"])
|
||||
self.assertTrue(profile["same_elf_for_both_launch_arms"])
|
||||
self.assertTrue(profile["adds_only_av_target_define"])
|
||||
for key in ("launcher_define_forbidden", "new_socket_or_address_forbidden",
|
||||
"runner_activation_forbidden"):
|
||||
self.assertTrue(profile[key])
|
||||
|
||||
def test_build_and_device_gates_stay_closed(self) -> None:
|
||||
auth = self.data["authorizations"]
|
||||
self.assertTrue(auth["target_source_patch_authorized"])
|
||||
self.assertTrue(auth["target_profile_addition_authorized"])
|
||||
for key, value in auth.items():
|
||||
if key not in ("target_source_patch_authorized", "target_profile_addition_authorized"):
|
||||
self.assertFalse(value, key)
|
||||
self.assertFalse(self.data["decision"]["cross_build_allowed"])
|
||||
self.assertFalse(self.data["decision"]["device_action_allowed"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Phase-1.0BB repository guardrails."""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
root = parser.parse_args().root
|
||||
|
||||
|
||||
class Phase10BBGuardrails(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.data = json.loads((root / "manifests/retroarch/phase-1.0bb-source-only-launch-canary-profile.json").read_text(encoding="utf-8"))
|
||||
|
||||
def test_source_properties(self) -> None:
|
||||
props = self.data["source_properties"]
|
||||
for key in ("post_s15_pre_exit_call", "distinct_launch_canary_profile",
|
||||
"inherits_write_diag", "launcher_independent_payload"):
|
||||
self.assertTrue(props[key])
|
||||
self.assertFalse(props["new_network_or_device_path"])
|
||||
self.assertFalse(props["runner_activation"])
|
||||
|
||||
def test_only_prerequisite_audit_is_open(self) -> None:
|
||||
auth = self.data["authorizations"]
|
||||
self.assertTrue(auth["build_prerequisite_audit_authorized"])
|
||||
for key, value in auth.items():
|
||||
if key != "build_prerequisite_audit_authorized":
|
||||
self.assertFalse(value, key)
|
||||
self.assertFalse(self.data["decision"]["cross_build_allowed"])
|
||||
self.assertFalse(self.data["decision"]["artifact_allowed"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Phase-1.0BC prerequisite gate tests."""
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
root = parser.parse_args().root
|
||||
|
||||
|
||||
class Phase10BCGuardrails(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.data = json.loads((root / "manifests/retroarch/phase-1.0bc-cross-build-prerequisite-audit.json").read_text(encoding="utf-8"))
|
||||
|
||||
def test_parent_binding(self) -> None:
|
||||
parent = root / "manifests/retroarch/phase-1.0bb-source-only-launch-canary-profile.json"
|
||||
self.assertEqual(hashlib.sha256(parent.read_bytes()).hexdigest(),
|
||||
self.data["source_bindings"]["phase10bb_manifest_sha256"])
|
||||
|
||||
def test_missing_prerequisites_are_explicit(self) -> None:
|
||||
observed = self.data["observed_prerequisites"]
|
||||
for key in ("wsl_cmake_present", "wsl_ninja_present",
|
||||
"exact_sdl_archive_present", "launch_canary_elf_present",
|
||||
"launch_canary_map_present"):
|
||||
self.assertFalse(observed[key])
|
||||
|
||||
def test_only_materializer_source_is_open(self) -> None:
|
||||
auth = self.data["authorizations"]
|
||||
self.assertTrue(auth["materializer_source_authorized"])
|
||||
for key, value in auth.items():
|
||||
if key != "materializer_source_authorized":
|
||||
self.assertFalse(value, key)
|
||||
decision = self.data["decision"]
|
||||
self.assertFalse(decision["prerequisites_complete"])
|
||||
self.assertFalse(decision["unknown_archive_reuse_allowed"])
|
||||
self.assertFalse(decision["cross_build_allowed"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Phase-1.0BD fail-closed repository tests."""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
root = parser.parse_args().root
|
||||
|
||||
|
||||
class Phase10BDGuardrails(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.data = json.loads((root / "manifests/retroarch/phase-1.0bd-dormant-sdl-materializer-policy.json").read_text(encoding="utf-8"))
|
||||
|
||||
def test_policy_is_dormant(self) -> None:
|
||||
props = self.data["policy_properties"]
|
||||
for key in ("exact_identity_validation", "clean_absent_output_required",
|
||||
"immutable_plan", "exact_three_file_patch_scope"):
|
||||
self.assertTrue(props[key])
|
||||
for key in ("process_adapter_present", "filesystem_adapter_present",
|
||||
"network_present", "cli_present", "retroarch_build_included",
|
||||
"device_action_included"):
|
||||
self.assertFalse(props[key])
|
||||
|
||||
def test_only_injected_adapter_source_is_open(self) -> None:
|
||||
auth = self.data["authorizations"]
|
||||
self.assertTrue(auth["injected_adapter_source_authorized"])
|
||||
for key, value in auth.items():
|
||||
if key != "injected_adapter_source_authorized":
|
||||
self.assertFalse(value, key)
|
||||
self.assertFalse(self.data["decision"]["materialization_allowed"])
|
||||
self.assertFalse(self.data["decision"]["cross_build_allowed"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Phase-1.0BE repository guardrails."""
|
||||
|
||||
import argparse, hashlib, json, subprocess
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
parser.add_argument("--retroarch-root", type=Path)
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
class Phase10BEGuardrails(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.data = json.loads((args.root / "manifests/retroarch/phase-1.0be-fake-only-sdl-materializer.json").read_text(encoding="utf-8"))
|
||||
|
||||
def test_parent_and_optional_source_binding(self) -> None:
|
||||
parent = args.root / "manifests/retroarch/phase-1.0bd-dormant-sdl-materializer-policy.json"
|
||||
bind = self.data["source_bindings"]
|
||||
self.assertEqual(hashlib.sha256(parent.read_bytes()).hexdigest(), bind["phase10bd_manifest_sha256"])
|
||||
if args.retroarch_root:
|
||||
def git(*values: str) -> str:
|
||||
return subprocess.run(["git", *values], cwd=args.retroarch_root,
|
||||
check=True, capture_output=True, text=True).stdout.strip()
|
||||
self.assertEqual(git("rev-parse", "HEAD"), bind["retroarch_commit"])
|
||||
self.assertEqual(git("rev-parse", bind["remote_ref"]), bind["retroarch_commit"])
|
||||
paths = {"makefile": "Makefile.ps5",
|
||||
"adapter": "tools/phase10be_sdl_materializer_fake_adapter.py",
|
||||
"validator": "tools/validate_ps5_phase10be.py",
|
||||
"tests": "tests/test_ps5_phase10be_fake_materializer.py",
|
||||
"source_doc": "docs/ps5-phase10be-fake-sdl-materializer.md"}
|
||||
for key, source in paths.items():
|
||||
self.assertEqual(git("rev-parse", f'{bind["retroarch_commit"]}:{source}'),
|
||||
bind[f"{key}_blob"])
|
||||
|
||||
def test_fake_only_and_live_gates(self) -> None:
|
||||
props = self.data["properties"]
|
||||
self.assertTrue(props["exact_fake_type_only"])
|
||||
self.assertEqual(props["ordered_operations"], 9)
|
||||
for key in ("process_capability", "filesystem_capability",
|
||||
"network_capability", "retroarch_build_capability",
|
||||
"device_capability"):
|
||||
self.assertFalse(props[key])
|
||||
auth = self.data["authorizations"]
|
||||
self.assertTrue(auth["live_adapter_source_reassessment_authorized"])
|
||||
for key, value in auth.items():
|
||||
if key != "live_adapter_source_reassessment_authorized":
|
||||
self.assertFalse(value, key)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Phase-1.0BF live-adapter boundary tests."""
|
||||
|
||||
import argparse, hashlib, json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
root = parser.parse_args().root
|
||||
|
||||
|
||||
class Phase10BFGuardrails(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.data = json.loads((root / "manifests/retroarch/phase-1.0bf-live-sdl-adapter-boundary-audit.json").read_text(encoding="utf-8"))
|
||||
|
||||
def test_parent_binding(self) -> None:
|
||||
parent = root / "manifests/retroarch/phase-1.0be-fake-only-sdl-materializer.json"
|
||||
self.assertEqual(hashlib.sha256(parent.read_bytes()).hexdigest(),
|
||||
self.data["source_bindings"]["phase10be_manifest_sha256"])
|
||||
|
||||
def test_adapter_contract_is_fail_closed(self) -> None:
|
||||
contract = self.data["adapter_contract"]
|
||||
for key, value in contract.items():
|
||||
if key in ("automatic_retry", "automatic_cleanup", "network_capability",
|
||||
"retroarch_build_capability", "device_capability"):
|
||||
self.assertFalse(value, key)
|
||||
else:
|
||||
self.assertTrue(value, key)
|
||||
|
||||
def test_only_dormant_source_is_open(self) -> None:
|
||||
auth = self.data["authorizations"]
|
||||
self.assertTrue(auth["dormant_live_adapter_source_authorized"])
|
||||
for key, value in auth.items():
|
||||
if key != "dormant_live_adapter_source_authorized":
|
||||
self.assertFalse(value, key)
|
||||
self.assertFalse(self.data["decision"]["tool_install_allowed"])
|
||||
self.assertFalse(self.data["decision"]["materialization_allowed"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Phase-1.0BG repository guardrails."""
|
||||
|
||||
import argparse, hashlib, json, subprocess
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
parser.add_argument("--retroarch-root", type=Path)
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
class Phase10BGGuardrails(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.data = json.loads((args.root / "manifests/retroarch/phase-1.0bg-dormant-sdl-request-compiler.json").read_text(encoding="utf-8"))
|
||||
|
||||
def test_parent_and_optional_source(self) -> None:
|
||||
bind = self.data["source_bindings"]
|
||||
parent = args.root / "manifests/retroarch/phase-1.0bf-live-sdl-adapter-boundary-audit.json"
|
||||
self.assertEqual(hashlib.sha256(parent.read_bytes()).hexdigest(), bind["phase10bf_manifest_sha256"])
|
||||
if args.retroarch_root:
|
||||
def git(*values: str) -> str:
|
||||
return subprocess.run(["git", *values], cwd=args.retroarch_root,
|
||||
check=True, capture_output=True, text=True).stdout.strip()
|
||||
self.assertEqual(git("rev-parse", "HEAD"), bind["retroarch_commit"])
|
||||
self.assertEqual(git("rev-parse", bind["remote_ref"]), bind["retroarch_commit"])
|
||||
|
||||
def test_graph_and_authority(self) -> None:
|
||||
graph = self.data["request_graph"]
|
||||
self.assertEqual(graph["operation_count"], 8)
|
||||
self.assertTrue(graph["fixed_argv"])
|
||||
self.assertTrue(graph["canonical_output_containment"])
|
||||
for key in ("shell_present", "package_manager_present", "retroarch_build_present",
|
||||
"network_present", "device_action_present", "executor_present"):
|
||||
self.assertFalse(graph[key])
|
||||
auth = self.data["authorizations"]
|
||||
self.assertTrue(auth["bounded_executor_source_authorized"])
|
||||
for key, value in auth.items():
|
||||
if key != "bounded_executor_source_authorized":
|
||||
self.assertFalse(value, key)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Phase-1.0BH repository guardrails."""
|
||||
|
||||
import argparse, hashlib, json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
root = parser.parse_args().root
|
||||
|
||||
|
||||
class Phase10BHGuardrails(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.data = json.loads((root / "manifests/retroarch/phase-1.0bh-bounded-sdl-executor.json").read_text(encoding="utf-8"))
|
||||
|
||||
def test_parent_and_executor(self) -> None:
|
||||
parent = root / "manifests/retroarch/phase-1.0bg-dormant-sdl-request-compiler.json"
|
||||
self.assertEqual(hashlib.sha256(parent.read_bytes()).hexdigest(),
|
||||
self.data["source_bindings"]["phase10bg_manifest_sha256"])
|
||||
executor = self.data["executor"]
|
||||
for key in ("attempt_consumed_before_preflight", "exact_request_order",
|
||||
"exit_output_utf8_semantics_bounded", "success_reuse_forbidden",
|
||||
"failure_reuse_forbidden", "exact_fake_facades_only"):
|
||||
self.assertTrue(executor[key])
|
||||
for key in ("real_process_present", "real_filesystem_present", "cli_present"):
|
||||
self.assertFalse(executor[key])
|
||||
|
||||
def test_only_reassessment_is_open(self) -> None:
|
||||
auth = self.data["authorizations"]
|
||||
self.assertTrue(auth["real_facade_reassessment_authorized"])
|
||||
for key, value in auth.items():
|
||||
if key != "real_facade_reassessment_authorized":
|
||||
self.assertFalse(value, key)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Phase-1.0BI audit guardrails."""
|
||||
|
||||
import argparse, hashlib, json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
parser = argparse.ArgumentParser(); parser.add_argument("--root", type=Path, required=True)
|
||||
root = parser.parse_args().root
|
||||
|
||||
|
||||
class Phase10BIGuardrails(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.data = json.loads((root / "manifests/retroarch/phase-1.0bi-real-facade-and-tool-install-audit.json").read_text(encoding="utf-8"))
|
||||
|
||||
def test_parent_and_facade_contract(self) -> None:
|
||||
parent = root / "manifests/retroarch/phase-1.0bh-bounded-sdl-executor.json"
|
||||
self.assertEqual(hashlib.sha256(parent.read_bytes()).hexdigest(), self.data["source_bindings"]["phase10bh_manifest_sha256"])
|
||||
contract = self.data["facade_contract"]
|
||||
for key, value in contract.items():
|
||||
if key in ("cli_present", "retry_present", "cleanup_present", "network_present", "device_present"):
|
||||
self.assertFalse(value, key)
|
||||
else:
|
||||
self.assertTrue(value, key)
|
||||
|
||||
def test_narrow_authority(self) -> None:
|
||||
auth = self.data["authorizations"]
|
||||
self.assertTrue(auth["real_facade_source_authorized"])
|
||||
self.assertTrue(auth["exact_package_install_authorized"])
|
||||
for key, value in auth.items():
|
||||
if key not in ("real_facade_source_authorized", "exact_package_install_authorized"):
|
||||
self.assertFalse(value, key)
|
||||
self.assertFalse(self.data["decision"]["materialization_allowed"])
|
||||
|
||||
|
||||
if __name__ == "__main__": unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Phase-1.0BJ installation-result guardrails."""
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
root = parser.parse_args().root
|
||||
|
||||
|
||||
class Phase10BJGuardrails(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.data = json.loads((root / "manifests/retroarch/phase-1.0bj-exact-host-tool-install-result.json").read_text(encoding="utf-8"))
|
||||
|
||||
def test_parent_and_exact_installation(self) -> None:
|
||||
parent = root / "manifests/retroarch/phase-1.0bi-real-facade-and-tool-install-audit.json"
|
||||
self.assertEqual(hashlib.sha256(parent.read_bytes()).hexdigest(), self.data["source_bindings"]["phase10bi_manifest_sha256"])
|
||||
install = self.data["installation"]
|
||||
self.assertEqual(install["attempts"], 1)
|
||||
self.assertTrue(install["completed"])
|
||||
self.assertEqual(install["cmake_version"], "4.2.3-2ubuntu2")
|
||||
self.assertEqual(install["ninja_build_version"], "1.13.2-1")
|
||||
for key in ("cmake_executable_sha256", "ninja_executable_sha256"):
|
||||
self.assertRegex(install[key], r"^[0-9a-f]{64}$")
|
||||
bindings = self.data["source_bindings"]
|
||||
self.assertRegex(bindings["retroarch_commit"], r"^[0-9a-f]{40}$")
|
||||
for key in ("real_facade_sha256", "static_validator_sha256", "source_test_sha256"):
|
||||
self.assertRegex(bindings[key], r"^[0-9a-f]{64}$")
|
||||
|
||||
def test_every_execution_path_remains_closed(self) -> None:
|
||||
auth = self.data["authorizations"]
|
||||
self.assertTrue(auth["real_facade_source_authorized"])
|
||||
for key, value in auth.items():
|
||||
if key != "real_facade_source_authorized":
|
||||
self.assertFalse(value, key)
|
||||
self.assertFalse(self.data["decision"]["materialization_allowed"])
|
||||
self.assertTrue(self.data["decision"]["dormant_real_facade_source_present"])
|
||||
self.assertFalse(self.data["decision"]["real_facade_invoked"])
|
||||
self.assertFalse(self.data["decision"]["cross_build_allowed"])
|
||||
self.assertFalse(self.data["decision"]["device_action_allowed"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Phase-1.0BK exact host-fixture gate guardrails."""
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
root = parser.parse_args().root
|
||||
|
||||
|
||||
class Phase10BKGuardrails(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.data = json.loads((root / "manifests/retroarch/phase-1.0bk-bounded-real-facade-host-fixture-gate.json").read_text(encoding="utf-8"))
|
||||
|
||||
def test_exact_parent_and_runtime(self) -> None:
|
||||
parent = root / "manifests/retroarch/phase-1.0bj-exact-host-tool-install-result.json"
|
||||
bindings = self.data["source_bindings"]
|
||||
self.assertEqual(hashlib.sha256(parent.read_bytes()).hexdigest(), bindings["phase10bj_manifest_sha256"])
|
||||
self.assertEqual(bindings["python_path"], "/usr/bin/python3.14")
|
||||
self.assertRegex(bindings["python_sha256"], r"^[0-9a-f]{64}$")
|
||||
|
||||
def test_four_exact_bounded_fixtures(self) -> None:
|
||||
fixtures = self.data["fixture_contract"]
|
||||
self.assertEqual(fixtures["suite_attempts"], 1)
|
||||
self.assertEqual(fixtures["per_fixture_attempts"], 1)
|
||||
self.assertEqual(set(fixtures) - {"suite_attempts", "per_fixture_attempts"}, {"success", "nonzero", "overflow", "timeout"})
|
||||
for name in ("success", "nonzero", "overflow", "timeout"):
|
||||
fixture = fixtures[name]
|
||||
self.assertEqual(fixture["argv"][0], "/usr/bin/python3.14")
|
||||
self.assertLessEqual(fixture["timeout_seconds"], 2)
|
||||
self.assertEqual(fixture["output_limit"], 64)
|
||||
|
||||
def test_authority_stays_host_fixture_only(self) -> None:
|
||||
auth = self.data["authorizations"]
|
||||
self.assertTrue(auth["exact_host_fixture_suite_authorized"])
|
||||
for key, value in auth.items():
|
||||
if key != "exact_host_fixture_suite_authorized":
|
||||
self.assertFalse(value, key)
|
||||
self.assertFalse(self.data["decision"]["materialization_allowed"])
|
||||
self.assertFalse(self.data["decision"]["device_action_allowed"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Phase-1.0BL consumed-attempt and new-gate guardrails."""
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
root = parser.parse_args().root
|
||||
|
||||
|
||||
class Phase10BLGuardrails(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.data = json.loads((root / "manifests/retroarch/phase-1.0bl-fixture-import-failure-and-new-gate.json").read_text(encoding="utf-8"))
|
||||
|
||||
def test_parent_and_corrected_source_bound(self) -> None:
|
||||
parent = root / "manifests/retroarch/phase-1.0bk-bounded-real-facade-host-fixture-gate.json"
|
||||
bindings = self.data["source_bindings"]
|
||||
self.assertEqual(hashlib.sha256(parent.read_bytes()).hexdigest(), bindings["phase10bk_manifest_sha256"])
|
||||
self.assertRegex(bindings["corrected_runner_sha256"], r"^[0-9a-f]{64}$")
|
||||
self.assertRegex(bindings["retroarch_commit"], r"^[0-9a-f]{40}$")
|
||||
|
||||
def test_bk_was_consumed_before_any_fixture(self) -> None:
|
||||
result = self.data["consumed_bk_attempt"]
|
||||
self.assertEqual(result["suite_attempts"], 1)
|
||||
self.assertEqual(result["exit_code"], 1)
|
||||
self.assertFalse(result["main_entered"])
|
||||
self.assertFalse(result["facade_constructed"])
|
||||
self.assertEqual(result["fixture_processes_started"], 0)
|
||||
for key in ("materializer_invoked", "sdl_build_invoked", "retroarch_build_invoked", "device_action_invoked"):
|
||||
self.assertFalse(result[key], key)
|
||||
|
||||
def test_new_permission_is_one_exact_nonautomatic_suite(self) -> None:
|
||||
gate = self.data["new_bl_gate"]
|
||||
self.assertEqual(gate["suite_attempts_authorized"], 1)
|
||||
self.assertEqual(len(gate["fixtures"]), 4)
|
||||
self.assertTrue(gate["contract_identical_to_phase10bk"])
|
||||
self.assertFalse(gate["automatic_retry"])
|
||||
auth = self.data["authorizations"]
|
||||
self.assertTrue(auth["exact_corrected_host_fixture_suite_authorized"])
|
||||
for key, value in auth.items():
|
||||
if key != "exact_corrected_host_fixture_suite_authorized":
|
||||
self.assertFalse(value, key)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Phase-1.0BM real-facade fixture-result guardrails."""
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--root", type=Path, required=True)
|
||||
root = parser.parse_args().root
|
||||
|
||||
|
||||
class Phase10BMGuardrails(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls.data = json.loads((root / "manifests/retroarch/phase-1.0bm-real-facade-fixture-result.json").read_text(encoding="utf-8"))
|
||||
|
||||
def test_parent_and_exact_success_result(self) -> None:
|
||||
parent = root / "manifests/retroarch/phase-1.0bl-fixture-import-failure-and-new-gate.json"
|
||||
self.assertEqual(hashlib.sha256(parent.read_bytes()).hexdigest(), self.data["source_bindings"]["phase10bl_manifest_sha256"])
|
||||
result = self.data["result"]
|
||||
self.assertEqual((result["suite_attempts"], result["fixture_attempts"], result["exit_code"]), (1, 4, 0))
|
||||
self.assertEqual((result["success_returncode"], result["success_output_hex"]), (0, "4f4b0a"))
|
||||
self.assertEqual((result["nonzero_returncode"], result["nonzero_output_hex"]), (7, "45370a"))
|
||||
self.assertEqual(result["overflow_error"], "output limit exceeded: fixture_overflow")
|
||||
self.assertEqual(result["timeout_error"], "process timeout: fixture_timeout")
|
||||
|
||||
def test_only_dormant_composition_source_is_open(self) -> None:
|
||||
for key in ("materializer_invoked", "sdl_build_invoked", "retroarch_build_invoked", "device_action_invoked"):
|
||||
self.assertFalse(self.data["result"][key], key)
|
||||
auth = self.data["authorizations"]
|
||||
self.assertTrue(auth["dormant_real_composition_source_authorized"])
|
||||
for key, value in auth.items():
|
||||
if key != "dormant_real_composition_source_authorized":
|
||||
self.assertFalse(value, key)
|
||||
self.assertFalse(self.data["decision"]["materialization_allowed"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(argv=[__file__])
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user