Files
tessera-ctl/.planning/quick/260911-gwh-mandantentrennung-etappe-2-bereiche-favo/260911-gwh-SUMMARY.md
T
schalli cc26197fa1
Tessera CI/CD / Lint & Type Check (push) Successful in 43s
Tessera CI/CD / Tests (push) Successful in 52s
Tessera CI/CD / Build & Publish Images (push) Successful in 27s
docs: Etappe 2 der Mandantentrennung abgeschlossen — alle zwoelf Bereiche gebunden und verifiziert
2026-09-11 14:18:11 +02:00

22 KiB

phase, plan, subsystem, tags, requires, provides, affects, actuals, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, coverage, duration, completed, status
phase plan subsystem tags requires provides affects actuals tech-stack key-files key-decisions patterns-established requirements-completed coverage duration completed status
quick-260911-gwh 01 database
prisma
postgresql
rls
multi-tenancy
nestjs
phase provides
quick-260911-fh9 Etappe 2 Bereich auth abgeschlossen (baseline 951 tests/60 files, tool 120/120)
favorites.service.ts: alle fuenf Methoden gebunden (forTenant), Widget-Besitzriegel in create() gegen den Fremdschluessel-Durchgriff
settings.service.ts: getSmtpConfig/saveSmtpConfig/getDecryptedSmtpConfig gebunden, Startpfad umbenannt und als sechster Hintergrunddienst-Fall markiert
Befund K (tenders/dkv haengen an getDecryptedSmtpConfig) erfuellt an allen drei Stellen
Etappe 2 der Mandantentrennung vollstaendig: 65 (Datei,Modell)-Paare klassifiziert, 68 ungebunden/178 gebunden, jeder ungebundene Rest benannt
etappe-3-mandantentrennung
etappe-4-rls-preflight
tokens tasks commits
40708 3 4
added patterns
Fremdschluessel-Durchgriff-Riegel: ein gebundener widgetInstance.findUnique VOR dem eigentlichen Schreibzugriff, damit ein FK auf eine zweite mandantengebundene Tabelle nicht am Zeilenschutz vorbei ein Existenzorakel oeffnet (T-GWH-05)
Hintergrunddienst-Startpfad-Markierung: umbenannte, eigenstaendige Methode (kein optionaler Parameter) mit Kopfkommentar, der beide Zustaende (heute falsch, nach dem Scharfschalten stumm) nennt — Vorlage DkvService.loadAnyActiveConfigForScheduler(), hier fortgeschrieben fuer SettingsService.loadAnySmtpConfigForStartupTransport()
created modified
apps/api/src/favorites/favorites.service.spec.ts
apps/api/src/settings/settings.service.spec.ts
apps/api/scripts/rls-scratch-check.mjs
apps/api/src/favorites/favorites.service.ts
apps/api/src/favorites/favorites.controller.ts
apps/api/src/settings/settings.service.ts
apps/api/src/mail/mail.module.ts
docs/mandantentrennung-etappe2-fehlerrichtung.md
docs/mandantentrennung-zugriffsklassifikation.md
docs/anleitung-entwicklung.md
.planning/WINDOWS.md
Startpfad-Ledger-Eintrag (#30) EIGENSTAENDIG, NICHT an WINDOWS #21 angeschlossen: andere Datei (mail.module.ts statt dkv-scheduler.service.ts), andere Reparatur (Transport je Versand statt Mehrmandanten-Planung), andere Verdeckungsform (Rueckfallkette statt blosser Leere)
Widget-Besitzriegel in favorites.service.ts create() gebaut, weil Pruefung 7 (Aufgabe 1) das Gelingen eines gebundenen create() mit fremdmandantiger widgetId tatsaechlich gemessen hat — der Fremdschluessel prueft am Zeilenschutz von WidgetInstance vorbei (dokumentiertes PostgreSQL-Verhalten)
settings.controller.ts bleibt unveraendert: req.tenantId ist fuer eine ADMIN-Konfigurationsseite die richtige Quelle (D-10), nicht das Claim wie bei auth
favorites.controller.ts extractContext bleibt wortgleich mit dashboard.controller.ts (Guard-Kennung), nicht das Claim wie bei auth — FavoriteLink haengt ueber widgetId an WidgetInstance, das unter der dashboard-Quelle gebunden ist
Zwei-Klienten-Testnachbau mit GRENZE als Bauform (settings.service.spec.ts): der ungebundene Nachbau bietet fuer ein Modell NUR die Methoden, die der bewusst ungebundene Pfad tatsaechlich braucht (hier: nur findFirst), der gebundene Klient NUR die Methoden der Anfragewege (findUnique/upsert) — ein gebundener Startpfad scheitert dadurch ebenso hart wie ein ungebundener Anfrageweg
WINDOWS-18
ETAPPE-2-FAVORITES
ETAPPE-2-SETTINGS
id description requirement verification human_judgment
D1 favorites.service.ts vollstaendig auf forTenant() umgestellt (5 Methoden, 7 gebundene Zugriffe, 5 Aufrufstellen), Widget-Besitzriegel in create() ETAPPE-2-FAVORITES
kind ref status
unit apps/api/src/favorites/favorites.service.spec.ts (23 Faelle) pass
kind ref status
other apps/api/scripts/rls-scratch-check.mjs runFavoritesAreaChecks (8 Pruefungen gegen den generierten Client) pass
false
id description requirement verification human_judgment
D2 settings.service.ts: getSmtpConfig/saveSmtpConfig/getDecryptedSmtpConfig gebunden, Startpfad umbenannt (loadAnySmtpConfigForStartupTransport), Befund K erfuellt ETAPPE-2-SETTINGS
kind ref status
unit apps/api/src/settings/settings.service.spec.ts (20 Faelle) pass
kind ref status
other apps/api/scripts/rls-scratch-check.mjs runSettingsAreaChecks (9 Pruefungen gegen den generierten Client) pass
false
id description requirement verification human_judgment
D3 Etappe 2 der Mandantentrennung vollstaendig dokumentiert: Klassifikation, Kritikschrift, Anleitung, Ledger auf Endstand WINDOWS-18
kind ref status
unit apps/api/src/prisma/rls-access-inventory.spec.ts (11 Faelle, Stand-Vergleich Dokument vs. Quelltext) pass
false
55min 2026-09-11 complete

Quick 260911-gwh: Etappe 2 der Mandantentrennung, Bereiche favorites und settings — LETZTER Lauf Summary

favorites.service.ts und settings.service.ts vollstaendig an forTenant() gebunden (12 gebundene Zugriffe, 8 Aufrufstellen), der Fremdschluessel-Durchgriff auf WidgetInstance gemessen und mit einem Besitzriegel geschlossen, der Mailmodul-Startpfad als sechster Hintergrunddienst-Fall markiert und ungebunden gelassen — Etappe 2 der Mandantentrennung ist damit vollstaendig: 65 (Datei,Modell)-Paare, 68 ungebunden/178 gebunden, jeder verbleibende ungebundene Rest ist ein benannter, bewusster Fall.

Performance

  • Duration: ca. 55 min
  • Tasks: 3/3
  • Files modified: 11 (2 neu, 9 geaendert)
  • Commits: 4 (plus die vorangehende PLAN.md-Ablage)

Accomplishments

  • apps/api/scripts/rls-scratch-check.mjs von 120 auf 137 bestandene Pruefungen erweitert (runFavoritesAreaChecks: 8, runSettingsAreaChecks: 9), beide an der Regel WORTGLEICH aus 20260909140000_rls_remaining_tenant_tables geschnitten, mit dem Fremdschluessel bzw. Eindeutigkeitsindex als mitgebauten Voraussetzungen.
  • favorites.service.ts: list, create, update, remove, getIconBytes laufen je ueber GENAU EINEN Klienten tenantPrisma (7 gebundene favoriteLink-Zugriffe, 1 gebundener widgetInstance-Besitzriegel in create, 5 Aufrufstellen des Bindungshilfsmittels).
  • settings.service.ts: getSmtpConfig, saveSmtpConfig, getDecryptedSmtpConfig gebunden (3 Zugriffe, 3 Aufrufstellen); Startpfad umbenannt in loadAnySmtpConfigForStartupTransport(), bleibt bewusst ungebunden, sechster Fall der Hintergrunddienst-Falle, WINDOWS #30.
  • Befund K (Reihenfolgebedingung aus tenders (t4) und dkv (d4)) ist ERFUELLT und an allen drei Stellen als solches vermerkt: (t4)-Nachtrag, (d4)-Nachtrag, Hintergrunddienst-Abschnitt der Klassifikation.
  • Zwei neue Testdateien mit dem Zwei-Klienten-Nachbau (23 + 20 = 43 neue Faelle), sechs Falsifizierungsnachweise durchgefuehrt und zurueckgenommen.
  • Alle fuenf handgepflegten Dokumentstellen auf den Endstand der Etappe 2 gebracht, DERIVIERT gegatet (rls-access-inventory.spec.ts).
  • Drei neue offene Ledger-Eintraege (#30, #31, #32).

Task Commits

  1. Aufgabe 1: Fehlerrichtung fuer favorites/settings messen — 88896d3 (docs) — 137 Pruefungen, ## Bereich favorites (f1-f5), ## Bereich settings (s1-s5), ## Etappe 2 — Abschluss, Nachtraege unter Befund K in (t4)/(d4)
  2. Aufgabe 2, RED: neue Testdateien — 8f2c13a (test) — favorites.service.spec.ts (23 Faelle), settings.service.spec.ts (20 Faelle), beide gegen die heutige Implementierung erwartungsgemaess rot
  3. Aufgabe 2, GREEN: binden, Startpfad umbenennen, Besitzriegel — b5f22e2 (feat) — alle Ziel-Signaturen, vier Falsifizierungsnachweise
  4. Aufgabe 3: Etappe 2 auf Endstand bringen — 1240932 (docs) — Ledger #30/#31/#32, Klassifikation, Anleitung, Nachtraege, zwei Dokument-Falsifizierungen

Plan metadata: 2a27d96 (docs: Plan fuer Etappe 2, Bereiche favorites und settings) — bereits vor dieser Ausfuehrung committet (Plan-Checker-Lauf).

Kein REFACTOR-Commit — die GREEN-Implementierung brauchte keine Nacharbeit.

Files Created/Modified

  • apps/api/src/favorites/favorites.service.spec.ts (NEU) — Zwei-Klienten-Nachbau, 23 Faelle
  • apps/api/src/settings/settings.service.spec.ts (NEU) — Zwei-Klienten-Nachbau mit Grenze als Bauform (ungebunden nur findFirst, gebunden nur findUnique/upsert), 20 Faelle
  • apps/api/scripts/rls-scratch-check.mjs — runFavoritesAreaChecks, runSettingsAreaChecks
  • apps/api/src/favorites/favorites.service.ts — Bindung, Besitzriegel
  • apps/api/src/favorites/favorites.controller.ts — reicht tenantId durch
  • apps/api/src/settings/settings.service.ts — Bindung, Startpfad-Umbenennung
  • apps/api/src/mail/mail.module.ts — ruft den umbenannten Startpfad
  • docs/mandantentrennung-etappe2-fehlerrichtung.md — zwei neue Bereichsabschnitte, Abschluss-Abschnitt, zwei Nachtraege
  • docs/mandantentrennung-zugriffsklassifikation.md — Uebersicht, Bestandsaufnahme, Klassen-Verteilung, Hintergrunddienst-Abschnitt
  • docs/anleitung-entwicklung.md — RLS-Tabellenzahl und Beispielabsatz auf den gemessenen Stand
  • .planning/WINDOWS.md — drei neue offene Eintraege (#30, #31, #32)

Tatsächlich gezählte Prüfungs- und Testzahlen

Werkzeug (rls-scratch-check.mjs): 120 → 137 bestandene Prüfungen (8 runFavoritesAreaChecks + 9 runSettingsAreaChecks).

Testsuite: Baseline 951 Tests / 60 Dateien (260911-fh9) → RED (Aufgabe 2, Commit 8f2c13a): 994 Tests entdeckt / 62 Dateien, 40 rot (22 favorites + 18 settings), 954 grün — beide RED-Zustände intentional, jeweils auf der geplanten Zielsignatur gescheitert, nicht an Syntax/Zero-Discovery → GREEN (Aufgabe 2, Commit b5f22e2): 43/43 neue Fälle grün, aber rls-access-inventory.spec.ts (Teil der ursprünglichen 951) mit 2 von 11 Fällen erwartungsgemäß rot, 992/994 gesamt grün → Aufgabe 3 (Commit 1240932): 994/994 grün in 62 Dateien.

Zwischenzeitlich rot: rls-access-inventory.spec.ts, zwischen Aufgabe 2 und Aufgabe 3. Genau wie das Aufgabe-3-Actionblock des Plans selbst vorhersagt ("Ohne Schritt 3 ist rls-access-inventory.spec.ts am Ende dieser Aufgabe rot") und wie der unmittelbare Vorgänger 260911-fh9 es bereits dokumentiert hat: sobald favorites.service.ts/settings.service.ts ihre Stand-Spalte änderten (ungebunden → gebunden/gemischt) und favorites.service.ts/widgetInstance als neue Fundstelle entstand, maß die Prüfung diese drei Fakten sofort — während das Klassifikationsdokument sie erst in Aufgabe 3 nachzieht. Zwei der elf Fälle scheiterten entsprechend (jede ... Fundstelle ist im Dokument eingetragen wegen der neuen widgetInstance-Zeile, der eingetragene Stand stimmt ... überein wegen der beiden Stand-Wechsel). Nicht als Blocker gewertet: (a) exakt die im Plan selbst vorausgesagte Form, (b) berührte keine Datei außerhalb der sechs für Aufgabe 2 erlaubten, (c) Aufgabe 3 folgte im selben Lauf und stellte die Baseline innerhalb von Minuten wieder her (994/994). "Baseline gehalten nach jeder Aufgabe" ist deshalb — wie schon bei 260911-fh9 — als "nach dem vollständigen Plan, mit einem im Plan selbst vorausgesagten Zwischenzustand" zu lesen, nicht als literarische Bedingung jedes einzelnen Aufgaben-<verify>-Blocks für sich.

Ergebnis von Prüfung 7 (Fremdschlüssel) — wörtlich

favoritelink-fremdschluessel-prueft-am-zeilenschutz-vorbei: ein gebundenes create unter TENANT-A mit widgetId='widget-b1' (gehört TENANT-B, unter TENANT-A per gebundenem widgetInstance.findUnique unsichtbar: null) GELINGT (id=fav-a1-fremdes-widget) — der Fremdschlüssel prüft am Zeilenschutz VORBEI, dokumentiertes PostgreSQL-Verhalten. Dasselbe create mit widgetId="widget-gibt-es-nicht" scheitert mit PrismaClientKnownRequestError (code P2003): Foreign key constraint violated on the constraint: FavoriteLink_widgetId_fkey. Ergebnis: der Besitzriegel in Aufgabe 2 war NÖTIG (nicht optional) — ohne ihn wäre der Unterschied zwischen beiden Antworten ein Existenzorakel über Mandantengrenzen gewesen (T-GWH-05).

Ergebnis von Prüfung 8 (Konfliktform) — wörtlich

smtpconfig-ungebundenes-upsert-auf-unsichtbare-zeile-scheitert-laut: ungebundenes prisma.smtpConfig.upsert({ where: { tenantId: 'TENANT-A' }, ... }) (die Form von saveSmtpConfig) wirft PrismaClientUnknownRequestError: ConnectorError(... PostgresError { code: "42501", message: "new row violates row-level security policy for table \"SmtpConfig\"" ... }) — dieselbe Fehlerklasse wie die 260910-krx-Messung für DashboardLayout (NICHT PrismaClientKnownRequestError/P2002, die Form von tenders/user). Die Regel weist den Schreibzugriff ab, bevor der Eindeutigkeitsindex überhaupt geprüft wird.

Alle sechs Falsifizierungsnachweise — Testname und Meldung wörtlich

(a) Aufgabe 2 — list probeweise auf den ungebundenen Basisclient zurückgebaut (const tenantPrisma = this.prisma as any;): 3 Fälle rot.

  • list > liefert nur die Zeilen von user-a1 für widget-a1, sortiert nach position, dann title — TypeError: Cannot read properties of undefined (reading 'findMany')
  • list > liefert unter einem FREMDEN Mandanten eine leere Liste, kein Fehler ... — dieselbe TypeError
  • Wachhund je Methode > genau EIN gebundener Klient je Aufruf von list/update/remove/getIconBytes — AssertionError: Aufruf erzeugte 0 gebundene Klienten, erwartet genau 1: expected +0 to be 1

(b) Aufgabe 2 — Widget-Besitzriegel in create probeweise entfernt. Der Plan sagte "genau die drei Widget not found-Fälle" voraus — GEMESSEN sind es 4, weil der Wachhund-Fall zusätzlich rot wird (Abweichung, siehe unten):

  • create > T-GWH-05: widgetId gehört einem ANDEREN Benutzer desselben Mandanten -> NotFoundException "Widget not found", KEIN create, KEINE Icon-Suche — AssertionError: promise resolved "{ …(10) }" instead of rejecting
  • create > T-GWH-05: widgetId gehört einem Widget unter FREMDEM Mandanten -> dieselbe NotFoundException, nennt weder Halter noch Mandant — dieselbe AssertionError-Form
  • create > T-GWH-05: unbekannte widgetId -> dieselbe NotFoundException — dieselbe Form
  • create > Wachhund: genau EIN gebundener Klient je create-Aufruf, Widget-Prüfung UND Schreibzugriff auf DEMSELBEN Klienten — AssertionError: expected [ { tenantId: 't1', …(2) } ] to deeply equal [ { tenantId: 't1', …(2) }, …(1) ]

(c) Aufgabe 2 — getDecryptedSmtpConfig probeweise auf den ungebundenen Basisclient verschoben: 9 Fälle rot, alle mit TypeError: tenantPrisma.smtpConfig.findUnique is not a function — betrifft die drei getDecryptedSmtpConfig-Fälle, alle vier testSmtpConfig-Fälle (ruft intern getDecryptedSmtpConfig auf) und beide betroffenen Wachhund-Fälle.

(d) Aufgabe 2 — Startpfad probeweise gebunden (forTenant(this.prisma, 'falsification-probe').smtpConfig.findFirst()): 4 Fälle rot, alle mit TypeError: (0 , forTenant)(...).smtpConfig.findFirst is not a function — beide Erfolgsfälle, der Leer-Nachbau-Fall und der Null-Klienten-Nachweis.

(e) Aufgabe 3 — Bestandsaufnahme-Zeile favorites.service.ts | favoriteLink probeweise auf ungebunden zurückgesetzt: rls-access-inventory.spec.ts > ... > der eingetragene Stand stimmt mit dem im Quelltext gemessenen überein — AssertionError: Abweichender Stand (Dokument vs. Quelltext): apps/api/src/favorites/favorites.service.ts::favoriteLink — dokumentiert=ungebunden, gemessen=gebunden.

(f) Aufgabe 3 — Übersichtszeile settings probeweise auf 9 | 9 gesetzt: das herleitende Gate (grep -qE "^\| settings \| ${SU} \| ${SB} \| ..." mit den tatsächlich gemessenen SU=1/SB=3) schlägt fehl — die Zeile 9 | 9 matcht die Anweisung nicht mehr.

Alle sechs Änderungen wurden unmittelbar nach der Messung zurückgenommen; diff gegen den vor der Probe gesicherten Stand bestätigt Identität in jedem Fall.

Decisions Made

  • Startpfad-Ledger-Eintrag (#30) eigenständig, nicht an WINDOWS #21 angeschlossen: andere Datei, andere Reparatur, andere Verdeckungsform — siehe key-decisions oben.
  • Widget-Besitzriegel in create() gebaut, weil Prüfung 7 (Aufgabe 1) das Gelingen des Fremdschlüssel-Durchgriffs tatsächlich gemessen hat (nicht angenommen).
  • settings.controller.ts und favorites.controller.tss extractContext bleiben unverändert — beide Mandantenquellen sind bereits die richtigen (siehe key-decisions).

Deviations from Plan

Auto-fixed / gemessene Abweichungen (keine Rule-1/2/3-Bugfixes — alles Messungen, die anders ausfielen als die Planungsvermutung)

1. Falsifizierungsnachweis (b): 4 statt 3 rote Fälle.

  • Gefunden während: Aufgabe 2, TEIL 5.
  • Planungsvermutung: "genau die drei Widget not found-Fälle werden rot".
  • Tatsächliche Messung: zusätzlich der Wachhund-Fall (create > Wachhund: ...), weil er das Bindungsprotokoll auf zwei Einträge (widgetInstance.findUnique, favoriteLink.create) prüft — ohne den Riegel gibt es nur den zweiten Eintrag.
  • Auswirkung: keine — die Falsifizierung bestätigt weiterhin, dass der Riegel notwendig ist; die Zahl ist hier korrigiert, nicht die Planungsaussage stillschweigend übernommen.

2. (d4)-Nachtrag: dkv.seed.ts/module-registry ist NICHT "gebunden seit 260910-exd".

  • Gefunden während: Aufgabe 3, TEIL 3, Nachtrag unter (d4).
  • Planungstext: "dkv.seed.ts/module-registry ist seit 260910-exd gebunden — prüfen und, falls zutreffend, in demselben Nachtrag mit einem Satz nennen."
  • Tatsächliche Messung: dkv.seed.ts ruft ModuleRegistryService.seedModule() (module-registry.service.ts:206, this.prisma.module.upsert) — UNGEBUNDEN, bewusst und unverändert, weil Module der plattformweite Modulkatalog ohne tenantId-Spalte ist (Befund E). Der Nachtrag in (d4) sagt das ausdrücklich, statt die Planungsvermutung zu übernehmen.

3. Zwischenzeitlich rotes rls-access-inventory.spec.ts zwischen Aufgabe 2 und Aufgabe 3 — siehe eigener Abschnitt oben ("Tatsächlich gezählte Prüfungs- und Testzahlen"). Vom Plan selbst vorausgesagt, kein Bug.


Total deviations: 3, alle Messergebnisse (keine Bugfixes, keine Scope-Erweiterung). Kein Rule-1/2/3-Autofix in diesem Lauf nötig. Impact on plan: keiner — der Plan bleibt in Kraft, alle drei Punkte sind Präzisierungen der eigenen Planungsvermutungen anhand der tatsächlichen Messung, wie es der Plan selbst an mehreren Stellen verlangt ("weicht eine Messung ab, gilt die Messung").

Issues Encountered

Keine. Alle Prüfungen liefen beim ersten Durchlauf durch (rls-scratch-check.mjs: 137/137 ohne Nacharbeit).

Ledger-Einträge und Entscheidung zu #21

Drei neue offene Einträge in .planning/WINDOWS.md:

  • #30 (apps/api/src/mail/mail.module.ts, deviation) — Startpfad des Mailmoduls, sechster Fall der Hintergrunddienst-Falle. Entscheidung: EIGENER Eintrag, NICHT an #21 angeschlossen — Grund: andere Datei (mail.module.ts/settings.service.ts statt dkv-scheduler.service.ts), andere Reparatur (Transport je Versand aus getDecryptedSmtpConfig(tenantId) statt Mehrmandanten-Planung), andere Verdeckungsform (Rückfallkette auf einen falschen, aber vorhandenen Transport statt bloßer Leere mit Protokollzeile).
  • #31 (apps/web/src/components/dashboard/widgets/favorites-widget.tsx, deviation) — verschluckte Leere favorites, Familie #23/#25/#26/#28.
  • #32 (apps/web/src/components/settings/smtp-settings-form.tsx, deviation) — verschluckte Leere settings, dieselbe 200-leerer-Rumpf-Kette wie #28.

Kopfzähler geprüft: open_count: 14, total_count: 32, Tabellenzeilen = 32, offene Zeilen = 14 — beide stimmen.

Endstand der Etappe 2 (aus dem Abschluss-Abschnitt der Kritikschrift, nicht neu gerechnet)

  • Zwölf Bereichs-/Regel-Läufe von 260909-ipc bis 260911-gwh.
  • Übersichtstabelle: 68 ungebundene / 178 gebundene Rohtreffer (Summe 246; zur Erinnerung: der ursprüngliche Kopf des Klassifikationsdokuments nannte 227 Rohtreffer über 59 Paare — die höhere Summe stammt vom neuen, zur Planungszeit noch nicht feststehenden widgetInstance-Besitzriegel).
  • Klassen-Verteilung: 65 (Datei,Modell)-Paare — 33 muss-mandantengebunden, 17 keine-mandantengebundene-tabelle, 13 beides, 2 bewusst-uebergreifend.
  • Werkzeug: 137/137 Prüfungen bestanden (rls-scratch-check.mjs).
  • Tests: 994/994 grün in 62 Dateien.
  • Jeder verbleibende ungebundene Rohtreffer ist einer der in docs/mandantentrennung-etappe2-fehlerrichtung.md bzw. docs/mandantentrennung-zugriffsklassifikation.md namentlich benannten, bewusst ungebundenen Fälle — keiner ist übersehen.
  • Schalter bleibt AUS (DATABASE_URL unverändert auf Rolle tessera), Schema/Migrationen/Compose/Umgebungsdateien unangetastet, Erlaubnisliste gegen 46f0e78 gehalten.

User Setup Required

None — keine externe Konfiguration nötig.

Next Phase Readiness

Etappe 2 ist mit diesem Lauf abgeschlossen. Was für Etappe 3 bleibt (siehe Abschluss-Abschnitt der Kritikschrift):

  • Anmeldeweg unter je Mandant eindeutigen Anmeldenamen (username/email).
  • Benutzerdimension der Regeln (mehrere Bereiche kennen sie nicht — FavoriteLink eingeschlossen).
  • Modulkatalog-Regel für Module (Befund E), falls Etappe 3 sie einführt.
  • Kennzeichnung der bewusst-uebergreifend-Stellen (Systemkontext).
  • Mandantenwechsel im Ausschreibungs-Digest.

Etappe 4 (rls-preflight.mjs) muss vor dem Scharfschalten die in den Bereichsabschnitten benannten Vorabprüfungen laufen lassen (Liste im Abschluss-Abschnitt der Kritikschrift) — die Befund-K-Bedingung ist davon jetzt ausgenommen, weil sie erfüllt ist.


Phase: quick-260911-gwh Completed: 2026-09-11

Self-Check: PASSED

All 12 created/modified files confirmed present on disk; all 4 task commits (88896d3, 8f2c13a, b5f22e2, 1240932) confirmed in git log.