Files
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

13 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-260911-gwh 2026-09-11T14:20:00Z passed 9/9 must-haves verified
.planning/WINDOWS.md
.planning/quick/260911-gwh-mandantentrennung-etappe-2-bereiche-favo/260911-gwh-PLAN.md
.planning/quick/260911-gwh-mandantentrennung-etappe-2-bereiche-favo/260911-gwh-SUMMARY.md
apps/api/scripts/rls-scratch-check.mjs
apps/api/src/favorites/favorites.controller.ts
apps/api/src/favorites/favorites.service.spec.ts
apps/api/src/favorites/favorites.service.ts
apps/api/src/mail/mail.module.ts
apps/api/src/settings/settings.service.spec.ts
apps/api/src/settings/settings.service.ts
docs/anleitung-entwicklung.md
docs/mandantentrennung-etappe2-fehlerrichtung.md
docs/mandantentrennung-zugriffsklassifikation.md
v1:sha256:2a5afae1c3039a871e737d6548a419ce5db94b270a0023fb53167db516d4b32f 0 0

Quick 260911-gwh: Etappe 2 der Mandantentrennung, Bereiche favorites/settings — Verification Report

Task Goal: Mandantentrennung Etappe 2, Bereiche favorites und settings — 7 favoriteLink- und 3 smtpConfig-Anfragewege binden, den umbenannten Startpfad bewusst ungebunden lassen (sechster Hintergrunddienst-Fall), den gemessenen Widget-Besitzriegel einbauen, beide fehlenden Spec-Dateien anlegen, Befund K schließen, das Klassifikationsdokument auf den Etappe-2-Endstand bringen. Verified: 2026-09-11 Status: passed Re-verification: No — initial verification

This report independently re-measures every claim in the SUMMARY against the live codebase and a live database container. No claim was accepted on the SUMMARY's word alone.

Goal Achievement

Observable Truths

