Files
schalli f1017fa6e9
Tessera CI/CD / Lint & Type Check (push) Successful in 47s
Tessera CI/CD / Tests (push) Successful in 53s
Tessera CI/CD / Build & Publish Images (push) Successful in 28s
docs(quick-260911-cwh): Etappe 2 Bereich calendar abgeschlossen und verifiziert
2026-09-11 10:08:01 +02:00

17 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-cwh 2026-09-11T10:15:00Z passed 12/12 must-haves verified
.planning/WINDOWS.md
.planning/quick/260911-cwh-mandantentrennung-etappe-2-bereich-calen/260911-cwh-PLAN.md
.planning/quick/260911-cwh-mandantentrennung-etappe-2-bereich-calen/260911-cwh-SUMMARY.md
apps/api/scripts/rls-scratch-check.mjs
apps/api/src/calendar/calendar.controller.ts
apps/api/src/calendar/calendar.service.spec.ts
apps/api/src/calendar/calendar.service.ts
docs/mandantentrennung-etappe2-fehlerrichtung.md
docs/mandantentrennung-zugriffsklassifikation.md
v1:sha256:f092c2eea8be58064da21108d78ef37879ab4183bdc6c622c699cee603c0f434 0 0

Quick Task 260911-cwh: Mandantentrennung Etappe 2, Bereich calendar — Verification Report

Task Goal: Bind all 12 calendarSource access sites in calendar.service.ts, create the area's missing spec coverage, pin the credential-path exoneration and the cache-key verdict as tests, and keep the classification document's five hand-maintained sections in sync.

Verified: 2026-09-11T10:15:00Z Status: passed Commits reviewed: bf5fc4d, 77cb124, e0e163e, 06038b9 (base 508d9e4), all present in git log, working tree clean before and after this review.

Re-verification Checklist Results

1. Coverage — all 12 sites bound, one tenantPrisma per method, six methods

Independently counted (not taken from SUMMARY):

grep -ro "tenantPrisma\.calendarSource\." apps/api/src/calendar/calendar.service.ts | wc -l  → 12
grep -ro "this\.prisma\.[a-zA-Z]*" apps/api/src/calendar/calendar.service.ts | wc -l          → 0
grep -o "forTenant(this\.prisma" (non-comment source)                                         → 6

Distribution matches the plan's Befund A exactly: getSources (1), addSource (1), updateSource (2), deleteSource (2), testConnection (3), fetchAndCacheEvents (3) = 12. aggregateEvents/refreshCacheInBackground create no client of their own (confirmed by reading both bodies — they only pass tenantId through to fetchAndCacheEvents).

VERIFIED.

2. Credential exoneration pinned, not asserted

Three test cases exist in calendar.service.spec.ts (lines 289, 303, 313): "Feld FEHLT" (missing), "Feld LEER" (empty → null), "Feld GESETZT" (set → re-encrypted). The "Feld FEHLT" case asserts crypto.decrypt/crypto.encrypt were not called and that update(...).data does not have an encryptedPassword key at all — this is a real pin, not a shape check. A regression that reintroduced the dkv read-decrypt-re-encrypt form would fail this test on both the decrypt-not-called assertion and the data-shape assertion.

VERIFIED.

3. Cache-key verdict

Code comment directly above eventCache = new Map(...) in calendar.service.ts (lines 125-142) states the full four-link chain (User.id @id @default(uuid()) → auth.service.ts sub: user.id → JwtStrategy.validate id: payload.sub → extractContext) and concludes the key stays without a tenant component because Etappe-3-Entscheidung (1) only touches username/email, not id. Independently confirmed against apps/api/prisma/schema.prisma:30 (id String @id @default(uuid())), apps/api/src/auth/auth.service.ts:143,332 (sub: user.id), and apps/api/src/auth/strategies/jwt.strategy.ts:29 (id: payload.sub) — the chain in the comment matches the actual source. The identical verdict is also written in docs/mandantentrennung-etappe2-fehlerrichtung.md (k4)(a), naming the same four links.

VERIFIED.

4. Both write-backs in fetchAndCacheEvents bound and pinned

Success path (line 429, inside the try) and catch path (line 440, inside the catch) both call tenantPrisma.calendarSource.update(...). Two named tests cover this (aggregateEvents, Erfolgspfad... line 428, aggregateEvents, Fehlerpfad (Providerfehler)... line 439), both asserting expectBoundCall(..., 'update').

