-- AwoooP RLS Canary Wave 1.2: projects table with explicit operator list path -- Date: 2026-05-12 -- -- Scope: -- - awooop_projects -- -- Safety model: -- - normal app access is tenant-scoped by app.project_id. -- - Operator Console cross-tenant list uses a fixed SECURITY DEFINER -- function owned by the migration/operator role. -- - no NULL/empty-string/__platform__ policy bypass. BEGIN; SET LOCAL lock_timeout = '5s'; SET LOCAL statement_timeout = '30s'; DO $$ DECLARE total_rows bigint; null_project_rows bigint; BEGIN IF to_regclass('public.awooop_projects') IS NULL THEN RAISE EXCEPTION 'RLS canary target table does not exist: awooop_projects'; END IF; IF NOT EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'awooop_projects' AND column_name = 'project_id' ) THEN RAISE EXCEPTION 'RLS canary target missing project_id: awooop_projects'; END IF; SELECT COUNT(*), COUNT(*) FILTER (WHERE project_id IS NULL) INTO total_rows, null_project_rows FROM awooop_projects; IF null_project_rows <> 0 THEN RAISE EXCEPTION 'RLS canary target has NULL project_id rows: %, nulls=%', 'awooop_projects', null_project_rows; END IF; IF total_rows > 20 THEN RAISE EXCEPTION 'RLS canary wave1.2 reviewed cap exceeded: %, rows=%', 'awooop_projects', total_rows; END IF; END $$; CREATE OR REPLACE FUNCTION public.awooop_operator_list_projects() RETURNS TABLE ( project_id varchar, display_name varchar, migration_mode varchar, budget_limit_usd numeric, is_active boolean, created_at timestamp without time zone ) LANGUAGE sql STABLE SECURITY DEFINER SET search_path = public, pg_catalog AS $$ SELECT p.project_id, p.display_name, p.migration_mode, p.budget_limit_usd, p.is_active, p.created_at FROM public.awooop_projects AS p ORDER BY p.created_at ASC; $$; REVOKE ALL ON FUNCTION public.awooop_operator_list_projects() FROM PUBLIC; GRANT EXECUTE ON FUNCTION public.awooop_operator_list_projects() TO awooop_app; ALTER TABLE awooop_projects ENABLE ROW LEVEL SECURITY; ALTER TABLE awooop_projects FORCE ROW LEVEL SECURITY; DROP POLICY IF EXISTS awooop_projects_select_tenant ON awooop_projects; DROP POLICY IF EXISTS awooop_projects_insert_tenant ON awooop_projects; DROP POLICY IF EXISTS awooop_projects_update_tenant ON awooop_projects; DROP POLICY IF EXISTS awooop_projects_delete_tenant ON awooop_projects; DROP POLICY IF EXISTS projects_tenant_isolation ON awooop_projects; CREATE POLICY awooop_projects_select_tenant ON awooop_projects FOR SELECT TO awooop_app USING (project_id = current_setting('app.project_id', TRUE)); CREATE POLICY awooop_projects_insert_tenant ON awooop_projects FOR INSERT TO awooop_app WITH CHECK (project_id = current_setting('app.project_id', TRUE)); CREATE POLICY awooop_projects_update_tenant ON awooop_projects FOR UPDATE TO awooop_app USING (project_id = current_setting('app.project_id', TRUE)) WITH CHECK (project_id = current_setting('app.project_id', TRUE)); CREATE POLICY awooop_projects_delete_tenant ON awooop_projects FOR DELETE TO awooop_app USING (project_id = current_setting('app.project_id', TRUE)); COMMIT;