# Verdict Manifest Specification > **Status**: Draft (Sprint 7100) > **Last Updated**: 2025-12-22 > **Source Advisory**: `docs/product-advisories/archived/22-Dec-2026 - Building a Trust Lattice for VEX Sources.md` ## 1. Overview A Verdict Manifest is a signed, immutable record of a VEX decisioning outcome. It captures all inputs used to produce a verdict, enabling deterministic replay and audit compliance. ### Purpose 1. **Auditability**: Prove exactly how a verdict was reached 2. **Reproducibility**: Replay the decision with identical results 3. **Compliance**: Meet regulatory requirements for security decisions 4. **Debugging**: Investigate unexpected verdict changes --- ## 2. Manifest Schema ### 2.1 Complete Schema ```typescript interface VerdictManifest { // Identity manifestId: string; // Unique identifier tenant: string; // Tenant scope // Scope assetDigest: string; // SHA256 of asset/SBOM vulnerabilityId: string; // CVE/GHSA/vendor ID // Inputs (pinned for replay) inputs: VerdictInputs; // Result result: VerdictResult; // Policy context policyHash: string; // SHA256 of policy file latticeVersion: string; // Trust lattice version // Metadata evaluatedAt: string; // ISO 8601 UTC timestamp manifestDigest: string; // SHA256 of canonical manifest } interface VerdictInputs { sbomDigests: string[]; // SBOM document digests vulnFeedSnapshotIds: string[]; // Feed snapshot identifiers vexDocumentDigests: string[]; // VEX document digests reachabilityGraphIds: string[]; // Call graph identifiers clockCutoff: string; // Evaluation timestamp } interface VerdictResult { status: "affected" | "not_affected" | "fixed" | "under_investigation"; confidence: number; // 0.0 to 1.0 explanations: VerdictExplanation[]; evidenceRefs: string[]; // Attestation/proof references } interface VerdictExplanation { sourceId: string; reason: string; provenanceScore: number; coverageScore: number; replayabilityScore: number; strengthMultiplier: number; freshnessMultiplier: number; claimScore: number; } ``` ### 2.2 Identity Fields | Field | Type | Description | |-------|------|-------------| | `manifestId` | string | Format: `verd:{tenant}:{asset_short}:{vuln_id}:{timestamp}` | | `tenant` | string | Tenant identifier for multi-tenancy | ### 2.3 Input Pinning All inputs that affect the verdict must be pinned for deterministic replay: | Field | Description | |-------|-------------| | `sbomDigests` | SHA256 digests of SBOM documents used | | `vulnFeedSnapshotIds` | Identifiers for vulnerability feed snapshots | | `vexDocumentDigests` | SHA256 digests of VEX documents considered | | `reachabilityGraphIds` | Identifiers for call graph snapshots | | `clockCutoff` | Timestamp used for freshness calculations | ### 2.4 Verdict Result The result section contains the actual verdict and full explanation: | Field | Description | |-------|-------------| | `status` | Final verdict: affected, not_affected, fixed, under_investigation | | `confidence` | Numeric confidence score (0.0 to 1.0) | | `explanations` | Per-source breakdown of scoring | | `evidenceRefs` | Links to attestations and proof bundles | --- ## 3. Deterministic Serialization ### 3.1 Canonical JSON Manifests are serialized using canonical JSON rules: 1. Keys sorted alphabetically (ASCII order) 2. No insignificant whitespace 3. UTF-8 encoding without BOM 4. Timestamps in ISO 8601 format with 'Z' suffix 5. Arrays sorted by natural key (sourceId, then score) 6. Numbers without trailing zeros ### 3.2 Digest Computation The manifest digest is computed over the canonical JSON: ```csharp public static string ComputeDigest(VerdictManifest manifest) { var json = CanonicalJsonSerializer.Serialize(manifest with { ManifestDigest = "" }); var bytes = Encoding.UTF8.GetBytes(json); var hash = SHA256.HashData(bytes); return $"sha256:{Convert.ToHexString(hash).ToLowerInvariant()}"; } ``` --- ## 4. Signing ### 4.1 DSSE Envelope Verdict manifests are signed using [DSSE (Dead Simple Signing Envelope)](https://github.com/secure-systems-lab/dsse): ```json { "payloadType": "application/vnd.stellaops.verdict+json", "payload": "", "signatures": [ { "keyid": "projects/stellaops/keys/verdict-signer-2025", "sig": "" } ] } ``` ### 4.2 Predicate Type For in-toto attestations: ``` https://stella-ops.org/attestations/vex-verdict/1 ``` ### 4.3 Rekor Integration Optionally, verdicts can be logged to Sigstore Rekor for transparency: ```json { "rekorLogId": "rekor.sigstore.dev", "rekorLogIndex": 12345678, "rekorEntryUrl": "https://rekor.sigstore.dev/api/v1/log/entries/..." } ``` --- ## 5. Storage ### 5.1 PostgreSQL Schema ```sql CREATE TABLE authority.verdict_manifests ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), manifest_id TEXT NOT NULL UNIQUE, tenant TEXT NOT NULL, -- Scope asset_digest TEXT NOT NULL, vulnerability_id TEXT NOT NULL, -- Inputs (JSONB) inputs_json JSONB NOT NULL, -- Result status TEXT NOT NULL, confidence DOUBLE PRECISION NOT NULL, result_json JSONB NOT NULL, -- Policy context policy_hash TEXT NOT NULL, lattice_version TEXT NOT NULL, -- Metadata evaluated_at TIMESTAMPTZ NOT NULL, manifest_digest TEXT NOT NULL, -- Signature signature_json JSONB, rekor_log_id TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); ``` ### 5.2 Indexing Strategy | Index | Purpose | |-------|---------| | `(tenant, asset_digest, vulnerability_id)` | Primary lookup | | `(tenant, policy_hash, lattice_version)` | Policy version queries | | `(evaluated_at)` BRIN | Time-based queries | | Unique: `(tenant, asset_digest, vulnerability_id, policy_hash, lattice_version)` | Ensure one verdict per configuration | --- ## 6. Replay Verification ### 6.1 Verification Protocol 1. Retrieve stored manifest by ID 2. Fetch pinned inputs (SBOM, VEX docs, feeds) by digest 3. Re-execute trust lattice evaluation with identical inputs 4. Compare result with stored verdict 5. Verify signature if present ### 6.2 Verification Request ```http POST /api/v1/authority/verdicts/{manifestId}/replay Authorization: Bearer ``` ### 6.3 Verification Response ```json { "success": true, "originalManifest": { ... }, "replayedManifest": { ... }, "differences": [], "signatureValid": true, "verifiedAt": "2025-12-22T15:30:00Z" } ``` ### 6.4 Failure Handling When replay produces different results: ```json { "success": false, "originalManifest": { ... }, "replayedManifest": { ... }, "differences": [ { "field": "result.confidence", "original": 0.82, "replayed": 0.79, "reason": "VEX document digest mismatch" } ], "signatureValid": true, "error": "Verdict replay produced different confidence score" } ``` --- ## 7. API Reference ### Get Verdict Manifest ```http GET /api/v1/authority/verdicts/{manifestId} Authorization: Bearer Response: 200 OK { "manifest": { ... }, "signature": { ... }, "rekorEntry": { ... } } ``` ### List Verdicts by Scope ```http GET /api/v1/authority/verdicts?assetDigest={digest}&vulnerabilityId={cve} Authorization: Bearer Response: 200 OK { "verdicts": [ ... ], "pageToken": "..." } ``` ### Replay Verdict ```http POST /api/v1/authority/verdicts/{manifestId}/replay Authorization: Bearer Response: 200 OK { "success": true, ... } ``` ### Download Signed Manifest ```http GET /api/v1/authority/verdicts/{manifestId}/download Authorization: Bearer Response: 200 OK Content-Type: application/vnd.stellaops.verdict+json Content-Disposition: attachment; filename="verdict-{manifestId}.json" ``` --- ## 8. JSON Schema ### verdict-manifest.schema.json ```json { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://stella-ops.org/schemas/verdict-manifest/1.0.0", "type": "object", "required": [ "manifestId", "tenant", "assetDigest", "vulnerabilityId", "inputs", "result", "policyHash", "latticeVersion", "evaluatedAt", "manifestDigest" ], "properties": { "manifestId": { "type": "string", "pattern": "^verd:[a-z0-9-]+:[a-f0-9]+:[A-Z0-9-]+:[0-9]+$" }, "tenant": { "type": "string", "minLength": 1 }, "assetDigest": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" }, "vulnerabilityId": { "type": "string", "minLength": 1 }, "inputs": { "$ref": "#/$defs/VerdictInputs" }, "result": { "$ref": "#/$defs/VerdictResult" }, "policyHash": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" }, "latticeVersion": { "type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" }, "evaluatedAt": { "type": "string", "format": "date-time" }, "manifestDigest": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" } }, "$defs": { "VerdictInputs": { "type": "object", "required": ["sbomDigests", "vulnFeedSnapshotIds", "vexDocumentDigests", "clockCutoff"], "properties": { "sbomDigests": { "type": "array", "items": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" } }, "vulnFeedSnapshotIds": { "type": "array", "items": { "type": "string" } }, "vexDocumentDigests": { "type": "array", "items": { "type": "string", "pattern": "^sha256:[a-f0-9]{64}$" } }, "reachabilityGraphIds": { "type": "array", "items": { "type": "string" } }, "clockCutoff": { "type": "string", "format": "date-time" } } }, "VerdictResult": { "type": "object", "required": ["status", "confidence", "explanations"], "properties": { "status": { "type": "string", "enum": ["affected", "not_affected", "fixed", "under_investigation"] }, "confidence": { "type": "number", "minimum": 0, "maximum": 1 }, "explanations": { "type": "array", "items": { "$ref": "#/$defs/VerdictExplanation" } }, "evidenceRefs": { "type": "array", "items": { "type": "string" } } } }, "VerdictExplanation": { "type": "object", "required": ["sourceId", "reason", "claimScore"], "properties": { "sourceId": { "type": "string" }, "reason": { "type": "string" }, "provenanceScore": { "type": "number", "minimum": 0, "maximum": 1 }, "coverageScore": { "type": "number", "minimum": 0, "maximum": 1 }, "replayabilityScore": { "type": "number", "minimum": 0, "maximum": 1 }, "strengthMultiplier": { "type": "number", "minimum": 0, "maximum": 1 }, "freshnessMultiplier": { "type": "number", "minimum": 0, "maximum": 1 }, "claimScore": { "type": "number", "minimum": 0, "maximum": 1 } } } } } ``` --- ## Related Documentation - [Trust Lattice Specification](../excititor/trust-lattice.md) - [Authority Architecture](./architecture.md) - [DSSE Signing](../../dev/dsse-signing.md) - [API Reference](../../09_API_CLI_REFERENCE.md)