Files
tessera-ctl/.planning/.continue-here.md
T
schalli 9641592a8c
Tessera CI/CD / Lint & Type Check (push) Successful in 46s
Tessera CI/CD / Tests (push) Successful in 52s
Tessera CI/CD / Build & Publish Images (push) Successful in 7s
docs: Handoff fuer die Fortsetzung der Mandantentrennung
WIP-Uebergabe vor Etappe 2. Der Arbeitsbaum ist sauber, alles gepusht, die CI
gruen — pausiert wird an einer Etappengrenze, nicht mitten in einer Aufgabe.

Die Uebergabe haelt vier Anti-Patterns fest, die alle aus tatsaechlichen
Fehlschlaegen dieser Sitzung stammen. Der wichtigste: auf einem ungeprueften
Fundament bauen. forTenant() war kaputt, und ohne die empirische Probe waere
das erst nach dem Scharfschalten aufgefallen — als stiller Datenverlust, weil
der LDAP-Loeschzweig leere Ergebnisse als "Gruppe verschwunden" deutet.

Ebenfalls festgehalten, weil beides Zeit gekostet hat: eine grep-Zaehlung, die
Kommentare mitzaehlte (36 vermeintliche Aufrufstellen, tatsaechlich 6), und die
falsche Compose-Datei auf dem Server, weil COMPOSE_FILE auf prod.yml zeigt.

Der Einstiegspunkt fuer Etappe 2 ist bewusst nicht der groesste Bereich,
sondern ldap: dort sitzt der gefaehrlichste Loeschzweig, und die Wirkung ist
dort am besten pruefbar.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
2026-09-09 11:18:12 +02:00

8.9 KiB

context, phase, task, total_tasks, status, last_updated
context phase task total_tasks status last_updated
default mandantentrennung-etappe-2 null 3 paused 2026-09-09T09:16:11.548Z

Wiedereinstieg — Mandantentrennung, vor Etappe 2

Critical Anti-Patterns

Alle vier stammen aus tatsaechlichen Fehlschlaegen dieser Sitzung, nicht aus Vorsicht.

Muster Beschreibung Schwere Vermeidung
Auf ein ungeprueftes Fundament bauen forTenant() — der Helfer, auf dem die ganze Mandantentrennung ruht — setzte den Kontext per set_config auf der Transaktionsverbindung, dispatchte die Abfrage aber ueber den aeusseren Client. Gemessen: set_config auf Backend-PID 254999, Abfrage auf 255000, Kontext dort NULL. Die Trennung hat nie funktioniert, auch nicht an den Stellen, die sie scheinbar nutzten. Waere vor dem Scharfschalten nicht geprueft worden, haetten die Abfragen danach NULL Zeilen geliefert und der LDAP-Loeschzweig haette das als "Gruppe im Verzeichnis verschwunden" gedeutet und geloescht. blocking Vor jedem Umbau, der auf einem Helfer aufsetzt, dessen Wirkung EMPIRISCH nachweisen — gegen eine Wegwerf-Datenbank, mit zwei Mandanten und einer echten Abfrage. Nicht den Code lesen und schliessen, dass er stimmt.
Zaehlung ohne Ansehen der Treffer Eine grep-Zaehlung ergab 36 mandantengebundene Stellen. Tatsaechlich waren die meisten Treffer Kommentare, die erklaeren, warum forTenant() dort FEHLT. Echte Aufrufstellen: 6. blocking Bei jeder Zahl, die eine Planung traegt, in die Treffer hineinsehen. Eine Zahl aus grep -c ist eine Behauptung, kein Befund.
Falsche Datei auf dem Server bearbeitet Die Volume-Zeile wurde in /opt/tessera/docker-compose.yml eingetragen — der Server nutzt aber docker-compose.prod.yml, weil die .env COMPOSE_FILE=docker-compose.prod.yml setzt. Die Aenderung waere wirkungslos geblieben und haette wie erledigt ausgesehen. blocking Nach JEDER Aenderung an einer Compose-Datei docker compose config rendern und pruefen, ob die Aenderung im Ergebnis auftaucht. Vorher docker compose ls --format json lesen, um zu sehen, welche Datei ueberhaupt gilt.
Zeichensatz beim Veroeffentlichen angenommen Die Handbuch-Webseite ging mit zerlegten Umlauten live ("Für" statt "Fuer"), weil im lokalen Test der Zeichensatz fehlte und ich annahm, das Veroeffentlichen setze ihn schon richtig. advisory Seiten mit deutschem Text als reines ASCII ausliefern (Sonderzeichen als \uXXXX in den Daten). Dann kann kein Zeichensatz sie falsch auslegen. Die fertige Datei mit all(ord(c)<128 ...) pruefen.

