VEX Proof Object Schema Reference

Schema: stellaops.vex-proof.v1  ·  Version: 1.0  ·  Last updated: 2026-05-30

This is the field-level reference for the StellaOps VEX Proof Object — the cryptographically verifiable record of how a VEX consensus verdict was reached. It is written for integrators consuming the VexLens consensus API, policy authors wiring up the proof gate, and reviewers auditing a release decision.

Overview

A VEX Proof Object provides a cryptographically verifiable audit trail of how a VEX consensus verdict is computed. Every VEX resolution in StellaOps produces a proof object that documents the inputs considered, the resolution process, and the decision rationale — so a verdict can be independently re-derived and trusted offline.

Implementation reference: the canonical record types are defined in src/VexLens/StellaOps.VexLens/Proof/VexProof.cs, built by VexProofBuilder (VexProofBuilder.cs), and serialized/digested by VexProofSerializer (VexProofSerializer.cs). The proof is surfaced over HTTP by the POST /api/v1/vexlens/consensus:withProof endpoint (see API Access). There is no separate JSON Schema (*.schema.json) artifact shipped for the proof object; the structure below is derived directly from the serialized C# record graph (System.Text.Json, camelCase property names).

Object Shape

The proof object is the JSON serialization of the VexProof record. Property names use camelCase. The schema constant is exposed as VexProof.SchemaVersion = "stellaops.vex-proof.v1".

FieldTypeRequiredDescription
schemastringYesSchema identifier; always stellaops.vex-proof.v1.
proofIdstringYesUnique identifier. Format: proof-{yyyy-MM-ddTHH:mm:ssZ}-{8 hex chars} (see Proof ID).
computedAtdatetimeYesWhen the proof was computed (DateTimeOffset, ISO 8601).
verdictobjectYesFinal consensus verdict. See Verdict.
inputsobjectYesAll statements considered plus evaluation context. See Inputs.
resolutionobjectYesResolution computation details. See Resolution.
propagationobjectNoDependency-graph propagation. Omitted (null) when no rules/paths were evaluated. See Propagation.
conditionsobjectNoCondition evaluation results. Omitted (null) when no conditions were evaluated. See Conditions.
confidenceobjectYesConfidence score breakdown. See Confidence.
digeststringNoLowercase hex SHA-256 of the canonical JSON of the proof excluding this field. Always populated by VexProofBuilder.Build(); excluded from the digest input itself. See Digest Computation.

Notes:

Object Definitions

Verdict

The final consensus result (VexProofVerdict).

FieldTypeRequiredDescription
vulnerabilityIdstringYesCVE or vulnerability identifier
productKeystringYesProduct identifier (typically a PURL)
statusenumYesResolved VEX status. See VEX Status values.
justificationenumNoJustification code (typically present when status is not_affected). See Justification values.
confidencenumberYesConfidence score [0.0, 1.0] (mirrors confidence.score)
{
  "vulnerabilityId": "CVE-2023-12345",
  "productKey": "pkg:npm/lodash@4.17.21",
  "status": "not_affected",
  "justification": "vulnerable_code_not_in_execute_path",
  "confidence": 0.78
}

VEX Status values

VexStatus (src/VexLens/StellaOps.VexLens/Models/NormalizedVexModels.cs) serializes to the OpenVEX-aligned snake_case values:

Justification values

VexJustification serializes to the OpenVEX justification codes:

Inputs

All VEX statements considered, plus the evaluation context (VexProofInputs).

FieldTypeRequiredDescription
statementsarrayYesArray of input statements (both qualified and disqualified). See Statement Object.
contextobjectYesEvaluation context. See Context Object.

Note: qualified/disqualified counts are not on inputs; they live on resolution (qualifiedStatements, disqualifiedStatements, disqualificationReasons). Each statement is tagged individually with qualified and an optional disqualificationReason.

Statement Object

A single VEX statement with its computed trust weight (VexProofStatement).

