from __future__ import annotations

import json
import os
import re
import shutil
import subprocess
import time
import uuid
from collections.abc import Iterator
from contextlib import contextmanager
from pathlib import Path

import pytest

from ops import verify_gcp_leoclean_runtime_permissions as runtime_verifier
from tests.docker_volume_lifecycle import (
    POSTGRES_TMPFS_SPEC,
    acquire_docker_volume_set_guard,
    inspect_postgres_tmpfs_container,
    release_docker_volume_set_guard,
    remove_container_and_volumes,
)

REPO_ROOT = Path(__file__).resolve().parents[1]
MIGRATION = REPO_ROOT / "scripts" / "kb_apply_prereqs.sql"
RUNTIME_ROLE_SQL = REPO_ROOT / "ops" / "gcp_leoclean_runtime_role.sql"
POSTGRES_IMAGE = os.environ.get("LEOCLEAN_RUNTIME_POSTGRES_IMAGE", "postgres:16-alpine")
DATABASE = "teleo_clone"
LEGACY_APPROVED_ID = "aaaaaaaa-0000-4000-8000-000000000001"
LEGACY_APPLIED_ID = "aaaaaaaa-0000-4000-8000-000000000002"
FRESH_PROPOSAL_ID = "aaaaaaaa-0000-4000-8000-000000000003"
TIMEZONE_PROPOSAL_ID = "aaaaaaaa-0000-4000-8000-000000000004"
REVIEWER_ID = "11111111-1111-1111-1111-111111111111"
STALE_REVIEWER_ID = "22222222-2222-2222-2222-222222222222"
LEGACY_REVIEWER_HANDLE = "m3ta"
CANONICAL_REVIEWER_HANDLE = "m3taversal"

BOOTSTRAP_SQL = r"""
create role kb_gate_owner nologin inherit;
create role kb_review login inherit;
create role kb_apply login inherit;
create role broad_writer nologin;
create role rogue_owner nologin;
create role rogue_grantee nologin;

create schema kb_stage;

create table public.agents (
  id uuid primary key,
  handle text not null unique,
  kind text not null
);
create table public.strategies (
  id uuid primary key,
  agent_id uuid not null,
  active boolean not null default true
);
create table public.strategy_nodes (id uuid primary key);
create table public.claim_evidence (
  id uuid primary key,
  claim_id uuid not null,
  source_id uuid not null,
  role text not null
);
create table public.claim_edges (id uuid primary key);
create table public.claims (id uuid primary key);
create table public.sources (
  id uuid primary key,
  hash text not null
);
create table public.reasoning_tools (id uuid primary key);

insert into public.agents (id, handle, kind)
values ('11111111-1111-1111-1111-111111111111', 'm3ta', 'human');

create table kb_stage.kb_proposals (
  id uuid primary key,
  proposal_type text not null,
  status text not null,
  proposed_by_handle text,
  proposed_by_agent_id uuid,
  channel text not null default 'cli',
  source_ref text,
  rationale text not null default '',
  payload jsonb not null,
  reviewed_by_handle text,
  reviewed_by_agent_id uuid,
  reviewed_at timestamptz,
  review_note text,
  applied_by_handle text,
  applied_by_agent_id uuid,
  applied_at timestamptz,
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now()
);
create table kb_stage.kb_review_principals (
  db_role name primary key,
  reviewed_by_handle text not null,
  reviewed_by_agent_id uuid not null references public.agents(id),
  active boolean not null default true,
  created_at timestamptz not null default now()
);
create table kb_stage.kb_proposal_approvals (
  proposal_id uuid primary key references kb_stage.kb_proposals(id),
  proposal_type text not null,
  payload jsonb not null,
  reviewed_by_handle text not null,
  reviewed_by_agent_id uuid not null references public.agents(id),
  reviewed_by_db_role name not null,
  reviewed_at timestamptz not null,
  review_note text not null
);

insert into kb_stage.kb_proposals (
  id, proposal_type, status, payload, reviewed_by_handle, reviewed_by_agent_id,
  reviewed_at, review_note, applied_by_handle, applied_by_agent_id, applied_at, updated_at
) values
  (
    'aaaaaaaa-0000-4000-8000-000000000001', 'add_edge', 'approved',
    '{"apply_payload":{"from_claim":"aaaaaaaa-1000-4000-8000-000000000001"}}'::jsonb,
    'm3ta', '11111111-1111-1111-1111-111111111111', '2026-07-18T10:00:00Z',
    'Exact still-approved legacy row.', null, null, null, '2026-07-18T10:00:01Z'
  ),
  (
    'aaaaaaaa-0000-4000-8000-000000000002', 'add_edge', 'applied',
    '{"apply_payload":{"from_claim":"aaaaaaaa-1000-4000-8000-000000000002"}}'::jsonb,
    'm3ta', '11111111-1111-1111-1111-111111111111', '2026-07-18T09:00:00Z',
    'Applied before exact approved rows were retained.', 'kb-apply',
    '44444444-4444-4444-4444-444444444444', '2026-07-18T09:01:00Z',
    '2026-07-18T09:01:00Z'
  );

insert into kb_stage.kb_proposal_approvals (
  proposal_id, proposal_type, payload, reviewed_by_handle, reviewed_by_agent_id,
  reviewed_by_db_role, reviewed_at, review_note
) values
  (
    'aaaaaaaa-0000-4000-8000-000000000001', 'add_edge',
    '{"apply_payload":{"from_claim":"aaaaaaaa-1000-4000-8000-000000000001"}}'::jsonb,
    'm3ta', '11111111-1111-1111-1111-111111111111', 'kb_review',
    '2026-07-18T10:00:00Z', 'Exact still-approved legacy row.'
  ),
  (
    'aaaaaaaa-0000-4000-8000-000000000002', 'add_edge',
    '{"apply_payload":{"from_claim":"aaaaaaaa-1000-4000-8000-000000000002"}}'::jsonb,
    'm3ta', '11111111-1111-1111-1111-111111111111', 'kb_review',
    '2026-07-18T09:00:00Z', 'Applied before exact approved rows were retained.'
  );

create function kb_stage.approve_strict_proposal(uuid, text, text)
returns jsonb language sql security definer
as $function$ select '{}'::jsonb $function$;
create function kb_stage.approve_strict_proposal(uuid, text, jsonb, text, text)
returns jsonb language sql security definer
as $function$ select '{}'::jsonb $function$;
create function kb_stage.assert_approved_proposal(
  uuid, text, jsonb, text, uuid, timestamptz, text
) returns void language sql security definer
as $function$ select $function$;
create function kb_stage.finish_approved_proposal(
  uuid, text, jsonb, text, uuid, timestamptz, text, text
) returns jsonb language sql security definer
as $function$ select '{}'::jsonb $function$;

grant all privileges on all tables in schema public
  to kb_gate_owner, kb_apply, kb_review;
grant all privileges on all tables in schema kb_stage
  to public, kb_gate_owner, kb_apply, kb_review;
grant all privileges on all functions in schema kb_stage
  to public, kb_gate_owner, kb_apply, kb_review;
grant update (review_note) on kb_stage.kb_proposals to kb_apply;
grant select (review_note) on kb_stage.kb_proposals to public;
"""


