-
Notifications
You must be signed in to change notification settings - Fork 3
plat 300
PLAT-300 — Gmail send-only default and Google Workspace (Drive/Sheets/Docs/Slides/Calendar) connections
| Coordination | Value |
|---|---|
| Assigned agent | Claude Code |
| Ticket state | Implemented and deployed (confida) — 2026-09-20 follow-up implemented locally, tests pass, not yet pushed/deployed |
| Last synchronized | 2026-09-20 |
| Priority | P2 platform capability |
The Gmail channel requested gmail.readonly by default even though every
existing use (Pulse and workflow notify_user) only ever sends, and a
send-only login could not report itself as authenticated or name its own
address — gmail.users.getProfile needs a read scope, so a send-only token
showed "Address not known yet" and, on the gog backend, read as needing
reconnect. Separately, whichever CLI backend actually serves a deployment
(gws or gog) was hardcoded as "gws" in the settings UI, so a gog-backed
deployment (gog is the only backend that supports anything beyond Gmail) saw
a permanently wrong "gws not installed" status. Beyond Gmail, there was no way
for a connection to authorize Drive, Sheets, Docs, Slides, or Calendar, and no
agent-facing way to use them even if there were.
-
Send-only default, opt-in read.
GmailConnection.AllowReadAccess(default false) requestsgmail.send+userinfo.emailonly; checking "Also allow reading this mailbox" in the UI addsgmail.readonly. Fixed at consent time, like every scope grant here — widening it means reconnecting. -
Identity for a send-only token. Both backends now resolve identity via
Google's
tokeninfoendpoint (googleTokenInfo) instead ofgmail.users.getProfile: tokeninfo returns the granted scopes and, whenuserinfo.emailwas granted (always), the address — and it works regardless of read access.computeAuthStatusGogandcomputeAuthStatus's server-managed-token branch both use it, withgetProfilekept only as a fallback (tokeninfo unreachable, or the--account/--clientpath with no raw token to introspect). -
Backend-aware UI labels.
GmailAuthStatus.Backend("gws"/"gog") is set by whichever path computed the status; the settings UI reads it instead of hardcoding "gws" /@googleworkspace/cliin the Connection card and the "no account connected" banner. -
Google Workspace service grants.
GmailConnection.Services []GoogleServiceGrant({service, write}) lets a connection additionally request Drive, Sheets, Docs, Slides, or Calendar, each independently read-only (default) or read+write. The scope catalog (services.GoogleServiceCatalog()) is the single source of the OAuth scope URIs per service/level; a newGET /api/human-feedback/gmail/service-catalogendpoint keeps the UI's checkbox list from drifting from it. Set on create only — likeAllowReadAccess, changing it means removing and re-adding the connection. -
google_workspace_cliagent tool. Rather than a bespoke tool per Drive/Sheets/etc. operation, the agent gets one tool that passes its chosengogarguments straight through (services.RunGoogleCLI). The server resolves the target connection (default or named), injects its access token, rejects--access-token/--account/--client/--homeif the caller tries to set them, and appends--readonlywhenever the connection's grant for that service is read-only — enforcement is gogcli's own flag, not argument sniffing. The token never appears in tool-call arguments or the agent's shell. Independent ofGmailConfig.UseGogBackend: that flag only picks which backend serves Gmail send/status, but Drive/Sheets/etc. have nogwsequivalent at all, so this tool always resolves togog.
-
go test ./agent_go/cmd/server/...passes (server, services, virtual-tools packages), including newTestComputeAuthStatusGogSendOnlyTokenIsAuthenticatedAndNamedViaTokenInfoandTestComputeAuthStatusGogReadOnlyTokenIsAuthenticatedButLacksSendScope. -
go build ./agent_go/...and frontendtsc --noEmitboth clean. - Deployed to confida (
confida-c36503a-...);/api/healthreports healthy post-deploy. - confida's
gmail-config.jsonconfirmed to carryuse_gog_backend: trueacross a redeploy (data is separate from the release). - A live connection has not yet been created with a Drive/Sheets/etc.
grant, so
google_workspace_clihas not been exercised end-to-end against a real Google account. - Existing pre-feature connections (e.g. confida's
manish.prakash@excellencetechnologies.com) still need reconnecting to pick up any new service grants — expected, not a bug, matching howAllowReadAccessalready behaves.
Shipped as part of confida's agent_go + frontend bundle
(deploy/cf/deploy-cf.sh, remote-main mode). Not evaluated
against RTS or Dominion — those deployments are managed separately.
After an "update permission" re-OAuth, Google shows success ("Connected as manish.prakash@excellencestechnologies.com — you can close this tab") but the app reports every permission revoked and the connection not Ready.
Evidence: with the backend's GOG_HOME, gog auth list --check --json
shows the imported account valid: true with scopes: []. The credential
is live; only gog's scope metadata is empty. Root cause: refresh tokens
imported from our own OAuth flow (ImportRefreshTokenIntoGog) carry no
scope metadata in gog's store, and the status path reads the empty list as
"gmail.send missing" (computeAuthStatusGog checked-account branch), which
clears HasGmailSendScope and the Ready flag. The completion path also
overwrites the connection's stored scopes with that empty observation.
Fix (implemented 2026-09-20, not yet pushed/deployed):
CompleteGmailOAuth returns the consent-time tokeninfo scopes, the
callback route threads them into CompleteGogConnection, which records
them via resolveCompletionScopes (granted > gog metadata > keep stored,
never wipe with empty); computeAuthStatusGog's checked-account branch
falls back to the connection's stored scopes when gog reports empty on a
valid account. Regression tests:
TestGogStatusFallsBackToStoredScopesWhenGogReportsNone (failed before,
passes after),
TestResolveCompletionScopesNeverWipesStoredScopes, and a granted-scopes
assertion in TestOAuthCallbackStoresOnlyInGog. go test ./cmd/server/services/ and go test ./cmd/server/ both pass.
- 2026-09-08: scope truth read live at status time (
tokeninfo, thengetProfilefallback, then gog--checkmetadata). Belief: gog's account record carries granted scopes. - 2026-09-20: supersedes the gog-metadata belief for imported accounts —
gog auth list --checkreturnsvalid: truewithscopes: []for externally imported refresh tokens, so consent-timetokeninfoscopes persisted on the connection become the scope source of truth, with live gog metadata as a non-empty-only refinement. This continues the running gog reliability thread (backend naming, send-only identity, shared store/GOG_HOMEin PLAT-312): gog owns the tokens, but our status and scope reporting cannot trust its metadata alone.
Auto-synced from docs/ on main. Edit there, not here.