Neon replacement login preflight
Superseded operational status: the owner subsequently authorized the credential switch. It completed at 23:05 UTC; see production cutover results. The remainder of this file is the historical preflight, not the current production state.
Checked September 12, 2026, 22:43 UTC. The owner created yumina_mycelium in the Neon Console and supplied its credential. The credential is saved in the existing ignored Windows-DPAPI store; it is not in this document or source control.
Verified
- Replacement authentication succeeds through the existing primary and read-replica poolers, using verified TLS. Primary reports
pg_is_in_recovery() = false; replica reports true. Both accessneondb. - All 207 public tables pass SELECT/INSERT/UPDATE/DELETE privilege checks. Critical-table SELECT statements were planned with LIMIT 0, without exporting user records. Actual production writes were not tested.
- The new role has Neon's console-created administrative attributes, including BYPASSRLS. It is not a restricted runtime role.
- The new role lacks CREATE on the public schema, USAGE on 28 public sequences, and ownership privileges on all 207 existing tables. It lacks the existing role's timeouts: statement 300s, lock 20s, idle-in-transaction 60s.
- Both live Railway connection variables still use
neondb_owner. Of eight inspected production services, direct PostgreSQL variables were found onyumina(primary and read) andamiable-truth(primary). The other six, including the analytics worker, have no PostgreSQL URL variables. This does not certify external jobs, local tools or CI credentials. - Logical replication is still off (
wal_level=replica).
Additional release prerequisite
Do not switch Railway merely because the new login authenticates. The deployed server still performs table/index/function maintenance at startup, so ordinary data permissions are insufficient.
An attempted GRANT neondb_owner TO yumina_mycelium in a rollback-only transaction failed with PostgreSQL 42501. No grant was committed. Neon's managed roles cannot directly administer that existing role's membership using the current credential.
Neon documents a shared ownership role approach: create a NOLOGIN group, grant it to both logins, and move the relevant owned objects into that group. For this application, retaining a stable yumina_schema_owner group would allow the old login to be retired without moving ownership again on each rotation.
An isolated PGlite fixture reproduced the failed direct grant and passed the shared-role approach: old and new logins retain serial-ID inserts; the new login can alter/index a table and replace a routine; migration objects created under the group work for both logins; changing the old fixture role to NOLOGIN does not remove the replacement's privileges. This is a representative local proof, not a production ownership migration or a full-server test under that role.
Exact migration preparation completed at 22:49 UTC
packages/server/scripts/lib/neon-role-cutover.mjs collects read-only catalog metadata and generates three SQL files. It has no database client and never applies a plan. The prepared production manifest names exactly 237 relations (208 tables and 29 sequences) and 16 application routines. It excludes extension members and objects owned by cloud_admin; it does not reassign database/schema/extension ownership wholesale.
The SQL checks the expected database/login/primary, exact object inventory, object OIDs/names/owners, and group attributes. It uses a transaction, 2-second lock waits, 15-second statement limits and a 30-second transaction limit. A separate script, connected as the replacement login, copies its three timeout defaults and adds default grants. The rollback restores listed ownership as the original login while retaining grants/group for retry; use it before retiring that login.
packages/server/scripts/verify-neon-role-cutover.mjs --schema <local schema export> passed against the complete local application schema: 137 fixture relations, five routines and two test types. Checks include provider-object exclusion, quoted identifiers, stale-inventory refusal, transaction rollback, replay, explicit reverse migration, both logins' sequence/table access, actual cleanup/recovery function replacement, recovery execution with preserved balance, timeout defaults, and access after disabling the old fixture login. The fixture substitutes a built-in SHA-256 shim for unavailable PGlite pgcrypto; it is not a full Linux-server/Neon rehearsal under the new role.
Startup-created objects receive default table/sequence/function grants to the shared group, preserving the old login's data access during overlapping deployments. They remain owned by their creating login. Explicit future schema migrations should SET ROLE yumina_schema_owner so both logins can maintain those objects. Regenerate/review the manifest after additive release migrations or other ownership/schema changes; the stale-inventory guard intentionally rejects the current plan after new objects appear.
The repository's PROD_DATABASE_URL GitHub Actions secret exists. Current callers include daily recovery, WeChat renewal reminders, auth/playtest cleanup and recommendation training. Its value cannot be read back through GitHub's API; it must be replaced and a relevant job verified during rotation. The authenticated GitHub CLI can perform that update, so no extra secret-copying task is currently needed from the owner.
Before applying the ownership change:
- Regenerate and re-review the prepared exact application-object migration for
publicanddrizzleafter the additive release DDL. Inventory: 207 public tables, 28 public sequences, one Drizzle table/sequence, and 16 non-extension public routines owned byneondb_owner. No public SECURITY DEFINER routines or forced-RLS tables were observed. - Keep Neon-managed objects separate: seven public views and 212 extension routines belong to
cloud_admin. Five extensions belong to the old role. Do not use a blanketREASSIGN OWNEDwithout reviewing extension, schema, database and shared-object effects. - Use bounded lock waits, preserve the existing login's access, and validate rollback. Ownership updates can lock tables even though they do not rewrite user data; schedule them with the cutover.
- Copy the verified timeouts to the new login. Validate both runtime access and the actual startup maintenance with the intended ownership/default-privilege strategy. Future migrations should create objects under the stable group; startup-created objects also need an explicit ownership/access policy.
- Re-inventory non-Railway consumers and only then stage service variables. Retire the old credential after replacement services pass; do not delete an owning role or extension dependencies.
Production ownership, permissions, old credentials, Railway variables, deployments and replication settings were not changed in this preflight. The user has no further credential-copying task for the new application login. The ownership preparation and service switch remain implementation/release work.
Local evidence
Ignored directory packages/server/scripts/admin-analytics.local/:
neon-replacement-verification.local.json: primary/replica privilege results.neon-replacement-permission-preparation.local.json: rejected direct grant, rolled back.neon-role-ownership-fixture.local.json: isolated ownership proof.neon-cutover-inventory.local.json: sanitized Railway and object inventory.neon-role-plan/manifest.json,01-ownership.sql,02-replacement-settings.sql,rollback-ownership.sql: exact prepared production catalog plan; not applied.../admin-release.local/neon-role-cutover-verification.json: complete local application-schema role rehearsal.