def _psql(container: str, sql: str, *, check: bool = True) -> subprocess.CompletedProcess[str]:
    completed = subprocess.run(
        [
            "docker",
            "exec",
            "-i",
            container,
            "psql",
            "-X",
            "-v",
            "ON_ERROR_STOP=1",
            "-U",
            "postgres",
            "-d",
            DATABASE,
            "-At",
            "-F",
            "|",
        ],
        input=sql,
        text=True,
        capture_output=True,
        check=False,
    )
    if check and completed.returncode != 0:
        raise AssertionError(
            f"psql failed with exit {completed.returncode}\nstdout:\n{completed.stdout}\nstderr:\n{completed.stderr}"
        )
    return completed


def _apply_migration(container: str, *, check: bool = True) -> subprocess.CompletedProcess[str]:
    return _psql(container, MIGRATION.read_text(), check=check)


@contextmanager
def _postgres_clone() -> Iterator[str]:
    if shutil.which("docker") is None:
        pytest.skip("Docker is required for the disposable PostgreSQL migration canary")

    volume_guard = acquire_docker_volume_set_guard(
        context="kb_apply_prereqs PostgreSQL fixture",
    )
    container = f"kb-apply-prereqs-{uuid.uuid4().hex[:12]}"
    try:
        started = subprocess.run(
            [
                "docker",
                "run",
                "--pull=never",
                "-d",
                "--name",
                container,
                "--tmpfs",
                POSTGRES_TMPFS_SPEC,
                "-e",
                "POSTGRES_PASSWORD=postgres",
                "-e",
                f"POSTGRES_DB={DATABASE}",
                POSTGRES_IMAGE,
            ],
            text=True,
            capture_output=True,
            check=False,
        )
        if started.returncode != 0:
            pytest.fail(f"could not start disposable PostgreSQL: {started.stderr}")
        inspect_postgres_tmpfs_container(container)

        for _ in range(120):
            logs = subprocess.run(
                ["docker", "logs", container],
                text=True,
                capture_output=True,
                check=False,
            )
            query = _psql(container, "select 1;", check=False)
            ready_transitions = (logs.stdout + logs.stderr).count("database system is ready to accept connections")
            if ready_transitions >= 2 and query.returncode == 0:
                break
            time.sleep(0.25)
        else:
            pytest.fail("disposable PostgreSQL did not become ready")

        _psql(container, BOOTSTRAP_SQL)
        yield container
    finally:
        try:
            remove_container_and_volumes(container)
        finally:
            release_docker_volume_set_guard(volume_guard)


@pytest.fixture(scope="module")
def migrated_postgres() -> Iterator[str]:
    with _postgres_clone() as container:
        _apply_migration(container)
        yield container


def _lines(completed: subprocess.CompletedProcess[str]) -> set[str]:
    return {line for line in completed.stdout.splitlines() if line}


def _read_allowlist_rows(sql: str, anchor: str) -> set[tuple[str, str, str, int]]:
    manifest = sql.split(anchor, 1)[1].split(";", 1)[0]
    return {
        (schema, relation, column, int(ordinal))
        for schema, relation, column, ordinal in re.findall(
            r"\('([^']+)', '([^']+)', '([^']+)', ([0-9]+)\)", manifest
        )
    }


def test_scoped_runtime_acl_allowlists_match_the_executable_runtime_contract() -> None:
    migration_sql = MIGRATION.read_text(encoding="utf-8")
    runtime_sql = RUNTIME_ROLE_SQL.read_text(encoding="utf-8")
    runtime_rows = _read_allowlist_rows(
        runtime_sql,
        "insert into pg_temp.leoclean_runtime_read_column_allowlist (",
    )
    prerequisite_rows = _read_allowlist_rows(
        migration_sql,
        "insert into pg_temp.kb_apply_prereqs_leoclean_read_column_allowlist (",
    )
    executable_rows = {
        (schema, relation, column, ordinal)
        for schema, relation, columns in runtime_verifier.READ_COLUMN_ALLOWLIST
        for ordinal, column in enumerate(columns, start=1)
    }

    assert len(runtime_rows) == 92
    assert prerequisite_rows == runtime_rows == executable_rows

    stage_manifest = migration_sql.split(
        "insert into pg_temp.kb_apply_prereqs_leoclean_stage_owner_column_allowlist (",
        1,
    )[1].split(";", 1)[0]
    stage_rows = set(
        re.findall(
            r"\('([^']+)', '([^']+)', '([^']+)', '([^']+)'\)",
            stage_manifest,
        )
    )
    expected_stage_rows = {
        ("public", "agents", "id", "SELECT"),
        ("public", "agents", "handle", "SELECT"),
        *{
            ("kb_stage", "kb_proposals", column, "INSERT")
            for column in runtime_verifier.STAGE_OWNER_INSERT_COLUMNS
        },
    }
    assert stage_rows == expected_stage_rows
    assert "stage_leoclean_proposal" not in migration_sql


def test_legacy_only_reviewer_is_canonicalized_without_rewriting_history(
    migrated_postgres: str,
) -> None:
    identity_rows = _psql(
        migrated_postgres,
        f"""
select id::text || '|' || handle || '|' || kind
  from public.agents
 where handle in ('{LEGACY_REVIEWER_HANDLE}', '{CANONICAL_REVIEWER_HANDLE}')
 order by handle;
""",
    ).stdout.splitlines()
    assert identity_rows == [f"{REVIEWER_ID}|{CANONICAL_REVIEWER_HANDLE}|human"]

    historical_rows = _psql(
        migrated_postgres,
        f"""
select 'proposal|' || id::text || '|' || reviewed_by_handle || '|' || reviewed_by_agent_id::text
  from kb_stage.kb_proposals
 where id in ('{LEGACY_APPROVED_ID}'::uuid, '{LEGACY_APPLIED_ID}'::uuid)
 order by id;
select 'approval|' || proposal_id::text || '|' || reviewed_by_handle || '|' ||
       reviewed_by_agent_id::text
  from kb_stage.kb_proposal_approvals
 where proposal_id in ('{LEGACY_APPROVED_ID}'::uuid, '{LEGACY_APPLIED_ID}'::uuid)
 order by proposal_id;
""",
    ).stdout.splitlines()
    assert historical_rows == [
        f"proposal|{LEGACY_APPROVED_ID}|{LEGACY_REVIEWER_HANDLE}|{REVIEWER_ID}",
        f"proposal|{LEGACY_APPLIED_ID}|{LEGACY_REVIEWER_HANDLE}|{REVIEWER_ID}",
        f"approval|{LEGACY_APPROVED_ID}|{LEGACY_REVIEWER_HANDLE}|{REVIEWER_ID}",
        f"approval|{LEGACY_APPLIED_ID}|{LEGACY_REVIEWER_HANDLE}|{REVIEWER_ID}",
    ]


