← back to George Gmail
Add OAuth durability decision memo — retire the weekly-token treadmill (SA/DWD for Workspace, fold/WARN for consumer)
360827684c2a0d9b99b93423db95a371cb58af06 · 2026-07-01 09:43:25 -0700 · Steve Abrams
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
A PLAN-OAUTH-DURABILITY.md
Diff
commit 360827684c2a0d9b99b93423db95a371cb58af06
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Jul 1 09:43:25 2026 -0700
Add OAuth durability decision memo — retire the weekly-token treadmill (SA/DWD for Workspace, fold/WARN for consumer)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
PLAN-OAUTH-DURABILITY.md | 86 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 86 insertions(+)
diff --git a/PLAN-OAUTH-DURABILITY.md b/PLAN-OAUTH-DURABILITY.md
new file mode 100644
index 0000000..e8422eb
--- /dev/null
+++ b/PLAN-OAUTH-DURABILITY.md
@@ -0,0 +1,86 @@
+# George OAuth durability — retire the weekly-token treadmill
+
+**Status:** decision memo (read-only inventory done 2026-07-01). Migration itself is Steve-gated
+(Google Cloud Console + Workspace Admin + identity). Council 2026-07-01 idea #3 (vp-engineering).
+
+## Problem
+George re-authorizes every ~7 days. Calendar push + fleet mail silently die on expiry, and we
+built a canary (`com.steve.dw-appointments-oauth-canary`) to WATCH the symptom instead of removing
+the cause. **Root cause:** the single Google Cloud OAuth app is in **"Testing"** publishing status,
+which caps refresh-token lifetime at 7 days. This is monitoring debt, not a monitoring gap.
+
+## Inventory (what actually exists)
+One shared **OAuth "Web application" client** (`GMAIL_CLIENT_ID`/`SECRET`), reused across 4 mailboxes,
+each with its own refresh token in `.env`:
+
+| Account | Address | Type | Refresh-token env |
+|--------------------|----------------------------------|---------------|---------------------------|
+| Steve Office (main)| steve@designerwallcoverings.com | **Workspace** | `GMAIL_REFRESH_TOKEN` |
+| Info catch-all | info@designerwallcoverings.com | **Workspace** | `INFO_REFRESH_TOKEN` |
+| Steve Personal | steveabramsdesigns@gmail.com | consumer gmail| `PERSONAL_REFRESH_TOKEN` |
+| Agent Abrams | theagentabrams@gmail.com | consumer gmail| `AGENTABRAMS_REFRESH_TOKEN`|
+
+Flow: `access_type:offline` + `prompt:consent`; each `/auth/*` callback captures a new refresh token
+and rewrites `.env` (server.js ~L291–472).
+
+**Scopes requested:** `gmail.modify`, `gmail.settings.basic`, `gmail.settings.sharing`, `drive`
+(**RESTRICTED**); `calendar`, `documents`, `spreadsheets`, `presentations`, `forms.body`,
+`forms.responses.readonly`, `tasks` (sensitive).
+
+## Why the naïve fix is a trap
+"Just flip the app to Production" — with **External** user type **and restricted scopes**
+(`gmail.modify`, `drive`) — triggers Google OAuth verification **including an annual third-party
+CASA security assessment** (paid, weeks of lead time) plus brand verification. Do NOT go here blindly.
+
+## The split that avoids verification entirely
+The 4 accounts fall into two classes with different best fixes:
+
+### A. The 2 Workspace accounts (steve@ + info@) — the fleet-critical ones
+These carry the whole fleet send path **and** the info@ catch-all across 209 domains. Two durable,
+**verification-free, $0** options:
+
+1. **Service account + domain-wide delegation (RECOMMENDED).** In the DW Workspace's Cloud project,
+ create a service account, enable domain-wide delegation, and authorize its client ID for the 11
+ scopes in **Workspace Admin → Security → API controls → Domain-wide delegation**. The SA then
+ impersonates steve@ / info@ and mints tokens per request — **no refresh token, no 7-day expiry,
+ no consent screen, no verification, no CASA.** This is the single biggest treadmill-killer.
+2. **Interim one-toggle:** if the Cloud project lives in the designerwallcoverings.com Workspace org,
+ flip the OAuth consent screen **User Type → Internal**. Internal apps skip verification and their
+ refresh tokens don't hit the 7-day cap. Zero code change. Caveat: Internal breaks the 2 consumer
+ accounts on the shared client (they can't authorize an Internal app), forcing the split below —
+ so SA/DWD is the cleaner long-term home for these two anyway.
+
+### B. The 2 consumer @gmail accounts (personal + agentabrams)
+SA/DWD and "Internal" are **impossible** here (no Workspace over gmail.com). Options, best first:
+
+1. **Fold their function into the Workspace and retire consumer OAuth.** If personal/agentabrams sends
+ can go out as steve@ or a Workspace alias, delete these two clients → the whole system becomes
+ durable. Lowest long-term maintenance.
+2. **Reduce scopes + verify.** If they must stay, a *separate* External client requesting only
+ NON-restricted scopes (e.g. `gmail.send` sensitive-tier for send-only, drop `gmail.modify`/`drive`)
+ avoids CASA — leaving only free brand verification (~days). Requires confirming send-only is enough.
+3. **Keep + pre-empt.** Leave them on weekly re-consent but add a **6-day token-age WARN** (Council #2
+ companion): stamp `last_good_ts` per account on each successful poll, emit a CNCP WARN at age > 6d
+ while the token still works, so re-consent is a scheduled 30-second task, never a silent mid-pipeline
+ failure. Folds into the existing 15-min poll, no new process.
+
+## Recommendation
+- **steve@ + info@ → service account + domain-wide delegation** (kills the treadmill for the
+ fleet-critical mail, verification-free, $0).
+- **personal + agentabrams → fold into Workspace and retire** if their sends can originate as steve@/alias;
+ otherwise keep them on weekly re-consent **plus the 6-day WARN** so it's never silent.
+
+## Gated actions (Steve only — I cannot touch Google consoles / identity)
+1. Confirm the Cloud project sits in the designerwallcoverings.com **Workspace org** (decides whether the
+ Internal toggle is even available and whether DWD can be authorized).
+2. Google Cloud Console → create the service account (+ JSON key routed via the `secrets` skill).
+3. Workspace Admin → API controls → authorize the SA client ID for the 11 scopes (domain-wide delegation).
+4. Decide consumer-account fate: **fold-into-Workspace** vs **keep + 6-day WARN**.
+
+## What I can do without a gate (once you decide)
+- Add an SA/DWD auth path in `server.js` (`google.auth.JWT` with `subject=<impersonated user>`) for
+ steve@ + info@, alongside the existing refresh-token path — reversible, behind a feature check so the
+ old path stays until the SA is proven.
+- Add the 6-day token-age WARN to the poll loop for whichever consumer accounts remain.
+- Once SA/DWD is proven for both Workspace accounts, retire `com.steve.dw-appointments-oauth-canary`
+ (the symptom-watcher) — the alert class is gone, not just quiet.
← 0ff8bf3 auto-save: 2026-06-26T16:04:29 (1 files) — server.js
·
back to George Gmail
·
george: pre-emptive OAuth token-age monitor + durable-migrat 9e11f3d →