Bluesky-Anbindung (technische Umsetzung)
Stand: 2026-11-02. Was gilt, regelt die Verordnung über die Interoperabilität (VO). Dieses Dokument beschreibt, wie sie umgesetzt ist.
Alle drei Wege sind im Code, in test ausgeschaltet (kein Relay,
APPVIEW_CROSSPOST_ENABLED=false,BSKY_POLL_ENABLED=false). In prod nach FEDERATION.md. Föderation der Teilnahme (fremde PDS, weitere Clients): ROADMAP.md.
atvoto-Records (ch.atvoto.*) sind für Bluesky unsichtbar: bsky.app zeigt nur
app.bsky.*. Die Anbindung läuft deshalb über Spiegelposts.
Crosspost (Writer) Poller (Indexer) Argument ─────────────────────► app.bsky.feed.post ─────────────────► app_comments (kanonische Kopie, im Community-Konto Antworten auf (origin = 'extern') Community-Konto) @dossier-<id>.… Bluesky ▲ │ getFeedSkeleton Feed-Generator (AppView)1. Crosspost: atvoto → Bluesky
Abschnitt betitelt „1. Crosspost: atvoto → Bluesky“- Wer: Writer (
services/writer/src/atproto/crosspost.py), Hintergrund-Schleife. - Was: jedes Argument ohne
bsky_post_urialsapp.bsky.feed.postunter dem Community-Konto des Dossiers ([PRO] Titel+ Text, max. 300 Zeichen; bei laufendem Peer Review mit[Preliminary]). URI und CID landen inapp_arguments.bsky_post_*. - Nicht: Dossiers selbst (CMS-Inhalt), Kommentare, Bewertungen.
- Steuerung:
APPVIEW_CROSSPOST_ENABLED(Standardfalse),APPVIEW_CROSSPOST_POLL_INTERVAL_SECONDS(Standard 30).
2. Poller: Bluesky → atvoto
Abschnitt betitelt „2. Poller: Bluesky → atvoto“- Wer: Indexer (
services/indexer/src/bsky_poller.js). - Was: für jedes gespiegelte Argument
app.bsky.feed.getPostThreadüber die öffentliche Bluesky-API (Tiefe 10); Antworten werden als Kommentare mitorigin = 'extern'inapp_commentsübernommen, Antwortzähler inapp_arguments.bsky_reply_count. Ältere Argumente (> 48 h) seltener. - Steuerung:
BSKY_POLL_ENABLED(Standardfalse),BSKY_POLL_INTERVAL_MS(Standard 10 min). - Grenzen: keine Zitat-Posts, Tiefe max. 10, Verzögerung bis zum Poll-Intervall.
3. Feed-Generator
Abschnitt betitelt „3. Feed-Generator“- Wer: AppView (
services/appview/src/routes/atproto/feed.py),app.bsky.feed.describeFeedGeneratorundgetFeedSkeleton. - Was: die gespiegelten Posts (Argumente und externe Kommentare mit
bsky_post_uri), neueste zuerst. Bluesky hydriert die URIs selbst. - Identität:
did:webder AppView (APPVIEW_FEED_GENERATOR_DID), Dienst#bsky_fgim DID-Dokument. - Noch zu tun für prod: den Feed als Record
app.bsky.feed.generatorin einem Konto veröffentlichen (FEED_ACCOUNT_DID). Der rkey istatvoto(FEED_URI); nach dem Veröffentlichen nicht mehr ändern.
Weitere app.bsky.*-Aufrufe (Timeline, Profile, Threads) reicht die AppView an
Bluesky weiter.
4. Konten
Abschnitt betitelt „4. Konten“- Konten von atvoto können sich bei Bluesky anmelden, wenn sie ein App-Passwort
haben. smartinfo stellt keines für die Person aus (nur
client:<id>für sich selbst). - Bluesky-Konten können bei smartinfo nicht mitreden (VO Konten und Teilnahme § 1). Die Möglichkeit für Clients: ROADMAP Stufe 1.
5. Varianten und Abwägungen
Abschnitt betitelt „5. Varianten und Abwägungen“Stand 2026-11-01. Technischer Hintergrund zu den Diskussionspunkten der VO. Code unverändert.
5.1 Sichtbarkeit in Bluesky
Abschnitt betitelt „5.1 Sichtbarkeit in Bluesky“| Variante | In bsky.app sichtbar | Eignung |
|---|---|---|
Spiegelpost mit Link-Card (app.bsky.embed.external) |
ja, Post mit Vorschaukarte | Argumente |
Custom Record einbetten (app.bsky.embed.record auf ch.atvoto.*) |
nein, Bluesky rendert fremde Typen nicht | – |
standard.site (site.standard.document) |
nicht als Post; nur Reader-Apps (Leaflet u.a.) bzw. Link-Card beim Teilen der URL | Langtexte, eher Dossiers (CMS) |
| Post im Repo des Autors statt der Community | ja, im Profil der Person | Kommentare, nur mit Opt-in |
Vorschlag für den Argument-Spiegel:
- Text nur
[PRO] Titel, dazuembed.externalauf die Argument-Seite des Clients (Titel, Anriss, Bild). Diskussion zu VO § 3; braucht eine Basis-URL als Client-Setting und OG-Tags (og:title,og:description,og:image) im Client. - Rückverweis als Zusatzfeld im Post, z.B.
"ch.atvoto.source": {uri, cid}. Lexicon-Objekte sind offen; Bluesky ignoriert das Feld, Poller und Dritte können damit zuordnen.
Kommentare nicht unter dem Community-Konto spiegeln (spricht im Namen der Person,
Autorschaft geht verloren). Höchstens als Opt-in im Repo der Person, als
app.bsky.feed.post mit reply: {root, parent} auf den Argument-Spiegel. Ob das
erlaubt ist, regelt der Client (bei smartinfo heikel: politische Meinung öffentlich
im eigenen Profil).
5.2 Antworten aus Bluesky
Abschnitt betitelt „5.2 Antworten aus Bluesky“Client Community-PDS Bluesky-Netz────── ───────────── ────────────1. Argument ──Writer──► ch.atvoto.dossier.argument app.bsky.feed.post (Spiegel) ────► Feed, Link-Card
2. Kommentar ──► User-Repo: ch.atvoto.dossier.comment (opt-in) Spiegel als Reply ───────────────► Antwort im Thread
3. Bluesky-User antwortet: app.bsky.feed.post im EIGENEN PDS reply.root = Argument-Spiegel reply.parent = Spiegel / andere Antwort │ Poller / Jetstream◄┘ ▼ AppView: app_comments (origin='extern') Community-PDS: Optionen untenDie Antwort ist bereits ein ATProto-Record im PDS der antwortenden Person. Niemand sonst kann ihn dort schreiben oder ändern. Offen ist nur, ob das Community-PDS zusätzlich etwas schreibt:
- Nur AppView (Ist-Zustand). Antworten nur in
app_comments. ➕ einfach, Hoheit bleibt bei der Person. ➖ Dritt-AppViews sehen die Auswahl nicht; Zugehörigkeit nur in der DB; Poller träge und erkennt Löschungen schlecht. - Kopie ins Community-PDS (Muster wie Argumente:
ch.atvoto.dossier.commentmitsource: extern,originUri,originCid). ➕ Community-Repo vollständig. ➖ veröffentlicht Inhalte von Nicht-Mitgliedern ohne Zustimmung (VO Konten und Teilnahme § 1, Datenschutz); Löschungen müssen nachgezogen werden; Kopie wirkt wie von der Community geprüft, lief aber nie durchparticipation_check. Eher vermeiden. - Nur Verweis ins Community-PDS, z.B.
ch.atvoto.dossier.externalReply {argument, subject: {uri, cid}, status}, ohne Text. ➕ Dritt-AppViews sehen dieselbe kuratierte Auswahl; Moderation nachvollziehbar (status: hidden); Löschung an der Quelle wirkt überall. ➖ zusätzliches Lexicon, ein Write pro Antwort.
Unabhängig davon der Eingang:
- Poller (heute): keine Infrastruktur, aber Verzögerung, Tiefe 10, Löschungen nur über Abgleich.
- Jetstream/Firehose:
app.bsky.feed.postfiltern, derenreply.rooteine Spiegel-URI ist. Echtzeit, Löschungen alsdelete-Event.
Threading: Werden Kommentare gespiegelt, antworten Leute auch auf diese Spiegel
(reply.parent = Kommentar-Spiegel). Siehe T4.
VO § 5 Abs. 4 hält Option 1 fest; Option 3 bleibt Diskussionspunkt, Option 2 ist zu vermeiden. Eingang: Poller (T2).
5.3 Rundlauf Client ↔ Bluesky
Abschnitt betitelt „5.3 Rundlauf Client ↔ Bluesky“Ziel: In einem Bluesky-Feed die Diskussion im Client verfolgen, dort antworten und bewerten; die Antworten erscheinen wieder im Client. Die Optionen aus 5.2 betreffen nur, was zusätzlich im Community-PDS steht. Der Rückweg in den Client läuft in allen drei über die AppView.
| Schritt | mit Option 1 | Wie |
|---|---|---|
| Argumente im Bluesky-Feed | ✅ | Crosspost + Feed-Generator |
| In Bluesky antworten | ✅ | normale Antwort auf den Spiegel, im PDS der Person |
| Antworten erscheinen im Client | ✅ | Poller → app_comments (origin='extern'); Anzeige ist Client-Regel |
| Likes aus Bluesky | teilweise | externe Kommentare: app_comments.bsky_like_count vorhanden; Argumente: Spalte fehlt (T3) |
Lücken, unabhängig von der Option:
- Client-Kommentare fehlen in Bluesky. Gespiegelt wird nur das Argument; wer in Bluesky mitliest, sieht Argument und Bluesky-Antworten, die Diskussion im Client nur hinter der Link-Card. Für einen gemeinsamen Thread müssten Kommentare als Replies gespiegelt werden: unter dem Community-Konto (einfach, spricht aber im Namen der Person; nur wenn die Client-Regeln das offenlegen) oder als Opt-in im Repo der Person (siehe 5.1).
- Bewerten heisst in Bluesky nur Like. Kein PRO/CONTRA, keine Skala, kein
ch.atvoto.content.rating, keine Teilnahmeregeln. Im Client getrennt anzeigen („♥ 12 auf Bluesky“), nicht mit eigenen Bewertungen vermischen. - Relay nötig. Spiegelposts erscheinen in Bluesky nur, wenn der Bluesky-Relay das Community-PDS crawlt. In test gibt es keinen Relay; für prod Voraussetzung (FEDERATION.md, T1).
5.4 Option 3 gegenüber Option 1
Abschnitt betitelt „5.4 Option 3 gegenüber Option 1“Option 3 schliesst keine der Lücken aus 5.3. Sie ändert nur, wo die Auswahl externer Antworten festgehalten ist: in der DB der AppView oder signiert im Community-Repo.
Besser:
- Dritt-AppViews und -Clients sehen dieselbe Auswahl ohne Zugriff auf die DB.
- Die AppView kann die Zuordnung aus dem Community-Repo neu aufbauen statt aus Poller-Stand oder Backup.
- Moderation ist nachvollziehbar: „ausgeblendet“ ist ein signierter Record, keine stille DB-Änderung.
Schlechter:
- Personenbezug bleibt. Auch ohne Text steht öffentlich „DID X hat auf Argument Y geantwortet“; bei politischen Themen lässt das Rückschlüsse zu. Löscht die Person ihren Post, muss der Verweis mitgelöscht werden.
- Ausblenden wird öffentlich.
status: hiddenzeigt jedem, wessen Antwort ausgeblendet wurde: transparent, aber Angriffsfläche für Zensurvorwürfe und blossstellend für die Person. - Jemand muss annehmen. Bei automatischer Annahme spiegelt der Record nur die DB (wenig Mehrwert); bei echter Auswahl braucht es einen Prozess.
- Aufwand: zusätzliches Lexicon, ein Write pro Antwort, wachsende Community-Repos, Konsistenz zwischen DB und Records.
Fazit: Option 3 erst, wenn es tatsächlich Dritt-AppViews mit Bedarf an derselben kuratierten Sicht gibt oder Moderation bewusst öffentlich prüfbar sein soll. Für Client ↔ Bluesky allein bringt sie Nutzern nichts Sichtbares.
6. Umsetzung der VO
Abschnitt betitelt „6. Umsetzung der VO“Stand 2026-11-02.
| VO | Inhalt | Stand im Code |
|---|---|---|
| § 3 Abs. 1–2, 4 | Argument-Spiegel unter Community-Konto, PRO/CONTRA, vorläufig; Feed | umgesetzt (§§ 1, 3 oben) |
| § 3 Abs. 3 | Spiegel beim Löschen/Zurückziehen entfernen | fehlt (T5) |
| § 4 | Kommentare nicht spiegeln | Ist-Zustand |
| § 5 Abs. 1–2 | externe Antworten eingeklappt anzeigen, nicht als Kommentare zählen, Hinweis «extern, nicht bei smartinfo registriert» | Poller schreibt origin='extern'; Anzeige im Client offen |
| § 5 Abs. 3 | Löschung an der Quelle übernehmen | fehlt, Poller erkennt Löschungen nicht (T6) |
| § 5 Abs. 4 | nichts ins Community-Konto | Ist-Zustand (Option 1) |
| § 6 | Bluesky-Likes getrennt anzeigen, nicht in like_count |
teilweise (T3) |
| § 7 | Ausblenden in der Ansicht | MODERATION.md |
| § 8 | Relay | T1 |
Technische Anforderungen
Abschnitt betitelt „Technische Anforderungen“- T1 Relay ist Bedingung (VO § 8). Ohne Relay, der das Community-PDS crawlt,
ist nichts davon in Bluesky sichtbar. Vor dem Einschalten von
APPVIEW_CROSSPOST_ENABLEDin prod muss das PDS am Relay hängen (FEDERATION.md). - T2 Eingang bleibt der Poller. Jetstream/Firehose ist Option für später (Echtzeit, Löschungen als Event), heute nicht nötig.
- T3 Neue Spalte
app_arguments.bsky_like_count(VO § 6). Der Poller holtlikeCountder Argument-Spiegel bereits übergetPosts, verwirft ihn aber. Externe Kommentare habenbsky_like_countschon, aktualisiert aber nur, wenn der Thread neu geladen wird (bei geänderter Antwortzahl); auch die Like-Zahlen der Kommentare übergetPostsauffrischen. - T4 Gespiegelte Kommentare (nur falls VO § 4 geändert wird): kein neues
Feld,
app_comments.bsky_post_uri/bsky_post_cidgibt es schon. Der Crosspost setzt sie bei internen Kommentaren; der Poller bildetreply.parentüberbsky_post_uriauf den nativen Kommentar ab statt auf die Bluesky-URI. - T5 Spiegel löschen (VO § 3 Abs. 3).
deleteArgumentim Indexer gibtbsky_post_urizurück, aber niemand löscht den Post im Community-Konto. Der Writer muss ihn bei Löschung und Rückzug entfernen. - T6 Löschungen an der Quelle (VO § 5 Abs. 3). Der Poller sieht nur vorhandene
Antworten. Fehlende Antworten bei einem vollständigen Thread-Abruf als gelöscht
markieren (
notFoundPostoder nicht mehr im Thread). Mit Jetstream kämen Löschungen als Event.
Offene Fragen
Abschnitt betitelt „Offene Fragen“Die normativen Fragen stehen als Diskussionspunkte in der VO (§§ 3, 4, 5, 7). Technisch offen:
- Teilnahmeregel für externe Antworten (VO § 5). Antworten aus Bluesky kommen
über den Poller, nicht über den Firehose, und laufen deshalb nicht durch
participation_check. Umsetzung z.B. als Aktioncomment.externodermembers: public(ROADMAP „Später“). - Form des Argument-Spiegels (VO § 3):
embed.externalbraucht eine Basis-URL als Client-Setting und OG-Tags im Client; Rückverweisfeldch.atvoto.sourceim Post (5.1). - Einklappen nicht prüfbar (VO § 5 Abs. 1–2, Stand 2026-11-06): Ohne externe Antworten gibt es dafür auf dem Testcluster nichts zu sehen, und auch keine Änderung für Nutzende.
APPVIEW_CROSSPOST_ENABLED=false Es wird nicht nach Bluesky gepostet (crosspost.py:113).