VexLens (VEX Consensus Lens) Architecture
Scope. Architecture and API contract for VexLens, the Stella Ops VEX Consensus Lens: trust modelling, deterministic consensus computation over multiple VEX statements, and the consensus/projection/issuer APIs. This dossier is for service implementers and for consumers of consensus output (Policy Engine, Vulnerability Explorer, Advisory AI, Console). Originates from Epic 7, the VEX Consensus Lens.
Related: VexHub (statement aggregation and the source catalog VexLens reads), Policy Engine (consumes consensus results), and Issuer Directory / Authority (issuer trust resolution).
1) Purpose
Compute a deterministic, reproducible consensus view over multiple VEX statements for each (artifact, advisory) tuple while preserving conflicts and provenance. Output is consumed by Policy Engine, Vulnerability Explorer, Advisory AI, and Console overlays.
2) Inputs
- Normalized VEX statements for a
(vulnerabilityId, productKey)pair. In the shipped WebService these are read from VexHub viaVexHubStatementProvider(IVexStatementRepository/IVexSourceRepository); VexHub in turn ingests connector output normalized from OpenVEX, CSAF VEX, and CycloneDX VEX (Excititor connectors stamp thevex.provenance.*metadata described below). Each statement carries status, justification, source/issuer id, first/last-seen timestamps, and a signature-verification context. - Issuer trust resolved through the Issuer Directory (
IIssuerDirectory→AuthorityIssuerDirectoryAdapter, backed byStellaOps.IssuerDirectorypersistence), supplying issuer category and trust tier. There is no standalonevex_issuer_registrytable; trust weighting is computed byTrustWeightEnginefrom these directory attributes plus signature/freshness/format/status factors. - Optional runtime context and policy precedence (
ConsensusPolicy:Mode,MinimumWeightThreshold,ConflictThreshold, preferred issuers) supplied per request.
Note: when no host wires a concrete
IIssuerDirectory/IConsensusEventEmitter/ snapshot store, the library binds fail-closedUnsupported*defaults (see §5) rather than in-memory fallbacks.
Provenance field mapping (new input contract)
Excititor connectors now stamp every raw VEX document with vex.provenance.* metadata. Lens ingests these keys alongside the normalized tuples:
| Field | Description | Lens usage |
|---|---|---|
vex.provenance.provider / providerName / providerKind | Logical issuer identity and type supplied by the connector (e.g., excititor:ubuntu, distro). | Seed issuer lookup, short-circuit Issuer Directory calls when we already trust the connector’s profile. |
vex.provenance.trust.weight | Connector-provided base weight (0–1). | Multiplied by freshness decay & justification multipliers; overrides registry default. |
vex.provenance.trust.tier & trust.note | Human/ops tier labels (vendor, distro-trusted, etc.) plus descriptive note. | Drives secondary sort (after timestamp) and Console labels; conflicts report per-tier deltas. |
vex.provenance.cosign.* | Cosign issuer/identity pattern (+ optional Fulcio/Rekor URIs). | When present, Lens marks the statement as “cryptographically attested” and applies the higher confidence bucket immediately. |
vex.provenance.pgp.fingerprints | Ordered list of PGP fingerprints used by the feed. | Enables Lens to validate deterministic fingerprint sets against Issuer Directory entries and flag mismatches in conflict summaries. |
Runtime note: provenance hints are not proof by themselves. Current VexLens runtime applies a cryptographically attested confidence tier only after configured signature verification and issuer trust validation succeed.
The trust engine preserves the raw metadata so downstream components can audit decisions or remap tiers without replaying ingestion.
3) Core algorithm
Consensus is computed per (vulnerabilityId, productKey) request by VexConsensusEngine (src/VexLens/StellaOps.VexLens/Consensus/VexConsensusEngine.cs). The engine is mode-driven rather than a single fixed lattice join — the lattice is one of four configurable modes.
- Trust weighting — each input statement is first weighted by
TrustWeightEngine. The combined weight is(issuerWeight × signatureWeight × freshnessWeight) + statusSpecificityWeight + customWeight, clamped to[0.0, 2.0](MaximumWeightwas raised from the earlier 1.5 so a first-party attestation is not clamped down to a vendor’s ceiling). Issuer multipliers default to vendor 1.0 / distributor 0.9 / internal 0.8 / community 0.7 / aggregator 0.6 / unknown 0.3, plus a first-party image-specific signed-attestation multiplier of 1.3 (e.g.excititor:oci-openvex, matched by issuer id) that strictly outranks vendor/distributor. Tier bonuses apply on top (authoritative +0.2, trusted +0.1, untrusted −0.3). Freshness buckets at 7 days (fresh, ×1.0), 90 days (stale, ×0.8), 365 days (expired/older, ×0.5). Signature outcomes scale from valid (×1.0) down to no/invalid/revoked signature. Signature verification is a weight input only — never a trust-tier grant (SPRINT_20260703_001):vexhub.sources.trust_tieris operator-curated via the VexLens issuer endpoints, and the Excititor projection sink lands every new source asUNKNOWN. - Threshold filter — statements below
MinimumWeightThreshold(default0.1) are dropped; if none qualify the result isNoData/under_investigationwith confidence0. WhenRequireJustificationForNotAffectedis set (defaulttruesince SPRINT_20260703_001; OpenVEX/CSAF conformance), anot_affectedstatement with no justification is dropped here too — it is not cap-eligible and can never win consensus in any mode. Config escape hatch for legacy tenants:VexLens:Consensus:RequireJustificationForNotAffected=false. - Mode evaluation — one of:
WeightedVote(default) — sum weight per status; the highest-weight status wins. Outcome isUnanimous/Majority(≥50% of weight) /Plurality. Confidence = vote fraction × weight-spread factor.HighestWeight— single highest-weighted statement wins.Lattice— selects the most conservative status by lattice order (see below).AuthoritativeFirst— vendor / authoritative-tier issuers take precedence (confidence pinned to0.95when an authoritative source wins). This is the mode the score-cap bridge pins (supplier-authoritative: curated-tier issuers outrank aggregate votes), whileWeightedVotestays the default for analytics/stats surfaces (SPRINT_20260703_001). Mode is selectable per request (ConsensusPolicy.Mode).
- Deterministic ordering & tie-breaks — statements are ordered by weight desc → statement timestamp (
LastSeen ?? FirstSeen) desc → lexical source id asc → statement id asc. An unresolved tie at the top (equal weight, timestamp, and source) yields an explicitUnknownstatus withIndeterminateoutcome and zero confidence (see §9). - Conflict detection — every disagreeing pair of statuses is recorded with a severity (
affected↔not_affected= Critical,affected↔fixed= High,fixed↔not_affected= Medium, anything touchingunder_investigation/unknown= Low) and a resolution note. Conflicts reduce confidence by a per-severity penalty (Critical −0.3 … Low −0.05).
The lattice StatusOrder (most conservative first) is affected (0) < unknown (1) < under_investigation (2) < fixed (3) < not_affected (4), with bottom affected and top not_affected. (This is the actual order in VexConsensusEngine.CreateDefaultConfiguration; the earlier doc ordering unknown < under_investigation < not_affected | affected < fixed was incorrect.)
A VexConsensusResult carries: consensusStatus, consensusJustification, confidenceScore, outcome, a rationale (summary + factors + per-status weights), contributions (per-statement weight, contribution fraction, winner flag), and optional conflicts. The :withProof variant additionally returns merge steps, conflict records with penalties, the trust-weight breakdown, the consensus mode used, and the winning issuer id + trust tier (resolution.winningIssuerId / winningIssuerTier, SPRINT_20260703_001) so the score-cap decision names who capped and under which mode. Conflicts remain visible so Policy Engine can choose a suppression strategy.
Persisted projections (see §5) record attestation_digest and input_hash columns for replay/attestation, and computation emits orchestrator-ledger events (consensus.computed, consensus.status_changed, consensus.conflict_detected, consensus.alert) via IConsensusEventEmitter when a real emitter is wired — not a single vex.consensus.updated DSSE event.
4) APIs
All VexLens HTTP endpoints are mounted under /api/v1/vexlens by StellaOps.VexLens.WebService (Program.cs → MapVexLensEndpoints, MapExportEndpoints). Tenancy is carried by the X-StellaOps-TenantId header (falling back to the request body tenantId where present). Authorization uses the vexlens.read / vexlens.write scope policies (AddStellaOpsScopePolicy), not generic “Authority scopes”. A named fixed-window rate limiter (vexlens, 100 req/min) is registered and UseRateLimiter is in the pipeline, but no endpoint group calls RequireRateLimiting("vexlens") and no global limiter is set — so as wired the 100 req/min limit is currently inert (defined, not enforced).
Implementation note (2026-05-29): the routes below reflect the shipped surface. Earlier
/v1/vex/*paths (/v1/vex/consensus,/v1/vex/conflicts,/v1/vex/trust/weights,/v1/vex/consensus/export) were never implemented and have been corrected here. Consensus is computed on demand viaPOST, not fetched byGETquery string.
Consensus (vexlens.read)
POST /api/v1/vexlens/consensus— evaluate all registered VEX statements for a(vulnerabilityId, productKey)pair; returns weighted consensus disposition, confidence, and per-issuer contributions.400on missing/unparseable input.POST /api/v1/vexlens/consensus:withProof— as above plus the full proof object (per-issuer verdicts, trust-weight breakdown, intermediate merge steps) for auditable evidence trails.POST /api/v1/vexlens/consensus:batch— multiple pairs in one request; per-pair errors are reported without failing the batch.
Projections (vexlens.read)
GET /api/v1/vexlens/projections— query stored projections (filters:vulnerabilityId,productKey,outcome,minimumConfidence,computedAfter/computedBefore,statusChanged; paginated, sortable).GET /api/v1/vexlens/projections/{projectionId}— full projection detail;404if not found.GET /api/v1/vexlens/projections/latest— most recent projection for a(vulnerabilityId, productKey)pair;404if none computed.GET /api/v1/vexlens/projections/history— ordered projection history (newest first) for a pair.GET /api/v1/vexlens/stats— tenant-scoped aggregate statistics (counts by disposition, average confidence, conflict rate).GET /api/v1/vexlens/conflicts— projections with one or more issuer conflicts (filtered server-side toconflictCount > 0).
Delta & noise-gating (vexlens.read)
POST /api/v1/vexlens/deltas/compute— structured delta report between two named snapshots;400if a snapshot ID is missing.GET /api/v1/vexlens/gating/statistics— aggregated noise-gating statistics over an optional time window.POST /api/v1/vexlens/gating/snapshots/{snapshotId}/gate— apply noise-gating to a raw snapshot, persist the gated snapshot + statistics, return edge/verdict counts;404if the snapshot is unknown.
Issuer directory (vexlens.write)
GET /api/v1/vexlens/issuers/GET /api/v1/vexlens/issuers/{issuerId}— list / detail.POST /api/v1/vexlens/issuers— register issuer (201); audited (VexLens.RegisterIssuer).DELETE /api/v1/vexlens/issuers/{issuerId}— revoke issuer (204); audited (VexLens.RevokeIssuer).POST /api/v1/vexlens/issuers/{issuerId}/keys/DELETE .../keys/{fingerprint}— add / revoke verification key; audited (VexLens.AddIssuerKey/VexLens.RevokeIssuerKey).
Note: there is no dedicated
trust/weightsmutation endpoint. Trust tiers and keys are governed through the Issuer Directory CRUD endpoints above (which the WebService backs with the sharedIssuerDirectorypersistence andAuthorityIssuerDirectoryAdapter). Statement-level trust weighting is computed deterministically byTrustWeightEnginefrom issuer category/tier, signature, freshness, source format, and status — not set via an admin API.
Export (vexlens.read) — defined but not yet wired
The export route group is implemented in ExportEndpointExtensions.MapExportEndpoints (/api/v1/vexlens/export/consensus/{vulnerabilityId}/{productId}, /export/projections/{projectionId}, POST /export/batch, POST /export/combined) supporting openvex, spdx3 (SPDX 3.0.1 security profile), and csaf formats. However, as of 2026-05-29 Program.cs calls only MapVexLensEndpoints() — MapExportEndpoints() is not invoked, so these routes are dormant code and not reachable in the running WebService. Treat the export surface as implemented-but-unwired until Program.cs maps it.
Consensus responses carry rationale, contributions, conflicts, and (for :withProof) the full proof. A signed consensus_digest / DSSE envelope is a forward-looking goal; the current VexConsensusResult does not emit a per-record DSSE signature.
5) Storage
Storage is PostgreSQL in the vexlens schema, created and converged by embedded startup migrations (AddStartupMigrations in Program.cs, in StellaOps.VexLens.Persistence). The live migration set on disk is:
| File | Adds |
|---|---|
001_v1_vexlens_baseline.sql | Collapsed pre-1.0 baseline (folds in the archived 001_consensus_projections.sql + 002_noise_gating_state.sql, which are under Migrations/_archived/ and are not embedded). |
002_consensus_winning_source.sql | winning_source on consensus_projections. |
003_consensus_winning_issuer_trust_tier.sql | winning_issuer_trust_tier (TRUST-CAP-0 — the Findings cap gates on the winning issuer’s curated tier). |
004_consensus_outcome_check_fix.sql | Repoints the outcome CHECK constraint at the real ConsensusOutcome set (CONS-C3). |
The earlier doc names (vex_consensus, vex_consensus_history, vex_conflict_queue “collections”) do not exist — they survive only as unused legacy VexLensStorageOptions.ProjectionsCollection/HistoryCollection config defaults. Actual tables:
vexlens.consensus_projections— computed consensus per(vulnerability_id, product_key, tenant_id)withstatus(not_affected/affected/fixed/under_investigation/unknown),justification,confidence_score(CHECK0.0..1.0),outcome,statement_count,conflict_count,rationale_summary,winning_source(issuer/provider id of the winning contribution — theStatementContribution.IsWinner; surfaces verdict provenance to the Findings layer for “not_affected from <source>” display + per-case-by-source trust overrides; added by002_consensus_winning_source.sql),computed_at/stored_at,attestation_digest, andinput_hash(SHA-256 of sorted inputs for replay). History is modelled in the same table viaprevious_projection_id+status_changed; a partial unique index (idx_consensus_projections_latestover(vulnerability_id, product_key, COALESCE(tenant_id, ''))whereprevious_projection_id IS NULL OR status_changed = TRUE) keeps one active projection per pair/tenant. ABEFORE UPDATEtrigger refreshesstored_at. Trigram (gin_trgm_opsonvulnerability_id) and B-tree indexes back the query surface.outcomeCHECK constraint — fixed, no longer a live bug. The pre-1.0 baseline pinnedoutcome ∈ ('unanimous', 'majority', 'conflict', 'single_source', 'none')whilePostgresConsensusProjectionStore.MapOutcomewrites theConsensusOutcomeset, so every non-unanimous outcome — in particularno_data, the common case across an estate — failed the check with SqlState 23514.004_consensus_outcome_check_fix.sql(SPRINT_20260703_004 / CONS-C3) drops and re-adds the constraint as('unanimous', 'majority', 'plurality', 'conflict_resolved', 'no_data', 'indeterminate'). The migration is forward-only and idempotent, so any database created from 001/002/003 converges on startup.
vexlens.consensus_inputs— the contributing statements per projection (statement_id,source_id,status,confidence,weight).vexlens.consensus_conflicts— detailed conflict records (issuer1/issuer2,status1/status2,severityin LOW/MEDIUM/HIGH/CRITICAL).vexlens.noise_gate_raw_snapshots,vexlens.noise_gate_gated_snapshots, andvexlens.noise_gate_statisticsstore the live noise-gating raw inputs, persisted gated outputs, and aggregated statistics for the/api/v1/vexlens/gating/*surface.
Storage driver is postgres by default; memory and dual-write drivers carry process-local state and require explicit local/test opt-in (AddVexLensForTesting or VexLens:Storage:AllowInMemoryStorage=true).
Runtime storage and service defaults (2026-04-24)
AddVexLensdefaults consensus projection storage to the PostgreSQL driver and fails at registration time ifVexLens:Storage:ConnectionStringis missing.- Process-local projection storage is available only through explicit local/test opt-in (
AddVexLensForTestingorVexLens:Storage:AllowInMemoryStorage=truein a non-production harness). - Runtime hosts must register real issuer directory, consensus event transport, snapshot store, and gating statistics store implementations. The library defaults for those dependencies are unsupported fail-closed services, not in-memory/null fallbacks.
- The built-in DSSE handler can verify against configured local trust roots through
StellaOps.AttestationwhenVexLens:Trust:SignatureTrustRootPathsor request-level trust roots are supplied. Without local roots it still returns unavailable (ERR_DSSE_004), and forged signatures fail closed (ERR_DSSE_005); structure-only parsing must not produce aValidtrust signal. - The built-in compact JWS handler can verify against local Fulcio root certificates from
VexLens:Trust:SignatureTrustRootPathsor request-level trust roots when the protected header carries anx5ccertificate chain. Success requires chain validation to a configured local root, a non-CA leaf certificate withdigitalSignaturekey usage, a Fulcio OIDC issuer extension accepted by request-level trusted issuer policy when present, and signature verification with the leaf certificate. Missing roots (ERR_JWS_004), unsupported critical headers (ERR_JWS_006), missingx5c(ERR_JWS_FULCIO_001), invalid chains (ERR_JWS_FULCIO_003), missing Fulcio issuer extensions (ERR_JWS_FULCIO_004), untrusted Fulcio issuers (ERR_JWS_FULCIO_005), unacceptable leaf certificates (ERR_JWS_FULCIO_007), unsupported algorithms, and tampered signatures fail closed; JWS header fields and Fulcio issuer strings are diagnostic metadata only. - The built-in compact JWS handler can verify local JWKS/kid signatures when the configured trust roots include JWKS files and the protected
kidresolves to a supported public key. WithCheckRevocation=true, both Fulciox5cand JWKS/kid paths require a local revocation bundle (revocations.json,revocation-bundle.json, or*.revocations.json) in the configured trust-root paths. Missing or malformed revocation material fails closed; revoked Fulcio leaf certificates and revoked JWKS public-key fingerprints returnRevokedCertificate. - Detached compact JWS verification is supported when
SignatureVerificationRequest.Contentcontains the signed payload bytes andSignatureVerificationRequest.DetachedSignaturecontains the compact JWS. The verifier reconstructs the signing input withbase64url(Content)before running the same local Fulcio/JWKS/revocation gates. Detached-shaped JWS passed withoutDetachedSignature(ERR_JWS_DETACHED_001), empty detached payload bytes, payload-part mismatches (ERR_JWS_DETACHED_002), and altered payload bytes fail closed. Online Fulcio discovery and online Rekor consistency/tile verification remain unavailable in the default runtime verifier and must not increase issuer trust; Scanner signed-SBOM Fulcio chain validation is a separate DSSE archive path, not VexLens JWS proof. - There is one signature-verification surface:
StellaOps.VexLens/Verification/SignatureVerifier.cs. The parallelStellaOps.VexLens.Coreproject (a never-wired duplicate carrying its own normalizer, product-mapper, signature verifier, and a divergentTrustFactor/TrustWeightmodel) was deleted in SPRINT_20260712_005 (DEAD-2) after a triple-check gate found no production reference to it. Do not reintroduce a second copy. - Runtime
AddVexLensdoes not register process-local rationale or source-trust caches by default. Hosts that need those caches must bind a durable or explicitly governed cache implementation; unit tests keep any in-memory cache helpers inside test projects. OrchestratorLedgerEventEmitterrequires a concreteIOrchestratorLedgerClient; constructing it without a ledger client is a configuration error instead of a silent no-op.- Test harnesses and statement-history in-memory helpers are not compiled into the production VexLens assembly; tests must define helper stores inside test projects.
AddVexLensForTestingremains the explicit in-memory harness for deterministic unit tests.
Noise-gating runtime contract (2026-04-14)
- The owning VexLens web runtime resolves
ISnapshotStoreandIGatingStatisticsStoreto PostgreSQL-backed implementations, not process-local in-memory stores. - Startup schema convergence for the noise-gating tables is owned by embedded startup migration
002_noise_gating_state.sqlinStellaOps.VexLens.Persistence; fresh local volumes must converge without manual SQL. POST /api/v1/vexlens/gating/snapshots/{snapshotId}/gatenow persists the gated snapshot and statistics as part of the live request path, so later delta/statistics reads reflect durable backend state.- The Angular production app binds the real VexLens noise-gating HTTP client through
app.config.ts, so triage surfaces call/api/v1/vexlens/gating/*instead of relying on optional providers or mock helpers.
6) Recompute strategy
- Recompute is scheduled through
IConsensusJobServiceusing orchestrator job types under theconsensus.prefix (ConsensusJobTypes):consensus.compute,consensus.batch-compute,consensus.incremental-update(triggered by new/updated VEX statement ingestion),consensus.trust-recalibration(after trust-weight config changes),consensus.projection-refresh, andconsensus.snapshot-create/consensus.snapshot-verifyfor export/mirror bundle assembly and verification. - Job priorities are fixed (incremental-update 50 > compute 40 > batch/refresh 30 > recalibration 20 > snapshots 10), and batching is supported for batch/incremental/recalibration/refresh jobs.
- Deterministic ordering (§3) ensures identical results for the same input set;
input_hashon each projection plus thesnapshot-verifyjob support replay verification.
7) Observability
Meter / activity source name: StellaOps.VexLens (VexLensMetrics, VexLensActivitySource).
- Metrics (actual names,
vexlens.*):vexlens.consensus.computed_total,vexlens.consensus.conflicts_total,vexlens.consensus.confidence,vexlens.consensus.duration_seconds,vexlens.consensus.status_changes_total; plus trust (vexlens.trust.weights_computed_total,vexlens.trust.weight_value), signature (vexlens.signature.verified_total,vexlens.signature.failures_total), normalization, product-mapping, projection, and issuer-directory counters. (The earliervex_consensus_conflicts_total/vex_consensus_latency_seconds/vex_consensus_recompute_secondsnames are not emitted.) - Traces (actual span names):
vexlens.compute_consensus,vexlens.normalize,vexlens.map_product,vexlens.verify_signature,vexlens.compute_trust_weight,vexlens.store_projection,vexlens.query_projections,vexlens.issuer.{operation}— notconsensus.group/join/persist. - Logs: structured event IDs in
VexLensLogEvents(consensus 5001–5006, projections 6001–6003, issuer 7001–7005, etc.) capturing vulnerability/product, issuer, status, and trust adjustments. - Runbook + dashboard stub (offline import):
runbooks/observability.md,runbooks/dashboards/vex-lens-observability.json.
8) Offline & export
- Per-record / batch export logic exists (
/api/v1/vexlens/export/*, §4) foropenvex,spdx3(SPDX 3.0.1 security profile), andcsaf, plus a combined SBOM+VEX SPDX export, backed byIConsensusExportServiceand the SPDX3 mappers (StellaOps.VexLens.Spdx3). Note these routes are not yet mapped inProgram.cs(see §4). - Export Center / Offline Kit mirror profiles consume the consensus data; deterministic JSONL bundle assembly (
consensus.jsonl,conflicts.jsonl, manifest, signatures) is the intended bundle shape.
CLI surface (corrected 2026-05-30). The wired
stella vexcommand is built byCommandFactory.BuildVexCommand(src/Cli/StellaOps.Cli/Commands/CommandFactory.cs, registered viaroot.Add(BuildVexCommand(...))). Its subcommands arevex consensus list/vex consensus show,vex simulate,vex export(+vex export verify),vex obs,vex explain,vex gen,vex providers,vex gate-scan,vex verdict, andvex unknowns.A consensus-export CLI command does exist:
stella vex exportexports VEX consensus data as an NDJSON bundle (signed by default;--unsignedopts out) viaCommandHandlers.HandleVexExportAsync, filtered by--vuln-id/--product-key/--purl/--status/--policy-versionand written to--output.stella vex export verifychecks the bundle’s--digestand--public-keysignature. This is a CLI-side NDJSON consensus export and is distinct from the WebService/api/v1/vexlens/export/*OpenVEX/SPDX3/CSAF routes (which remain unwired — see §4). The earlier doc claim that “astella vex consensus exportCLI command does not exist / is roadmap” was incorrect.The former unwired
VexCommandGroup.cssample-data group was deleted on 2026-07-29 (SPRINT_20260729_001_Cli_fabricated_reachability_verdicts). It was never referenced by the root factory. The shippedstella vexsurface comes from the registered VEX plug-in path;vex lens/vex advisoryentries incli-routes.jsonremain legacy aliases, not evidence that the deleted sample handlers were shipped.
9) Advisory Gap Status (2026-03-04 Batch)
Status: implementation delivered in Sprint 305.
- Normalized status contract now exposes explicit
unknown(VexStatus.Unknown) in active model paths. - Normalizers preserve unknown semantics instead of collapsing unrecognized statuses to
under_investigation:- OpenVEX unknown values map to
unknown. - CycloneDX unknown
analysis.statemaps tounknownwith warningWARN_CDX_008. - CSAF explicit unknown product status categories (
known_unknown,unknown) map tounknown.
- OpenVEX unknown values map to
- Consensus merge precedence is deterministic with explicit tie-breaks:
- trust weight desc
- statement timestamp desc
- lexical source id asc
- statement id asc
- Unresolvable ties now remain explicit
unknownwithindeterminateoutcome and zero confidence. - Projection storage/list/history ordering includes deterministic secondary keys for equal timestamps in both in-memory and Postgres paths.
- Projection API contracts include unknown audit fields (
unknownRationale,unknownProvenanceTrace) for summary/detail responses.
Closure sprint:
docs/implplan/SPRINT_20260304_305_VexLens_unknown_lifecycle_and_merge_determinism.md
Unified provider catalog (Sprint 20260503-012)
Provider discovery now flows through the federated advisory-sources catalog (GET /api/v1/advisory-sources/catalog). VexLens consumers resolve provider trust weights via the catalog entry’s ConfigurationDescriptorId, which maps 1:1 onto Excititor’s VexProviderRuntimeSettingsCache, rather than maintaining a parallel provider list. The 7 canonical VEX providers carry the excititor:{provider} source-id namespace; entries with Backend = "excititor" and Category = "Vex" denote VEX surfaces in both UI and CLI consumers. See docs/modules/vex-hub/architecture.md §11 for the contributor wiring and operator migration note.
