async def bootstrap_graph(
pool: Any, runtime_login: str, *, edge_upsert_mode: str = "tenant_guarded"
) -> None:
"""Owner-only opt-in first install/upgrade, then grant existing login DML.
Never creates roles, changes passwords, transfers ownership or swaps indexes.
A scoped install requires the operator's scoped index already present.
Upgrade is a maintenance-window operation: base ALTER/INDEX locks remain held
until commit. A failed check rolls back schema/grants together. No default grants to
future
tables: each new graph table requires this explicit contract to be revised.
"""
if not _ROLE.fullmatch(runtime_login):
raise ValueError("graph runtime login must be a simple PostgreSQL identifier")
connection = await pool.acquire()
try:
async with connection.transaction():
role = await connection.fetchrow(
"SELECT oid, rolcanlogin, rolsuper, rolcreatedb, rolcreaterole, "
"rolreplication, rolbypassrls FROM pg_roles WHERE rolname=$1",
runtime_login,
)
if (
not role
or not role["rolcanlogin"]
or any(
role[key]
for key in (
"rolsuper",
"rolcreatedb",
"rolcreaterole",
"rolreplication",
"rolbypassrls",
)
)
or await connection.fetchval(
"SELECT EXISTS(SELECT 1 FROM pg_auth_members WHERE member=$1)", role["oid"]
)
):
raise GraphRuntimeNotReady("bootstrap requires an unprivileged existing login")
if await connection.fetchval(
"SELECT datdba=$1 FROM pg_database WHERE datname=current_database()", role["oid"]
):
raise GraphRuntimeNotReady("graph runtime must not own the database")
# Pin the acquired connection: bootstrap DDL and grants share one txn.
class ConnectionPool:
async def acquire(self) -> Any:
return connection
async def release(self, acquired: Any) -> None:
assert acquired is connection
await PostgresGraphBackend(
ConnectionPool(), edge_upsert_mode=edge_upsert_mode
).ensure_schema()
for table in TABLES:
row = await connection.fetchrow(
"SELECT c.relowner=(SELECT oid FROM pg_roles WHERE rolname=current_user) "
"AS owns_table, c.relowner=$2 OR n.nspowner=$2 AS runtime_owner, "
"has_schema_privilege($2,n.oid,'CREATE') AS runtime_create, "
"n.nspname AS schema FROM pg_class c JOIN pg_namespace n "
"ON n.oid=c.relnamespace WHERE c.oid=to_regclass($1)",
table,
role["oid"],
)
if (
not row
or not row["owns_table"]
or row["runtime_owner"]
or row["runtime_create"]
):
raise GraphRuntimeNotReady("bootstrap requires separate owner and runtime")
schema = '"' + row["schema"].replace('"', '""') + '"'
await connection.execute(f'GRANT USAGE ON SCHEMA {schema} TO "{runtime_login}"')
await connection.execute(
f'GRANT SELECT,INSERT,UPDATE,DELETE ON {schema}.{table} TO "{runtime_login}"'
)
try: # CORE-199 S1: the migration may run before or after bootstrap
if jobs := await jobs_table(connection):
await grant_read_only(connection, jobs, runtime_login)
except MemoryContractError as error:
raise GraphRuntimeNotReady(f"bootstrap erase job table: {error}") from error
from symfonic.memory.backends import postgres_memory_generation as generation
try: # CORE-199: the generation migration may run before or after bootstrap
if generations := await generation.generations_table(connection):
await generation.grant_runtime(connection, generations, runtime_login)
except MemoryContractError as error:
raise GraphRuntimeNotReady(f"bootstrap erase generation table: {error}") from error
finally:
await pool.release(connection)