Files
git.stella-ops.org/devops/compose/postgres-init/10-attestor-verdict-ledger.sql

84 lines
4.0 KiB
SQL

-- -----------------------------------------------------------------------------
-- 001_verdict_ledger_initial.sql
-- Sprint: SPRINT_20260118_015_Attestor_verdict_ledger_foundation
-- Task: VL-001 - Create VerdictLedger database schema
-- Description: Append-only verdict ledger with SHA-256 hash chaining
-- -----------------------------------------------------------------------------
-- Create verdict decision enum
DO $$ BEGIN
CREATE TYPE verdict_decision AS ENUM ('unknown', 'approve', 'reject', 'pending');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
-- Create the verdict_ledger table
CREATE TABLE IF NOT EXISTS verdict_ledger (
ledger_id UUID PRIMARY KEY,
bom_ref VARCHAR(2048) NOT NULL,
cyclonedx_serial VARCHAR(512),
rekor_uuid VARCHAR(128),
decision verdict_decision NOT NULL DEFAULT 'unknown',
reason TEXT NOT NULL,
policy_bundle_id VARCHAR(256) NOT NULL,
policy_bundle_hash VARCHAR(64) NOT NULL,
verifier_image_digest VARCHAR(256) NOT NULL,
signer_keyid VARCHAR(512) NOT NULL,
prev_hash VARCHAR(64), -- SHA-256 hex, null for genesis entry
verdict_hash VARCHAR(64) NOT NULL UNIQUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
tenant_id UUID NOT NULL,
-- Constraints
CONSTRAINT verdict_hash_format CHECK (verdict_hash ~ '^[a-f0-9]{64}$'),
CONSTRAINT prev_hash_format CHECK (prev_hash IS NULL OR prev_hash ~ '^[a-f0-9]{64}$'),
CONSTRAINT policy_hash_format CHECK (policy_bundle_hash ~ '^[a-f0-9]{64}$')
);
-- Indexes for common query patterns
CREATE INDEX IF NOT EXISTS idx_verdict_ledger_bom_ref
ON verdict_ledger (bom_ref);
CREATE INDEX IF NOT EXISTS idx_verdict_ledger_rekor_uuid
ON verdict_ledger (rekor_uuid)
WHERE rekor_uuid IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_verdict_ledger_created_at
ON verdict_ledger (created_at DESC);
CREATE INDEX IF NOT EXISTS idx_verdict_ledger_tenant_created
ON verdict_ledger (tenant_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_verdict_ledger_prev_hash
ON verdict_ledger (prev_hash)
WHERE prev_hash IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_verdict_ledger_decision
ON verdict_ledger (decision);
-- Composite index for chain walking
CREATE INDEX IF NOT EXISTS idx_verdict_ledger_chain
ON verdict_ledger (tenant_id, verdict_hash);
-- Comments
COMMENT ON TABLE verdict_ledger IS 'Append-only ledger of release verdicts with SHA-256 hash chaining for cryptographic audit trail';
COMMENT ON COLUMN verdict_ledger.ledger_id IS 'Unique identifier for this ledger entry';
COMMENT ON COLUMN verdict_ledger.bom_ref IS 'Package URL (purl) or container digest reference';
COMMENT ON COLUMN verdict_ledger.cyclonedx_serial IS 'CycloneDX serialNumber URN linking to SBOM';
COMMENT ON COLUMN verdict_ledger.rekor_uuid IS 'Transparency log entry UUID for external verification';
COMMENT ON COLUMN verdict_ledger.decision IS 'The release decision: unknown, approve, reject, or pending';
COMMENT ON COLUMN verdict_ledger.reason IS 'Human-readable explanation for the decision';
COMMENT ON COLUMN verdict_ledger.policy_bundle_id IS 'Reference to the policy configuration used';
COMMENT ON COLUMN verdict_ledger.policy_bundle_hash IS 'SHA-256 hash of the policy bundle for reproducibility';
COMMENT ON COLUMN verdict_ledger.verifier_image_digest IS 'Container digest of the verifier service';
COMMENT ON COLUMN verdict_ledger.signer_keyid IS 'Key ID that signed this verdict';
COMMENT ON COLUMN verdict_ledger.prev_hash IS 'SHA-256 hash of previous entry (null for genesis)';
COMMENT ON COLUMN verdict_ledger.verdict_hash IS 'SHA-256 hash of this entry canonical JSON form';
COMMENT ON COLUMN verdict_ledger.created_at IS 'Timestamp when this verdict was recorded';
COMMENT ON COLUMN verdict_ledger.tenant_id IS 'Tenant identifier for multi-tenancy';
-- Revoke UPDATE and DELETE for application role (append-only enforcement)
-- This should be run after creating the appropriate role
-- REVOKE UPDATE, DELETE ON verdict_ledger FROM stellaops_app;
-- GRANT INSERT, SELECT ON verdict_ledger TO stellaops_app;