def test_already_current_reviewer_migration_is_idempotent() -> None:
    with _postgres_clone() as container:
        _psql(
            container,
            f"update public.agents set handle = '{CANONICAL_REVIEWER_HANDLE}' "
            f"where handle = '{LEGACY_REVIEWER_HANDLE}';",
        )
        _apply_migration(container)
        state_sql = f"""
select principal.reviewed_by_handle || '|' || principal.reviewed_by_agent_id::text || '|' ||
       principal.active::text || '|' || principal.created_at::text || '|' ||
       (select count(*) from public.agents where handle = '{LEGACY_REVIEWER_HANDLE}')::text
  from kb_stage.kb_review_principals principal
 where principal.db_role = 'kb_review';
"""
        first_state = _psql(container, state_sql).stdout.strip()
        _apply_migration(container)
        second_state = _psql(container, state_sql).stdout.strip()

    expected_state = f"{CANONICAL_REVIEWER_HANDLE}|{REVIEWER_ID}|true|1970-01-01 00:00:00+00|0"
    assert first_state == expected_state
    assert second_state == first_state


def test_stale_reviewer_principal_is_repaired_without_resetting_created_at() -> None:
    with _postgres_clone() as container:
        _psql(
            container,
            f"""
insert into public.agents (id, handle, kind)
values ('{STALE_REVIEWER_ID}', 'stale-reviewer', 'human');
insert into kb_stage.kb_review_principals
  (db_role, reviewed_by_handle, reviewed_by_agent_id, active, created_at)
values (
  'kb_review', 'stale-reviewer', '{STALE_REVIEWER_ID}', false,
  '2001-02-03T04:05:06Z'
);
""",
        )
        _apply_migration(container)
        principal_state = _psql(
            container,
            """
select reviewed_by_handle || '|' || reviewed_by_agent_id::text || '|' || active::text || '|' ||
       created_at::text
  from kb_stage.kb_review_principals
 where db_role = 'kb_review';
""",
        ).stdout.strip()

    expected_state = f"{CANONICAL_REVIEWER_HANDLE}|{REVIEWER_ID}|true|2001-02-03 04:05:06+00"
    assert principal_state == expected_state


def test_migration_refuses_distinct_legacy_and_canonical_reviewers() -> None:
    with _postgres_clone() as container:
        _psql(
            container,
            f"""
insert into public.agents (id, handle, kind)
values ('{STALE_REVIEWER_ID}', '{CANONICAL_REVIEWER_HANDLE}', 'human');
""",
        )
        refused = _apply_migration(container, check=False)
        identity_rows = _psql(
            container,
            f"""
select handle || '|' || id::text
  from public.agents
 where handle in ('{LEGACY_REVIEWER_HANDLE}', '{CANONICAL_REVIEWER_HANDLE}')
 order by handle;
""",
        ).stdout.splitlines()

    assert refused.returncode != 0
    assert "both reviewer handles m3ta" in refused.stderr
    assert "exist with different agent UUIDs" in refused.stderr
    assert identity_rows == [
        f"{LEGACY_REVIEWER_HANDLE}|{REVIEWER_ID}",
        f"{CANONICAL_REVIEWER_HANDLE}|{STALE_REVIEWER_ID}",
    ]


def test_migration_refuses_to_fabricate_missing_human_reviewer() -> None:
    with _postgres_clone() as container:
        _psql(
            container,
            f"""
delete from kb_stage.kb_proposal_approvals;
delete from public.agents where handle = '{LEGACY_REVIEWER_HANDLE}';
""",
        )
        refused = _apply_migration(container, check=False)
        remaining_agents = _psql(container, "select count(*) from public.agents;").stdout.strip()

    assert refused.returncode != 0
    assert "required human reviewer agent is missing" in refused.stderr
    assert "handle m3ta or m3taversal" in refused.stderr
    assert remaining_agents == "0"


def test_reviewer_principal_seed_timestamp_is_deterministic(migrated_postgres: str) -> None:
    created_at = _psql(
        migrated_postgres,
        "select created_at::text from kb_stage.kb_review_principals where db_role = 'kb_review';",
    ).stdout.strip()

    assert created_at == "1970-01-01 00:00:00+00"


def test_upgrade_backfills_only_still_exact_approved_rows(migrated_postgres: str) -> None:
    snapshot_state = _psql(
        migrated_postgres,
        f"""
select proposal_id::text || '|' || (approved_proposal_snapshot is not null)::text || '|' ||
       coalesce(approved_proposal_snapshot->>'status', 'unavailable') || '|' ||
       coalesce(approved_proposal_snapshot->>'reviewed_by_handle', 'unavailable')
  from kb_stage.kb_proposal_approvals
 where proposal_id in ('{LEGACY_APPROVED_ID}'::uuid, '{LEGACY_APPLIED_ID}'::uuid)
 order by proposal_id;
""",
    ).stdout.splitlines()

    assert snapshot_state == [
        f"{LEGACY_APPROVED_ID}|true|approved|{LEGACY_REVIEWER_HANDLE}",
        f"{LEGACY_APPLIED_ID}|false|unavailable|unavailable",
    ]
    unavailable = _psql(
        migrated_postgres,
        f"""
set session authorization kb_apply;
select kb_stage.export_applied_proposal_replay_rows('{LEGACY_APPLIED_ID}'::uuid);
""",
        check=False,
    )
    assert unavailable.returncode != 0
    assert "exact applied transition unavailable" in unavailable.stderr


