74c00166e2
6 plans (INGEST-02/03/07, SCHEMA-03) + Nyquist validation. Core (registry, fingerprint, dedup, TenderSource, backfill) lands first and is green independent of live scraping (D-01). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
6.3 KiB
6.3 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 13-scraping-adapters-cross-source-dedup | 02 | execute | 1 |
|
true |
|
|
Purpose: Compliance-/Sicherheitsgrenze gehoert testbar in den DI-Layer, bevor irgendein Poll laeuft. Output: source-registry.ts (Registry + DeniedPortalError), source-registry.spec.ts (Refusal-Beweis), generalisiertes TenderSourceAdapter-Interface.
<execution_context> @$HOME/.claude/gsd-core/workflows/execute-plan.md @$HOME/.claude/gsd-core/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/13-scraping-adapters-cross-source-dedup/13-CONTEXT.md @.planning/phases/13-scraping-adapters-cross-source-dedup/13-RESEARCH.md @apps/api/src/tenders/adapters/tender-source-adapter.interface.ts @apps/api/src/tenders/adapters/doe-opendata.adapter.ts Task 1: Adapter-Interface generalisieren (portals-Array) apps/api/src/tenders/adapters/tender-source-adapter.interface.ts Erweitere `TenderSourceAdapter` (13-RESEARCH.md Pattern 1) um `readonly portals: readonly string[]` — die Liste der von diesem Adapter bedienten Quell-Portale (DÖE: `['doe-opendata']`; NetServer spaeter: `['tender24','lhs-vpbw','vergabe.landbw']`). `sourceType` und `fetchTenders(dayCursor)` bleiben unveraendert. Aktualisiere den bestehenden `DoeOpenDataAdapter`, sodass er `readonly portals = ['doe-opendata'] as const` deklariert (minimaler additiver Edit, keine Verhaltensaenderung). Kommentar: `portals` entkoppelt "ein Adapter" von "ein Portal" (config-driven multi-portal, INGEST-02). cd apps/api && npx tsc --noEmit -p tsconfig.json Interface hat `portals`; DoeOpenDataAdapter deklariert es; `tsc --noEmit` gruen; bestehende doe-Adapter-Spec bleibt gruen. Task 2: SourceRegistry + Denylist-Gate + Refusal-Test (INGEST-07 / D-06) apps/api/src/tenders/source-registry.ts, apps/api/src/tenders/source-registry.spec.ts - register() eines Fake-Adapters mit portals ['vergabe24'] wirft DeniedPortalError. - register() mit portals ['aumass'] wirft DeniedPortalError. - register() eines legitimen Adapters (portals ['tender24']) fuegt ihn hinzu; get(sourceType) liefert ihn zurueck. - activeAdapters() liefert alle registrierten Adapter. - get() eines nicht registrierten sourceType liefert undefined (kein Throw). - Ein gemischter Adapter (portals ['tender24','aumass']) wird komplett abgelehnt (ein verbotenes Portal genuegt). Implementiere `source-registry.ts` nach 13-RESEARCH.md Pattern 2: exportiere `DENYLISTED_PORTALS = ['vergabe24','aumass'] as const`, Klasse `DeniedPortalError extends Error` (deutsche Meldung: AGB-seitig gesperrt), und `@Injectable() class SourceRegistry` mit `private adapters = new Map()`, `register(adapter)` (iteriert `adapter.portals`, wirft DeniedPortalError bei Treffer in DENYLISTED_PORTALS, sonst set), `get(type)` und `activeAdapters()`. Der Denylist-Vergleich ist der Sicherheitskern (D-06) — harte Code-Exception, NIE nur Doku. Schreibe die Spec RED-first exakt nach behavior-Block; sie ist der Beweis fuer Erfolgskriterium 4. Verwende einen Fake-Adapter-Stub im Test (kein echtes Scraping). Beachte die comment-text-Disziplin: kein negativer grep-Gate auf 'vergabe24'/'aumass' im Datei-Body ausserhalb der Konstante. pnpm --filter @tessera/api test -- source-registry register() wirft DeniedPortalError fuer vergabe24 UND aumass (Test gruen); legitime Registrierung + get()/activeAdapters() funktionieren; kein Prisma/Scraping-Import.<threat_model>
Trust Boundaries
| Boundary | Description |
|---|---|
| Adapter-Registrierung -> Poll-System | Ein verbotenes Portal darf nie in den aktiven Poll-Pfad gelangen (AGB/Compliance). |
STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|---|---|---|---|---|---|
| T-13-02-01 | Elevation of Privilege | Registrierung eines Denylist-Portals | high | mitigate | Harte DeniedPortalError-Exception in register() bei DI-Boot; Test beweist Refusal (D-06, INGEST-07). |
| T-13-02-02 | Tampering | Umgehung des Gates via gemischtem portals-Array | high | mitigate | Iteration ueber ALLE portals; ein verbotenes Portal lehnt den ganzen Adapter ab. |
| T-13-02-SC | Tampering | keine neue Dependency in diesem Plan | low | accept | Kern nutzt nur bestehende Deps; node-html-parser erst in 13-04 hinter Checkpoint. |
| </threat_model> |
<success_criteria> SourceRegistry lehnt vergabe24/aumass im Code ab (getestet); Adapter-Vertrag um portals generalisiert. Erfuellt INGEST-07 strukturell. </success_criteria>
Create `.planning/phases/13-scraping-adapters-cross-source-dedup/13-02-SUMMARY.md` when done