docs(quick-260910-das): Plan-Revision — alle vier handgepflegten Dokumentstellen maschinell gegatet

Der Plan benannte vier handgepflegte Stellen als die, die im dkv-Durchlauf
uebersehen wurden, gatete aber nur zwei davon. Ein Ausfuehrender haette Aufgabe 3
abschliessen, jede Pruefung bestehen und Uebersichtszeile, Summenzeile und
Klassen-Verteilung stehen lassen koennen — derselbe Fehler, gegen den der Plan
schuetzen sollte, im Gate des Plans selbst.

Die beiden fehlenden Pruefungen leiten ihre Erwartung ab, statt sie zu raten:
die Uebersichtszeile gegen die am Quelltext neu ermittelten Rohtrefferzahlen
(mit den beiden Befehlen, die das Dokument selbst nennt), die Summenzeile gegen
die Addition der zwoelf Bereichszeilen, die Klassen-Verteilung gegen die ueber
die Bestandsaufnahme nachgezaehlten Klassen samt Summe und Ueberschrift.

Falsifiziert statt behauptet: gruen gegen das heutige, in sich stimmige Dokument;
rot gegen den heutigen unumgestellten Baum; und rot in vier getrennten Mutationen,
je eine stehen gelassene Handstelle — Uebersichtszeile, Summenzeile, Klassenzahl,
Ueberschrift. Gruen erst, wenn Code umgestellt UND alle vier nachgezogen sind.

