from __future__ import annotations import os import shutil import subprocess import time import uuid from collections.abc import Iterator from contextlib import contextmanager from pathlib import Path import pytest REPO_ROOT = Path(__file__).resolve().parents[1] MIGRATION = REPO_ROOT / "scripts" / "kb_apply_prereqs.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" 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, 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, 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") container = f"kb-apply-prereqs-{uuid.uuid4().hex[:12]}" started = subprocess.run( [ "docker", "run", "--pull=never", "--rm", "-d", "--name", container, "-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}") try: 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: subprocess.run( ["docker", "rm", "-f", container], capture_output=True, check=False, ) @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 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 _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 || '|' || 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' ); """, ) ) assert function_rows == { 'approve_strict_proposal(uuid, text, jsonb, text, text)|kb_gate_owner|true|{"search_path=pg_catalog, pg_temp"}', "assert_approved_proposal(uuid, text, jsonb, text, uuid, timestamp with time zone, text)|" 'kb_gate_owner|true|{"search_path=pg_catalog, pg_temp"}', "finish_approved_proposal(uuid, text, jsonb, text, uuid, timestamp with time zone, text, text)|" 'kb_gate_owner|true|{"search_path=pg_catalog, pg_temp"}', 'export_applied_proposal_replay_rows(uuid)|kb_gate_owner|true|{"search_path=pg_catalog, pg_temp"}', } 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_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);")