308 lines
17 KiB
Markdown
308 lines
17 KiB
Markdown
---
|
|
phase: quick-260911-cwh
|
|
verified: 2026-09-11T10:15:00Z
|
|
status: passed
|
|
score: 12/12 must-haves verified
|
|
covered_files:
|
|
- ".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"
|
|
covered_digest: "v1:sha256:f092c2eea8be58064da21108d78ef37879ab4183bdc6c622c699cee603c0f434"
|
|
behavior_unverified: 0
|
|
overrides_applied: 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 entscheidet` mentions `calendar`. 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 |
|
|
|
|
### Key Link Verification
|
|
|
|
| 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)*
|