def test_new_approval_preserves_exact_post_approval_row_for_apply_export(migrated_postgres: str) -> None:
    payload = '{"apply_payload":{"from_claim":"aaaaaaaa-1000-4000-8000-000000000003"}}'
    _psql(
        migrated_postgres,
        f"""
insert into kb_stage.kb_proposals (id, proposal_type, status, payload, updated_at)
values ('{FRESH_PROPOSAL_ID}'::uuid, 'add_edge', 'pending_review', '{payload}'::jsonb,
        '2026-07-18T11:00:00Z');
set session authorization kb_review;
select kb_stage.approve_strict_proposal(
  '{FRESH_PROPOSAL_ID}'::uuid, 'add_edge', '{payload}'::jsonb, '{CANONICAL_REVIEWER_HANDLE}',
  'Fresh exact approval snapshot.'
);
""",
    )
    approval_identity = _psql(
        migrated_postgres,
        f"""
select proposal.reviewed_by_handle || '|' || proposal.reviewed_by_agent_id::text || '|' ||
       approval.reviewed_by_handle || '|' || approval.reviewed_by_agent_id::text || '|' ||
       (approval.approved_proposal_snapshot->>'reviewed_by_handle') || '|' ||
       (approval.approved_proposal_snapshot->>'reviewed_by_agent_id')
  from kb_stage.kb_proposals proposal
  join kb_stage.kb_proposal_approvals approval on approval.proposal_id = proposal.id
 where proposal.id = '{FRESH_PROPOSAL_ID}'::uuid;
""",
    ).stdout.strip()
    assert approval_identity == (
        f"{CANONICAL_REVIEWER_HANDLE}|{REVIEWER_ID}|"
        f"{CANONICAL_REVIEWER_HANDLE}|{REVIEWER_ID}|"
        f"{CANONICAL_REVIEWER_HANDLE}|{REVIEWER_ID}"
    )
    approval_state = _psql(
        migrated_postgres,
        f"""
select jsonb_build_object(
  'matches', approval.approved_proposal_snapshot = to_jsonb(proposal),
  'status', approval.approved_proposal_snapshot->>'status',
  'updated_at_matches', approval.approved_proposal_snapshot->'updated_at' = to_jsonb(proposal)->'updated_at'
)::text
from kb_stage.kb_proposal_approvals approval
join kb_stage.kb_proposals proposal on proposal.id = approval.proposal_id
where proposal.id = '{FRESH_PROPOSAL_ID}'::uuid;
""",
    ).stdout.strip()
    assert approval_state == '{"status": "approved", "matches": true, "updated_at_matches": true}'

    direct_approval_read = _psql(
        migrated_postgres,
        "set session authorization kb_apply; select * from kb_stage.kb_proposal_approvals;",
        check=False,
    )
    assert direct_approval_read.returncode != 0
    assert "permission denied for table kb_proposal_approvals" in direct_approval_read.stderr

    premature_export = _psql(
        migrated_postgres,
        f"""
set session authorization kb_apply;
select kb_stage.export_applied_proposal_replay_rows('{FRESH_PROPOSAL_ID}'::uuid);
""",
        check=False,
    )
    assert premature_export.returncode != 0
    assert "exact applied transition unavailable" in premature_export.stderr

    _psql(
        migrated_postgres,
        f"""
update kb_stage.kb_proposal_approvals
   set approved_proposal_snapshot =
       approved_proposal_snapshot || '{{"rationale":"tampered after approval"}}'::jsonb
 where proposal_id = '{FRESH_PROPOSAL_ID}'::uuid;
""",
    )
    for guard_call in (
        "kb_stage.assert_approved_proposal("
        "proposal.id, proposal.proposal_type, proposal.payload, proposal.reviewed_by_handle, "
        "proposal.reviewed_by_agent_id, proposal.reviewed_at, proposal.review_note)",
        "kb_stage.finish_approved_proposal("
        "proposal.id, proposal.proposal_type, proposal.payload, proposal.reviewed_by_handle, "
        "proposal.reviewed_by_agent_id, proposal.reviewed_at, proposal.review_note, 'kb-apply')",
    ):
        refused = _psql(
            migrated_postgres,
            f"""
set session authorization kb_apply;
select {guard_call}
from kb_stage.kb_proposals proposal where proposal.id = '{FRESH_PROPOSAL_ID}'::uuid;
""",
            check=False,
        )
        assert refused.returncode != 0
        assert "immutable approved snapshot" in refused.stderr
    _psql(
        migrated_postgres,
        f"""
update kb_stage.kb_proposal_approvals approval
   set approved_proposal_snapshot = to_jsonb(proposal)
  from kb_stage.kb_proposals proposal
 where approval.proposal_id = proposal.id
   and proposal.id = '{FRESH_PROPOSAL_ID}'::uuid;
""",
    )

    exported = _psql(
        migrated_postgres,
        f"""
set session authorization kb_apply;
select kb_stage.assert_approved_proposal(
  proposal.id, proposal.proposal_type, proposal.payload, proposal.reviewed_by_handle,
  proposal.reviewed_by_agent_id, proposal.reviewed_at, proposal.review_note
)
from kb_stage.kb_proposals proposal where proposal.id = '{FRESH_PROPOSAL_ID}'::uuid;
select kb_stage.finish_approved_proposal(
  proposal.id, proposal.proposal_type, proposal.payload, proposal.reviewed_by_handle,
  proposal.reviewed_by_agent_id, proposal.reviewed_at, proposal.review_note, 'kb-apply'
)
from kb_stage.kb_proposals proposal where proposal.id = '{FRESH_PROPOSAL_ID}'::uuid;
select jsonb_object_keys(kb_stage.export_applied_proposal_replay_rows('{FRESH_PROPOSAL_ID}'::uuid))
order by 1;
""",
    ).stdout.splitlines()
    assert exported[-3:] == ["applied_proposal", "approval_snapshot", "approved_proposal"]


def test_gate_functions_are_timezone_independent_across_connections(
    migrated_postgres: str,
) -> None:
    payload = '{"apply_payload":{"from_claim":"aaaaaaaa-1000-4000-8000-000000000004"}}'
    _psql(
        migrated_postgres,
        f"""
insert into kb_stage.kb_proposals (id, proposal_type, status, payload, updated_at)
values ('{TIMEZONE_PROPOSAL_ID}'::uuid, 'add_edge', 'pending_review', '{payload}'::jsonb,
        '2026-07-18T12:00:00Z');
set timezone = 'America/Los_Angeles';
set session authorization kb_review;
select kb_stage.approve_strict_proposal(
  '{TIMEZONE_PROPOSAL_ID}'::uuid, 'add_edge', '{payload}'::jsonb, '{CANONICAL_REVIEWER_HANDLE}',
  'Cross-timezone exact approval snapshot.'
);
""",
    )

    asserted_and_finished = _psql(
        migrated_postgres,
        f"""
set timezone = 'Asia/Tokyo';
set session authorization kb_apply;
select kb_stage.assert_approved_proposal(
  proposal.id, proposal.proposal_type, proposal.payload, proposal.reviewed_by_handle,
  proposal.reviewed_by_agent_id, proposal.reviewed_at, proposal.review_note
)
from kb_stage.kb_proposals proposal where proposal.id = '{TIMEZONE_PROPOSAL_ID}'::uuid;
select kb_stage.finish_approved_proposal(
  proposal.id, proposal.proposal_type, proposal.payload, proposal.reviewed_by_handle,
  proposal.reviewed_by_agent_id, proposal.reviewed_at, proposal.review_note, 'kb-apply'
)
from kb_stage.kb_proposals proposal where proposal.id = '{TIMEZONE_PROPOSAL_ID}'::uuid;
""",
    )
    assert '"status": "applied"' in asserted_and_finished.stdout

    exported = json.loads(
        _psql(
            migrated_postgres,
            f"""
set timezone = 'Pacific/Auckland';
set session authorization kb_apply;
select kb_stage.export_applied_proposal_replay_rows('{TIMEZONE_PROPOSAL_ID}'::uuid)::text;
""",
        ).stdout.splitlines()[-1]
    )
    approved = exported["approved_proposal"]
    applied = exported["applied_proposal"]
    approval = exported["approval_snapshot"]
    assert approved["reviewed_at"].endswith("+00:00")
    assert applied["reviewed_at"] == approved["reviewed_at"]
    assert approval["reviewed_at"] == approved["reviewed_at"]
    assert applied["status"] == "applied"


