drop old platform ID column

This commit is contained in:
Devin Zuczek
2026-08-03 22:36:27 -04:00
parent 821bf54b9b
commit d298977790
4 changed files with 49 additions and 12 deletions
+4 -1
View File
@@ -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
@@ -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;
+13 -5
View File
@@ -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 {
+9 -6
View File
@@ -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.