Files
tessera-ctl/.planning/quick/260914-eym-mandantentrennung-etappe-3c-systemkontex/260914-eym-VERIFICATION.md
T
schalli 5f80582a37
Tessera CI/CD / Lint & Type Check (push) Successful in 47s
Tessera CI/CD / Tests (push) Successful in 58s
Tessera CI/CD / Build & Publish Images (push) Successful in 7s
docs(quick-260914-eym): Etappe 3c abgeschlossen und verifiziert 9/9 — Zusammenfassung, Verifikation, Aktenstand
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
2026-09-14 12:06:13 +02:00

26 KiB

phase, verified, status, score, covered_files, covered_digest, behavior_unverified, overrides_applied
phase verified status score covered_files covered_digest behavior_unverified overrides_applied
quick-260914-eym 2026-09-14T12:40:00Z passed 9/9 must-haves verified
.planning/WINDOWS.md
.planning/quick/260914-eym-mandantentrennung-etappe-3c-systemkontex/260914-eym-PLAN.md
.planning/quick/260914-eym-mandantentrennung-etappe-3c-systemkontex/260914-eym-SUMMARY.md
apps/api/prisma/migrations/20260914120000_rls_system_context_read/migration.sql
apps/api/scripts/rls-scratch-check.mjs
apps/api/src/auth/auth.service.spec.ts
apps/api/src/auth/auth.service.ts
apps/api/src/dkv/dkv-scheduler.service.spec.ts
apps/api/src/dkv/dkv-scheduler.service.ts
apps/api/src/dkv/dkv.controller.ts
apps/api/src/dkv/dkv.service.spec.ts
apps/api/src/dkv/dkv.service.ts
apps/api/src/groups/migration-sql.spec.ts
apps/api/src/ldap/ldap-config.service.spec.ts
apps/api/src/ldap/ldap-config.service.ts
apps/api/src/mail/mail.module.ts
apps/api/src/mail/mail.service.spec.ts
apps/api/src/mail/mail.service.ts
apps/api/src/prisma/prisma-tenant.extension.spec.ts
apps/api/src/prisma/prisma-tenant.extension.ts
apps/api/src/prisma/rls-access-inventory.spec.ts
apps/api/src/settings/settings.service.spec.ts
apps/api/src/settings/settings.service.ts
apps/api/src/tenders/tender-digest.scheduler.spec.ts
apps/api/src/tenders/tender-digest.scheduler.ts
apps/api/src/tenders/tender-matching.service.spec.ts
apps/api/src/tenders/tender-matching.service.ts
apps/api/src/tenders/tender-notifications.integration.spec.ts
docs/mandantentrennung-datenbankrolle.md
docs/mandantentrennung-etappe2-fehlerrichtung.md
docs/mandantentrennung-etappe3-auftrag.md
docs/mandantentrennung-zugriffsklassifikation.md
v1:sha256:d81ca7fd6f1c5dd2e90f4b70f943467888d52417316824523b993980e9607e0e 0 0

Quick 260914-eym: Mandantentrennung Etappe 3c — Systemkontext fuer die Hintergrunddienste — Verifikation

Ziel: Benannter Systemkontext forSystem() fuer die Hintergrunddienste: dritte Sitzungsvariable app.system_context, Funktion is_system_context(), neue Migration 20260914120000_rls_system_context_read mit fuenf permissiven system_read_policy ... FOR SELECT (bestehende Migrationen unveraendert), Detektor mit fuenfter Erkennungsform und exakter Erlaubnisliste, Werkzeugabschnitt runSystemContextChecks, die sechs Faelle behandelt, Dokumente nachgezogen, Ledger #21/#30 fixed, #37 neu, gepusht. Der Schalter bleibt AUS. Verifiziert: 2026-09-14, ca. 12:10-12:40 (HEAD 939c812, Arbeitsbaum nur mit den Orchestrator-Aenderungen .planning/STATE.md und der neuen SUMMARY) Status: passed Erneute Verifikation: Nein — Erstverifikation

Grundhaltung: Die SUMMARY wurde nicht als Beleg genommen. Jede Zahl unten ist in dieser Sitzung selbst gemessen (Kommando und beobachtetes Ergebnis stehen dabei). Wo ich etwas absichtlich kaputtgemacht habe, um den Wachhund zu pruefen, ist die Restauration mit git status --porcelain -- apps/ (leer) belegt.

