Skip to content

control-plane: add the account functions and router mount that 49bec0a needs - #49

Merged
Viscous106 merged 2 commits into
mainfrom
fix/main-account-wiring
Nov 2, 2026
Merged

Viscous106 merged 2 commits into
mainfrom
fix/main-account-wiring

Conversation

@OfficialAbhinavSingh

Copy link
Copy Markdown
Member

Summary

49bec0a ("say the free-credit limits before a plan is approved") added routes_account.py, user_settings.py, migration 0017_user_settings.sql, and their tests, but never added the code they call: accounts.py had no unlink_wallet, unlink_identity, delete_account, credits_forfeited, or the NotFound/LastMethod/Busy exceptions; Settings had no model_choices; app.py never mounted the new router. main has been returning 500s on drafting a plan and approving a credits plan since that commit: pytest -m postgres was 84 failed / 245 passed, pyright 34 errors.

This PR adds exactly what those files and tests call for, and fixes one pre-existing bug the new tests happened to expose.

What's in it

  • 826983a: accounts.py (the four functions + exceptions), config.py (planner_models / model_choices), app.py (mounts the router), auth.py (the trial route now checks credits_forfeited, which the forfeit test needs and 49bec0a never added), routes_plans.py (one str | None narrowing pyright needed).
  • a7e8994: fixes test_signing_out_other_devices_keeps_this_one, which asserted two sessions from one browser that the OAuth callback was never going to give it (it deliberately links rather than re-sessioning an already-signed-in caller — that's been true since 7ed7c6b, this PR doesn't touch it).

What I verified

Two independent passes (implementer, then a from-scratch reviewer who re-ran every gate and redid 5 of 13 claimed mutation kills personally), both at control-plane/:

  • ruff check, ruff format --check: clean.
  • pyright: 0 errors (34 before).
  • pytest: 341 passed, unchanged.
  • pytest -m postgres: 329 passed, 0 failed (84 failed / 245 passed before).
  • Every anti-abuse/deletion guard mutation-tested: break it, confirm the relevant test fails, revert. 13 kills total, 5 independently redone.
  • Every query in the new accounts.py functions is scoped to the caller's own user_id/addresses and runs inside the transaction the route already opens; the in-flight-plan check uses the same lock pattern approval does.

What I verified is NOT yet safe to expose

Found while reviewing — none of this is new risk from this PR (it's either pre-existing or inherent to 49bec0a's own scope), but all of it should block turning on the account-settings/deletion UI:

  • Account deletion is currently unreachable in production. Caddy only forwards /v0/account/*, which doesn't match DELETE /v0/account. test_caddy_routes.py exists for exactly this and nobody added the new routes to its list. Deliberately left unfixed in this PR — it fails closed, and fixing it now would make the two gaps below reachable live before they're addressed.
  • "Once per person" isn't enforced at all, with or without this PR. A new wallet is already a new account with a new free-credit grant (main's own existing tests show this). Unlinking a wallet/identity compounds it: link a throwaway wallet, unlink GitHub, sign up again with the same GitHub — new account, new trial, no deletion needed. The same unlink also undoes a deletion-forfeit.
  • Redaction gap: a plan drafted on a wallet that's later unlinked keeps its full goal text after the account is deleted — reachable via an ordinary unlink-then-delete, not a race.
  • Minor: an approve racing a delete still completes (deleted user gains nothing but their own leftover credits); a plan mid-draft during deletion survives in full (planner-response-time window); pr_requests.pr_url still names the repo after redaction; rotating SESSION_SECRET silently un-forfeits every past deletion (undocumented).

These need a real decision on how "once per person" should actually be enforced before the account routes go live — happy to open an issue for it, or take direction here.

Type of change

  • fix — gets main back to green

Checklist

  • No secrets committed
  • Claims above are backed by the test counts and gate output quoted
  • Tests added/updated (the one test fix)

…browser session

The callback deliberately links rather than opening a new session when the caller is already
signed in (oauth.py, since 7ed7c6b) -- the test was asserting two sessions from one browser
that was never going to get them.
@OfficialAbhinavSingh

Copy link
Copy Markdown
Member Author

@Viscous106 this unblocks main (currently 500ing on drafting/approving a plan since 49bec0a). Both the fix and the gate numbers are independently verified, details in the description. The one thing in there that's your call, not something I fixed: the trial-abuse and redaction gaps under "What I verified is NOT yet safe to expose" — deletion is deliberately left unreachable (Caddy) until that's decided. Ready to merge whenever you've looked.

@Viscous106
Viscous106 merged commit b8ff365 into main Nov 2, 2026
1 check passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Kanban Nov 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants