Repository navigation
feat(db): Patreon, LuckPerms and diagnostics on native Postgres — #251 - #258
Merged
Merged
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Preserve the conversion recovered from the interrupted Claude session: native queries and types, required locks, atomic audit rows, race and wire-format tests, and the snapshot benchmark. Co-authored-by: Claude <noreply@anthropic.com>
Recheck sync lease expiry after waiting for the Patreon lock. Keep database calls off async route event loops, preserve literal Unicode escapes in diagnostic reports, and isolate actor lock order, token CAS and code-collision races. Resumed and verified by Codex (gpt-6.1-sol). Co-authored-by: Codex <noreply@openai.com>
Lock actor users before sessions and retain revalidation after waits. Evaluate Patreon credential and lease expiry after row locks, preserve full lease TTLs and owner/token CAS, and recheck later confirmation, token, session and poll waits. Prove both real staff-operation races and held-row expiry with controlled clocks and observed Postgres waits. Includes focused, affected and mutation proof for independent Sol round 1. Co-authored-by: Codex <noreply@openai.com>
ryanbarlow97
force-pushed
the
pg-area-patreon-lp
branch
from
October 16, 2026 23:38
a4f48ed to
23715a3
Compare
Check pending change age after settlement and candidate locks. Acquire required write locks before credential clocks, roll back expired confirmation mutations atomically, and validate the held sync lease after all writes. Keep ordinary confirmation-error spending, owner/token CAS and the existing lock order. Promote and extend the independent Sol R2 probes with observed Postgres waits. Validate real failure-writer overlap, fresh TTLs, post-audit session expiry and rollback snapshots; all 14 isolated mutations are detected. Co-authored-by: Codex <noreply@openai.com>
This was referenced Oct 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Converts Patreon, LuckPerms and diagnostic reports to native Postgres for #251, following the recipes in #252 and the accounts conventions in #253. Changes to LuckPerms and their audit entries now commit together. A conditional
UPDATE … RETURNINGspends each Patreon confirmation token once, fixing the ignored-row-count double-confirm bug.This resumes the interrupted conversion in the existing worktree. The recovered Claude implementation is preserved in
28be41c6with Claude attribution.69d50019contains only the resumed Codex/Sol changes, with Codex attribution. Existing foundation/accounts authorship is unchanged.Files and types
luckperms/{mirror,changes,commands,views}.py: native queries, COPY for complete snapshots, JSONB and boolean values, timestamp conversion, consistent reads, mirror/change locking and atomic staff audit entries.patreon/{service,client,linking}.py: native queries, leases, creator-token CAS, advisory locking, OAuth and confirmation consumption, JSONB outboxes, timestamp and boolean handling.diagnostic/reports.pyandapi/diagnostic_routes.py: native report storage and collision retries. Reject JSON values Postgres cannot store; preserve literal Unicode escapes.api/patreon_routes.pyand the diagnostic POST move blocking database calls to the thread pool.auth/users.session_user_inandauth/audit.record; moveauth/account_overview.rankto the Postgres mirror. Their callers now use the same transaction. The obsolete SQLite-bridge test is removed; the rank test takespostgres.backend/benchmarks/luckperms_snapshot.pyreproduces the snapshot timing.Recipes and proof
Races use separate real Postgres connections and assert
pg_stat_activity.wait_event_type = 'Lock', with the expected advisory or row wait, before releasing the holder. Tests live inluckperms/test_locking.py,patreon/test_locking.pyanddiagnostic/test_routes.py.test_two_snapshots_apply_one_after_the_other.lp_state, takes itFOR SHARE, then locks the actor session/user (X1), then change rows. One consistent mirror:test_a_change_is_checked_against_one_mirror. One open target change (partial unique index C):test_two_changes_to_one_target_queue_once. Atomic audit and demotion exclusion:test_a_demotion_waits_for_a_change_and_its_audit_row.lp_statefirst; pending actors are locked in id order before any change settlement._current_actoritself readsFOR SHARE. Tests:test_a_poll_locks_actors_before_timing_out_changes,test_current_actor_itself_waits_for_a_demotion,test_a_demotion_landing_first_cancels_the_change,test_a_demotion_waits_for_a_change_being_handed_out.test_a_change_is_handed_out_once,test_two_polls_hand_a_change_out_once.lp_statebefore change rows, then settle with a conditional update/RETURNING. Both directions are exercised bytest_a_result_and_a_poll_never_deadlockandtest_a_poll_and_a_result_never_deadlock;test_a_result_settles_a_change_onceisolates B. Timeout updates retain the expected status:test_a_timeout_never_overwrites_a_result_landing.patreonadvisory lock around every compute/plan path and every listed X4 site:test_every_recompute_takes_the_patreon_lock_firstparametrises create/update, recompute, unlink, brake release, staff link, import, dispatch, ack, full sync, member refresh and confirmation. Dispatch versus cancellation and subject uniqueness also race intest_a_dispatch_never_sends_a_change_being_cancelledandtest_two_patrons_cannot_claim_one_discord_account.ON CONFLICT … DO UPDATE … WHERE expires_at <= … RETURNING owner):test_an_expired_lease_is_taken_once.FOR UPDATE:test_a_sync_stores_nothing_once_its_lease_is_taken_over. Expiry is rechecked after waiting for X4:test_a_sync_rechecks_lease_expiry_after_waiting_for_the_patreon_lock.FOR UPDATEand token CAS:test_a_token_refresh_waits_for_a_lease_takeover,test_a_token_refresh_never_overwrites_tokens_rotated_meanwhile; both observe a concurrent row wait._sync_failurebegins with its atomic counter UPDATE/row lock:test_two_failures_count_twice_and_alert_once.alert()usesON CONFLICT (kind) WHERE acked_at IS NULL DO NOTHING:test_an_alert_raised_twice_at_once_is_kept_once.test_an_oauth_state_is_spent_once. Confirmation B:test_two_confirmations_link_once,test_a_cancel_and_a_confirm_cannot_both_spend_a_token, andtest_simultaneous_confirmation_consumes_token_onceintest_linking.py.test_patreon_is_called_with_no_transaction_open, plus the token-refresh mock HTTP handlers' transaction checks. HTTP finishes before the write transaction opens.test_two_reports_retry_a_colliding_codeobserves the conflicting insert wait and successful retry.22 deliberate mutations were checked in an isolated source copy, restoring each after its test. Every mutation was detected: X5, queue SHARE, open-change index, X1, actor pre-lock order,
_current_actorSHARE, poll/result/timeout B, all 11 X4 sites, lease C, both lease row locks, token CAS, failure counter, alert upsert, OAuth B, old ignored-row-count confirmation logic, HTTP inside the transaction, expiry recheck, and diagnostic collision upsert. Removing the result's initiallp_statelock reproduced a real Postgres deadlock. The reverted confirmation logic produced two successful confirmations. Mutation artifacts remain local under.scratch/sol/mutations/.Output compatibility
luckperms/test_wire_format.pyandpatreon/test_wire_format.pypin responses captured from the SQLite implementation at7d2a1ee7. TFMCWeb snapshot/unchanged/poll/results replies and Patreon plugin, Discord-bot and status responses remain byte-identical, including operation key order, outbox order, flags and timestamps. LuckPerms retains…Z; Patreon retainsdatetime.isoformat()(…+00:00, including microseconds). The commands-view wall-clock timestamp is normalised for comparison.Staff Patreon lookup and diagnostic report JSON objects retain their values but may have JSONB key order; those are compared structurally. Lookup's
activeremains 0/1. Repeated grace periods and link-event identities retain their stored string form, avoiding spurious generation changes and DMs.Migration marks and integration limits
Removed all six LuckPerms-owned marks: the accounts
session_user_inandaudit.recordSQLite bridges, account-overview rank, LuckPerms_account,_target_account_roleand_current_actorbridges. No remainingPG-MIGRATIONmark names LuckPerms, Patreon or Diagnostics.Added four Linking-owned marks, for reads of
discord_links/discord_link_codesinchanges.discord_link,views._account,service._discord_linksandlinking._known_name. These remain SQLite reads until the parallel Linking PR converts them, so those cross-store link reads are not yet atomic with Postgres transactions. No other area is converted here; no schema migration, deployment or merge is included.Original conversion timings and test results (before round 1)
Local
pgvector/pgvector:pg17, port 55437, Python 3.12. A 2,000-user snapshot contains 20,000 player nodes, 40 groups (1,199 group nodes) and 6 tracks; seven replacements over a populated mirror include parsing, locking, deletes and writes:Measurements were on the shared local test host while verification ran; they are comparative timings, not production latency guarantees.
postgresbaseline (7d2a1ee7): 2,454 passed, 4 skipped, 175 subtests passed in 1,518.88 s. Reused the completed/tmp/ps-patreon-baseline.log; did not repeat it.src/luckperms src/patreon src/diagnostic src/auth): 676 passed in 775.57 s.TEST_DATABASE_URLon port 55437 ata4f48ed9: 2,503 passed, 4 skipped, 175 subtests passed in 1,301.17 s (21m41s), an increase of 49 passing tests over the saved baseline.git diff --checkpasses. Test-only fixture imports follow the existing pytest pattern.No external Patreon requests were made; HTTP is mocked. No SSH, manual deployment, merge or changes to other area branches. The requested branch push triggered the repository’s automatic Preview workflow (run 38979079517). It was cancelled to honour the no-deploy instruction, but its logs show
pv-pg-area-patreon-lp-{backend,frontend}-1had started before cancellation took effect. The parent must check/clean up that automatic preview; no SSH was used here. The named local test containerps-patreon-pgwas stopped after verification; no other container was stopped.Independent Sol round 1 — implementation follow-up
Rebased the existing conversion onto
9a3835bbdd55e55c941470a57ff2b0c7b83021d0(merged ledger #254), preserving author names, attribution and author dates. The two preserved conversion commits are now28be41c6and69d50019; this follow-up is Codex/Sol work.Follow-up HEAD:
23715a30effa06c640208c0f3cf041e5531beb5f. All three reproduced P2 findings are fixed:auth/users.py:session_user_in(lock=True)first locks only the user row through a non-locking session lookup, then locks the session. The second query binds the session to that already-locked user, re-reads role/session data and checks expiry after any wait. Ordinary reads still take no row lock. LuckPerms keepslp_statefirst andlp_changeslast; its final expiry re-read takes no new row lock. Successful changes and their audit entries still commit together. Expiry after a later change-row wait rolls back the queued change and records a refusal.consume_stateand consuming_pendinglock the token row before reading a fresh clock, then use the existing conditionalUPDATE … RETURNINGin that transaction. The double-confirm/cancel guard remains. Confirmation also checks fresh expiry after waiting for X4 before linking; a token already spent before that wait remains spent. New OAuth states and confirmations compute their full TTL after cleanup waits.The analogue audit also moved token-refresh expiry/deadline checks after both lease and token row locks, sync time after the X4/lease waits, default link timestamps after X4, and LuckPerms poll timeout evaluation after state/actor locks. No external HTTP was moved under locks. Mock handlers assert that application transactions are closed when they run.
Deterministic proof:
patreon/test_expiry_locking.pyand the new cases inluckperms/test_locking.pyadvance a controlled clock only afterpg_stat_activityconfirms the competing backend's real row/advisory lock wait. They test realadmin.change_roleandadmin.revoke_sessionsin both directions, user/session expiry, later change-row expiry, OAuth/confirm/cancel expiry, X4 confirmation expiry, acquisition behind insertion/release, expired/live renewal and full TTLs, owner revalidation, token-row expiry, and sync/mint/link/poll analogues. Existing wire-format and atomic-audit checks remain in the affected gate.DeadlockDetectedagainst both actual staff paths. Removing fresh clocks/row locks or the original owner/token/used-at guards made the corresponding regression tests fail. Mutation sources were restored after each probe; they never modified the implementation checkout. Evidence:.scratch/sol-round1/.Shared integration hunk: only
auth/users.py:session_user_inis newly changed outside the owned areas. When integrating the Skins branch's separate auth edits, retain its unrelated changes and preserve this user-before-session lock sequence, the expected-user predicate and the fresh expiry check..scratch/sol-round1/auth-integration.patchcontains that hunk independently. The existing four Linking-owned bridges still await the parallel conversion; no bridge is added back here.No new schema changes, API fields or time formats. No agents, SSH, manual deployment or merge. The automatic Preview run from this push (38982462894) was cancelled before deployment; full Build is left to the parent without polling it. Only
ps-patreon-pgwas used locally, and it is now stopped.Independent Sol round 2 — implementation follow-up
Current follow-up HEAD:
d24731b0e8e6ce332ea47845c4480c251022d2b4. The existing conversion and round-1 authorship are preserved on base9a3835bbdd55e55c941470a57ff2b0c7b83021d0. This follow-up is Codex/Sol work. Earlier sections' test counts and HEADs are historical; the evidence below describes this follow-up.All four round-2 findings are addressed:
lp_stateremains first, users/sessions precedelp_changes, and dispatch keeps its status CAS. The analogue check also moved queueing's final session-expiry validation after the atomic audit insert; an audit-table wait cannot commit an expired-session change.ROW EXCLUSIVEtable lock their write requires before row locking and sampling UTC. This mode is compatible with ordinary concurrent DML; it adds no global permissions lock. It closes the reviewer's controlledSHARE-table interference case, which permits a row lock while blocking the later write. Owner, token and single-use CAS guards remain. Valid renewals and rotated tokens receive a full TTL measured after the wait. Acquisition already takes its actual write/row locks through the seed upsert before its time sample._sync_failurehandler still records failure metadata, including when another real failure writer caused the wait; its committed counter is preserved. The lease row remains held, preventing ownership changes until the transaction ends.Regression proof and provenance:
patreon/test_later_waits.pypromotes and extends the independent reviewer's nine probes from/tmp/sol-review-patreon-r2-probes.py. Every clock advance follows an observed real Postgres wait inpg_stat_activity. Tests include link/outbox and member/outbox row waits, controlled table-lock waits, the actual_sync_failurewriter overlap, full entitlement snapshots on rollback, expiry/error token-spend distinctions, timeout read/poll waits and full fresh TTLs. The table-lock probes are controlled interference, not claims that ordinary application DML takesSHAREtable locks. Existing actual staff demotion/revocation, wire-format, privacy and atomic-audit tests remain in the affected gate. R1's sharedauth/users.pyhunk is unchanged; no further shared auth edits are introduced.git diff --checkpass. Local evidence:.scratch/sol-round2/, including affected JUnit XML and each mutation's failure log. No duplicate local full-backend suite: exact-head Build is triggered by the push and left to the parent to monitor; its result is not claimed here.No schema, API-byte or timestamp-format changes; the existing four Linking-owned bridges remain. No new agents, SSH, merge or manual deployment. Only
ps-patreon-pgwas used for local Postgres and it is stopped. The push's automatic Preview run 38984190405 was cancelled before deployment. No unresolved implementation doubts; the four findings are ready for a new independent review, with full Build still owned by the parent.