Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
23 KiB
phase, plan, subsystem, tags, status, requires, provides, affects, tech-stack, key-files, decisions, metrics, actuals, plan_head_before
| phase | plan | subsystem | tags | status | requires | provides | affects | tech-stack | key-files | decisions | metrics | actuals | plan_head_before | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| quick-260914-eym | 01 | mandantentrennung |
|
complete |
|
|
|
|
|
|
|
|
02016e19eb |
Quick 260914-eym: Mandantentrennung Etappe 3c — Systemkontext fuer die Hintergrunddienste — Summary
Benannter Systemkontext forSystem(prisma) mit is_system_context() und je einer nur lesenden system_read_policy FOR SELECT auf fuenf Tabellen; alle sechs Hintergrunddienst-Faelle behandelt (DKV-Planer je Mandant, Mail-Transport je Versand mit entferntem Startpfad, ldap/digest/matching ueber den Systemkontext, admin-seed dokumentiert); Detektor mit fuenfter Erkennungsform und falsifizierbarer Erlaubnisliste; Werkzeug 203 -> 253; WINDOWS #21 und #30 geschlossen, #37 neu. Der Schalter bleibt AUS.
Commits
939c812 docs(quick-260914-eym): Etappe 3c abgeschlossen — Kritikschrift, Klassifikation, Auftrag, Datenbankrolle, WINDOWS #21/#30 geschlossen, Single-Flight-Riegel als Eintrag
6e2a641 feat(quick-260914-eym): Mail-Transport je Versand nach Mandant (WINDOWS #30), ldap/digest/matching ueber Systemkontext, vier Tabellen im Werkzeug, Erlaubnisliste vollstaendig
3d64567 feat(quick-260914-eym): forSystem(), is_system_context(), Systemleseregel auf fuenf Tabellen, DKV-Planer je Mandant — ein Pfad (WINDOWS #21)
git log --oneline 02016e1..HEAD (oben, drei Commits). git rev-list --count 02016e1..HEAD = 3. Gepusht: git push -> 5e0e408..939c812 main -> main; git fetch -q && git status -sb | head -1 -> ## main...origin/main; git rev-parse HEAD == git rev-parse origin/main.
git status --porcelain vor dem Schreiben dieser SUMMARY: leer (keine Ausgabe).
Hinweis zum Branch: das Projekt committet seit jeher direkt auf main (alle Quick-Tasks, Plan-Gate HEAD == origin/main, Auftrag "plain git push") — dem Projekt-Workflow gefolgt; git.allow_default_branch_commits ist in .planning/config.json nicht gesetzt.
Gemessene Zahlen (beobachtet, nicht abgeschrieben)
| Messpunkt | Baseline (HEAD 5e0e408/02016e1) | nach Aufgabe 1 (3d64567) |
nach Aufgabe 2 (6e2a641) |
Ende (939c812) |
|---|---|---|---|---|
npm --prefix apps/api run test — Test Files |
62 | 63 | 64 | 64 |
| Tests | 1028 | 1051 | 1054 | 1054 |
npm --prefix apps/api run type-check Exit |
0 | 0 | 0 | 0 |
rls-scratch-check.mjs Schlusszeile |
Alle 203 Pruefungen bestanden. | Alle 216 Pruefungen bestanden. | Alle 253 Pruefungen bestanden. | 253 (kein apps-Diff seit Aufgabe 2, Gate git diff --stat HEAD~1 -- apps leer) |
prisma migrate status |
35 Migrationen, up to date | 36 Migrationen, up to date | 36 | 36 |
pg_proc is_system_context |
0 | 1 | 1 | 1 |
pg_policies public gesamt / system_read_policy |
29 / 0 | 34 / 5 | 34 / 5 | 34 / 5 |
git diff --stat 5e0e408 -- . ':!.planning' Dateien |
— | — | — | 29 (29 files changed, 2499 insertions(+), 491 deletions(-)) |
| Ledger (aus Zeilen gezaehlt) | open 16 / waived 1 / fixed 19 / total 36 | — | — | open 15 / waived 1 / fixed 21 / total 37 (Frontmatter identisch) |
Plan-Erwartung vs. beobachtet: Tests erwartet >= 1040 / >= 1044, beobachtet 1051 / 1054; Werkzeug erwartet >= 216 / >= 250 (abgeleitet 216 / 253), beobachtet exakt 216 / 253; Uebersichtstabelle erwartet 61/179/5 (tenders 33/27/2, ldap 1/27/2, dkv 0/22/1, settings 0/3/0), mit der Gate-Schleife nachgerechnet: identisch.
Umgebung: Container tessera-ctl-db-1 lief beim Einstieg bereits (Up 25 minutes (healthy), vom Planer gestartet); IP per docker inspect 172.19.0.2; DB-Zugang tessera:tessera_dev; Prisma-Binary apps/api/node_modules/.bin/prisma. Schalter-Gate: git diff --name-only 5e0e408 nennt keine Compose-, .env-, schema.prisma-, package.json-, Lockfile-, rls-preflight.mjs- oder admin-seed.service.ts-Datei (in jedem der drei Gates geprueft).
[BLOCKING] Migration lokal angewendet — woertliche Ausgabe
cd apps/api && DATABASE_URL="postgresql://tessera:tessera_dev@172.19.0.2:5432/tessera" ./node_modules/.bin/prisma migrate deploy:
The following migration(s) have been applied:
migrations/
└─ 20260914120000_rls_system_context_read/
└─ migration.sql
All migrations have been successfully applied.
migrate status: 36 migrations found in prisma/migrations / Database schema is up to date!
pg_proc / pg_policies (Tabelle#Regelname#Befehl#PERMISSIV#USING#WITH CHECK):
pg_proc is_system_context = 1
DkvModuleConfig#system_read_policy#SELECT#PERMISSIVE#is_system_context()#
DkvModuleConfig#tenant_isolation_policy#ALL#PERMISSIVE#("tenantId" = current_tenant_id())#
LdapConfig#system_read_policy#SELECT#PERMISSIVE#is_system_context()#
LdapConfig#tenant_isolation_policy#ALL#PERMISSIVE#("tenantId" = current_tenant_id())#
LdapFieldMapping#system_read_policy#SELECT#PERMISSIVE#is_system_context()#
LdapFieldMapping#tenant_isolation_policy#ALL#PERMISSIVE#("ldapConfigId" IN ( SELECT "LdapConfig".id FROM "LdapConfig" WHERE ("LdapConfig"."tenantId" = current_tenant_id())))#
SmtpConfig#tenant_isolation_policy#ALL#PERMISSIVE#("tenantId" = current_tenant_id())#
TenderMatch#system_read_policy#SELECT#PERMISSIVE#is_system_context()#
TenderMatch#tenant_isolation_policy#ALL#PERMISSIVE#("tenantId" = current_tenant_id())#
TenderSavedSearch#system_read_policy#SELECT#PERMISSIVE#is_system_context()#
TenderSavedSearch#tenant_isolation_policy#ALL#PERMISSIVE#(("tenantId" = current_tenant_id()) AND ((current_user_id() IS NULL) OR ("userId" = current_user_id())))#
system_read_policy gesamt = 5
policies public gesamt = 34
Werkzeug — die vier Funktionsfaelle (woertlich, Lauf nach Aufgabe 2)
is-system-context-ungesetzt-false: bestanden — ohne gesetzte Variable: is_system_context() = false (Rohwert null) — die Vorher-Pruefung ohne-kontext-leer in rls-preflight.mjs bleibt gueltig
is-system-context-leer-false: bestanden — nach set_config('app.system_context', '', true): false
is-system-context-true-true: bestanden — nach set_config('app.system_context', 'true', true): true
is-system-context-fremdwert-false: bestanden — nach set_config('app.system_context', 'yes', true): false
Je Tabelle (dkvmoduleconfig, ldapconfig, ldapfieldmapping, tendermatch, tendersavedsearch) neun Kennungen gruen, plus ldapconfig-systemkontext-include-fieldmappings-beider-mandanten: bestanden — ... liefert 2 Zeile(n): ["TENANT-A:1","TENANT-B:1"]. Die vollstaendigen Zeilen stehen in docs/mandantentrennung-etappe2-fehlerrichtung.md, Abschnitt (y1).
Falsifizierung durch Rueckbau — woertliche Ausgaben
Jeder Rueckbau wurde ausgefuehrt, das rote Ergebnis protokolliert, die Datei restauriert (git checkout -- <Datei> fuer committete Dateien; fuer die in Aufgabe 2 noch uncommitteten Dateien Werkzeug/Detektor per Kopie mit identischem SHA-256-Praefix a1f845ba787cac52 bzw. d9595ef09df57fce) und der gruene Zustand erneut gemessen (Werkzeug 253, Detektor 30/30, git status --short danach nur die gewollten Aufgabe-2-Dateien).
(a) FOR SELECT bei "TenderMatch" in der Migrationsdatei entfernt (Regel wird ALL):
tendermatch-systemkontext-insert-abgewiesen-42501: FEHLGESCHLAGEN — system.tenderMatch.create({"id":"tm-system-schreibversuch","tenderId":"tender-2","savedSearchId":"ss-a","userId":"user-a","tenantId":"TENANT-A"}) ist NICHT fehlgeschlagen — angelegt: "tm-system-schreibversuch"
tendermatch-systemkontext-updatemany-count-0: FEHLGESCHLAGEN — system.tenderMatch.updateMany({ where: {}, data: {"notifiedChannel":"SYSTEM-SCHREIBVERSUCH"} }) liefert count=3
tendermatch-systemkontext-deletemany-count-0: FEHLGESCHLAGEN — system.tenderMatch.deleteMany({}) liefert count=3; Zeilen danach (Wartungsrolle): 0
tendermatch-fortenant-a-nach-systemkontext-nur-a: FEHLGESCHLAGEN — bound(TENANT-A).tenderMatch.findMany() unmittelbar nach system.findMany() auf demselben Client liefert 0 Zeile(n) aus []
tendermatch-pg-policies-genau-eine-system-read-policy-select: FEHLGESCHLAGEN — pg_policies fuer "TenderMatch" (system_read_policy): [{"policyname":"system_read_policy","cmd":"ALL","permissive":"PERMISSIVE","qual":"is_system_context()"}]
5 von 253 Pruefungen fehlgeschlagen.
Plan erwartete: …-insert-abgewiesen-42501 rot. Beobachtet: 5 rot — der Insert gelingt, danach auch updateMany/deleteMany (count 3, Zeilen 0), deshalb liefert der Folgeschritt …-nur-a 0 Zeilen, und pg_policies zeigt ALL. Die Kernaussage (Insert GELINGT ohne FOR SELECT) ist woertlich belegt.
(b) system_read_policy fuer "TenderSavedSearch" aus der Migrationsdatei entfernt:
tendersavedsearch-system-read-policy-aus-migration-gefunden: FEHLGESCHLAGEN — CREATE POLICY system_read_policy ON "TenderSavedSearch" nicht in der Systemkontext-Migration (20260914120000) gefunden
1 von 245 Pruefungen fehlgeschlagen.
Lebende Datenbank waehrend des Rueckbaus (per pg_policies): policies public gesamt = 34 | system_read_policy auf TenderSavedSearch = 1. Plan erwartete: …-sieht-beide-mandanten und …-pg-policies-genau-eine-… rot. Beobachtet: die innere Routine bricht fuer diese Tabelle mit einer eigenen roten Extraktions-Kennung ab (Muster runSingleRulePersonalTableCheck: nicht raten, wenn die Regel in der Migration fehlt), die neun Kennungen der Tabelle laufen nicht (253 -> 245). Die Aussage des Plans — das Werkzeug misst die geschnittene Regel, nicht die lebende Datenbank, und die Datenbank bleibt bei 34 — ist belegt; die Form des Rotwerdens ist strenger als erwartet, nicht lockerer. Beide Zahlen (erwartet 2 rote Kennungen von 253; beobachtet 1 rote von 245) stehen hier und in (y2).
(c) local=false in forSystemQuery/buildInlineSystemClient (Werkzeug): Alle 253 Pruefungen bestanden. — alle fuenf …-fortenant-a-nach-systemkontext-nur-a bleiben gruen, der Reset in buildInlineExtendedClient traegt. Zusaetzlich den Reset dort entfernt:
dkvmoduleconfig-fortenant-a-nach-systemkontext-nur-a: FEHLGESCHLAGEN — bound(TENANT-A).dkvModuleConfig.findMany() unmittelbar nach system.findMany() auf demselben Client liefert 2 Zeile(n) aus ["TENANT-A","TENANT-B"]
ldapconfig-fortenant-a-nach-systemkontext-nur-a: FEHLGESCHLAGEN — bound(TENANT-A).ldapConfig.findMany() unmittelbar nach system.findMany() auf demselben Client liefert 2 Zeile(n) aus ["TENANT-A","TENANT-B"]
ldapfieldmapping-fortenant-a-nach-systemkontext-nur-a: FEHLGESCHLAGEN — bound(TENANT-A).ldapFieldMapping.findMany() unmittelbar nach system.findMany() auf demselben Client liefert 2 Zeile(n) aus ["TENANT-A","TENANT-B"]
tendermatch-fortenant-a-nach-systemkontext-nur-a: FEHLGESCHLAGEN — bound(TENANT-A).tenderMatch.findMany() unmittelbar nach system.findMany() auf demselben Client liefert 2 Zeile(n) aus ["TENANT-A","TENANT-B"]
tendersavedsearch-fortenant-a-nach-systemkontext-nur-a: FEHLGESCHLAGEN — bound(TENANT-A).tenderSavedSearch.findMany() unmittelbar nach system.findMany() auf demselben Client liefert 2 Zeile(n) aus ["TENANT-A","TENANT-B"]
5 von 253 Pruefungen fehlgeschlagen.
(…-is-system-context-unter-fortenant-false blieb gruen, weil diese Kennung ihre Transaktion mit eigenem Reset-Literal baut, nicht ueber buildInlineExtendedClient.)
(d) Detektor — Zahl fuer tender-matching.service.ts auf 0:
AssertionError: apps/api/src/tenders/tender-matching.service.ts: gemessen 1 forSystem(-Aufruf(e), erlaubt sind genau 0: expected [ Array(1) ] to deeply equal []
AssertionError: apps/api/src/tenders/tender-matching.service.ts: Erlaubnisliste nennt 0, gemessen 1 — der Eintrag ist ueberholt: expected [ Array(1) ] to deeply equal []
Tests 2 failed | 28 passed (30)
Fremddatei admin-seed.service.ts voruebergehend mit forSystem( versehen:
AssertionError: apps/api/src/user/admin-seed.service.ts: 2 forSystem(-Aufruf(e), Datei steht NICHT in FORSYSTEM_ALLOWED_CALL_SITES — ein Anfrageweg darf den Systemkontext nie rufen: expected [ Array(1) ] to deeply equal []
AssertionError: apps/api/src/user/admin-seed.service.ts: 1 forSystem(-Aufruf(e) ausserhalb der Zuweisungsform: expected [ Array(1) ] to deeply equal []
AssertionError: apps/api/src/user/admin-seed.service.ts: 1 include:/select:/_count:-Angabe(n) ausserhalb eines erkannten Modellaufrufs: expected [ Array(1) ] to deeply equal []
Tests 3 failed | 27 passed (30)
Danach git checkout -- apps/api/src/user/admin-seed.service.ts, git diff --quiet 5e0e408 -- apps/api/src/user/admin-seed.service.ts -> unveraendert.
Ausserdem ein nicht geplanter Beleg fuer den Veraltet-Wachhund: in Aufgabe 1 war die Erlaubnisliste bereits mit dkv.service.ts: 1 gefuellt, bevor dkv.service.ts umgestellt war — die Spec wurde rot (Erlaubnisliste nennt 1, gemessen 0 — der Eintrag ist ueberholt) und erst mit der Umstellung gruen.
Identitaet mit einem Mandanten (morgen alpha, BYPASSRLS) — je Pfad als Test
- dkv (
dkv-scheduler.service.spec.ts, 7 Tests): ein aktiver Mandant, pollIntervalMin 15 -> genau ein Auftragdkv-inbox-poll:t1,cronTime.source === '*/15 * * * *',isActivetrue; 120 ->0 */2 * * *;fireOnTick()ruftprocessInbox('t1')genau einmal; inaktive/keine Config -> kein Auftrag, ProtokollzeileDKV scheduler: no active config found — cron job not registered; zwei Mandanten -> zwei Auftraege,setInterval(30,'t2')laesst das t1-Objekt identisch,stopJob('t1')entfernt nur t1; werfender Startpfad ->DKV scheduler init failed: db down, kein Auftrag;stopJobunbekannt -> No-Op. - mail (
mail.service.spec.ts, 4 Tests): Mandant MIT SmtpConfig ->getDecryptedSmtpConfig('t1')genau einmal,createTransport({host:'smtp-a.example.invalid',port:465,secure:true,requireTLS:false,auth:{user:'user-a',pass:'geheim-a'}}),from=noreply@a.example.invalid,close()einmal; ohne SmtpConfig -> MAIL_* vor TESSERA_SMTP_* vorlocalhost:1025,fromaus TESSERA_SMTP_FROM bzw.Tessera <tessera@tessera.local>; zwei Mandanten -> zwei Transporte, keiner enthaelt das Kennwort des anderen;sendMailwirft -> kein Throw, Protokoll ohne Kennwort,close()trotzdem. - ldap/digest/matching: bestehende Verhaltenstests unveraendert gruen (ldap 92, tenders 413 Tests in den Bereichen), plus je eine Zusicherung
forSystemgenau einmal undforTenantgenauso oft wie bisher (ldap:getAllActiveConfigsforSystem 1 / forTenant 0; Bootstrap leer: forSystem 1 / forTenant 0; Bootstrap mit Altzeile t1: forTenant genau einmal mitt1,updatetraegtaa11:bb22:<hex>; digest:__systemCallLoggenau[tenderMatch.findMany], forTenant weiter genau einmal; matching:__systemCallLoggenau[tenderSavedSearch.findMany],tender.findManyweiter auf dem rohen Client).
Deviations from Plan
Auto-fixed Issues
1. [Rule 3 - Blocking] Gate-Zaehlung const systemPrisma = forSystem(this.prisma) traf die Proben der Detektor-Spec
- Found during: Aufgabe 2, Gate-Lauf
- Issue: Das Gate zaehlt
grep -rh … | grep -v spec—-hlaesst den Dateinamen weg,specsteht nicht im Zeilentext, deshalb zaehlten die drei Proben C/D1/D2 mit (8 statt 5). Der Plan verlangt die Probe C mit genau diesem Text UND das Gate mit genau 5 — in sich widerspruechlich. - Fix: Empfaengername in den drei Proben auf
sysPrismageaendert (Regex des Detektors istconst\s+(\w+)\s*=\s*forSystem\(— die Probe prueft weiterhin dieselbe Form und belegt zusaetzlich, dass der Name nicht hartkodiert ist); Gate unveraendert, Zahl unveraendert (5). - Files modified:
apps/api/src/prisma/rls-access-inventory.spec.ts - Commit:
6e2a641. Als Falle indocs/mandantentrennung-etappe3-auftrag.md("Werkzeuge und Fallen") eingetragen.
2. [Rule 3 - Blocking] Kopfkommentar dkv-scheduler.service.ts nannte forSystem() als Text
- Found during: Aufgabe 2, Gate
test 4 -eq <Dateien mit forSystem(> - Issue: Das Gate zaehlt Dateien mit dem Text
forSystem(auch in Kommentaren; der in Aufgabe 1 geschriebene Kopfkommentar nannte den Helfer. - Fix: Kommentar umformuliert ("systemgebunden ueber den Systemkontext-Helfer"). Datei steht in
files_modifieddes Plans (Aufgabe-1-Liste), Aenderung im Aufgabe-2-Commit. - Files modified:
apps/api/src/dkv/dkv-scheduler.service.ts - Commit:
6e2a641
3. [Rule 3 - Blocking] Spec-Kopfkommentar nannte den geloeschten Methodennamen
- Found during: Aufgabe 2 (eigener Assert vor dem Gate)
- Issue: Das Gate verlangt null Treffer
loadAnySmtpConfigForStartupTransportin vier Dateien, auch in Kommentaren; mein erster Entwurf des Spec-Kopfkommentars nannte ihn. - Fix: Umschrieben ("der ungebundene Startpfad des Mailmoduls").
- Files modified:
apps/api/src/settings/settings.service.spec.ts - Commit:
6e2a641
4. [Rule 1 - Bug] pg_policies-Form in (y1)
- Found during: Aufgabe 3, Gate
- Issue: Ich hatte die Zeilen mit sechs Spalten (inkl. PERMISSIV) geschrieben; das Gate erwartet die 3b-Form
Tabelle#Regelname#Befehl#USING#WITH CHECK. - Fix: Zeilen auf die 3b-Form gebracht (die PERMISSIV-Eigenschaft steht im Satz davor).
- Files modified:
docs/mandantentrennung-etappe2-fehlerrichtung.md - Commit:
939c812
Abweichungen zum Auftrag (bewusst, im Plan so vorgesehen)
- Fuenf statt sechs Tabellen: SmtpConfig traegt keine
system_read_policy, weil der Mail-Startpfad ENTFERNT wurde (Transport je Versand nach Mandant des Empfaengers), nicht auf den Systemkontext umgestellt. - ldap hat ZWEI Systemkontext-Leser:
getAllActiveConfigs()und die Nachverschluesselung inonApplicationBootstrap()(je eigene Zuweisung, der Detektor zaehlt 2); die Schreibzeile je Altzeile laeuft ueberforTenant(this.prisma, config.tenantId). - Mail-Startpfad entfernt statt umgestellt:
MailerModule.forRootAsyncundloadAnySmtpConfigForStartupTransport()samt vier Spec-Tests geloescht;@nestjs-modules/mailerbleibt inpackage.json/Lockfile installiert, ist aber unbenutzt (kein Lockfile-Eingriff in diesem Durchlauf). - Container: musste NICHT gestartet werden — er lief beim Einstieg bereits (vom Planer gestartet,
Up 25 minutes (healthy)). - Rueckbau (b): rot in strengerer Form als im Plan beschrieben (Extraktions-Abbruch statt zwei rote Messkennungen), siehe oben.
- Doppelte Kennung im Werkzeug:
dkvmoduleconfig-ungebunden-null-zeilengibt es jetzt zweimal (einmal ausrunDkvAreaChecks, einmal aus dem neuen Abschnitt) — der Plan schreibt den Namen vor; beide gruen, das Gate greift per^…: bestanden.
Was bewusst offen bleibt
- WINDOWS #37 (neu, open): Der Single-Flight-Riegel
processinginDkvService.processInboxist EIN prozessweites Boolean. Seit je aktivem Mandanten ein eigener Cron-Auftrag laeuft, bricht bei Ueberschneidung zweier Ticks verschiedener Mandanten der zweite still ab (Warnzeilealready processing) und wartet bis zum naechsten Intervall — kein Datenverlust, Verzoegerung; mit einem Mandanten unveraendert (T-EYM-09, accept mit Aufzeichnung). Loesungsweg: Riegel je Mandant (Set<tenantId>) mit Test "zwei Mandanten gleichzeitig, beide werden bedient". sendWelcomeEmailhat weiterhin null Aufrufer;@nestjs-modules/mailerunbenutzt inpackage.json— Aufraeumen, kein Defekt.- Digest-Sonderfall "Nutzer mit Treffern unter zwei Mandanten" (
distinct: ['userId']) bleibt wie in (t4) beschrieben. rls-preflight.mjsbekommt in Etappe 4 die Pruefungmit-systemkontext-sichtbar;ohne-kontext-leerbleibt gueltig (Belegis-system-context-ungesetzt-false, Rohwertnull->false).- Etappe 3a (Anmeldenamen pro Mandant) und Etappe 4 (Scharfschalten) — unveraendert offen.
Was ohne den User nicht geht
Nichts Neues. Wie im Auftrag: 3a Weg (i) vs. (ii) (wie der Mandant beim Login bestimmt wird) und Etappe 4 (Scharfschalten, DATABASE_URL auf tessera_app) bleiben Rueckfragen. Dieser Durchlauf hat den Schalter nicht angefasst: keine Compose-Datei, keine .env, nichts auf einem Server, nichts in Active Directory.
Threat Flags
Keine neue Angriffsflaeche ausserhalb des <threat_model> des Plans: kein neuer Netzwerk-Endpunkt, kein neuer Auth-Pfad, keine Schemaaenderung an Vertrauensgrenzen (nur zusaetzliche, nur lesende Regeln plus eine Funktion ohne SECURITY DEFINER). T-EYM-01 bis T-EYM-08 mitigiert wie geplant (Belege oben), T-EYM-09 accept mit Ledger-Eintrag #37, T-EYM-SC: keine Paketinstallation, package.json/Lockfile unveraendert gegen 5e0e408.
Known Stubs
Keine. sendWelcomeEmail ist kein Stub (vollstaendig implementiert, nur ohne Aufrufer — seit vor diesem Durchlauf).
Self-Check: PASSED
- Dateien:
apps/api/prisma/migrations/20260914120000_rls_system_context_read/migration.sql,apps/api/src/dkv/dkv-scheduler.service.spec.ts,apps/api/src/mail/mail.service.spec.ts— FOUND (im Commit-Baum,git diff --stat 5e0e408nennt 29 Dateien). - Commits
3d64567,6e2a641,939c812— FOUND (git log --oneline 02016e1..HEAD), gepusht (HEAD == origin/main).