docs(quick-260921-iwr): 30 Stellen geprueft, kein echter Fehler darunter
Tessera CI/CD / Lint & Type Check (push) Successful in 53s
Tessera CI/CD / Tests (push) Failing after 11m29s
Tessera CI/CD / Desktop-Pakete bauen (push) Has been skipped
Tessera CI/CD / Build & Publish Images (push) Has been skipped

Zusammenfassung und STATE.md zum Quick-Vorgang 260921-iwr. Damit ist
der fehlerverdaechtige Lint-Rueckstand vollstaendig geprueft.

Beide Verdachtsmomente widerlegt, durch Messung statt Argument: die
LDAP-Seite nutzt fuer die Liste, die wirklich waechst und schrumpft,
laengst eine stabile Kennung; und im Cert-Manager enden alle vier Pfade
mit einer 83-Byte-Schrottdatei bei 400, nie 500 - node-forge wirft,
statt still ein falsches Zertifikat zu bauen.

Ein Fund dreht die Richtung um: bei grants/page.tsx waere die
Korrektur schaedlich, dort ist die Positionsnummer fuer die
Eindeutigkeit noetig.

Geaendert: 5 Stellen. 25 bleiben bewusst stehen und bleiben in der
Zaehlung sichtbar, mit Begruendung je Stelle in der Akte.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
This commit is contained in:
2026-09-21 14:10:51 +02:00
parent de6986340d
commit 6cc0d02e97
2 changed files with 270 additions and 4 deletions
@@ -0,0 +1,265 @@
---
phase: quick-260921-iwr
plan: 01
subsystem: testing
tags: [biome, lint, react, node-forge, pkcs12, imapflow, cert-manager]
# Dependency graph
requires: []
provides:
- 30 einzeln beurteilte Fundstellen (19 noArrayIndexKey, 11 noNonNullAssertion) mit Urteil und Folgenbegruendung
- Drei echte Waechter in cert-manager.service.ts statt sachlich falscher Zusicherungen (bag.cert = null)
- Zwei entfernte ueberfluessige Zusicherungen in imap.provider.ts (uid ist Pflichtfeld)
- Neue Tests: MergeTab.test.tsx (Entfernen mittig/vorne), Rundenliste in stopwatch-widget.test.tsx, sechs neue Faelle fuer die malformte PFX in cert-manager.service.spec.ts
affects: []
# Actuals (#2632)
actuals:
tokens: 5300
tasks: 3
commits: 4
plan_head_before: cfba3c953202057064adbe691b735977654f127c
# Tech tracking
tech-stack:
added: []
patterns:
- "Malformte PFX-Testdatei per forge.asn1 handgebaut statt Fixture-Datei — exakte Kontrolle ueber den Fehlerfall (bag.cert = null)"
- "Zusicherung durch Typpraedikat-Filter ersetzt statt Filtern/Ueberspringen bei fehlendem Zertifikat (D-04)"
key-files:
created:
- apps/web/src/app/(portal)/modules/cert-manager/components/MergeTab.test.tsx
modified:
- apps/api/src/cert-manager/cert-manager.service.ts
- apps/api/src/cert-manager/cert-manager.service.spec.ts
- apps/api/src/inbox/imap.provider.ts
- apps/web/src/components/dashboard/widgets/stopwatch-widget.tsx
- apps/web/src/components/dashboard/widgets/stopwatch-widget.test.tsx
key-decisions:
- "Alle 19 noArrayIndexKey-Fundstellen bleiben im Produktivcode unveraendert — zwoelf sind Ladeplatzhalter/Meldungslisten ohne Identitaet (tatsaechlich zehn, siehe Korrektur unten), eine (grants) braucht den Index sachlich, zwei (MergeTab, Stoppuhr) sind jetzt per Test belegt statt nur behauptet."
- "Drei bag.cert-Zusicherungen in cert-manager.service.ts sind sachlich falsch (node-forge kann bag.cert auf null setzen), aber keine Verfuegbarkeits- oder Integritaetsluecke: alle vier betroffenen Pfade antworteten schon vorher mit 400, nie 500. Der Eingriff ist Diagnose-/Lesbarkeitsarbeit — die Meldung wird praezise, der Statuscode bleibt 400 (D-02)."
- "Zwei uid!-Zusicherungen in imap.provider.ts entfernt: imapflow typisiert uid als Pflichtfeld, das Ausrufezeichen war wirkungslos."
- "Drei weitere Zusicherungen (cert-manager 133/516/661, tenders.controller.ts:244, favorites-widget.tsx:149, sidebar.tsx:80) bleiben unveraendert — jede durch eine konkrete vorgelagerte Zeile oder einen strukturellen Fakt garantiert."
patterns-established:
- "Handgebaute ASN.1-Testfixtures fuer node-forge-Grenzfaelle (forge.asn1.create) statt auf Zufallsdaten oder Mocks zu setzen"
requirements-completed: [D-01, D-02, D-03, D-04, D-05, D-06]
coverage:
- id: D1
description: "Alle 19 noArrayIndexKey-Fundstellen einzeln beurteilt, MergeTab-Entfernen (mittig + vorne) und Stoppuhr-Rundenreihenfolge per Test belegt"
requirement: "D-01"
verification:
- kind: unit
ref: "apps/web/src/app/(portal)/modules/cert-manager/components/MergeTab.test.tsx#quick-260921-iwr: Entfernen der mittleren Datei laesst genau die erste und dritte Datei uebrig..."
status: pass
- kind: unit
ref: "apps/web/src/components/dashboard/widgets/stopwatch-widget.test.tsx#quick-260921-iwr: zwei Runden nacheinander..."
status: pass
human_judgment: false
- id: D2
description: "Drei bag.cert-Zusicherungen in cert-manager.service.ts zu echten Waechtern mit praeziser 400-Meldung gemacht (parseCert, mergeCerts, convertCert)"
requirement: "D-04"
verification:
- kind: unit
ref: "apps/api/src/cert-manager/cert-manager.service.spec.ts#PFX mit unlesbarem Zertifikats-Bag (bag.cert = null)"
status: pass
human_judgment: false
- id: D3
description: "Zwei ueberfluessige Ausrufezeichen in imap.provider.ts entfernt (uid ist imapflow-Pflichtfeld)"
requirement: "D-05"
verification:
- kind: other
ref: "cd apps/api && npx tsc --noEmit (exit 0)"
status: pass
human_judgment: false
- id: D4
description: "Biome-Zaehlbefehl bestaetigt TOTAL 434 -> 429, ARRAYKEY unveraendert 19, NONNULL 11 -> 6, ERRORS 0"
requirement: "D-06"
verification:
- kind: other
ref: "npx biome lint --reporter=json . | Zaehlbefehl aus planning_observations"
status: pass
human_judgment: false
duration: 20min
completed: 2026-09-21
status: complete
---
# Quick 260921-iwr: Listenschluessel per Positionsnummer und Ausrufezeichen-Zusicherungen Summary
**30 Fundstellen zweier Lint-Klassen einzeln beurteilt — 5 geaenderte Stellen (3 echte Waechter in cert-manager.service.ts, 2 ueberfluessige Zusicherungen in imap.provider.ts entfernt), 25 bewusst stehengelassen, zwei bislang nur behauptete Faelle jetzt per Test belegt.**
## Performance
- **Duration:** ~20 min
- **Tasks:** 3
- **Files modified:** 6 (5 geaendert, 1 neu angelegt)
- **Commits:** 4 (3 Task-Commits + 1 Nachbesserung, siehe Deviations)
## Accomplishments
- Alle 19 `noArrayIndexKey`-Fundstellen beurteilt: zehn Ladeplatzhalter mit fester Laenge, sechs zustandslose Meldungslisten in admin/ldap, eine Fundstelle (grants), bei der der Index sachlich notwendig ist, und zwei echte Faelle (MergeTab, Stoppuhr-Runden), die jetzt per Test statt nur per Behauptung abgesichert sind.
- Alle 11 `noNonNullAssertion`-Fundstellen beurteilt: drei sind durch node-forges eigenes Verhalten sachlich falsch (bag.cert kann null sein) und wurden zu echten Waechtern mit praeziser 400-Meldung; zwei sind schlicht ueberfluessig (imapflow typisiert `uid` als Pflichtfeld) und wurden entfernt; sechs sind durch eine konkrete Zeile oder einen strukturellen Fakt garantiert und bleiben unveraendert.
- Neue `MergeTab.test.tsx` belegt: Entfernen der mittleren sowie der ersten Datei aus einer Dreier- bzw. Zweierliste laesst die verbleibenden Dateien mit ihrem eigenen Namen und eigenen Entfernen-Knopf zurueck — kein Verwechseln durch den Positionsschluessel.
- Neuer Testfall in `stopwatch-widget.test.tsx` belegt: zwei Runden nacheinander zeigen die neuere Runde oben mit Rundennummer 2, die aeltere unten mit Rundennummer 1, jede Zeit bei ihrer eigenen Nummer.
- Sechs neue Testfaelle in `cert-manager.service.spec.ts`, RED zuerst geschrieben (heutige Meldungen bestaetigt), dann GREEN nach Einbau der drei Waechter — inklusive einer selbst mit `forge.asn1` gebauten 83-Byte-PFX-Datei mit unlesbarem Zertifikats-Bag und einer Vorbedingungspruefung, dass diese Datei tatsaechlich `bag.cert = null` erzeugt.
## Task Commits
Jede Aufgabe wurde atomar committet:
1. **Task 1: Die 19 Positionsschluessel beurteilen und die zwei nicht offensichtlichen Faelle nachmessen** - `8716fa5` (feat)
2. **Task 2: Die sechs Zusicherungen in cert-manager.service.ts beurteilen, drei davon zu echten Waechtern machen** - `b4aaed4` (fix, TDD: RED zuerst, dann GREEN)
3. **Task 3: Die restlichen fuenf Zusicherungen beurteilen, zwei ueberfluessige entfernen** - `27909e4` (refactor)
**Nachbesserung (Deviation, siehe unten):** `de69863` (fix) — behebt einen durch Task 2 selbst eingefuehrten neuen Lint-Fund, der die Endverifikation (TOTAL=429) verfehlt haette.
**Plan-Metadaten:** wird vom Orchestrator committet (SUMMARY.md, STATE.md, ROADMAP.md).
## Files Created/Modified
- `apps/api/src/cert-manager/cert-manager.service.ts` - drei bag.cert-Zusicherungen durch echte Waechter mit praeziser 400-Meldung ersetzt
- `apps/api/src/cert-manager/cert-manager.service.spec.ts` - neue Testgruppe fuer die malformte PFX (Vorbedingung + vier Pfade + Regressionstest fuer eine gueltige PFX)
- `apps/api/src/inbox/imap.provider.ts` - zwei ueberfluessige `msg.uid!` zu `msg.uid` vereinfacht
- `apps/web/src/components/dashboard/widgets/stopwatch-widget.tsx` - wirkungslose eslint-disable-Zeile durch Sachhinweis ersetzt, kein Schluessel geaendert
- `apps/web/src/components/dashboard/widgets/stopwatch-widget.test.tsx` - neuer Testfall fuer zwei aufeinanderfolgende Runden
- `apps/web/src/app/(portal)/modules/cert-manager/components/MergeTab.test.tsx` - neu angelegt, zwei Testfaelle fuer das Entfernen aus der Dateiliste
## Die 30 Fundstellen — vollstaendiges Urteil
Urteil-Spalte: **Lesbarkeit** = geaendert (Meldung praezisiert bzw. ueberfluessige Zusicherung entfernt, Verhalten sonst unveraendert). **harmlos** = bewusst unveraendert stehengelassen, mit Begruendung.
### noArrayIndexKey (19 — alle unveraendert im Produktivcode, ARRAYKEY bleibt 19)
| # | Datei | Zeile | Regel | Urteil | Begruendung |
|---|-------|-------|-------|--------|--------------|
| 1 | InvoiceHistoryTable.tsx | 129 | noArrayIndexKey | harmlos | Ladeplatzhalter, `Array.from({length:5})`, feste Laenge, keine Identitaet, Reihenfolge aendert sich nie |
| 2 | InvoiceHistoryTable.tsx | 131 | noArrayIndexKey | harmlos | Verschachtelter Platzhalter, `Array.from({length:6})`, gleiche Begruendung |
| 3 | VehicleTable.tsx | 342 | noArrayIndexKey | harmlos | Ladeplatzhalter, `Array.from({length:3})`, feste Laenge |
| 4 | VehicleTable.tsx | 344 | noArrayIndexKey | harmlos | Verschachtelter Platzhalter, `Array.from({length:5})` |
| 5 | ResultsList.tsx | 240 | noArrayIndexKey | harmlos | Ladeplatzhalter, `Array.from({length:5})` |
| 6 | ResultsList.tsx | 242 | noArrayIndexKey | harmlos | Verschachtelter Platzhalter, `Array.from({length:6})` |
| 7 | InboxConfigForm.tsx | 251 | noArrayIndexKey | harmlos | Ladeplatzhalter, `Array.from({length:6})`, Formularfelder ohne Inhalt |
| 8 | EmailAlertConfigForm.tsx | 225 | noArrayIndexKey | harmlos | Ladeplatzhalter, `Array.from({length:5})` |
| 9 | RssFeedListForm.tsx | 151 | noArrayIndexKey | harmlos | Ladeplatzhalter, `Array.from({length:2})` |
| 10 | SourceConfigForm.tsx | 129 | noArrayIndexKey | harmlos | Ladeplatzhalter, `Array.from({length:2})` |
| 11 | admin/ldap/page.tsx | 1040 | noArrayIndexKey | harmlos | `nameCollisions`-Meldungsliste; `setGroupImportResult(data)` ersetzt das ganze Ergebnis auf einen Schlag, keine Zeile bleibt haengen |
| 12 | admin/ldap/page.tsx | 1049 | noArrayIndexKey | harmlos | `errors`-Meldungsliste im Gruppenimport, gleicher Ersetz-Mechanismus |
| 13 | admin/ldap/page.tsx | 1369 | noArrayIndexKey | harmlos | `emailConflicts`-Meldungsliste; `setSyncResult(null)` dann `setSyncResult(data)` — komplettes Ersetzen |
| 14 | admin/ldap/page.tsx | 1384 | noArrayIndexKey | harmlos | `skippedNoLogin`-Meldungsliste, gleicher Mechanismus |
| 15 | admin/ldap/page.tsx | 1396 | noArrayIndexKey | harmlos | `entryFailures`-Meldungsliste, gleicher Mechanismus |
| 16 | admin/ldap/page.tsx | 1405 | noArrayIndexKey | harmlos | `errors`-Meldungsliste im Sync-Ergebnis, gleicher Mechanismus |
| 17 | admin/modules/grants/page.tsx | 246 | noArrayIndexKey | harmlos | `Fragment key={cat-${category}-${groupIndex}}` — die Gruppierung fasst nur unmittelbar aufeinanderfolgende Module gleicher Kategorie zusammen, dieselbe Kategorie kann also mehrfach vorkommen; der Positionsanteil ist notwendig, Entfernen wuerde doppelte Schluessel erzeugen. Die Haken selbst haengen an `cellKey(mod.id, g.id)`, nicht an diesem Schluessel |
| 18 | MergeTab.tsx | 87 | noArrayIndexKey | harmlos | `key={\`${f.name}-${i}\`}` — jetzt per Test belegt statt nur behauptet: Entfernen der mittleren/ersten Datei verwechselt keine Zeile (neue MergeTab.test.tsx) |
| 19 | stopwatch-widget.tsx | 276 | noArrayIndexKey | harmlos | `key={idx}` — Zeilen halten keinen eigenen Zustand, Rundennummer wird aus Laenge und Position berechnet; jetzt per neuem Testfall belegt (zwei Runden, richtige Zeit bei richtiger Nummer). Wirkungslose eslint-disable-Zeile ersetzt durch Sachhinweis, keine neue Unterdrueckung |
**Korrektur zur Planvorgabe:** Die planning_observations sprechen von "zwoelf" Ladeplatzhaltern in Gruppe (a); die dort selbst aufgelistete Datei/Zeile-Aufstellung nennt aber nur zehn Stellen (InvoiceHistoryTable x2, VehicleTable x2, ResultsList x2, InboxConfigForm x1, EmailAlertConfigForm x1, RssFeedListForm x1, SourceConfigForm x1 = 10). Zusammen mit den sechs admin/ldap-Meldungslisten, der einen grants-Stelle und den zwei echten Faellen (MergeTab, Stoppuhr) ergeben sich 10+6+1+2 = 19 — die Gesamtzahl stimmt, nur die Zwischensumme der Gruppe (a) war im Plantext falsch benannt. Kein Code-Fund betroffen, reine Dokumentationskorrektur.
### noNonNullAssertion (11 — 5 geaendert, 6 unveraendert, NONNULL 11 -> 6)
| # | Datei | Zeile | Regel | Urteil | Begruendung |
|---|-------|-------|-------|--------|--------------|
| 20 | cert-manager.service.ts | 133 | noNonNullAssertion | harmlos | Zerlegung des Fingerabdruck-Hex in Zweiergruppen — SHA-1/SHA-256-Hex ist immer 40/64 Zeichen lang, `match(/.{2}/g)` findet garantiert etwas. Durch die Hash-Laenge garantiert, nicht durch Eingabedaten |
| 21 | cert-manager.service.ts | 207 | noNonNullAssertion | **Lesbarkeit** | `bags[0].cert!` in parseCert — node-forge kann bag.cert auf null setzen (lib/pkcs12.js Zeile 703-709), die Zusicherung war sachlich falsch. Folge war aber bereits behandelt (400, nie 500). Jetzt echter Waechter: praezise BadRequestException statt der irrefuehrenden "Failed to extract certificate details" |
| 22 | cert-manager.service.ts | 469 | noNonNullAssertion | **Lesbarkeit** | `bags.map((bag) => bag.cert!)` in mergeCerts — gleicher Grund. Jetzt: alle Bag-Zertifikate werden auf Vollstaendigkeit geprueft (kein Filtern, kein Ueberspringen, D-04), bei fehlendem Zertifikat wirft der Guard unter Nennung des Dateinamens, sonst wird die Liste ueber ein Typpraedikat zurueckgegeben |
| 23 | cert-manager.service.ts | 516 | noNonNullAssertion | harmlos | Kennwort beim PFX-Erzeugen in mergeCerts — durch die vorgelagerte Pruefung in Zeile 438-440 garantiert (bricht bei fehlendem/leerem Kennwort vorher mit 400 ab) |
| 24 | cert-manager.service.ts | 602 | noNonNullAssertion | **Lesbarkeit** | `bags[0].cert!` in convertCert — gleicher Grund wie #21. Jetzt echter Waechter mit praeziser Meldung statt der irrefuehrenden "Failed to convert certificate to pem: serialization error" |
| 25 | cert-manager.service.ts | 661 | noNonNullAssertion | harmlos | Kennwort beim PFX-Erzeugen in convertCert — durch die vorgelagerte Pruefung in Zeile 564-566 garantiert |
| 26 | imap.provider.ts | 214 | noNonNullAssertion | **Lesbarkeit** | `msg.uid!` — imapflow typisiert `uid` in `FetchMessageObject` als Pflichtfeld (lib/imap-flow.d.ts Zeile 469, "Always included in the response"). Zusicherung war ueberfluessig, entfernt (nur `!` weg, sonst nichts). tsc bleibt gruen |
| 27 | imap.provider.ts | 310 | noNonNullAssertion | **Lesbarkeit** | `msg.uid!` — gleicher Grund wie #26 |
| 28 | tenders.controller.ts | 244 | noNonNullAssertion | harmlos | `this.tenderIngestionService!.pollDueSources()` — das Fragezeichen im Konstruktor dient nur der Argumentreihenfolge; `TenderIngestionService` steht in tenders.module.ts unter providers, wird von Nest also immer eingesetzt. Durch den Provider-Eintrag garantiert |
| 29 | favorites-widget.tsx | 149 | noNonNullAssertion | harmlos | `byId.get(fid)!` — die Reihenfolge stammt aus `sortedFavorites` (Zeile 90-97, sortierte Kopie von `favorites`), die Zuordnungstabelle wird aus denselben Eintraegen gebaut. Durch die gemeinsame Herleitung garantiert |
| 30 | sidebar.tsx | 80 | noNonNullAssertion | harmlos | `categories.get(cat)!.push(mod)` — Zeile 79 legt den Eintrag an, falls er fehlt. Durch die Zeile unmittelbar davor garantiert |
## Vorher-Nachher-Zahlen (Zaehlbefehl aus den planning_observations, Feld `category`, nie Quelltext)
| Zaehlwert | Vorher | Nachher | Erwartung laut Plan | Ergebnis |
|-----------|--------|---------|----------------------|----------|
| TOTAL | 434 | 429 | 429 | ✓ (faellt um genau 5, die Zahl der geaenderten Stellen) |
| ERRORS | 0 | 0 | 0 | ✓ |
| ARRAYKEY | 19 | 19 | 19 | ✓ unveraendert |
| NONNULL | 11 | 6 | 6 | ✓ (5 geaendert: 3 cert-manager + 2 imap) |
## Verhaltensaenderung (vorab benannt, D-02)
Die einzige Verhaltensaenderung betrifft eine Eingabeklasse: eine PFX-Datei, deren Zertifikats-Bag wohlgeformt, aber nicht als X.509 lesbar ist. Vorher und nachher antworten alle vier betroffenen Pfade mit **Status 400** — das aendert sich nicht. Was sich aendert, ist ausschliesslich der **Text** der Fehlermeldung:
| Pfad | Meldung vorher | Meldung nachher |
|------|-----------------|-------------------|
| parseCert | "Failed to extract certificate details" | "Certificate bag in PFX/PKCS12 does not contain a readable X.509 certificate" |
| mergeCerts (pem) | "Failed to create merged certificate output" | "Certificate bag in \"<Dateiname>\" does not contain a readable X.509 certificate" |
| mergeCerts (pfx) | "Failed to create merged certificate output" | dieselbe praezise Meldung wie oben |
| convertCert | "Failed to convert certificate to pem: serialization error" | "Certificate bag in PFX/PKCS12 does not contain a readable X.509 certificate" |
Das ist **Diagnose-/Lesbarkeitsarbeit, keine Fehlerbehebung**: node-forge wirft bei einem fehlenden Zertifikat in `certificateToPem` und `toPkcs12Asn1` ausnahmslos, ein still falsches Zertifikat war schon vorher ausgeschlossen. Die drei Zusicherungen waren sachlich falsch, aber ihre Folge war bereits sicher behandelt.
## Decisions Made
- Die drei bag.cert-Zusicherungen wurden als "keine Verfuegbarkeits- und keine Integritaetsluecke" eingestuft, nachdem die gemessene Tabelle (400 statt 500 in allen vier Pfaden) und die node-forge-Pruefung (certificateToPem(null)/toPkcs12Asn1(null,...) werfen beide) das belegten. Der Eingriff blieb deshalb auf die Meldung beschraenkt, keine Statuscode- oder Fehlerbehandlungs-Aenderung.
- In mergeCerts wurde bewusst kein Filtern/Ueberspringen fehlender Zertifikate implementiert (D-04): stattdessen prueft der Guard alle Bags auf Vollstaendigkeit und wirft bei der ersten Luecke unter Nennung des Dateinamens, erst danach wird die Liste ueber ein Typpraedikat auf den non-null-Typ eingeengt.
- Fuer Task 1 wurde keine der 19 Fundstellen im Produktivcode geaendert — stattdessen wurden die zwei einzigen tatsaechlich dynamischen Listen (MergeTab, Stoppuhr) per neuem Test statt nur per Behauptung abgesichert, wie von der Aufgabe gefordert.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 1 - Bug] mergeCerts-Waechter loeste einen zusaetzlichen Biome-Lint-Fund aus**
- **Found during:** Endverifikation nach Task 3 (Schritt "1. `npx biome lint`" aus dem `<verification>`-Block des Plans)
- **Issue:** Die erste Fassung des Guards in mergeCerts (`bagCerts.findIndex((c) => c === null)`) erzeugte einen neuen `lint/complexity/useIndexOf`-Fund (info-Severity), der TOTAL auf 430 statt der erwarteten 429 anhob — ein Verstoss gegen D-06 ("TOTAL steigt nirgends anders an"). Ein direkter Fix mit `indexOf(null)` scheiterte an `tsc`, weil `@types/node-forge` `Bag.cert` als `Certificate | undefined` deklariert (nicht `| null`), obwohl die node-forge-Laufzeit selbst `null` setzt.
- **Fix:** Die Pruefung testet jetzt ausdruecklich auf `undefined` UND `null` (`c === undefined || c === null`) — das ist fuer Biome kein Single-Value-Vergleich mehr (kein useIndexOf-Vorschlag) und fuer TypeScript typsicher.
- **Files modified:** apps/api/src/cert-manager/cert-manager.service.ts
- **Verification:** `npx biome lint --reporter=json .` liefert danach TOTAL=429 ERRORS=0 ARRAYKEY=19 NONNULL=6; `tsc --noEmit` exit 0; volle api-Suite weiterhin 72/1143 gruen.
- **Committed in:** de69863 (eigener Fix-Commit, da Task 2 bereits committet war, als die Endverifikation dies aufdeckte)
---
**Total deviations:** 1 auto-fixed (Rule 1 - Bug, durch die eigene Aenderung eingefuehrt und in der Endverifikation entdeckt)
**Impact on plan:** Kein Scope-Creep; die Korrektur war notwendig, um D-06 exakt zu erfuellen (TOTAL=429, nicht 430).
## Issues Encountered
Keine ungeloesten Probleme. Die einzige Ueberraschung war die oben dokumentierte Deviation — sie wurde durch den letzten Verifikationsschritt selbst gefangen, bevor sie zur Endabgabe kam.
## Auth Gates
Keine — dieser Vorgang hatte keine Authentifizierungs-Interaktion.
## Known Stubs
Keine.
## Threat Flags
Keine neue sicherheitsrelevante Oberflaeche eingefuehrt. Die drei Waechter in cert-manager.service.ts verschaerfen die Fehlerbehandlung an einer bestehenden Vertrauensgrenze (Datei-Upload, T-iwr-02/T-iwr-03 aus dem Plan-Threat-Model), ohne neue Endpunkte, Auth-Pfade oder Schema-Aenderungen einzufuehren.
## User Setup Required
None - keine externe Dienstkonfiguration erforderlich.
## Next Phase Readiness
Die letzte Fehlerklasse vor den beiden neuen Dashboard-Widgets ist abgeschlossen: Lint-Rueckstand bei TOTAL=429, ERRORS=0. Keine Blocker fuer die naechsten geplanten Schritte (zwei neue Widgets, laut STATE.md).
## Self-Check
- FOUND: apps/api/src/cert-manager/cert-manager.service.ts (geaendert)
- FOUND: apps/api/src/cert-manager/cert-manager.service.spec.ts (geaendert)
- FOUND: apps/api/src/inbox/imap.provider.ts (geaendert)
- FOUND: apps/web/src/components/dashboard/widgets/stopwatch-widget.tsx (geaendert)
- FOUND: apps/web/src/components/dashboard/widgets/stopwatch-widget.test.tsx (geaendert)
- FOUND: apps/web/src/app/(portal)/modules/cert-manager/components/MergeTab.test.tsx (neu)
- FOUND: Commit 8716fa5 (git log --oneline --all)
- FOUND: Commit b4aaed4 (git log --oneline --all)
- FOUND: Commit 27909e4 (git log --oneline --all)
- FOUND: Commit de69863 (git log --oneline --all)
## Self-Check: PASSED
---
*Phase: quick-260921-iwr*
*Completed: 2026-09-21*