fix(beta): grant free premium on conflict so the first ~500 users are unlocked#99
Merged
Merged
Conversation
…ocked The 'first ~500 users get Full Moon free' beta grant was built in ensure_profile (BETA_TOTAL=500) but never fired. The grant ran only on the fresh-INSERT branch, yet the registration handlers in api/auth.py create the profiles row first, so ensure_profile always hit ON CONFLICT DO UPDATE SET email — leaving every user on premium=false and routing them to the Stripe paywall (FullMoonPage.jsx reads profile.premium). Grant on the conflict branch too: set premium/is_beta when a slot remains AND the row is not already premium or beta. This unlocks new registrations and retro-grants the users who were defectively denied on their next authenticated request (/me/profile runs ensure_profile then reads premium in the same request). Existing grants and paid premium are never revoked (OR with current value); a paid user is never relabelled beta (grant requires NOT premium); the 500 cap is enforced by the remaining-slots subquery. Also add migration 029 to record profiles.is_beta in version control: the column is read/written by ensure_profile and the /beta endpoint and cited by ADR 0012, but was created directly on prod and had no migration. Idempotent, no-op on prod. ADR 0012 corrected to note the grant was dead (its Context/Consequences had described it as live). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
Problem (the reported symptom)
A registered account is sent to the Stripe payment step for Full Moon instead of having it unlocked, even though beta slots remain.
Root cause (clear defect, not an undecided policy)
The "first ~500 users free" grant was built —
ensure_profile(api/main.py),BETA_TOTAL = 500, the public/betaslots endpoint, and ADR 0012 all assume it is live. But it never fired:ensure_profile's fresh-INSERTbranch.api/auth.pycreate theprofilesrow first (with no premium/beta).ensure_profileruns (/me/profile,/me/results,/results) the row already exists and onlyON CONFLICT DO UPDATE SET emailexecuted → every user stayedpremium = false.FullMoonPage.jsxreadsprofile.premium; false → paywall.This is a wiring defect against intended behaviour, so a fix is appropriate. (The enforcement side —
require_premium— and the grant's sunset/expiry remain genuinely open in ADR 0012; this PR does not decide them.)Fix
Grant on the conflict branch too: set
premium/is_betawhen a slot remains AND the row is not already premium or beta./me/profilerunsensure_profilethen readspremiumin the same request).ORwith the current value).NOT premium).Semantic note for ADR 0012: because
is_betawas ~0 in prod (the grant had never run), this shifts "first 500 to register" to "first 500 to make an authenticated request after the fix." Bounded and consistent with the early-adopter intent; flagged in the ADR.Migration 029 (idempotent, no-op on prod)
profiles.is_betais read/written byensure_profileand/betaand cited by ADR 0012, but was created directly on production and never had a migration. 029 records it (add column if not exists … default false) so the schema is reproducible. Not strictly required for the fix (prod already has the column); apply via the sanctionedapply-migrations.ymlat your convenience.Tests
New
api/tests/test_beta_grant.py: regression guard that the conflict branch grantspremium+is_beta(not email-only), plus a truth-table test of the grant boolean (grant eligible, cap exhausted, already-beta unchanged, paid never revoked/relabelled). No DB in CI, so the SQL boolean is mirrored in Python. Full backend suite green.ADR 0012 Context/Consequences corrected (they described the grant as live).
🤖 Generated with Claude Code