6fb32754d5
Handoff neu aufgenommen und gemessen statt erinnert: Arbeitsbaum leer, main == origin/main, keine Datei juenger als der letzte Commit vom 2026-09-11 — es lag keine angefangene Arbeit herum. Gegenueber dem alten Handoff ergaenzt: - WINDOWS #29 (PATCH /users/:id ohne Rollenausweitungs-Pruefung) als eigener Punkt; einziger offener Sicherheitsbefund ohne Bezug zum Scharfschalten. - Phase 17 ist VERIFIED, /gsd-ship lief nie — v1.2 formal nicht geschlossen, windows_enforce blockiert bei open_count 15. - Einordnung der 15 offenen WINDOWS-Eintraege: welche mit 3a/3c fallen, welche erst mit #18 akut werden, welche Netz-Luecken sind. - Placeholder-Suche ueber .planning/phases/ geprueft: 53 Treffer, alle Prosa ueber Debt-Marker, keine unfertigen Zusammenfassungen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H77uqgDTe82JHFaVx6S71s
200 lines
12 KiB
Markdown
200 lines
12 KiB
Markdown
---
|
|
context: default
|
|
phase: mandantentrennung-etappe-3
|
|
task: null
|
|
total_tasks: 4
|
|
status: paused
|
|
last_updated: 2026-09-14T08:02:29.277Z
|
|
---
|
|
|
|
# Wiedereinstieg — Etappe 3b abgeschlossen, offen sind 3c, 3a, Etappe 4
|
|
|
|
## Critical Anti-Patterns
|
|
|
|
Alle stammen aus tatsaechlichen Fehlschlaegen der Etappen 1-3b, nicht aus Vorsicht.
|
|
|
|
| Muster | Beschreibung | Schwere | Vermeidung |
|
|
|--------|--------------|---------|------------|
|
|
| Auf ein ungeprueftes Fundament bauen | `forTenant()` 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. | 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 ging nach `/opt/tessera/docker-compose.yml` — der Server nutzt `docker-compose.prod.yml`, weil die `.env` `COMPOSE_FILE` setzt. | 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. |
|
|
| Bericht statt Arbeitsbaum geglaubt | Zwei Agenten brachen am Sitzungslimit NACH getaner Arbeit ab; die Arbeit lag vollstaendig auf der Platte, der Bericht fehlte. | blocking | `git status` ist die Wahrheit, nicht der Agentenbericht. |
|
|
| Zeichensatz beim Veroeffentlichen angenommen | Die Handbuch-Webseite ging mit zerlegten Umlauten live ("Für" statt "Fuer"). | advisory | Seiten mit deutschem Text als reines ASCII ausliefern (Sonderzeichen als `\uXXXX`), mit `all(ord(c)<128 ...)` pruefen. |
|
|
|
|
<current_state>
|
|
**Seit dem 2026-09-11 17:58 wurde nichts mehr angefasst.** Arbeitsbaum leer
|
|
(`git status --porcelain` liefert nichts), `main == origin/main`, keine Datei im
|
|
Repo ist juenger als der letzte Commit. Es gibt also KEINE angefangene Arbeit,
|
|
die aufzunehmen waere — die Sitzung lief nach Etappe 3b bei ~70% Kontext aus,
|
|
der Handoff war geschrieben. Diese Datei ist die Neuaufnahme desselben Standes
|
|
am 2026-09-14, ergaenzt um zwei Punkte, die im alten Handoff fehlten
|
|
(WINDOWS #29 und der nie gelaufene Ship von Phase 17).
|
|
|
|
**Stand der Mandantentrennung:** Etappe 1 (Fundament repariert, 227 Zugriffe
|
|
klassifiziert), Etappe 2 (alle zwoelf Bereiche gebunden, jeder einzeln
|
|
verifiziert), die drei vorgezogenen Datenbankregeln (260910-jab), WINDOWS #27
|
|
(Relations-Blindstelle, 260911-mkj) und Etappe 3b (Benutzerdimension,
|
|
260911-nke) sind fertig und gepusht. Endstand 3b: Migration
|
|
`20260911120000_rls_user_dimension_personal_tables`, `current_user_id()`,
|
|
`forTenant(prisma, tenantId, userId?)`, zehn persoenliche Tabellen, 34
|
|
Aufrufstellen in acht Diensten, 1020 Tests in 62 Dateien, `rls-scratch-check.mjs`
|
|
203/203, sechs Loch-Pruefungen umgedreht.
|
|
|
|
**Der Umstellungsschalter ist AUS.** `DATABASE_URL` zeigt weiter auf die Rolle
|
|
`tessera` mit BYPASSRLS. Der User hat ausdruecklich verlangt, beim Scharfschalten
|
|
angehalten und gefragt zu werden.
|
|
</current_state>
|
|
|
|
<completed_work>
|
|
|
|
- Etappe 1 — `forTenant()` repariert, Anmeldeweg ueber SECURITY-DEFINER-Funktionen,
|
|
227 Zugriffe klassifiziert (`5228f28`).
|
|
- Etappe 2 — zwoelf Bereiche gebunden (ldap, groups, tenders, dkv, user,
|
|
module-registry, dashboard, calendar, tenant, auth, favorites, settings),
|
|
ueber zwoelf Quick-Tasks `260909-ipc` .. `260911-gwh` (`1240932`).
|
|
- Drei zu kurz greifende Datenbankregeln geschlossen, auf Anweisung des Users
|
|
vorgezogen (260910-jab, `03fb3bf`).
|
|
- WINDOWS #27 geschlossen — vierte Erkennungsform der Bestandsaufnahme,
|
|
72 Paare, Restmenge als #33 eingetragen (260911-mkj, `388690f`).
|
|
- Etappe 3b — Benutzerdimension in den Regeln (260911-nke, `f0b531b`, `07fc653`,
|
|
`b62a905`, `3b08d8e`), verifiziert 13/13.
|
|
</completed_work>
|
|
|
|
<remaining_work>
|
|
|
|
1. **Etappe 3c — Systemkontext fuer die Hintergrunddienste.** Sechs Faelle:
|
|
`dkv-scheduler` / `loadAnyActiveConfigForScheduler()` (WINDOWS #21, heute
|
|
bereits falsch — willkuerlicher Mandant), `mail.module` /
|
|
`loadAnySmtpConfigForStartupTransport()` (WINDOWS #30, dieselbe Form),
|
|
`ldap-config.service` `getAllActiveConfigs()`, `tender-digest.scheduler`,
|
|
`tender-matching.service`, `admin-seed.service`
|
|
`ensureDefaultGroupsForAllTenants`. Bauform: benannter Systemkontext
|
|
`forSystem(prisma)` mit dritter Sitzungsvariable `app.system_context`,
|
|
lesend erlaubt, Schreiben innerhalb der Schleife je Mandant gebunden.
|
|
**Braucht keine Entscheidung des Users — als naechstes empfohlen.**
|
|
2. **Etappe 3a — Anmeldenamen pro Mandant.** `@@unique([tenantId, username])`
|
|
und `@@unique([tenantId, email])` statt plattformweit, die drei
|
|
SECURITY-DEFINER-Funktionen bekommen `p_tenant_id`. Enthaelt **die eine
|
|
offene Produktfrage**: woran der Login den Mandanten erkennt — Subdomain je
|
|
Mandant (Empfehlung, weil Tessera hinter Nginx Proxy Manager laeuft) oder
|
|
Mandantenwahl im Anmeldeformular. Schliesst WINDOWS #22.
|
|
3. **Etappe 4 — Scharfschalten.** `rls-preflight.mjs` davor, dokumentierter
|
|
Rueckweg. NUR nach Rueckfrage beim User. WINDOWS #18.
|
|
4. **WINDOWS #29 — offene Rechteausweitung, unabhaengig von der
|
|
Mandantentrennung.** `adminResetPassword` verhindert ADMIN -> SUPER_ADMIN im
|
|
eigenen Handler (T-FH9-04), der Schwesterweg `PATCH /users/:id`
|
|
(`UserController.update`, T-02-08) prueft nur, ob die Rolle existiert. Das ist
|
|
der einzige offene Punkt, der nicht am Scharfschalten haengt, und der einzige
|
|
mit Sicherheitsbezug vor dem Live-Gehen.
|
|
5. **Phase 17 ist VERIFIED, aber `/gsd-ship` lief nie** — v1.2 ist formal nicht
|
|
geschlossen. Mit `windows_enforce` blockiert der Ship, solange
|
|
`open_count > 0` (aktuell 15).
|
|
|
|
Von den 15 offenen WINDOWS-Eintraegen sind #25, #26, #28, #31, #32 Beschreibungen
|
|
der umgedrehten Fehlerrichtung je Bereich — sie werden erst mit dem Scharfschalten
|
|
(#18) akut und sind bewusst so abgelegt. #33 und #34 sind Luecken im Netz der
|
|
Bestandsaufnahme, nicht im Produkt. #21, #22, #24, #30 fallen mit 3a bzw. 3c.
|
|
</remaining_work>
|
|
|
|
<decisions_made>
|
|
|
|
- **Anmeldenamen pro Mandant eindeutig, nicht plattformweit** (User, 2026-09-10) —
|
|
`m.schmidt` darf es bei Firma A und Firma B geben.
|
|
- **Kollegen derselben Firma strikt getrennt** (User, 2026-09-10) — jeder sieht
|
|
nur seine eigenen gespeicherten Suchen, Favoriten, Dashboard-Anordnung. Mit
|
|
Etappe 3b in den Datenbankregeln verankert.
|
|
- **Anmeldeweg ueber SECURITY-DEFINER-Funktionen**, nicht ueber eine Policy: eine
|
|
Policy ist ein Zeilenpraedikat, jede Regel die eine Suche nach Benutzername
|
|
erlaubt, erlaubt das Lesen der ganzen Tabelle. Die Funktion pinnt die Ausnahme
|
|
auf feste Spaltenliste, Gleichheit und `LIMIT 1`.
|
|
- **Helfer erweitern statt zweiten bauen** — `forTenant()` bekam den optionalen
|
|
dritten Parameter statt eines `forTenantAndUser()`.
|
|
- **Am Active Directory wird nichts veraendert** (User, 2026-09-09, mit Nachdruck).
|
|
- **Datenverlust in der Datenbank ist derzeit hinnehmbar** (User, 2026-09-09):
|
|
nichts laeuft produktiv. Gilt nur, solange das so bleibt.
|
|
- **Beim Scharfschalten anhalten und fragen** (User, seit 2026-09-09) — die einzige
|
|
Ausnahme von "nicht nachfragen".
|
|
</decisions_made>
|
|
|
|
<blockers>
|
|
|
|
**Etappe 4 darf nicht vorgezogen werden.** Wird scharf geschaltet, bevor Etappe 3
|
|
durch ist, 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.
|
|
Keine laufenden Hintergrundauftraege (`.planning/async-jobs/` existiert nicht).
|
|
</blockers>
|
|
|
|
## Required Reading (in order)
|
|
|
|
1. `docs/mandantentrennung-etappe3-auftrag.md` — der gemessene Auftrag fuer 3a/3b/3c;
|
|
3b ist dort als erledigt vermerkt, der historische Auftragstext steht daneben
|
|
2. `.planning/STATE.md` — Position, Quick-Task-Tabelle mit allen Ergebnissen
|
|
3. `docs/mandantentrennung-zugriffsklassifikation.md` — die Arbeitsgrundlage
|
|
4. `docs/mandantentrennung-etappe2-fehlerrichtung.md` — Abschnitt "Regelschluss
|
|
Benutzerdimension (Etappe 3b, 260911-nke)"
|
|
5. `docs/mandantentrennung-datenbankrolle.md` — Befund, Sperrgrund, Handgriffe, Rueckweg
|
|
6. `.planning/WINDOWS.md` — 15 offen, davon #29 als einziger ohne Bezug zum Schalter
|
|
7. `apps/api/src/prisma/prisma-tenant.extension.ts` — der Helfer, dreistellig
|
|
|
|
## Infrastructure State
|
|
|
|
- **alpha** (192.168.13.12, https://alpha.tessera.ctl.de): Stand `ea003d4`,
|
|
Container am 2026-09-09 neu erstellt, `user-files` als Volume `tessera_user-files`
|
|
am api-Container nachgewiesen. **Live-Gehen am Dienstag, 2026-09-15** — braucht
|
|
den Schalter NICHT (ein Mandant, BYPASSRLS-Stand laeuft heute dort).
|
|
- **Die Serverdatei ist `docker-compose.prod.yml`**, nicht `docker-compose.yml` — die
|
|
`.env` setzt `COMPOSE_FILE`. Sicherungen als `.bak.20260909-0818` daneben.
|
|
- **git push** geht ausschliesslich ueber `localhost:3002`; die Push-URL des Remotes
|
|
ist dauerhaft darauf gesetzt, ein schlichtes `git push` genuegt.
|
|
- **Lokal**: `api`, `db` und `web` laufen; KEIN mailhog-Container, deshalb scheitert
|
|
der Mailversand lokal mit `ENOTFOUND mailhog` — umgebungsbedingt, kein Defekt.
|
|
- **Datenbank**: kein Host-Port. IP per `docker inspect` frisch ermitteln,
|
|
`tessera:tessera_dev`. Prisma-Binary aus `apps/api/node_modules/.bin/prisma`,
|
|
NICHT `npx prisma` (zieht Prisma 8).
|
|
- **Worktree-Isolation ist abgeschaltet** (`workflow.use_worktrees=false`), weil
|
|
`origin/HEAD` in diesem Repo nicht aufloesbar ist.
|
|
|
|
## Pre-Execution Critique Required
|
|
|
|
Vor jedem weiteren Bereich gilt die Frage aus Etappe 2 unveraendert:
|
|
|
|
**Woran wuerde ich merken, dass eine umgestellte Abfrage jetzt zu WENIG liefert statt
|
|
zu viel?** Der Umbau dreht die Fehlerrichtung um. Der still gefaehrlichste Ort ist
|
|
jeder Code, der Leere als Abwesenheit deutet und daraufhin loescht.
|
|
|
|
<context>
|
|
Etappe 2 lief ueber zwoelf Quick-Tasks plus einen Regel-Durchlauf, jeder mit Planer,
|
|
Plan-Pruefer, Executor, Verifizierer. ZEHN Lieferungen wurden vom jeweils NAECHSTEN
|
|
Schritt gefangen, nie vom eigenen: vier geschrumpfte Zaehlungen, zwei nicht
|
|
committete Messungen, zwei Zusammenfassungen mit N statt N-1, uebersprungene
|
|
handgepflegte Dokumentstellen, Falsifizierungsnachweise nur in Commit-Nachrichten,
|
|
eine Wegwerf-Tabelle ohne `createdAt`/`updatedAt`, ein Selbstwiderspruch, ein Pruefer
|
|
der etwas als plausibel durchwinkte, ein still fehlgeschlagener `git add`, und zwei
|
|
Agenten die am Sitzungslimit NACH getaner Arbeit abbrachen. Die Kette vollstaendig zu
|
|
fahren ist deshalb keine Zeremonie, sondern das, was in dieser Arbeit tatsaechlich
|
|
Fehler gefangen hat.
|
|
|
|
Verifizierer-Hinweis zu 3b (WINDOWS #34, angenommenes Risiko): die dreistelligen
|
|
`forTenant()`-Zusicherungen sind je Spec-Datei, nicht je Methode; das Gate "keine
|
|
zweistellige Form in den acht Dateien" ist ein Shell-Check, nicht CI. Wer das
|
|
schliessen will, baut den Check in `rls-access-inventory.spec.ts` ein.
|
|
|
|
USER-ANWEISUNG 2026-09-11, weiter gueltig: "mach #27 und dann Etappe 3, nicht
|
|
nachfragen. du machst alles, was ohne mich geht. am dienstag geht eine voll
|
|
funktionsfaehige version live." Nach jedem Durchlauf pushen.
|
|
</context>
|
|
|
|
<next_action>
|
|
1. `docs/mandantentrennung-etappe3-auftrag.md` lesen — 3b ist dort als erledigt vermerkt.
|
|
2. **Etappe 3c** (Systemkontext, sechs Hintergrunddienst-Faelle) als
|
|
`/gsd-quick --validate` — braucht keine Entscheidung des Users.
|
|
3. **WINDOWS #29** (`PATCH /users/:id` ohne Rollenausweitungs-Pruefung) —
|
|
kleiner, unabhaengiger Durchlauf, der einzige Sicherheitspunkt vor dem Live-Gehen.
|
|
4. **Etappe 3a** — enthaelt die eine offene Produktfrage (Subdomain vs. Login-Wahl).
|
|
5. **Etappe 4** — Scharfschalten. NUR nach Rueckfrage beim User.
|
|
</next_action>
|