Conversation
`desk auth status` reported `missing_scopes: []` for every user because the granted scope set was never persisted, so the proactive half of ADR-030 §3 never worked (#82). Two compounding causes: `SCOPES` was passed into `Credentials.from_authorized_user_info()`, which makes `creds.scopes` the *requested* set (google-auth only reads the stored field when the argument is None); and `to_json()` doesn't serialize `granted_scopes`, so the consented set was discarded at first save. Fix: persist the granted set under its own key, re-attach it on load, and read it in `_missing_scopes()`. `None` means unknown, not empty — pre-fix tokens recover on their next refresh with no re-auth. Builds the scope-aware layer this unblocks (ADR-034): - `enforce_scopes()` fails fast with `INSUFFICIENT_SCOPES` naming the scope, affected commands, and the fix, before any API call. `@requires_scope` wraps it for partial-coverage scopes. - `SCOPE_COMMANDS` maps scope to a service or a specific command. - `--capabilities` reports per-command `scope` and tri-state `enabled`, derived from the map so it can't drift from the gate. - `slides` gates on `presentations` at its `_get_client()` choke point. Scopes resolve at invocation, never at import, and unknown grant sets fail open. Keyring reads now treat a missing backend as "nothing stored" — the capabilities scope read otherwise turned `desk --capabilities` into a NoKeyringError traceback on headless hosts. Refs #80, #81 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…Meet settings Closes #80. Closes the buildable half of #81. ## Calendar (ADR-035, #80) - `--meet` on create and update attaches a Google Meet conference. Idempotent on update; requestId is derived from the event since Calendar treats it as an idempotency key. - `--send-updates all|external-only|none` on create/update/delete/respond, defaulting to `all`. This was hardcoded in four places, so you could never opt out — deleting an event always mailed a cancellation to every attendee. - `--hide-guest-list`, `--no-guest-invites`, `--guests-can-modify`, `--location`, `--visibility`, `--free`. Only sent when passed, so Google's defaults stand. - Reads now surface `meetLink`, `conferenceId`, and `conferenceStatus`. Desk previously dropped `hangoutLink` entirely and couldn't show a Meet link on any event, whoever created it. `conferenceId` is what addresses a Meet space. ## Meet (ADR-036, #81) New `desk meet` service group with `read` and `update`, over the Meet REST API: auto-recording, auto-transcription, and auto smart notes. Each setting takes on/off/default, mapping to AutoGenerationType; only requested fields go in the updateMask. Not folded into `desk cal create --auto-record` as #81 suggested: that would make a Calendar command call the Meet API, which ADR-003 forbids. The two compose via `conferenceId` instead. Co-hosts are documented as UI-only rather than implemented — `spaces.members` is restricted to Google's Developer Preview Program. Parked in idea 081. Adds the `meetings.space.settings` scope. It's non-sensitive and documented for spaces created by other apps, so a Calendar conference is reachable without `meetings.space.created`. Existing tokens keep working; `desk meet` reports itself disabled and fails fast per ADR-034. Also removes the `@requires_scope` decorator added in ADR-034. It existed for a scope covering part of a service, which Meet was expected to be; making Meet its own group left it with no user and no reader. 948 tests pass, normally and under a keyring-less backend.
Revisited idea 080's open question with the user. The premise for switching to `none` was weaker than it looked: Calendar's own UI default (add a guest, hit the primary button) only notifies that guest, not the full attendee list — but that scoping happens as backend dedup under sendUpdates=all, not a distinct API value. all already tracks the UI's default lean for adds, and is correctly unscoped for deletes/reschedules where every attendee is genuinely affected.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #82, #80. Closes the buildable half of #81.
Three commits' worth of work in dependency order: the bug had to be fixed before the scope layer could work, and the scope layer had to exist before the Meet scope could be added without a coordinated re-auth.
1. The bug (#82) — ADR-034 §1
desk auth statushad reportedmissing_scopes: []for every user since ADR-030 shipped. Two compounding causes:SCOPESwas passed intoCredentials.from_authorized_user_info(), and google-auth only reads the token's storedscopesfield when that argument isNone. Socreds.scopesbecame the requested set and_missing_scopes()computedset(SCOPES) - set(SCOPES).to_json()serializesscopes(requested), nevergranted_scopes, andfrom_authorized_user_info()doesn't restore it — so the consented set was discarded at first save and lived only in memory right after a refresh.Fixing the load path alone would have done nothing; there was no persisted truth to read. Now the granted set is stored under its own key, re-attached on load, and read by
_missing_scopes().Nonemeans unknown, not empty.Verified on a real token during this work: it self-healed on an ordinary refresh, with no re-auth, and
auth statusthen reported the genuinepresentations-era drift for the first time.2. Scope-aware commands — ADR-034
enforce_scopes()fails fast withINSUFFICIENT_SCOPESnaming the scope, affected commands, anddesk auth login, before any API call.SCOPE_COMMANDSmaps a scope to a service or a specific command.--capabilitiesreports per-commandscopeand tri-stateenabled, derived from the map so it can't drift from the gate.Two properties taken from cafe's ADR-006/024: scopes resolve at invocation, never at import (desk imports every command module at startup, so an import-time keyring read crashes headless hosts), and unknown grants fail open.
Caught while implementing: the capabilities scope read turned
desk --capabilitiesinto aNoKeyringErrortraceback on hosts with no keyring backend — the exact regression cafe's ADR-024 documents. Keyring reads now treat a missing backend as "nothing stored"; writes still fail loudly.3. Calendar event fields (#80) — ADR-035
--meeton create and update. Idempotent on update;requestIdderived from the event because Calendar treats it as an idempotency key.--send-updates all|external-only|noneon create/update/delete/respond, defaultall. This was hardcoded in four places — worse than the issue reported, since you could never opt out: deleting an event always mailed a cancellation to every attendee.--hide-guest-list,--no-guest-invites,--guests-can-modify,--location,--visibility,--free. Only sent when passed.meetLink/conferenceId/conferenceStatus. A gap cal: create/update can't set Meet link, guest permissions, or sendUpdates #80 didn't mention:_parse_event()droppedhangoutLinkentirely, so desk couldn't display a Meet link on any event.4. Google Meet (#81) — ADR-036
New
desk meetgroup —readandupdatefor auto-recording, auto-transcription, auto smart notes. Each takeson/off/default; only requested fields enter theupdateMask.Not
desk cal create --auto-recordas #81 suggested: that makes a Calendar command call the Meet API, which ADR-003 forbids, bundles two failure modes, and would gatecal createon a scope most callers don't need. The two compose throughconferenceIdinstead:Co-hosts are documented, not implemented.
spaces.members.createis restricted to Google's Developer Preview Program, so shipping it would fail for anyone unenrolled with an error that looks like a desk bug.desk meet --helpstates the limitation and the ordering trap (add guests before picking co-hosts). Parked in idea 081.Adds
meetings.space.settings— non-sensitive, and documented for spaces created by other apps, someetings.space.createdisn't needed.Also removes the
@requires_scopedecorator from ADR-034: it existed for a scope covering part of a service, which Meet was expected to be. Making Meet its own group left it with no user and no reader, so it's deleted rather than shipped unused. ADR-036 §5 records the amendment.Verification
PYTHON_KEYRING_BACKEND=keyring.backends.fail.Keyring._missing_scopesfix fails 3 of the new tests, so they're a real regression guard.auth status --verifyreportsmeet: falsewithmissing_scopes: [meetings.space.settings];--capabilitiesreportsmeet enabled=false, slides enabled=true;desk meet readfails fast withINSUFFICIENT_SCOPESandaffected_commands: ["meet (all commands)"].Docs
ADR-034 (amends ADR-030 §3), ADR-035, ADR-036 (amends ADR-034 §2); ideas 079–081; a correction appended to idea 060, whose "rollout complete" note was wrong about the proactive half; CLAUDE.md architecture, README, and both README indexes (which were also missing ADRs 032–033 and ideas 077–078).
Left undone, deliberately
--send-updatesstill defaults toall; flipping it tononewould be safer but silently changes every existing script, so it's an open question in idea 080 rather than a decision made here.slidesandmeetare gated, since they're the only services with post-release scopes.🤖 Generated with Claude Code