VexHub Architecture
Scope. Architecture and operational contract for VexHub, the Stella Ops VEX aggregation service that normalizes, validates, and distributes VEX statements with deterministic, offline-friendly outputs. This dossier is for service implementers, operators configuring VEX sources, and integrators consuming the distribution API.
Last source verification: 2026-07-25 (active migrations, source-trust repository, Excititor projection and operator-authoring paths, and VexLens curation routes).
Related: VexLens (trust scoring / consensus over VexHub statements) · Excititor / Concelier (upstream connectors and AOC-checked claim projection, §9) · Policy Engine (consumes consensus).
1) Purpose
VexHub collects VEX statements from multiple upstream sources, validates and normalizes them, detects conflicts, and exposes a distribution API for internal services and external tools (Trivy/Grype). It is the canonical aggregation layer that feeds VexLens trust scoring and Policy Engine decisioning.
2) Responsibilities
- Scheduled ingestion of upstream VEX sources (connectors + mirrored feeds).
- Canonical normalization to OpenVEX-compatible structures.
- Validation pipeline (schema + signature/provenance checks).
- Conflict detection and provenance capture.
- Distribution API for CVE/PURL/source queries and bulk exports.
Non-goals: policy decisioning (Policy Engine), consensus computation (VexLens), raw ingestion guardrails (Excititor AOC).
2.1) Registry and Plugin Boundary
VexHub is a host-owned VEX validation, normalization, conflict, and distribution service. Its registry-shaped inputs are data/config by default, not executable plugins:
- VEX documents in OpenVEX, CSAF, CycloneDX VEX, SPDX VEX, or Stella-owned shapes.
- Trust lattice entries, source definitions, issuer metadata, schema/format identifiers, mirror metadata, and provenance records.
- Operator configuration for rate limits, trust roots, API keys, polling intervals, and signature requirements.
OpenVEX/CSAF/CycloneDX validation and normalization remain host-owned unless a future sprint creates a VexHub-owned signed normalizer interface. Optional source connector code belongs to Concelier/Excititor overlays because those services own upstream fetching and AOC-checked claim projection. The base VexHub image must not include optional connector payloads, and importing VEX data or source metadata must not execute code.
3) Component Model
- StellaOps.VexHub.WebService: Minimal API host (
src/VexHub/StellaOps.VexHub.WebService). Hosts distribution endpoints, the admin conflict-resolution endpoint, health probes, OpenAPI, rate limiting, API-key + bearer auth, and Stella Router microservice registration (serviceName: "vexhub"). - StellaOps.VexHub.Core (
src/VexHub/__Libraries/StellaOps.VexHub.Core): normalization pipeline (VexNormalizationPipeline), validation (VexSchemaValidatorFactoryoverOpenVexSchemaValidator/CsafVexSchemaValidator/CycloneDxVexSchemaValidator), signature verification (VexSignatureVerifier), statement flagging (StatementFlaggingService), export (VexExportService), and the ingestion service (VexIngestionService). - StellaOps.VexHub.Persistence (
src/VexHub/__Libraries/StellaOps.VexHub.Persistence): PostgreSQL data source, repositories (sources/statements/conflicts/provenance/ingestion-jobs), and embedded SQL migrations for schemavexhub. - Background ingestion scheduler:
VexIngestionScheduler(anIHostedService) is implemented in Core and registered viaAddVexHubIngestionScheduler(). Roadmap / not wired in the current WebService host —Program.csdoes not callAddVexHubIngestionScheduler(), and there is noStellaOps.VexHub.Workerproject. In production, scheduled upstream ingestion is performed by the Excititor Worker, which projects AOC-checked claims intovexhub.statements(see §9). VexHub’s own polling/scheduler path is implemented but inactive on the running service.
Status note. The VexHub WebService, Core pipelines, Persistence layer, distribution API, and the admin conflict-resolution endpoint are implemented and wired. The standalone background ingestion worker described in earlier drafts does not exist as a deployed component.
4) Data Model
Storage-model decision (2026-07-01): the deliberate
vex.claims(Excititor, all claims) /vexhub.statements(this schema, enforced/worker-projected only) double-store is documented and recommended KEEP — see storage-model-decision.md for the full blast-radius map of both consumers, the enforced-vs-advisory asymmetry, measured footprint, revisit triggers, and the projection-parity guard.
The PostgreSQL schema is vexhub (constant VexHubDataSource.DefaultSchemaName). Embedded migrations are applied on startup (AddStartupMigrations): 001_v1_vexhub_baseline.sql is the squashed pre-1.0 baseline, 002_source_trust_curation.sql establishes operator-owned source trust, and 003_statement_source_document_lookup.sql adds the projection-repair lookup index. Historical incrementals remain under Migrations/_archived/pre_1.0/; the active top-level migration directory is the ledger authority. VexHub statements are global, not tenant-scoped — VexHubDataSource documents that VEX statements and provenance are shared across all tenants; there is no tenant_id column on any VexHub table.
Tables (actual columns after the active embedded migration ledger converges):
vexhub.sources(configured VEX providers)source_id(PK, TEXT),name,source_uri,source_format(OPENVEX|CSAF_VEX|CYCLONEDX_VEX|SPDX_VEX|STELLAOPS),issuer_category(VENDOR|DISTRIBUTOR|COMMUNITY|INTERNAL|AGGREGATOR),trust_tier(AUTHORITATIVE|TRUSTED|UNTRUSTED|UNKNOWN),tier_curated_by,curated_at,is_enabled,polling_interval_seconds,last_polled_at,last_error_message,config(JSONB),created_at,updated_at,consecutive_failures,next_eligible_poll_at.- Seeded (disabled by default):
redhat-csaf,cisco-csaf,openvex-community. - Trust is operator-curated state, not a consequence of signature presence. New and non-curated sources converge to
UNKNOWN; the Excititor projection sink never escalatestrust_tierand preserves a curated value on conflict.PostgresVexSourceRepository.SetTrustTierAsyncis the only tier-grant path: non-UNKNOWNgrants stamptier_curated_by/curated_at, whileUNKNOWNrevokes the grant and clears that provenance.
vexhub.statements(aggregated normalized statements)id(UUID PK),source_statement_id,source_id(FK → sources),source_document_id,vulnerability_id,vulnerability_aliases(TEXT[]),product_key,status(not_affected|affected|fixed|under_investigation),justification(OpenVEX justification enum),status_notes,impact_statement,action_statement,versions(JSONB),issued_at,source_updated_at,verification_status(none|pending|verified|failed|untrusted),verified_at,signing_key_fingerprint,is_flagged,flag_reason,ingested_at,updated_at,content_digest(SHA-256 of normalized statement, used for dedup).- Uniqueness key:
(source_id, source_statement_id, vulnerability_id, product_key). - Local mirror/crash repair lookup:
idx_statements_source_document_identity (source_document_id, source_id, vulnerability_id, product_key).
vexhub.conflictsid(UUID PK),vulnerability_id,product_key,conflicting_statement_ids(UUID[]),severity(low|medium|high|critical),description,resolution_status(open|auto_resolved|manually_resolved|suppressed),resolution_method,winning_statement_id(FK → statements),detected_at,resolved_at.
vexhub.provenance(1:1 with statement;ON DELETE CASCADE)statement_id(UUID PK/FK),source_id,document_uri,document_digest,source_revision,issuer_id,issuer_name,fetched_at,transformation_rules(TEXT[]),raw_statement_json(JSONB).
vexhub.ingestion_jobsjob_id(UUID PK),source_id(FK → sources),status(queued|running|completed|failed|cancelled|paused),started_at,completed_at,documents_processed,statements_ingested,statements_deduplicated,conflicts_detected,error_count,error_message,checkpoint.
vexhub.webhook_subscriptionsid(UUID PK),name,callback_url,secret,event_types(TEXT[]),filter_vulnerability_ids,filter_product_keys,filter_sources,is_enabled,last_triggered_at,failure_count,created_at,updated_at.
Additional schema objects: updated_at columns are maintained by vexhub.update_updated_at triggers; a monitoring view vexhub.statistics aggregates counts. Historical full-text/trigram statement-search storage was removed because VexHub repositories use equality/range predicates and no supported API route issues FTS/trigram predicates. There is no export_cursor table — bulk export reads statements directly at request time.
UTC timestamps and stable ordering keys apply; there are no per-tenant ordering keys because the schema is global.
5) API Surface
All endpoints are under the /api/v1/vex group. Read endpoints require the vexhub:read policy; the conflict-resolution endpoint requires the vexhub:admin policy (see §7). Routes are defined in VexHubEndpointExtensions.MapVexHubEndpoints.
One gateway route in this namespace is intentionally owned elsewhere: POST /api/v1/vex/statements/simple is translated to Excititor POST /excititor/statements/simple before the generic VexHub route matches. It requires the deployment tenant header plus vex.admin, and authors through Excititor’s tenant-scoped raw-document and claim pipeline before projecting the result into this service’s global statement store. VexHub does not expose a parallel statement-create endpoint.
Read group (vexhub:read):
GET /api/v1/vex/cve/{cveId}— query paramslimit(default 100),offset(default 0);404when no statements match. ReturnsVexStatementsResponse.GET /api/v1/vex/package/{purl}— PURL is URL-decoded;limit/offset;404when empty. ReturnsVexStatementsResponse.GET /api/v1/vex/source/{sourceId}—limit/offset;404when empty. ReturnsVexStatementsResponse.GET /api/v1/vex/statement/{id:guid}— single statement;404when not found. ReturnsAggregatedVexStatement.GET /api/v1/vex/search— filterssourceId,vulnerabilityId,productKey,status(affected/fixed/not_affected/under_investigation),isFlagged, pluslimit/offset. ReturnsVexSearchResponsewithTotalCount/Limit/Offset.GET /api/v1/vex/stats— dashboard contract (see below). ReturnsVexHubStats.GET /api/v1/vex/export— bulk OpenVEX feed (see §7.1).GET /api/v1/vex/index— JSON index manifest (VexIndexManifest:version,lastUpdated, and anendpointsmap) advertising the by-cve/by-package/by-source/search/stats/export routes for tool integration. (This is a JSON response body, not a staticvex-index.jsonfile.)
Admin group (vexhub:admin):
POST /api/v1/vex/conflicts/resolve— bodyResolveVexConflictRequest(cveId,selectedStatementId(GUID),resolutionType, optionalnotes). Validates the selected statement matches the CVE (including aliases), resolves all open conflicts for the vulnerability-product pair, and emits an audit event (AuditModules.VexHub/ResolveConflict).resolutionType == "defer"maps tosuppressed(no winning statement); otherwisemanually_resolvedwith the selected statement as winner. Returns204on success;400/404for invalid input or no open conflicts.
Health/ops endpoints (anonymous, outside the auth group): GET /health, GET /health/liveness, GET /health/readiness (all return { Status, Service }), the OpenAPI document (MapOpenApi), and a build-info endpoint (MapBuildInfoEndpoint).
Repository queries order results deterministically; UTC timestamps and content digests (content_digest) provide stable identity and dedup.
GET /api/v1/vex/stats returns the dashboard contract consumed by the console VEX surfaces:
totalStatementsverifiedStatementsflaggedStatementsbyStatusbySourcerecentActivitytrendsgeneratedAt
The stats endpoint must keep working on fresh installs even when a committed EF compiled-model stub is empty; runtime model fallback is required until a real optimized model is generated. The service must also auto-apply embedded SQL migrations for schema vexhub on startup so wiped volumes converge without manual SQL bootstrap. The squashed baseline carries forward the historical removal of pg_trgm and full-text statement-search storage; 004_drop_unused_statement_search_vector.sql is archived pre-1.0 history, not an active top-level migration. Product, vulnerability, digest, source, status, verification, ingestion-time, alias, and uniqueness indexes remain because repository queries and measured plans still depend on equality/range access paths. The WebService exposes anonymous /health, /health/liveness, and /health/readiness probes for the hardened runtime image healthcheck; VEX data readiness remains enforced by repository and ingestion endpoints.
Console VEX Runtime Contract
- The browser search and statement-detail surfaces read from VexHub’s
GET /api/v1/vex/searchandGET /api/v1/vex/statement/{id}. - Consensus and conflict analysis are computed through
POST /api/v1/vexlens/consensus, which is hosted by the VexLens service (not VexHub). VexHub does not implement a consensus endpoint. - Conflict resolution is a real backend mutation through VexHub’s
POST /api/v1/vex/conflicts/resolve(admin scope).
6) Determinism & Offline Posture
- Each normalized statement carries a SHA-256
content_digestused for deduplication and stable identity. (Note: the bulk OpenVEX export document itself does not currently embed a top-levelsnapshot_hashfield — the exportid/timestamp are derived per request; document-level content addressing is a roadmap item.) - JSON serialization uses camelCase property naming and indented output (
VexExportService/ export endpoint). The OpenVEX document pins@context = https://openvex.dev/ns/v0.2.0andauthor = StellaOps VexHubwith roleaggregator. - No network egress outside configured connectors; offline/air-gap posture is preserved because the running service does no upstream polling (ingestion arrives via the Excititor projection, §9).
- Provenance for each statement captures the source document URI, digest, revision, issuer, fetch time, and (optionally) raw statement JSON for audit.
7) Security & Auth
- First-party StellaOps callers authenticate with Authority bearer tokens (
StellaOpsAuthenticationDefaults.AuthenticationScheme). Canonical scopes arevexhub:read(constantStellaOpsScopes.VexHubRead) andvexhub:admin(StellaOpsScopes.VexHubAdmin). The two named authorization policies (VexHubPolicies.Read/.Admin) each accept both the bearer scheme and theApiKeyscheme and require the matching scope viaStellaOpsScopeRequirement. Note: the broadervex:read/vex:ingest/vex.adminscope family inStellaOpsScopesbelongs to other VEX surfaces; VexHub authorizes strictly on thevexhub:*scopes. - Router-forwarded identities are hydrated from the gateway-signed identity envelope before bearer authentication, authorization, and tenant middleware. The envelope signature and validity window must verify before its subject, tenant, project, roles, and
scopevalues become the request principal; missing, invalid, expired, or unverifiable envelopes therefore leave protected routes unauthenticated and produce 401/403. The envelope must carry the exact VexHub policy scope (vexhub:readorvexhub:admin);vex:readis not a substitute. Direct bearer and explicit API-key authentication remain supported paths. - External tooling may authenticate with explicit API keys via the
X-Api-Keyheader orapi_keyquery parameter (ApiKeyAuthenticationHandler). Keys are configured underVexHub:ApiKeys(each entry carriesKeyId,ClientId,ClientName,Scopes, optionalRateLimitPerMinute). The handler normalizes legacy dotted scope values (vexhub.read/vexhub.admin) onto the canonicalvexhub:read/vexhub:adminscopes before authorization. The API-key scheme runs inAllowAnonymousmode, so requests without a key fall through to bearer-token authorization rather than being hard-rejected at the scheme level. - Signature verification (
VexSignatureVerifier) is explicit and offline.VexHub:SignatureTrustRootsmaps signing-key fingerprints to offline DSSE HMAC trust roots. When a signed document references a key without a configured trust root, verification returnsuntrustedwith errorvexhub.signature.trust_root_unavailable. A signature that does not match the trusted root returnsfailedwithvexhub.signature.invalid_or_untrusted; malformed envelopes returnfailedwith more specific codes (vexhub.signature.key_missing,vexhub.signature.malformed_envelope,vexhub.signature.envelope_required,vexhub.signature.payload_not_base64). The HMAC pre-authentication encoding is the DSSE PAE (DSSEv1 …) and signing is HMAC-SHA256 routed through the auditedCryptoInteropfacade. The default runtime has no public/default trust root and never reportspendingas successful verification. Toggles:VexHub:EnableSignatureVerification(default true) andVexHub:RequireSignedStatements(default false). - Rate limiting is enforced in-process by
RateLimitingMiddleware(sliding 1-minute window keyed by API-key id or client IP). Limit defaults come fromVexHub:Distribution:RateLimitPerMinute(default 60; authenticated callers get 2× the default). The middleware setsX-RateLimit-Limit/-Remaining/-Resetheaders and returns429withRetry-Afterwhen exceeded;/health*paths are exempt. A gateway may add an outer rate-limit tier, but the service does not depend on it.
7.1) Export Contract
GET /api/v1/vex/exportreturns OpenVEX JSON with content typeapplication/vnd.openvex+jsonwhen the backend export succeeds. The endpoint serializes all matching statements viaVexExportService.ExportToOpenVexAsync, normalizes the@contextkey, and guarantees astatementsarray is present.- Export generation failures surface as truthful
problem+json500responses (titleVEX export failed); the service does not fabricate empty OpenVEX success documents to mask backend state or persistence failures. - The exporter also offers CVE-scoped and PURL-scoped variants (
ExportCveToOpenVexAsync,ExportPackageToOpenVexAsync) and anExportStatisticssummary at the service level; only the bulkGET /api/v1/vex/exportroute is currently exposed over HTTP.
7.2) Configuration
All options bind from the VexHub configuration section (VexHubOptions.SectionName = "VexHub"); PostgreSQL binds from the Postgres section, and Router from Router. Defaults reflect VexHubOptions:
DefaultPollingIntervalSeconds(3600),MaxConcurrentPolls(4),StaleStatementAgeDays(365),StaleDataThresholdDays(90).AutoResolveLowSeverityConflicts(true),StoreRawStatements(true).MaxApiPageSize(1000),DefaultApiPageSize(100).EnableSignatureVerification(true),RequireSignedStatements(false),SignatureTrustRoots(map of key fingerprint → DSSE HMAC root; empty ⇒ signed VEX fails closed as unavailable).Ingestion:EnableDeduplication(true),EnableConflictDetection(true),BatchSize(500),FetchTimeoutSeconds(300),MaxRetries(3).Distribution:EnableBulkExport(true),EnableWebhooks(true),CacheDurationSeconds(300),RateLimitPerMinute(60).SourceBackoff:InitialBackoffSeconds(3600),MaxBackoffSeconds(86400),BackoffMultiplier(2.0),JitterFactor(0.1),MaxConsecutiveFailures(10).ApiKeys: optional map of external API keys (see §7).
Webhook subscriptions are modeled (WebhookSubscription, event types statement.created/updated/flagged, conflict.detected/resolved, source.polled/failed) and a vexhub.webhook_subscriptions table exists, but webhook delivery is gated by Distribution:EnableWebhooks and tied to the ingestion/polling path that is currently inactive on the WebService host.
8) Observability
- Logging: Serilog request logging is enabled (
UseSerilogRequestLogging); the export endpoint and rate-limiter log warnings/errors with relevant identifiers (client id, statement counts). Structured per-statement log fields are emitted as available, but VexHub does not enrich logs with atenant_id(the schema is global) — that field from earlier drafts does not apply. - Metrics / traces (roadmap): The named Prometheus metrics (
vexhub_ingest_total,vexhub_validation_failures_total,vexhub_conflicts_total,vexhub_export_duration_seconds) and dedicated tracing spans described in earlier drafts are not yet implemented — there is noMeter/Counter/Histogram/ActivitySourcedefined undersrc/VexHub. Treat this subsection as a target contract, not current behavior. Cross-cutting tracing/metrics that the shared hosting/router stack provides still apply at the HTTP layer. - The
vexhub.statisticsSQL view and theGET /api/v1/vex/statsendpoint provide operational counts (statements, verified, flagged, open conflicts, running jobs, last ingestion).
9) Integration Points
- Excititor: upstream connectors provide source payloads and trust hints.
- Excititor claim projection: for Stella-managed connector runs, Excititor is the ingestion and AOC authority. After the worker verifies a raw document, normalizes it, passes the AOC lineage guard, and appends claims to
vex.claims, it projects the same claim batch intovexhub.statements. VexHub source ids use the canonical Excititor provider id (excititor:redhat,excititor:ubuntu, etc.), andverification_statuscarries the upstream signature result. This path is separate from VexHub’s own upstream polling pipeline and keeps Stella Ops Mirror exports populated without re-fetching already verified connector data. The projector (StellaOps.Excititor.Persistence.Postgres.VexHub.PostgresVexHubProjectionSink) upserts bothvexhub.sources(keyed onclaim.ProviderId) andvexhub.statementsin a single transaction, deterministically derives the statementidfrom a SHA-256content_digestof the claim. Normal re-ingest always re-drives this idempotent writer even when claims already exist. New sources land withtrust_tier = UNKNOWNregardless of signature status, and the source upsert deliberately omitstrust_tierfrom its conflict update so an operator-curated grant cannot be overwritten by re-projection. Separately, the Worker’sVexProjectionRepairHostedServicerepairs missing statements from retained local claims plus raw provenance without fetching a provider, so compact mirror destinations converge while provider schedules remain disabled. Raw-only VEX expansion is intentionally fail-closed because it can recreate full-corpus product fan-out. The loop retries until the VexHub schema exists. - Operator authoring: the Console’s simple authoring request uses the same ownership boundary as connector ingestion rather than writing directly to
vexhub.statements. Excititor validates and retains one canonical OpenVEX raw document and claim under the authenticated tenant, stamps the claim as enforced and operator-authored, and synchronously invokes the normal projection sink with sourceexcititor:operator. The response id and persisted row use the shared deterministic statement-identity derivation. Success therefore means the statement is immediately available throughGET /api/v1/vex/statement/{id}; a projection outage returns503while retaining the tenant claim for repair. The distributed VexHub row remains deliberately global, consistent with §4; tenant provenance stays in Excititor’s raw/claim stores and is not represented as VexHub row tenancy. - VexLens: consumes normalized statements and provenance for trust scoring and consensus. Its
GET /api/v1/vexlens/sources/route exposes the source-trust registry, whilePUT /api/v1/vexlens/sources/{sourceId}/trustis the authorized operator grant/revoke surface (UNKNOWNrevokes back to uncurated). - Policy Engine: reads VexLens consensus results; VexHub provides external distribution.
- UI: VEX conflict studio consumes conflict API once available.
- Console UI: combines VexHub statement/search data with VexLens consensus results over the live HTTP contract.
10) Testing Strategy
- Unit tests for normalization and validation pipelines.
- Integration tests with Postgres for ingestion and API outputs.
- Persistence registration and runtime-model tests that prove source/conflict/ingestion-job repositories and startup migrations are wired on the service path.
- Determinism tests comparing repeated exports with identical inputs.
- Registry boundary tests should target
src/VexHub/__Tests/StellaOps.VexHub.Core.Tests/StellaOps.VexHub.Core.Tests.csprojandsrc/VexHub/__Tests/StellaOps.VexHub.WebService.Tests/StellaOps.VexHub.WebService.Tests.csprojfor VEX document validation/normalization, source metadata import, and confirmation that importing VEX data cannot execute connector payloads. Optional source connector code remains in Concelier/Excititor overlays; any future VexHub-owned normalizer requires a module-local signed-plugin sprint with admission tests.
11) Unified Source Catalog (Sprint 20260503-012)
The advisory and VEX provider catalogs are projected through a single read endpoint so CLI/UI consumers do not have to special-case “advisory” vs. “VEX” surfaces.
- Endpoint:
GET /api/v1/advisory-sources/catalog(Concelier WebService) composes contributors implementingIAdvisorySourceCatalogContributor(StellaOps.Concelier.Core.Sources.Configuration). - Contributors:
SourceRegistryCatalogContributor(Backend = "concelier") projects ConcelierSourceDefinitions.ExcititorAdvisorySourceCatalogContributor(Backend = "excititor") projects the 7 canonical VEX providers fromExcititorKnownProviders(StellaOps.Excititor.Core.Sources).
- Source-id namespace for VEX entries is
excititor:{provider}(excititor:redhat,excititor:ubuntu,excititor:oracle,excititor:cisco,excititor:suse-rancher,excititor:oci-openvex,excititor:msrc). Legacy short names (redhat,rancher,openvex, …) are accepted viaExcititorKnownProviders.Aliasesand resolve to the canonical id. - Authoritative state for trust weight, PGP fingerprints, cosign issuer/identity, crypto profile, and offline-snapshot toggles remains in Excititor’s
VexProviderRuntimeSettingsCacheandVexProviderConfigurationService. The unified catalog is a thin façade; mutations onexcititor:-prefixed ids are routed to those services. - Vendor-specific schemas are hand-rolled JSON Schema strings carried on
ExcititorCatalogProviderDefinition.ConfigurationSchemaJson. Adding a tunable property to a connectorOptionsclass requires editing the matching schema inStellaOps.Excititor.Core.Sources.ExcititorProviderSchemas— this is deliberate: the unified surface never flattens to “URL + token” because doing so would lose Stella Ops’ supply-chain integrity guarantees. - Migration note for operators: the
/excititor/providers/*endpoints remain authoritative for trust/signing mutations and continue to function. New tooling should read from the unified/api/v1/advisory-sources/catalog(CLI:stella sources list,stella vex providers list). The 3 legacy stubSourceDefinitionsentries (csaf,csaf-tc,vex) have been removed fromSourceDefinitions(Sprint 20260503-012 / B-VEXUNI-002) because they were never runnable advisory sources; tooling must target the canonicalexcititor:{provider}ids instead. Legacy short names are still resolved viaExcititorKnownProviders.Aliases(e.g.redhat/redhat-csaf→excititor:redhat,rancher/suse→excititor:suse-rancher,openvex/oci→excititor:oci-openvex,microsoft→excititor:msrc).
Last updated: 2026-05-30 (doc↔code reconciliation against src/VexHub/ and the Excititor→VexHub projection sink in src/Concelier/StellaOps.Excititor.Worker/VexHub/).
