Files
awoooi/scripts/ops/awooop-rls-canary-wave1-2-projects.sql
Your Name 7d92f0acd7
All checks were successful
Code Review / ai-code-review (push) Successful in 10s
CD Pipeline / tests (push) Successful in 1m8s
CD Pipeline / build-and-deploy (push) Successful in 3m49s
CD Pipeline / post-deploy-checks (push) Successful in 1m25s
chore(rls): stage projects canary path
2026-05-12 21:25:24 +08:00

108 lines
3.3 KiB
PL/PgSQL

-- 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;