release orchestrator pivot, architecture and planning
This commit is contained in:
549
docs/modules/release-orchestrator/appendices/evidence-schema.md
Normal file
549
docs/modules/release-orchestrator/appendices/evidence-schema.md
Normal file
@@ -0,0 +1,549 @@
|
||||
# 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<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:
|
||||
|
||||
```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 |
|
||||
| PDF | Human-readable compliance reports |
|
||||
| CSV | Spreadsheet analysis |
|
||||
| SLSA | SLSA provenance format |
|
||||
|
||||
## References
|
||||
|
||||
- [Security Overview](../security/overview.md)
|
||||
- [Deployment Artifacts](../deployment/artifacts.md)
|
||||
- [Audit Trail](../security/audit-trail.md)
|
||||
Reference in New Issue
Block a user