-- Findings Ledger: Consolidated init from migrations 001-009
-- Auto-generated for docker-compose postgres-init
-- Creates all tables required by stellaops-findings-ledger-web

-- ============================================================================
-- 001_initial.sql - Bootstrap schema (LEDGER-29-001)
-- ============================================================================

BEGIN;

CREATE TYPE ledger_event_type AS ENUM (
    'finding.created',
    'finding.status_changed',
    'finding.severity_changed',
    'finding.tag_updated',
    'finding.comment_added',
    'finding.assignment_changed',
    'finding.accepted_risk',
    'finding.remediation_plan_added',
    'finding.attachment_added',
    'finding.closed'
);

CREATE TYPE ledger_action_type AS ENUM (
    'assign',
    'comment',
    'attach_evidence',
    'link_ticket',
    'remediation_plan',
    'status_change',
    'accept_risk',
    'reopen',
    'close'
);

CREATE TABLE ledger_events (
    tenant_id         TEXT        NOT NULL,
    chain_id          UUID        NOT NULL,
    sequence_no       BIGINT      NOT NULL,
    event_id          UUID        NOT NULL,
    event_type        ledger_event_type NOT NULL,
    policy_version    TEXT        NOT NULL,
    finding_id        TEXT        NOT NULL,
    artifact_id       TEXT        NOT NULL,
    source_run_id     UUID,
    actor_id          TEXT        NOT NULL,
    actor_type        TEXT        NOT NULL,
    occurred_at       TIMESTAMPTZ NOT NULL,
    recorded_at       TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    event_body        JSONB       NOT NULL,
    event_hash        CHAR(64)    NOT NULL,
    previous_hash     CHAR(64)    NOT NULL,
    merkle_leaf_hash  CHAR(64)    NOT NULL,
    CONSTRAINT pk_ledger_events PRIMARY KEY (tenant_id, chain_id, sequence_no),
    CONSTRAINT uq_ledger_events_event_id UNIQUE (tenant_id, event_id),
    CONSTRAINT uq_ledger_events_chain_hash UNIQUE (tenant_id, chain_id, event_hash),
    CONSTRAINT ck_ledger_events_event_hash_hex CHECK (event_hash ~ '^[0-9a-f]{64}$'),
    CONSTRAINT ck_ledger_events_previous_hash_hex CHECK (previous_hash ~ '^[0-9a-f]{64}$'),
    CONSTRAINT ck_ledger_events_leaf_hash_hex CHECK (merkle_leaf_hash ~ '^[0-9a-f]{64}$'),
    CONSTRAINT ck_ledger_events_actor_type CHECK (actor_type IN ('system', 'operator', 'integration'))
) PARTITION BY LIST (tenant_id);

CREATE TABLE ledger_events_default PARTITION OF ledger_events DEFAULT;

CREATE INDEX ix_ledger_events_finding ON ledger_events (tenant_id, finding_id, policy_version);
CREATE INDEX ix_ledger_events_type ON ledger_events (tenant_id, event_type, recorded_at DESC);
CREATE INDEX ix_ledger_events_recorded_at ON ledger_events (tenant_id, recorded_at DESC);

CREATE TABLE ledger_merkle_roots (
    tenant_id        TEXT        NOT NULL,
    anchor_id        UUID        NOT NULL,
    window_start     TIMESTAMPTZ NOT NULL,
    window_end       TIMESTAMPTZ NOT NULL,
    sequence_start   BIGINT      NOT NULL,
    sequence_end     BIGINT      NOT NULL,
    root_hash        CHAR(64)    NOT NULL,
    leaf_count       INTEGER     NOT NULL,
    anchored_at      TIMESTAMPTZ NOT NULL,
    anchor_reference TEXT,
    CONSTRAINT [REDACTED]PRIMARY KEY (tenant_id, anchor_id),
    CONSTRAINT uq_ledger_merkle_root_hash UNIQUE (tenant_id, root_hash),
    CONSTRAINT ck_ledger_merkle_root_hash_hex CHECK (root_hash ~ '^[0-9a-f]{64}$')
) PARTITION BY LIST (tenant_id);

CREATE TABLE ledger_merkle_roots_default PARTITION OF ledger_merkle_roots DEFAULT;

