T10: gate review and first compression pass

All four gate criteria met. False Adaptation Rate 0/7 with 12 of 13 mechanical
mutations absorbed. 178 tests pass. TD-WP-0002 finished.

Fitness loop closed via F-0003: actor isolation was a property of scenarios
written to expose it, not of runs. Actors now carry an automatic private
marker and the runner examines all of them on every scenario, with two
permanent regressions behind it.

Compression - six abstractions removed, each declared and never used:
Verdict.SUSPICIOUS (a verdict no oracle could emit), Step.expect_refusal,
ActorIsolationError, World.seed, EvidencePack.latest, Trajectory.method.

F-0008: Temperature may be redundant. Crystallization was built without it
ever being consulted; measured stability of realization did the work, and is
observed rather than declared. Gated for removal alongside energy.py.

INTENT_CHANGED and REALIZATION_FAILED had never run. Both now have
purpose-built cases and a test that fails if a seventh outcome is added
without one.

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
This commit is contained in:
tegwick 2026-08-23 00:39:36 +02:00
parent 4f4219d8f7
commit 1b9860a8ee
40 changed files with 1074 additions and 46 deletions

View file

@ -10,10 +10,10 @@ from .oracles import Judgment, Oracle, Verdict, overall
from .provenance import InadmissibleProvenance, Provenance
from .runner import CollectorIndependenceError, Runner, RunResult
from .scenario import Scenario, Step, VerificationAsset
from .world import Actor, ActorIsolationError, Cast, World
from .world import Actor, Cast, World
__all__ = [
"Actor", "ActorIsolationError", "Cast", "Claim", "CollectorIndependenceError",
"Actor", "Cast", "Claim", "CollectorIndependenceError",
"DirectDriver", "EnergyEvent", "EnergyEventType", "EvidencePack",
"InadmissibleProvenance", "Invariant", "Judgment", "Observation", "Oracle",
"Provenance", "Realization", "RunResult", "Runner", "Scenario",

View file

@ -35,7 +35,6 @@ class Trajectory:
step_id: str
action_name: str
surface_id: str
method: str
target: str
fields: tuple[str, ...]
@ -43,7 +42,7 @@ class Trajectory:
return json.dumps(
{
"step": self.step_id, "action": self.action_name,
"surface": self.surface_id, "method": self.method,
"surface": self.surface_id,
"target": self.target, "fields": sorted(self.fields),
},
sort_keys=True,
@ -63,7 +62,6 @@ def capture(pack: Mapping[str, Any]) -> tuple[Trajectory, ...]:
step_id=obs["step_id"],
action_name=str(mechanics.get("action", "")).split("(")[0],
surface_id=obs["data"].get("surface", ""),
method=("POST" if obs["data"].get("surface") == "browser" else "CALL"),
target=target,
fields=tuple(sorted(fields)),
))

View file

