Files

9.9 KiB

phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, requirements-completed, coverage, duration, completed, status
phase plan subsystem tags requires provides affects tech-stack key-files key-decisions requirements-completed coverage duration completed status
10-ausschreibungs-radar-foundation-d-e-ingestion 02 api
nestjs
module-registry
prisma
next.js
module-loader
tender-radar
marketplace
phase provides
10-01 (foundation & dependencies) Tender / TenderSourcePollConfig Prisma models, applied migration
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
10-03 adapter/normalizer
10-04 ingestion/scheduler
10-05 controller/DTOs
phase 11 saved searches UI
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
created modified
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
apps/api/src/app.module.ts
apps/web/src/lib/module-loader.ts
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
CONFIG-01
id description requirement verification human_judgment
D1 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 CONFIG-01
kind ref status
unit apps/api/src/tenders/tenders.seed.spec.ts#calls moduleRegistryService.seedModule once with tender-radar slug pass
kind ref status
integration cd apps/api && pnpm exec tsc --noEmit -p tsconfig.json; grep -c TendersModule src/app.module.ts == 2 pass
false
id description requirement verification human_judgment rationale
D2 Activating the module opens /modules/tender-radar instead of 404, because the module-loader whitelist entry exists CONFIG-01
kind ref status
integration 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 pass
true 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 description requirement verification human_judgment rationale
D3 Singleton doe-opendata TenderSourcePollConfig row is seeded exactly once per boot (idempotent upsert), no per-tenant duplication possible (sourceType @unique) CONFIG-01
kind ref status
unit schema.prisma sourceType @unique constraint (10-01) + upsert-by-sourceType in tenders.module.ts onModuleInit pass
true 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).
~20min 2026-07-21 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