docs: Arbeitsstand pausiert — Etappe 3b zu, offen 3c/#29/3a/Etappe 4
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
This commit is contained in:
+140
-89
@@ -2,147 +2,198 @@
|
|||||||
context: default
|
context: default
|
||||||
phase: mandantentrennung-etappe-3
|
phase: mandantentrennung-etappe-3
|
||||||
task: null
|
task: null
|
||||||
total_tasks: 5
|
total_tasks: 4
|
||||||
status: paused
|
status: paused
|
||||||
last_updated: 2026-09-11T16:30:00.000Z
|
last_updated: 2026-09-14T08:02:29.277Z
|
||||||
---
|
---
|
||||||
|
|
||||||
# Wiedereinstieg — Mandantentrennung, Etappe 2 + #27 + 3b ABGESCHLOSSEN, vor 3c und 3a
|
# Wiedereinstieg — Etappe 3b abgeschlossen, offen sind 3c, 3a, Etappe 4
|
||||||
|
|
||||||
## Critical Anti-Patterns
|
## Critical Anti-Patterns
|
||||||
|
|
||||||
Alle vier stammen aus tatsaechlichen Fehlschlaegen dieser Sitzung, nicht aus Vorsicht.
|
Alle stammen aus tatsaechlichen Fehlschlaegen der Etappen 1-3b, nicht aus Vorsicht.
|
||||||
|
|
||||||
| Muster | Beschreibung | Schwere | Vermeidung |
|
| 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. |
|
| 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. |
|
| 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. |
|
| 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. |
|
||||||
| 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. |
|
| 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>
|
<current_state>
|
||||||
**Etappe 2 ist am 2026-09-11 abgeschlossen.** Alle zwoelf Bereiche sind gebunden und
|
**Seit dem 2026-09-11 17:58 wurde nichts mehr angefasst.** Arbeitsbaum leer
|
||||||
einzeln verifiziert (ldap, groups, tenders, dkv, user, module-registry, dashboard,
|
(`git status --porcelain` liefert nichts), `main == origin/main`, keine Datei im
|
||||||
calendar, tenant, auth, favorites+settings); die drei Datenbankregeln wurden auf
|
Repo ist juenger als der letzte Commit. Es gibt also KEINE angefangene Arbeit,
|
||||||
Anweisung des Users vorgezogen (260910-jab). Endstand: 994 Tests in 62 Dateien
|
die aufzunehmen waere — die Sitzung lief nach Etappe 3b bei ~70% Kontext aus,
|
||||||
(Ausgang 701/53), 137 Live-Pruefungen (Ausgang 8), 65 Paare / 68 ungebunden /
|
der Handoff war geschrieben. Diese Datei ist die Neuaufnahme desselben Standes
|
||||||
178 gebunden — jeder ungebundene Zugriff liegt auf einer plattformglobalen Tabelle
|
am 2026-09-14, ergaenzt um zwei Punkte, die im alten Handoff fehlten
|
||||||
oder einem benannten Startpfad. Alles gepusht, Arbeitsbaum sauber.
|
(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
|
**Der Umstellungsschalter ist AUS.** `DATABASE_URL` zeigt weiter auf die Rolle
|
||||||
`tessera` mit BYPASSRLS. Der User hat ausdruecklich verlangt, beim Scharfschalten
|
`tessera` mit BYPASSRLS. Der User hat ausdruecklich verlangt, beim Scharfschalten
|
||||||
angehalten und gefragt zu werden.
|
angehalten und gefragt zu werden.
|
||||||
|
|
||||||
Die maschinelle Bestandsaufnahme hat eine bekannte Blindstelle (WINDOWS #27):
|
|
||||||
`include:`/`_count:` in fremd geschuetzte Tabellen sieht sie nicht. Alle heutigen
|
|
||||||
Instanzen sind einzeln geprueft; der Mechanismus muss VOR Etappe 4 geschlossen werden.
|
|
||||||
</current_state>
|
</current_state>
|
||||||
|
|
||||||
<completed_work>
|
<completed_work>
|
||||||
|
|
||||||
Diese Sitzung, in Reihenfolge:
|
- Etappe 1 — `forTenant()` repariert, Anmeldeweg ueber SECURITY-DEFINER-Funktionen,
|
||||||
|
227 Zugriffe klassifiziert (`5228f28`).
|
||||||
1. **Aufraeumen** (`98a8c93`) — zwei veraltete Checkpoint-Dateien entfernt, ihr noch
|
- Etappe 2 — zwoelf Bereiche gebunden (ldap, groups, tenders, dkv, user,
|
||||||
gueltiges Wissen (5 Anti-Patterns, Infrastruktur-Stand) nach STATE.md gerettet.
|
module-registry, dashboard, calendar, tenant, auth, favorites, settings),
|
||||||
2. **Live-Test AD** (`a1a8b4f`, `b6b964b`) — WINDOWS #4 und #6 belegt und geschlossen,
|
ueber zwoelf Quick-Tasks `260909-ipc` .. `260911-gwh` (`1240932`).
|
||||||
ohne jede Aenderung am Verzeichnis: Umbenennung und Verschwinden wurden ueber den in
|
- Drei zu kurz greifende Datenbankregeln geschlossen, auf Anweisung des Users
|
||||||
Tessera gespeicherten Stand nachgestellt.
|
vorgezogen (260910-jab, `03fb3bf`).
|
||||||
3. **Verbindungstest Postfach** (`c4db3b2`, abgenommen) — WINDOWS #16.
|
- WINDOWS #27 geschlossen — vierte Erkennungsform der Bestandsaufnahme,
|
||||||
4. **Zwei Defekte behoben** (`e8c2411`, `1222951`, `2167046`) — WINDOWS #14 (Matrix-Suche)
|
72 Paare, Restmenge als #33 eingetragen (260911-mkj, `388690f`).
|
||||||
und #15 (Sync-Meldungen); dabei den Sicherheitsfund T-Q3-01 mitgeschlossen.
|
- Etappe 3b — Benutzerdimension in den Regeln (260911-nke, `f0b531b`, `07fc653`,
|
||||||
5. **Anleitungen** (`3501eb4`) — vier Handbuecher plus Einstieg unter `docs/`, gegen den
|
`b62a905`, `3b08d8e`), verifiziert 13/13.
|
||||||
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>
|
</completed_work>
|
||||||
|
|
||||||
<remaining_work>
|
<remaining_work>
|
||||||
|
|
||||||
**Etappe 2 — die eigentliche Umstellung.** 31 Einheiten vollstaendig, 9 teilweise.
|
1. **Etappe 3c — Systemkontext fuer die Hintergrunddienste.** Sechs Faelle:
|
||||||
Grundlage: `docs/mandantentrennung-zugriffsklassifikation.md`, maschinell gegen
|
`dkv-scheduler` / `loadAnyActiveConfigForScheduler()` (WINDOWS #21, heute
|
||||||
Abdriften abgesichert durch `apps/api/src/prisma/rls-access-inventory.spec.ts`.
|
bereits falsch — willkuerlicher Mandant), `mail.module` /
|
||||||
Geschaetzt 5-8 Durchlaeufe, nach Bereichen gebuendelt.
|
`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).
|
||||||
|
|
||||||
Groessen je Bereich: tenders 62, groups 37, ldap 21, dkv 21, user 17,
|
Von den 15 offenen WINDOWS-Eintraegen sind #25, #26, #28, #31, #32 Beschreibungen
|
||||||
module-registry 17, dashboard 13, calendar 12, tenant 8, favorites 7, settings 4.
|
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
|
||||||
**Etappe 3** — Systemkontext fuer Hintergrundlaeufe (ein Cron-Job liest bewusst ueber
|
Bestandsaufnahme, nicht im Produkt. #21, #22, #24, #30 fallen mit 3a bzw. 3c.
|
||||||
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>
|
</remaining_work>
|
||||||
|
|
||||||
<decisions_made>
|
<decisions_made>
|
||||||
|
|
||||||
- **Anmeldeweg ueber SECURITY-DEFINER-Funktionen**, nicht ueber eine Policy und nicht
|
- **Anmeldenamen pro Mandant eindeutig, nicht plattformweit** (User, 2026-09-10) —
|
||||||
ueber eine zweite Rolle. Eine Policy ist ein Zeilenpraedikat: jede Regel, die eine
|
`m.schmidt` darf es bei Firma A und Firma B geben.
|
||||||
Suche nach Benutzername erlaubt, erlaubt zwangslaeufig das Lesen der ganzen Tabelle.
|
- **Kollegen derselben Firma strikt getrennt** (User, 2026-09-10) — jeder sieht
|
||||||
Die Funktion pinnt die Ausnahme auf feste Spaltenliste, Gleichheit und `LIMIT 1`.
|
nur seine eigenen gespeicherten Suchen, Favoriten, Dashboard-Anordnung. Mit
|
||||||
- **Benanntes Volume statt Bind-Mount** fuer `user-files` — Eigentuemerschaft, nicht
|
Etappe 3b in den Datenbankregeln verankert.
|
||||||
Sicherungskomfort, gab den Ausschlag.
|
- **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).
|
- **Am Active Directory wird nichts veraendert** (User, 2026-09-09, mit Nachdruck).
|
||||||
Pruefungen, die nach einer Verzeichnis-Aenderung aussehen, werden ueber den in Tessera
|
- **Datenverlust in der Datenbank ist derzeit hinnehmbar** (User, 2026-09-09):
|
||||||
gespeicherten Stand nachgestellt — so wurden #4 und #6 geschlossen.
|
nichts laeuft produktiv. Gilt nur, solange das so bleibt.
|
||||||
- **Datenverlust in der Datenbank ist derzeit hinnehmbar** (User, 2026-09-09): nichts
|
- **Beim Scharfschalten anhalten und fragen** (User, seit 2026-09-09) — die einzige
|
||||||
laeuft produktiv. Erlaubt beim Scharfschalten den direkten Weg statt aufwendiger
|
Ausnahme von "nicht nachfragen".
|
||||||
Absicherung. Gilt nur, solange das so bleibt — vor einem Produktivbetrieb neu bewerten.
|
|
||||||
</decisions_made>
|
</decisions_made>
|
||||||
|
|
||||||
<blockers>
|
<blockers>
|
||||||
|
|
||||||
**Etappe 4 darf nicht vorgezogen werden.** Wird scharf geschaltet, bevor Etappe 2 und 3
|
**Etappe 4 darf nicht vorgezogen werden.** Wird scharf geschaltet, bevor Etappe 3
|
||||||
durch sind, liefern die noch nicht umgestellten Abfragen null Zeilen statt zu vieler.
|
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
|
Der gefaehrlichste Fall ist der Loeschzweig in `ldap.service.ts` (~Zeile 1559), der
|
||||||
Leere als "Gruppe im Verzeichnis verschwunden" deutet und samt Mitgliedschaften und
|
Leere als "Gruppe im Verzeichnis verschwunden" deutet und samt Mitgliedschaften und
|
||||||
Modulfreigaben loescht.
|
Modulfreigaben loescht.
|
||||||
|
|
||||||
Keine offenen Handgriffe des Users. Der Server ist auf dem aktuellen Stand.
|
Keine offenen Handgriffe des Users. Der Server ist auf dem aktuellen Stand.
|
||||||
|
Keine laufenden Hintergrundauftraege (`.planning/async-jobs/` existiert nicht).
|
||||||
</blockers>
|
</blockers>
|
||||||
|
|
||||||
## Required Reading (in order)
|
## Required Reading (in order)
|
||||||
|
|
||||||
1. `.planning/STATE.md` — Position, Quick-Task-Tabelle mit allen Ergebnissen dieser Sitzung
|
1. `docs/mandantentrennung-etappe3-auftrag.md` — der gemessene Auftrag fuer 3a/3b/3c;
|
||||||
2. `docs/mandantentrennung-zugriffsklassifikation.md` — die Arbeitsgrundlage fuer Etappe 2
|
3b ist dort als erledigt vermerkt, der historische Auftragstext steht daneben
|
||||||
3. `docs/mandantentrennung-datenbankrolle.md` — Befund, Sperrgrund, Handgriffe, Rueckweg
|
2. `.planning/STATE.md` — Position, Quick-Task-Tabelle mit allen Ergebnissen
|
||||||
4. `.planning/WINDOWS.md` — offen sind #18, #19, #20
|
3. `docs/mandantentrennung-zugriffsklassifikation.md` — die Arbeitsgrundlage
|
||||||
5. `apps/api/src/prisma/prisma-tenant.extension.ts` — der reparierte Helfer
|
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
|
## Infrastructure State
|
||||||
|
|
||||||
- **alpha** (192.168.13.12, https://alpha.tessera.ctl.de): auf dem Stand von `ea003d4`,
|
- **alpha** (192.168.13.12, https://alpha.tessera.ctl.de): Stand `ea003d4`,
|
||||||
Container am 2026-09-09 neu erstellt. `user-files` haengt als Volume
|
Container am 2026-09-09 neu erstellt, `user-files` als Volume `tessera_user-files`
|
||||||
`tessera_user-files` am api-Container, nachgewiesen.
|
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
|
- **Die Serverdatei ist `docker-compose.prod.yml`**, nicht `docker-compose.yml` — die
|
||||||
`.env` setzt `COMPOSE_FILE`. Sicherungen liegen als `.bak.20260909-0818` daneben.
|
`.env` setzt `COMPOSE_FILE`. Sicherungen als `.bak.20260909-0818` daneben.
|
||||||
- **git push** geht ausschliesslich ueber `localhost:3002`; die Push-URL des Remotes ist
|
- **git push** geht ausschliesslich ueber `localhost:3002`; die Push-URL des Remotes
|
||||||
seit dieser Sitzung dauerhaft darauf gesetzt, ein schlichtes `git push` genuegt.
|
ist dauerhaft darauf gesetzt, ein schlichtes `git push` genuegt.
|
||||||
- **Lokal**: `api`, `db` und `web` laufen; es gibt KEINEN mailhog-Container, deshalb
|
- **Lokal**: `api`, `db` und `web` laufen; KEIN mailhog-Container, deshalb scheitert
|
||||||
scheitert der Mailversand lokal mit `ENOTFOUND mailhog` — das ist umgebungsbedingt und
|
der Mailversand lokal mit `ENOTFOUND mailhog` — umgebungsbedingt, kein Defekt.
|
||||||
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
|
- **Worktree-Isolation ist abgeschaltet** (`workflow.use_worktrees=false`), weil
|
||||||
`origin/HEAD` in diesem Repo nicht aufloesbar ist und ein isolierter Baum von einem
|
`origin/HEAD` in diesem Repo nicht aufloesbar ist.
|
||||||
veralteten Stand abzweigen wuerde.
|
|
||||||
|
|
||||||
## Pre-Execution Critique Required
|
## Pre-Execution Critique Required
|
||||||
|
|
||||||
Bevor Etappe 2 beginnt, ist die Antwort auf diese Frage schriftlich festzuhalten:
|
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
|
**Woran wuerde ich merken, dass eine umgestellte Abfrage jetzt zu WENIG liefert statt
|
||||||
viel?** Der Umbau dreht die Fehlerrichtung um. Bis heute war der Fehlerfall "sieht zu
|
zu viel?** Der Umbau dreht die Fehlerrichtung um. Der still gefaehrlichste Ort ist
|
||||||
viel"; nach der Umstellung ist er "sieht nichts" — und der still gefaehrlichste Ort
|
jeder Code, der Leere als Abwesenheit deutet und daraufhin loescht.
|
||||||
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.
|
<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>
|
<next_action>
|
||||||
1. `docs/mandantentrennung-etappe3-auftrag.md` lesen — 3b ist dort als Erledigt vermerkt.
|
1. `docs/mandantentrennung-etappe3-auftrag.md` lesen — 3b ist dort als erledigt vermerkt.
|
||||||
2. Etappe 3c (Systemkontext fuer die sechs Hintergrunddienst-Faelle) als
|
2. **Etappe 3c** (Systemkontext, sechs Hintergrunddienst-Faelle) als
|
||||||
`/gsd-quick --validate` — braucht KEINE Entscheidung des Users.
|
`/gsd-quick --validate` — braucht keine Entscheidung des Users.
|
||||||
3. Etappe 3a (Anmeldenamen pro Mandant) — enthaelt die eine offene Produktfrage.
|
3. **WINDOWS #29** (`PATCH /users/:id` ohne Rollenausweitungs-Pruefung) —
|
||||||
4. Etappe 4 — Scharfschalten. NUR nach Rueckfrage beim User.
|
kleiner, unabhaengiger Durchlauf, der einzige Sicherheitspunkt vor dem Live-Gehen.
|
||||||
|
4. **Etappe 3a** — enthaelt die eine offene Produktfrage (Subdomain vs. Login-Wahl).
|
||||||
WINDOWS #27 ist geschlossen (260911-mkj). Frische Sitzung, dann `/gsd-resume-work`.
|
5. **Etappe 4** — Scharfschalten. NUR nach Rueckfrage beim User.
|
||||||
</next_action>
|
</next_action>
|
||||||
|
|||||||
+43
-17
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"version": "1.0",
|
"version": "1.0",
|
||||||
"timestamp": "2026-09-11T16:30:00.000Z",
|
"timestamp": "2026-09-14T08:02:29.277Z",
|
||||||
"phase": null,
|
"phase": null,
|
||||||
"phase_name": "Mandantentrennung wirksam machen (Etappenarbeit ausserhalb der Phasen, ueber Quick-Tasks)",
|
"phase_name": "Mandantentrennung wirksam machen (Etappenarbeit ausserhalb der Phasen, ueber Quick-Tasks)",
|
||||||
"phase_dir": null,
|
"phase_dir": null,
|
||||||
"plan": null,
|
"plan": null,
|
||||||
"task": null,
|
"task": null,
|
||||||
"total_tasks": 4,
|
"total_tasks": 5,
|
||||||
"status": "paused",
|
"status": "paused",
|
||||||
"completed_tasks": [
|
"completed_tasks": [
|
||||||
{
|
{
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 3,
|
"id": 3,
|
||||||
"name": "Zwischendurch auf Anweisung des Users: drei Datenbankregeln geschlossen (260910-jab)",
|
"name": "Auf Anweisung des Users vorgezogen: drei zu kurz greifende Datenbankregeln geschlossen (260910-jab)",
|
||||||
"status": "done",
|
"status": "done",
|
||||||
"commit": "03fb3bf"
|
"commit": "03fb3bf"
|
||||||
},
|
},
|
||||||
@@ -35,37 +35,58 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 5,
|
"id": 5,
|
||||||
"name": "Etappe 3b: Benutzerdimension — current_user_id(), forTenant() dreistellig, zehn Tabellen, 34 Aufrufstellen, sechs Pruefungen zu zwoelf (260911-nke, 13/13)",
|
"name": "Etappe 3b: Benutzerdimension — current_user_id(), forTenant() dreistellig, zehn Tabellen, 34 Aufrufstellen, sechs Pruefungen umgedreht (260911-nke, verifiziert 13/13)",
|
||||||
"status": "done",
|
"status": "done",
|
||||||
"commit": "b62a905"
|
"commit": "3b08d8e"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"remaining_tasks": [
|
"remaining_tasks": [
|
||||||
{
|
{
|
||||||
"id": 6,
|
"id": 6,
|
||||||
"name": "Etappe 3a: Anmeldenamen pro Mandant — enthaelt EINE offene Produktfrage (Subdomain vs Login-Wahl); fuer Dienstag nicht noetig; siehe docs/mandantentrennung-etappe3-auftrag.md",
|
"name": "Etappe 3c: Systemkontext fuer die sechs Hintergrunddienst-Faelle (forSystem(), dritte Sitzungsvariable) — EMPFOHLEN ALS NAECHSTES, braucht keine Produktentscheidung; schliesst WINDOWS #21 und #30; siehe docs/mandantentrennung-etappe3-auftrag.md",
|
||||||
"status": "not_started"
|
"status": "not_started"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 7,
|
"id": 7,
|
||||||
"name": "Etappe 3c: Systemkontext fuer die sechs Hintergrunddienst-Faelle — EMPFOHLEN ALS NAECHSTES (braucht keine Produktentscheidung); siehe docs/mandantentrennung-etappe3-auftrag.md",
|
"name": "WINDOWS #29: PATCH /users/:id (UserController.update) prueft die Rollenausweitung ADMIN -> SUPER_ADMIN nicht, der Schwesterweg adminResetPassword tut es. Unabhaengig vom Scharfschalten, einziger Sicherheitspunkt vor dem Live-Gehen",
|
||||||
"status": "not_started"
|
"status": "not_started"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 8,
|
"id": 8,
|
||||||
"name": "Etappe 4: Scharfschalten — USER WILL GEFRAGT WERDEN. Nicht noetig fuer das Live-Gehen am Dienstag 2026-09-15.",
|
"name": "Etappe 3a: Anmeldenamen pro Mandant (@@unique([tenantId, username/email]), p_tenant_id in den drei SECURITY-DEFINER-Funktionen) — enthaelt DIE EINE offene Produktfrage: Subdomain je Mandant vs. Mandantenwahl im Login; fuer Dienstag nicht noetig",
|
||||||
|
"status": "not_started"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 9,
|
||||||
|
"name": "Etappe 4: Scharfschalten (DATABASE_URL auf eine Rolle ohne BYPASSRLS, rls-preflight.mjs davor) — USER WILL GEFRAGT WERDEN. Nicht noetig fuer das Live-Gehen am Dienstag 2026-09-15",
|
||||||
|
"status": "not_started"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 10,
|
||||||
|
"name": "Phase 17 ist VERIFIED, aber /gsd-ship lief nie — v1.2 formal nicht geschlossen; windows_enforce blockiert den Ship bei open_count 15",
|
||||||
"status": "not_started"
|
"status": "not_started"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"blockers": [
|
"blockers": [
|
||||||
{
|
{
|
||||||
"description": "Etappe 4 darf erst nach Etappe 3 und nach Schliessen von WINDOWS #27 laufen. Der User hat ausdruecklich verlangt, beim Scharfschalten angehalten und gefragt zu werden.",
|
"description": "Etappe 4 darf erst nach Etappe 3 laufen. Vorher liefern die nicht umgestellten Abfragen null Zeilen statt zu vieler; der Loeschzweig in ldap.service.ts (~1559) deutet Leere als 'Gruppe im Verzeichnis verschwunden' und loescht. Der User hat ausdruecklich verlangt, beim Scharfschalten angehalten und gefragt zu werden.",
|
||||||
"type": "process",
|
"type": "process",
|
||||||
"workaround": "Reihenfolge einhalten"
|
"workaround": "Reihenfolge einhalten: 3c, dann 3a, dann fragen"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"async_jobs": [],
|
"async_jobs": [],
|
||||||
"human_actions_pending": [],
|
"human_actions_pending": [
|
||||||
|
{
|
||||||
|
"action": "Produktentscheidung fuer Etappe 3a: woran der Login den Mandanten erkennt — eigene Subdomain je Mandant (Empfehlung) oder Mandantenwahl im Anmeldeformular",
|
||||||
|
"context": "Ohne diese Entscheidung laesst sich 3a nicht planen; mit einem Mandanten ist plattformweit = pro Mandant, deshalb fuer das Live-Gehen am 2026-09-15 nicht noetig",
|
||||||
|
"blocking": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "Freigabe fuer Etappe 4 (Scharfschalten)",
|
||||||
|
"context": "Ausdrueckliche Anweisung des Users seit 2026-09-09",
|
||||||
|
"blocking": true
|
||||||
|
}
|
||||||
|
],
|
||||||
"decisions": [
|
"decisions": [
|
||||||
{
|
{
|
||||||
"decision": "Anmeldenamen pro Mandant eindeutig, nicht plattformweit",
|
"decision": "Anmeldenamen pro Mandant eindeutig, nicht plattformweit",
|
||||||
@@ -74,12 +95,17 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"decision": "Kollegen derselben Firma strikt getrennt — Benutzerdimension in die Datenbankregeln",
|
"decision": "Kollegen derselben Firma strikt getrennt — Benutzerdimension in die Datenbankregeln",
|
||||||
"rationale": "Produktentscheidung des Users am 2026-09-10; heute trennt nur der Anwendungscode, die Datenbank kennt nur den Mandanten",
|
"rationale": "Produktentscheidung des Users am 2026-09-10; mit Etappe 3b umgesetzt",
|
||||||
"phase": "Etappe 3"
|
"phase": "Etappe 3b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"decision": "Helfer erweitern statt zweiten bauen — forTenant(prisma, tenantId, userId?)",
|
||||||
|
"rationale": "Praezedenz withTenantTransaction(); Hintergrunddienste und Verwaltungswege rufen weiter zweistellig, die Regel traegt IS-NULL",
|
||||||
|
"phase": "260911-nke"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"decision": "req.tenantPrisma entfernt, Middleware geloescht",
|
"decision": "req.tenantPrisma entfernt, Middleware geloescht",
|
||||||
"rationale": "Bei jeder Anfrage gebaut, nirgends gelesen; Middleware war nirgends registriert; neun Bereiche haben dienst-internes forTenant() als Konvention festgelegt",
|
"rationale": "Bei jeder Anfrage gebaut, nirgends gelesen; Middleware war nirgends registriert",
|
||||||
"phase": "260911-e2s"
|
"phase": "260911-e2s"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -94,6 +120,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"uncommitted_files": [],
|
"uncommitted_files": [],
|
||||||
"next_action": "docs/mandantentrennung-etappe3-auftrag.md lesen. Etappe 3c (Systemkontext) als /gsd-quick --validate starten — braucht keine Entscheidung des Users. Danach 3a. NICHT nachfragen ausser beim Scharfschalten.",
|
"next_action": "docs/mandantentrennung-etappe3-auftrag.md lesen. Etappe 3c (Systemkontext) als /gsd-quick --validate starten — braucht keine Entscheidung des Users. Danach WINDOWS #29, dann 3a. NICHT nachfragen ausser beim Scharfschalten.",
|
||||||
"context_notes": "Sitzung 2026-09-11 endete bei ~70% Kontext nach Etappe 3b. Verifizierer-Hinweis zu 3b: 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 — eine Methode koennte still auf zweistellig zurueckfallen (WINDOWS #34, angenommenes Risiko). Wer das schliessen will: den Check in rls-access-inventory.spec.ts einbauen. USER-ANWEISUNG 2026-09-11: 'mach #27 und dann Etappe 3, nicht nachfragen. du machst alles, was ohne mich geht. am dienstag geht eine voll funktionsfaehige version live.' Live-Gehen braucht den Schalter NICHT (ein Mandant, BYPASSRLS-Stand laeuft heute auf alpha). Etappe 3 darf das Live-Gehen nicht blockieren. Nach jedem Durchlauf pushen. Die vorige Sitzung endete mit ~65% Kontext waehrend #27 lief; der Etappe-3-Auftrag wurde deshalb als eigenes Dokument geschrieben, gemessen statt erinnert. 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, handgepflegte Dokumentstellen uebersprungen, 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 (dkv-Executor, gwh-Planer — beide Male lag die Arbeit vollstaendig auf der Platte; git status ist die Wahrheit, nicht der Bericht). Die vollstaendige Liste steht in jedem Planer-Auftrag der spaeten Bereiche. Nebenfunde ohne Mandantenbezug, alle behoben: DKV-Download-Luecke, drohender DKV-Passwortverlust, Selbstloesch-Riegel der nie griff, Startfehler bei Neuinstallation, adminResetPassword ohne Mandanten- und Rollenpruefung, Widget-Besitzriegel bei Favoriten, sechs luegende Kommentare, und fuenf Bereiche ohne jede Testdatei."
|
"context_notes": "Gemessen am 2026-09-14: git status --porcelain leer, main == origin/main, keine Datei juenger als der letzte Commit vom 2026-09-11 17:58 — es gibt KEINE angefangene Arbeit. Die Sitzung vom 11.09. lief nach Etappe 3b bei ~70% Kontext aus, der Handoff war geschrieben; dieser hier ist die Neuaufnahme desselben Standes plus zwei Punkte, die im alten fehlten (WINDOWS #29, nie gelaufener Ship von Phase 17). Verifizierer-Hinweis zu 3b: 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 (WINDOWS #34, angenommenes Risiko) — 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.' Live-Gehen braucht den Schalter NICHT (ein Mandant, BYPASSRLS-Stand laeuft heute auf alpha); Etappe 3 darf es nicht blockieren. Nach jedem Durchlauf pushen. 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 Placeholder-Suche ueber .planning/phases/ meldet 53 Treffer, alle geprueft: es sind Prosa-Stellen, die Debt-Marker BESCHREIBEN (z.B. 17-VERIFICATION.md Zeile 135), keine unfertigen Zusammenfassungen."
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user