Identities for entities did.bot

fix(oauth): charge the DPoP replay share per key, not per account #2577

Merged permadeath.com
targeting main
Merged opened by and assigned to permadeath.com
targeting main

The DPoP replay-window share is now held per DPoP key rather than per account. A sign-in binds its tokens to one key, so each app's sign-in has its own share: an app looping getSession on an atproto-only login fills only its own, and the account's other apps keep writing, refreshing and exchanging codes. A key at its share is still refused alone, with the same default of 2,000 live proofs. The config key keeps its name, [oauth] dpop_proofs_per_account, so existing configs load unchanged; its docs now say it bounds one key.

POST /oauth/token now checks the key's share before it redeems an authorization code. A refused proof no longer spends the code, so the agent's approval is still there to retry with.

The new test fails on main twice over: with the per-account share, app B's write is refused once app A loops getSession; with the share check after redemption, the refused exchange spends the code. No deploy window is needed: this is an ordinary release with no data layout or config change, and rolling back is safe.

Closes https://tangled.org/permadeath.com/didbot/issues/241

🤖 Generated with Claude Code

This pull request has no versions submitted yet.