FieldTypeRequiredDescription
idstringYesStatement identifier
sourcestringYesSource format (e.g. openvex, csaf_vex, nvd)
issuerobjectYesIssuer information
statusenumYesVEX status from this statement
justificationenumNoJustification if status is not_affected
weightobjectYesTrust weight breakdown
timestampdatetimeYesWhen the statement was issued
signatureVerifiedbooleanYesWhether the statement signature was verified
qualifiedbooleanYesWhether the statement qualified for consensus
disqualificationReasonstringNoReason the statement was disqualified (null when qualified)

Context Object

Evaluation context for the proof (VexProofContext).

FieldTypeRequiredDescription
platformstringNoTarget platform (e.g. linux/amd64)
distrostringNoTarget distro (e.g. rhel:9)
featuresarray of stringYesEnabled features (may be empty)
buildFlagsarray of stringYesBuild flags (may be empty)
evaluationTimedatetimeYesTime of evaluation

Issuer Object

Issuer information for a statement (VexProofIssuer). The category and trustTier enums declare uppercase JSON member names via [JsonPropertyName] on IssuerCategory / TrustTier.

FieldTypeRequiredDescription
idstringYesIssuer identifier
categoryenumYesVENDOR, DISTRIBUTOR, COMMUNITY, INTERNAL, AGGREGATOR
trustTierenumYesAUTHORITATIVE, TRUSTED, UNTRUSTED, UNKNOWN

Weight Object

Trust weight breakdown for a statement (VexProofWeight).

FieldTypeRequiredDescription
compositenumberYesFinal composite weight [0.0, 1.0]
factorsobjectYesIndividual weight factors (VexProofWeightFactors)

Weight factors (all required, [0.0, 1.0]):

Resolution

Resolution computation details (VexProofResolution). This object replaces the older “merge trace” naming.

FieldTypeRequiredDescription
modeenumYesConsensus mode used. See Consensus Modes.
qualifiedStatementsintegerYesNumber of qualified statements
disqualifiedStatementsintegerYesNumber of disqualified statements
disqualificationReasonsarray of stringYesDistinct reasons for disqualification (may be empty)
latticeComputationobjectNoLattice computation details; present only in lattice mode when merge steps were recorded. See Lattice Computation.
conflictAnalysisobjectYesConflict analysis. See Conflict Analysis.

Consensus Modes

ConsensusMode serializes to snake_case:

Lattice Computation

Lattice-based computation details (VexProofLatticeComputation).

FieldTypeRequiredDescription
orderingarray of enumYesStatus ordering from bottom to top (array of VEX Status values)
mergeStepsarrayYesStep-by-step merge computation. See Merge Step.
finalPositionenumYesFinal lattice position (VEX status)

Merge Step

A single merge step in lattice computation (VexProofMergeStep).

FieldTypeRequiredDescription
stepintegerYesStep number (1-based)
statementIdstringYesStatement being merged
inputPositionenumYesStatus from this statement
weightnumberYesWeight of this statement
actionenumYesinitialize, merge, skip (MergeAction)
conflictbooleanYesWhether a conflict was detected
resolutionstringNoHow the conflict was resolved
resultPositionenumYesResulting position after this step

Conflict Analysis

Conflict analysis for the resolution (VexProofConflictAnalysis).

FieldTypeRequiredDescription
hasConflictsbooleanYesWhether any conflicts were detected
conflictsarrayYesList of conflicts (may be empty). See Conflict.
conflictPenaltynumberYesConfidence penalty due to conflicts (negative or zero)

Conflict

A single conflict between statements (VexProofConflict).

FieldTypeRequiredDescription
statementAstringYesFirst conflicting statement
statementBstringYesSecond conflicting statement
statusAenumYesStatus of first statement
statusBenumYesStatus of second statement
severityenumYeslow, medium, high, critical (ConflictSeverity)
resolutionstringYesResolution method
winnerstringNoWinning statement ID

Propagation

Dependency-graph propagation analysis (VexProofPropagation). The whole object is omitted (null) when no propagation rules or graph paths were evaluated.

