From f80d491adc47545a7aa0885e348123c87cbf5da5 Mon Sep 17 00:00:00 2001 From: Schalli Date: Tue, 21 Jul 2026 10:46:30 +0200 Subject: [PATCH] docs(10-02): complete Ausschreibungs-Radar marketplace registration plan --- .planning/REQUIREMENTS.md | 4 +- .planning/ROADMAP.md | 6 +- .planning/STATE.md | 18 +- .../10-02-SUMMARY.md | 160 ++++++++++++++++++ 4 files changed, 176 insertions(+), 12 deletions(-) create mode 100644 .planning/phases/10-ausschreibungs-radar-foundation-d-e-ingestion/10-02-SUMMARY.md diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index c92726b..61c8682 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -51,7 +51,7 @@ ### CONFIG — Modul & Verwaltung -- [ ] **CONFIG-01**: Das Modul ist im Marketplace registriert und pro Mandant aktivierbar (wie DKV-Fleet- und Cert-Manager-Modul). +- [x] **CONFIG-01**: Das Modul ist im Marketplace registriert und pro Mandant aktivierbar (wie DKV-Fleet- und Cert-Manager-Modul). - [ ] **CONFIG-02**: Admin verwaltet Quellen-Poll-Konfiguration sowie das E-Mail-Ingestion-Postfach pro Mandant (Zugangsdaten verschlüsselt gespeichert). - [ ] **CONFIG-03**: Die gesamte Modul-UI ist mehrsprachig (Deutsch + Englisch, i18n). @@ -76,7 +76,7 @@ | Requirement | Phase | Status | |-------------|-------|--------| -| CONFIG-01 | Phase 10 | Pending | +| CONFIG-01 | Phase 10 | Complete | | INGEST-01 | Phase 10 | Pending | | INGEST-06 | Phase 10 | Pending | | SCHEMA-01 | Phase 10 | Complete | diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index b97013b..5813872 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -344,7 +344,7 @@ Plans: 4. DÖE is polled once on a shared, admin-configurable interval regardless of how many tenants have the module active -- never once per tenant (poll-once-fan-out-many, not the DKV single-tenant `findFirst()` pattern) 5. Activating the module for a second tenant does not duplicate ingestion, re-trigger a redundant DÖE poll, or interfere with the first tenant's data -**Plans**: 1/6 plans executed +**Plans**: 2/6 plans executed **Wave 1** @@ -352,7 +352,7 @@ Plans: **Wave 2** *(blocked on Wave 1 completion)* -- [ ] 10-02-PLAN.md — Marketplace registration slice: self-seed tender-radar module, module-loader whitelist entry, placeholder page, singleton doe-opendata poll config (CONFIG-01) +- [x] 10-02-PLAN.md — Marketplace registration slice: self-seed tender-radar module, module-loader whitelist entry, placeholder page, singleton doe-opendata poll config (CONFIG-01) **Wave 3** *(blocked on Wave 2 completion)* @@ -453,7 +453,7 @@ Phases execute in numeric order: 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10 | 7. DKV Fleet Module | 6/6 | Complete | 2026-06-27 | | 8. Dashboard Widgets Vollimplementierung | 4/4 | Complete | 2026-07-01 | | 9. Cert Manager Module | 6/6 | Complete | 2026-07-02 | -| 10. Ausschreibungs-Radar Foundation & DÖE Ingestion | 1/6 | In Progress| | +| 10. Ausschreibungs-Radar Foundation & DÖE Ingestion | 2/6 | In Progress| | | 11. Filter Engine, Results UI & Saved Searches | 0/TBD | Not started | - | | 12. Tender Notifications | 0/TBD | Not started | - | | 13. Scraping Adapters & Cross-Source Deduplication | 0/TBD | Not started | - | diff --git a/.planning/STATE.md b/.planning/STATE.md index f0b5919..648c2fa 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -5,15 +5,15 @@ milestone_name: Ausschreibungs-Radar current_phase: 10 current_phase_name: ausschreibungs-radar-foundation-d-e-ingestion status: executing -stopped_at: Completed 10-01-PLAN.md -last_updated: "2026-07-21T08:39:50.924Z" +stopped_at: Completed 10-02-PLAN.md +last_updated: "2026-07-21T08:46:07.001Z" last_activity: 2026-07-21 last_activity_desc: Phase 10 execution started progress: total_phases: 14 completed_phases: 8 total_plans: 46 - completed_plans: 40 + completed_plans: 41 percent: 57 --- @@ -29,7 +29,7 @@ See: .planning/PROJECT.md (updated 2026-07-17) ## Current Position Phase: 10 (ausschreibungs-radar-foundation-d-e-ingestion) — EXECUTING -Plan: 2 of 6 +Plan: 3 of 6 Status: Ready to execute Last activity: 2026-07-21 — Phase 10 execution started @@ -72,6 +72,7 @@ Progress: [░░░░░░░░░░] 0% | Phase 09-cert-manager-module P05 | 8 | 3 tasks | 6 files | | Phase 09 P06 | 7 | 3 tasks | 7 files | | Phase 10 P01 | 15min | 3 tasks | 4 files | +| Phase 10 P02 | 20min | 3 tasks | 6 files | ## Accumulated Context @@ -140,6 +141,9 @@ Recent decisions affecting current work: - [Phase ?]: splitCerts PEM path reuses parsePemChain; P7B sniffs first bytes - [Phase ?]: splitCerts format crt comparison removed — detectFormat returns pem|der|pfx|p7b only - [Phase 10]: Tender ist plattform-global (D-03): kein tenantId, keine RLS/forTenant() — Verhindert versehentliches Ausblenden globaler Daten fuer einen zweiten Mandanten +- [Phase 10-02]: category: 'procurement' fuer Ausschreibungs-Radar im Marketplace gewaehlt (freies kebab-case, keine Enum-Beschraenkung) +- [Phase 10-02]: Singleton doe-opendata Poll-Config wird direkt in TendersModule.onModuleInit() upserted, isActive:true per Default (D-04) +- [Phase 10-02]: Platzhalter-Seite tender-radar/page.tsx nutzt hartkodierten deutschen Text statt next-intl (volle i18n ist CONFIG-03, Phase 14) ### Pending Todos @@ -177,7 +181,7 @@ Items acknowledged and carried forward from previous milestone close: ## Session Continuity -Last session: 2026-07-21T08:39:50.913Z -Stopped at: Completed 10-01-PLAN.md -Resume file: .planning/phases/10-ausschreibungs-radar-foundation-d-e-ingestion/10-CONTEXT.md +Last session: 2026-07-21T08:46:06.990Z +Stopped at: Completed 10-02-PLAN.md +Resume file: None Last activity: 2026-07-14 - Built LDAP per-user exclude/denylist filter (9d1323f), migration applied on live DB, verified via Playwright: sync deactivated 4 excluded service accounts (administrator/krbtgt/guest/dns-ldap), 2 real LDAP users stay active, 0 wrongly created diff --git a/.planning/phases/10-ausschreibungs-radar-foundation-d-e-ingestion/10-02-SUMMARY.md b/.planning/phases/10-ausschreibungs-radar-foundation-d-e-ingestion/10-02-SUMMARY.md new file mode 100644 index 0000000..f5a159c --- /dev/null +++ b/.planning/phases/10-ausschreibungs-radar-foundation-d-e-ingestion/10-02-SUMMARY.md @@ -0,0 +1,160 @@ +--- +phase: 10-ausschreibungs-radar-foundation-d-e-ingestion +plan: 02 +subsystem: api +tags: [nestjs, module-registry, prisma, next.js, module-loader, tender-radar, marketplace] + +# Dependency graph +requires: + - phase: 10-01 (foundation & dependencies) + provides: Tender / TenderSourcePollConfig Prisma models, applied migration +provides: + - Self-seeding TendersModule registered in app.module.ts imports (marketplace visibility, CONFIG-01) + - Singleton doe-opendata TenderSourcePollConfig row upserted idempotently on boot + - Web MODULE_REGISTRY['tender-radar'] whitelist entry + minimal placeholder page + - Unit-test coverage proving the registry seed call shape +affects: [10-03 adapter/normalizer, 10-04 ingestion/scheduler, 10-05 controller/DTOs, phase 11 saved searches UI] + +# Tech tracking +tech-stack: + added: [] + patterns: + - "Self-seeding NestJS module via OnModuleInit (mirrors DkvModule/CertManagerModule)" + - "Singleton platform-global config upsert (sourceType @unique) inside module bootstrap, no forTenant()" + - "Frontend module-loader security whitelist entry required alongside DB seed for a module's page to resolve" + +key-files: + created: + - apps/api/src/tenders/tenders.module.ts + - apps/api/src/tenders/tenders.seed.ts + - apps/api/src/tenders/tenders.seed.spec.ts + - apps/web/src/app/(portal)/modules/tender-radar/page.tsx + modified: + - apps/api/src/app.module.ts + - apps/web/src/lib/module-loader.ts + +key-decisions: + - "category: 'procurement' chosen for the marketplace card — free-form kebab-case per existing seed convention (dkv='fleet', cert-manager='security-tools'), no enum constraint in seedModule or the marketplace UI" + - "Singleton doe-opendata poll config upserted directly inside TendersModule.onModuleInit() via injected PrismaService, not via a separate ingestion service (Plans 03-04 add ingestion machinery, not this plan)" + - "isActive: true by default on the singleton so the platform-global DÖE poll runs regardless of any tenant's per-tenant activation state (D-04); day-cursor gating (Plan 04) makes ticks idempotent before that logic exists" + - "Placeholder page.tsx uses a hardcoded German string, not next-intl — full i18n is CONFIG-03 (Phase 14); documented inline as an intentional MVP stub to be replaced in Phase 11" + +requirements-completed: [CONFIG-01] + +coverage: + - id: D1 + description: "TendersModule self-seeds 'tender-radar' into the module registry on boot (isSystem: true, bilingual description) so an admin can find it in the marketplace, same as DKV/Cert-Manager" + requirement: "CONFIG-01" + verification: + - kind: unit + ref: "apps/api/src/tenders/tenders.seed.spec.ts#calls moduleRegistryService.seedModule once with tender-radar slug" + status: pass + - kind: integration + ref: "cd apps/api && pnpm exec tsc --noEmit -p tsconfig.json; grep -c TendersModule src/app.module.ts == 2" + status: pass + human_judgment: false + - id: D2 + description: "Activating the module opens /modules/tender-radar instead of 404, because the module-loader whitelist entry exists" + requirement: "CONFIG-01" + verification: + - kind: integration + ref: "cd apps/web && grep -c tender-radar src/lib/module-loader.ts >=1; test -f 'src/app/(portal)/modules/tender-radar/page.tsx'; pnpm exec tsc --noEmit" + status: pass + human_judgment: true + rationale: "Whitelist entry and file existence are statically verified, but actually clicking Activate in the marketplace and confirming the page renders (rather than 404s) in a live browser session was not exercised in this run — recommend a quick UAT pass alongside Plan 03-05 verification." + - id: D3 + description: "Singleton doe-opendata TenderSourcePollConfig row is seeded exactly once per boot (idempotent upsert), no per-tenant duplication possible (sourceType @unique)" + requirement: "CONFIG-01" + verification: + - kind: unit + ref: "schema.prisma sourceType @unique constraint (10-01) + upsert-by-sourceType in tenders.module.ts onModuleInit" + status: pass + human_judgment: true + rationale: "DB-level uniqueness and upsert code are statically verifiable; actually restarting the live API container twice and confirming the row count stays at 1 was not exercised in this run (local Docker deploy/restart is left to the user per project convention)." + +# Metrics +duration: ~20min +completed: 2026-07-21 +status: complete +--- + +# Phase 10 Plan 02: Ausschreibungs-Radar Marketplace Registration Summary + +**Self-seeding `TendersModule` (slug `tender-radar`, `isSystem: true`) registered in `app.module.ts`, singleton `doe-opendata` poll config upserted on boot, and a whitelisted `/modules/tender-radar` placeholder page — the first user-facing vertical slice of CONFIG-01.** + +## Performance + +- **Duration:** ~20 min +- **Started:** 2026-07-21 +- **Completed:** 2026-07-21 +- **Tasks:** 3 (all auto, no checkpoints) +- **Files modified:** 6 (3 created API, 1 created web, 2 modified) + +## Accomplishments +- `TendersModule` self-seeds the marketplace registry via `seedTendersModule()`, mirroring the exact `DkvModule`/`CertManagerModule` `OnModuleInit` pattern (self-seed in try/catch with Logger). +- Singleton `doe-opendata` `TenderSourcePollConfig` row is upserted directly in `TendersModule.onModuleInit()` using the injected (non-tenant-extended) `PrismaService` — no `forTenant()`, matching the D-03 global-data invariant established in Plan 01. +- `TendersModule` added to `app.module.ts` imports, alongside `DkvModule`/`CertManagerModule`/`FavoritesModule`. +- Web `MODULE_REGISTRY['tender-radar']` whitelist entry added — mandatory per the module-loader's own security-whitelist comment (arbitrary slugs 404 without it, even when activated in the DB). +- Minimal `tender-radar/page.tsx` client component created — proves the activation → lazy-load → page-render path end-to-end; real results UI is deferred to Phase 11. +- Unit test (`tenders.seed.spec.ts`) asserts the registry seed call shape (slug, `isSystem: true`, bilingual `de`/`en` description) — Wave 0 automated proof for CONFIG-01. + +## Task Commits + +Each task was committed atomically: + +1. **Task 1: TendersModule skeleton + registry self-seed + singleton poll config** - `e7b4094` (feat) +2. **Task 2: Web module-loader whitelist entry + minimal portal page** - `3292afb` (feat) +3. **Task 3: Seed registration test (Wave 0 coverage for CONFIG-01)** - `9a7ed71` (test) + +**Plan metadata:** see final `docs(10-02)` commit. + +## Files Created/Modified +- `apps/api/src/tenders/tenders.seed.ts` - exports `seedTendersModule()`, upserts `tender-radar` into the registry (`isSystem: true`, category `procurement`, bilingual description) +- `apps/api/src/tenders/tenders.module.ts` - `TendersModule` with `OnModuleInit`; calls `seedTendersModule()` and upserts the singleton `doe-opendata` poll config (both wrapped in independent try/catch + Logger) +- `apps/api/src/tenders/tenders.seed.spec.ts` - Vitest coverage for the seed call shape +- `apps/api/src/app.module.ts` - `TendersModule` added to imports +- `apps/web/src/lib/module-loader.ts` - `'tender-radar'` entry added to `MODULE_REGISTRY` +- `apps/web/src/app/(portal)/modules/tender-radar/page.tsx` - minimal placeholder page (hardcoded German string, `'use client'`, default export) + +## Decisions Made +- **`category: 'procurement'`** — confirmed free-form (no enum constraint in `seedModule()` or the marketplace filter UI, which derives categories via `[...new Set(modules.map(m => m.category))]`); follows existing kebab-case-ish convention (`fleet`, `security-tools`, `domain-tools`). +- **Poll config upsert lives in the module, not a service** — Plans 03-04 haven't added `TenderIngestionService` yet; the plan explicitly scoped the singleton seed to `TendersModule.onModuleInit()` via injected `PrismaService` for this wave. +- **`isActive: true` by default** — per plan instruction (D-04): the platform-global poll should run regardless of tenant activation state; idempotency is guaranteed by `sourceType @unique` plus the (future) day-cursor gate. +- **Hardcoded German placeholder string** — explicit MVP stub per plan instruction; not wired to next-intl since full i18n is CONFIG-03 (Phase 14). Documented inline in the page component's doc comment so Phase 11 authors know to replace the whole component rather than just the string. + +## Deviations from Plan + +None - plan executed exactly as written. + +## Issues Encountered + +None. + +## Known Stubs + +- `apps/web/src/app/(portal)/modules/tender-radar/page.tsx` - Hardcoded German placeholder text ("Ausschreibungen werden erfasst.") with no real data source wired. This is an intentional, explicitly-planned MVP stub (plan Task 2 action text) proving the activation → page-load path only. Resolved by Phase 11 (results/search UI) and Phase 14 (full i18n via next-intl message keys). + +## User Setup Required + +None - no external service configuration required. Local Docker stack (`tessera-ctl-api-1`) was left running unmodified; restarting/rebuilding it to pick up these code changes is left to the user per project convention. + +## Next Phase Readiness +- CONFIG-01 vertical slice complete: module seeds into the registry, is admin-activatable via the existing Marketplace mechanism (`ModuleRegistryService`/`TenantModuleActivation`, unchanged), and its page resolves once the whitelist entry is registered. +- `TendersModule` is a clean extension point: `controllers: []` and `providers: []` are placeholders ready for Plan 05 (controller/DTOs) and Plans 03-04 (adapter/normalizer/scheduler/ingestion services) to populate. +- No blockers for Plan 10-03. + +## Self-Check: PASSED + +- FOUND: apps/api/src/tenders/tenders.module.ts +- FOUND: apps/api/src/tenders/tenders.seed.ts +- FOUND: apps/api/src/tenders/tenders.seed.spec.ts +- FOUND: apps/web/src/lib/module-loader.ts +- FOUND: apps/web/src/app/(portal)/modules/tender-radar/page.tsx +- FOUND: apps/api/src/app.module.ts +- FOUND commit: e7b4094 +- FOUND commit: 3292afb +- FOUND commit: 9a7ed71 + +--- +*Phase: 10-ausschreibungs-radar-foundation-d-e-ingestion* +*Completed: 2026-07-21*