Findings.VulnCorrelation — retained analytics ingestion and correlation libraries
Ownership and schema verified at
c0a5692e8a022952d7a3c814919c8c485d173ca4on 2026-10-04 (PCD-267). The standalone VulnCorrelation host and its compose overlay were deleted at FND-10 (2026-08-27). The application and legacy persistence libraries remain, but no serving host callsAddAnalyticsIngestionorAddVulnCorrelationMigrations. Both current Findings roles select the consolidated persistence lineage described in section 6. This is source verification; the database contents of an installation were not inspected.
1. Overview
This page separates retained library behavior from the schema selected by the consolidated Findings hosts. See the Findings architecture for the serving module.
PAC-5 originally moved analytics ingestion out of Platform into a dedicated worker. That host is deleted. Its retained consumers still reference the old streams and database contracts; their presence in source does not establish a scheduled ingestion path.
findings-worker registers the advisory-corpus, advisory/SBOM and Scanner projections through the consolidated persistence library. Those registrations do not call the legacy ingestion extension. Their configuration and contracts are described in the Findings architecture.
2. Ownership and implementation limits
- The consolidated Findings family owns
stellaops_findings, including itsanalyticsschema. - Both
findings-webandfindings-workerapply the family’s embedded migration chain at startup. - The retained VulnCorrelation migration chain is not selected by either host.
- The retained application library still contains same-database reads of
vuln.*and writes toanalytics.vex_overrides, which the consolidated baseline does not provide. - Platform’s analytics read API and maintenance lane were withdrawn by PCD-127 on 2026-09-30.
3. Components
Paths below are relative to src/Findings/.
| Source | Role |
|---|---|
StellaOps.Findings.WebService/Program.cs | Serving HTTP host; calls AddConsolidatedFindingsPersistence. |
StellaOps.Findings.Worker/Program.cs | Serving worker; calls the same persistence registration and the current projection registrations. |
__Libraries/StellaOps.Findings.Persistence/ | Selected migration lineage, including 001_findings_consolidated_baseline.sql. |
__Libraries/StellaOps.Findings.VulnCorrelation.Application/ServiceCollectionExtensions.cs | Retained AddAnalyticsIngestion registration, with no serving caller. |
__Libraries/StellaOps.Findings.VulnCorrelation.Application/Services/ | Retained scanner/SBOM, attestation and correlation consumers, CAS reader and disposition cache. |
__Libraries/StellaOps.Findings.VulnCorrelation.Application/Options/ and Utilities/ | Library configuration, PURL/version rules, tenant normalization and hashing. |
__Libraries/StellaOps.Findings.VulnCorrelation.Persistence/ | Retained AddVulnCorrelationMigrations extension and embedded legacy analytics DDL. |
Tests that instantiate the retained library live under src/Findings/__Tests/StellaOps.Findings.VulnCorrelation.Tests/.
4. Data flow in the retained library
The following describes the unscheduled library implementation. It is not a serving data flow. File names in this section resolve under the application library’s Services/ or Models/ directories.
orchestrator:events ────────► AnalyticsIngestionService ──► CAS (SBOM) ──► analytics.artifacts
(scanner.event.report.ready │ analytics.raw_sboms
scanner.scan.completed) │ analytics.components
│ analytics.artifact_components
└─ finding.disposition.changed ──► in-memory FindingDispositionCache
(not persisted)
attestor:events ────────────► AttestationIngestionService ─► CAS (DSSE bundle) ─► analytics.attestations
(rekor.entry.logged analytics.raw_attestations
rekor.inclusion.verified) analytics.vex_overrides
analytics.artifacts (provenance/SLSA)
concelier:advisory.observation.updated:v1 ┐
concelier:advisory.linkset.updated:v1 ┴► VulnerabilityCorrelationService ─► analytics.component_vulns
(reads Concelier vuln.* in the same DB) analytics.artifacts (counts)
4.1 Scanner / SBOM lane (AnalyticsIngestionService)
- Subscribes to
Streams:ScannerStream(defaultorchestrator:events) asIEventStream<JobEngineEventEnvelope>; handles kindsscanner.event.report.readyandscanner.scan.completed(Models/ScannerJobEngineEvents.cs:160-164,AnalyticsIngestionService.cs:165-169). - Resolves the SBOM from CAS (
FileCasContentReader, root-jailed toCas:RootPath— a URI resolving outside the root is refused,CasContentReader.cs:52-56), parses it withIParsedSbomParser(StellaOps.Sbom.Parsing), then in one transaction upsertsanalytics.artifacts(ON CONFLICT (digest)),analytics.raw_sboms(ON CONFLICT (content_hash) DO NOTHING),analytics.components(ON CONFLICT (purl, hash_sha256)), andanalytics.artifact_components(ON CONFLICT (artifact_id, component_id) DO NOTHING). - After commit it calls
CorrelateForPurlsAsync(purls)andUpdateArtifactCountsAsync(artifactId)on the correlation service (AnalyticsIngestionService.cs:753-768) — so a newly ingested SBOM is correlated immediately, without waiting for a Concelier advisory event. - Checkpoint: after every handled event the stream entry id is written to
<Cas:RootPath>/.state/platform-scanner-stream.checkpoint(overridable viaStreams:ScannerCheckpointFilePath,AnalyticsIngestionService.cs:505-530). On start the subscription resumes from that entry unlessStartFromBeginning=true. - Disposition cache lane:
finding.disposition.changedpayloads ride the sameorchestrator:eventsstream under the generic EMIT envelope kind and are discriminated on the payloadeventTypemarker (AnalyticsIngestionService.cs:114-121,177-194). They populate an in-memoryIFindingDispositionCacheonly — nothing is persisted, and the enforced disposition read-model is served by Findings.Security (PAC-4), not from here (ServiceCollectionExtensions.cs:54-59). This worker-local cache is not the Security freshness authority. Findings.Security independently subscribes to the same domain-agnostic event and repairs its durable cache from the Ledger every 60 seconds; seesecurity-read-model.md.
4.2 Attestor lane (AttestationIngestionService)
- Subscribes to
Streams:AttestorStream(defaultattestor:events) asIEventStream<RekorEntryEvent>; handlesrekor.entry.loggedandrekor.inclusion.verified(Models/RekorEvents.cs:73-79,AttestationIngestionService.cs:104-106). - Reads the DSSE bundle from CAS through
Attestations:BundleUriTemplate(defaultbundle:{digest}), parses the in-toto statement, and writesanalytics.attestations(ON CONFLICT (dsse_payload_hash)) plusanalytics.raw_attestations(ON CONFLICT (content_hash) DO NOTHING). - Provenance predicates (predicate type containing
slsa/provenance) setanalytics.artifacts.provenance_attested = TRUEand raiseslsa_levelmonotonically (GREATEST,AttestationIngestionService.cs:1110-1125). - VEX predicates (OpenVEX statements or a CycloneDX
vulnerabilitiesarray) are projected intoanalytics.vex_overrideswith aNOT EXISTSdedupe guard on (attestation, vuln, artifact, component purl) (:1131-1170,:692-716).
4.3 Concelier / correlation lane (VulnerabilityCorrelationService)
- Subscribes to
Streams:ConcelierObservationStream(concelier:advisory.observation.updated:v1) andStreams:ConcelierLinksetStream(concelier:advisory.linkset.updated:v1). - For an advisory event it resolves the affected PURLs from Concelier’s schema (
SELECT DISTINCT aff.package_purl FROM vuln.advisory_affected aff JOIN vuln.advisories adv …) and correlates them. CorrelateForPurlsAsyncloads the matchinganalytics.components, then joins Concelier’svuln.advisory_affected/vuln.advisories/vuln.sources/vuln.advisory_cvss/vuln.kev_flags/vuln.advisory_canonical(state = 'active', best CVSS byis_primary DESC, base_score DESC) and upsertsanalytics.component_vulns(ON CONFLICT (component_id, vuln_id)).affectsis decided byVersionRuleEvaluatoragainst the advisory’snormalized_versions;fixed_version/fix_availablecome from the same rule set (VulnerabilityCorrelationService.cs:105-178,343-400).UpdateArtifactCountsAsyncrecomputesanalytics.artifacts.{vulnerability,critical,high,medium,low}_countfromartifact_components ⋈ component_vulns WHERE affects = TRUE(:193-218).
Unresolved legacy dependencies. The retained correlation SQL joins
vuln.*in the same database (Services/VulnerabilityCorrelationService.cs). The attestation consumer writesanalytics.vex_overrides(Services/AttestationIngestionService.cs). Neither dependency is supplied by the consolidated Findings migrations. These are not supported cross-service read contracts.
5. Current ownership boundary
| Concern | Current state | Source |
|---|---|---|
Serving analytics DDL | Findings consolidated persistence | Both serving Program.cs files call AddConsolidatedFindingsPersistence; see section 6. |
| Legacy analytics DDL | Embedded in the retained VulnCorrelation persistence library; no serving caller | Extensions/VulnCorrelationMigrationExtensions.cs and its project resource items. |
| Legacy SBOM/attestation ingestion and correlation | Retained application code, unscheduled | ServiceCollectionExtensions.AddAnalyticsIngestion; neither serving host calls it. |
GET /api/analytics/* | Withdrawn by PCD-127 | Former Platform sources are frozen under src/__Obsoleted/Platform/StellaOps.Platform.WebService/PCD-127/. |
| Daily rollups and materialized-view refresh | Former Platform maintenance lane withdrawn by PCD-127 | The consolidated Findings migration chain creates neither these rollups nor these views. |
The Platform dossier records the withdrawal. Retained legacy DDL is not evidence that the former analytics API or maintenance process is available.
6. Serving schema and retained legacy DDL
6.1 Selected Findings migration lineage
src/Findings/StellaOps.Findings.WebService/Program.cs and src/Findings/StellaOps.Findings.Worker/Program.cs call AddConsolidatedFindingsPersistence. Its implementation is src/Findings/__Libraries/StellaOps.Findings.Persistence/Extensions/ConsolidatedFindingsPersistenceExtensions.cs. It registers AddStartupMigrations with its own assembly, module Findings.Consolidated, and migration ledger findings.schema_migrations.
The service resolves STELLAOPS_POSTGRES_FINDINGS_CONNECTION, with the service-specific Postgres:Findings:ConnectionString configuration override, and fails closed without a value. It does not select the former shared-database connection fallbacks.
src/Findings/__Libraries/StellaOps.Findings.Persistence/Migrations/001_findings_consolidated_baseline.sql creates the following analytics tables. The numbered migrations beside it are the authority for later changes; migration 016 adds retention comments to these tables.
| Table | Purpose |
|---|---|
analytics.components | Component registry keyed by (purl, hash_sha256), with supplier/license data and inventory counts. |
analytics.artifacts | Artifact inventory keyed by digest, with SBOM pointers, severity counts and provenance fields. |
analytics.artifact_components | Artifact/component edges and dependency depth. |
analytics.component_vulns | Correlation results keyed by (component_id, vuln_id). |
analytics.attestations | Parsed attestation metadata and DSSE payload hashes. |
analytics.raw_sboms | SBOM pointers and deduplication by content_hash. |
analytics.raw_attestations | Attestation pointers and deduplication by content_hash. |
The baseline also creates the helpers analytics.normalize_supplier, analytics.categorize_license and analytics.parse_purl. Creating these objects does not schedule the retained ingestion services.
6.2 Legacy objects excluded from the serving chain
The retained StellaOps.Findings.VulnCorrelation.Persistence project embeds Migrations/001_v1_analytics_baseline.sql and excludes Migrations/_archived/ resources. AddVulnCorrelationMigrations would register that assembly under module Findings.VulnCorrelation and schema analytics, but a source-wide caller search finds only its declaration outside frozen code and tests.
These legacy objects are absent from the consolidated migration chain:
| Legacy objects | Consolidated disposition |
|---|---|
analytics.schema_version | Legacy version marker omitted; the family uses findings.schema_migrations. |
analytics.vex_overrides | Omitted under DC-32. The retained attestation consumer still references it. |
analytics.daily_vulnerability_counts, analytics.daily_component_counts | Rollup tables omitted under ruling A4. |
mv_supplier_concentration, mv_license_distribution, mv_vuln_exposure, mv_attestation_coverage | Materialized views omitted under ruling A4. |
sp_top_suppliers, sp_license_heatmap, sp_vuln_exposure, sp_fixable_backlog, sp_attestation_gaps, sp_mttr_by_severity | Legacy query functions omitted. |
compute_daily_rollups, refresh_all_views, update_updated_at_column | Legacy rollup, refresh and unused trigger functions omitted. |
The omitted view/function names above are in the analytics schema. The consolidation design preserves the dated disposition decisions. The analytics SQL reference documents the legacy design; it is not the serving startup migration source.
The legacy baseline concatenates historical migrations. Later definitions supersede earlier definitions within that file. Reading or retaining that DDL does not apply it to Findings, and this document does not establish whether an older installation retains predecessor objects.
6.3 Re-verify from the repository root
rg -n 'AddVulnCorrelationMigrations|AddAnalyticsIngestion' src -g '*.cs' -g '!__Obsoleted/**' -g '!**/__Tests/**'
rg -n 'AddConsolidatedFindingsPersistence|AddAdvisoryCorpusProjection|AddAdvisorySbomProjection|AddScannerSecurityProjection' src/Findings/StellaOps.Findings.WebService/Program.cs src/Findings/StellaOps.Findings.Worker/Program.cs
rg -n 'AddStartupMigrations|ModuleName|SchemaName|migrationsAssembly|ConnectionEnvironmentVariable|ConnectionConfigurationKey' src/Findings/__Libraries/StellaOps.Findings.Persistence/Extensions/ConsolidatedFindingsPersistenceExtensions.cs
rg -n 'CREATE.*(TABLE|VIEW|FUNCTION|PROCEDURE).*analytics|analytics[.](mv_|sp_|daily_|schema_version|vex_overrides|compute_daily_rollups|refresh_all_views)' src/Findings/__Libraries/StellaOps.Findings.Persistence/Migrations
Expected: the first search returns only the two retained declarations. The serving hosts select consolidated persistence. The migration search shows the ingestion tables and three helpers; excluded objects appear only in historical exclusion comments, not executable create statements. These checks establish source wiring and DDL, not deployed schema contents or ingestion acceptance.
7. Retained library configuration
The retained options use Platform:AnalyticsIngestion(AnalyticsIngestionOptions.SectionName). No serving host calls this registration, so these keys do not enable ingestion in current Findings.
| Key | Default | Notes |
|---|---|---|
Platform:AnalyticsIngestion:Enabled | true | false ⇒ the SBOM lane logs “disabled by configuration” and exits. |
…:PostgresConnectionString | — | Library data-source connection; AddAnalyticsIngestion can fill a missing value from its defaultConnectionString argument. This is not the consolidated migration connection selector. |
…:AllowedTenants | (empty) | Ingestion tenant allowlist. Empty ⇒ every tenant is accepted (see §8). |
…:Streams:ScannerStream | orchestrator:events | |
…:Streams:ConcelierObservationStream | concelier:advisory.observation.updated:v1 | |
…:Streams:ConcelierLinksetStream | concelier:advisory.linkset.updated:v1 | |
…:Streams:AttestorStream | attestor:events | |
…:Streams:StartFromBeginning | false | |
…:Streams:ResumeFromCheckpoint | true | |
…:Streams:ScannerCheckpointFilePath | (derived) | Defaults to <Cas:RootPath>/.state/platform-scanner-stream.checkpoint. |
…:Cas:RootPath | — | Must be the same CAS volume the scanner writes SBOMs to. |
…:Attestations:BundleUriTemplate | bundle:{digest} |
8. Retained analytics tenancy limits
- The retained analytics tables have no tenant column. Both the legacy baseline and the consolidated analytics table definitions key this inventory without a tenant dimension. This observation does not describe the tenant-bound
findingsorfindings_securityschemas. - Tenancy is enforced only at the ingestion boundary, from the event envelope’s tenant (
envelope.Tenant/ payloadTenantId) — never from a caller-supplied header — against theAllowedTenantsallowlist (TenantNormalizer.IsAllowed, which strips aurn:tenant:prefix and compares case-insensitively). - An empty
AllowedTenantslist allows every tenant (Utilities/TenantNormalizer.cs,IsAllowed:if (allowedTenants is null || allowedTenants.Count == 0) return true;). The old overlay is deleted; no current deployment setting activates this library registration. - The retained validator rejects multiple explicit tenants when ingestion is enabled (
AnalyticsIngestionOptions.Validate, CLO-4).Normalize()stripsurn:tenant:and de-duplicates case-insensitively before that check.AddAnalyticsIngestionregisters the validator throughValidateOnStart(). The empty allowlist still admits every tenant; the validator therefore does not establish tenant isolation for that default. - Consequently
analytics.*is a single-tenant read model per deployment. Platform’s former/api/analytics/*endpoints required theanalytics.readscope andRequireTenant(), but the tenant only participated in the cache key (PlatformAnalyticsService.cs:55-168) — the SQL cannot filter by tenant because the column does not exist. True per-tenant isolation would require a real tenant dimension end-to-end (schema column + ingestion write + read-path filter); that is a follow-up, not a current guarantee. Recorded inSPRINT_20260712_006andSPRINT_20260712_009(CLO-4) Decisions & Risks. - The VulnCorrelation library has no serving host or gateway route. The consolidated Findings security read model serves
/api/findings/v1/security/*throughfindings-web(StellaOps.Findings.Security.Application/Endpoints/SecurityReadModelEndpoints.cs). Platform’s former analytics read surface is withdrawn.
9. Deployment and operations
The former stellaops/findings-vulncorrelation image, host project and devops/compose/docker-compose.findings-vulncorrelation.override.yml overlay are retired. Their health, transport and shared-CAS instructions are historical and cannot deploy a current host.
Use the Findings architecture for the current findings-web and findings-worker roles. Their startup migrations create the consolidated schema described above. Neither host schedules the retained analytics consumers, and adding legacy configuration keys does not create that wiring.
10. Determinism & replay
The retained library implements at-least-once ingestion with upserts and dedupe-guarded inserts. The scanner checkpoint is advanced after the event is handled, so a crash between handling and checkpointing replays the last event (idempotently).
11. References
- Findings module map and sibling hosts:
architecture.md. - Withdrawn Platform analytics surface and maintenance lane:
../platform/platform-service.md. - Relocation sprint (PAC-5):
SPRINT_20260703_001_Platform_full_agnostic_cleanup.md. - Auto-migration contract:
AGENTS.md§2.7.
