{"slug":"george-gmail","total":201,"limit":100,"offset":0,"since":null,"commits":[{"hash":"2c8cc42","date":"2026-09-26 04:44:30 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-26T04:44:27 (2 data files) — data/archive-before-delete-2026-09-26.jsonl data/drain-old-drafts-latest.json","body":""},{"hash":"82835be","date":"2026-09-25 04:38:16 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-25T04:38:13 (1 data files) — data/drain-old-drafts-latest.json","body":""},{"hash":"628273f","date":"2026-09-24 04:27:36 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-24T04:27:32 (1 data files) — data/drain-old-drafts-latest.json","body":""},{"hash":"c06e511","date":"2026-09-23 11:50:03 -0700","author":"Steve Abrams","subject":"TK-11552: keep-list 6 content-bearing info@ drafts (Aug 26-28) + draft-id pins ahead of the 30-day drain","body":"Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01E1xReXdF9rRznsRv8hyTve"},{"hash":"462a184","date":"2026-09-23 04:15:29 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-23T04:15:24 (1 data files) — data/drain-old-drafts-latest.json","body":""},{"hash":"c1ded9d","date":"2026-09-21 04:28:34 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-21T04:28:28 (1 data files) — data/drain-old-drafts-latest.json","body":""},{"hash":"fdc9515","date":"2026-09-20 12:26:26 -0700","author":"Steve Abrams","subject":"TK-11952: add /api/live-check (real Google round-trip) so george arm-probe verifies deliverability, not object-existence","body":"/health reports ready:!!gmail (OAuth object exists in memory), which stays truthy\nthrough a DNS-to-Google outage (getaddrinfo ENOTFOUND oauth2.googleapis.com — the\n09-13 failure). New /api/live-check does the same gmail.users.getProfile round-trip\ngeorge runs on OAuth exchange and returns 200 {live:true} ONLY when Google is\nreachable (503 {live:false} otherwise). Exempted from both Basic-auth middlewares\nlike /health. Canaries repoint to this; fail-closed until george restart.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01ActKpZmgeAcGMo4esfSb3K"},{"hash":"fbea155","date":"2026-09-20 08:22:14 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-20T08:21:12 (1 data files) — data/bridge-alert-state.json","body":""},{"hash":"ab13f28","date":"2026-09-20 04:27:51 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-20T04:26:57 (1 data files) — data/drain-old-drafts-latest.json","body":""},{"hash":"a70f1df","date":"2026-09-19 04:40:55 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-19T04:40:20 (1 data files) — data/drain-old-drafts-latest.json","body":""},{"hash":"6784682","date":"2026-09-18 04:38:08 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-18T04:37:40 (1 data files) — data/drain-old-drafts-latest.json","body":""},{"hash":"32ab62b","date":"2026-09-18 00:54:43 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-18T00:54:05 (1 data files) — data/drain-keep-list.json","body":""},{"hash":"b43e27e","date":"2026-09-17 04:31:18 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-17T04:30:36 (1 data files) — data/drain-old-drafts-latest.json","body":""},{"hash":"b7d1887","date":"2026-09-16 04:40:10 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-16T04:39:34 (1 data files) — data/drain-old-drafts-latest.json","body":""},{"hash":"a98b641","date":"2026-09-15 04:28:19 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-15T04:27:49 (1 data files) — data/drain-old-drafts-latest.json","body":""},{"hash":"ec90fcf","date":"2026-09-14 04:38:01 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-14T04:37:30 (1 data files) — data/drain-old-drafts-latest.json","body":""},{"hash":"65cc579","date":"2026-09-14 00:23:15 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-14T00:22:42 (1 data files) — data/drain-keep-list.json","body":""},{"hash":"ca8ce70","date":"2026-09-13 23:51:03 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-13T23:50:25 (1 data files) — data/drain-keep-list.json","body":""},{"hash":"789f1c6","date":"2026-09-13 16:33:39 -0700","author":"Steve Abrams","subject":"TK-11552: derive the protected set from the KEEP-LIST, not from isExempt() + gate contradictions","body":"Second-model review (Grok) caught that b8f78c8 reproduced the very bug it was written\nto fix. The census built protectedBefore with isExempt(d) -- the same predicate that\nbuilds `matches`. A PARTIAL false-negative in isExempt (8 of 11 keep-listed drafts\nrecognized) drops the other 3 out of BOTH the census and the exemption: deleted,\nnever missed, reported PASS. Two paths cross-check each other only if they can\nDISAGREE, which means they must be different code even when they compute the same set.\n\n  - protectedBefore is now resolved by iterating KEEP_IDS against draft-id / message-id\n    maps built from the listing. isExempt() is never consulted there.\n  - CONTRADICTION GATE: if the keep-list says protect and isExempt selected the draft\n    for deletion, the run ABORTS before any delete. That turns a partial false-negative\n    from \"permanent loss detected afterwards\" into \"loss prevented\".\n  - An O(1) re-check immediately before each DELETE closes the in-batch window (a batch\n    of up to 500 deletes was previously only re-checked on the next batch).\n  - A full 500-draft listing while a keep-list is in force now ABORTS: the mailbox\n    cannot be fully seen, so neither the census nor the survival check is trustworthy,\n    and deleting permanently while blind to what is protected is the one thing this\n    must not do. Previously that was only a WARN.\n  - A failed post-run re-read is FAIL when the run actually deleted something -- \"I\n    could not check\" must not be quieter than \"I checked and it was fine\". WARN when\n    nothing was deleted, since there was nothing to lose.\n  - An empty/unparseable listing is reported as a READ FAILURE, not as \"everything was\n    lost\": same safe action, but the reason must not be fabricated.\n\nTests 20 -> 25 assertions, 5 -> 6 cases. CASE 6 is the headline: it breaks isExempt on\npurpose and asserts the run aborts with deleted=0. b8f78c8 passed every other case in\nthis file while still deleting the protected draft.\n\nLive read-only check of the new logic against info@: 27 keep-list ids -> 12 protected\ndrafts + 3 unresolved (matches the canary); >22d population 14 -> would delete 3 (the\n3 known-abandoned empties), 0 contradictions.\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01EBRBeHTnkTTdi3DYLePNSY"},{"hash":"b8f78c8","date":"2026-09-13 16:11:56 -0700","author":"Steve Abrams","subject":"TK-11552: drain asserts protected drafts SURVIVED, not just that it meant to skip them","body":"The exemption filter and the success verdict were the same code path: matches was\nbuilt with !isExempt(d) and the verdict was (totalFail>0 ? WARN : PASS). If isExempt\nwere ever wrong, the run would permanently delete (no Trash) every keep-listed draft\nAND report PASS. A filter cannot audit itself.\n\nAdds an outcome-side check observed through a different read than the one that made\nthe decision:\n  - census the keep-listed drafts that are LIVE before the drain loop\n  - re-verify their presence before each batch's deletes; a loss ABORTS the pass\n    (aborting can only prevent deletions, never cause one)\n  - re-read once more at the end; any loss is FAIL with the ids named\n\nNOT-MEASURED is never PASS (TK-11431 rule 1): a truncated 500-draft listing, a failed\npost-run re-read, or a keep-list whose ids resolve to no live draft each degrade to\nWARN with protected_verified null, rather than a clean PASS that implies protection\nwas proven. New heartbeat fields: protected_before, protected_lost_count,\nprotected_lost_ids, protected_verified, census_truncated, protection_note.\n\nShips test/test-protected-survival.sh + test/mock-george.js (TK-11431 rule 3): 17\nassertions over 5 cases against an isolated copy and a loopback mock, proving the\ncheck goes RED on an injected fault on BOTH abort paths (mid-drain and post-loop) and\non all three not-measured paths. Harness guards rc=127 and a missing heartbeat so a\ncase that never ran cannot read as a pass.\n\nAlso fixes the archive filename: stamp.slice(0,8) on an ISO stamp yielded\n\"archive-before-delete-2026-09-.jsonl\" (month, truncated). Now slice(0,10) = the date.\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01EBRBeHTnkTTdi3DYLePNSY"},{"hash":"b4c4762","date":"2026-09-13 09:47:44 -0700","author":"Steve Abrams","subject":"TK-11552: keep-list exempts on EITHER id — Gmail rotates message.id on draft edit","body":"The drain matched exemptions on d.message.id but deletes by the draft id. Gmail mints a\nNEW message id every time a draft is saved, while the draft id is stable — proven live:\ndraft r-5517420768746463086 kept its id while its message id went 1a09ba5cc706157b ->\n1a09ba5ce13ee153 across one edit. So editing a protected draft silently dropped its\nprotection and the 04:15 permanent-delete (no Trash) could destroy it. That fired in\nexactly the workflow the TK-11552 memo asks Steve to perform: open info@ and\nsend / rewrite-and-send / drop.\n\n- isExempt(d): match message.id OR draft id. Strictly preservative — can only ADD\n  exemptions, never remove one.\n- Pinned the 12 live keep-list entries by their stable draft id as well (15 -> 27).\n- DRAIN_KEEP_LIST test seam (mirrors the canary's). Verified the launchd plist does NOT\n  set it; its env block carries only CONFIRM/MAX_AUTO_DELETE/PATH.\n- test-draftid-exempt.sh 3/3 against the real script + real drafts, dry-run only. Case B\n  (pin removed -> deletable=1) is the negative control that proves the test can go red.\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01BdKLxdaTGYBRTVGAYwi8Vo"},{"hash":"f5f274f","date":"2026-09-13 04:15:47 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-13T04:15:01 (1 data files) — data/drain-old-drafts-latest.json","body":""},{"hash":"cdf6c50","date":"2026-09-13 04:06:46 -0700","author":"steve@designerwallcoverings.com","subject":"fix: add archive-before-delete path to drain script — saves subject/snippet locally before permanent-delete (TK-11609)","body":""},{"hash":"920a946","date":"2026-09-13 04:02:13 -0700","author":"steve@designerwallcoverings.com","subject":"fix: add 'calendar' to resolveAccount() map + add agentabrams+calendar to /health (TK-11605, TK-11606)","body":""},{"hash":"4411b8c","date":"2026-09-13 02:36:50 -0700","author":"Steve Abrams","subject":"drain: FAIL CLOSED when the keep-list cannot be loaded (it failed OPEN)","body":"The keep-list loader was:\n    try { JSON.parse(readFileSync(KEEP_PATH)) } catch (_) { return {}; }\n\nThat fails OPEN. A corrupted, truncated, wrong-shaped, or deleted keep-list\nsilently became 'zero exemptions', and the very next 04:15 run would\nPERMANENTLY delete (Gmail drafts.delete, no Trash) every draft the keep-list\nexisted to protect. Every protection this file provides — TK-11231's held vendor\nasks and TK-11552's 9 live customer replies — rested on a file whose read\nfailure was swallowed without a word.\n\nA no-Trash deleter must never infer 'nothing is protected' from 'I could not\nread what is protected'. Now any failure to load a usable keep-list ABORTS the\ndeletion pass (exit 1) and writes a FAIL heartbeat so it surfaces on the fleet\npanel instead of dying quietly. ENOENT aborts too: 'nothing is exempt' must be\nSTATED by writing '{}' — a deliberate one-second act — because an absent file is\nindistinguishable from one that went missing. Dry runs only warn, so the script\nstays usable for diagnosis when the keep-list is broken.\n\nStrictly preservative: this change can only ever prevent deletions, never cause\none.\n\nShips test-keeplist-failclosed.sh — 5 cases (missing / corrupt / wrong-shape must\nABORT; explicit {} and dry-run must NOT). SAFETY: it runs an isolated copy of the\nscript in a throwaway dir pointed at a dead GEORGE_BASE port, so even a FAILED\nabort has no reachable mailbox to delete from. The harness also guards itself\nagainst rc=127 — the first draft of this test silently 'passed' a case because\nthe command never ran.\n\nFound by an adversarial second-model review (Kimi k3) of the TK-11552 keep-list\nwork, which correctly pointed out I had fail-safed the canary but not the\ndelete site.\n\nTK-11552\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01Ux1A8z45sLwUTNzz8kxn68"},{"hash":"1fb6464","date":"2026-09-13 02:17:52 -0700","author":"Steve Abrams","subject":"george drain keep-list: exempt the 9 content-bearing TK-11552 info@ reply drafts from the 30-day permanent purge","body":"The 30-day info@ drain (com.steve.george-drain-old-drafts, daily 04:15) permanently\ndeletes drafts with NO Trash. 13 aged info@ drafts were on that fuse; 2 were already\nkeep-listed (TK-11231), 11 were not. Verified content: 9 carry live customer/vendor\ndata (a shipping PRO# the customer must call in, per-yd/per-roll net quotes, stock +\nbackorder terms, vinyl 60cm production specs, 8-panel mural set dims/pricing), 2 are\ngenuinely abandoned (one is pure signature, one is the word 'Looks').\n\nKeep-listing the 9 removes the permanent-loss DEADLINE without sending anything, so\nSteve decides on his own timing instead of racing a 04:15 cron. The first of these\nwould have been destroyed at the 2026-09-14 04:15 run.\n\nVerified with a two-sided negative test against the drain's real predicate\n(older.has(id) && !KEEP_IDS.has(id)): simulating all 13 as >30d, exactly the 2\nabandoned drafts remain deletable and all 11 content-bearing are exempt.\n\nReversible: cp /tmp/drain-keep-list.PRE-TK11552.json data/drain-keep-list.json\n(or git revert). No send, no externality, no Gmail write.\n\nTK-11552\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01Ux1A8z45sLwUTNzz8kxn68"},{"hash":"1cb1b7f","date":"2026-09-12 20:12:12 -0700","author":"Steve Abrams","subject":"george token-bridge: fix calendar re-consent link (TK-11553)","body":"AUTH_ROUTE mapped GOOGLE_CALENDAR_REFRESH_TOKEN -> 'steve-office', but server.js\nwrites GOOGLE_CALENDAR_REFRESH_TOKEN only under the dedicated calendar flow\n(state:'calendar', /auth/calendar). The steve-office /auth flow writes\nGMAIL_REFRESH_TOKEN, so every automated re-consent nudge for a dead calendar\ntoken pointed Steve at a flow that re-minted the healthy Gmail token and never\nrevived the calendar token. Advisory link text only; no runtime token behavior\nchanged.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01VggjiaLGkK4kjR1zCnVJ65"},{"hash":"50cc8f3","date":"2026-09-12 19:42:46 -0700","author":"Steve Abrams","subject":"chore: v1.2.1 (session close) — TK-11529","body":"Shellcheck-clean addition (matches existing SC2015 style used elsewhere\nin the file); no refactor needed for an 8-line, already-verified change.\n\nCo-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01ASu1mqEndsJjj5bpueXKiE"},{"hash":"bada0c2","date":"2026-09-12 14:31:53 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-12T14:31:21 (1 data files) — data/bridge-alert-state.json","body":""},{"hash":"ce59ce6","date":"2026-09-12 12:55:01 -0700","author":"Steve Abrams","subject":"Chain bridge.mjs onto token-age-warn.sh so the fleet-health artifact stays fresh","body":"cron-fire-canary was flagging com.steve.george-token-age as not-firing, but the\nlaunchd job itself was firing fine every 6h — it's the separate bridge.mjs\nrepublish script (~/.claude/skills/george-token-age/bridge.mjs, TK-11371) that\nwrites the monitored artifact, and it had no schedule of its own (its plist\nwas deliberately left uninstalled). Riding the detector's already-approved\ncadence keeps data/latest.json current without installing a new scheduled job.\n\nTK-11529\n\nCo-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01ASu1mqEndsJjj5bpueXKiE"},{"hash":"1f88069","date":"2026-09-12 04:28:07 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-12T04:26:59 (1 data files) — data/drain-old-drafts-latest.json","body":""},{"hash":"2193972","date":"2026-09-12 04:05:44 -0700","author":"steve@designerwallcoverings.com","subject":"fix: strip 'From job:' provenance banner from outbound email body (TK-11365)","body":""},{"hash":"297441c","date":"2026-09-11 14:01:32 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-11T13:57:49 (1 data files) — data/bridge-alert-state.json","body":""},{"hash":"f93cae9","date":"2026-09-11 04:24:15 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-11T04:24:06 (1 data files) — data/drain-old-drafts-latest.json","body":""},{"hash":"d751824","date":"2026-09-10 14:13:40 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-10T14:13:31 (1 data files) — data/bridge-alert-state.json","body":""},{"hash":"e7ccd8a","date":"2026-09-10 11:56:23 -0700","author":"Steve","subject":"TK-11365: never send the internal \"From job:\" banner to an outside party","body":"The banner is an internal provenance aid and was OPT-OUT (inferSource), so any\ncaller that forgot no_source_tag prepended an internal job label to the top of\nvendor and customer mail. Proven in prod on a real customer - four emails opening\n\"From job: kelly-reply-tk10830\" - and found today on 14 of 15 queued vendor\ndrafts in info@ (WallQuest, York, Thibaut, Kravet, Brewster, MDC, Astek, OAL,\nSancar, Innovations, Egg-and-Dart, Quadrille).\n\nThe opt-out was also unreachable from the agent path: the george MCP tool\ngmail_create_draft does not expose no_source_tag at all, so an agent drafting\nvendor mail through the MCP could not suppress the banner even knowing about it.\n\nNow: if ANY recipient is external, no banner. Internal mail keeps it, so the\nprovenance aid survives where it is actually useful. Fails open on error.\n\nLogic unit-tested 5/5 offline: external, mixed internal+external, internal-only,\nthe internal gmail allowlist, and the explicit opt-out.\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>"},{"hash":"ce00205","date":"2026-09-10 10:35:05 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-10T10:34:55 (1 data files) — data/drain-keep-list.json","body":""},{"hash":"0d2c8e2","date":"2026-09-10 09:20:15 -0700","author":"Steve Abrams","subject":"george: give the 30-day draft drainer a KEEP-LIST — TK-11231","body":"The drainer was a blanket age filter with no exemption mechanism, so a READY,\nwanted draft sat on exactly the same permanent-delete fuse as the garbage. That\nis why a live vendor cost-list ask (Newmor / LBI Boyd, 1,294 rows priced at 0)\nwas days from being destroyed with no Trash, purely because it happened to live\non info@ instead of steve-office.\n\nBeing able to protect a wanted draft WITHOUT sending it is the missing\ncapability: 'send it' and 'lose it' should never have been the only two options.\n\ndata/drain-keep-list.json maps messageId -> why it is kept; those ids are never\ndeleted at any age. Seeded with the Newmor ask; the entry documents that it is\nheld deliberately and says to remove it once sent or dropped.\n\nVerified against the real patched predicate (extracted from source, not\nretyped): a keep-listed >30d draft is excluded, an unprotected >30d draft is\nstill deleted (drainer not weakened), and fresh drafts stay untouched. Live dry\nrun confirms the list loads.\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>"},{"hash":"efdf4ea","date":"2026-09-10 08:03:41 -0700","author":"Steve Abrams","subject":"george: draft contract + duplicate detection at the SHARED layer — TK-11231","body":"Red-team finding: the duplicate refusal shipped in george-mcp protects exactly\nONE client. Anything POSTing /api/drafts directly — cron scripts, other repos,\nKamatera, the ~20 caller scripts under DW-Agents — walked straight past it. A\nsingle locked door on a building with no walls.\n\nNow, for EVERY caller:\n- POST /api/drafts returns status=awaiting_owner_send, delivered=false,\n  recipient_has_not_received_this=true + the do-not-close-the-ticket warning.\n  Free, non-breaking, and it reaches callers the MCP never touched.\n- audit() records acknowledged_existing and duplicates_to_recipient, so it is\n  now possible to LEARN that the escape hatch went reflexive. Previously there\n  was no telemetry pointed at that trapdoor.\n- Duplicate detection behind GEORGE_DRAFT_DEDUPE: off (default) | warn | enforce.\n\nDefault is off DELIBERATELY: the check costs one extra Gmail search per draft,\nand the bulk callers here (sample-followup drafts many vendors in a burst) are\nexactly the traffic that already exhausted the per-minute quota once. Turning\nit on is a one-env-var decision for Steve, not a surprise I impose on a shared\nbridge. 'warn' reports and never blocks; 'enforce' 409s and fails CLOSED on an\nunverifiable check.\n\nVerified on an ISOLATED instance (free port, live :9850 untouched): enforce\nreturns 409 naming the existing draft id; default-off leaves callers unchanged\nwhile still returning the new contract. Probe draft deleted; counts back to\nthe 136/41 baseline.\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01VTxE4MgnygQ9EY2rPZvtcK"},{"hash":"bf2fe4c","date":"2026-09-10 07:47:26 -0700","author":"Steve Abrams","subject":"george: add PUT /api/drafts/:id (drafts.update) — TK-11231","body":"The create-only draft surface is what let agents compose DUPLICATE vendor asks\n(Command x2, Maya Romanoff x2, Newmor x2): an agent that found its own stale\ndraft had no verb to revise it, so it composed a second one beside it.\ndrafts.update replaces in place, so revising is idempotent and leaves no\nsecond artifact. Echoes the overwritten to/subject/date back as `previous`\nso a bad edit is recoverable. Returns status=awaiting_owner_send /\ndelivered=false — a draft is never a delivered outcome.\n\nCo-Authored-By: Claude Opus 5 <noreply@anthropic.com>\nClaude-Session: https://claude.ai/code/session_01VTxE4MgnygQ9EY2rPZvtcK"},{"hash":"74ead01","date":"2026-09-10 04:15:59 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-10T04:15:51 (1 data files) — data/drain-old-drafts-latest.json","body":""},{"hash":"462d0c9","date":"2026-09-09 14:29:44 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-09T14:29:35 (1 data files) — data/bridge-alert-state.json","body":""},{"hash":"6d8fe8c","date":"2026-09-09 04:41:05 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-09T04:40:57 (1 data files) — data/drain-old-drafts-latest.json","body":""},{"hash":"451601d","date":"2026-09-08 14:18:10 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-08T14:18:02 (1 data files) — data/bridge-alert-state.json","body":""},{"hash":"0b56f12","date":"2026-09-08 04:33:19 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-08T04:33:12 (1 data files) — data/drain-old-drafts-latest.json","body":""},{"hash":"2924cca","date":"2026-09-07 04:16:04 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-07T04:15:57 (1 data files) — data/drain-old-drafts-latest.json","body":""},{"hash":"62fc29d","date":"2026-09-06 04:34:49 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-06T04:34:41 (1 data files) — data/drain-old-drafts-latest.json","body":""},{"hash":"7c929e5","date":"2026-09-05 04:17:48 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-05T04:17:39 (1 data files) — data/drain-old-drafts-latest.json","body":""},{"hash":"9b72c75","date":"2026-09-04 04:36:37 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-04T04:33:28 (1 data files) — data/drain-old-drafts-latest.json","body":""},{"hash":"4d62362","date":"2026-09-03 04:45:03 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-03T04:44:57 (1 data files) — data/drain-old-drafts-latest.json","body":""},{"hash":"5378a85","date":"2026-09-02 04:39:28 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-02T04:39:22 (1 data files) — data/drain-old-drafts-latest.json","body":""},{"hash":"e6b8c55","date":"2026-09-01 14:31:57 -0700","author":"Steve Abrams","subject":"feat(auth): info@ → Internal client (staggered stage 2) + token-age validates Workspace accts with Internal client","body":""},{"hash":"a3c366a","date":"2026-09-01 14:28:30 -0700","author":"Steve Abrams","subject":"Reapply \"feat(auth): steve-office → Internal OAuth client (staggered stage 1, guarded)\"","body":"This reverts commit 311f0a8dbf640247517286129f0dd280d420a3f2."},{"hash":"311f0a8","date":"2026-09-01 13:57:19 -0700","author":"Steve Abrams","subject":"Revert \"feat(auth): steve-office → Internal OAuth client (staggered stage 1, guarded)\"","body":"This reverts commit 9283fd8e94984c5a132a5be54f46824014be5c28."},{"hash":"9283fd8","date":"2026-09-01 13:52:13 -0700","author":"Steve Abrams","subject":"feat(auth): steve-office → Internal OAuth client (staggered stage 1, guarded)","body":""},{"hash":"9ca35af","date":"2026-09-01 12:49:04 -0700","author":"Steve","subject":"fix(token-age): valid-past-7d-horizon = OK (long-lived), not WARN — stops false age-WARN on Workspace accounts; keep 5-7d pre-emptive WARN + DEAD=CRIT","body":""},{"hash":"e9b8ed8","date":"2026-09-01 12:44:51 -0700","author":"Steve Abrams","subject":"feat(auth): add /auth/calendar re-consent route for GOOGLE_CALENDAR_REFRESH_TOKEN","body":""},{"hash":"93c7bf4","date":"2026-09-01 08:28:21 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-01T08:28:16 (1 data files) — data/bridge-alert-state.json","body":""},{"hash":"5b31e48","date":"2026-09-01 04:43:09 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-09-01T04:43:03 (1 data files) — data/drain-old-drafts-latest.json","body":""},{"hash":"6a7aa2f","date":"2026-08-31 14:29:02 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-31T14:28:57 (1 data files) — data/bridge-alert-state.json","body":""},{"hash":"5745a36","date":"2026-08-31 08:15:11 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-31T08:15:07 (1 data files) — data/bridge-alert-state.json","body":""},{"hash":"5df543f","date":"2026-08-31 04:30:29 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-31T04:30:23 (1 data files) — data/drain-old-drafts-latest.json","body":""},{"hash":"2339696","date":"2026-08-30 14:05:30 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-30T14:05:24 (1 data files) — data/bridge-alert-state.json","body":""},{"hash":"f270d52","date":"2026-08-30 08:29:08 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-30T08:29:04 (1 data files) — data/bridge-alert-state.json","body":""},{"hash":"bf4f079","date":"2026-08-30 04:46:28 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-30T04:46:23 (1 data files) — data/drain-old-drafts-latest.json","body":""},{"hash":"dc5da42","date":"2026-08-29 14:01:52 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-29T14:01:46 (1 data files) — data/bridge-alert-state.json","body":""},{"hash":"1d5b2eb","date":"2026-08-29 08:29:34 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-29T08:29:29 (1 data files) — data/bridge-alert-state.json","body":""},{"hash":"e9e8962","date":"2026-08-29 04:46:18 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-29T04:46:12 (1 data files) — data/drain-old-drafts-latest.json","body":""},{"hash":"50764cc","date":"2026-08-28 21:30:20 -0700","author":"Steve Abrams","subject":"Patch Nodemailer file and URL access advisory","body":""},{"hash":"9cfb00a","date":"2026-08-28 20:11:04 -0700","author":"Steve Abrams","subject":"Reject malformed explicit From identities","body":""},{"hash":"ed923bc","date":"2026-08-28 20:07:51 -0700","author":"Steve Abrams","subject":"Add staged outbound compliance preflight","body":""},{"hash":"615c560","date":"2026-08-28 20:07:44 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-28T20:07:34 (3 data files) — package-lock.json package.json verification/e2e-proof.json","body":""},{"hash":"6261e6d","date":"2026-08-28 14:35:04 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-28T14:34:59 (1 data files) — data/bridge-alert-state.json","body":""},{"hash":"9de5bf4","date":"2026-08-28 08:24:23 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-28T08:24:14 (1 data files) — data/bridge-alert-state.json","body":""},{"hash":"b619e1a","date":"2026-08-28 04:38:41 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-28T04:38:35 (1 data files) — data/drain-old-drafts-latest.json","body":""},{"hash":"8f043f8","date":"2026-08-27 14:29:20 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-27T14:29:15 (1 data files) — data/bridge-alert-state.json","body":""},{"hash":"9449567","date":"2026-08-27 08:19:40 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-27T08:19:36 (1 data files) — data/bridge-alert-state.json","body":""},{"hash":"d2dc3dc","date":"2026-08-27 04:38:16 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-27T04:38:11 (1 data files) — data/drain-old-drafts-latest.json","body":""},{"hash":"aba4922","date":"2026-08-26 14:03:02 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-26T14:02:58 (1 data files) — data/bridge-alert-state.json","body":""},{"hash":"8ab18c3","date":"2026-08-26 08:21:37 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-26T08:21:31 (1 data files) — data/bridge-alert-state.json","body":""},{"hash":"76f24c1","date":"2026-08-26 04:39:17 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-26T04:39:06 (1 data files) — data/drain-old-drafts-latest.json","body":""},{"hash":"c6459db","date":"2026-08-25 14:35:01 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-25T14:34:54 (1 data files) — data/bridge-alert-state.json","body":""},{"hash":"00dce60","date":"2026-08-25 08:16:34 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-25T08:16:28 (1 data files) — data/bridge-alert-state.json","body":""},{"hash":"cf956dd","date":"2026-08-25 04:35:43 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-25T04:35:37 (1 data files) — data/drain-old-drafts-latest.json","body":""},{"hash":"55414b5","date":"2026-08-24 14:30:03 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-24T14:29:57 (1 data files) — data/bridge-alert-state.json","body":""},{"hash":"89f7c24","date":"2026-08-24 08:30:48 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-24T08:30:43 (1 data files) — data/bridge-alert-state.json","body":""},{"hash":"efa58ef","date":"2026-08-24 04:45:09 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-24T04:45:01 (1 data files) — data/drain-old-drafts-latest.json","body":""},{"hash":"a901c19","date":"2026-08-23 14:25:22 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-23T14:25:17 (1 data files) — data/bridge-alert-state.json","body":""},{"hash":"244cb0e","date":"2026-08-23 08:03:46 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-23T08:03:41 (1 data files) — data/bridge-alert-state.json","body":""},{"hash":"8d0b000","date":"2026-08-23 04:19:09 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-23T04:19:03 (1 data files) — data/drain-old-drafts-latest.json","body":""},{"hash":"60d5c89","date":"2026-08-23 04:14:45 -0700","author":"Steve Abrams","subject":"Add labelIds support to POST /api/drafts and bulk-label endpoint","body":"- POST /api/drafts now accepts optional `labelIds` array — after draft creation,\n  applies labels via users.messages.modify (the reliable path since drafts.create\n  raw-message doesn't surface custom labelIds directly). Non-fatal if label apply fails.\n- New POST /api/messages/bulk-label endpoint — searches messages by query `q` and\n  applies/removes label IDs via batchModify in 1000-item chunks; paginated up to\n  5000 messages; supports dryRun mode.\n- New bulk-label-sample-followup-drafts.js script — uses the new endpoint to label\n  all existing info@ drafts matching subject:\"Sample Follow-Up\" with the\n  \"sample-followup\" label (TK-10744 info@ drafts cleanup).\n\nDeploy to Kamatera (pm2 restart george) is gated — Steve-go required.\n\nCo-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"},{"hash":"ba0786d","date":"2026-08-22 14:31:12 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-22T14:31:07 (1 data files) — data/bridge-alert-state.json","body":""},{"hash":"e0f737f","date":"2026-08-22 08:08:32 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-22T08:08:28 (1 data files) — data/bridge-alert-state.json","body":""},{"hash":"2bf4cf1","date":"2026-08-22 04:23:03 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-22T04:22:57 (1 data files) — data/drain-old-drafts-latest.json","body":""},{"hash":"97120a5","date":"2026-08-21 14:05:12 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-21T14:05:07 (1 data files) — data/bridge-alert-state.json","body":""},{"hash":"c6c98a3","date":"2026-08-21 08:14:03 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-21T08:13:58 (1 data files) — data/bridge-alert-state.json","body":""},{"hash":"f15ac18","date":"2026-08-21 04:29:13 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-21T04:29:08 (1 data files) — data/drain-old-drafts-latest.json","body":""},{"hash":"0c433b3","date":"2026-08-20 14:07:11 -0700","author":"auto-commit-fleet","subject":"auto-data-snapshot: 2026-08-20T14:07:04 (1 data files) — data/bridge-alert-state.json","body":""},{"hash":"5ab160f","date":"2026-08-20 11:35:05 -0700","author":"Steve Abrams","subject":"chore: sync package-lock to v1.2.0","body":"Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"},{"hash":"3308e2b","date":"2026-08-20 11:26:04 -0700","author":"Steve Abrams","subject":"chore: v1.1.1 -> v1.2.0 (session close: info@ old-draft auto-drain)","body":"Author: steve@designerwallcoverings.com\nCo-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"}]}