8.4 KiB
8.4 KiB
phase, slug, status, nyquist_compliant, wave_0_complete, created
| phase | slug | status | nyquist_compliant | wave_0_complete | created |
|---|---|---|---|---|---|
| 12 | tender-notifications | draft | true | false | 2026-07-22 |
Phase 12 — Validation Strategy
Per-phase validation contract for feedback sampling during execution. Extracted from 12-RESEARCH.md § Validation Architecture (Test framework, Requirements→Test map, Sampling rate, Wave-0 gaps).
Test Infrastructure
| Property | Value |
|---|---|
| Framework | Vitest (API 3.x, Web 4.x — both existing, no install) |
| Config file | apps/api/vitest.config.ts (existing — not modified by this phase) |
| Quick run command (API slice) | pnpm --filter api test -- <spec> |
| Quick run command (Web slice) | pnpm --filter web test -- <spec> |
| Full suite command | pnpm test (turbo) bzw. pnpm --filter api test && pnpm --filter web test |
| Estimated runtime | ~10-15 seconds (scoped) |
No new packages: nodemailer, @nestjs/schedule, cron, prisma are already in apps/api/package.json (legitimized in Phase 7/10).
Sampling Rate
- After every task commit: Run the affected slice spec (
pnpm --filter api test -- <spec>). - After every plan wave: Run
pnpm --filter api test && pnpm --filter web test. - Before
/gsd-verify-work:pnpm testmust be green. - Max feedback latency: ~15 seconds (scoped run).
Per-Task Verification Map
| Task ID | Plan | Wave | Requirement | Threat Ref | Secure Behavior | Test Type | Automated Command | File Exists | Status |
|---|---|---|---|---|---|---|---|---|---|
| 12-01-01 | 01 | 1 | NOTIFY-03 | T-12-01/03 | Schema TenderMatch (@@unique[tenderId,savedSearchId], EIN notifiedAt) + NotificationPref + instantAlert; Migration lokal angewendet | integration | cd apps/api && npx prisma validate && npx prisma generate (+ psql-Tabellencheck, manual) |
⚠️ migration | ⬜ pending |
| 12-01-02 | 01 | 1 | NOTIFY-03 | T-12-02/04 | matchDelta: delta-only (id IN newTenderIds), Match-Erzeugung, idempotenter Upsert (update:{} bewahrt notifiedAt) | unit | pnpm --filter api test -- tender-matching.service |
❌ W0 | ⬜ pending |
| 12-01-03 | 01 | 1 | NOTIFY-03 | T-12-02 | pollDueSources sammelt genuin neue Tender-IDs, ruft matchDelta nur mit diesen; Provider registriert | unit | pnpm --filter api test -- tender-ingestion.service |
⚠️ extend | ⬜ pending |
| 12-02-01 | 02 | 2 | NOTIFY-04 | T-12-06/08 | TenderMailService: getDecryptedSmtpConfig je Send, frischer Transport + close(), Skip bei fehlender Config, kein globaler Mailer | unit (mock nodemailer) | pnpm --filter api test -- tender-mail.service |
❌ W0 | ⬜ pending |
| 12-02-02 | 02 | 2 | NOTIFY-01/03 | T-12-05/07/09 | Digest-Cron: Fälligkeit daily/weekly/off + Default-daily, Multi-Tenant findMany, eine sektionierte Mail/Nutzer, notifiedAt-Stempel nur nach Erfolg, robust bei SMTP-Fehler | unit | pnpm --filter api test -- tender-digest.scheduler |
❌ W0 | ⬜ pending |
| 12-02-03 | 02 | 2 | NOTIFY-01/04 | — | SettingsModule-Import + Provider (TenderMailService, TenderDigestScheduler); DI-Graph auflösbar | typecheck | cd apps/api && npx tsc --noEmit -p tsconfig.json |
n/a | ⬜ pending |
| 12-03-01 | 03 | 3 | NOTIFY-02/03 | T-12-11/12/13 | Instant-Dispatch: nur instantAlert=true, Bündelung pro Profil/Tick, notifiedAt='instant' nur nach Erfolg, catch-and-log | unit | pnpm --filter api test -- tender-matching.service |
⚠️ extend | ⬜ pending |
| 12-03-02 | 03 | 3 | NOTIFY-03 | T-12-10 | Instant+Digest = genau EINE Mail (kein Doppelversand über beide Kanäle) | integration | pnpm --filter api test -- tender-notifications.integration |
❌ W0 | ⬜ pending |
| 12-04-01 | 04 | 3 | NOTIFY-01/02 | T-12-14/15/16 | Pref-Service (Default daily, per-user Upsert) + @IsIn-DTO + GET/PUT-Routen VOR :id + instantAlert-Durchreichung; userId aus Auth-Context | integration | pnpm --filter api test -- tender-notification-pref.service (+ npx tsc --noEmit) |
❌ W0 | ⬜ pending |
| 12-04-02 | 04 | 3 | NOTIFY-01/02 | — | API-Client: fetch/saveNotificationPref + instantAlert in SavedSearch-Typ/Payloads | typecheck | cd apps/web && npx tsc --noEmit (+ grep notification-pref/instantAlert) |
n/a | ⬜ pending |
| 12-04-03 | 04 | 3 | NOTIFY-01/02 | T-12-16 | Settings-Digest-Intervall-Auswahl (lädt+speichert); SavedSearchBar Sofort-Alert-Toggle pro Profil | component | pnpm --filter web test -- SavedSearchBar (+ npx tsc --noEmit) |
⚠️ extend | ⬜ pending |
Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky
Wave 0 Requirements
apps/api/src/tenders/tender-matching.service.spec.ts— matchDelta: delta-only (0 Matches bei 2188 Bestand), Match-Erzeugung, Idempotenz (update:{} bewahrt notifiedAt), leeres Delta (Plan 12-01 Task 2, RED first); in Plan 12-03 Task 1 um Instant-Dispatch erweitert (RED first)apps/api/src/tenders/tender-mail.service.spec.ts— getDecryptedSmtpConfig je Send, frischer Transport + close() im finally, Skip bei fehlender Config ohne Throw, sektionierter Digest-Body (Plan 12-02 Task 1, RED first) —nodemailermocken wie im DKV-Vorbildapps/api/src/tenders/tender-digest.scheduler.spec.ts— Fälligkeit daily/weekly/off + Default-daily, Multi-Tenant findMany (kein findFirst), eine Mail/Nutzer, kein Doppelversand, Robustheit (Plan 12-02 Task 2, RED first)apps/api/src/tenders/tender-notifications.integration.spec.ts— instant-on ⇒ 1 Instant/0 Digest; instant-off ⇒ 0 Instant/1 Digest — genau eine Mail über beide Kanäle (Plan 12-03 Task 2)apps/api/src/tenders/tender-notification-pref.service.spec.ts— per-user CRUD, Default 'daily', Upsert auf @@unique userId, @IsIn-Validierung (Plan 12-04 Task 1, RED first)- (extend)
apps/api/src/tenders/tender-ingestion.service.spec.ts— matchDelta wird nur mit den genuin neuen Tender-IDs des Ticks aufgerufen (Plan 12-01 Task 3) - (extend)
apps/web/src/app/(portal)/modules/tender-radar/components/SavedSearchBar.test.tsx— Sofort-Alert-Toggle rendert Zustand + ruft updateSavedSearch({ instantAlert }) (Plan 12-04 Task 3)
Vitest-Framework existiert bereits (api + web) — keine Framework-Installation nötig.
Manual-Only Verifications
| Behavior | Requirement | Why Manual | Test Instructions |
|---|---|---|---|
Migration 20260722100000_add_tender_notifications auf DB angewendet |
NOTIFY-03 | DB hat keinen Host-Port; lokale Anwendung via docker compose exec -T db psql -U tessera -d tessera_dev (MEMORY-Hinweis); kein Docker-Deploy auf dem Testserver durch Claude |
SELECT to_regclass('public."TenderMatch"'), to_regclass('public."TenderNotificationPref"') ≠ NULL; \d "TenderSavedSearch" zeigt instantAlert; prisma migrate status grün |
| Tatsächlicher SMTP-Versand (Transportaufbau + Zustellung) | NOTIFY-04 | Realer Mailversand ist mit gemocktem nodemailer nicht beobachtbar; braucht lokales Relay | Digest-/Instant-Testlauf gegen Mailhog localhost:1025; Mail erscheint dort, Body ist nach Profil gegliedert (Digest) bzw. eine Sammelmail (Instant) |
| Zwei-Mandanten-Digest-Isolation | NOTIFY-01/03 | Echte Cross-Tenant-Isolation braucht zwei reale Nutzer in zwei Mandanten mit eigener SmtpConfig — im Unit-Test nur gemockt | Zwei Nutzer/zwei Mandanten daily → jeder erhält genau seine eigenen Treffer über seine eigene SMTP; kein Nutzer sieht Treffer des anderen |
| Ein-Mail-Garantie über beide Kanäle (End-to-End) | NOTIFY-03 | Automatisiert durch 12-03-02 (integration), aber finale Bestätigung am echten Pipeline-Lauf | Profil instant=on + user daily, ein neuer Match → insgesamt genau EINE Mail (die Instant-Mail); der folgende Digest sendet für dieses Paar nichts |
| Backfill-Nicht-Flut bei Profil-Anlage | NOTIFY-03 (D-07) | Beobachtung am realen Bestand (~2188 Tender) | Neues Profil bei vorhandenem Bestand anlegen → 0 un-benachrichtigte Matches, keine Mail-Flut; Treffer bleiben in der Trefferliste sichtbar |
Validation Sign-Off
- All tasks have
<automated>verify or a documented Wave 0 / manual-gate dependency - Sampling continuity: no 3 consecutive tasks without automated verify
- Wave 0 covers all MISSING references (spec scaffolds; RED-first for pure-logic units)
- No watch-mode flags (all use
vitest runviapnpm --filter … test) - Feedback latency < 15s (scoped runs)
nyquist_compliant: trueset in frontmatter
Approval: draft — pending execution (wave_0_complete: false)