docs: close the DOE notice-link backlog item
Tessera CI/CD / Lint & Type Check (push) Successful in 44s
Tessera CI/CD / Tests (push) Successful in 54s
Tessera CI/CD / Build & Publish Images (push) Successful in 28s

Records the user's choice between the two candidate link targets (the notice
page on oeffentlichevergabe.de, not the awarding portal's own page), what was
checked before touching the adapter, and the trap in verifying it: the target
is a single-page app that answers 200 with an identical shell for any id, so
only rendered content proves the link works.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-11 13:45:44 +02:00
parent ecf7872730
commit 637866b9f2
3 changed files with 72 additions and 1 deletions
+2 -1
View File
@@ -8,7 +8,7 @@ status: complete
stopped_at: Phase 16 abgeschlossen — UAT durchgefuehrt, kritischer Sweep-Fehler gefunden und behoben (260811-f9i), Rest-Annahme A1 bewusst als offen akzeptiert stopped_at: Phase 16 abgeschlossen — UAT durchgefuehrt, kritischer Sweep-Fehler gefunden und behoben (260811-f9i), Rest-Annahme A1 bewusst als offen akzeptiert
last_updated: "2026-08-11T09:25:00.000Z" last_updated: "2026-08-11T09:25:00.000Z"
last_activity: 2026-08-11 last_activity: 2026-08-11
last_activity_desc: Phase 16 UAT abgeschlossen; objectGUID-Existenzpruefung repariert last_activity_desc: DOE-Bekanntmachungslinks repariert (260811-j04)
progress: progress:
total_phases: 16 total_phases: 16
completed_phases: 15 completed_phases: 15
@@ -309,6 +309,7 @@ None yet.
| 260728-lih | LDAP: Sync strikt selektiv (leere Auswahl = No-Op statt Voll-Import) + Auto-Sync-Default aus (syncIntervalMin 60→0) | 2026-07-28 | c54e424,57bc7f9,63a07ab | [260728-lih-ldap-sync-selektiv-und-auto-sync-default](.planning/quick/260728-lih-ldap-sync-selektiv-und-auto-sync-default/) | | 260728-lih | LDAP: Sync strikt selektiv (leere Auswahl = No-Op statt Voll-Import) + Auto-Sync-Default aus (syncIntervalMin 60→0) | 2026-07-28 | c54e424,57bc7f9,63a07ab | [260728-lih-ldap-sync-selektiv-und-auto-sync-default](.planning/quick/260728-lih-ldap-sync-selektiv-und-auto-sync-default/) |
| 260729-d3k | LDAP: Multi-Base-DN und Base-DN als Sync-Scope (statt leerer Gruppenfilter = No-Op) | 2026-07-29 | 5cbd530,96be7e1 | [260729-d3k-ldap-multi-base-dn-und-base-dn-als-scope](.planning/quick/260729-d3k-ldap-multi-base-dn-und-base-dn-als-scope/) | | 260729-d3k | LDAP: Multi-Base-DN und Base-DN als Sync-Scope (statt leerer Gruppenfilter = No-Op) | 2026-07-29 | 5cbd530,96be7e1 | [260729-d3k-ldap-multi-base-dn-und-base-dn-als-scope](.planning/quick/260729-d3k-ldap-multi-base-dn-und-base-dn-als-scope/) |
| 260805-d0r | Benutzer-Detail zeigte Gruppenmitgliedschaften aus Freigaben statt aus Mitgliedschaften — GET /module-grants/users/:userId liefert jetzt { groups, modules }, Chips mit Herkunfts-Badge; im Browser gegengeprüft: Mitgliedschaft bleibt sichtbar, auch wenn die Gruppe kein Modul freigibt | 2026-08-05 | ecadf69,f8ff74b,8ce3748 | [260805-d0r-benutzer-detail-zeigt-gruppenmitgliedsch](.planning/quick/260805-d0r-benutzer-detail-zeigt-gruppenmitgliedsch/) | | 260805-d0r | Benutzer-Detail zeigte Gruppenmitgliedschaften aus Freigaben statt aus Mitgliedschaften — GET /module-grants/users/:userId liefert jetzt { groups, modules }, Chips mit Herkunfts-Badge; im Browser gegengeprüft: Mitgliedschaft bleibt sichtbar, auch wenn die Gruppe kein Modul freigibt | 2026-08-05 | ecadf69,f8ff74b,8ce3748 | [260805-d0r-benutzer-detail-zeigt-gruppenmitgliedsch](.planning/quick/260805-d0r-benutzer-detail-zeigt-gruppenmitgliedsch/) |
| 260811-j04 | DOE-Ausschreibungen verlinkten auf die API (rohes JSON) statt auf die Bekanntmachung — betraf Trefferliste, Detailansicht und Alarm-Mails. Adapter baut die URL jetzt aus der Bekanntmachungsnummer, Migration schreibt 2846 bestehende Zeilen in Tender und TenderSource um. Zielseite im Browser fuer beide Kennungsformen verifiziert (numerisch und UUID) — ein HTTP-Statuscheck taugt dort nicht, die Seite antwortet auf jede Kennung mit 200 | 2026-08-11 | ecf7872 | [260811-j04-doe-tender-links-point-to-the-api-instea](.planning/quick/260811-j04-doe-tender-links-point-to-the-api-instea/) |
| 260811-f9i | KRITISCH: objectGUID-Existenzpruefung fand nie etwas — der Filter wurde als `\xx`-escapter String gebaut, ldapts wandelt das nicht in Rohbytes; beide Suchen (Base-DNs und WR-03-Fallback) teilten ihn, also haette der erste echte Sync JEDE AD-gebundene Gruppe samt Mitgliedschaften und Modulfreigaben geloescht. Jetzt EqualityFilter ueber den rohen Buffer, `escapeLdapFilterBuffer()` entfernt. Read-only am echten AD gemessen (escapter String 0 Treffer, EqualityFilter 1 korrekter Treffer); Regressionstests gegengeprueft (alter Code = 8 rote Tests) | 2026-08-11 | d2019dc | [260811-f9i-fix-objectguid-existence-sweep-to-use-eq](.planning/quick/260811-f9i-fix-objectguid-existence-sweep-to-use-eq/) | | 260811-f9i | KRITISCH: objectGUID-Existenzpruefung fand nie etwas — der Filter wurde als `\xx`-escapter String gebaut, ldapts wandelt das nicht in Rohbytes; beide Suchen (Base-DNs und WR-03-Fallback) teilten ihn, also haette der erste echte Sync JEDE AD-gebundene Gruppe samt Mitgliedschaften und Modulfreigaben geloescht. Jetzt EqualityFilter ueber den rohen Buffer, `escapeLdapFilterBuffer()` entfernt. Read-only am echten AD gemessen (escapter String 0 Treffer, EqualityFilter 1 korrekter Treffer); Regressionstests gegengeprueft (alter Code = 8 rote Tests) | 2026-08-11 | d2019dc | [260811-f9i-fix-objectguid-existence-sweep-to-use-eq](.planning/quick/260811-f9i-fix-objectguid-existence-sweep-to-use-eq/) |
| 260805-fok | Standardgruppe bei Mandanten-Anlage + Startup-Reparatur — GroupsService.ensureDefaultGroup(tenantId) mit D-13-Waechter (null Gruppen, nicht fehlende Markierung), verdrahtet in TenantService.create und AdminSeedService.ensureDefaultGroupsForAllTenants; schliesst die Migrations-Backfill-Luecke auf frischen Installationen (Testserver: tenants=1 users=4 groups=0) | 2026-08-05 | 9d1254c,0d7d8a5 | [260805-fok-standardgruppe-bei-mandanten-anlage-und-](.planning/quick/260805-fok-standardgruppe-bei-mandanten-anlage-und-/) | | 260805-fok | Standardgruppe bei Mandanten-Anlage + Startup-Reparatur — GroupsService.ensureDefaultGroup(tenantId) mit D-13-Waechter (null Gruppen, nicht fehlende Markierung), verdrahtet in TenantService.create und AdminSeedService.ensureDefaultGroupsForAllTenants; schliesst die Migrations-Backfill-Luecke auf frischen Installationen (Testserver: tenants=1 users=4 groups=0) | 2026-08-05 | 9d1254c,0d7d8a5 | [260805-fok-standardgruppe-bei-mandanten-anlage-und-](.planning/quick/260805-fok-standardgruppe-bei-mandanten-anlage-und-/) |
@@ -0,0 +1,64 @@
---
quick_id: 260811-j04
slug: doe-tender-links-point-to-the-api-instea
date: 2026-08-11
status: complete
relates_to: tender-radar
closes_todo: 2026-08-05-ausschreibungsportal-falsche-url
commits:
- ecf7872 fix(tenders): link DOE notices to the notice page, not the API
---
# Summary: DÖE-Ausschreibungen verlinken auf die Bekanntmachungsseite
## Entscheidung des Users
Von den beiden möglichen Zielen — Bekanntmachungsseite auf oeffentlichevergabe.de
oder Link auf das ausschreibende Portal selbst (`tender.documents[0].url`) — hat
der User am 2026-08-11 die **Bekanntmachungsseite** gewählt.
## Was gemacht wurde
`doe-opendata.adapter.ts` baut `sourceUrl` jetzt über die neue Funktion
`buildDoeNoticeUrl()` aus der Bekanntmachungsnummer, statt `ocdsDocument.uri`
durchzureichen. Die Nummer liegt ohnehin schon als `sourceNoticeId` im Datensatz,
es braucht also keinen zweiten Abruf.
Dazu die Migration `20260811120000_doe_notice_url_backfill`, die die bereits
importierten Zeilen umschreibt — in `Tender` und in `TenderSource`, weil die
Detailansicht die Quell-Links zusammengeführter Ausschreibungen einzeln auflistet.
## Was vorher geprüft wurde
- **Ist `ocdsDocument.uri` anderswo als Kennung in Gebrauch?** Nein — die einzige
Verwendung im gesamten Code war genau diese eine Zeile.
- **Ist die Nummer in der API-URL dieselbe wie `releases[0].id`?** Ja, live gegen
den Feed geprüft. Der eine auffällige Datensatz in der Datenbank
(`sourceNoticeId` 25671684, URL auf 25671680) stammt nicht aus einer
Feld-Verwechslung, sondern aus der Dedup-Zusammenführung zweier echter,
gleichnamiger Bekanntmachungen ("Bauleistungen"). Nach dem Backfill folgt die
URL der Kennung der Zeile und ist damit in sich stimmig.
- **Funktioniert die Zielseite für beide Kennungsformen?** Ja, im Browser
verifiziert: numerisch (25673764 → "Feuerwehr-Gerätehaus Miehlen
Fliesenarbeiten") und als UUID (7085ba12-… → "Holzfassade").
- **Reicht ein HTTP-Statuscheck?** Nein, und das ist die Falle an dieser Stelle:
die Zielseite ist eine Single-Page-App und antwortet auf JEDE Kennung mit 200
und identischen 1309 Bytes, auch auf `NONSENSE123`. Nur der gerenderte Inhalt
beweist etwas.
- **Wie viele Zeilen betrifft der Nachlauf?** Read-only auf alpha gezählt: 2846
DÖE-Zeilen betroffen, 0 fallen durch die Kennungsprüfung.
## Verifikation
- `pnpm --filter @tessera/api test` — 575 Tests grün (41 Dateien)
- `npx tsc --noEmit` — sauber
- Neue Tests: der Adapter setzt für jeden Datensatz die Bekanntmachungs-URL und
nie mehr `/api/notices/`; `buildDoeNoticeUrl()` escapet die Kennung; das
Migrations-SQL fasst beide Tabellen an, ist idempotent und rührt andere Portale
nicht an.
## Offen
Der Nachlauf läuft erst beim nächsten Deploy an, weil Migrationen beim
Containerstart ausgeführt werden. Danach im Browser gegenprüfen, dass ein
Treffer aus der Liste auf der Bekanntmachung landet.
@@ -1,5 +1,11 @@
--- ---
created: 2026-08-05 created: 2026-08-05
resolved: 2026-08-11
resolution: |
Behoben in Quick-Task 260811-j04, Commit ecf7872. Der User hat sich fuer die
Bekanntmachungsseite entschieden (nicht fuer den Link auf das ausschreibende
Portal). Adapter baut die URL jetzt aus der Bekanntmachungsnummer, Migration
20260811120000_doe_notice_url_backfill schreibt die 2846 bestehenden Zeilen um.
title: DÖE-Ausschreibungen verlinken auf die API statt auf die Bekanntmachungsseite title: DÖE-Ausschreibungen verlinken auf die API statt auf die Bekanntmachungsseite
area: tender-radar area: tender-radar
severity: major severity: major