CREATE INDEX ix_merkle_sequences ON ledger_merkle_roots (tenant_id, sequence_end DESC);

CREATE TABLE findings_projection (
    tenant_id        TEXT        NOT NULL,
    finding_id       TEXT        NOT NULL,
    policy_version   TEXT        NOT NULL,
    status           TEXT        NOT NULL,
    severity         NUMERIC(6,3),
    labels           JSONB       NOT NULL DEFAULT '{}'::JSONB,
    current_event_id UUID        NOT NULL,
    explain_ref      TEXT,
    updated_at       TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    cycle_hash       CHAR(64)    NOT NULL,
    CONSTRAINT [REDACTED]PRIMARY KEY (tenant_id, finding_id, policy_version),
    CONSTRAINT ck_findings_projection_cycle_hash_hex CHECK (cycle_hash ~ '^[0-9a-f]{64}$')
) PARTITION BY LIST (tenant_id);

CREATE TABLE findings_projection_default PARTITION OF findings_projection DEFAULT;

CREATE INDEX ix_projection_status ON findings_projection (tenant_id, status, severity DESC);
CREATE INDEX ix_projection_labels_gin ON findings_projection USING GIN (labels JSONB_PATH_OPS);

CREATE TABLE finding_history (
    tenant_id      TEXT        NOT NULL,
    finding_id     TEXT        NOT NULL,
    policy_version TEXT        NOT NULL,
    event_id       UUID        NOT NULL,
    status         TEXT        NOT NULL,
    severity       NUMERIC(6,3),
    actor_id       TEXT        NOT NULL,
    comment        TEXT,
    occurred_at    TIMESTAMPTZ NOT NULL,
    CONSTRAINT pk_finding_history PRIMARY KEY (tenant_id, finding_id, event_id)
) PARTITION BY LIST (tenant_id);

CREATE TABLE finding_history_default PARTITION OF finding_history DEFAULT;

CREATE INDEX ix_finding_history_timeline ON finding_history (tenant_id, finding_id, occurred_at DESC);

CREATE TABLE triage_actions (
    tenant_id   TEXT              NOT NULL,
    action_id   UUID              NOT NULL,
    event_id    UUID              NOT NULL,
    finding_id  TEXT              NOT NULL,
    action_type ledger_action_type NOT NULL,
    payload     JSONB             NOT NULL DEFAULT '{}'::JSONB,
    created_at  TIMESTAMPTZ       NOT NULL DEFAULT NOW(),
    created_by  TEXT              NOT NULL,
    CONSTRAINT pk_triage_actions PRIMARY KEY (tenant_id, action_id)
) PARTITION BY LIST (tenant_id);

CREATE TABLE triage_actions_default PARTITION OF triage_actions DEFAULT;

CREATE INDEX ix_triage_actions_event ON triage_actions (tenant_id, event_id);
CREATE INDEX ix_triage_actions_created_at ON triage_actions (tenant_id, created_at DESC);

COMMIT;

-- ============================================================================
-- 002_projection_offsets.sql - Projection worker checkpoints (LEDGER-29-003)
-- ============================================================================

BEGIN;

CREATE TABLE IF NOT EXISTS ledger_projection_offsets (
    worker_id        TEXT        NOT NULL PRIMARY KEY,
    last_recorded_at TIMESTAMPTZ NOT NULL,
    last_event_id    UUID        NOT NULL,
    updated_at       TIMESTAMPTZ NOT NULL
);

INSERT INTO ledger_projection_offsets (worker_id, last_recorded_at, last_event_id, updated_at)
VALUES (
    'default',
    '1970-01-01T00:00:00Z',
    '00000000-0000-0000-0000-000000000000',
    NOW())
ON CONFLICT (worker_id) DO NOTHING;

COMMIT;

-- ============================================================================
-- 002_add_evidence_bundle_ref.sql - Evidence bundle references (LEDGER-OBS-53-001)
-- ============================================================================

ALTER TABLE ledger_events
    ADD COLUMN evidence_bundle_ref text NULL;

CREATE INDEX IF NOT EXISTS ix_ledger_events_finding_evidence_ref
    ON ledger_events (tenant_id, finding_id, recorded_at DESC)
    WHERE evidence_bundle_ref IS NOT NULL;