FieldTypeRequiredDescription
appliedbooleanYesWhether propagation was applied (true once any rule triggered)
rulesarrayYesPropagation rules evaluated. See Propagation Rule.
graphPathsarrayYesDependency graph paths analyzed (may be empty). See Graph Path.
inheritedStatusenumNoStatus inherited from a dependency, if any (VEX status)
overrideAppliedbooleanYesWhether propagation overrode the consensus verdict

Propagation Rule

Each evaluated rule (VexProofPropagationRule) records:

FieldTypeRequiredDescription
ruleIdstringYesRule identifier
descriptionstringYesRule description
triggeredbooleanYesWhether the rule was triggered
effectstringNoEffect applied if triggered

The built-in rule IDs emitted by PropagationRuleEngine (src/VexLens/StellaOps.VexLens/Propagation/PropagationRuleEngine.cs) are hyphen-delimited:

Graph Path

A path through the dependency graph (VexProofGraphPath).

FieldTypeRequiredDescription
rootstringYesRoot product
patharray of stringYesPath of dependencies
pathTypeenumYesdirect_dependency, transitive_dependency, dev_dependency, optional_dependency (DependencyPathType)
depthintegerYesDepth in the dependency tree

Conditions

Condition evaluation results (VexProofConditions). The whole object is omitted (null) when no conditions were evaluated.

FieldTypeRequiredDescription
evaluatedarrayYesCondition evaluation results. See Condition Result.
unevaluatedarray of stringYesCondition IDs that could not be evaluated (may be empty)
unknownCountintegerYesCount of conditions whose result was unknown

Note: condition coverage is not on this object; it is carried in confidence.breakdown.conditionCoverage (the fraction of conditions that resolved to a definite true/false).

Condition Result

A single condition evaluation (VexProofConditionResult).

FieldTypeRequiredDescription
conditionIdstringYesCondition identifier
expressionstringYesCondition expression
resultenumYestrue, false, unknown (ConditionOutcome)
contextValuestringNoContext value used in evaluation

Condition Types

ConditionType (src/VexLens/StellaOps.VexLens/Conditions/IConditionEvaluator.cs) — the supported condition handlers:

Confidence

Confidence score breakdown (VexProofConfidence).

FieldTypeRequiredDescription
scorenumberYesOverall confidence [0.0, 1.0]
tierenumYesConfidence tier. See Confidence Tiers.
breakdownobjectYesConfidence factor breakdown (VexProofConfidenceBreakdown)
improvementsarray of stringYesSuggestions for improving confidence (may be empty)

Confidence Tiers

ConfidenceTier serializes to snake_case and has five levels (classified by VexProofBuilder):

The VexProofGate policy gate only recognizes the three tiers low / medium / high for its MinimumConfidenceTier comparison (see Policy Integration); very_high and very_low are emitted by the proof but are not distinct comparison points in the gate’s tier ordering.

Confidence breakdown

Fields of breakdown (all required, in score order). The score is computed as weightSpread + conflictPenalty + freshnessBonus + signatureBonus, then multiplied by conditionCoverage, then clamped to [0.0, 1.0].

Proof ID

proofId is generated by VexProofBuilder.GenerateProofId as:

proof-{computedAt:yyyy-MM-ddTHH:mm:ssZ}-{8 lowercase hex chars}

The 8-character suffix derives from a GUID provider (IGuidProvider). Because it is random, the proofId is not part of the digest computation in determinism tests (callers normalize it before digesting; see VexProofDeterminismTests).

Digest Computation

The proof digest is computed over the canonical (RFC 8785, sorted-key) JSON serialization, via VexProofSerializer.ComputeDigest:

  1. Set digest to null and serialize the proof to canonical JSON using StellaOps.Canonical.Json.CanonJson (sorted keys, no whitespace, minimal escaping).
  2. Compute the SHA-256 hash of the UTF-8 canonical JSON bytes.
  3. Encode as lowercase hexadecimal (Convert.ToHexStringLower) — a 64-character string.