def test_transition_export_rollback_preserves_evidence_and_reinstalls_cleanly() -> None:
    with _postgres_clone() as container:
        _apply_migration(container)
        before = _psql(
            container,
            f"""
select approved_proposal_snapshot::text
  from kb_stage.kb_proposal_approvals
 where proposal_id = '{LEGACY_APPROVED_ID}'::uuid;
""",
        ).stdout.strip()

        _psql(
            container,
            """
revoke all on function kb_stage.export_applied_proposal_replay_rows(uuid)
  from public, kb_gate_owner, kb_apply, kb_review;
drop function kb_stage.export_applied_proposal_replay_rows(uuid);
""",
        )
        rolled_back = _psql(
            container,
            f"""
select pg_catalog.to_regprocedure(
         'kb_stage.export_applied_proposal_replay_rows(uuid)'
       ) is null;
select approved_proposal_snapshot::text
  from kb_stage.kb_proposal_approvals
 where proposal_id = '{LEGACY_APPROVED_ID}'::uuid;
""",
        ).stdout.splitlines()
        assert rolled_back == ["t", before]

        _apply_migration(container)
        restored = _psql(
            container,
            f"""
select pg_catalog.to_regprocedure(
         'kb_stage.export_applied_proposal_replay_rows(uuid)'
       ) is not null;
select approved_proposal_snapshot::text
  from kb_stage.kb_proposal_approvals
 where proposal_id = '{LEGACY_APPROVED_ID}'::uuid;
""",
        ).stdout.splitlines()
        assert restored == ["t", before]


def _catalog_snapshot(container: str) -> str:
    return _psql(
        container,
        r"""
select 'role|' || rolname || '|' || rolcanlogin || '|' || rolinherit || '|' || rolsuper
  from pg_catalog.pg_roles
 where rolname in ('kb_gate_owner', 'kb_review', 'kb_apply')
 order by rolname;
select 'owner|' || namespace.nspname || '|' || relation.relname || '|' || owner_role.rolname
  from pg_catalog.pg_class relation
  join pg_catalog.pg_namespace namespace on namespace.oid = relation.relnamespace
  join pg_catalog.pg_roles owner_role on owner_role.oid = relation.relowner
 where (namespace.nspname, relation.relname) in (
   ('public', 'agents'), ('public', 'strategies'), ('public', 'strategy_nodes'),
   ('public', 'claim_evidence'), ('public', 'claim_edges'), ('public', 'claims'),
   ('public', 'sources'), ('public', 'reasoning_tools'),
   ('kb_stage', 'kb_proposals'), ('kb_stage', 'kb_review_principals'),
   ('kb_stage', 'kb_proposal_approvals')
 )
 order by namespace.nspname, relation.relname;
select 'function|' || procedure.proname || '(' || pg_catalog.oidvectortypes(procedure.proargtypes) ||
       ')|' || owner_role.rolname || '|' || procedure.prosecdef || '|' || procedure.proconfig::text
  from pg_catalog.pg_proc procedure
  join pg_catalog.pg_namespace namespace on namespace.oid = procedure.pronamespace
  join pg_catalog.pg_roles owner_role on owner_role.oid = procedure.proowner
 where namespace.nspname = 'kb_stage'
   and procedure.proname in (
     'approve_strict_proposal', 'assert_approved_proposal', 'finish_approved_proposal',
     'export_applied_proposal_replay_rows'
   )
 order by procedure.proname, procedure.proargtypes::text;
select 'column|' || attribute.attname || '|' || attribute.atttypid::regtype::text || '|' ||
       attribute.attnotnull
  from pg_catalog.pg_attribute attribute
 where attribute.attrelid = 'kb_stage.kb_proposal_approvals'::regclass
   and attribute.attname = 'approved_proposal_snapshot'
   and not attribute.attisdropped;
select 'table_acl|' || namespace.nspname || '|' || relation.relname || '|' ||
       case when acl.grantee = 0 then 'PUBLIC' else grantee_role.rolname end || '|' ||
       pg_catalog.upper(acl.privilege_type) || '|' || acl.is_grantable
  from pg_catalog.pg_class relation
  join pg_catalog.pg_namespace namespace on namespace.oid = relation.relnamespace
  cross join lateral pg_catalog.aclexplode(
    coalesce(relation.relacl, pg_catalog.acldefault('r', relation.relowner))
  ) acl
  left join pg_catalog.pg_roles grantee_role on grantee_role.oid = acl.grantee
 where (namespace.nspname, relation.relname) in (
   ('public', 'agents'), ('public', 'strategies'), ('public', 'strategy_nodes'),
   ('public', 'claim_evidence'), ('public', 'claim_edges'), ('public', 'claims'),
   ('public', 'sources'), ('public', 'reasoning_tools'),
   ('kb_stage', 'kb_proposals'), ('kb_stage', 'kb_review_principals'),
   ('kb_stage', 'kb_proposal_approvals')
 )
   and acl.grantee <> relation.relowner
   and (acl.grantee = 0 or grantee_role.rolname in ('kb_gate_owner', 'kb_review', 'kb_apply'))
 order by namespace.nspname, relation.relname, grantee_role.rolname, acl.privilege_type;
select 'function_acl|' || procedure.proname || '(' ||
       pg_catalog.oidvectortypes(procedure.proargtypes) || ')|' ||
       case when acl.grantee = 0 then 'PUBLIC' else pg_catalog.pg_get_userbyid(acl.grantee) end ||
       '|' || pg_catalog.upper(acl.privilege_type) || '|' || acl.is_grantable
  from pg_catalog.pg_proc procedure
  join pg_catalog.pg_namespace namespace on namespace.oid = procedure.pronamespace
  cross join lateral pg_catalog.aclexplode(
    coalesce(procedure.proacl, pg_catalog.acldefault('f', procedure.proowner))
  ) acl
 where namespace.nspname = 'kb_stage'
   and procedure.proname in (
     'approve_strict_proposal', 'assert_approved_proposal', 'finish_approved_proposal',
     'export_applied_proposal_replay_rows'
   )
   and acl.grantee <> procedure.proowner
 order by procedure.proname, procedure.proargtypes::text, acl.grantee;
""",
    ).stdout