-- ============================================================================
-- 003_policy_rationale.sql - Policy rationale column (LEDGER-29-004)
-- ============================================================================

BEGIN;

ALTER TABLE findings_projection
    ADD COLUMN IF NOT EXISTS policy_rationale JSONB NOT NULL DEFAULT '[]'::JSONB;

ALTER TABLE findings_projection
    ALTER COLUMN policy_rationale SET DEFAULT '[]'::JSONB;

UPDATE findings_projection
   SET policy_rationale = '[]'::JSONB
 WHERE policy_rationale IS NULL;

COMMIT;

-- ============================================================================
-- 004_ledger_attestations.sql - Attestation verification exports (LEDGER-OBS-54-001)
-- ============================================================================

BEGIN;

CREATE TABLE IF NOT EXISTS ledger_attestations (
    tenant_id           text        NOT NULL,
    attestation_id      uuid        NOT NULL,
    artifact_id         text        NOT NULL,
    finding_id          text        NULL,
    verification_status text        NOT NULL,
    verification_time   timestamptz NOT NULL,
    dsse_digest         text        NOT NULL,
    rekor_entry_id      text        NULL,
    evidence_bundle_ref text        NULL,
    ledger_event_id     uuid        NOT NULL,
    recorded_at         timestamptz NOT NULL,
    merkle_leaf_hash    text        NOT NULL,
    root_hash           text        NOT NULL,
    cycle_hash          text        NOT NULL,
    projection_version  text        NOT NULL
);

ALTER TABLE ledger_attestations
    ADD CONSTRAINT [REDACTED]PRIMARY KEY (tenant_id, attestation_id);

CREATE INDEX IF NOT EXISTS ix_ledger_attestations_recorded
    ON ledger_attestations (tenant_id, recorded_at, attestation_id);

CREATE INDEX IF NOT EXISTS ix_ledger_attestations_artifact
    ON ledger_attestations (tenant_id, artifact_id, recorded_at DESC);

CREATE INDEX IF NOT EXISTS ix_ledger_attestations_finding
    ON ledger_attestations (tenant_id, finding_id, recorded_at DESC)
    WHERE finding_id IS NOT NULL;

CREATE INDEX IF NOT EXISTS ix_ledger_attestations_status
    ON ledger_attestations (tenant_id, verification_status, recorded_at DESC);

COMMIT;

-- ============================================================================
-- 004_risk_fields.sql - Risk scoring fields (LEDGER-RISK-66-001/002)
-- ============================================================================

BEGIN;

ALTER TABLE findings_projection
    ADD COLUMN IF NOT EXISTS risk_score NUMERIC(6,3),
    ADD COLUMN IF NOT EXISTS risk_severity TEXT,
    ADD COLUMN IF NOT EXISTS risk_profile_version TEXT,
    ADD COLUMN IF NOT EXISTS risk_explanation_id UUID,
    ADD COLUMN IF NOT EXISTS risk_event_sequence BIGINT;

CREATE INDEX IF NOT EXISTS ix_projection_risk ON findings_projection (tenant_id, risk_severity, risk_score DESC);

COMMIT;

-- ============================================================================
-- 005_risk_fields.sql - Risk scoring fields idempotent re-add (LEDGER-RISK-66-001)
-- ============================================================================

BEGIN;

ALTER TABLE findings_projection
    ADD COLUMN IF NOT EXISTS risk_score numeric(6,2) NULL,
    ADD COLUMN IF NOT EXISTS risk_severity text NULL,
    ADD COLUMN IF NOT EXISTS risk_profile_version text NULL,
    ADD COLUMN IF NOT EXISTS risk_explanation_id text NULL,
    ADD COLUMN IF NOT EXISTS risk_event_sequence bigint NULL;

CREATE INDEX IF NOT EXISTS ix_findings_projection_risk
    ON findings_projection (tenant_id, risk_severity, risk_score DESC);

COMMIT;

-- ============================================================================
-- 006_orchestrator_airgap.sql - Export and import provenance (LEDGER-34-101, LEDGER-AIRGAP-56-001)
-- ============================================================================

BEGIN;

