[object Object]

← back to George Gmail

TK-11952: add /api/live-check (real Google round-trip) so george arm-probe verifies deliverability, not object-existence

fdc9515f046c01ec8a7d11620f9258c094ff235b · 2026-09-20 12:26:26 -0700 · Steve Abrams

/health reports ready:!!gmail (OAuth object exists in memory), which stays truthy
through a DNS-to-Google outage (getaddrinfo ENOTFOUND oauth2.googleapis.com — the
09-13 failure). New /api/live-check does the same gmail.users.getProfile round-trip
george runs on OAuth exchange and returns 200 {live:true} ONLY when Google is
reachable (503 {live:false} otherwise). Exempted from both Basic-auth middlewares
like /health. Canaries repoint to this; fail-closed until george restart.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ActKpZmgeAcGMo4esfSb3K

Files touched

Diff

commit fdc9515f046c01ec8a7d11620f9258c094ff235b
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sun Sep 20 12:26:26 2026 -0700

    TK-11952: add /api/live-check (real Google round-trip) so george arm-probe verifies deliverability, not object-existence
    
    /health reports ready:!!gmail (OAuth object exists in memory), which stays truthy
    through a DNS-to-Google outage (getaddrinfo ENOTFOUND oauth2.googleapis.com — the
    09-13 failure). New /api/live-check does the same gmail.users.getProfile round-trip
    george runs on OAuth exchange and returns 200 {live:true} ONLY when Google is
    reachable (503 {live:false} otherwise). Exempted from both Basic-auth middlewares
    like /health. Canaries repoint to this; fail-closed until george restart.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01ActKpZmgeAcGMo4esfSb3K
---
 server.js | 40 ++++++++++++++++++++++++++++++++++++++--
 1 file changed, 38 insertions(+), 2 deletions(-)

diff --git a/server.js b/server.js
index 471b21c..cdd932d 100644
--- a/server.js
+++ b/server.js
@@ -346,7 +346,7 @@ function _ctEq(a, b) {
 
 // ─── Basic Auth (skip health + OAuth callbacks) ───
 app.use((req, res, next) => {
-  const publicPaths = ['/health', '/auth', '/auth/info', '/auth/steve-office', '/auth/steve-personal', '/auth/agentabrams', '/auth/calendar', '/oauth2callback'];
+  const publicPaths = ['/health', '/api/live-check', '/auth', '/auth/info', '/auth/steve-office', '/auth/steve-personal', '/auth/agentabrams', '/auth/calendar', '/oauth2callback'];
   if (publicPaths.includes(req.path)) return next();
   const auth = req.headers.authorization;
   if (!auth || !auth.startsWith('Basic ')) {
@@ -908,7 +908,7 @@ app.get('/api/auth-url', (req, res) => {
 // ─── Basic Auth (exempt health + OAuth routes) ───
 app.use((req, res, next) => {
   const publicApiPaths = [
-    '/health', '/api/health', '/auth', '/auth/info', '/auth/steve-office', '/auth/steve-personal', '/auth/agentabrams',
+    '/health', '/api/health', '/api/live-check', '/auth', '/auth/info', '/auth/steve-office', '/auth/steve-personal', '/auth/agentabrams',
     '/oauth2callback', '/oauth2callback-info',
     '/api/exchange-code', '/api/exchange-code/info', '/api/exchange-code/steve-personal',
     '/api/auth-url', '/api/auth-url/info', '/api/auth-url/steve-personal', '/api/auth-url/agentabrams'
@@ -2729,6 +2729,42 @@ app.post('/api/calendar/events', async (req, res) => {
   } catch (e) { res.status(500).json({ error: e.message }); }
 });
 
+// ─── Live-Check (TK-11952) — REAL Google round-trip, NOT object-existence ───
+// /health reports ready:!!gmail — the in-memory OAuth object EXISTS. That stays truthy
+// straight through a DNS-to-Google outage (getaddrinfo ENOTFOUND oauth2.googleapis.com,
+// the actual 09-13 failure), so a canary greening on /health has a silent-PASS risk.
+// This route instead performs the SAME cheap live call george already runs on OAuth
+// exchange (gmail.users.getProfile, server.js:~698/882) and returns 200 {live:true} ONLY
+// when the round-trip to Google succeeds. Any failure — unconfigured client, DNS/network
+// unreachable, auth error, timeout — returns 503 {live:false}. FAIL-CLOSED: it can only
+// ever be MORE conservative than /health, never a false green.
+app.get('/api/live-check', async (req, res) => {
+  const started = Date.now();
+  // Default to the account the arm-health canaries watch (steve-office = `gmail`),
+  // but allow ?account= to probe any configured account via resolveAccount.
+  let gm, account;
+  try {
+    if (req.query.account) {
+      const r = resolveAccount(req); gm = r.gmail; account = r.key;
+    } else {
+      gm = gmail; account = 'steve-office';
+    }
+  } catch (e) {
+    return res.status(503).json({ live: false, account: account || null, error: 'account-resolve: ' + e.message, latency_ms: Date.now() - started });
+  }
+  if (!gm) {
+    // OAuth object absent — george is not authed for this account. Fail closed.
+    return res.status(503).json({ live: false, account: account || null, error: 'gmail client not configured (not authed)', latency_ms: Date.now() - started });
+  }
+  try {
+    // Bounded live round-trip. A DNS/network failure or auth error throws → 503.
+    const profile = await gm.users.getProfile({ userId: 'me' }, { timeout: 8000 });
+    return res.json({ live: true, account, email: profile.data.emailAddress, latency_ms: Date.now() - started });
+  } catch (e) {
+    return res.status(503).json({ live: false, account, error: e.message, latency_ms: Date.now() - started });
+  }
+});
+
 // ─── Health Check (updated for 3 accounts) ───
 app.get('/health', (req, res) => {
   res.json({

← fbea155 auto-data-snapshot: 2026-09-20T08:21:12 (1 data files) — dat  ·  back to George Gmail  ·  auto-data-snapshot: 2026-09-21T04:28:28 (1 data files) — dat c1ded9d →