. The inline basic-auth credential\n(\"admin:DWSecure2024!\") was already in this file at lines 60 + 66 before\nthis commit and is unchanged by it. Credential rotation across the fleet\nis tracked separately (cred is also in dozens of sibling repos per MEMORY).\n\nSurfaced by tick 169 session #102 Teal mechanical refactor pass; that\npass deferred the fix because the diff touches the cred line, but the\nLIVE-BROKEN production UI takes precedence — fixing now, rotation later.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) "},{"h":"941feb1","d":"2026-05-19","s":"Snapshot-files cleanup: untrack server.js.bak-*, broaden .gitignore, add 404 guard","b":"- Untrack the 7-month-old server.js.bak-20260421-094351 snapshot that was\n tracked in the repo (still present on disk, now ignored).\n- Broaden .gitignore to cover *.bak.*, *.bak-*, *.pre-*, *.orig, *.rej,\n *.swp, *~ so future snapshots don't slip back in.\n- Add a static-snapshot 404 guard middleware that runs before\n express.static('public'); any URL matching .bak, .bak-*, .pre-*, .orig,\n .rej, .swp, or trailing ~ returns 404 regardless of disk state.\n Defense-in-depth on top of the existing basic-auth gate.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) "},{"h":"c2bdfca","d":"2026-05-19","s":"add trash-fleet-mailtests.js — one-off cleanup of 89 fleet delivery-test emails (modeled on trash-redfin-zillow.js)","b":""},{"h":"ab70d2e","d":"2026-05-19","s":"George: make the 'From job:' provenance banner opt-out (no_source_tag flag / x-george-no-tag header) so customer-facing sends can be banner-free","b":"Co-Authored-By: Claude Opus 4.7 (1M context) "},{"h":"fd1481e","d":"2026-05-19","s":"Fleet auto-responder: stop logging non-fleet mail to UI run-log (run-log tracks fleet inquiries only)","b":""},{"h":"8f310af","d":"2026-05-19","s":"Fleet auto-responder: update usage docstring (daemon/force flags, HTML reply, UI)","b":""},{"h":"2f343b5","d":"2026-05-19","s":"Fleet auto-responder: fix scan query (bare deliveredto:steve+ never matches in Gmail) + probe-subject safety guard (RETEST/FLEETMAILTEST/DTEST/PMRCV)","b":""},{"h":"e9fdf0f","d":"2026-05-19","s":"Fleet auto-responder: --daemon self-scheduling mode (pm2-managed, 3-min interval); UI on port 9768","b":""},{"h":"9c7a470","d":"2026-05-19","s":"fleet-autoresponder UI: move port 9766→9768 (9766 reserved for agent-architect mindmap viewer)","b":""},{"h":"d6e30ce","d":"2026-05-19","s":"Fleet auto-responder: pm2 ecosystem config — 3-min cron schedule + UI process","b":""},{"h":"fb57ee9","d":"2026-05-19","s":"Fleet auto-responder admin UI dashboard (Express viewer, port 9766, pm2-managed)","b":""},{"h":"13d4e3b","d":"2026-05-19","s":"Fleet auto-responder: multipart/alternative HTML+plain reply, structured run-log + state","b":""},{"h":"7d77cad","d":"2026-05-19","s":"Fleet auto-responder: deliveredto query fix + --only-ids scoped-test flag","b":"Tested live on barwallpaper.com + grassclothwallcovering.com (2-domain test):\n- Gmail does not match plus-tags via to:; switched candidate query to\n deliveredto: and rely on Delivered-To header parsing for domain recovery.\n- Added --only-ids flag so a scoped test does not touch the inbox backlog\n of unrelated probe mail.\n- Verified: reply sent from info@designerwallcoverings.com landed in the\n recipient INBOX (not Spam), correct per-domain sign-off, no loop, label\n + 7-day ledger dedup both idempotent on re-run.\n\nLedger (data/fleet-autoresponder-ledger.json) gitignored — runtime state.\n\nCo-Authored-By: Claude Opus 4.7 (1M context) "},{"h":"d135118","d":"2026-05-19","s":"Add fleet auto-responder for the 44 dw-domain-fleet domains","b":"Sends one warm acknowledgment from info@designerwallcoverings.com to genuine\nnew customer inquiries arriving at info@ (forwarded into the\nsteve-office Gmail via Purelymail catch-all + plus-tag). Reply-To is set to\ninfo@ so customer replies route back to the right site.\n\nSpam-safety: skips Spam, no-reply/bounce senders, bulk/auto/list mail, and any\nmail from designerwallcoverings.com or a fleet domain (loop guard). One reply\nper sender per rolling 7-day window (persisted ledger). Marks every processed\nmessage with the 'fleet-autoreplied' Gmail label — idempotent on re-run.\n\nReuses George's DW-MCP/.env OAuth credentials; no prod deploy needed.\nNot yet scheduled — run manually with: node fleet-autoresponder.js\n\nCo-Authored-By: Claude Opus 4.7 (1M context) "},{"h":"02ef28b","d":"2026-05-18","s":"Make /api/profile honor ?account= param like the other 31 routes","b":""},{"h":"2cbc311","d":"2026-05-18","s":"remove bogus stevesclaude account from George — theagentabrams@gmail.com (agentabrams) is the real AI account; also exempt /auth/agentabrams from basic auth","b":""},{"h":"23979b0","d":"2026-05-18","s":"Add agentabrams (theagentabrams@gmail.com) as 5th george account slot","b":""},{"h":"ec801aa","d":"2026-05-18","s":"george /auth honors ?account= — dispatches to per-account re-auth routes","b":""},{"h":"a4d0448","d":"2026-05-12","s":"fix(george): ENV_PATH portable across Mac2/Kamatera","b":"ENV_PATH was hardcoded to /root/Projects/Designer-Wallcoverings/DW-MCP/.env\nwhich only exists on Kamatera. On Mac2 the same .env lives at\n~/Projects/Designer-Wallcoverings/DW-MCP/.env. The readFileSync would\nquietly fail in the try/catch upstream, INFO_REFRESH_TOKEN stayed empty,\nand the boot log said 'Info@ not configured' even though the token IS\nin the Mac2 .env.\n\nFix: try $DW_MCP_ENV then $HOME/Projects/.../DW-MCP/.env then the\nlegacy /root/ path. First readable wins.\n\nSymptom that exposed this: GET /api/search?account=info returned\n'unknown account: info' because infoGmail was never built. After\nrestart with the fix, log says 'Info@ account initialized (full\nWorkspace)' and /api/search returns the real DW inbox (201 messages\nfor a Shopify-orders query, including 'New Order on the New DW')."},{"h":"facbb3b","d":"2026-05-06","s":"initial scaffold (gitify-all 2026-05-06)","b":""}]; const q = document.getElementById('q'); const list = document.getElementById('bp-commits'); const tally = document.getElementById('bp-tally'); function applyFilter() { const term = q.value.toLowerCase().trim(); let shown = 0; for (const li of list.children) { const match = !term || li.dataset.blob.includes(term); li.style.display = match ? '' : 'none'; if (match) shown++; } tally.textContent = term ? `${shown} / ${DATA.length} match "${q.value}"` : `${DATA.length} commits indexed`; // Reflect search state in URL so a filtered view is shareable. Use // replaceState so the back button still escapes the project page. const url = new URL(window.location.href); if (term) url.searchParams.set('q', q.value); else url.searchParams.delete('q'); history.replaceState(null, '', url); } q.addEventListener('input', applyFilter); // Apply the initial ?q= from the server-rendered value field. if (q.value) applyFilter();

George Gmail

repo: ~/Projects/george-gmail · 201 commits · 1 in last 24h, 9 in last 7d · █▃ ▅▃▃▃

Search the build

201 commits indexed

  1. 2c8cc42 2026-09-26 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
  2. 82835be 2026-09-25 auto-data-snapshot: 2026-09-25T04:38:13 (1 data files) — data/drain-old-drafts-latest.json
  3. 628273f 2026-09-24 auto-data-snapshot: 2026-09-24T04:27:32 (1 data files) — data/drain-old-drafts-latest.json
  4. c06e511 2026-09-23 TK-11552: keep-list 6 content-bearing info@ drafts (Aug 26-28) + draft-id pins ahead of the 30-day drain
  5. 462a184 2026-09-23 auto-data-snapshot: 2026-09-23T04:15:24 (1 data files) — data/drain-old-drafts-latest.json
  6. c1ded9d 2026-09-21 auto-data-snapshot: 2026-09-21T04:28:28 (1 data files) — data/drain-old-drafts-latest.json
  7. fdc9515 2026-09-20 TK-11952: add /api/live-check (real Google round-trip) so george arm-probe verifies deliverability, not object-existence
  8. fbea155 2026-09-20 auto-data-snapshot: 2026-09-20T08:21:12 (1 data files) — data/bridge-alert-state.json
  9. ab13f28 2026-09-20 auto-data-snapshot: 2026-09-20T04:26:57 (1 data files) — data/drain-old-drafts-latest.json
  10. a70f1df 2026-09-19 auto-data-snapshot: 2026-09-19T04:40:20 (1 data files) — data/drain-old-drafts-latest.json
  11. 6784682 2026-09-18 auto-data-snapshot: 2026-09-18T04:37:40 (1 data files) — data/drain-old-drafts-latest.json
  12. 32ab62b 2026-09-18 auto-data-snapshot: 2026-09-18T00:54:05 (1 data files) — data/drain-keep-list.json
  13. b43e27e 2026-09-17 auto-data-snapshot: 2026-09-17T04:30:36 (1 data files) — data/drain-old-drafts-latest.json
  14. b7d1887 2026-09-16 auto-data-snapshot: 2026-09-16T04:39:34 (1 data files) — data/drain-old-drafts-latest.json
  15. a98b641 2026-09-15 auto-data-snapshot: 2026-09-15T04:27:49 (1 data files) — data/drain-old-drafts-latest.json
  16. ec90fcf 2026-09-14 auto-data-snapshot: 2026-09-14T04:37:30 (1 data files) — data/drain-old-drafts-latest.json
  17. 65cc579 2026-09-14 auto-data-snapshot: 2026-09-14T00:22:42 (1 data files) — data/drain-keep-list.json
  18. ca8ce70 2026-09-13 auto-data-snapshot: 2026-09-13T23:50:25 (1 data files) — data/drain-keep-list.json
  19. 789f1c6 2026-09-13 TK-11552: derive the protected set from the KEEP-LIST, not from isExempt() + gate contradictions
  20. b8f78c8 2026-09-13 TK-11552: drain asserts protected drafts SURVIVED, not just that it meant to skip them
  21. b4c4762 2026-09-13 TK-11552: keep-list exempts on EITHER id — Gmail rotates message.id on draft edit
  22. f5f274f 2026-09-13 auto-data-snapshot: 2026-09-13T04:15:01 (1 data files) — data/drain-old-drafts-latest.json
  23. cdf6c50 2026-09-13 fix: add archive-before-delete path to drain script — saves subject/snippet locally before permanent-delete (TK-11609)
  24. 920a946 2026-09-13 fix: add 'calendar' to resolveAccount() map + add agentabrams+calendar to /health (TK-11605, TK-11606)
  25. 4411b8c 2026-09-13 drain: FAIL CLOSED when the keep-list cannot be loaded (it failed OPEN)
  26. 1fb6464 2026-09-13 george drain keep-list: exempt the 9 content-bearing TK-11552 info@ reply drafts from the 30-day permanent purge
  27. 1cb1b7f 2026-09-12 george token-bridge: fix calendar re-consent link (TK-11553)
  28. 50cc8f3 2026-09-12 chore: v1.2.1 (session close) — TK-11529
  29. bada0c2 2026-09-12 auto-data-snapshot: 2026-09-12T14:31:21 (1 data files) — data/bridge-alert-state.json
  30. ce59ce6 2026-09-12 Chain bridge.mjs onto token-age-warn.sh so the fleet-health artifact stays fresh
  31. 1f88069 2026-09-12 auto-data-snapshot: 2026-09-12T04:26:59 (1 data files) — data/drain-old-drafts-latest.json
  32. 2193972 2026-09-12 fix: strip 'From job:' provenance banner from outbound email body (TK-11365)
  33. 297441c 2026-09-11 auto-data-snapshot: 2026-09-11T13:57:49 (1 data files) — data/bridge-alert-state.json
  34. f93cae9 2026-09-11 auto-data-snapshot: 2026-09-11T04:24:06 (1 data files) — data/drain-old-drafts-latest.json
  35. d751824 2026-09-10 auto-data-snapshot: 2026-09-10T14:13:31 (1 data files) — data/bridge-alert-state.json
  36. e7ccd8a 2026-09-10 TK-11365: never send the internal "From job:" banner to an outside party
  37. ce00205 2026-09-10 auto-data-snapshot: 2026-09-10T10:34:55 (1 data files) — data/drain-keep-list.json
  38. 0d2c8e2 2026-09-10 george: give the 30-day draft drainer a KEEP-LIST — TK-11231
  39. efdf4ea 2026-09-10 george: draft contract + duplicate detection at the SHARED layer — TK-11231
  40. bf2fe4c 2026-09-10 george: add PUT /api/drafts/:id (drafts.update) — TK-11231
  41. 74ead01 2026-09-10 auto-data-snapshot: 2026-09-10T04:15:51 (1 data files) — data/drain-old-drafts-latest.json
  42. 462d0c9 2026-09-09 auto-data-snapshot: 2026-09-09T14:29:35 (1 data files) — data/bridge-alert-state.json
  43. 6d8fe8c 2026-09-09 auto-data-snapshot: 2026-09-09T04:40:57 (1 data files) — data/drain-old-drafts-latest.json
  44. 451601d 2026-09-08 auto-data-snapshot: 2026-09-08T14:18:02 (1 data files) — data/bridge-alert-state.json
  45. 0b56f12 2026-09-08 auto-data-snapshot: 2026-09-08T04:33:12 (1 data files) — data/drain-old-drafts-latest.json
  46. 2924cca 2026-09-07 auto-data-snapshot: 2026-09-07T04:15:57 (1 data files) — data/drain-old-drafts-latest.json
  47. 62fc29d 2026-09-06 auto-data-snapshot: 2026-09-06T04:34:41 (1 data files) — data/drain-old-drafts-latest.json
  48. 7c929e5 2026-09-05 auto-data-snapshot: 2026-09-05T04:17:39 (1 data files) — data/drain-old-drafts-latest.json
  49. 9b72c75 2026-09-04 auto-data-snapshot: 2026-09-04T04:33:28 (1 data files) — data/drain-old-drafts-latest.json
  50. 4d62362 2026-09-03 auto-data-snapshot: 2026-09-03T04:44:57 (1 data files) — data/drain-old-drafts-latest.json
  51. 5378a85 2026-09-02 auto-data-snapshot: 2026-09-02T04:39:22 (1 data files) — data/drain-old-drafts-latest.json
  52. e6b8c55 2026-09-01 feat(auth): info@ → Internal client (staggered stage 2) + token-age validates Workspace accts with Internal client
  53. a3c366a 2026-09-01 Reapply "feat(auth): steve-office → Internal OAuth client (staggered stage 1, guarded)"
  54. 311f0a8 2026-09-01 Revert "feat(auth): steve-office → Internal OAuth client (staggered stage 1, guarded)"
  55. 9283fd8 2026-09-01 feat(auth): steve-office → Internal OAuth client (staggered stage 1, guarded)
  56. 9ca35af 2026-09-01 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
  57. e9b8ed8 2026-09-01 feat(auth): add /auth/calendar re-consent route for GOOGLE_CALENDAR_REFRESH_TOKEN
  58. 93c7bf4 2026-09-01 auto-data-snapshot: 2026-09-01T08:28:16 (1 data files) — data/bridge-alert-state.json
  59. 5b31e48 2026-09-01 auto-data-snapshot: 2026-09-01T04:43:03 (1 data files) — data/drain-old-drafts-latest.json
  60. 6a7aa2f 2026-08-31 auto-data-snapshot: 2026-08-31T14:28:57 (1 data files) — data/bridge-alert-state.json
  61. 5745a36 2026-08-31 auto-data-snapshot: 2026-08-31T08:15:07 (1 data files) — data/bridge-alert-state.json
  62. 5df543f 2026-08-31 auto-data-snapshot: 2026-08-31T04:30:23 (1 data files) — data/drain-old-drafts-latest.json
  63. 2339696 2026-08-30 auto-data-snapshot: 2026-08-30T14:05:24 (1 data files) — data/bridge-alert-state.json
  64. f270d52 2026-08-30 auto-data-snapshot: 2026-08-30T08:29:04 (1 data files) — data/bridge-alert-state.json
  65. bf4f079 2026-08-30 auto-data-snapshot: 2026-08-30T04:46:23 (1 data files) — data/drain-old-drafts-latest.json
  66. dc5da42 2026-08-29 auto-data-snapshot: 2026-08-29T14:01:46 (1 data files) — data/bridge-alert-state.json
  67. 1d5b2eb 2026-08-29 auto-data-snapshot: 2026-08-29T08:29:29 (1 data files) — data/bridge-alert-state.json
  68. e9e8962 2026-08-29 auto-data-snapshot: 2026-08-29T04:46:12 (1 data files) — data/drain-old-drafts-latest.json
  69. 50764cc 2026-08-28 Patch Nodemailer file and URL access advisory
  70. 9cfb00a 2026-08-28 Reject malformed explicit From identities
  71. ed923bc 2026-08-28 Add staged outbound compliance preflight
  72. 615c560 2026-08-28 auto-data-snapshot: 2026-08-28T20:07:34 (3 data files) — package-lock.json package.json verification/e2e-proof.json
  73. 6261e6d 2026-08-28 auto-data-snapshot: 2026-08-28T14:34:59 (1 data files) — data/bridge-alert-state.json
  74. 9de5bf4 2026-08-28 auto-data-snapshot: 2026-08-28T08:24:14 (1 data files) — data/bridge-alert-state.json
  75. b619e1a 2026-08-28 auto-data-snapshot: 2026-08-28T04:38:35 (1 data files) — data/drain-old-drafts-latest.json
  76. 8f043f8 2026-08-27 auto-data-snapshot: 2026-08-27T14:29:15 (1 data files) — data/bridge-alert-state.json
  77. 9449567 2026-08-27 auto-data-snapshot: 2026-08-27T08:19:36 (1 data files) — data/bridge-alert-state.json
  78. d2dc3dc 2026-08-27 auto-data-snapshot: 2026-08-27T04:38:11 (1 data files) — data/drain-old-drafts-latest.json
  79. aba4922 2026-08-26 auto-data-snapshot: 2026-08-26T14:02:58 (1 data files) — data/bridge-alert-state.json
  80. 8ab18c3 2026-08-26 auto-data-snapshot: 2026-08-26T08:21:31 (1 data files) — data/bridge-alert-state.json
  81. 76f24c1 2026-08-26 auto-data-snapshot: 2026-08-26T04:39:06 (1 data files) — data/drain-old-drafts-latest.json
  82. c6459db 2026-08-25 auto-data-snapshot: 2026-08-25T14:34:54 (1 data files) — data/bridge-alert-state.json
  83. 00dce60 2026-08-25 auto-data-snapshot: 2026-08-25T08:16:28 (1 data files) — data/bridge-alert-state.json
  84. cf956dd 2026-08-25 auto-data-snapshot: 2026-08-25T04:35:37 (1 data files) — data/drain-old-drafts-latest.json
  85. 55414b5 2026-08-24 auto-data-snapshot: 2026-08-24T14:29:57 (1 data files) — data/bridge-alert-state.json
  86. 89f7c24 2026-08-24 auto-data-snapshot: 2026-08-24T08:30:43 (1 data files) — data/bridge-alert-state.json
  87. efa58ef 2026-08-24 auto-data-snapshot: 2026-08-24T04:45:01 (1 data files) — data/drain-old-drafts-latest.json
  88. a901c19 2026-08-23 auto-data-snapshot: 2026-08-23T14:25:17 (1 data files) — data/bridge-alert-state.json
  89. 244cb0e 2026-08-23 auto-data-snapshot: 2026-08-23T08:03:41 (1 data files) — data/bridge-alert-state.json
  90. 8d0b000 2026-08-23 auto-data-snapshot: 2026-08-23T04:19:03 (1 data files) — data/drain-old-drafts-latest.json
  91. 60d5c89 2026-08-23 Add labelIds support to POST /api/drafts and bulk-label endpoint
  92. ba0786d 2026-08-22 auto-data-snapshot: 2026-08-22T14:31:07 (1 data files) — data/bridge-alert-state.json
  93. e0f737f 2026-08-22 auto-data-snapshot: 2026-08-22T08:08:28 (1 data files) — data/bridge-alert-state.json
  94. 2bf4cf1 2026-08-22 auto-data-snapshot: 2026-08-22T04:22:57 (1 data files) — data/drain-old-drafts-latest.json
  95. 97120a5 2026-08-21 auto-data-snapshot: 2026-08-21T14:05:07 (1 data files) — data/bridge-alert-state.json
  96. c6c98a3 2026-08-21 auto-data-snapshot: 2026-08-21T08:13:58 (1 data files) — data/bridge-alert-state.json
  97. f15ac18 2026-08-21 auto-data-snapshot: 2026-08-21T04:29:08 (1 data files) — data/drain-old-drafts-latest.json
  98. 0c433b3 2026-08-20 auto-data-snapshot: 2026-08-20T14:07:04 (1 data files) — data/bridge-alert-state.json
  99. 5ab160f 2026-08-20 chore: sync package-lock to v1.2.0
  100. 3308e2b 2026-08-20 chore: v1.1.1 -> v1.2.0 (session close: info@ old-draft auto-drain)
  101. 9fa1a8a 2026-08-20 auto-data-snapshot: 2026-08-20T11:21:15 (1 data files) — data/drain-old-drafts-latest.json
  102. 0fd5c29 2026-08-20 Schedule daily info@ old-draft drain (launchd) with runaway cap + heartbeat
  103. 7106f29 2026-08-20 Add drain script: permanently delete info@ drafts older than 30 days
  104. f1e3c56 2026-08-20 auto-data-snapshot: 2026-08-20T08:00:08 (1 data files) — data/bridge-alert-state.json
  105. dd2149a 2026-08-19 auto-data-snapshot: 2026-08-19T14:01:25 (1 data files) — data/bridge-alert-state.json
  106. 8feac94 2026-08-19 auto-data-snapshot: 2026-08-19T08:31:04 (1 data files) — data/bridge-alert-state.json
  107. db120b7 2026-08-18 auto-data-snapshot: 2026-08-18T08:10:11 (1 data files) — data/bridge-alert-state.json
  108. 9087729 2026-08-17 auto-data-snapshot: 2026-08-17T08:03:25 (1 data files) — data/bridge-alert-state.json
  109. ef9251f 2026-08-16 auto-data-snapshot: 2026-08-16T08:23:33 (1 data files) — data/bridge-alert-state.json
  110. 83594ac 2026-08-15 auto-data-snapshot: 2026-08-15T08:12:28 (1 data files) — data/bridge-alert-state.json
  111. 6f7ee2f 2026-08-14 auto-data-snapshot: 2026-08-14T08:19:45 (1 data files) — data/bridge-alert-state.json
  112. 0d1ea76 2026-08-13 auto-data-snapshot: 2026-08-13T08:21:55 (1 data files) — data/bridge-alert-state.json
  113. a290b97 2026-08-12 auto-data-snapshot: 2026-08-12T08:23:24 (1 data files) — data/bridge-alert-state.json
  114. 973eaef 2026-08-11 auto-data-snapshot: 2026-08-11T20:01:55 (1 data files) — data/bridge-alert-state.json
  115. c7204f0 2026-08-11 auto-data-snapshot: 2026-08-11T08:06:39 (1 data files) — data/bridge-alert-state.json
  116. 037a7a4 2026-08-10 auto-data-snapshot: 2026-08-10T08:23:29 (1 data files) — data/bridge-alert-state.json
  117. 7286ade 2026-08-09 auto-data-snapshot: 2026-08-09T08:14:10 (1 data files) — data/bridge-alert-state.json
  118. 1d881ff 2026-08-08 auto-data-snapshot: 2026-08-08T08:25:55 (1 data files) — data/bridge-alert-state.json
  119. 798716a 2026-08-07 auto-data-snapshot: 2026-08-07T08:00:20 (1 data files) — data/bridge-alert-state.json
  120. 4514b6f 2026-08-07 security: npm audit fix --omit=dev (non-breaking) — resolves 6 vulns (3 moderate, 3 high) leaving 1 high (nodemailer; needs major bump to fix, gated)
  121. 921bc47 2026-08-06 auto-save: 2026-08-06T08:19:59 (1 files) — data/bridge-alert-state.json
  122. f4075be 2026-08-05 auto-save: 2026-08-05T08:10:17 (1 files) — data/bridge-alert-state.json
  123. a582deb 2026-08-04 auto-save: 2026-08-04T20:06:17 (1 files) — data/bridge-alert-state.json
  124. d1bf3c8 2026-08-04 auto-save: 2026-08-04T08:01:22 (1 files) — data/bridge-alert-state.json
  125. 8d2041f 2026-08-03 auto-save: 2026-08-03T20:27:06 (1 files) — data/bridge-alert-state.json
  126. 57102f6 2026-08-03 auto-save: 2026-08-03T08:22:05 (1 files) — data/bridge-alert-state.json
  127. fc6cb6f 2026-08-02 auto-save: 2026-08-02T08:13:37 (1 files) — data/bridge-alert-state.json
  128. a492884 2026-08-01 auto-save: 2026-08-01T08:05:12 (1 files) — data/bridge-alert-state.json
  129. 2131392 2026-07-31 auto-save: 2026-07-31T08:25:30 (1 files) — data/bridge-alert-state.json
  130. 7438631 2026-07-30 auto-save: 2026-07-30T08:16:08 (1 files) — data/bridge-alert-state.json
  131. 4fef724 2026-07-29 auto-save: 2026-07-29T08:07:14 (1 files) — data/bridge-alert-state.json
  132. bc88ec5 2026-07-28 auto-save: 2026-07-28T08:28:39 (1 files) — data/bridge-alert-state.json
  133. b05223a 2026-07-27 auto-save: 2026-07-27T14:21:14 (1 files) — data/bridge-alert-state.json
  134. 82cb9b3 2026-07-27 auto-save: 2026-07-27T08:19:05 (1 files) — data/bridge-alert-state.json
  135. 5aad99c 2026-07-26 auto-save: 2026-07-26T14:13:54 (1 files) — data/bridge-alert-state.json
  136. 569e097 2026-07-26 auto-save: 2026-07-26T08:11:40 (1 files) — data/bridge-alert-state.json
  137. a782058 2026-07-25 auto-save: 2026-07-25T14:06:43 (1 files) — data/bridge-alert-state.json
  138. 4beb2bc 2026-07-25 auto-save: 2026-07-25T08:04:52 (1 files) — data/bridge-alert-state.json
  139. 2435de6 2026-07-24 auto-save: 2026-07-24T14:28:09 (1 files) — data/bridge-alert-state.json
  140. 59af615 2026-07-24 auto-save: 2026-07-24T08:26:42 (1 files) — data/bridge-alert-state.json
  141. bb6e047 2026-07-23 auto-save: 2026-07-23T14:21:54 (1 files) — data/bridge-alert-state.json
  142. 742a6a4 2026-07-23 auto-save: 2026-07-23T08:19:35 (1 files) — data/bridge-alert-state.json
  143. 3d4e782 2026-07-22 auto-save: 2026-07-22T14:14:43 (1 files) — data/bridge-alert-state.json
  144. 7af8d23 2026-07-22 auto-save: 2026-07-22T08:12:31 (1 files) — data/bridge-alert-state.json
  145. cc6d073 2026-07-21 auto-save: 2026-07-21T14:07:54 (1 files) — data/bridge-alert-state.json
  146. f40bafb 2026-07-21 auto-save: 2026-07-21T08:06:27 (1 files) — data/bridge-alert-state.json
  147. 1720459 2026-07-20 auto-save: 2026-07-20T14:02:31 (1 files) — data/bridge-alert-state.json
  148. bf346cb 2026-07-20 auto-save: 2026-07-20T08:00:46 (1 files) — data/bridge-alert-state.json
  149. 7357bd8 2026-07-19 auto-save: 2026-07-19T14:26:43 (1 files) — data/bridge-alert-state.json
  150. fd852cb 2026-07-19 auto-save: 2026-07-19T08:25:31 (1 files) — data/bridge-alert-state.json
  151. 9d08c5a 2026-07-18 auto-save: 2026-07-18T14:21:38 (1 files) — data/bridge-alert-state.json
  152. f9948ba 2026-07-18 auto-save: 2026-07-18T08:20:26 (1 files) — data/bridge-alert-state.json
  153. 5f87f20 2026-07-17 auto-save: 2026-07-17T14:16:47 (1 files) — data/bridge-alert-state.json
  154. 373e7d4 2026-07-17 auto-save: 2026-07-17T08:15:49 (1 files) — data/bridge-alert-state.json
  155. 0432323 2026-07-16 auto-save: 2026-07-16T08:12:03 (1 files) — data/bridge-alert-state.json
  156. a078db7 2026-07-15 auto-save: 2026-07-15T12:36:34 (1 files) — data/bridge-alert-state.json
  157. c4c9ba4 2026-07-15 bridge: every-6-day 1-click renewal email to steve-office for the 2 consumer accounts (CADENCE mode) + dead-token safety net stays on 8/2/8
  158. a2eaa2d 2026-07-15 bridge: nudge to renew at day 5 (2-day runway before the 7-day expiry)
  159. bb1ae3a 2026-07-15 auto-save: 2026-07-15T12:06:24 (1 files) — data/
  160. 7b49e78 2026-07-15 feat(bridge): painless George token renewal — WARN/CRIT accounts get a one-click re-consent email (DTD verdict B, 2026-07-15)
  161. 76f3426 2026-07-15 docs: lead the OAuth runbook with the Internal-flip fast path + consumer-account fork
  162. fbeffde 2026-07-15 docs: service-account + domain-delegation runbook — permanent zero-expiry fix for steve@/info@ Workspace accounts
  163. 2c2c36d 2026-07-13 chore: version v1.1.1 (session close)
  164. a9140cb 2026-07-13 auth: unify both middlewares on resolved GEORGE_BASIC_AUTH cred + fail-closed on empty password (rotation completed 2026-07-13)
  165. b6e91b3 2026-07-10 auto-save: 2026-07-10T11:07:57 (1 files) — server.js
  166. e86cb67 2026-07-07 feat(api): add /api/attachment-local — fetch a Gmail attachment by filename to /tmp (normal auth, /tmp-only)
  167. ee22604 2026-07-03 chore: macstudio3 migration — reconcile from mac2 + repoint paths (stevestudio2→macstudio3, node/npm/npx→/opt/homebrew)
  168. 89b542a 2026-07-01 chore: v1.1.0 (session close — dormant SA/DWD path)
  169. 769c500 2026-07-01 george-gmail: pre-write dormant SA/DWD (domain-wide delegation) auth path for Workspace accounts
  170. 9e11f3d 2026-07-01 george: pre-emptive OAuth token-age monitor + durable-migration decision memo
  171. 3608276 2026-07-01 Add OAuth durability decision memo — retire the weekly-token treadmill (SA/DWD for Workspace, fold/WARN for consumer)
  172. 0ff8bf3 2026-06-26 auto-save: 2026-06-26T16:04:29 (1 files) — server.js
  173. 23d49a6 2026-06-24 send-with-attachment: resolve any account (steve-personal/agentabrams), not just office/info
  174. 2626a50 2026-06-24 Add DELETE /api/drafts/:id route + make GET /api/drafts account-aware (was always steve-office)
  175. a42bc74 2026-06-17 George external-send guard: block outbound sends to non-Steve recipients unless human-approval token present (X-Send-Approval / send_approval_token); fail-closed. Born from autonomous-worker auto-send incident 2026-06-17. NOTE: must also deploy to the Kamatera george (the instance MCP actually uses).
  176. 918647c 2026-06-17 Add launchd KeepAlive supervisor for george-gmail :9850 (durable fix for the unsupervised-death outage)
  177. 8c22e56 2026-06-12 secret-strip george-gmail: env-first the hardcoded George basic-auth cred (server) + remove browser-embedded copy (client)
  178. cf27272 2026-06-12 Add scoped one-shot trash for benign cron-drift alert flood in info@
  179. 8599fb2 2026-05-31 Link favicon.svg in head (use branded SVG, not .ico fallback)
  180. 0d55d20 2026-05-31 Add per-site favicon (kills /favicon.ico 404)
  181. ac33e88 2026-05-26 george-gmail: recover prod hand-patches (basic-auth env migration) into source + preserve Mac2 static-snapshot 404 guard
  182. 630eea8 2026-05-19 fix(public/index.html): wrap xfetch helper in <script> tags
  183. 941feb1 2026-05-19 Snapshot-files cleanup: untrack server.js.bak-*, broaden .gitignore, add 404 guard
  184. c2bdfca 2026-05-19 add trash-fleet-mailtests.js — one-off cleanup of 89 fleet delivery-test emails (modeled on trash-redfin-zillow.js)
  185. ab70d2e 2026-05-19 George: make the 'From job:' provenance banner opt-out (no_source_tag flag / x-george-no-tag header) so customer-facing sends can be banner-free
  186. fd1481e 2026-05-19 Fleet auto-responder: stop logging non-fleet mail to UI run-log (run-log tracks fleet inquiries only)
  187. 8f310af 2026-05-19 Fleet auto-responder: update usage docstring (daemon/force flags, HTML reply, UI)
  188. 2f343b5 2026-05-19 Fleet auto-responder: fix scan query (bare deliveredto:steve+ never matches in Gmail) + probe-subject safety guard (RETEST/FLEETMAILTEST/DTEST/PMRCV)
  189. e9fdf0f 2026-05-19 Fleet auto-responder: --daemon self-scheduling mode (pm2-managed, 3-min interval); UI on port 9768
  190. 9c7a470 2026-05-19 fleet-autoresponder UI: move port 9766→9768 (9766 reserved for agent-architect mindmap viewer)
  191. d6e30ce 2026-05-19 Fleet auto-responder: pm2 ecosystem config — 3-min cron schedule + UI process
  192. fb57ee9 2026-05-19 Fleet auto-responder admin UI dashboard (Express viewer, port 9766, pm2-managed)
  193. 13d4e3b 2026-05-19 Fleet auto-responder: multipart/alternative HTML+plain reply, structured run-log + state
  194. 7d77cad 2026-05-19 Fleet auto-responder: deliveredto query fix + --only-ids scoped-test flag
  195. d135118 2026-05-19 Add fleet auto-responder for the 44 dw-domain-fleet domains
  196. 02ef28b 2026-05-18 Make /api/profile honor ?account= param like the other 31 routes
  197. 2cbc311 2026-05-18 remove bogus stevesclaude account from George — theagentabrams@gmail.com (agentabrams) is the real AI account; also exempt /auth/agentabrams from basic auth
  198. 23979b0 2026-05-18 Add agentabrams (theagentabrams@gmail.com) as 5th george account slot
  199. ec801aa 2026-05-18 george /auth honors ?account= — dispatches to per-account re-auth routes
  200. a4d0448 2026-05-12 fix(george): ENV_PATH portable across Mac2/Kamatera
  201. facbb3b 2026-05-06 initial scaffold (gitify-all 2026-05-06)

Authors

  • Steve113
  • auto-commit-fleet88

Agents used

  • none detected

Skills used

  • /bridge-alert-state82
  • /drain-old-drafts-latest38
  • /claude12
  • /code12
  • /drain-keep-list7
  • /drafts7
  • /auth6
  • /health3
  • /info3
  • /live-check2
  • /calendar2
  • /agentabrams2
  • /search2
  • /archive-before-delete-2026-09-261
  • /unparseable1
  • /test-protected-survival1
  • /mock-george1
  • /snippet1
  • /vendor1
  • /per-roll1
  • /pricing1
  • /skills1
  • /george-token-age1
  • /bridge1
  • /latest1
  • /subject1
  • /date1
  • /e2e-proof1
  • /messages1
  • /bulk-label1

Creative ideas + design notes

Commits with substantial prose (≥120 chars) — the rationale behind each move.

c06e511 · 2026-09-23 · TK-11552: keep-list 6 content-bearing info@ drafts (Aug 26-28) + draft-id pins ahead of the 30-day drain
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E1xReXdF9rRznsRv8hyTve
fdc9515 · 2026-09-20 · 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
789f1c6 · 2026-09-13 · TK-11552: derive the protected set from the KEEP-LIST, not from isExempt() + gate contradictions
Second-model review (Grok) caught that b8f78c8 reproduced the very bug it was written
to fix. The census built protectedBefore with isExempt(d) -- the same predicate that
builds `matches`. A PARTIAL false-negative in isExempt (8 of 11 keep-listed drafts
recognized) drops the other 3 out of BOTH the census and the exemption: deleted,
never missed, reported PASS. Two paths cross-check each other only if they can
DISAGREE, which means they must be different code even when they compute the same set.

  - protectedBefore is now resolved by iterating KEEP_IDS against draft-id / message-id
    maps built from the listing. isExempt() is never consulted there.
  - CONTRADICTION GATE: if the keep-list says protect and isExempt selected the draft
    for deletion, the run ABORTS before any delete. That turns a partial false-negative
    from "permanent loss detected afterwards" into "loss prevented".
  - An O(1) re-check immediately before each DELETE closes the in-batch window (a batch
    of up to 500 deletes was previously only re-checked on the next batch).
  - A full 500-draft listing while a keep-list is in force now ABORTS: the mailbox
    cannot be fully seen, so neither the census nor the survival check is trustworthy,
    and deleting permanently while blind to what is protected is the one thing this
    must not do. Previously that was only a WARN.
  - A failed post-run re-read is FAIL when the run actually deleted something -- "I
    could not check" must not be quieter than "I checked and it was fine". WARN when
    nothing was deleted, since there was nothing to lose.
  - An empty/unparseable listing is reported as a READ FAILURE, not as "everything was
    lost": same safe action, but the reason must not be fabricated.

Tests 20 -> 25 assertions, 5 -> 6 cases. CASE 6 is the headline: it breaks isExempt on
purpose and asserts the run aborts with deleted=0. b8f78c8 passed every other case in
this file while still deleting the protected draft.

Live read-only check of the new logic against info@: 27 keep-list ids -> 12 protected
drafts + 3 unresolved (matches the canary); >22d population 14 -> would delete 3 (the
3 known-abandoned empties), 0 contradictions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBRBeHTnkTTdi3DYLePNSY
b8f78c8 · 2026-09-13 · TK-11552: drain asserts protected drafts SURVIVED, not just that it meant to skip them
The exemption filter and the success verdict were the same code path: matches was
built with !isExempt(d) and the verdict was (totalFail>0 ? WARN : PASS). If isExempt
were ever wrong, the run would permanently delete (no Trash) every keep-listed draft
AND report PASS. A filter cannot audit itself.

Adds an outcome-side check observed through a different read than the one that made
the decision:
  - census the keep-listed drafts that are LIVE before the drain loop
  - re-verify their presence before each batch's deletes; a loss ABORTS the pass
    (aborting can only prevent deletions, never cause one)
  - re-read once more at the end; any loss is FAIL with the ids named

NOT-MEASURED is never PASS (TK-11431 rule 1): a truncated 500-draft listing, a failed
post-run re-read, or a keep-list whose ids resolve to no live draft each degrade to
WARN with protected_verified null, rather than a clean PASS that implies protection
was proven. New heartbeat fields: protected_before, protected_lost_count,
protected_lost_ids, protected_verified, census_truncated, protection_note.

Ships test/test-protected-survival.sh + test/mock-george.js (TK-11431 rule 3): 17
assertions over 5 cases against an isolated copy and a loopback mock, proving the
check goes RED on an injected fault on BOTH abort paths (mid-drain and post-loop) and
on all three not-measured paths. Harness guards rc=127 and a missing heartbeat so a
case that never ran cannot read as a pass.

Also fixes the archive filename: stamp.slice(0,8) on an ISO stamp yielded
"archive-before-delete-2026-09-.jsonl" (month, truncated). Now slice(0,10) = the date.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBRBeHTnkTTdi3DYLePNSY
b4c4762 · 2026-09-13 · TK-11552: keep-list exempts on EITHER id — Gmail rotates message.id on draft edit
The drain matched exemptions on d.message.id but deletes by the draft id. Gmail mints a
NEW message id every time a draft is saved, while the draft id is stable — proven live:
draft r-5517420768746463086 kept its id while its message id went 1a09ba5cc706157b ->
1a09ba5ce13ee153 across one edit. So editing a protected draft silently dropped its
protection and the 04:15 permanent-delete (no Trash) could destroy it. That fired in
exactly the workflow the TK-11552 memo asks Steve to perform: open info@ and
send / rewrite-and-send / drop.

- isExempt(d): match message.id OR draft id. Strictly preservative — can only ADD
  exemptions, never remove one.
- Pinned the 12 live keep-list entries by their stable draft id as well (15 -> 27).
- DRAIN_KEEP_LIST test seam (mirrors the canary's). Verified the launchd plist does NOT
  set it; its env block carries only CONFIRM/MAX_AUTO_DELETE/PATH.
- test-draftid-exempt.sh 3/3 against the real script + real drafts, dry-run only. Case B
  (pin removed -> deletable=1) is the negative control that proves the test can go red.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdKLxdaTGYBRTVGAYwi8Vo
4411b8c · 2026-09-13 · drain: FAIL CLOSED when the keep-list cannot be loaded (it failed OPEN)
The keep-list loader was:
    try { JSON.parse(readFileSync(KEEP_PATH)) } catch (_) { return {}; }

That fails OPEN. A corrupted, truncated, wrong-shaped, or deleted keep-list
silently became 'zero exemptions', and the very next 04:15 run would
PERMANENTLY delete (Gmail drafts.delete, no Trash) every draft the keep-list
existed to protect. Every protection this file provides — TK-11231's held vendor
asks and TK-11552's 9 live customer replies — rested on a file whose read
failure was swallowed without a word.

A no-Trash deleter must never infer 'nothing is protected' from 'I could not
read what is protected'. Now any failure to load a usable keep-list ABORTS the
deletion pass (exit 1) and writes a FAIL heartbeat so it surfaces on the fleet
panel instead of dying quietly. ENOENT aborts too: 'nothing is exempt' must be
STATED by writing '{}' — a deliberate one-second act — because an absent file is
indistinguishable from one that went missing. Dry runs only warn, so the script
stays usable for diagnosis when the keep-list is broken.

Strictly preservative: this change can only ever prevent deletions, never cause
one.

Ships test-keeplist-failclosed.sh — 5 cases (missing / corrupt / wrong-shape must
ABORT; explicit {} and dry-run must NOT). SAFETY: it runs an isolated copy of the
script in a throwaway dir pointed at a dead GEORGE_BASE port, so even a FAILED
abort has no reachable mailbox to delete from. The harness also guards itself
against rc=127 — the first draft of this test silently 'passed' a case because
the command never ran.

Found by an adversarial second-model review (Kimi k3) of the TK-11552 keep-list
work, which correctly pointed out I had fail-safed the canary but not the
delete site.

TK-11552

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ux1A8z45sLwUTNzz8kxn68
1fb6464 · 2026-09-13 · george drain keep-list: exempt the 9 content-bearing TK-11552 info@ reply drafts from the 30-day permanent purge
The 30-day info@ drain (com.steve.george-drain-old-drafts, daily 04:15) permanently
deletes drafts with NO Trash. 13 aged info@ drafts were on that fuse; 2 were already
keep-listed (TK-11231), 11 were not. Verified content: 9 carry live customer/vendor
data (a shipping PRO# the customer must call in, per-yd/per-roll net quotes, stock +
backorder terms, vinyl 60cm production specs, 8-panel mural set dims/pricing), 2 are
genuinely abandoned (one is pure signature, one is the word 'Looks').

Keep-listing the 9 removes the permanent-loss DEADLINE without sending anything, so
Steve decides on his own timing instead of racing a 04:15 cron. The first of these
would have been destroyed at the 2026-09-14 04:15 run.

Verified with a two-sided negative test against the drain's real predicate
(older.has(id) && !KEEP_IDS.has(id)): simulating all 13 as >30d, exactly the 2
abandoned drafts remain deletable and all 11 content-bearing are exempt.

Reversible: cp /tmp/drain-keep-list.PRE-TK11552.json data/drain-keep-list.json
(or git revert). No send, no externality, no Gmail write.

TK-11552

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ux1A8z45sLwUTNzz8kxn68
1cb1b7f · 2026-09-12 · george token-bridge: fix calendar re-consent link (TK-11553)
AUTH_ROUTE mapped GOOGLE_CALENDAR_REFRESH_TOKEN -> 'steve-office', but server.js
writes GOOGLE_CALENDAR_REFRESH_TOKEN only under the dedicated calendar flow
(state:'calendar', /auth/calendar). The steve-office /auth flow writes
GMAIL_REFRESH_TOKEN, so every automated re-consent nudge for a dead calendar
token pointed Steve at a flow that re-minted the healthy Gmail token and never
revived the calendar token. Advisory link text only; no runtime token behavior
changed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VggjiaLGkK4kjR1zCnVJ65
50cc8f3 · 2026-09-12 · chore: v1.2.1 (session close) — TK-11529
Shellcheck-clean addition (matches existing SC2015 style used elsewhere
in the file); no refactor needed for an 8-line, already-verified change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ASu1mqEndsJjj5bpueXKiE
ce59ce6 · 2026-09-12 · Chain bridge.mjs onto token-age-warn.sh so the fleet-health artifact stays fresh
cron-fire-canary was flagging com.steve.george-token-age as not-firing, but the
launchd job itself was firing fine every 6h — it's the separate bridge.mjs
republish script (~/.claude/skills/george-token-age/bridge.mjs, TK-11371) that
writes the monitored artifact, and it had no schedule of its own (its plist
was deliberately left uninstalled). Riding the detector's already-approved
cadence keeps data/latest.json current without installing a new scheduled job.

TK-11529

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ASu1mqEndsJjj5bpueXKiE
e7ccd8a · 2026-09-10 · TK-11365: never send the internal "From job:" banner to an outside party
The banner is an internal provenance aid and was OPT-OUT (inferSource), so any
caller that forgot no_source_tag prepended an internal job label to the top of
vendor and customer mail. Proven in prod on a real customer - four emails opening
"From job: kelly-reply-tk10830" - and found today on 14 of 15 queued vendor
drafts in info@ (WallQuest, York, Thibaut, Kravet, Brewster, MDC, Astek, OAL,
Sancar, Innovations, Egg-and-Dart, Quadrille).

The opt-out was also unreachable from the agent path: the george MCP tool
gmail_create_draft does not expose no_source_tag at all, so an agent drafting
vendor mail through the MCP could not suppress the banner even knowing about it.

Now: if ANY recipient is external, no banner. Internal mail keeps it, so the
provenance aid survives where it is actually useful. Fails open on error.

Logic unit-tested 5/5 offline: external, mixed internal+external, internal-only,
the internal gmail allowlist, and the explicit opt-out.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
0d2c8e2 · 2026-09-10 · george: give the 30-day draft drainer a KEEP-LIST — TK-11231
The drainer was a blanket age filter with no exemption mechanism, so a READY,
wanted draft sat on exactly the same permanent-delete fuse as the garbage. That
is why a live vendor cost-list ask (Newmor / LBI Boyd, 1,294 rows priced at 0)
was days from being destroyed with no Trash, purely because it happened to live
on info@ instead of steve-office.

Being able to protect a wanted draft WITHOUT sending it is the missing
capability: 'send it' and 'lose it' should never have been the only two options.

data/drain-keep-list.json maps messageId -> why it is kept; those ids are never
deleted at any age. Seeded with the Newmor ask; the entry documents that it is
held deliberately and says to remove it once sent or dropped.

Verified against the real patched predicate (extracted from source, not
retyped): a keep-listed >30d draft is excluded, an unprotected >30d draft is
still deleted (drainer not weakened), and fresh drafts stay untouched. Live dry
run confirms the list loads.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
efdf4ea · 2026-09-10 · george: draft contract + duplicate detection at the SHARED layer — TK-11231
Red-team finding: the duplicate refusal shipped in george-mcp protects exactly
ONE client. Anything POSTing /api/drafts directly — cron scripts, other repos,
Kamatera, the ~20 caller scripts under DW-Agents — walked straight past it. A
single locked door on a building with no walls.

Now, for EVERY caller:
- POST /api/drafts returns status=awaiting_owner_send, delivered=false,
  recipient_has_not_received_this=true + the do-not-close-the-ticket warning.
  Free, non-breaking, and it reaches callers the MCP never touched.
- audit() records acknowledged_existing and duplicates_to_recipient, so it is
  now possible to LEARN that the escape hatch went reflexive. Previously there
  was no telemetry pointed at that trapdoor.
- Duplicate detection behind GEORGE_DRAFT_DEDUPE: off (default) | warn | enforce.

Default is off DELIBERATELY: the check costs one extra Gmail search per draft,
and the bulk callers here (sample-followup drafts many vendors in a burst) are
exactly the traffic that already exhausted the per-minute quota once. Turning
it on is a one-env-var decision for Steve, not a surprise I impose on a shared
bridge. 'warn' reports and never blocks; 'enforce' 409s and fails CLOSED on an
unverifiable check.

Verified on an ISOLATED instance (free port, live :9850 untouched): enforce
returns 409 naming the existing draft id; default-off leaves callers unchanged
while still returning the new contract. Probe draft deleted; counts back to
the 136/41 baseline.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VTxE4MgnygQ9EY2rPZvtcK
bf2fe4c · 2026-09-10 · george: add PUT /api/drafts/:id (drafts.update) — TK-11231
The create-only draft surface is what let agents compose DUPLICATE vendor asks
(Command x2, Maya Romanoff x2, Newmor x2): an agent that found its own stale
draft had no verb to revise it, so it composed a second one beside it.
drafts.update replaces in place, so revising is idempotent and leaves no
second artifact. Echoes the overwritten to/subject/date back as `previous`
so a bad edit is recoverable. Returns status=awaiting_owner_send /
delivered=false — a draft is never a delivered outcome.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VTxE4MgnygQ9EY2rPZvtcK
60d5c89 · 2026-08-23 · Add labelIds support to POST /api/drafts and bulk-label endpoint
- POST /api/drafts now accepts optional `labelIds` array — after draft creation,
  applies labels via users.messages.modify (the reliable path since drafts.create
  raw-message doesn't surface custom labelIds directly). Non-fatal if label apply fails.
- New POST /api/messages/bulk-label endpoint — searches messages by query `q` and
  applies/removes label IDs via batchModify in 1000-item chunks; paginated up to
  5000 messages; supports dryRun mode.
- New bulk-label-sample-followup-drafts.js script — uses the new endpoint to label
  all existing info@ drafts matching subject:"Sample Follow-Up" with the
  "sample-followup" label (TK-10744 info@ drafts cleanup).

Deploy to Kamatera (pm2 restart george) is gated — Steve-go required.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
0fd5c29 · 2026-08-20 · Schedule daily info@ old-draft drain (launchd) with runaway cap + heartbeat
- Daily 04:15 launchd job com.steve.george-drain-old-drafts runs the drain
  in CONFIRM=1 to keep info@ drafts >30d trimmed automatically.
- Runaway guard: MAX_AUTO_DELETE=800 — if a run ever finds a huge backlog it
  refuses to auto-delete and emits WARN for human review (never silent mass-nuke).
- Heartbeat data/drain-old-drafts-latest.json in PASS/WARN/FAIL vocabulary for
  the fleet-health rollup + meta-watchdog.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
7106f29 · 2026-08-20 · Add drain script: permanently delete info@ drafts older than 30 days
Batched drain (drafts.list caps at 500, no pagination): discovers the
authoritative in:drafts older_than:30d message-id set once, then loops
list->delete-matches until none remain. Quota backoff, dry-run default,
per-deletion jsonl log. Steve-authorized run 2026-08-20: 3665 deleted, 0 failed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
7b49e78 · 2026-07-15 · feat(bridge): painless George token renewal — WARN/CRIT accounts get a one-click re-consent email (DTD verdict B, 2026-07-15)
token-age-warn.mjs computed WARN/CRIT but never alerted. token-bridge.mjs reads its latest.json and emails Steve a "Renew now" button (George /auth/<account>) for each aging/dead token, throttled 1/20h per account, +CNCP card. 3x/day launchd plist runs monitor then bridge. Verified: emailed 4 accounts (steve-personal dead, calendar dead, steve@/info@ aging).
769c500 · 2026-07-01 · george-gmail: pre-write dormant SA/DWD (domain-wide delegation) auth path for Workspace accounts
Adds a service-account/JWT impersonation path alongside the existing OAuth refresh-token
path so the Workspace pair (steve@ + info@) can drop off the ~7-day Testing-mode token
treadmill. Fully DORMANT by default: with no GOOGLE_SA_KEYFILE set, saAuthFor() returns
null and the SA-override block is a no-op — every account keeps its refresh-token client
byte-for-byte. Arm via GOOGLE_SA_KEYFILE + GEORGE_SA_ACCOUNTS=steve-office,info (Workspace
only; consumer gmail keys can never be SA-backed). resolveAccount() reads the module-level
service-client vars per request, so it transparently picks up the SA clients when armed.

Verified by boot tests: dormant -> 0 SA lines + all accounts on refresh tokens; armed w/
missing or bogus keyfile -> clear warn + safe fallback + still boots. Live JWT impersonation
awaits Steve's gated console work (SA creation + domain-wide delegation authorization).
Arming checklist + rollback in PLAN-OAUTH-DURABILITY.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
9e11f3d · 2026-07-01 · george: pre-emptive OAuth token-age monitor + durable-migration decision memo
Council #3 — end the weekly-token treadmill. Read-only token-age-warn.mjs probes each
refresh token against Google's token endpoint (idempotent refresh, never writes creds) and
combines validity with a hash-tracked age; WARNs ~1d before the 7d Testing-mode expiry,
CRIT on invalid_grant. Alerts on worsening transition only (CNCP + George email via run.sh).
First run surfaced a live finding: GOOGLE_CALENDAR_REFRESH_TOKEN is DEAD (appointments push
silently broken). Migration paths (service-account/DWD or Internal user-type) in
PLAN-OAUTH-DURABLE-TOKENS.md — those steps are Steve-gated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
918647c · 2026-06-17 · Add launchd KeepAlive supervisor for george-gmail :9850 (durable fix for the unsupervised-death outage)
Tracked plist + install note. Not bootstrapped here — Steve runs the bootstrap
(starts the email service + touches launchd). RunAtLoad+KeepAlive so the :9850
send service self-respawns; daily-overnight-skus + idea-loop-digest were only
failing because this send path died.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
630eea8 · 2026-05-19 · fix(public/index.html): wrap xfetch helper in <script> tags
xfetch was sitting OUTSIDE any <script> tag, wedged between the
react.production.min.js and react-dom.production.min.js script tags.
Browsers parsed it as text content → xfetch was undefined → every
api()/apiPost() call (lines 67-74, 88, 96, 105, 113) threw ReferenceError
on page load. George admin inbox UI was unusable in any browser.

Pure positional fix — wrapped the existing one-line function in
<script>...</script>. The inline basic-auth credential
("admin:DWSecure2024!") was already in this file at lines 60 + 66 before
this commit and is unchanged by it. Credential rotation across the fleet
is tracked separately (cred is also in dozens of sibling repos per MEMORY).

Surfaced by tick 169 session #102 Teal mechanical refactor pass; that
pass deferred the fix because the diff touches the cred line, but the
LIVE-BROKEN production UI takes precedence — fixing now, rotation later.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
941feb1 · 2026-05-19 · Snapshot-files cleanup: untrack server.js.bak-*, broaden .gitignore, add 404 guard
- Untrack the 7-month-old server.js.bak-20260421-094351 snapshot that was
  tracked in the repo (still present on disk, now ignored).
- Broaden .gitignore to cover *.bak.*, *.bak-*, *.pre-*, *.orig, *.rej,
  *.swp, *~ so future snapshots don't slip back in.
- Add a static-snapshot 404 guard middleware that runs before
  express.static('public'); any URL matching .bak, .bak-*, .pre-*, .orig,
  .rej, .swp, or trailing ~ returns 404 regardless of disk state.
  Defense-in-depth on top of the existing basic-auth gate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7d77cad · 2026-05-19 · Fleet auto-responder: deliveredto query fix + --only-ids scoped-test flag
Tested live on barwallpaper.com + grassclothwallcovering.com (2-domain test):
- Gmail does not match plus-tags via to:; switched candidate query to
  deliveredto: and rely on Delivered-To header parsing for domain recovery.
- Added --only-ids flag so a scoped test does not touch the inbox backlog
  of unrelated probe mail.
- Verified: reply sent from info@designerwallcoverings.com landed in the
  recipient INBOX (not Spam), correct per-domain sign-off, no loop, label
  + 7-day ledger dedup both idempotent on re-run.

Ledger (data/fleet-autoresponder-ledger.json) gitignored — runtime state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
d135118 · 2026-05-19 · Add fleet auto-responder for the 44 dw-domain-fleet domains
Sends one warm acknowledgment from info@designerwallcoverings.com to genuine
new customer inquiries arriving at info@<fleet-domain> (forwarded into the
steve-office Gmail via Purelymail catch-all + plus-tag). Reply-To is set to
info@<fleet-domain> so customer replies route back to the right site.

Spam-safety: skips Spam, no-reply/bounce senders, bulk/auto/list mail, and any
mail from designerwallcoverings.com or a fleet domain (loop guard). One reply
per sender per rolling 7-day window (persisted ledger). Marks every processed
message with the 'fleet-autoreplied' Gmail label — idempotent on re-run.

Reuses George's DW-MCP/.env OAuth credentials; no prod deploy needed.
Not yet scheduled — run manually with: node fleet-autoresponder.js

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
a4d0448 · 2026-05-12 · fix(george): ENV_PATH portable across Mac2/Kamatera
ENV_PATH was hardcoded to /root/Projects/Designer-Wallcoverings/DW-MCP/.env
which only exists on Kamatera. On Mac2 the same .env lives at
~/Projects/Designer-Wallcoverings/DW-MCP/.env. The readFileSync would
quietly fail in the try/catch upstream, INFO_REFRESH_TOKEN stayed empty,
and the boot log said 'Info@ not configured' even though the token IS
in the Mac2 .env.

Fix: try $DW_MCP_ENV then $HOME/Projects/.../DW-MCP/.env then the
legacy /root/ path. First readable wins.

Symptom that exposed this: GET /api/search?account=info returned
'unknown account: info' because infoGmail was never built. After
restart with the fix, log says 'Info@ account initialized (full
Workspace)' and /api/search returns the real DW inbox (201 messages
for a Shopify-orders query, including 'New Order on the New DW').

File tree

43 files tracked. Click any to browse the source at HEAD.

Other build journals

← Generative Art Canvas  ·  all 4 projects  ·  George Mcp →

Export

commits.csv · feed.atom · project.json · commits.json

rendered in 6ms