feat(13-03): wire SourceRegistry + TenderDedupService into TendersModule

Register SourceRegistry and TenderDedupService as providers.
onModuleInit registers DoeOpenDataAdapter with the registry before the
scheduler's first tick — the DI-boot-time enforcement point for the
INGEST-07 denylist gate (D-06). This is Wave 2's sole writer of
tenders.module.ts; 13-04 (NetServer) and 13-05 (cosinex) add their
own registry.register(...) calls additively in later waves.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-23 08:51:47 +02:00
parent d453dbbd1f
commit 1d4f9cf1b3
+34
View File
@@ -4,7 +4,9 @@ import { ModuleRegistryService } from '../module-registry/module-registry.servic
import { PrismaService } from '../prisma/prisma.service'; import { PrismaService } from '../prisma/prisma.service';
import { SettingsModule } from '../settings/settings.module'; import { SettingsModule } from '../settings/settings.module';
import { DoeOpenDataAdapter } from './adapters/doe-opendata.adapter'; import { DoeOpenDataAdapter } from './adapters/doe-opendata.adapter';
import { SourceRegistry } from './source-registry';
import { seedTendersModule } from './tenders.seed'; import { seedTendersModule } from './tenders.seed';
import { TenderDedupService } from './tender-dedup.service';
import { TenderDigestScheduler } from './tender-digest.scheduler'; import { TenderDigestScheduler } from './tender-digest.scheduler';
import { TenderIngestionService } from './tender-ingestion.service'; import { TenderIngestionService } from './tender-ingestion.service';
import { TenderMailService } from './tender-mail.service'; import { TenderMailService } from './tender-mail.service';
@@ -61,13 +63,27 @@ import { TendersController } from './tenders.controller';
* *
* Seeds itself into the module registry on application startup via * Seeds itself into the module registry on application startup via
* OnModuleInit lifecycle hook — same pattern as DkvModule. * OnModuleInit lifecycle hook — same pattern as DkvModule.
*
* Phase 13, Plan 03 (SCHEMA-03): adds `SourceRegistry` (fan-out lookup +
* INGEST-07 denylist gate, Plan 13-02) and `TenderDedupService`
* (three-tier cross-source dedup resolver, Plan 13-03 Task 1) as
* providers. `onModuleInit` registers every known source adapter with the
* registry via `SourceRegistry.register()` BEFORE the scheduler's first
* tick can run — this is the DI-boot-time hook where the denylist gate
* (D-06) structurally applies. `DoeOpenDataAdapter` is registered here;
* this is deliberately the ONE place that grows an additional
* `registry.register(...)` call as 13-04 (NetServer) and 13-05 (cosinex)
* add their adapters in later waves — this plan is Wave 2's sole writer
* of tenders.module.ts, so those additions are purely additive follow-ups.
*/ */
@Module({ @Module({
imports: [ModuleRegistryModule, SettingsModule], imports: [ModuleRegistryModule, SettingsModule],
controllers: [TendersController], controllers: [TendersController],
providers: [ providers: [
DoeOpenDataAdapter, DoeOpenDataAdapter,
SourceRegistry,
TenderNormalizerService, TenderNormalizerService,
TenderDedupService,
TenderIngestionService, TenderIngestionService,
TenderSchedulerService, TenderSchedulerService,
TenderTriageService, TenderTriageService,
@@ -84,9 +100,27 @@ export class TendersModule implements OnModuleInit {
constructor( constructor(
private readonly moduleRegistryService: ModuleRegistryService, private readonly moduleRegistryService: ModuleRegistryService,
private readonly prisma: PrismaService, private readonly prisma: PrismaService,
private readonly sourceRegistry: SourceRegistry,
private readonly doeAdapter: DoeOpenDataAdapter,
) {} ) {}
async onModuleInit(): Promise<void> { async onModuleInit(): Promise<void> {
try {
// D-06/INGEST-07: registration itself is the denylist-gate enforcement
// point — SourceRegistry.register() throws for any adapter serving a
// denylisted portal. DoeOpenDataAdapter is legitimate; this call is
// additive-only for 13-04/13-05's future adapters.
this.sourceRegistry.register(this.doeAdapter);
this.logger.log(
`Registered source adapters: ${this.sourceRegistry
.activeAdapters()
.map((a) => a.sourceType)
.join(', ')}`,
);
} catch (error) {
this.logger.error('Failed to register tender source adapters', error);
}
try { try {
await seedTendersModule(this.moduleRegistryService); await seedTendersModule(this.moduleRegistryService);
this.logger.log('Ausschreibungs-Radar module seeded in registry'); this.logger.log('Ausschreibungs-Radar module seeded in registry');