Adds a checkbox to the handle-entry screen (with a preview of the red
Bluesky LIVE avatar ring, since that's the headline thing being declined)
that requests an OAuth scope without the repo?collection=app.bsky.* write
grants. The read-only rpc:app.bsky.* scopes are kept for now; removing
them entirely (in favor of caching profiles/avatars ourselves) is a
follow-up.
Server side, sessions now track their granted scope via oatproxy (bumped
to the fable/dynamic-scopes branch, which accepts subset scopes at PAR
time, reports honest scope in tokens, and adds the RFC 7662
/oauth/introspect endpoint it always advertised):
- statedb.GetSessionByDIDWithScope scans a user's valid sessions for one
holding a grant, newest first; sessions from before scope tracking
count as full grants, so no data migration.
- The director's live-status writes (the red ring) pick a session with
the app.bsky.actor.status grant and quietly skip when the user declined
it everywhere; startLivestream skips the go-live post the same way.
The frontend learns its own scope from /oauth/introspect after login and
locks the "Create Bluesky post" checkbox when posting wasn't granted.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>