1. Git-Historie, Umfang und Schalter-Gates

Pruefung Kommando Beobachtet Status
Drei Commits seit Planstand git log --oneline 02016e1..HEAD / git rev-list --count 02016e1..HEAD 939c812, 6e2a641, 3d64567; count=3 VERIFIZIERT
29 Dateien ausserhalb .planning git diff --stat 5e0e408 -- . ':!.planning' 29 files changed, 2499 insertions(+), 491 deletions(-); 29 Zeilen mit | VERIFIZIERT
Schalter-Gate leer git diff --name-only 5e0e408 -- apps/api/prisma/schema.prisma docker-compose.yml docker-compose.prod.yml docker-compose.dev.yml package.json apps/api/package.json pnpm-lock.yaml apps/api/scripts/rls-preflight.mjs apps/api/src/user/admin-seed.service.ts keine Ausgabe VERIFIZIERT
Keine Umgebungs-/Compose-Datei im Diff git diff --name-only 5e0e408 | awk 'index($0,"env") || index($0,"compose")' keine Ausgabe VERIFIZIERT
Keine bestehende Migration geaendert git diff --name-only 5e0e408 -- apps/api/prisma/migrations | grep -v 20260914120000 keine Ausgabe VERIFIZIERT
Gepusht git fetch -q && git status -sb | head -1; git rev-parse HEAD / origin/main ## main...origin/main (kein [ahead); beide 939c8121a182fb8ad93b3b7f5b9fdcecc17a3ffd; Push-URL zeigt auf localhost:3002 VERIFIZIERT
Arbeitsbaum git status --porcelain nur M .planning/STATE.md und ?? .../260914-eym-SUMMARY.md (Orchestrator-Dateien, unangetastet) VERIFIZIERT

2. Baseline-Messungen (frisch, nicht aus der SUMMARY)

Messpunkt Kommando Beobachtet Erwartung (Plan) Status
API-Testsuite cd apps/api && npx vitest run Test Files 64 passed (64), Tests 1054 passed (1054), Exit 0 >= 1044 VERIFIZIERT
Typpruefung cd apps/api && npx tsc --noEmit Exit 0, keine Ausgabe Exit 0 VERIFIZIERT
Werkzeug gegen lebende DB (Lauf 1) TESSERA_SCRATCH_ADMIN_URL="postgresql://tessera:tessera_dev@172.19.0.2:5432/postgres" node apps/api/scripts/rls-scratch-check.mjs Alle 253 Pruefungen bestanden., Exit 0, FEHLGESCHLAGEN-Zeilen: 0 N >= 250 VERIFIZIERT
Werkzeug (Lauf 2, nach allen Rueckbauten und Restaurationen) dito Alle 253 Pruefungen bestanden., Exit 0 253 VERIFIZIERT
Vier Funktionsfaelle grep -E "^is-system-context-" <log> ungesetzt-false (Rohwert null), leer-false, true-true, fremdwert-false — alle bestanden vier gruen VERIFIZIERT
Neun Kennungen je Tabelle (5 x 9 = 45) Schleife ueber dkvmoduleconfig/ldapconfig/ldapfieldmapping/tendermatch/tendersavedsearch x wegwerftabelle-deckt-alle-spalten / ungebunden-null-zeilen / sieht-beide-mandanten / insert-abgewiesen-42501 / updatemany-count-0 / deletemany-count-0 / fortenant-a-nach-systemkontext-nur-a / is-system-context-unter-fortenant-false / pg-policies-genau-eine-system-read-policy-select keine fehlende, keine rote Kennung 45 gruen VERIFIZIERT
Relations-Kennung grep '^ldapconfig-systemkontext-include-fieldmappings-beider-mandanten' bestanden — ... liefert 2 Zeile(n): ["TENANT-A:1","TENANT-B:1"] gruen VERIFIZIERT

3. Lebende Datenbank (Container tessera-ctl-db-1, IP 172.19.0.2, Rolle tessera)

