You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Both worker compose targets pass the same DB_USERNAME as the api (prod-worker/docker-compose.yml:12). Once that role is demoted to a non-owner with RLS applied, the worker can no longer use it — it would only reach public data.
Scope
mavedb_worker as a login role, INHERIT, a member of mavedb_system, with DML inherited through that membership and no ownership. Created in Role topology and idempotent bootstrap script #827's bootstrap script.
A distinct credential in both staging-worker and prod-worker compose targets, and in the local stack.
app_refresh_mat_views(), a SECURITY DEFINER function owned by mavedb_definer, wrapping REFRESH MATERIALIZED VIEW. Refresh requires ownership; there is no grantable REFRESH privilege in PG 15. This is the only ownership-dependent operation in the worker.
The refresh wrapper must be owned by mavedb_definer, not mavedb_owner
mavedb_owner is a member of nothing, so a function owned by it runs with filtered visibility. The failure is silent: the materialized view refreshes successfully and is populated from public rows only. Measured 1 of 2 rows, no error. A silently-truncated published_variants_materialized_view feeds unauthenticated statistics routes.
The materialized views themselves must also be owned by mavedb_definer for the same reason — an MV created while SET ROLE mavedb_owner captures filtered rows at creation time.
Verified: REFRESH MATERIALIZED VIEW CONCURRENTLY works inside a SECURITY DEFINER plpgsql function called by the worker within an explicit transaction — the shape job_manager.db actually produces — and the worker retains no CREATE on schema public.
refresh_all_mat_views(db) in db/view.py becomes a call to that function rather than issuing REFRESH directly.
Acceptance criteria
mavedb_worker exists, is INHERIT, is a mavedb_system member, and owns nothing.
Both worker compose targets and the local stack use the worker credential, not mavedb_api's.
REFRESH MATERIALIZED VIEW runs through a SECURITY DEFINER wrapper owned by mavedb_definer.
Materialized views are owned by mavedb_definer.
A test asserts the refreshed MV contains rows derived from a private score set's published variants — the negative case here is a full view, not an error.
A test asserts the worker can read a private score set and its calibrations.
A test asserts the worker cannot perform DDL and cannot SET ROLE to mavedb_owner or mavedb_definer, run on a real connection as the worker rather than via SET ROLE from a superuser session.
Worker boot fails if the private canary row is not visible.
Why the worker needs its own principal
Both worker compose targets pass the same
DB_USERNAMEas the api (prod-worker/docker-compose.yml:12). Once that role is demoted to a non-owner with RLS applied, the worker can no longer use it — it would only reach public data.Scope
mavedb_workeras a login role,INHERIT, a member ofmavedb_system, with DML inherited through that membership and no ownership. Created in Role topology and idempotent bootstrap script #827's bootstrap script.staging-workerandprod-workercompose targets, and in the local stack.app_refresh_mat_views(), aSECURITY DEFINERfunction owned bymavedb_definer, wrappingREFRESH MATERIALIZED VIEW. Refresh requires ownership; there is no grantable REFRESH privilege in PG 15. This is the only ownership-dependent operation in the worker.pg_has_role(..., 'USAGE').The refresh wrapper must be owned by
mavedb_definer, notmavedb_ownermavedb_owneris a member of nothing, so a function owned by it runs with filtered visibility. The failure is silent: the materialized view refreshes successfully and is populated from public rows only. Measured 1 of 2 rows, no error. A silently-truncatedpublished_variants_materialized_viewfeeds unauthenticated statistics routes.The materialized views themselves must also be owned by
mavedb_definerfor the same reason — an MV created whileSET ROLE mavedb_ownercaptures filtered rows at creation time.Verified:
REFRESH MATERIALIZED VIEW CONCURRENTLYworks inside aSECURITY DEFINERplpgsql function called by the worker within an explicit transaction — the shapejob_manager.dbactually produces — and the worker retains noCREATEon schemapublic.refresh_all_mat_views(db)indb/view.pybecomes a call to that function rather than issuingREFRESHdirectly.Acceptance criteria
mavedb_workerexists, isINHERIT, is amavedb_systemmember, and owns nothing.mavedb_api's.REFRESH MATERIALIZED VIEWruns through aSECURITY DEFINERwrapper owned bymavedb_definer.mavedb_definer.SET ROLEtomavedb_ownerormavedb_definer, run on a real connection as the worker rather than viaSET ROLEfrom a superuser session.