Below is the expanded, “maximum documentation” package for Epic 7. It is paste‑ready for your repo and deliberately formal so engineering, docs, governance, and audit can work from the same source of truth. > **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied. --- # Epic 7: VEX Consensus Lens **Short name:** `VEX Lens` **Services touched:** Excitator (Vexer), Conseiller (Feedser), SBOM Service, Policy Engine, Findings Ledger, Web API Gateway, Authority (authN/Z), Console (Web UI), CLI, Telemetry/Analytics **AOC ground rule:** Excitator and Conseiller aggregate but never merge or rewrite source documents. The Lens only computes a **derived, reproducible** consensus view while preserving all raw evidence and provenance. --- ## 1) What it is The VEX Consensus Lens is a deterministic computation and presentation layer that ingests all available VEX statements about a given advisory key (e.g., CVE) and product/context, then produces an **explicit consensus state** for each `(artifact_id, purl, version, advisory_key)` tuple. It answers: “Given multiple, possibly conflicting VEX statements from different issuers, what is the most trustworthy, policy‑consistent interpretation for this artifact and version?” It never edits or merges VEX documents. It normalizes them, aligns their **product scope** to SBOM inventory, scores issuers via a tenant‑configurable **trust model**, applies time and version scoping, and outputs: * `consensus_state`: `NOT_AFFECTED | AFFECTED | FIXED | UNDER_INVESTIGATION | INCONCLUSIVE | DISPUTED` * `confidence`: 0.0–1.0 numeric score * `rationale`: structured explanation of evidence, weights, and rules applied * `quorum`: weighted vote breakdown by issuer, timestamp, justification, and version applicability The Lens plugs into the Policy Engine. Policy remains the source of truth for applicability, severity, and obligations; the Lens supplies structured signals and provenance that policy can use to suppress findings, downgrade risk, or set SLAs. Key properties: * **Reproducible:** Same inputs and policy yield same outputs. * **Explainable:** Every output carries a machine‑readable rationale chain. * **Scoped:** Product trees and version ranges are resolved against actual SBOM inventory. * **Immutable evidence:** All raw VEX docs remain intact, retrievable, and linkable. --- ## 2) Why (concise) Real ecosystems have overlapping VEX statements from upstream maintainers, vendors, distros, and third parties. Operators need a single, defensible view without losing provenance. The Lens reduces noise, flags conflicts, and turns fragmented VEX evidence into auditable signals that policy and humans can act on. --- ## 3) How it should work (maximum detail) ### 3.1 Input model **From Excitator (Vexer):** * Raw `VexEvidence` documents (CSAF VEX, OpenVEX, CycloneDX VEX) with: * `advisory_key` (canonicalized) * product tree or component coordinates (purl, CPE, vendor IDs) * status (`not_affected`, `affected`, `fixed`, `under_investigation`) * justifications (CSAF, OpenVEX enumerations) * version ranges or fixed versions if available * timestamps (issued, last_updated), source URLs * cryptographic metadata (signature, issuer, certificate chain) if present **From SBOM Service:** * Inventory for each artifact: purls, versions, dependency paths, scopes, env flags. **From Issuer Directory (new)** * Directory of known VEX issuers: * identity, organization, domain, CSAF publisher metadata * public keys and trust anchors (Ed25519/X.509/PKIX/PKCS7/DSSE) * default trust weight * tenancy‑specific overrides **From Policy Engine:** * Policy version and options relevant to VEX evaluation: * trust model parameters * confidence thresholds per environment/tier * justification whitelist/blacklist * recency requirements and expiry windows * precedence rules for status conflicts ### 3.2 Normalization 1. **Canonical advisory key:** CVE preferred; GHSA mapped; vendor IDs namespaced. 2. **Status mapping:** Normalize all encodings to `NOT_AFFECTED | AFFECTED | FIXED | UNDER_INVESTIGATION`. 3. **Product scope alignment:** Convert CSAF product tree/CPE to purl variants using deterministic mappings; store mapping evidence. 4. **Version scoping:** For each evidence, compute `applies_to(version)` using ecosystem comparators (npm semver, Maven, PEP 440, Go semver, RPM/DEB EVR). 5. **Signature verification:** If signed, verify against Issuer Directory trust anchors; attach `sig_verified=true/false` and chain metadata. 6. **Temporal fields:** Compute `effective_at` (issued), `observed_at` (ingested), and staleness. ### 3.3 Trust model Each `VexEvidence` gets a base weight `w_base` from issuer type and verification: * Maintainer/Upstream signed: 0.9 * Maintainer/Upstream unsigned: 0.7 * Distro/SIG signed: 0.8 * Vendor of downstream product signed: 0.8 * Third‑party scanner VEX: 0.4 * Unknown/unsigned with weak provenance: 0.2 Weights are then adjusted: ``` w = w_base * f_signature(sig_verified) // e.g., 1.1 if verified, 0.8 if unverifiable * f_recency(age_days) // decay after T days (policy) * f_justification(type) // e.g., "component_not_present" lower if SBOM shows present * f_scope_match(score) // quality of product match: exact purl > family > CPE wildcard * f_env(app_env) // optional env-specific multipliers ``` All `f_*` are bounded to [0.5, 1.2] by default to prevent runaway effects. Tenants can override per Policy Studio. ### 3.4 Consensus algorithm For a tuple `(artifact_id, purl, version, advisory_key)`: 1. **Collect** all normalized `VexEvidence` whose product scope maps to `purl` and `applies_to(version)==true`. 2. **Bucket** by normalized status; compute `W(status) = Σ w(evidence)` per status. 3. **Apply precedence** rules from policy: * If `W(NOT_AFFECTED)` exceeds threshold `T_na` and there is no `FIXED` evidence contradictory for the same version, propose `NOT_AFFECTED`. * If any `FIXED` applies and inventory version ≥ fixed version, propose `FIXED`. * If `W(AFFECTED)` ≥ `T_aff` and no dominating `NOT_AFFECTED`, propose `AFFECTED`. * If `W(UNDER_INVESTIGATION)` dominates and others below thresholds, propose `UNDER_INVESTIGATION`. * If both `AFFECTED` and `NOT_AFFECTED` exceed thresholds within a small margin `δ`, mark `DISPUTED`. * Otherwise `INCONCLUSIVE`. 4. **Confidence score:** ``` confidence = W(winning_status) / (W(AFFECTED)+W(NOT_AFFECTED)+W(FIXED)+W(UNDER_INVESTIGATION) + ε) ``` Clip to [0.0, 1.0]. 5. **Rationale chain:** * Winning status, thresholds used, top 5 contributing evidences with weights and reasons, product mapping quality, version scoping evidence, and policy knobs that influenced the result. 6. **Quorum summary:** * List issuers and their votes, signature state, timestamps, and justifications. ### 3.5 Policy interaction * The Lens returns `consensus_state`, `confidence`, and structured `signals`. * Policy Engine consumes these and may: * Suppress findings automatically on `NOT_AFFECTED` confidence ≥ `P_na`. * Downgrade severity or extend SLA when `UNDER_INVESTIGATION` from high‑trust issuers. * Require human approval when `DISPUTED`. * Treat `FIXED` as resolved only when SBOM crawl verifies the fixed version or a `verify_fix` ledger event exists. **Simulation:** Policy Studio can simulate different trust weights and thresholds and see consensus deltas without side effects. ### 3.6 Data contracts **ConsensusRecord (materialized)** ```json { "id": "cons:sha256(tenant|artifact|purl|version|advisory|policy)", "tenant": "acme", "artifact_id": "svc:payments@1.14.0", "purl": "pkg:npm/lodash@4.17.20", "advisory_key": "CVE-2024-12345", "policy_version": "1.3.0", "consensus_state": "NOT_AFFECTED", "confidence": 0.87, "weights": {"AFFECTED":0.31,"NOT_AFFECTED":1.25,"FIXED":0.00,"UNDER_INVESTIGATION":0.12}, "top_evidence": ["vex_evd:...","vex_evd:..."], "quorum": [ {"issuer":"lodash-maintainers","status":"NOT_AFFECTED","w":0.9,"sig_verified":true,"just":"vulnerable_code_not_present","issued":"2024-06-08"}, {"issuer":"vendorX-distro","status":"AFFECTED","w":0.25,"sig_verified":false,"just":"generic_advisory","issued":"2024-06-07"} ], "rationale": [ {"rule":"trust.weight.issuer","detail":"maintainer signed evidence 0.9"}, {"rule":"scope.match.exact","detail":"exact purl match"}, {"rule":"justification.vcnp","detail":"supported by SBOM callgraph hint"} ], "updated_at": "2024-06-12T10:00:00Z" } ``` **Issuer Directory entry** ```json { "issuer_id": "iss:lodash", "org": "Lodash Maintainers", "domains": ["lodash.com"], "keys": [{"type":"ed25519","pub":"...","expires":"2026-12-31"}], "default_weight": 0.9, "metadata": {"csaf_publisher": true} } ``` ### 3.7 APIs **Compute/Query** ``` GET /vex/consensus?artifact=svc:payments@1.14.0&purl=pkg:npm/lodash@4.17.20&advisory=CVE-2024-12345&policy=1.3.0 POST /vex/consensus/query { "policy":"1.3.0", "filter": { "state":["DISPUTED","INCONCLUSIVE"], "confidence":"<0.6", "env":["prod"] }, "page":{...} } GET /vex/consensus/{id} // full record POST /vex/consensus/simulate // override trust knobs and thresholds for what-if ``` **Issuer Directory** ``` GET /vex/issuers POST /vex/issuers (admin) POST /vex/issuers/{id}/keys (admin) ``` **Exports** ``` POST /vex/consensus/export { "format":"ndjson","scope":{"filter":{...}} } ``` **Errors** * `400` invalid mapping, `403` RBAC, `404` not found, `409` conflict, `429` rate limit. ### 3.8 Console (Web UI) Routes: * `/vex/consensus` overview with filters: state, confidence, issuer, advisory, artifact, env. * `/vex/consensus/:id` detail: Evidence pane, Quorum graph, Policy impact, SBOM path links. UX elements: * **Quorum bar:** stacked bar showing weights per status; hover reveals issuer contributions. * **Confidence chip:** numeric and qualitative band (Low/Med/High). * **Evidence table:** paged list of VEX docs with signature icon, scope match quality tag, justification tag, issued/updated timestamps. * **Conflict view:** for `DISPUTED`, show side-by-side issuer rationales and suggested next steps. * **Deep link** into Vulnerability Explorer detail, preselecting the Policy version used for the consensus. A11y: * ARIA roles on grid and bars; keyboard shortcuts `S` switch policy, `T` trust presets, `E` export. ### 3.9 CLI Commands: ``` stella vex consensus list --policy 1.3.0 --state disputed,inconclusive --confidence '<0.6' --artifact payments --json stella vex consensus show --id --policy 1.3.0 stella vex simulate --policy 1.3.0 --trust 'issuer:lodash=1.0,issuer:vendorX=0.5' --thresholds 'na=1.0,aff=0.6' --json stella vex issuers list stella vex export --filter 'artifact:payments advisory:CVE-2024-12345' --out vex-consensus.ndjson ``` Exit codes: `0` ok, `2` invalid args, `4` not found, `5` denied. ### 3.10 Storage schema (illustrative) ```sql -- Normalized VEX (reference Excitator id; do not alter raw) CREATE TABLE vex_normalized ( id uuid PRIMARY KEY, tenant text NOT NULL, evidence_id text NOT NULL, -- link to Excitator advisory_key text NOT NULL, issuer_id text, status text NOT NULL, -- NOT_AFFECTED|AFFECTED|FIXED|UNDER_INVESTIGATION justification text, purl text, -- normalized mapping target version_range jsonb, -- ecosystem-specific encoding sig_verified boolean, scope_score real, -- 0..1 quality of mapping issued timestamptz, updated timestamptz, w_base real, UNIQUE (tenant, evidence_id) ); -- Issuer Directory CREATE TABLE vex_issuers ( issuer_id text PRIMARY KEY, tenant text NOT NULL, org text NOT NULL, default_weight real NOT NULL, metadata jsonb, UNIQUE(tenant, org) ); CREATE TABLE vex_issuer_keys ( id uuid PRIMARY KEY, issuer_id text NOT NULL REFERENCES vex_issuers(issuer_id), key_type text NOT NULL, pubkey text NOT NULL, expires timestamptz ); -- Consensus projection CREATE TABLE vex_consensus ( id bytea PRIMARY KEY, tenant text NOT NULL, artifact_id text NOT NULL, purl text NOT NULL, version text NOT NULL, advisory_key text NOT NULL, policy_version text NOT NULL, consensus_state text NOT NULL, confidence real NOT NULL, weights jsonb NOT NULL, top_evidence text[] NOT NULL, updated_at timestamptz NOT NULL ); CREATE INDEX vc_query ON vex_consensus(tenant, policy_version, consensus_state, confidence); ``` ### 3.11 Integration with Findings Ledger and Vuln Explorer * The Vuln Explorer reads `vex_consensus` for each finding and renders: * Consensus chip, confidence, and a link to full quorum. * For `NOT_AFFECTED` with confidence ≥ policy threshold, show “Suppressed by VEX (Consensus)” badge. * For `DISPUTED`, open a triage banner prompting manual review and optional ledger comment/assignment. * Ledger receives no new event type from lens computation itself. Human actions triggered by lens views produce standard events (`comment`, `assign`, `change_state`). ### 3.12 Security & RBAC Roles: * Viewer: query consensus read‑only. * Investigator: Viewer + export. * Operator: Investigator + trust simulation. * Admin: manage Issuer Directory entries and keys. CSRF for Console; ABAC scoping by artifact ownership and environment. ### 3.13 Observability Metrics: * `vex_consensus_compute_latency_ms` (histogram) * `vex_consensus_records_total` (counter) * `vex_consensus_disputed_total` (counter by issuer combinations) * `vex_consensus_staleness_seconds` (gauge) * `vex_signature_verification_rate` (gauge) Logs: structured events with `tenant`, `policy_version`, `advisory_key`, `quorum_summary`. Traces: spans for normalization, mapping, trust weighting, consensus decision, DB writes. ### 3.14 Performance & scaling Targets: * P95 query under 500 ms for 100‑row pages at 10M consensus records/tenant. * Projection jobs are idempotent and keyed by `(tenant, artifact, purl, version, advisory, policy)`; backpressure with work queues. * Cache popular queries with tenant‑scoped TTL; invalidate on Excitator or policy changes. ### 3.15 Edge cases * **Ambiguous product mapping:** mark low `scope_score`, cap weight, surface warning in UI. * **VEX “not present” vs SBOM shows present:** down‑weight with `f_justification`, require manual check. * **Withdrawn or superseded VEX:** decay to near zero; keep provenance. * **Partial fixes:** if fixed version applies to subset of platforms, map to env or arch dimension when available. * **Time travel:** consensus recalculated as of a timestamp using only evidence ≤ `as_of` and the corresponding policy version. --- ## 4) Implementation plan ### 4.1 Services * **VEX Lens Service (new)** * Normalization pipeline, trust weighting, consensus computation, and projections. * Batch recompute on policy activation and Excitator deltas. * **Excitator (updates)** * Ensure all VEX evidence carries issuer hints and raw signature blobs when present. * Publish product trees and original coordinates intact. * **Policy Engine (updates)** * Add VEX trust knobs, thresholds, recency decay, and status precedence. * Batch eval endpoint accepts `consensus inputs` where needed. * **Issuer Directory (new)** * Manage issuer metadata and keys; tenant overrides; audit logs. ### 4.2 Code structure ``` /src/StellaOps.VexLens /normalizer /mapping # CPE/purl translators /trust # weighting functions /consensus # algorithm and projections /api /src/StellaOps.Excititor # updates /src/StellaOps.Policy # updates /src/StellaOps.IssuerDirectory /packages/console/features/vex-consensus /src/StellaOps.Cli ``` ### 4.3 Rollout * Phase 1: API read‑only with basic trust model, Console list/detail, no simulation. * Phase 2: Policy Studio integrations and simulation. * Phase 3: Issuer Directory admin flows, exports, and advanced mapping diagnostics. --- ## 5) Documentation changes (create/update) 1. `/docs/vex/consensus-overview.md` Purpose, scope, terminology, evidence vs derived view, AOC guarantees. 2. `/docs/vex/consensus-algorithm.md` Normalization, mapping, weighting, thresholds, precedence, formulas, examples. 3. `/docs/vex/issuer-directory.md` Managing issuers, keys, trust overrides, security model. 4. `/docs/vex/consensus-api.md` Endpoints, request/response schemas, errors, pagination, rate limits. 5. `/docs/vex/consensus-console.md` Screens, filters, conflict workflows, a11y, deep links. 6. `/docs/policy/vex-trust-model.md` Policy knobs, thresholds, decay, simulation. 7. `/docs/sbom/vex-mapping.md` Product tree mapping to purl/version, ecosystem comparators, edge cases. 8. `/docs/security/vex-signatures.md` Signature verification flows, key management, auditing. 9. `/docs/runbooks/vex-ops.md` Recompute storms, mapping failures, signature errors, lag, quotas. All docs end with the imposed rule statement. --- ## 6) Engineering tasks ### Backend core * [ ] Implement normalization for CSAF VEX, OpenVEX, CycloneDX VEX. * [ ] Build product mapping library (CPE→purl, vendor tokens→purl families). * [ ] Implement signature verification (Ed25519/PKIX/DSSE) using Issuer Directory keys. * [ ] Implement trust weighting functions and configurable parameters. * [ ] Implement consensus algorithm with unit tests and property tests. * [ ] Materialize `vex_consensus` projection with indexes and idempotent workers. * [ ] Batch recompute on policy activation and Excitator deltas. ### APIs & Integrations * [ ] `/vex/consensus` query, detail, simulate, export. * [ ] Policy Engine: consume consensus signals; add thresholds and precedence. * [ ] Vuln Explorer: show consensus chip and triage banners; deep link to Lens. ### Issuer Directory * [ ] CRUD for issuers and keys, audit logs, RBAC. * [ ] Import common CSAF publishers; seed with sane defaults. ### Console * [ ] Build list grid with filters and saved views. * [ ] Quorum bar and Evidence table with signature icons and scope quality tags. * [ ] Conflict view for `DISPUTED`. * [ ] Simulation drawer integrated with Policy Studio. ### CLI * [ ] `stella vex consensus list|show|simulate|export` with JSON/CSV. * [ ] Stable schemas; tests for piping and scripting. ### Observability/Perf * [ ] Metrics, logs, traces as specified; dashboards. * [ ] Load tests at 10M consensus records/tenant; optimize indexes and caches. ### Docs * [ ] Author and cross‑link all docs listed in §5. * [ ] Add examples and screenshots to Console doc. > **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied. --- ## 7) Acceptance criteria * Normalization supports CSAF VEX, OpenVEX, CycloneDX VEX with product tree mapping to purls across npm, Maven, PyPI, Go, RPM/DEB. * Signature verification works and affects weights; unverifiable signatures do not crash flows. * Consensus outputs are reproducible, explainable, and queryable at scale. * Vuln Explorer displays consensus state and affects policy outcomes per thresholds. * Simulation reflects policy trust changes without side effects and returns rationale deltas. * CLI/API feature parity; evidence and quorum are exportable. * P95 performance budgets met; dashboards reflect health. --- ## 8) Risks and mitigations * **Mapping errors (CPE→purl):** use conservative scope scores, cap weights, surface warnings, manual override hooks in Policy Studio. * **Malicious or mistaken issuer:** signature verification plus trust weighting and tenancy overrides. * **Evidence storms:** debounce recompute; batch and shard workers; backpressure and prioritization. * **User confusion with conflicting VEX:** clear conflict UI, rationale chains, suggested actions, and policy banners. * **Stale statements:** recency decay and expiry windows in policy. --- ## 9) Test plan * **Unit:** status mapping, comparators per ecosystem, trust weighting, threshold math. * **Property tests:** invariants such as monotonicity with added supporting evidence and idempotent recompute. * **Integration:** Excitator→Lens→Policy→Vuln Explorer pipeline with signed/unsigned, conflicting, and stale evidence. * **E2E Console:** list filters, detail with quorum, conflict workflows, export, simulation. * **Security:** RBAC on Issuer Directory, CSRF for Console, signature verification path traversal guards. * **Performance:** cold/hot query latencies, recompute throughput, cache hit ratios. * **Determinism:** time‑travel snapshots reproduce prior consensus states. --- ## 10) Philosophy * **Consensus, not replacement.** The Lens summarizes without erasing dissent. * **Trust is contextual.** Tenants must tune weights and thresholds to their environments. * **Proof over prose.** Every decision comes with math, provenance, and a rationale chain. * **Safety by design.** When in doubt, surface conflicts instead of silently suppressing. > Final reminder: **Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.**