yau-plant-assistant/db/003_roles.sql
Claude 34d2ccc576 Scaffold the WRPS plant operations assistant repository
Build spec and host brief carried in from C:\Claude and WRPS/02-env; the
plant model (equipment, tags, alarm bitmask, enums, unit conversions) is
derived from WRPS/04-plc/register-map.csv, WRPS/05-scada/modbus/scada-points.csv
and WRPS-CTL-003.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 13:56:32 +10:00

74 lines
2.9 KiB
SQL

-- =============================================================================
-- 003_roles.sql — least privilege inside pg-ai.
--
-- psql -h pg-ai -U postgres -d plant -f 003_roles.sql
--
-- Passwords are NOT in this file. Set them from the 0600 env files:
-- \set agent_pw `echo "$AGENT_DB_PASSWORD"`
-- or ALTER ROLE ... PASSWORD after creation, from a shell that reads ~/ai/*.env.
--
-- Two roles, deliberately different:
-- agent_ro the API. SELECT only, everywhere. It must not be able to write.
-- cube_rw Cube. SELECT on reference data, full rights on cube_preagg only,
-- because pre-aggregation refresh creates and drops tables there.
-- =============================================================================
-- --- agent_ro — the application role. SELECT and nothing else. ---------------
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'agent_ro') THEN
CREATE ROLE agent_ro LOGIN;
END IF;
END
$$;
REVOKE ALL ON DATABASE plant FROM agent_ro;
GRANT CONNECT ON DATABASE plant TO agent_ro;
REVOKE ALL ON SCHEMA public FROM agent_ro;
GRANT USAGE ON SCHEMA public TO agent_ro;
REVOKE ALL ON ALL TABLES IN SCHEMA public FROM agent_ro;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO agent_ro;
-- Applies to tables created later, including the fixture tables.
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO agent_ro;
-- No sequences, no functions, no temp tables, no schema creation.
REVOKE ALL ON ALL SEQUENCES IN SCHEMA public FROM agent_ro;
REVOKE TEMPORARY ON DATABASE plant FROM agent_ro;
REVOKE CREATE ON SCHEMA public FROM agent_ro;
-- --- cube_rw — Cube. Read reference data, own cube_preagg. -------------------
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'cube_rw') THEN
CREATE ROLE cube_rw LOGIN;
END IF;
END
$$;
GRANT CONNECT ON DATABASE plant TO cube_rw;
GRANT USAGE ON SCHEMA public TO cube_rw;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO cube_rw;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO cube_rw;
GRANT ALL ON SCHEMA cube_preagg TO cube_rw;
ALTER SCHEMA cube_preagg OWNER TO cube_rw;
-- agent_ro reads pre-aggregations but never writes them.
GRANT USAGE ON SCHEMA cube_preagg TO agent_ro;
ALTER DEFAULT PRIVILEGES FOR ROLE cube_rw IN SCHEMA cube_preagg
GRANT SELECT ON TABLES TO agent_ro;
-- =============================================================================
-- Phase 1 gate — verify, do not assume. As agent_ro:
--
-- SELECT count(*) FROM equipment; -- must work
-- INSERT INTO equipment VALUES ('X'); -- must be REJECTED
-- CREATE TABLE t (i int); -- must be REJECTED
--
-- An INSERT that succeeds here is a Phase 1 failure, not a detail to fix later.
-- The API's SQL allow-list in guardrails.py is the second line of defence, not
-- the first; this role is the first.
-- =============================================================================