Files
tessera-ctl/.planning/phases/17-eigene-ausschreibungs-quellen-je-nutzer/17-03-PLAN.md
T
2026-08-12 08:26:00 +02:00

364 lines
22 KiB
Markdown

---
phase: 17-eigene-ausschreibungs-quellen-je-nutzer
plan: 03
type: execute
wave: 3
depends_on: ["17-01", "17-02"]
files_modified:
- apps/web/src/app/(portal)/modules/tender-radar/my-sources/page.tsx
- apps/web/src/app/(portal)/modules/tender-radar/my-sources/my-sources.test.tsx
- apps/web/src/app/(portal)/modules/tender-radar/settings/page.tsx
- apps/web/src/app/(portal)/modules/tender-radar/settings/settings-roles.test.tsx
- apps/web/src/app/(portal)/modules/tender-radar/settings/components/RssFeedListForm.tsx
- apps/web/src/app/(portal)/modules/tender-radar/settings/components/RssFeedListForm.test.tsx
- apps/web/src/app/(portal)/modules/tender-radar/settings/components/DigestIntervalForm.tsx
- apps/web/src/app/(portal)/modules/tender-radar/page.tsx
- apps/web/src/lib/tender-radar-api.ts
- apps/web/src/messages/de.json
- apps/web/src/messages/en.json
- .planning/todos/pending/2026-08-11-tender-radar-einstellungen-mischen-rollen.md
autonomous: true
requirements: [SRC-04, SRC-05]
user_setup: []
estimate:
tokens: 55000
raw_tokens: 55000
tasks: 3
confidence: low
must_haves:
truths:
- "Auf `/modules/tender-radar/my-sources` findet ein normaler Nutzer alles, was ihm gehoert: sein Postfach, seine Feeds, sein Benachrichtigungsintervall (D-01, D-02, D-04)."
- "Auf `/modules/tender-radar/settings` bleibt nur Administrationskram: Abrufintervall der oeffentlichen Quelle und die plattformweiten Feeds (D-03)."
- "Ein normaler Nutzer sieht auf der Administrationsseite keine Bedienelemente mehr, die beim Speichern in eine Fehlermeldung laufen (offener Punkt 5)."
- "Das Zahnrad auf der Modulseite fuehrt jeden Nutzer dorthin, wo er etwas einstellen darf."
- "Alle neuen Beschriftungen liegen in beiden Sprachdateien vor."
artifacts:
- apps/web/src/app/(portal)/modules/tender-radar/my-sources/page.tsx
- apps/web/src/app/(portal)/modules/tender-radar/settings/components/DigestIntervalForm.tsx
- apps/web/src/app/(portal)/modules/tender-radar/my-sources/my-sources.test.tsx
- apps/web/src/app/(portal)/modules/tender-radar/settings/settings-roles.test.tsx
key_links:
- "RssFeedListForm(scope) <-> POST /rss-feeds mit `scope` — dieselbe Komponente bedient beide Seiten, die Berechtigung entscheidet der Server."
- "useAuthStore().user.role <-> Sichtbarkeit der Administrationsabschnitte — reine Anzeigefrage, die verbindliche Pruefung bleibt im Server."
- "Zahnrad in tender-radar/page.tsx <-> /modules/tender-radar/my-sources — der Weg, den jeder Nutzer gehen darf."
---
<objective>
Die Oberflaeche wird nach Zustaendigkeit getrennt. Bisher stehen vier Abschnitte
untereinander auf einer Seite, die drei verschiedenen Zustaendigkeiten gehoeren —
wer nur seine Benachrichtigungen auf woechentlich stellen will, scrollt an
Abrufintervallen, Feed-Adressen und Postfach-Zugangsdaten vorbei und sieht
Bedienelemente, die er gar nicht bedienen darf.
Nach diesem Plan gibt es zwei Seiten:
- **Meine Quellen** (`/modules/tender-radar/my-sources`, fuer jeden mit
Modulzugang): mein Postfach, meine Feeds, mein Benachrichtigungsintervall.
- **Einstellungen** (`/modules/tender-radar/settings`, nur Administration):
Abrufintervall der oeffentlichen Quelle und die plattformweiten Feeds.
Purpose: D-03, D-04 und den urspruenglichen Anlass des Backlog-Punkts abschliessen.
Output: Vollstaendige Nutzerseite, reduzierte und rollengepruefte
Administrationsseite, angepasstes Zahnrad, Beschriftungen in beiden Sprachen.
**Entscheidung zum offenen Punkt 5 (Rollenpruefung).** Die Administrationsseite
prueft die Rolle des angemeldeten Kontos aus dem bereits vorhandenen
Anmelde-Speicher im Browser und zeigt einem normalen Nutzer statt der
Bedienelemente einen kurzen Satz mit einem Verweis auf "Meine Quellen".
Begruendung: Die verbindliche Absicherung liegt und bleibt im Server — die
Endpunkte fuer das Abrufintervall und fuer plattformweite Feeds sind dort
rollengeprueft. Die Pruefung im Browser ist ausschliesslich eine Anzeigefrage:
sie verhindert, dass jemand Knoepfe sieht, die beim Speichern in eine
Fehlermeldung laufen. Genau das war der Anlass des Backlog-Punkts.
</objective>
<execution_context>
@$HOME/.claude/gsd-core/workflows/execute-plan.md
@$HOME/.claude/gsd-core/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/17-eigene-ausschreibungs-quellen-je-nutzer/17-CONTEXT.md
@.planning/phases/17-eigene-ausschreibungs-quellen-je-nutzer/17-01-SUMMARY.md
@.planning/phases/17-eigene-ausschreibungs-quellen-je-nutzer/17-02-SUMMARY.md
@.planning/todos/pending/2026-08-11-tender-radar-einstellungen-mischen-rollen.md
@apps/web/src/app/(portal)/modules/tender-radar/settings/page.tsx
@apps/web/src/app/(portal)/modules/tender-radar/settings/components/RssFeedListForm.tsx
@apps/web/src/lib/tender-radar-api.ts
@apps/web/src/lib/stores/auth-store.ts
</context>
<tasks>
<task type="tracer" tdd="true">
<name>Task 1: "Meine Quellen" wird vollstaendig — Postfach, eigene Feeds, Benachrichtigung</name>
<files>
apps/web/src/lib/tender-radar-api.ts,
apps/web/src/app/(portal)/modules/tender-radar/settings/components/RssFeedListForm.tsx,
apps/web/src/app/(portal)/modules/tender-radar/settings/components/DigestIntervalForm.tsx,
apps/web/src/app/(portal)/modules/tender-radar/my-sources/page.tsx,
apps/web/src/messages/de.json,
apps/web/src/messages/en.json
</files>
<read_first>
apps/web/src/app/(portal)/modules/tender-radar/settings/page.tsx,
apps/web/src/app/(portal)/modules/tender-radar/settings/components/RssFeedListForm.tsx,
apps/web/src/lib/tender-radar-api.ts (Zeilen 370-560)
</read_first>
<behavior>
- Auf "Meine Quellen" stehen drei Abschnitte untereinander: Mein Postfach, Meine Feeds, Benachrichtigung.
- Die Feed-Liste dort zeigt ausschliesslich die eigenen Feeds; plattformweite Feeds erscheinen darunter als kurze, nicht bedienbare Aufzaehlung mit dem Hinweis, dass die Administration sie pflegt.
- Ein neu angelegter Feed erscheint sofort in der eigenen Liste.
- Zu einem plattformweiten Feed gibt es auf dieser Seite keinen Entfernen-Knopf.
- Die Auswahl des Benachrichtigungsintervalls speichert und quittiert wie bisher.
</behavior>
<action>
**Zugriffsschicht (`lib/tender-radar-api.ts`).** Der Typ `RssFeedSource` bekommt
das vom Server gelieferte Kennzeichen, ob der Eintrag plattformweit ist.
`createRssFeed` bekommt einen zweiten Parameter fuer den Wirkungsbereich
(`personal` oder `platform`, Vorgabe `personal`), der in den Rumpf der Anfrage
wandert. `listRssFeeds` und `deleteRssFeed` bleiben in ihrer Signatur, ihr
Verhalten aendert sich serverseitig.
**Feed-Liste (`RssFeedListForm.tsx`).** Die Komponente bekommt eine
Eigenschaft `scope` mit den Werten `personal` und `platform`. Sie laedt
weiterhin die eine Liste vom Server und teilt sie selbst auf:
- `scope="personal"`: bearbeitbar sind die eigenen Eintraege (anlegen,
entfernen). Die plattformweiten Eintraege erscheinen darunter als schlichte
Aufzaehlung ohne Knoepfe, mit einem Satz, dass die Administration sie pflegt —
so ist auf einen Blick klar, dass niemand bei null anfaengt.
- `scope="platform"`: bearbeitbar sind die plattformweiten Eintraege; angelegt
wird mit dem entsprechenden Wirkungsbereich. Eigene Eintraege anderer Nutzer
tauchen hier gar nicht auf, weil der Server sie nicht liefert.
Die Fehleranzeige bleibt wie sie ist: eine abgelehnte Adresse zeigt weiterhin
die ausfuehrliche Meldung des Servers, nicht einen selbstgebauten Ersatztext.
Die Ablehnung wegen erreichter Obergrenze (aus Plan 17-02) laeuft ueber denselben
Weg und braucht keine Sonderbehandlung.
**Benachrichtigungsintervall (`DigestIntervalForm.tsx`).** Den heute in der
Einstellungsseite eingebetteten Block (Laden beim Aufbau, Auswahlfeld
Taeglich/Woechentlich/Aus, Speichern bei Aenderung, Erfolgs- und Fehlermeldung)
unveraendert in eine eigene Komponente herausloesen. Kein Verhalten aendern,
keine neuen Beschriftungsschluessel — nur verschieben, damit beide Seiten
dieselbe Umsetzung benutzen koennen und die Einstellungsseite in Task 2 sauber
kleiner wird.
**Seite (`my-sources/page.tsx`).** Die in Plan 17-01 angelegte Seite um zwei
Abschnitte erweitern: unter "Mein Postfach" folgt "Meine Feeds"
(`RssFeedListForm` mit `scope="personal"`), darunter "Benachrichtigung"
(`DigestIntervalForm`). Aufbau, Abstaende und Trennlinien wie auf der heutigen
Einstellungsseite uebernehmen, damit nichts fremd wirkt. Fuer Konten mit
Administratorrolle unten ein Verweis auf die Administrationsseite; fuer alle
anderen erscheint dieser Verweis nicht. Die Rolle kommt aus dem vorhandenen
Anmelde-Speicher; solange dieser noch nicht befuellt ist, den Verweis
weglassen statt kurz aufblitzen zu lassen.
**Beschriftungen (`messages/de.json`, `messages/en.json`).** Alle neuen
Schluessel im Namensraum `tenderRadar` unter `mySources` anlegen und in beiden
Dateien pflegen: Seitentitel, die drei Abschnittsueberschriften, der ehrliche
Hinweis zu D-05 (Ausschreibungen aus den eigenen Quellen erscheinen auch bei
Kollegen desselben Mandanten), der Satz zu den von der Administration
gepflegten Feeds, der Verweis auf die Administrationsseite. Keine Beschriftung
nur auf Deutsch anlegen.
</action>
<verify>
<automated>cd /home/vicolab/projects/tessera-ctl &amp;&amp; pnpm --filter @tessera/web type-check</automated>
<automated>cd /home/vicolab/projects/tessera-ctl &amp;&amp; node -e "const de=require('./apps/web/src/messages/de.json'),en=require('./apps/web/src/messages/en.json');const walk=(o,p='')=>Object.entries(o).flatMap(([k,v])=>typeof v==='object'&amp;&amp;v?walk(v,p+k+'.'):[p+k]);const d=walk(de.tenderRadar),e=walk(en.tenderRadar);const miss=d.filter(k=>!e.includes(k)).concat(e.filter(k=>!d.includes(k)));if(miss.length){console.error('Fehlende Uebersetzungen:',miss);process.exit(1)}console.log('i18n vollstaendig:',d.length,'Schluessel')"</automated>
<human-check>Als normaler Nutzer `/modules/tender-radar/my-sources` oeffnen: drei Abschnitte sind sichtbar. Einen eigenen RSS-Feed anlegen — er erscheint in der eigenen Liste, der plattformweite service.bund.de-Feed steht darunter ohne Entfernen-Knopf. Das Benachrichtigungsintervall auf "Woechentlich" stellen, Seite neu laden, Wert steht noch. Kein Verweis auf die Administrationsseite sichtbar.</human-check>
</verify>
<done>
Die Typpruefung laeuft fehlerfrei, beide Sprachdateien decken denselben
Schluesselsatz ab. Ein normaler Nutzer kann auf einer Seite Postfach, eigene
Feeds und Benachrichtigungsintervall pflegen und sieht keinen Knopf, der ihm
verwehrt ist.
</done>
</task>
<task type="auto" tdd="true">
<name>Task 2: Die Administrationsseite schrumpft auf das, was Administration ist</name>
<files>
apps/web/src/app/(portal)/modules/tender-radar/settings/page.tsx,
apps/web/src/app/(portal)/modules/tender-radar/page.tsx,
apps/web/src/messages/de.json,
apps/web/src/messages/en.json
</files>
<read_first>
apps/web/src/app/(portal)/modules/tender-radar/settings/page.tsx,
apps/web/src/app/(portal)/modules/tender-radar/page.tsx (Zeilen 60-95),
apps/web/src/lib/stores/auth-store.ts
</read_first>
<behavior>
- Ein Konto mit Rolle ADMIN oder SUPER_ADMIN sieht auf der Einstellungsseite genau zwei Abschnitte: Abrufintervall und plattformweite Feeds.
- Ein Konto mit Rolle USER sieht dort keinen dieser Abschnitte, sondern einen kurzen Satz und einen Verweis auf "Meine Quellen".
- Solange die Kontodaten im Browser noch nicht geladen sind, erscheint weder das eine noch das andere, sondern ein Platzhalter — kein kurzes Aufblitzen der Administrationsabschnitte.
- Postfach und Benachrichtigungsintervall kommen auf dieser Seite nicht mehr vor.
- Das Zahnrad auf der Modulseite fuehrt fuer jeden Nutzer nach "Meine Quellen".
</behavior>
<action>
**Einstellungsseite (`settings/page.tsx`).** Die Abschnitte "E-Mail-Alerts" und
"Benachrichtigungen" ersatzlos herausnehmen — sie leben ab jetzt auf "Meine
Quellen" (D-01, D-04). Uebrig bleiben das Abrufintervall (`SourceConfigForm`,
D-03) und die plattformweiten Feeds (`RssFeedListForm` mit `scope="platform"`).
Der ganze Inhalt wird von einer Rollenpruefung umschlossen: Rolle aus dem
vorhandenen Anmelde-Speicher lesen; ist sie noch nicht bekannt, einen
Ladeplatzhalter zeigen; ist sie ADMIN oder SUPER_ADMIN, den Inhalt zeigen; sonst
einen kurzen Satz ("Diese Seite verwaltet plattformweite Einstellungen und ist
Administratoren vorbehalten.") mit einem Verweis auf `/modules/tender-radar/my-sources`.
Der Kopfkommentar der Datei wird auf den neuen Zuschnitt umgeschrieben; die
Absaetze zu den entfernten Abschnitten entfallen, ein Absatz zu Phase 17 kommt
hinzu. Beim Abschnitt der plattformweiten Feeds einen Satz ergaenzen, dass diese
fuer alle Nutzer gelten und persoenliche Feeds woanders liegen.
**Modulseite (`page.tsx`).** Das Zahnrad zeigt heute auf die
Administrationsseite. Es zeigt kuenftig auf `/modules/tender-radar/my-sources` —
das ist die Seite, die jeder Nutzer aufrufen darf, und Administratoren gehen von
dort mit einem Klick weiter. Beschriftung und Vorlesetext des Knopfes
entsprechend anpassen (neuer Schluessel, alter Schluessel bleibt fuer den Verweis
auf der Nutzerseite in Gebrauch, falls passend — sonst entfernen). Den
vorhandenen Kommentar zum absoluten Verweis (die dynamische Einstellungsroute
greift fuer dieses Modul nicht) beibehalten und auf den neuen Zielpfad
anpassen.
**Beschriftungen.** Neue Schluessel in beiden Sprachdateien anlegen: der
Hinweistext fuer Nutzer ohne Administratorrolle, der Verweistext auf "Meine
Quellen", der Zusatzsatz beim Abschnitt der plattformweiten Feeds, die neue
Zahnrad-Beschriftung. Nicht mehr benutzte Schluessel (`settings.emailSectionTitle`,
`settings.emailSectionBody`, `settings.notificationsSectionTitle` und die
Digest-Schluessel, sofern sie auf die Nutzerseite umziehen) erst entfernen,
nachdem im gesamten `apps/web/src` nachgesehen wurde, dass sie nirgends mehr
verwendet werden.
</action>
<verify>
<automated>cd /home/vicolab/projects/tessera-ctl &amp;&amp; pnpm --filter @tessera/web type-check</automated>
<automated>cd /home/vicolab/projects/tessera-ctl &amp;&amp; node -e "const de=require('./apps/web/src/messages/de.json'),en=require('./apps/web/src/messages/en.json');const walk=(o,p='')=>Object.entries(o).flatMap(([k,v])=>typeof v==='object'&amp;&amp;v?walk(v,p+k+'.'):[p+k]);const d=walk(de.tenderRadar),e=walk(en.tenderRadar);const miss=d.filter(k=>!e.includes(k)).concat(e.filter(k=>!d.includes(k)));if(miss.length){console.error('Fehlende Uebersetzungen:',miss);process.exit(1)}console.log('i18n vollstaendig:',d.length,'Schluessel')"</automated>
<human-check>Als Konto mit Rolle USER `/modules/tender-radar/settings` direkt in der Adresszeile aufrufen: es erscheinen keine Bedienelemente, sondern der Hinweis samt Verweis. Danach als Administrator dieselbe Adresse: Abrufintervall und plattformweite Feeds sind da, Postfach und Benachrichtigungsintervall nicht mehr. Auf der Modulseite fuehrt das Zahnrad in beiden Faellen nach "Meine Quellen".</human-check>
</verify>
<done>
Die Einstellungsseite traegt nur noch Abrufintervall und plattformweite Feeds
und ist fuer Konten ohne Administratorrolle leer bis auf Hinweis und Verweis.
Das Zahnrad fuehrt jeden Nutzer auf eine Seite, auf der er etwas einstellen darf.
Beide Sprachdateien decken denselben Schluesselsatz ab.
</done>
</task>
<task type="auto" tdd="true">
<name>Task 3: Tests fuer beide Seiten und der Backlog-Punkt wird zugemacht</name>
<files>
apps/web/src/app/(portal)/modules/tender-radar/my-sources/my-sources.test.tsx,
apps/web/src/app/(portal)/modules/tender-radar/settings/settings-roles.test.tsx,
apps/web/src/app/(portal)/modules/tender-radar/settings/components/RssFeedListForm.test.tsx,
.planning/todos/pending/2026-08-11-tender-radar-einstellungen-mischen-rollen.md
</files>
<read_first>
apps/web/src/app/(portal)/modules/tender-radar/settings/components/RssFeedListForm.test.tsx,
apps/web/src/app/(portal)/modules/tender-radar/settings/components/SourceConfigForm.test.tsx
</read_first>
<behavior>
- `RssFeedListForm` mit `scope="personal"`: die gelieferte Liste enthaelt einen plattformweiten und einen eigenen Eintrag; nur der eigene traegt einen Entfernen-Knopf, der plattformweite erscheint ohne.
- `RssFeedListForm` mit `scope="platform"`: umgekehrt.
- `RssFeedListForm` mit `scope="personal"` schickt beim Anlegen den Wirkungsbereich `personal` mit, mit `scope="platform"` entsprechend `platform`.
- Einstellungsseite mit Rolle USER: die Ueberschriften der Administrationsabschnitte kommen nicht vor, der Verweis auf "Meine Quellen" schon.
- Einstellungsseite mit Rolle ADMIN: die Administrationsabschnitte kommen vor.
- Einstellungsseite ohne geladene Kontodaten: weder das eine noch das andere.
- "Meine Quellen": alle drei Abschnittsueberschriften kommen vor; der Verweis auf die Administrationsseite nur bei Rolle ADMIN.
</behavior>
<action>
**Testaufbau.** Vorbild sind die vorhandenen Tests im selben Verzeichnis: die
Zugriffsschicht `@/lib/tender-radar-api` wird ersetzt, `next-intl` wird durch
eine Zuordnung von Schluessel auf Text ersetzt. Neu hinzu kommt das Ersetzen des
Anmelde-Speichers, damit die Rolle im Test gesetzt werden kann — dieselbe
Ersetzungsform wie bei den uebrigen Ersetzungen, ueber den Modulpfad des
Speichers.
**Wichtig zur Aussagekraft.** Die Pruefungen muessen an dem haengen, was der
Nutzer sieht: vorhandene beziehungsweise fehlende Beschriftungen und Knoepfe im
gerenderten Ergebnis. Nicht pruefen, ob eine Komponente "aufgerufen wurde" —
und die erwarteten Texte im Test woertlich hinschreiben, nicht aus derselben
Zuordnung ableiten, die auch die Komponente benutzt. Ein Test, der seine
Erwartung mit dem Pruefling selbst baut, beweist nichts.
**`RssFeedListForm.test.tsx`.** Bestehende Faelle auf die neue Eigenschaft
`scope` nachziehen (Vorgabe im Test ausdruecklich setzen, nicht raten) und die
drei Faelle aus dem Abschnitt "behavior" ergaenzen. Der bestehende Fall, dass die
ausfuehrliche Servermeldung bei einer gesperrten Adresse angezeigt wird, muss
unveraendert weiterlaufen.
**`settings-roles.test.tsx` (neu).** Die drei Rollenfaelle der
Einstellungsseite. Zusaetzlich ein Fall, der belegt, dass die Ueberschriften der
entfernten Abschnitte (Postfach, Benachrichtigung) auf dieser Seite nicht mehr
vorkommen.
**`my-sources.test.tsx` (neu).** Die drei Abschnitte sind da; der Verweis auf
die Administrationsseite erscheint nur bei Administratorrolle.
**Backlog-Punkt.** `2026-08-11-tender-radar-einstellungen-mischen-rollen.md`
nach `.planning/todos/completed/` verschieben und im Kopf einen kurzen Abschnitt
ergaenzen, wie er geloest wurde: nicht nur die Rollenpruefung aus der
urspruenglichen Skizze, sondern der Umbau auf nutzereigene Quellen, und die
Grundsatzfrage aus Punkt 2 ist mit "eigene nutzerseitige Modulseite" beantwortet
— das ist ab jetzt die Bauform, an der sich DKV-Fleet und kuenftige Module
orientieren.
</action>
<verify>
<automated>cd /home/vicolab/projects/tessera-ctl &amp;&amp; pnpm --filter @tessera/web exec vitest run src/app/\(portal\)/modules/tender-radar</automated>
<automated>cd /home/vicolab/projects/tessera-ctl &amp;&amp; pnpm --filter @tessera/web exec vitest run</automated>
<automated>cd /home/vicolab/projects/tessera-ctl &amp;&amp; pnpm --filter @tessera/api exec vitest run src/tenders &amp;&amp; pnpm --filter @tessera/api type-check &amp;&amp; pnpm --filter @tessera/web type-check</automated>
<automated>test ! -f .planning/todos/pending/2026-08-11-tender-radar-einstellungen-mischen-rollen.md &amp;&amp; test -f .planning/todos/completed/2026-08-11-tender-radar-einstellungen-mischen-rollen.md</automated>
</verify>
<done>
Alle Tests in Web und API laufen gruen, beide Typpruefungen sind sauber, der
Backlog-Punkt liegt im Ordner der erledigten Punkte.
</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| Browser-Anzeige -> Serverpruefung | Die Rollenpruefung im Browser ist eine Anzeigefrage; die verbindliche Pruefung liegt im Server. |
| Nutzerseite -> Endpunkte fuer plattformweite Einstellungen | Die Nutzerseite darf keinen Weg zu plattformweiten Schreibvorgaengen oeffnen. |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
| T-17-14 | Elevation of Privilege | Rollenpruefung im Browser | medium | mitigate | Die Pruefung blendet nur Bedienelemente aus. Die Endpunkte fuer Abrufintervall und plattformweite Feeds bleiben serverseitig rollengeprueft (Plan 17-02, T-17-08). Ein Test belegt, dass die Rolle USER die Abschnitte nicht zu sehen bekommt. |
| T-17-15 | Elevation of Privilege | `RssFeedListForm` mit `scope="personal"` | high | mitigate | Der Wirkungsbereich wandert als Wunsch in die Anfrage; ob er gewaehrt wird, entscheidet ausschliesslich der Server. Ein manipuliertes Frontend erreicht nichts. |
| T-17-16 | Information Disclosure | Kurzes Aufblitzen der Administrationsabschnitte vor dem Laden der Kontodaten | low | mitigate | Solange die Rolle unbekannt ist, wird ein Platzhalter gezeigt; ein Test deckt genau diesen Zustand ab. |
| T-17-17 | Repudiation | Unvollstaendige Uebersetzung | low | mitigate | Ein Pruefschritt vergleicht die Schluesselsaetze beider Sprachdateien und bricht bei Abweichung ab. |
| T-17-SC | Tampering | Paketinstallationen | low | accept | Diese Phase installiert kein neues Paket. |
</threat_model>
<verification>
1. `pnpm --filter @tessera/web exec vitest run` — vollstaendig gruen.
2. `pnpm --filter @tessera/api exec vitest run src/tenders` — vollstaendig gruen.
3. Beide Typpruefungen fehlerfrei.
4. Schluesselvergleich der beiden Sprachdateien ohne Abweichung.
5. Browser-Gegenprobe mit zwei Konten (Rolle USER und Rolle ADMIN) wie in den
`human-check`-Schritten der Tasks 1 und 2. Eine erfolgreiche Antwort des
Servers allein reicht als Nachweis nicht — es zaehlt, was auf der Seite steht.
</verification>
<success_criteria>
- "Meine Quellen" traegt Postfach, eigene Feeds und Benachrichtigungsintervall.
- Die Einstellungsseite traegt nur Abrufintervall und plattformweite Feeds.
- Ein normaler Nutzer sieht auf der Einstellungsseite keine Bedienelemente mehr,
die beim Speichern in eine Fehlermeldung laufen.
- Das Zahnrad fuehrt jeden Nutzer auf eine fuer ihn bedienbare Seite.
- Alle Beschriftungen liegen in Deutsch und Englisch vor.
- Der Backlog-Punkt ist abgeschlossen und die Grundsatzfrage darin beantwortet.
</success_criteria>
<output>
Create `.planning/phases/17-eigene-ausschreibungs-quellen-je-nutzer/17-03-SUMMARY.md` when done.
</output>