# 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 ```typescript 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 ```typescript 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 ```typescript 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 ```typescript 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 ```typescript 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 ```typescript 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 ```typescript 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 ```typescript interface PreviousStateEvidence { releaseId: UUID; releaseName: string; deployedAt: DateTime; deployedBy: ActorRef; components: Array<{ name: string; digest: string; }>; } ``` ## Example Evidence Packet ```json { "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 ```typescript async function verifyEvidencePacket(packet: EvidencePacket): Promise { // 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: ```sql 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 | | PDF | 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](../../../api/evidence-decision-api.openapi.yaml) for schema details. ## References - [Security Overview](../security/overview.md) - [Deployment Artifacts](../deployment/artifacts.md) - [Audit Trail](../security/audit-trail.md)