atvoto Lexicons
Custom AT Protocol record schemas under ch.atvoto.*. Schema files: lexicons/.
Status: draft (POC). Every schema description starts with “Entwurf”. Breaking changes are still allowed while nobody outside atvoto depends on them; they get frozen before the prod launch (see Evolution).
XRPC methods of the AppView (ch.atvoto.argument.list, ch.atvoto.comment.get,
ch.atvoto.peerreview.pending, …) are a separate family and are not published as
lexicons yet.
Overview
Abschnitt betitelt „Overview“| NSID | Written by | Repo | rkey |
|---|---|---|---|
ch.atvoto.dossier.argument |
user (original) → writer (canonical copy) | user / community | TID / sha256(originUri)[:24] |
ch.atvoto.dossier.comment |
user | user | TID (imports: any) |
ch.atvoto.dossier.commentTranslation |
writer (translator) | community | {commentRkey}-{lang} |
ch.atvoto.content.rating |
user | user | TID |
ch.atvoto.peerreview.request |
user | user | TID |
ch.atvoto.peerreview.invitation |
writer | community | {argumentRkey}-{didSuffix} |
ch.atvoto.peerreview.response |
user (original) → writer (canonical copy) | user / community | TID / {argumentRkey}-{didSuffix} |
ch.atvoto.peerreview.result |
writer (finaliser) | community | {argumentRkey} |
ch.atvoto.taxonomy.snapshot |
registrar (taxonomy.publishSnapshot) |
community | TID |
“Community” = the per-dossier community account (dossier-{rkey}.id.<domain>).
Dossier (CMS content, no record; community account per dossier) ├── dossier.argument ──┬── dossier.comment ── dossier.commentTranslation │ ├── content.rating │ └── peerreview.invitation / peerreview.response / peerreview.result └── taxonomy.snapshot (topic tree, references arguments by rkey)Acceptance pattern (originals → canonical copies)
Abschnitt betitelt „Acceptance pattern (originals → canonical copies)“User-authored arguments and review responses are written self-signed into the
user’s own repo. The indexer stages them in app_acceptance_queue; the writer
gates them (eligibility, quota, review authorization) and writes the canonical
copy into the community repo with provenance fields:
dossier.argument:source.originUri/source.originCid(inside#sourceUser)peerreview.response: top-leveloriginUri/originCid
originCid pins the exact accepted version of the original. Only canonical copies
are projected into the read model (app_arguments, app_peerreview_responses).
Comments and ratings are projected straight from user repos (eligibility checked by
the indexer).
Records
Abschnitt betitelt „Records“ch.atvoto.dossier.argument
Abschnitt betitelt „ch.atvoto.dossier.argument“| Field | Type | Req | Notes |
|---|---|---|---|
| title | string ≤300 | ✓ | |
| body | string ≤10000 | ✓ | |
| type | PRO | CONTRA |
✓ | |
| dossier | string ≤64 | ✓ | dossier rkey |
| source | union #sourceUser | #sourceOfficial | #sourceOrganization |
✓ | #sourceUser: authorDid (+ originUri/originCid on the canonical copy) |
| langs | language[] ≤3 | original languages | |
| translations | #translation[] ≤10 |
embedded translations (lang, title, body, source manual|ai, model) |
|
| createdAt | datetime | ✓ |
ch.atvoto.dossier.comment
Abschnitt betitelt „ch.atvoto.dossier.comment“| Field | Type | Req | Notes |
|---|---|---|---|
| body | string ≤5000 | ✓ | |
| argument | at-uri | ✓ | parent argument (canonical community record) |
| parent | at-uri | parent comment (threading) | |
| title | string ≤300 | unstable, from the Demokratiefabrik import | |
| langs | language[] ≤3 | ||
| createdAt | datetime | ✓ |
External Bluesky replies to cross-posted arguments are imported as comments with
origin = 'extern' without a dossier.comment record.
ch.atvoto.dossier.commentTranslation
Abschnitt betitelt „ch.atvoto.dossier.commentTranslation“Sidecar translation, because the store cannot write into user repos. One language per record.
| Field | Type | Req | Notes |
|---|---|---|---|
| subject | {uri, cid?} |
✓ | translated comment (may be an external Bluesky post) |
| dossier | string ≤64 | denormalized dossier rkey | |
| lang | language | ✓ | |
| body | string ≤10000 | ✓ | |
| source | manual | ai |
✓ | |
| model | string ≤200 | when source = ai |
|
| translatedAt | datetime | ✓ |
ch.atvoto.content.rating
Abschnitt betitelt „ch.atvoto.content.rating“| Field | Type | Req | Notes |
|---|---|---|---|
| subject | com.atproto.repo.strongRef |
✓ | rated record |
| preference | integer 0–100 | ✓ | 0 = neutral, 100 = strong support |
| createdAt | datetime | ✓ |
ch.atvoto.peerreview.request
Abschnitt betitelt „ch.atvoto.peerreview.request“Only createdAt. Its existence is the trigger: the writer runs the reviewer
lottery (daily limit, anti-collusion, slots) and writes invitations.
ch.atvoto.peerreview.invitation
Abschnitt betitelt „ch.atvoto.peerreview.invitation“| Field | Type | Req | Notes |
|---|---|---|---|
| argument | at-uri | ✓ | |
| invitee | did | ✓ | |
| invited | boolean | default true; false = lottery candidate not selected (dedup) | |
| createdAt | datetime | ✓ |
ch.atvoto.peerreview.response
Abschnitt betitelt „ch.atvoto.peerreview.response“| Field | Type | Req | Notes |
|---|---|---|---|
| argument | at-uri | ✓ | |
| reviewer | did | ✓ | must match an invitation |
| vote | APPROVE | REJECT |
✓ | the only binding signal |
| criteria | #criterion[] ≥1 |
✓ | key, label, assessment ok|flagged |
| originUri / originCid | at-uri / cid | canonical copy only | |
| createdAt | datetime | ✓ |
ch.atvoto.peerreview.result
Abschnitt betitelt „ch.atvoto.peerreview.result“Outcome of a closed peer review, written once by the writer’s finaliser. Public and recountable against the invitations and responses in the same repo (VERFASSUNG Art. 29); clients learn about outcomes from the firehose.
| Field | Type | Req | Notes |
|---|---|---|---|
| argument | at-uri | ✓ | argument in the same community repo |
| outcome | approved | rejected |
✓ | tie = rejected |
| approvals / rejections | integer | ✓ | invited responses only |
| quorum | integer | quorum that applied | |
| createdAt | datetime | ✓ | time of closure |
ch.atvoto.taxonomy.snapshot
Abschnitt betitelt „ch.atvoto.taxonomy.snapshot“Append-only snapshot of a dossier’s topic tree (source of truth for
app_taxonomy_node/_membership): dossierRkey, version, contentHash, prev
(strongRef to the predecessor), attribution, nodes[] (DFS pre-order; key,
name, description, introduction, importance, parent, arguments[] with
rkey/confidence/stance; stance is PRO/CONTRA like the argument type).
Considered, not published
Abschnitt betitelt „Considered, not published“ch.atvoto.peerreview.note— procedural note of a reviewer on an argument (e.g. “duplicate of …”, wording, wrong PRO/CONTRA), visible only within the review process. Purpose: transparency / traceability of review decisions for authors and other reviewers. Removed because peer review is vote-only (no free text) and no code used it. Can be added later at any time (a new NSID is non-breaking); the old schema is in the git history (lexicons/ch/atvoto/peerreview/note.json).ch.atvoto.review.*— poltr legacy duplicates ofpeerreview.*; removed.
Publishing
Abschnitt betitelt „Publishing“Lexicon resolution: per NSID group (authority) a DNS TXT record
_lexicon.<authority> → did=<publisher DID>; the schemas live as
com.atproto.lexicon.schema records (rkey = NSID) in that account’s repo.
| NSID group | DNS TXT name | Value |
|---|---|---|
ch.atvoto.dossier.* |
_lexicon.dossier.atvoto.ch |
did=did:plc:kcwu3fqgy3wi7lamal6jtnn6 |
ch.atvoto.content.* |
_lexicon.content.atvoto.ch |
same |
ch.atvoto.peerreview.* |
_lexicon.peerreview.atvoto.ch |
same |
ch.atvoto.taxonomy.* |
_lexicon.taxonomy.atvoto.ch |
same |
Publisher account: lexicon.atvoto.ch (custom-domain handle via TXT
_atproto.lexicon.atvoto.ch; a store-owned account under the store domain).
Credentials: secret pds-account-lexicon. The DNS authority is shared by
test and prod: when the account moves (prod PDS) or the test PDS is reset, publish
from the new account and update the four TXT values.
goat lex lint lexicons # style / best practicegoat lex check-dns lexicons # which TXT records are missingscripts/publish_lexicons.sh atvoto-test # publish new schemasscripts/publish_lexicons.sh atvoto-test --update # overwrite changed onesgoat lex breaking lexicons # breaking changes vs. publishedgoat lex resolve ch.atvoto.dossier.argument # check from outsideNote: the reference PDS does not resolve third-party lexicons. Writes with
validate: true (goat’s default) fail with Unknown lexicon type even after
publishing — use goat record create --no-validate; clients write without
validate and the PDS stores the record unvalidated. Publishing serves discovery
and client-side validation (goat lex validate).
Evolution
Abschnitt betitelt „Evolution“A published schema is an ordinary record and can be overwritten. Convention after
freezing: backwards-compatible changes only — add optional fields, add
knownValues. Not allowed: removing/renaming fields, changing types, optional →
required, tightening limits. A breaking change gets a new NSID (e.g.
ch.atvoto.dossier.argumentV2). goat lex breaking checks this against the
published version. During the POC (disposable test data) breaking changes remain
possible.