Alice/Bob/Carol runs end to end, deterministically, replayable from seed. 16 tests pass, no third-party dependencies. - src/testdriver: intent, provenance, world, actions, drivers, observers, oracles, evidence, energy, scenario, runner - lab/minimal.py: the SUT, exposing the independent observation channel required by D-07 - evidence is stratified S1/S2/S3; Runner refuses to attribute S2/S3 to an actor; claims are frozen and provenance-checked at construction - missing evidence yields INCONCLUSIVE, which outranks PASS in the run verdict - EnergyEvents captured, no scoring (H-005 dormant) The observation channel records both stored state and an out-of-band enforcement probe; their disagreement is an invariant and is what detects an authorization defect that leaves the audit trail intact. A seeded RevokeIsCosmetic lab fails the run via both the claim and that invariant. Also closes TD-WP-0001-T02 (stack and commands now exist). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 1629012@bnt-lap001 Assistant-Session: 78d4fb13-8a1e-474b-87a3-9b9261c49a39
79 lines
2.4 KiB
Python
79 lines
2.4 KiB
Python
"""The situation layer: who is acting, and in what state of the world.
|
|
|
|
Actor isolation is structural. An Actor holds its own credentials and private
|
|
memory and has no reference to the Cast or to any sibling. The orchestrator may
|
|
know the whole world; actors must not learn anything merely because they happen
|
|
to be executed by the same process.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from typing import Any, Iterator
|
|
|
|
|
|
class ActorIsolationError(Exception):
|
|
"""Raised when one actor is offered another actor's private state."""
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class Actor:
|
|
"""An independent execution entity.
|
|
|
|
Deliberately holds no back-reference to the Cast or the World. An actor that
|
|
can enumerate its siblings can leak knowledge it was never given, and no
|
|
later check can reliably detect that it did.
|
|
"""
|
|
|
|
id: str
|
|
display_name: str
|
|
credentials: dict[str, str] = field(default_factory=dict)
|
|
session: dict[str, Any] = field(default_factory=dict)
|
|
_memory: dict[str, Any] = field(default_factory=dict, repr=False)
|
|
|
|
def remember(self, key: str, value: Any) -> None:
|
|
self._memory[key] = value
|
|
|
|
def recall(self, key: str, default: Any = None) -> Any:
|
|
return self._memory.get(key, default)
|
|
|
|
def known_keys(self) -> tuple[str, ...]:
|
|
return tuple(sorted(self._memory))
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class Cast:
|
|
"""The set of actors in a scenario. Known to the orchestrator, not to actors."""
|
|
|
|
actors: dict[str, Actor] = field(default_factory=dict)
|
|
|
|
def add(self, actor: Actor) -> Actor:
|
|
if actor.id in self.actors:
|
|
raise ValueError(f"duplicate actor id {actor.id!r}")
|
|
self.actors[actor.id] = actor
|
|
return actor
|
|
|
|
def __getitem__(self, actor_id: str) -> Actor:
|
|
return self.actors[actor_id]
|
|
|
|
def __iter__(self) -> Iterator[Actor]:
|
|
return iter(self.actors.values())
|
|
|
|
def __len__(self) -> int:
|
|
return len(self.actors)
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class World:
|
|
"""Initial state plus the handle to the system under test.
|
|
|
|
`seed` is everything needed to rebuild the initial state, so that a run can
|
|
be replayed from a known starting point rather than from wherever the
|
|
previous run happened to leave things.
|
|
"""
|
|
|
|
id: str
|
|
sut: Any
|
|
sut_version: str
|
|
seed: dict[str, Any] = field(default_factory=dict)
|
|
cast: Cast = field(default_factory=Cast)
|