81 lines
2.4 KiB
Python
81 lines
2.4 KiB
Python
|
|
"""Stratified evidence — decision D-01.
|
||
|
|
|
||
|
|
S1 Surface how an action was performed (selectors, routes, payloads)
|
||
|
|
S2 Realization whether it happened, and through which surface
|
||
|
|
S3 Judgment whether that was correct
|
||
|
|
|
||
|
|
Each stratum has a different authority. Models may write S1. Nothing but an
|
||
|
|
independent Observer writes S2 or S3.
|
||
|
|
|
||
|
|
See docs/TestDriverClassificationDesign.md, Part A.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import json
|
||
|
|
from dataclasses import dataclass, field, asdict
|
||
|
|
from datetime import datetime, timezone
|
||
|
|
from enum import Enum
|
||
|
|
from typing import Any
|
||
|
|
|
||
|
|
|
||
|
|
class Stratum(str, Enum):
|
||
|
|
SURFACE = "S1"
|
||
|
|
REALIZATION = "S2"
|
||
|
|
JUDGMENT = "S3"
|
||
|
|
|
||
|
|
|
||
|
|
def _now() -> str:
|
||
|
|
return datetime.now(timezone.utc).isoformat()
|
||
|
|
|
||
|
|
|
||
|
|
@dataclass(frozen=True, slots=True)
|
||
|
|
class Observation:
|
||
|
|
"""One recorded fact, attributed to a stratum and a collector.
|
||
|
|
|
||
|
|
`collector` is never an actor for S2/S3 observations. The runner enforces
|
||
|
|
this; see runner._assert_collector_independence.
|
||
|
|
"""
|
||
|
|
|
||
|
|
id: str
|
||
|
|
stratum: Stratum
|
||
|
|
collector: str
|
||
|
|
step_id: str | None
|
||
|
|
kind: str
|
||
|
|
data: dict[str, Any]
|
||
|
|
at: str = field(default_factory=_now)
|
||
|
|
|
||
|
|
|
||
|
|
@dataclass(slots=True)
|
||
|
|
class EvidencePack:
|
||
|
|
"""Everything retained from one run.
|
||
|
|
|
||
|
|
The pack must be sufficient to replay the run and to diagnose a finding
|
||
|
|
without the original process. An assertion that cannot be supported from the
|
||
|
|
pack is an EVIDENCE_FAILURE, not a defect in the system under test.
|
||
|
|
"""
|
||
|
|
|
||
|
|
run_id: str
|
||
|
|
scenario_id: str
|
||
|
|
use_case_id: str
|
||
|
|
sut_version: str
|
||
|
|
started_at: str = field(default_factory=_now)
|
||
|
|
finished_at: str | None = None
|
||
|
|
observations: list[Observation] = field(default_factory=list)
|
||
|
|
verdicts: list[dict[str, Any]] = field(default_factory=list)
|
||
|
|
energy_events: list[dict[str, Any]] = field(default_factory=list)
|
||
|
|
provenance_index: dict[str, str] = field(default_factory=dict)
|
||
|
|
|
||
|
|
def record(self, observation: Observation) -> None:
|
||
|
|
self.observations.append(observation)
|
||
|
|
|
||
|
|
def of_stratum(self, stratum: Stratum) -> list[Observation]:
|
||
|
|
return [o for o in self.observations if o.stratum is stratum]
|
||
|
|
|
||
|
|
def to_json(self) -> str:
|
||
|
|
payload = asdict(self)
|
||
|
|
payload["observations"] = [
|
||
|
|
{**asdict(o), "stratum": o.stratum.value} for o in self.observations
|
||
|
|
]
|
||
|
|
return json.dumps(payload, indent=2, sort_keys=True, default=str)
|