T04: deterministic semantic kernel

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
This commit is contained in:
tegwick 2026-08-22 23:21:07 +02:00
parent 8da4c5bf7a
commit 04e9573b5a
42 changed files with 1533 additions and 20 deletions

63
src/testdriver/intent.py Normal file
View file

@ -0,0 +1,63 @@
"""The intent layer: what is supposed to be true.
Claims and invariants are *inputs* to a run and are frozen — decision D-02.
There is deliberately no code path by which adaptation, retry, or a learned
trajectory can modify them. That absence is what makes False Adaptation Rate = 0
an architectural property rather than a tuning target.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Callable, Mapping
from .provenance import Provenance, require_admissible
# A predicate over the independent observations gathered during a run.
# It receives the observation mapping and returns True when satisfied.
Predicate = Callable[[Mapping[str, object]], bool]
@dataclass(frozen=True, slots=True)
class Claim:
"""A statement that must hold at a specific point in a scenario."""
id: str
text: str
provenance: Provenance
predicate: Predicate
after_step: str
source_ref: str | None = None
def __post_init__(self) -> None:
require_admissible(self.provenance, f"Claim {self.id!r}")
@dataclass(frozen=True, slots=True)
class Invariant:
"""A statement that must hold after *every* step, not merely at one point."""
id: str
text: str
provenance: Provenance
predicate: Predicate
source_ref: str | None = None
def __post_init__(self) -> None:
require_admissible(self.provenance, f"Invariant {self.id!r}")
@dataclass(frozen=True, slots=True)
class UseCase:
"""Purposeful behaviour, described independently of mechanics."""
id: str
title: str
narrative: str
provenance: Provenance
claims: tuple[Claim, ...] = field(default_factory=tuple)
invariants: tuple[Invariant, ...] = field(default_factory=tuple)
source_ref: str | None = None
def __post_init__(self) -> None:
require_admissible(self.provenance, f"UseCase {self.id!r}")