From d298977790562435991bb5b10d97ed9f31577d5e Mon Sep 17 00:00:00 2001 From: Devin Zuczek Date: Mon, 3 Aug 2026 22:36:27 -0400 Subject: [PATCH] drop old platform ID column --- apps/auth/README.md | 5 +++- .../0008_account_drop_platform_id.sql | 23 +++++++++++++++++++ apps/auth/src/platform-db.ts | 18 +++++++++++---- packages/domain/src/accounts-db.ts | 15 +++++++----- 4 files changed, 49 insertions(+), 12 deletions(-) create mode 100644 apps/auth/migrations/0008_account_drop_platform_id.sql diff --git a/apps/auth/README.md b/apps/auth/README.md index 1a64c60..5ee1c60 100644 --- a/apps/auth/README.md +++ b/apps/auth/README.md @@ -98,7 +98,10 @@ cached login. The account blob keeps `platform`/`platformId` as the account's **primary** identity (the first one linked). It feeds the account DTO and a refreshed token's claims, and -nothing authorizes off it. +nothing authorizes off it. It is no longer indexed: migration 0008 drops the +`account.platform_id` generated column that 0004 added, since leaving a queryable copy +of one identity per account invites exactly the picker/grant disagreement above. Look +identities up in `platform_account`. ## Signup caps diff --git a/apps/auth/migrations/0008_account_drop_platform_id.sql b/apps/auth/migrations/0008_account_drop_platform_id.sql new file mode 100644 index 0000000..01ab054 --- /dev/null +++ b/apps/auth/migrations/0008_account_drop_platform_id.sql @@ -0,0 +1,23 @@ +-- Drop the `platform_id` generated column added by 0004. Nothing reads it any more: +-- 0007 moved every account ↔ identity link into `platform_account`, which is now the +-- one source of truth for the login picker and the `cached_login` grant. The column's +-- last reader was 0007's own backfill, which has already run. +-- +-- Leaving it would leave a SECOND, stale answer to "which account does this identity +-- open?" — it only ever holds the account's primary identity, so an account reachable +-- from a PC and a headset appears here under one of them. That is exactly the split +-- that used to have the picker offer an account the grant then refused. +-- +-- The underlying `platformId` in the JSON blob STAYS: it is the account's primary +-- identity, and feeds the account DTO and a refreshed token's claims. This drops the +-- generated column and its index only — a virtual column stores nothing, so no account +-- data is rewritten or lost. The index has to go first; SQLite refuses to drop an +-- indexed column. Kept in sync with SCHEMA_DDL in @repo/domain's accounts-db.ts. +-- +-- Safe to run before or after the deploy that ships it: no worker queries this column, +-- so the currently-deployed code doesn't notice it go. (`PLATFORM_BACKFILL_SQL` in +-- src/platform-db.ts still names it in 0007's text — that statement has run and won't +-- run again; the exported copy selects the blob instead so tests keep working.) + +DROP INDEX IF EXISTS idx_accounts_platform_id; +ALTER TABLE account DROP COLUMN platform_id; diff --git a/apps/auth/src/platform-db.ts b/apps/auth/src/platform-db.ts index 4fbcc30..d4bf128 100644 --- a/apps/auth/src/platform-db.ts +++ b/apps/auth/src/platform-db.ts @@ -39,22 +39,30 @@ export const PLATFORM_SCHEMA_DDL: string[] = [ ] /** - * The one-time backfill 0007 runs after creating the table: every identity already - * bound to an account becomes a link, so nobody loses their cached login at deploy. - * Exported so a test can run exactly the statement the migration does. + * The one-time backfill 0007 ran after creating the table: every identity already bound + * to an account became a link, so nobody lost their cached login at deploy. It has run; + * this exists so a test can still exercise it, which is the only coverage that legacy + * blob-bound accounts get a link at all. * * `platform` is COALESCEd to 0 because nothing ever defaulted that field — an account * can carry a platformId with no platform recorded, and back when Steam was the only * verifiable platform an unset one *was* Steam. + * + * NOT byte-identical to the migration any more, deliberately. 0007 selected the + * `account.platform_id` generated column; 0008 drops it, so that text is unrunnable + * against the head schema the tests build. This selects the blob directly instead — + * the same values, since the dropped column was DEFINED as + * `json_extract(data, '$.platformId')`. 0007 is left exactly as it ran on prod. */ export const PLATFORM_BACKFILL_SQL = `INSERT OR IGNORE INTO platform_account (account_id, platform, platform_id, linked_at) SELECT account_id, COALESCE(json_extract(data, '$.platform'), 0), - platform_id, + json_extract(data, '$.platformId'), COALESCE(json_extract(data, '$.createdAt'), '1970-01-01T00:00:00Z') FROM account - WHERE platform_id IS NOT NULL AND platform_id <> ''` + WHERE json_extract(data, '$.platformId') IS NOT NULL + AND json_extract(data, '$.platformId') <> ''` /** One account ↔ platform identity link. */ export interface PlatformLink { diff --git a/packages/domain/src/accounts-db.ts b/packages/domain/src/accounts-db.ts index 3173910..664405b 100644 --- a/packages/domain/src/accounts-db.ts +++ b/packages/domain/src/accounts-db.ts @@ -11,18 +11,20 @@ * it from `@repo/domain` (each uses the subset it needs). */ -/** Schema DDL (mirror of migrations 0001_accounts + 0002_avatar, sans seed INSERTs). */ +/** + * Schema DDL — the head schema, i.e. what the table looks like after every migration + * (0001_accounts + 0002_avatar, sans seed INSERTs; 0004 added a `platform_id` generated + * column and 0008 dropped it again, so it appears here in neither form). + */ export const SCHEMA_DDL: string[] = [ `CREATE TABLE IF NOT EXISTS account ( data TEXT NOT NULL, avatar TEXT, account_id INTEGER GENERATED ALWAYS AS (json_extract(data, '$.accountId')) VIRTUAL, - username_lower TEXT GENERATED ALWAYS AS (lower(json_extract(data, '$.username'))) VIRTUAL, - platform_id TEXT GENERATED ALWAYS AS (json_extract(data, '$.platformId')) VIRTUAL + username_lower TEXT GENERATED ALWAYS AS (lower(json_extract(data, '$.username'))) VIRTUAL )`, `CREATE UNIQUE INDEX IF NOT EXISTS idx_accounts_account_id ON account (account_id)`, `CREATE INDEX IF NOT EXISTS idx_accounts_username_lower ON account (username_lower)`, - `CREATE INDEX IF NOT EXISTS idx_accounts_platform_id ON account (platform_id)`, ] /** Client-facing account shape (camelCase, exactly as the client's AccountDTO). */ @@ -225,8 +227,9 @@ export async function searchAccounts( * * Reads `deviceId` straight out of the JSON blob, so this is a table scan — no * generated column, no migration. Fine at our account count and for the occasional - * linkup lookup this exists for; if it ever gets hot, promote `deviceId` to an - * indexed generated column the way `platformId` is (see the 0004 migration). + * linkup lookup this exists for; if it ever gets hot, promote `deviceId` to an indexed + * generated column (migration 0004 did that for `platformId`, and 0008 undid it once + * nothing queried it — that pair is the recipe both ways). * * The device id is unverified client input, so treat a match as a *hint* (these * accounts share a device) and never as proof of identity.