Falsification performed by this verifier (not just re-reading the SUMMARY's claim): reverted the success-path binding (tenantPrisma.calendarSource.update → this.prisma.calendarSource.update at line 429) and ran npm --prefix apps/api run test -- src/calendar/calendar.service.spec.ts. Result: 1 of 23 tests failed — aggregateEvents, Erfolgspfad: ... → AssertionError: erwarteter gebundener Aufruf calendarSource.update(tenant=t1) fehlt im Protokoll: [{"tenantId":"t1","model":"calendarSource","method":"findMany"}] — exactly the failure mode described in the SUMMARY's own falsification proof #1. Reverted the change; re-ran the same test file: 23/23 green. Working tree confirmed clean afterward (git status --short empty).

VERIFIED (independently reproduced, not merely re-read).

5. Ownership checks survived

updateSource (line 221), deleteSource (line 273), testConnection (line 289) all still compare existing.userId !== userId / source.userId !== userId against the session-derived userId parameter and throw ForbiddenException. Three ForbiddenException test cases and three NotFoundException test cases exist and pass.

VERIFIED.

6. No conflict translation added

grep over calendar.service.ts shows no P2002/unique-constraint handling anywhere; the only Prisma-error-sensitive code is the generic catch blocks in testConnection/fetchAndCacheEvents, which existed before this plan and are unrelated to uniqueness. Matches Befund H (no uniqueness chain on CalendarSource besides the client-generated UUID PK).

VERIFIED.

7. Frontend NOT touched

git diff --name-only 508d9e4 HEAD and git diff --name-only 50b3a36 HEAD both show zero files under apps/web. The silent-empty-state behaviour is recorded, not fixed: docs/mandantentrennung-etappe2-fehlerrichtung.md (k3) names calendar-widget.tsx/calendar-settings-panel.tsx/calendar-source-form.tsx by file and line, and .planning/WINDOWS.md entry #26 (open, table row + JSON block both present) describes the same silent-empty-state defect with "das Frontend wird von 260911-cwh NICHT geaendert" stated explicitly.

VERIFIED.

8. Generated-client measurements — committed and honest

Re-ran apps/api/scripts/rls-scratch-check.mjs live against the running tessera-ctl-db-1 container (freshly resolved IP 172.19.0.2, not reused from any cached value):

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

Exit code: 0. Final line: Alle 101 Pruefungen bestanden. — matches the SUMMARY's claimed total (101). All 13 calendarsource-* named checks passed (confirmed by name), including the 4 generated-client checks: calendarsource-generierter-client-gebundene-quellenliste-nur-eigener-mandant, calendarsource-generierter-client-ungebundene-quellenliste-null-zeilen, calendarsource-generierter-client-gebundenes-update-auf-unsichtbare-zeile-scheitert-laut, calendarsource-generierter-client-gebundenes-anlegen-eigener-mandant-gelingt.

The runtime column-set comparison (calendarsource-wegwerftabelle-deckt-alle-spalten-des-generierten-clients) actually executed and printed both sets: schema.prisma yields 17 fields, the scratch table's information_schema.columns yields the same 17 fields, sorted identically — this is a real comparison, not a hardcoded pass.

Call-order gate confirmed: runCalendarAreaChecks is invoked after runDashboardAreaChecks and before runTransactionShapeMeasurement in main() (source inspection, line 3335).

VERIFIED.

9. Five hand-maintained sections — class distribution recomputed independently

Recomputed the class distribution directly from the Bestandsaufnahme rows with an independent awk one-liner (not copy-pasted from the plan's gate):

muss-mandantengebunden: 31
keine-mandantengebundene-tabelle: 17
beides: 13
bewusst-uebergreifend: 2
TOTAL: 63

Matches the documented table exactly (31/17/13/2, Summe 63, heading "63 Paare"). Also recomputed the overview table's column sums across all 12 area rows (tenders 35/27, groups 0/31, ldap 4/26, dkv 1/22, user 8/14, module-registry 7/10, dashboard 1/12, auth 8/5, calendar 0/12, tenant 8/0, favorites 7/0, settings 4/0) → 83/159, matching the documented Summe row. The calendar row itself reads 0 | 12 with the required **war 12/0** marker and explicit no-remaining-unbound justification. The "Stand 260911-cwh" unchanged-marker paragraph is present under ## Klassen-Verteilung. The background-service section header reads "fünf Fälle" (matching its own bullet count) and contains a plain paragraph naming refreshCacheInBackground and calendar without adding a sixth bullet-point case (confirmed: no new - **\...`**entry and no "Der ... Fall, anderer Bauart" line was added for this area).## Was diese Etappe NICHT entscheidetmentionscalendar. WINDOWS #26 present in table row, JSON block, open status, and header counters (total_count: 26= 26 table rows,open_count: 8= 8 rows with| open |`, both independently recomputed and matching).

VERIFIED (all five sections, independently recomputed, not re-read from SUMMARY).

10. Falsification proofs — three claimed

  1. Aggregation write-back binding revert — independently reproduced by this verifier (see item 4 above). Confirmed identical failure mode and message pattern to the one quoted in the SUMMARY.
  2. Bestandsaufnahme Stand mismatch — mechanism confirmed present: apps/api/src/prisma/rls-access-inventory.spec.ts:321 contains the exact assertion template Abweichender Stand (Dokument vs. Quelltext): that the SUMMARY's quoted failure message is built from. Not independently re-triggered (would require editing and reverting the classification doc under time budget), but the underlying gate genuinely exists and matches the described mechanism precisely — not a fabricated quote.
  3. Uebersichtszeile wrong-number gate — the awk gate quoted in the SUMMARY (grep -qE "^\| calendar \| ${DU} \| ${DB} \| \*\*war 12/0\*\*") is present verbatim in the plan's Aufgabe 3 <verify> block, which executed successfully as part of the task-3 commit's automated gate (the task would not have committed otherwise, since these are autonomous: true plans with gated commits).

VERIFIED (one directly reproduced by this verifier, two confirmed as genuinely-wired mechanisms matching the quoted evidence, not fabricated).

11. Constraints held

  • Allow-list scope against 50b3a36: git diff --name-only 50b3a36 HEAD shows exactly the 7 files in files_modified (rls-scratch-check.mjs, mandantentrennung-etappe2-fehlerrichtung.md, calendar.service.spec.ts, calendar.service.ts, calendar.controller.ts, mandantentrennung-zugriffsklassifikation.md, .planning/WINDOWS.md) plus .planning/STATE.md and the plan/summary files themselves under .planning/. No apps/api/prisma, no apps/web, no compose/env file.
  • Providers not exercised against real endpoints: confirmed — icsProvider/caldavProvider/exchangeProvider are vi.fn() mocks throughout the spec file; no network call is made.
  • Switch OFF: no compose/env file in the diff; nothing under apps/api/prisma changed (git diff --name-only 50b3a36 -- apps/api/prisma is empty).

VERIFIED.

Observable Truths

# Truth Status Evidence
1 All 12 calendarSource accesses bound via tenantPrisma, one client per method (6 methods) ✓ VERIFIED Independent grep count: 12 bound, 0 unbound, 6 forTenant() call sites
2 Ownership checks (updateSource/deleteSource/testConnection) read+write over the same bound client, pinned as tests ✓ VERIFIED Source read + 2 tests per path (ForbiddenException/NotFoundException) + expectBoundCall pairs
3 Reverse error direction measured against the real post-20260910120000 rule, ≥4 checks via generated client on a schema-matching scratch table ✓ VERIFIED Live tool run: 101/101, 13 named calendarsource-* checks, 4 via generated client, column-set comparison executed with real 17/17 match
4 Reverse error direction's silent-empty shape named, including frontend swallowing ✓ VERIFIED (k3) names both backend spots and 3 frontend files; WINDOWS #26 open entry
5 Credential-preservation question answered by measurement, pinned as 3 tests ✓ VERIFIED 3 tests at lines 289/303/313, one asserting decrypt/encrypt NOT called
6 Cache-key verdict, 4-link chain, written in code AND critique ✓ VERIFIED Comment above eventCache, (k4)(a) in critique doc, chain matches schema/auth source
7 Ownership checks read (not assumed), kept, pinned ✓ VERIFIED Same as #2
8 Test suite created from nothing, two-client proof, providers not exercised ✓ VERIFIED 23 test cases, __makeBoundClient, providers are vi.fn()
9 Controller passes resolved tenant through to all 5 (of 6) previously-discarding handlers ✓ VERIFIED 6/6 handlers destructure { userId, tenantId }, 0 handlers take userId alone
10 Five hand-maintained classification sections in sync, allow-list gated ✓ VERIFIED Independently recomputed sums/class distribution match exactly
11 Baseline held at end of every task ✓ VERIFIED (via orchestrator measurement) 883/883 tests, 57 files, type-check clean — independently measured by orchestrator per task instructions

Score: 12/12 truths verified (0 present-but-behavior-unverified).

Required Artifacts

Artifact Expected Status Details
apps/api/scripts/rls-scratch-check.mjs 11th section runCalendarAreaChecks, ≥12 named checks, ≥4 via generated client ✓ VERIFIED 13 named checks in normal path, 4 generated-client; live-run confirmed
docs/mandantentrennung-etappe2-fehlerrichtung.md ## Bereich calendar with (k1)-(k5) ✓ VERIFIED All 5 subsections present, content spot-checked
apps/api/src/calendar/calendar.service.spec.ts New, two-client proof, mocks for crypto+3 providers ✓ VERIFIED 23 tests, vi.mock on prisma-tenant.extension, makeFakeCrypto, makeFakeProviders
apps/api/src/calendar/calendar.service.ts All 12 accesses bound, cache-key verdict as comment ✓ VERIFIED 12/12 bound, comment present and accurate
apps/api/src/calendar/calendar.controller.ts 5 discarding handlers pass tenant through ✓ VERIFIED 6/6 handlers pass { userId, tenantId }
docs/mandantentrennung-zugriffsklassifikation.md Bestandsaufnahme, overview, sum, class distribution, background-service section, "was NICHT entscheidet" ✓ VERIFIED All independently recomputed and matched
.planning/WINDOWS.md New open entry via gsd-tools windows append ✓ VERIFIED Entry #26, table+JSON+counters consistent
From To Via Status Details
calendar.controller.ts extractContext CalendarService methods Destructured { userId, tenantId } passed as args ✓ WIRED All 6 handlers
fetchAndCacheEvents success write-back tenantPrisma.calendarSource.update Same client as the findMany load ✓ WIRED Falsified and restored by this verifier
fetchAndCacheEvents catch write-back tenantPrisma.calendarSource.update Same client as the findMany load ✓ WIRED Test exists, source confirmed
eventCache key User.id via extractContext→JwtStrategy→auth.service.ts→schema.prisma Comment chain ✓ WIRED All 4 links independently checked against source

Behavioral Spot-Checks

Behavior Command Result Status
Reverting one write-back binding breaks exactly the named test Edited line 429, ran vitest run src/calendar/calendar.service.spec.ts, restored 22 passed, 1 failed with quoted message; then 23/23 after restore ✓ PASS
rls-scratch-check.mjs passes live against running DB TESSERA_SCRATCH_ADMIN_URL=... node apps/api/scripts/rls-scratch-check.mjs exit 0, "Alle 101 Pruefungen bestanden." ✓ PASS

Anti-Patterns Found

None. Grepped modified files for TBD|FIXME|XXX|TODO|HACK|PLACEHOLDER and empty-return stubs — no matches beyond pre-existing, unrelated code.

Human Verification Required

None. All must-haves resolved to VERIFIED via direct source inspection, independent recomputation, and live tool execution (including one directly reproduced falsification).

Gaps Summary

None found. Working tree is clean; all commits (bf5fc4d, 77cb124, e0e163e, 06038b9) are present in git log on main, in the correct order, on top of base 508d9e4. Scope is allow-listed against 50b3a36 with zero unexpected files. The one minor documentation wrinkle — the plan's Aufgabe 2 carries tdd="true" while the SUMMARY states "Kein TDD-Modus fuer diesen Plan" under "Deviations from Plan: None" — is a self-contradiction inside the SUMMARY's own prose (claims "executed exactly as written" while also describing a TDD-flag deviation), but it has no bearing on the delivered artifacts: the test file exists, is substantive, and all pins are real and falsifiable as demonstrated above. Noted here for completeness, not raised as a gap since it does not affect goal achievement.


Verified: 2026-09-11T10:15:00Z Verifier: Claude (gsd-verifier)