This commit is contained in:
@@ -0,0 +1,229 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
"""Inactive Phase-1.0X transport orchestration with injected adapters only.
|
||||
|
||||
This module has no socket, DNS, CLI, target address, byte-command formatter or
|
||||
live prompt detector. It implements local exclusive evidence and orchestrates
|
||||
synthetic adapter boundaries under an injected monotonic clock.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any, Protocol
|
||||
|
||||
from phase10v_shsrv_collector_model import CollectorError, OfflineCollector
|
||||
from phase10w_shsrv_client_policy import (
|
||||
COLLECTOR_SHA256,
|
||||
SessionPlan,
|
||||
)
|
||||
|
||||
|
||||
POLICY_SHA256 = \
|
||||
"747d23c88f2722e8e8846599c3ac1dae3826eb3fca881caaad36b251f30f3592"
|
||||
MAX_BOUNDARY_CHUNKS = 64
|
||||
|
||||
|
||||
class SessionFailure(RuntimeError):
|
||||
"""Generic failure that never embeds adapter data or target values."""
|
||||
|
||||
|
||||
class EvidenceFailure(RuntimeError):
|
||||
"""Generic exclusive-evidence failure."""
|
||||
|
||||
|
||||
class MonotonicClock(Protocol):
|
||||
def monotonic(self) -> float: ...
|
||||
|
||||
|
||||
class InjectedSessionAdapter(Protocol):
|
||||
def open_once(self, plan: SessionPlan, remaining_seconds: float) -> None: ...
|
||||
def receive_boundary(
|
||||
self, boundary: str, remaining_seconds: float,
|
||||
) -> list[bytes]: ...
|
||||
def send_command_token(
|
||||
self, command: str, exact_path: str | None, remaining_seconds: float,
|
||||
) -> None: ...
|
||||
def close_once(self) -> None: ...
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class EvidenceRecord:
|
||||
path: Path
|
||||
size: int
|
||||
sha256: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SessionOutcome:
|
||||
receipt: EvidenceRecord
|
||||
output: EvidenceRecord
|
||||
classification: str
|
||||
exact_identity: bool
|
||||
|
||||
|
||||
class ExclusiveEvidenceStore:
|
||||
"""Write local JSON once with O_EXCL; never overwrite or clean up."""
|
||||
|
||||
def __init__(self, root: Path) -> None:
|
||||
self.root = root
|
||||
|
||||
@staticmethod
|
||||
def _encode(record: dict[str, Any]) -> bytes:
|
||||
return (json.dumps(
|
||||
record, sort_keys=True, separators=(",", ":"), ensure_ascii=True,
|
||||
) + "\n").encode("ascii")
|
||||
|
||||
def _create(self, filename: str, record: dict[str, Any]) -> EvidenceRecord:
|
||||
try:
|
||||
self.root.mkdir(parents=True, exist_ok=True)
|
||||
path = self.root / filename
|
||||
payload = self._encode(record)
|
||||
except (OSError, TypeError, ValueError) as error:
|
||||
raise EvidenceFailure("exclusive evidence preparation failed") from error
|
||||
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
|
||||
if hasattr(os, "O_BINARY"):
|
||||
flags |= os.O_BINARY
|
||||
descriptor = -1
|
||||
close_failure: OSError | None = None
|
||||
try:
|
||||
descriptor = os.open(path, flags, 0o600)
|
||||
offset = 0
|
||||
while offset < len(payload):
|
||||
written = os.write(descriptor, payload[offset:])
|
||||
if written <= 0:
|
||||
raise OSError("short local evidence write")
|
||||
offset += written
|
||||
os.fsync(descriptor)
|
||||
except OSError as error:
|
||||
raise EvidenceFailure("exclusive evidence creation failed") from error
|
||||
finally:
|
||||
if descriptor >= 0:
|
||||
try:
|
||||
os.close(descriptor)
|
||||
except OSError as error:
|
||||
close_failure = error
|
||||
if close_failure is not None:
|
||||
raise EvidenceFailure("exclusive evidence close failed") from close_failure
|
||||
try:
|
||||
reopened = path.read_bytes()
|
||||
except OSError as error:
|
||||
raise EvidenceFailure("exclusive evidence reopen failed") from error
|
||||
if reopened != payload:
|
||||
raise EvidenceFailure("exclusive evidence reopen mismatch")
|
||||
return EvidenceRecord(
|
||||
path=path,
|
||||
size=len(reopened),
|
||||
sha256=hashlib.sha256(reopened).hexdigest(),
|
||||
)
|
||||
|
||||
def create_consumed_receipt(
|
||||
self, plan: SessionPlan, monotonic_value: float,
|
||||
) -> EvidenceRecord:
|
||||
return self._create(f"{plan.run_id}.consumed.json", {
|
||||
"schema_version": 1,
|
||||
"status": "CONSUMED_BEFORE_ADAPTER_OPEN",
|
||||
"run_id": plan.run_id,
|
||||
"policy_sha256": POLICY_SHA256,
|
||||
"collector_sha256": COLLECTOR_SHA256,
|
||||
"window": plan.window,
|
||||
"deadline_seconds": plan.deadline_seconds,
|
||||
"created_monotonic": monotonic_value,
|
||||
"target_retained": False,
|
||||
"retry_allowed": False,
|
||||
})
|
||||
|
||||
def create_sanitized_output(
|
||||
self, plan: SessionPlan, receipt: EvidenceRecord,
|
||||
sanitized: dict[str, Any],
|
||||
) -> EvidenceRecord:
|
||||
return self._create(f"{plan.run_id}.sanitized.json", {
|
||||
"schema_version": 1,
|
||||
"status": "SANITIZED_OUTPUT_COMPLETE",
|
||||
"run_id": plan.run_id,
|
||||
"receipt_sha256": receipt.sha256,
|
||||
"raw_transcript_persisted": False,
|
||||
"result": sanitized,
|
||||
})
|
||||
|
||||
|
||||
def _remaining(clock: MonotonicClock, deadline: float) -> float:
|
||||
remaining = deadline - clock.monotonic()
|
||||
if remaining <= 0:
|
||||
raise SessionFailure("session deadline reached")
|
||||
return remaining
|
||||
|
||||
|
||||
def _feed_boundary(
|
||||
collector: OfflineCollector, chunks: list[bytes],
|
||||
) -> None:
|
||||
if not isinstance(chunks, list) or len(chunks) > MAX_BOUNDARY_CHUNKS:
|
||||
raise SessionFailure("adapter boundary is invalid")
|
||||
try:
|
||||
for chunk in chunks:
|
||||
collector.feed(chunk)
|
||||
except CollectorError as error:
|
||||
raise SessionFailure("collector rejected adapter input") from error
|
||||
|
||||
|
||||
def run_injected_session(
|
||||
plan: SessionPlan,
|
||||
adapter: InjectedSessionAdapter,
|
||||
clock: MonotonicClock,
|
||||
evidence: ExclusiveEvidenceStore,
|
||||
) -> SessionOutcome:
|
||||
"""Run one injected session; never retries and never performs live I/O."""
|
||||
start = clock.monotonic()
|
||||
deadline = start + plan.deadline_seconds
|
||||
receipt = evidence.create_consumed_receipt(plan, start)
|
||||
collector = OfflineCollector()
|
||||
open_attempted = False
|
||||
sanitized: dict[str, Any] | None = None
|
||||
primary_failure: SessionFailure | None = None
|
||||
try:
|
||||
open_attempted = True
|
||||
adapter.open_once(plan, _remaining(clock, deadline))
|
||||
chunks = adapter.receive_boundary(
|
||||
"INITIAL_PROMPT", _remaining(clock, deadline))
|
||||
_feed_boundary(collector, chunks)
|
||||
for command in plan.commands:
|
||||
adapter.send_command_token(
|
||||
command, plan.exact_literal_path, _remaining(clock, deadline))
|
||||
chunks = adapter.receive_boundary(
|
||||
f"AFTER_{command.upper()}", _remaining(clock, deadline))
|
||||
_feed_boundary(collector, chunks)
|
||||
_remaining(clock, deadline)
|
||||
expected_paths = (
|
||||
{plan.exact_literal_path}
|
||||
if plan.exact_literal_path is not None else set()
|
||||
)
|
||||
sanitized = collector.finalize(expected_paths)
|
||||
except Exception: # noqa: BLE001 - injected adapter boundary
|
||||
# Adapter exceptions are deliberately normalized; raw messages never
|
||||
# become evidence or user output.
|
||||
primary_failure = SessionFailure("injected session failed")
|
||||
finally:
|
||||
if open_attempted:
|
||||
try:
|
||||
adapter.close_once()
|
||||
except Exception: # noqa: BLE001 - injected adapter
|
||||
if primary_failure is None:
|
||||
primary_failure = SessionFailure("injected session close failed")
|
||||
if primary_failure is not None:
|
||||
raise primary_failure from None
|
||||
if sanitized is None:
|
||||
raise SessionFailure("sanitized result is missing")
|
||||
try:
|
||||
output = evidence.create_sanitized_output(plan, receipt, sanitized)
|
||||
except EvidenceFailure as error:
|
||||
raise SessionFailure("sanitized output creation failed") from error
|
||||
return SessionOutcome(
|
||||
receipt=receipt,
|
||||
output=output,
|
||||
classification=str(sanitized["classification"]),
|
||||
exact_identity=bool(sanitized["exact_identity"]),
|
||||
)
|
||||
Reference in New Issue
Block a user