CREATE TABLE IF NOT EXISTS orchestrator_exports
(
    tenant_id     TEXT      NOT NULL,
    run_id        UUID      NOT NULL,
    job_type      TEXT      NOT NULL,
    artifact_hash TEXT      NOT NULL,
    policy_hash   TEXT      NOT NULL,
    started_at    TIMESTAMPTZ NOT NULL,
    completed_at  TIMESTAMPTZ,
    status        TEXT      NOT NULL,
    manifest_path TEXT,
    logs_path     TEXT,
    merkle_root   CHAR(64)  NOT NULL,
    created_at    TIMESTAMPTZ NOT NULL,
    PRIMARY KEY (tenant_id, run_id)
);

CREATE UNIQUE INDEX IF NOT EXISTS ix_orchestrator_exports_artifact_run
    ON orchestrator_exports (tenant_id, artifact_hash, run_id);

CREATE INDEX IF NOT EXISTS ix_orchestrator_exports_artifact
    ON orchestrator_exports (tenant_id, artifact_hash);

CREATE TABLE IF NOT EXISTS airgap_imports
(
    tenant_id        TEXT      NOT NULL,
    bundle_id        TEXT      NOT NULL,
    mirror_generation TEXT,
    merkle_root      TEXT      NOT NULL,
    time_anchor      TIMESTAMPTZ NOT NULL,
    publisher        TEXT,
    hash_algorithm   TEXT,
    contents         JSONB,
    imported_at      TIMESTAMPTZ NOT NULL,
    import_operator  TEXT,
    ledger_event_id  UUID,
    PRIMARY KEY (tenant_id, bundle_id, time_anchor)
);

CREATE INDEX IF NOT EXISTS ix_airgap_imports_bundle
    ON airgap_imports (tenant_id, bundle_id);

CREATE INDEX IF NOT EXISTS ix_airgap_imports_event
    ON airgap_imports (tenant_id, ledger_event_id);

COMMIT;

-- ============================================================================
-- 007_enable_rls.sql - Row-Level Security (LEDGER-TEN-48-001-DEV)
-- ============================================================================

BEGIN;

CREATE SCHEMA IF NOT EXISTS findings_ledger_app;

CREATE OR REPLACE FUNCTION findings_ledger_app.require_current_tenant()
RETURNS TEXT
LANGUAGE plpgsql
STABLE
AS $$
DECLARE
    tenant_text TEXT;
BEGIN
    tenant_text := current_setting('app.current_tenant', true);
    IF tenant_text IS NULL OR length(trim(tenant_text)) = 0 THEN
        RAISE EXCEPTION 'app.current_tenant is not set for the current session'
            USING ERRCODE = 'P0001';
    END IF;
    RETURN tenant_text;
END;
$$;

COMMENT ON FUNCTION findings_ledger_app.require_current_tenant() IS
    'Returns the current tenant ID from session variable, raises exception if not set';

ALTER TABLE ledger_events ENABLE ROW LEVEL SECURITY;
ALTER TABLE ledger_events FORCE ROW LEVEL SECURITY;

DROP POLICY IF EXISTS ledger_events_tenant_isolation ON ledger_events;
CREATE POLICY ledger_events_tenant_isolation
    ON ledger_events
    FOR ALL
    USING (tenant_id = findings_ledger_app.require_current_tenant())
    WITH CHECK (tenant_id = findings_ledger_app.require_current_tenant());

ALTER TABLE ledger_merkle_roots ENABLE ROW LEVEL SECURITY;
ALTER TABLE ledger_merkle_roots FORCE ROW LEVEL SECURITY;

DROP POLICY IF EXISTS ledger_merkle_roots_tenant_isolation ON ledger_merkle_roots;
CREATE POLICY ledger_merkle_roots_tenant_isolation
    ON ledger_merkle_roots
    FOR ALL
    USING (tenant_id = findings_ledger_app.require_current_tenant())
    WITH CHECK (tenant_id = findings_ledger_app.require_current_tenant());

ALTER TABLE findings_projection ENABLE ROW LEVEL SECURITY;
ALTER TABLE findings_projection FORCE ROW LEVEL SECURITY;

DROP POLICY IF EXISTS findings_projection_tenant_isolation ON findings_projection;
CREATE POLICY findings_projection_tenant_isolation
    ON findings_projection
    FOR ALL
    USING (tenant_id = findings_ledger_app.require_current_tenant())
    WITH CHECK (tenant_id = findings_ledger_app.require_current_tenant());