Pruefung Kommando Beobachtet Status
Container docker ps --filter name=tessera-ctl-db-1 Up About an hour (healthy) —
Migrationsstand DATABASE_URL=... ./node_modules/.bin/prisma migrate status 36 migrations found, Database schema is up to date! VERIFIZIERT
Funktion vorhanden SELECT count(*) FROM pg_proc WHERE proname='is_system_context' 1; provolatile='s' (STABLE), prosecdef=false (kein SECURITY DEFINER) VERIFIZIERT
Systemleseregeln SELECT tablename, cmd, permissive, qual, with_check FROM pg_policies WHERE policyname='system_read_policy' genau 5 Zeilen: DkvModuleConfig, LdapConfig, LdapFieldMapping, TenderMatch, TenderSavedSearch — je SELECT / PERMISSIVE / is_system_context() / with_check null VERIFIZIERT
Gesamtzahl Regeln SELECT count(*) FROM pg_policies WHERE schemaname='public' 34 VERIFIZIERT
SmtpConfig ohne Systemregel Regeln der sechs Tabellen gelistet SmtpConfig nur tenant_isolation_policy (ALL); die fuenf anderen je zwei Regeln VERIFIZIERT
Schalter AUS SELECT rolname, rolsuper, rolbypassrls FROM pg_roles tessera super+bypassrls; tessera_app weder noch — Anwendung verbindet unveraendert als tessera VERIFIZIERT
Nach Rueckbau (a) pg_policies erneut, plus SELECT datname FROM pg_database WHERE datname LIKE '%scratch%' 34 Regeln; TenderMatch: system_read_policy SELECT + tenant_isolation_policy ALL; keine Wegwerf-DB uebrig VERIFIZIERT

4. Beobachtbare Wahrheiten (must_haves.truths)

