Files
schalli ad834076c2
Tessera CI/CD / Lint & Type Check (push) Successful in 49s
Tessera CI/CD / Tests (push) Successful in 1m6s
Tessera CI/CD / Desktop-Pakete bauen (push) Successful in 18s
Tessera CI/CD / Build & Publish Images (push) Successful in 3m0s
docs(quick-260921-m34): 288 any auf 15 gesenkt, jede verbliebene mit Urteil
Zusammenfassung mit Urteilsregister und STATE.md zum Quick-Vorgang
260921-m34.

Der groesste Posten war ein einziges Missverstaendnis: 105 Stellen
trugen eine Zusicherung an der Mandantenbindung, die nie noetig war -
prisma.$extends() liefert laengst einen getypten Klienten.

Die tenantId-Frage wurde hergeleitet statt nach Bequemlichkeit
entschieden: string ohne Fragezeichen, belegt aus Pflichtspalte im
Schema, Bestandstyp und dem Super-Admin-Zweig der Mandantenpruefung -
der diesen Typ gar nicht liest und deshalb nicht zu totem Code werden
kann. Der Waechter blieb ueber den ganzen Lauf unveraendert.

Vier Befunde gemeldet statt still repariert. Zwei brauchen eine
Entscheidung, beide wuerden Verhalten aendern - darunter ein
sicherheitsrelevanter: die IMAP-Einstellung STARTTLS erzwingt nichts,
weil die gesetzte Option in imapflow 1.4.3 nicht existiert.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
2026-09-21 17:35:00 +02:00

22 KiB

phase, plan, subsystem, tags, status, requires, provides, affects, tech-stack, key-files, decisions, metrics, actuals, plan_head_before
phase plan subsystem tags status requires provides affects tech-stack key-files decisions metrics actuals plan_head_before
quick-260921-m34 01 apps/api (Typdisziplin)
typescript
any
refactor
mandantentrennung
prisma
node-forge
imapflow
complete
apps/api/src/auth/types/auth-user.ts (AuthUser, AuthenticatedRequest, JwtPayload, UploadedFileLike)
apps/api/src/prisma/prisma-error.ts (prismaErrorCode, prismaErrorTarget)
apps/api/src (48 Dateien)
apps/web/src/test/setup.ts
added patterns
catch (e: unknown) plus Form-Eingrenzung statt catch (e: any)
Mitgelieferte Bibliothekstypen vor eigenen Handschnittstellen
Ehrliches any mit geschriebener Begruendung statt erzwungener Zusicherung
created modified
apps/api/src/auth/types/auth-user.ts
apps/api/src/prisma/prisma-error.ts
apps/api/src/prisma/prisma-tenant.extension.ts
apps/api/src/cert-manager/cert-manager.service.ts
apps/api/src/inbox/imap.provider.ts
apps/api/src/inbox/exchange-inbox.provider.ts
apps/api/src/calendar/providers/exchange.provider.ts
apps/api/src/user/user.service.ts
apps/api/src/groups/groups.service.ts
apps/api/src/ldap/ldap.service.ts
apps/api/src/mail/mail.service.ts
AuthUser.tenantId ist string, aus drei Belegen hergeleitet; der SUPER_ADMIN-Zweig in TenantGuard bleibt unangetastet
Fehlereingrenzung per Form-Pruefung statt instanceof PrismaClientKnownRequestError, weil alle Testdoppel angehaengte .code-Felder werfen
15 Befunde bleiben mit Urteil und Begruendung stehen; Null war ausdruecklich nicht das Ziel
duration completed
1h 20min (16:09 bis 17:29 Uhr, 21.09.2026) 2026-09-21
tokens tasks commits
151201 3 7
8d845e7

Quick-Aufgabe 260921-m34: 288 any im Backend einzeln beurteilen Summary

Die 288 any-Befunde in apps/api sind auf 15 gefallen, jeder der 288 hat ein Urteil mit Begruendung, und die Arbeit hat sieben falsche Annahmen im Bestandscode aufgedeckt, die alle gemeldet und keine still repariert wurden.

