Roadmap zu einer maximal föderalen Platform für Abstimmungsdiskurse
Status: Konzept (2026-10-26). Erledigtes steht nicht mehr hier: Das Modell aus Stufe 0 (Mitgliedschaft, Teilnahmeregeln, Handle-Domain pro Client) ist im Code und im Test-Cluster, beschrieben in NEW_CLIENT.md und ARCHITECTURE_SPLIT.md. Stufen 0 und 0c sind vor prod umzusetzen, alles andere bei Bedarf.
Lesen ist ab dem ersten Tag föderal, Mitreden wird schrittweise geöffnet. Das Datenmodell ist darauf ausgelegt, keine Stufe braucht eine Migration.
Grundsätze
Abschnitt betitelt „Grundsätze“- Rechte folgen der Bürgschaft eines Clients, nicht dem PDS. Der Client bürgt für seine Mitglieder und meldet ihre Prüfstufe.
- Das Verfahren gehört dem Store, die Zulassung dem Client. Verfahren für alle gleich (Verfassung), Teilnahmeregeln pro Client.
- Jeder Client arbeitet in eigenen Dossiers (
app_dossier_meta.client_id). Dieselbe Abstimmung bei zwei Clients ergibt zwei Dossiers, verknüpft übervoteRef. - Der Store setzt durch, der Client konfiguriert (über den Registrar).
| Stufe | Inhalt | Abhängig von |
|---|---|---|
| 0 | Restarbeiten vor prod | – |
| 0b | Bluesky-Integration: Crosspost, Poller, Feed | 0, R |
| 0c | Konto-Löschung überarbeiten | 0 |
| 1 | direkte ATProto-Logins (OAuth, fremde PDS), wenn der Client es erlaubt | 0 |
| 2 | weitere Clients mit eigenen Dossiers und Regeln | 0 |
| 3 | Portierung gehosteter Konten zu einem anderen PDS | 1 |
| R | Prod-PDS am Relay, Zeitpunkt offen | prod |
| X | eID, jederzeit einschiebbar | – |
Jede Stufe ist eine Möglichkeit für Clients, keine Pflicht.
Stufe 0: Restarbeiten
Abschnitt betitelt „Stufe 0: Restarbeiten“-
Moderation: Payload-Plugin und Label-Modul im Registrar, Beiträge werden ausgeblendet statt gelöscht (MODERATION.md, VO Moderation). Labels sind ab dem Start signiert; das Live-Abo und die Deklaration für andere Apps folgen später (Prio 3).
-
Im Client (smartinfo): PDS-Zugriff hinter eine kleine Session-Schnittstelle legen (DID, Schreiben ins Repo). Heute App-Passwort, später zusätzlich OAuth.
-
Kontingente pro Client: Die Limits für Argumente und Kommentare (heute
APPVIEW_*_LIMIT, gleich für alle) werden eine Client-Einstellung, nach dem Muster von Peer Review (Migration006): Spalteclients.content_quota, setzt der Client über den Registrar, mit Log;NULL= heutige Werte. AppView und Writer lesen sie über das Dossier.lock_key()bleibt Code. -
Profile (
displayName) projiziert der Indexer aus dem Firehose unseres PDS (Migration009). Fremde PDS: Stufe 1.
Bullet-Proofing: Angaben in Records
Abschnitt betitelt „Bullet-Proofing: Angaben in Records“Grundsatz: Indexer und Writer glauben den Angaben eines Records nichts. Wer etwas
geschrieben hat, ist das Repo (bei Kopien: der DID in originUri). Typ, Quelle und
Status kommen aus der DB oder vom Store selbst. Alles andere wird nach dem Lexicon
geprüft oder verworfen. Umgesetzt (2026-10-28): Der Writer baut die Kopien von Argumenten und
Antworten neu auf (Autor, Reviewer, Quelle vom Store, keine fremden Übersetzungen).
Der Indexer leitet Autor und Reviewer aus originUri ab, prüft preference,
die Längen von Kommentaren und den Thread einer Antwort, und kappt createdAt in
der Zukunft. Offen:
- Sprache (
langs) ist frei wählbar. Wer alle Sprachen angibt, verhindert die Übersetzung, eine falsche Originalsprache führt zu falschen Übersetzungen. Idee: Sprache im Writer erkennen und mitlangsvergleichen. - Anzeigename (
displayName) ist frei wählbar, etwa «Bundeskanzlei» oder der Name einer anderen Person. Idee: Liste geschützter Namen im Client, sonst Moderation (MODERATION.md). - Kommentare zu abgelehnten Argumenten nimmt der Indexer weiter an.
Abgelehnte Argumente bleiben absichtlich sichtbar (rotes Badge,
routes/participation/arguments.py), ihre Kommentare also auch. Zu entscheiden: Sollen solche Argumente noch kommentiert werden können? - Antwort im falschen Community-Repo: Der Indexer prüft nicht, ob eine Peer-Review-Antwort im Community-Repo des Arguments liegt. Dort schreibt nur der Writer, darum offen gelassen. Nachziehen, sobald andere Dienste in Community-Repos schreiben.
- Rückdatieren von
createdAtbleibt möglich: Es schiebt einen Beitrag nur nach unten und ist für Importe nötig.
- Client-Seite (smartinfo) gegen
registrar.test.atvoto.chundapi.test.atvoto.chanbinden.
- extended funcitonal tests. (manual)
Stufe 0b: Bluesky-Integration
Abschnitt betitelt „Stufe 0b: Bluesky-Integration“Code vorhanden, in test per Flag aus (APPVIEW_CROSSPOST_ENABLED,
BSKY_POLL_ENABLED). Regeln:
VO Interoperabilität. Umsetzung:
INTEROPERABILITY.md.
- Crosspost (Writer), Poller (Indexer), Feed-Generator (AppView).
- (auch env variable)
- Bedingung: PDS am Relay (Stufe R). Ohne
Relay ist in Bluesky nichts sichtbar; vor
APPVIEW_CROSSPOST_ENABLED=truein prod erledigen. Noch nicht end-to-end getestet: Der Test-Cluster hängt nicht am Relay, der Test braucht einen temporären Anschluss oder prod. - Bluesky-Likes im Client (VO § 6, INTEROPERABILITY T3): Spalte
app_arguments.bsky_like_count, Poller schreibt sie ausgetPosts; Like-Zahlen externer Kommentare ebenfalls auffrischen. Im Client getrennt von den eigenen Bewertungen anzeigen. - Antworten aus Bluesky im Client anzeigen und zählen (VO § 5); Client-Regel offen.
- Spiegel mitlöschen (VO § 3 Abs. 3, T5) und Löschungen aus Bluesky übernehmen (VO § 5 Abs. 3, T6): beides fehlt im Code.
- Eingang bleibt der Poller; Jetstream erst bei Bedarf.
- Offen (Diskussionspunkte der VO): Form des Argument-Spiegels (Link-Card, Basis-URL), Kommentare spiegeln, Teilnahmeregel für externe Antworten, Moderation externer Antworten.
- Lizenz: atvoto und smartinfo sind lizenziert (AGPL-3.0-or-later,
doc/CC BY 4.0, in atvotolexicons/MIT). Die Lizenz der Beiträge (Argumente, Kommentare) ist noch offen: NUTZUNGSBEDINGUNGEN.md. - Automatische Updates: atvoto und smartinfo haben weder Dependabot noch
Renovate (npm, pip, Docker-Images, GitHub Actions). Die Python-Abhängigkeiten
sind fest gepinnt und veralten unbemerkt, in smartinfo z.B.
fastapiundhttpxinservices/client-api/requirements.txt.
Stufe 0c: Konto-Löschung
Abschnitt betitelt „Stufe 0c: Konto-Löschung“Konzept überarbeiten und umsetzen: ACCOUNT_DELETION.md (Verfassung Art. 16a).
- Offene Fragen 1–7 entscheiden (Herkunft an kanonischen Kopien, Gutachten, rückwirkende Ergebnisse, Kontingent-Ledger, gelöschte Elternkommentare, Backups, Bluesky).
- Konzept an das Modell aus Stufe 0 anpassen: Mitgliedschaften pro
(did, client_id), Löschung bei mehreren Clients. - Indexer:
#account-Events verarbeiten, Daten der DID entfernen bzw. markieren;deactivated/takendownnur ausblenden. - Admin-Skript zum Löschen eines Kontos auf dem PDS.
Stufe 1: Direkte ATProto-Logins
Abschnitt betitelt „Stufe 1: Direkte ATProto-Logins“Store:
- Firehose fremder PDS (Jetstream bzw. Relay, gefiltert auf
ch.atvoto.*und registrierte DIDs). Records ohne bekanntes Dossier zurückhalten und später erneut prüfen. registerParticipant(did, assurance)mitorigin=external.- Profile (
app.bsky.actor.profile) auch aus dem Firehose fremder PDS (ARCHITECTURE_SPLIT §5.5). - Code nach der Annahme „DID liegt auf
PDS_HOSTNAME“ durchsehen. - DID-Dokument bei Signaturfehler neu laden.
- Service-Auth über fremden PDS prüfen (
scripts/service_auth_check.sh, Ergebnis in RUNBOOK §14). - Neuladen der Community-Liste bei
taxonomy.snapshotfremder DIDs drosseln. - App-Passwörter rotieren:
rotateAppPassword(did)und Massenvariante im Registrar.
Client: OAuth (Metadaten, JWKS, PAR, PKCE, DPoP), eigene Prüfung externer Konten,
sonst assurance=none.
Details: THIRD_PARTY_PDS.md.
Stufe 2: Weitere Clients
Abschnitt betitelt „Stufe 2: Weitere Clients“- Die Verfassung gilt heute nur für einen Client. Sie ist neu zu regeln: Rollen von Store und Client, Zulassung, Teilnahmeregeln pro Client, Wegfall eines Clients, Moderation pro Client (Titel XI), Änderung der Verfassung (Art. 50).
- Registrar pro Client absichern (
ipAllowList, Rate-Limits), Kosten pro Client (Übersetzungen, Embeddings im Writer). - Handle-Domain: Eintrag in
registry.clients,PDS_SERVICE_HANDLE_DOMAINS, DNS, Zertifikat. - Mitglied eines Clients tritt einem zweiten bei: Nachweis der DID fehlt für gehostete Konten (zusammen mit Stufe 3 lösen).
- Kontingente gelten pro DID über alle Clients: prüfen, ob gewollt.
- Sprachen pro Client:
ATVOTO_LANGUAGESbleibt die Menge, die die Plattform kann. Jeder Client wählt daraus seine Sprachen und seine Standardsprache. Übersetzt und eingebettet wird nur in die Sprachen des Clients, dem das Dossier gehört. Betrifft Writer (Übersetzungen, Embeddings), AppView (_lang.py, Suche, Kommentare) und den Indexer. Erst angehen, wenn ein Client andere Sprachen braucht. - AppView: Dossiers mit gleichem
voteRefverknüpfen, Filter nach Client. - Föderale Moderation:
moderation.hide/unhide/listReportsim Registrar für eigene Dossiers, optional eigener Labeler.
Stufe 3: Portierung gehosteter Konten
Abschnitt betitelt „Stufe 3: Portierung gehosteter Konten“Verfassung Art. 3. Registrar-Methode z.B. releaseAccount(did): temporäres
Passwort per PDS-Admin, PLC-Operation mit dem Rotation-Key des PDS (die
Konto-Adresse …@participants.invalid empfängt keinen Code), Handle neu oder
weiter aufgelöst. Danach ist das Konto external (braucht Stufe 1).
Stufe R: Relay-Anschluss in prod
Abschnitt betitelt „Stufe R: Relay-Anschluss in prod“Entscheid (ARCHITECTURE_SPLIT E25): Die Testumgebung bekommt keinen Relay-Anschluss, höchstens temporär. Für prod ist er vorgesehen, der Zeitpunkt ist offen.
- Prod-PDS nach FEDERATION.md anschliessen.
- Schon beim ersten Start von prod einhalten, auch wenn der Anschluss später kommt:
Server-DID als
did:plc, endgültiger Hostname, goldene Regeln. - Ein temporärer Anschluss in test ist nur unter einem Wegwerf-Hostnamen erlaubt
(eigene
did:plc). Danach wieder trennen, und diesen Hostnamen nicht weiterverwenden: Ein Reset unter einem dem Relay bekannten Hostnamen führt zum Throttling.
Stufe X: eID
Abschnitt betitelt „Stufe X: eID“- X1, Client bürgt:
updateParticipant(did, assurance=eid), keine Änderung im Store. Eine Person mit zwei Konten hat zweimaleid. - X2, Store prüft: Nachweis gegen Aussteller, nur Pseudonym (Hash mit Salz),
verified_at,expires_at. Optional Merkmale wieresidence: [ZH]. Abklären: DSG, Merkmale der Schweizer E-ID, Prüfintervall.
Später, vielleicht nie
Abschnitt betitelt „Später, vielleicht nie“members: public: nicht registrierte ATProto-Konten.- Teilnahmeregeln als öffentlicher Record.
Bekannte Lücken
Abschnitt betitelt „Bekannte Lücken“- Doppelter Code, weil jedes Image nur seinen eigenen Service-Ordner sieht:
content_quotaunderrors(AppView, Writer),languages(AppView, Writer; dazulanguages.jsim Indexer). Ein gemeinsames Paket lohnt sich nicht. Besser ein Check in CI, der meldet, wenn die Kopien auseinanderlaufen. Quota und Sprachen sollen pro Client werden (ROADMAP, Stufe 0 und 2). - CI rollt nicht automatisch aus, Tags werden von Hand gesetzt.
- Der Registrar läuft absichtlich als genau ein Pod (
replicas: 1,strategy: Recreate), weil die Sperre gegen gleichzeitige Snapshot-Veröffentlichungen nur pro Prozess gilt. Nicht skalieren und Uvicorn nicht mit mehreren Workern starten.