diff --git a/verfahren/aar/2026-08-11-apo-calls-profile-zeile.md b/verfahren/aar/2026-08-11-apo-calls-profile-zeile.md new file mode 100644 index 0000000..a60c6b8 --- /dev/null +++ b/verfahren/aar/2026-08-11-apo-calls-profile-zeile.md @@ -0,0 +1,102 @@ +# AAR — `@apo` konnte nicht telefonieren: fehlende Synapse-`profiles`-Zeile + +**Datum:** 2026-08-11 · **Beteiligt:** sorb + Mac-Session · **Stack:** Synapse, +MAS, Authentik, Element Web / Element Call, MatrixRTC (K3s-Cluster) +**Auftrag:** `@apo` kann sich anmelden und schreiben, aber **kein Call kommt +zustande** — Grundursache finden und beheben, ohne weiter zu raten. + +## 1. Ergebnis + +**Behoben und verifiziert:** +- `@apo` telefoniert wieder. Grundursache belegt: dem Konto fehlte die Zeile in + Synapses `profiles`-Tabelle. Fix war ein einzelnes `INSERT` der + Registrierungs-Default-Zeile, an zwei gesunden Konten (`clark`, + `calltest01`) gegengeprüft. +- Gegenprobe nach dem Fix: `displayname` gesetzt (vorher keine Zeile), + `open_id_tokens` **0 → 6**, aktives `org.matrix.msc3401.call.member` im Raum. +- Dokumentiert: Runbook `docs/troubleshooting/CALLS-FEHLEN-PROFILE-ZEILE.md` im + gitops-Repo (inkl. Index-Eintrag), Merksatz im Session-Gedächtnis. + +**Nebenbefund, separat behoben:** +- **Kontoübernahme-Lücke:** Der MAS-Upstream-Provider stand auf + `claims_imports.localpart.on_conflict: add` — bei Localpart-Kollision verknüpfte + MAS die neue Upstream-Identität mit einem **bestehenden** Konto (inkl. + Dienstkonten ohne Upstream-Link). Auf `on_conflict: fail` umgestellt + (gitops `ef04d86`, nach git.lab gepusht), dokumentiert als + [gitops#61](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/61), + `priority:high`. Ausgelöst durch die live reproduzierte case-sensitive Dublette + `boje`/`Boje`; das Zweitkonto `boje` (Authentik-ID 11) wurde gelöscht. + +## 2. Die Kausalkette (belegt, nicht vermutet) + +| Glied | Beleg | +|---|---| +| `@apo` hat **keine `profiles`-Zeile** | `SELECT count(*) … = 0`, während `clark`/`sorb`/`calltest01` je eine haben | +| Displayname-Setzen crasht | `PUT …/displayname → 500`, `TypeError: 'NoneType' object is not subscriptable` in `_check_profile_size` (`storage/databases/main/profile.py:354`) — `txn.fetchone()` liefert `None`, `row[0]` fliegt | +| kein Displayname → Widget-Init bricht ab | Call-Klick erzeugte **null** Server-Aktivität: kein `openid/request_token`, kein `call.member`; Browser-Log damals „Messaging present but not yet started" (iframe meldet nie `ContentLoaded`) | +| kein Widget → kein Token → keine SFU | `@apo` als einziger aktiver Nutzer mit **0** Einträgen in `open_id_tokens` (die nicht geprunt werden) | + +Herkunft der fehlenden Zeile: `@apo` ist ein **Vor-Authentik-Konto**, das durch +sechs Identitäts-Resets ging. Deaktivieren löscht in Synapse das Profil, +Reaktivieren legt es nicht neu an. `frank` (noch älter, nie zurückgesetzt) behielt +seine Zeile. Ob einer der früheren manuellen Eingriffe der auslösende Reset war, +ist nicht mehr zweifelsfrei zu klären — die Zeile ist jetzt wieder da. + +## 3. Was ausgeschlossen wurde (gemessen) + +| Verdacht | Warum entkräftet | +|---|---| +| Krypto / Cross-Signing (18 Pseudo-Geräte aus 6 Resets) | Testraum ist **unverschlüsselt** → Call braucht keine Krypto; `clark` telefoniert mit ebenfalls zurückgesetzten Schlüsseln | +| Server-Call-Pfad (SFU, RTC-Auth, OpenID-Endpoint) | `calltest01`/`sorb` bekommen sauber 200 auf `openid/request_token` und die Federation-Auflösung | +| `@apo`s Token / Session | `/sync` läuft durchgehend mit 200, Messaging intakt | +| Login-Verknüpfung MAS↔Authentik | `subject` = Authentik-`uid` `2fafe38b…`, korrekt | + +## 4. Was zur Lösung geführt hat + +- **Der Sprung von „welcher Nutzer telefoniert nicht" zu „welche *Tabelle* ist + anders".** Der Durchbruch war die `open_id_tokens`-Abfrage über *alle* aktiven + Nutzer: `@apo` = 0, alle anderen zweistellig+. Ein Vergleich statt einer + Einzelbetrachtung. +- **Ein unverschlüsselter Testraum** hat das größte Ablenkungsfeld + (Cross-Signing) in einem Schritt geschlossen. +- **Der Live-Mitschnitt beim echten Call-Klick** zeigte die Abwesenheit jeder + Aktivität — nicht ein Fehler, sondern *nichts* war der Befund. +- **Der Nutzer-Hinweis „Anzeigename konnte nicht gesetzt werden"** lieferte den + 500er mit vollständigem Stacktrace — die letzte Meile von Korrelation zu + Ursache. +- **Der entscheidende Kontext kam von sorb:** „`apo` ist ein Alt-Konto von vor + der Authentik-Integration." Das lenkte die Suche von „angesammelter Müll" auf + „Migrations-/Provisionierungs-Lücke". + +## 5. Lehren für die Zukunft + +1. **Bei Call-Problemen zuerst `open_id_tokens` je Nutzer vergleichen.** 0 bei + einem sonst aktiven Konto ist das schnellste, eindeutigste Alarmsignal und + trennt Client- von Server-Ursache in einer Abfrage. +2. **Immer im unverschlüsselten Raum reproduzieren, bevor man Krypto verdächtigt.** + Das schließt einen ganzen Ursachenblock kostenlos aus. +3. **„Nichts passiert" ist ein Messergebnis, kein Sackgassen-Signal.** Die + Abwesenheit eines `openid`-Aufrufs hat den Fehler lokalisiert, nicht ein + Fehlercode. +4. **Alt-/mehrfach-zurückgesetzte Konten gegen frisch provisionierte diffen, + nicht nur gegen die Erwartung.** Der Unterschied war eine *fehlende* Zeile — + sichtbar nur im direkten Vergleich mit `clark`/`calltest01`. +5. **Jeder DB-Schreib strukturiert: betroffene Zeile vorher anzeigen, an einem + gesunden Konto gegenprüfen, per `INSERT … ON CONFLICT DO NOTHING` statt + Überschreiben.** Das ist die direkte Konsequenz aus den früheren + unstrukturierten MAS-Eingriffen dieses Vorgangs — und diesmal eingehalten. +6. **Beiläufige Symptome ernst nehmen:** die Dublette `boje`/`Boje` beim + Testkonto-Anlegen war der Faden, der die Kontoübernahme-Lücke (gitops#61) + aufdeckte — ein Sicherheitsfund, der ohne den `@apo`-Vorgang unentdeckt + geblieben wäre. + +## 6. Offen / Folgetodos + +- **gitops#61** (`on_conflict`-Härtung) ist gepusht und rollt über Flux; der + case-insensitive Eindeutigkeits-Check im `matrix-invitation`-Prompt-Stage + (damit der Nutzer schon bei der Registrierung statt erst beim Login scheitert) + ist dort als bewusst offener Rest vermerkt. +- Verwaiste Altlasten bei `@apo` (10 `local_notification_settings` für längst + gelöschte Geräte, 18 Cross-Signing-Pseudoeinträge) sind **kosmetisch** und + wurden bewusst **nicht** angefasst — sie haben mit dem Call-Problem nichts zu + tun, und ein weiterer Eingriff widerspräche der Lehre oben.