Die Zahl, und was sie bedeutet

Groesse Ausgang Ende
lint/suspicious/noExplicitAny in apps/api/src 288 (48 Dateien) 15 (7 Dateien)
dasselbe in apps/web/src 1 0
lint/style/noNonNullAssertion in apps/api/src 56 56
as unknown as in apps/api/src 33 33
ts-expect-error / ts-ignore 0 / 0 0 / 0
Lint-Unterdrueckungsmarker 1 1
Befunde der Schwere error 0 0

Die vier Zeilen in der Mitte sind die wichtigsten der Tabelle. Sie belegen, dass die 273 verschwundenen Befunde tatsaechlich getypt und nicht bloss stillgelegt wurden: haette die Arbeit die bequeme Abkuerzung genommen, waere mindestens einer dieser Zaehlwerte gestiegen. Keiner ist gestiegen.

Aufteilung der 288 auf die drei Urteile (D-01)

Urteil Anzahl Was dahintersteckt
typisiert 252 Die Zusicherung war ueberfluessig oder der richtige Typ war ableitbar — aus Prismas Erweiterungstypen, aus den beiden Signierstellen des Tokens, aus schema.prisma, oder aus den mitgelieferten Typen einer Fremdbibliothek.
auf unknown umgestellt 21 18 Fehlerfaenger, die beiden .then((results: unknown[]) => ...) in prisma-tenant.extension.ts und intercept(): Observable<unknown>.
bleibt 15 Register unten. Jede Stelle traegt ihre Begruendung ausserdem direkt im Code.
288

Der Plan hat 20 bis 40 verbleibende Befunde erwartet; es sind 15 geworden. Der Unterschied kommt nicht daher, dass hier mehr erzwungen wurde, sondern aus drei Messungen, die guenstiger ausfielen als die Vorschau: @types/node-forge beschreibt PKCS7 besser als angenommen (die vier p7: any liessen sich mit dem mitgelieferten Captured<...>-Typ aufloesen), imapflow deklariert node.parameters bereits vollstaendig, und expect.extend(matchers) in apps/web brauchte seine Zusicherung schlicht nicht mehr. Die Zaehlwerte oben sind der Beleg, dass dabei nichts gegen eine Behauptung getauscht wurde.

Urteilsregister: die 15 Stellen, die bleiben

Jede dieser Zeilen steht so auch als Kommentar an der Codestelle. Wer spaeter hier aufraeumen will, findet die Begruendung dort, wo er zuerst nachsieht.