@ -72,12 +72,6 @@ class EvidencePack:
def of_stratum(self, stratum: Stratum) -> list[Observation]:
return [o for o in self.observations if o.stratum is stratum]
def latest(self, kind: str) -> Observation | None:
for observation in reversed(self.observations):
if observation.kind == kind:
return observation
return None
def to_json(self) -> str:
payload = asdict(self)
payload["observations"] = [

View file

@ -3,6 +3,10 @@
An oracle reads only the independent observation snapshot. It has no access to
the actor, to the driver, or to what either of them believes happened.
`SUSPICIOUS` was removed at T10: no oracle could produce it, and a verdict
nothing can emit is a promise the framework does not keep. It returns, with an
identifier, if a mechanism ever needs it.
`INCONCLUSIVE` is a first-class outcome, not a failure mode of the framework. An
oracle that cannot support a judgment from the retained evidence must say so
rather than defaulting either way silently defaulting to PASS hides defects,
@ -21,7 +25,6 @@ from .intent import Claim, Invariant
class Verdict(str, Enum):
PASS = "PASS"
FAIL = "FAIL"
SUSPICIOUS = "SUSPICIOUS"
INCONCLUSIVE = "INCONCLUSIVE"
@ -101,6 +104,4 @@ def overall(judgments: list[Judgment]) -> Verdict:
return Verdict.FAIL
if Verdict.INCONCLUSIVE in verdicts:
return Verdict.INCONCLUSIVE
if Verdict.SUSPICIOUS in verdicts:
return Verdict.SUSPICIOUS
return Verdict.PASS

View file

@ -56,6 +56,24 @@ class Runner:
# -- independence guards ---------------------------------------------
def _isolation_violations(self) -> list[str]:
"""Does any actor hold another's canary?
Run on every scenario, not only on ones written to test isolation.
"""
canaries = {actor.canary: actor.id for actor in self._world.cast}
violations: list[str] = []
for actor in self._world.cast:
for key in actor.known_keys():
value = actor.recall(key)
owner = canaries.get(value) if isinstance(value, str) else None
if owner is not None and owner != actor.id:
violations.append(
f"actor {actor.id!r} holds the private marker of {owner!r} "
f"under key {key!r}"
)
return violations
def _assert_collector_independence(self, stratum: Stratum, collector: str) -> None:
"""S2 and S3 evidence may never be attributed to an actor.
@ -111,6 +129,12 @@ class Runner:
EnergyEvent(asset.id, run_id, EnergyEventType.EXECUTED).as_dict()
)
isolation = self._isolation_violations()
self._record(
pack, Stratum.JUDGMENT, self._observer.name, "actor_isolation",
{"violations": isolation, "actors": sorted(self._world.cast.actors)}, None,
)
judgments: list[Judgment] = []
scenario_sound = True
claims_by_step: dict[str, list] = {}
@ -166,7 +190,6 @@ class Runner:
"action": step.action.name,
"surface_used": realization.surface_id,
"refused_by_sut": refused,
"refusal_expected": step.expect_refusal,
"postcondition_met": postcondition_met,
},
step.id,
@ -176,7 +199,7 @@ class Runner:
# An action that was accepted but did not take effect did happen —
# and that is a statement about the system, judged below, not a
# reason to stop judging.
if refused and not step.expect_refusal:
if refused:
scenario_sound = False
# --- invariants after every step -----------------------------

View file

@ -17,7 +17,6 @@ class Step:
id: str
actor_id: str
action: SemanticAction
expect_refusal: bool = False
@dataclass(frozen=True, slots=True)

View file

@ -8,12 +8,20 @@ to be executed by the same process.
from __future__ import annotations
import uuid
from dataclasses import dataclass, field
from typing import Any, Iterator
class ActorIsolationError(Exception):
"""Raised when one actor is offered another actor's private state."""
def _canary() -> str:
"""A value private to one actor, unguessable and unique per construction.
Exists so that isolation is *observable*. Without it a run in which every
actor shares one memory store produces evidence indistinguishable from a
correct one the guarantee holds only in scenarios written to expose it,
which is no guarantee at all (F-0003).
"""
return f"canary-{uuid.uuid4().hex}"
@dataclass(slots=True)
@ -28,8 +36,13 @@ class Actor:
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)
canary: str = field(default_factory=_canary)
def __post_init__(self) -> None:
# Seeded automatically, on every actor, in every scenario. An isolation
# violation now leaves a trace whether or not anyone thought to look.
self._memory.setdefault("__canary__", self.canary)
def remember(self, key: str, value: Any) -> None:
self._memory[key] = value
@ -65,15 +78,14 @@ class Cast:
@dataclass(slots=True)
class World:
"""Initial state plus the handle to the system under test.
"""The handle to the system under test, plus the cast acting on it.
`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.
Replay comes from rebuilding the lab through `build_lab`, not from a seed
dict carried here the dict was written at T04, never read, and removed at
T10.
"""
id: str
sut: Any
sut_version: str
seed: dict[str, Any] = field(default_factory=dict)
cast: Cast = field(default_factory=Cast)