def test_upgrade_normalizes_exact_role_ownership_and_acl_matrix(migrated_postgres: str) -> None:
    role_rows = _lines(
        _psql(
            migrated_postgres,
            """
select rolname || '|' || rolcanlogin || '|' || rolinherit || '|' || rolsuper || '|' ||
       rolcreatedb || '|' || rolcreaterole || '|' || rolreplication || '|' || rolbypassrls
  from pg_catalog.pg_roles
 where rolname in ('kb_gate_owner', 'kb_review', 'kb_apply')
 order by rolname;
""",
        )
    )
    assert role_rows == {
        "kb_apply|true|false|false|false|false|false|false",
        "kb_gate_owner|false|false|false|false|false|false|false",
        "kb_review|true|false|false|false|false|false|false",
    }

    assert (
        _psql(
            migrated_postgres,
            """
select count(*)
  from pg_catalog.pg_auth_members membership
  join pg_catalog.pg_roles member_role on member_role.oid = membership.member
  join pg_catalog.pg_roles granted_role on granted_role.oid = membership.roleid
 where member_role.rolname in ('kb_gate_owner', 'kb_review', 'kb_apply')
    or granted_role.rolname in ('kb_gate_owner', 'kb_review', 'kb_apply');
""",
        ).stdout.strip()
        == "0"
    )

    schema_rows = _lines(
        _psql(
            migrated_postgres,
            """
select protected_role.rolname || '|' || protected_schema.nspname || '|' ||
       pg_catalog.has_schema_privilege(protected_role.rolname, protected_schema.nspname, 'USAGE') || '|' ||
       pg_catalog.has_schema_privilege(protected_role.rolname, protected_schema.nspname, 'CREATE')
  from (values ('kb_gate_owner'), ('kb_review'), ('kb_apply')) protected_role(rolname)
  cross join (values ('public'), ('kb_stage')) protected_schema(nspname);
""",
        )
    )
    assert schema_rows == {
        f"{role}|{schema}|true|false"
        for role in ("kb_gate_owner", "kb_review", "kb_apply")
        for schema in ("public", "kb_stage")
    }

    function_rows = _lines(
        _psql(
            migrated_postgres,
            """
select procedure.proname || '(' || pg_catalog.oidvectortypes(procedure.proargtypes) || ')|' ||
       owner_role.rolname || '|' || procedure.prosecdef || '|' ||
       (
         pg_catalog.cardinality(procedure.proconfig) = 2
         and procedure.proconfig @> array[
           'search_path=pg_catalog, pg_temp'::text,
           'TimeZone=UTC'::text
         ]
       )
  from pg_catalog.pg_proc procedure
  join pg_catalog.pg_namespace namespace on namespace.oid = procedure.pronamespace
  join pg_catalog.pg_roles owner_role on owner_role.oid = procedure.proowner
 where namespace.nspname = 'kb_stage'
   and procedure.proname in (
     'approve_strict_proposal', 'assert_approved_proposal', 'finish_approved_proposal',
     'export_applied_proposal_replay_rows'
   );
""",
        )
    )
    assert function_rows == {
        "approve_strict_proposal(uuid, text, jsonb, text, text)|kb_gate_owner|true|true",
        "assert_approved_proposal(uuid, text, jsonb, text, uuid, timestamp with time zone, text)|"
        "kb_gate_owner|true|true",
        "finish_approved_proposal(uuid, text, jsonb, text, uuid, timestamp with time zone, text, text)|"
        "kb_gate_owner|true|true",
        "export_applied_proposal_replay_rows(uuid)|kb_gate_owner|true|true",
    }
    assert (
        _psql(
            migrated_postgres,
            "select pg_catalog.to_regprocedure('kb_stage.approve_strict_proposal(uuid,text,text)') is null;",
        ).stdout.strip()
        == "t"
    )
    assert (
        _psql(
            migrated_postgres,
            """
select count(*)
  from pg_catalog.pg_attribute attribute
  join pg_catalog.pg_class relation on relation.oid = attribute.attrelid
  join pg_catalog.pg_namespace namespace on namespace.oid = relation.relnamespace
  cross join lateral pg_catalog.aclexplode(attribute.attacl) acl
  left join pg_catalog.pg_roles grantee_role on grantee_role.oid = acl.grantee
 where (namespace.nspname, relation.relname) in (
   ('public', 'agents'), ('public', 'strategies'), ('public', 'strategy_nodes'),
   ('public', 'claim_evidence'), ('public', 'claim_edges'), ('public', 'claims'),
   ('public', 'sources'), ('public', 'reasoning_tools'),
   ('kb_stage', 'kb_proposals'), ('kb_stage', 'kb_review_principals'),
   ('kb_stage', 'kb_proposal_approvals')
 )
   and (acl.grantee = 0 or grantee_role.rolname in ('kb_gate_owner', 'kb_review', 'kb_apply'));
""",
        ).stdout.strip()
        == "0"
    )

    for runtime_role, target_role in (
        ("kb_apply", "broad_writer"),
        ("kb_apply", "kb_gate_owner"),
        ("kb_review", "kb_gate_owner"),
    ):
        denied = _psql(
            migrated_postgres,
            f"set session authorization {runtime_role}; set role {target_role};",
            check=False,
        )
        assert denied.returncode != 0
        assert "permission denied to set role" in denied.stderr

    snapshot_lines = set(_catalog_snapshot(migrated_postgres).splitlines())
    expected_table_acls = {
        "table_acl|public|agents|kb_gate_owner|SELECT|false",
        "table_acl|public|agents|kb_apply|SELECT|false",
        "table_acl|public|agents|kb_review|SELECT|false",
        "table_acl|public|strategies|kb_apply|SELECT|false",
        "table_acl|public|strategies|kb_apply|INSERT|false",
        "table_acl|public|strategies|kb_apply|UPDATE|false",
        "table_acl|public|strategy_nodes|kb_apply|SELECT|false",
        "table_acl|public|strategy_nodes|kb_apply|INSERT|false",
        "table_acl|public|strategy_nodes|kb_apply|UPDATE|false",
        "table_acl|public|claim_evidence|kb_apply|SELECT|false",
        "table_acl|public|claim_evidence|kb_apply|INSERT|false",
        "table_acl|public|claim_edges|kb_apply|SELECT|false",
        "table_acl|public|claim_edges|kb_apply|INSERT|false",
        "table_acl|public|claims|kb_apply|SELECT|false",
        "table_acl|public|claims|kb_apply|INSERT|false",
        "table_acl|public|sources|kb_apply|SELECT|false",
        "table_acl|public|sources|kb_apply|INSERT|false",
        "table_acl|public|reasoning_tools|kb_apply|SELECT|false",
        "table_acl|public|reasoning_tools|kb_apply|INSERT|false",
        "table_acl|kb_stage|kb_proposals|kb_gate_owner|SELECT|false",
        "table_acl|kb_stage|kb_proposals|kb_gate_owner|UPDATE|false",
        "table_acl|kb_stage|kb_proposals|kb_apply|SELECT|false",
        "table_acl|kb_stage|kb_proposals|kb_review|SELECT|false",
    }
    assert {line for line in snapshot_lines if line.startswith("table_acl|")} == expected_table_acls
    assert {line for line in snapshot_lines if line.startswith("function_acl|")} == {
        "function_acl|approve_strict_proposal(uuid, text, jsonb, text, text)|kb_review|EXECUTE|false",
        "function_acl|assert_approved_proposal(uuid, text, jsonb, text, uuid, timestamp with time zone, text)|"
        "kb_apply|EXECUTE|false",
        "function_acl|finish_approved_proposal(uuid, text, jsonb, text, uuid, timestamp with time zone, text, text)|"
        "kb_apply|EXECUTE|false",
        "function_acl|export_applied_proposal_replay_rows(uuid)|kb_apply|EXECUTE|false",
    }


def test_migration_rerun_preserves_catalog_snapshot(migrated_postgres: str) -> None:
    before = _catalog_snapshot(migrated_postgres)
    _apply_migration(migrated_postgres)
    assert _catalog_snapshot(migrated_postgres) == before


