VEX Consensus Troubleshooting Guide
Audience: Support engineers and operators who diagnose VEX outcomes on scans, findings and release gates. Verified against
src/: 2026-10-04 (src/Vulnerabilities,src/Policy,src/Scanner/StellaOps.Scanner.Worker,src/Findings,src/Cli).
This guide maps VEX symptoms to their causes and to the reads that confirm them. The VEX Consensus Operations Runbook describes the consensus rules, where the effective status is applied, and the configuration keys.
Earlier model. This page replaces the “trust lattice” troubleshooting guide. The product does not compute a Provenance/Coverage/Replayability (PCR) trust vector, freshness decay, a conflict penalty or a confidence score for VEX, and it has no VexLens or Excititor service. The retired design is archived at docs-archive/modules/excititor/retired-20260926/trust-lattice.md.
Quick reference
| Symptom | Most likely cause | Section |
|---|---|---|
A not_affected statement does not suppress a finding or a release block | Another statement in the same issuer tier states affected or fixed | 2.1 |
Every finding shows none or unknown as its VEX status | The tenant has no projection rows, or Policy cannot answer | 2.2 |
| A release gate denies with “findings source UNAVAILABLE” | The gate projection was never fed for the tenant | 2.3 |
| The drift gate blocks with AffectedReachable | A newly reachable CVE is affected or under_investigation in the target snapshot | 2.4 |
stella vex explain disagrees with a finding or a gate | Issuer tiers, distro release scoping or projection lag | 2.5 |
| A console VEX decision changes nothing | The decision cannot apply (scope, subject, status, window), or its feed does not reach Policy | 2.6 |
| A signed OpenVEX push is rejected | Issuer allowlist, signature or document | 2.7 |
| The effective status has no justification | The winning statements carry none | 2.8 |
1. Diagnostic reads
1.1 The statements and the global consensus of one issue
stella vex explain CVE-2024-1234 --product-key pkg:npm/lodash
stella vex explain CVE-2024-1234 --product-key pkg:npm/lodash --json
The command reads the issue’s live statements from the hub and evaluates them with the shared consensus evaluator. The output gives the consensus status, the winning issuers, contested, the affected ranges of the winning statements, the decision hash, and one line per statement (<issuer>: <status> (<justification>) [<fact id>]). When the hub holds no statements, the output says so and the status is under_investigation.
The evaluation is global: it uses the default issuer tiers, no tenant overlays and every statement of the issue, without distro release scoping. The stella vex guide describes the options, the hub URL setting and the exit codes.
The hub routes behind it (scope advisory:read; escape the product key as one path segment):
| Route | Answer |
|---|---|
GET /api/vulnerabilities/v1/issues/{vulnerabilityId}/{productKey}/facts | The live statements: issuer, status, justification, severity, and applicability with platform (the distro release). |
GET /api/vulnerabilities/v1/facts/{factId}/evidence | The provenance of one statement: origin, document digest, verification status and signing key fingerprint. |
GET /api/vulnerabilities/v1/products/{productKey}/issues | The issues that the hub links to one product key. |
1.2 A tenant’s effective status
Policy answers for the caller’s tenant, which it takes from the token:
POST /api/policy/vex/effective-status
Authorization: Bearer <token with policy:read for the tenant>
Content-Type: application/json
{"items":[{"vulnerabilityId":"CVE-2024-1234","productKey":"pkg:npm/lodash"}]}
| Answer | Meaning |
|---|---|
200 with an item | The tenant’s row in the gate projection: status, justification, winningIssuers, contested, overlayApplied, decisionHash. |
200 without the item | The tenant has no row for the issue: no statement, no policy pack, or the projection has not applied the change yet. |
503 vex_projection_not_ready | The gate projection has never applied a hub event in this Policy database. |
400 | The request names no item, more than 500 items, or an item without both keys. |
1.3 The VEX status on a finding
Findings shows each finding’s vexStatus and vexJustification from the read in section 1.2.
vexStatus | Meaning |
|---|---|
affected, fixed, not_affected, under_investigation | The tenant’s effective status from the gate projection. |
none | Policy answered and holds no row for the issue. |
unknown | Policy could not answer (not ready, unreachable, refused, or an answer that breaks the contract), or the finding has no product key. findings-web logs the failed read at Debug level. |
1.4 Projection and matcher state in the logs
| Log message (start of the text) | Service | Meaning |
|---|---|---|
Gate projection is not enabled or Gate projection is DISABLED | policy-engine | PolicyEngine:VulnGateProjection:Enabled is false. |
Gate projection has NO active tenants | policy-engine | This Policy database has no policy pack and no overlay, so no row is derived. |
Gate projection has no checkpoint yet, so the event drain is standing aside | policy-engine | The first fill has not finished. |
Corpus bulk seed finished with <n> issues | policy-engine | The first fill finished. |
Gate projection lag: <n> hub events outstanding on vuln.consensus | policy-engine | Hub changes not applied yet. |
policy.findings_lookup.projection_not_ready | policy-engine | A gate evaluation found the projection not ready for the tenant. |
Hub-backed dependency reachability v2 completed for job <id> | scanner-worker | Per-scan counts: excludedByStatus, excludedByVersion, excludedByPlatform, noLiveStatements, factsUnavailable. |
Hub statements for job <id> name other releases of the image's distro but not <releases> | scanner-worker | No ingested feed covers the image’s distro release (for example an Alpine branch the secdb source does not fetch); the report is unavailable with reason release-not-covered:<release>. |
2. Symptoms
2.1 A not_affected statement does not suppress a finding or a release block
- Run
stella vex explainfor the finding’s vulnerability id and product key (section 1.1).- The statement is not listed. The hub holds no live statement from that issuer for this product key. Compare the product key of the finding with the product of the VEX document. A rejected push writes no statement (section 2.7).
- The status is
affectedorfixedandContested: yes. Another statement in the same tier wins by status precedence (affected, thenfixed, thennot_affected). On a default install no issuer tiers are configured, so every issuer iscommunityand oneaffectedstatement from any feed outranks the vendor’snot_affected. Resolution: assign the issuer a higher tier in Policy and in the Scanner Worker (runbook, section 5.2).stella vex explainkeeps showing the default-tier result after that change. - The status is
not_affected. Go to step 2.
- Read the tenant’s effective status (section 1.2).
503: see section 2.3.- No item: the tenant has no policy pack, or the projection has not applied the change yet (check the lag message, section 1.4).
- A status other than step 1: see section 2.5.
- For a finding from an earlier scan: the Scanner Worker applied consensus when the scan ran. A statement that arrived later removes the match at the next scan. The finding’s
vexStatusalready shows the tenant’s current status. - For a distro package: the Scanner Worker evaluates only the statements whose
platformmatches the image’s release. Check theplatformof each statement on the facts route.
2.2 Every finding shows none or unknown
unknownon every finding. Policy did not answer. Send the read from section 1.2 with apolicy:readtoken. A503answer is section 2.3. When Policy answers, check the Findings settingsFindingsSecurity:PolicyExceptions: the VEX read uses the same Policy base URL and service identity as the exception read.noneon every finding. Policy answered with no rows. The projection derives rows only for tenants that have a policy pack or an overlay; a tenant without one readsnone. A tenant that receives its first policy pack after the first fill is back-filled by the projection host. Until the back-fill finishes, the release findings lookup reports the projection unavailable for that tenant.
2.3 A release gate denies with “findings source UNAVAILABLE”
The gate evidence note reads findings source UNAVAILABLE; denied fail-closed. The policy pack carries CVE rules, and the release findings lookup could not answer, so the gate did not certify the release. Causes:
- The projection is disabled (
POLICY_VULN_GATE_PROJECTION_ENABLED=false). - The projection was never fed: no checkpoint because the first fill has not run or has not finished. The first fill waits until the Policy database has a tenant with a policy pack or an overlay (section 1.4).
- The tenant was activated after the first fill and is not back-filled yet.
- The release inputs are unavailable: the log shows
policy.findings_lookup.release_owner_unavailable, or the release has no components.
PolicyEngine:FindingsLookup:OnUnavailable=WarnAndAllow (POLICY_FINDINGS_ON_UNAVAILABLE) allows the release with a warning instead. Use it only where an unverified release is acceptable.
2.4 The drift gate blocks with AffectedReachable
The AffectedReachable check of POST /api/v1/policy/gate/evaluate blocks when the delta makes a CVE newly reachable and the target knowledge snapshot records that CVE as affected or under_investigation (condition delta_reachable > 0 AND vex_status IN ['affected', 'under_investigation']). A not_affected or fixed status, or no recorded status, gives a warning. The status comes from the knowledge snapshot, not from a separate consensus evaluation. SmartDiff:Gates:BlockOnAffectedReachable=false turns the block into a warning.
2.5 stella vex explain disagrees with a finding or a gate
| Difference | Cause |
|---|---|
| The issue is decided by an issuer with a configured tier | stella vex explain always uses the default tiers. |
| The Scanner Worker and the release gate disagree | The two services have different issuer tiers (Vulnerabilities:Consensus:Trust and Vulnerabilities:Consensus:Trust). |
| A distro package | The Scanner Worker scopes statements to the image’s release; stella vex explain and Policy’s projection evaluate every statement of the issue. |
| The tenant’s row is older than the statements | The projection has not applied the change yet, or the issuer tiers changed after the row was derived (runbook, section 5.2). |
overlayApplied is true | A tenant overlay decided the outcome. |
The decision hash covers the statements, the trust configuration, the overlays and the outcome. Two evaluations with the same hash had the same arguments. A different hash for the same issue shows that one of those arguments differs.
2.6 A console VEX decision or trust override changes nothing
Policy applies a tenant VEX decision as a status overlay (runbook, section 6). When a decision changes nothing:
Check that the decision can apply. Read the tenant’s decision states from findings-web with a
findings:projection:readtoken for the tenant:GET /api/findings/v1/ledger/vex-decision-overlays/snapshot. An item with"effect": "withdraw"gives the reason:withdrawReasonMeaning scopedThe decision names environments, projects or one SBOM node. subject_not_componentThe subject is an image, a repository or another non-component subject. product_key_underivableThe component purl has no hub product key (for example pkg:generic/...).status_not_assertedThe status is UNDER_INVESTIGATION.supersededAnother decision supersedes it. Check the validity window. Before
validNotBeforeand fromvalidNotAfteron, the decision does not apply.Check the package. The decision applies to the hub product key of the component (no version; the source package for Debian and Alpine). Compare its
productKeywith the key in Policy’s answer (section 1.2).Check that the feed reaches Policy. The projection and
PolicyEngine:VulnGateProjection:FindingsOverlays:Enabledmust be on. Thepolicy-enginelog showsVEX decision overlay feed for tenant <tenant> failedwhen the feed cannot be read: findings-web is unreachable atFindingsOverlays:BaseAddress, or Authority refused the token (thepolicy-engineclient lacksfindings:projection:reador the tenant). The feed is retried every minute.Check that the tenant is active. The projection reads decisions only for tenants that have a policy pack or an overlay.
Trust overrides are recorded but not applied to the effective status (runbook, section 6). The drift gate does not read decisions.
2.7 A signed OpenVEX push is rejected
POST /api/vulnerabilities/v1/issuers/{issuerId}/vex answers:
| Answer | Cause | Action |
|---|---|---|
202 Accepted | Stored. The body gives documentDigest, factsWritten and factsChanged. | None. |
403 issuer_not_authorized | The issuer is not allowlisted, or the caller’s OAuth client is not the client bound to the issuer. | Add or correct the Vulnerabilities:Ingestion:InternalIssuers entry (runbook, section 5.4). |
422 dsse_verification_failed | The DSSE signature does not verify against the issuer’s registered public key. | Sign with the key whose public half is registered. |
422 issuer_document_invalid | The signed payload has no valid supported OpenVEX statement. | Correct the document. |
400 invalid_envelope | The body is not a DSSE envelope. | Send the DSSE envelope. |
The caller also needs the vuln:submit scope.
2.8 The effective status has no justification
The justification is the first non-empty justification among the winning statements. Red Hat and SUSE CSAF statements carry no justification, so a not_affected that these issuers win has none.
3. Data to collect for support
stella vex explain <vulnerability-id> --product-key <product-key> --jsonfor each affected issue.- The Policy answer from section 1.2 for the same issues and tenant.
- The
policy-engineandscanner-workerlog messages from section 1.4. - The gate decision with its evidence note, for a gate symptom.
- The configured
Vulnerabilities:Consensus:TrustandVulnerabilities:Consensus:Trustvalues.
