docs(11): phase verification — 5/5 criteria verified, 293 tests green, UAT pending rebuild
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
---
|
||||
phase: 11-filter-engine-results-ui-saved-searches
|
||||
verified: 2026-07-21T14:56:03Z
|
||||
status: human_needed
|
||||
score: 5/5 must-haves verified
|
||||
behavior_unverified: 0
|
||||
overrides_applied: 0
|
||||
human_verification:
|
||||
- test: "Docker-Stack neu bauen (`docker compose build api web && docker compose up -d`, User-Aktion), dann im Browser: Ausschreibungs-Radar-Modul öffnen, Freitext 'Bau' eingeben, Sortierung nach Frist/Wert/Datum klicken, 'nur noch offene' togglen, PLZ/Bundesland-Dropdown wählen, CPV-Autocomplete 'Bau' -> Division 45 wählen, Wert-min/max setzen bei aktivem 'ohne Wertangabe einschließen'-Toggle."
|
||||
expected: "Trefferliste (~1671 DÖE-Zeilen) rendert; jeder Filter schränkt sichtbar ein; Zeilen ohne Wertangabe bleiben sichtbar mit 'keine Wertangabe'; Coverage-Banner erklärt Oberschwelle-DÖE-Abdeckung."
|
||||
why_human: "Docker-Stack lief zum Zeitpunkt aller 6 Pläne noch auf altem Image (kein Rebuild durch Claude erlaubt — Projektkonvention). Alle zugrunde liegenden Verhaltensweisen sind unit-/component-testbewiesen (293 automatisierte Tests gruen), aber niemand hat den echten Browser-Roundtrip nach dem Rebuild beobachtet."
|
||||
- test: "Nach Rebuild: Zeile in der Trefferliste anklicken -> Detailansicht (?tender=<id>) öffnet als Overlay; Quell-Link in neuem Tab öffnen; Schließen entfernt den Parameter; Ausschreibung ohne Frist/Wert zeigt graceful Text; kein Dokument-Download in der App."
|
||||
expected: "Overlay öffnet/schließt korrekt; sourceUrl-Link führt zum echten DÖE-Portal-Eintrag; keine lokale Spiegelung sichtbar."
|
||||
why_human: "Component-Tests decken Render-/Formatierungslogik ab (TenderDetail.test.tsx, 3 Tests gruen), nicht die echte Klick-Interaktion im laufenden Container."
|
||||
- test: "Nach Rebuild, zwei Testnutzer (unterschiedliche Accounts, ggf. unterschiedliche Browser-Sessions): Nutzer A markiert eine Ausschreibung als gelesen + Favorit und speichert ein Suchprofil 'Test'. Nutzer B öffnet dasselbe Modul."
|
||||
expected: "Nutzer B sieht die Ausschreibung als ungelesen/nicht-favorisiert und sieht das Suchprofil 'Test' NICHT in seiner eigenen Suchprofil-Liste (D-11: per-user, nicht tenant-weit)."
|
||||
why_human: "Cross-User-Sichtbarkeit ist ein echter Mehrbenutzer-Browser-Flow (11-VALIDATION.md Manual-Only Verifications-Tabelle, explizit als solcher deklariert). Auf Code-/Unit-Ebene ist userId-Scoping vollständig bewiesen (TenderTriageService/TenderSavedSearchService: Fremd-userId sieht nichts, Ownership-Check vor Mutation, IDOR-Tests gruen) — nur die tatsächliche Zwei-Browser-Beobachtung fehlt.
|
||||
---
|
||||
|
||||
# Phase 11: Filter Engine, Results UI & Saved Searches Verification Report
|
||||
|
||||
**Phase Goal:** Users can search, filter, and personally triage DÖE tender results, and save reusable filter combinations as personal search profiles, with the UI transparently communicating data coverage.
|
||||
**Verified:** 2026-07-21T14:56:03Z
|
||||
**Status:** human_needed
|
||||
**Re-verification:** No — initial verification
|
||||
|
||||
## Zusammenfassung
|
||||
|
||||
Alle 5 ROADMAP-Erfolgskriterien sind auf Code-, Schema- und Testebene durch eigene Nachprüfung bestätigt — nicht nur aus den SUMMARY.md-Behauptungen übernommen. Ich habe den tatsächlichen Controller-, Query-Builder-, Service- und Komponentencode gelesen, das Schema gegen die Live-DB verifiziert (`psql` gegen `tessera-ctl-db-1`), alle 4 neuen Phase-11-Migrationen als angewendet bestätigt (inkl. der beiden Backfill-Zeilenzahlen 933/1671 Bundesland und 1612/1671 CPV-Divisionen), beide Testsuiten selbst ausgeführt (API 164/164, Web 129/129, beide grün) und `tsc --noEmit` in beiden Apps selbst laufen lassen (beide sauber, Exit 0).
|
||||
|
||||
Der einzige Befund, der den Status von `passed` auf `human_needed` senkt: der Docker-Stack lief während der gesamten Phase-11-Ausführung auf einem älteren Image (alle 6 Plan-SUMMARYs dokumentieren dies selbst konsistent als "Manuelle UAT ausstehend"). Das ist kein Code-Defekt, sondern eine Infrastruktur-/Deployment-Lücke, die laut Projektkonvention beim User liegt (kein Docker-Rebuild durch Claude). Die drei offenen Punkte sind: (1) der volle Browser-Filter-Roundtrip, (2) die Detailansicht-Klick-Interaktion, und (3) die echte Zwei-Nutzer-Cross-Isolation im Browser (D-11) — letztere ist in `11-VALIDATION.md`s eigener "Manual-Only Verifications"-Tabelle bereits so deklariert.
|
||||
|
||||
## Goal Achievement
|
||||
|
||||
### Observable Truths (ROADMAP Success Criteria)
|
||||
|
||||
| # | Truth | Status | Evidence |
|
||||
|---|-------|--------|----------|
|
||||
| 1 | Durchsuchbare/sortierbare Trefferliste + Filter (Freitext, Region/PLZ/Bundesland, CPV hierarchisch+Autocomplete, Frist, Wert); Ausschreibungen ohne Wertangabe werden graceful behandelt | ✓ VERIFIED | `tender-query.builder.ts`: alle 8 AND-Branches (status, openOnly+NULL-Deadline, deadlineFrom/To-Range, q, value+includeNullValue-OR-null, plz, bundesland, region, cpv-hasSome, favOnly) im Code gelesen und mit `tender-query.builder.spec.ts` (25 Tests) abgeglichen — Kern-Test "estimatedValue=null überlebt aktiven Wertfilter" bestätigt vorhanden. `FilterPanel.tsx` (411 Zeilen) hat alle UI-Elemente: Freitext, Sort-Dropdown, "nur noch offene"-Toggle, Frist-von/bis-Datumsfelder, PLZ-Feld, 16-Länder-Bundesland-Dropdown, CPV-Autocomplete mit Chips (45 Divisionen), Wert-min/max + "ohne Wertangabe einschließen"-Toggle. `ResultsList.tsx` rendert `formatValue()` → "keine Wertangabe" bei `null`/NaN (nie leer/0/Fehler). Sort-Whitelist (`SORT_MAP`) verhindert dynamische orderBy-Keys. |
|
||||
| 2 | Detailansicht pro Ausschreibung mit Quell-Link + Dokument-URLs falls vorhanden; keine lokale Spiegelung | ✓ VERIFIED | `TenderDetail.tsx` gelesen: rendert alle Felder (Titel, Auftraggeber, Region/PLZ/Bundesland, CPV, Frist, Wert, Verfahrensart, Status, Veröffentlichungsdatum), `sourceUrl`-Link mit `target="_blank" rel="noopener noreferrer"`, expliziter Hinweistext "Vergabeunterlagen werden nicht lokal gespiegelt". `?tender=<id>`-URL-Vertrag in `page.tsx` verdrahtet (liest `useSearchParams`, rendert Overlay). `TenderDetail.test.tsx` (3 Tests) grün. `rawPayload` ist laut RESEARCH/Plan zu 100% NULL — kein Dokument-Mirroring implementiert (korrekt gemäß D-07). |
|
||||
| 3 | Suchprofil speichern, editieren, löschen — personal per-user, mandanten-gescoped | ✓ VERIFIED (Code+Tests) — Cross-User-Sichtbarkeit im Browser: siehe Human-Item 3 | `TenderSavedSearch`-Modell live in der DB bestätigt (`\d "TenderSavedSearch"`: `userId`, `tenantId`, `name`, `filters jsonb`, `@@unique(userId,name)` als Unique-Index sichtbar). `TenderSavedSearchService` gelesen: `list()`/`create()`/`update()`/`remove()` alle `where:{userId}`-gescoped; Ownership-Check vor Mutation (Fremdbesitz → `NotFoundException`, kein Existence-Leak); P2002 → 409 ConflictException. Controller-Routen `GET/POST /saved-searches`, `PATCH/DELETE /saved-searches/:searchId` alle vor `@Get(':id')` deklariert (grep bestätigt). `SavedSearchBar.tsx` implementiert Speichern/Laden/Umbenennen/Löschen mit `serializeFiltersFromSearchParams`/`filtersToSearchParams`-Rundreise, deckungsgleich mit den FilterPanel-URL-Param-Namen. 10 Service-Tests + 7 UI-Tests grün. |
|
||||
| 4 | Gelesen/Ungelesen + Favorit markieren, Merklisten-Filter; per-user, nicht tenant-weit | ✓ VERIFIED (Code+Tests) — Cross-User-Sichtbarkeit im Browser: siehe Human-Item 3 | `TenderTriage`-Modell live in der DB bestätigt (`\d "TenderTriage"`: FK `ON DELETE CASCADE`, `@@unique(userId,tenderId)`). `TenderTriageService.setTriage` (idempotenter Upsert), `listForUser`/`favoriteIds` beide `where:{userId}`-gescoped. `GET/PUT /triage` vor `@Get(':id')` deklariert. `favOnly`-Branch im Builder: leere Favoritenliste → Sentinel `'__none__'` (garantiert 0 Treffer, nie die ungefilterte Liste). `ResultsList.tsx`: Batch-`fetchTriage` nach jedem Laden, optimistisches Toggle-Update mit Revert bei Fehler, `stopPropagation` verhindert versehentliches Öffnen der Detailansicht. Cascade-Delete zusätzlich live gegen die Dev-DB verifiziert (laut 11-05-SUMMARY: Insert/Delete/Rollback-Transaktion, Zeilenzahl 1→0). 8 Triage-Service-Tests grün. |
|
||||
| 5 | UI weist Datenabdeckung transparent aus (Oberschwelle/Unterschwelle), damit eine dünne Liste nicht als Fehler gelesen wird | ✓ VERIFIED | `GET /modules/tender-radar/coverage` (vor `@Get(':id')` deklariert) liefert `groupBy(sourcePortal)` + Gesamtzahl. `CoverageBanner.tsx` gelesen: zeigt den deutschen Hinweistext ("Aktuell werden nur EU-weite Oberschwellen-Ausschreibungen aus der zentralen DÖE-Quelle erfasst…"), solange alle Quellen `doe-opendata` sind; scheitert lautlos (rendert nichts) bei Fetch-Fehler statt die Seite zu blockieren. In `page.tsx` oberhalb der Liste eingebunden. |
|
||||
|
||||
**Score:** 5/5 truths verified (0 present, behavior-unverified)
|
||||
|
||||
### Required Artifacts
|
||||
|
||||
| Artifact | Expected | Status | Details |
|
||||
|----------|----------|--------|---------|
|
||||
| `apps/api/src/tenders/tender-query.builder.ts` | where-/orderBy-Builder mit allen Filter-Branches | ✓ VERIFIED | Gelesen; 25 Unit-Tests grün |
|
||||
| `apps/api/src/tenders/geo/nuts-bundesland.ts` | NUTS-1→Bundesland-Map | ✓ VERIFIED | 7 Tests grün; Backfill-Effekt live in DB bestätigt (933/1671 Zeilen über 16 Länder) |
|
||||
| `apps/api/src/tenders/cpv/cpv-catalog.ts` | CPV-Divisions-Katalog + Normalisierung | ✓ VERIFIED | 17 Tests grün; Backfill live bestätigt (1612/1671 Zeilen mit gefülltem `cpvDivisions`) |
|
||||
| `apps/api/prisma/migrations/20260721140000_tender_bundesland_backfill/` | Bundesland-Backfill | ✓ VERIFIED | In `_prisma_migrations` als angewendet bestätigt (`finished_at` gesetzt) |
|
||||
| `apps/api/prisma/migrations/20260721150000_tender_cpv_divisions_backfill/` | CPV-Divisions-Backfill | ✓ VERIFIED | Angewendet bestätigt |
|
||||
| `apps/api/prisma/migrations/20260721160000_add_tender_triage/` | TenderTriage-Tabelle | ✓ VERIFIED | Angewendet bestätigt; Tabelle + FK Cascade + Unique-Index live per `\d` bestätigt |
|
||||
| `apps/api/prisma/migrations/20260721170000_add_tender_saved_search/` | TenderSavedSearch-Tabelle | ✓ VERIFIED | Angewendet bestätigt; Tabelle + Unique-Index live per `\d` bestätigt |
|
||||
| `apps/api/src/tenders/tender-triage.service.ts` | Per-user Triage-CRUD | ✓ VERIFIED | Gelesen; where:{userId} durchgängig, kein forTenant/RLS |
|
||||
| `apps/api/src/tenders/tender-saved-search.service.ts` | Per-user Suchprofil-CRUD | ✓ VERIFIED | Gelesen; Ownership-Check, 409-Translation |
|
||||
| `apps/web/.../components/ResultsList.tsx` | Trefferliste + Triage-Toggles | ✓ VERIFIED | Gelesen (346 Zeilen); vollständig verdrahtet |
|
||||
| `apps/web/.../components/FilterPanel.tsx` | Alle Filter-UI-Elemente | ✓ VERIFIED | Gelesen (411 Zeilen); alle Felder aus D-01..D-05/D-09/D-10 vorhanden |
|
||||
| `apps/web/.../components/CoverageBanner.tsx` | Abdeckungs-Hinweis | ✓ VERIFIED | Gelesen; korrekt bedingt gerendert |
|
||||
| `apps/web/.../components/TenderDetail.tsx` | Detailansicht | ✓ VERIFIED | Gelesen; alle Felder + Quell-Link + Kein-Mirroring-Hinweis |
|
||||
| `apps/web/.../components/SavedSearchBar.tsx` | Suchprofil-UI | ✓ VERIFIED | Gelesen (311 Zeilen); Speichern/Laden/Umbenennen/Löschen vollständig |
|
||||
|
||||
### Key Link Verification
|
||||
|
||||
| From | To | Via | Status | Details |
|
||||
|------|-----|-----|--------|---------|
|
||||
| `FilterPanel` URL-searchParams | `ResultsList` → `listTenders()` | `useSearchParams()` liest, `router.replace()` schreibt | ✓ WIRED | Code bestätigt; `paramsKey` als `useCallback`-Dependency triggert Re-Fetch |
|
||||
| `GET /coverage`, `/source-config`, `/triage`, `/saved-searches` | vs. `@Get(':id')` | Deklarationsreihenfolge im Controller | ✓ WIRED (kein Shadowing) | grep bestätigt: alle statischen Routen VOR `@Get(':id')` (Zeile 309) deklariert; eigene Regressionstests in `tenders.controller.spec.ts` (`declares X before getTender`) vorhanden und grün |
|
||||
| `ResultsList`-Zeilenklick | `TenderDetail` (Overlay) | `?tender=<id>` via `router.replace` | ✓ WIRED | `openDetail()` in ResultsList, gelesen von `page.tsx` |
|
||||
| `PUT /triage` | `TenderTriageService.setTriage` → Upsert `@@unique([userId,tenderId])` | Controller-Aufruf | ✓ WIRED | Code + 8 Service-Tests |
|
||||
| `favOnly=true` | `TenderTriageService.favoriteIds(userId)` → `buildTenderWhere(dto, favIds)` | Controller-seitige Auflösung vor Builder-Aufruf | ✓ WIRED | Code bestätigt; leere Liste → Sentinel `'__none__'` |
|
||||
| `SavedSearchBar` Speichern | `POST /saved-searches` → `TenderSavedSearchService.create` | `serializeFiltersFromSearchParams` + `createSavedSearch()` | ✓ WIRED | Code + 7 UI-Tests (Rundreise-Vertrag geprüft) |
|
||||
| `SavedSearchBar` Laden | URL-searchParams (Filter-Zustand) | `filtersToSearchParams()` + `router.replace` | ✓ WIRED | Code bestätigt; identische Param-Namen wie FilterPanel |
|
||||
|
||||
### Behavioral Spot-Checks / Aggregatprüfungen (selbst ausgeführt)
|
||||
|
||||
| Behavior | Command | Result | Status |
|
||||
|----------|---------|--------|--------|
|
||||
| API-Testsuite vollständig | `cd apps/api && npx vitest run` | 14 Dateien, **164/164 Tests grün** | ✓ PASS |
|
||||
| Web-Testsuite vollständig | `cd apps/web && npx vitest run` | 23 Dateien, **129/129 Tests grün** | ✓ PASS |
|
||||
| API `tsc --noEmit` | `cd apps/api && npx tsc --noEmit` | Exit 0, keine Ausgabe | ✓ PASS |
|
||||
| Web `tsc --noEmit` | `cd apps/web && npx tsc --noEmit` | Exit 0, keine Ausgabe | ✓ PASS |
|
||||
| Alle 5 Phase-11-Migrationen angewendet | `psql` gegen `_prisma_migrations` (`tessera-ctl-db-1`) | 5/5 Migrationen mit `finished_at` gesetzt | ✓ PASS |
|
||||
| Bundesland-Backfill live | `SELECT bundesland, count(*) FROM "Tender" GROUP BY bundesland` | 933 von 1671 Zeilen über alle 16 Länder befüllt (738 NULL, wo `region` selbst NULL ist) | ✓ PASS |
|
||||
| CPV-Divisions-Backfill live | `SELECT count(*) FILTER (WHERE cardinality("cpvDivisions")>0)` | 1612 von 1671 Zeilen befüllt | ✓ PASS |
|
||||
| `TenderTriage`/`TenderSavedSearch` live vorhanden | `\d "TenderTriage"` / `\d "TenderSavedSearch"` | Beide Tabellen inkl. FK Cascade + Unique-Indizes bestätigt | ✓ PASS |
|
||||
| Route-Order-Pitfall respektiert | grep `@Get\|@Put\|@Post\|@Patch\|@Delete` in Deklarationsreihenfolge | Alle statischen Routen vor `@Get(':id')` (Zeile 309) | ✓ PASS |
|
||||
| Keine Anti-Pattern-Marker | grep TBD/FIXME/XXX/TODO/HACK/PLACEHOLDER über 14 Phase-11-Kerndateien | 0 Treffer | ✓ PASS |
|
||||
| Git-Commits vorhanden | `git log --oneline` über Phase-11-Pfade | Alle in den SUMMARYs zitierten Commit-Hashes (426a1ea … dd3ff9e) im Log gefunden | ✓ PASS |
|
||||
|
||||
### Requirements Coverage
|
||||
|
||||
| Requirement | Source Plan | Description | Status | Evidence |
|
||||
|--------------|------------|-------------|--------|----------|
|
||||
| FILTER-01 | 11-01 | Volltext-Stichwortsuche (Titel/Auftraggeber) | ✓ SATISFIED | `buildTenderWhere` q-Branch (title/buyerName, insensitive); FilterPanel-Suchfeld |
|
||||
| FILTER-02 | 11-02 | Region/PLZ/Bundesland-Filter | ✓ SATISFIED | NUTS-Ableitung + Backfill (933 Zeilen live bestätigt) + Builder-Branches + Dropdown/Feld |
|
||||
| FILTER-03 | 11-03 | CPV-Code/Branche hierarchisch + Autocomplete | ✓ SATISFIED | CPV-Divisions-Katalog (45 Divisionen) + Backfill (1612 Zeilen live bestätigt) + hasSome-Branch + Autocomplete-UI mit Chips |
|
||||
| FILTER-04 | 11-01 | Abgabefrist-Filter inkl. "nur noch offene" | ✓ SATISFIED | openOnly-Default (NULL-Deadlines eingeschlossen) + expliziter Frist-Range, kombinierbar |
|
||||
| FILTER-05 | 11-01, 11-03 | Wert min/max, NULL-Wertangabe graceful | ✓ SATISFIED | Backend-Builder (11-01) + UI-Toggle "ohne Wertangabe einschließen" (11-03), Default an |
|
||||
| FILTER-06 | 11-06 | Suchprofile speichern/bearbeiten/löschen, per-user, mandantenbewusst | ✓ SATISFIED | `TenderSavedSearch`-Modell live + Service + Routen + UI; Cross-User-Sichtbarkeit im Browser noch nicht live beobachtet (Human-Item 3) |
|
||||
| UI-01 | 11-01 | Durchsuchbare/sortierbare Trefferliste (Frist/Wert/Datum) | ✓ SATISFIED | Sort-Whitelist + sortierbare Spaltenköpfe in ResultsList |
|
||||
| UI-02 | 11-04 | Detailansicht + Quell-Link, keine lokale Spiegelung | ✓ SATISFIED | TenderDetail-Komponente, Quell-Link mit sicherem target/rel, expliziter Hinweis |
|
||||
| UI-03 | 11-05 | Gelesen/Ungelesen pro Nutzer | ✓ SATISFIED | TenderTriage + Toggle in ResultsList; Cross-User-Sichtbarkeit im Browser noch nicht live beobachtet (Human-Item 3) |
|
||||
| UI-04 | 11-05 | Favorit/Merkliste + Merklisten-Filter pro Nutzer | ✓ SATISFIED | favOnly-Branch (Sentinel für leere Liste) + Toggle + Checkbox; Cross-User-Sichtbarkeit im Browser noch nicht live beobachtet (Human-Item 3) |
|
||||
| UI-05 | 11-01 | Abdeckungs-Transparenz (Oberschwelle/Unterschwelle) | ✓ SATISFIED | GET /coverage + CoverageBanner |
|
||||
|
||||
Keine verwaisten (orphaned) Requirements gefunden — REQUIREMENTS.md listet für Phase 11 exakt FILTER-01..06 und UI-01..05, alle sind über die 6 Pläne deklariert und im Code bestätigt. (UI-06 ist korrekt Phase 14 zugeordnet, nicht Teil dieser Phase.)
|
||||
|
||||
### Anti-Patterns Found
|
||||
|
||||
| File | Line | Pattern | Severity | Impact |
|
||||
|------|------|---------|----------|--------|
|
||||
| — | — | Keine TBD/FIXME/XXX/TODO/HACK/PLACEHOLDER-Marker in den 14 geprüften Kerndateien (Builder, Controller, Services, Katalog-Dateien, alle Web-Komponenten) gefunden | — | — |
|
||||
| Live-Docker-Container `tessera-ctl-api-1`/`tessera-ctl-web-1` | — | Container-Images sind älter als der gesamte Phase-11-Code (alle 6 SUMMARYs dokumentieren dies konsistent selbst) | ⚠️ Warning | Kein Code-Defekt — Migration + Code sind korrekt und durch 293 automatisierte Tests + Live-DB-Introspektion bewiesen; aber niemand hat den echten Browser-Roundtrip nach einem Rebuild beobachtet. Rebuild liegt laut Projektkonvention beim User |
|
||||
|
||||
### Human Verification Required
|
||||
|
||||
Siehe YAML-Frontmatter `human_verification` für die drei Items (voller Filter-Roundtrip im Browser, Detailansicht-Klick-Interaktion, Zwei-Nutzer-Cross-Isolation für Triage + Suchprofile). Alle drei sind bereits von den Executor-SUMMARYs selbst als offene UAT-Punkte dokumentiert (11-01 bis 11-06 SUMMARY "Manuelle UAT ausstehend" / "Next Phase Readiness") und werden hier bestätigt, nicht neu erfunden. Keines davon ist ein Code-Gap — die zugrunde liegende Logik (Filter-Korrektheit, IDOR-Scoping, Cascade-Verhalten) ist bereits durch Unit-/Integrationstests UND teilweise durch direkte Live-DB-Verifikation bewiesen.
|
||||
|
||||
### Gaps Summary
|
||||
|
||||
Keine Gaps auf Code-Ebene gefunden. Alle 5 ROADMAP-Erfolgskriterien sowie alle 11 Requirements (FILTER-01..06, UI-01..05) sind durch eigene Nachprüfung bestätigt: Ich habe den Controller-, Builder-, Service- und Komponentencode selbst gelesen (nicht nur Summary-Behauptungen übernommen), das Schema live gegen die Dev-DB verifiziert, alle 5 Migrationen als angewendet bestätigt inkl. der konkreten Backfill-Zeilenzahlen, beide Testsuiten selbst ausgeführt (164 + 129 Tests grün) und `tsc --noEmit` in beiden Apps selbst laufen lassen (beide sauber). Route-Order-Pitfall (Phase-10-Regression) ist an allen 7 neuen statischen Routen korrekt vermieden und durch eigene Regressionstests abgesichert. IDOR-Mitigationen (per-user `where:{userId}`, Ownership-Check vor Mutation, userId/tenantId ausschließlich aus dem Auth-Kontext, nie aus Body/Query) sind für Triage UND Saved-Search vollständig vorhanden und unit-getestet.
|
||||
|
||||
Der Status ist `human_needed` statt `passed` ausschließlich wegen des veralteten laufenden Docker-Images — dies ist keine Code-Lücke, sondern eine Infrastruktur-/Deployment-Lücke, die dem Projekt-Konvention entsprechend beim User liegt (Rebuild wird nicht von Claude auf dem Testserver/lokal durchgeführt). Empfehlung: kurzer manueller UAT-Durchlauf nach Stack-Rebuild (Filter-Roundtrip, Detail-Klick, Zwei-Nutzer-Isolation), danach kann Phase 12 starten.
|
||||
|
||||
---
|
||||
|
||||
_Verified: 2026-07-21T14:56:03Z_
|
||||
_Verifier: Claude (gsd-verifier)_
|
||||
Reference in New Issue
Block a user