refactor(accounts): the key store upserts on (user, provider, kind)
Merged
CH
5d ago+21+21+01234567890123456789-6-6-0123456789
Step one of P04b in the "Bringing Codex or Claude Code Max plans into Luke" rollout (plan). It is code only, with no migration.
Why two steps
The live deployment (#4180) runs upsertRow with ON CONFLICT (user_id, provider). If the primary key moved to (user_id, provider, kind) in the same PR, nothing unique would remain on (user_id, provider). Key saves on the previous deployment would then fail for the whole time between the build's db:migrate and promotion. So this PR first moves the code off the two-column target. The PK swap follows once this is live.
What changes
upsertRow's conflict target is now(user_id, provider, kind). #4180's unique indexaccount_provider_key_user_provider_kind_idxalready backs it. ThesetWherekind match stays for now. It becomes redundant after the swap.- While the PK is still
(user_id, provider), a save of one kind where the provider holds another kind no longer matches the conflict target. It now fails on the primary key (UniqueViolation).saveProviderKeycatches that and answersfalse(not kept), as before. Soother_kind_heldand the procedures'subscriptionHeldpre-check behave exactly as they do today. - Behavior is unchanged. The existing store and procedure tests cover both paths: the same-kind replace now runs through the three-column target, and the cross-kind refusal through the caught violation.
Next step (separate PR, after this one is deployed)
The migration moves the PK onto #4180's unique index in one ALTER, so nothing is rebuilt and the table never stands without a key:
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM pg_constraint
WHERE conrelid = 'public.account_provider_key'::regclass
AND conname = 'account_provider_key_user_id_provider_pk'
) THEN
ALTER TABLE "account_provider_key"
DROP CONSTRAINT "account_provider_key_user_id_provider_pk",
ADD CONSTRAINT "account_provider_key_pkey" PRIMARY KEY USING INDEX "account_provider_key_user_provider_kind_idx";
END IF;
END $$;
Notes for that PR:
- Generate the migration with
pnpm --filter @luke/database db:generate --name=account_provider_key_primary_key_kind --hints '[{"type":"create","kind":"primary_key","entity":["public","account_provider_key","account_provider_key_pkey"]}]'. Then replace the generated SQL (DROP INDEX …; DROP CONSTRAINT …; ADD PRIMARY KEY …) with the block above. The snapshot names the new PKaccount_provider_key_pkey, which is the nameUSING INDEXgives it. - In the same PR: drop the
uniqueIndexfrom the schema, theotherKindHeldcatch,setWhere, thesubscriptionHeldpre-check andother_kind_held. Add coexistence, deletion and isolation tests, and addkindto the rewrap'swhere.
Checks
pnpm check:affectedran locally. Typecheck and tests passed.discarded-effectwas OOM-killed in the sandbox (exit 137), so it was rerun on its own. CI gates the rest.- The developer waived WORKFLOW.md's Preview/Mac runs for this rollout until the cohort.
Made with Luke