---
phase: 17-eigene-ausschreibungs-quellen-je-nutzer
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- apps/api/prisma/schema.prisma
- apps/api/prisma/migrations/20260812100000_tender_email_config_per_user/migration.sql
- apps/api/src/tenders/tender-email-config.service.ts
- apps/api/src/tenders/tenders.controller.ts
- apps/api/src/tenders/adapters/email-alert.adapter.ts
- apps/api/src/tenders/tender-email-config.service.spec.ts
- apps/api/src/tenders/adapters/email-alert.adapter.spec.ts
- apps/api/src/tenders/email-config-migration-sql.spec.ts
- apps/web/src/app/(portal)/modules/tender-radar/my-sources/page.tsx
autonomous: false
requirements: [SRC-01, SRC-03]
user_setup: []
estimate:
tokens: 60000
raw_tokens: 60000
tasks: 3
confidence: low
must_haves:
truths:
- "Zwei verschiedene Nutzer desselben Mandanten koennen gleichzeitig je ein eigenes Alert-Postfach hinterlegen (D-01)."
- "Beim Abruf werden beide Postfaecher in einem Durchlauf geleert; faellt eines aus, laufen die anderen weiter (D-01, offener Punkt 3)."
- "Was aus dem Postfach eines Nutzers hereinkommt, bleibt weiterhin auf dessen Mandanten begrenzt — die in Phase 14 gebaute Herkunftsmarkierung bleibt unveraendert (D-05)."
- "Eine bereits vorhandene Postfach-Zeile geht bei der Umstellung nicht verloren, sondern bekommt einen Besitzer (offener Punkt 1)."
- "Die Seite `/modules/tender-radar/my-sources` ist fuer jeden Nutzer mit Modulzugang erreichbar und zeigt sein eigenes Postfach."
artifacts:
- apps/api/prisma/migrations/20260812100000_tender_email_config_per_user/migration.sql
- apps/api/src/tenders/email-config-migration-sql.spec.ts
- apps/web/src/app/(portal)/modules/tender-radar/my-sources/page.tsx
key_links:
- "TenderEmailConfig.userId <-> extractTriageContext(req).userId — Besitz kommt ausschliesslich aus dem angemeldeten Konto, nie aus dem Request-Body (IDOR)."
- "TenderEmailConfig.tenantId <-> EmailAlertAdapter.extractCandidates(..., cfg.tenantId, ...) — die Herkunftsmarkierung der eingelesenen Ausschreibungen haengt weiter an diesem Feld."
- "GET/PUT /modules/tender-radar/email-config <-> Reihenfolge vor `@Get(':id')` — statische Route darf nicht von der Parameter-Route verdeckt werden."
---
Das Alert-Postfach wechselt vom Mandanten zum einzelnen Nutzer. Heute gibt es
genau ein Postfach pro Mandant (`TenderEmailConfig.tenantId @unique`) — ein
zweiter Kollege mit eigenem Portal-Konto kann seine Ausschreibungs-Alarme
schlicht nicht anbinden. Nach diesem Plan hinterlegt jeder Nutzer sein eigenes
Postfach, und der Abruf holt alle hinterlegten Postfaecher in einem Durchlauf ab.
Dieser Plan ist die duenne, aber vollstaendige Bahn durch alle Schichten:
Datenbank -> Migration -> Dienst -> Endpunkt -> eine eigene, fuer jeden
Modulnutzer erreichbare Seite. Erst wenn diese eine Bahn nachweislich laeuft,
bauen die Plaene 17-02 (RSS-Feeds) und 17-03 (Oberflaeche aufteilen) daneben
weiter.
Purpose: D-01 aus 17-CONTEXT.md umsetzen und damit die eigentliche Ursache des
Backlog-Punkts beheben — nicht nur die Anzeige, sondern den Zuschnitt.
Output: Geaenderte Tabelle samt Umzug der Bestandsdaten, nutzerbezogener Dienst
und Endpunkt, neue Seite "Meine Quellen" mit dem Postfach-Formular.
**Harte Grenze (D-05):** Ausschreibungsdaten bleiben plattform-global. `Tender`
bekommt kein Mandantenfeld, keine RLS, keinen Mandanten-Wrapper. Geaendert wird
ausschliesslich, wer Quellen einspeist — nicht, wer Treffer sieht. Innerhalb
eines Mandanten sieht weiterhin jeder alles.
**Entscheidung zum offenen Punkt 1 (Besitzer der Bestandszeile):** Eine
vorhandene Postfach-Zeile wird dem aeltesten aktiven ADMIN bzw. SUPER_ADMIN
ihres Mandanten zugeordnet, nicht geloescht. Begruendung: Die Zeile wurde
seinerzeit von genau dieser Rolle angelegt, sie enthaelt verschluesselte
Zugangsdaten, und ein Loeschen wuerde den laufenden Abruf ohne Vorwarnung
stilllegen. Nur falls ein Mandant ueberhaupt keinen aktiven Administrator hat,
wird die Zeile entfernt — dann gaebe es niemanden, der sie je wieder bearbeiten
koennte, waehrend das Postfach im Hintergrund weiter abgefragt wuerde.
**Entscheidung zum offenen Punkt 4 (wohin mit den nutzereigenen Abschnitten):**
Es entsteht eine eigene nutzerseitige Modulseite `/modules/tender-radar/my-sources`
("Meine Quellen"), getrennt von der Administrationsseite. Begruendung: Die
Alternative, alles nach `/settings/general` zu verschieben, sammelt dort mit
jedem neuen Modul modulfremde Einstellungen an — genau der Einwand aus dem
Backlog-Punkt. Modulsachen bleiben beim Modul; DKV-Fleet und kuenftige Module
koennen dieselbe Bauform uebernehmen. Der Adresspfad bleibt englisch
(`my-sources`), wie jeder andere Pfad im Projekt auch; die Beschriftung ist
deutsch.
@$HOME/.claude/gsd-core/workflows/execute-plan.md
@$HOME/.claude/gsd-core/templates/summary.md
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/17-eigene-ausschreibungs-quellen-je-nutzer/17-CONTEXT.md
@apps/api/prisma/schema.prisma
@apps/api/src/tenders/tender-email-config.service.ts
@apps/api/src/tenders/adapters/email-alert.adapter.ts
@apps/api/prisma/migrations/20260811140000_encrypt_ldap_bind_password/migration.sql
@apps/api/src/tenders/doe-url-migration-sql.spec.ts
Task 1: Freigabe fuer die beiden Datenbank-Umbauten dieser PhaseDarf ich die Tabellenstruktur fuer Postfaecher und RSS-Feeds jetzt umbauen?
Diese Phase aendert zwei Tabellen dauerhaft. Beides laesst sich nur mit einer
weiteren Datenbank-Aenderung zurueckdrehen, nicht durch einfaches Rueckgaengigmachen
der Dateien:
1. **Postfaecher** (dieser Plan): Das Postfach haengt danach am Nutzer statt am
Mandanten. Eine bereits eingerichtete Postfach-Zeile bekommt dabei den
aeltesten aktiven Administrator ihres Mandanten als Besitzer. Findet sich dort
kein Administrator, wird die Zeile entfernt — sie waere sonst fuer niemanden
mehr bearbeitbar, wuerde aber weiter abgefragt.
2. **RSS-Feeds** (Plan 17-02): Die Feeds bekommen ein Besitzer-Feld. Alle heute
vorhandenen Feeds bleiben unveraendert plattformweit fuer alle aktiv — dort
geht nichts verloren.
Auf dem Testserver liegen echte Daten. Der Umbau selbst laeuft hier lokal; auf
dem Testserver wird er erst wirksam, wenn Sie dort selbst neu ausrollen. Vor
diesem Ausrollen ist eine Datenbank-Sicherung sinnvoll.
Bevor ich anfange, zaehle ich nach, wie viele Postfach-Zeilen es auf dem
Testserver ueberhaupt gibt, und nenne Ihnen die Zahl — nur lesend, ich fasse
dort nichts an.
Antworten Sie mit "weiter" oder "stopp".Task 2: Ein eigenes Postfach je Nutzer — durchgehend von der Datenbank bis zur SeiteDie lokale Entwicklungsdatenbank ist ohne Host-Port erreichbar: Container-IP des `db`-Containers ermitteln (`docker inspect`) und `DATABASE_URL` mit Zugang `tessera:tessera_dev` darauf zeigen lassen — sonst laesst sich die Migration nicht anwenden (Projektwissen "Lokale DB-Migrationen").Die Migration schreibt Bestandsdaten um und entfernt eine Eindeutigkeitsregel; ein Rueckweg braucht eine zweite Migration. Freigabe erfolgt in Task 1.
apps/api/prisma/schema.prisma,
apps/api/prisma/migrations/20260812100000_tender_email_config_per_user/migration.sql,
apps/api/src/tenders/tender-email-config.service.ts,
apps/api/src/tenders/tenders.controller.ts,
apps/web/src/app/(portal)/modules/tender-radar/my-sources/page.tsx
apps/api/src/tenders/tender-email-config.service.ts,
apps/api/src/tenders/tenders.controller.ts (Zeilen 90-115 und 270-305),
apps/api/prisma/migrations/20260723113917_tender_email_config_owner_tenant_id/migration.sql,
apps/web/src/app/(portal)/modules/tender-radar/settings/page.tsx
- Ein Nutzer speichert sein Postfach; danach liest derselbe Nutzer genau diese Werte zurueck.
- Ein zweiter Nutzer desselben Mandanten speichert ein anderes Postfach; beide Zeilen existieren nebeneinander, keine ueberschreibt die andere.
- Nutzer B liest nie die Werte von Nutzer A — gelesen wird ausschliesslich nach dem angemeldeten Konto.
- Das Passwort verlaesst den Server nie: die Antwort enthaelt weiterhin nur `hasPassword: boolean`.
- Wird nur der Benutzername geaendert und das Passwortfeld leer gelassen, bleibt das gespeicherte Passwort erhalten (bestehende Semantik, unveraendert).
**Vorabpruefung (nur lesend, Testserver).** Ueber SSH auf 192.168.13.12 mit
`psql` zaehlen, wie viele Zeilen in `TenderEmailConfig` stehen und zu welchem
Mandanten sie gehoeren. Ergebnis im SUMMARY festhalten. Nichts aendern, kein
`docker compose`-Kommando dort ausfuehren — Ausrollen ist Sache des Nutzers.
**Schema (`apps/api/prisma/schema.prisma`, Modell `TenderEmailConfig`).**
Feld `userId String @unique` ergaenzen. `tenantId` von `@unique` auf ein
gewoehnliches Feld zurueckstufen (bleibt erhalten, denormalisiert, fuer die
SMTP-Aufloesung und die Herkunftsmarkierung — gleiche Rolle wie in
`TenderMatch`, D-01). Bestehenden `@@index([tenantId])` behalten,
`@@index([userId])` ergaenzen. Den Kopfkommentar des Modells um zwei Saetze
erweitern: Besitz liegt beim Nutzer (D-01, Phase 17), `tenantId` bleibt
denormalisiert.
**Migration.** Verzeichnis
`apps/api/prisma/migrations/20260812100000_tender_email_config_per_user/`
anlegen, `migration.sql` von Hand schreiben (Projektentscheidung
"Migrationsverfahren angepasst": `prisma migrate dev` verweigert die
nicht-interaktive Shell). Erwarteter Ablauf, in dieser Reihenfolge:
1. Spalte `userId` als `TEXT` ohne Pflicht ergaenzen.
2. Bestandszeilen befuellen: je Zeile den aeltesten aktiven Nutzer mit Rolle
`ADMIN` oder `SUPER_ADMIN` desselben Mandanten ermitteln (sortiert nach
`createdAt`, bei Gleichstand nach `id`, `LIMIT 1`) und dessen `id` eintragen.
3. Zeilen, die danach immer noch keinen Besitzer haben, entfernen (kein
Administrator im Mandanten vorhanden — siehe Begruendung im Objective).
4. Eindeutigkeitsregel auf `tenantId` entfernen
(`TenderEmailConfig_tenantId_key`), gewoehnlichen Index `..._tenantId_idx`
stehen lassen.
5. Spalte `userId` auf Pflicht setzen, eindeutigen Index
`TenderEmailConfig_userId_key` und Index `TenderEmailConfig_userId_idx`
anlegen.
Ueber die Anweisungen einen deutschen Kommentarkopf setzen, der erklaert, warum
umgestellt wird und warum die Bestandszeile dem aeltesten Administrator
zufaellt — gleiche Form wie
`20260811140000_encrypt_ldap_bind_password/migration.sql`.
Anwenden mit `prisma migrate deploy` gegen die lokale Datenbank, danach
`prisma generate`.
**Dienst (`tender-email-config.service.ts`).** `getConfigForApi` nimmt statt
`tenantId` nun `userId` und sucht darueber. `saveConfig` nimmt ein Objekt
`{ userId, tenantId }` als ersten Parameter; im `upsert` ist `userId` der
Schluessel, `tenantId` wird sowohl beim Anlegen als auch beim Aktualisieren
mitgeschrieben (der Mandant eines Nutzers kann sich aendern, das
denormalisierte Feld muss mitziehen). `EMAIL_CONFIG_SAFE_SELECT` um `userId`
ergaenzen; der verschluesselte Zugangsdaten-Block bleibt wie bisher aus jeder
Antwort ausgeschlossen. Die Semantik "Passwort leer heisst gespeichertes
Passwort behalten" bleibt unveraendert; nur die Suchbedingungen wechseln von
`tenantId` auf `userId`. Klassenkommentar entsprechend nachziehen.
**Endpunkt (`tenders.controller.ts`).** `GET`/`PUT /email-config`: die
Rollenpruefung `@Roles(ADMIN, SUPER_ADMIN)` entfaellt und wird durch
`@UseModule('tender-radar')` ersetzt — das Postfach ist ab jetzt eine
Nutzereinstellung, der Zugang haengt an der Modulfreigabe aus Phase 15.
`userId` und `tenantId` kommen weiterhin ausschliesslich aus
`extractTriageContext(req)`, nie aus dem Rumpf der Anfrage. **Die Position
beider Handler im Datei-Aufbau nicht verschieben**: sie stehen vor der
Parameter-Route `@Get(':id')`, sonst verdeckt diese sie und die Endpunkte
antworten mit 404 (dokumentierte Falle, Unit-Tests fangen sie nicht). Die
vorhandenen Kommentare zu dieser Reihenfolge stehen lassen und den Hinweis auf
die Mandantenbindung im Kommentar auf die Nutzerbindung umschreiben.
**Seite (`apps/web/src/app/(portal)/modules/tender-radar/my-sources/page.tsx`).**
Neue Client-Seite, gleiche Bauform wie die vorhandene Einstellungsseite:
Ueberschrift aus dem i18n-Namensraum `tenderRadar` (neuer Schluessel
`mySources.title`, deutsch "Meine Quellen", englisch "My sources"; beide
Sprachdateien `apps/web/src/messages/de.json` und `en.json` ergaenzen),
darunter ein Abschnitt "Mein Postfach" mit dem **unveraenderten** bestehenden
`EmailAlertConfigForm` (nur importieren, nicht kopieren, nicht umbauen — es
spricht dieselben Endpunkte an). Ein kurzer Hinweistext unter der Ueberschrift
benennt ehrlich, was D-05 bedeutet: die hier eingespeisten Ausschreibungen
erscheinen in der Trefferliste aller Kollegen desselben Mandanten. Keine
Aenderung am Modul-Verzeichnis noetig — verschachtelte Route unterhalb der
bereits freigeschalteten Modulseite, gleiche Begruendung wie bei der
Einstellungsseite.
cd /home/vicolab/projects/tessera-ctl/apps/api && npx prisma validate && npx prisma migrate statuscd /home/vicolab/projects/tessera-ctl && pnpm --filter @tessera/api type-check && pnpm --filter @tessera/web type-checkcd /home/vicolab/projects/tessera-ctl/apps/api && npx prisma db execute --stdin <<'SQL'
SELECT indexname FROM pg_indexes WHERE tablename = 'TenderEmailConfig' ORDER BY indexname;
SQLIm Browser als normaler Nutzer (Rolle USER mit Modulfreigabe) `/modules/tender-radar/my-sources` oeffnen: die Seite laedt, das Postfach-Formular ist bedienbar, Speichern quittiert erfolgreich und nach einem Neuladen stehen die Werte wieder da. Danach mit einem zweiten Konto desselben Mandanten anmelden: dort steht ein leeres Formular, nicht die Werte des ersten Nutzers.
`prisma migrate status` meldet alle Migrationen angewandt. Die Indexliste zeigt
`TenderEmailConfig_userId_key` und enthaelt `TenderEmailConfig_tenantId_key`
nicht mehr. Beide Typpruefungen laufen fehlerfrei. Zwei Konten desselben
Mandanten haben je eine eigene Postfach-Zeile.
Task 3: Der Abruf holt alle Postfaecher — und beweist es mit Tests
apps/api/src/tenders/adapters/email-alert.adapter.ts,
apps/api/src/tenders/adapters/email-alert.adapter.spec.ts,
apps/api/src/tenders/tender-email-config.service.spec.ts,
apps/api/src/tenders/email-config-migration-sql.spec.ts
apps/api/src/tenders/adapters/email-alert.adapter.spec.ts,
apps/api/src/tenders/tender-email-config.service.spec.ts,
apps/api/src/tenders/doe-url-migration-sql.spec.ts
- Zwei aktive Postfaecher **desselben** Mandanten werden in einem einzigen Abruf beide geleert. Das ist genau die Faehigkeit, die es vorher nicht gab — der Test faellt ohne Task 2 durch.
- Faellt eines von zwei Postfaechern mit einem Fehler aus, liefert der Abruf trotzdem die Datensaetze des anderen und protokolliert eine Warnung.
- Die eingelesenen Datensaetze tragen weiterhin die Herkunftsmarkierung aus dem Mandantenfeld der jeweiligen Postfach-Zeile (unveraenderte Sichtbarkeit gegenueber anderen Mandanten, D-05).
- Die Sammelabfrage liest weiterhin alle aktiven Postfaecher ueber Mandantengrenzen hinweg in einem Zug — der Plattform-Zeitplan ist keine Anfrage eines einzelnen Mandanten.
- Die Migrationsdatei ordnet Bestandszeilen einem Administrator zu, bevor sie unbesetzte Zeilen entfernt.
**Anpassung im Abruf (`email-alert.adapter.ts`).** Inhaltlich aendert sich
nichts an der Mechanik: die Sammelabfrage bleibt exakt wie sie ist (alle
aktiven Zeilen in einem Zug), und die Herkunftsmarkierung der Datensaetze kommt
weiterhin aus dem Mandantenfeld der jeweiligen Zeile — dieses Feld existiert
nach Task 2 unveraendert. Zu aendern sind nur zwei Dinge: (a) die Warnmeldung
im Fehlerfall benennt jetzt das Postfach ueber die Zeilen-`id` und die
Besitzer-Kennung statt des Mandanten, weil ein Mandant ab sofort mehrere
Postfaecher haben kann und die alte Meldung sonst mehrdeutig waere — Zugangsdaten
duerfen dabei nach wie vor nirgends im Protokoll landen; (b) der
Klassenkommentar erklaert, dass die Auffaecherung ab Phase 17 pro Postfach und
nicht mehr pro Mandant laeuft. Die ausdrueckliche Warnung im Kommentar, dass
diese Sammelabfrage niemals in einen Mandanten-Wrapper gehoert, bleibt Wort fuer
Wort stehen. Die Fehlerbehandlung je Postfach (ein kaputtes Postfach blockiert
die anderen nicht) bleibt unangetastet.
**Tests `email-alert.adapter.spec.ts`.** Bestehende Faelle auf die neue
Datenform nachziehen (Zeilen tragen jetzt zusaetzlich `userId`) und drei neue
Faelle ergaenzen:
1. Zwei aktive Zeilen mit **gleichem** Mandanten, aber verschiedenen Besitzern
und verschiedenen Postfachdaten: der Postfach-Anbieter wird zweimal
aufgerufen, mit den jeweils eigenen Zugangsdaten, und das Ergebnis enthaelt
die Kandidaten aus beiden Postfaechern.
2. Von zwei Zeilen wirft die erste beim Abholen einen Fehler: das Ergebnis
enthaelt trotzdem die Datensaetze der zweiten, und eine Warnung wurde
protokolliert.
3. Die Herkunftsmarkierung der erzeugten Datensaetze entspricht dem
Mandantenfeld der jeweiligen Zeile — bei zwei Zeilen unterschiedlicher
Mandanten also zwei unterschiedliche Werte.
Wichtig: Die Erwartungswerte in diesen Tests von Hand hinschreiben. Sie duerfen
**nicht** ueber dieselbe Hilfsfunktion erzeugt werden, die auch der Produktivcode
benutzt — ein Test, der seine Erwartung mit dem Pruefling selbst baut, beweist
nichts (dokumentierte Anti-Pattern im Projekt).
**Tests `tender-email-config.service.spec.ts`.** Auf den neuen Schluessel
umstellen: gelesen und geschrieben wird nach Besitzer. Zwei Faelle ergaenzen:
Speichern legt beim Anlegen sowohl Besitzer als auch Mandant an; ein zweiter
Nutzer im selben Mandanten erzeugt eine zweite Zeile statt die erste zu
ueberschreiben. Der bestehende Fall, dass der verschluesselte Zugangsdatenblock
nie in der Antwort auftaucht, bleibt erhalten und muss weiterhin durchlaufen.
**Neuer Test `email-config-migration-sql.spec.ts`.** Nach dem Vorbild von
`doe-url-migration-sql.spec.ts` die von Hand geschriebene Migrationsdatei ohne
Datenbank pruefen, rein als Text: die Zuordnung zum Administrator kommt in der
Datei vor dem Entfernen unbesetzter Zeilen; die Auswahl beschraenkt sich auf
aktive Nutzer mit Administratorrolle desselben Mandanten und nimmt genau einen;
die alte Eindeutigkeitsregel auf dem Mandantenfeld wird entfernt und eine neue
auf dem Besitzerfeld angelegt; die Pflichtsetzung der neuen Spalte steht nach
der Befuellung.
cd /home/vicolab/projects/tessera-ctl && pnpm --filter @tessera/api exec vitest run src/tenders/adapters/email-alert.adapter.spec.ts src/tenders/tender-email-config.service.spec.ts src/tenders/email-config-migration-sql.spec.tscd /home/vicolab/projects/tessera-ctl && pnpm --filter @tessera/api exec vitest run src/tenders
Alle Tests im Bereich `src/tenders` laufen gruen. Der Fall "zwei Postfaecher
desselben Mandanten werden beide abgeholt" existiert und besteht. Der Fall "ein
kaputtes Postfach blockiert die anderen nicht" existiert und besteht. Die
Migrationsdatei wird textuell geprueft.
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| Browser -> API (`/modules/tender-radar/email-config`) | Ab sofort erreicht jeder Nutzer mit Modulfreigabe diese Endpunkte, nicht mehr nur Administratoren. Besitzangaben aus der Anfrage sind grundsaetzlich nicht vertrauenswuerdig. |
| API -> Datenbank (`TenderEmailConfig`) | Traegt verschluesselte Postfach-Zugangsdaten. |
| Plattform-Zeitplan -> fremdes Postfach | Ausgehende Verbindung an eine vom Nutzer angegebene Adresse. |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
| T-17-01 | Elevation of Privilege | `GET/PUT /email-config` | high | mitigate | Besitzer und Mandant kommen ausschliesslich aus `extractTriageContext(req)`; die DTO traegt kein Besitzer- und kein Mandantenfeld. Test: ein zweiter Nutzer liest nie die Zeile des ersten. |
| T-17-02 | Information Disclosure | Antwort von `GET /email-config` | high | mitigate | `EMAIL_CONFIG_SAFE_SELECT` schliesst den verschluesselten Zugangsdatenblock weiterhin aus; die Antwort traegt nur `hasPassword`. Bestehender Test bleibt aktiv. |
| T-17-03 | Information Disclosure | Protokollausgabe im Abruf | medium | mitigate | Die neue Warnmeldung nennt nur Zeilen-`id` und Besitzer-Kennung, nie Benutzername, Passwort oder Postfachadresse. |
| T-17-04 | Denial of Service | Abruf ueber mehrere Postfaecher | medium | mitigate | Fehlerbehandlung je Postfach bleibt erhalten — ein haengendes Postfach blockiert die uebrigen nicht. Test deckt den Fall ab. |
| T-17-05 | Tampering | Hand geschriebene Migration | high | mitigate | Reihenfolge (befuellen vor loeschen, Pflicht nach Befuellung) wird textuell getestet; Anwendung zuerst nur lokal, Ausrollen auf den Testserver bleibt Nutzeraktion. |
| T-17-06 | Elevation of Privilege | Wegfall der Rollenpruefung an den Postfach-Endpunkten | medium | accept | Beabsichtigt (D-01): das Postfach ist eine Nutzereinstellung. Der Zugang bleibt ueber die Modulfreigabe aus Phase 15 gedeckelt. |
| T-17-SC | Tampering | Paketinstallationen | low | accept | Diese Phase installiert kein neues Paket — keine Lieferketten-Pruefung noetig. |
1. `pnpm --filter @tessera/api exec vitest run src/tenders` — vollstaendig gruen.
2. `pnpm --filter @tessera/api type-check` und `pnpm --filter @tessera/web type-check` — fehlerfrei.
3. `npx prisma migrate status` im API-Verzeichnis — keine offene Migration.
4. Indexliste von `TenderEmailConfig` enthaelt einen eindeutigen Index auf dem
Besitzerfeld und keinen mehr auf dem Mandantenfeld.
5. Browser-Gegenprobe mit zwei Konten desselben Mandanten (siehe `human-check`
in Task 2).
- Zwei Nutzer desselben Mandanten haben je eine eigene Postfach-Zeile.
- Ein Nutzer sieht ueber die API ausschliesslich sein eigenes Postfach.
- Der Abruf holt in einem Durchlauf alle aktiven Postfaecher; ein Ausfall
blockiert die anderen nicht.
- Eine vorhandene Postfach-Zeile hat nach der Migration einen Besitzer.
- `/modules/tender-radar/my-sources` ist fuer jeden Nutzer mit Modulfreigabe
erreichbar und zeigt dessen Postfach.
- `Tender` bleibt unveraendert ohne Mandantenfeld und ohne RLS (D-05).