f2b9afbfea
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2546 lines
154 KiB
Markdown
2546 lines
154 KiB
Markdown
# Pyramid – Arbeitsprotokoll
|
||
|
||
Neueste Einträge OBEN. Jede Claude-Session schreibt hier nach jedem abgeschlossenen
|
||
Schritt (und beim Abbruch mitten im Schritt den Zwischenstand). Format:
|
||
|
||
```
|
||
## JJJJ-MM-TT – Kurztitel
|
||
**Erledigt:** was fertig ist (mit Commit-Hash)
|
||
**Offen/Nächster Schritt:** der konkrete nächste Handgriff
|
||
**Stolperfallen:** was schiefging und wie es gelöst wurde (damit es nie zweimal passiert)
|
||
**Fragen an Bernd:** (optional) gesammelte Richtungsfragen, die nicht blockieren
|
||
```
|
||
|
||
---
|
||
|
||
## 2026-07-06 – SQLCipher auf ANDROID getestet (Emulator API 36): Neuanlage, Migration und Neustart grün
|
||
|
||
**Erledigt:** Der größte offene Teil von Testplan Punkt 4
|
||
(`docs/SQLCIPHER_MIGRATION.md`) ist jetzt real auf Android belegt – kein
|
||
Gerät angeschlossen, aber der vorhandene Emulator (Medium Phone API 36.1,
|
||
x86_64, MIT Play Services) reicht für die DB-Schicht:
|
||
|
||
- **Szenario A – frische Installation:** Debug-APK installiert, App
|
||
gestartet → `pyramid.sqlite` wird von Anfang an VERSCHLÜSSELT angelegt
|
||
(Header = Zufallsbytes statt „SQLite format 3", per `run-as`/`od`
|
||
verifiziert); auth_log ohne Cipher-Fallback oder Fehler.
|
||
- **Szenario B – Update-Szenario (Utas Fall):** `pm clear` (auch der
|
||
Keystore-Eintrag weg) + synthetische Klartext-DB (2 Tabellen, 550
|
||
Zeilen) VOR dem ersten Start in `files/` gelegt → beim Start lief die
|
||
echte Migration: auth_log „Klartext-DB erfolgreich nach SQLCipher
|
||
migriert (2 Tabellen verifiziert)", `.premigration`-Backup liegt da,
|
||
DB danach verschlüsselt, App lief normal weiter (Login-Screen,
|
||
Screenshot geprüft). **Bewusst NICHT mit Bernds echter DB getestet** –
|
||
die enthält seine Session/Olm-Account, ein Duplikat auf einem zweiten
|
||
„Gerät" würde den E2EE-State korrumpieren.
|
||
- **Szenario C – Neustart:** force-stop + Relaunch → verschlüsselte DB
|
||
öffnet mit dem Schlüssel aus dem Android-Keystore, KEINE zweite
|
||
Migration, kein Fehler. Damit sind `openCipherOnAndroid`,
|
||
`sqlcipher_export` und flutter_secure_storage auf Android
|
||
(Haupt-Isolate) praktisch belegt.
|
||
- Doku nachgezogen: Status-Kopf `docs/SQLCIPHER_MIGRATION.md`,
|
||
ROADMAP-M1-Vermerk. Emulator danach heruntergefahren. Nebenbei:
|
||
`android/gradle.properties` bekam beim APK-Build automatisch zwei
|
||
Flutter-Migrator-Flags (`builtInKotlin=false`, `newDsl=false` – pinnt
|
||
bestehendes Verhalten), mit committet.
|
||
|
||
**UNGETESTET bleibt (ROADMAP-Punkt weiter NICHT abgehakt):** echtes
|
||
ARM-Gerät, Migration einer ECHTEN eingeloggten Session auf Android und
|
||
der Push-Test (FCM-Background-Isolate liest verschlüsselte DB,
|
||
secure_storage im Background-Isolate) – braucht ein Testgerät mit
|
||
eingeloggtem Account (Login-Daten hat nur Bernd). Erst danach Release
|
||
und Utas Gerät.
|
||
|
||
**Offen/Nächster Schritt:** auth_log-Check des weiterlaufenden
|
||
Windows-Beobachtungslaufs (Einträge seit 2026-07-06 18:19). Für Bernd
|
||
unverändert: PC_TESTPLAN 1 (Rest) + 2–8 durchklicken; fürs
|
||
SQLCipher-Finale ein echtes Android-Gerät anstecken (Debug-Build reicht,
|
||
Ablauf wie hier im Emulator).
|
||
|
||
**Stolperfallen:** (1) Ein Dart-Skript außerhalb des Repos kann
|
||
`package:`-Imports nicht auflösen – Hilfsskripte kurz ins Repo legen
|
||
(`tool/`, untracked) und nach dem Lauf löschen. (2) Für Datei-Zugriff im
|
||
App-Sandbox-Verzeichnis braucht es eine DEBUG-APK (`run-as` geht nur bei
|
||
debuggable); das Emulator-Image hat kein `sqlite3`-Binary – Test-DBs
|
||
also am PC bauen und pushen.
|
||
|
||
---
|
||
|
||
## 2026-07-06 – auth_log-Check 17:43-Fenster: echter ~7-min-Netzausfall OHNE Logout überstanden (stärkster Positiv-Befund bisher)
|
||
|
||
**Erledigt:** Vierte PC-Session, zuerst der Pflicht-Check der Vorsession
|
||
(auth_log seit 17:43), Ergebnis im PC_TESTPLAN Abschnitt 1 eingetragen:
|
||
|
||
- **Fenster 17:43–18:19:** `loggedIn` um 17:43:14, dann um 18:09:57 ein
|
||
ECHTER Verbindungsabriss zum Homeserver – „Syncloop failed: Client has
|
||
not connection to the server" alle 3 s bis 18:16:39 (~7 Minuten;
|
||
Server war zum Check-Zeitpunkt von diesem PC wieder erreichbar,
|
||
HTTP 200). **Kein Logout, keine Eskalation** – der Sync erholte sich
|
||
von selbst (WAL-Write 18:18:13 NACH der letzten Fehlerzeile, Prozess
|
||
durchgehend am Leben). Das ist genau der Fehlerpfad, den die
|
||
Uta-Logout-Fixes (63e4919/9dc83f7/891f348) absichern sollen, erstmals
|
||
mit einem realen Netzausfall belegt.
|
||
- Beobachtungslauf (PID seit 17:43) läuft unangetastet weiter; kumulativ
|
||
heute 12:26–18:19 über 7 Starts ohne einen einzigen Random-Logout.
|
||
|
||
**Offen/Nächster Schritt:** SQLCipher-Android-Test per Emulator
|
||
(Medium Phone API 36.1 ist verfügbar, kein echtes Gerät angeschlossen) –
|
||
Testplan Punkt 4 in `docs/SQLCIPHER_MIGRATION.md`, so weit im Emulator
|
||
abbildbar; Rest (echtes Gerät, FCM-Push-Isolate) klar abgrenzen.
|
||
|
||
**Stolperfallen:** Erfolgreiche Syncs werden NICHT geloggt – das Ende
|
||
einer Fehlerserie im auth_log erkennt man am Ausbleiben weiterer
|
||
Fehlerzeilen plus einem WAL-Write danach, nicht an einem „recovered"-Eintrag.
|
||
|
||
---
|
||
|
||
## 2026-07-06 – Analyse-Korrektur (MediaCache NICHT risikoarm) + VoicePrefs-Vertragstests, Session-Ende sauber
|
||
|
||
**Erledigt:** Abschluss der dritten PC-Session:
|
||
|
||
- **Ehrlichkeits-Korrektur im State-Management-Doc (3300a44):** Die
|
||
Einstufung „MediaCache hinter Provider = risikoarmer Lückenfüller" war
|
||
FALSCH – sie beruhte auf einem durch `head_limit` abgeschnittenen grep.
|
||
Tatsächlich nutzt auch `message_media.dart` den Cache (7 Stellen, inkl.
|
||
statischem `_DocBytesCache`-Wrapper ohne ref-Zugang), und genau dieser
|
||
Chat-Timeline-Code ist laut PC_TESTPLAN 3 der am wenigsten geprüfte
|
||
Teil. Schritt als ZURÜCKGESTELLT markiert (gehört zum
|
||
chat_timeline-Modul, Schnitt-Doc 9); ROADMAP-Vermerk angepasst.
|
||
- **VoicePrefs-Vertragstests (3698048):** 3 neue Tests in
|
||
`test/settings_prefs_test.dart` nageln die dokumentierten Garantien der
|
||
gestrigen/heutigen Fassade fest (historische voice_*-Keys unverändert →
|
||
Utas Bestands-Einstellungen bleiben erhalten; null/leer LÖSCHT das Pref
|
||
statt leeren String zu speichern; Fallback-Lautstärke 1.0). Gesamt jetzt
|
||
**32 Tests grün**, analyze sauber.
|
||
- **auth_log-Endkontrolle 17:54:** Beobachtungslauf seit 17:43 unauffällig
|
||
(keine neuen Einträge, DB-Writes laufen, Prozess lebt) – App bleibt über
|
||
das Session-Ende hinaus als Langzeit-Beobachtung laufen.
|
||
|
||
**Offen/Nächster Schritt:** auth_log-Check (Einträge seit 2026-07-06
|
||
17:43) in der nächsten Session – das Fenster wächst jetzt ungestört, kein
|
||
weiterer Neustart geplant. Ohne Bernds Klick-Tests (PC_TESTPLAN 1 Rest,
|
||
2–8) ist am PC nichts Risikoarmes mehr offen; danach: State-Management
|
||
Schritte 1–2 (docs/M2_STATE_MANAGEMENT.md) bzw. SQLCipher-Gerätetest
|
||
(Android, `docs/SQLCIPHER_MIGRATION.md` Punkt 4).
|
||
|
||
**Stolperfallen:** grep-Werkzeuge mit Treffer-Limit (`head_limit`)
|
||
schneiden still ab – vor Architektur-Aussagen („X wird nur von Y benutzt")
|
||
die Trefferliste IMMER ohne Limit bzw. mit Vollständigkeits-Check erheben.
|
||
Der Fehler stand schon committet im Doc und hätte die nächste Session in
|
||
einen „risikoarmen" Umbau am ungeprüftesten Code der App gelockt.
|
||
|
||
---
|
||
|
||
## 2026-07-06 – State-Management-Ist-Analyse (docs/M2_STATE_MANAGEMENT.md) + toter incomingCallProvider entfernt
|
||
|
||
**Erledigt:** Nächster offener M2-Punkt „Einheitliches State-Management" –
|
||
nach dem erprobten Vorgehen erst die Analyse, Umsetzung folgt gestaffelt:
|
||
|
||
- **`docs/M2_STATE_MANAGEMENT.md` geschrieben:** Repo-weite Bestandsaufnahme
|
||
(Riverpod schon dominant, ~50 Provider, keine ValueNotifier/eigenen
|
||
StreamController). 4 Misch-Patterns identifiziert und gewichtet:
|
||
(1) Call-Singletons + ChangeNotifier hinter den Pilot-Fassaden mit
|
||
UI-Zustand IN den Managern, (2) `activeVoiceRoomIdProvider`/
|
||
`voiceParticipantsProvider` liegen in app_state.dart statt im
|
||
voice_channel-Modul, (3) `MediaCache.instance`, (4) Push-Globals
|
||
(bewusst so, Isolate-Zwang – KEIN Umbau-Kandidat). Zielbild +
|
||
Schrittfolge mit klarem Gating: Schritte 1–2 (Call-Pfad) GESPERRT bis
|
||
Bernds Klick-Test des Piloten; frei ist nur MediaCache-hinter-Provider.
|
||
- **Toter `incomingCallProvider` entfernt (41bcaf2):** einzige Fundstelle
|
||
repo-weit war die Definition in app_state.dart – Überbleibsel des
|
||
Call-Piloten (Provider ersatzlos in die Module verlegt). analyze sauber,
|
||
29 Tests grün.
|
||
|
||
**Offen/Nächster Schritt:** auth_log-Check (Einträge seit 2026-07-06
|
||
17:43). Umsetzbar ohne Bernd wäre noch Schritt 3 aus dem neuen Doc
|
||
(MediaCache hinter Provider); Schritte 1–2 erst nach dem Klick-Test.
|
||
|
||
**Stolperfallen:** Keine – Analyse + risikofreie Leichen-Entfernung.
|
||
|
||
---
|
||
|
||
## 2026-07-06 – auth_log-Check 17:14-Fenster sauber + settings_encryption.dart in 4 part-Dateien geteilt (6128953)
|
||
|
||
**Erledigt:** Dritte PC-Session des Tages. Zuerst der von der Vorsession
|
||
hinterlassene Pflicht-Check, dann der nächste risikoarme M2-Schritt:
|
||
|
||
- **auth_log-Check (PC_TESTPLAN Abschnitt 1 aktualisiert):** Fenster
|
||
2026-07-06 17:14–17:42 sauber – einziger Eintrag ist das `loggedIn` des
|
||
17:14-Starts, kein Logout, kein Fehler, DB-Writes liefen durchgehend
|
||
(WAL zuletzt 17:37 beim Check). Lauf um 17:42 vom Autopilot beendet
|
||
(Rebuild nötig). Kumulativ heute 12:26–17:42 über 6 Starts ohne einen
|
||
Random-Logout; für „mehrere Stunden am Stück" bleibt der Punkt offen.
|
||
- **`settings_encryption.dart` (1470 Zeilen, größte verbliebene
|
||
Settings-Teildatei) nach dem 7-fach erprobten Muster geteilt (6128953):**
|
||
4 part-Dateien unter `lib/widgets/settings/encryption/` –
|
||
`megolm_crypto` (Export/Import-Krypto-Helfer, PBKDF2/AES-CTR),
|
||
`encryption_section` (Enums + _EncryptionSection),
|
||
`recovery_key_dialogs` (_ChangeRecoveryKeyDialog/_ShowKeyBody/
|
||
_ConfirmKeyBody), `fingerprint_passphrase` (_FingerprintGroup/
|
||
_PassphraseDialog). Das ist Vorbereitung für Schnitt-Doc Schritt 3
|
||
(verification-Modul), OHNE den Verschlüsselungs-heiligen Code
|
||
anzufassen: Skript-Split mit Rekonstruktions-Verifikation, alle 1468
|
||
Code-Zeilen ZEICHENIDENTISCH (ordinaler Vergleich), keine Zeile geändert.
|
||
- **Geprüft:** analyze sauber (nur bekannter onUpdate-Hinweis), 29 Tests
|
||
grün, `flutter build windows --debug` + entkoppelter App-Start: auth_log
|
||
17:43:14 `loggedIn`, keine erneute Migration, kein Fehler. Beobachtung
|
||
läuft seit 17:43 mit dem neuen Code.
|
||
|
||
**Offen/Nächster Schritt:** auth_log-Check (Einträge seit 2026-07-06
|
||
17:43). Klick-Tests (PC_TESTPLAN 1 Rest + 2–8) weiter bei Bernd – der
|
||
Verschlüsselungs-Reiter (Abschnitt 2) deckt jetzt zugleich diesen Split ab.
|
||
Verification-Modul (Schnitt-Doc 3) NICHT ohne Bernd anfangen
|
||
(2-Geräte-Test nötig); UI-State-Herauslösen wartet auf den Klick-Test.
|
||
|
||
**Stolperfallen:** Ein Debug-Rebuild kann die laufende exe nicht
|
||
überschreiben – den entkoppelten Beobachtungs-Lauf vorher gezielt beenden
|
||
(Stop-Process), das Fenster im PC_TESTPLAN sauber abschließen und nach dem
|
||
Build neu starten; sonst schlägt der Build mit gesperrter Datei fehl.
|
||
|
||
---
|
||
|
||
## 2026-07-06 – call_ui/voice_channel.dart in 5 part-Dateien geteilt (letzte Gott-Datei der Call-Schicht, 95d9b07)
|
||
|
||
**Erledigt:** Die beim M0-Aufräumen bewusst zurückgestellte Datei
|
||
`voice_channel.dart` (1123 Zeilen – wartete auf die Call-Fassaden-
|
||
Entscheidung, die seit dem Piloten gefallen ist) ist nach dem 6-fach
|
||
erprobten Muster geteilt: Owner (19 Zeilen, Imports + part-Direktiven) +
|
||
5 thematische part-Dateien unter `lib/features/call_ui/view/`
|
||
(`voice_channel_core` = VoiceChannelView/_ParticipantData, `call_view` =
|
||
_UnifiedCallView/Header, `participant_layout` = Grid/Hero/Strip/Tile,
|
||
`avatars_controls` = Avatare/_CallControls/_NoCall/Fullscreen,
|
||
`quality_menu` = ScreenShare-/Qualitäts-Menüs/_VoiceCtrlBtn).
|
||
|
||
- **Beweis:** Skript-Split mit Rekonstruktions-Verifikation – Header +
|
||
Teile sind ZEICHENIDENTISCH zum Original (1123 Zeilen, case-sensitiver
|
||
Zeilenvergleich). Keine einzige Code-Zeile geändert.
|
||
- **Geprüft:** analyze sauber (nur bekannter onUpdate-Hinweis), 29 Tests
|
||
grün, Build + App-Start ok (auth_log 17:14:31 `loggedIn`); App läuft
|
||
seit 17:14 wieder entkoppelt als Langzeit-Beobachtung.
|
||
- `app_shell.dart` (1073 Zeilen) bleibt bewusst ungeteilt –
|
||
Bootstrap/Login-Pfad ist heilig, kein Anlass.
|
||
|
||
**Offen/Nächster Schritt:** auth_log-Check (Einträge seit 2026-07-06
|
||
17:14). Danach wartet die Call-Schicht auf Bernds Klick-Test
|
||
(PC_TESTPLAN Abschnitt 8); nächster risikoarmer M2-Kandidat wäre
|
||
Schnitt-Doc Schritt 3 (verification-Modul) – aber Verifikations-Flows
|
||
sind Verschlüsselungs-heilig und brauchen einen 2-Geräte-Test, daher
|
||
nicht ohne Bernd anfangen.
|
||
|
||
**Stolperfallen:** In PowerShell bindet das Komma stärker als Minus:
|
||
`@(999, $l.Count - 1)` ist `(999, $l.Count) - 1` (Array-Minus = Fehler) –
|
||
immer `($l.Count - 1)` klammern. Und `Measure-Object -Line` zählt
|
||
Leerzeilen NICHT mit – für echte Zeilenzahlen `$array.Count` nehmen.
|
||
|
||
---
|
||
|
||
## 2026-07-06 – M2-Call-Schicht, zwei weitere Teilschritte: call_ui-Modul + VoicePrefs-Fassade (verhaltensgleich)
|
||
|
||
**Erledigt:** Nach dem auth_log-Check die zwei risikoärmsten der offenen
|
||
inneren Aufräumarbeiten der Call-Schicht (ROADMAP „Call-Schicht
|
||
entwirren", `docs/M2_MODULE_SCHNITT.md`):
|
||
|
||
- **`call_ui` als Modul (b2335da):** `lib/features/call/` →
|
||
`lib/features/call_ui/` (git mv, 100 % rename), Importe in den einzigen
|
||
zwei Verwendern (`rooms_panel.dart`, `app_shell.dart`) angepasst. Die
|
||
beiden UI-Dateien konsumierten seit dem Pilot schon ausschließlich die
|
||
Service-Interfaces – damit ist Pilot-Schritt 1 aus dem Schnitt-Doc
|
||
(call_signaling + voice_channel + call_ui) strukturell komplett.
|
||
- **VoicePrefs-Fassade (d22f9c2):** Die 4 voice_*-Prefs (Mikro/Kamera/
|
||
Lautsprecher-Gerät, Ausgabelautstärke) laufen jetzt über eine neue
|
||
`VoicePrefs`-Kapsel in `core/settings_prefs.dart` – vorher griffen
|
||
`voip_manager.dart` (2 Stellen), `livekit_call_manager.dart` und
|
||
`settings_voice.dart` direkt auf SharedPreferences zu (die im
|
||
Schnitt-Doc Punkt 6 genannten Stellen). Keys und Semantik (null/leer =
|
||
Pref löschen, Fallback-Lautstärke 1.0) exakt beibehalten; der
|
||
shared_preferences-Import ist aus settings_modal.dart, beiden Managern
|
||
und damit aus der ganzen Call-Schicht verschwunden.
|
||
- **Geprüft:** `flutter analyze` sauber (nur bekannter onUpdate-Hinweis),
|
||
29 Tests grün, `flutter build windows --debug` + App-Start: Login/Sync
|
||
laufen (auth_log 17:08:57 `loggedIn`, keine Migration, kein Fehler).
|
||
App läuft seit 17:08 wieder ENTKOPPELT als Langzeit-Beobachtung – jetzt
|
||
mit dem NEUEN Code.
|
||
- **Bewusst zurückgestellt:** „UI-State aus den Managern herauslösen" –
|
||
das wäre ein echter Logik-Umbau auf dem heiligen Call-Pfad, während
|
||
Bernds Klick-Test des Piloten (PC_TESTPLAN Abschnitt 8) noch aussteht.
|
||
Kein zweiter ungetesteter Umbau obendrauf; steht als Nächstes an, SOBALD
|
||
der Klick-Test grün ist.
|
||
|
||
**Offen/Nächster Schritt:** auth_log-Check (Einträge seit 2026-07-06
|
||
17:08) in der nächsten Session bzw. vor Session-Ende. Danach ist auf dem
|
||
PC ohne Bernds Klick-Tests nichts Risikoarmes mehr offen: M2-Schritt 2
|
||
(settings_modal weiter aufsplitten) wäre der nächste Kandidat.
|
||
|
||
**Stolperfallen:** Nach dem Umstellen einer part-Datei (settings_voice)
|
||
den dann ungenutzten Import (shared_preferences) beim OWNER
|
||
(settings_modal.dart) entfernen – analyze meldet ihn sonst als unused,
|
||
und die Fundstelle ist nicht die geänderte Datei.
|
||
|
||
---
|
||
|
||
## 2026-07-06 – Langzeit-Check auth_log: 12:43-Lauf endete früh (kein Logout im Fenster), Beobachtung 16:57 neu gestartet
|
||
|
||
**Erledigt:** Der von der Vorsession hinterlassene Pflicht-Check
|
||
(auth_log seit 12:43 prüfen) ist ausgeführt, Ergebnis im PC_TESTPLAN
|
||
(Abschnitt 1) eingetragen:
|
||
|
||
- **Befund:** Der entkoppelte 12:43-Lauf lief nur ~20 Minuten – letzter
|
||
DB-Write 13:03:21, danach Prozess weg. KEINE Absturzspur (Application-
|
||
Event-Log leer, keine WER-Reports), kein Logout-/Fehler-Eintrag im
|
||
auth_log → vermutlich wurde das Fenster manuell geschlossen (Bernds
|
||
PC). Im 20-Minuten-Fenster kein Random-Logout, aber das ist zu kurz
|
||
für „mehrere Stunden" – Punkt bleibt offen.
|
||
- **Positiv-Evidenz obendrauf:** Neustart der exe um 16:57 → auth_log
|
||
`LoginState -> loggedIn` (16:57:46), KEINE erneute Migration, kein
|
||
Fehler – die Session überlebt jetzt nachweislich den 4. Start mit
|
||
SQLCipher-verschlüsselter DB.
|
||
- **Beobachtung neu gestartet:** App läuft seit 16:57 wieder ENTKOPPELT
|
||
(Start-Process, überlebt Session-Ende). Die nächste Session (oder das
|
||
Ende dieser) muss `auth_log.txt` auf Einträge seit 2026-07-06 16:57
|
||
prüfen und im PC_TESTPLAN eintragen.
|
||
|
||
**Offen/Nächster Schritt:** Weiter mit dem obersten offenen, hier
|
||
bearbeitbaren ROADMAP-Punkt: innere M2-Aufräumarbeiten der Call-Schicht
|
||
(UI-State aus den Managern, `call_ui`-Modul, storage-Fassade für die
|
||
voice_*-Prefs). Vor Session-Ende auth_log erneut prüfen.
|
||
|
||
**Stolperfallen:** Ein entkoppelter Beobachtungs-Lauf auf Bernds PC kann
|
||
jederzeit von Bernd geschlossen werden – das ist KEIN Fehlerbefund
|
||
(keine Panik wegen „Prozess weg"): erst Event-Log/WER auf Absturz und
|
||
auth_log auf Logout prüfen, dann als „manuell beendet" einordnen. Die
|
||
Beobachtungsdauer misst man am letzten DB-Write (`pyramid.sqlite-wal`),
|
||
nicht an der Start-Zeit.
|
||
|
||
---
|
||
|
||
## 2026-07-06 – Härtetest (PC_TESTPLAN): automatisierbarer Teil erledigt, Klick-Teil vorbereitet, Langzeit-Lauf gestartet
|
||
|
||
**Erledigt:** Dritter Auftragspunkt der PC-Session (nach SQLCipher und
|
||
Call-Pilot): `docs/PC_TESTPLAN.md` so weit abgearbeitet, wie es ohne
|
||
UI-Klicks geht, Ergebnisse dort eingetragen:
|
||
|
||
- **Abschnitt 0 ✅:** `flutter test` grün – 29 Tests (24 Bestand + 5 neue
|
||
SQLCipher-Tests), mehrfach gelaufen (vor/nach jedem Umbau des Tages).
|
||
- **Abschnitt 1 (Teil-Evidenz, dokumentiert):** Bernds Bestands-Session
|
||
blieb über drei App-Starts UND die SQLCipher-Migration hinweg eingeloggt;
|
||
die App entschlüsselte live to_device-Events von @uta und forwardete
|
||
Room-Keys (E2EE-Empfangspfad belegt); `auth_log.txt` schreibt sauber
|
||
LoginState-Wechsel mit, keine Soft-Logout-/Fehler-Einträge.
|
||
- **Langzeit-Beobachtung GESTARTET:** Die App läuft seit 12:43 vom
|
||
Autopilot ENTKOPPELT (Start-Process, überlebt das Session-Ende) –
|
||
die NÄCHSTE Session muss `auth_log.txt` (App-Support-Verzeichnis
|
||
`%APPDATA%\chat.pyramid\pyramid\`) auf Random-Logout-Einträge seit
|
||
2026-07-06 12:43 prüfen und das Ergebnis im PC_TESTPLAN eintragen.
|
||
- **Klar abgegrenzt, was Bernd braucht:** Alles Klick-Interaktive
|
||
(Nachricht senden, Logout-Warnung, Re-Login → alte Nachrichten lesbar,
|
||
Abschnitte 2–8 der Splits, Voice-Channel-Join) kann der Autopilot nicht
|
||
ausführen – im PC_TESTPLAN als Grenze vermerkt. ROADMAP-Härtetest-Punkt
|
||
entsprechend NUR annotiert, nicht abgehakt.
|
||
|
||
**Offen/Nächster Schritt:** Nächste Session: auth_log-Langzeit-Check (s. o.).
|
||
Für Bernd: PC_TESTPLAN Abschnitte 1 (Rest) und 2–8 durchklicken – dabei
|
||
deckt „Voice-Channel beitreten/verlassen" (Abschnitt 8) zugleich den
|
||
Klick-Test des heutigen M2-Call-Piloten ab. Danach ggf. SQLCipher auf dem
|
||
Android-Testgerät (Testplan Punkt 4 in `docs/SQLCIPHER_MIGRATION.md`).
|
||
|
||
**Stolperfallen:** Ein per `flutter run` gestarteter Lauf stirbt mit der
|
||
Session – für Beobachtungen über das Session-Ende hinaus die gebaute exe
|
||
direkt und entkoppelt starten (`Start-Process` auf
|
||
`build\windows\x64\runner\Debug\pyramid.exe`); das auth_log persistiert
|
||
die Ereignisse für die Folgesession.
|
||
|
||
---
|
||
|
||
## 2026-07-06 – M2-Call-Pilot: Module call_signaling + voice_channel mit Fassaden umgesetzt (Entscheidung 2)
|
||
|
||
**Erledigt:** Das erste M2-Umbau-Modul nach `docs/M2_MODULE_SCHNITT.md`
|
||
(Bernds Entscheidung 2: Call-Pilot zuerst, nicht settings_modal):
|
||
|
||
- **Zwei neue Module mit schmaler öffentlicher Schnittstelle:**
|
||
`lib/features/call_signaling/` (`CallSignalingService` +
|
||
`callSignalingProvider`; Implementierung `voip_manager.dart` hierher
|
||
verschoben) und `lib/features/voice_channel/` (`VoiceChannelService` +
|
||
`voiceChannelProvider`; Implementierung `livekit_call_manager.dart`
|
||
hierher verschoben). Die zwei Call-Mechanismen bleiben wie im Doc
|
||
begründet GETRENNT (Matrix-1:1-VoIP vs. LiveKit-SFU-Kanäle).
|
||
- **Alle Konsumenten entkoppelt:** app_shell, Call-UI (`voice_channel.dart`,
|
||
`mini_call_widget.dart`), Rooms-Panel (Join/Leave, Call-Dock),
|
||
Chat-Header (Call-Start), Settings (`_VoiceSection` ist jetzt
|
||
ConsumerStatefulWidget und nutzt die Provider statt der Singletons) –
|
||
niemand importiert mehr die Implementierungsdateien, repo-weit verifiziert.
|
||
Einzige dokumentierte Ausnahme: `matrix_client.dart` als Kompositions-
|
||
Punkt, der `PyramidVoipManager(client)` konstruiert. Die alten Provider
|
||
`callStateProvider`/`voipStateProvider` in app_state.dart sind ersatzlos
|
||
durch die Modul-Provider ersetzt (reine Umbenennung + Typ = Interface).
|
||
- **Bewusste Abweichungen vom Schnitt-Entwurf** (Verhaltensgleichheit vor
|
||
Schönheit, heiliger Call-Pfad; im Doc-Status-Kopf dokumentiert):
|
||
ChangeNotifier statt Streams, Methodennamen wie im Bestand
|
||
(`startCall`/`hangUp` statt `join`/`leave`), `CallSession` und
|
||
LiveKit-`Room` bleiben in den Schnittstellen sichtbar (UI rendert direkt
|
||
Renderer/Tracks – für die WebRTC-Renderer sieht das Doc das selbst vor).
|
||
Kein einziger Handler-/Logik-Block wurde verändert, nur Klassen-Köpfe
|
||
(`extends` + `@override`), Imports, Provider-Namen und Typ-Annotationen.
|
||
- **Geprüft:** `flutter analyze` sauber (nur bekannter onUpdate-Hinweis),
|
||
alle 29 Tests grün, `flutter run -d windows`: App startet, Login/Sync/E2EE
|
||
laufen, sauberer Quit. (Commit-Hash siehe git log dieses Eintrags.)
|
||
|
||
**UNGETESTET (interaktiv/Gerät – deshalb ROADMAP-Punkt „Call-Schicht
|
||
entwirren" nur annotiert, nicht abgehakt):** echter Voice-Channel-Join per
|
||
Klick auf Windows und echter 1:1-Call zwischen zwei Geräten. Der Umbau ist
|
||
bewusst rein mechanisch (Fassade davor, Logik unangetastet), aber die
|
||
CLAUDE.md-Regel „nach Refactoring Kernflows prüfen" ist für die Call-Flows
|
||
erst nach einem Klick-/Gerätetest voll erfüllt – steht im PC_TESTPLAN
|
||
(Abschnitt 8) bzw. braucht Bernd/Uta.
|
||
|
||
**Offen/Nächster Schritt:** Härtetest nach `docs/PC_TESTPLAN.md` (so weit
|
||
ohne Klick-Interaktion möglich); die inneren M2-Aufräumarbeiten der
|
||
Call-Schicht (UI-State aus den Managern, `call_ui`-Modul, storage-Fassade
|
||
für die voice_*-Prefs) folgen als eigene Schritte.
|
||
|
||
**Stolperfallen:** (1) Bei part-Dateien (settings, rooms_panel, chat_view)
|
||
hängen die Imports an der EINEN Owner-Datei – wer eine part-Datei umstellt,
|
||
muss die Imports beim Owner ändern und findet Verwender nur per Repo-grep
|
||
auf den Provider-Namen. (2) Ein `ChangeNotifierProvider<Interface>` braucht
|
||
ein Interface, das ChangeNotifier ERWEITERT (abstract class X extends
|
||
ChangeNotifier), sonst passt der Typparameter nicht.
|
||
|
||
---
|
||
|
||
## 2026-07-06 – SQLCipher-Migration UMGESETZT + auf Windows mit echter DB praxisgetestet (M1, Bernds Prio 1)
|
||
|
||
**Erledigt:** Der M1-Punkt „Sichere Speicherung von Access-Token & Krypto-DB"
|
||
ist nach `docs/SQLCIPHER_MIGRATION.md` umgesetzt (PC-Session laut
|
||
Entscheidung 1 in `ANTWORTEN_BERND.md`):
|
||
|
||
- **Neue Storage-Fassade `lib/core/app_database.dart`** (erster Baustein im
|
||
Sinne des M2-Modularitäts-Leitsatzes): kapselt Factory-Wahl, SQLCipher-
|
||
Schlüssel (32 Zufallsbytes, hex im Keystore via flutter_secure_storage,
|
||
Eintrag `db_cipher_key`, mit Read-Back-Verifikation gegen stilles
|
||
Write-Versagen), Klartext→SQLCipher-Migration und die vorher duplizierten
|
||
PRAGMAs. `matrix_client.dart` und `background_push.dart` rufen nur noch
|
||
`AppDatabase.open()` / `AppDatabase.openForPush()`.
|
||
- **Migration exakt nach Plan:** Marker gegen Push-Isolate-Race →
|
||
WAL-Checkpoint → Byte-Backup `.premigration` (nach 7 Tagen auto-gelöscht) →
|
||
`sqlcipher_export` in NEUE Datei → Verifikation (integrity_check +
|
||
Zeilenzahlen ALLER Tabellen) → atomarer rename-Tausch. Jeder Fehlpfad lässt
|
||
die Klartext-DB unangetastet (App läuft dann unverschlüsselt weiter,
|
||
Fehler im auth_log). Absturz zwischen Löschen und Umbenennen wird beim
|
||
nächsten Start aus dem Byte-Backup repariert.
|
||
- **Wichtige Plan-Korrektur:** Die Plan-Annahme „sqlcipher_flutter_libs hat
|
||
keine Desktop-Binaries" ist seit 0.6.x überholt – das Plugin baut SQLCipher
|
||
auf Windows als `sqlite3.dll` MIT (im Build-Output verifiziert, nur dieses
|
||
eine sqlite-Plugin ist im Build). Deshalb verschlüsseln Windows/Linux
|
||
ebenfalls (kein Test-Override nötig); iOS/macOS bleiben bewusst Klartext
|
||
(Pod-Kollision mit firebase_messaging, kein Release-Ziel). Laufzeit-Wächter
|
||
`PRAGMA cipher_version` verhindert, dass je stillschweigend die falsche
|
||
Bibliothek benutzt wird; verschlüsselte DB ohne Cipher-Lib/Schlüssel ist
|
||
ein harter Fehler mit Klartext-Meldung – NIE stille Neuanlage.
|
||
- **Tests `test/app_database_test.dart`** (5 neue, gesamt 29, alle grün):
|
||
fahren die ECHTE Migration mit der SQLCipher-DLL aus dem Build-Output –
|
||
cipher_version-Wächter, Header-Erkennung, Migration mit Zeilenzahl-
|
||
Verifikation, falscher Schlüssel scheitert sofort, erzwungener Fehlschlag
|
||
lässt das Klartext-Original intakt.
|
||
- **Praxistest Windows (`flutter run -d windows`), echte Daten:** Bernds
|
||
gewachsene 14-MB-DB (inkl. 4 MB WAL) wurde beim Start migriert
|
||
(auth_log: „23 Tabellen verifiziert"), Session blieb eingeloggt, E2EE
|
||
nachweislich intakt (App entschlüsselte live to_device-Events von
|
||
@uta, forwardete Room-Keys). Zweiter Start öffnet die verschlüsselte DB
|
||
ohne erneute Migration, sauberer Quit per `q`. DB-Header jetzt zufällig
|
||
(verschlüsselt), `.premigration` = Klartext-Original. Zusätzliche manuelle
|
||
Sicherung liegt als `pyramid.sqlite*.claude-vormigration-20260706` im
|
||
App-Verzeichnis (kann nach ein paar Tagen weg).
|
||
- `sqlite3` als direkte Dependency ergänzt (war schon transitiv im Lockfile –
|
||
kein neues externes Paket), für `open.overrideFor`/`openCipherOnAndroid`.
|
||
- `flutter analyze` sauber (nur der bekannte, bewusst zurückgestellte
|
||
onUpdate-Hinweis).
|
||
|
||
**UNGETESTET (Android – deshalb ROADMAP-Punkt NICHT abgehakt):** Migration
|
||
auf echtem Android-Gerät, Push-Isolate liest verschlüsselte DB
|
||
(`databaseFactoryFfiNoIsolate` + `openCipherOnAndroid`), flutter_secure_storage
|
||
aus dem Background-Isolate (bekanntes Risiko, Code steigt dann kontrolliert
|
||
mit Platzhalter-Notification aus). Testplan Punkt 4 in
|
||
`docs/SQLCIPHER_MIGRATION.md`; erst danach Release + Uta (vorher ihr
|
||
Key-Backup verifizieren).
|
||
|
||
**Offen/Nächster Schritt:** M2-Call-Pilot (Entscheidung 2: Call-Fassade nach
|
||
`docs/M2_MODULE_SCHNITT.md`), danach Härtetest nach `docs/PC_TESTPLAN.md`.
|
||
|
||
**Stolperfallen:** (1) Plan-Annahmen über Paket-Fähigkeiten VOR der Umsetzung
|
||
gegen den Pub-Cache/Build-Output prüfen – die „keine Desktop-Binaries"-Annahme
|
||
hätte unnötig einen Test-Override-Sonderpfad erzeugt. (2) `sqfliteFfiInit()`
|
||
war auf Windows faktisch wirkungslos fürs DB-Isolate (Override wirkt nur im
|
||
aufrufenden Isolate; das Worker-Isolate lädt die exe-nachbarschaftliche
|
||
`sqlite3.dll` – und die IST hier SQLCipher). Für eigene Overrides deshalb
|
||
`createDatabaseFactoryFfi(ffiInit: ...)` benutzen, das läuft IM DB-Isolate.
|
||
(3) Beim Schlüssel-Anlegen im Keystore IMMER read-back-verifizieren, bevor
|
||
damit verschlüsselt wird – ein still verlorener Schlüssel wäre Totalverlust.
|
||
(4) `flutter_test` findet keine `sqlite3.dll` neben dem Tester-Binary – die
|
||
DLL aus `build/windows/x64/runner/*/` explizit laden; Tests überspringen sich
|
||
selbst, wenn kein Build-Output da ist (Pi-tauglich).
|
||
|
||
---
|
||
|
||
## 2026-07-06 – Fable-5-Review (aa): autopilot.sh fest auf nordmann-Konto (uncommittete Änderung geprüft, E-Mail-Leak vor Publikation gefixt, committet)
|
||
|
||
**Erledigt:** Session-Start nach Arbeitszyklus zeigte echten neuen Anlass (also
|
||
kein reiner Protokoll-Commit, Ende-Kriterium aus (z) greift nicht): eine
|
||
uncommittete Änderung an `autopilot.sh` von heute plus Backup
|
||
`autopilot.sh.bak-20260706-vor-nordmann-pin`. Inhalt: Die Zwei-Konten-Rotation
|
||
(pyramid↔haupt) ist komplett entfernt, der Autopilot läuft laut Kommentar auf
|
||
Bernds Vorgabe (2026-07-06) nur noch mit dem nordmann-Konto
|
||
(Standard-Profil `~/.claude`). Der CHANGES-Hook hat dafür KEINEN Eintrag
|
||
(Log endet 2026-07-05 19:03) – die Änderung kam also von außerhalb einer
|
||
gehookten Claude-Session (Bernd/manuell). Als Punkt (aa) im
|
||
Fable-5-Review-Abschnitt abgehakt:
|
||
|
||
- **Umbau korrekt:** Rotation restlos raus (repo-weit kein
|
||
`get_account`/`switch_account`/`JUST_SWITCHED`/`ACCOUNT_FILE`-Rest),
|
||
`apply_account` macht nur noch `unset CLAUDE_CONFIG_DIR` – exakt die Lehre
|
||
aus Review (w) (Hauptprofil liegt unter `~/.claude.json`, die Variable darf
|
||
nicht gesetzt sein). `bash -n` sauber. Limit-Pfad: 20 min warten statt
|
||
Konto-Wechsel, PAUSE-/Notify-Logik unverändert.
|
||
- **Am lebenden System belegt:** tmux-Session „pyramid" wurde heute 11:54 neu
|
||
gestartet, `autopilot.log` zeigt das neue Log-Format, und DIESE Session
|
||
läuft nachweislich als nordmann-Konto mit leerem `CLAUDE_CONFIG_DIR`.
|
||
- **Befund (gefixt VOR dem Commit):** Die neue Fassung enthielt Bernds
|
||
private E-Mail-Adresse im Klartext (Kommentar + `ACCOUNT_NAME`-Log-Zeile).
|
||
Das Gitea-Repo ist ÖFFENTLICH, und bisher enthielt keine einzige getrackte
|
||
Datei diese Adresse – ein Commit hätte sie erstmals veröffentlicht
|
||
(PII/Spam-Harvesting, gleiche Familie wie der Secrets-Leitsatz in
|
||
CLAUDE.md). Funktional ist die Adresse unnötig (stand nur in Kommentar und
|
||
gitignoriertem `autopilot.log`) → durch neutrales „nordmann" ersetzt,
|
||
Begründungs-Kommentar dagelassen, damit sie niemand „zur Klarheit" wieder
|
||
einträgt.
|
||
- Aufräumen drumherum: `*.bak-*` in `.gitignore` aufgenommen (das heutige
|
||
Backup bleibt als Rollback auf der Platte, hält aber den Arbeitsbaum-
|
||
Detektor künftiger Sessions sauber). Hinweis: `~/.claude-accounts/active`
|
||
(Inhalt „haupt") liegt verwaist außerhalb des Repos – wird von nichts mehr
|
||
gelesen, bewusst nicht angefasst. Memory (Haupt-Profil) auf den neuen
|
||
Ein-Konto-Stand aktualisiert.
|
||
|
||
**ROADMAP-Check danach (Schritt 2 des Auftrags):** Unverändert kein offener
|
||
Punkt auf dem Pi sicher bearbeitbar – SQLCipher und Härtetest brauchen
|
||
PC/Gerät (Entscheidung 1, `docs/PC_TESTPLAN.md`), der M2-Call-Pilot braucht
|
||
Gerätetest (Entscheidung 2, heiliger Call-Pfad), Secrets/History ruhen
|
||
bewusst (Entscheidung 4). Keine Arbeit erfunden – Session endet sauber nach
|
||
diesem Commit.
|
||
|
||
**Offen/Nächster Schritt:** Unverändert – nächster PC-/Gerätelauf startet mit
|
||
SQLCipher (`docs/SQLCIPHER_MIGRATION.md`), danach Call-Pilot; Härtetest nach
|
||
`docs/PC_TESTPLAN.md`.
|
||
|
||
**Stolperfallen:** (1) Der CHANGES-Hook-Detektor sieht nur Claude-Edits –
|
||
Bernds manuelle Änderungen tauchen NUR in `git status` auf; deshalb gehört
|
||
`git status` (nicht nur das Hook-Log) an jeden Session-Start. (2) Vor dem
|
||
Committen fremder/manueller Änderungen in ein ÖFFENTLICHES Repo den Diff auf
|
||
PII/Secrets abklopfen – hier wäre sonst eine private E-Mail-Adresse
|
||
mitpubliziert worden.
|
||
|
||
---
|
||
|
||
## 2026-07-05 – Fable-5-Review (z): (y)-Protokoll-Commit geprüft (kein Befund) + Ende-Kriterium geschärft
|
||
|
||
**Erledigt:** Session-Start nach Arbeitszyklus: Arbeitsbaum sauber, Gitea nicht
|
||
voraus, CHANGES-Hook-Log endet 18:41 = exakt die (y)-PROGRESS-Edits → keine
|
||
unprotokollierte Pi-Arbeit. Einziger seit (y) ungeprüfter Rest war der
|
||
(y)-Protokoll-Commit 725f03a selbst; als Punkt (z) im Fable-5-Review-Abschnitt
|
||
abgehakt (kurzer Gegenlese-Pass, keine Volltiefen-Runde):
|
||
|
||
- Diff von 725f03a ist reine Doku und deckt sich exakt mit dem (y)-Eintrag:
|
||
PROGRESS.md +45 Zeilen ((y)-Eintrag + Checklisten-Häkchen), CHANGES.md
|
||
+2 Hook-Zeilen. Kein Code, nichts Halbfertiges.
|
||
- (y)-Kernaussagen stichprobenhaft gegengeprüft, beide korrekt: 15001ae ist
|
||
wirklich PROGRESS +46/CHANGES +2 (reine Doku), und
|
||
`git log 925aafb..HEAD -- lib/ test/ pubspec.*` ist leer – seit dem letzten
|
||
grünen Testlauf (24/24) ist kein Code angefasst, kein flutter-Lauf nötig.
|
||
|
||
**Ende-Kriterium geschärft (in der Review-Checkliste dokumentiert):** Die
|
||
Kette (a)–(z) reviewte zuletzt nur noch die Protokoll-Commits der jeweiligen
|
||
Vorsession – das terminiert nie von selbst. Ab jetzt gilt: Ein reiner,
|
||
befundloser Protokoll-Commit einer Review-Session ist KEIN neuer
|
||
Review-Anlass. Neue Review-Punkte gibt es nur noch für Arbeit mit Substanz
|
||
(Code- oder inhaltliche Doku-Änderungen, z. B. neue PC-Commits).
|
||
|
||
**ROADMAP-Check danach (Schritt 2 des Auftrags):** Unverändert kein offener
|
||
Punkt auf dem Pi sicher bearbeitbar – SQLCipher und Härtetest brauchen
|
||
PC/Gerät (Entscheidung 1, `docs/PC_TESTPLAN.md`), der M2-Call-Pilot braucht
|
||
Gerätetest (Entscheidung 2, heiliger Call-Pfad), Secrets/History ruhen
|
||
bewusst (Entscheidung 4, nicht anstoßen). Keine Arbeit erfunden – Session
|
||
endet sauber nach diesem Protokoll-Commit.
|
||
|
||
**Offen/Nächster Schritt:** Unverändert – nächster PC-/Gerätelauf startet mit
|
||
SQLCipher (`docs/SQLCIPHER_MIGRATION.md`), danach Call-Pilot; Härtetest nach
|
||
`docs/PC_TESTPLAN.md`.
|
||
|
||
**Stolperfallen:** Eine Review-Kette, die Protokoll-Commits reviewt, erzeugt
|
||
mit jedem Review einen neuen Protokoll-Commit – ohne explizites
|
||
„Protokoll-only zählt nicht"-Kriterium terminiert sie nie. Das Kriterium
|
||
gehört an die Checkliste selbst, nicht nur in einen Session-Eintrag.
|
||
|
||
---
|
||
|
||
## 2026-07-05 – Fable-5-Review (y): liegengebliebenen (x)-Protokoll-Commit der Vorsession geprüft und nachgeholt
|
||
|
||
**Erledigt:** Session-Start zeigte einen unsauberen Arbeitsbaum: Die
|
||
(x)-Session wurde NACH dem Schreiben ihres PROGRESS-Eintrags (15:18 laut
|
||
CHANGES-Hook-Log), aber VOR ihrem Protokoll-Commit abgebrochen – der
|
||
(x)-Eintrag samt Review-Checklisten-Häkchen lag uncommitted da. Als Punkt (y)
|
||
im Fable-5-Review-Abschnitt abgehakt:
|
||
|
||
- Diff der Hinterlassenschaft ist reine Doku (PROGRESS.md +46 Zeilen =
|
||
(x)-Eintrag + Checklisten-Punkt, CHANGES.md +2 Hook-Zeilen) – kein Code,
|
||
nichts Halbfertiges darüber hinaus.
|
||
- Kernaussagen des (x)-Eintrags stichprobenhaft gegengeprüft, alle korrekt:
|
||
66bf54b-Diff-Umfang stimmt (4 Dateien, +77/−21, nur Doku); seit dem
|
||
letzten grünen Testlauf (925aafb) keinerlei Änderung an
|
||
`lib/`/`test/`/`pubspec.*` (git log leer); A/B-Frage wird außerhalb des
|
||
historischen PROGRESS-Logs nirgends mehr als offen dargestellt
|
||
(`ANTWORTEN_BERND.md` sagt „geschlossen", Hardening-Doc-Status
|
||
ABGESCHLOSSEN/Archiv, ROADMAP-Abschluss-Block vorhanden).
|
||
- Eintrag unverändert übernommen und als eigener Commit nachgeholt
|
||
(15001ae). Review-Stand damit (a)–(y), Kette weiterhin konvergiert.
|
||
|
||
**ROADMAP-Check danach (Schritt 2 des Auftrags):** Kein offener Punkt ist auf
|
||
dem Pi sicher bearbeitbar – SQLCipher und Härtetest brauchen PC/Gerät
|
||
(Entscheidung 1, `docs/PC_TESTPLAN.md`), der M2-Call-Pilot braucht Gerätetest
|
||
(Entscheidung 2, heiliger Call-Pfad), Secrets/History ruhen bewusst
|
||
(Entscheidung 4, nicht anstoßen). Keine Arbeit erfunden – Session endet
|
||
sauber nach diesem Protokoll-Commit.
|
||
|
||
**Offen/Nächster Schritt:** Unverändert – nächster PC-/Gerätelauf startet mit
|
||
SQLCipher (`docs/SQLCIPHER_MIGRATION.md`), danach Call-Pilot; Härtetest nach
|
||
`docs/PC_TESTPLAN.md`.
|
||
|
||
**Stolperfallen:** Ein abgebrochener „Protokoll-Commit" hinterlässt genau das
|
||
Muster „PROGRESS.md modified, sonst nichts" – das ist harmlos und einfach
|
||
nachzuholen, aber IMMER erst den Diff gegenlesen statt blind zu committen
|
||
(hier stimmte alles).
|
||
|
||
---
|
||
|
||
## 2026-07-05 – Fable-5-Review (x): (v)-Doku-Fix (66bf54b) geprüft – korrekt, KEIN Befund, Review-Stand wieder konvergiert
|
||
|
||
**Erledigt:** Review-Pass über die einzige seit (v)+(w) dazugekommene Arbeit –
|
||
den Doku-Schließungs-Commit 66bf54b (+ Hook-Commit 1ef32c0) aus derselben
|
||
Session, der den (v)-Befund fixte. Als Punkt (x) im Fable-5-Review-Abschnitt
|
||
abgehakt. Wie bei (u) ein kurzer Diff-Gegenlese-Pass, keine Volltiefen-Runde:
|
||
|
||
- Diff von 66bf54b ist exakt der protokollierte Fix: ROADMAP-Dashboard-Punkt
|
||
abgehakt mit Abschluss-Block (Heimnetz-Gate = Endzustand, Restrisiken bewusst
|
||
akzeptiert, „Nicht weiter daran arbeiten"), Nr.-4-Vermerk am Secrets-Punkt
|
||
(Rotation/History ruhen, nicht anstoßen), `docs/DASHBOARD_AUTH_HARDENING.md`
|
||
Status-Kopf auf ABGESCHLOSSEN, Weg A/B als Archiv markiert, alle „bis zur
|
||
A/B-Entscheidung"-Formulierungen bereinigt. Alles deckungsgleich mit
|
||
`ANTWORTEN_BERND.md` Nr. 3 und 4.
|
||
- Vollständigkeits-grep über alle .md-Dateien: Außerhalb der HISTORISCHEN
|
||
PROGRESS-Einträge (Log wird nicht umgeschrieben – korrekt so) stellt keine
|
||
Datei die A/B-Frage mehr als offen dar. Das „(empfohlen)" an Option A im
|
||
Hardening-Doc steht unter dem Archiv-Vermerk des Status-Kopfs – ausreichend.
|
||
- Kein unprotokollierter Rest: CHANGES-Hook-Log endet 14:54 (= Commit-Zeit
|
||
von 1ef32c0), Arbeitsbaum sauber. Seit dem letzten grünen Testlauf
|
||
(24/24, Eintrag (i)/925aafb) wurde nichts in `lib/`/`test/`/`pubspec.yaml`
|
||
angefasst (per `git log` geprüft) – kein neuer flutter-Lauf nötig, der
|
||
Commit ist reine Doku.
|
||
|
||
**Konvergenz-Kriterium aus (u) erfüllt:** Der letzte Diff fixt nur den
|
||
Vorbefund, das Muster-grep ist leer → auch diese Kette ist geschlossen.
|
||
ALLE als erledigt markierten ROADMAP-/PROGRESS-Punkte sind reviewt (a)–(x).
|
||
|
||
**Offen/Nächster Schritt:** Unverändert – alle offenen ROADMAP-Punkte brauchen
|
||
PC/Gerät (SQLCipher zuerst laut Entscheidung 1, dann Call-Pilot; Härtetest
|
||
nach `docs/PC_TESTPLAN.md`) oder ruhen bewusst (Secrets, Entscheidung 4).
|
||
Auf dem Pi ohne neuen Anlass nichts sicher Bearbeitbares – Session endet
|
||
sauber nach diesem Protokoll-Commit.
|
||
|
||
**Stolperfallen:** Keine – reiner Gegenlese-Pass ohne Befund.
|
||
|
||
---
|
||
|
||
## 2026-07-05 – Dashboard-ROADMAP-Punkt GESCHLOSSEN (Bernds Entscheidung 3 nachgezogen)
|
||
|
||
**Erledigt:** Folge aus Review-Befund (v): Bernds Entscheidungen 3 und 4 aus
|
||
`ANTWORTEN_BERND.md` sind jetzt überall nachgezogen, damit keine künftige
|
||
Session die geschlossenen Fragen wieder aufmacht:
|
||
|
||
- **ROADMAP M0 „Dashboard-Admin-Endpoints": abgehakt.** Das Heimnetz-Gate ist
|
||
laut Bernd der Endzustand (kein Fernzugriff, unterwegs WireGuard, A/B-Frage
|
||
geschlossen). Restrisiken (öffentliche Nutzerlisten-Ansicht, theoretisches
|
||
LAN-CSRF) bewusst akzeptiert – im ROADMAP-Punkt und in
|
||
`docs/DASHBOARD_AUTH_HARDENING.md` vermerkt.
|
||
- **`docs/DASHBOARD_AUTH_HARDENING.md`:** Status-Kopf auf „ABGESCHLOSSEN"
|
||
umgestellt, „Interims-Gate" heißt jetzt Endzustand, Weg A/B als Archiv
|
||
markiert, „bis zur A/B-Entscheidung"-Formulierungen bereinigt.
|
||
- **ROADMAP M0 Secrets-Punkt:** Vermerk zu Entscheidung 4 ergänzt – Rotation
|
||
und History-Rewrite ruhen bewusst (Bernds manuelle Sache, nicht vom
|
||
Autopilot anstoßen oder nachfragen); der Punkt bleibt nur als Merkposten
|
||
offen. KEINE Code-/Serveränderung in diesem Schritt, nur Doku –
|
||
`server.py` und das Gate sind unangetastet.
|
||
|
||
**Offen/Nächster Schritt:** In M0 sind damit nur noch zwei Punkte offen, beide
|
||
nicht Pi-bearbeitbar: SQLCipher (PC-/Gerätetest, laut Entscheidung 1 als
|
||
Erstes am PC) und der Härtetest (`docs/PC_TESTPLAN.md`). Danach M2 mit
|
||
Call-Pilot (Entscheidung 2, Gerätetest nötig). Auf dem Pi bleibt ohne neuen
|
||
Anlass nichts sicher Bearbeitbares – Session darf sauber enden.
|
||
|
||
**Stolperfallen:** Keine – reine Doku-Angleichung. Lehre aus dem
|
||
(v)-Befund: Wenn Entscheidungen in einer eigenen Datei landen
|
||
(`ANTWORTEN_BERND.md`), müssen die referenzierten offenen Punkte in
|
||
ROADMAP/Docs im SELBEN Zug geschlossen/annotiert werden, sonst stellt die
|
||
nächste Session die beantwortete Frage erneut.
|
||
|
||
---
|
||
|
||
## 2026-07-05 – Fable-5-Review (v)+(w): die zwei PC-Commits geprüft – Fix korrekt, 1 Doku-Lücke gefunden
|
||
|
||
**Erledigt:** Review-Pass über die seit (u) dazugekommene Arbeit – die zwei am
|
||
PC entstandenen Commits als Punkte (v) und (w) im Fable-5-Review-Abschnitt
|
||
abgehakt:
|
||
|
||
- **(v) 825481e (`ANTWORTEN_BERND.md` + CLAUDE.md-Verweis):** inhaltlich
|
||
konsistent – alle referenzierten Pläne/Dateien existieren
|
||
(`docs/SQLCIPHER_MIGRATION.md`, `docs/M2_MODULE_SCHNITT.md`,
|
||
`docs/DASHBOARD_AUTH_HARDENING.md`, `docs/PC_TESTPLAN.md`;
|
||
`voip_manager.dart`/`livekit_call_manager.dart` liegen in `lib/core/`), die
|
||
vier Entscheidungen decken exakt die in PROGRESS.md gesammelten Fragen ab.
|
||
**Befund (Doku-Lücke):** Entscheidung 3 (Dashboard bleibt Heimnetz-only,
|
||
A/B-Frage GESCHLOSSEN) war noch nicht in ROADMAP.md und
|
||
`docs/DASHBOARD_AUTH_HARDENING.md` nachgezogen – beide präsentierten die
|
||
Frage weiterhin als offen, künftige Sessions hätten Bernd erneut gefragt.
|
||
Direkt im nächsten Schritt dieser Session behoben (eigener Commit,
|
||
Eintrag oben).
|
||
- **(w) 4477ed9 (`autopilot.sh`, CLAUDE_CONFIG_DIR-Falle):** Fix ist korrekt
|
||
und am lebenden System belegt: Mit gesetztem `CLAUDE_CONFIG_DIR` sucht die
|
||
Claude-CLI ihre Config unter `$CLAUDE_CONFIG_DIR/.claude.json` – das
|
||
Hauptkonto-Profil liegt aber unter `~/.claude.json`, das alte
|
||
`export CLAUDE_CONFIG_DIR="$HOME/.claude"` zeigte also auf ein leeres
|
||
Zweitprofil (`~/.claude/.claude.json`). `unset` ist richtig und wirkt auch
|
||
nach einer vorherigen pyramid-Iteration derselben Shell (apply_account
|
||
läuft vor jeder Session). Lebender Beweis: DIESE Session lief als Konto
|
||
„haupt" (`~/.claude-accounts/active` = haupt, `CLAUDE_CONFIG_DIR` leer,
|
||
`~/.claude.json` vorhanden) und funktionierte. Kein weiterer
|
||
`CLAUDE_CONFIG_DIR`-Verweis im Repo. Kein Befund.
|
||
|
||
**Offen/Nächster Schritt:** Befund aus (v) nachziehen (ROADMAP-Dashboard-Punkt
|
||
schließen, Doku-Status aktualisieren, Vermerk zu Entscheidung 4 am
|
||
Secrets-Punkt) – passiert direkt als nächster Schritt dieser Session.
|
||
|
||
**Stolperfallen:** Der CHANGES.md-Hook (in `.claude/settings.json`, schreibt
|
||
relativ nach `CHANGES.md`) protokolliert nur Arbeit auf der jeweiligen
|
||
Maschine – die PC-Commits vom 2026-07-05 haben hier auf dem Pi KEINE
|
||
Hook-Einträge hinterlassen. Der „Hook-Log als Detektor für unprotokollierte
|
||
Arbeit"-Trick aus Review (p) funktioniert also nur pro Maschine; für
|
||
PC-Arbeit bleibt `git log` die einzige Quelle.
|
||
|
||
---
|
||
|
||
## 2026-07-05 – Bernds Richtungsfragen beantwortet (am PC)
|
||
|
||
**Erledigt:** Die über mehrere Sessions gesammelten „Fragen an Bernd" sind beantwortet
|
||
und in `ANTWORTEN_BERND.md` festgehalten (maßgeblich, in CLAUDE.md verlinkt):
|
||
1. SQLCipher → **vor M2** priorisieren (M1-Punkt); Migration braucht PC-/Gerätetest.
|
||
2. M2 erstes Modul → **Call-Pilot zuerst** (nicht `settings_modal.dart`); braucht Gerätetest.
|
||
3. Dashboard-Fernzugriff → **Heimnetz-only lassen**; A/B-Frage geschlossen.
|
||
4. Git-History / Token-Rotation → **vorerst nichts tun** (Bernds manuelle Sache).
|
||
|
||
**Offen/Nächster Schritt:** Die entschiedenen Punkte bleiben PC-/gerätegebunden
|
||
(SQLCipher-Migration, Call-Pilot-Umbau). Auf dem Pi ohne GUI nur sicher Vorbereitbares
|
||
angehen und klar als UNGETESTET markieren; Verhaltensrelevantes erst nach
|
||
`docs/PC_TESTPLAN.md` als erledigt abhaken. Reihenfolge: erst SQLCipher, dann Call-Pilot.
|
||
|
||
**Stolperfallen:** Beide priorisierten Punkte sind trotz Entscheidung NICHT auf dem Pi
|
||
abschließbar (kein GUI-/Gerätetest) – nicht als „jetzt endlich machbar" missverstehen.
|
||
|
||
---
|
||
|
||
|
||
## 2026-07-04 – Fable-5-Review (u): (t)-Fix geprüft – korrekt, KEIN Befund, server.py-Review-Kette abgeschlossen
|
||
|
||
**Erledigt:** Review-Pass über die letzte noch ungeprüfte Arbeit – den
|
||
(t)-Restbefund-Fix (Commit d3e75c3) gegengelesen und als Punkt (u) im
|
||
Fable-5-Review-Abschnitt abgehakt. Wie im Arbeitsstand-Memory vorgesehen war
|
||
das ein kurzer Diff-Gegenlese-Pass, KEINE weitere Volltiefen-Runde:
|
||
|
||
- Diff gegen `server.py.bak-20260704-review-t` ist exakt der protokollierte
|
||
Mini-Fix (1 Fundstelle: `registration-status` antwortet generisch, Detail
|
||
per stderr ins Journal); `sys` ist importiert, `py_compile` sauber.
|
||
- Dienst läuft mit dem aktuellen Code (Start 19:19:40 NACH Datei-mtime
|
||
19:19:22), kein unprotokollierter Rest im CHANGES-Hook-Log (letzte Einträge
|
||
19:19–19:22 gehören zur (t)-Session, danach Commit 4c6efa2).
|
||
- Die 4 verbliebenen `str(e)`-Antworten liegen alle im `with ADMIN_LOCK:`-Block
|
||
HINTER dem Heimnetz-Gate (run-stats, create-invite, toggle-registration,
|
||
ban/unban) – absichtlich, LAN-only, für Bernd nützlich. Kein Handlungsbedarf.
|
||
- Kurz-Check des laufenden Dienstes: stats.html 200, registration-status 200
|
||
(`{"enabled": false}`), unbekannte Route 404.
|
||
|
||
**Damit ist die server.py-Härtungskette (o)–(u) KONVERGIERT** – ohne neuen
|
||
Anlass (neuer Code, neue Route) gibt es dort nichts mehr zu prüfen. ALLE als
|
||
erledigt markierten ROADMAP-/PROGRESS-Punkte sind reviewt.
|
||
|
||
**Offen/Nächster Schritt:** Unverändert – alle offenen ROADMAP-Punkte brauchen
|
||
PC/Gerät (SQLCipher, LiveKit-Client-Umstellung, Härtetest, M2-Call/State) oder
|
||
Bernd (Secret-Rotation, Dashboard-Fernzugriff A/B, Call-Pilot-Go). Auf dem Pi
|
||
ist nichts sicher Bearbeitbares mehr offen (Memory: nicht erneut suchen,
|
||
UNGETESTET-Stapel bewusst gedeckelt).
|
||
|
||
**Stolperfallen:** Keine – der Befund aus (t) war korrekt gefixt. Eine
|
||
Review-Kette braucht ein dokumentiertes Ende-Kriterium („konvergiert wenn der
|
||
letzte Diff nur noch den Vorbefund fixt und das Muster-grep leer ist"), sonst
|
||
reviewt jede Session den Review der Vorsession bis in alle Ewigkeit.
|
||
|
||
---
|
||
|
||
## 2026-07-04 – Fable-5-Review (t): (s)-Fixes geprüft – korrekt, 1 kleiner Restbefund gefunden und gefixt
|
||
|
||
**Erledigt:** Review-Pass über die jüngste erledigte Arbeit – die zwei
|
||
(s)-Restbefund-Fixes (Commit 13b333c) kritisch gegengelesen und als Punkt (t)
|
||
im Fable-5-Review-Abschnitt abgehakt. Der (s)-Diff selbst ist korrekt (gegen
|
||
Backup `server.py.bak-20260704-review-s` verifiziert: ban/unban-Body-Lesen
|
||
sitzt vollständig VOR dem Lock inkl. Validierung, die generischen 500er auf
|
||
beiden öffentlichen POST-Routen stimmen; Dienst lief mit dem aktuellen Code,
|
||
kein unprotokollierter Rest im CHANGES-Hook-Log). Beim Ganzdatei-Gegenlesen
|
||
EIN Restbefund derselben Familie wie (s)-Befund 2:
|
||
|
||
1. **`GET /api/registration-status` leakte Exception-Texte:** Die Route ist
|
||
öffentlich erreichbar (in `do_GET` gibt es kein Heimnetz-Gate) und
|
||
antwortete bei Fehlern mit `str(e)` – ein Lesefehler an `conduit.toml`
|
||
hätte den internen Dateipfad an Unauthentifizierte verraten. (s) hatte nur
|
||
die zwei öffentlichen POST-Routen gefixt und die öffentliche GET-Route
|
||
übersehen. Fix: gleiches Muster – generische 500-Antwort („Interner
|
||
Fehler"), Detail per stderr ins Journal.
|
||
|
||
**Verifiziert (headless, nach Dienst-Neustart per kill/`Restart=always`):**
|
||
`py_compile` sauber, Diff gegen Backup minimal wie geplant
|
||
(`server.py.bak-20260704-review-t`). 8 Routen-Checks wie in (s) alle
|
||
unverändert (stats 200, registration-status 200, ban ohne Nutzer 400,
|
||
8-KB-Body 413, livekit-token ungültig 401, Diagnose falscher Token 403,
|
||
gespoofter Cf-Header 403, unbekannte Route 404). **500-Beweis (neu):**
|
||
`conduit.toml` für ~1 s per `chmod 000` unlesbar gemacht →
|
||
`{"error": "Interner Fehler"}` nach außen, `PermissionError` im Journal
|
||
(vorher wäre `[Errno 13] … '/home/steggi/matrix/conduit.toml'` – also der
|
||
Pfad – nach draußen gegangen); Rechte sofort wiederhergestellt (664), Route
|
||
danach wieder 200. Test-Deny-Zeile aus `security_alerts.log` wieder entfernt.
|
||
|
||
**Offen/Nächster Schritt:** Unverändert – alle offenen ROADMAP-Punkte brauchen
|
||
PC/Gerät (SQLCipher, LiveKit-Client-Umstellung, Härtetest) oder Bernd
|
||
(Secret-Rotation, Dashboard-Fernzugriff A/B, Call-Pilot-Go).
|
||
|
||
**Stolperfallen:** Bei einem Info-Leak-Fix nicht nur die Methode fixen, in
|
||
der der Befund auftauchte: (s) hatte `do_POST` gefixt, aber `do_GET` derselben
|
||
Datei hat auch öffentliche Routen mit demselben `str(e)`-Muster. Lehre: Nach
|
||
einem Musterfund (`str(e)` an Unauthentifizierte) einmal `grep str(e)` über
|
||
die GANZE Datei und jede Fundstelle als „öffentlich oder LAN-only?"
|
||
einordnen, statt nur die Nachbarroute zu fixen.
|
||
|
||
---
|
||
|
||
## 2026-07-04 – Fable-5-Review (s): (r)-Fixes geprüft – korrekt, 2 kleinere Restbefunde gefunden und gefixt
|
||
|
||
**Erledigt:** Review-Pass über die jüngste erledigte Arbeit – die drei
|
||
(r)-Restbefund-Fixes (Commit 5ada0e5) kritisch gegengelesen und als Punkt (s)
|
||
im Fable-5-Review-Abschnitt abgehakt. Der (r)-Diff selbst ist korrekt (gegen
|
||
Backup `server.py.bak-20260704-review-r` verifiziert, Dienst lief mit dem
|
||
aktuellen Code). Beim Gegenlesen der GANZEN Datei (Lehre aus (o)) zwei
|
||
kleinere Restbefunde in `server.py` gefunden und gefixt:
|
||
|
||
1. **ban/unban las den Body INNERHALB des `ADMIN_LOCK`:** Das 4-KB-Limit aus
|
||
(r) deckelt nur die Größe – ein LAN-Client, der `Content-Length` ankündigt
|
||
und nichts schickt, hielt den Lock trotzdem bis zum 30-s-Socket-Timeout
|
||
fest (der (r)-Kommentar behauptete fälschlich, der Deckel verhindere das).
|
||
Seit (r) wartet auch der öffentliche GET-Leser von `registration-status`
|
||
auf diesen Lock → der hing mit. Fix: Body-Lesen/Validieren VOR den Lock
|
||
gezogen (Handler-Logik identisch, nur verschoben).
|
||
2. **Öffentliche Routen leakten Exception-Texte:** `livekit-token` und
|
||
`e2ee-diagnostics` antworteten Unauthentifizierten bei Fehlern mit
|
||
`str(e)` – das kann interne Pfade (`livekit.yaml`-Pfad bei
|
||
FileNotFoundError) oder interne Adressen preisgeben. Fix: generische
|
||
500-Antwort („Interner Fehler"), Details per stderr ins Journal. Die
|
||
Admin-Routen behalten `str(e)` (LAN-only, für Bernd nützlich).
|
||
|
||
**Verifiziert (headless, nach Dienst-Neustart per kill/`Restart=always`):**
|
||
`py_compile` sauber, Diff gegen Backup minimal wie geplant
|
||
(`server.py.bak-20260704-review-s`). 8 Routen-Checks wie in (r) alle
|
||
unverändert (stats 200, registration-status 200, ban ohne Nutzer 400,
|
||
8-KB-Body 413, `Content-Length: -5` 413, livekit-token ungültig 401,
|
||
Diagnose falscher Token 403, gespoofter Cf-Header 403). **Lock-Beweis (neu):
|
||
ban-Request mit angekündigtem, nie gesendetem Body + paralleler
|
||
`registration-status`-GET → Antwort in 15 ms statt Lock-Blockade** (vorher
|
||
hätte der GET bis zu 30 s gewartet). **500-Beweis (neu):** ungültiges JSON an
|
||
livekit-token → `{"error": "Interner Fehler"}`, Detail
|
||
(`JSONDecodeError(...)`) steht im Journal. Test-Deny-Zeile aus
|
||
`security_alerts.log` wieder entfernt.
|
||
|
||
**Offen/Nächster Schritt:** Unverändert – alle offenen ROADMAP-Punkte brauchen
|
||
PC/Gerät (SQLCipher, LiveKit-Client-Umstellung, Härtetest) oder Bernd
|
||
(Secret-Rotation, Dashboard-Fernzugriff A/B, Call-Pilot-Go).
|
||
|
||
**Stolperfallen:** (1) Ein Größen-Limit ist KEIN Zeit-Limit: `read(n)` mit
|
||
kleinem n blockiert trotzdem bis zum Socket-Timeout, wenn der Client nichts
|
||
schickt – blockierende I/O gehört grundsätzlich VOR einen Lock, nicht
|
||
hinein. (2) `str(e)` in Fehlerantworten öffentlicher Endpoints ist ein
|
||
Info-Leak (Pfade, interne Hosts) – Details gehören ins Journal, nach draußen
|
||
nur Generisches.
|
||
|
||
---
|
||
|
||
## 2026-07-04 – Fable-5-Review (r): ADMIN_LOCK-Umbau (q) geprüft – korrekt, aber 3 kleinere Restbefunde gefunden und gefixt
|
||
|
||
**Erledigt:** Review-Pass über die jüngste erledigte Arbeit – den ADMIN_LOCK-Umbau
|
||
aus (q) (Commit c8ff850) kritisch gegengelesen und als Punkt (r) im
|
||
Fable-5-Review-Abschnitt abgehakt. Der Umbau selbst ist korrekt (Locking-Struktur,
|
||
Routen-Reihenfolge, Handler-Logik unverändert). Drei kleinere Restbefunde in
|
||
`server.py` gefunden und gefixt:
|
||
|
||
1. **GET-Leser-Race (gleiche Familie wie (q), dort übersehen):** (q) hatte nur
|
||
POST-gegen-POST betrachtet – `GET /api/registration-status` liest
|
||
`conduit.toml` aber OHNE Lock, während `toggle-registration`/`create-invite`
|
||
die Datei per Truncate+Write in-place neu schreiben → seit multithreaded (p)
|
||
kann der Leser eine halb geschriebene/leere Datei sehen (Dashboard zeigt
|
||
transient falschen Registrierungs-Status). Fix: Lesen unter `ADMIN_LOCK`.
|
||
**Wichtige Erkenntnis dabei:** Der naheliegendere Fix (atomares `os.replace`
|
||
statt In-Place-Write) wäre FALSCH – `conduit.toml` ist als **einzelne Datei**
|
||
in den Container gemountet (`./conduit.toml:/etc/conduit.toml:ro`), ein
|
||
Replace tauscht den Inode und der Container sähe für immer die alte Datei.
|
||
Im Code als Kommentar festgehalten, damit das nie jemand „verbessert".
|
||
2. **Negative `Content-Length`:** `livekit-token`, `e2ee-diagnostics` und
|
||
`ban`/`unban` prüften nur `length > MAX` – ein negativer Wert wäre
|
||
durchgerutscht und `rfile.read(-n)` liest bis EOF (Thread hängt bis zum
|
||
30-s-Timeout). Fix: `length < 0` wird wie „zu groß" abgelehnt (413).
|
||
3. **`ban`/`unban` ohne Body-Limit + `random` für Einladungs-Token:**
|
||
ban/unban lasen beliebig große Bodies (und halten dabei seit (q) den
|
||
ADMIN_LOCK) → 4-KB-Deckel wie bei livekit-token. Einladungs-Token
|
||
(`create-invite`) kamen aus dem vorhersagbaren `random`-PRNG, obwohl sie
|
||
die Registrierung am Homeserver freischalten → `secrets.choice`.
|
||
|
||
**Verifiziert (headless, nach Dienst-Neustart per kill/`Restart=always`):**
|
||
`py_compile` sauber, Diff gegen Backup minimal wie geplant
|
||
(`server.py.bak-20260704-review-r`). 8 Routen-Checks: stats.html 200,
|
||
registration-status 200 (liest jetzt unter Lock), ban ohne Nutzer localhost →
|
||
400 „Kein Nutzer angegeben", **ban mit 8-KB-Body → 413 (neu)**, **livekit-token
|
||
mit `Content-Length: -5` → sofort 413 statt Hänger (neu)**, livekit-token
|
||
ungültiger Matrix-Token → 401, e2ee-diagnostics falscher Token → 403,
|
||
Admin-Route öffentlich über die Dashboard-Domain → 403. Test-Deny-Zeile aus
|
||
`security_alerts.log` wieder entfernt.
|
||
|
||
**Offen/Nächster Schritt:** Unverändert – alle offenen ROADMAP-Punkte brauchen
|
||
PC/Gerät (SQLCipher, LiveKit-Client-Umstellung, Härtetest) oder Bernd
|
||
(Secret-Rotation, Dashboard-Fernzugriff A/B, Call-Pilot-Go).
|
||
|
||
**Stolperfallen:** (1) Bei Nebenläufigkeits-Reviews nicht nur Schreiber gegen
|
||
Schreiber prüfen, sondern auch die LESER derselben Ressource – `do_GET` war in
|
||
(q) komplett außen vor. (2) „Atomares Schreiben per `os.replace`" ist bei
|
||
Docker-**Datei**-Bind-Mounts eine Falle: der Mount hängt am Inode, nach einem
|
||
Replace sieht der Container die alte Datei weiter. In-Place-Write ist dort
|
||
absichtlich richtig. (3) `Content-Length` kann negativ sein – wer nur nach oben
|
||
deckelt, lässt `read()` bis EOF blockieren.
|
||
|
||
---
|
||
|
||
## 2026-07-04 – Fable-5-Review (q): Threading-Härtung (p) tiefer geprüft – echte Nebenläufigkeits-Race in den Admin-Routen gefunden und mit ADMIN_LOCK behoben
|
||
|
||
**Erledigt:** Review-Pass über die jüngste erledigte Arbeit – die Threading-Umstellung
|
||
aus (p) (Commit 343545b, `ThreadingHTTPServer`) noch einmal kritisch gegengelesen und
|
||
als Punkt (q) im Fable-5-Review-Abschnitt abgehakt. Befund: Die Umstellung auf
|
||
multithreaded hat einen **echten Race erzeugt, den es vorher (single-threaded, alles
|
||
serialisiert) nicht geben konnte** – (p) hat nur den öffentlich beschreibbaren
|
||
Diagnose-Pfad per `DIAG_LOCK` abgesichert, die **Admin-Routen aber übersehen**:
|
||
- `toggle-registration` und `create-invite` machen ein Read-Modify-Write auf
|
||
`conduit.toml` **plus** `docker compose restart conduit` – zwei gleichzeitige Klicks
|
||
= verlorenes Update (lost update) + doppelter Container-Neustart.
|
||
- `send_admin()` schläft 4 s und liest danach die **letzten** Nachrichten aus dem
|
||
Admin-Raum zurück, um „die" Antwort zuzuordnen – zwei gleichzeitige Admin-Befehle
|
||
können sich vertauschte Antworten einfangen.
|
||
|
||
**Fix (`server.py`):** Neues `ADMIN_LOCK` (threading.Lock), das **nur** die
|
||
Admin-Mutationen serialisiert. Dafür `do_POST` minimal umgebaut: die beiden
|
||
öffentlichen Routen (`/api/livekit-token`, `/api/e2ee-diagnostics`) werden jetzt
|
||
**zuerst** behandelt und returnen, danach folgt das Heimnetz-Gate und der komplette
|
||
Admin-Dispatch in einem `with ADMIN_LOCK:`. Damit bleiben die öffentlichen Pfade
|
||
**nebenläufig** – der Slow-Loris-Schutz aus (p) ist unangetastet. Handler-Logik
|
||
wortwörtlich unverändert (nur verschoben/eingerückt, per `diff` gegen Backup
|
||
verifiziert). `python3 -m py_compile` sauber; Backup `server.py.bak-20260704-adminlock`.
|
||
|
||
**Verifiziert (headless, nach Dienst-Neustart per kill/`Restart=always`):**
|
||
8 Routen-Checks identisch wie vorher (stats 200; ban localhost → 400 „Kein Nutzer",
|
||
ban „aus dem Internet"/gespoofter Cf-Header → 403; livekit-token 401/400/413;
|
||
e2ee-diagnostics falscher Token 403; unbekannte Route 404). **Race-Beweis:** ein
|
||
`run-stats` allein 1388 ms, **zwei parallel 2633 ms** (≈ 2× → serialisiert; ohne
|
||
Lock wären es ~1400 ms). **Non-Blocking-Beweis:** ein öffentliches `livekit-token`
|
||
**während** ein Admin-`run-stats` den Lock hält → in **11 ms** beantwortet (nicht
|
||
blockiert). Test-Deny-Zeile aus `security_alerts.log` wieder entfernt.
|
||
|
||
**Offen/Nächster Schritt:** Unverändert – alle offenen ROADMAP-Punkte brauchen
|
||
PC/Gerät (SQLCipher, LiveKit-Client-Umstellung, Härtetest) oder Bernd
|
||
(Secret-Rotation, Dashboard-Fernzugriff A/B, Call-Pilot-Go).
|
||
|
||
**Stolperfallen:** Eine Threading-Umstellung ist nie „nur" ein Server-Konstruktor-
|
||
Tausch – sie macht **jeden** geteilten Zustand (Dateien, Read-Modify-Write,
|
||
Readback-nach-sleep) plötzlich race-fähig. (p) hatte richtig den einen öffentlich
|
||
beschreibbaren Pfad (`DIAG_LOCK`) gesehen, aber die Admin-Routen mit `conduit.toml`
|
||
und dem Admin-Raum-Readback sind derselbe Fall. Lehre: Nach „Server ist jetzt
|
||
multithreaded" ALLE schreibenden/zustandsändernden Handler durchgehen, nicht nur
|
||
den einen, der den Anlass gab.
|
||
|
||
---
|
||
|
||
## 2026-07-04 – Fable-5-Review (p): Interims-Gate geprüft (korrekt) + halbfertige Threading-Härtung einer abgebrochenen Session vollendet
|
||
|
||
**Erledigt:** Review-Pass über die jüngste erledigte Arbeit (Interims-Gate,
|
||
Commits e97a748/455742d) als Punkt (p) im Fable-5-Review-Abschnitt abgehakt.
|
||
Das Gate selbst ist korrekt (Socket-IP als belastbare Prüfung, Cf-Header nur
|
||
Zweitschranke, Deny-Log gedeckelt, kein Angreifer-Text im Log). Dabei über das
|
||
CHANGES.md-Hook-Log entdeckt: Eine Session hat um 09:43 (NACH dem letzten
|
||
Commit) **drei unprotokollierte server.py-Edits** gemacht und wurde mitten in
|
||
einer Threading-Härtung abgebrochen – `threading`-Import + `DIAG_LOCK` waren da,
|
||
aber der Lock wurde **nirgends benutzt**, und die eigentliche Umstellung auf
|
||
`ThreadingHTTPServer` fehlte (der Kommentar behauptete sie schon). Obendrein
|
||
lief der Dienst noch mit dem Code von 09:14 – nichts davon war je geladen.
|
||
Die angefangene Härtung ist fachlich richtig (der single-threaded `HTTPServer`
|
||
ließ EINEN langsamen Client den ganzen Server blockieren – inkl. des öffentlich
|
||
erreichbaren `/api/livekit-token`), darum vollendet statt zurückgebaut:
|
||
`ThreadingHTTPServer` in `__main__`, Diagnose-Log-Kappen+Anhängen in den
|
||
`DIAG_LOCK` gewickelt (einziger öffentlich beschreibbarer Pfad), `timeout = 30`
|
||
der Vorsession belassen. Dienst-Neustart (kill/`Restart=always`), headless
|
||
verifiziert (11 Prüfungen): stats 200; Admin-Route localhost UND 192.168.178.71
|
||
→ 400 „Kein Nutzer angegeben"; öffentlich UND gespoofter Cf-Header → 403;
|
||
livekit-token ungültig → 401; Diagnose falscher Token 403, gültig 200 + im Log,
|
||
300 KB → 413; **Slow-Loris-Test: parallele Anfrage in 10 ms beantwortet,
|
||
während ein Client hing** (vorher unmöglich), hängender Body-Read nach exakt
|
||
~30 s serverseitig beendet, Dienst danach gesund. Test-Einträge aus beiden
|
||
Logs wieder entfernt.
|
||
|
||
**Offen/Nächster Schritt:** Unverändert – alle offenen ROADMAP-Punkte brauchen
|
||
PC/Gerät (SQLCipher, LiveKit-Client-Umstellung, Härtetest) oder Bernd
|
||
(Secret-Rotation, Dashboard-Fernzugriff A/B, Call-Pilot-Go).
|
||
|
||
**Stolperfallen:** (1) Das CHANGES.md-Hook-Log ist der zuverlässigste Detektor
|
||
für unprotokollierte Arbeit abgebrochener Sessions – Zeitstempel der letzten
|
||
Einträge mit dem letzten Commit vergleichen. (2) „Datei geändert" heißt NICHT
|
||
„läuft": Dienststart-Zeit (`systemctl status`) gegen Datei-mtime prüfen – hier
|
||
lagen 30 Minuten Code unausgeführt auf der Platte. (3) Ein definierter, aber
|
||
nie benutzter Lock ist ein Halbfertig-Marker, kein Schutz.
|
||
|
||
---
|
||
|
||
## 2026-07-04 – Dashboard-Befund (o): akutes Loch mit aussperr-sicherem Interims-Gate geschlossen
|
||
|
||
**Erledigt:** Der KRITISCHE Befund (o) (Dashboard-Admin-Endpoints ohne Auth,
|
||
öffentlich erreichbar) ist entschärft, OHNE Bernds A/B-Entscheidung
|
||
vorwegzunehmen und OHNE ihn auszusperren. Erkenntnis, die das möglich machte:
|
||
Öffentlicher Verkehr erreicht `server.py` (Port 8080) ausschließlich über den
|
||
Cloudflare-Tunnel-Container (Absender-IP 172.x + `Cf-*`-Header) – Bernds
|
||
Heimnetz-Zugriffe kommen dagegen direkt mit `192.168.*`-Socket-IP an. Daher
|
||
„Weg C" als Interims-Gate in `server.py`: Die 5 Admin-Routen (`/api/ban`,
|
||
`/api/unban`, `/api/toggle-registration`, `/api/create-invite`, `/api/run-stats`)
|
||
akzeptieren nur noch Anfragen mit Heimnetz-/localhost-Socket-IP (nicht spoofbar)
|
||
und ohne Cloudflare-Header; sonst 403 mit deutscher Erklärung inkl.
|
||
Heimnetz-URL – das Dashboard zeigt `d.error` per `alert()`, Bernd sieht also
|
||
genau, was zu tun ist. Abgelehnte Versuche landen größenbegrenzt (5-MB-Deckel,
|
||
Lehre aus Befund (l)) in `security_alerts.log` (nur Fixpfad + Socket-IP, kein
|
||
attacker-kontrollierter Text → kein HTML-Injection-Risiko in der
|
||
Dashboard-Anzeige). Dienst-Neustart per kill/`Restart=always`, headless
|
||
verifiziert (9 Prüfungen): öffentlich ban/toggle → 403, localhost UND
|
||
192.168.178.71 → 400 „Kein Nutzer angegeben" (Gate passiert, App-Validierung
|
||
erreicht), gespoofter `Cf-Connecting-Ip` aus dem LAN → 403, `GET /` öffentlich
|
||
200, `/api/livekit-token` 401 und `/api/e2ee-diagnostics` 403 wie bisher
|
||
(bleiben bewusst öffentlich, die App braucht sie). Test-Einträge aus dem
|
||
Alert-Log wieder entfernt. Rückbau jederzeit: `server.py.bak-20260704-dashgate`.
|
||
Doku/ROADMAP nachgezogen (`docs/DASHBOARD_AUTH_HARDENING.md`: Status, Weg C,
|
||
Restrisiken).
|
||
|
||
**Zusätzlicher Befund (für Bernds Secret-Rotation wichtig):** Das Gitea-Repo
|
||
einfach privat zu schalten ist KEINE schnelle Lösung für den Secret-Leak (k):
|
||
Der In-App-Updater (`lib/core/update_checker.dart`) holt Releases **anonym**
|
||
über die öffentliche Gitea-API – Repo privat = Utas installierte App findet
|
||
keine Updates mehr, und ein Fix ließe sich nicht mehr per Update verteilen
|
||
(Henne-Ei). In ROADMAP M0 beim Rotations-Punkt als Warnung vermerkt.
|
||
|
||
**Offen/Nächster Schritt:** Bernds Entscheidung bleibt für den FERNZUGRIFF
|
||
relevant (Weg A empfohlen – schließt auch die weiterhin öffentliche
|
||
Nutzerlisten-/Präsenz-Ansicht; Restrisiken im Doc: LAN-CSRF, offene Ansicht).
|
||
Danach ggf. Gate ergänzen/ersetzen. Sonst unverändert: übrige M0-Punkte
|
||
brauchen PC/Gerät oder Bernd (SQLCipher, Rotation, LiveKit-Client-Umstellung,
|
||
Härtetest).
|
||
|
||
**Stolperfallen:** (1) `stats.html` wird alle 5 Minuten von `stats.sh` (Cron)
|
||
NEU GENERIERT – Änderungen an der HTML-Datei sind flüchtig, UI-Änderungen
|
||
müssten in `stats.sh` (deshalb bewusst NUR server-seitig gefixt). (2) Auf dem
|
||
Pi laufen ZWEI cloudflared-Instanzen: die lokal konfigurierte
|
||
(`cloudflared.yml`, nur Jellyfin) und eine remote-verwaltete
|
||
(`cloudflared-pingvin`, Ingress liegt im Cloudflare-Dashboard, lokal nicht
|
||
einsehbar) – Letztere trägt `dashboard.`/`git.steggi-matrix.work`. Wer nur die
|
||
lokale YAML liest, hält die Subdomains fälschlich für tot. (3) Der
|
||
Header-Check allein würde nicht reichen (Header sind von Direktverbindern
|
||
spoofbar/wegzulassen) – die Socket-IP ist die belastbare Wahrheit, der
|
||
Cf-Header-Check ist nur die zweite Schranke, falls der Tunnel je im
|
||
Host-Netz läuft.
|
||
|
||
---
|
||
|
||
## 2026-07-04 – Fable-5-Review (n)+(o): LiveKit-Route live+geprüft, KRITISCHER Auth-Befund am Dashboard
|
||
|
||
**Erledigt:** Review-Pass über das, was seit (l)/(m) am Server dazugekommen ist –
|
||
zwei neue Punkte (n)/(o) im Fable-5-Review-Abschnitt abgehakt (Details dort).
|
||
- **(n):** Beim Gegenlesen von `server.py` gefunden, dass die geplante
|
||
LiveKit-Token-Route `POST /api/livekit-token` von einer vorher abgebrochenen
|
||
(unprotokollierten) Session **bereits live gebaut** wurde. Kritisch geprüft und
|
||
headless verifiziert: Auth per whoami greift (ungültig→401), Body-Limit (413),
|
||
gültig→200; das JWT unabhängig dekodiert und die **Signatur gegen `livekit.yaml`
|
||
gegengerechnet – gültig**; Identität = geprüfte Matrix-user_id (kein Spoofing).
|
||
Route ist gefahrlos additiv. Server-Seite des M0-LiveKit-Punkts damit erledigt;
|
||
Client-Umstellung + Rotation bleiben PC/Gerät (heiliger Call-Pfad).
|
||
- **(o) KRITISCH:** Derselbe Dashboard-Server hat für seine **Admin-Endpoints
|
||
(`/api/ban`, `/api/unban`, `/api/toggle-registration`, `/api/create-invite`,
|
||
`/api/run-stats`) KEINE Authentifizierung** und ist über
|
||
`https://dashboard.steggi-matrix.work` **öffentlich erreichbar**. Belegt per
|
||
`curl` (App-eigene 400-Antwort über die öffentliche URL, kein Login davor).
|
||
Ein Fremder könnte damit Uta (jeden Nutzer) sperren oder die offene
|
||
Registrierung am Homeserver aktivieren. Der Hostname ist über CT-Logs auffindbar.
|
||
|
||
**Offen/Nächster Schritt:** **Bernd muss entscheiden, WIE das Dashboard künftig
|
||
authentifiziert** – Weg A (Cloudflare Access vor die Subdomain, kein Code, kein
|
||
Aussperr-Risiko, empfohlen) oder Weg B (eigener `DASH_TOKEN` + `stats.html`-Prompt).
|
||
Fertiger Plan mit beiden Wegen: `docs/DASHBOARD_AUTH_HARDENING.md`. Danach sofort
|
||
umsetzbar + headless verifizierbar. Nicht blind gefixt, weil jede Auth-Ergänzung
|
||
Bernds eigenes Admin-Panel bis zur Token-Eingabe lahmlegt („kein Aussperren") –
|
||
das mitten in einer autonomen Session zu deployen ist die falsche Reihenfolge.
|
||
|
||
**Stolperfallen:** Ein Review, der sich auf den *geänderten* Endpoint (Diagnose,
|
||
Disk-Fill) konzentriert, übersieht leicht die *unveränderten Nachbarn* in
|
||
derselben Datei. Lehre: bei einem Sicherheits-Review einer Datei nicht nur den
|
||
Diff prüfen, sondern die ganze Angriffsfläche der Datei (hier: alle Routen des
|
||
`do_POST`-Handlers auf Auth abklopfen, nicht nur die neu berührte).
|
||
|
||
---
|
||
|
||
## 2026-07-04 – Fable-5-Review (l)+(m): Diagnose-Endpoint hatte kein Größenlimit (gefixt)
|
||
|
||
**Erledigt:** Review-Pass über die zwei seit (k) dazugekommenen Arbeiten – als
|
||
Punkte (l) und (m) im Fable-5-Review-Abschnitt abgehakt (Details dort). Bei (l)
|
||
ein **echter Befund** am Server-Gegenpart des Admin-Token-Fixes: Der neue
|
||
`DIAG_TOKEN` steht bewusst im öffentlichen Repo (eng begrenzt, nur Log-Anhängen) –
|
||
aber `/api/e2ee-diagnostics` in `/home/steggi/matrix/server.py` las den
|
||
Request-Body **ohne Größenlimit** und hängte ihn unbegrenzt an die Log-Datei an.
|
||
Jeder Fremde hätte damit die Pi-Platte volllaufen lassen können (Disk-Fill-DoS
|
||
auf dem Host, der auch den Homeserver trägt). Gefixt in `server.py`:
|
||
Body-Limit 256 KB pro Upload (413 bei Überschreitung) + harte Obergrenze 20 MB
|
||
für die Log-Datei (ältere Hälfte fliegt raus). Headless verifiziert nach
|
||
Dienst-Neustart (kill, `Restart=always`): stats.html 200, gültiger Upload 200
|
||
und im Log gelandet, falscher Token 403, 300-KB-Body 413; Kappungslogik isoliert
|
||
getestet (100 Zeilen → 50, neueste überleben). Test-Einträge wieder entfernt.
|
||
|
||
**Offen/Nächster Schritt:** Weiter mit dem obersten offenen ROADMAP-Punkt
|
||
(M0 LiveKit-Token): Server-Route nach `docs/LIVEKIT_TOKEN_MIGRATION.md` bauen –
|
||
rein additiv, Client bleibt unangetastet (heiliger Call-Pfad wartet auf PC/Gerät).
|
||
|
||
**Stolperfallen:** Ein „eng begrenzter" Token im öffentlichen Repo ist nur dann
|
||
wirklich harmlos, wenn das Endpoint dahinter auch Missbrauch begrenzt
|
||
(Größe/Menge) – „keine Admin-Rechte" allein reicht nicht. Bei künftigen
|
||
Endpoints mit öffentlich bekanntem Token immer an Body-/Speicher-Limits denken.
|
||
|
||
---
|
||
|
||
## 2026-07-04 – LiveKit-Token-Minting: Migrationsplan geschrieben (Prep, kein Code)
|
||
|
||
**Erledigt:** Für den offenen M0-Punkt „LiveKit-Token server-seitig minten"
|
||
(letzter Client-eingebetteter Secret nach der Diagnose-Entkopplung) den fertigen
|
||
Umsetzungsplan `docs/LIVEKIT_TOKEN_MIGRATION.md` geschrieben – gegen den echten
|
||
Client-Code (`livekit_token.dart`, `livekit_call_manager.dart:154`) und die echte
|
||
Pi-Konfig (`livekit.yaml`, `server.py`, Continuwuity `whoami`). Kern: Token-Route
|
||
`POST /api/livekit-token` am bestehenden Dashboard-Server, die den Matrix-Access-Token
|
||
per `whoami` prüft und daraus die LiveKit-Identität ableitet (kein Spoofing), JWT
|
||
per stdlib-HMAC mintet (kein neues Paket); Client holt nur noch das fertige JWT und
|
||
`apiSecret` verschwindet aus dem App-Code. Analog zum bewährten
|
||
`SQLCIPHER_MIGRATION.md`-Muster.
|
||
|
||
**Offen/Nächster Schritt:** Umsetzung braucht echten Call-Test auf einem Gerät
|
||
(Calls „heilig", auf dem Pi kein GUI) – daher bewusst nur der Plan, kein Code.
|
||
Danach Secret-Rotation zusammen mit einem Release. Verbleibende M0-Secrets
|
||
(Admin-Token, Gitea-, Giphy-Token) brauchen Bernds Konten-Zugriff.
|
||
|
||
**Stolperfallen:** Keine – reine Doku.
|
||
|
||
---
|
||
|
||
## 2026-07-04 – Admin-Token aus dem Client entfernt (E2EE-Diagnose entkoppelt)
|
||
|
||
**Erledigt:** Den kritischsten Teil des Secret-Befunds (k) wirklich behoben, statt
|
||
ihn nur zu dokumentieren – und zwar sicher, weil diese Session direkt auf dem Pi
|
||
läuft, der den Dashboard-Server hostet (frühere Sessions kannten den Server-Code
|
||
nicht). Kernursache gefunden: `/home/steggi/matrix/server.py` benutzte für das
|
||
Diagnose-Endpoint `/api/e2ee-diagnostics` **denselben String** wie den
|
||
Matrix-Admin-Token (`DIAG_TOKEN = TOKEN`). Deshalb musste der Client diesen Token
|
||
mitliefern – d. h. jeder App-Nutzer (auch Uta) trug den vollen Server-Admin-Token
|
||
im Gerät.
|
||
|
||
- **Server (`server.py`):** `DIAG_TOKEN` ist jetzt ein eigener, zufälliger,
|
||
eng begrenzter Token (`pyr-diag-…`), unabhängig vom Admin-`TOKEN`. Das
|
||
Diagnose-Endpoint hängt nur an den Log an (keine Admin-Rechte) – der neue Token
|
||
ist also von geringer Sensibilität. Der Admin-`TOKEN` (für Ban/Invite/
|
||
Registrierung) bleibt **unverändert**, damit das Dashboard weiterläuft.
|
||
- **Client (`settings_encryption.dart`):** `_uploadDiagnostics` schickt jetzt den
|
||
neuen Diagnose-Token statt des Admin-Tokens. **`grep J5lax lib/` ist jetzt leer**
|
||
– der Admin-Token ist raus aus dem App-Code.
|
||
- **Verifiziert (headless, kein GUI nötig):** Dashboard-Server nach Neustart
|
||
(Prozess gekillt, systemd `Restart=always` lud den neuen Code): `stats.html` 200,
|
||
`registration-status` 200, `/api/e2ee-diagnostics` mit neuem Token **200**, mit
|
||
altem Admin-Token **403**. `flutter analyze` unverändert (1 bekannter Hinweis),
|
||
`flutter test` 24/24 grün. Meine Test-Einträge aus dem Diagnose-Log wieder
|
||
entfernt.
|
||
|
||
**Offen/Nächster Schritt:** Die **Rotation des Admin-Tokens `J5lax…` bleibt
|
||
Pflicht und Bernds Sache** – der Token ist weiterhin gültig und steht in der
|
||
Git-History sowie in Utas bereits installierter APK. Erst nach der Rotation ist
|
||
der Leak wirklich wertlos. Ebenso offen: LiveKit-`apiSecret` (server-seitiges
|
||
Minting), Gitea- und Giphy-Token (siehe ROADMAP M0). **Wichtig für den nächsten
|
||
PC-Lauf / das nächste Release:** Utas aktuell installierte App schickt beim
|
||
Diagnose-Upload noch den alten Token → dieser Button liefert bei ihr jetzt 403,
|
||
bis sie ein neues Release bekommt (nur die Diagnose-Funktion betroffen, nichts
|
||
Heiliges). Der Diagnose-Upload-Button gehört damit in den PC-Testplan (mit neuem
|
||
Build einmal drücken, Log auf dem Pi prüfen).
|
||
|
||
**Stolperfallen:** `systemctl restart matrix-stats.service` scheiterte an
|
||
fehlender polkit-Auth (kein Terminal für sudo). Lösung ohne root: den von systemd
|
||
verwalteten Prozess killen – `Restart=always`/`RestartSec=5` lädt server.py neu
|
||
(neue MainPID bestätigt). Nur anwendbar, weil der Dienst als User `steggi` läuft
|
||
und der eigene Prozess killbar ist.
|
||
|
||
---
|
||
|
||
## 2026-07-04 – Fable-5-Review (k): Secret-Scan – ECHTER Sicherheitsbefund, teils gefixt
|
||
|
||
**Erledigt:** Beim Review-Pass zusätzlich einen systematischen Secret-Scan über
|
||
`lib/`, `scripts/`, `docs/`, `android/` gemacht (CLAUDE.md-Leitplanke „Keine
|
||
Secrets ins Repo"). **Ergebnis: mehrere echte, aktive Zugangsdaten liegen im
|
||
Repo – und das Gitea-Repo ist als `private:false` unter der Internet-Domain
|
||
`git.steggi-matrix.work` erreichbar** (per `curl` bestätigt: Repo-Metadaten und
|
||
Releases anonym abrufbar). Damit sind alle folgenden Secrets als **kompromittiert
|
||
zu behandeln** (seit Commit 25ed765 in der History):
|
||
|
||
- **Matrix-Server-ADMIN-Token** `J5lax…` – lag doppelt drin:
|
||
(1) als Bearer-Token in `docs/matrix-sdk/13-server-admin-continuwuity.md`
|
||
(19 Stellen) und (2) **im ausgelieferten App-Code**
|
||
`lib/widgets/settings/settings_encryption.dart:431` (E2EE-Diagnose-Upload).
|
||
Per `whoami` gegen den Homeserver geprüft: **Token ist noch gültig**
|
||
(`@todesneutron:steggi-matrix.work`, voller Server-Admin). Kritischster Fund –
|
||
jeder App-Nutzer (auch Uta) trägt den Server-Admin-Token im Client.
|
||
- **Gitea-Release-Token** `bb522f96…` (Repo-Schreibrechte) hartcodiert in
|
||
`scripts/release.ps1:14`.
|
||
- **LiveKit-`apiSecret`** `rYUT2PRa…` in `lib/core/livekit_token.dart:6` – die
|
||
App mintet LiveKit-JWTs **client-seitig**, das SFU-Secret muss also im Client
|
||
liegen (Architekturproblem, nicht nur ein Repo-Leak).
|
||
- **Giphy-API-Key** `QtEy…` in `lib/features/chat/gif_sticker_picker.dart:9`
|
||
(geringe Sensibilität, aber ebenfalls im Client).
|
||
|
||
**Sofort (gefahrlos, kein Laufzeit-Bezug) bereinigt:**
|
||
- `scripts/release.ps1`: Token raus, kommt jetzt aus Umgebungsvariable
|
||
`PYRAMID_GITEA_TOKEN`; fehlt sie, bricht das Skript mit klarer Anleitung ab.
|
||
- `docs/matrix-sdk/13-server-admin-continuwuity.md`: alle 19 Token-Stellen durch
|
||
`<ADMIN_TOKEN>`-Platzhalter ersetzt.
|
||
|
||
**Offen/Nächster Schritt (BRAUCHT BERND / PC – nicht blind auf dem Pi fixen):**
|
||
1. **Alle vier Secrets rotieren** (sie sind bereits geleakt, egal was im Code
|
||
steht): neuen Matrix-Admin-Token erzeugen und alten widerrufen; neuen
|
||
Gitea-Token; LiveKit-`apiKey`/`apiSecret` neu setzen (Hetzner-LiveKit +
|
||
`ice.json`); Giphy-Key neu ziehen. **Das ist der wichtigste Schritt** – die
|
||
Repo-Bereinigung allein nützt nichts, solange die alten Werte gültig sind.
|
||
2. **Client-seitige Secrets entschärfen (Architektur):** LiveKit-Token gehört
|
||
server-seitig gemintet (kleiner Token-Endpoint auf dem Pi, App holt sich nur
|
||
das fertige JWT) – dann verschwindet `apiSecret` aus dem Client. Der
|
||
E2EE-Diagnose-Upload darf **niemals** den Server-Admin-Token benutzen: eigenen,
|
||
eng begrenzten Token (nur der Dashboard-Endpoint) oder besser einen
|
||
unauthentifizierten Upload mit Rate-Limit. Beides als ROADMAP-M0-Punkt neu
|
||
aufgenommen. Blind entfernen verbietet sich, weil Calls/Diagnose sonst brechen
|
||
(Calls sind „heilig") und auf dem Pi kein GUI-Test möglich ist.
|
||
3. **Git-History:** Die alten Werte bleiben in der Gitea-History sichtbar. Nach
|
||
der Rotation sind sie wertlos; ein History-Rewrite (force-push) ist optional
|
||
und Bernds Entscheidung (das Repo dient auch als Backup) – daher NICHT
|
||
eigenmächtig gemacht.
|
||
|
||
**Stolperfallen:** Der Diagnose-„Token" in `settings_encryption.dart` ist
|
||
NICHT ein harmloser Dashboard-Key, sondern derselbe String wie der
|
||
Matrix-Server-Admin-Token – beim Lesen leicht zu übersehen, weil er dort nur
|
||
`token:` heißt. Lehre: bei Secret-Funden immer prüfen, ob derselbe Wert an
|
||
mehreren Stellen (Doku UND Client) wiederkehrt und WAS er wirklich autorisiert.
|
||
|
||
---
|
||
|
||
## 2026-07-03 – Fable-5-Review (h)–(j): Test-/Doku-Commits geprüft, 1 echter Befund gefixt
|
||
|
||
**Erledigt:**
|
||
- **Review-Pass über die drei seit (g) dazugekommenen Commits** – als Punkte
|
||
(h)–(j) im Fable-5-Review-Abschnitt abgehakt (Details dort): AuthLog-Tests
|
||
e0ac5cb korrekt (h), PC-Testplan-Update 03b84e0 korrekt (j), und bei den
|
||
settings_prefs-Tests 7dfdbf1 (i) beim Gegenlesen des Produktivcodes ein
|
||
**echter Befund**: Lade-Race in `BoolPref`/`StringSetPref` –
|
||
`StringSetPref.add()` vor abgeschlossenem `_load` konnte **bereits
|
||
persistierte Einträge löschen** (Read-Modify-Write auf ungeladenem State;
|
||
per Regressionstest am alten Code bewiesen, dann gefixt: 7b90f20).
|
||
Vorgehen: Test zuerst geschrieben, Fehlschlag am alten Code gesehen, dann
|
||
Fix, dann alles grün – kein blinder Fix.
|
||
- Unabhängige Verifikation: `flutter test` jetzt **24/24 grün auf dem Pi**
|
||
(2 neue Lade-Race-Tests), `flutter analyze` unverändert 1 bekannter Hinweis
|
||
(`chat_provider.dart:42`). PC-Testplan Schritt 0 auf 24 Tests nachgezogen.
|
||
|
||
**Offen/Nächster Schritt:** Unverändert blockiert – alle offenen ROADMAP-Punkte
|
||
(M0 SQLCipher, M1 Härtetest, M2 Call-Schicht/State-Management) brauchen PC/GUI
|
||
oder Bernds Antworten (Call-Pilot, SQLCipher-Priorität), siehe
|
||
`docs/PC_TESTPLAN.md` und „Fragen an Bernd" weiter unten. Der Lade-Race-Fix
|
||
ist headless voll getestet, gehört aber der Vollständigkeit halber mit in den
|
||
nächsten PC-Klickdurchlauf (Einstellungs-Toggles, Space-Beitritt).
|
||
|
||
**Stolperfallen:** Der BoolPref-Teil des Race ist im Test-Mock NICHT
|
||
reproduzierbar (Microtask-Reihenfolge dort zufällig gutartig: `set()`
|
||
persistiert vor dem `_load`-Read) – nur der StringSetPref-Teil schlug real
|
||
fehl. Lehre: Bei Race-Befunden nicht vom „Test wird schon rot"-Reflex leiten
|
||
lassen, sondern die konkrete Microtask-Reihenfolge durchdenken; ein Guard kann
|
||
richtig sein, auch wenn der Test ihn in dieser Umgebung nicht erzwingen kann
|
||
(dokumentiert im Review-Punkt (i)).
|
||
|
||
---
|
||
|
||
## 2026-07-03 – Review-Nachzügler abgeschlossen + AuthLog-Regressionstests
|
||
|
||
**Erledigt:**
|
||
- **Fable-5-Review vervollständigt (eebf8ae):** Die drei seit Review-Abschluss
|
||
dazugekommenen erledigten Arbeiten kritisch geprüft und als Punkte (e)–(g) im
|
||
Review-Abschnitt abgehakt: Unit-Tests 99cde9a (korrekt, Fake-Clients schlagen
|
||
bei jedem Fremd-Aufruf laut fehl), Doku-Commits 16561bb/566938f (alle
|
||
Behauptungen gegen Code/pubspec/matrix-6.2.0 verifiziert – Factory-Stellen,
|
||
Tabellenname `box_inbound_group_session`), Autopilot-Konto-Wechsel bfe5876
|
||
(kein App-Code, Logik in Ordnung; harmloser False-Positive der
|
||
Limit-Erkennung dokumentiert). **Kein Befund, keine Code-Änderung nötig.**
|
||
Damit sind ALLE als erledigt markierten Punkte reviewt.
|
||
- **`test/auth_log_test.dart` (4 Tests):** `AuthLog` ist die einzige weitere
|
||
öffentliche Schnittstelle auf dem heiligen Pfad, die headless testbar ist
|
||
(der Soft-Logout-Guard ruft `AuthLog.write` im catch-Pfad auf – würde es
|
||
werfen, eskalierte das SDK zu `logout()`+`clear()`). Getestet: write/read
|
||
werfen NIE, auch wenn die Plattform fehlt (kaputter PathProvider);
|
||
Platzhalter statt Fehler bei leerem Log; ISO-Zeitstempel pro Zeile;
|
||
500-Zeilen-Kappung (älteste Einträge fliegen raus, neueste überleben).
|
||
Dafür `path_provider_platform_interface ^2.1.2` als dev_dependency ergänzt –
|
||
offizieller Mock-Weg, war schon transitiv im Lockfile, kein neues externes
|
||
Paket. `flutter test`: alle 14 Tests grün, `flutter analyze`
|
||
unverändert (1 bekannter Hinweis `chat_provider.dart:42`).
|
||
- **`test/settings_prefs_test.dart` (8 Tests):** `core/settings_prefs.dart`
|
||
ist die Storage-Fassade, die laut `docs/M2_MODULE_SCHNITT.md` zur
|
||
Pflicht-Schnittstelle ausgebaut werden soll – dieses Vertragsnetz muss beim
|
||
M2-Umbau stehen bleiben. Getestet mit dem offiziellen In-Memory-Mock
|
||
(`SharedPreferences.setMockInitialValues`, kein neues Paket): `BoolPref`
|
||
(persistierter Wert überstimmt Default, set/toggle persistieren),
|
||
`StringSetPref` (Laden/add/remove, No-Ops bei Duplikat/Fehlendem),
|
||
Theme-Prefs (Defaults, Roundtrip, Teil-Save lässt Rest unangetastet),
|
||
Server-Banner (Roundtrip, Entfernen per null/leer, **korruptes JSON liefert
|
||
leere Map statt Crash** – echter Fehlerpfad). `flutter test`: **alle
|
||
22 Tests grün auf dem Pi.**
|
||
|
||
**Offen/Nächster Schritt:** Unverändert blockiert: Alle offenen ROADMAP-Punkte
|
||
(M0 SQLCipher, M1 Härtetest, M2 Call-Schicht/State-Management) brauchen PC/GUI
|
||
oder Bernds Antworten (Call-Pilot, SQLCipher-Priorität) – siehe
|
||
`docs/PC_TESTPLAN.md` und die Fragen an Bernd weiter unten. Weitere sichere
|
||
Pi-Testkandidaten sind jetzt wirklich erschöpft (Rest ist bibliotheksprivat
|
||
oder braucht Widget-Tests mit gemocktem Matrix-Client – lohnt erst nach dem
|
||
M2-Modulschnitt mit echten öffentlichen Schnittstellen).
|
||
|
||
**Stolperfallen:** `AuthLog` cached sein File-Handle statisch – der Test für
|
||
„Plattform kaputt" muss deshalb VOR den Tests mit funktionierender Plattform
|
||
laufen (im Testfile kommentiert). Bei künftigen Tests an Singletons mit
|
||
statischem Zustand auf Testreihenfolge/Isolate-Grenzen achten (flutter test
|
||
isoliert pro Datei, nicht pro Test).
|
||
|
||
---
|
||
|
||
## 2026-07-03 – Erste Unit-Tests: Regressionsnetz für die zwei heiligsten Codepfade
|
||
|
||
**Erledigt:** Eine Vorsession hatte (unprotokolliert, Abbruch vor dem Testlauf –
|
||
sichtbar nur am untrackten `test/`-Ordner + CHANGES.md-Hook-Log 18:16/18:17)
|
||
zwei Testdateien angelegt. Diese Session hat sie gegen den echten Code
|
||
gegengelesen, ausgeführt und committet (99cde9a) – damit hat das Projekt erstmals
|
||
überhaupt eine Testsuite, und zwar genau dort, wo CLAUDE.md „heilig" sagt:
|
||
|
||
- `test/soft_logout_guard_test.dart` (4 Tests): `guardedSoftLogoutRefresh`
|
||
wirft NIE nach außen (weder Exception noch Error) – ein Throw würde im SDK
|
||
zu `logout()`+`clear()` eskalieren (der Uta-Bug). Geprüft: Erfolg beim
|
||
1. Versuch = genau 1 Aufruf, transienter Fehler wird per Retry geheilt,
|
||
dauerhafter Fehler = genau 3 Versuche + normale Rückkehr. Der Fake-Client
|
||
schlägt bei JEDEM anderen SDK-Aufruf laut fehl – riefe der Guard je
|
||
`logout()`/`clear()` auf, fiele der Test sofort um.
|
||
- `test/power_levels_safety_test.dart` (6 Tests): `updatePowerLevelsSafely`
|
||
(Selbst-Aussperr-Schutz im Space-Admin) – Server-Stand bleibt erhalten
|
||
(fremde users-Einträge überleben; genau der Bug, der schon mal Admins
|
||
ausgesperrt hat), Selbst-Degradierung bricht ab ohne zu schreiben,
|
||
users_default-Fallback, explizites `events[m.room.power_levels]`-
|
||
Erfordernis, M_NOT_FOUND-Start mit leerem Event, fremde Serverfehler
|
||
(M_FORBIDDEN) werden durchgereicht.
|
||
- `flutter test`: **alle 10 Tests grün auf dem Pi** (läuft headless, im
|
||
Gegensatz zu `flutter run` – d. h. dieser Punkt ist NICHT „UNGETESTET (Pi)").
|
||
`flutter analyze` unverändert 1 bekannter Hinweis (`chat_provider.dart:42`).
|
||
- Wert fürs Refactoring: Beide Funktionen sind Vertragsgrenzen, die bei
|
||
M2-Umbauten (Call-Fassade, Storage) nicht wackeln dürfen – künftige
|
||
Sessions sollten `flutter test` fest in Schritt 3 des Arbeitszyklus
|
||
aufnehmen (geht auch auf dem Pi).
|
||
|
||
**Offen/Nächster Schritt:** Unverändert: PC-Termin (docs/PC_TESTPLAN.md) oder
|
||
Bernds Antworten (Call-Pilot, SQLCipher-Priorität). Weitere Test-Kandidaten
|
||
scheitern derzeit an der Sichtbarkeit: `_detectType`, `_formatMessageTime`,
|
||
`_compareSpaceChildren`, die Megolm-Export-Kryptohelfer usw. sind alle
|
||
bibliotheksprivat – testbar erst, wenn beim M2-Modulschnitt echte öffentliche
|
||
Schnittstellen entstehen (dann Tests gleich mitschreiben).
|
||
|
||
**Stolperfallen:** Die Vorsession wurde mitten im Schritt abgebrochen, OHNE
|
||
den Zwischenstand zu protokollieren (Pflicht laut CLAUDE.md) – der untrackte
|
||
`test/`-Ordner war nur per `git status` auffindbar. Lehre: `git status` am
|
||
Sessionstart ernst nehmen; untrackte Dateien können unprotokollierte,
|
||
halbfertige Arbeit einer abgebrochenen Session sein.
|
||
|
||
---
|
||
|
||
## 2026-07-03 – M1-Vorbereitung: SQLCipher-Migrationsplan + konsolidierter PC-Testplan
|
||
|
||
**Erledigt:** Nach Abschluss des Fable-5-Reviews (Abschnitt darunter) die zwei
|
||
obersten offenen ROADMAP-Punkte (SQLCipher, Härtetest) so weit gebracht, wie es
|
||
auf dem Pi ohne GUI/Testgerät sicher geht – beide bleiben offen, aber der
|
||
nächste PC-Termin kann jetzt direkt loslegen statt zu planen:
|
||
|
||
- `docs/SQLCIPHER_MIGRATION.md`: vollständiger Umsetzungsplan, gegen den echten
|
||
Code geschrieben (beide DB-Öffnungsstellen, Factory-Wahl pro Plattform).
|
||
Kernpunkte: gemeinsame DB-Fassade `app_database.dart` (zahlt aufs
|
||
M2-Storage-Modul ein), Android via `databaseFactoryFfi` +
|
||
`openCipherOnAndroid`, im Push-Isolate zwingend `databaseFactoryFfiNoIsolate`
|
||
(dieselbe Isolate-Falle, an der schon `NativeImplementationsIsolate`
|
||
scheiterte), Schlüssel im Android Keystore (`flutter_secure_storage`, schon
|
||
Abhängigkeit), Migration per `sqlcipher_export` in NEUE Datei mit
|
||
Backup/Verifikation/atomarem Tausch – kein Fehlerpfad darf die Klartext-DB
|
||
beschädigen. Marker-Datei gegen das Migration/Push-Race. Phase 1 nur
|
||
Android (sqlcipher_flutter_libs liefert keine Windows-Binaries).
|
||
Tabellenname für die Verifikation gegen matrix-6.2.0 geprüft
|
||
(`box_inbound_group_session`).
|
||
- `docs/PC_TESTPLAN.md`: ALLE aufgelaufenen UNGETESTET(Pi)-Punkte aus den
|
||
bisherigen PROGRESS-Einträgen an einer Stelle konsolidiert (Härtetest/
|
||
Krypto-Kreislauf zuerst, dann die 6 Split-Bereiche, Reorder-Off-by-One-Check,
|
||
PDF-Zoom, Push-Beobachtung, „Uta einmal neu einloggen"). Bisher waren die
|
||
über 7 Einträge verstreut – Gefahr, dass der PC-Lauf etwas übersieht.
|
||
|
||
**Offen/Nächster Schritt:** Unverändert: PC-Termin (jetzt mit fertigem
|
||
Testplan + Migrationsplan) oder Bernds Antworten (Call-Pilot, SQLCipher-
|
||
Priorität). Auf dem Pi ist damit alles abgearbeitet, was ohne GUI/Entscheidung
|
||
sicher geht – inklusive des kompletten Qualitäts-Reviews.
|
||
|
||
**Stolperfallen:** Keine neuen – reine Dokumentation, kein Code angefasst.
|
||
|
||
---
|
||
|
||
## Fable-5-Review (Qualitäts-Review aller Autopilot-Commits 25ed765..HEAD)
|
||
|
||
Bernds Auftrag (ROADMAP M0): alle bisherigen Sonnet-5-Commits kritisch und in
|
||
der Tiefe nachprüfen. Jeder Punkt hier wird abgehakt, sobald er geprüft ist –
|
||
**bereits abgehakte Punkte in künftigen Sessions NICHT erneut prüfen.**
|
||
|
||
Hinweis zur Historie: Eine frühere Fable-5-Session hatte den Review begonnen
|
||
und zwei Fix-Commits gepusht (f9979e4, 891f348), wurde aber VOR dem
|
||
Protokollieren abgebrochen – dieser Abschnitt holt das Protokoll nach und
|
||
schließt den Review ab.
|
||
|
||
- [x] **(a) Die 6 Gott-Datei-Aufteilungen fachlich geprüft – Ergebnis: korrekt,
|
||
1 Befund bereits gefixt (f9979e4).** Unabhängige Nachprüfung (nicht nur den
|
||
alten Blockvergleich wiederholt): eigener Zeilen-Multiset-Vergleich
|
||
(Code-Zeilen ohne Kommentare/Imports/part-Direktiven) Original vor dem Split
|
||
vs. heutiger Stand für alle 6 Bibliotheken → **keine Code-Zeile verloren,
|
||
keine hinzugekommen** (settings_modal 5055, message_group 2409, rooms_panel
|
||
2308, space_admin_dialog 2079, chat_view 1813, document_viewer 1543 Zeilen,
|
||
jeweils exakt identisch). Alle `part`-Direktiven == Dateien auf Platte,
|
||
alle Part-Dateien mit korrektem `part of`. Befund der Vorsession bestätigt:
|
||
Beim Split gingen 24 **Erklärkommentar-Blöcke** verloren (u. a. die
|
||
Selbst-Aussperr-Warnung über `updatePowerLevelsSafely`, HTML-Subset-Doku,
|
||
PDF-Cache-Doku) – mit f9979e4 an den richtigen Klassen wiederhergestellt
|
||
(Stichprobe geprüft: sitzt korrekt). `flutter analyze`: unverändert 1
|
||
bekannter Hinweis (`chat_provider.dart:42`).
|
||
- [x] **(b) Uta-Logout-/Push-Fix (63e4919, 9dc83f7) gegen das matrix-SDK 6.2.0
|
||
verifiziert – Analyse war richtig, aber unvollständig; Lücke mit 891f348
|
||
geschlossen (Vorsession), heute vollständig gegenverifiziert:**
|
||
- SDK-Quelle bestätigt (client.dart:2374 ff.): Ohne eigenen `onSoftLogout`-
|
||
Handler eskaliert JEDER fehlgeschlagene Token-Refresh (auch bloße
|
||
Netzwerkfehler) zu `logout()` + `clear()` → Session inkl. Krypto-Schlüssel
|
||
weg. Mit `refreshToken: true` (9dc83f7) wären Refreshes Routine geworden –
|
||
das Restrisiko wäre also GESTIEGEN. Der Guard (`soft_logout_guard.dart`,
|
||
891f348) ist die notwendige zweite Hälfte des Fixes.
|
||
- Guard-Code geprüft: wirft nie (alle Pfade gefangen; `AuthLog.write` fängt
|
||
intern selbst alles – wichtig, sonst würde ein Logging-Fehler im
|
||
catch-Pfad doch wieder `logout()` auslösen). SDK ruft bei nicht-werfendem
|
||
Handler nachweislich nie `logout()` auf.
|
||
- „Retry heilt das Token-Rotations-Race mit dem Push-Isolate"-Behauptung
|
||
gegen SDK-Quelle geprüft: stimmt – `refreshAccessToken()` liest den
|
||
Refresh-Token bei jedem Aufruf frisch per `database.getClient()` aus der
|
||
geteilten DB.
|
||
- Beide `Client(`-Konstruktionen der App (matrix_client.dart,
|
||
background_push.dart) haben den Guard – keine ungeschützte übrig.
|
||
- `isLoggedInProvider`-Änderung (softLoggedOut ≠ ausgeloggt) korrekt:
|
||
verhindert Login-Screen-Flackern bei Routine-Refresh; echtes `loggedOut`
|
||
führt weiterhin zum Login-Screen.
|
||
- **Dokumentiertes Restrisiko (kein Fix möglich/nötig):** Ein hartes
|
||
`M_UNKNOWN_TOKEN` OHNE `soft_logout`-Flag führt im SDK weiterhin direkt zu
|
||
`clear()`. Das ist ein echter serverseitiger Session-Widerruf (z. B.
|
||
Admin-Logout) – lokal nicht abwendbar; Schlüssel kommen dann übers
|
||
Key-Backup zurück. Kein stiller Fehlerpfad, sondern legitimer Server-Befehl.
|
||
- [x] **(c) analyze-/Lint-Fixes (9dc1175, 133a8ac, 99f10c5, 2395db5, 057ac1e,
|
||
401bd6a) auf Verhaltensänderungen geprüft – alle äquivalent, 1 Kleinigkeit
|
||
gefunden (bewusst belassen):**
|
||
- `onReorder` → `onReorderItem` (401bd6a): gegen Flutter-SDK-Quelle
|
||
verifiziert – der neue Callback korrigiert den Index intern, das entfernte
|
||
manuelle `newIdx--` ist korrekt; einzige Aufrufstelle migriert. Kein
|
||
Off-by-One beim Raum-Umsortieren.
|
||
- `Matrix4.translate/scale` → `translateByDouble/scaleByDouble` (PDF-Zoom):
|
||
mathematisch äquivalent (uniforme Skalierung, w=1).
|
||
- `withOpacity`→`withValues`, `activeColor`→`activeThumbColor`,
|
||
`cacheExtent`→`scrollCacheExtent`, `PublicRoomsChunk`→`PublishedRoomsChunk`,
|
||
print→debugPrint, `__`→`_`-Parameter, Curly-Braces: reine Renames/Syntax.
|
||
- Toter Code (9dc1175): entfernte Funktionen (`_showNotif`, `_fetchTitle`,
|
||
`_get`, `_applyNoiseSuppression`, `destructive`-Param, `_saved`-Feld) per
|
||
grep bestätigt nirgends referenziert.
|
||
- **Kleinigkeit:** In `mini_call_widget.dart` wurde das ungenutzte
|
||
`_hovered`-Feld nicht gelöscht, sondern verdrahtet (Hover-Farbe auf
|
||
Call-Buttons) – streng genommen eine Mini-Verhaltensänderung in einem
|
||
Lint-Commit. Offensichtlich die ursprünglich gemeinte Funktion des Feldes,
|
||
rein kosmetisch, kein Risiko → belassen, hier nur dokumentiert.
|
||
- [x] **(d) Abgehakte ROADMAP-Punkte gegen Code gegengeprüft – alle stimmen:**
|
||
`isKeyBackupMissing`-Warnung sitzt nach dem Settings-Split weiterhin in
|
||
beiden Logout-Pfaden (settings_about.dart, settings_shared.dart);
|
||
`refreshToken: true` in login_notifier.dart:84; Bootstrap-Flows
|
||
(`askUnlockSsss`, `_triggerBootstrap`) vorhanden; `features/calls/`-Leiche
|
||
existiert nicht; `docs/M2_MODULE_SCHNITT.md` vorhanden; `flutter analyze`
|
||
sauber (1 bekannter Hinweis). Nebenbefund gefixt: `docs/PROGRESS.md` war ein
|
||
eingefrorener Alt-Stand (2026-06-04) mit Verwechslungsgefahr zum echten
|
||
Protokoll im Root → Archiv-Banner eingefügt.
|
||
|
||
- [x] **(e) Unit-Tests (99cde9a) gegengelesen – korrekt, keine Änderung nötig.**
|
||
Beide Fake-Clients schlagen bei jedem nicht implementierten SDK-Aufruf laut
|
||
fehl (NoSuchMethodError) – ein versehentlicher `logout()`/`clear()`-Aufruf im
|
||
Guard würde sofort auffallen. `soft_logout_guard_test`: deckt Erfolg,
|
||
transienten Fehler (Retry), dauerhaften Fehler (genau 3 Versuche, kein Throw)
|
||
und `Error` (nicht nur `Exception`) ab – passt zum `catch (e)` im Guard, das
|
||
bewusst alles fängt. Dass die Tests grün sind, beweist nebenbei, dass
|
||
`AuthLog.write` ohne path_provider-Plattform (Testumgebung) sauber schluckt
|
||
statt zu werfen – genau die Eigenschaft, an der der Guard hängt.
|
||
`power_levels_safety_test`: alle 6 Fälle gegen den echten Code
|
||
(`space_admin_core.dart:10-56`) nachvollzogen, inkl. der wichtigen Kante
|
||
„explizites `events[m.room.power_levels]` schlägt `state_default`".
|
||
Einzige Anmerkung: die zwei Retry-Tests schlafen real 2s+4s (Suite ~14s) –
|
||
bewusst so gelassen, statt für Testbarkeit einen Delay-Parameter in den
|
||
heiligen Produktivpfad zu bohren.
|
||
- [x] **(f) Doku-Commits 16561bb/566938f (SQLCipher-Plan, PC-Testplan) gegen
|
||
Code verifiziert – Behauptungen stimmen:** Factory-Stellen exakt wie
|
||
dokumentiert (`matrix_client.dart:24` nativ Android/iOS, `:27` FFI Desktop;
|
||
`background_push.dart:188` nativ), `sqlcipher_flutter_libs ^0.6.8` +
|
||
`flutter_secure_storage ^10` in pubspec vorhanden/ungenutzt wie beschrieben,
|
||
Verifikations-Tabellenname `box_inbound_group_session` steht wirklich in
|
||
matrix-6.2.0 `matrix_sdk_database.dart:139`, beide Prozesse öffnen dieselbe
|
||
`pyramid.sqlite` mit WAL + busy_timeout 5000. PC-Testplan deckt alle in
|
||
PROGRESS verstreuten UNGETESTET-Punkte ab (stichprobenartig gegengeprüft).
|
||
- [x] **(g) Autopilot-Konto-Wechsel (bfe5876, dazu 73420fe) geprüft – kein
|
||
App-Code, funktional in Ordnung:** Wechsel-Logik (pyramid↔haupt, `JUST_SWITCHED`
|
||
verhindert Endlos-Ping-Pong, 20-min-Wartepause wenn beide Konten am Limit),
|
||
`CLAUDE_CONFIG_DIR` wird vor jeder Session neu gesetzt, Matrix-Token kommt aus
|
||
`~/gatus/.env` (nicht im Repo). Einzige Anmerkung (belassen): die
|
||
Limit-Erkennung greppt die GESAMTE Session-Ausgabe auf „usage limit" u. ä. –
|
||
zitiert eine Session diese englischen Phrasen wörtlich, gibt es einen
|
||
unnötigen (aber harmlosen) Konto-Wechsel.
|
||
|
||
- [x] **(h) AuthLog-Tests (e0ac5cb) geprüft – korrekt, keine Änderung nötig.**
|
||
Alle 4 Tests gegen `core/auth_log.dart` nachvollzogen: Der Kaputt-Plattform-
|
||
Test läuft korrekt VOR den Tests mit funktionierender Plattform (das statische
|
||
`_file`-Handle wird beim Throw nie befüllt, daher kein vergifteter Cache);
|
||
Kappungs-Test rechnet richtig (521. Zeile triggert Kappung auf 500, „Hallo
|
||
Log" aus dem Vortest fliegt raus). pubspec-Ergänzung sauber (offizielles
|
||
`path_provider_platform_interface`, war transitiv im Lockfile). Unabhängig
|
||
verifiziert: `flutter test` 22/22 grün, `flutter analyze` 1 bekannter
|
||
Hinweis. Anmerkung (belassen): Die Kappung schreibt die Datei nicht-atomar
|
||
neu und ist nicht gegen parallele Schreiber aus dem Push-Isolate gesperrt –
|
||
für ein reines Diagnose-Log akzeptabel, kein Fix nötig.
|
||
- [x] **(i) settings_prefs-Tests (7dfdbf1) geprüft – Tests korrekt, aber beim
|
||
Gegenlesen des Produktivcodes 1 echter Befund gefunden und gefixt (7b90f20):
|
||
Lade-Race in `BoolPref`/`StringSetPref`.** `StringSetPref.add/remove` machten
|
||
Read-Modify-Write auf möglicherweise noch ungeladenem State – ein `add()`
|
||
direkt nach Erstellung persistierte nur den neuen Eintrag und LÖSCHTE alle
|
||
bereits gespeicherten (per neuem Regressionstest am alten Code bewiesen:
|
||
`['!a:hs']` im Store, `add('!b:hs')` → Store enthielt nur noch `['!b:hs']`).
|
||
Fix: `add`/`remove` warten jetzt auf `_loaded`. `BoolPref` zusätzlich mit
|
||
`_userSet`-Guard, damit ein spät landender persistierter Wert nie eine schon
|
||
getätigte Nutzeraktion zurücküberschreibt (die Microtask-Reihenfolge ist
|
||
plattform-/versionsabhängig – im Test-Mock zufällig gutartig). Einordnung:
|
||
in der heutigen App kaum treffbar (die Provider werden beim App-Start geladen,
|
||
lange bevor jemand klickt – deshalb nie aufgefallen), aber die Fassade wird
|
||
laut `docs/M2_MODULE_SCHNITT.md` zur Pflicht-Schnittstelle für neue, ggf.
|
||
früh laufende Aufrufer. 2 neue Regressionstests, alle 24 grün auf dem Pi.
|
||
- [x] **(j) PC-Testplan-Update (03b84e0) geprüft – stimmt.** Teststand „22
|
||
Tests" war korrekt (durch (i) jetzt 24 – im Testplan nachgezogen, siehe
|
||
Eintrag oben).
|
||
- [x] **(k) Secret-Scan über das ganze Repo – ECHTER Sicherheitsbefund
|
||
(2026-07-04).** Vier aktive Zugangsdaten im öffentlich erreichbaren Repo
|
||
(Matrix-Admin-Token doppelt – auch im Client!, Gitea-Release-Token,
|
||
LiveKit-`apiSecret`, Giphy-Key). Gefahrlose Stellen sofort bereinigt
|
||
(release.ps1 → Env-Var, Admin-Doku → Platzhalter); Rotation aller Secrets +
|
||
server-seitiges LiveKit-Token-Minting + nicht-Admin-Diagnose-Token als neue
|
||
M0-Punkte aufgenommen (brauchen Bernd/Server-Zugriff). Details im
|
||
PROGRESS-Eintrag 2026-07-04 oben.
|
||
- [x] **(l) Admin-Token-Entkopplung (925aafb) geprüft – Client-Seite korrekt,
|
||
am Server-Gegenpart 1 echter Befund gefunden und gefixt (2026-07-04).**
|
||
Client-Diff verifiziert: neuer `pyr-diag-…`-Token stimmt mit `DIAG_TOKEN` in
|
||
`server.py` überein, `grep J5lax lib/ scripts/` unabhängig nachgeprüft (leer;
|
||
einziges verbliebenes Client-Secret ist das LiveKit-`apiSecret`, eigener
|
||
M0-Punkt). Befund: `/api/e2ee-diagnostics` hatte **kein Body-/Log-Größenlimit**
|
||
bei öffentlich bekanntem Token → Disk-Fill-DoS möglich. Gefixt + headless
|
||
verifiziert (200/403/413, Kappung), Details im PROGRESS-Eintrag 2026-07-04.
|
||
- [x] **(m) LiveKit-Migrationsplan (78e4174) gegen Code + Pi-Konfig geprüft –
|
||
Behauptungen stimmen.** `livekit_token.dart:6` enthält das `apiSecret`,
|
||
Aufrufstelle `livekit_call_manager.dart:154` mit `ttlSeconds: 21600`, Grants
|
||
im Plan identisch mit dem Dart-Code (roomJoin/room/canPublish/canSubscribe/
|
||
canPublishData, metadata = Anzeigename, iss = LKMatrixPi, sub = identity),
|
||
`livekit.yaml` `keys:` wie beschrieben, Continuwuity antwortet auf
|
||
`127.0.0.1:6167` (whoami-Basis). Python-Snippet (stdlib-HS256) fachlich
|
||
korrekt (base64url ohne Padding, kompaktes JSON, HMAC-SHA256). Anmerkung:
|
||
Der Plan setzt zusätzlich `nbf`/`name` – LiveKit akzeptiert beides, harmlos.
|
||
- [x] **(n) LiveKit-Token-Route ist inzwischen LIVE auf dem Pi gebaut (bisher
|
||
unprotokolliert) – geprüft und funktionsfähig, aber Client noch nicht
|
||
umgestellt (2026-07-04).** Beim Review von `server.py` gefunden: Eine
|
||
Vorsession hat die in `docs/LIVEKIT_TOKEN_MIGRATION.md` geplante Route
|
||
`POST /api/livekit-token` bereits in `server.py` implementiert (whoami-Auth,
|
||
stdlib-HS256-Minting aus `livekit.yaml`) – der CHANGES-Hook zeigt drei
|
||
server.py-Edits um 04:33, Dienst-Neustart 04:34, aber kein PROGRESS-Eintrag
|
||
(Session vor dem Protokoll abgebrochen). Headless verifiziert (127.0.0.1:8080
|
||
UND öffentlich): ungültiger Matrix-Token → 401, fehlende Felder → 400, 5-KB-Body
|
||
→ 413 (4-KB-Limit), gültiger Admin-Token → 200; das geminete JWT unabhängig
|
||
dekodiert: iss=LKMatrixPi, sub/name/metadata = geprüfte user_id (kein Spoofing,
|
||
Client kann Identität NICHT wählen), video-Grants wie im Client, exp−nbf =
|
||
21600 s, **Signatur unabhängig gegen `livekit.yaml` gegengerechnet: gültig**.
|
||
Die Route ist gefahrlos additiv (altes Client-Secret bleibt bis zur Rotation
|
||
gültig, nichts bricht). **Offen bleibt:** Client-Umstellung
|
||
(`livekit_token.dart`/`livekit_call_manager.dart`) + Secret-Rotation – beides
|
||
weiterhin PC/Gerät (heiliger Call-Pfad), siehe Migrationsplan. Server-Seite des
|
||
M0-LiveKit-Punkts ist damit erledigt.
|
||
- [x] **(o) KRITISCHER Befund beim server.py-Review: die Admin-Endpoints des
|
||
Dashboards haben KEINE Authentifizierung und sind öffentlich erreichbar
|
||
(2026-07-04).** Der Review von (l) hatte nur das Diagnose-Endpoint auf
|
||
Disk-Fill gehärtet – der viel größere Nachbar blieb unbemerkt:
|
||
`https://dashboard.steggi-matrix.work` ist über Cloudflare offen aus dem
|
||
Internet erreichbar, und `POST /api/ban`, `/api/unban`, `/api/toggle-registration`,
|
||
`/api/create-invite`, `/api/run-stats` prüfen **keinen** Token. Belegt:
|
||
`curl -X POST .../api/ban -d '{"user":""}'` liefert die App-eigene 400-Antwort
|
||
`{"error":"Kein Nutzer angegeben"}` – identisch zu 127.0.0.1, also KEIN
|
||
Cloudflare-Login davor. Damit könnte jeder Fremde Uta (jeden Nutzer) sperren
|
||
oder die offene Registrierung am Homeserver aktivieren. Der Hostname ist über
|
||
Certificate-Transparency-Logs praktisch auffindbar – „geheim" trägt nicht.
|
||
**Bewusst NICHT blind gefixt:** Jede Auth-Ergänzung macht Bernds
|
||
Browser-Dashboard funktionslos, bis er einen Token einträgt / Cloudflare Access
|
||
einrichtet – das mitten in einer autonomen Session zu deployen sperrt ihn ohne
|
||
Vorwarnung aus seinem eigenen Admin-Panel („kein Aussperren"). Fertiger,
|
||
gegen den echten Code geschriebener Plan mit zwei Wegen (A: Cloudflare Access =
|
||
Königsweg, kein Code; B: eigener `DASH_TOKEN` + `stats.html`-Prompt) liegt in
|
||
`docs/DASHBOARD_AUTH_HARDENING.md`. **Braucht Bernds Wahl A oder B, dann
|
||
sofortige Umsetzung + headless-Verifikation.** In ROADMAP M0 als dringender
|
||
Punkt aufgenommen. **Update (später am 2026-07-04):** Das akute Loch ist mit
|
||
einem aussperr-sicheren Interims-Gate geschlossen (Admin-Routen nur noch
|
||
Heimnetz/localhost, siehe PROGRESS-Eintrag „Dashboard-Befund (o)" oben) –
|
||
offen bleibt nur noch die Fernzugriffs-Entscheidung A/B.
|
||
- [x] **(p) Interims-Gate (e97a748/455742d) reviewt – korrekt; dabei halbfertige,
|
||
unprotokollierte Threading-Härtung einer abgebrochenen Session gefunden und
|
||
vollendet (2026-07-04).** Gate-Logik bestätigt: Socket-IP ist die belastbare
|
||
Prüfung (nicht spoofbar), Cf-Header nur Zweitschranke, Deny-Log mit 5-MB-Deckel
|
||
und ohne attacker-kontrollierten Text; Restrisiken (LAN-CSRF, offene
|
||
Nutzerlisten-Ansicht) waren bereits in `docs/DASHBOARD_AUTH_HARDENING.md`
|
||
dokumentiert – kein neuer Befund am Gate. ABER: CHANGES-Hook zeigte drei
|
||
server.py-Edits um 09:43 ohne Commit/Protokoll – abgebrochene Session hatte
|
||
`DIAG_LOCK` definiert, aber nie benutzt, und `ThreadingHTTPServer` im Kommentar
|
||
behauptet, aber nicht umgestellt; der Dienst lief zudem noch mit altem Code.
|
||
Vollendet (Threading + Lock-Nutzung), Dienst neu gestartet, 11 Headless-
|
||
Prüfungen grün inkl. Slow-Loris (parallele Anfrage 10 ms statt blockiert;
|
||
hängender Body-Read nach 30 s gekappt). Details im PROGRESS-Eintrag „(p)" oben.
|
||
- [x] **(q) Threading-Umstellung aus (p) tiefer geprüft – echte Nebenläufigkeits-Race
|
||
in den Admin-Routen gefunden und mit `ADMIN_LOCK` behoben (2026-07-04).** (p) hatte
|
||
den öffentlich beschreibbaren Diagnose-Pfad per `DIAG_LOCK` abgesichert, aber die
|
||
Admin-Routen übersehen: `toggle-registration`/`create-invite` machen ein
|
||
Read-Modify-Write auf `conduit.toml` + `docker restart`, und `send_admin()` liest
|
||
nach 4 s die letzten Admin-Raum-Nachrichten zurück – beides seit multithreaded
|
||
race-fähig (lost update, doppelter Neustart, vertauschte Antworten). Fix: neues
|
||
`ADMIN_LOCK`, das **nur** die Admin-Mutationen serialisiert; die öffentlichen Routen
|
||
(`livekit-token`, `e2ee-diagnostics`) laufen zuerst und bleiben nebenläufig
|
||
(Slow-Loris-Schutz aus (p) unangetastet). Handler-Logik wortwörtlich unverändert
|
||
(per `diff` gegen Backup verifiziert). Headless verifiziert: 8 Routen-Checks wie
|
||
vorher; Race-Beweis (2 parallele `run-stats` 2633 ms ≈ 2× einzeln 1388 ms →
|
||
serialisiert); Non-Blocking-Beweis (öffentliches `livekit-token` während gehaltenem
|
||
Lock in 11 ms). Details im PROGRESS-Eintrag „(q)" oben.
|
||
- [x] **(r) ADMIN_LOCK-Umbau aus (q) (c8ff850) geprüft – Umbau korrekt, 3 kleinere
|
||
Restbefunde gefixt (2026-07-04).** Locking-Struktur und Routen-Reihenfolge von (q)
|
||
bestätigt (öffentliche Routen vor dem Gate/Lock, Handler-Logik unverändert).
|
||
Befunde: (1) `GET /api/registration-status` las `conduit.toml` ohne Lock, während
|
||
die Admin-Routen sie per Truncate+Write neu schreiben – Leser konnte seit
|
||
multithreaded (p) eine halb geschriebene Datei sehen; Fix: Lesen unter `ADMIN_LOCK`
|
||
(bewusst KEIN `os.replace`: die Datei ist als Einzeldatei in den Container
|
||
gemountet, Inode-Falle – im Code dokumentiert). (2) Negative `Content-Length`
|
||
ließ `read()` bis EOF blockieren (Thread hing bis zum 30-s-Timeout) – bei
|
||
`livekit-token`/`e2ee-diagnostics`/`ban`/`unban` wird `< 0` jetzt mit 413
|
||
abgelehnt. (3) `ban`/`unban` ohne Body-Limit (hielten dabei den ADMIN_LOCK) →
|
||
4-KB-Deckel; Einladungs-Token von `random` auf `secrets` umgestellt (Token
|
||
schaltet Registrierung frei). Headless verifiziert (8 Checks inkl. 8-KB-Body →
|
||
413, `Content-Length: -5` → sofort 413 statt Hänger). Details im
|
||
PROGRESS-Eintrag „(r)" oben.
|
||
- [x] **(s) (r)-Fixes (5ada0e5) geprüft – korrekt, 2 kleinere Restbefunde gefixt
|
||
(2026-07-04).** (r)-Diff gegen Backup verifiziert (GET-Leser-Lock,
|
||
Content-Length-Härtung, secrets-Token – alles wie protokolliert), Dienst lief
|
||
mit aktuellem Code, kein unprotokollierter Rest im CHANGES-Hook-Log. Befunde
|
||
beim Ganzdatei-Gegenlesen: (1) ban/unban las den Body innerhalb des
|
||
`ADMIN_LOCK` – ein Client mit angekündigtem, nie gesendetem Body hielt den
|
||
Lock bis zum 30-s-Socket-Timeout (das 4-KB-Limit deckelt nur die Größe, nicht
|
||
die Wartezeit; seit (r) hing damit auch der `registration-status`-Leser mit);
|
||
Fix: Body-Lesen/Validieren vor den Lock gezogen, Handler-Logik unverändert.
|
||
(2) `livekit-token`/`e2ee-diagnostics` leakten bei Fehlern `str(e)` an
|
||
Unauthentifizierte (interne Pfade/Adressen); Fix: generisches
|
||
`{"error": "Interner Fehler"}`, Details ins Journal (stderr). Headless
|
||
verifiziert: 8 Routen-Checks unverändert, Lock-Beweis (hängender ban-Body,
|
||
paralleler registration-status-GET in 15 ms), 500-Beweis (ungültiges JSON →
|
||
generisch, Detail im Journal). Details im PROGRESS-Eintrag „(s)" oben.
|
||
- [x] **(t) (s)-Fixes (13b333c) geprüft – korrekt, 1 kleiner Restbefund gefixt
|
||
(2026-07-04).** (s)-Diff gegen Backup verifiziert (ban/unban-Body vor dem
|
||
Lock inkl. Validierung, generische 500er auf den öffentlichen POST-Routen –
|
||
alles wie protokolliert), Dienst lief mit aktuellem Code, kein
|
||
unprotokollierter Rest im CHANGES-Hook-Log. Befund beim
|
||
Ganzdatei-Gegenlesen: `GET /api/registration-status` (öffentlich, `do_GET`
|
||
hat kein Gate) antwortete bei Fehlern noch mit `str(e)` – hätte z. B. den
|
||
`conduit.toml`-Pfad geleakt; (s) hatte nur die POST-Routen gefixt. Fix:
|
||
generisches `{"error": "Interner Fehler"}`, Detail ins Journal. Headless
|
||
verifiziert: 8 Routen-Checks unverändert, 500-Beweis per kurzzeitigem
|
||
`chmod 000` auf `conduit.toml` (generisch nach außen, `PermissionError` im
|
||
Journal, danach wieder 200). Details im PROGRESS-Eintrag „(t)" oben.
|
||
- [x] **(u) (t)-Fix (d3e75c3) geprüft – korrekt, KEIN Befund; server.py-Kette
|
||
abgeschlossen (2026-07-04).** Diff gegen `server.py.bak-20260704-review-t`
|
||
= exakt der protokollierte Mini-Fix, Dienst läuft mit aktuellem Code, kein
|
||
unprotokollierter Rest. Verbliebene `str(e)`-Stellen alle LAN-gegated
|
||
(absichtlich). Ohne neuen Anlass keine weitere Review-Runde an `server.py`.
|
||
Details im PROGRESS-Eintrag „(u)" oben.
|
||
- [x] **(v) Entscheidungs-Doku 825481e (`ANTWORTEN_BERND.md`, am PC entstanden)
|
||
geprüft – konsistent, 1 Doku-Lücke gefunden und behoben (2026-07-05).** Alle
|
||
referenzierten Pläne/Dateien existieren, die vier Entscheidungen decken die
|
||
gesammelten „Fragen an Bernd" ab. Befund: Entscheidung 3 (Dashboard
|
||
Heimnetz-only, A/B geschlossen) war nicht in ROADMAP.md /
|
||
`docs/DASHBOARD_AUTH_HARDENING.md` nachgezogen – beide stellten die Frage
|
||
weiter als offen dar. In derselben Session behoben (ROADMAP-Punkt
|
||
geschlossen, Doku-Status aktualisiert, Vermerk zu Entscheidung 4 am
|
||
Secrets-Punkt). Details im PROGRESS-Eintrag „(v)+(w)" oben.
|
||
- [x] **(w) autopilot.sh-Fix 4477ed9 (CLAUDE_CONFIG_DIR unset für Hauptkonto,
|
||
am PC entstanden) geprüft – korrekt, kein Befund (2026-07-05).** Begründung
|
||
im Commit stimmt (CLI sucht Config unter `$CLAUDE_CONFIG_DIR/.claude.json`,
|
||
Hauptprofil liegt unter `~/.claude.json`); `unset` wirkt auch nach
|
||
pyramid-Iterationen derselben Shell. Am lebenden System verifiziert: die
|
||
Review-Session selbst lief als Konto „haupt" mit leerem `CLAUDE_CONFIG_DIR`
|
||
und korrekt geladenem Profil. Kein weiterer `CLAUDE_CONFIG_DIR`-Verweis im
|
||
Repo. Details im PROGRESS-Eintrag „(v)+(w)" oben.
|
||
- [x] **(x) (v)-Doku-Fix (66bf54b) geprüft – korrekt, kein Befund (2026-07-05).**
|
||
Diff = exakt der protokollierte Fix (ROADMAP-Dashboard-Punkt geschlossen,
|
||
Nr.-4-Vermerk, Hardening-Doc auf ABGESCHLOSSEN/Archiv), deckungsgleich mit
|
||
`ANTWORTEN_BERND.md` Nr. 3/4; repo-weites grep: keine Datei außerhalb des
|
||
historischen PROGRESS-Logs stellt die A/B-Frage noch als offen dar; kein
|
||
unprotokollierter Rest, keine `lib/`-Änderung seit dem letzten grünen
|
||
Testlauf. Konvergenz-Kriterium aus (u) erfüllt – Review-Stand (a)–(x)
|
||
vollständig. Details im PROGRESS-Eintrag „(x)" oben.
|
||
- [x] **(y) Liegengebliebenen (x)-Protokoll-Commit geprüft und nachgeholt (2026-07-05).**
|
||
Die (x)-Session brach vor ihrem Protokoll-Commit ab; der uncommittete Rest
|
||
war reine Doku (der (x)-Eintrag selbst + Hook-Zeilen). Kernaussagen
|
||
stichprobenhaft verifiziert (66bf54b-Umfang, keine `lib/`-Änderungen seit
|
||
925aafb, A/B-Frage überall geschlossen) und unverändert committet
|
||
(15001ae). Details im PROGRESS-Eintrag „(y)" oben.
|
||
- [x] **(z) (y)-Protokoll-Commit 725f03a gegengelesen – korrekt, KEIN Befund;
|
||
Ende-Kriterium der Kette geschärft (2026-07-05).** Diff ist exakt der
|
||
(y)-Eintrag + Checklisten-Häkchen + 2 Hook-Zeilen (kein Code); Kernaussagen
|
||
stichprobenhaft verifiziert (15001ae-Umfang PROGRESS +46/CHANGES +2,
|
||
`git log 925aafb..HEAD -- lib/ test/ pubspec.*` leer). **Ab jetzt gilt:**
|
||
Ein reiner, befundloser Protokoll-Commit einer Review-Session ist KEIN
|
||
neuer Review-Anlass mehr – künftige Sessions prüfen nur noch Commits mit
|
||
Code-/Doku-Substanz (alles außer dem eigenen Review-Protokoll), sonst
|
||
reviewt die Kette ewig ihr eigenes Protokoll. Details im
|
||
PROGRESS-Eintrag „(z)" oben.
|
||
- [x] **(aa) Uncommittete autopilot.sh-Umstellung auf EIN Konto geprüft und
|
||
committet – 1 Befund gefixt (2026-07-06).** Die Umstellung (Konto-Rotation
|
||
raus, Autopilot fest auf dem nordmann-Konto = Standard-Profil `~/.claude`;
|
||
Bernds Vorgabe laut Kommentar, Backup
|
||
`autopilot.sh.bak-20260706-vor-nordmann-pin`) lag uncommitted im
|
||
Arbeitsbaum. Geprüft: Rotation restlos entfernt (kein
|
||
`get_account`/`switch_account`/`JUST_SWITCHED`/`ACCOUNT_FILE`-Rest, repo-weit
|
||
gegrept), `apply_account` = `unset CLAUDE_CONFIG_DIR` deckt sich mit dem
|
||
(w)-Befund, `bash -n` sauber, Limit-Pfad wartet jetzt 20 min statt zu
|
||
wechseln. Am lebenden System belegt: tmux-Session „pyramid" am 2026-07-06
|
||
11:54 neu gestartet, autopilot.log zeigt das neue Format, DIESE Session
|
||
läuft als nordmann-Konto mit leerem `CLAUDE_CONFIG_DIR`. **Befund (vor dem
|
||
Commit gefixt):** Die neue Fassung enthielt Bernds private E-Mail-Adresse
|
||
(Kommentar + `ACCOUNT_NAME`) – das Gitea-Repo ist ÖFFENTLICH, und bisher
|
||
enthielt keine getrackte Datei diese Adresse; ein Commit hätte sie erstmals
|
||
veröffentlicht. Durch neutrales „nordmann" ersetzt (funktionsgleich, die
|
||
Adresse stand nur in Kommentar und Log-Zeile).
|
||
|
||
**Fazit:** Die Sonnet-5-Arbeit war handwerklich sauber; die zwei echten Lücken
|
||
(verlorene Kommentare, fehlender Soft-Logout-Guard) hatte die abgebrochene
|
||
Fable-5-Vorsession bereits gefixt – beide Fixes heute unabhängig verifiziert.
|
||
Alles weiterhin UNGETESTET (Pi) im Sinne von „nicht in echter UI gesehen";
|
||
der ausstehende PC-Praxistest (siehe Einträge unten) bleibt Pflicht.
|
||
|
||
---
|
||
|
||
## 2026-07-03 – M2: Gott-Datei `document_viewer.dart` aufgeteilt (5 Dateien)
|
||
|
||
**Erledigt:** Vor dem Weiterarbeiten wie immer Arbeitszyklus durchlaufen
|
||
(`PROGRESS.md`/`ROADMAP.md` gelesen, `flutter doctor` geprüft – GUI-Lücken
|
||
auf dem Pi unverändert). Die Ist-Analyse aus M2 hatte ursprünglich 8 große
|
||
Dateien gefunden, aber nur 5 wurden als „Gott-Dateien" tatsächlich
|
||
aufgeteilt (`settings_modal.dart`, `message_group.dart`, `rooms_panel.dart`,
|
||
`space_admin_dialog.dart`, `chat_view.dart`); `document_viewer.dart` (1773
|
||
Zeilen, 31 Klassen/Enums + 3 lose Top-Level-Helfer) war übrig, ist aber
|
||
inhaltlich genau derselbe Fall (viele unabhängige Widget-Klassen in einer
|
||
Datei) und – anders als `voice_channel.dart`/`app_shell.dart` – reines
|
||
Medien-/PDF-Anzeige-Feature ohne Berührung mit Login/Session/Verschlüsselung
|
||
oder der noch unentschiedenen Call-Fassade. Deshalb mit derselben
|
||
bewährten Technik aufgeteilt:
|
||
|
||
- `part`/`part of` wie bei den 5 vorigen Umbauten, per Skript extrahiert
|
||
(Klammer-Zählung für Blockgrenzen inkl. der 3 losen Top-Level-Funktionen
|
||
`_saveBytes`, `_extractPsdThumbnail`, `_detectType` – nicht nur
|
||
`class`/`enum`, das wurde diesmal zusätzlich automatisiert gesucht, um
|
||
keine lose Funktion zu übersehen).
|
||
- 5 neue Dateien unter `lib/features/chat/document/`:
|
||
`document_viewer_core.dart` (`DocumentViewer`-Dispatcher, `_DocHeader`,
|
||
`_ExtBadge`, `_DocContent`, `_DocType`-Enum + `_detectType`, `_ErrorView`,
|
||
`_UnsupportedViewer`, `_FileFallback`), `document_viewer_pdf.dart`
|
||
(größter Teil: `_RenderCache`, `PdfInlinePreview`+State für die
|
||
Chat-Inline-Vorschau, `_NavArrow`, `_saveBytes`, `_PdfFullViewer`+State,
|
||
`_PdfFullscreenViewer`+State, `_FsBtn`, `_ThumbnailStrip`+State),
|
||
`document_viewer_text.dart` (SVG/Markdown/Text/Office-Text-Extraktion),
|
||
`document_viewer_archive.dart` (ZIP/TAR-Archiv-Browser, PSD-Thumbnail-
|
||
Extraktion inkl. `_extractPsdThumbnail`, Sketch/XD-ZIP-Preview),
|
||
`document_viewer_misc.dart` (`_ProprietaryViewer`-Infokarte für nicht
|
||
darstellbare Formate). `document_viewer.dart` selbst: nur noch
|
||
Bibliothekskopf (Imports + 5 `part`-Direktiven, 20 Zeilen).
|
||
- Verifikation wie bei den 5 vorigen Umbauten: automatisierte
|
||
Klammer-Zählung fand alle 34 Top-Level-Blöcke (31 Klassen/Enums + 3 lose
|
||
Funktionen), alle wortwörtlich in den neuen Dateien wiedergefunden,
|
||
`class`/`enum`-Anzahl (31) vorher/nachher identisch, `flutter analyze`
|
||
unverändert bei 1 bekanntem Hinweis (`chat_provider.dart:42`). Einzige
|
||
externe Importstelle (`message_group.dart`) bleibt unverändert gültig, da
|
||
`document_viewer.dart` weiterhin der Bibliothekseinstieg ist.
|
||
- **Bewusst NICHT angefasst:** `voice_channel.dart` (1123 Zeilen) und
|
||
`app_shell.dart` (1073 Zeilen) – auch große Dateien mit vielen Klassen,
|
||
aber `voice_channel.dart` gehört zur noch unentschiedenen Call-Fassade
|
||
(siehe Fragen an Bernd unten) und `app_shell.dart` enthält den
|
||
Bootstrap-Trigger (`_triggerBootstrap`) für Cross-Signing/Key-Backup –
|
||
laut CLAUDE.md heiliger Bereich. Eine reine Datei-Aufteilung wäre zwar
|
||
vermutlich genauso risikoarm wie bei den anderen 6, aber ohne GUI-Test auf
|
||
dem Pi wollte ich das Risiko nicht zusätzlich zum ohnehin schon
|
||
aufgelaufenen UNGETESTET-Stapel erhöhen, bevor nicht wenigstens einmal ein
|
||
PC-Durchlauf stattgefunden hat.
|
||
|
||
**Offen/Nächster Schritt:** UNGETESTET (Pi) – siehe ROADMAP-Eintrag oben,
|
||
gehört zum selben ausstehenden Windows-Praxistest wie die 5 vorigen
|
||
Datei-Aufteilungen (PDF/Text/Archiv/Bild im Chat öffnen, Vollbild-PDF-Viewer
|
||
inkl. Thumbnail-Leiste und Speichern-Button prüfen). Danach bleibt exakt
|
||
dieselbe Blockade wie im Eintrag direkt darunter: kein M1/M2-Punkt mehr
|
||
ohne Bernds Antwort (Call-Pilot, SQLCipher-Priorität) oder PC/GUI-Zugriff
|
||
sicher bearbeitbar.
|
||
|
||
**Stolperfallen:** Anders als bei den 5 vorigen Umbauten gab es diesmal 3
|
||
lose Top-Level-Funktionen zusätzlich zu den Klassen/Enums
|
||
(`_saveBytes`/`_extractPsdThumbnail`/`_detectType`) – eine reine
|
||
`grep -E '^(class|enum) '`-Suche hätte sie übersehen und stillschweigend aus
|
||
der neuen Datei-Struktur fallen lassen. Deshalb diesmal zusätzlich per
|
||
Diff aller durch Klammer-Zählung abgedeckten vs. nicht abgedeckten
|
||
Zeilenbereiche geprüft, dass keine „Lücke" zwischen zwei Klassenblöcken
|
||
mehr als Kommentar/Leerzeile ist. Für künftige Datei-Aufteilungen (z. B.
|
||
`voice_channel.dart`/`app_shell.dart`, falls Bernd grünes Licht gibt): immer
|
||
zuerst auf lose Top-Level-Funktionen prüfen, nicht nur auf Klassen/Enums.
|
||
|
||
---
|
||
|
||
## 2026-07-03 – Session-Check: weiterhin kein sicher bearbeitbarer Punkt auf dem Pi
|
||
|
||
**Erledigt:** Vor dem Weiterarbeiten Arbeitszyklus komplett durchlaufen: `PROGRESS.md`
|
||
+ `ROADMAP.md` gelesen, `flutter analyze` (weiterhin sauber, nur der eine bekannte
|
||
Hinweis in `chat_provider.dart:42`), `git status` (sauber, `master` = `origin/master`),
|
||
`flutter doctor` erneut geprüft (Android-SDK, Chrome, Linux-Desktop-Toolchain fehlen
|
||
weiterhin auf diesem Pi – keine GUI möglich). Damit alle offenen Punkte in M1/M2
|
||
gegengeprüft:
|
||
- M1 „SQLCipher" und „Härtetest dokumentieren": unverändert PC-only (siehe Einträge
|
||
weiter unten), keine neue Information seit letzter Session.
|
||
- M2 „Call-Schicht entwirren": zum Einstieg `core/voip_manager.dart` (538 Zeilen)
|
||
gelesen, um eine Fassade zu entwerfen. Ergebnis: Das ist **kein** Datei-Split wie die
|
||
letzten 5 Gott-Datei-Umbauten (dort: reines `part`/`part of`, null Logikänderung,
|
||
automatisiert byte-genau verifizierbar). `PyramidVoipManager` ist ein Singleton
|
||
(`_instance`), der gleichzeitig `ChangeNotifier` UND die vom matrix-SDK verlangte
|
||
`WebRTCDelegate`-Schnittstelle implementiert, mit Timer-basiertem Renderer-Polling,
|
||
impliziten Reihenfolgen bei Mute/Unmute/Screenshare und einem `MediaDevicesWrapper`,
|
||
der direkt auf private Felder des Managers zugreift. Eine Fassade davor UND die
|
||
Umstellung der 7 Importstellen lässt sich nicht mehr rein mechanisch verifizieren –
|
||
echtes Risiko für Verhaltensänderung an Calls, die echte Nutzer (Uta) im Einsatz
|
||
haben. Genau das hatte die vorige Session bereits zweimal als „erste Stelle mit
|
||
echtem Verhaltensrisiko in M2" geflaggt und Bernds Entscheidung dazu angefragt
|
||
(siehe „Fragen an Bernd" weiter unten) – hier bestätigt und bewusst NICHT
|
||
eigenmächtig angefasst, weil auf diesem Pi ohnehin kein GUI-Test möglich ist, um
|
||
eine Regression aufzufangen.
|
||
|
||
**Offen/Nächster Schritt:** Es gibt aktuell **keinen** ROADMAP-Punkt in M1/M2, der auf
|
||
diesem Pi ohne (a) Bernds Antwort auf die Call-Pilot-Frage oder (b) echten PC/GUI-Zugriff
|
||
sicher bearbeitet werden kann. Nächster sinnvoller Schritt ist entweder ein PC-Termin
|
||
(Härtetest + SQLCipher-Migrationsplanung + Call-Fassade mit echtem Test) oder Bernds
|
||
kurze Antwort auf die unten stehende Frage, damit die nächste Pi-Session direkt starten
|
||
kann.
|
||
|
||
**Stolperfallen:** Keine neuen – Bestätigung, dass die Vorsicht der letzten Session
|
||
richtig war: „sieht aus wie ein weiterer Datei-Split" ist bei der Call-Schicht trügerisch,
|
||
weil Singleton-Lifecycle + SDK-Delegate-Vertrag + Timer-Polling nicht mit einem
|
||
Textvergleich absicherbar sind wie bei reinen `part`/`part of`-Umbauten.
|
||
|
||
**Fragen an Bernd:** Unverändert offen (wiederholt aus den letzten beiden Einträgen,
|
||
damit sie nicht übersehen wird):
|
||
1. Call-Pilot (Fassade für `voip_manager.dart`/`livekit_call_manager.dart`) jetzt
|
||
angehen (nur auf einem PC mit echtem Test), oder erstmal zurückstellen?
|
||
2. SQLCipher-Verschlüsselung der lokalen DB: Priorität vor M2, oder reicht die
|
||
App-Sandbox als Schutz vorerst?
|
||
Ohne eine dieser beiden Antworten bleibt der Pi bei jedem weiteren Durchlauf an
|
||
derselben Stelle stehen – ein kurzer PC-Termin für Härtetest + Call-Pilot würde
|
||
gleich mehrere blockierte Punkte auf einmal lösen.
|
||
|
||
---
|
||
|
||
## 2026-07-03 – M2: Gott-Datei `chat_view.dart` aufgeteilt (6 Dateien) – ROADMAP-Punkt abgehakt
|
||
|
||
**Erledigt:** Fünfter und letzter Durchgang derselben `part`/`part of`-Technik:
|
||
`features/chat/chat_view.dart` (2116 Zeilen, 22 Klassen, keine losen
|
||
Top-Level-Helfer) aufgeteilt in 6 Dateien unter `features/chat/view/`:
|
||
`chat_view_core.dart` (`ChatView`, `_ChatViewState` – der komplette
|
||
Nachrichten-Senden/Scroll/Attachment-Zustandsautomat, mit 521 Zeilen
|
||
weiterhin der größte Einzelblock, aber nicht weiter unterteilt, da es EINE
|
||
zusammenhängende State-Klasse ist, keine künstliche Aufspaltung mitten in
|
||
einer Klasse), `chat_header.dart` (`_ChatHeader`, DM-Header inkl.
|
||
`_DmExpandingHeader`+State, `_DmHeaderAvatar`, `_InitialCircle`,
|
||
`_PresenceLine`+State), `chat_connection.dart` (`_ConnectionBanner`+State,
|
||
Offline/Reconnect-Banner), `chat_message_list.dart` (`_MessageList`,
|
||
`_SelectionHeader`, `_SelBtn`, `_UnreadDivider`), `chat_typing.dart`
|
||
(`_TypingIndicator`+State, `_TypingDots`+State), `chat_misc.dart`
|
||
(`_DragOverlay`+State für Datei-Drag&Drop, `NoChatSelected` – bleibt
|
||
öffentlich, `app_shell.dart` importiert `chat_view.dart` und nutzt es).
|
||
`chat_view.dart` selbst: nur noch Bibliothekskopf (33 Zeilen).
|
||
|
||
Verifikation identisch zu den vier vorigen Umbauten: automatisierte
|
||
Klammer-Zählung, alle 22 Blöcke wortwörtlich wiedergefunden,
|
||
`class`/`enum`-Anzahl (22) und `Widget build(`-Methoden (15) vorher/nachher
|
||
identisch, `flutter analyze` unverändert bei 1 bekanntem Hinweis.
|
||
|
||
**Damit sind alle 5 in der M2-Ist-Analyse gefundenen Gott-Dateien aufgeteilt**
|
||
(`settings_modal.dart`, `message_group.dart`, `rooms_panel.dart`,
|
||
`space_admin_dialog.dart`, `chat_view.dart` – siehe die vier Einträge
|
||
darunter). ROADMAP-Punkt „Toten Code & Duplikate entfernen, Ordnerstruktur
|
||
vereinheitlichen" jetzt abgehakt.
|
||
|
||
**Offen/Nächster Schritt:** **Wichtig, bevor der nächste ROADMAP-Punkt
|
||
angefasst wird:** all diese Aufteilungen sind bisher NUR per `flutter
|
||
analyze` + automatisiertem Textvergleich geprüft, NICHT in echter UI
|
||
gesehen (kein GUI-Toolchain auf dem Pi verfügbar – kein Android-SDK, kein
|
||
Linux-Desktop-Build, siehe `flutter doctor`). Der nächste PC-/Windows-Lauf
|
||
MUSS vor dem nächsten inhaltlichen Schritt einmal durch alle 5 betroffenen
|
||
Bereiche klicken (Einstellungen komplett, ein Chat mit allen
|
||
Nachrichtentypen, die Raumliste, eine Space-Verwaltung, ein normaler
|
||
Chat-Screen mit Header/Tippanzeige/Drag&Drop) – das Risiko einer reinen
|
||
`part`/`part-of`-Verschiebung ist strukturell gering (Dart-Compiler hätte
|
||
jeden fehlenden Import/jede falsche Sichtbarkeit sofort als Analyzer-Fehler
|
||
gemeldet, nicht erst zur Laufzeit), aber „gering" ist nicht „null", und
|
||
CLAUDE.md verlangt nach jedem Refactoring-Schritt einen echten App-Start.
|
||
Danach nächster ROADMAP-Punkt in M2: „Call-Schicht entwirren" – dafür
|
||
weiterhin Bernds Entscheidung zum Call-Pilot aus „M2: Modul-Schnitt
|
||
definiert" ausstehend, UND dieser Punkt braucht ohnehin einen PC/Windows-Test
|
||
(Verhaltensänderung an State-Management, nicht nur Datei-Verschiebung).
|
||
|
||
**Stolperfallen:** Keine neuen. Generelle Lehre aus allen 5 Durchgängen:
|
||
Skript-gestützte Klammer-Zählung + automatisierter Verbatim-Abgleich
|
||
(„jeder Originalblock muss wortwörtlich in den neuen Dateien auftauchen")
|
||
war der entscheidende Sicherheitsnetz-Schritt, um trotz fehlendem GUI-Test
|
||
auf dem Pi mit vertretbarem Risiko fortzufahren – sollte bei künftigen
|
||
reinen Datei-Umbauten (z. B. `voice_channel.dart` beim Call-Pilot) als
|
||
Vorgehen wiederverwendet werden.
|
||
|
||
---
|
||
|
||
## 2026-07-03 – M2: Gott-Datei `space_admin_dialog.dart` aufgeteilt (6 Dateien)
|
||
|
||
**Erledigt:** Vierter Durchgang derselben `part`/`part of`-Technik:
|
||
`features/spaces/space_admin_dialog.dart` (2278 Zeilen, 32 Klassen/Enums +
|
||
6 lose Top-Level-Helfer) aufgeteilt in 6 Dateien unter
|
||
`features/spaces/admin/`: `space_admin_core.dart` (`SpaceAdminDialog`,
|
||
`_SpaceAdminDialogState`, `_DialogHeader` + die breit genutzten Helfer
|
||
`_kSpaceBannerEvent`, `updatePowerLevelsSafely` (öffentliche Funktion –
|
||
schützt vor Selbst-Aussperrung beim Ändern der Power-Levels, genutzt von
|
||
Members- UND Permissions-Tab), `_kPowerAdmin`/`_kPowerMod`/`_kPowerMember`,
|
||
`_powerLabel`, `_powerColor`), `space_admin_overview.dart` (`_OverviewTab`+State,
|
||
`_SpaceVisibilityToggle`+State, `_DangerZone`), `space_admin_members.dart`
|
||
(`_MembersTab`+State, `_InviteButton`+State, `_MemberTile`+State,
|
||
`_MemberActions`, `_MemberAction`), `space_admin_permissions.dart`
|
||
(`_PermissionsTab`+State, `_PermissionRow`), `space_admin_channels.dart`
|
||
(`_ChannelsTab`+State, `_ChannelItem`+State, `_CreateChannelDialog`+State,
|
||
`_AddExistingDialog`+State + die dort lokal genutzten Helfer `_kSpaceChild`/
|
||
`_kSpaceParent`/`_kVoiceChannelType`), `space_admin_shared.dart`
|
||
(`_SectionLabel`, `_Field`, `_SearchField`, `_AvatarEditor`, `_InitialAvatar`).
|
||
`space_admin_dialog.dart` selbst: nur noch Bibliothekskopf (19 Zeilen).
|
||
|
||
Auffällig beim Durchlesen (nicht angefasst, nur notiert): `updatePowerLevelsSafely`
|
||
ist als einzige Funktion in dieser Datei öffentlich (kein `_`-Präfix), wird
|
||
aber nirgends außerhalb von `space_admin_dialog.dart` importiert/aufgerufen –
|
||
vermutlich für einen mal geplanten, nie gebrauchten externen Aufrufer public
|
||
gemacht. Bewusst nicht auf privat zurückgestuft, um diesen Schritt nicht mit
|
||
einer inhaltlichen Änderung zu vermischen; wäre ein Kandidat für den nächsten
|
||
"toter Code"-Durchgang.
|
||
|
||
Verifikation wie bei den drei vorigen Umbauten: automatisierte
|
||
Klammer-Zählung für Blockgrenzen, alle 32 Blöcke + 4 Helfer-Gruppen
|
||
wortwörtlich wiedergefunden, `class`/`enum`-Anzahl (32) und
|
||
`Widget build(`-Methoden (20) vorher/nachher identisch, `flutter analyze`
|
||
unverändert bei 1 bekanntem Hinweis.
|
||
|
||
**Offen/Nächster Schritt:** UNGETESTET (Pi). Nächster Windows-Lauf: Space-
|
||
Verwaltung öffnen – Übersicht (Sichtbarkeit umschalten, Banner ändern,
|
||
Danger-Zone-Buttons NUR ansehen, nicht bestätigen), Mitglieder (einladen,
|
||
Rolle ändern, kicken/bannen – wenn möglich an einem Test-Space, nicht an
|
||
Utas echtem), Berechtigungen, Kanäle (erstellen, bestehenden Raum hinzufügen).
|
||
Danach `chat_view.dart` (2116 Zeilen) als letzte der 5 in der Ist-Analyse
|
||
gefundenen Gott-Dateien – damit wäre der komplette „Ordnerstruktur
|
||
vereinheitlichen"-Katalog aus M2 abgearbeitet (Häkchen dann setzen).
|
||
|
||
**Stolperfallen:** Keine neuen.
|
||
|
||
---
|
||
|
||
## 2026-07-03 – M2: Gott-Datei `rooms_panel.dart` aufgeteilt (8 Dateien)
|
||
|
||
**Erledigt:** Gleiche `part`/`part of`-Technik ein drittes Mal angewandt:
|
||
`features/rooms/rooms_panel.dart` (2555 Zeilen, 37 Klassen/Enums + 4 lose
|
||
Top-Level-Helfer) aufgeteilt in 8 Dateien unter `features/rooms/panel/`:
|
||
`rooms_panel_core.dart` (`RoomsPanel`, `_RoomsPanelState` + die
|
||
Top-Level-Helfer `_kVoiceRoomType`/`_kSpaceBannerType`/`_isVoiceRoom`/
|
||
`_compareSpaceChildren`, die von mehreren anderen Teilen genutzt werden –
|
||
deshalb bewusst NICHT in ein eigenes „shared"-File, sondern beim Hauptwidget
|
||
belassen, wo sie ursprünglich standen), `rooms_header.dart` (`_Header`,
|
||
`_MenuRow`, `_NotificationsButton`, `_FilterInput`, `_SpaceMenuItem`),
|
||
`rooms_space_invite.dart` (`_SpaceInviteView`+State), `rooms_space_list.dart`
|
||
(`_SpaceRoomsList`+State, `_JoinableRoom`+Item+State, `_CategoryHeader`+State),
|
||
`rooms_list.dart` (`_RoomsList`, `_SectionHeader`+State, `_RoomItem`+State,
|
||
`_DraggableRoomItem`+State), `rooms_voice.dart` (`_VoiceParticipantList`,
|
||
`_Initials`, `_VoiceBarBtn`+State), `rooms_invite_item.dart`
|
||
(`_InviteItem`+State), `rooms_shared.dart` (`_DmAvatar`+State,
|
||
`_InitialCircle`, `_RoomMenuBtn`+State, `_HoverAction`+State, `_UnreadBadge`).
|
||
`rooms_panel.dart` selbst: nur noch Bibliothekskopf (Imports + 8
|
||
`part`-Direktiven, 31 Zeilen).
|
||
|
||
Verifikation wie bei den beiden vorigen Umbauten: automatisierte
|
||
Klammer-Zählung für Blockgrenzen, alle 37 Blöcke + 4 Helfer wortwörtlich in
|
||
den neuen Dateien wiedergefunden, Anzahl `class`/`enum` (37) und
|
||
`Widget build(`-Methoden (22) vorher/nachher identisch, `flutter analyze`
|
||
unverändert bei 1 bekanntem Hinweis. `dart format` wieder bewusst nicht
|
||
gelaufen.
|
||
|
||
**Offen/Nächster Schritt:** UNGETESTET (Pi). Nächster Windows-Lauf: Raumliste
|
||
prüfen – Space wechseln, Raum beitreten/verlassen, Voice-Channel-Teilnehmerliste
|
||
in der Raumliste, Einladung annehmen/ablehnen, Drag&Drop zum Umsortieren,
|
||
Ungelesen-Badge, DM-Avatare. Danach `space_admin_dialog.dart` (2278 Zeilen)
|
||
oder `chat_view.dart` (2116 Zeilen) nach demselben Muster – damit wären alle
|
||
in der Ist-Analyse gefundenen Gott-Dateien abgearbeitet.
|
||
|
||
**Stolperfallen:** Keine neuen.
|
||
|
||
---
|
||
|
||
## 2026-07-03 – M2: Gott-Datei `message_group.dart` aufgeteilt (7 Dateien)
|
||
|
||
**Erledigt:** Gleiche Technik wie beim `settings_modal.dart`-Umbau direkt
|
||
darunter angewandt: `features/chat/message_group.dart` (2775 Zeilen, 41
|
||
Klassen/Enums + 5 lose Top-Level-Hilfsfunktionen) per Dart `part`/`part of`
|
||
in 7 thematische Dateien unter `features/chat/message/` aufgeteilt:
|
||
`message_group_core.dart` (`MessageGroup`, `_MessageGroupState`,
|
||
`_SendFailedRow`, `_MessageBody`, `_ContinuationMessage`(+State), inkl. der in
|
||
der letzten Session vereinheitlichten `_formatMessageTime`), `message_text.dart`
|
||
(`_MessageContent`, `_MatrixHtmlText`, `_PlainLinkText`),
|
||
`message_link_preview.dart` (`_LinkPreview`(+State/Data), `_ReplyPreview`),
|
||
`message_media.dart` (alle Medientypen: Bild/Audio/Video/Doc/Datei/proprietäre
|
||
Inline-Karten + die zugehörigen Top-Level-Helfer `_reservedImageSize`/
|
||
`_classifyMediaError`/`_checkedDownload`/`_mediaFallback` – mit 1000 Zeilen
|
||
weiterhin der größte Teil, aber thematisch klar abgegrenzt),
|
||
`message_reactions.dart`, `message_actions.dart` (Hover-Aktionsleiste),
|
||
`message_shared.dart` (`_Avatar`, `DateDivider` – bleibt öffentlich, da
|
||
`chat_view.dart` es importiert; über `part`/`part of` weiterhin ohne
|
||
Umbenennung nutzbar). `message_group.dart` selbst ist jetzt nur noch der
|
||
Bibliothekskopf (Imports + 7 `part`-Direktiven, 30 Zeilen).
|
||
|
||
Verifikation identisch zum Settings-Umbau: automatisierte Klammer-Zählung
|
||
für die Blockgrenzen (kein manuelles Abtippen), alle 41 Original-Blöcke +
|
||
5 Helfer wortwörtlich in den neuen Dateien wiedergefunden, Anzahl
|
||
`class`/`enum` (45) und `Widget build(`-Methoden (27) vorher/nachher
|
||
identisch, `flutter analyze` unverändert bei 1 bekanntem Hinweis. `dart format`
|
||
diesmal bewusst NICHT ausgeführt (siehe Stolperfalle im Eintrag darunter).
|
||
|
||
**Offen/Nächster Schritt:** UNGETESTET (Pi). Nächster Windows-Lauf: einen
|
||
Chat mit mehreren Nachrichtentypen öffnen (Text mit Link-Vorschau, Bild,
|
||
Video, Audio, Datei-Anhang, Reaktionen setzen, Hover-Aktionsleiste,
|
||
Datums-Trenner, gescheiterte Nachricht mit Retry) – rein mechanische
|
||
Verschiebung, aber `message_media.dart` ist der bisher am wenigsten oft
|
||
geprüfte Bereich (Download/Entschlüsselungs-Fehlerpfade), daher dort genauer
|
||
hinschauen. Danach ggf. `rooms_panel.dart` (2555 Zeilen) oder
|
||
`space_admin_dialog.dart` (2278 Zeilen) nach demselben Muster aufteilen.
|
||
|
||
**Stolperfallen:** Keine neuen – siehe `dart format`-Hinweis im
|
||
`settings_modal.dart`-Eintrag darunter, hier von vornherein vermieden.
|
||
|
||
---
|
||
|
||
## 2026-07-03 – M2: Gott-Datei `settings_modal.dart` aufgeteilt (12 Dateien)
|
||
|
||
**Erledigt:** `widgets/settings_modal.dart` (5541 Zeilen, 50+ Klassen) war laut
|
||
Ist-Analyse die größte „Gott-Datei". „Call-Schicht entwirren" (nächster
|
||
ROADMAP-Punkt) bleibt weiter blockiert, da es dabei um Verhaltens-/State-
|
||
Management-Änderungen an einem CLAUDE.md-„heiligen" Bereich (Calls) geht, die
|
||
laut Arbeitsregeln nach jedem Schritt einen echten App-Test brauchen –
|
||
auf dem Pi ohne GUI-Toolchain (kein Android-SDK, kein Linux-Desktop-Build,
|
||
siehe `flutter doctor`) unmöglich zu prüfen. Stattdessen den in
|
||
`docs/M2_MODULE_SCHNITT.md` als zweiten (risikoärmeren) Schritt vorgeschlagenen
|
||
Umbau vorgezogen: reine Datei-Aufteilung ohne Verhaltensänderung, komplett über
|
||
`flutter analyze` prüfbar.
|
||
|
||
- Technik: Dart `part`/`part of` statt eigenständiger Libraries – dadurch
|
||
bleiben alle privaten Klassen (`_ProfileSection`, `_SettingsGroup`, …) ohne
|
||
Umbenennung nutzbar, und alle Imports bleiben zentral in `settings_modal.dart`
|
||
(Part-Dateien brauchen keine eigenen Imports). Kein Klassenname geändert,
|
||
kein Zeichen Logik angefasst.
|
||
- 12 neue Dateien unter `lib/widgets/settings/`: `settings_shared.dart`
|
||
(gemeinsame Low-Level-Widgets wie `_SettingsGroup`/`_Toggle`/`_Divider`),
|
||
`settings_profile.dart`, `settings_notifications.dart`,
|
||
`settings_appearance.dart`, `settings_voice.dart`, `settings_keybinds.dart`
|
||
(Tastaturkürzel-Anzeige, NICHT zu verwechseln mit Verschlüsselungs-Keys),
|
||
`settings_privacy.dart`, `settings_sessions.dart`, `settings_verification.dart`
|
||
(SAS/QR-Dialog), `settings_encryption.dart` (inkl. der Megolm-Export/Import-
|
||
Kryptofunktionen `_runPbkdf2`/`_aesCtr`/`_encryptMegolmKeys`/`_decryptMegolmKeys`,
|
||
die bisher lose im Datei-Kopf lagen), `settings_account.dart` (Passwort ändern/
|
||
Account löschen), `settings_about.dart`.
|
||
- `settings_modal.dart` selbst: von 5541 auf 270 Zeilen geschrumpft – enthält
|
||
nur noch Imports, die 12 `part`-Direktiven, `SettingsModal` und
|
||
`_SettingsModalState` (Navigation/Layout).
|
||
- Verifikation (da kein GUI-Test möglich): Skript-gestützte Extraktion per
|
||
Klammer-Zählung (kein manuelles Copy-Paste, um Übertragungsfehler
|
||
auszuschließen); anschließend automatisiert geprüft, dass alle 61
|
||
ursprünglichen Klassen/Enums wortwörtlich (`in`-Substring-Check) in den neuen
|
||
Dateien wieder auftauchen; Anzahl `class`/`enum`-Deklarationen (60) und
|
||
`Widget build(`-Methoden (40) vorher/nachher identisch; alle Lücken zwischen
|
||
den Klassenblöcken im Original einzeln geprüft – ausschließlich Leerzeilen
|
||
und dekorative Trennkommentare, kein Code verloren.
|
||
- `flutter analyze`: weiterhin nur der eine bekannte, zurückgestellte Hinweis
|
||
(`chat_provider.dart:42`).
|
||
- **Stolperfalle vermieden:** `dart format` versuchte 40 neue Lint-Hinweise
|
||
einzuführen (`curly_braces_in_flow_control_structures`) – Ursache: die
|
||
Originaldatei war selbst nie komplett `dart format`-clean (bestätigt per
|
||
`dart format --set-exit-if-changed` auf dem Original-Git-Stand), und Reflow
|
||
einzelner langer Einzeiler-`if`s ohne Klammern erzeugte neue Zeilenumbrüche,
|
||
die der Linter anders bewertet als die Originalformatierung. Formatierung
|
||
komplett verworfen und die Dateien unformatiert aus dem Original übernommen,
|
||
um „keine Verhaltensänderung ohne Not" strikt einzuhalten und die
|
||
`flutter analyze`-Baseline (1 Hinweis) nicht zu verwässern.
|
||
|
||
**Offen/Nächster Schritt:** UNGETESTET (Pi) im Sinne von „nicht in echter UI
|
||
gesehen". Nächster Windows-Lauf: Einstellungen öffnen, durch alle Reiter
|
||
klicken (Profil, Benachrichtigungen, Erscheinungsbild, Voice, Tastaturkürzel,
|
||
Privatsphäre, Sessions, Verschlüsselung inkl. SAS/QR-Verifizierung und
|
||
Recovery-Key-Anzeige/-Import, Passwort ändern, Account löschen-Dialog öffnen
|
||
(ohne zu bestätigen!), Über). Da rein mechanisch verschoben, ist das Risiko
|
||
gering, aber Verschlüsselung/Recovery-Key ist laut CLAUDE.md heilig – bitte
|
||
gezielt den Recovery-Key-Anzeige- und Passphrase-Export/Import-Flow einmal
|
||
durchklicken. Danach ROADMAP-Häkchen für „Ordnerstruktur vereinheitlichen"
|
||
erst setzen, wenn auch die übrigen Gott-Dateien (`message_group.dart`,
|
||
`rooms_panel.dart`, `space_admin_dialog.dart`, `chat_view.dart`,
|
||
`document_viewer.dart`) angegangen sind – aktuell nur `settings_modal.dart`
|
||
erledigt.
|
||
|
||
**Stolperfallen:** `dart format` NICHT blind nach einem Refactoring laufen
|
||
lassen, wenn die Baseline vorher nicht schon formatter-clean war – kann neue,
|
||
unrelated Lint-Hinweise einführen und die vermeintlich reine Verschiebung mit
|
||
Formatierungsrauschen vermischen (siehe oben).
|
||
|
||
**Fragen an Bernd:** Keine neuen – die offene Frage zum Call-Pilot aus dem
|
||
Eintrag „M2: Modul-Schnitt definiert" ist weiterhin unbeantwortet, blockiert
|
||
aber nicht (siehe dort).
|
||
|
||
---
|
||
|
||
## 2026-07-03 – M2: Toten Code & Duplikate entfernt (Chat-Timeline)
|
||
|
||
**Erledigt:** Vorgriff auf den ROADMAP-Punkt „Toten Code & Duplikate entfernen"
|
||
(technisch der 5. Punkt in M2, aber risikofrei und ohne GUI testbar – im
|
||
Gegensatz zum aktuell blockierten „Call-Schicht entwirren", siehe Frage an
|
||
Bernd im Eintrag darunter):
|
||
- `lib/features/chat/message_bubble.dart` (178 Zeilen) gelöscht – vollständig
|
||
toter Code, wurde nirgends importiert/instanziiert (durch `message_group.dart`
|
||
abgelöst), per `grep` doppelt bestätigt.
|
||
- Die zwei identischen `_formatTime`-Methoden in `message_group.dart`
|
||
(`_MessageGroupState` Zeile 322, `_ContinuationMessageState` Zeile 475 –
|
||
Vorher-Zeilennummern) zu einer gemeinsamen Top-Level-Funktion
|
||
`_formatMessageTime()` zusammengeführt, beide Aufrufstellen umgestellt.
|
||
Reine Extraktion, keine Verhaltensänderung (identischer Code vorher/nachher).
|
||
`pinned_messages_panel.dart:_formatTime` bewusst NICHT angefasst – hat
|
||
andere Logik (relatives Datum „vor Xd"), ist kein echtes Duplikat.
|
||
- `flutter analyze` weiterhin sauber: nur der eine bekannte, bewusst
|
||
zurückgestellte Hinweis (`chat_provider.dart:42`, `Timeline.onUpdate`
|
||
deprecated, hängt am E2EE-Redecrypt-Pfad, siehe M0-Eintrag).
|
||
|
||
**Offen/Nächster Schritt:** UNGETESTET (Pi) im Sinne von „nicht in echter UI
|
||
gesehen" – kein GUI auf diesem Pi verfügbar. Da es sich aber um reine
|
||
Code-Verschiebung ohne Logikänderung handelt (identischer Funktionskörper),
|
||
ist das Risiko gering; trotzdem beim nächsten Windows-Lauf kurz einen
|
||
Chat mit mehreren aufeinanderfolgenden Nachrichten (Zeit-Anzeige) und eine
|
||
Fortsetzungsnachricht (Hover zeigt Uhrzeit) ansehen.
|
||
|
||
**Stolperfallen:** Keine.
|
||
|
||
---
|
||
|
||
## 2026-07-03 – M2: Modul-Schnitt definiert (docs/M2_MODULE_SCHNITT.md)
|
||
|
||
**Erledigt:** Ausgehend von der Ist-Analyse (Eintrag darunter) die konkreten
|
||
Modul-Grenzen + öffentliche Schnittstellen entworfen, dokumentiert in
|
||
`docs/M2_MODULE_SCHNITT.md`. Reine Planung, kein Code geändert. Wichtigste
|
||
Erkenntnis beim genaueren Lesen von `voip_manager.dart` und
|
||
`livekit_call_manager.dart`: „Calls" ist architektonisch **kein einzelnes
|
||
Feature**, sondern zwei unabhängige Mechanismen, die getrennt bleiben sollten:
|
||
- `PyramidVoipManager` = natives Matrix-1:1-VoIP (`m.call.*`, direkte
|
||
WebRTC-Peer-Connection, kein SFU)
|
||
- `LiveKitCallManager` = SFU-basierte Voice-Channels (LiveKit-Raum, Presence
|
||
via Matrix-State-Event, Screensharing, Mehrpersonen) – das ist der
|
||
Discord-artige Kanal aus M3/M4
|
||
|
||
Modul-Liste im Dokument: `call_signaling`, `voice_channel`, `call_ui`,
|
||
`settings` (aufgesplittet in Sektionsdateien), `verification` (neu, aus
|
||
Settings herausgelöst – SAS/QR/Recovery-Key), `storage` (Ausbau der
|
||
bestehenden `settings_prefs.dart`-Abstraktion zur Pflicht), `auth` (schon
|
||
relativ sauber, kein akuter Bedarf), `push` (schon getrennt, eigener
|
||
`sqflite`-Zugriff in `background_push.dart` ist bewusst so, kein
|
||
Refactoring-Kandidat), `chat_timeline`/`rooms` (niedrigste Priorität, da
|
||
strukturell am wenigsten verwoben – Hauptproblem dort ist Dateigröße/Duplikate,
|
||
nicht Kopplung). Jeweils mit Dart-Interface-Skizze bzw. Datei-Zielliste.
|
||
|
||
Empfohlene Umsetzungsreihenfolge: 1) Call-Pilot (`call_signaling` +
|
||
`voice_channel` + `call_ui`, deckt gleich zwei ROADMAP-Punkte ab), 2)
|
||
`settings_modal.dart` aufsplitten (geringes Risiko, reine Datei-Teilung),
|
||
3) `verification` herauslösen, 4) `storage`-Fassade schrittweise erzwingen,
|
||
5) `chat_timeline`/`rooms` zuletzt.
|
||
|
||
**Offen/Nächster Schritt:** Wartet auf Bernds Entscheidung, ob der Call-Pilot
|
||
wie vorgeschlagen als Nächstes umgesetzt wird (siehe Frage unten). Erst danach
|
||
den ROADMAP-Punkt „Call-Schicht entwirren" wirklich anfassen – das ist die
|
||
erste Stelle mit echtem Verhaltensrisiko in M2 (CLAUDE.md: nach Refactoring-
|
||
Schritten immer App starten + Kernflows prüfen, hier zusätzlich brisant, weil
|
||
GUI-Test auf dem Pi nicht möglich ist).
|
||
|
||
**Stolperfallen:** Der erste Ist-Analyse-Durchgang (Subagent) hatte einen
|
||
falschen Befund (`rooms_provider.dart`-Import in `app_state.dart` sei toter
|
||
Code) – beim genaueren Lesen für den Modul-Schnitt widerlegt
|
||
(`voiceParticipantsProvider` nutzt `roomListProvider` daraus). Bereits im
|
||
Ist-Analyse-Eintrag korrigiert. Lehre: Subagent-Ist-Analysen sind ein guter
|
||
Startpunkt, aber vor jedem darauf aufbauenden Schritt einzelne Befunde
|
||
gegenlesen, nicht blind übernehmen.
|
||
|
||
**Fragen an Bernd:**
|
||
- Call-Pilot wie in `docs/M2_MODULE_SCHNITT.md` vorgeschlagen (zuerst
|
||
`call_signaling`/`voice_channel`/`call_ui` entwirren) als nächster
|
||
Umsetzungsschritt in Ordnung, oder lieber zuerst `settings_modal.dart`
|
||
aufsplitten (geringeres Risiko, aber deckt keinen ROADMAP-Punkt aus M2
|
||
direkt ab)? Ohne GUI-Zugriff auf dem Pi kann der riskantere Call-Umbau
|
||
hier ohnehin nicht praktisch getestet werden – das spricht eher für
|
||
„auf dem PC anfangen", falls Zeitdruck besteht.
|
||
|
||
---
|
||
|
||
## 2026-07-03 – M2: Ist-Analyse der Architektur geschrieben
|
||
|
||
**Erledigt:** M1 hat aktuell keinen auf dem Pi bearbeitbaren Punkt mehr offen
|
||
(SQLCipher wartet auf Bernds Priorisierung, Härtetest braucht GUI/PC – siehe
|
||
Fragen an Bernd unten und Eintrag 2026-07-03 „3 schon vorhanden, 1 echte
|
||
Lücke"). Deshalb mit M2 weitergemacht, erster Punkt: Ist-Analyse. Per
|
||
Code-Lektüre (Zeilenzahlen, Import-Graph, grep) folgende Schwachstellen
|
||
gefunden:
|
||
|
||
- **Gott-Dateien:** `widgets/settings_modal.dart` mit 5541 Zeilen / 50+ Klassen
|
||
bündelt mindestens 8 unabhängige Bereiche (Profil, Benachrichtigungen,
|
||
Voice/TURN, E2EE-Verifizierung inkl. SAS/QR, Sessions, Account, Erscheinungsbild,
|
||
Updates). Weitere große Dateien: `message_group.dart` (2780), `rooms_panel.dart`
|
||
(2555), `space_admin_dialog.dart` (2278), `chat_view.dart` (2116),
|
||
`document_viewer.dart` (1773), `voice_channel.dart` (1123), `app_shell.dart` (1073).
|
||
- **Call-Schicht unsauber verteilt statt hinter einer Schnittstelle:**
|
||
`core/voip_manager.dart` (Matrix-VoIP-Signalisierung), `core/livekit_call_manager.dart`
|
||
(LiveKit-Transport), `features/call/voice_channel.dart` + `mini_call_widget.dart`
|
||
(UI) – aber `chat_view.dart`, `matrix_client.dart`, `settings_modal.dart` greifen
|
||
alle DIREKT auf die Manager zu statt über eine gemeinsame Call-Fassade. Vier
|
||
verschiedene Module importieren `voice_channel.dart` direkt.
|
||
Genau das Gegenteil des „austauschbare Bausteine"-Ziels aus CLAUDE.md.
|
||
- **State-Management gemischt:** 2 zentrale `ChangeNotifier`-Singletons
|
||
(`voip_manager.dart`, `livekit_call_manager.dart`) statt Riverpod-`Notifier`,
|
||
eingebunden über `app_state.dart` (`callStateProvider`, `voipStateProvider`).
|
||
Migration sinnvoll, aber riskant wegen breiter Call-Sites und enger
|
||
SDK-Callback-Kopplung (LiveKit-`Room`-Events, Matrix-`WebRTCDelegate`) – sollte
|
||
zusammen mit der Call-Fassade angegangen werden, nicht isoliert.
|
||
- **`core/` ist nicht rein generisch:** importiert Feature-Code direkt
|
||
(`app.dart`/`router.dart` → `features/auth/*`, `matrix_client.dart` instanziiert
|
||
`PyramidVoipManager` direkt). `app_state.dart` selbst ist ein Sammelbecken für
|
||
Theme-, Call-, Voice-, Share-Target- und Spaces-Provider aus praktisch allen
|
||
Features – zentraler Kopplungspunkt, der jedem Modulschnitt im Weg steht.
|
||
(Korrektur beim genaueren Hinsehen für den Modul-Schnitt-Punkt: der
|
||
`rooms_provider.dart`-Import dort ist entgegen der ersten Vermutung KEIN
|
||
toter Altlast-Import – `voiceParticipantsProvider` nutzt `roomListProvider`
|
||
daraus. Zeigt: Subagent-Befunde vor dem Weiterbauen selbst nachprüfen.)
|
||
- **Keine gemeinsame Storage-Schnittstelle:** `SharedPreferences.getInstance()`
|
||
wird an 12 Stellen direkt aufgerufen, obwohl `core/settings_prefs.dart` bereits
|
||
eine `StateNotifier`-Abstraktion (`BoolPref`, `StringSetPref`) anbietet – wird
|
||
meist umgangen. `sqflite` wird zusätzlich unabhängig in `background_push.dart`
|
||
nochmal geöffnet (eigene DB-Instanz fürs Background-Isolate). `media_cache.dart`
|
||
ist dagegen sauber gekapselt – gutes Vorbild für die anderen Speicherzugriffe.
|
||
- **Toter Code bestätigt:** `features/chat/message_bubble.dart` (178 Zeilen,
|
||
Klasse `MessageBubble`) wird nirgends instanziiert – von `message_group.dart`
|
||
abgelöst, kann beim Refactoring entfernt werden.
|
||
- **Duplikate:** `_formatTime`/`_formatDate` unabhängig implementiert in
|
||
`pinned_messages_panel.dart:206`, `message_group.dart:322` UND `:475` (zwei Mal
|
||
im selben File!), sowie im toten `message_bubble.dart:80/165`.
|
||
|
||
**Modul-Kandidaten für den nächsten Punkt („Modul-Schnitt definieren"):**
|
||
1. `call_signaling` (Matrix-VoIP) – `core/voip_manager.dart`
|
||
2. `call_transport` (LiveKit) – `core/livekit_call_manager.dart`
|
||
3. `call_ui` (nur UI, konsumiert 1+2 über Interface) – `features/call/*`
|
||
4. `settings/*` aufsplitten: profile, encryption_verification, sessions,
|
||
notifications, appearance, account – aus `widgets/settings_modal.dart`
|
||
5. `verification` als eigenständiges Feature (SAS/QR/Recovery-Key), nicht
|
||
Teil von Settings – wiederverwendbar (z. B. künftig beim Login)
|
||
6. `storage` – gemeinsame Fassade, `settings_prefs.dart` als Pflichtzugang
|
||
statt 12 Direktzugriffsstellen
|
||
7. `app_state.dart` auflösen in feature-lokale Provider-Dateien
|
||
8. `chat_timeline` – `message_group.dart` + `pinned_messages_panel.dart`,
|
||
dabei toten Code (`message_bubble.dart`) entfernen, `_formatTime`/`_formatDate`
|
||
in `chat/format_utils.dart` zusammenführen
|
||
|
||
**Offen/Nächster Schritt:** M2, zweiter Punkt – „Modul-Schnitt definieren":
|
||
aus der obigen Kandidatenliste konkrete Modul-Interfaces (Dart-Abstract-Classes
|
||
o. ä.) entwerfen, dann EIN Modul zuerst umbauen (Vorschlag: `call_*`, da am
|
||
klarsten abgegrenzt und am dringendsten laut CLAUDE.md-Leitprinzip) als Pilot,
|
||
bevor der Rest folgt. Kein Codeumbau in diesem Schritt, nur Schnittstellen-Design.
|
||
|
||
**Stolperfallen:** Keine – reine Lesearbeit, kein Risiko für Bestandsdaten.
|
||
Analyse wurde von einem Explore-Subagent erstellt und stichprobenartig
|
||
plausibilisiert (Zeilenzahlen/Imports selbst nachgezählt), nicht jede
|
||
Einzelbehauptung im Detail nachverifiziert.
|
||
|
||
**Fragen an Bernd:**
|
||
- SQLCipher-Priorität weiterhin offen (siehe Eintrag „3 schon vorhanden, 1 echte
|
||
Lücke" weiter unten) – blockiert nicht, aber ohne Antwort bleibt der M1-Punkt
|
||
bis zum nächsten PC-Praxistest liegen.
|
||
- Für M2: Soll ich die Call-Schicht als erstes Pilot-Modul umbauen (mein
|
||
Vorschlag oben), oder hast du eine andere Präferenz (z. B. erst die
|
||
`settings_modal.dart` aufsplitten, weil die am unübersichtlichsten ist)?
|
||
|
||
---
|
||
|
||
## 2026-07-03 – M1: Restliche Punkte geprüft – 3 schon vorhanden, 1 echte Lücke gefunden
|
||
|
||
**Erledigt:**
|
||
- **Logout-Warnung bei fehlendem Key-Backup** (Commit b5762ea): neue Funktion
|
||
`isKeyBackupMissing(client)` in `bootstrap_dialog.dart` (prüft Cross-Signing +
|
||
`keyManager.isCached()`). Beide Logout-Dialoge in `settings_modal.dart` zeigen
|
||
jetzt eine rote Klartext-Warnung statt des neutralen Textes, wenn kein Backup
|
||
eingerichtet ist.
|
||
- **Drei ROADMAP-Punkte waren bereits umgesetzt** (nur nicht abgehakt) – durch
|
||
Code-Lektüre verifiziert, nicht neu gebaut:
|
||
- Key-Backup einrichten/wiederherstellen: `bootstrap_dialog.dart` deckt beides
|
||
über den `Bootstrap`-Zustandsautomaten des matrix-SDK ab (`askNewSsss` für
|
||
Neueinrichtung, `askUnlockSsss` fragt auf einem neuen Gerät nach dem
|
||
Recovery-Key). Wird automatisch getriggert, wenn Cross-Signing fehlt
|
||
(`app_shell.dart:_triggerBootstrap`).
|
||
- Geräte-/Sessionverwaltung: `_SessionsSection` in `settings_modal.dart` –
|
||
Liste, Umbenennen, SAS/QR-Geräteverifizierung, Einzel- und
|
||
Massen-Abmeldung (mit Passwort-Reauth bei UIA-Anforderung).
|
||
- **Echte Sicherheitslücke gefunden:** `sqlcipher_flutter_libs` ist eine
|
||
Abhängigkeit in `pubspec.yaml`, wird aber **nirgends im Code benutzt**.
|
||
`matrix_client.dart` (Haupt-App) und `background_push.dart` (Push-Hintergrund)
|
||
öffnen `pyramid.sqlite` beide über den normalen, unverschlüsselten
|
||
`sqflite`/`sqflite_common_ffi`-Factory. Die Krypto-DB (Access-Token,
|
||
gepickelter Olm-Account, Megolm-Sessions, komplette Nachrichtenhistorie)
|
||
liegt also **im Klartext** auf der Platte – nur durch Androids App-Sandbox
|
||
geschützt, nicht durch eine eigene Verschlüsselung.
|
||
|
||
**Offen/Nächster Schritt:** SQLCipher-Anbindung NICHT blind auf dem Pi
|
||
umgesetzt – siehe Stolperfalle unten. Braucht einen eigenen, vorsichtig
|
||
geplanten PC-Termin: Backup der Test-DB, Migrationscode (bestehende
|
||
Klartext-DB einmalig nach SQLCipher überführen), Test auf einem Gerät mit
|
||
echten Daten, erst dann Rollout.
|
||
|
||
**Fragen an Bernd:**
|
||
- Priorität der SQLCipher-Lücke: Soll das vor M2 (Refactoring) angegangen
|
||
werden, oder reicht die App-Sandbox als Schutz erstmal aus (Angreifer bräuchte
|
||
Root oder physischen Zugriff + ADB, um die Datei überhaupt zu lesen)? Die
|
||
Migration bestehender Installationen (Uta!) ist der aufwändige/riskante Teil,
|
||
nicht die Verschlüsselung selbst.
|
||
|
||
**Stolperfallen:**
|
||
- Eine DB-Verschlüsselungs-Migration ist genau die Art Änderung, die laut
|
||
CLAUDE.md nicht "mal eben" auf dem Pi ohne Testgerät gemacht werden darf:
|
||
scheitert die Migration (falscher Schlüssel, falsche PRAGMA-Reihenfolge,
|
||
Timing zwischen Haupt-App und Push-Hintergrund-Client), sind Utas
|
||
Nachrichten und Krypto-Schlüssel unwiederbringlich weg. Deshalb hier nur
|
||
dokumentiert, nicht implementiert.
|
||
|
||
---
|
||
|
||
## 2026-07-03 – M1: Push-Regression vermutlich dieselbe Ursache wie Random-Logout
|
||
|
||
**Erledigt:** Vor dem Blick in den Push-Code die Vermutung geprüft, ob die
|
||
Push-Entschlüsselungs-Regression und der Uta-Random-Logout-Bug (siehe Eintrag
|
||
weiter unten) zusammenhängen – Ergebnis: **sehr wahrscheinlich ja, exakt
|
||
dieselbe Ursache.**
|
||
|
||
- `client.getEventByPushNotification(...)` (aufgerufen in `_bgDecryptAndShow`,
|
||
`background_push.dart:84`) ruft als ALLERERSTES `ensureNotSoftLoggedOut()`
|
||
auf (matrix-Paket `client.dart:1878`). Diese Funktion prüft, ob der
|
||
Access-Token in < 1 Minute abläuft, und löst dann `_handleSoftLogout()` aus
|
||
— **dieselbe Funktion**, die ohne Refresh-Token (unser Zustand vor Commit
|
||
9dc83f7) sofort einen echten `client.logout()` auslöst (siehe Eintrag unten).
|
||
- Der Hintergrund-Client aus `_buildClient()` liest **dieselbe** `pyramid.sqlite`
|
||
wie der Haupt-Client. Ein `logout()`/`clear()` aus dem Hintergrund-Isolat
|
||
würde also die komplette Session (inkl. Krypto-Schlüssel) für die GESAMTE
|
||
App zerstören – nicht nur für den einen Push. Das erklärt, warum das Problem
|
||
als dauerhafte Regression erscheint ("seit dem letzten Update nie wieder"):
|
||
Nach dem ersten Auto-Logout-Ereignis (ausgelöst beim Sync ODER beim
|
||
Entschlüsseln eines Pushs) sind die Schlüssel weg, jede folgende
|
||
Entschlüsselung schlägt fehl → `BgEngine` zeigt nur noch den
|
||
„Neue Nachricht"-Platzhalter (Fallback nach 6 s), nie mehr den echten Text.
|
||
- Der bereits committete Fix (`refreshToken: true` beim Login, 9dc83f7) behebt
|
||
damit vermutlich BEIDE Symptome gleichzeitig: der Refresh-Token liegt in der
|
||
gemeinsamen DB, also kann auch der Hintergrund-Client ihn lesen und einen
|
||
drohenden Soft-Logout durch stillen Refresh abwenden, statt die Session zu
|
||
zerstören.
|
||
|
||
**Offen/Nächster Schritt:** Gehört zum selben Praxistest wie der
|
||
Random-Logout-Punkt oben – zusätzlich beobachten, ob nach einer Weile
|
||
(mehrere Stunden/Tage) echte Push-Inhalte statt Platzhalter ankommen. Schlägt
|
||
das fehl, liegt eine zweite, unabhängige Ursache vor (dann `docs/NOTIFICATIONS.md`
|
||
+ Memory „Pyramid Push" erneut konsultieren, insbesondere die 3 dort gelisteten
|
||
Fallstricke der Hintergrund-Entschlüsselung).
|
||
|
||
**Stolperfallen:** Diese Verbindung wurde rein durch Lesen des matrix-SDK-
|
||
Quellcodes hergeleitet (`~/.pub-cache/hosted/pub.dev/matrix-6.2.0/lib/src/client.dart`),
|
||
nicht durch Beobachtung auf einem echten Gerät – bewusst als Vermutung
|
||
markiert, bis der PC-Praxistest sie bestätigt.
|
||
|
||
---
|
||
|
||
## 2026-07-03 – M1: Uta-Random-Logout – Ursache gefunden + Fix (UNGETESTET/Pi)
|
||
|
||
**Erledigt:**
|
||
- Alle Logout-Auslöser im Code kartiert:
|
||
- 3 bewusste Nutzer-Logout-Buttons in `settings_modal.dart` (alle hinter
|
||
Bestätigungsdialog) – kein Kandidat für "random"
|
||
- Der eigentliche Verdächtige: `matrix`-Paket (v6.2.0) `Client._innerSync()`
|
||
behandelt `M_UNKNOWN_TOKEN` vom `/sync`-Endpunkt so: bei `soft_logout: true`
|
||
→ `_handleSoftLogout()` → versucht `refreshAccessToken()` → **das SDK
|
||
hatte nie einen Refresh-Token angefordert** (`client.login(...)` in
|
||
`login_notifier.dart` rief ohne `refreshToken: true` auf) → `refreshAccessToken()`
|
||
wirft sofort "No refresh token available" → Catch-Block ruft `await logout()`
|
||
auf: ein **echter, destruktiver Logout**, ausgelöst rein durch einen normalen
|
||
Soft-Logout (z. B. abgelaufenes Access-Token), ganz ohne Nutzerinteraktion.
|
||
Quelle: `~/.pub-cache/hosted/pub.dev/matrix-6.2.0/lib/src/client.dart:2374-2384`
|
||
und `:2496-2509`.
|
||
- **Logging (Commit 63e4919):** neue `lib/core/auth_log.dart` schreibt jeden
|
||
Login-Status-Wechsel (`client.onLoginStateChanged`) sowie SDK-Warnungen/-Fehler
|
||
(`Logs().onLog`) in `auth_log.txt` im App-Support-Verzeichnis – übersteht
|
||
App-Neustart. Zusätzlich loggen alle 3 Nutzer-Logout-Buttons sich selbst, damit
|
||
sich künftig unterscheiden lässt: Nutzer-Logout vs. SDK-Auto-Logout. Reine
|
||
Zusatzfunktion, keine Verhaltensänderung.
|
||
- **Fix (Commit 9dc83f7):** `client.login(...)` fordert jetzt `refreshToken: true`
|
||
an. Damit kann das SDK einen Soft-Logout künftig durch stillen Token-Refresh
|
||
überstehen, statt die Sitzung zu zerstören. Unterstützt der Server keine
|
||
Refresh-Tokens, bleibt das Verhalten exakt wie vorher (`response.refreshToken`
|
||
ist dann `null`) – der Fix kann also nichts verschlimmern.
|
||
|
||
**Offen/Nächster Schritt:**
|
||
1. **Praxistest auf dem PC (zwingend vor Haken in ROADMAP!):** Login → Nachrichten
|
||
senden/lesen → Logout → erneuter Login → alte verschlüsselte Nachrichten noch
|
||
lesbar? Dabei prüfen, ob `auth_log.txt` (App-Support-Verzeichnis) die
|
||
Login-Status-Wechsel sauber mitschreibt.
|
||
2. Danach: den nächsten Punkt in M1 angehen (Push-Entschlüsselungs-Regression).
|
||
3. **Wichtig für Bernd/Uta:** Der Fix wirkt nur für NEUE Logins. Utas bereits
|
||
bestehende Sitzung bekommt erst nach einem einmaligen manuellen Neu-Login
|
||
einen Refresh-Token und ist bis dahin weiterhin anfällig. Sobald der PC-Test
|
||
grün ist, sollte Uta sich einmal aus- und wieder einloggen.
|
||
|
||
**Stolperfallen:**
|
||
- Konnte auf dem Pi nicht mit echtem Login/Logout getestet werden (kein GUI,
|
||
kein Matrix-Testaccount zur Hand) – Fix beruht auf genauem Lesen des
|
||
matrix-SDK-Quellcodes, nicht auf Beobachtung im Betrieb. Deshalb zwingend
|
||
vor dem nächsten Haken in ROADMAP am PC nachprüfen.
|
||
- `chat_provider.dart:42` nutzt weiterhin das deprecated `Timeline.onUpdate`
|
||
(Re-Entschlüsseln nach Schlüssel-Empfang) – hängt mit demselben Sync-Bereich
|
||
zusammen, aber bewusst nicht angefasst, um nicht zwei riskante Änderungen im
|
||
selben Bereich gleichzeitig ungetestet zu committen.
|
||
|
||
---
|
||
|
||
## 2026-07-03 – M0 abgeschlossen: Ordnung, flutter analyze sauber, README
|
||
|
||
**Erledigt:**
|
||
- Liegen gebliebene Deprecation-Fixes aus einer abgebrochenen Session committet
|
||
(`withOpacity`→`withValues`, `activeColor`→`activeThumbColor`, `cacheExtent`→
|
||
`ScrollCacheExtent`, `PublicRoomsChunk`→`PublishedRoomsChunk`) – Commit 057ac1e
|
||
- Restliche `flutter analyze`-Hinweise behoben – Commit 401bd6a:
|
||
- `document_viewer.dart`: `Matrix4.translate/scale` → `translateByDouble`/
|
||
`scaleByDouble` (PDF-Zoom, gleiches Verhalten)
|
||
- `rooms_panel.dart`: `onReorder` → `onReorderItem` (Index-Korrektur jetzt intern,
|
||
manuelles `newIdx--` entfernt); `implementation_imports`-Hinweis kommentiert
|
||
(SpaceChild wird vom `matrix`-Paket nicht öffentlich exportiert, kein Fix möglich)
|
||
- `voip_manager.dart`: `getSources()`-Override kommentiert (Pflicht-Override der
|
||
abstrakten `MediaDevices`-Klasse, die die Methode selbst deprecated hat)
|
||
- `settings_modal.dart`: zwei `BuildContext`-nach-`await`-Stellen mit
|
||
`context.mounted` abgesichert (Geräte umbenennen, Passwortabfrage bei Massenlogout)
|
||
- **Bewusst NICHT gefixt:** `chat_provider.dart:42` (`Timeline.onUpdate` deprecated,
|
||
Ersatz wäre `client.onSync` + Raum-Filter). Hängt am E2EE-Redecrypt-Pfad
|
||
(Re-Entschlüsseln nach Schlüssel-Empfang) – laut CLAUDE.md heiliger Bereich,
|
||
Umbau nur mit dediziertem Login→Nachrichten→Logout→Login-Test, nicht nebenbei.
|
||
- `lib/features/calls/` (Leiche) geprüft: existiert nicht mehr, nur `lib/features/call/`
|
||
mit den echten Dateien – Punkt war bereits erledigt, nur nicht abgehakt.
|
||
- README.md durch echte Projektbeschreibung ersetzt (Funktionsumfang, Build, Struktur) – Commit 74d38fd
|
||
- ROADMAP.md: M0 komplett abgehakt.
|
||
|
||
**Offen/Nächster Schritt:** M1, erster Punkt – Uta-Random-Logout-Bug untersuchen
|
||
(Logging an allen Logout-Pfaden einbauen, dann Ursache finden). Direkt danach die
|
||
Push-Entschlüsselungs-Regression (`docs/NOTIFICATIONS.md` + Memory „Pyramid Push"
|
||
vorher lesen).
|
||
|
||
**Stolperfallen:**
|
||
- Kein `test/`-Ordner vorhanden → `flutter test` läuft ins Leere, keine automatisierte
|
||
Absicherung. `flutter analyze` lief sauber (0 Fehler/Warnungen), aber die Lint-Fixes
|
||
in `document_viewer.dart` (PDF-Zoom) und `rooms_panel.dart` (Drag-Reorder der
|
||
Raumliste) sind **UNGETESTET** in echter UI – nächster Windows-Lauf sollte kurz
|
||
PDF-Zoom und Raum-Umsortieren per Drag prüfen.
|
||
- Der `ignore:`-Kommentar für einen deprecated-Aufruf muss auf der Zeile *unmittelbar*
|
||
über der betroffenen Codezeile stehen, nicht nur "in der Nähe" – sonst wirkt er nicht
|
||
(bei `voip_manager.dart` erst falsch über `@override` gesetzt, dann korrigiert).
|
||
|
||
---
|
||
|
||
## 2026-07-03 – Arbeitsstruktur angelegt + Vollbackup (Setup, noch kein Code)
|
||
|
||
**Erledigt:**
|
||
- Vollbackup inkl. Git-Historie: `MatrixPi\backups\pyramid-kopie_backup_2026-07-03.tar.gz` (510 MB)
|
||
- `CLAUDE.md` (Arbeitsregeln), `ROADMAP.md` (M0–M7 nach Bernds Prioritäten), dieses Protokoll
|
||
- Klargestellt: `C:\Users\nordm\pyramid - Kopie` ist das EINZIGE aktuelle Repo;
|
||
`C:\Users\nordm\pyramid` und `MatrixPi\pyramid` sind veraltete April-Stände (Commit c9af913)
|
||
|
||
**Offen/Nächster Schritt:** M0, Punkt 2 – die ~140 uncommitteten Dateien mit `git status`
|
||
sichten und in thematische Commits aufteilen (diese drei neuen Dateien mit committen),
|
||
dann `git push origin master`.
|
||
|
||
**Stolperfallen:**
|
||
- Letzter Commit ist vom 2026-04-28 (d706ace), gearbeitet wurde aber bis mindestens
|
||
2026-06-12 → zwischen Commits und Realität liegen 6 Wochen. Genau deshalb: ab jetzt
|
||
kleine Commits nach jedem Schritt.
|
||
- Git-Remote (Gitea auf dem Pi) enthält Zugangsdaten in der URL – funktioniert, aber
|
||
nur erreichbar, wenn der Pi läuft (unterwegs: WireGuard nötig).
|