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 byVexProofBuilder(VexProofBuilder.cs), and serialized/digested byVexProofSerializer(VexProofSerializer.cs). The proof is surfaced over HTTP by thePOST /api/v1/vexlens/consensus:withProofendpoint (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".
| Field | Type | Required | Description |
|---|---|---|---|
schema | string | Yes | Schema identifier; always stellaops.vex-proof.v1. |
proofId | string | Yes | Unique identifier. Format: proof-{yyyy-MM-ddTHH:mm:ssZ}-{8 hex chars} (see Proof ID). |
computedAt | datetime | Yes | When the proof was computed (DateTimeOffset, ISO 8601). |
verdict | object | Yes | Final consensus verdict. See Verdict. |
inputs | object | Yes | All statements considered plus evaluation context. See Inputs. |
resolution | object | Yes | Resolution computation details. See Resolution. |
propagation | object | No | Dependency-graph propagation. Omitted (null) when no rules/paths were evaluated. See Propagation. |
conditions | object | No | Condition evaluation results. Omitted (null) when no conditions were evaluated. See Conditions. |
confidence | object | Yes | Confidence score breakdown. See Confidence. |
digest | string | No | Lowercase 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:
digestis a plain hex string, not a nested{algorithm, value}object.- Nullable fields (
propagation,conditions,digest,verdict.justification) are omitted from the output when null (JsonIgnoreCondition.WhenWritingNull).
Object Definitions
Verdict
The final consensus result (VexProofVerdict).
| Field | Type | Required | Description |
|---|---|---|---|
vulnerabilityId | string | Yes | CVE or vulnerability identifier |
productKey | string | Yes | Product identifier (typically a PURL) |
status | enum | Yes | Resolved VEX status. See VEX Status values. |
justification | enum | No | Justification code (typically present when status is not_affected). See Justification values. |
confidence | number | Yes | Confidence 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:
not_affectedaffectedfixedunder_investigationunknown
Justification values
VexJustification serializes to the OpenVEX justification codes:
component_not_presentvulnerable_code_not_presentvulnerable_code_not_in_execute_pathvulnerable_code_cannot_be_controlled_by_adversaryinline_mitigations_already_exist
Inputs
All VEX statements considered, plus the evaluation context (VexProofInputs).
| Field | Type | Required | Description |
|---|---|---|---|
statements | array | Yes | Array of input statements (both qualified and disqualified). See Statement Object. |
context | object | Yes | Evaluation context. See Context Object. |
Note: qualified/disqualified counts are not on
inputs; they live onresolution(qualifiedStatements,disqualifiedStatements,disqualificationReasons). Each statement is tagged individually withqualifiedand an optionaldisqualificationReason.
Statement Object
A single VEX statement with its computed trust weight (VexProofStatement).
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Statement identifier |
source | string | Yes | Source format (e.g. openvex, csaf_vex, nvd) |
issuer | object | Yes | Issuer information |
status | enum | Yes | VEX status from this statement |
justification | enum | No | Justification if status is not_affected |
weight | object | Yes | Trust weight breakdown |
timestamp | datetime | Yes | When the statement was issued |
signatureVerified | boolean | Yes | Whether the statement signature was verified |
qualified | boolean | Yes | Whether the statement qualified for consensus |
disqualificationReason | string | No | Reason the statement was disqualified (null when qualified) |
Context Object
Evaluation context for the proof (VexProofContext).
| Field | Type | Required | Description |
|---|---|---|---|
platform | string | No | Target platform (e.g. linux/amd64) |
distro | string | No | Target distro (e.g. rhel:9) |
features | array of string | Yes | Enabled features (may be empty) |
buildFlags | array of string | Yes | Build flags (may be empty) |
evaluationTime | datetime | Yes | Time 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.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Issuer identifier |
category | enum | Yes | VENDOR, DISTRIBUTOR, COMMUNITY, INTERNAL, AGGREGATOR |
trustTier | enum | Yes | AUTHORITATIVE, TRUSTED, UNTRUSTED, UNKNOWN |
Weight Object
Trust weight breakdown for a statement (VexProofWeight).
| Field | Type | Required | Description |
|---|---|---|---|
composite | number | Yes | Final composite weight [0.0, 1.0] |
factors | object | Yes | Individual weight factors (VexProofWeightFactors) |
Weight factors (all required, [0.0, 1.0]):
issuer: Issuer trust factorsignature: Signature verification factorfreshness: Freshness/recency factorformat: Format quality factorspecificity: Specificity factor (how targeted the statement is)
Resolution
Resolution computation details (VexProofResolution). This object replaces the older “merge trace” naming.
| Field | Type | Required | Description |
|---|---|---|---|
mode | enum | Yes | Consensus mode used. See Consensus Modes. |
qualifiedStatements | integer | Yes | Number of qualified statements |
disqualifiedStatements | integer | Yes | Number of disqualified statements |
disqualificationReasons | array of string | Yes | Distinct reasons for disqualification (may be empty) |
latticeComputation | object | No | Lattice computation details; present only in lattice mode when merge steps were recorded. See Lattice Computation. |
conflictAnalysis | object | Yes | Conflict analysis. See Conflict Analysis. |
Consensus Modes
ConsensusMode serializes to snake_case:
highest_weight: Use the statement with highest trust weightweighted_vote: Weighted voting among all statements (default for the API service)lattice: Lattice-based consensus (most conservative status wins ties; default inVexProofBuilder)authoritative_first: Prefer vendor/authoritative sources over others
Lattice Computation
Lattice-based computation details (VexProofLatticeComputation).
| Field | Type | Required | Description |
|---|---|---|---|
ordering | array of enum | Yes | Status ordering from bottom to top (array of VEX Status values) |
mergeSteps | array | Yes | Step-by-step merge computation. See Merge Step. |
finalPosition | enum | Yes | Final lattice position (VEX status) |
Merge Step
A single merge step in lattice computation (VexProofMergeStep).
| Field | Type | Required | Description |
|---|---|---|---|
step | integer | Yes | Step number (1-based) |
statementId | string | Yes | Statement being merged |
inputPosition | enum | Yes | Status from this statement |
weight | number | Yes | Weight of this statement |
action | enum | Yes | initialize, merge, skip (MergeAction) |
conflict | boolean | Yes | Whether a conflict was detected |
resolution | string | No | How the conflict was resolved |
resultPosition | enum | Yes | Resulting position after this step |
Conflict Analysis
Conflict analysis for the resolution (VexProofConflictAnalysis).
| Field | Type | Required | Description |
|---|---|---|---|
hasConflicts | boolean | Yes | Whether any conflicts were detected |
conflicts | array | Yes | List of conflicts (may be empty). See Conflict. |
conflictPenalty | number | Yes | Confidence penalty due to conflicts (negative or zero) |
Conflict
A single conflict between statements (VexProofConflict).
| Field | Type | Required | Description |
|---|---|---|---|
statementA | string | Yes | First conflicting statement |
statementB | string | Yes | Second conflicting statement |
statusA | enum | Yes | Status of first statement |
statusB | enum | Yes | Status of second statement |
severity | enum | Yes | low, medium, high, critical (ConflictSeverity) |
resolution | string | Yes | Resolution method |
winner | string | No | Winning statement ID |
Propagation
Dependency-graph propagation analysis (VexProofPropagation). The whole object is omitted (null) when no propagation rules or graph paths were evaluated.
| Field | Type | Required | Description |
|---|---|---|---|
applied | boolean | Yes | Whether propagation was applied (true once any rule triggered) |
rules | array | Yes | Propagation rules evaluated. See Propagation Rule. |
graphPaths | array | Yes | Dependency graph paths analyzed (may be empty). See Graph Path. |
inheritedStatus | enum | No | Status inherited from a dependency, if any (VEX status) |
overrideApplied | boolean | Yes | Whether propagation overrode the consensus verdict |
Propagation Rule
Each evaluated rule (VexProofPropagationRule) records:
| Field | Type | Required | Description |
|---|---|---|---|
ruleId | string | Yes | Rule identifier |
description | string | Yes | Rule description |
triggered | boolean | Yes | Whether the rule was triggered |
effect | string | No | Effect applied if triggered |
The built-in rule IDs emitted by PropagationRuleEngine (src/VexLens/StellaOps.VexLens/Propagation/PropagationRuleEngine.cs) are hyphen-delimited:
direct-dependency-affected: Affected status propagates to direct dependentstransitive-dependency-affected: Affected status propagates through the transitive dependency treedependency-fixed: Fixed status handling for dependenciesdependency-not-affected: NotAffected propagation for dependencies
Graph Path
A path through the dependency graph (VexProofGraphPath).
| Field | Type | Required | Description |
|---|---|---|---|
root | string | Yes | Root product |
path | array of string | Yes | Path of dependencies |
pathType | enum | Yes | direct_dependency, transitive_dependency, dev_dependency, optional_dependency (DependencyPathType) |
depth | integer | Yes | Depth in the dependency tree |
Conditions
Condition evaluation results (VexProofConditions). The whole object is omitted (null) when no conditions were evaluated.
| Field | Type | Required | Description |
|---|---|---|---|
evaluated | array | Yes | Condition evaluation results. See Condition Result. |
unevaluated | array of string | Yes | Condition IDs that could not be evaluated (may be empty) |
unknownCount | integer | Yes | Count 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 definitetrue/false).
Condition Result
A single condition evaluation (VexProofConditionResult).
| Field | Type | Required | Description |
|---|---|---|---|
conditionId | string | Yes | Condition identifier |
expression | string | Yes | Condition expression |
result | enum | Yes | true, false, unknown (ConditionOutcome) |
contextValue | string | No | Context value used in evaluation |
Condition Types
ConditionType (src/VexLens/StellaOps.VexLens/Conditions/IConditionEvaluator.cs) — the supported condition handlers:
Platform: OS/architecture (e.g.,linux/amd64); supports*wildcardsDistro: Distribution (e.g.,rhel:9); supports*wildcardsFeature: Feature flag conditionBuildFlag: Compile-time flagEnvironment: Environment variableCustom: Custom expression (parsed forplatform/distro/feature/env.KEY/buildFlag.KEYsyntax)
Confidence
Confidence score breakdown (VexProofConfidence).
| Field | Type | Required | Description |
|---|---|---|---|
score | number | Yes | Overall confidence [0.0, 1.0] |
tier | enum | Yes | Confidence tier. See Confidence Tiers. |
breakdown | object | Yes | Confidence factor breakdown (VexProofConfidenceBreakdown) |
improvements | array of string | Yes | Suggestions for improving confidence (may be empty) |
Confidence Tiers
ConfidenceTier serializes to snake_case and has five levels (classified by VexProofBuilder):
very_high(score >= 0.9)high(score >= 0.75)medium(score >= 0.5)low(score >= 0.25)very_low(score < 0.25)
The
VexProofGatepolicy gate only recognizes the three tierslow/medium/highfor itsMinimumConfidenceTiercomparison (see Policy Integration);very_highandvery_loware 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].
weightSpread: Base weight from statement weightsconflictPenalty: Penalty from conflicts (negative or zero)freshnessBonus: Bonus from recent statementssignatureBonus: Bonus from verified signaturesconditionCoverage: Coverage of conditions evaluated [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:
- Set
digestto null and serialize the proof to canonical JSON usingStellaOps.Canonical.Json.CanonJson(sorted keys, no whitespace, minimal escaping). - Compute the SHA-256 hash of the UTF-8 canonical JSON bytes.
- 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:
| Method | Path | Auth (scope policy) | Description |
|---|---|---|---|
| POST | /api/v1/vexlens/consensus:withProof | vexlens.read | Computes VEX consensus for a vulnerability/product pair and returns the full proof object alongside the verdict. |
| POST | /api/v1/vexlens/consensus | vexlens.read | Computes consensus without the proof object. |
| POST | /api/v1/vexlens/consensus:batch | vexlens.read | Computes consensus for multiple pairs in one request. |
Scope note: the
vexlens.read/vexlens.writeauthorization 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 catalogsrc/Authority/.../StellaOps.Auth.Abstractions/StellaOpsScopes.cs(which declares VEX-ingestion scopesvex:read,vex:ingest,vex.adminand VexHub scopesvexhub:read,vexhub:admin). Treatvexlens.read/vexlens.writeas service-scoped names pending catalog reconciliation.
Request: ComputeConsensusWithProofRequest
| Field | Type | Required | Description |
|---|---|---|---|
vulnerabilityId | string | Yes | CVE / vulnerability identifier |
productKey | string | Yes | Product key (typically a PURL) |
tenantId | string | No | Tenant; otherwise taken from the X-StellaOps-TenantId header |
mode | enum | No | Consensus mode (defaults to weighted_vote in the API service) |
minimumWeightThreshold | number | No | Minimum trust weight (default 0.1) |
storeResult | boolean | No | Persist the resulting consensus projection |
proofContext | object | No | { 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:
| Field | Type | Description |
|---|---|---|
proofId | string | Proof identifier |
schema | string | stellaops.vex-proof.v1 |
vulnerabilityId | string | From the proof verdict |
productKey | string | From the proof verdict |
finalStatus | string | Verdict status (enum name) |
justification | string | Verdict justification (enum name), if any |
confidenceScore | number | Proof confidence score |
confidenceTier | string | Proof confidence tier (enum name) |
statementCount | integer | Number of input statements |
conflictCount | integer | Number of conflicts |
mergeAlgorithm | string | Consensus mode (enum name) |
digest | string | Proof digest |
generatedAt | datetime | Proof computedAt |
rawProofJson | string | The 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 asNotAffected,Lattice), whereasrawProofJsonuses 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:
| Setting | Type | Default | Description |
|---|---|---|---|
Enabled | bool | true | Enable/disable gate |
MinimumConfidenceTier | string | medium | Required confidence tier (low/medium/high) |
RequireProofForNotAffected | bool | true | Require proof for NotAffected |
RequireProofForFixed | bool | false | Require proof for Fixed |
MaxAllowedConflicts | int | 5 | Maximum allowed conflicts (set -1 for unlimited) |
MaxProofAgeHours | int | 168 | Maximum proof age in hours (7 days; set -1 for no limit) |
RequireSignedStatements | bool | false | Require all statements signed |
MinimumInputStatements | int | 1 | Minimum input statement count |
EnvironmentConfidenceTiers | map<string,string> | production=high, staging=medium, development=low | Per-environment overrides of the required minimum confidence tier |
AnchorAwareMode | bool | false | Enable additional DSSE/Rekor anchoring validation |
RequireVexAnchoring | bool | false | (anchor-aware) Require a verified DSSE envelope digest (sha256:<64 hex>) |
RequireRekorVerification | bool | false | (anchor-aware) Require Rekor transparency verification with a log index |
The gate reads proof attributes from
PolicyGateContext.Metadatakeys: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 presetVexProofGateOptions.StrictAnchorAwareenables 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.
Related Documentation
- VEX Consensus Guide — how statements are gathered, weighted, and resolved
- Trust weight breakdown — the per-statement weight factors that feed consensus (in this document)
- Policy Integration — how proofs are consumed by the
VexProofGate(in this document) - OpenVEX Specification
- CycloneDX VEX