def _assert_migration_fails(container: str, expected_error: str) -> None:
    completed = _apply_migration(container, check=False)
    assert completed.returncode != 0
    assert expected_error in completed.stderr


def test_migration_fails_closed_on_protected_role_membership(migrated_postgres: str) -> None:
    _psql(migrated_postgres, "grant broad_writer to kb_apply;")
    try:
        _assert_migration_fails(migrated_postgres, "unexpected protected role membership")
    finally:
        _psql(migrated_postgres, "revoke broad_writer from kb_apply;")


def test_migration_fails_closed_on_ownership_drift(migrated_postgres: str) -> None:
    _psql(migrated_postgres, "alter table kb_stage.kb_proposal_approvals owner to rogue_owner;")
    try:
        _assert_migration_fails(migrated_postgres, "unexpected protected table owner")
    finally:
        _psql(migrated_postgres, "alter table kb_stage.kb_proposal_approvals owner to kb_gate_owner;")


def test_migration_fails_closed_on_unexpected_table_grantee(migrated_postgres: str) -> None:
    _psql(migrated_postgres, "grant select on kb_stage.kb_proposal_approvals to rogue_grantee;")
    try:
        _assert_migration_fails(migrated_postgres, "unexpected protected table grantee")
    finally:
        _psql(migrated_postgres, "revoke all on kb_stage.kb_proposal_approvals from rogue_grantee;")


def test_migration_preserves_current_and_complete_observatory_read_surfaces() -> None:
    observatory_acl_sql = """
select namespace.nspname || '|' || relation.relname || '|' || acl.privilege_type || '|' ||
       acl.is_grantable::text
  from pg_catalog.pg_class relation
  join pg_catalog.pg_namespace namespace on namespace.oid = relation.relnamespace
  cross join lateral pg_catalog.aclexplode(relation.relacl) acl
  join pg_catalog.pg_roles grantee on grantee.oid = acl.grantee
 where grantee.rolname = 'kb_observatory_read'
 order by namespace.nspname, relation.relname, acl.privilege_type;
"""
    with _postgres_clone() as container:
        _psql(
            container,
            """
create role kb_observatory_read nologin noinherit nosuperuser nocreatedb nocreaterole
  noreplication nobypassrls;
create role observatory_principal login noinherit nosuperuser nocreatedb nocreaterole
  noreplication nobypassrls;
grant kb_observatory_read to observatory_principal;
grant usage on schema public, kb_stage to kb_observatory_read;
grant select on
  public.claims,
  public.claim_evidence,
  public.claim_edges
to kb_observatory_read;
""",
        )

        # The current VPS manifest has this exact three-table read projection.
        _apply_migration(container)
        _apply_migration(container)
        assert _lines(_psql(container, observatory_acl_sql)) == {
            "public|claim_edges|SELECT|false",
            "public|claim_evidence|SELECT|false",
            "public|claims|SELECT|false",
        }

        # The independently managed Observatory installer extends the same
        # bounded surface to source and proposal provenance reads.
        _psql(
            container,
            """
grant select on public.sources, kb_stage.kb_proposals to kb_observatory_read;
""",
        )
        _apply_migration(container)
        assert _lines(_psql(container, observatory_acl_sql)) == {
            "kb_stage|kb_proposals|SELECT|false",
            "public|claim_edges|SELECT|false",
            "public|claim_evidence|SELECT|false",
            "public|claims|SELECT|false",
            "public|sources|SELECT|false",
        }

        for grant_sql, revoke_sql, expected_error in (
            (
                "grant insert on public.claims to kb_observatory_read;",
                "revoke insert on public.claims from kb_observatory_read;",
                "unexpected protected table grantee",
            ),
            (
                "grant select on public.agents to kb_observatory_read;",
                "revoke select on public.agents from kb_observatory_read;",
                "unexpected protected table grantee",
            ),
            (
                "grant select on public.sources to kb_observatory_read with grant option;",
                "revoke grant option for select on public.sources from kb_observatory_read;",
                "unexpected protected table grantee",
            ),
            (
                "grant select (id) on public.claims to kb_observatory_read;",
                "revoke select (id) on public.claims from kb_observatory_read;",
                "unexpected protected table grantee",
            ),
            (
                "grant execute on function "
                "kb_stage.assert_approved_proposal(uuid,text,jsonb,text,uuid,timestamptz,text) "
                "to kb_observatory_read;",
                "revoke execute on function "
                "kb_stage.assert_approved_proposal(uuid,text,jsonb,text,uuid,timestamptz,text) "
                "from kb_observatory_read;",
                "unexpected protected function grantee",
            ),
            (
                "grant broad_writer to kb_observatory_read;",
                "revoke broad_writer from kb_observatory_read;",
                "unexpected protected role membership",
            ),
        ):
            _psql(container, grant_sql)
            try:
                _assert_migration_fails(container, expected_error)
            finally:
                _psql(container, revoke_sql)

        _apply_migration(container)


def _scoped_runtime_acl_snapshot(container: str) -> str:
    return _psql(
        container,
        r"""
select 'role|' || rolname || '|' || rolcanlogin || '|' || rolinherit || '|' || rolsuper
  from pg_catalog.pg_roles
 where rolname in ('leoclean_kb_runtime', 'leoclean_kb_stage_owner')
 order by rolname;
select 'membership|' || member_role.rolname || '|' || granted_role.rolname
  from pg_catalog.pg_auth_members membership
  join pg_catalog.pg_roles member_role on member_role.oid = membership.member
  join pg_catalog.pg_roles granted_role on granted_role.oid = membership.roleid
 where member_role.rolname in ('leoclean_kb_runtime', 'leoclean_kb_stage_owner')
    or granted_role.rolname in ('leoclean_kb_runtime', 'leoclean_kb_stage_owner')
 order by member_role.rolname, granted_role.rolname;
select 'table|' || namespace.nspname || '|' || relation.relname || '|' ||
       grantee_role.rolname || '|' || pg_catalog.upper(acl.privilege_type) || '|' ||
       acl.is_grantable
  from pg_catalog.pg_class relation
  join pg_catalog.pg_namespace namespace on namespace.oid = relation.relnamespace
 cross join lateral pg_catalog.aclexplode(
   coalesce(relation.relacl, pg_catalog.acldefault('r', relation.relowner))
 ) acl
  join pg_catalog.pg_roles grantee_role on grantee_role.oid = acl.grantee
 where grantee_role.rolname in ('leoclean_kb_runtime', 'leoclean_kb_stage_owner')
 order by namespace.nspname, relation.relname, grantee_role.rolname, acl.privilege_type;
select 'column|' || namespace.nspname || '|' || relation.relname || '|' ||
       attribute.attname || '|' || grantee_role.rolname || '|' ||
       pg_catalog.upper(acl.privilege_type) || '|' || acl.is_grantable
  from pg_catalog.pg_attribute attribute
  join pg_catalog.pg_class relation on relation.oid = attribute.attrelid
  join pg_catalog.pg_namespace namespace on namespace.oid = relation.relnamespace
 cross join lateral pg_catalog.aclexplode(attribute.attacl) acl
  join pg_catalog.pg_roles grantee_role on grantee_role.oid = acl.grantee
 where grantee_role.rolname in ('leoclean_kb_runtime', 'leoclean_kb_stage_owner')
 order by namespace.nspname, relation.relname, attribute.attnum,
          grantee_role.rolname, acl.privilege_type;
""",
    ).stdout


