Academy Network: A Peer-to-Peer System for Universal Education
Ricardo Satoshi academynetwork.net Spec version 1
WHITEPAPER.md and SPEC.md in the spec repositoryAbstract
A record of practical knowledge that anyone can extend would let people learn to do things without paying for access or asking permission. The web already holds most of this knowledge, but it is scattered across sites that decay, gated behind tuition, and shaped by whoever owns the server. What is missing is not the information but a structure for it and a home that no single party can revoke. We propose a system where knowledge is published as signed, content-addressed objects that every participant stores and relays, so there is no server to subpoena, block, or switch off. Quality is not enforced by an owner but by an editorial manifest: a signed index that a rotating set of editors extend only when a randomly drawn jury of qualified reviewers has approved a submission, a rule every node checks for itself. Readers rank and discuss the result with open signals that refine discovery without gating it, and the community can delist a guide by a supermajority of earned standing. Transport runs over Tor, so participation reveals no address and reading reveals nothing at all. The result is a library that is free, hard to censor, and able to outlive its founders.
1.Introduction
Education on the internet has a distribution problem, not a supply problem. A determined learner can assemble almost any skill from search results, forums, and videos, but only by knowing where to look, in what order, and which sources are wrong. The material that goes deepest is the most likely to sit behind tuition or to vanish when a channel is deleted. Encyclopedias teach facts rather than procedures. General purpose language models answer fluently but vary with the prompt and draw on a bounded, curated slice of what is knowable.
What has not been tried is a single body of practical knowledge, structured as a curriculum, that anyone may extend and that no organization owns. The obstacle is not writing the software. It is that any such library, once useful, becomes something powerful parties want gone, and a library with an owner and a server has an address to attack and a switch to flip.
We need a library whose contents are held by everyone who reads them, whose editorial standard is enforced by rules that every reader can verify rather than by an administrator who must be trusted, and whose participants cannot be identified by the act of participating. The sections that follow describe such a system, most of which is built.
2.Knowledge Objects
We define a unit of published knowledge as a signed object. Every object is a small record (an envelope) carrying a version, a type, an author public key, a body, a proof-of-work nonce, and one or more signatures. Objects are encoded in a deterministic form (canonical CBOR, RFC 8949) so that the same content always produces the same bytes.
An object's identity is the BLAKE3-256 hash of its complete signed bytes. The hash is the object's permanent address: to name a thing is to name its content, so an object cannot be altered without becoming a different object. Authorship is an Ed25519 key pair generated locally; an identity simply is its public key, with no account, no registration, and no link to a person.
The primary types form a curriculum. A hierarchy is a subject root. A topic is the stable handle for one definitive guide, carrying the subject it belongs to and its level. A revision is one immutable version of a topic's text, in Markdown, referencing the revision it supersedes. Editing is not mutation: an edit is a new revision that points back at the old one, so a topic accumulates a verifiable history rather than overwriting itself, in the manner of a chain of blocks.
Large media (images, diagrams, video) are not embedded. Each is hashed, addressed by that hash, and referenced from the text. A guide must attach its media rather than link to an external site, so that the guide survives the source going offline. Media can also stand on its own: a participant may publish a book, a video, or any file to a shared media library without attaching it to a guide, and the same content-addressing splits a large upload into parts that spread across many machines, so no single node has to hold the whole of it.
3.The Hierarchy
Structure is the system's contribution, more than any single guide. Each subject is a ladder. Level one assumes the reader knows nothing and teaches the smallest building blocks. A guide at level N may assume only what the levels below it already teach, and it declares the lower-level guides a reader should complete first. This rule is enforced at review time, which guarantees that a beginner can climb from the bottom of any subject to its frontier without encountering a gap. There is one definitive guide per topic; alternatives live as methods inside it, and a better method can replace the main one. The corpus is therefore a curriculum first and a reference second.
4.The Manifest
Content-addressed objects can be published by anyone, so the network needs a way to say which of them are approved without appointing an owner to decide. We use a manifest: a signed index mapping each topic to its approved revision, listing the recognized hierarchies, the verifier roster, the governance parameters, and the editor set permitted to sign the next manifest. The manifest is the editorial truth, and it is the only object clients display by default. Unapproved content still exists on the network but is not in the index unless the reader goes looking.
A manifest at sequence N is valid only if signed by at least a threshold of the editor keys named in manifest N-1 (for example three of five). Each manifest names the editor set for the next, so authority rotates by construction and no single key is ever a point of failure. The founders sign the genesis manifest, whose hash is compiled into the client, and hand off as standing accrues in the community. The chain of manifests is a public, append-only record of every editorial decision.
5.Verifier Juries
Editors do not decide what is true; they record what a jury has already decided. When a revision is published, its jury is drawn deterministically by sorting the eligible verifiers by BLAKE3 of the revision id joined with the docketing manifest id and each verifier's key, then taking the first five. The draw is random but reproducible: no one can select their own jury, and every node computes the same jury from the same inputs. Jurors vote within a fixed window (fourteen days), and every vote must carry written reasoning that returns to the author, so review is a feedback loop and not a verdict.
A revision that a majority of its jury approves within the window MUST appear in the next manifest. Any node checks this, and a manifest that publishes a failed submission or buries a passed one is rejected as invalid. Editors curate; they cannot silently censor. Verifier status is earned per subject and per level by demonstrating knowledge, so the pool that judges a guide is the pool qualified to judge it.
6.The Duke
The identity that authored a topic's first published revision is its duke. The duke owns the guide's properties, meaning its display title and its donation link, and nothing else. Properties live in a separate duke-signed object, so a content edit cannot carry a property change, and no one but the duke can alter them. Content itself belongs to everyone: anyone may edit the text, images, and attachments through the ordinary jury process, the duke included and with no veto. The dukedom can be transferred or renounced, and editors can reassign it for a lost key or for abuse. This separates the one thing an author should keep (attribution and a way to be supported) from the thing the community should hold in common (the knowledge).
7.Community Signals
Once published, a revision accrues open signals. A rating is one counted vote per identity, positive or negative, the latest winning, and a topic's score is the tally of its currently approved revision. A comment is a signed post on a guide's public talk page, threaded, permanent, and readable by all. From these, and from the taxonomy, every client computes the same discovery views locally: sort by curriculum (the default), by top rating, by most discussed, or by newest, with a total and deterministic tie-break so two readers see the same order. Filters narrow a view by subject and level. Search runs offline over approved titles and text and never leaves the machine.
These signals refine discovery but never gate it. Because identities are free, priced only by proof of work rather than by scarcity, a rating tally is an honest measure of sentiment, not a consensus value, so a low score sorts a guide downward but never hides it. Only the manifest decides what is approved. Heavy sustained downvoting flags a revision for the editors' advisory queue, which can docket a replacement through the ordinary process but cannot suppress display.
8.Removal and Governance
Some decisions are the whole community's, and for these one-identity- one-vote fails, since identities are free to mint. We weight these votes by standing: an earned, verifiable measure of a participant's contribution history that a new key cannot fake. A guide can be delisted by a removal petition that reaches a quorum of standing (two hundred at launch) and a supermajority (seventy-five percent) of the standing cast within a window. A passed removal moves the topic to the manifest's removed list; compliant nodes then purge its content and never serve it again, and the petition and its votes remain as the public record.
Governance parameters, editor sets, and verifier certification rules are changed the same way, by standing-weighted petitions bounded to sane ranges (supermajorities between sixty and ninety percent depending on the kind, with no-confidence in the editor set requiring the highest bar). True deletion is impossible on a peer-to-peer network, since a modified client can retain a copy; what removal guarantees is delisting everywhere, purging from every compliant node, and no path back into the index without a fresh review.
9.Network
Nodes communicate only as Tor onion services. Every node runs its own onion service and connects to peers onion to onion, so no address is ever exposed and there is no clearnet listener to find. There is no server. A small set of long-lived seed nodes, whose addresses are compiled into the client, let a fresh install find its first peers; after that, peers exchange addresses and the network survives the loss of any seed, and of the download site itself.
Text is small, so it is fully replicated: the manifest, guide text, votes, and comments flood to every node, which makes reading instant and fully offline, makes every node a complete backup, and leaks nothing about what a reader reads because a reader already holds everything. Large media does not flood. A node that holds a blob announces it; other nodes fetch it directly on demand, verify it against its hash, cache it, and re-announce it, so replication grows with popularity. Nodes also pin a share of rare blobs so that unpopular media does not disappear. Peers reconcile which objects each is missing using a compact set reconciliation rather than shipping full inventories.
10.Incentive
The network needs no revenue: its infrastructure cost is a slice of disk that participants donate. There is no advertising anywhere, and no mechanism in the protocol to place any. The single point at which money appears is the duke's optional donation link, rendered as one button at the foot of a guide, pointing wherever the author takes support. It is an external link, so the client warns before leaving the anonymized network, and a link that points at a store, an affiliate scheme, or anyone but the duke is struck editorially. The reward for contributing is standing and authorship, not payment by the system.
11.Longevity
The knowledge must outlive the software that serves it. The protocol is specified in full so that anyone can build a compatible client; the reference implementation is one implementation, not the definition of the network. Objects use boring, durable formats: Markdown text, canonical CBOR, standard Ed25519 and BLAKE3. Any node can export the entire approved corpus to a folder of plain Markdown files with one action, so even the total death of the protocol leaves every reader holding a complete, human-readable copy of everything.
12.Privacy
Anonymity of transport and pseudonymity of identity are separate and both required. Tor hides where a participant is; the key pair is who they are on the network, unlinkable to a person but stable enough to carry reputation. Reading sends no request, since the reader already holds the text, so no one, including a hostile observer of the network, can learn what any reader studies. Contribution is signed by a pseudonym, and standing attaches to that pseudonym exactly as a respected editor's reputation attaches to a handle whose real name no one knows.
13.Conclusion
We have described a system for universal education that has no owner and no server, in which knowledge is published as signed, content-addressed objects held by everyone; quality is set by verifier juries and recorded in a signed editorial manifest whose every step any node can check; authorship is preserved through the duke while the content stays common; discovery is shaped by open community signals that never gate it; removal and governance are decided by earned standing rather than by disposable identities; and all of it runs over Tor so that neither participation nor reading exposes anyone. The parts fit together into a library that is free to use, structured as a curriculum, resistant to censorship because there is no central point to censor, and able to be rebuilt from its specification and exported to plain text so that it survives the people and the code that started it.
References
- S. Nakamoto, "Bitcoin: A Peer-to-Peer Electronic Cash System," 2008.
- Academy Network, "Protocol Specification" (SPEC.md), Spec version 1.
- The Tor Project, "Tor: The Second-Generation Onion Router."
- J. Aumasson et al., "BLAKE3: One Function, Fast Everywhere," 2020.
- D. Bernstein et al., "Ed25519: High-Speed High-Security Signatures" (RFC 8032).
- C. Bormann, P. Hoffman, "Concise Binary Object Representation (CBOR)" (RFC 8949).
- Bisq, "A Decentralized Bitcoin Exchange Network."
Protocol specification
The whitepaper above describes the system in prose. What follows is the authoritative specification: the exact objects, signing rules, sync protocol, governance, and networking that define the network. The reference client is one implementation of it, never the other way around.
This document defines the Academy Network wire objects, signing rules,
and sync behavior. It is the authoritative description of the network.
The Rust implementation in core/ is an implementation of this spec,
not the definition of the network. Anyone must be able to build a
compatible client from this document alone.
Design goals, in priority order:
- Longevity: objects readable in 50 years; boring formats; no flag days.
- Accessibility: full text replicated to every node; offline reading.
- Anonymity: Tor-only transport; pseudonymous keys; reads leak nothing.
- Verifiability: every object signed; manifest correctness checkable by any node; no trusted counters.
1.Terminology
| Term | Meaning |
|---|---|
| Node | A running Academy client. Every user is a node. |
| Serving node | A node that publishes an onion service and answers peers that dial it. The default, and what "node" means wherever this document does not say otherwise (§20). |
| Client node | A node that dials peers but publishes no onion service and serves no one: a device that cannot keep a service reachable (typically mobile). Same keys, same rights, same full text replica; it simply adds no capacity (§20). |
| Seed | A serving node that is always on and generously resourced. No special authority (§18). |
| Object | A signed, immutable, content-addressed unit of data (guide revision, vote, manifest, ...). |
| Blob | Large binary media (image/video/diagram). Content-addressed but NOT flooded. |
| Topic | A stable identity for "the one definitive guide about X". |
| Revision | One immutable version of a topic's text. |
| Manifest | The signed canonical index: which revision is the approved one for each topic, the hierarchy, and the editor/verifier rosters. |
| Editor | Keypair authorized to co-sign manifests. |
| Verifier | Keypair authorized to vote on submissions for a given subject + level. |
| Duke | The guide's original poster: the identity that authored the topic's first published revision. Only the duke can change the guide's properties (title, donate link). No special power over content. |
2.Encodings, Hashing, Signatures
2.1Canonical encoding
All signed objects are encoded as deterministic CBOR (RFC 8949 §4.2.1):
- Map keys sorted bytewise ascending.
- Shortest-form integers, no indefinite-length items, no floats, no tags except where this spec says so.
JSON is used only for debugging and archive export; CBOR bytes are what gets hashed, signed, and gossiped.
2.2Hashing
- Algorithm: BLAKE3-256.
- Text form of any hash:
b3:+ lowercase hex (64 chars). Example:b3:9f2a…c41d - The
b3:prefix is an algorithm tag. Future algorithms get new prefixes; parsers MUST ignore objects whose hash algorithm they do not know rather than error.
2.3Object ID
object_id = BLAKE3-256(canonical CBOR of the complete envelope, including signature). IDs are permanent addresses. Objects are
immutable; "editing" means publishing a new object that references
the old one.
2.4Signatures & identity
- Algorithm: Ed25519 (RFC 8032).
- An identity IS its public key. Binary form: 32 bytes.
Text form:
ed:+ lowercase hex. - Sign over the canonical CBOR of the envelope with the
sigfield absent. Verify likewise. - Keys are generated locally and silently on first launch. The protocol has no account creation, recovery, or revocation of personal keys (a lost key = a new pseudonym). Editor-set rotation handles the keys that actually matter (§7).
3.The Envelope
Every flooded object is an envelope:
Envelope {
v: u16 // spec version, currently 1
type: text // object type name, e.g. "revision"
ts: u64 // unix seconds, author-claimed, informational
author: bytes(32) // Ed25519 public key
body: map // type-specific fields (§5, §6, §7)
pow: u64 // proof-of-work nonce (§9); 0 where not required
sig: bytes(64) // Ed25519 signature (§2.4)
}
Rules:
- A node receiving an envelope with an unknown
vortypeMUST store-and-forward it if well-formed (future compatibility) unless local policy caps disk, but MUST NOT try to interpret it. Upgrades never require a flag day. - A node MUST drop (not forward) any envelope failing signature or PoW checks.
tsis untrusted (no global clock). Ordering that matters comes from explicit references (prev,seq), never timestamps.
4.Addressing model
Identity = Ed25519 pubkey (who)
Object ID = b3 hash of signed envelope (immutable what)
Topic ID = object ID of the Topic object (stable subject handle)
Blob ID = b3 hash of raw blob bytes (media address)
The manifest (§7) is the only mutable-seeming thing, and it is
actually a chain of immutable objects with increasing seq.
5.Content objects
5.1Hierarchy
A subject tree root, e.g. "Computer Engineering".
body {
name: text // "Computer Engineering"
slug: text // "computer-engineering", unique-ish, [a-z0-9-]
description: text // markdown, what this subject covers
lang: text // BCP 47, e.g. "en"
}
Anyone may propose a Hierarchy; it becomes real when a manifest lists it (§7). Levels are properties of topics, not separate objects.
5.2Topic
The stable identity for one definitive guide (or one method).
body {
kind: text // "guide" | "method"
hierarchy: objid // Hierarchy this belongs to
level: u16 // 1 = simplest; level N may only assume
// knowledge from levels < N (progression rule)
title: text
slug: text
lang: text // BCP 47 language of this topic
parent: objid / null // for kind="method": the guide Topic it
// belongs to. null for kind="guide".
origin: objid / null // if this is a translation: the Topic it
// translates. Translations are linked
// objects, never forks.
}
5.3Revision
One immutable version of a topic's content. Publishing a Revision to the network IS a submission for review.
body {
topic: objid // Topic this revises
prev: objid / null // previous Revision (null = first)
content: text // Markdown (CommonMark). Media embedded as
// 
// Links to other topics as
// [text](topic:<objid>)
blobs: [ {id: b3hash, bytes: u64, mime: text} ]
// every blob referenced, with sizes, so
// readers can decide before fetching
requires: [objid] // Topics a learner must know first; all
// MUST be lower level (progression rule,
// checked by verifiers and by clients)
note: text // author's summary of what changed / why
}
Markdown is the only content format. It must render fine as plain text; the corpus stays readable without any of our software. Guide properties (the title and the donate button) are NOT part of the revision; they belong to the duke and live in topic-props (SPEC 5.7). A content edit physically cannot touch them.
Money rules:
- There are no ads. The protocol has no ad object, no placement mechanism, and no tracking to sell. Clients MUST NOT inject advertising into guides.
- The duke's
donateproperty (SPEC 5.7) is the only money surface: clients render it as a single, clearly labeled Donate button at the bottom of the guide, nothing more. The duke sets it or deletes it; deleting is always allowed, and nobody else can touch it. - The button is an external link. It is the only thing on a guide that leaves the network, and clients MUST warn before opening it (external requests bypass the network's anonymity properties).
- Property changes don't pass through juries, so abuse is handled
editorially: a donate link pointing at a store, an affiliate scheme,
or someone other than the duke gets the props object revoked
(SPEC 7
revoked), and repeated abuse costs the dukedom (SPEC 5.7 reassignment).
5.4Vote
A verifier's vote on a pending Revision.
body {
revision: objid
verdict: text // "approve" | "reject"
reason: text // REQUIRED, non-empty. Written feedback to
// the author. Votes without reasoning are
// invalid and dropped.
}
Only votes from verifiers in the roster (per the manifest current at
the revision's announcement) for the topic's subject + level count.
Everyone can tally; no trusted counter exists. One counted vote per
juror per revision, latest wins (higher ts; a tie prefers the lower
object ID), so a verifier may change their verdict while the window
is open and every node still reaches the same count.
5.5Rating (post-publication)
Reader feedback on published revisions (the podcast's up/down vote).
body {
revision: objid
value: i8 // +1 | -1
}
One counted rating per identity per revision: the latest by ts
wins, and if two ratings from the same key carry an equal ts, the
one with the lower object id wins (the §5.4 revote rule, so every
node reaches the same tally). A rating whose value is not exactly
+1 or -1 is ignored entirely (it neither counts nor displaces an
earlier valid rating). Heavy sustained downvoting flags a revision
for editor re-review; thresholds are editorial policy, not protocol.
How clients consume the tally for discovery (sort orders, search
ranking, the editors' advisory queue) is SPEC 12.2.
5.6Removal petition and removal vote
Community removal is the delisting mechanism for guides that should not be on the network at all. Ordinary quality problems go through review and improvement instead; removal is deliberately hard.
type "removal-petition"
body {
topic: objid // Topic proposed for removal
reason: text // REQUIRED, non-empty
}
type "removal-vote"
body {
petition: objid
verdict: text // "remove" | "keep"
reason: text // optional
}
Rules:
- One counted vote per identity per petition (latest wins), weighted by the voter's standing (§5.8), so a removal cannot be forced or blocked by spinning up empty identities.
- Voting window:
removal.window_daysfrom the petition's first sighting (launch value: 30). - The petition passes iff ALL hold:
- the combined standing of the votes cast is at least
removal.quorum(a quorum of standing, launch: 200), and - at least
removal.min_votersdistinct standing-bearing identities voted (launch: 15), and - at least
removal.threshold_pctpercent of the standing cast is "remove" (launch: 75).
- the combined standing of the votes cast is at least
- The quorum and voter floor ride the §9 growth ramp: they are judged at the manifest values only once the network holds MATURE_STANDING total standing (and MATURE_VOTERS standing-bearing identities); below that, both scale down in proportion to what exists, with a floor of 1, so a young community can still remove bad content. The threshold percentage never scales.
- A passed petition MUST move the topic to the next manifest's
removedlist. Nodes then delete the topic's revisions, and any blobs referenced only by that topic, from local storage, and MUST NOT store, forward, or display them again. Archive exports exclude removed topics. - The petition and its votes remain on the network as the public, auditable record of the removal.
- Deletion in a peer-to-peer system is enforced by compliant software; a modified client can retain private copies. The guarantees removal actually provides: delisted from the official index everywhere, purged from every compliant node, and never re-admissible into the official tree without a new review.
5.7The duke: guide properties
The identity that authored a topic's first published revision is that
topic's duke: the guide's original poster. The duke owns the
guide's PROPERTIES and nothing else. Content editing is completely
unaffected: anyone (including the duke) edits text, images, and
attachments exactly as in SPEC 5.3 and 8. The current duke of every
topic is recorded in the manifest's dukes map.
type "topic-props"
body {
topic: objid // Topic these properties belong to
seq: u64 // strictly increasing per topic
title: text / null // display title override; null = keep
// the Topic object's original title
donate: text / null // donation URL; null = no donate button
}
type "duke-transfer"
body {
topic: objid
to: pubkey / null // new duke; null = renounce (props
// freeze until editors reassign)
}
Rules:
- Properties are duke-only. A
topic-propsobject is valid only if signed by the topic's current duke; nodes drop any other. The valid object with the highestseqwins (tie: lower object ID). It applies immediately: no jury, no review, no waiting. - Properties are display-level. Clients show
title(when set) instead of the Topic object's original title, and renderdonateper the money rules (SPEC 5.3). The underlying objects never change; props are a signed overlay. - The donate link is http(s) or nothing. A
donatevalue that does not begin withhttp://orhttps://is treated as null at fold time by every node: no donate button renders, and the value never reaches a link handler, so a hostile scheme (javascript:,file:, a custom OS handler) cannot ride a props object into a reader's click. The rest of the props object (itstitle, itsseq) still applies; only the field is dropped. Opening a valid donate link MUST go through the same leave-the-network warning as any external link (SPEC 5.3). - Content is not the duke's. A duke's own content edits go through jury review like everyone else's, and other people's jury-passed edits publish without the duke's involvement.
- Transfer and renounce. A duke may hand the dukedom to another
identity with
duke-transfer, or renounce it (to: null). - Reassignment. Editors MAY set a topic's duke in the manifest
dukesmap for lost keys, abandonment, or property abuse. Reassignments are public in the manifest chain. Removal votes (SPEC 5.6) apply to duked guides like any others.
5.8Standing (earned, verifiable weight)
Community votes must be Sybil-resistant: identities are free, so raw one-key-one-vote can be manufactured. Every community tally is therefore weighted by standing, a number each node computes deterministically from on-network history, with no trusted counter:
standing(identity) =
author_weight * (# of that identity's revisions ever listed in
any manifest's `approved`)
+ review_weight * (# of jury votes that identity cast, with reason,
on revisions that reached a final jury outcome)
capped at `standing.cap`
- Weights and the cap live in the manifest (
standing, §7), bounded by §9. Launch: author_weight 3, review_weight 1, cap 1000. - Standing is earned only by contributing. It cannot be bought, transferred, or granted. A fresh key has standing 0, so a Sybil farm carries no weight no matter how many keys it holds.
- Standing is a pure function of objects every node already holds, so every node computes the same value and any tally is recheckable.
- Windows use the manifest
seqas the network's coarse clock (there is no global wall clock); "recent" means "within the last N manifests," never a timestamp.
5.9Verifier certification (merit, not appointment)
Who reviews content is decided by demonstrated competence, not editor favor. A user becomes eligible to be a verifier by collecting certifications from people already trusted in that subject:
type "verifier-cert"
body {
candidate: pubkey // the identity being vouched for
hierarchy: objid // subject this certification is for
max_level: u16 // up to what level (<= the signer's own)
reason: text // REQUIRED, non-empty: why they qualify
}
- Valid only if signed by a current verifier for
hierarchywhose ownmax_level>= this one. Nobody can certify above their own level. - A candidate is eligible for (hierarchy, level) once they hold
certs_mincertifications from distinct current verifiers (manifestcerts_min, §7; launch: 3). Genesis seeds the first verifiers; the web of trust grows from there. - Editors MUST seat every eligible candidate in the next manifest and MUST drop any verifier removed by a passed recall (§5.10). A manifest that omits an eligible candidate or keeps a recalled verifier is invalid: the roster is community-earned, not editor-granted. (An optional subject test MAY gate certification in a future spec version; peer certification is the baseline.)
5.10Community governance petitions
Removal (§5.6) lets the community delist bad content. The same petition primitive, standing-weighted, lets the community govern the network's people and parameters, so no minority can capture it:
type "petition"
body {
kind: text // "recall-verifier" | "seat-verifier" |
// "no-confidence" | "restore" | "set-param" |
// "remove-post" | "remove-board" | "remove-media" |
// "remove-thread" | "lock" | "unlock" | "pin" | "unpin"
target: text // verifier/candidate key, revision/object id, param
// name, or "editors" (for no-confidence), as b3/ed
// text
extra: map / null // kind-specific payload (below)
reason: text // REQUIRED, non-empty
}
type "petition-vote"
body {
petition: objid
verdict: text // "yes" | "no"
reason: text // optional
}
Kinds:
- recall-verifier (
target= verifier key): passing strips that verifier from the roster and voids their outstanding certs. - seat-verifier (
target= candidate key):extranames{hierarchy: objid, max_level: u16}. Passing seats the candidate on the roster at that grant. This is the community's backstop when the peer-certification web (§5.9) goes closed or dormant: certification by working verifiers stays the normal path, but a guild that refuses to grow can be routed around by standing-weighted vote, so review capacity is never one clique's gift. - no-confidence (
target= "editors"):extranames a successor{editors: [pubkey], threshold: u8}. Passing seats that set as the legitimate editors (§7): the community can always replace captured or absent editors, with no one's permission. - restore (
target= revision/object): passing removes an object from the manifestrevokedlist, an appeal against editor revocation. - set-param (
target= param name):extranames the value. Passing forces that parameter, within the §9 bounds, into the next manifest. Governed parameters change ONLY this way: a manifest that alters one without citing the passed petition is invalid (§7). A passed set-param that cannot lawfully be applied (a target that is not a governed parameter, a value that is not its canonical decimal form, a value outside the §9 bounds, or a quorum raise beyond the §9 one-step rate limit measured from the manifest in force) is void, not a standing obligation: editors are never bound to publish an invalid manifest, and one malformed petition must not wedge every future succession (the no-hostages principle). The community petitions again within the limits (a large quorum raise proceeds in steps). - remove-post (
target= post object id): passing removes an abusive forum post (§5.13). Removing a thread starter hides its whole thread. Tallied like any petition, at the restore-tier parameters (governance.restore); a dedicated bar would need a new manifest field, which would change the pinned genesis id (§7). VOID if the target is an approved guide topic: guides are delisted only through §5.6 removal at its higher bar, never through the forum path. - remove-board (
target= board object id): passing removes an abusive board (§5.14) and the posts on it. Tallied at the restore-tier parameters, with the same guard as remove-post. - remove-media (
target= media item object id): passing removes an abusive standalone media item (§5.19). Tallied at the restore-tier parameters, with the same guard as remove-post. - remove-thread (
target= thread-starter object id): passing removes a whole thread and cascades to every reply in it (§5.14). The starter is listed inremoved; compliant nodes hide it and every post whose thread it starts. Same tier and guard as remove-post; the one-level-down partner of remove-board. - lock / unlock (
target= board or thread-starter object id): a community freeze (or its lifting) that stops new threads/replies without deleting anything (§5.14). Unlike a §5.16 sticky/lock (instant, standing- gated curation), this is a vote. A DISPLAY/compose rule, NOT baked into the manifest (which would need a new field and move the pinned genesis id, §7): clients derive the current state from the passed-petition set, honoring the LATEST-passed of a target's lock/unlock petitions. - pin / unpin (
target= board object id): a community vote to float a board to the top of the listing, or lift that (§5.14). Derived from the passed-petition set like lock/unlock (latest-passed wins); a display rule, not a manifest field.
Tallying (identical for every kind, and for removal §5.6):
- One counted vote per identity per petition (latest wins, as §5.4:
higher
ts, a tie prefers the lower object ID), each weighted by the voter's standing (§5.8). - The petition passes iff, among votes cast before the close seq
(
governance.<kind>.window_seqmanifests after it is first seen), ALL hold: the combined standing of the votes is at least the kind'squorum(a quorum of STANDING); at leastmin_votersdistinct standing-bearing identities voted; and "yes" standing is at leastthreshold_pctof the standing cast. - Every kind's
quorumandmin_votersride the §9 growth ramp (exactly as removal does, §5.6): scaled in proportion to the network's total standing and standing-bearer count until those reach MATURE_STANDING / MATURE_VOTERS, with a floor of 1. Because every kind rides the same ramp, the §9 ratios between bars (no-confidence highest) hold at every network size. Thresholds and windows never scale. - The remedy never runs on the hostage's clock. Windows are
normally counted in manifests, but manifests come from the editors,
and a dead or hostile editor set must not be able to freeze the very
clock its own replacement runs on. So: if a node's accepted chain
head does not advance for 30 consecutive days (
STALL_DAYS, a protocol constant, deliberately NOT a manifest parameter), every open petition window on that node closes 30 days after its local first sighting, on the node's own clock, the same way removal windows already run (§5.6). - An irreversible supermajority passes immediately (no-confidence
only): the moment a no-confidence petition's "yes" standing meets
the quorum, meets
min_voters, and is at leastthreshold_pctof the TOTAL standing of every identity computable from the corpus, no votes that could still arrive can change the outcome, so the petition passes at once. Editor replacement therefore cannot be stalled even by a chain that will never advance again. - Every node computes the same result from the same objects. Petitions and their votes are permanent and public: the governance record is auditable forever. For no-confidence, nodes act on the result themselves (§7); they do not need the incumbent editors' cooperation, which is the entire point.
Bars rise with power: restore is the gentlest (it is the pro-content direction); recall and seat are moderate; no-confidence is the highest bar on the network (§9). Governance is deliberately hard, but never impossible, and never in one group's hands.
5.11Availability (review duty is opt-in)
Almost everyone here is a reader, and a reader is never called on for anything. Nobody is drafted onto a jury, assigned a submission, or pinged to vote. Participation is a signal a user chooses to send, never a duty the network imposes. Reviewing in particular is opt-in and opt-out at will:
type "availability"
body {
available: bool // "I am reviewing right now" on/off
seq: u64 // highest manifest seq the signer has seen
}
- Default off. A key that has never published an
availabilityobject withavailable=trueis not review-active, so it is never placed on a jury (§8). Installing the app, reading, and even contributing a guide never enrolls anyone in review duty; only the explicit "I want to help review" setting does, and it is off until the user turns it on. - Latest-by-
seqwins, so a user flips the switch off (going inactive, on vacation) at any time and stops being drawn, with no recall and no ceremony. If two signals from the same key carry an equalseq,available=falsewins: opting out is never lost to message ordering. - Freshness, not a promise. The signal is jury-relevant only while
fresh:
available=trueandseq >= current_manifest_seq - status_ttl(manifeststatus_ttl, §7, bounded §9; launch: 8 manifests). A verifier who simply stops refreshing quietly falls out of the eligible pool, so an abandoned key can never stall reviews. Clients that hold the setting on refresh the signal automatically as new manifests arrive. - Being review-active is not the same as being a verifier. Only a certified verifier (§5.9) is drawn for a jury, and only while also review-active; the flag governs when an already-qualified reviewer is on call, so competence and consent are separate gates and both are required.
5.12Comment (talk pages)
Every guide has a public discussion thread, the community layer's forum idiom: improvements are argued in public, before and after submission, and the thread is part of the permanent record. A comment is an ordinary signed, flooded, immutable object:
type "comment"
body {
topic: objid // the Topic under discussion
reply_to: objid | null // parent comment (null = a new thread)
text: text // CommonMark; non-empty, <= 8192 bytes
}
- Immutable like everything else: no edits, no deletions. A correction or retraction is a new reply saying so. Whether the community ever wants comment curation is a governance question for a later spec revision; v1 has none beyond admission control.
- Well-formedness (display rule): a comment with empty
text,textover 8192 bytes, or areply_tothat does not resolve to a comment on the SAME topic is invalid for display; nodes may still store and forward the envelope (it is signed and paid for), clients simply never render it. - Deterministic order: display order is
tsascending, ties by lower object id. Threading followsreply_to; rendering depth is a client choice. - Admission: the §9 vote-tier proof of work (16 bits at launch) and the §9 per-author rate limit. Clients MUST render comment text through the same sanitizer as guide content; a comment can never carry active content.
5.13Forum post (community boards)
Beyond a guide's talk page (§5.12), the network carries a community-wide forum: discussion that belongs to the whole network rather than to any one guide. A post lives on a board (§5.14). Like a comment it is an ordinary signed, flooded, immutable object.
type "post"
body {
board: objid // the Board (§5.14) this belongs to
reply_to: objid / null // parent thread starter (null = a new thread)
title: text / null // thread title; REQUIRED on a thread starter
// (null reply_to), MUST be absent on a reply
text: text // CommonMark; non-empty, <= 16384 bytes
}
- Threads and replies. A post with
reply_to = nullstarts a thread and carries atitle; a post withreply_toset is a reply and points at the thread starter it answers. Replies are flat within a thread:reply_toMUST resolve to a thread starter (areply_to = nullpost) on the SAME board. Deeper quoting is a matter for the text, not the object graph. - Immutable like everything else: no edits, no deletions. A correction or retraction is a new reply saying so.
- Well-formedness (display rule): a post is invalid for display if
its
textis empty or over 16384 bytes, if a thread starter'stitleis empty or over 200 bytes, if a reply carries anytitle, or if a reply'sreply_todoes not resolve to a thread starter on the same board. Nodes may still store and forward the envelope (it is signed and paid for); clients simply never render it. - Deterministic order: within a thread, replies display by
tsascending, ties by lower object id. Thread and board ORDERING for discovery (newest, most-active, top-rated) is a client choice (§12.2), never consensus. - Rating. A reader may rate a post +1 / -1, the forum's first-line moderation signal, so junk sinks before any removal vote is needed:
type "post-rating"
body {
post: objid // the post being rated
value: i8 // +1 or -1; any other value is ignored
}
One counted rating per identity per post (latest by ts wins, ties by
lower object id). Ratings never gate storage or propagation; they only
order display (§12.2), exactly like guide ratings (§5.5).
- Admission: the §9 vote-tier proof of work (16 bits at launch) and the §9 per-author rate limit. Clients MUST render post text through the same sanitizer as guide content; a post can never carry active content.
- Removal. An abusive post is removed by a standing-weighted
community vote: a
remove-postpetition (§5.10) whosetargetis the post's object id, tallied at the restore-tier parameters. On passing, the next manifest lists the post's id inremovedand compliant nodes hide it; removing a thread starter hides the whole thread. The petition and its votes remain on the network as the public record of the decision, exactly as with guide removal (§5.6).
5.14Forum board
A board is a community-created category that posts (§5.13) live on: e.g. "General", "Help", or any subject the community wants a standing place to discuss. Boards are the forum's structure, so they sync with the topic tree (§16.3) rather than with the posts inside them.
type "board"
body {
slug: text // URL-safe short id for links: non-empty, lowercase
// [a-z0-9-], <= 32 bytes. Cosmetic; a board is
// referenced by object id, and two boards may share a
// slug.
name: text // display name, non-empty, <= 80 bytes
about: text // one-line description, <= 280 bytes
category: text // OPTIONAL grouping label for the listing (the phpBB
// "category > forum" layout), <= 60 bytes. Absent or
// empty = uncategorized. Cosmetic, like the slug.
}
- Immutable like every object; a rename is a new board.
- Category (display only).
categorygroups boards in the listing and nothing more; two boards with the same label sit under one heading. It is optional and, being a later-added field, isserde(default): a board signed before the field existed decodes as uncategorized rather than being dropped, so adding it is not a wire break. - Standing-gated creation (display rule). Anyone may sign a board,
but a board is LISTED only while its author's earned standing (§5.8) is
at least
BOARD_MIN_STANDING(3 at launch): starting a board is reserved for proven contributors, while posting inside one is open to every identity. This is a client display rule, not a propagation gate: the envelope still floods on signature and proof of work like anything else, and every node computes the author's standing from the corpus it already holds, so all clients agree on which boards show. A board whose author is below the floor (and any threads under it) is simply never rendered.BOARD_MIN_STANDINGis a protocol constant rather than a manifest parameter, so introducing the forum does not change the pinned genesis id (§7); a later revision may promote it to a governed parameter once a checkpoint manifest is re-cut. - Well-formedness (display rule): a board with an empty or
non-
[a-z0-9-]slug, a blank or over-long name, an over-longabout, or an over-longcategoryis invalid for display; the envelope may still be stored and forwarded. - Admission: the §9 topic-tier proof of work (22 bits at launch, the scarce-object tier) and the §9 per-author rate limit.
- Removal (cascade). A board is removed by a
remove-boardpetition (§5.10) whosetargetis the board's object id, tallied at the restore-tier parameters. On passing, the board's id is listed in the manifestremovedset, and compliant nodes hide the board AND everything in it: every thread and reply whose board this is cascades out of every listing and direct link. A single thread is removed the same way with aremove-threadpetition (the starter is listed inremoved, and its replies cascade), so the community can take down one bad thread without the whole board. - Community lock / pin (§5.10). Besides removal, the community can vote to lock a board or a thread (freeze it to new posts without deleting it) and lift that with unlock, and to pin a board to the top of the listing and lift that with unpin. These are DISPLAY/compose rules a client derives from the passed-petition set (latest-passed per target wins), never baked into the manifest, so they add no new manifest field and cannot move the pinned genesis id (§7). They sit alongside the §5.16 sticky/lock, which is instant, standing-gated curation rather than a vote.
5.15Forum post edit
Posts (§5.13) are immutable objects, but a forum needs the ordinary courtesy of fixing a typo. An edit is a NEW object that overlays the DISPLAY of the post it names, exactly as topic properties (§5.7) overlay a topic: the original envelope never changes on the wire.
type "post-edit"
body {
post: objid // the post (§5.13) this revises
text: text // replacement CommonMark, <= 16384 bytes; empty = delete
}
- Author only. An edit applies only when its author is the SAME identity that signed the post it names. An edit by anyone else is ignored (it is still stored and forwarded, never rendered).
- Latest wins. Among an author's valid edits of one post, the display
uses the latest by
ts(ties by lower object id), so re-editing works. - Deletion. An edit with empty
textis the tombstone: the client renders "removed by the author" in place of the body. The post and its replies stay in the thread; this is a courtesy self-retraction, NOT the community removal of §5.10 (which lists the id in the manifestremovedset and purges it). - Display marker. A post with any applied edit is shown "edited", with the edit's timestamp, so the change is never silent.
- Admission: §9 vote-tier proof of work and the per-author rate limit (charged the post budget).
5.16Forum thread moderation (sticky / lock)
Community-governed curation of a thread: pin it to the top of its board, or close it to new replies, the phpBB sticky and lock.
type "moderation"
body {
thread: objid // the thread starter (§5.13) this flags
sticky: bool // pin to the top of the board listing
locked: bool // close to new replies
seq: u64 // strictly increasing per thread; latest wins
}
- Standing-gated (display rule). A flag TAKES EFFECT only while its
author's earned standing (§5.8) is at least
MOD_MIN_STANDING(10 at launch): curation power is reserved for proven contributors, a higher bar than starting a board because it acts on other people's threads. LikeBOARD_MIN_STANDINGthis is a client rule, not a propagation gate, and a protocol constant so the forum does not change the pinned genesis id (§7). - Latest wins, reversible. The flag with the highest
seqper thread wins (ties by lower object id), so any sufficiently-standing member can undo another's flag by signing a newer one. Sticky and lock are independent booleans carried together. - Locked threads. A compliant client refuses to COMPOSE a reply to a locked thread; a reply that is signed anyway (a non-compliant client) still floods and stores, but compliant clients do not render it under a locked thread. Locking is curation, never removal.
- Admission: §9 topic-tier proof of work (the scarce curation tier, like a board) and the per-author rate limit (the board budget).
5.17Forum poll
A thread may carry a poll, the phpBB staple: a question and a set of options the community votes on.
type "poll"
body {
thread: objid // the thread starter (§5.13) this poll belongs to
question: text // non-empty, <= 300 bytes
options: [text] // 2..=20 options, each non-empty and <= 120 bytes
multi: bool // may a voter choose more than one option?
}
type "poll-vote"
body {
poll: objid // the poll being voted on
choices: [u32] // indices into the poll's options; >=1 choice
}
- Author only, latest wins. A poll applies only when authored by the
thread starter's own author; among that author's valid polls for the
thread, the latest by
tswins (ties by lower id), so a typo can be fixed. A poll by anyone else is ignored. - One vote per identity. A poll-vote counts once per identity (latest
by
tswins, ties by lower id). Out-of-range indices are dropped, and if the poll is notmultionly the first surviving choice counts. - Display, never consensus. Poll results order and inform display only; they gate nothing, exactly like ratings.
- Admission: §9 vote-tier proof of work and the per-author rate limit (a poll charges the post budget; a poll-vote the votes+ratings budget).
5.18Forum signature
A member may publish a short signature, rendered as CommonMark under each of their posts, the phpBB signature line.
type "signature"
body {
text: text // CommonMark, <= 512 bytes; empty clears it
}
- Latest per author wins (highest
ts, ties by lower id). A pure display overlay keyed on the signer, so it carries no target; emptytextclears the signature. Rendered through the same sanitizer as all other forum content. - Admission: §9 vote-tier proof of work and the per-author rate limit (the comment budget; signatures change rarely).
5.19Media item (standalone media)
A standalone book, video, or file, shared on its own rather than attached to a guide revision (§5.3) or a forum post (§5.13). The bytes ride the §6 blob machinery unchanged; this object is the small, flooded record that names them, gives them a title, and makes them discoverable.
type "media"
body {
title: text // non-empty, <= 200 bytes
description: text // <= 2048 bytes; may be empty (omitted when empty)
kind: text // "book" | "video" | "audio" | "image" | "file"
mime: text // non-empty, <= 128 bytes
size: u64 // the ORIGINAL file size in bytes, > 0
blob: b3hash // the payload: a plain blob when small, or a
// §6.1 media index when chunked
blobs: [blobref] // every blob needed to reconstruct (the payload
// plus its parts when chunked); non-empty, and
// always lists `blob`
encoding: text // "raw" | "deflate": how to rebuild the file
}
- Immutable and flooded like any object, at background priority (a fresh install reads guides before the media archive). An item is a leaf: nothing hangs off it.
- Standing-gated display, exactly as a board (§5.14): the object floods
on signature + PoW regardless, but a node LISTS an item only while its
author's earned standing (§5.8) is at least
MEDIA_MIN_STANDING(3 at launch, a protocol constant for the same genesis-safety reason as the board floor). So publishing to the shared archive is reserved for proven contributors, while the bytes still store and forward. - Storage (§6 / §6.1). A file at or under the blob cap is one content-addressed blob; a larger one is cut into 8 MiB parts named by a media index, so a huge book or video is split across peers and replicated by the ordinary durability scheme (§17), never stored whole on any one node. Content-addressing dedups identical uploads.
encoding. Blob verification is unchanged: every blob is addressed and checked by the hash of its RAW bytes.encodingonly tells a client how to reconstruct the original file from the payload:rawmeans the bytes ARE the file;deflatemeans they are a DEFLATE stream to inflate. Already-compressed media (image/video/audio) is alwaysraw; an uncompressed upload (a plain-text book) may be storeddeflateto save space, deterministically so identical uploads still dedup. A client that does not understand anencodingstill stores and forwards the bytes.- Admission: §9 topic-tier proof of work (the scarce, standing-gated tier, since an item pins bytes on peers) and the §9 per-author rate limit.
- Removal. An abusive item is taken down by a
remove-mediapetition (§5.10) whosetargetis the item's object id, tallied at the restore-tier parameters like the other forum removals. The uploader can also withdraw their OWN item unilaterally, without a vote, via an author retraction (§5.20).
5.20Author retraction (self-delete)
The §5.15 post tombstone generalized to any content type: an author's withdrawal of their OWN object. A media item (§5.19) is the first user today; any future single-author object may adopt it. Like a post edit, it is a NEW object that overlays the DISPLAY of the object it names; the target envelope never changes on the wire.
type "author-retract"
body {
target: objid // the object this author withdraws
}
- Author only. A retraction takes effect ONLY when its author is the
SAME identity that signed the
target. A retraction by anyone else is ignored (still stored and forwarded, never acted on), so no one can withdraw another person's work. Checking this needs the store, so it is a read-time rule. - Effect. A client HIDES a withdrawn object: it drops out of listings
and its detail renders "withdrawn by its author" in place of the
content. This is a display overlay, a courtesy self-retraction, NOT the
community removal of §5.10: the object is not listed in the manifest
removedset. But because interested readers stop displaying a withdrawn item, they stop fetching and re-pinning its blobs, so its bytes decay from the network the way any unwanted content does (§17). - Terminal and idempotent. There is no un-retract (a withdrawn object stays withdrawn, matching the post tombstone), and signing the same retraction more than once has no added effect.
- Admission: §9 vote-tier proof of work (a cheap author act, like a post edit), and the §9 per-author rate limit.
6.Blobs
- Blob ID = BLAKE3-256 of the raw bytes. Max blob size: 32 MiB (bigger media must be split or hosted as multiple parts).
- Blobs are fetched on demand over a direct peer stream, verified against their hash, cached, then re-announced (popularity = replication).
6.1Chunked media (files over one blob)
A file larger than the cap is cut into fixed-size parts (the reference client uses 8 MiB; the last part is ragged), each an ordinary blob, named by a media index blob:
bytes = "AMI1" || canonical CBOR {
mime: text, // the FILE's media type, e.g. "video/mp4"
size: u64, // total file bytes
chunk: u64, // the fixed part size this index was cut with
parts: [b3hash] // the parts, in file order
}
The index's own blob id is what guide markdown references. A Revision
using chunked media MUST list the index AND every part in its blobs
(§5.3), so readers replicate and fetch the whole file from one
Revision and can weigh its full size before fetching. Parsers MUST
treat a blob as an index only when the magic matches, the CBOR
decodes, and ceil(size / chunk) == len(parts); anything else is
plain bytes. Parts and index ride the identical announce / fetch /
pin machinery above; nothing else in the protocol changes.
Markdown media forms (§5.3):  renders an image;
a #video or #audio fragment on the id is a client rendering hint
for a native player; [name](blob:b3:<id>) is a downloadable
attachment. <id> may be a plain blob or a media index; clients
resolve both the same way. Clients MUST NOT load media from any
non-blob origin when rendering (a remote URL would deanonymize every
reader).
- Announce (flooded, small, expiring):
type "blob-have"
body {
blob: b3hash
bytes: u64
}
Nodes maintain a blob -> [peer] map from announces. Announces are
re-gossiped by holders roughly daily and expire from maps after ~3
days. Nodes SHOULD pin a random selection of RARE blobs (few
announcers) within their donated disk budget so nothing dies out.
7.The Manifest (editorial layer)
The manifest is the canonical, curated index. Clients show manifest-approved content by default.
type "manifest"
body {
seq: u64 // strictly increasing
prev: objid / null // previous manifest (chain, auditable)
editors: [pubkey] // editor set for the NEXT manifest
threshold: u8 // signatures required (e.g. 3 of 5)
hierarchies:[objid] // recognized subject roots
docket: [objid] // revisions acknowledged for review;
// THIS manifest's id salts each one's
// jury draw (SPEC 8), which is what
// makes juries grind-proof
approved: { topicid -> revisionid } // THE canonical index
verifiers: [ {key: pubkey, hierarchy: objid, max_level: u16} ]
// who may vote, on what, up to what level
revoked: [objid] // revisions/objects editors mark as
// superseded-for-cause (still exist on
// the network: curation, not deletion)
removed: [objid] // topics removed by community vote
// (SPEC 5.6); nodes purge their content
removal: {threshold_pct: u8, quorum: u64, min_voters: u32,
window_days: u16}
// remove-topic vote params (SPEC 5.6);
// quorum is STANDING, not identities
standing: {author_weight: u16, review_weight: u16, cap: u32}
// SPEC 5.8 standing weights (bounded §9)
certs_min: u8 // SPEC 5.9: distinct verifier-certs a
// candidate needs to be seated (launch 3)
status_ttl: u16 // SPEC 5.11: manifests an availability
// signal stays jury-fresh (launch 8)
governance: { <kind> -> {quorum: u64, threshold_pct: u8,
min_voters: u32, window_seq: u16} }
// SPEC 5.10 petition params per kind
// (recall/seat/no-confidence/restore/
// set-param); quorum is STANDING; all
// bounded by §9
param_changes: [ {param: text, petition: objid} ]
// SPEC 5.10: every governed parameter
// that differs from the previous
// manifest MUST cite the passed
// set-param petition ordering it (rules
// below); empty when nothing changed
mandate: objid / null // SPEC 5.10: the passed no-confidence
// petition that seated THIS editor set
// after a handoff; null on normal
// succession
dukes: { topicid -> pubkey }
// current duke of every topic
// (SPEC 5.7); default = author of the
// topic's first published revision
base: objid / null // the CHECKPOINT this manifest's
// approved/dukes are expressed against
// (§7.1). null => this manifest is
// itself a checkpoint and approved/dukes
// are the FULL index. non-null => a
// DELTA carrying only the entries that
// changed since `base`
snapshot: objid / null // at a checkpoint, an optional
// standing-snapshot object (§7.2) so a
// joining node need not replay the whole
// vote history; null on deltas and when
// no snapshot is published (e.g. genesis)
}
sigs: [ {key: pubkey, sig: bytes(64)} ] // replaces single `sig`;
// must satisfy prev manifest's editors +
// threshold, OR a no-confidence mandate
// (rules below)
Rules:
- Manifest
seqN is valid iff signed by ≥thresholdkeys from manifest N-1'seditorslist. Editor succession is built in: each manifest names the next editor set, so keys rotate without any single point of failure. Founders sign the genesis manifest (seq 0, hardcoded hash in the client), then hand off as the community grows. - Every structurally valid revision a node has seen MUST appear in the
docketof the next manifest or the one after it: acknowledging a submission is not discretionary, so editors cannot censor by pretending not to see. The docketing manifest's own id then salts that revision's jury draw (§8). - A revision with a majority of approve-votes from its selected jury (§8) within the voting window MUST appear in the next manifest or the one after it. Any node can check this; a manifest that breaks the rule is invalid and clients reject it. Editors curate; they cannot silently censor a passed submission or publish a failed one. (The one-manifest grace exists for propagation lag on a gossip network: an honest editor set is never invalidated by a vote that reached it one cycle late, and a node judging a manifest invalid over an obligation should first ask whether ITS view is behind; inventory reconciliation (§16) and the fork choice below converge honest disagreement.)
- Two valid manifests with the same
seq(editor split) = clients follow the chain with more distinct editor signatures, then lower object ID as tiebreak, and surface a loud warning. Human dispute resolution takes it from there.
Editors are bound delegates, not owners. Every node also enforces:
- The roster is earned, not granted (§5.9). Editors MUST seat
every candidate holding
certs_mindistinct verifier-certs, MUST seat every candidate a passed seat-verifier petition named, and MUST drop any verifier a passed recall removed. A manifest that omits an earned verifier or keeps a recalled one is invalid. - Passed petitions bind the editors (§5.10). Any passed petition (remove-topic, recall-verifier, seat-verifier, restore, set-param) MUST be reflected in the next manifest or the one after it (the same propagation grace as above). Editors execute the community's decisions; they do not veto them.
- Parameters move only on a mandate. A governed parameter (the
removal,standing,governance, and PoW blocks,certs_min,status_ttl) that differs from the previous manifest MUST have aparam_changesentry citing the passed set-param petition that ordered it, and the applied value must be the petition's value. A manifest changing a governed parameter without its citation is INVALID: the §9 bounds cap how bad a value can be, this rule removes the editors' ability to choose values at all. The chain's own content (editor set, threshold, hierarchies, docket, approved, verifiers, revoked, removed, dukes, mandate) is not a governed parameter. - Revocation is not a censorship backdoor.
revokedMUST NOT contain a currently jury-approved revision unless a newer approved revision of the same topic supersedes it, or a passed remove-topic/restore/dispute justifies it. Arestorepetition reverses a wrongful revocation. - Parameters are bounded. Every tunable value (standing weights,
petition and removal quorums/thresholds/windows, PoW,
certs_min) MUST stay within the hard limits in §9. A manifest outside them is invalid, so editors cannot set removal out of reach or PoW to zero. - The community can re-seat the editors (§5.10 no-confidence). A
manifest at seq N is ALSO valid if signed by >=
thresholdof a successor editor set named in a PASSED no-confidence petition; such a manifest cites that petition inmandate. Clients verify the mandate themselves and follow the mandated lineage over the old editors, who cannot block it. If two lineages compete, follow the one with more standing behind its mandate, then more distinct editor signatures, then lower object ID, and surface a loud warning. No editor set is permanent; none can hold the network hostage.
7.1Delta and checkpoint manifests (bounded growth)
The approved and dukes maps have one entry per topic, so a manifest
that always carried them in full would grow with the whole corpus and
be re-published, signed, and validated every editorial cycle: an
O(corpus) object emitted forever. To keep the chain flat as the library
grows, every manifest is either a checkpoint or a delta, chosen
by its base field, and only these two corpus-linear maps are
compressed. Every other field (removed, revoked, verifiers,
hierarchies, every governed parameter, docket, editors) is carried
in full in every manifest, delta or checkpoint: those grow with events
or roster size, not with the topic count, and keeping them whole keeps
validation and fork choice unchanged.
- Checkpoint (
base = null).approvedanddukesare the complete index as of thisseq. The genesis manifest is always a checkpoint, and a checkpoint MUST be published at least everyCHECKPOINT_INTERVALmanifests (a protocol constant, 64 at launch), so the fold below is bounded no matter how old the chain is. - Delta (
base = <checkpoint id>).approvedanddukescarry ONLY the entries added or overwritten since the checkpoint named bybase.baseMUST be the most recent checkpoint at or before this manifest's own lineage; a delta whosebaseis not an accepted checkpoint on this chain is invalid.
Folding to the effective index. The approved index a client acts on
is not read from a single manifest; it is folded over the run from the
base checkpoint to the head:
(approved, dukes) = (checkpoint.approved, checkpoint.dukes) // full
for each delta from base+1 to head, in seq order:
approved.extend(delta.approved) // add / overwrite per topic
dukes.extend(delta.dukes)
for each topic in head.removed: // removed is full at the head
approved.remove(topic); dukes.remove(topic)
The result is identical to what an all-full-manifest chain would have
carried, so nothing downstream of the fold (the library index, guide
lookup, the duke overlay, §5.7) changes. Validation, hashing, and fork
choice operate on the wire manifest exactly as before: base and
snapshot are ordinary signed fields, and a delta is threshold-signed
and chained by seq/prev like any manifest. A node that has not yet
received the base checkpoint holds the delta until it does (the same
out-of-order handling the chain already uses), and reconciliation
fetches manifests approved-index-first (§16.3).
This section is a pure encoding of the same index; it introduces no new authority and changes no rule about what may be approved, removed, or who may sign. It exists solely so a decade-long chain does not carry a decade of full-corpus snapshots.
7.2Standing snapshots (bounded cold-start)
Earned standing (§5.8) is a pure function of the whole object history: every approved revision an identity authored and every reasoned jury vote it cast. Recomputing it from genesis is fine while the history is small and unbounded work once it is not, and a joining node needs it to weigh every governance tally. A standing snapshot lets a node start from a trusted summary instead of replaying from the beginning.
type "standing-snapshot"
body {
seq: u64 // the manifest seq this
// summarizes standing as of
prev: objid / null // previous snapshot (audit chain)
entries: { pubkey -> {authored: u32, reviewed: u32} }
// the two counts §5.8 scores:
// approved revisions authored,
// finalized reasoned votes cast
}
sigs: [ {key: pubkey, sig: bytes(64)} ] // threshold-signed by the
// editor set in force at `seq`,
// exactly as a manifest is
- A checkpoint manifest MAY reference a snapshot in its
snapshotfield; the snapshot'sseqMUST equal the manifest'sseq, and it MUST be signed by that manifest's authorizing editor set to its threshold. Genesis references none (there is no history to summarize). - A snapshot is an acceleration, not an authority. Standing remains
defined as the replay of §5.8; a node MAY trust a threshold-signed
snapshot as the starting point and apply only the objects after
seq, and a node that prefers MAY verify a snapshot by recomputing it. Because the counts are a deterministic function every node can check, a dishonest snapshot is detectable and a manifest that references one editors could not reproduce is rejected by any node that checks. The snapshot never changes what standing is; it only saves the work of recomputing the settled part. - Snapshots chain by
prevand are pruned like any superseded object (§7.3): only the newest, plus enough history to audit back to the last one a node trusts, need be kept.
7.3Prunable object classes (bounded disk)
Text is never pruned (§10, §17.2). But some object classes are settled: once a checkpoint and its standing snapshot cover them, their individual objects are no longer needed to compute any current value, only their already-summarized effect. A node MAY reclaim disk by dropping, from local storage only, objects it can prove are settled:
- Ratings on a superseded revision. A rating targets a specific revision (§5.5). Once that revision is neither the approved revision of its topic nor on the docket, no current tally reads its ratings; they MAY be dropped.
- Finalized jury votes older than the last snapshot. A vote's only
lasting effect is the standing it conferred (§5.8) and the jury
outcome it decided, both captured once a standing snapshot at a later
seqcovers it. Votes on revisions finalized before the snapshot'sseqMAY be dropped.
Pruning is local policy, never a consensus act: a node MUST NOT refuse to serve, or treat as invalid, an object a peer still holds, and a node that kept everything is fully compliant. Pruning is also conservative by construction: nothing prunable until a checkpoint (and, for votes, a standing snapshot) has settled it, so a fresh network prunes nothing. Removed-topic purging (§5.6) is a separate, mandatory deletion and is unaffected.
8.Verifier selection (jury)
Deterministic and verifiable, with no coordinator:
- A published Revision is first docketed: listed in the
docketof the next manifest (§7; acknowledging it is mandatory, not discretionary). Its jury is drawn from the verifier roster of THAT docketing manifest: grants whosehierarchymatches the topic's, whosemax_level >= topic.level, and who are review-active (a freshavailable=trueavailability signal, §5.11). Review duty is opt-in: a certified verifier who has not turned the switch on, or who has let it go stale, is simply not in the pool, and a pure reader never is. - Sort eligible verifier keys by
BLAKE3(revision_id || docketing_manifest_id || verifier_pubkey)ascending; take the first 5 (or all, if fewer; minimum 3 to proceed). The salt is what makes the draw grind-proof: the docketing manifest CONTAINS the revision's id, so its own id cannot exist until after the revision is committed, and an author cannot re-mine a revision until a friendly jury appears (PoW is cheap; this makes prediction impossible, not merely expensive). Grinding the manifest id instead requires the editors' collusion: that is full-capture behavior, visible in the chain, and answered by no-confidence (§5.10). - Voting window: 14 days from the docketing manifest's first sighting (nodes use their own clock; editors' inclusion decision is what finalizes).
- Majority approve: MUST be in the next manifest or the one after (§7 grace). Otherwise the author revises and republishes (new revision, new jury).
Manual mode (the small-pool escape hatch, bounded): if fewer than 3 eligible review-active verifiers exist for a docketed revision, and only then, editors may include or exclude it by judgment. The precondition is checkable by anyone (the roster and availability signals are public objects), a manual inclusion is visible in the chain precisely because no valid jury for it exists, and it remains subject to removal (§5.6) and its revocation to restore (§5.10) like anything else. The community's remedy against ENGINEERED scarcity (editors starving the pool to rule by manual mode) is seat-verifier plus recall (§5.10).
Odd jury size = no ties. Selection is random-but-verifiable: nobody, including the submission's own author, can pick the jury, and anyone can recompute it.
9.Spam control
Flooded objects hit every disk, so admission is controlled:
- Proof of work:
BLAKE3(canonical envelope with pow=0 || pow_le_bytes)must have ≥ N leading zero bits:- Revision: 22 bits (~seconds of laptop CPU)
- Topic / Hierarchy / Petition (any kind, incl. removal) / Board / Moderation flag / Media item: 22 bits (the scarce, standing-gated tier)
- Vote / Rating / Comment / Post / Post rating / Post edit / Author retraction / Poll / Poll vote / Signature / Removal vote / Petition vote / Verifier cert / Availability / Topic props / Duke transfer / blob-have: 16 bits
- Manifest: 0 (already threshold-signed) PoW parameters live in the manifest body from v1 so they can be tuned without a protocol change.
- Hard parameter bounds (governance guardrail). A manifest whose
parameters fall outside these is invalid, so no editor set can quietly
neuter the community's powers:
- any
threshold_pctin [60, 90], except removal in [70, 90] (purging content is the harshest power; a bare 60% majority must never suffice) and no-confidence in [75, 90]; - PoW bits in [8, 26] (never 0 for a flooded non-manifest object);
certs_minin [1, 7];status_ttlin [2, 26]; anywindow_seqin [1, 52]; removalwindow_daysin [7, 60];- standing
author_weightandreview_weightin [1, 10] andstanding.capin [100, 10000]: the cap is what stops seniority from compounding into permanent rule, so the cap itself is capped; - no-confidence
quorum>= 4x the removalquorum(the highest bar), and noquorummay exceedstanding.cap* 100 (so a quorum can always be reached by real participation, even while the network is young); - quorums rise slowly: from one manifest to the next, no quorum
may more than double. Lowering a bar is unrestricted (that returns
power to the community); raising one is rate-limited so it can
only grow roughly as fast as the community that must clear it.
Changing a bounded parameter requires a passed
set-parampetition cited in the manifest (§5.10, §7): the community, not the editors, sets the rules it lives by, and every node checks it.
- any
- The growth ramp (bars scale with the network). Manifest quorums
and voter floors describe a MATURE network; a young one cannot meet
them, which would leave removal and every remedy unreachable exactly
when the community is smallest. So every
quorumis judged atquorum * min(total_standing, MATURE_STANDING) / MATURE_STANDINGand everymin_votersatmin_voters * min(bearers, MATURE_VOTERS) / MATURE_VOTERS, each with a floor of 1, wheretotal_standingis the summed standing of every identity computable from the corpus andbearersthe count of identities bearing any. MATURE_STANDING = 2000 and MATURE_VOTERS = 100 are protocol constants, deliberately NOT manifest parameters: the ramp the community's remedies run on must never be settable by the people the remedies exist to remove. The jury target (§8) scales the same way, from its own roster count. Both inputs are pure functions of objects every node holds, so every node derives the same effective bar. Thresholds and windows never scale: 75% of the votes cast means the same among 10 people as among 10,000. - Rate limits (local policy, recommended): per author key, nodes forward at most 20 revisions/day, 200 votes+ratings/day (poll votes included), 100 comments/day (signatures included), 100 posts/day (post edits and polls included), and 5 boards/day (moderation flags included). Excess is stored-not-forwarded.
- Well-formedness: wrong-schema objects of a known type are dropped. Objects of an UNKNOWN type (a newer protocol version than this node speaks) are skipped INDIVIDUALLY: one unparseable envelope never fails the batch it arrived in and never penalizes the peer that sent it. Reconciliation may re-offer such objects on later syncs; that churn is the accepted cost of running old software on a newer network, and it ends when the node updates.
10.Sync protocol (gossip/flood)
Transport: peers are Tor onion services (via arti). All connections are onion-to-onion streams. No clearnet listener exists at all. In networks that block Tor itself (national firewalls), the client reaches the Tor network through bridges and pluggable transports; this is specified in §19, and it is a first-class requirement, not an add-on.
A node that cannot keep a service reachable (a phone, whose OS suspends background work) runs as a client node: it dials over the same Tor client, speaks this exact protocol, and publishes no onion service of its own. It is not a second protocol and not a reduced one; see §20. Everything below applies to it unchanged.
On connect (either direction), peers exchange:
1. HELLO { v, latest_manifest_seq, object_count }
2. MANIFESTS exchange manifest chain heads; sync missing chain first
3. INVENTORY ranges of object IDs (sorted), exchanged as compact
interval digests; each side computes what it's missing
4. WANT [objid, ...]
5. OBJECTS the envelopes, streamed; receiver validates
(sig, PoW, schema) before storing/re-forwarding
6. GOSSIP after sync, new objects are pushed to all connected
peers as they arrive (dedup by object ID)
- Sync order: manifests, then topics/hierarchies, then revisions referenced by the manifest (approved text first, so the app is usable in minutes), then votes/ratings, then everything else in the background.
- Peer discovery: hardcoded seed .onion addresses in the client +
peer-exchangemessages (each peer shares up to 50 known-good onion addresses). The network outlives its seeds. - Default: maintain 8-12 concurrent peer connections; user-cappable bandwidth.
The concrete algorithms behind step 3 (inventory reconciliation), peer discovery and eclipse resistance, blob replication, and the seed/headless build are specified normatively in §15 through §18. Reaching the network from a censored country is specified in §19.
11.Archive export
One click produces a plain folder (or zip):
archive/
MANIFEST.json // latest manifest, pretty JSON
hierarchy/<subject>/level-<N>/<topic-slug>.md
media/<b3hash>.<ext> // whatever blobs are cached locally
keys/README.txt // explains the formats; includes this spec
Markdown files get a small YAML front-matter block (topic ID, revision ID, authors, level). The knowledge must survive the death of the protocol. An archive is readable with a text editor.
12.Client UI direction (non-normative)
The protocol is UI-agnostic, but the reference client commits to a Wikipedia × phpBB look for maximum familiarity and accessibility:
- Reading = Wikipedia: dense, text-first article pages; left sidebar tree (hierarchy, then levels, then topics); table of contents box; classic blue underlined links; period-correct mid-2000s gloss (glossy gradient bars, beveled buttons, inset boxes) on the chrome, but no hero images, no big cards, and no motion for its own sake. Default serif/sans stack, user-adjustable size.
- Community = phpBB: the review queue, votes, and disputes render as forum-style tables (rows, alternating shading, status columns); a submission thread shows the revision, its jury, votes, and reasoning as posts.
- Semantic HTML everywhere (real headings, lists, tables), fully keyboard-navigable, screen-reader tested, high-contrast and large-text modes. It should feel like 2005 in the best way: fast, obvious, and it works on anything.
The desktop client is a full node (§18.1) and keeps the library reachable whenever it runs. Closing the window does not stop it: by default the app hides to the system tray and goes on syncing and serving in the background, and a tray menu reopens it or quits. Quitting is the only action that stops the node, and it runs a clean shutdown first (goodbyes to peers, peer table persisted). The reference client also starts on login by default, so a reader's node is available without being opened by hand; both behaviors are switchable in settings. Neither spawns a second process, and a hidden window renders nothing, so the only standing cost is the node itself. A device whose OS suspends background work (a phone) cannot keep a service reachable and runs as a client node (§20) instead.
12.1Machine translation (reading aid)
The client ships an on-device machine translator so any guide can be read in the reader's language even before a human translation exists. Design rules, in order of importance:
- Local only. Translation runs entirely on the reader's machine using small open-weight neural MT models (the Bergamot / OPUS-MT family or equivalent; CPU-only, quantized, tens of MB per language pair). A cloud translation API is never called: reading sends no request, and that guarantee (§0, §10) extends to reading in translation.
- Language packs are blobs. Model packs are content-addressed blobs (§6) announced and fetched from peers over Tor like any media blob, pinned by hash in the app release notes. No third party CDN, no extra infrastructure, and packs keep working offline once fetched.
- A reading aid, never content. Machine output is rendered
client-side, clearly labeled ("machine translated, unreviewed"),
is never signed, never gossiped, and never enters the object
store. There is exactly one path to a published translation: a
human submits a translation Topic (
originfield, §5.2) and it passes review like any other revision. The translator may pre-fill a draft for that submission; the submitting human owns every word of it. - Honest failure. Where no pack exists for a pair, the client says so and shows the original; it never silently routes through a pivot language without labeling the loss.
12.2Discovery: sorting, filtering, and search
The corpus is a curriculum first: the default order everywhere is the taxonomy (hierarchy, then level ascending, then display title). Community signals refine discovery on top of that; they never gate it. Only the manifest decides what is approved (SPEC 7), and a score, however low, never hides a guide. Everything in this section is display-level, computed locally from objects every node already holds: offering it costs no network request and reveals nothing about the reader.
Clients SHOULD offer these views, and a client that offers one MUST compute it as defined here, so two readers of the same corpus see the same order:
- Topic score. A topic's score is the SPEC 5.5 rating tally (up minus down) of its currently approved revision. Ratings target revisions, so when a new revision is approved the topic's score restarts from that revision's own tally: a rewritten guide earns its standing again. Clients SHOULD show the score wherever guides are listed.
- Sort orders. Curriculum (the default): level ascending, then
display title. Top rated: topic score descending. Most discussed:
count of displayable comments (SPEC 5.12) on the topic,
descending. Newest: the approved revision's
ts, descending. Every order breaks ties by falling back to curriculum order and finally the lower topic id, so each is total and deterministic. - Filters. A reader may narrow any view by hierarchy and by level range. A filter is a lens, never a curation act: it changes what this reader is looking at, not what any node stores, forwards, or shows by default.
- Search. Local and offline over approved titles and text, case-insensitive; the query never leaves the machine (the reading-sends-no-request guarantee extends to search). Order: title matches before text-only matches, then topic score descending, then curriculum order. Relevance may be refined in later revisions; the rating signal and the tie-break chain stay.
- The re-review flag. Heavy sustained downvoting flags a revision for editor re-review (SPEC 5.5). Clients that render the editors' console SHOULD list approved revisions whose score is at or below a visible threshold as an advisory queue. The threshold is editorial policy, not protocol (SPEC 5.5); the reference client ships -5 as its default and shows the number it used. The queue is advisory: acting on it means docketing a replacement revision or a revocation through the ordinary manifest process, never suppressing display.
Why ratings refine and never gate: identities are free (spam is priced by proof of work, SPEC 9, not by scarcity of keys), so a rating tally is an honest signal, not a consensus value. Visibility is decided by juries and the signed manifest; the community's votes order the shelf, they do not burn the books.
13.Compatibility & change policy
- New object types / new body fields: allowed anytime (old nodes forward, ignore).
- Changed meaning of existing fields: never. Add a field instead.
- New hash/signature algorithms: new prefixes, announced via manifest
bodyparameters, both accepted during a long overlap. - This file is versioned with the code and is part of every release and every archive export.
14.Governance and community sovereignty
Everything above serves one rule: the community is the root of authority, and every delegated power is checkable, bounded, and recallable. The network is built to run forever precisely because no person, key, company, or founder is load-bearing.
Who holds what:
- The community (everyone, weighted by earned standing, §5.8) is sovereign. It admits verifiers (§5.9), removes bad content (§5.6), recalls bad verifiers, appeals wrongful revocation, tunes the parameters, and can re-seat the editors entirely (§5.10). None of this needs anyone's permission.
- Verifiers review submissions. They are certified by their peers, not appointed, and can be recalled. Their jury decides publication; editors cannot overrule it (§7, §8).
- Editors curate the index and rotate their own keys, but are bound on every side: they cannot publish a failed submission, drop a passed one, omit an earned verifier, keep a recalled one, revoke approved content without cause, set a parameter out of bounds, or ignore a passed petition. Every node checks all of it, and a no-confidence vote can replace them.
- The duke owns only a guide's title and donate button (§5.7), never its content.
The guarantees, stated plainly:
- No capture. A minority that seizes the editor keys still cannot censor passed content, refuse to docket a submission, stack the verifier roster, block removals, or move a single governed parameter without a community mandate cited in the chain, and the community can vote them out and seat a new set that clients will follow.
- No hostages. If the editors vanish, are compromised, or turn
hostile, a standing-weighted no-confidence vote seats successors
with no cooperation from the incumbents. The remedy does not run on
the incumbents' clock: a stalled chain moves petition windows onto
wall time (§5.10
STALL_DAYS), and an irreversible supermajority passes the moment it is irreversible. - No Sybil rule. "The community" means earned standing, not raw key count, so consensus cannot be manufactured by spinning up identities. Standing is capped, and the cap itself is bounded (§9), so seniority cannot compound into permanent rule either.
- No rigged juries. Jury draws are salted by the docketing manifest's id, which cannot exist before the submission does (§8): an author cannot mine their way to a friendly jury, and a verifier cannot maneuver onto a chosen case.
- No lock-in. The corpus is CC BY-SA, one-click exportable to plain files (§11), and reimplementable from this document alone. Even total governance failure cannot destroy the knowledge or prevent a clean fork.
Founders sign the genesis manifest and seat the first verifiers, then that authority flows to the community and never returns to a single point. The network outlives its founders, its editors, its seeds, and its website by design.
15.Peer discovery and connection management
Everything here runs over onion-to-onion streams (§10): there are no
IP addresses, no ports, no geography, no ASN. A peer is a 56-character
v3 .onion address and nothing else. Classic IP-diversity anti-eclipse
tactics (Bitcoin's addrman buckets) do not apply and are not used; the
design leans instead on a signed trust anchor and on the fact that the
data itself is unforgeable.
15.1The signed bootstrap list
New installs need first peers, but a hardcoded list rots and an open self-add list invites eclipse and abuse. The network therefore carries its own seed directory as a signed object in the manifest lineage:
type "bootstrap"
body {
seq: u64 // strictly increasing; newer supersedes
seeds: [onion, ...] // vetted always-on seed addresses
min_client: u16 // lowest spec version these seeds serve
mandate: objid / null // the passed petition admitting any
// seeds ADDED since the previous object
// (one petition may admit several); null
// when nothing was added
}
- Signed by the same editor threshold as the manifest (§7): valid iff signed by >= threshold keys of the current manifest's editor set. It is not the manifest (seeds change far more often than the editorial index), but it inherits the manifest's trust.
- Monotonic. A node accepts a
bootstrapobject only if itsseqexceeds the highest it has verified. A captured old key cannot roll the seed set backward, and a stale cached list cannot be replayed over a fresher one. - Rotation without a release. Seeds are added or dropped by
publishing a new
bootstrapobject. This is what makes "the network outlives its seeds" (§10) literally true: the client hardcodes only a tiny genesis seed set (2-3 onions) as the cold-start anchor, and from first contact onward the signed list is authoritative. - Admission is a governance act, and checkably so. A host joins
seedsonly through aset-param-class petition (§5.10) that meets the community's uptime and trust bar, and a bootstrap object that ADDS a seed must cite that passed petition inmandate: a node holding the petition record rejects an uncited addition, so editors assemble the object but cannot invent members the community did not admit (the same citation discipline as manifestparam_changes, §7). Dropping a seed needs no mandate: removal is the fast, safe direction when a seed is compromised. Being a seed grants no protocol privilege (§18); the list is purely a discovery convenience.
15.2Peer exchange (PEX)
After the handshake, peers may exchange address books:
type "peer-exchange" // not an envelope; a live-stream message
body { peers: [onion, ...] } // up to 50, each locally "known-good"
- Known-good = this node completed a valid sync with that address within the last 14 days. A node never forwards an address it has not itself reached; hearsay does not propagate.
- Poisoning cap. No more than 30% of a node's candidate table may originate from any single PEX source, so one peer cannot flood the table with attacker addresses.
- New addresses enter a candidate table (capped ~1000, LRU by last success), promoted to the active set only after a successful feeler connection (§15.3).
- Client nodes are excluded automatically, not by rule. An onion service never learns the address of whoever dialed it, and a node forwards only addresses it has itself dialed and synced with (above). A client node, having published no address, is therefore never advertised, never entered in a candidate table, and never dialed back. Implementations MUST NOT add a wire field by which a peer announces its own address or role to fix this: that field would be a fingerprint, and a lie the moment a peer had a reason to tell one (§20.1).
15.3Connection management and eclipse resistance
A node maintains a target of 8-12 active peers (§10; seeds far more, §18). The active set is chosen to be hard to capture:
- Persistent peer table. The candidate table (address, last success, success/fail counts, first-seen) is saved across restarts. Long-lived, historically reliable peers are preferred, so an attacker cannot cheaply displace them by spinning up fresh onions: reliability is aged, and age cannot be faked.
- Reserved anchor slots. At least 2 active slots are reserved for addresses from the signed bootstrap list (§15.1). An attacker who is not in the signed list can never fill these, so a node is never surrounded entirely by attacker-chosen peers.
- Feeler connections. Every few minutes the node opens one short-lived probe to a random candidate, validates it, updates its score, and disconnects. This keeps the table fresh and detects dead or hostile addresses before they are needed.
- Rotation. A fraction of non-anchor slots is recycled on a slow timer so the active set is not static and long-term partitioning is costly.
Why this is enough over Tor. An eclipse attack can only withhold
data, never forge it: every object is signature- and PoW-checked (§9)
and the manifest is a monotonic signed chain (§7), so an attacker who
owns all of a victim's peers still cannot feed a forged manifest, a
fake publication, or a rolled-back seed list. The attack degrades to
stalling (hiding that newer data exists). That is defeated by the
anchor slots plus a liveness check: if a node's manifest head falls
behind the latest_manifest_seq reported by any bootstrap seed it
contacts, it treats itself as possibly eclipsed and force-dials
additional bootstrap seeds. Unforgeable data turns eclipse from a
content attack into a mere freshness attack, and freshness is
re-anchored on every bootstrap contact.
15.4Local peer scoring
Reputation is local only (a gossiped score is a Sybil target):
- Delivering a valid object or completing a clean reconciliation raises a peer's score.
- Delivering an envelope that fails signature, PoW, or schema lowers it sharply, and the object is dropped per §9; repeated failures evict and briefly ban the address.
- Scores decay toward neutral so a peer is not condemned forever.
- Scoring never crosses nodes; each node judges only what it has directly observed.
16.Inventory reconciliation
Step 3 of §10 must let two peers discover exactly which objects each lacks, cheaply, when both already hold (eventually) millions of objects and differ by only a handful. A full inventory exchange is O(n) per connection and would move megabytes over Tor every sync; the protocol uses range-based set reconciliation instead, which costs O(d log n) for d differences.
16.1Fingerprints
Object IDs are 32-byte BLAKE3 hashes (§4), held sorted. For any range
[lo, hi) of the ID space, define an order-independent fingerprint:
fp(lo, hi) = ( count, XOR of BLAKE3(0x01 || id) for id in range )
The nested hash and the paired count defeat XOR's cancellation weakness (two differing sets colliding to the same XOR must also match on count and survive a second hash), and because IDs are content hashes an adversary cannot cheaply craft inputs to force a collision. Fingerprints are computed incrementally from a sorted structure, so a range fingerprint is cheap to recompute as the set grows.
16.2The recursion
1. Initiator sends fp over the whole ID space [0, 2^256).
2. Receiver compares with its own fp for that range:
- equal -> that range is in sync; stop.
- differ, and
range holds
<= 40 IDs -> send the full ID list; peer replies with the
IDs it lacks (-> WANT, §10 step 4).
- differ, and
range larger -> split into 16 equal sub-ranges, send
(bound, fp) for each; recurse per sub-range.
Only ranges that actually differ are ever expanded, so a two-node difference is found in a logarithmic number of round trips while identical regions collapse to a single fingerprint compare. Round trips, not bytes, are the Tor cost, and this keeps them at O(log n).
16.2a Subject-scoped reconciliation
§20.2 allows a node that cannot hold the whole corpus to mirror a chosen set of whole subjects instead, never a guide at a time. The reconciliation that implements this is the same recursion above, run over a restricted keyspace: a node advertises the hierarchy roots it mirrors in its HELLO, and two peers reconcile only the object classes for topics under the intersection of what each carries. The membership test is on the topic's hierarchy (§5.2), fixed at authoring and known from the object itself, never on what the user read; a peer learns which subjects a node mirrors and nothing finer, which is exactly the boundary §20.2 draws. A full node advertises "all" and reconciles the whole space as today, so a scoped node and a full node interoperate with no flag day: scope is the intersection of two advertised sets, and "all" intersect anything is that thing.
Scoping is refused for the chain itself: manifests, standing snapshots, verifier grants, hierarchy roots, and the governance object classes are global and every node takes them whole regardless of subject scope, because they define what any subject even means. Only per-topic content (revisions, ratings, comments, and their blobs) is subject-scopable.
16.3Class-ordered reconciliation
Objects are reconciled in the priority order of §10 (manifests, then manifest-referenced revisions, then the rest) by running the recursion over each object class as a separate keyspace, approved text first. The app becomes usable while votes and history are still reconciling in the background.
17.Blob replication
Text is fully replicated (§10); blobs are not (§6), so blob durability is an explicit, coordination-free control loop rather than luck.
17.1What each node tracks
From blob-have announces (§6) a node builds:
blob_id -> { announcers: u32, // distinct peers seen announcing it
bytes: u64,
last_seen: u64,
held: bool } // do we have the bytes ourselves
announcers is the node's local estimate of that blob's replication
count and is the signal the loop runs on.
17.2Disk budget
Each node caps the media (blob) bytes it stores, so the corpus is spread across many nodes rather than hoarded on a few. Guide text is fully replicated (§10) and never evicted; it is kept in full ON TOP of this cap, not counted against it. The reference client defaults the blob budget to 5 GB and lets the user raise or lower it (0 means unbounded), so no ordinary node holds more than a few GB of media plus the shared text. The blob budget splits into:
- Cache (~70%). Blobs the user actually opened. LRU eviction.
- Pinned durability reserve (~30%). Blobs deliberately held for the network's sake. Never LRU-evicted; released only by the loop below.
The cap is a hard ceiling: over it, blobs are evicted least-recently-used first, and the durability reserve is touched only as a last resort, so the ceiling is never breached. Pinning (§17.3) is budget-aware too: a node at its cap declines new pins rather than evicting its own reserve to make room, which leaves those blobs' replica counts low so peers WITH space self-elect for them. Storage therefore flows to nodes that have room instead of thrashing a full one.
17.3Coordination-free rare-blob pinning
The goal is a target replication factor R (default 4) for every
blob, reached without any coordinator deciding who stores what. Each
node independently pins blob b iff:
BLAKE3(node_pubkey || blob_id) mod SCALE < (R - announcers)
- When
announcersis low (a blob one node-loss from extinction), the right-hand side is large and many nodes are eligible, so copies are made quickly. - As copies appear,
announcersrises, the right-hand side shrinks, and fewer nodes pin, until atannouncers >= Rpinning stops. The loop self-terminates at R with no messaging. - The hash of
node_pubkey || blob_idspreads the responsibility pseudo-randomly and disjointly across nodes, so they do not all pin the same rare blob while others go bare. Coverage is even in expectation and needs no agreement.
Eligible blobs are fetched (§17.4) into the durability reserve, newest rarity first, until the reserve is full; if full, the node pins the rarer blob and releases a less rare one it holds. Re-announce and expiry follow §6 (holders re-gossip roughly daily; maps expire after ~3 days).
A client node (§20) skips this loop entirely: it has no durability reserve and announces nothing, because a node that cannot be dialed cannot serve the bytes it pins, and a pin no one can fetch is not a replica. It keeps an ordinary LRU blob cache (§17.2) for what its user opens, and is counted by no one as an announcer.
17.4Fetch path
On demand or when pinning: look up blob_id -> [peer], request from
holders in random order (spreading load and resisting a single hostile
source), verify bytes against the BLAKE3 ID before use, store, and
announce blob-have so the node becomes a source. A blob whose bytes
fail the hash is discarded and that peer is downscored (§15.4).
18.Seed nodes and the headless build
A seed is an ordinary full node that is always on and generously resourced. It has no special authority: it cannot admit content, influence a jury, or sign a manifest any more than a desktop client can. Its only power is availability, and the network depends on it for nothing but a faster cold start and better blob durability. Presence in the signed bootstrap list (§15.1) is a discovery convenience, not a role.
18.1How a seed differs from a desktop node
Same protocol, different dials:
- No GUI; runs as a background service.
- Holds the entire replicated text corpus (never prunes text).
- Large blob budget and a higher target replication factor, so it pins aggressively (§17.3) and anchors rare media.
- Higher connection ceiling (64-256 peers vs 8-12), so many new nodes can sync from it at once.
- Stable, backed-up onion identity (§18.3).
18.2The headless binary
Shipped as academy-seed (the seed crate), separate from the Tauri
desktop app but built on the same core, net, and editorial crates.
Config is a single file:
onion_key_path = "..." // persistent v3 onion secret key
data_dir = "..." // object store + blob store
text_budget = "all" // seeds hold all text
blob_budget_gb = 200
max_peers = 128
bandwidth_cap = "..." // optional up/down ceiling
status_addr = "127.0.0.1:9777" // localhost only, never public
It installs as a system service (systemd / launchd / Windows service), logs structured status, and exposes a localhost-only status endpoint (peer count, manifest head seq, object and blob counts, disk used) so an operator can confirm reachability without exposing anything to the network.
18.3Onion key persistence
The onion secret key must persist across restarts and be
backupable: losing it changes the .onion address, which silently
removes the seed from anyone's peer table and, if it was a bootstrap
seed, breaks its signed-list entry until a new object is published.
The key is generated once, written with owner-only permissions, and
the operator is prompted to back it up on first run.
18.4Update flow
Releases carry threshold signatures, and a seed rebuilds and runs new code only after verifying them. Code distribution is on the same footing as everything else on the network: no single key, not even a founder's, can push code (§14). The mechanism:
- A release manifest names an exact node-repo commit and a monotonic
version (the repo's commit count):
{version, commit, date}. It is signed by a threshold of the release roster (M of N ed25519 keys), not by one key. - The roster names the current release signers and threshold. Its private keys never live on any server. Rosters form a succession chain: roster N is valid only if signed by >= roster N-1's threshold of roster N-1's signers, exactly like editor succession (§7). The chain's first link is the genesis roster, baked into every seed at install (arriving with the install script over HTTPS, the same trust as the install itself, never from the code repo). Rotating the set (add a signer, drop one) is a single signed successor roster; after a rotation the old signers no longer count. This is how release authority moves to the community and how a founder steps away for good: they rotate the roster to successors, signed by their still-current keys, then delete their keys, holding nothing and unable to sign. Every seed verifies the whole chain back to the baked genesis, so the succession is checkable by all.
- The updater is a small root-side helper on a timer, not the node itself: the seed runs as a sandboxed, no-privilege service and cannot rewrite its own binary (§18.2 hardening), which is exactly the property that keeps a compromised node process from tampering with its own updates.
- Each check: fetch the roster chain and the release + its signatures, verify the chain back to the baked genesis and require a threshold of the current roster over the release, failing closed on any break; refuse any version not strictly higher than installed (a downgrade guard against a replayed older release); fetch and check out the exact signed commit (git content-addressing means the server cannot substitute different code for a signed id); rebuild, smoke-test, swap in with a kept-back rollback copy, and restart the self-healing service. If the node does not come back healthy, roll back to the previous binary.
- The roster and release are served from a different system than the code repo (the website, not the git server), so neither one alone can push code: forging a release needs a threshold of the roster's keys, and swapping the code needs breaking a git hash. Compromising the git server is not sufficient (§14 no-single-point-of-failure).
- Canary window. A seed adopts a new release only after it has been the latest for a hold period (default 3 days), measured from that seed's own first sighting (no fleet telemetry, matching the network's design). A bad release is withdrawn by publishing a good release over it: that changes the latest version and resets every seed's window, so the bad one is never adopted. A small, coordination-free canary cohort (a hash of the node id and the version selects it, the §17.3 pattern) adopts a release early so the code gets real exposure on live seeds while the rest wait, giving the community time to notice trouble and withdraw. A subtly-broken but healthy release that the per-seed health-gate would not catch is caught here, by people, before it reaches the fleet.
- Auto-update is the default for headless seeds but is a plain systemd timer an operator can disable; the desktop app asks the user. The intended endgame is for the release roster to converge on the community-elected manifest editor set, so code releases are authorized by the same recallable body that governs content and rules (§5.10), and no founder is load-bearing anywhere.
18.5Operator responsibilities
A seed stores and serves the entire replicated text corpus, so seed operators carry the full node-operator responsibilities of TERMS.md. Some VPS providers prohibit Tor services; operators must check provider terms and local law. The seed how-to and the app both surface this before anyone volunteers a host.
19.Censorship circumvention and reachability
The transport is Tor (§10), and universal education is precisely for places where knowledge is restricted, so the client must reach the network from countries that block Tor itself (national firewalls that block Tor's directory and relays by address, actively probe and block bridges, and fingerprint protocols with deep packet inspection). This section is a first-class requirement: a client that only speaks plain Tor does not meet the spec's stated goal.
19.1The reachability property
Because all text is fully replicated and reading is entirely offline (§10), a node needs to reach the network only intermittently to sync, and thereafter the corpus is local forever. Circumvention must succeed occasionally, not continuously. This is the design's central advantage in a hostile network and clients are built around it: getting through the firewall once turns into permanent, offline, unobservable access to everything synced so far, and reading never again touches the network.
19.2Bridges and pluggable transports
To reach the Tor network where it is blocked, the client MUST support bridges (unlisted entry relays) reached through pluggable transports that disguise the traffic:
- obfs4: randomized, fingerprint-resistant obfuscation. The baseline; effective in most censored networks, increasingly detected by the most advanced firewalls.
- Snowflake: ephemeral volunteer WebRTC proxies with a broker reached by domain fronting. There is no static address for the censor to enumerate and block, which makes it the primary recommendation against the most aggressive firewalls. A Snowflake-class transport is a hard requirement, not optional.
- meek: domain fronting through a large CDN. High latency; a last-resort fallback for when the others are blocked.
The client MUST:
- Auto-detect and fall back. Try a direct Tor bootstrap first; if it fails or is blocked, automatically switch to bridges and pluggable transports with no user configuration. Circumvention is invisible plumbing, like the rest of the app.
- Ship a built-in bridge set so a fresh install can connect the first time, and treat those built-ins as expendable (see §19.3).
- Be built on a Tor implementation (arti) with bridge and pluggable-transport support; where a needed transport is not yet available in the library, the client bundles it.
19.3Getting fresh bridges (the distribution problem)
Bridges shipped inside the app can be extracted and blocked by the adversary, so static built-in bridges degrade over time. The client therefore also obtains bridges out of band:
- Signed bridge list. Like the signed bootstrap list (§15.1), an
editor-threshold-signed, monotonic
bridgesobject distributes fresh bridge lines to nodes that can already reach the network, so they, and anyone they share the app with, carry current bridges forward. This cannot bootstrap a fully-blocked first connection by itself (that is the chicken-and-egg of all bridge distribution), which is why Snowflake matters. - Snowflake needs no list. Its rendezvous is a domain-fronted broker and its proxies are ephemeral, so there is no bridge list to distribute or block; this is the primary path for a first connection from the most hostile networks.
- Manual bridge entry. The client accepts bridge lines pasted or scanned (QR) from an out-of-band channel (a friend, a message), the same human distribution Tor itself relies on.
19.4Getting the app in
Reaching the network is moot if the installer cannot be obtained, and the download site may itself be blocked, so the client and the release process MUST make the app freely redistributable:
- The installer is small (~10 MB), signed and reproducibly built (§ download policy), so any mirror, USB copy, or peer-to-peer share is verifiable and cannot be tampered with. Distribution does not depend on the official site being reachable.
- No dependency on a gatekept app store: the desktop installers (Windows, macOS, Linux) are direct downloads that any mirror, USB copy, or peer can redistribute, so distribution never rides on one store's policy.
- Because every node already holds the whole corpus and can export it to plain files (§11), one person who gets through can seed both the app and the knowledge to others entirely offline.
19.5Honest limits
This is an arms race. Specific transports are detected and must be rotated; a determined national adversary can always raise the cost. The spec commits to supporting and rotating within the pluggable-transport ecosystem, not to any single transport working forever. The durable guarantees are narrower and real: reading is unobservable and works offline, the corpus survives once synced, and knowledge that reaches a country cannot be recalled from it.
20.Node roles (the capability ladder)
Goal 2 says the full text reaches every node and goal 3 says reads leak nothing. Both hold on a phone. What does not hold is serving: a mobile OS suspends background processes, so a device that publishes an onion service cannot keep it reachable, and a peer that appears for twenty minutes and vanishes costs the peers that dialed it more than it returns. Pretending otherwise would not add capacity; it would add churn, and call it capacity.
So a node declares what it can honestly sustain. The roles differ in what a node does with its bandwidth and disk, and in nothing else. No role can admit content, influence a jury, sign a manifest, or read an object another role cannot. Standing (§5.8), verifier certification (§5.9), the duke's rights (§5.7), and every governance power are properties of a key, not of the device the key is used from. A reader on a phone and a seed in a datacenter carry exactly the same authority, and this document never grants a right to a role.
| Role | Publishes an onion | Serves peers | Replicates text | Pins blobs |
|---|---|---|---|---|
| Client node | no | no | yes, in full | no; cache only |
| Serving node | while running | 8-12 peers | yes, in full | yes (§17.3) |
| Seed | always | 64-256 peers | yes, in full | aggressively (§18) |
A client node dials but never accepts. It bootstraps from the signed seed list (§15.1), dials peers, and runs the ordinary sync of §10. It both reads and publishes with no additional protocol, because reconciliation is symmetric (§16): an object the client holds and its peer lacks is sent in the very exchange that fetches what the client is missing. Authoring a revision, voting, rating, petitioning, and every other write are therefore available to a client node exactly as they are to a seed. A phone is a full citizen of the network that happens not to be a road.
20.1The wire does not change
A client node is indistinguishable on the wire from a serving node that happens to be dialing you. This is not a concession negotiated into the protocol; it falls out of the transport. An onion service does not learn the address of whoever dialed it, so every node already treats every inbound caller as an address it does not know, and PEX (§15.2) forwards only addresses a node has itself dialed and synced with. A client node is therefore never advertised and never dialed back, by construction.
It follows that no HELLO field announces a role, and none may be added. A role flag would be a fingerprint on a network whose whole purpose is that reading is unobservable, and it would be a lie the moment a peer had a reason to tell one. A node learns what a peer is by whether it can dial it, which is the only claim a peer cannot fake.
This is also why the role split needs no flag day (§13): a client node is not a new protocol version, and a node from before this section was written serves one correctly without knowing it exists.
20.2Reads still leak nothing
The privacy guarantee of this network is a consequence of taking everything. A client node reconciles the entire text inventory (§16), not the guides its user opens, so the peer serving it learns nothing about what is read, exactly as for a seed.
This is load-bearing, and it is the one thing an implementer must not optimize away. A client that fetched only the guides its user opened would hand the serving peer a reading profile, and a reading profile is precisely what an adversary wants from a library. Bandwidth is the cost of goal 3, and it is not negotiable:
- Selective text fetch is forbidden. A node takes the whole text inventory or it takes a whole class of it; never a guide at a time because someone asked for that guide.
- If the corpus ever outgrows the smallest supported device, the answer is a subject-scoped full replica: the node takes all of a subject or none of it, and the set it takes is chosen by the user ahead of time, not by what they read. A peer may learn which subjects a node mirrors. It must never learn which guides a node opened.
Blobs are the exception the design already carries: they are fetched on demand (§17.4) by every role, so opening a guide's media does reveal that guide to whichever holder serves the bytes. That is a known, pre-existing leak of the media path, not a new one introduced by client nodes, and it is why text is the thing that is replicated whole.
20.3Client nodes are guests, and a node may treat them as such
A client node consumes capacity and returns none. Phones will outnumber desktops, so this is a real cost and not a rounding error. The network is allowed to defend itself, and the mechanisms are the ones it already has:
- A serving node MAY prefer inbound peers it can dial back over ones it cannot, and MAY shed the latter first when it is at its connection ceiling (§15.3). It learns which is which by trying, not by asking.
- The write path is already priced by proof-of-work (§9), so an unreachable identity is cheap to create and no cheaper to abuse. The read path is bounded by ordinary per-connection rate limits.
- A client node is never entitled to service. Falling back to the signed seeds (§15.1) when ordinary peers shed it is expected, and is exactly the cold-start role seeds already exist for (§18).
A node that shed a client and a node that served it are both compliant. This section grants no obligation in either direction; it only says the imbalance is real and refuses to hide it.
20.4Opting up
The ladder is a floor, not a ceiling. A device that can sustain more SHOULD offer more, and the reference client asks rather than assumes:
- A desktop is a serving node by default: it publishes an onion while the app is open and stops when it closes. Churn at that timescale is ordinary, and the peer table (§15.3) is built for it.
- A mobile device is a client node by default, and MAY offer the user an explicit serve while charging on an unmetered network mode that promotes it to a serving node for as long as those conditions hold. Holding an onion open from a phone is technically possible, and this is the honest way to do it: opt-in, plugged in, announced, and reversible. Never silent, and never the default, because a node whose battery is being spent to serve strangers must be a node whose owner chose that.
- No role is inferred from behavior and none is enforced on a peer. A serving node that stops serving becomes a client node; nothing detects it, nothing punishes it, and nothing needs to.