13 KiB
13 KiB
Evidence Packet Schema
Overview
Evidence packets are cryptographically signed, immutable records of deployment decisions and outcomes. They provide audit-grade proof of who did what, when, and why.
Evidence Packet Types
| Type | Description | Generated When |
|---|---|---|
release_decision |
Promotion decision evidence | Promotion approved/rejected |
deployment |
Deployment execution evidence | Deployment completes |
rollback |
Rollback evidence | Rollback completes |
ab_promotion |
A/B release promotion evidence | A/B promotion completes |
Schema Definition
Evidence Packet Structure
interface EvidencePacket {
// Identification
id: UUID;
version: "1.0";
type: EvidencePacketType;
// Metadata
generatedAt: DateTime;
generatorVersion: string;
tenantId: UUID;
// Content
content: EvidenceContent;
// Integrity
contentHash: string; // SHA-256 of canonical JSON content
signature: string; // Base64-encoded signature
signatureAlgorithm: string; // "RS256", "ES256"
signerKeyRef: string; // Reference to signing key
}
type EvidencePacketType =
| "release_decision"
| "deployment"
| "rollback"
| "ab_promotion";
Evidence Content
interface EvidenceContent {
// What was released
release: ReleaseEvidence;
// Where it was released
environment: EnvironmentEvidence;
// Who requested and approved
actors: ActorEvidence;
// Why it was allowed
decision: DecisionEvidence;
// How it was executed (deployment only)
execution?: ExecutionEvidence;
// Previous state (for rollback)
previous?: PreviousStateEvidence;
}
Release Evidence
interface ReleaseEvidence {
id: UUID;
name: string;
displayName: string;
createdAt: DateTime;
createdBy: ActorRef;
components: Array<{
id: UUID;
name: string;
digest: string;
semver: string;
tag: string;
role: "primary" | "sidecar" | "init" | "migration";
}>;
sourceRef?: {
scmIntegrationId?: UUID;
repository?: string;
commitSha?: string;
branch?: string;
ciIntegrationId?: UUID;
buildId?: string;
pipelineUrl?: string;
};
}
Environment Evidence
interface EnvironmentEvidence {
id: UUID;
name: string;
displayName: string;
orderIndex: number;
targets: Array<{
id: UUID;
name: string;
type: string;
healthStatus: string;
}>;
configuration: {
requiredApprovals: number;
requireSeparationOfDuties: boolean;
promotionPolicy?: string;
deploymentTimeout: number;
};
}
Actor Evidence
interface ActorEvidence {
requester: ActorRef;
requestReason: string;
requestedAt: DateTime;
approvers: Array<{
actor: ActorRef;
action: "approved" | "rejected";
comment?: string;
timestamp: DateTime;
roles: string[];
}>;
deployer?: {
agent: AgentRef;
triggeredBy: ActorRef;
startedAt: DateTime;
};
}
interface ActorRef {
id: UUID;
type: "user" | "system" | "agent";
name: string;
email?: string;
}
interface AgentRef {
id: UUID;
name: string;
version: string;
}
Decision Evidence
interface DecisionEvidence {
promotionId: UUID;
decision: "allow" | "block";
decidedAt: DateTime;
gateResults: Array<{
gateName: string;
gateType: string;
passed: boolean;
blocking: boolean;
message: string;
evaluatedAt: DateTime;
details: object;
}>;
freezeWindowCheck: {
checked: boolean;
windowActive: boolean;
windowId?: UUID;
exemption?: {
grantedBy: UUID;
reason: string;
};
};
separationOfDuties: {
required: boolean;
satisfied: boolean;
requesterIds: UUID[];
approverIds: UUID[];
};
}
Execution Evidence
interface ExecutionEvidence {
deploymentJobId: UUID;
strategy: string;
startedAt: DateTime;
completedAt: DateTime;
status: "succeeded" | "failed" | "rolled_back";
tasks: Array<{
targetId: UUID;
targetName: string;
agentId: UUID;
status: string;
startedAt: DateTime;
completedAt: DateTime;
digest: string;
stickerWritten: boolean;
error?: string;
}>;
artifacts: Array<{
name: string;
type: string;
sha256: string;
storageRef: string;
}>;
metrics: {
totalTasks: number;
succeededTasks: number;
failedTasks: number;
totalDurationSeconds: number;
};
}
Previous State Evidence
interface PreviousStateEvidence {
releaseId: UUID;
releaseName: string;
deployedAt: DateTime;
deployedBy: ActorRef;
components: Array<{
name: string;
digest: string;
}>;
}
Example Evidence Packet
{
"id": "evid-12345-uuid",
"version": "1.0",
"type": "deployment",
"generatedAt": "2026-01-10T14:35:00Z",
"generatorVersion": "stella-evidence-generator@1.5.0",
"tenantId": "tenant-uuid",
"content": {
"release": {
"id": "rel-uuid",
"name": "myapp-v2.3.1",
"displayName": "MyApp v2.3.1",
"createdAt": "2026-01-10T10:00:00Z",
"createdBy": {
"id": "user-uuid",
"type": "user",
"name": "John Doe",
"email": "john@example.com"
},
"components": [
{
"id": "comp-api-uuid",
"name": "api",
"digest": "sha256:abc123def456...",
"semver": "2.3.1",
"tag": "v2.3.1",
"role": "primary"
},
{
"id": "comp-worker-uuid",
"name": "worker",
"digest": "sha256:789xyz...",
"semver": "2.3.1",
"tag": "v2.3.1",
"role": "primary"
}
],
"sourceRef": {
"repository": "github.com/myorg/myapp",
"commitSha": "abc123",
"branch": "main",
"buildId": "build-456"
}
},
"environment": {
"id": "env-prod-uuid",
"name": "production",
"displayName": "Production",
"orderIndex": 2,
"targets": [
{
"id": "target-1-uuid",
"name": "prod-web-01",
"type": "compose_host",
"healthStatus": "healthy"
},
{
"id": "target-2-uuid",
"name": "prod-web-02",
"type": "compose_host",
"healthStatus": "healthy"
}
],
"configuration": {
"requiredApprovals": 2,
"requireSeparationOfDuties": true,
"deploymentTimeout": 600
}
},
"actors": {
"requester": {
"id": "user-john-uuid",
"type": "user",
"name": "John Doe",
"email": "john@example.com"
},
"requestReason": "Release v2.3.1 with performance improvements",
"requestedAt": "2026-01-10T12:00:00Z",
"approvers": [
{
"actor": {
"id": "user-jane-uuid",
"type": "user",
"name": "Jane Smith",
"email": "jane@example.com"
},
"action": "approved",
"comment": "LGTM, tests passed",
"timestamp": "2026-01-10T13:00:00Z",
"roles": ["release_manager"]
},
{
"actor": {
"id": "user-bob-uuid",
"type": "user",
"name": "Bob Johnson",
"email": "bob@example.com"
},
"action": "approved",
"comment": "Approved for production",
"timestamp": "2026-01-10T13:30:00Z",
"roles": ["approver"]
}
],
"deployer": {
"agent": {
"id": "agent-prod-uuid",
"name": "prod-agent-01",
"version": "1.5.0"
},
"triggeredBy": {
"id": "system",
"type": "system",
"name": "Stella Orchestrator"
},
"startedAt": "2026-01-10T14:00:00Z"
}
},
"decision": {
"promotionId": "promo-uuid",
"decision": "allow",
"decidedAt": "2026-01-10T13:55:00Z",
"gateResults": [
{
"gateName": "security-gate",
"gateType": "security",
"passed": true,
"blocking": true,
"message": "No critical or high vulnerabilities",
"evaluatedAt": "2026-01-10T13:50:00Z",
"details": {
"critical": 0,
"high": 0,
"medium": 5,
"low": 12
}
},
{
"gateName": "approval-gate",
"gateType": "approval",
"passed": true,
"blocking": true,
"message": "2/2 required approvals received",
"evaluatedAt": "2026-01-10T13:55:00Z",
"details": {
"required": 2,
"received": 2
}
}
],
"freezeWindowCheck": {
"checked": true,
"windowActive": false
},
"separationOfDuties": {
"required": true,
"satisfied": true,
"requesterIds": ["user-john-uuid"],
"approverIds": ["user-jane-uuid", "user-bob-uuid"]
}
},
"execution": {
"deploymentJobId": "job-uuid",
"strategy": "rolling",
"startedAt": "2026-01-10T14:00:00Z",
"completedAt": "2026-01-10T14:35:00Z",
"status": "succeeded",
"tasks": [
{
"targetId": "target-1-uuid",
"targetName": "prod-web-01",
"agentId": "agent-prod-uuid",
"status": "succeeded",
"startedAt": "2026-01-10T14:00:00Z",
"completedAt": "2026-01-10T14:15:00Z",
"digest": "sha256:abc123def456...",
"stickerWritten": true
},
{
"targetId": "target-2-uuid",
"targetName": "prod-web-02",
"agentId": "agent-prod-uuid",
"status": "succeeded",
"startedAt": "2026-01-10T14:20:00Z",
"completedAt": "2026-01-10T14:35:00Z",
"digest": "sha256:abc123def456...",
"stickerWritten": true
}
],
"artifacts": [
{
"name": "compose.stella.lock.yml",
"type": "compose-lock",
"sha256": "checksum...",
"storageRef": "s3://artifacts/job-uuid/compose.stella.lock.yml"
}
],
"metrics": {
"totalTasks": 2,
"succeededTasks": 2,
"failedTasks": 0,
"totalDurationSeconds": 2100
}
}
},
"contentHash": "sha256:content-hash...",
"signature": "base64-signature...",
"signatureAlgorithm": "RS256",
"signerKeyRef": "stella/signing/prod-key-2026"
}
Signature Verification
async function verifyEvidencePacket(packet: EvidencePacket): Promise<VerificationResult> {
// 1. Verify content hash
const canonicalContent = canonicalize(packet.content);
const computedHash = sha256(canonicalContent);
if (computedHash !== packet.contentHash) {
return { valid: false, error: "Content hash mismatch" };
}
// 2. Get signing key
const publicKey = await getPublicKey(packet.signerKeyRef);
// 3. Verify signature
const signatureValid = await verify(
packet.signature,
packet.contentHash,
publicKey,
packet.signatureAlgorithm
);
if (!signatureValid) {
return { valid: false, error: "Invalid signature" };
}
return { valid: true };
}
Storage
Evidence packets are stored in an append-only table:
CREATE TABLE release.evidence_packets (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id),
promotion_id UUID NOT NULL REFERENCES release.promotions(id),
type TEXT NOT NULL,
version TEXT NOT NULL DEFAULT '1.0',
content JSONB NOT NULL,
content_hash TEXT NOT NULL,
signature TEXT NOT NULL,
signature_algorithm TEXT NOT NULL,
signer_key_ref TEXT NOT NULL,
generated_at TIMESTAMPTZ NOT NULL,
generator_version TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
-- Note: No updated_at - packets are immutable
);
-- Prevent modifications
REVOKE UPDATE, DELETE ON release.evidence_packets FROM app_role;
Export Formats
Evidence packets can be exported in multiple formats:
| Format | Use Case |
|---|---|
| JSON | API consumption, archival |
| SignedJSON | DSSE-signed JSON for verification workflows |
| Markdown | Human-readable documentation |
| HTML | Styled web reports |
| Human-readable compliance reports | |
| CSV | Spreadsheet analysis |
| SLSA | SLSA provenance format |
| EvidenceCard | Single-file evidence card with SBOM excerpt, DSSE envelope, and Rekor receipt (v1.1) |
| EvidenceCardCompact | Compact evidence card without full SBOM (v1.1) |
Evidence Card Format (v1.1)
The evidence-card format packages related artifacts into a single JSON file for offline verification:
- SBOM Excerpt: Relevant component information from the full SBOM
- DSSE Envelope: Dead Simple Signing Envelope containing the signed payload
- Rekor Receipt: Optional Sigstore Rekor transparency log receipt for audit trail
Content type: application/vnd.stellaops.evidence-card+json
See Evidence Decision API for schema details.