<current_state> Etappe 1 der Mandantentrennung ist abgeschlossen, committet und gepusht (5228f28). Der Arbeitsbaum ist sauber, die CI gruen, 701 Tests gruen.

Die Sitzung lief ueber Quick-Tasks, nicht ueber Phasen — es gibt daher kein aktives Phasenverzeichnis. Der Meilenstein v1.2 ist seit dem 2026-09-07 zu, ein neuer wurde nicht begonnen.

Der Umstellungsschalter ist AUS: DATABASE_URL zeigt weiterhin auf die Rolle tessera mit BYPASSRLS. Das ist Absicht — siehe Sperrgrund unten. </current_state>

<completed_work>

Diese Sitzung, in Reihenfolge:

  1. Aufraeumen (98a8c93) — zwei veraltete Checkpoint-Dateien entfernt, ihr noch gueltiges Wissen (5 Anti-Patterns, Infrastruktur-Stand) nach STATE.md gerettet.
  2. Live-Test AD (a1a8b4f, b6b964b) — WINDOWS #4 und #6 belegt und geschlossen, ohne jede Aenderung am Verzeichnis: Umbenennung und Verschwinden wurden ueber den in Tessera gespeicherten Stand nachgestellt.
  3. Verbindungstest Postfach (c4db3b2, abgenommen) — WINDOWS #16.
  4. Zwei Defekte behoben (e8c2411, 1222951, 2167046) — WINDOWS #14 (Matrix-Suche) und #15 (Sync-Meldungen); dabei den Sicherheitsfund T-Q3-01 mitgeschlossen.
  5. Anleitungen (3501eb4) — vier Handbuecher plus Einstieg unter docs/, gegen den Quelltext geschrieben und unabhaengig gegengeprueft. Zusaetzlich als Webseite veroeffentlicht: https://claude.ai/code/artifact/67372b7f-4d7c-49c7-9642-1c5ef576f245
  6. Dateisicherung (dab72eb) — user-files als benanntes Volume; auf dem Server nachgetragen und am laufenden System belegt, WINDOWS #17 geschlossen.
  7. Versionsangaben (c807049) — CLAUDE.md auf den installierten Stand; sechs nie eingebaute Empfehlungen benannt (u.a. Keycloak, Redis, shadcn/ui).
  8. Mandantentrennung Etappe 1 (bbf1795, de50297, 5f3a39c, da0ac04). </completed_work>

<remaining_work>

Etappe 2 — die eigentliche Umstellung. 31 Einheiten vollstaendig, 9 teilweise. Grundlage: docs/mandantentrennung-zugriffsklassifikation.md, maschinell gegen Abdriften abgesichert durch apps/api/src/prisma/rls-access-inventory.spec.ts. Geschaetzt 5-8 Durchlaeufe, nach Bereichen gebuendelt.

Groessen je Bereich: tenders 62, groups 37, ldap 21, dkv 21, user 17, module-registry 17, dashboard 13, calendar 12, tenant 8, favorites 7, settings 4.

Etappe 3 — Systemkontext fuer Hintergrundlaeufe (ein Cron-Job liest bewusst ueber alle Mandanten, muss aber INNERHALB der Schleife je Mandant binden) plus WINDOWS #19.

Etappe 4 — Scharfschalten mit rls-preflight.mjs davor und dokumentiertem Rueckweg. </remaining_work>