def test_migration_composes_with_scoped_leoclean_acls_and_rejects_drift() -> None:
    with _postgres_clone() as container:
        _apply_migration(container)
        _psql(
            container,
            """
create role leoclean_kb_runtime
  nologin noinherit nosuperuser nocreatedb nocreaterole noreplication nobypassrls;
create role leoclean_kb_stage_owner
  nologin noinherit nosuperuser nocreatedb nocreaterole noreplication nobypassrls;

grant usage on schema public, kb_stage to leoclean_kb_runtime, leoclean_kb_stage_owner;
grant select (id, handle) on public.agents to leoclean_kb_runtime, leoclean_kb_stage_owner;
grant select (agent_id, active) on public.strategies to leoclean_kb_runtime;
grant select (claim_id, source_id, role) on public.claim_evidence to leoclean_kb_runtime;
grant select (id) on public.claim_edges, public.claims to leoclean_kb_runtime;
grant select (id, hash) on public.sources to leoclean_kb_runtime;
grant select (
  id, proposal_type, status, proposed_by_handle, proposed_by_agent_id, channel,
  source_ref, rationale, payload, reviewed_by_handle, reviewed_at, review_note,
  applied_by_handle, applied_at, created_at, updated_at
) on kb_stage.kb_proposals to leoclean_kb_runtime;

grant select on kb_stage.kb_proposals to leoclean_kb_stage_owner;
grant insert (
  proposal_type, status, proposed_by_handle, proposed_by_agent_id, channel,
  source_ref, rationale, payload
) on kb_stage.kb_proposals to leoclean_kb_stage_owner;
""",
        )
        before = _scoped_runtime_acl_snapshot(container)

        _apply_migration(container)
        _apply_migration(container)
        assert _scoped_runtime_acl_snapshot(container) == before

        allowed_reads = _psql(
            container,
            """
set session authorization leoclean_kb_runtime;
select id, handle from public.agents limit 0;
select id, proposal_type, status, payload from kb_stage.kb_proposals limit 0;
""",
        )
        assert allowed_reads.returncode == 0

        for denied_sql in (
            "set session authorization leoclean_kb_runtime; "
            "insert into public.claims (id) values ('99999999-9999-4999-8999-999999999998');",
            "set session authorization leoclean_kb_runtime; "
            "insert into kb_stage.kb_proposals (id, proposal_type, status, rationale, payload) "
            "values ('99999999-9999-4999-8999-999999999997', 'add_edge', "
            "'pending_review', 'direct write', '{}'::jsonb);",
            "set session authorization leoclean_kb_runtime; set role kb_apply;",
        ):
            denied = _psql(container, denied_sql, check=False)
            assert denied.returncode != 0
            assert "permission denied" in denied.stderr

        for grant_sql, revoke_sql, expected_error in (
            (
                "grant select (reviewed_by_agent_id) on kb_stage.kb_proposals "
                "to leoclean_kb_runtime;",
                "revoke select (reviewed_by_agent_id) on kb_stage.kb_proposals "
                "from leoclean_kb_runtime;",
                "unexpected protected table grantee",
            ),
            (
                "grant select (id) on public.claims to leoclean_kb_runtime with grant option;",
                "revoke grant option for select (id) on public.claims from leoclean_kb_runtime;",
                "unexpected protected table grantee",
            ),
            (
                "grant update on kb_stage.kb_proposals to leoclean_kb_stage_owner;",
                "revoke update on kb_stage.kb_proposals from leoclean_kb_stage_owner;",
                "unexpected protected table grantee",
            ),
            (
                "grant broad_writer to leoclean_kb_runtime;",
                "revoke broad_writer from leoclean_kb_runtime;",
                "unexpected protected role membership",
            ),
        ):
            _psql(container, grant_sql)
            try:
                _assert_migration_fails(container, expected_error)
            finally:
                _psql(container, revoke_sql)

        _apply_migration(container)
        assert _scoped_runtime_acl_snapshot(container) == before


def test_migration_refuses_rogue_canonical_insert_before_install_and_normalizes_after_removal() -> None:
    with _postgres_clone() as container:
        _psql(container, "grant usage on schema public to rogue_grantee;")
        for grant_sql, revoke_sql in (
            (
                "grant insert on public.claims to rogue_grantee;",
                "revoke insert on public.claims from rogue_grantee;",
            ),
            (
                "grant insert (id) on public.claims to rogue_grantee;",
                "revoke insert (id) on public.claims from rogue_grantee;",
            ),
        ):
            _psql(container, grant_sql)
            refused = _apply_migration(container, check=False)
            assert refused.returncode != 0
            assert "unexpected protected table grantee" in refused.stderr
            assert _psql(container, "select count(*) from public.claims;").stdout.strip() == "0"
            _psql(container, revoke_sql)

        _apply_migration(container)
        rogue_insert = _psql(
            container,
            "set session authorization rogue_grantee; "
            "insert into public.claims (id) values ('99999999-9999-4999-8999-999999999999');",
            check=False,
        )
        assert rogue_insert.returncode != 0
        assert "permission denied for table claims" in rogue_insert.stderr
        assert _psql(container, "select count(*) from public.claims;").stdout.strip() == "0"


def test_migration_fails_closed_on_unexpected_column_grantee(migrated_postgres: str) -> None:
    _psql(
        migrated_postgres,
        "grant select (review_note) on kb_stage.kb_proposal_approvals to rogue_grantee;",
    )
    try:
        _assert_migration_fails(migrated_postgres, "unexpected protected table grantee")
    finally:
        _psql(
            migrated_postgres,
            "revoke select (review_note) on kb_stage.kb_proposal_approvals from rogue_grantee;",
        )


def test_migration_fails_closed_on_unexpected_function_grantee(migrated_postgres: str) -> None:
    signature = "kb_stage.assert_approved_proposal(uuid,text,jsonb,text,uuid,timestamptz,text)"
    _psql(migrated_postgres, f"grant execute on function {signature} to rogue_grantee;")
    try:
        _assert_migration_fails(migrated_postgres, "unexpected protected function grantee")
    finally:
        _psql(migrated_postgres, f"revoke all on function {signature} from rogue_grantee;")


def test_migration_fails_closed_on_unexpected_gate_overload(migrated_postgres: str) -> None:
    _psql(
        migrated_postgres,
        """
create function kb_stage.assert_approved_proposal(text)
returns void language sql security definer as $function$ select $function$;
""",
    )
    try:
        _assert_migration_fails(migrated_postgres, "unexpected protected function overload")
    finally:
        _psql(migrated_postgres, "drop function kb_stage.assert_approved_proposal(text);")
