-- Policy exceptions schema bootstrap for compose environments.
-- Ensures exception endpoints can start against a clean database.

CREATE EXTENSION IF NOT EXISTS pgcrypto;

CREATE SCHEMA IF NOT EXISTS policy;

CREATE TABLE IF NOT EXISTS policy.recheck_policies (
    policy_id TEXT PRIMARY KEY,
    tenant_id TEXT NOT NULL,
    name TEXT NOT NULL,
    conditions JSONB NOT NULL,
    default_action TEXT NOT NULL,
    is_active BOOLEAN NOT NULL DEFAULT TRUE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_recheck_policies_tenant
    ON policy.recheck_policies (tenant_id, is_active);

CREATE TABLE IF NOT EXISTS policy.exceptions (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id TEXT NOT NULL,
    name TEXT NOT NULL,
    description TEXT,
    rule_pattern TEXT,
    resource_pattern TEXT,
    artifact_pattern TEXT,
    project_id TEXT,
    reason TEXT NOT NULL,
    status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('proposed', 'approved', 'active', 'expired', 'revoked')),
    expires_at TIMESTAMPTZ,
    approved_by TEXT,
    approved_at TIMESTAMPTZ,
    revoked_by TEXT,
    revoked_at TIMESTAMPTZ,
    metadata JSONB NOT NULL DEFAULT '{}',
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    created_by TEXT,
    exception_id TEXT NOT NULL UNIQUE,
    version INTEGER NOT NULL DEFAULT 1,
    type TEXT NOT NULL DEFAULT 'policy' CHECK (type IN ('vulnerability', 'policy', 'unknown', 'component')),
    artifact_digest TEXT,
    purl_pattern TEXT,
    vulnerability_id TEXT,
    policy_rule_id TEXT,
    environments TEXT[] NOT NULL DEFAULT '{}',
    owner_id TEXT,
    requester_id TEXT,
    approver_ids TEXT[] NOT NULL DEFAULT '{}',
    reason_code TEXT DEFAULT 'other' CHECK (reason_code IN (
        'false_positive',
        'accepted_risk',
        'compensating_control',
        'test_only',
        'vendor_not_affected',
        'scheduled_fix',
        'deprecation_in_progress',
        'runtime_mitigation',
        'network_isolation',
        'other'
    )),
    rationale TEXT,
    evidence_refs JSONB NOT NULL DEFAULT '[]',
    compensating_controls JSONB NOT NULL DEFAULT '[]',
    ticket_ref TEXT,
    recheck_policy_id TEXT REFERENCES policy.recheck_policies(policy_id),
    last_recheck_result JSONB,
    last_recheck_at TIMESTAMPTZ,
    UNIQUE (tenant_id, name)
);

CREATE INDEX IF NOT EXISTS idx_exceptions_tenant ON policy.exceptions(tenant_id);
CREATE INDEX IF NOT EXISTS idx_exceptions_status ON policy.exceptions(tenant_id, status);
CREATE INDEX IF NOT EXISTS idx_exceptions_expires ON policy.exceptions(expires_at) WHERE status = 'active';
CREATE INDEX IF NOT EXISTS idx_exceptions_project ON policy.exceptions(tenant_id, project_id);
CREATE INDEX IF NOT EXISTS idx_exceptions_vuln_id ON policy.exceptions(vulnerability_id) WHERE vulnerability_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_exceptions_purl ON policy.exceptions(purl_pattern) WHERE purl_pattern IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_exceptions_artifact ON policy.exceptions(artifact_digest) WHERE artifact_digest IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_exceptions_policy_rule ON policy.exceptions(policy_rule_id) WHERE policy_rule_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_exceptions_owner ON policy.exceptions(owner_id) WHERE owner_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_exceptions_recheck_policy ON policy.exceptions(tenant_id, recheck_policy_id) WHERE recheck_policy_id IS NOT NULL;

CREATE TABLE IF NOT EXISTS policy.exception_events (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    exception_id TEXT NOT NULL REFERENCES policy.exceptions(exception_id) ON DELETE CASCADE,
    sequence_number INTEGER NOT NULL,
    event_type TEXT NOT NULL CHECK (event_type IN (
        'created',
        'updated',
        'approved',
        'activated',
        'extended',
        'revoked',
        'expired',
        'evidence_attached',
        'compensating_control_added',
        'rejected'
    )),
    actor_id TEXT NOT NULL,
    occurred_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    previous_status TEXT,
    new_status TEXT NOT NULL,
    new_version INTEGER NOT NULL,
    description TEXT,
    details JSONB NOT NULL DEFAULT '{}',
    client_info TEXT,
    UNIQUE (exception_id, sequence_number)
);

CREATE INDEX IF NOT EXISTS idx_exception_events_exception ON policy.exception_events(exception_id);
CREATE INDEX IF NOT EXISTS idx_exception_events_time ON policy.exception_events USING BRIN (occurred_at);