# Datei:Zeile Form Begruendung (gemessen)
1 cert-manager/cert-manager.service.ts:296 cert.publicKey as any @types/node-forge kennt nur PublicKey = rsa.PublicKey | ed25519.Key (index.d.ts:232). Der EC-Zweig darunter liest curve und params.curve.q.bitLength() — Felder, die node-forge zur Laufzeit liefert, die der mitgelieferte Typ aber gar nicht kennt. Eine Umdeutung ueber zwei Stufen wuerde dieselbe Luecke verdecken und zusaetzlich geprueft aussehen.
2 cert-manager/cert-manager.service.ts:318 (e: any) Certificate.extensions ist in @types/node-forge als any[] deklariert (index.d.ts:435). Der mitgelieferte Typ sagt ueber den Inhalt einer Erweiterung nichts aus.
3 cert-manager/cert-manager.service.ts:319 (sanExt as any) wie 2 — sanExt stammt aus demselben any[].
4 cert-manager/cert-manager.service.ts:319 (n: any) wie 2. Eine eigene Schnittstelle fuer altNames waere unbelegt: der Compiler koennte sie an keiner Stelle gegen etwas pruefen, sie saehe aber geprueft aus (D-02).
5 cert-manager/cert-manager.service.ts:589 null as any node-forge 1.4.0 nimmt hier einen fehlenden Schluessel an und erzeugt ein reines Zertifikatsbuendel; @types/node-forge schliesst null aus. Die mitgelieferten Typen beschreiben die Bibliothek an dieser Stelle nachweislich falsch.
6 cert-manager/cert-manager.service.ts:752 null as any wie 5, zweite Aufrufstelle.
7 dkv/dkv-scheduler.service.ts:144 job as any Ohne die Zusicherung meldet tsc, dass das lokale job nur die Form { start(): void } hat, waehrend addCronJob() einen vollstaendigen CronJob verlangt. Ursache ist der require()-Umweg aus 07-04 (pnpm-Isolation, cron ist nur mittelbare Abhaengigkeit). Aufloesen hiesse die Beschaffung der Klasse aendern (Verhaltensaenderung, D-03) oder cron direkt aufnehmen (neue Abhaengigkeit, D-04).
8 tenders/tender-digest.scheduler.ts:94 job as any wie 7.
9 tenders/tender-scheduler.service.ts:143 job as any wie 7.
10 groups/groups.service.ts:373 (u: any) Gefolge von 12: tx ist selbst any, und any.map() gibt dem Parameter keine kontextuelle Typisierung. Faellt automatisch mit 12.
11 groups/groups.service.ts:390 (a: any) wie 10.
12 prisma/prisma-tenant.extension.ts:264 fn: (tx: any) In Aufgabe 1 gemessen und verworfen: Prisma.TransactionClient erzwingt an den vier Aufrufstellen vollstaendige Prisma-Erzeugungstypen und bricht das absichtlich unvollstaendige Testdoppel in prisma-tenant.extension.spec.ts (TS2322). Das waere eine Aenderung an einer Teststruktur, kein ehrliches Typisieren.
13 prisma/prisma-tenant.extension.ts:266 async (tx: any) wie 12, dieselbe Funktion.
14 inbox/imap.provider.ts:78 (node as any).disposition... Befund B-05. Bleibt absichtlich sichtbar: der Ausdruck liest .parameters von einer Zeichenkette und ist zur Laufzeit immer undefined. Umbiegen auf dispositionParameters waere eine Verhaltensaenderung.
15 inbox/imap.provider.ts:402 } as any Befund B-06. Bleibt absichtlich sichtbar: die Zusicherung verdeckt, dass requireTLS in imapflow 1.4.3 gar nicht existiert. Die Option zu entfernen waere eine stille Reparatur.

Die Gruppen dahinter sind klein: sechs Stellen an node-forge, drei an der Cron-Beschaffung, vier an der Transaktionshilfe, zwei absichtlich stehen gelassene Befunde an imapflow.

Die aufgedeckten falschen Annahmen (D-03)

Diese Aufgabe hat ohne bekannten Fehler begonnen. Das hier ist ihr eigentlicher Ertrag: sieben Stellen, an denen der Bestandscode etwas annimmt, was nicht stimmt. Keine davon wurde still repariert — das Verhalten der API ist unveraendert.

B-01 — tenders/tender-matching.service.ts:159 (Aufgabe 1). Die Handannotation (match: { tender: unknown }) verengte den Wert falsch, sobald der Prisma-Klient richtig getypt war. Sie existierte nur, um unter dem any-Klienten TS7006 zu vermeiden. Annotation geloescht, damit der hergeleitete Typ durchkommt; keine Zusicherung an ihrer Stelle.

B-02 — dashboard/dashboard.controller.ts:74 (Aufgabe 2b). Der Handler las req.user?.role nach extractContext() und gab die Rolle an getWidgets(role: Role) weiter, das sie zwingend verlangt. Die Annahme "hier gibt es immer einen Aufrufer" stimmt — die Pruefung "No user context" erzwingt sie —, aber sie stand in einer anderen Methode, wo der Compiler sie nicht sehen konnte. extractContext() gibt die Rolle jetzt mit zurueck: keine neue Pruefung, kein erfundener Wert, gleiche Reihenfolge, gleiche Meldungen.

