diff --git a/scripts/deploy-preflight.ts b/scripts/deploy-preflight.ts index 3e190e9..ad93ade 100644 --- a/scripts/deploy-preflight.ts +++ b/scripts/deploy-preflight.ts @@ -107,6 +107,39 @@ try { } if (schema.app_has_ddl) throw new Error('DB_URL must use the app role (personal_app), not the schema owner'); + // Private health schema: the app role reads and writes it, nobody else (not even personal_ro). + const [health] = await client` + select + count(*) filter (where has_table_privilege(c.oid, 'select, insert, update, delete')) as app_rw, + count(*) filter ( + where exists ( + select 1 from aclexplode(c.relacl) a join pg_roles r on r.oid = a.grantee + where r.rolname = 'personal_ro' + ) + ) as ro_read, + count(*) as tables, + has_schema_privilege('public', 'health', 'usage') as public_usage + from pg_class c join pg_namespace n on n.oid = c.relnamespace + where n.nspname = 'health' and c.relkind = 'r' + `; + if ( + Number(health.tables) !== 7 || + Number(health.app_rw) !== 7 || + Number(health.ro_read) !== 0 || + health.public_usage + ) + throw new Error(`health schema grants are wrong: ${JSON.stringify(health)}`); + // Energy tracker reads the ned-collector tables (SELECT only). + const [energy] = await client` + select count(*) as hours, max(valid_from) as latest from energy.ned_utilization + where granularity = 'Hour' and classification = 'Current' + and valid_from > now() - interval '7 days' + `; + if (!Number(energy.hours)) + console.warn('Warning: energy.ned_utilization has no hourly data from the last 7 days'); + console.log( + `health: ${health.tables} tables, app read/write, personal_ro none; energy: ${energy.hours} hourly rows, latest ${energy.latest ? new Date(energy.latest).toISOString() : 'none'}` + ); const [catalog] = await client` select (select count(*) from ref.pokemon) as pokemon,