Let users claim unowned developer profiles - #154
Merged
Conversation
Adds a claim/approve/reject flow so a signed-in user can request
ownership of a legacy (owner_user_id IS NULL) profile instead of
needing a moderator to do it out-of-band. A claim never changes
ownership by itself; approval moves ownership atomically and
auto-rejects any other pending claims on the same profile.
Also fixes an unrelated pre-existing typecheck failure in
versions/v1: bearerAuth({ token }) couldn't infer its Env generic
from the options object and fell back to the default Env, which
didn't match the route's VersionsEnv context.
Claude-Session: https://claude.ai/code/session_01Co1bu2gU5kEHN65KooNUHN
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
api | a7af6f7 | Commit Preview URL Branch Preview URL |
Jul 26 2026, 05:03 PM |
Contributor
There was a problem hiding this comment.
All reported issues were addressed across 7 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
- claim(): fold the "author still unowned" and "claimant owns nothing else" checks into the INSERT itself (EXISTS/NOT EXISTS), closing a TOCTOU window where a claim could be created just after the profile was claimed or transferred elsewhere. - approveClaim(): wrap the preflight ownership reads in try/catch so a DB failure there returns a structured DATABASE_ERROR instead of throwing; move the batch-support check before any mutation; guard the ownership UPDATE with `owner_user_id IS NULL` and gate the auto-reject-competing-claims statement on `changes() = 1`, so two concurrent approvals of different pending claims on the same profile can't both move ownership (the loser now reverts its claim to pending and returns CONFLICT). - Migration: add a partial index on (created_at) WHERE status = 'pending' so the moderator queue query doesn't need a separate sort as claim history grows. Verified the ownership race fix against real local D1 by manually interleaving two claims' status-transition and ownership-UPDATE statements in the vulnerable order. Claude-Session: https://claude.ai/code/session_01Co1bu2gU5kEHN65KooNUHN
Contributor
There was a problem hiding this comment.
0 issues found across 3 files (changes from recent commits).
Requires human review: Adds a new claim flow for profile ownership with a database migration and new public endpoints. This is a new feature with product and operational tradeoffs (moderator approval, pending claim enforcement) that require human sign-off.
Re-trigger cubic
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.
Summary
owner_user_id IS NULL) instead of needing a moderator to hand it over out-of-band.0007_add_author_claims.sql:author_claimstable with a partial unique index enforcing one pending claim per (author, claimant).POST /authors/{id}/claim,GET /authors/claims/mine,GET /authors/claims(moderator),POST /authors/claims/{id}/approve(moderator),POST /authors/claims/{id}/reject(moderator).versions/v1/index.ts:bearerAuth({ token })wasn't inferring itsEnvgeneric from the options object and fell back to the defaultEnv, which didn't match the route'sVersionsEnvcontext.