Files

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 test must 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) — nodemailer mocken wie im DKV-Vorbild
  • apps/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 run via pnpm --filter … test)
  • Feedback latency < 15s (scoped runs)
  • nyquist_compliant: true set in frontmatter

Approval: draft — pending execution (wave_0_complete: false)