Enrichment Seam Contract
This document defines an implementation-ready seam contract for enrichment, with staged migration and rollback guidance.
Objective
Create a clear orchestration boundary between:
- Source loading and cache retrieval
- Finding-level enrichment application
- Post-enrichment indexing and pass handoff
The goal is to reduce coupling in run_enrichment_pipeline while preserving current behavior, performance, and auditability.
Current State (baseline)
Today, the enrichment stage combines several concerns in one flow:
- Source selection and fetch/import (
KEV,EPSS,Exploit-DB,NVD) - Finding mutation and enrichment status updates
- Enrichment telemetry aggregation
- Post-enrichment index build and service wiring
- Immediate pass-runner handoff
Primary orchestration lives in src/vulnparse_pin/app/enrichment.py.
Seam Contract
Contract A: Source adapter boundary
Each enrichment source should expose one source adapter contract.
class SourceAdapter(Protocol):
key: str # kev | epss | exploit_db | nvd
def load(self, ctx: RunContext, plan: SourceLoadPlan) -> SourceLoadResult:
...
SourceLoadPlan
Required fields:
mode:onlineorofflineforce_refresh: boolallow_regen: boolpath_or_url: optional source locator
SourceLoadResult
Required fields:
enabled: boolavailable: boolpayload: typed source data orNonestatus: canonical status string for runmanifest/docsmeta: source metadata (ttl/cache/source-type)
Contract B: Enrichment application boundary
Enrichment application should consume source results and mutate findings through one orchestrated step.
class EnrichmentApplicator(Protocol):
def apply(
self,
ctx: RunContext,
scan: ScanResult,
sources: dict[str, SourceLoadResult],
*,
offline_mode: bool,
) -> EnrichmentApplyResult:
...
EnrichmentApplyResult
Required fields:
scan_result: enrichedScanResultsource_summary: stable source availability summarystats: normalized enrichment counterswarnings: machine-readable warning list
Contract C: Pipeline handoff boundary
Post-enrichment indexing and derived pass handoff should consume EnrichmentApplyResult and produce one handoff object.
@dataclass(frozen=True)
class EnrichmentPipelineState:
scan_result: ScanResult
sources: dict
nvd_status: str
Current EnrichmentPipelineState in src/vulnparse_pin/app/enrichment.py remains the compatibility target for this spike.
Non-goals
- No scoring/TopN algorithm changes
- No scanner parser behavior changes
- No runmanifest schema changes
- No feed format changes
Migration Sketch
Stage 0 (compatibility wrappers)
- Keep
run_enrichment_pipelineas entrypoint. - Add wrapper adapters around existing loaders (
load_kev,load_epss,load_exploit_data, and the NVD cache refresh flow). - Preserve all existing source flags and semantics.
Stage 1 (source loading split)
- Move source loading decisions into a dedicated source-orchestrator module.
- Return
SourceLoadResultper source. - Keep current apply logic unchanged.
Stage 2 (application split)
- Move finding-level enrichment into an applicator module.
- Keep exploit batch/parallel behavior unchanged.
- Keep existing stats fields and status update behavior.
Stage 3 (index/handoff split)
- Isolate post-enrichment index build and
RunContext.servicesrewire. - Keep pass execution order and dependency validation unchanged.
Stage 4 (cleanup)
- Remove transitional glue once tests prove parity.
- Keep backwards-compatible output shape and source summary keys.
Blast Radius
Potentially affected areas:
src/vulnparse_pin/app/enrichment.pysrc/vulnparse_pin/utils/enricher.pysrc/vulnparse_pin/utils/exploit_enrichment_service.pysrc/vulnparse_pin/utils/nvdcacher.py- runmanifest source summary consumers
Low-risk by design because migration stages preserve existing entrypoints and output contracts.
Rollback Strategy
- Retain original
run_enrichment_pipelinepath behind one feature-toggle branch during staged implementation. - If drift is detected, route back to legacy orchestration immediately.
- Keep legacy source-summary map keys stable:
exploitdb,kev,epss,nvd,stats.
Verification Plan
Minimum validation for implementation phase:
- Existing enrichment-related tests pass unchanged.
- Runmanifest tests confirm unchanged summary shape and expected stats.
- Offline/online matrix validation for KEV, EPSS, Exploit-DB, and NVD status paths.
- Large-input sanity run to verify no regression in batching/parallel behavior.
Exit Criteria
- Seam contracts are documented and accepted.
- Migration plan is staged and reversible.
- Blast radius and rollback steps are explicit.
- Test matrix for parity is defined.