Zusatzauflage, die daraus folgt: der gebundene Klient heisst in jeder Methode
tenantPrisma (Konvention aus ldap/groups/dkv/auth) — ein anderer Name liesse die
Gebunden-Zaehlung untertreiben und die Zeile ihrer Aussage berauben.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
This commit is contained in:
2026-09-10 10:02:39 +02:00
parent 5f6088cc11
commit 4fdd6eed34
@@ -35,7 +35,7 @@ must_haves:
- "Die uebergreifende Sicht des Plattform-Administrators bleibt erhalten, ohne ungebunden zu sein: sie laeuft als Schleife ueber alle Mandanten mit je EINEM gebundenen Lesezugriff im Rumpf — dieselbe Form, die die Standardgruppen-Reparatur beim Start bereits benutzt. Dass der Schleifentreiber selbst ungebunden lesen darf, haengt daran, dass die Mandantentabelle keinen Zeilenschutz traegt, und das ist gemessen."
- "Eine bereits bestehende, heute wirksame Luecke ist geschlossen: der Riegel gegen das Loeschen des eigenen Kontos vergleicht gegen ein Feld, das der Sitzungsnachweis gar nicht traegt, und greift deshalb nie. Ein Administrator kann sich heute selbst loeschen und einen Mandanten ohne Verwaltung zuruecklassen."
- "Die Testlage dieses Bereichs ist repariert und die Form der Luecke ist benannt: zwei vorhandene Testdateien haben KEINE Attrappe fuer das Bindungshilfsmittel und waeren nach der Umstellung aus dem falschen Grund rot geworden, die Steuerungsschicht hat gar keine Testdatei. Nach diesem Durchlauf existiert je Datei ein Zwei-Klienten-Nachweis, dessen Rotwerden durch probeweisen Rueckbau belegt ist."
- "Klassifikationsdokument und `rls-access-inventory.spec.ts` zeigen fuer alle Paare dieses Bereichs denselben, maschinell gemessenen Stand — einschliesslich des NEUEN Paares, das die Schleife ueber alle Mandanten in `user.service.ts` erzeugt. Uebersichtszeile, Summenzeile, Klassen-Verteilungstabelle samt ihrer Summe und der Abschnitt zur Hintergrunddienst-Falle sind von Hand nachgezogen; genau diese vier Stellen wurden im `dkv`-Durchlauf uebersehen."
- "Klassifikationsdokument und `rls-access-inventory.spec.ts` zeigen fuer alle Paare dieses Bereichs denselben, maschinell gemessenen Stand — einschliesslich des NEUEN Paares, das die Schleife ueber alle Mandanten in `user.service.ts` erzeugt. Uebersichtszeile, Summenzeile, Klassen-Verteilungstabelle samt ihrer Summe und der Abschnitt zur Hintergrunddienst-Falle sind von Hand nachgezogen — und jede der vier haengt an einer eigenen maschinellen Pruefung, die die Zahlen aus Quelltext beziehungsweise Bestandsaufnahme neu ableitet, statt nur das Vorhandensein einer Zeile zu bestaetigen. Genau diese vier Stellen wurden im `dkv`-Durchlauf uebersehen; eine Pruefung, die eine stehen gebliebene Fassung bestehen liesse, waere schlimmer als keine, weil sie Sicherheit vortaeuscht."
- "789 Tests und die Typpruefung sind gruen, das Wegwerf-Werkzeug meldet alle Pruefungen bestanden; Schema, Migrationen, alle vier Compose-Dateien und beide Beispiel-Umgebungsdateien sind unveraendert; `DATABASE_URL` zeigt weiterhin auf die Rolle `tessera`, der Schalter bleibt AUS."
artifacts:
- apps/api/scripts/rls-scratch-check.mjs
@@ -893,8 +893,22 @@ Stellen, die im `dkv`-Durchlauf uebersehen wurden, und sie sind alle handgepfleg
- Die Bereichszeile `user` der Uebersichtstabelle mit den bei der Ausfuehrung NEU
gemessenen Zahlen fortschreiben, im Stil der bereits fortgeschriebenen Zeilen
(`war X/0` plus eine Begruendung, welche Zugriffe umgestellt wurden und welche
bewusst nicht). Die Summenzeile mitziehen.
(`war 17/0` plus eine Begruendung, welche Zugriffe umgestellt wurden und welche
bewusst nicht). Die Zahlen werden mit den beiden Befehlen ermittelt, die das
Dokument selbst ueber dieser Tabelle nennt — der eine zaehlt die ungebundenen
Rohtreffer, der andere die ueber den Namen `tenantPrisma` gebundenen. Die
Pruefung dieser Aufgabe ermittelt sie erneut und vergleicht sie gegen die Zeile:
das Dokument wird also an den Code angeglichen, nicht umgekehrt. Zur
Planungszeit an vier bereits umgestellten Bereichen gegengeprueft, dass diese
beiden Befehle deren Tabellenzeilen exakt reproduzieren. Daraus folgt eine
Auflage fuer Aufgabe 2 und 3, die sonst still unterlaufen wuerde: der gebundene
Klient heisst in jeder Methode `tenantPrisma`, wie in `ldap`, `groups`, `dkv` und
`auth` — ein anderer Name wuerde die zweite Zaehlung untertreiben lassen und die
Zeile ihrer Aussage berauben.
- Die Summenzeile mitziehen. Die Pruefung dieser Aufgabe addiert die zwoelf
Bereichszeilen und haelt das Ergebnis gegen die Summenzeile — eine
fortgeschriebene Bereichszeile ueber einer stehen gebliebenen Summe faellt damit
durch, genau der Fehler, der im `dkv`-Durchlauf durchrutschte.
- Die vier vorhandenen Bestandsaufnahme-Zeilen des Bereichs auf den maschinell
gemessenen Stand setzen und dabei ZWEI Klassenkorrekturen vornehmen, jede mit
Begruendung: `user.service.ts`/`user` wechselt wegen der einen bewusst
@@ -911,7 +925,15 @@ Stellen, die im `dkv`-Durchlauf uebersehen wurden, und sie sind alle handgepfleg
wird `rls-access-inventory.spec.ts` rot (Befund N).
- Die Klassen-Verteilungstabelle samt ihrer Summe und der Ueberschrift, die die
Paarzahl nennt, auf die neuen Zahlen ziehen. Die Zahlen kommen aus der Ausgabe der
Inventarpruefung, nicht aus einer Rechnung im Kopf.
Inventarpruefung, nicht aus einer Rechnung im Kopf. Die Pruefung dieser Aufgabe
zaehlt die Klassen ueber die Bestandsaufnahme-Zeilen selbst nach und haelt vier
Dinge gegeneinander: jede einzelne Klassenzahl, die Summe der Klassenzeilen, die
ausgewiesene Summenzeile und die Paarzahl in der Ueberschrift. Weil dieser
Durchlauf mit dem Schleifentreiber ein zusaetzliches Paar erzeugt, wandern alle
vier gemeinsam — eine stehen gebliebene faellt durch. Zur Planungszeit gegen das
heutige, in sich stimmige Dokument getestet (es besteht) und gegen eine
mutierte Fassung, in der genau dieser Fehler nachgestellt wurde (sie faellt
durch, mit der Meldung, welche Zahl klemmt).
- Den Abschnitt zum Hintergrunddienst als Falle um den fuenften Fall erweitern und
seine Ueberschrift, die heute vier Faelle nennt, mitziehen. Der fuenfte Fall ist die
Standardgruppen-Reparatur beim Start (Befund K) und ist ausdruecklich als der
@@ -934,9 +956,9 @@ urspruengliche Text NICHT umgeschrieben — er beschreibt korrekt den Zustand zu
Zeitpunkt der Umstellung; der Nachtrag steht daneben.
</action>
<verify>
<automated>DB_IP=$(docker inspect tessera-ctl-db-1 --format '{{range $k,$v := .NetworkSettings.Networks}}{{$v.IPAddress}}{{end}}') && TESSERA_SCRATCH_ADMIN_URL="postgresql://tessera:tessera_dev@${DB_IP}:5432/postgres" node apps/api/scripts/rls-scratch-check.mjs && npm --prefix apps/api run test && npm --prefix apps/api run type-check && npm --prefix apps/api run test -- src/prisma/rls-access-inventory.spec.ts && test -f apps/api/src/user/user.controller.spec.ts && grep -q '__makeBoundClient' apps/api/src/user/user.controller.spec.ts && grep -qE '^\| apps/api/src/user/user\.controller\.ts \| user \| muss-mandantengebunden \| gebunden \|' docs/mandantentrennung-zugriffsklassifikation.md && grep -qE '^\| apps/api/src/user/user\.service\.ts \| user \| beides \| gemischt \|' docs/mandantentrennung-zugriffsklassifikation.md && grep -qE '^\| apps/api/src/user/user\.service\.ts \| tenant \| keine-mandantengebundene-tabelle \| ungebunden \|' docs/mandantentrennung-zugriffsklassifikation.md && grep -qE '^\| apps/api/src/user/admin-seed\.service\.ts \| user \| beides \| gemischt \|' docs/mandantentrennung-zugriffsklassifikation.md && grep -qE '^\| apps/api/src/user/admin-seed\.service\.ts \| tenant \| keine-mandantengebundene-tabelle \| ungebunden \|' docs/mandantentrennung-zugriffsklassifikation.md && grep -qE '^## Der Hintergrunddienst als Falle — fünf Fälle$' docs/mandantentrennung-zugriffsklassifikation.md && test -z "$(git diff --name-only HEAD -- apps/api/prisma apps/api/src/auth apps/api/src/ldap docker-compose.yml docker-compose.dev.yml docker-compose.prod.yml docker-compose.ci.yml .env.example .env.prod.example)"</automated>
<automated>DB_IP=$(docker inspect tessera-ctl-db-1 --format '{{range $k,$v := .NetworkSettings.Networks}}{{$v.IPAddress}}{{end}}') && TESSERA_SCRATCH_ADMIN_URL="postgresql://tessera:tessera_dev@${DB_IP}:5432/postgres" node apps/api/scripts/rls-scratch-check.mjs && npm --prefix apps/api run test && npm --prefix apps/api run type-check && npm --prefix apps/api run test -- src/prisma/rls-access-inventory.spec.ts && test -f apps/api/src/user/user.controller.spec.ts && grep -q '__makeBoundClient' apps/api/src/user/user.controller.spec.ts && grep -qE '^\| apps/api/src/user/user\.controller\.ts \| user \| muss-mandantengebunden \| gebunden \|' docs/mandantentrennung-zugriffsklassifikation.md && grep -qE '^\| apps/api/src/user/user\.service\.ts \| user \| beides \| gemischt \|' docs/mandantentrennung-zugriffsklassifikation.md && grep -qE '^\| apps/api/src/user/user\.service\.ts \| tenant \| keine-mandantengebundene-tabelle \| ungebunden \|' docs/mandantentrennung-zugriffsklassifikation.md && grep -qE '^\| apps/api/src/user/admin-seed\.service\.ts \| user \| beides \| gemischt \|' docs/mandantentrennung-zugriffsklassifikation.md && grep -qE '^\| apps/api/src/user/admin-seed\.service\.ts \| tenant \| keine-mandantengebundene-tabelle \| ungebunden \|' docs/mandantentrennung-zugriffsklassifikation.md && grep -qE '^## Der Hintergrunddienst als Falle — fünf Fälle$' docs/mandantentrennung-zugriffsklassifikation.md && U=$(grep -ro "this\.prisma\.[a-zA-Z]*" apps/api/src/user | grep -v spec | wc -l | tr -d ' ') && B=$(grep -ro "tenantPrisma\.[a-zA-Z]*\." apps/api/src/user | grep -v spec | wc -l | tr -d ' ') && { test "$U" -lt 17 || { echo "UEBERSICHTSZEILE: ungebundene Rohtreffer in apps/api/src/user sind $U, also nicht gesunken — es wurde nichts umgestellt"; exit 1; }; } && { test "$B" -gt 0 || { echo "UEBERSICHTSZEILE: gebundene Rohtreffer in apps/api/src/user sind $B"; exit 1; }; } && { grep -qE "^\| user \| ${U} \| ${B} \| \*\*war 17/0\*\*" docs/mandantentrennung-zugriffsklassifikation.md || { echo "UEBERSICHTSZEILE user nennt nicht die neu gemessenen Zahlen ${U}/${B} im etablierten Stil"; exit 1; }; } && awk -F'|' '$2 ~ /^ *[a-z][a-z-]* *$/ && $3 ~ /^ *[0-9]+ *$/ && $4 ~ /^ *[0-9]+ *$/ { su+=$3; sb+=$4; n++ } $2 ~ /^ *\*\*Summe\*\* *$/ && $4 !~ /^ *$/ { g3=$3; g4=$4; gsub(/[^0-9]/,"",g3); gsub(/[^0-9]/,"",g4); ru=g3+0; rb=g4+0; seen=1 } END { if (!seen || n+0 != 12) { print "UEBERSICHTSTABELLE nicht erkannt, Bereichszeilen: " n; exit 1 } if (su+0 != ru || sb+0 != rb) { print "SUMMENZEILE stimmt nicht: Bereichszeilen ergeben " su "/" sb ", Summenzeile nennt " ru "/" rb; exit 1 } }' docs/mandantentrennung-zugriffsklassifikation.md && awk -F'|' '$2 ~ /^ *apps\/api\/src\// { k=$4; gsub(/^ +| +$/,"",k); cls[k]++; pairs++ } $2 ~ /^ *(muss-mandantengebunden|keine-mandantengebundene-tabelle|beides|bewusst-uebergreifend) *$/ && $3 ~ /^ *[0-9]+ *$/ && $4 ~ /^ *$/ { k=$2; gsub(/^ +| +$/,"",k); v=$3; gsub(/[^0-9]/,"",v); tab[k]=v+0; tn++ } $2 ~ /^ *\*\*Summe\*\* *$/ && $4 ~ /^ *$/ { v=$3; gsub(/[^0-9]/,"",v); tsum=v+0; tseen=1 } /^## Klassen-Verteilung/ { h=$0; gsub(/[^0-9]/,"",h); hp=h+0; hseen=1 } END { if (tn+0 != 4 || !tseen || !hseen) { print "KLASSEN-VERTEILUNG nicht erkannt: Klassenzeilen " tn ", Summenzeile " tseen ", Ueberschrift " hseen; exit 1 } if (tsum != pairs+0) { print "KLASSEN-SUMME stimmt nicht: Bestandsaufnahme hat " pairs " Paare, Tabellensumme nennt " tsum; exit 1 } if (hp != pairs+0) { print "UEBERSCHRIFT der Klassen-Verteilung nennt " hp " Paare, Bestandsaufnahme hat " pairs; exit 1 } s=0; for (k in tab) { if (tab[k] != cls[k]+0) { print "KLASSE " k ": Tabelle nennt " tab[k] ", Bestandsaufnahme zaehlt " cls[k]+0; exit 1 } s+=tab[k] } if (s != pairs+0) { print "KLASSENZEILEN ergeben " s ", Bestandsaufnahme hat " pairs; exit 1 } }' docs/mandantentrennung-zugriffsklassifikation.md && test -z "$(git diff --name-only HEAD -- apps/api/prisma apps/api/src/auth apps/api/src/ldap docker-compose.yml docker-compose.dev.yml docker-compose.prod.yml docker-compose.ci.yml .env.example .env.prod.example)"</automated>
</verify>
<done>Alle sieben Zugriffe der Steuerungsschicht laufen ueber `forTenant()` beziehungsweise ueber die uebergreifenden Methoden des Dienstes, deren Rumpf je Mandant gebunden ist; die Rollenverzweigung zwischen mandantengebundener und uebergreifender Sicht ist erhalten und durch Tests belegt; der Riegel gegen das Loeschen des eigenen Kontos greift, und sein Rotwerden vor der Reparatur ist im SUMMARY festgehalten; `apps/api/src/user/user.controller.spec.ts` existiert, nutzt den Zwei-Klienten-Nachweis und deckt die acht in `<behavior>` genannten Faelle ab; der Testlauf ist gruen mit mehr als 789 Tests und die Typpruefung sauber; `rls-access-inventory.spec.ts` laeuft gruen und stimmt mit den fuenf Bestandsaufnahme-Zeilen des Bereichs ueberein, einschliesslich der neu ergaenzten Zeile fuer den Schleifentreiber; die beiden Klassenkorrekturen sind mit Begruendung vollzogen; Uebersichtszeile, Summenzeile, Klassen-Verteilungstabelle samt Summe und Ueberschrift sowie der Abschnitt zur Hintergrunddienst-Falle samt seiner Ueberschrift sind von Hand nachgezogen; die Kritikschrift traegt den Nachtrag mit den tatsaechlich umgesetzten Pfaden; das Wegwerf-Werkzeug meldet alle Pruefungen bestanden; `auth.service.ts`, `ldap.service.ts`, Schema, Migrationen, Compose- und Beispiel-Umgebungsdateien sind unveraendert und `DATABASE_URL` zeigt weiterhin auf die Rolle `tessera`.</done>
<done>Alle sieben Zugriffe der Steuerungsschicht laufen ueber `forTenant()` beziehungsweise ueber die uebergreifenden Methoden des Dienstes, deren Rumpf je Mandant gebunden ist; die Rollenverzweigung zwischen mandantengebundener und uebergreifender Sicht ist erhalten und durch Tests belegt; der Riegel gegen das Loeschen des eigenen Kontos greift, und sein Rotwerden vor der Reparatur ist im SUMMARY festgehalten; `apps/api/src/user/user.controller.spec.ts` existiert, nutzt den Zwei-Klienten-Nachweis und deckt die acht in `<behavior>` genannten Faelle ab; der Testlauf ist gruen mit mehr als 789 Tests und die Typpruefung sauber; `rls-access-inventory.spec.ts` laeuft gruen und stimmt mit den fuenf Bestandsaufnahme-Zeilen des Bereichs ueberein, einschliesslich der neu ergaenzten Zeile fuer den Schleifentreiber; die beiden Klassenkorrekturen sind mit Begruendung vollzogen; alle VIER handgepflegten Stellen sind nachgezogen UND einzeln maschinell gegatet — die Uebersichtszeile gegen die am Code neu ermittelten Rohtrefferzahlen, die Summenzeile gegen die Addition der zwoelf Bereichszeilen, die Klassen-Verteilung gegen die ueber die Bestandsaufnahme nachgezaehlten Klassen samt Summe und Ueberschrift, und der Abschnitt zur Hintergrunddienst-Falle gegen seine byte-genaue Ueberschrift; keine dieser vier Pruefungen kann von einer stehen gebliebenen Fassung bestanden werden; die Kritikschrift traegt den Nachtrag mit den tatsaechlich umgesetzten Pfaden; das Wegwerf-Werkzeug meldet alle Pruefungen bestanden; `auth.service.ts`, `ldap.service.ts`, Schema, Migrationen, Compose- und Beispiel-Umgebungsdateien sind unveraendert und `DATABASE_URL` zeigt weiterhin auf die Rolle `tessera`.</done>
</task>
</tasks>