Zum Inhalt springen

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.

  1. Rechte folgen der Bürgschaft eines Clients, nicht dem PDS. Der Client bürgt für seine Mitglieder und meldet ihre Prüfstufe.
  2. Das Verfahren gehört dem Store, die Zulassung dem Client. Verfahren für alle gleich (Verfassung), Teilnahmeregeln pro Client.
  3. Jeder Client arbeitet in eigenen Dossiers (app_dossier_meta.client_id). Dieselbe Abstimmung bei zwei Clients ergibt zwei Dossiers, verknüpft über voteRef.
  4. 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.


  • 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 (Migration 006): Spalte clients.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 (Migration 009). Fremde PDS: Stufe 1.

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 mit langs vergleichen.
  • 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 createdAt bleibt möglich: Es schiebt einen Beitrag nur nach unten und ist für Importe nötig.
  1. Client-Seite (smartinfo) gegen registrar.test.atvoto.ch und api.test.atvoto.ch anbinden.
  • extended funcitonal tests. (manual)

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=true in 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 aus getPosts; 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 atvoto lexicons/ 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. fastapi und httpx in services/client-api/requirements.txt.

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/takendown nur ausblenden.
  • Admin-Skript zum Löschen eines Kontos auf dem PDS.

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) mit origin=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.snapshot fremder 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.

  • 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_LANGUAGES bleibt 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 voteRef verknüpfen, Filter nach Client.
  • Föderale Moderation: moderation.hide/unhide/listReports im Registrar für eigene Dossiers, optional eigener Labeler.

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).

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.
  • X1, Client bürgt: updateParticipant(did, assurance=eid), keine Änderung im Store. Eine Person mit zwei Konten hat zweimal eid.
  • X2, Store prüft: Nachweis gegen Aussteller, nur Pseudonym (Hash mit Salz), verified_at, expires_at. Optional Merkmale wie residence: [ZH]. Abklären: DSG, Merkmale der Schweizer E-ID, Prüfintervall.
  • members: public: nicht registrierte ATProto-Konten.
  • Teilnahmeregeln als öffentlicher Record.
  • Doppelter Code, weil jedes Image nur seinen eigenen Service-Ordner sieht: content_quota und errors (AppView, Writer), languages (AppView, Writer; dazu languages.js im 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.