Summary
- Implements POST /v1/devices — device registration via claim code (MM-87)
- V006 migration rebuilds devices to reference pending_accounts.id (registration precedes DID assignment), adding platform, public_key, device_token_hash; cascades to rebuild sessions, oauth_tokens, refresh_tokens
- Device token is 32 random bytes, returned as base64url (no padding), stored as SHA-256 hex — plaintext never persisted
- Claim-code redemption uses UPDATE-and-check-rows-affected in a single transaction (atomic, no race window)
Acceptance Criteria
- Valid claim code registers device and returns { deviceId, deviceToken, accountId } (201 Created)
- Invalid/expired/redeemed code returns 400 INVALID_CLAIM
- Device appears in devices table with correct columns
- Claim code marked redeemed (redeemed_at set) — single-use enforced
- Platform validation: ios, android, macos, linux, windows (case-sensitive)
Test plan
- cargo test --package relay — 143 tests pass
- cargo clippy --workspace -- -D warnings — clean
- Bruno: create account → copy claimCode → POST /v1/devices → verify 201
- POST same claim code again → verify 400 INVALID_CLAIM