# Wahrheit Status Beleg
1 forSystem(prisma) in Array-Form-$transaction, EINE getaggte Anweisung setzt app.system_context='true', app.current_tenant='', app.current_user=''; forTenant()/withTenantTransaction() setzen app.system_context=''; Kein-Erben gemessen und Reset per Rueckbau falsifiziert VERIFIZIERT Datei gelesen: forSystem baut $executeRaw\SELECT set_config('app.system_context', 'true', true), set_config('app.current_tenant', '', true), set_config('app.current_user', '', true)`und$transaction([setContext, query(args)]); grep -cF "set_config('app.system_context', '', true)"= 2 (forTenant + withTenantTransaction). Werkzeug:-fortenant-a-nach-systemkontext-nur-aund-is-system-context-unter-fortenant-false` fuer alle fuenf Tabellen gruen. Rueckbau (c) nicht selbst wiederholt (siehe Angenommene Risiken); Helfer-Spec 15 Tests gruen
2 Migration mit is_system_context() (STABLE, COALESCE) und genau fuenf PERMISSIVE system_read_policy ... FOR SELECT auf den fuenf Tabellen, SmtpConfig nicht dabei, bestehende Migrationen unveraendert, Schalter AUS VERIFIZIERT Datei gelesen; Zaehlung ohne Kommentarzeilen: CREATE POLICY system_read_policy=5, FOR SELECT=5, DROP POLICY=0, SmtpConfig=0. Lebende DB s. Abschnitt 3. git diff --name-only 5e0e408 -- apps/api/prisma/migrations | grep -v 20260914120000 leer
3 Regel erweitert NUR das Lesen: INSERT 42501, updateMany/deleteMany count 0, je Tabelle die neun Kennungen ueber den generierten Client VERIFIZIERT 45 Kennungen gruen (Abschnitt 2). Eigener Rueckbau (a): FOR SELECT bei TenderMatch entfernt -> tendermatch-systemkontext-insert-abgewiesen-42501: FEHLGESCHLAGEN — ... ist NICHT fehlgeschlagen — angelegt: "tm-system-schreibversuch", dazu updatemany count=3, deletemany count=3, nur-a 0 Zeilen, pg-policies zeigt cmd: ALL; 5 von 253 Pruefungen fehlgeschlagen. Danach git checkout -- <Migration>, git status --porcelain -- apps/ leer, Werkzeug wieder 253
4 Sechs Faelle behandelt: DKV Auftrag je Mandant; Mail Transport je Versand, Startpfad geloescht; ldap beide Leser ueber forSystem, Schreibzeile forTenant; digest Kandidaten forSystem; matching Suchprofile forSystem, Katalog ungebunden; admin-seed unveraendert VERIFIZIERT dkv: loadActiveConfigsForScheduler() = forSystem(this.prisma).dkvModuleConfig.findMany({ where: { isActive: true }, select: CONFIG_SAFE_SELECT, orderBy: { tenantId: 'asc' } }); Scheduler jobNameFor = dkv-inbox-poll:<tenantId>, setInterval(intervalMin, tenantId)/stopJob(tenantId) nur dieser Name, Controller setInterval(dto.pollIntervalMin, tenantId) / stopJob(tenantId); activeTenantId im Code 0, loadAnyActiveConfigForScheduler 0. mail: mail.module.ts nur SettingsModule + MailService; MailerModule/MailerService im Code 0; resolveTransport(tenantId) -> getDecryptedSmtpConfig(tenantId) (gebunden, findUnique({ where: { tenantId } })) sonst Env-Kette; transport?.close() im finally; loadAnySmtpConfigForStartupTransport in vier Dateien 0; this.prisma.smtpConfig in settings.service 0; auth.service.ts:248 sendPasswordResetEmail(email, token, user.tenantId). ldap: Zeile 71 forSystem (Bootstrap, select id/tenantId/encryptedBindPassword), Zeile 87/88 forTenant(this.prisma, config.tenantId) + ldapConfig.update je Altzeile; Zeile 322 forSystem getAllActiveConfigs mit include: { tenant, fieldMappings }; this.prisma.ldapConfig im Code 0. digest: Zeile 124 forSystem tenderMatch.findMany({ where: { notifiedAt: null }, select: { userId, tenantId }, distinct: ['userId'] }), Schleife forTenant(this.prisma, tenantId); matching: Zeile 75 forSystem tenderSavedSearch.findMany(), Zeile 90 this.prisma.tender.findMany (D-03), Zeile 98 forTenant(this.prisma, search.tenantId). admin-seed: git diff --quiet 5e0e408 -- apps/api/src/user/admin-seed.service.ts unveraendert
5 Mit EINEM Mandanten unter BYPASSRLS je Pfad identisch — als Test festgenagelt VERIFIZIERT (verhaltensabhaengig, durch benannte Tests belegt) npx vitest run der zehn betroffenen Specs: 10 Dateien / 172 Tests gruen. dkv-scheduler.service.spec.ts (7 Tests, ECHTES cron): Test 1 cronTime.source === '*/15 * * * *', isActive true, genau ein Auftrag dkv-inbox-poll:t1; Test 2 0 */2 * * *; Test 3 fireOnTick() -> processInbox('t1'); Test 4 inaktiv/keine -> 0 Auftraege + no active config found; Test 5 zwei Mandanten, setInterval(30,'t2') laesst t1-Objekt identisch (toBe(t1JobBefore)), stopJob('t1') nur t1; Test 6 werfender Startpfad; Test 7 stopJob No-Op. mail.service.spec.ts (4 Tests): Test 1 createTransport({host:'smtp-a.example.invalid',port:465,secure:true,requireTLS:false,auth:{user:'user-a',pass:'geheim-a'}}), from = fromAddress, close() einmal, MAIL_HOST darf nicht greifen; Test 2 MAIL_* vor TESSERA_SMTP_* vor localhost:1025, from aus TESSERA_SMTP_FROM bzw. Vorgabe, TESSERA_SMTP_SECURE; Test 3 zwei Mandanten, kein Kennwort des anderen; Test 4 Throw verschluckt, Protokoll ohne Kennwort, close() trotzdem. Env-Kette feldweise gegen git show 5e0e408:apps/api/src/mail/mail.module.ts verglichen: host/port/user/pass/from/secure identisch; SmtpConfig-Zweig bildet secure = encryption==='ssl-tls', requireTLS = encryption==='starttls' exakt wie die geloeschte loadAnySmtpConfigForStartupTransport und wie dkv-mail.service.ts. ldap-spec: getAllActiveConfigs forSystem 1 / forTenant nie; Bootstrap leer forSystem 1 / forTenant nie / kein Update; Bootstrap mit Altzeile forTenant(prisma,'t1'), update aa11:bb22:<hex>. digest/matching: __systemCallLog genau [tenderMatch.findMany] bzw. [tenderSavedSearch.findMany], forTenant weiter genau einmal, tender.findMany auf rohem Client. auth-spec Zeile 392: sendPasswordResetEmail('bob@example.com', expect.any(String), 't1')
6 Detektor: fuenfte Erkennungsform, system-gebunden, Vorrangregel, FORSYSTEM_ALLOWED_CALL_SITES mit exakten Zahlen (dkv 1, ldap 2, digest 1, matching 1), Fremddatei/Abweichung/veralteter Eintrag -> rot VERIFIZIERT Spec: Regex /const\s+(\w+)\s*=\s*forSystem\(/g (Zeile 439), STAND_TOKENS = ['gebunden','ungebunden','gemischt','system-gebunden'], Map exakt wie im Plan. npx vitest run src/prisma/rls-access-inventory.spec.ts -> 30/30 gruen. Quelltextzaehlung: grep -rl 'forSystem(' apps/api/src ohne spec/Helfer = genau die 4 Dateien; const systemPrisma = forSystem(this.prisma) = 5. EIGENE Falsifikation 1: const x = forSystem(this.prisma); in apps/api/src/groups/groups.service.ts (nicht in der Liste) -> 1 failed | 29 passed, Meldung apps/api/src/groups/groups.service.ts: 1 forSystem(-Aufruf(e), Datei steht NICHT in FORSYSTEM_ALLOWED_CALL_SITES — ein Anfrageweg darf den Systemkontext nie rufen. EIGENE Falsifikation 2: zweite Zuweisung in dkv.service.ts (erlaubte Datei) -> 2 failed | 28 passed, Meldungen gemessen 2 forSystem(-Aufruf(e), erlaubt sind genau 1 und Erlaubnisliste nennt 1, gemessen 2 — der Eintrag ist ueberholt. Beide per git checkout -- restauriert; git status --porcelain -- apps/ leer; git diff --quiet HEAD -- <Datei> sauber
7 Frage aus Etappe 2 je Pfad beantwortet (Leere ist nie Abwesenheit), Abschnitt (y1)-(y5) in der Kritikschrift VERIFIZIERT ## Systemkontext (Etappe 3c, 260914-eym) Zeile 3250 VOR ## Etappe 2 — Abschluss 3465; ### (y1) 3270, (y2) 3333, (y3) 3399, (y4) 3431, (y5) 3451. (y1): 22 bestanden-Zeilen, enthaelt is-system-context-ungesetzt-false: bestanden, tendermatch-systemkontext-insert-abgewiesen-42501: bestanden und fuenf #system_read_policy#SELECT#is_system_context()#-Zeilen. (y3) nennt dkv-scheduler.service.ts, ldap-sync.scheduler.ts, ldap.service.ts, ldap-config.service.ts, tender-digest.scheduler.ts, tender-matching.service.ts, admin-seed.service.ts je einmal. Nachtrag (260914-eym) in (d4)/(s4)/(b4) je 1, Abschluss 2 Treffer
8 Aktenstand kohaerent: sechs Zeilen system-gebunden, settings/smtpConfig gebunden, 72 Paare / 35/21/14/2, Uebersichtstabelle mit Spalte System und ABGELEITETER Summenzeile, Hintergrunddienst-Regelschluesse, Auftrag 3c erledigt, Datenbankrolle mit dritter Variable + preflight-Aussage, Ledger #21/#30 fixed, #37 neu, Zaehler 15/1/21/37 VERIFIZIERT Gate-Schleife aus Aufgabe 3 selbst ausgefuehrt: PAARE=72; Klassen gezaehlt 35/21/14/2 = dokumentiert; system-gebunden-Zeilen = 6 (dkv/dkvModuleConfig, ldap-config/ldapConfig, /ldapFieldMapping, /tenant, digest/tenderMatch, matching/tenderSavedSearch), settings/smtpConfig gebunden; Ableitung ABGELEITET 61/179/5 (dkv 0/22/1, ldap 1/27/2, tenders 33/27/2) = Summenzeile **61** | **179** | **5**; Kopf | Bereich | Ungebunden | Gebunden | System |; Regelschluss (260914-eym) im Hintergrunddienst-Abschnitt 7x (>= 6); **Stand 260914-eym vorhanden. Auftrag: Erledigt (260914-eym, 3d64567/6e2a641 ... Zeile 145. Datenbankrolle: app.system_context (Z. 90-103), is-system-context-ungesetzt-false (Z. 166), preflight-Aussage (Z. 163); im Diff gegen 5e0e408 kein SECURITY DEFINER (0). Ledger: gsd-tools windows status -> #21 fixed resolved_at 2026-09-14T09:51:23Z, #30 fixed resolved_at 2026-09-14T09:51:24Z, #37 open (quick-260914-eym, deviation, dkv.service.ts, Single-Flight-Riegel); Frontmatter open 15 / waived 1 / fixed 21 / total 37 = aus Zeilen gezaehlt 15/21/1/37
9 Schalter AUS, Gates gegen 5e0e408 leer, Tests >= 1044, tsc 0, Werkzeug >= 250, sauber, gepusht VERIFIZIERT Abschnitte 1-3

Score: 9/9 Wahrheiten verifiziert (0 present-behavior-unverified)

Verhaltensabhaengige Wahrheiten — Belegform

Wahrheiten 1, 3, 5 und 6 behaupten Laufzeitverhalten (Kontext-Reset, Schreibverbot, Identitaet je Pfad, Wachhund). Keine davon ist auf Symbolpraesenz allein als VERIFIZIERT gesetzt: 1 und 3 sind live im Werkzeug gegen eine Rolle ohne BYPASSRLS gemessen (253 gruen, Rueckbau (a) selbst wiederholt), 5 durch die benannten Specs (172 Tests, echtes cron), 6 durch zwei eigene Falsifikationen.

5. Artefakte

Artefakt Erwartet Status Details
apps/api/prisma/migrations/20260914120000_rls_system_context_read/migration.sql NEU, Funktion + fuenf Regeln + Abschnitt "bewusst NICHT" VERIFIZIERT 5/5/0/0-Zaehlung (s. o.); Abschnitt "Was diese Migration bewusst NICHT tut" vorhanden (SmtpConfig, Tenant/Tender, keine Schreibregel, Schalter); lokal angewendet (36 Migrationen)
apps/api/src/prisma/prisma-tenant.extension.ts forSystem() mit Kopfkommentar, Reset in forTenant/withTenantTransaction, $transaction-Feld zwei Eintraege VERIFIZIERT gelesen; Abschnitt "SYSTEMKONTEXT (Etappe 3c, 260914-eym)" im Kopf; Array [setContext, query(args)]
apps/api/src/prisma/prisma-tenant.extension.spec.ts >= 3 neue Tests VERIFIZIERT 11 -> 15 Tests, gruen
apps/api/src/groups/migration-sql.spec.ts describe fuer neue Migration VERIFIZIERT _rls_system_context_read vorhanden; 32 Tests gruen
apps/api/src/prisma/rls-access-inventory.spec.ts fuenfte Form, systemModels, system-gebunden, Erlaubnisliste VERIFIZIERT 30 Tests; zwei eigene Falsifikationen rot
apps/api/scripts/rls-scratch-check.mjs runSystemContextChecks, extractSystemReadPolicySql, buildInlineSystemClient VERIFIZIERT grep-Treffer; 253 Pruefungen, Abschnitt laeuft im Hauptlauf
dkv (service, scheduler, controller, zwei Specs) Auftrag je Mandant, registeredTenantIds, stopJob(tenantId), neue Spec >= 6 Tests VERIFIZIERT 7 Scheduler-Tests, 17 Service-Tests gruen
mail (module, service, NEUE spec), settings (service, spec), auth (service, spec) Startpfad weg, resolveTransport, Transport je Versand, user.tenantId durchgereicht VERIFIZIERT 4 Mail-Tests, 16 Settings-Tests, 29 Auth-Tests gruen
ldap-config, tender-digest, tender-matching (+Specs), tender-notifications.integration.spec forSystem-Leser, Mocks ergaenzt VERIFIZIERT 19/16/17 Tests gruen; Integrationsspec in Vollsuite gruen
vier Dokumente + .planning/WINDOWS.md Nachtraege, 3c erledigt, Ledger VERIFIZIERT Abschnitt 4, Wahrheiten 7/8
Von Nach Ueber Status Details
FOR SELECT in der Migration Schreibverbot unter Systemkontext permissive ODER-Verknuepfung VERBUNDEN Rueckbau (a) selbst wiederholt: ohne FOR SELECT gelingt der Insert (5/253 rot); mit: 253 gruen
Detektor-Form const X = forSystem( Bestandsaufnahme-Stand system-gebunden Regex Zeile 439 + Erlaubnisliste VERBUNDEN 6 Zeilen system-gebunden in der Klassifikation; Falsifikationen rot
set_config(..., true) + Reset Kein Erben zwischen Kontexten Werkzeug fortenant-a-nach-systemkontext-nur-a VERBUNDEN 5x gruen; Rueckbau (c) nicht selbst wiederholt (Angenommene Risiken)
auth.service.ts:248 MailService.sendPasswordResetEmail(..., tenantId) user.tenantId VERBUNDEN Quelltext + auth-spec Zeile 392
…-ungebunden-null-zeilen + …-sieht-beide-mandanten zu-wenig-statt-zu-viel-Falle Werkzeugpaar je Tabelle VERBUNDEN 5 Paare gruen; (y3) beantwortet je Pfad

7. Datenfluss (Level 4)

Artefakt Variable Quelle Echte Daten Status
dkv-scheduler onModuleInit configs forSystem(prisma).dkvModuleConfig.findMany({ where: { isActive: true } }) ja FLIESST
mail resolveTransport smtpConfig settingsService.getDecryptedSmtpConfig(tenantId) -> forTenant(...).smtpConfig.findUnique({ where: { tenantId } }) ja (Rueckfall Env-Kette explizit) FLIESST
ldap getAllActiveConfigs / Bootstrap configs forSystem(prisma).ldapConfig.findMany(...) ja FLIESST
digest candidates / matching savedSearches — forSystem(prisma).tenderMatch.findMany / .tenderSavedSearch.findMany() ja FLIESST

8. Verhaltens-Stichproben

Verhalten Kommando Ergebnis Status
Vollsuite npx vitest run 64 Dateien / 1054 Tests PASS
Typpruefung npx tsc --noEmit Exit 0 PASS
Zehn betroffene Specs benannt npx vitest run <10 Dateien> 10 / 172 gruen PASS
Detektor npx vitest run src/prisma/rls-access-inventory.spec.ts 30/30 PASS
Detektor mit Fremddatei s. Wahrheit 6 1 failed / 29 passed PASS (rot wie gefordert)
Detektor mit Zahlabweichung s. Wahrheit 6 2 failed / 28 passed PASS (rot wie gefordert)
Werkzeug gruen node apps/api/scripts/rls-scratch-check.mjs (2x) 253 / 253 PASS
Werkzeug Rueckbau (a) FOR SELECT bei TenderMatch entfernt 5 von 253 Pruefungen fehlgeschlagen. — Insert GELINGT PASS (rot wie gefordert)

9. Sonde-Ausfuehrung

Keine scripts/*/tests/probe-*.sh im Projekt; das Werkzeug rls-scratch-check.mjs ist die Sonde dieses Durchlaufs und wurde zweimal selbst ausgefuehrt (Abschnitt 2, 8).

10. Anforderungsabdeckung

Anforderung Plan Beschreibung Status Beleg
ETAPPE-3C 01 Systemkontext fuer Hintergrunddienste ERFUELLT Wahrheiten 1-9
WINDOWS-21 01 DKV-Planer je Mandant ERFUELLT Wahrheit 4/5, Ledger #21 fixed
WINDOWS-30 01 Mail-Startpfad ERFUELLT (durch Entfernen, nicht Umstellen — im Plan so vorgesehen) Wahrheit 4/5, Ledger #30 fixed

Keine Zuordnung in .planning/REQUIREMENTS.md fuer Quick-Tasks — keine verwaisten Anforderungen.

11. Anti-Pattern-Scan

grep -n -E "\b(TBD|FIXME|XXX|TODO|HACK|PLACEHOLDER)\b" ueber alle 29 geaenderten Dateien: keine Treffer. console.log in geaenderten Nicht-Spec-Dateien: keine Treffer. Keine Stubs: sendWelcomeEmail ist vollstaendig implementiert und hat null Aufrufer ausserhalb mail/ (Bestand seit vor diesem Durchlauf, in (y4) benannt).

12. Menschliche Pruefung erforderlich

Keine — jede Zusicherung des Plans ist entweder statisch, per Test oder live gegen die Datenbank gemessen. Was ausserhalb dieses Repos liegt, steht unter "Angenommene Risiken".

13. Luecken

Keine.

Angenommene Risiken

Was ich NICHT selbst messen konnte oder bewusst nicht wiederholt habe:

  1. Rueckbau (b), (c) und (d) nicht selbst wiederholt. Der Auftrag verlangte EINE Rueckbau-Falsifikation (empfohlen (a)); die habe ich vollstaendig wiederholt und dasselbe Ergebnis wie die SUMMARY beobachtet (5 von 253 rot, Insert gelingt). Fuer (b) (Regel aus der Migration entfernt -> Werkzeug folgt der Datei, DB bleibt 34), (c) (local=false + Reset entfernt -> Erben sichtbar) und (d) (Erlaubniszahl auf 0) stuetze ich mich auf die woertlichen Ausgaben der SUMMARY. (d) ist durch meine beiden eigenen Detektor-Falsifikationen in der Sache gedeckt (Fremddatei rot, Zahlabweichung rot); (c) ist durch die fuenf gruenen …-fortenant-a-nach-systemkontext-nur-a-Kennungen und den gelesenen Helfer-Quelltext (Reset in allen drei Formen) gedeckt, nur der Beleg "Reset traegt bei local=false" ist nicht erneut erzeugt.
  2. Alpha-Go-live morgen ist nicht beobachtet. Dass DKV-Postfach-Abruf und Kennwort-Zuruecksetzung auf alpha.tessera.ctl.de mit dem echten SMTP-Server und dem echten Postfach identisch zu heute laufen, ist hier per Test festgenagelt (Cron-Expression, Tick, Transport aus der SmtpConfig des Mandanten), nicht gegen den Testserver gemessen — Deploy und Beobachtung auf dem Server macht der User selbst (Absprache). Unter BYPASSRLS sind forSystem/forTenant wirkungslos; die einzigen beobachtbaren Verhaltensaenderungen sind (i) der Registry-Name dkv-inbox-poll:<tenantId> statt dkv-inbox-poll und (ii) der Transport je Versand statt beim Start — beide durch Tests gedeckt, (ii) zusaetzlich feldweise gegen den geloeschten Startpfad verglichen.
  3. Migration auf alpha. 20260914120000_rls_system_context_read ist rein additiv (CREATE FUNCTION, CREATE POLICY) auf Tabellen, die RLS bereits aus frueheren Migrationen tragen; sie ist lokal per migrate deploy gruen. Ob migrate deploy auf alpha morgen ebenso sauber laeuft, ist hier nicht messbar (kein Deploy durch mich).
  4. @nestjs-modules/mailer bleibt installiert und unbenutzt (package.json/Lockfile bewusst unveraendert, im Plan so vorgesehen). Kein Defekt, aber Aufraeumbedarf — in (y4) und in der SUMMARY benannt.
  5. WINDOWS #37 (Single-Flight-Riegel prozessweit) ist bewusst offen; mit einem Mandanten ohne Wirkung, mit mehreren nur Verzoegerung, kein Datenverlust — im Ledger als open eingetragen, nicht Teil dieses Auftrags.
  6. Ledger-Zeitstempel: resolved_at von #21/#30 liegt bei 09:51 UTC (11:51 lokal), passend zur SUMMARY-Sitzung 11:10-12:05; nicht weiter pruefbar.

Der Arbeitsbaum wurde exakt so hinterlassen wie vorgefunden: git status --porcelain zeigt nur M .planning/STATE.md und die neue SUMMARY (beide unangetastet) sowie jetzt diese VERIFICATION.md. Alle drei temporaeren Aenderungen (groups.service.ts, dkv.service.ts, migration.sql) sind per git checkout -- restauriert und mit git status --porcelain -- apps/ (leer) belegt; die lebende Datenbank steht bei 34 Regeln, keine Wegwerf-Datenbank blieb zurueck.


Verifiziert: 2026-09-14T12:40:00Z Verifier: Claude (gsd-verifier)