Replay Proof Schema
Ownership: Replay Guild, Scanner Guild, Attestor Guild Audience: Service owners, platform engineers, auditors, compliance teams Related: Platform Architecture, Replay Architecture, Facet Sealing (archived), DSSE Specification
This document defines the schema for Replay Proofs - compact, cryptographically verifiable artifacts that attest to deterministic policy evaluation outcomes.
1. Overview
A Replay Proof is a DSSE-signed artifact that proves a policy evaluation produced a specific verdict given a specific set of inputs. Replay proofs enable:
- Audit trails: Compact proof that a verdict was computed correctly
- Determinism verification: Re-running with same inputs produces identical output
- Time-travel debugging: Understand why a past decision was made
- Compliance evidence: Cryptographic proof for regulatory requirements
2. Replay Bundle Structure
A complete replay bundle consists of three artifacts stored in CAS:
cas://replay/<run-id>/
manifest.json # DSSE-signed manifest (this document's focus)
inputbundle.tar.zst # Compressed input artifacts
outputbundle.tar.zst # Compressed output artifacts
2.1 Directory Layout
<run-id>/
manifest.json
inputbundle.tar.zst
feeds/
nvd/<date>.json
osv/<date>.json
ghsa/<date>.json
policy/
bundle.tar
version.json
sboms/
<sbom-id>.spdx.json
<sbom-id>.cdx.json
vex/
<vex-id>.openvex.json
config/
lattice.json
feature-flags.json
seeds/
random-seeds.json
clock-offsets.json
outputbundle.tar.zst
verdicts/
<verdict-id>.json
findings/
<finding-id>.json
merkle/
verdict-tree.json
finding-tree.json
logs/
replay.log
trace.json
3. Core Schema Definitions
3.1 ReplayProof
The primary proof artifact - a compact summary suitable for verification:
public sealed record ReplayProof
{
// Identity
public required Guid ProofId { get; init; }
public required Guid RunId { get; init; }
public required string Subject { get; init; } // Image digest or SBOM ID
// Input digest
public required KnowledgeSnapshotDigest InputDigest { get; init; }
// Output digest
public required VerdictDigest OutputDigest { get; init; }
// Execution metadata
public required ExecutionMetadata Execution { get; init; }
// CAS references
public required BundleReferences Bundles { get; init; }
// Signature
public required DateTimeOffset SignedAt { get; init; }
public required string SignedBy { get; init; }
}
3.2 KnowledgeSnapshotDigest
Cryptographic digest of all inputs:
public sealed record KnowledgeSnapshotDigest
{
// Component digests
public required string SbomsDigest { get; init; } // SHA-256 of sorted SBOM hashes
public required string VexDigest { get; init; } // SHA-256 of sorted VEX hashes
public required string FeedsDigest { get; init; } // SHA-256 of feed version manifest
public required string PolicyDigest { get; init; } // SHA-256 of policy bundle
public required string LatticeDigest { get; init; } // SHA-256 of lattice config
public required string SeedsDigest { get; init; } // SHA-256 of random seeds
// Combined root
public required string RootDigest { get; init; } // SHA-256 of all component digests
// Counts for quick comparison
public required int SbomCount { get; init; }
public required int VexCount { get; init; }
public required int FeedCount { get; init; }
}
3.3 VerdictDigest
Cryptographic digest of all outputs:
public sealed record VerdictDigest
{
public required string VerdictMerkleRoot { get; init; } // Merkle root of verdicts
public required string FindingMerkleRoot { get; init; } // Merkle root of findings
public required int VerdictCount { get; init; }
public required int FindingCount { get; init; }
public required VerdictSummary Summary { get; init; }
}
public sealed record VerdictSummary
{
public required int Critical { get; init; }
public required int High { get; init; }
public required int Medium { get; init; }
public required int Low { get; init; }
public required int Informational { get; init; }
public required int Suppressed { get; init; }
public required int Total { get; init; }
}
3.4 ExecutionMetadata
Execution environment and timing:
public sealed record ExecutionMetadata
{
// Timing
public required DateTimeOffset StartedAt { get; init; }
public required DateTimeOffset CompletedAt { get; init; }
public required long DurationMs { get; init; }
// Engine version
public required EngineVersion Engine { get; init; }
// Environment
public required string HostId { get; init; }
public required string RuntimeVersion { get; init; } // e.g., ".NET 10.0.0"
public required string Platform { get; init; } // e.g., "linux-x64"
// Determinism markers
public required bool DeterministicMode { get; init; }
public required string ClockMode { get; init; } // "frozen", "simulated", "real"
public required string RandomMode { get; init; } // "seeded", "recorded", "real"
}
public sealed record EngineVersion
{
public required string Name { get; init; } // e.g., "PolicyEngine"
public required string Version { get; init; } // e.g., "2.1.0"
public required string SourceDigest { get; init; } // SHA-256 of engine source/binary
}
3.5 BundleReferences
CAS URIs to full bundles:
public sealed record BundleReferences
{
public required string ManifestUri { get; init; } // cas://replay/<run-id>/manifest.json
public required string InputBundleUri { get; init; } // cas://replay/<run-id>/inputbundle.tar.zst
public required string OutputBundleUri { get; init; } // cas://replay/<run-id>/outputbundle.tar.zst
public required string ManifestDigest { get; init; } // SHA-256 of manifest.json
public required string InputBundleDigest { get; init; } // SHA-256 of inputbundle.tar.zst
public required string OutputBundleDigest { get; init; } // SHA-256 of outputbundle.tar.zst
public required long InputBundleSize { get; init; }
public required long OutputBundleSize { get; init; }
}
4. DSSE Envelope
Replay proofs are wrapped in DSSE envelopes for cryptographic binding:
{
"payloadType": "application/vnd.stellaops.replay-proof.v1+json",
"payload": "<base64url-encoded canonical JSON>",
"signatures": [
{
"keyid": "sha256:abc123...",
"sig": "<base64url-encoded signature>"
}
]
}
4.1 Payload Type URI
- v1:
application/vnd.stellaops.replay-proof.v1+json - in-toto compatible:
https://stellaops.io/ReplayProof/v1
4.2 Canonical JSON Encoding
Payloads MUST be encoded using RFC 8785 canonical JSON:
- Keys sorted lexicographically using Unicode code points
- No whitespace between structural characters
- No trailing commas
- Numbers without unnecessary decimal points or exponents
- Strings with minimal escaping (only required characters)
5. Full Manifest Schema
The manifest.json file contains the complete proof plus additional metadata:
{
"_type": "https://stellaops.io/ReplayManifest/v1",
"proofId": "550e8400-e29b-41d4-a716-446655440000",
"runId": "660e8400-e29b-41d4-a716-446655440001",
"subject": "sha256:abc123def456...",
"tenant": "acme-corp",
"inputDigest": {
"sbomsDigest": "sha256:111...",
"vexDigest": "sha256:222...",
"feedsDigest": "sha256:333...",
"policyDigest": "sha256:444...",
"latticeDigest": "sha256:555...",
"seedsDigest": "sha256:666...",
"rootDigest": "sha256:aaa...",
"sbomCount": 1,
"vexCount": 5,
"feedCount": 3
},
"outputDigest": {
"verdictMerkleRoot": "sha256:bbb...",
"findingMerkleRoot": "sha256:ccc...",
"verdictCount": 42,
"findingCount": 156,
"summary": {
"critical": 2,
"high": 8,
"medium": 25,
"low": 12,
"informational": 3,
"suppressed": 106,
"total": 156
}
},
"execution": {
"startedAt": "2026-01-05T10:00:00.000Z",
"completedAt": "2026-01-05T10:00:05.123Z",
"durationMs": 5123,
"engine": {
"name": "PolicyEngine",
"version": "2.1.0",
"sourceDigest": "sha256:engine123..."
},
"hostId": "scanner-worker-01",
"runtimeVersion": ".NET 10.0.0",
"platform": "linux-x64",
"deterministicMode": true,
"clockMode": "frozen",
"randomMode": "seeded"
},
"bundles": {
"manifestUri": "cas://replay/660e8400.../manifest.json",
"inputBundleUri": "cas://replay/660e8400.../inputbundle.tar.zst",
"outputBundleUri": "cas://replay/660e8400.../outputbundle.tar.zst",
"manifestDigest": "sha256:manifest...",
"inputBundleDigest": "sha256:input...",
"outputBundleDigest": "sha256:output...",
"inputBundleSize": 10485760,
"outputBundleSize": 2097152
},
"signedAt": "2026-01-05T10:00:06.000Z",
"signedBy": "scanner-worker-01"
}
6. Verification Protocol
6.1 Quick Verification (Proof Only)
Verify the DSSE signature and check digest consistency:
public async Task<VerificationResult> VerifyProofAsync(
ReplayProof proof,
DsseEnvelope envelope,
CancellationToken ct)
{
// 1. Verify DSSE signature
var sigValid = await _dsseVerifier.VerifyAsync(envelope, ct);
if (!sigValid)
return VerificationResult.Failed("DSSE signature invalid");
// 2. Verify input digest consistency
var inputRoot = ComputeInputRoot(
proof.InputDigest.SbomsDigest,
proof.InputDigest.VexDigest,
proof.InputDigest.FeedsDigest,
proof.InputDigest.PolicyDigest,
proof.InputDigest.LatticeDigest,
proof.InputDigest.SeedsDigest);
if (inputRoot != proof.InputDigest.RootDigest)
return VerificationResult.Failed("Input root digest mismatch");
return VerificationResult.Passed();
}
6.2 Full Verification (With Replay)
Download bundles and re-execute to verify determinism:
public async Task<VerificationResult> VerifyWithReplayAsync(
ReplayProof proof,
CancellationToken ct)
{
// 1. Quick verification first
var quickResult = await VerifyProofAsync(proof, envelope, ct);
if (!quickResult.Passed)
return quickResult;
// 2. Download bundles from CAS
var inputBundle = await _cas.DownloadAsync(proof.Bundles.InputBundleUri, ct);
var outputBundle = await _cas.DownloadAsync(proof.Bundles.OutputBundleUri, ct);
// 3. Verify bundle digests
if (ComputeDigest(inputBundle) != proof.Bundles.InputBundleDigest)
return VerificationResult.Failed("Input bundle digest mismatch");
if (ComputeDigest(outputBundle) != proof.Bundles.OutputBundleDigest)
return VerificationResult.Failed("Output bundle digest mismatch");
// 4. Extract and verify individual input digests
var inputs = await ExtractInputsAsync(inputBundle, ct);
var computedInputDigest = ComputeKnowledgeDigest(inputs);
if (computedInputDigest.RootDigest != proof.InputDigest.RootDigest)
return VerificationResult.Failed("Computed input digest mismatch");
// 5. Re-execute policy evaluation
var replayResult = await _replayEngine.ExecuteAsync(inputs, ct);
// 6. Compare output digests
var computedOutputDigest = ComputeVerdictDigest(replayResult);
if (computedOutputDigest.VerdictMerkleRoot != proof.OutputDigest.VerdictMerkleRoot)
return VerificationResult.Failed("Verdict Merkle root mismatch - non-deterministic!");
if (computedOutputDigest.FindingMerkleRoot != proof.OutputDigest.FindingMerkleRoot)
return VerificationResult.Failed("Finding Merkle root mismatch - non-deterministic!");
return VerificationResult.Passed();
}
7. Digest Computation
7.1 Input Root Digest
public string ComputeInputRoot(
string sbomsDigest,
string vexDigest,
string feedsDigest,
string policyDigest,
string latticeDigest,
string seedsDigest)
{
// Concatenate in fixed order with separators
var combined = string.Join("|",
sbomsDigest,
vexDigest,
feedsDigest,
policyDigest,
latticeDigest,
seedsDigest);
return ComputeSha256(combined);
}
7.2 SBOM Collection Digest
public string ComputeSbomsDigest(IEnumerable<SbomRef> sboms)
{
// Sort by ID for determinism
var sorted = sboms.OrderBy(s => s.SbomId, StringComparer.Ordinal);
// Concatenate hashes
var combined = string.Join("|", sorted.Select(s => s.ContentHash));
return ComputeSha256(combined);
}
7.3 Verdict Merkle Root
public string ComputeVerdictMerkleRoot(IEnumerable<Verdict> verdicts)
{
// Sort by verdict ID for determinism
var sorted = verdicts.OrderBy(v => v.VerdictId, StringComparer.Ordinal);
// Compute leaf hashes
var leaves = sorted.Select(v => ComputeVerdictLeafHash(v)).ToArray();
// Build Merkle tree
return MerkleTreeBuilder.ComputeRoot(leaves);
}
private string ComputeVerdictLeafHash(Verdict verdict)
{
var canonical = CanonicalJsonSerializer.Serialize(verdict);
return ComputeSha256(canonical);
}
8. Database Schema
-- Replay proof storage
CREATE TABLE replay_proofs (
proof_id UUID PRIMARY KEY,
run_id UUID NOT NULL,
tenant TEXT NOT NULL,
subject TEXT NOT NULL,
input_root_digest TEXT NOT NULL,
output_verdict_root TEXT NOT NULL,
output_finding_root TEXT NOT NULL,
execution_json JSONB NOT NULL,
bundles_json JSONB NOT NULL,
dsse_envelope JSONB NOT NULL,
signed_at TIMESTAMPTZ NOT NULL,
signed_by TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uq_replay_run UNIQUE (run_id)
);
CREATE INDEX ix_replay_proofs_tenant ON replay_proofs (tenant, created_at DESC);
CREATE INDEX ix_replay_proofs_subject ON replay_proofs (subject);
CREATE INDEX ix_replay_proofs_input ON replay_proofs (input_root_digest);
-- Replay verification log
CREATE TABLE replay_verifications (
verification_id UUID PRIMARY KEY,
proof_id UUID NOT NULL REFERENCES replay_proofs(proof_id),
tenant TEXT NOT NULL,
verification_type TEXT NOT NULL, -- 'quick', 'full'
passed BOOLEAN NOT NULL,
failure_reason TEXT,
duration_ms BIGINT NOT NULL,
verified_at TIMESTAMPTZ NOT NULL,
verified_by TEXT NOT NULL,
CONSTRAINT fk_proof FOREIGN KEY (proof_id) REFERENCES replay_proofs(proof_id)
);
CREATE INDEX ix_replay_verifications_proof ON replay_verifications (proof_id);
9. CLI Integration
9.1 stella prove
Replay supplied local bundle evidence and compare its CGS hash with the manifest’s expected hash. This command uses one structurally checked manifest and requires the requested complete SHA-256 or SHA-512 image identity to match the manifest. It does not evaluate policy or verify a signature. Declared paths must stay inside the selected bundle root; child filesystem links are refused. PCD-430 withdrew timestamp/latest lookup. PCD-431 withdrew explicit snapshot selection and bundle-store reads: Timeline event/HLC queries, Replay advisory/feed snapshots and AuditPack bundle production do not provide this verdict snapshot/store contract. The supported command requires a local bundle.
# Use the actual image digest recorded in the supplied bundle manifest.
stella prove --image sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa --bundle /path/to/bundle --output compact
# Emit the unsigned evidence-hash comparison as JSON or a table.
stella prove --image sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa --bundle /path/to/bundle --output json
stella prove --image sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa --bundle /path/to/bundle --output full
Options:
-i, --image <digest>- Complete SHA-256 or SHA-512 image digest; required and bound to the manifest-b, --bundle <path>- Existing local replay bundle directory; required-o, --output <format>-compact,jsonorfull; defaultcompact-v, --verbose- Enable diagnostics
Missing bundle selection, retired --snapshot/-s or --at/-a options, invalid image identity and an image mismatch fail without a proof or provider request. The command leaves bundle files unchanged. A genuine replay matching the expected CGS hash returns 0; changed evidence returns 5. These results establish an evidence-hash comparison, with no policy or trusted-signature claim. See the local prove reference for input and output limits.
Exit Codes:
| Code | Name | Description |
|---|---|---|
| 0 | Success | Replayed CGS hash matches the expected hash |
| 1 | InvalidInput | Invalid options, image digest or image/manifest mismatch |
| 2 | ArgumentParsing | Missing required arguments or unknown options |
| 4 | ReplayFailed | Verdict evidence replay failed |
| 5 | VerdictMismatch | Replayed CGS hash differs from expected |
| 7 | FileNotFound | Local directory or manifest is absent |
| 8 | InvalidBundle | Bundle manifest is invalid or incomplete |
| 99 | SystemError | Unexpected error |
| 130 | Cancelled | Operation cancelled |
Historical values 2 (SnapshotNotFound), 3 (BundleNotFound) and 6 (ServiceUnavailable) remain defined with no active online branch; the withdrawn modes no longer produce them.
9.2 stella verify
# Verify a replay proof (quick - signature only)
stella verify --proof proof.json
# Verify with full replay
stella verify --proof proof.json --replay
# Verify from CAS URI
stella verify --bundle cas://replay/660e8400.../manifest.json
9.3 stella replay
# Export proof for audit
stella replay export --run-id 660e8400-... --output proof.json
# List proofs for an image
stella replay list --subject sha256:abc123...
# Diff two replay results
stella replay diff --run-id-a 660e8400... --run-id-b 770e8400...
10. API Endpoints
Implementation status (verified 2026-09-26 against commit
942a18727c): not implemented. No host insrc/maps the proof routes below.replay-webserves/v1/replay/*and/v1/pit/*(see architecture.md section 4). The Policy decision-replay draft was never compiled or mapped; PCD-253 froze it undersrc/__Obsoleted/Policy/StellaOps.Policy.Api/on 2026-10-03 (source1a151be799610035edeb55f289ead89549de9919; recheck withpython tools/scripts/validate/check-projectless-source.py). Treat this section as a design target.
# Get proof by run ID
GET /api/v1/replay/{runId}/proof
Response: ReplayProof (JSON)
# Verify proof
POST /api/v1/replay/{runId}/verify
Request: { "type": "quick" | "full" }
Response: VerificationResult
# List proofs for subject
GET /api/v1/replay/proofs?subject={digest}&tenant={tenant}
Response: ReplayProofSummary[]
# Download bundle
GET /api/v1/replay/{runId}/bundles/{type}
Response: Binary stream (tar.zst)
# Compare two runs
GET /api/v1/replay/diff?runIdA={id}&runIdB={id}
Response: ReplayDiffResult
11. Error Codes
| Code | Description |
|---|---|
REPLAY_001 | Proof not found |
REPLAY_002 | DSSE signature verification failed |
REPLAY_003 | Input digest mismatch |
REPLAY_004 | Output digest mismatch (non-deterministic) |
REPLAY_005 | Bundle not found in CAS |
REPLAY_006 | Bundle digest mismatch |
REPLAY_007 | Engine version mismatch |
REPLAY_008 | Replay execution failed |
REPLAY_009 | Insufficient permissions |
REPLAY_010 | Bundle format invalid |
12. Migration from v0
If upgrading from pre-v1 replay bundles:
- Schema migration: Run
migrate-replay-schema.sql - Re-sign existing proofs: Use
stella replay migrate --signto add DSSE envelopes - Verify migration: Run
stella replay verify --allto check integrity - Update consumers: Point to new
/api/v1/replayendpoints
13. Security Considerations
- Key Management: Signing keys managed by Authority service with rotation support
- Tenant Isolation: Proofs scoped to tenants; cross-tenant access prohibited
- Integrity: All digests use SHA-256; Merkle proofs enable partial verification
- Immutability: Proofs cannot be modified once signed
- Audit: All verification attempts logged with correlation IDs
- Air-gap: Proofs and bundles can be exported for offline verification
14. References
- DSSE Specification
- RFC 8785 - JSON Canonicalization
- in-toto Attestation Framework
- SLSA Provenance
- Platform Architecture
- Facet Sealing Architecture
Last updated: 2026-01-05
