An easy-to-host PDS on the ATProtocol, MacOS. Grandma-approved.

feat(relay): V008 migration — nullable accounts.password_hash, pending_did column (MM-89)

authored by malpercio.dev and committed by

Tangled e523544e c1c1e357

+47
+4
crates/relay/src/db/CLAUDE.md
··· 2 2 3 3 Last verified: 2026-03-13 4 4 5 + ## Latest Updates 6 + - **V008**: Rebuilt accounts with nullable password_hash (mobile accounts have no password); added pending_did column to pending_accounts for DID pre-store retry resilience 7 + 5 8 ## Purpose 6 9 Owns SQLite connection lifecycle and schema migration for the relay's server-level database. 7 10 Keeps database concerns out of handler code and provides a reusable pool+migration API ··· 37 40 - `migrations/V005__pending_accounts.sql` - pending_accounts table: pre-provisioned account slots (id, email, handle, tier, claim_code) 38 41 - `migrations/V006__devices_v2.sql` - Rebuilds devices: replaces did FK (accounts) with account_id FK (pending_accounts); adds platform, public_key, device_token_hash; also rebuilds sessions, oauth_tokens, refresh_tokens (cascade due to FK references) 39 42 - `migrations/V007__pending_sessions.sql` - pending_sessions table: id, account_id (FK→pending_accounts), device_id (FK→devices), token_hash (UNIQUE), created_at, expires_at; used by POST /v1/accounts/mobile to issue a pre-DID session for the DID-creation step 43 + - `migrations/V008__did_promotion.sql` - Rebuilds accounts with nullable password_hash (mobile accounts have no password); adds pending_did column to pending_accounts for DID pre-store retry resilience
+39
crates/relay/src/db/migrations/V008__did_promotion.sql
··· 1 + -- V008: DID promotion support 2 + -- Applied in a single transaction by the migration runner. 3 + -- 4 + -- 1. Rebuilds the accounts table with nullable password_hash. 5 + -- Mobile-provisioned accounts (via POST /v1/dids) have no password; 6 + -- only accounts created via POST /v1/accounts have a password_hash. 7 + -- SQLite does not support ALTER COLUMN, so a full table rebuild is required. 8 + -- 9 + -- 2. Adds pending_did to pending_accounts for retry-safe DID pre-storage. 10 + -- Populated by POST /v1/dids before calling plc.directory (pre-store pattern). 11 + -- If the promotion transaction fails after plc.directory accepts the op, 12 + -- a client retry detects this non-NULL value and skips the directory call. 13 + 14 + -- ── Rebuild accounts with nullable password_hash ───────────────────────────── 15 + 16 + CREATE TABLE accounts_new ( 17 + did TEXT NOT NULL, 18 + email TEXT NOT NULL, 19 + password_hash TEXT, -- NULL for mobile-provisioned accounts 20 + created_at TEXT NOT NULL, 21 + updated_at TEXT NOT NULL, 22 + email_confirmed_at TEXT, 23 + deactivated_at TEXT, 24 + PRIMARY KEY (did) 25 + ); 26 + 27 + INSERT INTO accounts_new 28 + SELECT did, email, password_hash, created_at, updated_at, email_confirmed_at, deactivated_at 29 + FROM accounts; 30 + 31 + DROP TABLE accounts; 32 + 33 + ALTER TABLE accounts_new RENAME TO accounts; 34 + 35 + CREATE UNIQUE INDEX idx_accounts_email ON accounts (email); 36 + 37 + -- ── Add pending_did to pending_accounts ────────────────────────────────────── 38 + 39 + ALTER TABLE pending_accounts ADD COLUMN pending_did TEXT;
+4
crates/relay/src/db/mod.rs
··· 56 56 version: 7, 57 57 sql: include_str!("migrations/V007__pending_sessions.sql"), 58 58 }, 59 + Migration { 60 + version: 8, 61 + sql: include_str!("migrations/V008__did_promotion.sql"), 62 + }, 59 63 ]; 60 64 61 65 /// Open a WAL-mode SQLite connection pool with a maximum of 1 connection.