B-03 — tenders/tenders.controller.ts:142 (Aufgabe 2b). resolveRequestingTenantId() erklaerte string | undefined, liest aber req.tenantId, das TenantGuard fuer einen SUPER_ADMIN ohne Mandanten auf null setzt. Die Erklaerung war nie vollstaendig. Auf string | null | undefined erweitert — reine Erklaerung: die Funktion entscheidet seit jeher ueber den Wahrheitswert und faellt bei null wie bei undefined zu (nur global sichtbare Ausschreibungen).

B-04 — Falle im Mandantentrennungs-Erkenner (Aufgabe 3b). Die naheliegende Prisma-Schreibweise Prisma.UserGetPayload<{ select: typeof X }> laesst rls-access-inventory.spec.ts rot werden: der Erkenner zaehlt jede select:-Angabe ausserhalb eines erkannten Modellaufrufs als Verstoss und unterscheidet Typposition nicht von Aufrufposition. Beim ersten Versuch gemessen. Der Erkenner ist die Mandantenkontrolle (T-M34-03) und wurde nicht aufgeweicht — stattdessen leitet der Zeilentyp ueber Pick<User, keyof typeof PLATFORM_USER_SELECT> her, was ohne das Wort select auskommt. Wer kuenftig UserGetPayload einsetzen will, muss zuerst den Erkenner erweitern, nicht die Ausnahmeliste.

B-05 — inbox/imap.provider.ts:78 (Aufgabe 3c). Sicherheitsnah, offen. (node as any).disposition?.parameters?.filename liest .parameters von einer Zeichenkette: imapflow deklariert disposition als string (imap-flow.d.ts:448, also "attachment"/"inline"), die zugehoerigen Parameter liegen in einem eigenen Feld dispositionParameters (:450). Der Ausdruck ist zur Laufzeit immer undefined, dispositionFilename ist stets ''. Folge: Anhaenge, die als application/octet-stream kommen (typisch fuer Outlook), werden ueber den Dateinamen aus Content-Disposition nicht erkannt — nur ueber den aus Content-Type. Betrifft den DKV-Rechnungseinzug. Nicht repariert, weil das Umbiegen erstmals Anhaenge einsammeln wuerde, die heute uebersprungen werden — eine Verhaltensaenderung, die ein Mensch entscheiden muss.

B-06 — inbox/imap.provider.ts:402 (Aufgabe 3c). Sicherheitsnah, offen. requireTLS: config.encryption === 'starttls' wird an new ImapFlow(...) uebergeben, aber requireTLS kommt in imapflow 1.4.3 nirgends vor — weder in ImapFlowOptions (lib/imap-flow.d.ts) noch im Laufzeitcode (lib/imap-flow.js), beides durchsucht. Die Option wird still verworfen; ein STARTTLS-Zwang entsteht durch sie nicht. Genau das } as any hat es verdeckt. Die Einstellung "STARTTLS" in der Postfach-Konfiguration bewirkt damit nicht das, was ihr Name verspricht. Nicht repariert (D-03), Zusicherung bleibt sichtbar stehen, damit der Befund in der Zaehlung nicht verschwindet.

B-07 — httpntlm-Antwortrumpf (Aufgabe 3c). Ohne Auswirkung, aber falsch. Beide Exchange-Wege riefen res.body?.toString('utf-8') auf und nahmen damit einen Buffer an. Gemessen: httpntlm reicht an httpreq durch, und httpreq gibt den Rumpf als Zeichenkette zurueck, solange die Option binary nicht gesetzt ist (httpreq@1.1.1/lib/httpreq.js:391) — keiner der beiden Aufrufer setzt sie. Das ging bisher nur gut, weil String.prototype.toString() sein Argument ignoriert. Die Testdoppel reichen umgekehrt wirklich einen Buffer herein, beide Formen kommen also vor. NtlmResponse.body nennt jetzt beide; die Fallunterscheidung liefert fuer jede exakt dasselbe Ergebnis wie zuvor.

Die tenantId-Entscheidung im gemeinsamen Aufrufer-Typ