The result is stored in the top-level digest string field. VexProofSerializer.VerifyDigest recomputes the digest and compares case-insensitively. There is no algorithm/value wrapper object.

{
  "digest": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}

Example Proof Object

The example below reflects the actual serialized shape (camelCase, digest as a hex string, resolution object, confidence.breakdown, per-statement qualified flag, and an inputs.context block).

{
  "schema": "stellaops.vex-proof.v1",
  "proofId": "proof-2026-01-02T10:30:00Z-1a2b3c4d",
  "computedAt": "2026-01-02T10:30:00Z",
  "verdict": {
    "vulnerabilityId": "CVE-2023-12345",
    "productKey": "pkg:npm/lodash@4.17.21",
    "status": "not_affected",
    "justification": "vulnerable_code_not_in_execute_path",
    "confidence": 0.78
  },
  "inputs": {
    "statements": [
      {
        "id": "stmt-001",
        "source": "openvex",
        "issuer": {
          "id": "lodash-maintainers",
          "category": "VENDOR",
          "trustTier": "AUTHORITATIVE"
        },
        "status": "not_affected",
        "justification": "vulnerable_code_not_in_execute_path",
        "weight": {
          "composite": 0.85,
          "factors": {
            "issuer": 0.90,
            "signature": 1.00,
            "freshness": 0.95,
            "format": 1.00,
            "specificity": 0.70
          }
        },
        "timestamp": "2023-06-15T10:30:00Z",
        "signatureVerified": true,
        "qualified": true
      }
    ],
    "context": {
      "platform": "linux/amd64",
      "features": ["esm"],
      "buildFlags": [],
      "evaluationTime": "2026-01-02T10:30:00Z"
    }
  },
  "resolution": {
    "mode": "lattice",
    "qualifiedStatements": 1,
    "disqualifiedStatements": 0,
    "disqualificationReasons": [],
    "latticeComputation": {
      "ordering": ["affected", "unknown", "under_investigation", "fixed", "not_affected"],
      "mergeSteps": [
        {
          "step": 1,
          "statementId": "stmt-001",
          "inputPosition": "not_affected",
          "weight": 0.85,
          "action": "initialize",
          "conflict": false,
          "resultPosition": "not_affected"
        }
      ],
      "finalPosition": "not_affected"
    },
    "conflictAnalysis": {
      "hasConflicts": false,
      "conflicts": [],
      "conflictPenalty": 0.00
    }
  },
  "confidence": {
    "score": 0.78,
    "tier": "high",
    "breakdown": {
      "weightSpread": 0.85,
      "conflictPenalty": 0.00,
      "freshnessBonus": 0.00,
      "signatureBonus": 0.00,
      "conditionCoverage": 1.00
    },
    "improvements": []
  },
  "digest": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}

API Access

The proof object is produced by the VexLens service. The relevant endpoint is mapped in src/VexLens/StellaOps.VexLens.WebService/Extensions/VexLensEndpointExtensions.cs:

MethodPathAuth (scope policy)Description
POST/api/v1/vexlens/consensus:withProofvexlens.readComputes VEX consensus for a vulnerability/product pair and returns the full proof object alongside the verdict.
POST/api/v1/vexlens/consensusvexlens.readComputes consensus without the proof object.
POST/api/v1/vexlens/consensus:batchvexlens.readComputes consensus for multiple pairs in one request.

Scope note: the vexlens.read / vexlens.write authorization policies are registered locally by the VexLens WebService (Program.cs, auth.AddStellaOpsScopePolicy("vexlens.read", "vexlens.read")). As of this writing they are not declared in the canonical scope catalog src/Authority/.../StellaOps.Auth.Abstractions/StellaOpsScopes.cs (which declares VEX-ingestion scopes vex:read, vex:ingest, vex.admin and VexHub scopes vexhub:read, vexhub:admin). Treat vexlens.read/vexlens.write as service-scoped names pending catalog reconciliation.

Request: ComputeConsensusWithProofRequest

