"""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)