Files
pad/docs
xarmian d95f90850c fix(store): a role change or disable of a vanished user is 404, and the unguarded DisableUser is gone (BUG-3826) (#2196)
* fix(store): a role change or disable of a vanished user is 404, and the unguarded DisableUser is gone (BUG-3826)

Follow-ups to BUG-3811's last-active-admin guard, one unit per the lead:

1. Store.DisableUser wrote disabled_at with no last-admin guard and no
   lock, the one admin-removing writer outside the invariant. Removed.
   Its test callers need the pre-BUG-3349 state (disabled, credentials
   not revoked), so they move to DisableUserLeavingCredentialsForTesting.
2. A user deleted between the handler's read and the write: a promotion
   answered ErrLastAdmin (a false 409 last_active_admin), a demotion a
   plain error (500), and a disable succeeded on nothing (200). All three
   now return store.ErrUserNotFound, which both handlers answer 404. The
   disable takes the users row FOR NO KEY UPDATE after the guard, so a
   concurrent deletion cannot slip between the check and the UPDATE.
3. The race test only held demote or disable open as the first door.
   Account deletion is now held at its user-row lock too, so moving its
   guard after that lock goes red.
4. docs/api.md documents 409 last_active_admin on delete-account, the
   admin role PATCH and disable, and the new 404.

Claude-Session: https://claude.ai/code/session_01Rj4CMyK2EqxerMihbGs3Kr

* docs(api): the vanished-user 404 is promised for a role change only (BUG-3826)

Codex r1 (Low): the plan fields of the same PATCH do not map ErrUserNotFound.

Claude-Session: https://claude.ai/code/session_01Rj4CMyK2EqxerMihbGs3Kr

* docs,test: the delete-account 409 is checked before the Stripe cancel and re-checked after; the race test comment names its contrast (BUG-3826)

Blind hunter r2 (Lows): the docs said the refusal comes before any Stripe
cancel, which is true of the pre-check only; the handler answers 500
partial_delete when the locked re-check refuses after a cancel ran. The race
test's comment read as if it described the other test.

Claude-Session: https://claude.ai/code/session_01Rj4CMyK2EqxerMihbGs3Kr
2026-10-15 08:23:56 -04:00
..