PLANNED — not yet created (#6857) — AI-worker maintenance path + p24-status.py, replacing shared root admin
Note: prometheus credentials are temporarily in both mongodb-bms.env.sops and n8n-bms4.env.sops.
Cleanup tracked in issue #2732.
Scope note (#6857, 2026-09-04): the per-service application users this repo’s isolation plan
called for (per-service-credential-isolation-plan.md §3.2 — svc-pinbox24-w3/w4, svc-etop-*)
already exist as w3_app / w4_app / w3_read_v42 / et_oper above (delivered by #3754 / #2728
/ #2834). The Pinbox24 v32-prod / v42-prod containers on bms-1 and et-op Vercel no longer
authenticate as root admin. The only remaining gap from the plan’s Part A is the AI-worker role
sys-admin-ai (below) and moving p24-status.py off root admin. The plan’s §3.4 step 5 (“rotate +
demote root once no app depends on it”) is therefore already de-risked — the #1716 outage vector
(root baked into app containers) is closed; root remains only in maintenance/rotation scripts, which
genuinely need userAdmin/root and must keep using it.
Creating the sys-admin-ai worker role + retiring root admin from AI paths (#6857)
This is the execution runbook for the only remaining piece of the per-service isolation plan’s
Part A (see the Scope note at the top of this file — the W3/W4/et-op app users already exist). It is a
cross-role, human-supervised procedure: a secret-manager step (SOPS write), a sys-admin step
(live mongosh on the rs0 PRIMARY), and a live-verify gate. A dev-issue/infra-task worker on its
own cannot complete it — it must not write SOPS and must not run an unsupervised credential
mutation on production rs0 (the #1716 caution). Run the steps in order; do not skip the verify gate.
Why sys-admin-ai needs clusterMonitor — correction to the plan (#6857)
per-service-credential-isolation-plan.md §3.2 gives sys-admin-ai only readWriteAnyDatabase +
dbAdminAnyDatabase and lists p24-status.py as a consumer. That combination is insufficient for
the one thing p24-status.py actually does: gather_mongodb() runs rs.status()
(scripts/p24-status.py), which requires the clusterMonitor action replSetGetStatus — not
granted by readWriteAny/dbAdminAny. Repointing p24-status.py at a sys-admin-ai that lacks
clusterMonitor would make it fail exactly the way it fails today without admin. Two valid fixes:
Preferred: grant sys-admin-aiclusterMonitor in addition to the two *AnyDatabase roles
(still far below root/userAdmin), so it is a genuine one-stop worker role. This is what the user
map row above records.
Alternative: leave sys-admin-ai as the plan wrote it and point p24-status.py at the existing
least-privilege prometheus (clusterMonitor) user instead — since its only query is a
read-only rs status. Then sys-admin-ai is reserved for actual read/write maintenance.
Confirm which model the executor wants before creating the role; the commands below assume the
preferred model (with clusterMonitor).
Step 1 — secret-manager: generate + store the credential (SOPS write)
A secret-manager role/session (never inline in sys-admin/dev-coder) generates a URL-safe password
and stores it in secrets/mongodb-bms.env.sops under keys MONGODB_SYS_ADMIN_AI_USER (value
sys-admin-ai) and MONGODB_SYS_ADMIN_AI_PASSWORD. Follow the SOPS write path in “Adding a new
MongoDB user → Step 4” above (LF-only WriteAllText, canary-decrypt, commit, PR to main). Do not
distribute yet — secrets-sync.yml wiring is Step 4.
Generate the value with a URL-safe alphabet, e.g. openssl rand -base64 24 | tr -d '@#/?+='.
Step 2 — sys-admin: create the user on the live PRIMARY (additive, reversible)
Read the values from SOPS (a sys-admin session may read SOPS for verification) into shell
variables — never echo them. Find the PRIMARY first (it moves): rs.isMaster().primary.
# PLAYBOOK: mongodb-user-isolation.md# ADMIN_PW and NEW_PW are read silently from SOPS into vars — never printed.mongosh admin -u admin -p "$ADMIN_PW" --authenticationDatabase admin --quiet --eval " db.createUser({ user: 'sys-admin-ai', pwd: '$NEW_PW', roles: [ { role: 'readWriteAnyDatabase', db: 'admin' }, { role: 'dbAdminAnyDatabase', db: 'admin' }, { role: 'clusterMonitor', db: 'admin' } ] });"
This is additive — it touches no existing consumer and replicates to bms-3 automatically (the
bms-4 arbiter holds no users). Rollback at this stage is a single db.dropUser('sys-admin-ai').
Step 3 — Verify the new user BEFORE any cutover (the gate)
# rs.status() must succeed as sys-admin-ai (proves clusterMonitor is effective):mongosh "mongodb://sys-admin-ai:$NEW_PW@145.239.133.104:27017,51.68.155.224:27017/admin?authSource=admin&replicaSet=rs0" \ --quiet --eval "rs.status().members.map(m=>m.stateStr)"
If this does not return the member states, the role grant is wrong — stop, fix the role
(db.updateUser/db.grantRolesToUser), and re-verify. Do not proceed to Step 4 until it passes.
Step 4 — Distribute + cut over consumers one at a time
Add a sync-mongodb-bms distribution path (or extend the existing one) in secrets-sync.yml so
MONGODB_SYS_ADMIN_AI_USER/PASSWORD reach the hosts that run the AI consumers (vps-i1 for
p24-status.py; bms-4 for worker maintenance paths). Keep the change additive — do not remove
MONGODB_RS0_ADMIN_* yet.
Repoint p24-status.py to prefer the new credential with a fallback, so the cutover is a no-op
until the key is present and reversible by unsetting it:
mongo_user = os.environ.get("MONGODB_SYS_ADMIN_AI_USER") or os.environ.get("MONGODB_RS0_ADMIN_USER", "")mongo_pass = os.environ.get("MONGODB_SYS_ADMIN_AI_PASSWORD") or os.environ.get("MONGODB_RS0_ADMIN_PASSWORD", "")
(Apply the same to scripts/tests/test-mongodb-connectivity.py.) Verify p24-status.py MongoDB
section still renders after the key lands.
Any AI maintenance path that today reuses root admin for read/write (not user administration)
→ switch to sys-admin-ai. Do not switch the rotation scripts
(scripts/rotate/w{3,4}_app_mongodb_password.sh, mongodb-admin-password-recovery.md) — those need
userAdmin/root and correctly keep the break-glass admin.
Step 5 — No root demotion required for apps; register + log
The app-container demotion in plan §3.4 step 5 is already done (W3/W4/et-op are on their own users).
After Steps 1–4:
Register sys-admin-ai in Supabase dev_r_services (element_type='credential', criticality,
auto_rotate=true once a rotate entry exists) and reflect it in dev_r_ai_systems if it is consumed
by an Annex-III AI system.
Log the new user in secrets-rotation-log.md.
Add a sys-admin-ai auto-rotate entry to scripts/rotate-credentials.py (Tier 1 — its own SOPS
file, its own consumer restart; no cross-consumer fan-out). Tracked as plan §3.5 / P2 follow-up.
Rollback (any step)
The new user is additive, so rollback never touches a live app: unset the new env keys (consumers
fall back to MONGODB_RS0_ADMIN_* via the Step 4 pattern) and db.dropUser('sys-admin-ai') on the
PRIMARY. Because no app container was ever repointed, there is no 1716-class outage vector in this
migration.