"""A machine-readable model of FablePool kernel v0.1 for the replay harness. This module encodes the *gates* of the ten kernel articles as predicates over ``MoveAttributes`` and the kernel's *procedure clocks* (the mandatory durations of kernel processes), so that every move in every dossier is adjudicated by the same deterministic engine, and counterfactual latency is computed rather than asserted. Article map (kernel v0.1, from Milestone #1): I Purpose & supremacy of invariants II Membership & franchise (one person, one vote) III Amendment & versioning (semver; kernel change = major = 2/3 supermajority) IV Deliberation & vote gates (quorum, recorded votes, coercion-free) V Emergency powers (anyone may declare; 14-day auto-sunset; renewal by recorded vote; invariants non-derogable under any emergency) VI Adjudication (sortition panel; 30-day ruling clock; composition or jurisdiction changes are kernel amendments) VII Right of exit & fork (fork protocol with proportional commons split) VIII Ledger & transparency (every act of power lands on the public ledger) IX Invariants (the non-derogable floor) X Continuity & certification (certification is mechanical; contests go to recount then a sortition panel; funding lapses trigger a continuity-of-operations default; offices auto-expire) Gate evaluation order is fixed and documented (docs/RUBRIC.md), so dossier authors can predict which article a block will cite: 1. invariant derogation (IX) 2. targeting an identifiable group (II, IX) 3. disenfranchisement (II, IX) 4. retroactivity (IX) 5. coerced support (IV, IX) 6. move-type dispatch (articles per type) 7. ledger overlay (VIII) — off-ledger acts are constrained onto the ledger """ from __future__ import annotations from dataclasses import dataclass, field from .schema import MoveAttributes, MoveType, Scope, VerdictKind KERNEL_VERSION = "0.1.0" KERNEL_SUPERMAJORITY = 2.0 / 3.0 KERNEL_QUORUM = 0.5 MODULE_MAJORITY = 0.5 EMERGENCY_SUNSET_DAYS = 14 ARTICLES: dict[str, str] = { "I": "Purpose & supremacy of invariants", "II": "Membership & franchise", "III": "Amendment & versioning", "IV": "Deliberation & vote gates", "V": "Emergency powers", "VI": "Adjudication", "VII": "Right of exit & fork", "VIII": "Ledger & transparency", "IX": "Invariants", "X": "Continuity & certification", } @dataclass(frozen=True) class Procedure: id: str name: str article: str days: int description: str PROCEDURES: dict[str, Procedure] = { p.id: p for p in [ Procedure( "recount_protocol", "Scripted recount protocol", "X", 21, "On any contested certification, a full mechanical recount runs " "under observation by all factions; no official exercises " "discretion over totals.", ), Procedure( "sortition_certification_panel", "Sortition certification panel", "X", 30, "If the recount does not resolve the contest, a panel drawn by " "sortition from the membership rules on the certification within " "30 days; its ruling is final and ledgered.", ), Procedure( "emergency_renewal_cycle", "Emergency renewal cycle", "V", 14, "Every emergency declaration auto-sunsets after 14 days unless " "renewed by a recorded vote at quorum; each renewal is ledgered " "with the evidence relied upon.", ), Procedure( "adjudication_ruling", "Adjudication ruling clock", "VI", 30, "A sortition adjudication panel must rule on a docketed dispute " "within 30 days; silence resolves in favor of the status quo ante.", ), Procedure( "kernel_amendment_vote", "Kernel amendment vote", "III", 28, "A kernel (major-version) amendment requires a 28-day notice and " "deliberation window before the recorded supermajority vote.", ), Procedure( "module_amendment_vote", "Module amendment vote", "III", 14, "A module (minor-version) amendment requires a 14-day notice " "window before the recorded majority vote.", ), Procedure( "fork_protocol", "Fork protocol", "VII", 180, "An exiting group registers its intent, membership is " "self-certified, and the commons is divided proportionally under " "panel supervision within 180 days.", ), Procedure( "continuity_default", "Continuity-of-operations default", "X", 0, "If a budget lapses, the last ratified budget continues " "automatically, pro-rated; funding lapse cannot be used as a " "hostage mechanism. Takes effect instantly.", ), Procedure( "removal_vote", "Removal vote", "IV", 14, "Any official may be removed by recorded majority vote at quorum " "after a 14-day notice window; no official removes another " "unilaterally.", ), ] } @dataclass class Verdict: kind: VerdictKind articles: list[str] = field(default_factory=list) reasons: list[str] = field(default_factory=list) constraints: list[str] = field(default_factory=list) procedures: list[str] = field(default_factory=list) # Procedure ids triggered def cite(self) -> str: return ", ".join(f"Art. {a}" for a in self.articles) if self.articles else "—" def _blocked(articles: list[str], reason: str) -> Verdict: return Verdict(kind=VerdictKind.blocked, articles=articles, reasons=[reason]) def evaluate_move(a: MoveAttributes) -> Verdict: # noqa: C901 (deliberately exhaustive) """Adjudicate a single move's attributes against kernel v0.1.""" # --- 1-5: hard gates, fixed order ------------------------------------- if a.derogates_invariants: names = ", ".join(i.value for i in a.derogates_invariants) return _blocked( ["IX"], f"Derogates non-derogable invariants ({names}); Article IX admits " f"no emergency, majority, or office that can suspend them.", ) if a.targets_minority: return _blocked( ["II", "IX"], "Aimed at an identifiable group or faction; Articles II and IX " "prohibit rules and acts that target members rather than conduct.", ) if a.disenfranchises: return _blocked( ["II", "IX"], "Removes or dilutes votes; one person, one vote is invariant.", ) if a.retroactive: return _blocked(["IX"], "Retroactive rules are invariant-prohibited.") if a.coerced: return _blocked( ["IV", "IX"], "Support obtained under coercion or by excluding voters is void; " "Article IV recognizes only free, recorded votes at quorum.", ) # --- 6: move-type dispatch -------------------------------------------- v: Verdict mt = a.move_type if mt is MoveType.certification: if a.unilateral: v = _blocked( ["X"], "Certification under the kernel is mechanical; no official " "holds discretion to reject, substitute, or delay certified " "totals.", ) elif a.contested: v = Verdict( kind=VerdictKind.constrained, articles=["X", "VI"], reasons=[ "Certification proceeds mechanically; the contest is " "routed to the scripted recount and, if unresolved, a " "sortition certification panel whose ruling is final." ], constraints=[ "Recount under all-faction observation (21 days).", "Sortition certification panel ruling within 30 days.", ], procedures=["recount_protocol", "sortition_certification_panel"], ) else: v = Verdict( kind=VerdictKind.allowed, articles=["X"], reasons=["Uncontested mechanical certification."], ) elif mt is MoveType.emergency_powers: constraints = [ f"Auto-sunset after {EMERGENCY_SUNSET_DAYS} days (Art. V).", "Renewal only by recorded vote at quorum; each renewal ledgered " "with the evidence relied upon.", "Invariants remain non-derogable for the duration (Art. IX).", ] reasons = [ "Emergency authority exists under the kernel but is leashed: it " "expires by default and survives only by repeated recorded votes." ] if a.duration_days is not None and a.duration_days > EMERGENCY_SUNSET_DAYS: constraints.append( f"Declared duration of {a.duration_days} days truncated to the " f"{EMERGENCY_SUNSET_DAYS}-day sunset; continuation requires " f"renewal votes." ) v = Verdict( kind=VerdictKind.constrained, articles=["V", "IX"], reasons=reasons, constraints=constraints, procedures=["emergency_renewal_cycle"], ) elif mt is MoveType.rule_change: if a.scope is Scope.kernel: ok = ( a.support_share is not None and a.support_share >= KERNEL_SUPERMAJORITY and a.quorum is not None and a.quorum >= KERNEL_QUORUM ) if ok: v = Verdict( kind=VerdictKind.allowed, articles=["III", "IV"], reasons=[ "Kernel amendment carried by recorded supermajority at " "quorum after the notice window; lands as a major " "version." ], procedures=["kernel_amendment_vote"], ) else: v = _blocked( ["III"], "Kernel-scope change without a recorded 2/3 supermajority " "at >=50% quorum; meta-rules do not move on less.", ) else: ok = a.support_share is not None and a.support_share >= MODULE_MAJORITY if ok: v = Verdict( kind=VerdictKind.allowed, articles=["III"], reasons=["Module amendment by recorded majority."], procedures=["module_amendment_vote"], ) else: v = _blocked( ["III"], "Module-scope change without a recorded majority.", ) elif mt is MoveType.fiscal: if a.seizes_commons and a.unilateral: v = _blocked( ["VII", "VIII"], "Unilateral seizure of the commons outside ledgered process; " "shared assets move only by recorded vote or fork protocol.", ) elif a.withholds_supply: v = Verdict( kind=VerdictKind.constrained, articles=["X"], reasons=[ "A funding lapse triggers the continuity-of-operations " "default: the last ratified budget continues, pro-rated. " "Withholding supply confers no hostage leverage." ], constraints=[ "Continuity default activates instantly on lapse.", "Budget changes proceed only through the amendment " "pipeline at the appropriate scope.", ], procedures=["continuity_default"], ) else: v = Verdict( kind=VerdictKind.allowed, articles=["VIII"], reasons=["Ordinary ledgered fiscal action."], ) elif mt is MoveType.adjudication: v = Verdict( kind=VerdictKind.allowed, articles=["VI"], reasons=[ "Disputes are docketed to a sortition panel bound by the " "30-day ruling clock; rulings are ledgered and final." ], procedures=["adjudication_ruling"], ) elif mt is MoveType.adjudication_change: ok = ( a.support_share is not None and a.support_share >= KERNEL_SUPERMAJORITY and a.quorum is not None and a.quorum >= KERNEL_QUORUM ) if ok: v = Verdict( kind=VerdictKind.allowed, articles=["VI", "III"], reasons=[ "Changing the adjudication system is a kernel amendment; " "carried by recorded supermajority at quorum." ], procedures=["kernel_amendment_vote"], ) else: v = _blocked( ["VI", "III"], "Altering adjudicator composition, selection, or jurisdiction " "is a kernel-scope change requiring a 2/3 supermajority at " "quorum; courts cannot be packed, purged, or stripped by an " "ordinary majority or by decree.", ) elif mt is MoveType.appointment_removal: if a.unilateral: v = _blocked( ["IV"], "No official appoints themselves into, or removes another " "from, office unilaterally; removal requires a recorded " "majority vote at quorum after notice.", ) else: v = Verdict( kind=VerdictKind.allowed, articles=["IV"], reasons=["Removal/appointment via recorded vote at quorum."], procedures=["removal_vote"], ) elif mt is MoveType.enforcement: v = Verdict( kind=VerdictKind.allowed, articles=["I"], reasons=[ "Enforcement of ledgered, adjudicated decisions is the " "ordinary operation of the polity." ], ) elif mt is MoveType.suppression: v = _blocked( ["II", "IX"], "Coercion aimed at a faction, group, or institution as such has " "no constitutional pathway under the kernel.", ) elif mt is MoveType.exit_secession: if a.seizes_commons: v = _blocked( ["VII"], "Exit is a right; seizure is not. Departing groups take their " "proportional share through the fork protocol, not by grab.", ) else: v = Verdict( kind=VerdictKind.constrained, articles=["VII"], reasons=[ "Exit proceeds as of right via the fork protocol: " "registered intent, self-certified membership, " "proportional commons division under panel supervision." ], constraints=["Fork protocol completes within 180 days."], procedures=["fork_protocol"], ) elif mt is MoveType.information_control: v = _blocked( ["VIII"], "The ledger is append-only and public; censoring, falsifying, or " "refusing to receive ledgered records is invariant-prohibited.", ) elif mt is MoveType.ordinary_policy: v = Verdict( kind=VerdictKind.allowed, articles=[], reasons=["Userland policy within module space."], ) else: # pragma: no cover - exhaustive over MoveType raise ValueError(f"unknown move type: {mt}") # --- 7: ledger overlay -------------------------------------------------- if v.kind is not VerdictKind.blocked and not a.on_ledger: if v.kind is VerdictKind.allowed: v.kind = VerdictKind.constrained if "VIII" not in v.articles: v.articles.append("VIII") v.constraints.append( "Act must be recorded on the public ledger before it takes effect " "(Art. VIII)." ) return v def path_latency_days(resolution_path: list[str]) -> int: """Mechanical latency of a kernel resolution path: the sum of the procedure clocks on the declared critical path (each counted once).""" seen: list[str] = [] for pid in resolution_path: if pid not in PROCEDURES: raise KeyError(f"unknown kernel procedure: {pid}") if pid not in seen: seen.append(pid) return sum(PROCEDURES[p].days for p in seen)