feat(scouts): debug scripts + deliver_pending diagnostic logs

- scripts/trigger_gmail_scout.py: manually fire ScoutEngine.trigger_scout
- scripts/inspect_gmail_scout_token.py: decrypt + show stored OAuth scopes
- scripts/show_gmail_scout_state.py: print scout config + queue/log counts
- scripts/reset_triage_queue_to_queued.py: revert delivered → queued for re-delivery
- engine.py: info logs around deliver_pending (rows found, send_json roundtrip)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Roberto
2026-06-11 00:27:04 +02:00
parent f64ca11888
commit 79a926e4d8
5 changed files with 227 additions and 0 deletions

View File

@@ -0,0 +1,56 @@
"""Decrypt and inspect the Gmail scout's stored OAuth token.
Shows what scopes were granted at consent time. If gmail.readonly / gmail.modify
are missing, the consent screen didn't actually grant them.
Usage:
python scripts/inspect_gmail_scout_token.py
"""
from __future__ import annotations
import asyncio
import sys
from pathlib import Path
_API_ROOT = Path(__file__).resolve().parent.parent
if str(_API_ROOT) not in sys.path:
sys.path.insert(0, str(_API_ROOT))
from sqlalchemy import select
from app.db import async_session
from app.integrations import decrypt_token
from app.models import CloudScoutConfig
async def main() -> None:
async with async_session() as session:
scouts = (
await session.execute(
select(CloudScoutConfig).where(CloudScoutConfig.provider == "gmail")
)
).scalars().all()
if not scouts:
print("No Gmail scouts found.")
return
for scout in scouts:
print(f"\nScout: {scout.name} (id={scout.id})")
if not scout.oauth_token_encrypted:
print(" (no token stored)")
continue
try:
creds = decrypt_token(scout.oauth_token_encrypted)
except Exception as exc:
print(f" decrypt failed: {exc}")
continue
print(f" has refresh_token: {bool(creds.get('refresh_token'))}")
print(f" stored scopes: {creds.get('scopes')}")
print(f" token_uri: {creds.get('token_uri')}")
print(f" client_id (last 8): ...{(creds.get('client_id') or '')[-8:]}")
if __name__ == "__main__":
asyncio.run(main())