ALTER TABLE finding_history ENABLE ROW LEVEL SECURITY;
ALTER TABLE finding_history FORCE ROW LEVEL SECURITY;

DROP POLICY IF EXISTS finding_history_tenant_isolation ON finding_history;
CREATE POLICY finding_history_tenant_isolation
    ON finding_history
    FOR ALL
    USING (tenant_id = findings_ledger_app.require_current_tenant())
    WITH CHECK (tenant_id = findings_ledger_app.require_current_tenant());

ALTER TABLE triage_actions ENABLE ROW LEVEL SECURITY;
ALTER TABLE triage_actions FORCE ROW LEVEL SECURITY;

DROP POLICY IF EXISTS triage_actions_tenant_isolation ON triage_actions;
CREATE POLICY triage_actions_tenant_isolation
    ON triage_actions
    FOR ALL
    USING (tenant_id = findings_ledger_app.require_current_tenant())
    WITH CHECK (tenant_id = findings_ledger_app.require_current_tenant());

ALTER TABLE ledger_attestations ENABLE ROW LEVEL SECURITY;
ALTER TABLE ledger_attestations FORCE ROW LEVEL SECURITY;

DROP POLICY IF EXISTS ledger_attestations_tenant_isolation ON ledger_attestations;
CREATE POLICY ledger_attestations_tenant_isolation
    ON ledger_attestations
    FOR ALL
    USING (tenant_id = findings_ledger_app.require_current_tenant())
    WITH CHECK (tenant_id = findings_ledger_app.require_current_tenant());

ALTER TABLE orchestrator_exports ENABLE ROW LEVEL SECURITY;
ALTER TABLE orchestrator_exports FORCE ROW LEVEL SECURITY;

DROP POLICY IF EXISTS orchestrator_exports_tenant_isolation ON orchestrator_exports;
CREATE POLICY orchestrator_exports_tenant_isolation
    ON orchestrator_exports
    FOR ALL
    USING (tenant_id = findings_ledger_app.require_current_tenant())
    WITH CHECK (tenant_id = findings_ledger_app.require_current_tenant());

ALTER TABLE airgap_imports ENABLE ROW LEVEL SECURITY;
ALTER TABLE airgap_imports FORCE ROW LEVEL SECURITY;

DROP POLICY IF EXISTS airgap_imports_tenant_isolation ON airgap_imports;
CREATE POLICY airgap_imports_tenant_isolation
    ON airgap_imports
    FOR ALL
    USING (tenant_id = findings_ledger_app.require_current_tenant())
    WITH CHECK (tenant_id = findings_ledger_app.require_current_tenant());

DO $$
BEGIN
    IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'findings_ledger_admin') THEN
        CREATE ROLE findings_ledger_admin NOLOGIN BYPASSRLS;
    END IF;
END;
$$;

COMMENT ON ROLE findings_ledger_admin IS
    'Admin role that bypasses RLS for migrations and cross-tenant operations';

COMMIT;

-- ============================================================================
-- 008_attestation_pointers.sql - Finding-to-attestation pointers (LEDGER-ATTEST-73-001)
-- ============================================================================

BEGIN;

CREATE TABLE IF NOT EXISTS ledger_attestation_pointers (
    tenant_id           text        NOT NULL,
    pointer_id          uuid        NOT NULL,
    finding_id          text        NOT NULL,
    attestation_type    text        NOT NULL,
    relationship        text        NOT NULL,
    attestation_ref     jsonb       NOT NULL,
    verification_result jsonb       NULL,
    created_at          timestamptz NOT NULL,
    created_by          text        NOT NULL,
    metadata            jsonb       NULL,
    ledger_event_id     uuid        NULL
);

ALTER TABLE ledger_attestation_pointers
    ADD CONSTRAINT [REDACTED]PRIMARY KEY (tenant_id, pointer_id);

CREATE INDEX IF NOT EXISTS ix_ledger_attestation_pointers_finding
    ON ledger_attestation_pointers (tenant_id, finding_id, created_at DESC);

CREATE INDEX IF NOT EXISTS ix_ledger_attestation_pointers_digest
    ON ledger_attestation_pointers (tenant_id, (attestation_ref->>'digest'));

