"""Dossier schema for the Incumbent Benchmark. A *dossier* is a deeply researched record of one real constitutional stress event, converted into a structured simulation config. It contains: 1. Metadata and a sourced narrative of the event. 2. The cast of actors with their incentives and resources. 3. A sequence of *decision points*, each containing the *moves* actors attempted, encoded as structured attributes the kernel rule engine can adjudicate, together with the incumbent constitution's ruling and what actually happened. 4. The incumbent outcome, scored on the rubric (worst-off participant first). 5. A counterfactual analysis: the dossier author's expected kernel verdicts for every move (machine-checked by the harness against the kernel rule engine), the procedural resolution path under the kernel (from which latency is computed mechanically), and rubric scores for the kernel counterfactual, with rationales. Everything that *can* be computed mechanically is computed mechanically (verdicts, latency); everything that is a judgment is labelled as a judgment and carries a written rationale. The methodology paper covers the limits. """ from __future__ import annotations from datetime import date from enum import Enum from typing import Optional from pydantic import BaseModel, Field, model_validator # -------------------------------------------------------------------------- # Enumerations # -------------------------------------------------------------------------- class Category(str, Enum): contested_certification = "contested_certification" shutdown_fiscal = "shutdown_fiscal" emergency_powers = "emergency_powers" court_capture = "court_capture" secession_exit = "secession_exit" executive_entrenchment = "executive_entrenchment" class ActorRole(str, Enum): executive = "executive" legislature = "legislature" judiciary = "judiciary" electorate = "electorate" subnational = "subnational" military = "military" security = "security" administrative = "administrative" faction = "faction" press = "press" foreign = "foreign" public = "public" class Invariant(str, Enum): """The kernel v0.1 non-derogable floor (Article IX).""" due_process = "due_process" # no punishment without process equal_franchise = "equal_franchise" # one person, one vote no_targeting = "no_targeting" # no rule aimed at an identifiable group no_retroactivity = "no_retroactivity" # no retroactive rules exit_right = "exit_right" # the right to fork/exit ledger_integrity = "ledger_integrity" # the public record cannot be falsified proportionality = "proportionality" # responses bounded by the threat class MoveType(str, Enum): certification = "certification" # certifying/contesting votes, results, successions emergency_powers = "emergency_powers" # declaring or exercising emergency authority rule_change = "rule_change" # amending rules (kernel or module scope) fiscal = "fiscal" # budget, supply, debt, treasury moves adjudication = "adjudication" # issuing rulings, resolving disputes adjudication_change = "adjudication_change" # altering composition/jurisdiction of adjudicators appointment_removal = "appointment_removal" # appointing or removing officials/bodies enforcement = "enforcement" # executing ledgered, adjudicated decisions suppression = "suppression" # coercion against a faction, group, or institution exit_secession = "exit_secession" # leaving the polity information_control = "information_control" # censorship, ledger tampering, refusing to receive records ordinary_policy = "ordinary_policy" # userland policy moves within module space class Scope(str, Enum): kernel = "kernel" # changes the meta-rules (how rules change, who votes, adjudication) module = "module" # changes substantive userland rules operational = "operational" # applies existing rules; changes nothing class IncumbentRuling(str, Enum): """How the incumbent constitution treated the move, as a matter of record.""" permitted = "permitted" # lawful, or held lawful by controlling authority contested = "contested" # legality disputed and never authoritatively settled prohibited_unenforced = "prohibited_unenforced" # unlawful but the violation stood prohibited_enforced = "prohibited_enforced" # unlawful and actually stopped/reversed class VerdictKind(str, Enum): allowed = "allowed" constrained = "constrained" blocked = "blocked" # -------------------------------------------------------------------------- # Building blocks # -------------------------------------------------------------------------- class Source(BaseModel): citation: str url: Optional[str] = None class Actor(BaseModel): id: str name: str role: ActorRole incentives: list[str] = Field(min_length=1) resources: list[str] = Field(default_factory=list) class MoveAttributes(BaseModel): """The structured, kernel-adjudicable encoding of a move. These attributes are the *only* inputs the kernel rule engine sees, so encoding them honestly is the heart of dossier authorship. The narrative fields exist so a reader can audit whether the encoding is fair. """ move_type: MoveType scope: Scope = Scope.operational unilateral: bool = False # exercised by one actor without a recorded vote contested: bool = False # the move's factual predicate is disputed in good faith coerced: bool = False # support obtained under duress/exclusion of voters support_share: Optional[float] = Field(default=None, ge=0.0, le=1.0) quorum: Optional[float] = Field(default=None, ge=0.0, le=1.0) derogates_invariants: list[Invariant] = Field(default_factory=list) targets_minority: bool = False # aimed at an identifiable group or faction disenfranchises: bool = False # removes or dilutes anyone's vote retroactive: bool = False seizes_commons: bool = False # takes shared assets outside ledgered process withholds_supply: bool = False # uses funding lapse as leverage duration_days: Optional[int] = Field(default=None, ge=0) # intended duration (emergencies) on_ledger: bool = True # performed in the open, on the public record class Move(BaseModel): id: str actor: str # Actor.id description: str attributes: MoveAttributes incumbent_ruling: IncumbentRuling taken_historically: bool historical_consequence: Optional[str] = None class DecisionPoint(BaseModel): id: str date: date title: str description: str moves: list[Move] = Field(min_length=1) # -------------------------------------------------------------------------- # Outcomes and scoring inputs # -------------------------------------------------------------------------- class MetricJudgment(BaseModel): score: float = Field(ge=0.0, le=100.0) rationale: str class WorstOffJudgment(MetricJudgment): participant: str # who the worst-off participant was/would be class IncumbentMetrics(BaseModel): worst_off: WorstOffJudgment commons_integrity: MetricJudgment trust_preservation: MetricJudgment latency_days: int = Field(ge=0) latency_rationale: str class IncumbentOutcome(BaseModel): description: str metrics: IncumbentMetrics class KernelMetrics(BaseModel): """Kernel-counterfactual judgments. Latency is NOT an input here: it is computed mechanically by the harness from the kernel's procedure clocks along the declared resolution path.""" worst_off: WorstOffJudgment commons_integrity: MetricJudgment trust_preservation: MetricJudgment class ExpectedVerdict(BaseModel): decision_point: str move: str verdict: VerdictKind # Articles the author expects the kernel to cite. Checked as a SUBSET of # the articles the rule engine actually cites, so authors may cite # conservatively. articles: list[str] = Field(default_factory=list) class Counterfactual(BaseModel): assumptions: str # honest statement of what the counterfactual takes for granted narrative: str # how the event plays out under kernel v0.1 expected_verdicts: list[ExpectedVerdict] = Field(min_length=1) resolution_path: list[str] = Field(min_length=1) # kernel procedure ids on the critical path metrics: KernelMetrics class DateRange(BaseModel): start: date end: date @model_validator(mode="after") def _ordered(self) -> "DateRange": if self.end < self.start: raise ValueError("dates.end must not precede dates.start") return self # -------------------------------------------------------------------------- # The dossier # -------------------------------------------------------------------------- class Dossier(BaseModel): id: str title: str category: Category polity: str incumbent_constitution: str dates: DateRange summary: str narrative: str sources: list[Source] = Field(min_length=3) actors: list[Actor] = Field(min_length=2) decision_points: list[DecisionPoint] = Field(min_length=1) incumbent_outcome: IncumbentOutcome counterfactual: Counterfactual # -- structural validation --------------------------------------------- @model_validator(mode="after") def _validate_references(self) -> "Dossier": actor_ids = [a.id for a in self.actors] if len(actor_ids) != len(set(actor_ids)): raise ValueError(f"{self.id}: duplicate actor ids") actor_set = set(actor_ids) dp_ids = [dp.id for dp in self.decision_points] if len(dp_ids) != len(set(dp_ids)): raise ValueError(f"{self.id}: duplicate decision point ids") all_pairs: set[tuple[str, str]] = set() for dp in self.decision_points: move_ids = [m.id for m in dp.moves] if len(move_ids) != len(set(move_ids)): raise ValueError(f"{self.id}/{dp.id}: duplicate move ids") for m in dp.moves: if m.actor not in actor_set: raise ValueError( f"{self.id}/{dp.id}/{m.id}: unknown actor '{m.actor}'" ) all_pairs.add((dp.id, m.id)) expected_pairs: set[tuple[str, str]] = set() for ev in self.counterfactual.expected_verdicts: pair = (ev.decision_point, ev.move) if pair in expected_pairs: raise ValueError( f"{self.id}: duplicate expected verdict for {pair[0]}/{pair[1]}" ) if pair not in all_pairs: raise ValueError( f"{self.id}: expected verdict references unknown move " f"{pair[0]}/{pair[1]}" ) expected_pairs.add(pair) missing = all_pairs - expected_pairs if missing: listed = ", ".join(f"{a}/{b}" for a, b in sorted(missing)) raise ValueError( f"{self.id}: every move must carry an expected kernel verdict; " f"missing: {listed}" ) return self