# Truth Status Evidence
1 All 7 favoriteLink sites bound (5 methods), 3 smtpConfig request-path sites bound, exactly 1 smtpConfig site (startup path) deliberately unbound ✓ VERIFIED grep -n "favoriteLink\." → 7 hits, all on tenantPrisma. grep -n "smtpConfig\." → 3 on tenantPrisma (getSmtpConfig, saveSmtpConfig, getDecryptedSmtpConfig), 1 on this.prisma (loadAnySmtpConfigForStartupTransport, line 244)
2 mail.module.ts calls the new name; old name getStartupSmtpConfig no longer exists anywhere in apps/api/src ✓ VERIFIED mail.module.ts calls settingsService.loadAnySmtpConfigForStartupTransport(). grep -rn getStartupSmtpConfig apps/api/src → 0 hits. Old name only appears in historical phase artifacts (.planning/phases/07-*, .planning/phases/12-*, untouched history) and as an explicit "(vormals getStartupSmtpConfig())" annotation in the ledger/critique docs — never as a live call
3 Befund K closed and recorded as closed at (t4), (d4), and in the classification doc ✓ VERIFIED getDecryptedSmtpConfig(tenantId) runs over forTenant() (1 client). (t4) carries **Nachtrag (260911-gwh):** confirming the ordering condition is fulfilled (line ~625). (d4) carries the matching **Nachtrag (260911-gwh):** (line ~935), plus a correction that dkv.seed.ts/module-registry is NOT bound (measured, not copied from the plan's suggestion). Classification doc's background-service section states "Befund K ist mit dieser Bindung ERFÜLLT"
4 Widget-ownership guard in favorites.create(), measured necessary via Prüfung 7 ✓ VERIFIED Guard exists in favorites.service.ts (tenantPrisma.widgetInstance.findUnique → NotFoundException('Widget not found') on null/foreign owner). Prüfung 7 (favoritelink-fremdschluessel-prueft-am-zeilenschutz-vorbei) is committed in rls-scratch-check.mjs:3986-4046 and reproduced independently against the live tessera-ctl-db-1 container: a bound create with a foreign tenant's widgetId succeeds (FK bypasses RLS) while a nonexistent widgetId throws P2003 — confirming the guard is necessary, not decorative. Reverted the guard live and re-ran the spec: exactly the 4 claimed failures reproduced (3 "Widget not found" cases + 1 watchdog case), then restored (clean git diff)
5 Startup path names both states (today: arbitrary tenant's SMTP; post-cutover: null/silent) and is named so it can't be mistaken for a request-path method ✓ VERIFIED loadAnySmtpConfigForStartupTransport() doc-comment explicitly states both states, the fallback-chain double-concealment, the asymmetry to ldap/dkv, and the "own ledger entry, not attached to #21" decision with reason. mail.module.ts header comment mirrors this
6 Three ledger entries #30/#31/#32 exist, open, and match plan rationale ✓ VERIFIED .planning/WINDOWS.md rows 47-49 confirmed: #30 (mail.module.ts startup path, own entry not attached to #21), #31 (favorites-widget.tsx silent-empty), #32 (smtp-settings-form.tsx silent-empty). All status: open. Header counters cross-checked: open_count=14/total_count=32 vs. 32 table rows / 14 open rows — match
7 Two new spec files use the two-client harness, nodemailer is vi.mock'd, no real send attempted ✓ VERIFIED favorites.service.spec.ts: bound/unbound client separation via __makeBoundClient, IconDiscoveryService fully mocked (vi.fn), no network calls. settings.service.spec.ts: unbound client offers ONLY findFirst, bound client offers ONLY findUnique/upsert; nodemailer is vi.mock('nodemailer', ...) with createTransport returning stub verify/sendMail. Reproduced falsification (a): reverting the create() guard reproduced the exact claimed 4 test failures
8 Generated-client measurements committed — 137 total checks, named favoritelink-*/smtpconfig-* checks, throwaway tables column-checked (10 scalar fields each) ✓ VERIFIED Re-ran rls-scratch-check.mjs fresh against the live tessera-ctl-db-1 container (resolved IP freshly: 172.19.0.2). Output: "Alle 137 Pruefungen bestanden." 8 favoritelink-* named checks + 9 smtpconfig-* named checks observed, including the two column-coverage checks confirming 10 scalar fields each match schema.prisma exactly, and the SmtpConfig_tenantId_key unique index presence
9 Final stage-2 state of the classification document: recomputed sums, six-case heading, closing section numbers match derived measurements ✓ VERIFIED Recomputed independently: Übersicht column sums 68 (ungebunden) / 178 (gebunden) — matches Summenzeile exactly. Klassen-Verteilung 33+17+13+2 = 65 — matches. ## Der Hintergrunddienst als Falle — sechs Fälle heading present; sixth case (mail.module.ts/loadAnySmtpConfigForStartupTransport) documented with Befund-K-erfüllt statement. ## Etappe 2 — Abschluss closing section cites 68/178, 65 Paare, 12 runs, matching the same derived numbers. rls-access-inventory.spec.ts (11/11 tests) independently re-run and green, confirming the doc-vs-source consistency gate holds

Score: 9/9 truths verified (0 present, behavior-unverified)

Required Artifacts

Artifact Expected Status Details
apps/api/scripts/rls-scratch-check.mjs runFavoritesAreaChecks (≥7 named checks), runSettingsAreaChecks, positioned after runAuthAreaChecks ✓ VERIFIED 8 + 9 = 17 new named checks confirmed by live re-run; 137/137 total
docs/mandantentrennung-etappe2-fehlerrichtung.md ## Bereich favorites, ## Bereich settings, ## Etappe 2 — Abschluss, Nachträge at (t4)/(d4) ✓ VERIFIED All sections present and content-checked above
apps/api/src/favorites/favorites.service.ts 5 methods, 1 client each, widget-ownership guard in create ✓ VERIFIED Confirmed by direct read; 7 bound favoriteLink + 1 bound widgetInstance accesses
apps/api/src/favorites/favorites.service.spec.ts NEW, two-client harness, icon service mocked, watchdog, edge cases ✓ VERIFIED 23 cases, all green in full suite run
apps/api/src/favorites/favorites.controller.ts passes tenantId from extractContext to all 5 service methods ✓ VERIFIED Direct read confirms all 5 call sites pass tenantId
apps/api/src/settings/settings.service.ts 3 methods bound, startup path renamed with header comment ✓ VERIFIED Direct read confirms
apps/api/src/settings/settings.service.spec.ts NEW, two-client harness with boundary (unbound only findFirst, bound only findUnique/upsert), nodemailer/CryptoService mocked, null-client proof for startup path ✓ VERIFIED 20 cases, all green; forTenant call-count assertions confirm boundary
apps/api/src/mail/mail.module.ts calls renamed startup path, comment names both states ✓ VERIFIED Direct read confirms
docs/mandantentrennung-zugriffsklassifikation.md overview rows, Summenzeile, Bestandsaufnahme, Klassen-Verteilung, six-case section, "was diese Etappe nicht entscheidet" ✓ VERIFIED All recomputed and matched independently
docs/anleitung-entwicklung.md paragraph updated to 23 RLS tables / 3 migrations, FavoriteLink no longer named as rule-less ✓ VERIFIED Confirmed: 4+3+16=23 tables independently recounted from the three migration files
.planning/WINDOWS.md 3 new open entries via gsd-tools windows append ✓ VERIFIED #30/#31/#32 present, open, header counters consistent
From To Via Status Details
tender-mail.service.ts / dkv-mail.service.ts settingsService.getDecryptedSmtpConfig(tenantId) direct call ✓ WIRED Method now runs over forTenant(); Befund K closed
mail.module.ts useFactory settingsService.loadAnySmtpConfigForStartupTransport() direct call, startup only ✓ WIRED Confirmed call site and naming; fallback chain (env vars → localhost:1025) confirmed unchanged
FavoriteLink.widgetId → WidgetInstance.id app-level ownership check tenantPrisma.widgetInstance.findUnique in create() ✓ WIRED Live-measured: FK bypasses RLS (Prüfung 7); guard closes the existence-oracle gap
favorites.controller.ts extractContext dashboard.controller.ts (same tenant source) textual identity of extraction logic ✓ WIRED Confirmed identical req.tenantId ?? req.user?.tenantId pattern
settings.controller.ts req.tenantId (unchanged) direct read ✓ WIRED Controller correctly left unchanged per D-10 rationale

Behavioral Spot-Checks / Probe Execution

Behavior Command Result Status
Full test suite npm --prefix apps/api run test 994/994 passed, 62 files ✓ PASS
Type-check npm --prefix apps/api run type-check exit 0 ✓ PASS
rls-access-inventory.spec.ts (doc-vs-source consistency) npx vitest run src/prisma/rls-access-inventory.spec.ts 11/11 passed ✓ PASS
Generated-client tool, live re-run against DB container TESSERA_SCRATCH_ADMIN_URL=... node apps/api/scripts/rls-scratch-check.mjs "Alle 137 Pruefungen bestanden." ✓ PASS
Falsification reproduction: widget-ownership guard removed manual revert + npx vitest run src/favorites/favorites.service.spec.ts 4 failures (matches claimed deviation note), restored cleanly ✓ PASS
this.prisma.<model> raw count across apps/api/src grep -rn "this\.prisma\.[a-zA-Z]*" apps/api/src --include="*.ts" | grep -v spec | wc -l 68 ✓ PASS (matches Summenzeile)
Class-distribution sums recomputed from table rows 33+17+13+2 = 65; 68+178=246 raw hits ✓ PASS

Anti-Patterns Found

None. No TBD, FIXME, XXX, TODO, HACK, or PLACEHOLDER markers found in any modified file. No empty stub implementations. No hardcoded empty data flowing to render paths.

Constraints Held

  • Allow-list scope against 46f0e78: git diff --name-only 46f0e78 lists exactly the 12 files declared in files_modified (plus the PLAN.md itself, committed separately, and WINDOWS.md) — no unexpected files.
  • No schema/migration/compose/environment file appears in the diff.
  • Switch remains OFF (DATABASE_URL role tessera/BYPASSRLS unchanged — no env file touched).
  • No Active Directory / LDAP code changed (only a comment reference in a doc-string).
  • No multi-tenant mail transport built — startup path remains deliberately unbound, only renamed and documented.

Requirements Coverage

Requirement Source Plan Description Status Evidence
WINDOWS-18 260911-gwh-PLAN.md Etappe 2 fully documented at endstate ✓ SATISFIED Classification doc, critique doc, anleitung, ledger all recomputed and matched
ETAPPE-2-FAVORITES 260911-gwh-PLAN.md favorites.service.ts fully bound with ownership guard ✓ SATISFIED Verified directly
ETAPPE-2-SETTINGS 260911-gwh-PLAN.md settings.service.ts bound, startup path renamed, Befund K closed ✓ SATISFIED Verified directly

Human Verification Required

None. All must-haves were verifiable programmatically and against a live database container.

Gaps Summary

No gaps found. Every must-have in the plan's frontmatter was independently re-measured against the current codebase and/or a live database container — not accepted from the SUMMARY's narrative. The one place the SUMMARY itself documents a deviation from its own prediction (4 vs. 3 falsification failures) was independently reproduced and confirmed accurate.


Verified: 2026-09-11 Verifier: Claude (gsd-verifier)