docs(quick-260911-cwh): Etappe 2 Bereich calendar abgeschlossen und verifiziert
This commit is contained in:
+1
-1
@@ -135,7 +135,7 @@ _Kein TDD-Modus fuer diesen Plan — die Testlage wurde in Aufgabe 2 als Vorauss
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
None - plan executed exactly as written. Alle Planungsbefunde (A-L) wurden zur Ausfuehrungszeit nachgeprueft und bestaetigt; keine Abweichung von den Planungsbefunden trat auf.
|
||||
Eine, dokumentarischer Art: Aufgabe 2 war im Plan als `tdd="true"` markiert, wurde aber nicht als RED/GREEN-Zyklus je Testfall entwickelt — die Testdatei entstand als Voraussetzung im Ganzen (siehe Hinweis oben unter der Testtabelle). Inhaltlich ohne Folge: die Falsifizierung durch Rueckbau ersetzt den RED-Nachweis. Dieser Abschnitt sagte zunaechst 'None' und widersprach damit dem eigenen Hinweis weiter oben; vom Verifizierer bemerkt, hier berichtigt. Alle Planungsbefunde (A-L) wurden zur Ausfuehrungszeit nachgeprueft und bestaetigt.
|
||||
|
||||
## Falsifizierungsnachweise (drei, alle durchgefuehrt und zurueckgenommen)
|
||||
|
||||
|
||||
+307
@@ -0,0 +1,307 @@
|
||||
---
|
||||
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)*
|
||||
Reference in New Issue
Block a user