"""One-off PRODUCTION probe for ``software_seat.idle_reclaim``. The idle-reclaim cron ships **disabled** (migration 204, ``enabled=0``) because warnings are user-facing and must not fire against an unverified Atlassian Org Admin API ``product_access`` JSON shape — and that shape can only be observed on the prod worker, since ``CREDENTIAL_ENCRYPTION_KEY`` is production-only. This script does the one-time verification: 1. Pulls the real Org Admin API directory and prints the raw shape of one account record + one ``product_access`` entry, so a human can confirm the parser's field names (``account_id`` / ``email`` / ``account_status`` / ``last_active`` / ``product_access[].{key,url, last_active}``) match reality. 2. Spot-checks a few known daily users — prints the computed production-site MAX ``last_active`` per product. These people must NOT show as years-idle (the bug the previous attempt shipped, where a sandbox's frozen ``last_active`` won). 3. Dry-runs the handler (``config={"dry_run": True}``) — computes every warn / reclaim / undetermined / admin-skip / manual-review decision WITHOUT sending a single notification or removing a single group — and prints the summary counts. Eyeball these before flipping the cron to ``enabled=1``. Run on the prod worker (output redirected — SSM truncates ~2400 chars): aws ssm send-command --region ap-southeast-1 \\ --instance-ids i-0c15d98cba3ebba9c \\ --document-name AWS-RunShellScript \\ --parameters 'commands=["sudo docker exec -e PYTHONPATH=/app -w /app \\ compose-worker-1 /app/.venv/bin/python \\ server/scripts/probe_idle_reclaim.py > /tmp/idle_probe.txt 2>&1; \\ cat /tmp/idle_probe.txt"]' Read-only except for the dry-run's single ``write_sync_audit`` row (records that a dry-run happened) — it never warns, reclaims, or removes a group. """ from __future__ import annotations import asyncio import json # Accounts to spot-check — daily prod users who must NOT read as idle. # Override via argv if you want to probe different people. _SPOT_CHECK_EMAILS = ( "hu.guo@pingcap.com", "shuai.feng@pingcap.com", "charles.m@pingcap.com", ) async def main() -> None: import app.main # noqa: F401 — registers ORM mappers + drivers from app.core.coordinators.jira_seat_coordinator import load_jira_driver from app.core.database import async_session_factory from app.integrations.atlassian_activity import ( ALL_TRACKED_PRODUCTS, fetch_atlassian_activity, ) from app.scheduled_jobs.idle_reclaim_handlers import ( run_software_seat_idle_reclaim, ) async with async_session_factory() as db: driver = await load_jira_driver(db) if driver is None: print("ABORT: no enabled Jira integration (provider=jira).") return users = await driver.list_org_users_with_activity() print(f"[1] org accounts pulled: {len(users)}") if users: sample = users[0] print(f"[1] sample record top-level keys: {sorted(sample.keys())}") pa = sample.get("product_access") or [] print( "[1] sample product_access[0]: " + json.dumps(pa[0] if pa else None, ensure_ascii=False), ) directory = await fetch_atlassian_activity(db, driver=driver) print(f"[2] directory indexed by email: {len(directory.by_email)}") for email in _SPOT_CHECK_EMAILS: acct = directory.lookup_by_emails(email) if acct is None: print(f"[2] {email}: NOT FOUND in org directory") continue la = { p: ( acct.last_active_for(p).isoformat() if acct.last_active_for(p) else None ) for p in ALL_TRACKED_PRODUCTS } print( f"[2] {email}: status={acct.account_status} " f"admin={acct.is_org_or_site_admin} prod_last_active={la}", ) # Dry-run in its own session: no warn / no reclaim / no group removal. async with async_session_factory() as db: summary = await run_software_seat_idle_reclaim(db, {"dry_run": True}) compact = { k: v for k, v in summary.items() if not isinstance(v, list) } compact["errors_sample"] = (summary.get("errors") or [])[:5] print("[3] DRY-RUN SUMMARY: " + json.dumps(compact, ensure_ascii=False, default=str)) print("DONE. If [2] shows recent prod dates for daily users and [3] " "warned/reclaimed look sane, enable the cron in /admin/scheduled-jobs.") if __name__ == "__main__": asyncio.run(main())