AuthUser.tenantId ist string, nicht string | undefined. Die Messung allein haette in die falsche Richtung gedraengt (string | undefined erzeugte acht Fehler im Produktivcode, string keinen); entschieden wurde auf drei Belegen:

  1. apps/api/prisma/schema.prisma deklariert User.tenantId String ohne ?. Die Spalte ist Pflicht, und beide Signierstellen schreiben genau diesen Spaltenwert — seit dem ersten Commit des Anmeldedienstes (6190f3d) gibt es keine Token-Erzeugung ohne diesen Anspruch.
  2. Der Bestand beschreibt dasselbe Objekt in SessionUser (bug-reports.service.ts) bereits als tenantId: string. SessionUser ist jetzt ein Pick<> von AuthUser, damit es keine zweite, abweichende Beschreibung desselben Objekts gibt.
  3. TenantGuard haelt fuer SUPER_ADMIN einen Zweig ohne Mandanten vor und setzt dort req.tenantId = null.

Beleg 3 spricht nicht gegen string, und genau daran haengt die Sicherheitsfrage: der Zweig in TenantGuard ist eine Tiefenverteidigung gegen ein Token ohne diesen Anspruch, und er liest AuthUser gar nicht — der Waechter holt sein Anfrageobjekt ungetypt. Dieser Typ kann den Zweig also nicht zu totem Code machen. Dass es den Zweig gibt, steht ausserdem weiterhin im Typsystem, nur an der richtigen Stelle: AuthenticatedRequest.tenantId ist string | null | undefined.

Am Typ steht dazu eine ausdrueckliche Warnung fuer spaetere Leser, dass der SUPER_ADMIN-Zweig ein Schutzzweig ist und nicht entfernt oder wegtypisiert werden darf (T-M34-01). tenant.guard.ts wurde in diesem ganzen Lauf nicht angefasst — in Aufgabe 2 per git diff --stat nachgewiesen.

Was die Arbeit sonst noch geaendert hat

Zwei neue Dateien, beide klein und begruendet. apps/api/src/auth/types/auth-user.ts traegt den gemeinsamen Aufrufer-Typ; jedes Feld hat seine Herkunft als Kommentar. apps/api/src/prisma/prisma-error.ts traegt prismaErrorCode() und prismaErrorTarget().

Warum die Fehlereingrenzung Form-Pruefungen macht und kein instanceof. Der naheliegende Weg waere err instanceof Prisma.PrismaClientKnownRequestError gewesen. Gemessen: samtliche Testdoppel in apps/api werfen new Error(...) mit angehaengtem .code (groups, user, ldap, tenders, module-grants, admin-seed) und dashboard.service.spec.ts:451 ein reines { code: 'P2002' }. Ein instanceof-Test haette all diese Werte in den jeweils anderen Zweig geschickt — eine Verhaltensaenderung, und nach T-M34-06 genau die Art von Aenderung, die einen Hintergrundlauf kuenftig abbrechen laesst, der heute weiterlaeuft. Die Helfer bilden err?.code und err?.meta?.target deshalb eins zu eins ab, nur ohne any.

Nebengewinne ohne neue Zusicherungen. In cert-manager.service.ts sind 25 Zusicherungen der Form file.buffer as Buffer weggefallen, weil file kein any mehr ist. In dkv und settings fielen req.tenantId as string | undefined weg. as unknown as ist trotzdem bei 33 geblieben — dieselbe Zahl wie zu Beginn.

Abweichungen vom Plan

1. [Regel 3 — blockierend] Neue Datei prisma/prisma-error.ts statt 18 Eingrenzungen von Hand. Der Plan nennt fuer Aufgabe 3 Schritt A keine neue Datei. 18 Fehlerfaenger einzeln mit einer vierzeiligen Form-Pruefung zu versehen haette dieselbe Logik achtzehnmal wiederholt und die Begruendung, warum kein instanceof verwendet wird, achtzehnmal daneben. Die Datei liegt in prisma/, weil alle Aufrufer Prisma-Fehlercodes pruefen. Keine neue Abhaengigkeit. Commit: 32591b6.

