9641592a8c
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
142 lines
8.9 KiB
Markdown
142 lines
8.9 KiB
Markdown
---
|
|
context: default
|
|
phase: mandantentrennung-etappe-2
|
|
task: null
|
|
total_tasks: 3
|
|
status: paused
|
|
last_updated: 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>
|
|
|
|
<blockers>
|
|
|
|
**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.
|
|
</blockers>
|
|
|
|
## 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>
|