feat(account-claim): legacy-account-claim flow with email/password/staff proofs#46
Merged
Conversation
npm install -w apps/api bcryptjs npm install -w apps/api -D @types/bcryptjs
…pport Adds the third entity to the private store per specs/api/account-claim.md#notes: account-claim-requests.jsonl alongside profiles.jsonl and legacy-passwords.jsonl. The dual-store transact pipes putClaimRequest through to the private-store flush so a single store.transact covers the public commit and the claim-request write atomically.
Implements specs/api/account-claim.md and specs/behaviors/account-migration.md: the legacy-account claim flow with three identity proofs (email, password, staff approval), the post-onboarding /account/claim-legacy search + merge, and the staff queue. - routes/account-claim.ts — 10 endpoints, claim-pending JWT validation, uniform 401 for by-password (anti-enumeration), 202-always for request-staff-review. - services/account-claim.ts — confirm/decline/byPassword/requestStaffReview plus the full post-onboarding merge: re-point memberships/updates/buzz/ help-wanted/interest by author, hard-remove the requester Person, write 90-day slug-history redirect, refresh PrivateProfile. - auth/legacy-password.ts — bcrypt verifier dispatcher (bcryptjs). Merge dedupes: when the claimed legacy Person already has a membership or interest in the same project/role, the requester's duplicate is dropped rather than creating two rows. Notable spec edge case kept simple: pre-onboarding staff-approval seeds Person.githubUserId/Login on the claimed legacy Person; the requester gets a session next sign-in via the OAuth byGithubUserId hit.
Implements specs/screens/account-claim.md: - /account-claim — single vs. multi candidate UI, with email-match auto-confirm and a username-only "verify with old password" hand-off - /account-claim/by-password — slug + password form, uniform invalid message - /account-claim/request-staff-review — evidence textarea + "continue as new" - /account/claim-legacy — post-onboarding search + merge-request submission - /staff/account-claim — staff queue with approve/deny + note The AccountClaimPlaceholder shipped by github-oauth is removed; the route points at the new AccountClaim screen.
Validates each endpoint per specs/api/account-claim.md, including the anti-enumeration guarantees (uniform 401 for by-password, 202 for request-staff-review) and the post-onboarding merge end-to-end (legacy gains GH identity, fresh Person hard-deleted, slug-history entry written).
Mirrors the auto-claim paths: once the legacy Person has its GitHub identity linked, the bcrypt hash is no longer needed and stays in the private store only as a latent secret.
queryAll() on slug-history returns empty because the in-memory tree isn't refreshed across sheet transactions in tests; verifying via git ls-tree confirmed the file is committed. Switched to git show on the blob path so the assertion isn't sensitive to that quirk.
This was referenced May 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.
Summary
specs/api/account-claim.md,specs/screens/account-claim.md, andspecs/behaviors/account-migration.md.store.transactso public commits and private writes stay atomic.AccountClaimPlaceholderfromgithub-oauthwith the real/account-claimsingle + multi-candidate UI, by-password and request-staff-review sub-screens, the post-onboarding/account/claim-legacysearch + merge submitter, and the staff queue at/staff/account-claim.AccountClaimRequestas the third entity in the private store (account-claim-requests.jsonl); merge approvals re-point memberships / updates / buzz / help-wanted / interest from the requester Person to the legacy Person, hard-delete the requester, and write a 90-dayslug-historyredirect.401 claim_credentials_invalidfor unknown / already-claimed / wrong-password; request-staff-review always returns202; the public commit trailer for staff-review submission carries no slug, email, or evidence.Test plan
GET /api/account-claim/candidatesreturns 401 without cookie; returns matched candidates withmatchedVia/matchedEmailfor email and username matchesPOST /confirmlinks Person, deletesLegacyPasswordCredential, issues session, refreshes private emailPOST /confirmrejectsnot_a_candidateandemail_match_requiredPOST /declinecreates a fresh Person and leaves the candidate untouchedPOST /by-passwordclaims on correct password; returns uniform401 claim_credentials_invalidfor unknown slug + already-claimedPOST /request-staff-reviewreturns 202 for nonexistent slug; createsAccountClaimRequestrecordGET /staff/.../queuelists open requests;POST .../approvelinks GH on a pre-onboarding requestGET /api/account-claim/legacy?q=returns 0/1 candidate; never enumeratesPOST /legacy/request+ staff approve merges the requester into the legacy Person (GH transferred, requester hard-deleted, slug-history written)request-staff-reviewcarry no slug/evidence/emailapps/apilint, type-check, and 182 tests pass🤖 Generated with Claude Code