e5098603b4
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
180 lines
18 KiB
Markdown
180 lines
18 KiB
Markdown
---
|
|
phase: quick-260914-m97
|
|
verified: 2026-09-14T15:15:19Z
|
|
status: passed
|
|
score: 9/9 must-haves verified (Code/Tests/CI) + Browser-Beweis durch den Orchestrator am 2026-09-14 15:16Z bestanden (siehe Nachtrag unten)
|
|
behavior_unverified: 0
|
|
overrides_applied: 0
|
|
human_verification:
|
|
- test: "Browser-Check mit mailhog (SUMMARY-Abschnitt 'Fuer den Verifizierer')"
|
|
expected: "Kaefer-Knopf nimmt Bild VOR dem Dialog auf (Vorschau zeigt NICHT den Dialog, OKLCH-Farben korrekt); Senden erzeugt E-Mail in mailhog mit Betreff '[Tessera Fehlermeldung] ...' und PNG-Anhang < 2 MB; leeres Empfaenger-Feld -> 409-Text mit Admin-Link; sechste Meldung -> 429-Text; API-Log zeigt eine Zeile je Meldung ohne Bild/Beschreibung"
|
|
why_human: "jsdom rastert nicht (HTMLVideoElement is not defined, kein Canvas-Backend) - html-to-image kann nur im echten Browser beobachtet werden; dies ist laut Auftrag ausdruecklich Aufgabe des Orchestrators, NICHT dieses Verifizierers"
|
|
---
|
|
|
|
# Quick 260914-m97: Fehler-melden-Knopf — Verifikationsbericht
|
|
|
|
**Auftrag:** Fehler-melden-Knopf in der Kopfzeile — Bildschirmfoto VOR dem Dialog (html-to-image 1.11.13), Dialog mit Vorschau/Beschreibung/Haekchen, `POST /bug-reports` (Multipart, nur angemeldet, Mandant/Benutzer nur aus der Sitzung, 4 MiB -> 413, PNG-Signatur -> 400, fehlender Empfaenger -> 409, Drossel 5/10 min -> 429, Versandfehler -> 502), E-Mail mit PNG-Anhang und Kontext, Empfaenger als neue Spalte `SmtpConfig.bugReportRecipient`, Rueckfall `TESSERA_BUGREPORT_TO`, Handbuecher, gepusht, CI gruen.
|
|
|
|
**Verifiziert:** 2026-09-14T15:15Z
|
|
**Status:** human_needed (Code/Tests/Migration/CI vollstaendig verifiziert; einziger offener Punkt ist der Browser-Beweis, der laut Auftrag dem Orchestrator obliegt)
|
|
|
|
Umgebungshinweis: Zu Beginn dieser Verifikation war die Festplatte `/` kurzzeitig zu 100% voll, wodurch drei parallel gestartete `npx tsc`-Aufrufe mit `ENOSPC` fehlschlugen (npx wollte Cache-Metadaten schreiben, auch fuer bereits lokal vorhandene Binaries). Ich habe daraufhin `node node_modules/typescript/bin/tsc --noEmit` direkt aufgerufen (umgeht den npx-Cache) — alle drei Pakete meldeten danach Exit 0. Kein Projektartefakt betroffen; df zeigte kurz danach wieder 8,4 GiB frei (90% belegt), vermutlich ein voruebergehender Cache-Peak eines Fremdprozesses auf der Maschine.
|
|
|
|
## 1. Git-Historie und Datei-Umfang
|
|
|
|
| Pruefung | Befehl | Ergebnis | Status |
|
|
|---|---|---|---|
|
|
| Vier Commits | `git log --oneline 17a7e5e..HEAD` | `77117de`, `b41be21`, `60b0ee8`, `54121c1` — exakt die vier erwarteten | OK |
|
|
| Datei-Umfang | `git diff --stat 5c42c55 -- . ':!.planning'` | `35 files changed, 2026 insertions(+), 17 deletions(-)` | OK |
|
|
| Sensible Dateien unangetastet | `git diff --name-only 5c42c55 -- '.env*' apps/api/src/main.ts biome.json` | leer | OK |
|
|
| Bestehende Migrationen unangetastet | `git diff --name-only 5c42c55 -- apps/api/prisma/migrations \| grep -v 20260914170000` | leer (grep exit 1) | OK |
|
|
| Commit `60b0ee8` Datei-Zeilen | `git show --stat 60b0ee8 \| grep -c '\|'` | `13` | OK, passt zu 13 Dateien |
|
|
| Commit `b41be21` Datei-Zeilen | `git show --stat b41be21 \| grep -c '\|'` | `7` — bei Pruefung: Commit-Botschaft selbst enthaelt zwei `\|`-Zeichen (`string \| null`, `trim() \|\| null`); tatsaechliche Datei-Zeilen im Diffstat sind **5** (`smtp-settings-form.test.tsx`, `smtp-settings-form.tsx`, `settings-api.ts`, `de.json`, `en.json`) — passt zu den 5 erwarteten Dateien | OK (Messmuster liefert falsches Positiv, Datei-Zaehlung selbst stimmt) |
|
|
| Nur die zwei erwarteten Paket-Dateien geaendert | `git diff --name-only 5c42c55 -- apps/api/package.json packages/shared/package.json package.json apps/web/package.json pnpm-lock.yaml` | nur `apps/web/package.json`, `pnpm-lock.yaml` | OK |
|
|
|
|
## 2. Testsuiten und Typprüfung
|
|
|
|
| Pruefung | Befehl | Ergebnis | Status |
|
|
|---|---|---|---|
|
|
| API-Suite | `cd apps/api && npx vitest run` | `Test Files 67 passed (67)` / `Tests 1076 passed (1076)` | OK, passt exakt |
|
|
| Web-Suite | `cd apps/web && node node_modules/vitest/vitest.mjs run` (npx scheiterte an ENOSPC, siehe Umgebungshinweis) | `Test Files 43 passed (43)` / `Tests 260 passed (260)` | OK, passt exakt |
|
|
| `tsc --noEmit` api | `node node_modules/typescript/bin/tsc --noEmit` | Exit 0 | OK |
|
|
| `tsc --noEmit` web | `node node_modules/typescript/bin/tsc --noEmit` | Exit 0 | OK |
|
|
| `tsc --noEmit` shared | `node node_modules/typescript/bin/tsc --noEmit` | Exit 0 | OK |
|
|
| `pnpm install --frozen-lockfile` | `pnpm install --frozen-lockfile` | Exit 0, `Lockfile is up to date, resolution step is skipped`; `git status --porcelain -- pnpm-lock.yaml apps/web/package.json` danach leer | OK |
|
|
| `rls-access-inventory.spec.ts` | `node node_modules/vitest/vitest.mjs run src/prisma/rls-access-inventory.spec.ts` | `30 passed (30)` | OK |
|
|
| `umlaut-guard.spec.ts` + `tenderRadar-parity.spec.ts` | `node node_modules/vitest/vitest.mjs run src/messages` | `Test Files 2 passed (2)` / `Tests 6 passed (6)` | OK |
|
|
|
|
## 3. Migration und Datenbank
|
|
|
|
| Pruefung | Befehl | Ergebnis | Status |
|
|
|---|---|---|---|
|
|
| Migrationsstatus | `prisma migrate status` (DATABASE_URL gegen `172.19.0.2`, nicht in `.env` geschrieben) | `37 migrations found` / `Database schema is up to date!` | OK |
|
|
| Migrations-SQL additiv | `cat .../20260914170000_.../migration.sql` | genau EINE Anweisung `ALTER TABLE "SmtpConfig" ADD COLUMN "bugReportRecipient" TEXT;` (nullbar, keine weiteren Statements), davor nur Kommentarzeilen | OK |
|
|
| Spalte in lokaler DB | `docker exec ... psql -c '\d "SmtpConfig"'` | `bugReportRecipient \| text \| \| \|` (nullable) | OK |
|
|
|
|
## 4. API-Modul `bug-reports`
|
|
|
|
Gelesen: `bug-reports.module.ts`, `bug-reports.controller.ts`, `bug-reports.service.ts`, `dto/bug-report.dto.ts`, `mail.service.ts`.
|
|
|
|
| Anforderung | Befund | Status |
|
|
|---|---|---|
|
|
| Kein `@Roles`, offen fuer alle angemeldeten Rollen | `@Controller('bug-reports')` / `@Post()` ohne Rollen-Dekorator; `ROLES_KEY`-Metadatum ist laut `bug-reports.controller.spec.ts` Test 3 `undefined` | OK |
|
|
| `@CurrentUser()` einzige Quelle fuer Mandant/Benutzer | `submit(@CurrentUser() user, @Body() dto, @UploadedFile() file)`; DTO hat keine `tenantId`/`userId`-Felder | OK |
|
|
| `FileInterceptor` 4 MiB | `FileInterceptor('screenshot', { limits: { fileSize: 4 * 1024 * 1024, files: 1 } })` | OK |
|
|
| PNG-Signatur | `PNG_SIGNATURE = Buffer.from([0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a])`, Pruefung vor Versand, `BadRequestException` bei Fehlschlag | OK |
|
|
| Drossel 5/10min -> 429 | `WINDOW_MS = 10*60*1000`, `MAX_PER_WINDOW = 5`, `HttpException(..., 429)`; **unabhaengig falsifiziert** (siehe unten) | OK |
|
|
| 409 bei fehlendem Empfaenger | `getBugReportRecipient(tenantId) \|\| TESSERA_BUGREPORT_TO.trim() \|\| null`, sonst `ConflictException` mit Text „Fehlermeldungen an" | OK |
|
|
| 502 bei Versandfehler | `try { sendBugReport(...) } catch { throw new BadGatewayException(...) }` | OK |
|
|
| Genau eine Protokollzeile, nie Bild/Beschreibung | `this.logger.log('Bug report from ... sent to ... page ..., screenshot ... bytes')` | OK |
|
|
| `mail.service.ts`: `sendBugReport` wirft, `sendPasswordResetEmail` verschluckt weiter | `deliver()` wirft, `sendViaTenantTransport` faengt (T-02-12 unveraendert), `sendBugReport` ruft `deliver` direkt; `mail.service.spec.ts` Test 5/6 pinnt genau das | OK |
|
|
|
|
**Unabhaengige Falsifizierung (Punkt 4 der Vorgabe):** `MAX_PER_WINDOW` in `bug-reports.service.ts` temporaer von `5` auf `6` geaendert, `bug-reports.service.spec.ts` erneut gelaufen -> Test 3 ("Falsifizierung a") wird ROT (`expected undefined to be an instance of HttpException`), die uebrigen 7 Tests bleiben gruen. Danach `git checkout -- apps/api/src/bug-reports/bug-reports.service.ts`; `grep MAX_PER_WINDOW` zeigt wieder `= 5`; `git status --porcelain -- apps/` ist leer — Ruecksetzung bewiesen.
|
|
|
|
## 5. Web: Fehlerpuffer, Bildaufnahme, Knopf, Dialog
|
|
|
|
| Anforderung | Befund | Status |
|
|
|---|---|---|
|
|
| Ringpuffer 20, `error`/`unhandledrejection`/`console.error`/`fetch` | `error-buffer.ts`: `MAX_ENTRIES = 20`, alle vier Quellen registriert | OK |
|
|
| Fetch-Wrapper notiert nur `!response.ok`, nie Anfrage-Rumpf/Cookies/Suchteil | Wrapper prueft `if (!response.ok)`, nutzt `pathOf()` (nur `pathname`, kein `search`), liest nur `response.clone().text()` (Antwort, nicht Anfrage); Test 2 pinnt „enthaelt NICHT geheim/password" | OK |
|
|
| SSR-sicher, idempotent | `if (typeof window === 'undefined') return;`, Guard `__tesseraErrorBufferInstalled` | OK |
|
|
| `computeCaptureSize` exportiert und rein | `apps/web/src/lib/bug-report-api.ts`, reine Funktion, Test 11 (eigener describe-Block) prueft 5 Faelle direkt | OK |
|
|
| `toPng` mit `pixelRatio: 1, skipFonts: true, cacheBust: true, canvasWidth/canvasHeight` | `captureScreenshot()` genau so implementiert | OK |
|
|
| Bild VOR Dialog | `bug-report-button.tsx`: `const shot = await captureScreenshot(); setScreenshot(shot); setOpen(true);` — Reihenfolge im Code UND in Test 1 (`toPng`-Mock prueft `queryByRole('dialog')` ist `null` waehrend seines eigenen Aufrufs) gepinnt | OK |
|
|
| Knopf vor ThemeToggle in `header.tsx` | Zeile 113/114: `<BugReportButton />` unmittelbar vor `<ThemeToggle />` | OK |
|
|
| Dialog: Escape, Fokus, Zustaende, 409/413/429/502/allgemein | `bug-report-dialog.tsx`: `role="dialog" aria-modal`, Escape-Handler (nicht waehrend `sending`), Fokus auf Textarea beim Oeffnen, `errorKey`-Zuordnung 409/429/413/502/sonst; 11 Tests in `bug-report-button.test.tsx` (echte `it(...)`-Zeilen gezaehlt: 11, eine weitere Fundstelle war ein Kommentar/Helper, kein Test) decken Tests 7-10 fuer 413/429/502/allgemein mit Texten aus `de.json` | OK |
|
|
| `installErrorBuffer()` in `app-shell.tsx` | `useEffect(() => { installErrorBuffer(); }, [])` vorhanden | OK |
|
|
| `html-to-image` exakt `1.11.13` | `apps/web/package.json` Zeile 15 | OK |
|
|
|
|
## 6. i18n
|
|
|
|
| Pruefung | Ergebnis | Status |
|
|
|---|---|---|
|
|
| `bugReport`-Namensraum in de.json/en.json identisch strukturiert | 20 Schluessel in beiden Dateien, gleiche Schluesselmenge (Python-Vergleich) | OK |
|
|
| `settings.smtp.bugReportRecipient`/`bugReportRecipientHelp` in beiden Sprachen | vorhanden, Sie-Form in de | OK |
|
|
| `umlaut-dictionary.ts` um `passiert` erweitert | Zeile 178, Kommentar `// 260914-m97` | OK |
|
|
| `umlaut-guard.spec.ts` / `tenderRadar-parity.spec.ts` gruen | siehe Abschnitt 2 | OK |
|
|
|
|
## 7. Einstellungen (SMTP-Formular)
|
|
|
|
| Pruefung | Ergebnis | Status |
|
|
|---|---|---|
|
|
| `SMTP_SAFE_SELECT` enthaelt `bugReportRecipient` | `settings.service.ts` Zeile 22 | OK |
|
|
| DTO `@IsOptional() @IsEmail()` | `smtp-config.dto.ts` Zeile 59-61 | OK |
|
|
| `PUT /settings/smtp` weiterhin nur ADMIN/SUPER_ADMIN | `@Put('smtp') @Roles(Role.ADMIN, Role.SUPER_ADMIN)` | OK |
|
|
| Web-Formular: Feld „Fehlermeldungen an" mit Hinweistext | `smtp-settings-form.tsx` Zeile 308-325, `id="smtp-bug-report-recipient"`, `type="email"` | OK |
|
|
|
|
## 8. `docker-compose.prod.yml`
|
|
|
|
`grep TESSERA_BUGREPORT_TO docker-compose.prod.yml` -> `TESSERA_BUGREPORT_TO: ${TESSERA_BUGREPORT_TO:-}` (Zeile 52, im `api.environment`-Block). `.env*` unveraendert (siehe Abschnitt 1).
|
|
|
|
## 9. Handbuecher
|
|
|
|
| Datei | Pruefung | Ergebnis |
|
|
|---|---|---|
|
|
| `docs/anleitung-anwender.md` | Abschnitt „Einen Fehler melden", Datenschutz-Satz, „drei Bedienelemente" | vorhanden (Zeilen 43, 160, 166) |
|
|
| `docs/anleitung-administration.md` | Feld „Fehlermeldungen an" unter Administrator -> SMTP, Fehlersuche-Zeile | vorhanden (Zeilen 204, 208, 241) |
|
|
| `docs/anleitung-betrieb.md` | `TESSERA_BUGREPORT_TO` in Konfigurationstabelle | vorhanden (Zeile 161, 340) |
|
|
| `docs/mandantentrennung-zugriffsklassifikation.md` | neue Zeile fuer den gebundenen `user`-Lesezugriff in `bug-reports.service.ts` | vorhanden (Zeile 664) plus Bereichs-Tabellen-Zeile (Zeile 176) |
|
|
|
|
## 10. CI und Container-Abbilder
|
|
|
|
| Pruefung | Befehl/Quelle | Ergebnis | Status |
|
|
|---|---|---|---|
|
|
| CI-Lauf fuer `77117de` | Gitea-API `.../actions/tasks?limit=5` (Token nur aus `git remote get-url --push origin` gelesen, nie ausgegeben) | Run 299/215: `Lint & Type Check`, `Tests`, `Build & Publish Images` je `status: success` fuer `head_sha 77117de3d0f1bb82df7b659f46fe26244ca6f164` | OK |
|
|
| `api:beta`-Abbild traegt den richtigen Commit | `docker run --rm --entrypoint node localhost:3002/schalli/tessera-ctl/api:beta -e "console.log(process.env.APP_VERSION, process.env.APP_CHANNEL)"` | `77117de beta` | OK |
|
|
| `web:beta`-Abbild enthaelt html-to-image im Client-Bundle | `docker run --rm --entrypoint sh localhost:3002/.../web:beta -c 'grep -rl "toPng\|html-to-image" apps/web/.next/static'` | Treffer in `chunks/3717.5ecd3f65b9b8111a.js` und `chunks/app/(portal)/layout-....js` | OK |
|
|
|
|
## 11. Push-Status
|
|
|
|
`git fetch -q && git status -sb | head -1` -> `## main...origin/main` (kein `[ahead`). OK.
|
|
|
|
## 12. Ledger `.planning/WINDOWS.md` #38
|
|
|
|
Eintrag #38 (`status: open`) beschreibt die Rule-1-Selbstkorrektur des Executors: `@Expose()` wurde auf `errors` im DTO ergaenzt, weil `class-transformer` `@Transform` sonst nur fuer im Rumpf VORHANDENE Schluessel aufruft (ohne die Korrektur haette ein Anwender ohne Browserfehler 400 statt 200 erhalten). Diese Korrektur ist bereits im selben Commit (`54121c1`) enthalten, verifiziert (`bug-reports.controller.spec.ts` Test 1 gruen, siehe Abschnitt 2) und im Code vorhanden (`bug-report.dto.ts` Zeile 71 `@Expose()`).
|
|
|
|
**Meine Einschaetzung:** Dies ist eine reine Protokollzeile eines bereits erledigten, verifizierten In-Scope-Fixes — kein offener technischer Mangel im Code. Der `status: open` bedeutet hier lediglich, dass niemand `gsd-tools windows fixed 38` ausgefuehrt hat, nicht dass am Code noch etwas fehlt. Zum Vergleich: Eintrag #37 (Single-Flight-Riegel prozessweit statt je Mandant) beschreibt eine tatsaechlich noch bestehende Einschraenkung im laufenden Code — #38 ist damit nicht vergleichbar und sollte administrativ geschlossen werden, ohne dass ein Folgeauftrag noetig ist.
|
|
|
|
## Vom Orchestrator im Browser zu pruefen
|
|
|
|
Dieser Verifizierer hat KEINE Container gestartet/gestoppt (Vorgabe). Folgende Schritte aus dem SUMMARY-Abschnitt „Fuer den Verifizierer" bleiben fuer den Orchestrator:
|
|
|
|
1. `docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d mailhog`, danach `docker compose up -d --build api web`.
|
|
2. Anmelden, Administrator -> SMTP: Host `mailhog`, Port `1025`, Verschluesselung „Keine", Absender `tessera@tessera.local`, „Fehlermeldungen an" `fehler@example.invalid`, speichern.
|
|
3. Auf einer Seite mit Inhalt (dunkles Erscheinungsbild) Kaefer-Knopf klicken: Vorschau darf den Dialog NICHT zeigen, Farben muessen stimmen; Beschreibung eintragen, „Senden" -> „Vielen Dank, die Meldung wurde gesendet."
|
|
4. `http://localhost:8025`: E-Mail mit Betreff `[Tessera Fehlermeldung] dev dev - /admin/users`, Anhang `fehlermeldung-<yyyymmdd-hhmm>.png` unter 2 MB, alle Kontextzeilen im Text.
|
|
5. Feld leeren, speichern, senden -> 409-Text mit Admin-Link.
|
|
6. Sechs Meldungen hintereinander -> sechste zeigt 429-Text.
|
|
7. `docker compose logs api | grep "Bug report"` -> je Meldung eine Zeile ohne Bild/Beschreibung.
|
|
|
|
## Angenommene Risiken
|
|
|
|
- **Drossel im Prozessspeicher:** gilt je API-Instanz, geht bei Neustart verloren. Fuer den Livestart mit einer Instanz korrekt; bei mehreren Instanzen waere die effektive Grenze `n x 5`. So im Plan akzeptiert, nicht Teil dieses Auftrags zu loesen.
|
|
- **`TESSERA_BUGREPORT_TO` auf dem Produktivserver:** die Zeile in `/opt/tessera/docker-compose.prod.yml` muss von Hand ergaenzt werden (wie `IMAGE_TAG`) — ohne diesen manuellen Schritt gilt dort ausschliesslich das UI-Feld, was fuer den Livebetrieb morgen ausreicht.
|
|
- **Ledger #38:** administrative Restarbeit (Eintrag als `fixed` markieren), kein Code-Risiko — siehe Abschnitt 12.
|
|
- **Commit-`b41be21`-Messmuster:** das im Auftrag vorgegebene `grep -c '|'` liefert wegen Pipe-Zeichen in der Commit-Botschaft selbst `7` statt `5`; die tatsaechliche Datei-Zaehlung (5) stimmt nachweislich mit den erwarteten Dateien ueberein — kein Befund, nur eine Schwaeche des Messinstruments (bereits im Plan als "Messinstrument, nicht Datei"-Muster fuer den Compose-Grep dokumentiert, hier dasselbe Phaenomen bei einem anderen Grep).
|
|
- **Browser-Beweis steht aus** (siehe `human_verification` oben) — laut Auftragstext ausdruecklich Aufgabe des Orchestrators nach dieser Verifikation, nicht dieses Verifizierers.
|
|
|
|
---
|
|
|
|
_Verifiziert: 2026-09-14T15:15Z_
|
|
_Verifizierer: Claude (gsd-verifier)_
|
|
|
|
## Nachtrag des Orchestrators — Browser-Check durchgefuehrt (2026-09-14, 15:15-15:17Z)
|
|
|
|
Umgebung: lokale Container aus `main` (`77117de`) frisch gebaut (`docker compose up -d --build api web`), `mailhog` aus `docker-compose.dev.yml`, Playwright MCP (Chromium 153). Vorher musste die Platte freigeraeumt werden (`docker builder prune` 9,25 GB, `docker image prune` 4,85 GB; der erste Bau scheiterte mit `no space left on device`).
|
|
|
|
| Schritt | Beobachtung |
|
|
|---|---|
|
|
| Administrator -> SMTP | Feld "Fehlermeldungen an" mit Hinweistext vorhanden; Host `mailhog`, Port 1025, Keine Verschluesselung, Absender `tessera@tessera.local`, Empfaenger `fehler@example.invalid` gespeichert (DB: `bugReportRecipient = fehler@example.invalid`) |
|
|
| Kopfzeile | Knopf "Fehler melden" (aria-label) links neben dem Erscheinungsbild-Knopf; Seitenleiste unten: Abzeichen `dev · Entwicklung`, Tooltip `API dev (dev)` |
|
|
| Klick auf /admin/users | Dialog mit Hinweistext, Vorschau, Haekchen (an), Feld "Was ist passiert?", Abbrechen/Senden — die Vorschau zeigt die Seite OHNE den Dialog |
|
|
| Senden mit Beschreibung | "Vielen Dank, die Meldung wurde gesendet." |
|
|
| mailhog `GET /api/v2/messages` | 1 Nachricht, Von `tessera@tessera.local`, An `fehler@example.invalid`, Betreff `[Tessera Fehlermeldung] dev dev - /admin/users`; Text mit Beschreibung, Seite, Zeitpunkt (Server/Browser), Benutzer `admin`, Rolle, Mandant, Web `dev (dev)`, API `Tessera API dev (dev)`, Browser, Fenster `1920x949`, "Letzte Fehlermeldungen im Browser (0)" |
|
|
| Anhang | `fehlermeldung-20260914-1516.png`, 85619 Bytes, PNG-Signatur korrekt; Bild 1600 px breit, Farben der Seite korrekt (OKLCH), ohne Dialog |
|
|
| API-Protokoll | `Bug report email sent to fehler@example.invalid (transport: tenant)` und `Bug report from admin (tenant ...) sent to ... — page /admin/users, screenshot 85619 bytes` — ohne Beschreibung, ohne Bild |
|
|
| Gegenprobe ohne Empfaenger | `bugReportRecipient` auf NULL gesetzt, Senden -> "Fuer Fehlermeldungen ist noch kein Postfach eingerichtet." + Admin-Hinweis mit Link "Zu den SMTP-Einstellungen" (409-Pfad) |
|
|
| Drossel (429) | im Browser NICHT wiederholt (sechs Mails); durch die Spec gedeckt, die der Verifizierer unabhaengig falsifiziert hat (MAX_PER_WINDOW 5 -> 6 macht Test 3 rot) |
|
|
|
|
Nach der Probe: Empfaenger in der lokalen DB wieder gesetzt, Playwright-Artefakte entfernt, Arbeitsbaum unveraendert bis auf die Akten dieses Quick-Tasks.
|