FieldTypeRequiredDescription
vulnerabilityIdstringYesCVE / vulnerability identifier
productKeystringYesProduct key (typically a PURL)
tenantIdstringNoTenant; otherwise taken from the X-StellaOps-TenantId header
modeenumNoConsensus mode (defaults to weighted_vote in the API service)
minimumWeightThresholdnumberNoMinimum trust weight (default 0.1)
storeResultbooleanNoPersist the resulting consensus projection
proofContextobjectNo{ platform, distribution, enabledFeatures[], buildFlags[] } mapped into the proof’s evaluation context

Response: ComputeConsensusWithProofResponse.proof (ProofResponse)

The HTTP response embeds a flattened proof summary plus the full serialized proof. The summary (ProofResponse) fields are:

FieldTypeDescription
proofIdstringProof identifier
schemastringstellaops.vex-proof.v1
vulnerabilityIdstringFrom the proof verdict
productKeystringFrom the proof verdict
finalStatusstringVerdict status (enum name)
justificationstringVerdict justification (enum name), if any
confidenceScorenumberProof confidence score
confidenceTierstringProof confidence tier (enum name)
statementCountintegerNumber of input statements
conflictCountintegerNumber of conflicts
mergeAlgorithmstringConsensus mode (enum name)
digeststringProof digest
generatedAtdatetimeProof computedAt
rawProofJsonstringThe full proof object serialized via VexProofSerializer.Serialize (the structure documented above)

Note: the flattened summary fields (e.g. finalStatus, mergeAlgorithm) use .ToString() on the enums (PascalCase member names such as NotAffected, Lattice), whereas rawProofJson uses the JSON serializer’s snake_case/uppercase member names described in the field tables above.

Policy Integration

VEX proofs integrate with the policy gate system via VexProofGate (src/Policy/__Libraries/StellaOps.Policy/Gates/VexProofGate.cs), configured through VexProofGateOptions:

SettingTypeDefaultDescription
EnabledbooltrueEnable/disable gate
MinimumConfidenceTierstringmediumRequired confidence tier (low/medium/high)
RequireProofForNotAffectedbooltrueRequire proof for NotAffected
RequireProofForFixedboolfalseRequire proof for Fixed
MaxAllowedConflictsint5Maximum allowed conflicts (set -1 for unlimited)
MaxProofAgeHoursint168Maximum proof age in hours (7 days; set -1 for no limit)
RequireSignedStatementsboolfalseRequire all statements signed
MinimumInputStatementsint1Minimum input statement count
EnvironmentConfidenceTiersmap<string,string>production=high, staging=medium, development=lowPer-environment overrides of the required minimum confidence tier
AnchorAwareModeboolfalseEnable additional DSSE/Rekor anchoring validation
RequireVexAnchoringboolfalse(anchor-aware) Require a verified DSSE envelope digest (sha256:<64 hex>)
RequireRekorVerificationboolfalse(anchor-aware) Require Rekor transparency verification with a log index

The gate reads proof attributes from PolicyGateContext.Metadata keys: vex_proof_id, vex_proof_confidence_tier, vex_proof_confidence_score, vex_proof_conflict_count, vex_proof_statement_count, vex_proof_all_signed, vex_proof_computed_at, vex_proof_consensus_outcome, and (anchor-aware) vex_proof_anchored, vex_proof_envelope_digest, vex_proof_rekor_verified, vex_proof_rekor_log_index. A preset VexProofGateOptions.StrictAnchorAware enables strict production settings (high tier, signed statements, anchoring + Rekor, 0 conflicts, 72-hour max age).

Persistence

VexLens persists consensus projections (not the proof object itself) under the vexlens PostgreSQL schema. Migrations are embedded in StellaOps.VexLens.Persistence (Migrations/001_consensus_projections.sql, Migrations/002_noise_gating_state.sql) and applied on startup. The full proof object is returned inline by the consensus:withProof endpoint as rawProofJson; it is not stored as a standalone row in the projection tables.