2. [Regel 1 — Fehler] Erste Fassung von user.service.ts machte die Mandanten-Spec rot. Prisma.UserGetPayload<{ select: typeof X }> hat rls-access-inventory.spec.ts gebrochen (siehe B-04). Sofort im selben Schritt auf Pick<User, ...> umgestellt, der Erkenner blieb unangetastet, Spec wieder 30/30. Commit: 3892c5f.

3. [Messung weicht vom Plan ab] calendar.service.ts:206/255 sind keine Prisma-JSON-Eingaben. Der Plan vermutete Prisma.InputJsonValue. Gemessen: es sind dynamisch gebaute Erzeugungs- und Aenderungseingaben fuer CalendarSource. Richtig getypt mit Prisma.CalendarSourceUncheckedCreateInput / ...UncheckedUpdateInput. Commit: 3892c5f.

4. [Messung weicht vom Plan ab] node-forge war besser beschrieben als erwartet. Der Plan rechnete damit, dass ein Teil der elf Stellen bleibt. Gemessen: die vier p7: any liessen sich mit dem mitgelieferten Captured<PkcsEnvelopedData | PkcsSignedData> aufloesen, cert.siginfo war ohnehin getypt. Sechs bleiben, fuenf wurden getypt. Commit: d8fb9ae.

Pruefungen — die echten Ausgaben

noExplicitAny, noNonNullAssertion, error-Befunde

$ npx biome lint --reporter=json --max-diagnostics=2000 apps/api/src | python3 -c "..."
any 15 nonnull 56 error 0
Rueckgabewert: 0

Schranke der Aufgabe: any <= 45, nonnull <= 56, error == 0. Alle drei eingehalten.

Vollstaendige Restliste (Pruefung 2 der Aufgabe)

