feat(13-05): register CosinexAdapter as tenders module provider

Adds CosinexAdapter to TendersModule's providers and registers it with
SourceRegistry at DI boot, alongside DoeOpenDataAdapter/NetServerAdapter
(cosinex-dtvp is not AGB-denylisted, so registration succeeds). Seeds a
cosinex-dtvp TenderSourcePollConfig row with isActive: false, matching
the ai-netserver "framework ready, activation deferred" stance (D-02).

tsc --noEmit clean; src/tenders slice: 21 files, 221/221 tests pass
(205 pre-existing + 16 new cosinex.adapter.spec.ts).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-23 09:49:19 +02:00
parent 12fc5ac02d
commit 6fe0dd07fe
+34 -3
View File
@@ -3,6 +3,7 @@ import { ModuleRegistryModule } from '../module-registry/module-registry.module'
import { ModuleRegistryService } from '../module-registry/module-registry.service'; import { ModuleRegistryService } from '../module-registry/module-registry.service';
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 { CosinexAdapter } from './adapters/cosinex.adapter';
import { DoeOpenDataAdapter } from './adapters/doe-opendata.adapter'; import { DoeOpenDataAdapter } from './adapters/doe-opendata.adapter';
import { NetServerAdapter } from './adapters/netserver.adapter'; import { NetServerAdapter } from './adapters/netserver.adapter';
import { SourceRegistry } from './source-registry'; import { SourceRegistry } from './source-registry';
@@ -84,6 +85,14 @@ import { TendersController } from './tenders.controller';
* plan does not force-activate the new source; activation is an * plan does not force-activate the new source; activation is an
* admin/seed decision (Phase 14 UI), matching D-02's "framework ready, * admin/seed decision (Phase 14 UI), matching D-02's "framework ready,
* activation deferred" stance. * activation deferred" stance.
*
* Phase 13, Plan 05 (INGEST-03): adds `CosinexAdapter` — a SEPARATE
* single-portal HTML adapter for the cosinex/DTVP satellite (cosinex-dtvp
* is not denylisted) — as a provider and registers it alongside
* `DoeOpenDataAdapter`/`NetServerAdapter` in `onModuleInit`, the third and
* final Wave-4 additive `registry.register(...)` call. Its
* `TenderSourcePollConfig` row is likewise seeded with `isActive: false`
* (D-02: activation is a later admin/seed decision, Phase 14 UI).
*/ */
@Module({ @Module({
imports: [ModuleRegistryModule, SettingsModule], imports: [ModuleRegistryModule, SettingsModule],
@@ -91,6 +100,7 @@ import { TendersController } from './tenders.controller';
providers: [ providers: [
DoeOpenDataAdapter, DoeOpenDataAdapter,
NetServerAdapter, NetServerAdapter,
CosinexAdapter,
SourceRegistry, SourceRegistry,
TenderNormalizerService, TenderNormalizerService,
TenderDedupService, TenderDedupService,
@@ -113,17 +123,19 @@ export class TendersModule implements OnModuleInit {
private readonly sourceRegistry: SourceRegistry, private readonly sourceRegistry: SourceRegistry,
private readonly doeAdapter: DoeOpenDataAdapter, private readonly doeAdapter: DoeOpenDataAdapter,
private readonly netServerAdapter: NetServerAdapter, private readonly netServerAdapter: NetServerAdapter,
private readonly cosinexAdapter: CosinexAdapter,
) {} ) {}
async onModuleInit(): Promise<void> { async onModuleInit(): Promise<void> {
try { try {
// D-06/INGEST-07: registration itself is the denylist-gate enforcement // D-06/INGEST-07: registration itself is the denylist-gate enforcement
// point — SourceRegistry.register() throws for any adapter serving a // point — SourceRegistry.register() throws for any adapter serving a
// denylisted portal. DoeOpenDataAdapter and NetServerAdapter are both // denylisted portal. DoeOpenDataAdapter, NetServerAdapter, and
// legitimate (tender24/lhs-vpbw/vergabe.landbw are not on the // CosinexAdapter are all legitimate (none of tender24/lhs-vpbw/
// denylist); this call is additive-only for 13-05's future adapter. // vergabe.landbw/cosinex-dtvp are on the denylist).
this.sourceRegistry.register(this.doeAdapter); this.sourceRegistry.register(this.doeAdapter);
this.sourceRegistry.register(this.netServerAdapter); this.sourceRegistry.register(this.netServerAdapter);
this.sourceRegistry.register(this.cosinexAdapter);
this.logger.log( this.logger.log(
`Registered source adapters: ${this.sourceRegistry `Registered source adapters: ${this.sourceRegistry
.activeAdapters() .activeAdapters()
@@ -180,5 +192,24 @@ export class TendersModule implements OnModuleInit {
} catch (error) { } catch (error) {
this.logger.error('Failed to seed ai-netserver poll config', error); this.logger.error('Failed to seed ai-netserver poll config', error);
} }
try {
// Phase 13, Plan 05 (INGEST-03): seed the cosinex-dtvp poll config
// row so it is admin-drivable, but isActive defaults false — same
// "framework ready, activation deferred" stance as ai-netserver
// (D-02, Phase 14 UI).
await this.prisma.tenderSourcePollConfig.upsert({
where: { sourceType: 'cosinex-dtvp' },
update: {},
create: {
sourceType: 'cosinex-dtvp',
pollIntervalMin: 60,
isActive: false,
},
});
this.logger.log('cosinex-dtvp poll config seeded (inactive)');
} catch (error) {
this.logger.error('Failed to seed cosinex-dtvp poll config', error);
}
} }
} }