Die drei human-check-Punkte aus Phase 17 waren am 2026-08-12 offen geblieben,
weil in der Ausfuehrungssitzung kein Browser-Tool verfuegbar war. Sie wurden
jetzt gegen frisch gebaute Images aus main (79015dd) mit echtem Chrome und
leerer Datenbank durchgeklickt.
#7 (17-01 Task 2): nutzer1 speichert sein Postfach, die Werte ueberleben den
Reload; nutzer2 sieht ein leeres Formular statt der Werte von nutzer1. Danach
zwei TenderEmailConfig-Zeilen mit je eigenem userId.
#8 (17-03 Task 1): Meine Quellen zeigt alle drei Abschnitte, ein eigener
RSS-Feed erscheint sofort mit Entfernen-Knopf, der plattformweite service-bund
steht darunter ohne, und das Digest-Intervall "Woechentlich" ueberlebt den
Reload.
#9 (17-03 Task 2): USER sieht auf /settings keine Bedienelemente, nur Hinweis
und Verweis; SUPER_ADMIN sieht Abrufintervall und plattformweite Feeds, aber
kein Postfach und keine Benachrichtigung mehr. Das Zahnrad fuehrt in beiden
Faellen nach Meine Quellen.
Zusaetzlich am laufenden Server gemessen, weil eine ausgeblendete Schaltflaeche
kein Beweis fuer eine serverseitige Sperre ist: als nutzer2 liefert GET
/rss-feeds nur den plattformweiten Feed, DELETE auf den plattformweiten wie auf
den fremden Feed antwortet 404 ohne die Zeile anzufassen, und POST mit
scope=platform wird mit 403 abgewiesen.
WINDOWS.md #7/#8/#9 auf fixed (open_count 9 -> 6), Verifikationsbericht mit
Nachtrag und Screenshots auf passed, ROADMAP auf Complete, STATE nachgezogen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
24 KiB
phase, verified, reverified, status, score, behavior_unverified, overrides_applied, human_verification, human_verification_completed
| phase | verified | reverified | status | score | behavior_unverified | overrides_applied | human_verification | human_verification_completed | |||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 17-eigene-ausschreibungs-quellen-je-nutzer | 2026-08-12T15:10:00Z | 2026-09-07T07:55:00Z | passed | 7/7 must-haves verified | 0 | 0 |
|
Phase 17: Eigene Ausschreibungs-Quellen je Nutzer — Verification Report
Phase Goal: Jeder Nutzer speist seine eigenen Ausschreibungs-Quellen ein — eigenes Alert-Postfach und eigene RSS-Feeds statt einer gemeinsamen Konfiguration. Die Einstellungsseite trennt danach sauber nach Zustaendigkeit: Plattform-Administration (Abrufintervall) bleibt Admin-Sache, Quellen und Digest-Intervall gehoeren dem Nutzer.
Verified: 2026-08-12 — re-verifiziert im Browser: 2026-09-07 Status: passed Re-verification: Yes — die drei offenen Browser-Gegenproben wurden am 2026-09-07 nachgeholt (siehe Nachtrag am Ende)
Summary Verdict
The phase goal is achieved in the codebase. All backend and frontend mechanisms described in 17-CONTEXT.md (D-01..D-05), all five ROADMAP requirements (SRC-01..SRC-05), and all seven ROADMAP Success Criteria are implemented, migrated locally, and covered by 362/362 API src/tenders tests + 58/58 web tender-radar tests, both re-run fresh in this verification (not taken from SUMMARY claims). The one hard scope fence (D-05: Tender stays platform-global) holds — verified directly against the live database schema, not just against source comments. Test quality on inspection is genuinely good: hand-written expectations, evaluating Prisma fakes for ownership/delete-protection, no tautological "expectation built from the same helper as the code under test" pattern found.
Stand 2026-08-12 konnte die Phase nicht auf passed gesetzt werden, weil drei human-check-Punkte aus den Plaenen (17-01 Task 2, 17-03 Task 1, 17-03 Task 2) nie in einem echten Browser ausgefuehrt worden waren — offen ausgewiesen in den SUMMARYs und in WINDOWS.md (#7/#8/#9). Die programmatischen Pruefungen liefen damals schon alle gruen; unbewiesen blieb allein der tatsaechliche Durchklick (Formular-Vorbefuellung, Speichern-und-Neuladen, Rollensichtbarkeit im echten Next.js-Router, Navigation ueber das Zahnrad).
Am 2026-09-07 wurden genau diese drei Punkte im echten Browser nachgeholt und alle drei bestanden — Aufbau, Beobachtungen und Belege stehen im Nachtrag am Ende dieses Berichts. WINDOWS.md #7/#8/#9 stehen auf fixed. Damit wechselt die Phase auf passed.
Goal Achievement
Observable Truths (ROADMAP Success Criteria, SC 1–7)
| # | Truth | Status | Evidence |
|---|---|---|---|
| 1 | Zwei Nutzer desselben Mandanten koennen gleichzeitig je ein eigenes Alert-Postfach anbinden; keiner sieht/ueberschreibt das des anderen | ✓ VERIFIED (mechanism) / see human-check | TenderEmailConfig.userId @unique confirmed live in DB (TenderEmailConfig_userId_key, no _tenantId_key). getConfigForApi(userId)/saveConfig({userId,tenantId}) scoped strictly by userId from extractTriageContext(req), never DTO. Unit + IDOR-shaped controller test pass. Real two-account browser click-through NOT run (WINDOWS.md #7). |
| 2 | Nutzer legt eigene RSS-Feeds an, sieht eigene + plattformweite; service.bund.de bleibt fuer alle aktiv | ✓ VERIFIED | TenderRssFeedSource.userId/tenantId nullable, @@unique([userId,url]) confirmed live in DB. listForUser returns OR:[{userId:null},{userId}]. Live DB query confirms exactly one row: service.bund.de, isActive=true, userId/tenantId empty — unchanged since Phase 14. |
| 3 | Kein Nutzer kann fremden/plattformweiten Feed loeschen oder plattformweiten Feed anlegen | ✓ VERIFIED | remove() is a single conditional deleteMany (id AND (userId=caller OR (isAdmin AND userId=null))) — no TOCTOU window, ownership comparison lives in the DB condition. NotFoundException on no match (never confirms existence). POST .../rss-feeds with scope:'platform' requires `role===ADMIN |
| 4 | Abruf holt alle Postfaecher/Feeds in einem Durchlauf; Ausfall einer Quelle blockiert die anderen nicht | ✓ VERIFIED | EmailAlertAdapter.fetchTenders: findMany({where:{isActive:true}}) (unwrapped, cross-tenant, deliberate — comment intact), for loop with per-row try/catch. RssAdapter.fetchTenders: same shape, per-feed try/catch. Both confirmed unchanged in code and covered by new multi-row/catch-per-row tests (hand-verified, not tautological — see Test Quality section). |
| 5 | /modules/tender-radar/my-sources zeigt Postfach, eigene Feeds, Benachrichtigungsintervall an einer Stelle |
✓ VERIFIED (mechanism) / see human-check | my-sources/page.tsx renders EmailAlertConfigForm + RssFeedListForm scope="personal" + DigestIntervalForm in sequence, gated only by module access (no admin whitelist change needed — nested route). Web tests (my-sources.test.tsx, 5 tests) pass. Real browser click-through NOT run (WINDOWS.md #8). |
| 6 | /modules/tender-radar/settings nur Abrufintervall + plattformweite Feeds; normaler Nutzer sieht keine fehlschlagenden Bedienelemente |
✓ VERIFIED (mechanism) / see human-check | settings/page.tsx: three-state display gate (user===null → placeholder, isAdmin → content, else → hint+link). Mailbox/notification sections physically removed from this file. settings-roles.test.tsx (5 tests) confirms USER sees neither section heading, ADMIN/SUPER_ADMIN see both, unresolved role shows neither. Server-side enforcement independently confirmed (@UseModule + inline admin check). Real browser click-through NOT run (WINDOWS.md #9). |
| 7 | Tender bleibt unveraendert ohne Mandantenfeld und ohne RLS (D-05) |
✓ VERIFIED | Live \d "Tender" shows no tenantId column — only the pre-existing, nullable ownerTenantId (Phase 14, D-13, reused by design per plan's own D-06 decision, not a new mechanism). No CREATE POLICY/RLS migration touches Tender. grep -r forTenant across tenders/ finds only explanatory comments ("must NEVER be wrapped in forTenant()"), zero actual wrapping calls. |
Score: 7/7 truths verified as implemented mechanisms. 3 of the 7 additionally carry an outstanding real-browser confirmation step (SC 1, 5, 6) — these are not counted as failed (the mechanism is verified), but are surfaced below as required human verification per the routing rules (a ⚠️/human-check item takes precedence over passed).
Requirements Coverage (SRC-01..SRC-05)
| Requirement | Description | Status | Evidence |
|---|---|---|---|
| SRC-01 | Jeder Nutzer bindet eigenes Alert-Postfach an; ein Mandant kann mehrere Postfaecher haben (D-01) | ✓ SATISFIED | Schema/migration/service/controller all confirmed live; TenderEmailConfig.userId @unique, tenantId plain. |
| SRC-02 | RSS-Feeds haben einen Besitzer; Feeds ohne Besitzer bleiben plattformweit, admin-gepflegt (D-02) | ✓ SATISFIED | Schema/migration/service confirmed live; delete-protection + 20-feed cap + SSRF guard on both create paths confirmed by reading tender-rss-feed.service.ts directly. |
| SRC-03 | Zeitgesteuerter Abruf holt weiterhin alle Quellen in einem Durchlauf; kaputte Quelle blockiert andere nicht | ✓ SATISFIED | Both adapters confirmed unwrapped, per-row try/catch intact. |
| SRC-04 | Nutzereigene Einstellungen liegen auf einer eigenen, fuer jeden Modulnutzer erreichbaren Seite (D-04) | ✓ SATISFIED (mechanism) | /my-sources page confirmed to render all three sections; gear icon confirmed pointed at /my-sources. Real navigation click NOT run (WINDOWS.md #8/#9). |
| SRC-05 | Verbleibende Administrationseinstellungen sind fuer normale Nutzer nicht sichtbar; Rollenpruefung greift in UI wie API (D-03) | ✓ SATISFIED (mechanism) | Display-gate confirmed in code + tests; server-side @UseModule/inline-admin-check confirmed independently. Real browser role-switch NOT run (WINDOWS.md #9). |
REQUIREMENTS.md traceability table (| SRC-01 | Phase 17 | Complete | … | SRC-05 | Phase 17 | Complete |) matches this assessment. No orphaned SRC requirements found.
Required Artifacts
| Artifact | Expected | Status | Details |
|---|---|---|---|
apps/api/prisma/migrations/20260812100000_tender_email_config_per_user/migration.sql |
Backfill-then-cutover migration | ✓ VERIFIED | Read in full; correct order (add nullable → backfill → delete unowned → drop old unique → set NOT NULL → new unique). Applied locally, prisma migrate status clean. |
apps/api/src/tenders/email-config-migration-sql.spec.ts |
Text-only order proof | ✓ VERIFIED | 6 tests, all assert on real file content/ordering, not vacuous. |
apps/web/src/app/(portal)/modules/tender-radar/my-sources/page.tsx |
Full 3-section user page | ✓ VERIFIED | Renders EmailAlertConfigForm + RssFeedListForm scope="personal" + DigestIntervalForm, admin-only link to settings. |
apps/api/prisma/migrations/20260812110000_tender_rss_feed_owner/migration.sql |
Nullable owner-column migration | ✓ VERIFIED | Read in full; no backfill needed (nullable = today's state), correct index swap. Applied locally. |
apps/api/src/tenders/rss-feed-migration-sql.spec.ts |
Text-only order proof | ✓ VERIFIED | 6 tests present and passing. |
apps/web/.../my-sources/my-sources.test.tsx |
3-section coverage test | ✓ VERIFIED | 5 tests, passing. |
apps/web/.../settings/settings-roles.test.tsx |
Role-gate coverage test | ✓ VERIFIED | 5 tests, passing, checks rendered DOM not internal state. |
apps/web/.../settings/components/DigestIntervalForm.tsx |
Extracted, behavior-unchanged component | ✓ VERIFIED | Confirmed used identically on both pages. |
Key Link Verification
| From | To | Via | Status | Details |
|---|---|---|---|---|
TenderEmailConfig.userId |
extractTriageContext(req).userId |
GET/PUT email-config | ✓ WIRED | Confirmed in tenders.controller.ts — userId/tenantId never read from body/query. |
TenderEmailConfig.tenantId |
EmailAlertAdapter.extractCandidates(..., cfg.tenantId, ...) |
poll fan-out | ✓ WIRED | records.push(...this.extractCandidates(messages, cfg.tenantId, fetchedAt)) confirmed in source. |
GET/PUT /email-config route position |
before @Get(':id') |
route-order pitfall | ✓ WIRED | Confirmed: all static routes (source-config, rss-feeds, email-config, coverage, denylisted-portals, triage, saved-searches) precede @Get(':id') at line 582. |
TenderRssFeedSource.userId |
extractTriageContext(req).userId |
POST/DELETE rss-feeds | ✓ WIRED | Confirmed — DTO carries no ownership field. |
TenderRssFeedSource.tenantId |
RawTenderRecord.ownerTenantId |
RssAdapter.fetchTenders |
✓ WIRED | if (feed.tenantId) { for (const record of feedRecords) record.ownerTenantId = feed.tenantId; } confirmed in source. |
TendersModule.onModuleInit() |
service.bund.de seed | idempotent find-then-create | ✓ WIRED | seedServiceBundRssFeed() in tenders.seed.ts — findFirst({where:{userId:null,url:...}}) then create — confirmed, matches live DB (exactly 1 row). |
RssFeedListForm(scope) |
POST /rss-feeds with scope |
dual-purpose component | ✓ WIRED | createRssFeed(payload, scope) confirmed passing scope into request body; server re-checks admin role independently. |
useAuthStore().user.role |
admin section visibility | display-only gate | ✓ WIRED | Confirmed in both settings/page.tsx and my-sources/page.tsx; user===null renders neither state (no flash). |
Zahnrad in tender-radar/page.tsx |
/modules/tender-radar/my-sources |
universal entry point | ✓ WIRED | href="/modules/tender-radar/my-sources" confirmed at line 84. |
Data-Flow Trace (Level 4)
| Artifact | Data Variable | Source | Produces Real Data | Status |
|---|---|---|---|---|
TenderEmailConfig rows |
userId, tenantId |
Live Postgres (172.19.0.2) | Confirmed via psql \d and index listing |
✓ FLOWING |
TenderRssFeedSource rows |
url, label, isActive, userId, tenantId |
Live Postgres | Confirmed: exactly 1 row, service.bund.de, platform-wide, active | ✓ FLOWING |
Tender schema |
column list | Live Postgres | Confirmed: no tenantId column, only pre-existing ownerTenantId |
✓ FLOWING (negative check — absence confirmed) |
Behavioral Spot-Checks
| Behavior | Command | Result | Status |
|---|---|---|---|
API src/tenders full suite (independent re-run, not from SUMMARY) |
pnpm --filter @tessera/api exec vitest run src/tenders |
28 files, 362 tests passed | ✓ PASS |
| API type-check | pnpm --filter @tessera/api type-check |
clean | ✓ PASS |
| Web type-check | pnpm --filter @tessera/web type-check |
clean | ✓ PASS |
| Web tender-radar test suite | pnpm --filter @tessera/web exec vitest run "src/app/(portal)/modules/tender-radar" |
10 files, 58 tests passed | ✓ PASS |
| Prisma migration status | npx prisma migrate status (via container-IP DATABASE_URL) |
"Database schema is up to date!" — 29 migrations | ✓ PASS |
TenderEmailConfig index list |
live psql query |
_userId_key present, _tenantId_key absent |
✓ PASS |
TenderRssFeedSource index list |
live psql query |
_userId_url_key present, old _url_key absent |
✓ PASS |
Tender schema, no tenant column |
live psql \d "Tender" |
no tenantId; ownerTenantId unchanged |
✓ PASS |
i18n key parity, tenderRadar namespace |
node key-diff script | 239/239 keys match de/en | ✓ PASS |
| Backlog item moved to completed | ls .planning/todos/completed/... |
file present, pending copy absent | ✓ PASS |
| Git commits exist | git log --oneline -- apps/api/src/tenders ... |
all 9 task commits found (05b1d29, 55ceb24, adb72f6, 9616155, 7dee116, 4100bb5, 150046e, 809afbc) |
✓ PASS |
Probe Execution
Not applicable — no scripts/*/tests/probe-*.sh-style probes declared or referenced by this phase's plans.
Test Quality Spot-Check (requested item 7)
Inspected tender-rss-feed.service.spec.ts, email-alert.adapter.spec.ts, RssFeedListForm.test.tsx, settings-roles.test.tsx, email-config-migration-sql.spec.ts in full.
- No tautological "expectation built from the production helper" pattern found. Every test file that could tempt this (in-memory Prisma fakes, next-intl translation stubs) instead hand-writes expected values and hand-writes the mock translation table separately from the component/message files under test — explicitly called out in comments, e.g.
RssFeedListForm.test.tsxline 19: "Text is hand-written here, NOT derived from the component/message files under test — a test that builds its own expectation from the same helper the component uses proves nothing." - No status-code-as-proof pattern found. Delete-protection tests assert both the thrown exception type/instance AND the row-count side effect (
expect(prisma.__rows.size).toBe(1)after a rejected delete) — not merely that an HTTP status or exception class was thrown. - The in-memory Prisma fakes genuinely evaluate
whereclauses (matchesWherewith recursiveOR/ANDhandling) rather than ignoring the condition and always "succeeding" — the test file itself documents why this matters ("a double that ignoredwhereand always 'succeeded' would only fake the protection, not test it"). - One minor, non-blocking observation:
createForUser's 20-feed cap check (count()thencreate()) is not atomic — a genuine race between two concurrent requests from the same user could both pass the count check before either creates a row, allowing a transient overshoot of the cap by a small margin. This is a soft rate-limit, not a security boundary (ownership/deletion protection IS atomic via the single conditionaldeleteMany), and is not called out as a threat in the plan's own STRIDE table, so it is noted here as an observation, not a gap.
Anti-Patterns Found
None. Grep for TBD|FIXME|XXX|TODO|HACK|PLACEHOLDER (and case-insensitive placeholder/not yet implemented) across all 15 phase-modified backend/frontend files returned only pre-existing, benign uses of the word "placeholder" (documenting the portals: ['rss'] symbolic constant, and the loading-skeleton UI state) — none indicate unfinished work.
Human Verification Required — am 2026-09-07 vollstaendig nachgeholt (alle drei bestanden)
-
17-01 Task 2 human-check (WINDOWS.md #7, open) Test: As a normal USER-role account with module access, open
/modules/tender-radar/my-sources, fill and save the mailbox form, reload — values persist. Then log in as a second account of the same tenant — the form is empty, not pre-filled with the first user's values. Expected: Each user sees and edits only their own mailbox. Why human: Requires two live authenticated browser sessions; the underlyinguserId-scoped query and IDOR-safe controller wiring is verified by code/DB inspection and unit tests, but the actual UX round-trip was never clicked through. -
17-03 Task 1 human-check (WINDOWS.md #8, open) Test: As a normal user, open
/modules/tender-radar/my-sources— three sections visible. Add a personal RSS feed — it appears immediately in the editable list; the service.bund.de feed appears below without a remove button. Set the digest interval to "Woechentlich", reload — value persists. Expected: All three sections work end-to-end in a real browser. Why human: No browser tool was available in the executing session. -
17-03 Task 2 human-check (WINDOWS.md #9, open) Test: As a USER-role account, navigate directly to
/modules/tender-radar/settings— no controls, only the hint + link. As an ADMIN account, same URL — poll interval + platform feeds show, mailbox/notification sections do not. The gear icon on the module page leads to "Meine Quellen" in both cases. Expected: Role-based visibility and navigation work identically to the passing component tests, in the real Next.js router. Why human: No browser tool was available in the executing session.
All three items are honestly recorded as open/unrun-verify in .planning/WINDOWS.md (entries #7, #8, #9) and in each plan's own SUMMARY.md — none of the three SUMMARYs claim these were passed. STATE.md's stopped_at field also explicitly flags "Browser-Gegenprobe aller drei Plaene ... stehen als offene Punkte in WINDOWS.md" as outstanding before /gsd-ship. This verification confirms that disclosure is accurate and does not overstate what has actually been proven.
Gaps Summary
No implementation gaps found. All must-haves at the mechanism level (schema, migration, service, controller, adapter, frontend wiring, tests, i18n, requirements traceability, backlog closure) are verified directly against the live codebase and a live local database — not inferred from SUMMARY claims. The phase's own scope fence (D-05, Tender stays platform-global) is independently confirmed intact.
Der einzige offene Punkt war die Gruppe der drei echten Browser-Durchlaeufe — eine Luecke in der Vollstaendigkeit der Pruefung, keine Luecke in der Umsetzung. Sie wurde am 2026-09-07 geschlossen (siehe Nachtrag); es sind keine offenen Punkte mehr verzeichnet.
Verified: 2026-08-12 Verifier: Claude (gsd-verifier)
Nachtrag: Browser-Gegenprobe vom 2026-09-07
Die drei am 2026-08-12 offen gebliebenen human-check-Punkte wurden nachgeholt. Aufbau: frisch
gebaute Images aus main (79015dd), lokaler Docker-Stack, leere Datenbank, echter Chrome
ueber Playwright. Testdaten ueber die Oberflaeche angelegt — Modul im Marktplatz aktiviert,
Freigabe in der Matrix an die Standardgruppe erteilt, zwei USER-Konten (nutzer1, nutzer2)
im selben Mandanten.
WINDOWS #7 — eigenes Postfach je Nutzer (17-01 Task 2) — BESTANDEN
| Schritt | Beobachtung |
|---|---|
nutzer1 oeffnet /modules/tender-radar/my-sources |
Postfach-Formular leer, Hinweis „Noch nicht gespeichert" |
| Postfach ausfuellen und speichern | „Einstellungen gespeichert.", Hinweis verschwindet |
| Seite neu laden | imap.nutzer1.test, alerts@nutzer1.test, Benutzername nutzer1-postfach stehen wieder da |
nutzer2 oeffnet dieselbe Seite |
Formular leer, Hinweis „Noch nicht gespeichert" — nicht die Werte von nutzer1 |
nutzer2 speichert sein eigenes Postfach |
Zweite Zeile entsteht, erste bleibt unveraendert |
Datenbank danach: zwei TenderEmailConfig-Zeilen, je ein eigener userId, beide mit gefuelltem
encryptedInboxCreds. Beleg: uat-2026-09-07/w7-nutzer2-leer.png.
WINDOWS #8 — Meine Quellen, alle drei Abschnitte (17-03 Task 1) — BESTANDEN
| Schritt | Beobachtung |
|---|---|
| Seitenaufbau | Drei Abschnitte: „Mein Postfach", „Meine Feeds", „Benachrichtigung" |
| Eigenen RSS-Feed anlegen | „Mein Testfeed" erscheint sofort in der eigenen Liste, mit Entfernen-Knopf |
| Plattformweiter Feed | service-bund steht darunter unter „Diese Feeds werden von der Administration gepflegt und gelten fuer alle." — ohne Entfernen-Knopf |
| Digest-Intervall auf „Woechentlich", dann neu laden | Wert steht noch (TenderNotificationPref.digestInterval = weekly, nur fuer nutzer1) |
| Verweis auf die Administrationsseite | Fuer das USER-Konto nicht vorhanden |
nutzer2 sieht in „Meine Feeds" nur „Noch keine RSS-Feeds hinterlegt" plus den plattformweiten
Feed — der Feed von nutzer1 taucht bei ihm nicht auf. Beleg:
uat-2026-09-07/w8-nutzer1-my-sources.png.
WINDOWS #9 — Rollentrennung der Einstellungsseite (17-03 Task 2) — BESTANDEN
| Rolle | /modules/tender-radar/settings direkt aufgerufen |
|---|---|
USER (nutzer1) |
Keine Bedienelemente. Nur „Diese Seite verwaltet plattformweite Einstellungen und ist Administratoren vorbehalten." plus Verweis „Meine Quellen →" |
SUPER_ADMIN (admin) |
Abrufintervall (60 Min.), Aktiv-Schalter, Speichern, plus plattformweite RSS-Feeds mit Entfernen-Knopf. Postfach und Benachrichtigung sind auf dieser Seite nicht mehr vorhanden |
Das Zahnrad auf der Modulseite fuehrt in beiden Faellen nach /modules/tender-radar/my-sources
— als Link angeklickt, nicht nur im Markup gelesen. Belege:
uat-2026-09-07/w9-user-settings.png, uat-2026-09-07/w9-admin-settings.png.
Zusatzbeleg: Schutz gegen fremde und plattformweite Feeds am laufenden Server
Der Verifikationsbericht hatte SC 3 aus dem Quelltext belegt. Die Oberflaeche blendet den
Entfernen-Knopf nur aus — das ist kein Beweis, dass der Server ihn auch verweigert. Deshalb
zusaetzlich gegen die laufende API gemessen, angemeldet als nutzer2:
| Aufruf | Antwort | Wirkung |
|---|---|---|
GET /modules/tender-radar/rss-feeds |
200, genau ein Eintrag: service-bund, isPlatformWide: true |
Der Feed von nutzer1 ist fuer nutzer2 unsichtbar |
DELETE .../rss-feeds/<plattformweiter Feed> |
404 | Zeile bleibt bestehen |
DELETE .../rss-feeds/<Feed von nutzer1> |
404 | Zeile bleibt bestehen |
POST .../rss-feeds mit scope: "platform" |
403 „Nur Administratoren duerfen plattformweite RSS-Feeds anlegen." | Kein Eintrag entsteht |
Der Bestand war danach unveraendert: eine plattformweite Zeile, eine Zeile von nutzer1.
Ergebnis
Alle sieben Erfolgskriterien sind jetzt nicht nur als Mechanismus, sondern auch im laufenden
System belegt. WINDOWS.md #7, #8 und #9 stehen auf fixed. Der Phasenstatus wechselt von
human_needed auf passed.
Nachtrag verfasst: 2026-09-07