apps/api/src/cert-manager/cert-manager.service.ts:296:40  |  const pubKey = cert.publicKey as any;
apps/api/src/cert-manager/cert-manager.service.ts:318:48  |  const sanExt = cert.extensions?.find((e: any) => e.name === 'subjectAltName');
apps/api/src/cert-manager/cert-manager.service.ts:319:41  |  const san: string[] = ((sanExt as any)?.altNames ?? []).map((n: any) =>
apps/api/src/cert-manager/cert-manager.service.ts:319:71  |  const san: string[] = ((sanExt as any)?.altNames ?? []).map((n: any) =>
apps/api/src/cert-manager/cert-manager.service.ts:589:19  |  null as any, // cert-only PFX - null key accepted by node-forge 1.4.0
apps/api/src/cert-manager/cert-manager.service.ts:752:19  |  null as any, // cert-only PFX - null key accepted by node-forge 1.4.0
apps/api/src/dkv/dkv-scheduler.service.ts:144:55  |  this.schedulerRegistry.addCronJob(jobName, job as any);
apps/api/src/groups/groups.service.ts:373:33  |  data: users.map((u: any) => ({
apps/api/src/groups/groups.service.ts:390:39  |  data: activations.map((a: any) => ({
apps/api/src/inbox/imap.provider.ts:78:15  |  ((node as any).disposition?.parameters?.filename as string | undefined)?.toLowerCase() ?? '';
apps/api/src/inbox/imap.provider.ts:402:10  |  } as any);
apps/api/src/prisma/prisma-tenant.extension.ts:264:12  |  fn: (tx: any) => Promise<T>,
apps/api/src/prisma/prisma-tenant.extension.ts:266:41  |  return prisma.$transaction(async (tx: any) => {
apps/api/src/tenders/tender-digest.scheduler.ts:94:63  |  this.schedulerRegistry.addCronJob(this.JOB_NAME, job as any);
apps/api/src/tenders/tender-scheduler.service.ts:143:61  |  this.schedulerRegistry.addCronJob(this.JOB_NAME, job as any);
TOTAL 15

Jede dieser 15 Zeilen steht im Urteilsregister oben. apps/web/src liefert keine Zeile mehr.

Unterdrueckungsmarker

### 3. Unterdrueckungsmarker
keine stillgelegten Stellen

Das heisst im Einzelnen: ts-expect-error 0, ts-ignore 0, biome-ignore 1 (der eine Bestandsmarker), as unknown as 33.

biome.json und Abhaengigkeiten

### 4. biome.json
biome.json unberuehrt

### 5. Abhaengigkeiten
keine neuen Abhaengigkeiten, keine Versionsspruenge

Geprueft ueber git diff --stat HEAD gegen biome.json, alle drei package.json und pnpm-lock.yaml — jede Ausgabe leer.

Typlauf und Lint

### 6. type-check / lint
type-check 4/4, lint 5/5

Testsuiten

$ pnpm --dir apps/api run test
 Test Files  72 passed (72)
      Tests  1143 passed (1143)
   Duration  8.05s

$ pnpm --dir apps/web run test
 Test Files  73 passed (73)
      Tests  531 passed (531)
   Duration  23.48s

Unveraenderte Zahlen gegenueber dem Ausgang (72/1143 und 73/531).

Mandantentrennungs-Erkenner, ausdruecklich einzeln

$ pnpm --dir apps/api exec vitest run src/prisma/rls-access-inventory.spec.ts
 Test Files  1 passed (1)
      Tests  30 passed (30)
   Duration  557ms

Das ist die Kontrolle aus T-M34-03. Sie war in Aufgabe 3b einmal rot (B-04) und wurde nicht durch Aufweichen, sondern durch eine andere Typschreibweise wieder gruen.

Commits

Commit Aufgabe any in apps/api danach
b188946 1 — Mandantenbindung entzaubert, 105 unnoetige Zusicherungen 149
f2fc39f 2a — gemeinsamer Aufrufer-Typ, aus den Signierstellen abgeleitet 137
7c9d7c1 2b — getypte Anfrage in elf Controllern, zwei Befunde gemeldet 66
52668c2 2c — Hochladewege getypt, 25 Zusicherungen fallen mit 56
32591b6 3a — 18 Fehlerfaenger auf unknown, mit echter Eingrenzung 38
3892c5f 3b — Prisma-nahe Formen getypt, Erkenner-Falle gemeldet 31
d8fb9ae 3c — Randschicht beurteilt, drei Befunde gemeldet 15

Jeder Commit war fuer sich gruen: nach jedem lief pnpm type-check 4/4, pnpm lint 5/5 mit 0 Befunden der Schwere error, und beide Testsuiten mit unveraenderten Zahlen (D-05, D-06).

Offene Punkte fuer den Menschen

Zwei der sieben Befunde brauchen eine Entscheidung, die diese Aufgabe nicht treffen durfte, weil beide das Verhalten aendern wuerden:

  • B-05 (imap.provider.ts:78): sollen Anhaenge mit application/octet-stream kuenftig auch ueber den Dateinamen aus Content-Disposition erkannt werden? Heute werden sie es nicht.
  • B-06 (imap.provider.ts:402): soll die Postfach-Einstellung "STARTTLS" tatsaechlich einen Zwang bewirken? Heute wird die Option von imapflow verworfen.

Beide sind im Code markiert und stehen in der Zaehlung, verschwinden also nicht aus dem Blick.

Self-Check

BESTANDEN.

Geprueft, nicht behauptet:

  • Beide neu angelegten Dateien existieren auf der Platte (apps/api/src/auth/types/auth-user.ts, apps/api/src/prisma/prisma-error.ts).
  • Alle sieben genannten Commits sind in git log vorhanden.
  • apps/api/src/tenant/tenant.guard.ts ist ueber den gesamten Lauf (git diff --stat 8d845e7..HEAD) unveraendert — keine einzige Zeile.
  • Die 15 Zeilen der Restliste stammen aus der maschinellen Biome-Ausgabe, nicht aus dem Gedaechtnis, und stimmen eins zu eins mit dem Urteilsregister ueberein.
  • Die Aufteilung 252 + 21 + 15 ergibt 288.