# Identity routing and recovery

Decision date: 2026-10-12. Status: provisional, reversible implementation. Human reviewer/date/outcome: unsigned. Independent automated source/protocol review: search_spend_audit agent, 2026-10-12, no demonstrated critical uniqueness/CAS defect; fixed shard count, absent maintenance scheduling, retention, and explicit fresh-layout migration limitations retained. Additional independent automated review: semantic_runtime agent, 2026-10-12, found and reproduced a confirmed-commit response race with SQLite; outcome propagation fix reviewed sound and all 14 identity tests passed. This is not human or production release approval. Production release remains disabled; no production deployment, provider throughput measurement, or migration sign-off is implied.

## Durable routing

The Worker adapter opts into `IDENTITY_LAYOUT=v1-64-fresh`. SHA-256 of the exact canonical lookup key, first digest byte modulo 64, selects a bucket within each independent family: account, handle, email, device, device-code. Names are `identity-v1/<family>/<00..63>`. The identity service already canonicalizes handles, email addresses and device user codes; the repository does not introduce another normalization policy. The existing IdentityDirectory Durable Object class supplies independent persistent instances. Ordinary account reads and CAS updates with unchanged lookup fields touch only their account bucket. Device reads and ordinary updates similarly use only the device bucket. Handle/email/code lookup reads a bucket and verifies the authoritative record and claim token before returning it.

This is 64 account partitions, not an unlimited throughput promise. Hot accounts and keys still serialize, and fixed bucket count bounds scale. Increasing the count or changing hashing requires a new layout and an explicit migration. The Node adapter retains its durable single-process identity repository; Worker horizontal distribution is not a claim about one self-hosted Node process.

## Cross-bucket uniqueness

A short account/device transaction prepares an operation, checking the expected revision and immutable identity fields. It persists the complete proposed record, old/new lookup links and random operation token before writing any lookup claim. A lookup transaction grants a name only if absent; an existing claim is compared by owner and operation token. The account transaction commits only its currently prepared operation. It retains the pending operation until obsolete lookup claims have been conditionally released and cleanup finalized. The same protocol aborts a conflict and releases only claims owned by that operation. No transaction holds a remote call or a global lock.

Reads recover interrupted operations. A claim resolving to an uncommitted record causes bounded recovery of that record; ordinary lookups never return an orphan or a record whose lookup fields or claim token disagree. Claim conflicts may recover their owner, with recursion bounded to two levels. Cleanup compares the exact claim token so a delayed retry cannot delete a later owner's claim. A stale retry can leave an obsolete claim, but the next lookup/claim verifies its owner state and removes it safely. A failed request can already have committed: clients still receive a conflict/error and must reread state. A commit outcome already observed by a caller is carried through cleanup even if a concurrent helper finalizes it and a later revision replaces the latest stored result. The independent SQLite audit tests this healthy-concurrency case. Truly ambiguous failures (including lost RPC responses) still require rereading; the repository does not promise exactly-once request responses.

Handle changes are rejected. Verified email changes claim the replacement before committing the record and release the prior email afterward. Device codes can be reclaimed after the original immutable expiration; changing a device record's code or expiration is rejected. Expired authoritative records cannot resolve through the user-code lookup. Every account/device revision increases by exactly one.

## Recovery and rollout limits

`lifecycleCandidates` traverses one account bucket/page at a time, including prepared records that crashed before their first lookup claim. The caller must persist and advance its opaque cursor; a full pass takes at least 64 pages. Lookup and direct-ID reads provide immediate recovery for reachable pending operations. A create interrupted before claim publication needs this maintenance pass (or a direct-ID retry). This method is a recovery primitive, not evidence that a hosted scheduler has completed a pass. Expired devices and failed-operation record retention have no new purge policy in this change.

The layout option is mandatory: absent/other values fail closed. Existing `directory-v1` and `devices-v1` data are neither read nor automatically moved. Enabling the option is authorized only for a fresh identity namespace or after an offline migration verifies every record, unique lookup, and device expiration before cutover; an existing deployment must not simply set the option. This avoids silent fallback traffic through a singleton and accidental duplicate identities across layouts. The local Worker configuration is a fresh-layout development choice and does not constitute migration of any prior local persistence. Production rollout remains gated by the existing security/release checklist.

## Validation

`tests/identity-sharding.test.mjs` covers distribution, explicit layout gating, parallel handle/email uniqueness, stale CAS, direct account routing, durable interruption at prepare/claim/commit/finalize, lookup and maintenance recovery, conditional email cleanup, device expiry reuse, and immutable device expiration. The test metadata implementation serializes transactions and persists across repository reconstruction. Actual Cloudflare deployment limits and horizontal load behavior require provider-backed validation before capacity commitments.