<decisions_made>

  • Anmeldeweg ueber SECURITY-DEFINER-Funktionen, nicht ueber eine Policy und nicht ueber eine zweite Rolle. Eine Policy ist ein Zeilenpraedikat: jede Regel, die eine Suche nach Benutzername erlaubt, erlaubt zwangslaeufig das Lesen der ganzen Tabelle. Die Funktion pinnt die Ausnahme auf feste Spaltenliste, Gleichheit und LIMIT 1.
  • Benanntes Volume statt Bind-Mount fuer user-files — Eigentuemerschaft, nicht Sicherungskomfort, gab den Ausschlag.
  • Am Active Directory wird nichts veraendert (User, 2026-09-09, mit Nachdruck). Pruefungen, die nach einer Verzeichnis-Aenderung aussehen, werden ueber den in Tessera gespeicherten Stand nachgestellt — so wurden #4 und #6 geschlossen.
  • Datenverlust in der Datenbank ist derzeit hinnehmbar (User, 2026-09-09): nichts laeuft produktiv. Erlaubt beim Scharfschalten den direkten Weg statt aufwendiger Absicherung. Gilt nur, solange das so bleibt — vor einem Produktivbetrieb neu bewerten. </decisions_made>

Etappe 4 darf nicht vorgezogen werden. Wird scharf geschaltet, bevor Etappe 2 und 3 durch sind, liefern die noch nicht umgestellten Abfragen null Zeilen statt zu vieler. Der gefaehrlichste Fall ist der Loeschzweig in ldap.service.ts (~Zeile 1559), der Leere als "Gruppe im Verzeichnis verschwunden" deutet und samt Mitgliedschaften und Modulfreigaben loescht.

Keine offenen Handgriffe des Users. Der Server ist auf dem aktuellen Stand.

Required Reading (in order)

  1. .planning/STATE.md — Position, Quick-Task-Tabelle mit allen Ergebnissen dieser Sitzung
  2. docs/mandantentrennung-zugriffsklassifikation.md — die Arbeitsgrundlage fuer Etappe 2
  3. docs/mandantentrennung-datenbankrolle.md — Befund, Sperrgrund, Handgriffe, Rueckweg
  4. .planning/WINDOWS.md — offen sind #18, #19, #20
  5. apps/api/src/prisma/prisma-tenant.extension.ts — der reparierte Helfer

Infrastructure State

  • alpha (192.168.13.12, https://alpha.tessera.ctl.de): auf dem Stand von ea003d4, Container am 2026-09-09 neu erstellt. user-files haengt als Volume tessera_user-files am api-Container, nachgewiesen.
  • Die Serverdatei ist docker-compose.prod.yml, nicht docker-compose.yml — die .env setzt COMPOSE_FILE. Sicherungen liegen als .bak.20260909-0818 daneben.
  • git push geht ausschliesslich ueber localhost:3002; die Push-URL des Remotes ist seit dieser Sitzung dauerhaft darauf gesetzt, ein schlichtes git push genuegt.
  • Lokal: api, db und web laufen; es gibt KEINEN mailhog-Container, deshalb scheitert der Mailversand lokal mit ENOTFOUND mailhog — das ist umgebungsbedingt und kein Defekt.
  • Worktree-Isolation ist abgeschaltet (workflow.use_worktrees=false), weil origin/HEAD in diesem Repo nicht aufloesbar ist und ein isolierter Baum von einem veralteten Stand abzweigen wuerde.

Pre-Execution Critique Required

Bevor Etappe 2 beginnt, ist die Antwort auf diese Frage schriftlich festzuhalten:

Woran wuerde ich merken, dass eine umgestellte Abfrage jetzt zu WENIG liefert statt zu viel? Der Umbau dreht die Fehlerrichtung um. Bis heute war der Fehlerfall "sieht zu viel"; nach der Umstellung ist er "sieht nichts" — und der still gefaehrlichste Ort dafuer ist jeder Code, der Leere als Abwesenheit deutet und daraufhin loescht. Vor der Umstellung eines Bereichs ist zu pruefen, ob er solchen Code enthaelt.

<next_action> Etappe 2 beginnen, und zwar NICHT mit dem groessten Bereich. Einstieg ist ldap (21 Zugriffe, davon 9 bereits mandantengebunden): dort sitzt der gefaehrlichste Loeschzweig, die Wirkung ist dort am besten pruefbar, und der Bereich ist klein genug fuer einen Durchlauf. Danach groups (37), dann tenders (62).

Frische Sitzung, dann /gsd-resume-work. </next_action>