architecture_excititor_mirrors.md — Excititor Mirror Distribution
Status: Draft (Sprint 7). Complements
docs/modules/excititor/architecture.mdby describing the mirror export surface exposed byExcititor.WebServiceand the configuration hooks used by operators and downstream mirrors.
0) Purpose
Excititor publishes canonical VEX consensus data. Operators (or StellaOps-managed mirrors) need a deterministic way to sync those exports into downstream environments. Mirror distribution provides:
- A declarative map of export bundles (
json,jsonl,openvex,csaf) reachable via signed HTTP endpoints under/excititor/mirror. - Thin quota/authentication controls on top of the existing export cache so mirrors cannot starve the web service.
- Stable payload shapes that downstream automation can monitor (index → fetch updates → download artifact → verify signature).
Mirror endpoints are intentionally read-only. Write paths (export generation, attestation, cache) remain the responsibility of the export pipeline.
1) Configuration model
The web service reads mirror configuration from Excititor:Mirror (YAML/JSON/appsettings). Each domain groups a set of exports that share rate limits and authentication rules.
Excititor:
Mirror:
Domains:
- id: primary
displayName: Primary Mirror
requireAuthentication: false
maxIndexRequestsPerHour: 600
maxDownloadRequestsPerHour: 1200
exports:
- key: consensus
format: json
filters:
vulnId: CVE-2025-0001
productKey: pkg:test/demo
sort:
createdAt: false # descending
limit: 1000
- key: consensus-openvex
format: openvex
filters:
vulnId: CVE-2025-0001
Root settings
| Field | Required | Description |
|---|---|---|
outputRoot | – | Filesystem root where mirror artefacts are written. Defaults to the Excititor file-system artifact store root when omitted. |
directoryName | – | Optional subdirectory created under outputRoot; defaults to mirror. |
targetRepository | – | Hint propagated to manifests/index files indicating the operator-visible location (for example s3://mirror/excititor). |
signing | – | Bundle signing configuration. When enabled, the exporter emits a detached JWS (bundle.json.jws) alongside each domain bundle. |
signing supports the following fields:
| Field | Required | Description |
|---|---|---|
enabled | – | Toggles detached signing for domain bundles. |
algorithm | – | Signing algorithm identifier (default ES256). |
keyId | ✅ (when enabled) | Signing key identifier resolved via the configured crypto provider registry. |
provider | – | Optional provider hint when multiple registries are available. |
keyPath | – | Optional PEM path used to seed the provider when the key is not already loaded. |
Domain field reference
| Field | Required | Description |
|---|---|---|
id | ✅ | Stable identifier. Appears in URLs (/excititor/mirror/domains/{id}) and download filenames. |
displayName | – | Human-friendly label surfaced in the /domains listing. Falls back to id. |
requireAuthentication | – | When true the service enforces that the caller is authenticated (Authority token). |
maxIndexRequestsPerHour | – | Per-domain quota for index endpoints. 0/negative disables the guard. |
maxDownloadRequestsPerHour | – | Per-domain quota for artifact downloads. |
exports | ✅ | Collection of export projections. |
Export-level fields:
| Field | Required | Description |
|---|---|---|
key | ✅ | Unique key within the domain. Used in URLs (/exports/{key}) and filenames/bundle entries. |
format | ✅ | One of json, jsonl, openvex, csaf. Maps to VexExportFormat. |
filters | – | Key/value pairs executed via VexQueryFilter. Keys must match export data source columns (e.g., vulnId, productKey). |
sort | – | Key/boolean map (false = descending). |
limit, offset, view | – | Optional query bounds passed through to the export query. |
⚠️ Misconfiguration: invalid formats or missing keys cause exports to be flagged with status in the index response; they are not exposed downstream.
2) HTTP surface
Routes are grouped under /excititor/mirror.
| Method | Path | Description |
|---|---|---|
GET | /domains | Returns configured domains with quota metadata. |
GET | /domains/{domainId} | Domain detail (auth/quota + export keys). 404 for unknown domains. |
GET | /domains/{domainId}/index | Lists exports with exportId, query signature, format, artifact digest, attestation metadata, and size. Applies index quota. |
GET | /domains/{domainId}/exports/{exportKey} | Returns manifest metadata (single export). 404 if unknown/missing. |
GET | /domains/{domainId}/exports/{exportKey}/download | Streams export content from the artifact store. Applies download quota. |
Responses are serialized via VexCanonicalJsonSerializer ensuring stable ordering. Download responses include a content-disposition header naming the file <domain>-<export>.<ext>.
Error handling
401– authentication required (requireAuthentication=true).404– domain/export not found or manifest not persisted.429– per-domain quota exceeded (Retry-Afterheader set in seconds).503– export misconfiguration (invalid format/query).
3) Rate limiting
MirrorRateLimiter implements a simple rolling 1-hour window using IMemoryCache. Each domain has two quotas:
indexscope →maxIndexRequestsPerHourdownloadscope →maxDownloadRequestsPerHour
0 or negative limits disable enforcement. Quotas are best-effort (per-instance). For HA deployments, configure sticky routing at the ingress or replace the limiter with a distributed implementation.
4) Interaction with export pipeline
Mirror endpoints consume manifests produced by the export engine (MongoVexExportStore). They do not trigger new exports. Operators must configure connectors/exporters to keep targeted exports fresh (see EXCITITOR-EXPORT-01-005/006/007).
Recommended workflow:
- Define export plans at the export layer (JSON/OpenVEX/CSAF).
- Configure mirror domains mapping to those plans.
- Downstream mirror automation:
GET /domains/{id}/index- Compare
exportId/consensusRevision GET /downloadwhen new- Verify digest + attestation
When the export engine runs, it materializes the following artefacts under outputRoot/<directoryName>:
index.json– canonical index listing each configured domain, manifest/bundle descriptors (with SHA-256 digests), and available export keys.<domain>/manifest.json– per-domain summary with export metadata (query signature, consensus/score digests, source providers) and a descriptor pointing at the bundle.<domain>/bundle.json– canonical payload containing serialized consensus, score envelopes, and normalized VEX claims for the matching export definitions.<domain>/bundle.json.jws– optional detached JWS when signing is enabled.
Downstream automation reads manifest.json/bundle.json directly, while /excititor/mirror endpoints stream the same artefacts through authenticated HTTP.
5) Operational guidance
- Track quota utilisation via HTTP 429 metrics (configure structured logging or OTEL counters when rate limiting triggers).
- Mirror domains can be deployed per tenant (e.g.,
tenant-a,tenant-b) with different auth requirements. - Ensure the underlying artifact stores (
FileSystem,S3, offline bundle) retain artefacts long enough for mirrors to sync. - For air-gapped mirrors, combine mirror endpoints with the Offline Kit (see
docs/24_OFFLINE_KIT.md).
6) Future alignment
- Replace manual export definitions with generated mirror bundle manifests once
EXCITITOR-EXPORT-01-007ships. - Extend
/indexpayload with quiet-provenance whenEXCITITOR-EXPORT-01-006adds that metadata. - Integrate domain manifests with DevOps mirror profiles (
DEVOPS-MIRROR-08-001) so helm/compose overlays can enable or disable domains declaratively.
7) Runbook & observability checklist (Sprint 22 demo refresh · 2025-11-07)
Daily / on-call checks
- Index freshness – watch
excitor_mirror_export_latency_seconds(p95 < 180) grouped bydomainId. If latency grows past 10 minutes, verify the export worker queue (stellaops-export-workerlogs) and ensure Mongovex_exportshas entries newer thannow()-10m. - Quota exhaustion – alert on
excitor_mirror_quota_exhausted_total{scope="download"}increases. When triggered, inspect structured logs (MirrorDomainId,QuotaScope,RemoteIp) and either raise limits or throttle abusive clients. - Bundle signature health – metric
excitor_mirror_bundle_signature_verified_totalshould match download counts when signing enabled. Deltas indicate missing.jwsfiles; rebuild the bundle via export job or copy artefacts from the authority mirror cache. - HTTP errors – dashboards should track 4xx/5xx rates split by route; repeated
503statuses imply misconfigured exports. Checkmirror/indexlogs forstatus=misconfigured.
Incident steps
- Use
GET /excititor/mirror/domains/{id}/indexto capture current manifests. Attach the response to the incident log for reproducibility. - For quota incidents, temporarily raise
maxIndexRequestsPerHour/maxDownloadRequestsPerHourvia theExcititor:Mirror:Domainsconfig override, redeploy, then work with the consuming team on caching. - For stale exports, trigger the export job (
Excititor.ExportRunner) and confirm the artefacts are written tooutputRoot/<domain>. - Validate DSSE artefacts by running
cosign verify-blob --certificate-rekor-url=<rekor> --bundle <domain>/bundle.json --signature <domain>/bundle.json.jws.
Logging fields (structured)
| Field | Description |
|---|---|
MirrorDomainId | Domain handling the request (matches id in config). |
QuotaScope | index / download, useful when alerting on quota events. |
ExportKey | Included in download logs to pinpoint misconfigured exports. |
BundleDigest | SHA-256 of the artefact; compare with index payload when debugging corruption. |
OTEL signals
- Counters:
excitor.mirror.requests,excitor.mirror.quota_blocked,excitor.mirror.signature.failures. - Histograms:
excitor.mirror.download.duration,excitor.mirror.export.latency. - Spans:
mirror.index,mirror.downloadinclude attributesmirror.domain,mirror.export.key, andmirror.quota.remaining.
Add these instruments via the MirrorEndpoints middleware; see StellaOps.Excititor.WebService/Telemetry/MirrorMetrics.cs.