This commit is contained in:
@@ -0,0 +1,287 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Offline Phase-1.0AA integration for the passive batch contract.
|
||||
|
||||
Only the exact built-in fake adapter and synthetic clock are accepted. This
|
||||
module has no live adapter protocol, network import, address, CLI or real clock.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
import hashlib
|
||||
import math
|
||||
from typing import Any
|
||||
|
||||
from phase10w_shsrv_client_policy import SessionPlan
|
||||
from phase10x_inactive_transport import (
|
||||
EvidenceFailure,
|
||||
EvidenceRecord,
|
||||
ExclusiveEvidenceStore,
|
||||
)
|
||||
from phase10z_passive_batch_contract import (
|
||||
PassiveBatch,
|
||||
PassiveContractError,
|
||||
PassiveResultAccumulator,
|
||||
build_passive_batch,
|
||||
)
|
||||
|
||||
|
||||
PHASE10Z_CONTRACT_SHA256 = \
|
||||
"0728c2be7f368e0a7f4b68efe86f6e0c5c2f50704a41d0e1992b0bfec19dde06"
|
||||
MAX_FAKE_EVENTS = 257
|
||||
MAX_FAKE_ADVANCE_SECONDS = 60.0
|
||||
EVENT_DATA = "DATA"
|
||||
EVENT_HARD_DEADLINE = "HARD_DEADLINE"
|
||||
EVENT_REMOTE_EOF = "REMOTE_EOF"
|
||||
EVENT_BLOCKED = "BLOCKED"
|
||||
EVENT_KINDS = {
|
||||
EVENT_DATA, EVENT_HARD_DEADLINE, EVENT_REMOTE_EOF, EVENT_BLOCKED,
|
||||
}
|
||||
|
||||
|
||||
class OfflineIntegrationError(RuntimeError):
|
||||
"""Normalized offline failure without supplied data, target or path."""
|
||||
|
||||
|
||||
class FakeAdapterError(RuntimeError):
|
||||
"""Built-in fake-adapter state failure."""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class FakeReceiveEvent:
|
||||
"""One caller-supplied synthetic event; never a live receive result."""
|
||||
|
||||
kind: str
|
||||
data: bytes = b""
|
||||
advance_seconds: float = 0.0
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if self.kind not in EVENT_KINDS:
|
||||
raise FakeAdapterError("fake event kind is invalid")
|
||||
if not isinstance(self.data, bytes):
|
||||
raise FakeAdapterError("fake event data must be bytes")
|
||||
if self.kind == EVENT_DATA and not self.data:
|
||||
raise FakeAdapterError("fake data event is empty")
|
||||
if self.kind != EVENT_DATA and self.data:
|
||||
raise FakeAdapterError("fake control event contains data")
|
||||
value = self.advance_seconds
|
||||
if not isinstance(value, (int, float)) or isinstance(value, bool) or \
|
||||
not math.isfinite(value) or not 0.0 <= value <= \
|
||||
MAX_FAKE_ADVANCE_SECONDS:
|
||||
raise FakeAdapterError("fake time advance is invalid")
|
||||
|
||||
|
||||
class OfflineFakeClock:
|
||||
"""Explicit synthetic monotonic value; never acquires host time."""
|
||||
|
||||
def __init__(self, initial: float = 0.0) -> None:
|
||||
if not isinstance(initial, (int, float)) or isinstance(initial, bool) or \
|
||||
not math.isfinite(initial):
|
||||
raise FakeAdapterError("fake clock initial value is invalid")
|
||||
self._value = float(initial)
|
||||
|
||||
def monotonic(self) -> float:
|
||||
return self._value
|
||||
|
||||
def advance(self, seconds: float) -> None:
|
||||
if not isinstance(seconds, (int, float)) or isinstance(seconds, bool) or \
|
||||
not math.isfinite(seconds) or not 0.0 <= seconds <= \
|
||||
MAX_FAKE_ADVANCE_SECONDS:
|
||||
raise FakeAdapterError("fake clock advance is invalid")
|
||||
self._value += float(seconds)
|
||||
|
||||
|
||||
class OfflineFakeBatchAdapter:
|
||||
"""Closed fake only: one open, one exact batch and scripted events."""
|
||||
|
||||
def __init__(
|
||||
self, clock: OfflineFakeClock, events: tuple[FakeReceiveEvent, ...],
|
||||
) -> None:
|
||||
if type(clock) is not OfflineFakeClock:
|
||||
raise FakeAdapterError("only the exact fake clock is accepted")
|
||||
if not isinstance(events, tuple) or not 1 <= len(events) <= MAX_FAKE_EVENTS:
|
||||
raise FakeAdapterError("fake event sequence is invalid")
|
||||
if any(type(event) is not FakeReceiveEvent for event in events):
|
||||
raise FakeAdapterError("fake event type is invalid")
|
||||
self.clock = clock
|
||||
self._events = list(events)
|
||||
self.state = "NEW"
|
||||
self.open_count = 0
|
||||
self.send_count = 0
|
||||
self.close_count = 0
|
||||
self.sent_sha256: str | None = None
|
||||
self.trace: list[str] = []
|
||||
self.logical_event_buffer_discarded = False
|
||||
|
||||
def open_once(self) -> None:
|
||||
if self.state != "NEW":
|
||||
raise FakeAdapterError("fake adapter cannot open")
|
||||
self.open_count += 1
|
||||
self.trace.append("FAKE_OPEN")
|
||||
self.state = "OPEN"
|
||||
|
||||
def send_one_batch(self, batch: PassiveBatch) -> None:
|
||||
if self.state != "OPEN" or type(batch) is not PassiveBatch:
|
||||
raise FakeAdapterError("fake adapter cannot accept a batch")
|
||||
self.send_count += 1
|
||||
if self.send_count != 1:
|
||||
raise FakeAdapterError("second fake send is forbidden")
|
||||
self.sent_sha256 = hashlib.sha256(batch.payload).hexdigest()
|
||||
self.trace.append("FAKE_SEND_ONE_BATCH")
|
||||
self.state = "SENT"
|
||||
|
||||
def next_event(self) -> FakeReceiveEvent:
|
||||
if self.state not in {"SENT", "RECEIVING"} or not self._events:
|
||||
raise FakeAdapterError("fake adapter has no next event")
|
||||
event = self._events.pop(0)
|
||||
self.clock.advance(event.advance_seconds)
|
||||
self.trace.append(f"FAKE_EVENT_{event.kind}")
|
||||
self.state = "RECEIVING"
|
||||
return event
|
||||
|
||||
def close_once(self) -> None:
|
||||
if self.state not in {"OPEN", "SENT", "RECEIVING"}:
|
||||
raise FakeAdapterError("fake adapter cannot close")
|
||||
self.close_count += 1
|
||||
if self.close_count != 1:
|
||||
raise FakeAdapterError("second fake close is forbidden")
|
||||
self._events.clear()
|
||||
self.logical_event_buffer_discarded = True
|
||||
self.trace.append("FAKE_CLOSE")
|
||||
self.state = "CLOSED"
|
||||
|
||||
|
||||
class OfflineFakeEvidenceStore(ExclusiveEvidenceStore):
|
||||
"""Phase-specific exclusive local evidence; no raw transcript."""
|
||||
|
||||
def create_fake_consumed_receipt(
|
||||
self, plan: SessionPlan, batch: PassiveBatch, start: float,
|
||||
) -> EvidenceRecord:
|
||||
return self._create(f"{plan.run_id}.aa-consumed.json", {
|
||||
"schema_version": 1,
|
||||
"status": "OFFLINE_FAKE_ATTEMPT_CONSUMED_BEFORE_OPEN",
|
||||
"run_id": plan.run_id,
|
||||
"phase10z_contract_sha256": PHASE10Z_CONTRACT_SHA256,
|
||||
"window": batch.window,
|
||||
"batch_sha256": hashlib.sha256(batch.payload).hexdigest(),
|
||||
"batch_size": len(batch.payload),
|
||||
"deadline_seconds": batch.deadline_seconds,
|
||||
"created_fake_monotonic": start,
|
||||
"target_retained": False,
|
||||
"raw_transcript_persisted": False,
|
||||
"retry_allowed": False,
|
||||
"reconnect_allowed": False,
|
||||
"resume_allowed": False,
|
||||
"device_behavior_proven": False,
|
||||
})
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class OfflineFakeOutcome:
|
||||
receipt: EvidenceRecord
|
||||
output: EvidenceRecord
|
||||
classification: str
|
||||
exact_identity: bool
|
||||
batch_sha256: str
|
||||
trace: tuple[str, ...]
|
||||
fake_only: bool = True
|
||||
device_behavior_proven: bool = False
|
||||
|
||||
|
||||
def run_offline_fake_batch(
|
||||
plan: SessionPlan,
|
||||
adapter: OfflineFakeBatchAdapter,
|
||||
clock: OfflineFakeClock,
|
||||
evidence: OfflineFakeEvidenceStore,
|
||||
) -> OfflineFakeOutcome:
|
||||
"""Exercise Z end-to-end with exact built-in fakes and local evidence."""
|
||||
if type(adapter) is not OfflineFakeBatchAdapter or \
|
||||
type(clock) is not OfflineFakeClock or \
|
||||
type(evidence) is not OfflineFakeEvidenceStore or \
|
||||
adapter.clock is not clock:
|
||||
raise OfflineIntegrationError("offline fake boundary type is invalid")
|
||||
try:
|
||||
batch = build_passive_batch(plan)
|
||||
except PassiveContractError as error:
|
||||
raise OfflineIntegrationError("passive batch preparation failed") from error
|
||||
start = clock.monotonic()
|
||||
deadline = start + batch.deadline_seconds
|
||||
try:
|
||||
receipt = evidence.create_fake_consumed_receipt(plan, batch, start)
|
||||
except EvidenceFailure as error:
|
||||
raise OfflineIntegrationError("offline receipt creation failed") from error
|
||||
trace = ["RECEIPT_CREATED"]
|
||||
accumulator = PassiveResultAccumulator(batch)
|
||||
sanitized: dict[str, Any] | None = None
|
||||
opened = False
|
||||
failure: OfflineIntegrationError | None = None
|
||||
try:
|
||||
opened = True
|
||||
adapter.open_once()
|
||||
trace.extend(adapter.trace[-1:])
|
||||
if clock.monotonic() >= deadline:
|
||||
raise OfflineIntegrationError("deadline reached before fake send")
|
||||
adapter.send_one_batch(batch)
|
||||
trace.extend(adapter.trace[-1:])
|
||||
for _index in range(MAX_FAKE_EVENTS):
|
||||
event = adapter.next_event()
|
||||
trace.extend(adapter.trace[-1:])
|
||||
now = clock.monotonic()
|
||||
if event.kind == EVENT_DATA:
|
||||
if now >= deadline:
|
||||
raise OfflineIntegrationError("fake data reached deadline")
|
||||
accumulator.feed_supplied_chunk(event.data)
|
||||
continue
|
||||
if event.kind == EVENT_HARD_DEADLINE:
|
||||
if now < deadline:
|
||||
raise OfflineIntegrationError("fake deadline arrived early")
|
||||
sanitized = accumulator.seal_at_hard_deadline(True)
|
||||
break
|
||||
if event.kind == EVENT_REMOTE_EOF:
|
||||
raise OfflineIntegrationError("remote EOF is not completion")
|
||||
if event.kind == EVENT_BLOCKED:
|
||||
raise OfflineIntegrationError("blocked fake receive is invalid")
|
||||
if sanitized is None:
|
||||
raise OfflineIntegrationError("hard deadline result is missing")
|
||||
except Exception as error: # noqa: BLE001 - exact fake boundary normalization
|
||||
failure = OfflineIntegrationError("offline fake integration failed")
|
||||
failure.__cause__ = error
|
||||
finally:
|
||||
if opened:
|
||||
try:
|
||||
adapter.close_once()
|
||||
trace.extend(adapter.trace[-1:])
|
||||
except FakeAdapterError as error:
|
||||
if failure is None:
|
||||
failure = OfflineIntegrationError("offline fake close failed")
|
||||
failure.__cause__ = error
|
||||
if failure is not None:
|
||||
raise failure
|
||||
if sanitized is None or adapter.sent_sha256 is None:
|
||||
raise OfflineIntegrationError("offline fake result is incomplete")
|
||||
sanitized["phase10aa_fake_integration"] = {
|
||||
"offline_fake_only": True,
|
||||
"exact_builtin_adapter_required": True,
|
||||
"exact_builtin_clock_required": True,
|
||||
"receipt_created_before_fake_open": True,
|
||||
"one_fake_open": adapter.open_count == 1,
|
||||
"one_fake_batch_send": adapter.send_count == 1,
|
||||
"one_fake_close": adapter.close_count == 1,
|
||||
"logical_event_buffer_discarded": adapter.logical_event_buffer_discarded,
|
||||
"physical_memory_erasure_proven": False,
|
||||
"network_transport_present": False,
|
||||
"device_behavior_proven": False,
|
||||
}
|
||||
try:
|
||||
output = evidence.create_sanitized_output(plan, receipt, sanitized)
|
||||
except EvidenceFailure as error:
|
||||
raise OfflineIntegrationError("offline sanitized output failed") from error
|
||||
return OfflineFakeOutcome(
|
||||
receipt=receipt,
|
||||
output=output,
|
||||
classification=str(sanitized["classification"]),
|
||||
exact_identity=bool(sanitized["exact_identity"]),
|
||||
batch_sha256=adapter.sent_sha256,
|
||||
trace=tuple(trace),
|
||||
)
|
||||
Reference in New Issue
Block a user