CREATE INDEX IF NOT EXISTS ix_ledger_attestation_pointers_type
    ON ledger_attestation_pointers (tenant_id, attestation_type, created_at DESC);

CREATE INDEX IF NOT EXISTS ix_ledger_attestation_pointers_verified
    ON ledger_attestation_pointers (tenant_id, ((verification_result->>'verified')::boolean))
    WHERE verification_result IS NOT NULL;

CREATE INDEX IF NOT EXISTS ix_ledger_attestation_pointers_signer
    ON ledger_attestation_pointers (tenant_id, (attestation_ref->'signer_info'->>'subject'))
    WHERE attestation_ref->'signer_info' IS NOT NULL;

CREATE INDEX IF NOT EXISTS ix_ledger_attestation_pointers_predicate
    ON ledger_attestation_pointers (tenant_id, (attestation_ref->>'predicate_type'))
    WHERE attestation_ref->>'predicate_type' IS NOT NULL;

ALTER TABLE ledger_attestation_pointers ENABLE ROW LEVEL SECURITY;
ALTER TABLE ledger_attestation_pointers FORCE ROW LEVEL SECURITY;

DROP POLICY IF EXISTS ledger_attestation_pointers_tenant_isolation ON ledger_attestation_pointers;
CREATE POLICY ledger_attestation_pointers_tenant_isolation
    ON ledger_attestation_pointers
    FOR ALL
    USING (tenant_id = findings_ledger_app.require_current_tenant())
    WITH CHECK (tenant_id = findings_ledger_app.require_current_tenant());

COMMENT ON TABLE ledger_attestation_pointers IS
    'Links findings to verification reports and attestation envelopes for explainability (LEDGER-ATTEST-73-001)';

COMMIT;

-- ============================================================================
-- 009_snapshots.sql - Ledger snapshots for time-travel
-- ============================================================================

CREATE TABLE IF NOT EXISTS ledger_snapshots (
    tenant_id TEXT NOT NULL,
    snapshot_id UUID NOT NULL,
    label TEXT,
    description TEXT,
    status TEXT NOT NULL DEFAULT 'Creating',
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ,
    expires_at TIMESTAMPTZ,
    sequence_number BIGINT NOT NULL,
    snapshot_timestamp TIMESTAMPTZ NOT NULL,
    findings_count BIGINT NOT NULL DEFAULT 0,
    vex_statements_count BIGINT NOT NULL DEFAULT 0,
    advisories_count BIGINT NOT NULL DEFAULT 0,
    sboms_count BIGINT NOT NULL DEFAULT 0,
    events_count BIGINT NOT NULL DEFAULT 0,
    size_bytes BIGINT NOT NULL DEFAULT 0,
    merkle_root TEXT,
    dsse_digest TEXT,
    metadata JSONB,
    include_entity_types JSONB,
    sign_requested BOOLEAN NOT NULL DEFAULT FALSE,
    PRIMARY KEY (tenant_id, snapshot_id)
);

CREATE INDEX IF NOT EXISTS idx_ledger_snapshots_status
    ON ledger_snapshots (tenant_id, status, created_at DESC);

CREATE INDEX IF NOT EXISTS idx_ledger_snapshots_expires
    ON ledger_snapshots (expires_at)
    WHERE expires_at IS NOT NULL AND status = 'Available';

CREATE INDEX IF NOT EXISTS idx_ledger_snapshots_sequence
    ON ledger_snapshots (tenant_id, sequence_number);

CREATE INDEX IF NOT EXISTS idx_ledger_snapshots_label
    ON ledger_snapshots (tenant_id, label)
    WHERE label IS NOT NULL;

ALTER TABLE ledger_snapshots ENABLE ROW LEVEL SECURITY;

DO $$
BEGIN
    IF NOT EXISTS (
        SELECT 1 FROM pg_policies
        WHERE tablename = 'ledger_snapshots'
        AND policyname = 'ledger_snapshots_tenant_isolation'
    ) THEN
        CREATE POLICY ledger_snapshots_tenant_isolation ON ledger_snapshots
            USING (tenant_id = current_setting('app.tenant_id', true))
            WITH CHECK (tenant_id = current_setting('app.tenant_id', true));
    END IF;
END $$;

COMMENT ON TABLE ledger_snapshots IS 'Point-in-time snapshots of ledger state for time-travel queries';
