18 KiB
phase, verified, status, score, covered_files, covered_digest, behavior_unverified, overrides_applied, gaps
| phase | verified | status | score | covered_files | covered_digest | behavior_unverified | overrides_applied | gaps | |||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| quick-260910-krx | 2026-09-11T09:15:00Z | gaps_found | 10/11 must-haves verified |
|
v1:sha256:76946e6dc0468eaade2fe79aaa847c342e94364af5040814f495ba1e2c460d09 | 0 | 0 |
|
Quick Task 260910-krx: Mandantentrennung Etappe 2, Bereich dashboard — Verification Report
Task Goal: Bind the tenant-bound access sites in apps/api/src/dashboard/dashboard.service.ts, leave the platform-wide module catalogue unbound with the measured reason, create the missing spec coverage, record the evidence-destroying loop, and leave the classification document's five hand-maintained sections in sync.
Verified: 2026-09-11T09:15:00Z Status: gaps_found (one partial gap — see below; all other checked items pass) Re-verification: No — initial verification
Goal Achievement
Observable Truths
| # | Truth | Status | Evidence |
|---|---|---|---|
| 1 | 13 Datenbankzugriffe vollstaendig entschieden: 12 gebunden, 1 begruendet ungebunden, keiner unentschieden | ✓ VERIFIED | git show c7d93f2:.../dashboard.service.ts | grep -oE "this\.prisma\.[a-zA-Z]+" | wc -l = 13 (2 dashboardLayout, 1 module, 4 searchProvider, 6 widgetInstance) at base; current file: 12 tenantPrisma.* call sites over 9 forTenant() invocations + 1 this.prisma.module.findMany (unbound, catalogue). Zero occurrences of tenantPrisma.module. (negative gate confirmed by direct grep of the committed file). |
| 2 | Lesen/Schreiben derselben Tabelle nie auf gebunden/ungebunden aufgeteilt; getLayout/saveLayout gemeinsam gebunden; drei Besitzpruefungen laufen ueber denselben Klienten | ✓ VERIFIED | Read dashboard.service.ts: every method creates exactly one const tenantPrisma = forTenant(this.prisma, tenantId) and both queries of updateWidgetConfig, removeWidget, removeSearchProvider use that same tenantPrisma. Test "Anordnung lesen und speichern sind GEMEINSAM gebunden" and "keine Methode ... erzeugt mehr als EINEN gebundenen Klienten" pin this down; both pass (27/27). |
| 3 | Umgekehrte Fehlerrichtung an den echten Regeln nach Migration 20260910120000 gemessen, mit Signal je Pfad | ✓ VERIFIED | Re-ran rls-scratch-check.mjs live against tessera-ctl-db-1 (172.19.0.2): "Alle 87 Pruefungen bestanden." (74 baseline + 13 new, matching the SUMMARY's claim exactly). docs/mandantentrennung-etappe2-fehlerrichtung.md ## Bereich dashboard (w1)/(w2) present with per-path signal table. |
| 4 | Beweisvernichtende Schleife ausdruecklich benannt: in Kritikschrift UND offener WINDOWS-Eintrag mit Etappe-4-Vorabpruefung | ✓ VERIFIED | (w3) in fehlerrichtung.md describes the loop (empty dashboard → rebuild → auto-writeback → overwritten row → duplicate widgets) at the two actual web files. .planning/WINDOWS.md entry #25 present in both the markdown table and the JSON block, status: open, names the concrete Etappe-4 preflight check and cross-references #22. Frontend files (apps/web/**) confirmed untouched by git diff --name-only c7d93f2..HEAD. |
| 5 | Widerlegte SearchProvider-Praemisse eigenstaendig nachgeprueft, mit benannter Reichweite | ✓ VERIFIED | (w1) names the exact search instruction and scope limits (no dynamic model-name write path, no manual DB edit covered). Database-side defense reproduced live: searchprovider-gebundenes-einfuegen-ohne-mandant-abgelehnt: bestanden with SQLSTATE 42501. |
| 6 | Geerbte Entlastung (Modul-Zugriffsaufloesung bereits gebunden seit 260910-exd) nachgeprueft, nicht doppelt repariert | ✓ VERIFIED | git diff --name-only c7d93f2..HEAD -- apps/api/src/module-registry/ returns nothing — no file under that path touched. dashboard.service.ts calls this.moduleAccessService.getAccessibleModuleIds(...) unchanged, with an explicit comment that it "already binds internally". |
| 7 | Modulkatalog bleibt ungebunden, MESSUNG und BEDINGUNG getrennt, beruft sich auf bestehende Werkzeugpruefung | ✓ VERIFIED | End-of-file comment block in dashboard.service.ts (lines 340-354) separates MESSUNG (pg_class.relrowsecurity false, cites module-tabelle-traegt-keinen-zeilenschutz) from BEDINGUNG (becomes catastrophic once Etappe 3 adds a rule). module-tabelle-traegt-keinen-zeilenschutz: bestanden reproduced live. |
| 8 | Testlage deckt vorher ungetestete Pfade ab; vergessener Bindungsaufruf wird rot | ✓ VERIFIED | dashboard.service.spec.ts: 27 test cases (8 pre-existing, unchanged; 19 new, counted). Reverted tenantPrisma.widgetInstance.delete → this.prisma.widgetInstance.delete in removeWidget: exactly the claimed test failed with the exact claimed message; restored, 27/27 green again (independently reproduced, see Behavioral Spot-Checks). |
| 9 | Regeln kennen keine Benutzerdimension; Besitzpruefungen ueber Benutzerkennung bleiben einziger Schutz, unveraendert | ✓ VERIFIED | widget.userId !== userId present in updateWidgetConfig and removeWidget; provider.userId !== userId present in removeSearchProvider — all three intact, all comparing against the session-sourced userId parameter, not touched by the binding. Three *-fremder-nutzer-desselben-mandanten-gebunden-sichtbar checks pass (gelingen IS the expected/passing result), confirming the DB layer alone has no user dimension. |
| 10 | Baseline gehalten am Ende jeder Aufgabe (≥839 Tests, ≥56 Dateien, saubere Typpruefung, Werkzeug 0) | ✓ VERIFIED | Per orchestrator's independent measurement: 858/858 tests green across 56 files, type-check exit 0. Independently reproduced for the two most relevant suites (dashboard.service.spec.ts 27/27, rls-access-inventory.spec.ts 10/10) and for rls-scratch-check.mjs (87/87, live run). |
| 11 | Schalter bleibt AUS; kein Schema-, Migrations-, Compose- oder Umgebungs-Datei angefasst | ✓ VERIFIED | git diff --name-only c7d93f2..HEAD touches exactly 9 files (confirmed independently), none under apps/api/prisma, no compose/env file. groups.service.ts and apps/api/src/module-registry/** confirmed untouched. |
| 12 | Die gemessene Konfliktbehandlung in saveLayout ist im committeten Werkzeug reproduzierbar belegt und durch einen Test abgesichert |
✗ FAILED (partial) | See gaps in frontmatter. The committed rls-scratch-check.mjs only measures a raw-SQL $executeRaw ... ON CONFLICT path (reproduced live: SQLSTATE 42501), never the actual generated prisma.dashboardLayout.upsert() call that saveLayout uses. The specific, stronger claim in the SUMMARY/critique document — that the generated client throws PrismaClientUnknownRequestError rather than the P2002-shaped PrismaClientKnownRequestError — is attributed to a separate, non-committed ad-hoc measurement and is not independently reproducible from repo state. No unit test exercises saveLayout's catch branch (0 hits for ConflictException/PrismaClientUnknownRequestError in dashboard.service.spec.ts). |
Score: 10/11 truths fully verified, 1 partial gap (0 present-but-behavior-unverified truths; the one gap is a documented, provable absence, not an uncertainty).
Deferred Items
None identified — no later-phase success criteria in the current milestone roadmap were found to cover this gap; this quick task is not tied to a numbered ROADMAP phase, so no deferral cross-check applies.
Required Artifacts
| Artifact | Expected | Status | Details |
|---|---|---|---|
apps/api/scripts/rls-scratch-check.mjs |
9th section runDashboardAreaChecks, 13 named checks |
✓ VERIFIED | Present at line 2425; live re-run: "Alle 87 Pruefungen bestanden." |
docs/mandantentrennung-etappe2-fehlerrichtung.md |
## Bereich dashboard with (w1)-(w5) + Nachtrag in ## Bereich module-registry |
✓ VERIFIED | Section at line 1745; (w4)/(w5) at 1930/1966; Nachtrag at 1614. |
apps/api/src/dashboard/dashboard.service.ts |
12 bound, 1 unbound-with-reason | ✓ VERIFIED | Confirmed by direct read and grep counts above. |
apps/api/src/dashboard/dashboard.controller.ts |
5 handlers pass tenant through | ✓ VERIFIED | All 8 handlers call extractContext(req) and pass tenantId positionally after userId, matching service signatures. |
apps/api/src/dashboard/dashboard.service.spec.ts |
Two-client TDD proof, 8→27 cases, all 8 original green | ✓ VERIFIED | 27/27 passing; describe block 1 (getWidgets — Modulfilter) unchanged with 8 cases. |
docs/mandantentrennung-zugriffsklassifikation.md |
4 Bestandsaufnahme rows, overview row, sum row, class distribution, background-service section, "Was NICHT entscheidet" | ✓ VERIFIED | All five confirmed by direct read; rls-access-inventory.spec.ts 10/10 (machine-gated consistency). |
.planning/WINDOWS.md |
Open entry for evidence-destroying loop, table + JSON | ✓ VERIFIED | Entry #25 present in both forms, status: open. |
Key Link Verification
| From | To | Via | Status | Details |
|---|---|---|---|---|
getLayout/saveLayout |
Same DashboardLayout row |
Shared userId uniqueness key, no tenant component |
✓ WIRED | Both bound over the same tenantPrisma per call; co-binding test present and passing. |
dashboard.controller.ts |
dashboard.service.ts |
extractContext(req) → positional tenantId argument |
✓ WIRED | All 8 handlers pass tenantId from session-derived context, no new trust source introduced. |
dashboard.service.ts getWidgets |
module-access.service.ts getAccessibleModuleIds |
Direct call, already bound since 260910-exd | ✓ WIRED | Confirmed unchanged; module-registry files untouched by this diff. |
dashboard.service.ts saveLayout catch branch |
ConflictException |
instanceof Prisma.PrismaClientUnknownRequestError |
⚠️ WIRED BUT UNTESTED | Code path exists and reads correctly, but no test exercises it — see gap above. |
Behavioral Spot-Checks
| Behavior | Command | Result | Status |
|---|---|---|---|
rls-scratch-check.mjs reproducibly passes against live DB |
TESSERA_SCRATCH_ADMIN_URL=... node apps/api/scripts/rls-scratch-check.mjs |
"Alle 87 Pruefungen bestanden." | ✓ PASS |
rls-access-inventory.spec.ts reproducibly passes |
npm --prefix apps/api run test -- src/prisma/rls-access-inventory.spec.ts |
10/10 | ✓ PASS |
| Falsification proof 1 (removeWidget binding reverted) | Edited tenantPrisma.widgetInstance.delete → this.prisma.widgetInstance.delete, ran suite, reverted |
Exact claimed test failed with exact claimed message; restored clean, 27/27 green | ✓ PASS |
| Falsification proof 2 (module catalogue bound) | Edited this.prisma.module.findMany → tenantPrisma.module.findMany, ran suite, reverted |
8 tests failed incl. the named watchdog, TypeError: Cannot read properties of undefined (reading 'findMany') at dashboard.service.ts:175 — matches SUMMARY verbatim; restored clean, 27/27 green |
✓ PASS |
Falsification proof 3 (classification Stand set wrong) |
Edited row 320 gebunden→ungebunden, ran inventory spec, reverted |
Failed with exact claimed mismatch message; restored clean, 10/10 green | ✓ PASS |
saveLayout ConflictException translation |
Searched for a test exercising it | 0 hits for ConflictException/PrismaClientUnknownRequestError in dashboard.service.spec.ts |
✗ FAIL (gap, see above) |
Anti-Patterns Found
None (TBD/FIXME/XXX/TODO/HACK/placeholder scan of all 9 changed files: no hits). No hardcoded-empty-return stubs found; getLayout's default-arrangement return and getWidgets'/getSearchProviders' empty-list behavior are explicitly documented as intentional, pre-existing "emptiness as absence" semantics, not stubs introduced by this task.
Requirements Coverage
No .planning/REQUIREMENTS.md entries exist for WINDOWS-18 or ETAPPE-2-DASHBOARD (this is a quick task, not tracked against the formal requirements ledger) — not orphaned, simply out of scope for that document's tracking.
Human Verification Required
None — the one gap found (saveLayout conflict-translation test/measurement coverage) is a provable absence, not an item requiring human judgment to establish the facts. It is reported as a gaps_found item so a human can decide whether to accept it via an override or request a follow-up fix.
Gaps Summary
Twelve of thirteen checked truths verify cleanly against the codebase, and three separate, independently-reproduced falsification proofs (all three claimed in the SUMMARY) matched verbatim — this is a well-evidenced piece of work overall, with the classification document, the WINDOWS ledger entry, the critique document sections, the ownership checks, and the "module catalogue stays unbound" negative gate all holding up under direct adversarial re-verification.
The one real gap: the SUMMARY's most load-bearing new technical claim — that a bound conflicting saveLayout write throws PrismaClientUnknownRequestError (not the P2002-shaped error the tenders area handles) — is asserted with unusual specificity ("am echten, generierten Prisma Client gemessen, nicht nur an rohem SQL") but that specific measurement was not committed anywhere reproducible: rls-scratch-check.mjs's check 5 only exercises raw SQL via $executeRaw, and no unit test exercises saveLayout's catch (error) branch. The production code itself (instanceof Prisma.PrismaClientUnknownRequestError) is plausible and well-reasoned, and the raw-SQL measurement it's grounded in did reproduce live with SQLSTATE 42501 — but the exact claim made in the document goes beyond what's in the repository, and nothing would catch a regression if the catch clause were later changed or removed.
This looks like a real, if narrow, evidence gap rather than intentional scope creep — the fix is small (either extend the scratch tool to call the real .upsert() and log the observed error class, or add one unit test asserting saveLayout throws ConflictException when the bound client's upsert rejects with a Prisma.PrismaClientUnknownRequestError). To accept the current state as-is instead of requiring that follow-up, add to this file's frontmatter:
overrides:
- must_have: "Die gemessene Konfliktbehandlung in saveLayout ist im committeten Werkzeug reproduzierbar belegt und durch einen Testfall abgesichert."
reason: "Die Codepfad-Logik ist inhaltlich plausibel und durch eine verwandte (rohe SQL) Messung gestuetzt; die fehlende .upsert()-spezifische Messung/der fehlende Unit-Test werden als Nachtrag statt als Blocker akzeptiert."
accepted_by: "<name>"
accepted_at: "<ISO timestamp>"
Verified: 2026-09-11T09:15:00Z Verifier: Claude (gsd-verifier)