-- ----------------------------------------------------------------------------- -- 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;