17 KiB
phase, plan, subsystem, tags, status, dependency-graph, tech-stack, key-files, decisions, metrics, actuals
| phase | plan | subsystem | tags | status | dependency-graph | tech-stack | key-files | decisions | metrics | actuals | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| quick-260910-jab | 01 | mandantentrennung-datenbankrolle |
|
complete |
|
|
|
|
|
|
Quick 260910-jab: Die drei zu kurz greifenden Datenbankregeln schliessen — Summary
Eine handgeschriebene, lokal angewandte PostgreSQL-Migration schliesst T-JTS-02 (GroupMembership prueft nur die Gruppenseite), T-JTS-03 (ModuleGrant prueft nur die Mandantenkennung der Zeile, nicht wohin sie zeigt) und WINDOWS #19 (plattformweite TenderRssFeedSource-Zeilen waeren nach dem Scharfschalten fuer JEDEN Mandanten unsichtbar gewesen); das Wegwerf-Werkzeug misst alle drei Reparaturen an der lebenden Datenbank, mit den drei loch-behauptenden Pruefungen umgekehrt statt geloescht.
Ausgangslage — selbst gemessen (nicht uebernommen)
Zu Beginn von Aufgabe 1 frisch gemessen, gegen HEAD 93444aa:
- Tests: 833 bestanden, 56 Dateien (
npm --prefix apps/api run test) - Typpruefung: sauber (
npm --prefix apps/api run type-check) - Wegwerf-Werkzeug: 66/66 Pruefungen bestanden
(
rls-scratch-check.mjsgegentessera-ctl-db-1, Adresse172.19.0.2, zur Laufzeit ermittelt)
Diese drei Zahlen decken sich mit der in der Aufgabenstellung genannten Baseline — hier trotzdem selbst nachgemessen, nicht abgeschrieben (die Vorgabe der Aufgabenstellung verlangt genau das).
Am Ende gemessen
- Tests: 839 bestanden, 56 Dateien (+6, alle aus dem neuen
migration-sql.spec.ts-Beschreibungsblock) - Typpruefung: sauber
- Wegwerf-Werkzeug: 74/74 Pruefungen bestanden (+8: siehe Aufschlüsselung unten unter "Neue/umgekehrte Pruefungen")
Bestandszaehlung ueber Mandantengrenzen (Befund H, vor Anwendung der Migration)
Gegen die lokale Datenbank tessera ausgefuehrt, vor dem Anwenden der neuen
Migration:
memberships-cross-tenant | 0
grants-cross-tenant-group | 0
grants-cross-tenant-user | 0
total-memberships | 3
total-grants | 4
total-tenants | 1
tenderrssfeed-rows | 2
tenderrssfeed-platform-rows | 1
searchprovider-rows | 0
searchprovider-null-tenant | 0
Ergebnis null — es gibt lokal keine Zeile, die von der verschärften Regel unsichtbar würde. Für das Testsystem ist das NICHT gemessen und muss vor Etappe 4 als eigene Vorabprüfung wiederholt werden (siehe Befund H im Plan).
Was gebaut wurde
Aufgabe 1 — Migration + Wegwerf-Werkzeug
Neue Migration 20260910120000_rls_widen_membership_grant_and_platform_read
(lokal angewandt via prisma migrate deploy — nicht über npx prisma,
das versuchte ungefragt Prisma 8.0.0-rc.13 herunterzuladen und wurde
abgebrochen; stattdessen apps/api/node_modules/.bin/prisma, die im Projekt
gepinnte Version 6.19.3, dieselbe, die rls-scratch-check.mjs selbst
verwendet):
GroupMembership— die Regel prüft jetztgroupId IN (...)UNDuserId IN (...), beide gegen den laufenden Mandanten.ModuleGrant— die Regel prüft weiterhintenantId = current_tenant_id()UND zusätzlichgroupId IS NULL OR groupId IN (...)UNDuserId IS NULL OR userId IN (...).TenderRssFeedSource— vier Regeln statt einer:tenant_platform_read_policy(SELECT, schließttenantId IS NULLein),tenant_insert_policy/tenant_update_policy/tenant_delete_policy(verlangen ausnahmslos einen Mandanten).SearchProvider— unverändert, nur der erklärende Absatz im Migrationskopf.
Regelliste der lebenden Datenbank nach Anwendung (pg_policies, Auszug):
GroupMembership#tenant_isolation_policy#ALL#(groupId IN (...) AND userId IN (...))
ModuleGrant#tenant_isolation_policy#ALL#(tenantId = ... AND (groupId IS NULL OR ...) AND (userId IS NULL OR ...))
SearchProvider#tenant_isolation_policy#ALL#(tenantId = current_tenant_id())
TenderRssFeedSource#tenant_delete_policy#DELETE#(tenantId = current_tenant_id())
TenderRssFeedSource#tenant_insert_policy#INSERT##WITH CHECK (tenantId = current_tenant_id())
TenderRssFeedSource#tenant_platform_read_policy#SELECT#(tenantId = current_tenant_id() OR tenantId IS NULL)
TenderRssFeedSource#tenant_update_policy#UPDATE#USING(...)#WITH CHECK(...)
Neue/umgekehrte Pruefungen im Wegwerf-Werkzeug (66 → 74):
| Kennung | Art | Ergebnis |
|---|---|---|
groupmembership-schreiben-fremder-benutzer-abgelehnt |
Umkehr (T-JTS-02) | bestanden — Insert mit echtem user-b abgewiesen |
groupmembership-fremder-benutzer-ueber-die-wartungsrolle-weiterhin-moeglich |
neu | bestanden — Gegenmessung über Wartungsrolle gelingt |
modulegrant-fremde-gruppe-abgelehnt |
Umkehr (T-JTS-03) | bestanden |
modulegrant-fremder-benutzer-abgelehnt |
neu (zweiter D-04-Zweig) | bestanden |
modulegrant-fremde-gruppe-ueber-die-wartungsrolle-weiterhin-moeglich |
neu (legt grant-foreign-group an) |
bestanden |
tenderrssfeed-plattformzeile-gebunden-sichtbar |
Umkehr (WINDOWS #19) | bestanden |
tenderrssfeed-eigene-zeile-gebunden-weiterhin-sichtbar |
neu | bestanden |
tenderrssfeed-ungebunden-nur-die-plattformzeile |
neu (Befund F) | bestanden |
tenderrssfeed-gebundenes-aendern-der-plattformzeile-abgelehnt |
neu | bestanden |
tenderrssfeed-gebundenes-loeschen-der-plattformzeile-abgelehnt |
neu | bestanden |
searchprovider-mandantenlose-zeile-bleibt-unter-jedem-kontext-unsichtbar |
neu (eigener Bereich) | bestanden |
Extraktion umgeleitet: readRlsWidenMigrationSql() liest die neue Datei
für GroupMembership/ModuleGrant; extractAllPolicySql() (neu, mit
globalem Regex-Flag) liefert alle vier TenderRssFeedSource-Policies. Die
Meldung von gruppenpfad-gebunden-schliesst-die-fremde-gruppe-aus im
Bereich module-registry ist richtiggestellt (behauptete vorher fälschlich,
die Regel lasse die fremde Zeile durch).
Aufgabe 2 — listForUser binden
TenderRssFeedSourceService.listForUser(userId, tenantId) läuft jetzt über
forTenant(). TendersController.listRssFeeds reicht die Mandantenkennung
aus dem Sitzungsnachweis durch (Aufrufform geprüft, nicht die Antwort).
createPlatform/remove bleiben bewusst ungebunden. Fünf Aufzeichnungen im
Quelltext (drei Kopfkommentare in tender-rss-feed.service.ts,
module-access.service.ts, groups.service.ts, module-grants.service.ts,
rls-coverage.spec.ts) nennen die neue Migration und beschreiben die neue
Regel statt der alten. Die Mandanten-Gegenprüfung
(assertTargetBelongsToTenant) bleibt unverändert bestehen — zwei bestehende
Testfälle in module-grants.service.spec.ts wurden um einen Kommentar
ergänzt, der festhält, warum sie nach der Regeländerung NICHT entbehrlich
geworden sind.
Deviation, gemessen statt geglaubt (siehe <output>-Vorgabe des Plans):
die Behauptung "kein Anwendungscode muss sich ändern" trifft für GENAU EINEN
Pfad nicht zu — TenderRssFeedSourceService.listForUser. Vor der
Regeländerung lieferte der ungebundene Pfad nach dem Scharfschalten NICHTS
(gemessen: tenderrssfeed-ungebunden-nur-die-plattformzeile würde ohne
Bindung 0 statt 1 Zeile liefern). Nach der Regeländerung liefert derselbe
ungebundene Pfad NUR die plattformweiten Zeilen — eine kurze, glaubhafte
Teilantwort statt einer schreienden Leere. Das Binden gehört deshalb zur
Reparatur selbst, nicht daneben: ohne diese Bindung hätte 260910-jab einen
Pfad still von "meldet sich laut" auf "täuscht Vollständigkeit vor"
verschlechtert.
Falsifizierungsnachweis (Bindungsaufruf zurückgenommen, Test rot gesehen, Rücknahme zurückgenommen):
Backup genommen → const tenantPrisma = ... entfernt, this.prisma direkt verwendet
npm --prefix apps/api run test -- src/tenders/tender-rss-feed.service.spec.ts
× listForUser() bindet — forTenant() wird mit der uebergebenen Mandantenkennung aufgerufen
AssertionError: expected "spy" to be called with arguments: [ {…(4)}, 'tenant-a' ]
Number of calls: 0
Test Files 1 failed (1)
Tests 1 failed | 30 passed (31)
→ Rücknahme rückgängig gemacht (Backup wiederhergestellt), erneuter Lauf: 31/31 grün
Genau EIN Test wurde rot, wie erwartet — kein anderer Test hing versehentlich an dieser Bindung.
Rule 1 (Auto-Fix): listRssFeeds destrukturierte feeds.map(({userId, ...rest}) => ...).
Da listForUser jetzt über forTenant(...) as any läuft, wurde feeds
implizit any, und TypeScript meldete TS7031: Binding element 'ownerUserId' implicitly has an 'any' type. Behoben mit expliziter (... : any)-Annotation,
Muster aus anderen .map((g: any) => ...)-Stellen derselben Datei.
Typpruefung danach wieder sauber.
Aufgabe 3 — Aktenstand kohärent
.planning/WINDOWS.md: #19 auffixed(Tabelle + JSON-Block,resolved_at: 2026-09-10T12:35:40.000Z), Beleg nennt die neue Migration und die Prüfungen für beide Fehlerrichtungen des Lese-/Schreibsplits. #18/#20/#21/#22/#23 unverändert offen. Neuer Eintrag #24 (offen, Kinddeviation): der Verwaltungsweg für plattformweite Zeilen unter der Anwendungsrolle fehlt — weder Anlegen noch Entfernen ist unter der Anwendungsrolle möglich, in der alten wie in der neuen Regel. Kopfzahlen aus dem JSON-Block abgeleitet und mit einem eigenen Python-Skript gegengeprüft (identisch zum Verify-Skript des Plans): 6 offen, 17 behoben, 1 zurückgestellt, 24 gesamt.- Klassifikation: #19-Block beantwortet, die vier betroffenen
Bestandsaufnahme-Zeilen nachgezogen, Übersichtszeile
tenders(35/27, gemessen mit den beiden im Dokument selbst geführten grep-Anweisungen) und Summenzeile (107/135) neu aus dem Quelltext abgeleitet — beide mit dem exaktenawk-Prüfskript des Plans gegengeprüft. - Kritikschrift: neuer Abschnitt
## Regelschluss T-JTS-02, T-JTS-03 und WINDOWS #19mit (r1) tatsächlich beobachteter Ausgabe + Regelliste aus der lebenden Datenbank, (r2) Signaltabelle mit beiden Fehlerrichtungen je Regel, (r3) den Stellen, an denen Leere weiterhin als Abwesenheit gedeutet wird (inklusive der neuen Stelle aus Befund F), (r4) was dieser Durchlauf nicht löst, (r5) was er nicht anfasst. Fünf überholte Bestandsstellen mit Nachträgen versehen (der Punkt in (g4), der Punkt in (t4), die Signaltabellenzeile der drei RSS-Pfade, DREI aufgezeichnete Werkzeugausgaben — eine mehr als die im Plan als Minimum genannten zwei, weil beim Durchsuchen eine dritte literale Zitatstelle im Abschnittmodule-registrygefunden wurde — und der Punkt in (m4)). Alle alten Messprotokolle bleiben wörtlich stehen, die Nachträge stehen daneben. - Selbst ausgeführte Messung zur fehlenden Benutzerdimension (Aufgabe 3,
wie vom Plan verlangt):
grep -rn "current_setting\|set_config" apps/api/src apps/api/prisma/migrationsfindet GENAU EINE Sitzungsvariable,app.current_tenant(prisma-tenant.extension.ts,20260618112133_rls_policies). Es gibt KEINE zweite Sitzungsvariable für den Benutzer — Tabellen wieTenderSavedSearchhaben deshalb strukturell keine Möglichkeit, eine Benutzerdimension auf Datenbankebene durchzusetzen, ohne eine solche Variable erst einzuführen. Ergebnis in (r4) der Kritikschrift festgehalten, nicht gebaut. - Betriebsanleitung: die eine Stelle, die #19 als offen führte, nennt jetzt den Auflösungsstand und WINDOWS #24.
Deviations from Plan
Auto-fixed Issues
1. [Rule 1 - Bug] TenderRssFeedSource-Wegwerf-UPDATE-Prüfung zielte auf
eine nicht existierende Spalte label
- Gefunden während: Aufgabe 1, erster Lauf des Wegwerf-Werkzeugs nach dem Hinzufügen der neuen UPDATE-Prüfung
- Problem:
tenderrssfeed-gebundenes-aendern-der-plattformzeile-abgelehntversuchteSET label = ..., aber die im selben Abschnitt angelegte Wegwerf-Tabelle hat keinelabel-Spalte (id, url, userId, tenantId) — die Prüfung "bestand", aber aus dem FALSCHEN Grund (SQL-Fehler42703statt der beabsichtigten RLS-Abweisung). - Fix: Spalte auf
urlgeändert, die tatsächlich existiert; danach bestand die Prüfung aus dem richtigen Grund (tenant_update_policyfiltert die Zielzeile heraus, 0 betroffene Zeilen). - Dateien:
apps/api/scripts/rls-scratch-check.mjs - Commit:
f4f3115
2. [Rule 1 - Bug] Implizites any beim Destrukturieren in listRssFeeds
- Gefunden während: Aufgabe 2, Typprüfung nach dem Binden von
listForUser - Problem:
feeds.map(({ userId: ownerUserId, ...rest }) => ...)lösteTS7031aus, weilfeedsdurch die neueforTenant(...) as any-Bindung implizitanywurde. - Fix: explizite
(... : any)-Annotation am Destrukturierungsparameter. - Dateien:
apps/api/src/tenders/tenders.controller.ts - Commit:
6b23735
Package-Legitimitätsgatter
3. [Rule 3 – ausgeschlossen, keine Installation] npx prisma migrate deploy versuchte beim Ausführen ungefragt, prisma@8.0.0-rc.13
herunterzuladen (weiter als die im Projekt gepinnte 6.19.3) — abgebrochen
(pkill), stattdessen apps/api/node_modules/.bin/prisma verwendet, die
lokale, im Lockfile gepinnte Version. Keine Installation fand statt, daher
kein Checkpoint nötig — dokumentiert, weil es beinahe eine unbeabsichtigte
Fremdversion in den Migrationslauf eingeschleust hätte.
Keine weiteren Abweichungen — alle übrigen Aufgaben wie im Plan beschrieben ausgeführt.
Known Stubs
Keine.
Threat Flags
Keine neuen — das Threat-Model des Plans (T-JAB-01 bis T-JAB-15) deckt alle in diesem Durchlauf berührten Flächen bereits ab; keine neue, dort nicht erfasste Angriffsfläche entstanden.
Self-Check: PASSED
apps/api/prisma/migrations/20260910120000_rls_widen_membership_grant_and_platform_read/migration.sql— FOUND- Commit
f4f3115(Aufgabe 1) — FOUND (git log --oneline --all | grep f4f3115) - Commit
6b23735(Aufgabe 2) — FOUND - Commit
03fb3bf(Aufgabe 3) — FOUND rls-scratch-check.mjsmeldet 74/74 bestanden gegen die lebende Datenbank — bestätigt (letzter Lauf vor diesem Bericht).planning/WINDOWS.mdbesteht das plan-eigene Python-Kohärenzskript — bestätigtnpm --prefix apps/api run test— 839/839 grün,type-checksauber — bestätigt