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

SymptomMost likely causeSection
A not_affected statement does not suppress a finding or a release blockAnother statement in the same issuer tier states affected or fixed2.1
Every finding shows none or unknown as its VEX statusThe tenant has no projection rows, or Policy cannot answer2.2
A release gate denies with “findings source UNAVAILABLE”The gate projection was never fed for the tenant2.3
The drift gate blocks with AffectedReachableA newly reachable CVE is affected or under_investigation in the target snapshot2.4
stella vex explain disagrees with a finding or a gateIssuer tiers, distro release scoping or projection lag2.5
A console VEX decision changes nothingThe decision cannot apply (scope, subject, status, window), or its feed does not reach Policy2.6
A signed OpenVEX push is rejectedIssuer allowlist, signature or document2.7
The effective status has no justificationThe winning statements carry none2.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):

RouteAnswer
GET /api/vulnerabilities/v1/issues/{vulnerabilityId}/{productKey}/factsThe live statements: issuer, status, justification, severity, and applicability with platform (the distro release).
GET /api/vulnerabilities/v1/facts/{factId}/evidenceThe provenance of one statement: origin, document digest, verification status and signing key fingerprint.
GET /api/vulnerabilities/v1/products/{productKey}/issuesThe 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"}]}
AnswerMeaning
200 with an itemThe tenant’s row in the gate projection: status, justification, winningIssuers, contested, overlayApplied, decisionHash.
200 without the itemThe 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_readyThe gate projection has never applied a hub event in this Policy database.
400The 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.

vexStatusMeaning
affected, fixed, not_affected, under_investigationThe tenant’s effective status from the gate projection.
nonePolicy answered and holds no row for the issue.
unknownPolicy 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)ServiceMeaning
Gate projection is not enabled or Gate projection is DISABLEDpolicy-enginePolicyEngine:VulnGateProjection:Enabled is false.
Gate projection has NO active tenantspolicy-engineThis 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 asidepolicy-engineThe first fill has not finished.
Corpus bulk seed finished with <n> issuespolicy-engineThe first fill finished.
Gate projection lag: <n> hub events outstanding on vuln.consensuspolicy-engineHub changes not applied yet.
policy.findings_lookup.projection_not_readypolicy-engineA gate evaluation found the projection not ready for the tenant.
Hub-backed dependency reachability v2 completed for job <id>scanner-workerPer-scan counts: excludedByStatus, excludedByVersion, excludedByPlatform, noLiveStatements, factsUnavailable.
Hub statements for job <id> name other releases of the image's distro but not <releases>scanner-workerNo 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

  1. Run stella vex explain for 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 affected or fixed and Contested: yes. Another statement in the same tier wins by status precedence (affected, then fixed, then not_affected). On a default install no issuer tiers are configured, so every issuer is community and one affected statement from any feed outranks the vendor’s not_affected. Resolution: assign the issuer a higher tier in Policy and in the Scanner Worker (runbook, section 5.2). stella vex explain keeps showing the default-tier result after that change.
    • The status is not_affected. Go to step 2.
  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.
  3. 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 vexStatus already shows the tenant’s current status.
  4. For a distro package: the Scanner Worker evaluates only the statements whose platform matches the image’s release. Check the platform of each statement on the facts route.

2.2 Every finding shows none or unknown

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:

  1. The projection is disabled (POLICY_VULN_GATE_PROJECTION_ENABLED=false).
  2. 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).
  3. The tenant was activated after the first fill and is not back-filled yet.
  4. 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

DifferenceCause
The issue is decided by an issuer with a configured tierstella vex explain always uses the default tiers.
The Scanner Worker and the release gate disagreeThe two services have different issuer tiers (Vulnerabilities:Consensus:Trust and Vulnerabilities:Consensus:Trust).
A distro packageThe 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 statementsThe projection has not applied the change yet, or the issuer tiers changed after the row was derived (runbook, section 5.2).
overlayApplied is trueA 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:

  1. Check that the decision can apply. Read the tenant’s decision states from findings-web with a findings:projection:read token 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.
  2. Check the validity window. Before validNotBefore and from validNotAfter on, the decision does not apply.

  3. 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 productKey with the key in Policy’s answer (section 1.2).

  4. Check that the feed reaches Policy. The projection and PolicyEngine:VulnGateProjection:FindingsOverlays:Enabled must be on. The policy-engine log shows VEX decision overlay feed for tenant <tenant> failed when the feed cannot be read: findings-web is unreachable at FindingsOverlays:BaseAddress, or Authority refused the token (the policy-engine client lacks findings:projection:read or the tenant). The feed is retried every minute.

  5. 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:

AnswerCauseAction
202 AcceptedStored. The body gives documentDigest, factsWritten and factsChanged.None.
403 issuer_not_authorizedThe 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_failedThe DSSE signature does not verify against the issuer’s registered public key.Sign with the key whose public half is registered.
422 issuer_document_invalidThe signed payload has no valid supported OpenVEX statement.Correct the document.
400 invalid_envelopeThe 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