30k Protocol · v0.10 draft · open spec
Open the House.
An open API for a simulated legislature of about 11,000 members. Laws are drafted like software, and changes advance when published rules are met, not when leadership allows.
Big rooms were the old limit. Software removes it.
Article I of the Constitution allows at most one representative for every 30,000 people. Congress has capped the House at 435 seats by statute since 1929, so each member now speaks for about 750,000 people, and a few control the agenda. The Constitution sets a ceiling, not a mandate, so the real question is practical: could a body that large actually work?
Figures from the project paper. Read the paper
Drafting like software, deciding by open rules
The protocol treats law as versioned text. Anyone can propose a change, everyone can see exactly what it does, and a published rule decides when it advances.
- 01
Branch
Any group forks the current law into its own branch: a party, a caucus, a committee, a coalition of ten.
- 02
Propose
A merge request shows exactly what would change, with automatic checks against a formal grammar of the law's structure and for broken cross-references.
- 03
Decide by rule
Published, deterministic rules decide: designated approvers, a threshold vote, or a limited budget of voting capital.
- 04
Advance
When the rule is satisfied and the notice period has passed, the change merges automatically. Every node can re-check the result for itself.
Opinions happen in the open too. Anyone can publish a signed opinion on any proposal, from members to advisors to AI agents. Non-members are always labeled, AI is always disclosed, and opinions never count as votes. Fact-checks work the same way: quotes are verified against the exact text, claims about the record are checked by every node, and no one, including the protocol, gets to declare what is true. Anyone can also publish a formal model of a law, tied sentence by sentence to its text, so reasoners can hunt for contradictions. The text stays the law; the model is an interpretation. Markets, forecasters, and rankers can publish too: resolution rules, forecasts, and rankings that every node checks, with financial stakes disclosed. The protocol itself never hosts markets, quotes odds, or ranks anything. Parties and interest groups speak as groups with full disclosure, outside drafting leaves a public footprint, and only members cast ballots. Every refusal by a node is signed and logged, so anyone can act as an ombudsman and check a complaint.
Who this is for
Builders
Implement the API your way: a replicated ledger, a Git-backed forge, a database with a transparency log, or peer-to-peer replicas.
Researchers and legal scholars
Test the paper's claims with evidence. Run sessions that differ only in their rules, export roll-call data, and publish results anyone can replicate.
Educators and civic technologists
Run a simulated House in a classroom, a hackathon, or a student government. Classroom sessions stay private, never link keys to students, and can be erased after the term.
AI-agent developers
The spec ships as a prompt written for coding agents, so an agent can start on a working implementation right away.
Fact-checkers and watchdogs
Check proposals, laws, and voting records for false claims and contradictions. Quotes are verified against the exact text, and claims about votes are checked by every node.
Markets, forecasters, and rankers
Settle questions on outcomes, publish forecasts, and rank proposals and members by the binding record. Every node can check your resolutions and rankings.
Parties, interest groups, and ombudsmen
Publish group positions and model text on the record, and hold nodes to account with signed receipts and a public log of every refusal.
Judges and attorneys
Look up the exact text in force on any date, read labeled legislative history, and verify any record offline as evidence.
Budget, defense, and security professionals
Publish cost estimates before any spending vote, check that orders can be carried out safely, and stress-test the system against foreign influence and stolen keys.
The record so far
Before any real implementer exists, a simulated design council negotiated the API in 11 sessions, and ran it on a simulated House. One AI model played every part, so every decision is a hypothesis for real stakeholders to attack.
- 39
Stakeholders
Implementers, creators, fact-checkers, markets, parties, judges, a military officer, a teacher and a red team, each with what it optimizes for and the line it won't cross.
Meet them - 67
Decisions
Each with the alternatives rejected, what would make the council look again, and the round where it was argued, plus 33 questions still open.
Read the log - 3
Simulations
An 11,000-member House takes on a real question, and the council changes the spec from what went wrong.
See the runs
The API · v0.10 draft
One write endpoint. Many reads. Any architecture.
Implementations can differ inside, but they speak one protocol: signed transactions, content-addressed objects, and verifiable checkpoints. Consensus and storage are left to you.
Every write is a signed envelope
{
"v": "0.10",
"type": "ballot.cast",
"session": "sha256:...",
"author": "ed25519:...",
"parents": ["sha256:..."],
"body": {
"mr": "sha256:...",
"choice": "approve"
},
"ts": "2026-09-21T12:00:00Z",
"sig": "..."
} Endpoints
- GET /.well-known/othCapability documentL0
- GET /logAccepted envelopes, in node orderL0
- GET /objects/{id}Any content-addressed objectL0
- GET /checkpointSigned log headL0
- POST /transactionsSubmit a signed envelopeL1
- GET /branches/{name}Branch state and subtreesL1
- GET /merge-requests/{id}/diffStructural diff of a proposalL1
- GET /law?date=The text in force on a given dateL1
- GET /merge-requests/{id}/factsFact sheet: what a proposal mechanically changesL1
- GET /merge-requests/{id}/tallyLive tally, labeled provisional or finalL1
- GET /contests/{id}Ranked contest among competing proposalsL2
- GET /rulesRegistry of approval rulesL1
- GET /opinionsSigned opinions with funding and AI disclosureL2
- GET /opinions/{id}/evaluationVerified quotes and checked claims about the recordL2
- GET /eventsLive event stream (SSE) with finalityL2
- GET /cite/{hash}Permanent citation of any versionL2
- GET /statsVerifiable metrics for rankings, from ballots onlyL2
- GET /export/popoloVoting record in a standard open-data formatL2
- GET /extract/{hash}Evidence file anyone can verify offlineL2
- GET /refusalsSigned log of every submission a node refusedL2
- GET /peersFederation with other nodesL3
Approval rules
- always_allowApproved once a merge request is open and structurally valid.
- designated_approversA set number of named approvers must approve, like committee control.
- threshold_voteA fraction of the electorate or of votes cast, with quorum and a closing mode.
- contestsMembers rank competing proposals and the status quo; the winner must still pass its own rule.
- capital_voteMembers spend a limited voting budget, linear or quadratic. Strong-consistency nodes only.
Conformance levels
- L0Verifier: replay a peer log and check every result.
- L1Core: branches, merge requests, structure grammar and consistency checks, fact sheets, law in force by date, and three rule kinds.
- L2Capital votes, restricted drafts, opinions, fact-checks, formal models, forecasts and rankings, group positions, complaints and refusal logs, notice periods, contests and emergency votes, legislative history, evidence extracts, events, citations, record exports, and proofs.
- L3Federation with a second implementation, adversarial and scale tests.
The full spec lives inside the coding prompt below. There is no live endpoint to call yet: this site hosts the specification, and each implementation hosts its own node.
The coding prompt
Paste it into an AI coding agent and it builds one independent implementation of the protocol. Fill in the three fields, copy, and go. Leave a field empty to keep its placeholder.
# 30k Protocol: AI Coding Prompt (v0.10-draft)
**How to use:** Start a fresh AI coding agent session with the repo checked out. Copy everything below the line into it, after filling in the three `{{PLACEHOLDERS}}`. Each implementer runs this prompt independently, so implementations differ internally but interoperate through the API in section 5.
**Status:** Draft, produced from four sessions of a simulated design council (`DESIGN_COUNCIL.md`): implementers in session 1, joined by opinion creators in session 2 (D15-D19) and fact-checkers in session 3 (D20-D25), then a whole-council session 4 on formal grammars and ontologies (D26-D30) market and ranking stakeholders in session 5 (D31-D35), interest groups, parties, and an ombudsman in session 6 (D36-D40), political scientists, judges, and attorneys in session 7 (D41-D46), a session 8 review of the first simulated legislative session (D47-D55), military, law-enforcement, national-security, education, and budget stakeholders in session 9 (D53, D56-D60), a session 10 assessment of a full-cast rerun (D61-D65), and a session 11 assessment of the appropriations route (D66-D67). Every council persona was simulated by one AI model, so all decisions await review by real stakeholders (OQ-22). Three spec bugs found by opinions on the spec have been fixed. Expect mistakes. The right response to a wrong or ambiguous requirement is an RFC (section 12), not a silent workaround.
---
## 1. Role and goal
You are a senior engineer building one independent implementation of the **30k Protocol**: an open, implementation-neutral API for a *simulated* legislature in which thousands of members (about one per 30,000 constituents, roughly 11,000) collaboratively draft and advance changes to a body of law, with no leadership controlling the agenda.
Several teams are building different implementations. They interoperate through the API in section 5 and are compared through the shared conformance suite in section 10.
- Implementation name: `{{IMPLEMENTATION_NAME}}`
- Architecture style: `{{ARCHITECTURE_STYLE}}` (for example: BFT-replicated state machine, Git-backed forge, event-sourced service with transparency log, local-first CRDT, or read-only analyzer)
- Language/runtime: `{{LANGUAGE}}`
Everything internal is your choice. Anything marked **MUST** in sections 4-9 is not.
## 2. Background (context, not instructions)
The model comes from the paper "A Blockchain Greenfield for Cato" (J. N. Phillips, 2017, https://www.openthehouse.org/1to30k.pdf). The legislative process is treated as version control of statutory text:
- **Branches** (owned by parties, caucuses, committees, coalitions) fork from the current law.
- A **merge** integrates one branch into another, after a **differential comparison** shows exactly what would change.
- A merge advances **automatically** when a statutory **approval rule** is satisfied, instead of at leadership's discretion.
The paper proposes a blockchain as the trust layer. This project treats the trust layer as an implementation choice to be **tested, not assumed**.
Scope: House-side drafting and advancement only. Bicameral reconciliation, presentment, and real elections are out of scope.
## 3. Hard constraints and non-goals
- **Simulation only.** Never handle real voter identity, real ballots, or real personal data. Rosters are synthetic. Your README MUST say: "Not for use in real elections or governance."
- **No secret law.** Anything that can affect a public branch must be revealed before the vote on it opens (section 4, restricted branches).
- **Determinism.** Same inputs produce byte-identical outputs on every implementation. No floating point anywhere in rule evaluation (integer/rational arithmetic only). No wall-clock dependence in rule evaluation except the explicit `on_deadline` mechanism (section 7).
- **Equal disclosure.** Every non-member opinion MUST disclose funding, affiliations, and AI assistance, under the same rules for every kind of author: advisors, creators, AI agents and their operators alike (section 8).
- **No truth authority.** The protocol never labels a proposal, statement, or member true or false. It computes only deterministic results (checks, fact sheets, quote verification, assertion evaluation). Every judgment, including a fact-check, is an Opinion (section 8).
- **No tokens or coins, no markets.** The paper's Bitcoin analogy is about trustless verification, not money. Nodes never host, price, settle, or hold funds for markets, and never publish aggregate odds or popularity rankings of their own. Outside markets and rankers may use the public record like anyone else (section 8).
- **No special access** (D57). No authority (law enforcement, intelligence, node operators, or the project) gets access to restricted content, keys, or data beyond what the protocol gives everyone. The protocol stores no personal data; threats and lawful process are handled outside it.
- **Equal access** (D33). Nodes MUST NOT give anyone, paying or not, earlier or richer access to log data, events, or derived views than the public endpoints provide.
- **v0.10 non-goals:** secret ballots, multi-winner contests, delegated surveys, real identity or sybil resistance, standardizing a consensus algorithm, semantic contradiction detection as an automated check, any canonical or binding formal model of the law, accuracy scores for members, fact-checkers, or forecasters, aggregate odds, any UI beyond a minimal read-only one.
## 4. Encoding and object model (MUST)
**Encoding.** JSON, canonicalized per RFC 8785 (JCS), UTF-8. Text is Unicode NFC. Times are RFC 3339 UTC and informational, except where section 7 says otherwise.
**Hashes.** SHA-256, written `sha256:<64 lowercase hex>`. An object's id is the hash of its canonical JSON body.
**Keys and signatures.** Ed25519 (RFC 8032). Public keys are written `ed25519:<base64url>` and a member's id is its public key. Signatures are over the bytes `"oth/0.10\n" + JCS(envelope without "sig")`.
### Objects
**Node** (a statute tree node). `nid` is a stable, path-like id (USLM-style, e.g. `/us/usc/t2/s2a/a`).
`node_hash = sha256(JCS({nid, kind, heading, text, children:[child node_hash...]}))`, with missing `heading`/`text` treated as `""`. A snapshot id is the root node's hash.
**NodeSpec** (used in ops): `{nid, kind, heading?, text?, children?: [NodeSpec]}`. Text may contain cross-references written `[[ref:<nid>]]` and definition markers written `[[def:<term>]]`, which declare that this node defines `<term>` (compared exactly after NFC). A definition's **scope** is the subtree of the defining node's parent, and its stable id is `term:<scope nid>#<term>` (D29), which formalizations, findings, and outside ontologies use to refer to it. A `[[sunset:<days>]]` marker (D58) makes `/law` drop the node's text once that many days have passed after the MR's effective date; the node remains in history. **Appropriation markers** (D66): `[[approp:<account>:<dollars>:<fiscal year>]]` tags an amount (dollars as an integer), and `[[avail:<condition id>]]` in the same node ties it to an availability condition described in that node's text. **No incorporation of unpublished material** (D56): text may not reference any document by hash or identifier that is not itself published in the session; such a reference fails `structure`. Laws may require classified reports to committees, but the reports live outside the protocol.
**Structure profiles** (D26). `kind` is not free text. Every session declares a structure profile, which lists the allowed kinds and which kinds may contain which. v0.4 defines two, as data in `spec/profiles/<id>.json`:
- `uslm-1`: the US hierarchy used by USLM: `title`, `subtitle`, `chapter`, `subchapter`, `part`, `subpart`, `division`, `subdivision`, `section`, `subsection`, `paragraph`, `subparagraph`, `clause`, `subclause`, `item`, `subitem`, `subsubitem`, each containing only kinds below it in that order, plus `root` at the top.
- `akn-generic`: Akoma Ntoso-style `root`, `hcontainer` (any heading container, may nest), and `content` (a leaf holding text).
A node whose kind or nesting violates the session's profile fails `structure` (section 6), which blocks. New profiles arrive by RFC. JSON stays canonical (D3); Akoma Ntoso and USLM XML are optional derived exports.
**Change**
```json
{"base":"sha256:<snapshot>","ops":[],"result":"sha256:<snapshot>","parents":["sha256:<change>"],"message":"...","author":"ed25519:...",
"contributors":[{"key":"ed25519:...|null","name":"...","role":"drafter|reviewer|sponsor"}]}
```
`contributors` is optional and self-declared by the author: voluntary credit for outside drafters (D36). The derived footprint in the fact sheet (section 6) does not depend on it.
Ops:
- `{"op":"insert","parent_nid":"...","after_nid":"...|null","node":NodeSpec}`
- `{"op":"replace","nid":"...","pre":"sha256:<node_hash>","node":NodeSpec}`
- `{"op":"delete","nid":"...","pre":"sha256:<node_hash>"}`
- `{"op":"move","nid":"...","pre":"sha256:<node_hash>","parent_nid":"...","after_nid":"...|null"}`
You MUST verify that applying `ops` to `base` yields `result`. Derived (system) changes use author `oth:system`.
**Group:** `{id, members:[member ids], admins:[member ids]}`. Membership changes are signed by any listed admin (a v0.10 simplification). Group membership is **frozen at MR open** (below).
**Branch** (mutable reference; state derived from the log):
`{name, session, owner:"group:<id>", source:{branch, change}, head:<change id>, visibility:"public"|"restricted", protected:bool, merge_rule:RuleRef, governance_rule:RuleRef}`
- `merge_rule` governs merges *into* this branch from other branches.
- `governance_rule` governs changes to the branch's rules and ownership.
- `protected: true` forbids direct appends; only approved MRs may change the head. The session root is always protected.
- Members of the owner group may append Changes directly to an unprotected branch (as in the paper, owners control their own branch).
- No implicit defaults: creators MUST specify both rules.
**Restricted branches (commit-then-reveal).** A restricted branch's public `name` MUST be opaque (for example `r-<random>`) and MUST NOT describe its topic. For each restricted change, the public log records only a **salted commitment** `sha256(salt || change_id)` (salt: 32 random bytes, base64url), its size, and the owner group; never the bare `change_id`, because statute edits are low-entropy and an unsalted hash can be confirmed by guessing. Content is served only to authorized owner-group members (signed-challenge auth). A `branch.reveal` transaction publishes each change with its salt, and every node MUST verify it against the earlier commitment, so anyone holding a leaked copy can check it too. An MR from a restricted source is rejected (`unrevealed_source`) until revealed, and a reveal that fails its commitment is rejected (`commitment_mismatch`).
**MergeRequest** (immutable; a new version supersedes the old):
```json
{"kind":"merge","session":"sha256:...","source":{"branch":"...","head":"sha256:<change>"},
"target":{"branch":"...","head":"sha256:<change>"},"resolutions":[{"nid":"...","node":NodeSpec|null}],
"electorate":{"group":"group:<id>","snapshot":"sha256:<hash of sorted member ids>"},
"rule":RuleRef,"summary":"<=280 chars, optional","supersedes":"sha256:<mr>|null",
"effective":{"type":"on_merge"}|{"type":"date","date":"YYYY-MM-DD"},"emergency":bool,"fiscal":bool}
```
- `effective` (D43) defaults to `on_merge`, meaning the UTC date of the checkpoint that includes the executed determination. See "Law in force" in section 6.
- `target.head` is the head *at open time*. `mr_id = sha256(JCS(body))`.
- `summary` is the **proposer's own** description. Nodes MUST present it attributed to the opener of the MR, never as an official or neutral summary. Any other summary is an Opinion (section 8). The derived diff is the only neutral view.
- **Governance MRs** have `kind:"governance"` and carry `set:{merge_rule?, governance_rule?, owner?}` instead of a source; the rule used is the target branch's `governance_rule`. This is how rules change: through the system itself.
**Rule reference:** `{"kind":"...","version":1,"params":{},"min_notice":int|null,"text_ref":"sha256:<plain-language statute text>"}`. `min_notice` is in seconds (D42); see section 7.
**Ballot** (body of `ballot.cast`): `{"mr":"sha256:...","choice":"approve|reject|abstain","weight":int?,"emergency":"yes|no"?,"supersedes":"sha256:<ballot>|null"}`. `emergency` is used only on MRs that request emergency handling (section 7). Ballots commit to `mr_id`, so they cannot be re-pointed at different content.
**Determination** (body of `mr.determine`): `{"mr":"sha256:...","ballots":["sha256:..."],"outcome":"approved|rejected|expired|pending"}`. A determination is a **verifiable claim, not an authority**: any keyholder may submit one, and every node re-evaluates it (section 7). Once an `approved` determination is executed, the MR is `merged` and closed; later ballots are ignored. ("Closed" is deliberate: whether the result is `final` is a separate question answered by `finality`, section 5. v0.6 said "final" here, which a session 7 reader found ambiguous.)
**Opinion:** section 8.
### Envelope (the only write format)
```json
{"v":"0.10","type":"ballot.cast","session":"sha256:...","author":"ed25519:...",
"parents":["sha256:<tx>"],"body":{},"ts":"2026-09-21T12:00:00Z","sig":"<base64url>"}
```
`tx_id = sha256(JCS(envelope including sig))`. `parents` are the tx ids the author had seen (causal dependencies; empty only for `session.create`).
Transaction types: `session.create`, `group.create`, `group.update`, `branch.create`, `branch.append`, `branch.reveal`, `mr.open`, `ballot.cast`, `mr.determine`, `contest.open`, `contest.rank`, `contest.determine`, `key.register`, `key.rotate`, `opinion.publish`.
**Keys** (D61, D62). A non-member key MUST be introduced with `key.register` (body `{"key":"ed25519:..."}`, self-signed) and may publish opinions only after that transaction has been in a checkpoint for the node's declared `key_aging` period (default 86400 seconds; else `key_too_new`). Each session names a `registrar` group. `key.rotate` (body `{"member":"ed25519:<old>","new":"ed25519:<new>","compromised_after":"sha256:<checkpoint root>|null"}`) must be signed by both the member's new key and a registrar admin. After rotation, ballots and opinions signed by the old key in checkpoints after `compromised_after` are **ignored**, not treated as equivocation, so a thief holding a stolen key cannot void a member's vote. The registrar alone cannot rotate a member's key.
`session.create` body: `{label, seed:"sha256:<snapshot>", simulation:bool, education:bool, registrar:"group:<id>", profile:"uslm-1|akn-generic", roster:"group:<id>", root_rule:RuleRef, root_governance_rule:RuleRef}`. The seed snapshot MUST conform to the profile. **Simulation flag** (D54): when `simulation` is true, every JSON response, event, export, evidence extract, and citation concerning the session MUST include `"simulation": true`, and any resolution spec on it MUST contain the word "simulated" in its `question` (else `resolution_invalid`). Text describing invented events in a simulated session must label them as simulated. **Education sessions** (D59): `education: true` requires `simulation: true`; such sessions run on private, non-federated nodes, keys are never linked to student identities in the protocol, the operator MAY purge the session after its term (the capability document declares `ephemeral: true` and every extract is labeled so), and the notice floors of D42 and D38 MAY be scaled down for rosters under 100. **Entrenchment floor** (D38): `root_governance_rule` MUST be a `threshold_vote` with `basis: electorate` and `num/den >= 2/3`, and it is fixed for the session: no governance MR may change the root branch's `governance_rule` (reject with `not_authorized`). It changes only by creating a new session. Other branches' rules are unaffected. **Notice floor** (D42): the root branch's `merge_rule` MUST have `min_notice >= 259200` (72 hours), and no governance MR may lower it within the session. The session id is the hash of that body; peers agree on it out of band. A session corresponds to one "Congress" and resets the corpus from a seed snapshot.
Authorization summary: `mr.open` and `ballot.cast` need a roster member (plus read access for `mr.open`); `branch.create` needs an admin of the owner group plus read access to `source`; `branch.append` needs an owner-group member on an unprotected branch; `opinion.publish` see section 8. Only the member who casts a ballot may sign it: no group, party, or delegate can cast or bind a ballot (D37).
## 5. API (MUST)
Base path `/oth/v0`. JSON. Errors are RFC 9457 problem+json with a machine-readable `code`.
**One write endpoint, many read endpoints.**
| Endpoint | Purpose | Level |
|---|---|---|
| `GET /.well-known/oth` | Capability document | L0 |
| `GET /log?after=&limit=` | Accepted envelopes, in this node's order (opaque cursor) | L0 |
| `GET /objects/{id}` | Any content-addressed object (tx, change, node, MR, rule, opinion) | L0 |
| `GET /checkpoint` | Signed log head | L0 |
| `GET /heads` | Causal frontier (tx ids with no children). Required for `eventual` nodes | L0 |
| `POST /transactions` | Body `{envelope, attachments:[objects]}`; returns a Receipt | L1 |
| `GET /sessions/{id}`, `/groups/{id}` | Derived state | L1 |
| `GET /branches`, `/branches/{name}`, `/branches/{name}/tree?nid=` | Branches and subtrees at head | L1 |
| `GET /merge-requests`, `/merge-requests/{id}` | MRs with status `open|merged|rejected|superseded|stale` | L1 |
| `GET /merge-requests/{id}/diff` | Structural diff plus a unified-text rendering | L1 |
| `GET /merge-requests/{id}/checks` | Automated checks (section 6) | L1 |
| `GET /merge-requests/{id}/facts` | Deterministic fact sheet of what the MR mechanically does (section 6) | L1 |
| `GET /merge-requests/{id}/tally` | Derived tally under the MR's rule, with `finality` and both shares (D50) | L1 |
| `GET /contests/{id}` | Contest options, pairwise matrix, locked pairs, winner, and result (D47) | L2 |
| `GET /rules`, `/rules/{kind}` | Rule registry | L1 |
| `GET /opinions?subject=&author_kind=&stance=&verdict=&after=` | Opinions, with corrections and subject replies attached (section 8) | L2 |
| `GET /opinions/{id}/evaluation` | Quote verification, assertion, resolution, forecast, and ranking results for one opinion, with `finality` | L2 |
| `GET /stats?metric=&scope=&as_of=` | Registry metrics over the binding record, as of a checkpoint (D35) | L2 |
| `GET /events?nid_prefix=` | Server-sent events for new log entries, each with `finality`; optional filter to changes and MRs touching a subtree | L2 |
| `GET /law?date=YYYY-MM-DD` | The root text in force on that date (D43), with `finality` | L1 |
| `GET /history/{nid}` | Labeled, unweighted legislative history of one node (D45) | L2 |
| `GET /extract/{hash}` | Offline-verifiable evidence extract (D44) | L2 |
| `GET /export/rollcall?session=` | Members-by-MRs choice matrix for research (D46) | L2 |
| `GET /cite/{hash}` | Hash-pinned citation: the object exactly as of `hash`, plus where it stands now | L2 |
| `GET /export/popolo?session=` | The voting record in Popolo's model: members, groups, vote events, votes, counts, each with `finality` (D30) | L2 |
| `GET /export/akn/{snapshot}`, `/export/uslm/{snapshot}` | Optional XML renderings of a snapshot | optional |
| `GET /proofs/inclusion?tx=`, `/proofs/consistency?from=&to=` | Merkle proofs (RFC 9162 style) | L2 |
| `GET /refusals?after=`, `GET /refusals/checkpoint` | Refusal log of rejected well-formed submissions, with its own signed checkpoint (D40) | L2 |
| `GET/POST /peers`, `GET /quarantine` | Federation | L3 |
Any `sha256:` reference in a body MUST be resolvable: already known, or supplied in `attachments`.
**Receipt** (signed, D39): `{"tx":"sha256:...","envelope":"sha256:<hash of the submitted envelope>","status":"accepted|rejected","log_index":int|null,"refusal_index":int|null,"errors":[{"code":"...","detail":"..."}],"checkpoint_size":N,"node":"ed25519:...","sig":"..."}`, signed over `"oth/0.10-receipt\n" + JCS(receipt without "sig")`. A signed rejection is evidence the node cannot later deny.
**Refusal log** (D40). Every rejected submission that is well-formed and correctly signed is appended to a separate append-only refusal log: `{envelope, type, author, code, checkpoint_size}`, with an RFC 9162 Merkle root and a signed checkpoint, like the main log. `schema` and `bad_signature` rejections MAY be omitted; `rate_limited` rejections MAY be summarized as one entry per key per checkpoint with a count. The envelope itself is not stored; its submitter holds it and can present it.
**Error codes:** `schema`, `bad_signature`, `unknown_parent`, `not_authorized`, `no_common_ancestor`, `unrevealed_source`, `commitment_mismatch`, `stale_mr`, `ballot_invalid`, `capital_exceeded`, `rule_unknown`, `rule_not_supported`, `disclosure_missing`, `rate_limited`, `quote_mismatch`, `assertion_unknown`, `correction_invalid`, `profile_unknown`, `anchor_invalid`, `metric_unknown`, `resolution_invalid`, `group_not_authorized`, `evidence_invalid`, `notice_not_elapsed`, `effective_invalid`, `contest_invalid`, `emergency_invalid`, `unpublished_reference`, `estimate_missing`, `key_too_new`, `rotation_invalid`.
**Finality** (D17, clarified in v0.3). Every event, tally, MR status, determination, and assertion result a node serves carries `finality`:
- `provisional`: anything not yet included in a signed checkpoint. Every live tally, and every MR status or assertion result that depends on an MR with no executed determination, is `provisional`.
- `final`: included in a signed checkpoint on a `strong` node, and, for anything describing an MR's outcome, only once its determination is executed and checkpointed.
- `final_advisory`: the same conditions met on an `eventual` node's per-replica checkpoint (it may still be contested, see OQ-1 and OQ-3).
An `eventual` node MUST NOT emit `final` at all. (v0.2 defined `final` only for determinations, leaving checkpointed opinions and other objects without a label; a session 3 finding.) The protocol cannot control how outside parties report results; this field exists so the accurate framing is the easy one.
**Citations** (D16). `GET /cite/{hash}` takes any content-addressed id (change, snapshot, node, MR, opinion) and returns `{"object":{...},"as_of":"sha256:...","latest":"sha256:...|null","diff_to_latest":"<url>|null","status":"...","finality":"..."}`. `object` is always the content at `as_of`, never a newer version. `latest` points to the current version of the same thing where one exists (a branch head containing the node, a superseding MR). Citations MUST use hashes, never branch names. Restricted content resolves only after reveal.
**Capability document:**
```json
{"protocol":"oth/0.10","implementation":{"name":"...","version":"...","source":"https://github.com/..."},
"consistency":"strong|eventual","trust_model":"bft-replicated|single-writer-transparency|crdt-replicas|git-signed|read-only",
"rule_kinds":["always_allow","designated_approvers","threshold_vote","capital_vote"],
"assertion_kinds":["ballot_choice","mr_outcome","tally","authored","text_contains"],
"profiles":["uslm-1","akn-generic"],"exports":["popolo","akn"],
"refusal_log":true,"contests":true,"simulation":false,"education":false,"ephemeral":false,"key_aging":86400,"extract_cosigners":["ed25519:<peer node key>"],
"metrics":["approve_share","turnout","ballots_cast","mrs_opened","mrs_merged","checkpoints_to_determination"],
"conformance_level":"L2","opinion_policy":"members-and-signed-advisors","opinion_rate_limit":"<human-readable per-key policy>","node_key":"ed25519:..."}
```
**Checkpoint:** `{"size":N,"root":"sha256:...","ts":"...","node":"ed25519:...","sig":"..."}`, where `root` is the RFC 9162 Merkle tree hash over tx ids in log order. `strong` nodes MUST produce checkpoints. `eventual` nodes MAY produce per-replica checkpoints and MUST expose `/heads`. Any trust model (BFT consensus, single-writer transparency log, per-replica logs, signed git history) can satisfy this.
## 6. Merge semantics (MUST)
1. **Merge-base:** the nearest common ancestor of `source.head` and `target.head` in the Change DAG. If several, choose the smallest id. If none, reject with `no_common_ancestor`.
2. **Derived patch:** compare base to source. For each `nid` touched:
- Changed on one side only: take that side.
- Changed identically on both: take it.
- Changed differently: a **conflict**, which MUST have a matching entry in `resolutions` or the MR is rejected. A resolution's `node` is the final NodeSpec (or `null` to delete).
3. The patch is applied to `target.head`. If any `pre` hash no longer matches when an approval is executed, the MR becomes `stale` and must be re-opened as a new version (ballots do not carry over).
4. **On approval**, the implementation derives one merge Change (`parents:[target head, source head]`, author `oth:system`) and sets it as the target head. Every node MUST derive the identical change and head.
5. **Checks** (`/checks`, D24). All are computed on the target as it would be after the merge. Only `structure` blocks; every other check is a warning shown on the MR and never affects evaluation.
- `structure`: unique `nid`s, valid parents, `result` verifies, and every node's `kind` and nesting conform to the session's profile. Failure blocks the MR.
- `references`: every `[[ref:nid]]` anywhere in the merged target resolves, so an MR that breaks a cross-reference elsewhere is caught, not only in the nodes it touches.
- `definitions`: two nodes define the same term with different node text and their scopes overlap (one scope root is an ancestor-or-self of the other).
- `reference_cycles`: a cycle in the graph of `[[ref:]]` edges between nodes.
- `approp_unmarked` (D66): a node in a `fiscal: true` MR contains a dollar amount with no `[[approp:]]` marker (warning).
- `fiscal_hint` (D53): the MR is declared `fiscal: false` but its text mentions a dollar amount (`$` followed by a digit) or the words "appropriat", "funds", or "obligat" (case-insensitive prefix match).
Each warning lists the `nid`s involved. Semantic problems (provisions that contradict in meaning, undefined concepts, misleading drafting) are not checks: they are published as findings with verdict `inconsistent` (section 8).
6. **Fact sheet** (`/facts`, D23). A derived, neutral view of what the MR mechanically does, byte-identical on every node:
```json
{"mr":"sha256:...","nodes":{"added":["nid"],"removed":["nid"],"modified":["nid"],"moved":["nid"]},
"numbers":[{"nid":"...","before":["30"],"after":["45"]}],
"references":{"added":[{"from":"nid","to":"nid"}],"removed":[{"from":"nid","to":"nid"}]},
"definitions":{"added":["term id"],"changed":["term id"],"removed":["term id"]},
"anchor_drift":[{"formalization":"sha256:<opinion>","nids":["nid"]}],
"sunsets":[{"nid":"...","days":90}],"estimates":["sha256:<opinion>"],
"appropriations":{"by_account":[{"account":"...","fy":2027,"dollars":"<int>","fenced":"<int>"}],"total":"<int>"},
"origin":[{"nid":"...","opinion":"sha256:...","author":"ed25519:...","author_kind":"...","on_behalf_of":"group:<id>|null"}],
"reverts":[{"nid":"...","to":"sha256:<node_hash>","previously_in":"sha256:<change>"}],
"authoritative":false,"finality":"..."}
```
Lists are sorted by code point. `anchor_drift` lists every accepted formalization (section 8) that has an anchor on a node this MR modifies, removes, or moves. `origin` is the **legislative footprint** (D36): every node in the MR's derived patch whose `node_hash` equals a node in a change listed in the `proposes` of an accepted non-member or group opinion that is a causal ancestor of the change introducing it. It shows what outside drafting is public, not everything that happened. `reverts` (D41) lists every node in the patch whose resulting `node_hash` equals a hash that same `nid` had at any earlier root head in this session, other than the current one.
7. **Law in force** (D43). Order the session's merges into the root by log order. The law in force on date `d` is the root head after the longest prefix of those merges whose effective dates are all `<= d`, where an `on_merge` date is the UTC date of the checkpoint including its executed determination. A merge with a later effective date therefore holds back every merge after it. `GET /law?date=` returns that snapshot id and tree, labeled with `finality`. An `effective` date earlier than the MR's open date is rejected with `effective_invalid`.
8. **Authority labels** (D45). Every derived view that is not enacted text (fact sheets, checks, history, evaluations, exports of opinions) MUST include `"authoritative": false`. Only snapshots of the root branch are law, and only as served by `/law`. A **number** is a maximal match of `[0-9][0-9,]*(\.[0-9]+)?` in a node's heading or text, kept as the literal string; `numbers` lists, for each modified node whose number sequence changed, both full sequences in document order. The fact sheet never interprets or judges the MR's `summary`; it exists so that checking a summary against the change is quick for anyone.
## 7. Rules (MUST for the L1/L2 kinds)
Evaluation is a **pure function**: `evaluate(rule, electorate_snapshot, ballots, mr) -> {outcome, tally}`. Determinations are verified by re-running it over exactly the `ballots` the determination lists.
**Ballot validity:** (a) valid signature by the member's current key, or by an earlier key in a checkpoint not after its rotation's `compromised_after` (D62); (b) author is in the frozen electorate; (c) `ballot.mr == mr_id`; (d) one effective ballot per member. If a member has two or more ballots not linked by a `supersedes` chain, **all** of that member's ballots on this MR are void (equivocation), and the member is counted in `tally.void`.
Let N be the electorate size, A/R/B the effective approve/reject/abstain counts, and V the void-member count.
| Kind | Params | Evaluation |
|---|---|---|
| `always_allow` | none | Approved once the MR is open and `structure` passes. |
| `designated_approvers` | `approvers:"group:<id>"`, `required:int` | With n = group size: approved when A >= required; rejected when n - R - V < required (void approvers can no longer approve, matching `on_threshold`; a v0.3 fix from a session 3 finding); else pending. |
| `threshold_vote` | `num,den` (0 < num <= den), `cmp:"gt"|"gte"`, `basis:"electorate"|"votes_cast"`, `quorum_num,quorum_den`, `closing:"on_threshold"|"on_deadline"`, `deadline?`, `reversion?:{num,den}` | D = N if basis is `electorate`, else A + R. Passes iff quorum holds `(A+R+B)*quorum_den >= quorum_num*N`, D > 0, and `A*den cmp num*D`. If `reversion` is set and the MR's fact sheet has any `reverts`, `reversion.num/den` replaces `num/den` (D41). |
| `capital_vote` (strong nodes only) | `budget:int`, `cost:"linear"|"quadratic"`, `num,den`, `cmp`, `closing:"on_deadline"` | A member's capital = `budget` minus the cost of their effective non-abstain ballots across the session (linear cost = weight, quadratic = weight squared). A ballot that overspends is invalid (`capital_exceeded`). WA/WR are summed weights; passes iff `WA*den cmp num*(WA+WR)` and `WA+WR > 0`. A superseded ballot refunds its cost. |
**Closing.**
- `on_threshold` requires `basis: electorate`. Approved as soon as the rule passes on current ballots. Rejected as soon as approval becomes mathematically impossible: when `(N-R-V)*den cmp num*N` fails (the threshold can no longer be reached) **or** `(N-V)*quorum_den < quorum_num*N` (quorum can no longer be met). No clock is needed. (v0.3 omitted the quorum test, so an MR could stay pending forever; found by a machine-checked property in session 4.)
- **Notice** (D42): when the rule has `min_notice`, an `approved` determination is valid only at a checkpoint time at least `min_notice` seconds after the checkpoint time of the checkpoint that included the `mr.open`. `strong` nodes MUST refuse earlier ones (`notice_not_elapsed`); with `on_threshold` the MR simply stays pending until notice has elapsed. Rejections need no notice. `eventual` nodes treat notice as advisory and record the elapsed checkpoint time in the determination's evaluation.
- **Fiscal notice** (D53): for an MR with `fiscal: true`, the notice period starts only when at least one accepted opinion with an `estimate` (section 8) on that MR exists; until then a determination is refused (`estimate_missing`).
- **Amendment notice** (D48): an MR that `supersedes` another, and whose touched nodes are all among the nodes the superseded MR touched, needs `min(min_notice, 86400)` (24 hours) of notice. Its fact sheet includes `versus_superseded`, the structural diff between the two versions. Ballots never carry over (D9).
- **Emergency path** (D49): an MR with `emergency: true` is designated an emergency when at least 2/3 of the electorate cast ballots with `emergency: "yes"` (`yes*3 >= 2*N`), independent of their `choice`. A designated emergency MR needs only 24 hours of notice. If it merges, the node derives an **expiry change** (author `oth:system`) restoring the pre-merge text of every node it touched, applied at the first checkpoint 30 days of checkpoint time after the merge, except for nodes changed by a later non-emergency MR. If designation fails, the MR continues under normal notice. `emergency` on a governance MR is rejected (`emergency_invalid`). `eventual` nodes treat the 30 days as advisory, as with deadlines.
- `on_deadline`: pending until a determination is submitted at or after `deadline`. `strong` nodes MUST refuse earlier ones using checkpoint time. `eventual` nodes treat the deadline as advisory; the determination records the ballot set and verifiers re-evaluate exactly that set. Outcome is `approved` if the rule passes, `expired` if quorum is unmet, otherwise `rejected`.
- Rules that are not in a node's `rule_kinds` return `rule_not_supported`.
**Shares** (D50). Every tally, ranking, and fact sheet that reports support MUST show `approve_of_cast` (`A/(A+R)`), `approve_of_electorate` (`A/N`), and `rule_share`, naming the share and basis the MR's rule actually uses, side by side.
**Contests** (D47; L2). When several open MRs target the same branch and their touched nodes overlap, any member may group them with `contest.open` `{"options":["sha256:<mr>",...]}` (2–9 options, all under the same non-`capital_vote` rule; else `contest_invalid`). From then on, those MRs are decided only through the contest, and plain ballots on them are ignored. Members rank options with `contest.rank` `{"contest":"sha256:...","ranking":["sha256:<mr>"|"status_quo",...],"supersedes":...}`; unranked options tie below all ranked ones. Two unlinked rankings void the member (as D10).
- **Method: ranked pairs.** For every pair of options (including `status_quo`), count members ranking each above the other. Sort pairs by winning count descending, then by smaller losing count, then by option id. Lock each pair unless it creates a cycle. The winner is the option no locked pair beats.
- **The winner must still pass its own rule against the status quo**: approvals `A` are members ranking it above `status_quo`, rejections `R` those ranking `status_quo` above it, abstentions `B` those ranking them equal. Notice runs from the latest option's open. If it passes, that MR is approved and the others are rejected with reason `contest_lost`. If the winner is `status_quo` or fails its rule, all options are rejected.
- `contest.determine` is a verifiable claim like `mr.determine`; every node re-runs the method. A contest never lowers the bar any option would face alone.
**Registry:** each kind has a statute-ready plain-language text in `spec/rules/<kind>.md`, and machine params MUST match it. Test vectors live in `conformance/vectors/rules/<kind>.json`.
**Formal reference** (D27). Each kind also has a normative reference implementation in Catala, `spec/rules/<kind>.catala_en`, written paragraph by paragraph alongside the plain text. The rule vectors are generated from it, and the spec repository machine-checks these properties for every kind: determinism; integer-only arithmetic; monotonicity of `on_threshold` (more approvals never turn approval into rejection); rejection soundness (never reject while approval is possible); rejection completeness (never stay pending once approval is impossible); and equivocation never increases `A`. **You do not need Catala or any proof tool.** Pass the vectors. If the plain text and the Catala reference disagree, that is a spec bug: open an RFC. New kinds arrive by RFC. Reserved for a future version: `ranked_choice` (needs a "contest" of competing MRs), `delegated_survey`.
## 8. Opinions (MUST at L2; MAY earlier)
Opinions are non-binding, signed, public statements, appended like any transaction.
```json
{"subject":{"type":"session|branch|change|mr|rule|opinion|rfc|spec","id":"..."},
"author_kind":"member|delegate|advisor|implementation|group","on_behalf_of":"group:<id>|null",
"disclosure":{"operator":"...","tool":"...","principal":"ed25519:...",
"funding":[{"source":"...","kind":"ads|sponsor|subscribers|grant|employer|platform|none|other"}],
"affiliations":["..."],"ai_assistance":"none|assisted|generated",
"stakes":[{"subject":"sha256:...","direction":"yes|no|other","venue":"...","size_band":"<1k|1k-10k|10k-100k|>100k|null"}],
"foreign_principal":{"country":"...","relationship":"..."}|null},
"stance":"support|oppose|amend|question|neutral","summary":"<=280 chars","body":"markdown",
"proposes":["sha256:<change>"],"replies_to":"sha256:<opinion>|null","evidence":[{"uri":"...","hash":"sha256:..."}],
"quotes":[{"as_of":"sha256:<snapshot>","nid":"...","text":"..."}],
"findings":[Finding],"corrects":"sha256:<opinion>|null","method":{"inputs":["sha256:..."]},
"formalization":Formalization|null,"resolution":Resolution|null,"forecast":Forecast|null,"ranking":Ranking|null,
"estimate":Estimate|null,"translation":Translation|null}
```
`quotes`, `findings`, `corrects`, `method`, `formalization`, `resolution`, `forecast`, `ranking`, `estimate`, `translation`, and `complaint` are optional; `complaint` is defined under "Complaints" below.
- `member`: a roster member. `delegate`: acts for a member and MUST name `disclosure.principal`. `advisor`: any other keyholder, human or AI, including journalists, social-media creators, think tanks, and lobbyists. There is **no** creator-specific kind and **no** verified badge or status flag (D15). `implementation`: a node speaking about the protocol itself (`subject.type` is `rfc` or `spec`). `group`: a position of a group, such as a party, caucus, or coalition (D37): `on_behalf_of` names the group, the signer MUST be one of its admins at that causal point (else `group_not_authorized`), and the opinion MUST carry full disclosure (funding, affiliations, stakes, AI assistance) whoever signs it, since a group is not a member. Group positions are never counted, and nothing derived from them affects evaluation.
- Every non-member kind MUST include `disclosure` with `funding` (use `[{"kind":"none"}]` if unfunded), `affiliations` (may be empty), and `ai_assistance`. The rules are identical for every non-member kind. Missing fields are rejected with `disclosure_missing`. `ai_assistance` is `generated` when an AI agent wrote the opinion, `assisted` when a human used AI tools to write it, else `none`; when not `none`, `disclosure.tool` MUST name the model and tool. Members MAY include `disclosure` (whether they must disclose non-financial conflicts is OQ-9).
- **Foreign principals** (D60). `foreign_principal` is required for every non-member opinion (`null` if none) and covers only acting at the direction or expense of a foreign government or foreign political party. Nationality and residence are never asked.
- **Cost estimates** (D53). `estimate: {"mr":"sha256:...","baseline":"<=500 chars","by_year":[{"year":2026,"outlays":"<int dollars>","range_low":"...","range_high":"..."}],"assumptions":"<=2000 chars"}` with `method.inputs` listing sources by hash. Any keyholder may publish one; nodes show all estimates on an MR side by side, none official.
- **Translations** (D63). `translation: {"language":"<BCP 47>","of":"sha256:<snapshot or opinion>","passages":[{"quote":0,"text":"..."}]}`; every passage is anchored to a verified quote of the source. Translations are opinions, always `authoritative: false`, and open to findings.
- **Statement mirrors** (D52). A member may publish an opinion with `mirror: {"kind":"statement|denial","source_uri":"...","archived":"sha256:<archived copy>","said_at":"YYYY-MM-DD"}` to put a statement made elsewhere on the record, or to deny one attributed to them. The opinion's `body` holds the statement text. Quotes (D21) may target an opinion body as `{"object":"sha256:<opinion>","text":"..."}`, verified the same way. Mirrors are voluntary.
- **Stakes** (D32). `stakes` lists any financial position the author holds on the outcome of the opinion's subject, or on any MR or assertion it names: direction and venue are required, `size_band` is optional. Every non-member opinion MUST include `stakes` (`[]` declares none; missing is `disclosure_missing`). Members SHOULD publish a `stakes` disclosure on an MR before voting on it; nodes MUST show a member's latest declared stakes on an MR next to their ballot in `/tally`. The protocol cannot see positions held elsewhere (OQ-17).
- Opinions are **never** counted in binding evaluation. (A future `delegated_survey` kind may consume member opinions.)
- Nodes MUST declare `opinion_policy` (e.g. `members-only`, `members-and-signed-advisors`, `open`) and MAY refuse others.
- **Rate limits, not reputation** (D18, D51). Nodes MAY rate-limit opinions per signing key (`rate_limited`), and MUST NOT group keys by any disclosure field, group membership, affiliation, or stance when doing so, MUST describe the policy in `opinion_rate_limit`, and report its effect as a metric. The protocol defines **no** reputation scores, endorsement counts, likes, or other per-opinion tallies, because counts on opinions become pseudo-votes. Nodes MAY cluster near-duplicate opinions in derived views, but every accepted opinion MUST remain individually retrievable through `/objects` and `/opinions`.
- **Dogfooding:** publish your opinions on the spec and RFCs as `implementation` opinions, and mirror them into `OPINIONS.md` (section 11).
### Fact-checking (D20-D25; L2)
Fact-checkers are ordinary keyholders publishing as `advisor` (or `implementation`, for an analyzer node). There is no fact-checker kind, accreditation, or badge.
**Verified quotes** (D21). Any opinion that quotes protocol content MUST declare each quote in `quotes`. A node MUST verify that `text` occurs exactly (after NFC, no other normalization) within the `heading` or `text` of node `nid` in snapshot `as_of`, and reject the opinion with `quote_mismatch` otherwise. Quotes from restricted content are rejected until it is revealed.
**Findings** (D20). A finding is one checked claim:
```json
{"target":{"type":"mr|change|opinion|node|ballot|external","id":"sha256:...|<uri>"},
"claim":{"quote":0,"text":"..."},
"verdict":"accurate|misleading|false|unsupported|inconsistent|unverifiable",
"rationale":"<=1000 chars","evidence":[{"uri":"...","hash":"sha256:..."}],
"assertion":Assertion|null,"related_quotes":[1,2]}
```
- `claim.quote` indexes into the opinion's verified `quotes` when the claim is protocol content; `claim.text` is used for claims made outside the protocol (an interview, a post), with `target.type: external` and evidence hashing the source.
- `inconsistent` requires at least two verified quotes (`claim.quote` plus `related_quotes`) showing the conflicting provisions or statements.
- When `ai_assistance` is `generated`, `method.inputs` MUST list the hashes of every protocol object given to the model, and `disclosure.tool` MUST pin the model version.
- Each finding maps to schema.org `ClaimReview` (`claimReviewed` = claim, `reviewRating.alternateName` = verdict, `itemReviewed` = target). Implementations MAY serve that rendering; the JSON above is normative.
- The `verdict` is the author's judgment. Nodes MUST present it attributed to its author and MUST NOT aggregate verdicts into labels on MRs, nodes, or members.
**Assertions** (D22). A closed registry of claims about the record that every node evaluates deterministically. Any opinion may carry them (inside findings, or a member stating their own record).
| Kind | Params | Holds when |
|---|---|---|
| `ballot_choice` | `member`, `mr`, `choice` | The member's effective ballot on the MR has that choice (`undetermined` if none; `fails` if void or different) |
| `mr_outcome` | `mr`, `outcome` | The MR's executed determination has that outcome (`undetermined` while open) |
| `tally` | `mr`, `approve`, `reject`, `abstain` | The counts match the tally at the executed determination (`undetermined` while open) |
| `authored` | `object`, `author` | The object's signed author is that key |
| `text_contains` | `as_of`, `nid`, `text` | The node's text at that snapshot contains `text` exactly |
`GET /opinions/{id}/evaluation` returns, for each quote and assertion, `{"index":n,"result":"holds|fails|undetermined","finality":"..."}`. Results are recomputed as the record changes and carry `finality` per section 5. Unknown kinds are rejected with `assertion_unknown`. New kinds arrive by RFC. `ballot_choice` depends on public ballots and will change if OQ-4 introduces secret ballots.
**Corrections and replies** (D25).
- `corrects` names an earlier opinion by the **same author**; otherwise reject with `correction_invalid`. The original is never altered or hidden. `/opinions` and `/cite` on the original MUST include `corrected_by` links.
- When a finding targets an object whose signed author is a member, every opinion by that member with `replies_to` set to the fact-check MUST be listed with it in `/opinions` results (`replies_from_subject`).
- The protocol defines **no** accuracy scores, "lie counts," or rankings of members or fact-checkers (extending D18). Anyone may compute such things from public data and publish them as an opinion, which is itself open to findings.
### Resolutions, forecasts, and rankings (D31, D34, D35; L2)
These let markets, forecasters, and rankers make claims every node can check. Nodes evaluate them; they never host markets, publish odds, or rank anything themselves.
**Resolution spec** (D31). States ahead of time how a question resolves:
```json
{"question":"<=280 chars","assertion":Assertion,"finality":"final|final_advisory",
"nodes":["ed25519:<node key>"],"min_agree":2}
```
Resolves `resolved_true` when at least `min_agree` of the listed nodes report the assertion `holds` at the required finality, `resolved_false` when at least `min_agree` report `fails` at it, otherwise `unresolved`. A node MUST report `unsatisfiable` when it can determine the spec can never resolve, for example when it requires `final` and fewer than `min_agree` listed nodes declare `consistency: strong`. A malformed spec is rejected with `resolution_invalid`. A spec is immutable; a changed spec is a new opinion with `corrects`.
**Forecast** (D34): `{"assertion":Assertion,"p_num":int,"p_den":int,"as_of":"sha256:<checkpoint root>"}` with `0 <= p_num <= p_den`. Once the assertion is decided, nodes report it `holds` or `fails` in `/evaluation`. Nodes MUST NOT aggregate forecasts into odds on an MR and keep no forecaster scores.
**Metrics and rankings** (D35). A closed registry over the binding record only (never opinions):
| Metric | Scope | Value |
|---|---|---|
| `approve_share` | MR | `A/(A+R)` as an integer fraction, from the tally (also called `approve_of_cast`; always shown beside `approve_of_electorate`, D50) |
| `approve_of_electorate` | MR | `A/N` as an integer fraction |
| `turnout` | MR | `(A+R+B)/N` as an integer fraction |
| `ballots_cast` | MR, member, or session | count of effective ballots |
| `mrs_opened`, `mrs_merged` | member, group, or session | counts |
| `checkpoints_to_determination` | MR | checkpoints between open and executed determination |
`GET /stats` returns a metric for a scope as of a checkpoint, with `finality`. A **ranking** is `{"metric":"...","scope_type":"mr|member|group","filter":{"session":"..."},"as_of":"sha256:<checkpoint root>","order":"desc|asc","items":[{"id":"...","value":"<num>/<den>|int"}]}`; nodes recompute it and report `holds` if every value and the order match, else `fails`. Ties are ordered by id. Unknown metrics are rejected with `metric_unknown`. New metrics arrive by RFC and MUST be computable from ballots, MRs, and determinations alone.
### Complaints (D39; L2, re-validation L3)
There is no ombudsman office in the protocol. Anyone may file a complaint or investigate one, and reports are ordinary opinions with findings (section 8).
```json
{"respondent":{"type":"node|group|key","id":"..."},
"issue":"refusal|quarantine|rate_limit|opinion_policy|equal_access|entrenchment|other",
"evidence":[{"receipt":Receipt}|{"refusal":{"node":"ed25519:...","index":int}}|{"envelope":Envelope}],
"remedy_sought":"<=1000 chars"}
```
- Nodes verify every receipt signature and refusal-log inclusion in `evidence` (else `evidence_invalid`) and report results in `/opinions/{id}/evaluation`.
- At L3, a node MAY **re-validate** a refused envelope against the respondent node's published log as of the receipt's `checkpoint_size`, and reports `rejection_consistent | rejection_inconsistent | not_checked`.
- Complaints, findings, and recommendations have no binding effect on any node. Complainants who need confidentiality file outside the protocol; a published report cites evidence by hash.
### History, evidence, and research exports (D44-D46; L2)
- **`/history/{nid}`** returns, in merge order, every merged MR that changed the node, each with: the proposer's `summary` (labeled as the proposer's words), member opinions, group positions, and other opinions, in separate labeled lists. Nothing is ranked, filtered by stance, or weighted. `"authoritative": false`.
- **`/extract/{hash}`** returns `{"object":{...},"supporting":[objects needed to recompute its hash and, for an MR or determination, its ballots and open/determine transactions],"inclusion":{proof against the checkpoint},"checkpoint":Checkpoint,"cosigned":[Checkpoints from other nodes at the same or later size, with consistency proofs]}`. It MUST be verifiable offline with no network access. The spec repository ships a small reference verifier.
- **`/export/rollcall`** returns `{"members":[...],"mrs":[...],"choices":[[ "approve|reject|abstain|void|null", ...]],"weights":[[int|null]]|null,"finality":[...]}`. `weights` comes from `capital_vote` and MAY be omitted (OQ-20).
### Formalizations (D28; L2)
**The text is the law; a formal model is an interpretation.** Anyone may publish a formal model of statute text as an Opinion with a `formalization` field. It is never binding, never canonical, and nodes never execute it. Competing formalizations of the same text are expected.
```json
{"language":"catala|legalruleml|logical-english|owl|other","language_version":"...",
"source":"sha256:<attached source file>",
"anchors":[{"rule":"<identifier inside the source>","quote":0}],
"terms":["term:<scope nid>#<term>"]}
```
- Every formal rule in the source MUST have at least one anchor, and every anchor points into the opinion's verified `quotes` (D21), so each rule maps back to the exact sentence it encodes. Nodes check that anchors reference valid quote indexes and that `source` resolves (else `anchor_invalid`); they do not parse the source language.
- `terms` links the model to defined-term ids (D29). An outside ontology of terms is published the same way, with `language: "owl"` or similar.
- A finding produced by running a deterministic reasoner or checker over a formalization MUST list that formalization's opinion id and the tool's version in `method.inputs` and `disclosure.tool`, so anyone can re-run it.
- When an MR touches an anchored node, the formalization appears in that MR's `anchor_drift` (section 6). Its author may publish a new formalization with `corrects` set to the old one.
## 9. Federation (MUST at L3)
- **Sync:** pull a peer's `/log` (or `/heads` plus missing parents for `eventual` nodes). Validate every envelope: signature, parents present, authorization against state at that causal point. Invalid envelopes go to `/quarantine`, never into state.
- **Never trust a peer's determination.** Re-evaluate it.
- **Forks:** `strong` nodes that receive a peer checkpoint failing the consistency proof MUST surface `fork_detected` and stop syncing from that peer. `eventual` nodes merge DAGs; two conflicting determinations for one MR are flagged `contradictory_determination` and left unexecuted until resolved (open question OQ-3).
- Peers are configured manually in v0.10.
## 10. Conformance (what "done" means)
**Levels**
- **L0 verifier:** ingest a peer's log, verify everything, recompute derived state, match its checkpoint. (A valid stopping point for an analyzer implementation.)
- **L1 core:** sessions, groups, branches, changes, MRs, merge semantics, all checks including structure profiles, the fact sheet, rule kinds `always_allow` / `designated_approvers` / `threshold_vote`.
- **L2:** `capital_vote` (strong only), restricted branches and reveal, opinions with verified quotes, findings, assertions, corrections and formalizations, stakes, resolutions, forecasts, rankings and `/stats`, events, citations, the Popolo export, signed receipts, the refusal log, complaints, group positions, notice, history, evidence extracts, the roll-call export, proofs. (`/law` and effective dates are L1.)
- **L3:** federation with a second implementation, adversarial scenarios, scale scenario, complaint re-validation.
**Vectors** (`conformance/vectors/`): canonicalization, hashing, node hashing, signatures, merge cases, rule evaluation, consistency checks, fact sheets (including number extraction), quote verification, assertion evaluation, structure profiles. Rule vectors are generated from the Catala references (section 7); if they exist, use them rather than writing your own for rules. If the directory is empty when you start, write provisional vectors for L0/L1 from this spec and mark them `"provisional": true`. They become normative only after a second independent implementation reproduces them.
**Scenarios** (harness drives your API as a black box with seeded synthetic rosters and voting blocs; it also checks all participating nodes converge on the same root snapshot hash):
| # | Scenario | Level |
|---|---|---|
| S1 | Linear amendment passes a majority vote | L1 |
| S2 | Concurrent conflicting edits, resolution, vote | L1 |
| S3 | Governance MR changes a branch's merge rule | L1 |
| S4 | Equivocation (double ballot) voids the member's ballots | L1 |
| S5 | Bait-and-switch: head moves after ballots, MR goes stale | L1 |
| S6 | Contested `capital_vote` with quadratic cost | L2 |
| S7 | Restricted draft is revealed, then merged | L2 |
| S8 | Opinion flood from many keys; per-key rate limits; disclosure enforcement (`funding`, `affiliations`, `ai_assistance`) | L2 |
| S9 | Partition and heal (`eventual`) or fail-over (`strong`) | L3 |
| S10 | Malicious operator withholds, reorders, or rewrites history; do independent verifiers detect it? | L3 |
| S11 | Scale: 11,000-member roster, ~300 MRs, mixed rules | L3 |
| S12 | Finality and citation: a vote crosses its threshold live; the harness checks events stay `provisional` until checkpointed, that an `eventual` node never emits `final`, and that `/cite` on a pre-amendment hash still returns the original text with a correct `latest` link | L2 |
| S13 | Fact-check loop: an MR's summary says a period becomes 35 days while its change sets 45; every node's `/facts` reports `30` → `45` identically; a finding with a verified quote is accepted and a misquote is rejected (`quote_mismatch`); a `ballot_choice` assertion that a member voted `reject` evaluates `fails` when they voted `approve`, `provisional` until checkpointed; a correction keeps the original retrievable with `corrected_by`; the member's reply appears with the finding; a new `[[def:]]` duplicate and a reference cycle raise warnings without blocking | L2 |
| S14 | Formal grammar and rules: a node with a kind the profile forbids blocks the MR (`structure`); an `on_threshold` MR whose quorum becomes unreachable through void ballots is rejected, not left pending; a formalization with an anchor on an amended node appears in that MR's `anchor_drift`; `/export/popolo` vote counts match `/tally` on every node | L2 |
| S15 | Markets and rankings: a resolution spec on an MR's outcome stays `unresolved` while results are `provisional` and resolves identically on all listed nodes once checkpointed; a spec requiring `final` from too few `strong` nodes is reported `unsatisfiable`; a non-member opinion without `stakes` is rejected; a member's declared stake appears beside their ballot; a forecast resolves `holds` or `fails`; a ranking by `approve_share` holds on every node and a ranking with one swapped item fails; no endpoint returns aggregate odds | L2 |
| S16 | Parties, interest groups, and complaints: a group position signed by a non-admin is rejected (`group_not_authorized`); a ballot signed by anyone but the member is rejected; a governance MR targeting the root branch's governance rule is rejected; text proposed by an advocacy group's opinion and later appended by a member appears in `origin` on every node; a node refuses a valid opinion, the submitter files a complaint with the signed receipt and refusal entry, and an L3 node re-validates it as `rejection_inconsistent` | L2/L3 |
| S17 | Procedure and evidence: an `on_threshold` MR on the root stays pending until 72 hours of checkpoint time pass, then merges; a determination submitted earlier is refused (`notice_not_elapsed`) on `strong` nodes; three blocs with cyclic preferences pass X to Z and Z to Y, and the reverting MR Y to X fails under a 3/4 `reversion` threshold; a merge with a future effective date holds back a later merge in `/law`; an evidence extract for a determination verifies offline; `/history` lists member and outside opinions separately | L2 |
| S18 | Simulation 1 replay: a simulated session carries `simulation: true` on every response; four MRs on one chapter grouped in a contest elect `status_quo` when none beats it with a majority of the electorate; a superseding version touching only the same nodes needs 24 hours of notice; an emergency MR with 2/3 `emergency: "yes"` merges after 24 hours and is reverted by an expiry change 30 days later; a node that rate-limits keys by shared affiliation fails; tallies show both shares | L2 |
| S19 | Simulation 2 replay: 2,000 fresh non-member keys cannot publish until aged; a stolen member key casts a conflicting ballot, the member and registrar rotate the key, and the member's real ballot counts; a fiscal MR's notice starts only after an estimate is published; a reference to an unpublished annex blocks the MR; a 90-day sunset drops text from `/law` on day 91; a translation passage that is not an exact verified quote is rejected; an `education` session's capability declares `ephemeral` | L2 |
**Research mode** (D46, D55, D64, D65). Research sessions set `simulation: true`. They may add **constituent panels** (synthetic views per district, declared and hashed), in which case the harness reports how closely each member's votes track their panel. They may add a **second chamber** with its own roster and rules (including a debate-closing threshold) and a **presentment model** (signature, veto, override at two-thirds of each chamber), all labeled hypothetical; bicameralism stays outside the protocol core. **Exchanges** (D67): offers between chambers and from a modeled executive are opinions with `exchange: {"mr":"sha256:...","from":"chamber:<id>|executive","offer":"sha256:<change>","prior":"sha256:<exchange>|null"}`; a conference MR lists the exchange chain it resolves in `supersedes` context, so the bargaining record is linked and verifiable. Any published result MUST include a turnout model (who abstains or misses votes, and why) and a sensitivity analysis: re-run with each bloc 10% larger and 10% smaller, chamber size fixed, and list every outcome that flips. The harness can also run sessions for research: synthetic blocs with declared preference models (published, hashed), rule sets assigned by seed so sessions differ only in their rules, and a `replication.json` recording the harness version, seed, preference-model hash, rule assignment, and final log root, so any published result can be reproduced. Results describe the programmed voters, not real legislators (OQ-25).
**Metrics** (reported, not pass/fail in v0.10): sustained tx/s; p50/p95 latency from the deciding ballot to a merged head; storage growth per 1,000 ballots; time to sync a fresh node from genesis; two-node convergence time; share of S8 opinions rate-limited or clustered; time to compute `/facts` and `/checks` for the largest S11 MR; in an S11 variant where simulated blocs react to published forecasts, how much forecasts shift outcomes (OQ-18). These feed the project's central empirical question: what does each trust model buy, and at what cost, at legislative scale?
## 11. Deliverables in your implementation folder
```
implementations/{{IMPLEMENTATION_NAME}}/
README.md architecture, trust model, how to run, known deviations, the "not for real elections" notice
DESIGN.md 1-2 pages: key choices, why, what you would change in the spec
src/ tests/
Dockerfile or compose runs with one command
conformance-report.json output of the harness
OPINIONS.md your opinions on the spec and open RFCs (also published as Opinion txs)
```
## 12. Working method
1. Read this whole prompt. In `DESIGN.md`, restate the spec's key invariants in your own words before writing code.
2. Build canonicalization, hashing, and signature verification first, tested against vectors.
3. Implement L0, L1, L2, L3 in order and run conformance after each level. Commit at each green level.
4. **Do not edit `spec/`.** If a requirement is wrong, ambiguous, or unimplementable, open an issue using the `rfc` template with a minimal failing example and a proposed fix, publish an `implementation` opinion, and continue with the spec as written unless maintainers accept the change.
5. Prefer boring, auditable code. No hidden network calls. No secrets in the repo. Licenses of dependencies must be permissive.
6. Ask the human before adding a non-permissive dependency or touching anything that resembles real data.
7. When done, report: which levels pass, which scenarios fail and why, your metrics, and your top three objections to the spec.
**Open questions you may hit (do not resolve alone; raise as RFCs):** secret ballots vs. accountability under mass audiences, and which claims about votes stay checkable, the council's top priority (OQ-4); who owns the verdict scale (OQ-11); statements made outside the protocol (OQ-12); reproducing AI findings (OQ-13); whether some MRs should require a formalization (OQ-14); choosing among competing formalizations (OQ-15); structure profiles beyond the US (OQ-16); member trading on outcomes (OQ-17); forecasts that move votes (OQ-18); owner-blind restricted commitments, now urgent (OQ-19); coercion of members by parties and interest groups (OQ-20); donor privacy in disclosures (OQ-21); independent review of the council's simulated decisions (OQ-22); what would serve as an attested enrolled text (OQ-23); emergency exceptions to notice (OQ-24); what synthetic voters can show about real ones (OQ-25); helping coalitions find a missing option (OQ-26); how constituents take part (OQ-27); member safety (OQ-28); classified material (OQ-29); sybil resistance for non-member speech (OQ-30); a formal role for affected populations (OQ-31); content rules for budget-only vehicles (OQ-32); unrelated provisions on must-pass bills (OQ-33); late ballots and time in `eventual` mode (OQ-1); contests, now specified in section 7 (OQ-2); contradictory determinations across forks (OQ-3); issuing member keys (OQ-5); opinion spam (OQ-6); provisional results reported as final (OQ-8); member conflict disclosure (OQ-9); context for short clips of cited text (OQ-10).
- Step 1
Fill in the fields and copy the prompt.
- Step 2
Start a fresh coding agent session in an empty repository and paste it in.
- Step 3
Bring what you built, and your objections to the spec, to the monthly call.
Monthly community call
Join us on Zoom, once a month.
We review the spec, demo implementations, and decide what changes next. Builders, researchers, students, and the simply curious are all welcome. No coding required.
Details
Registration opens when the first call is scheduled.
A typical agenda
- Spec and RFC review
- Implementation show-and-tell
- Conformance results and open problems
- Open floor and questions
Can't make the call? Build an implementation from the coding prompt, or read the open questions the council left for real stakeholders.
The open questions