A Cloud Run Job that posts one message a week to a private Slack channel: what changed across every connected Google Workspace organization, sorted by how loudly it should be saying it.
It reuses workspace.py’s per-tenant delegated credentials, so it needs no
new scopes and no new Admin Console work — if Greg can answer questions
about a workspace, the digest already covers it. It is as read-only as the
assistant is.
Greg answers well when someone remembers to ask. The most valuable thing it found in its first week — an organization with zero 2SV enrollment — surfaced by accident, as a footnote to a question about licenses. The digest flips that around: the report arrives whether or not anyone thought to ask.
Google Workspace weekly digest - 2026-09-04
<one-sentence headline>
30/30 workspaces | 2,925 users | 2,074/2,248 licenses (174 free)
:red_circle: Needs attention now
- 8 super admins without 2-Step Verification in 6 orgs
- Schmidt Entity Sandbox: jfrum@..., jjaquez@..., jjimenez@...
:credit_card: Licenses
- Out of licenses (5): The Schmidt (182/182), Ocean Hour Farm (72/72), ...
- Nearly out (15): AGOG (3), Rise for the World (2), ...
- Reclaimable: 174 suspended accounts still holding licenses ...
- Fleet: 2,074/2,248 in use, 174 free
:large_orange_circle: Heads up
- New third-party apps authorized
- No 2SV enforcement policy in 17 orgs (1,302 accounts) - unchanged from last week
:white_circle: Trends
- 943 dormant accounts (no sign-in in 90 days) and 125 never signed in
Tier is severity and decides whether anyone is @-mentioned: red and
amber findings ping, info never does. Section is layout — what sits
next to what. Licenses are amber (so they still ping) but display in their own
block, because that is what the IT team acts on most weeks and it should not
be hunted for inside a list of security items.
| Section | Contents |
|---|---|
| :red_circle: Needs attention now | Super admin without 2SV, new super admin, 2SV enforcement policy removed, all SSO assignments removed, Google-flagged suspicious logins, a workspace that failed to report |
| :credit_card: Licenses | Out of licenses, nearly out, reclaimable suspended seats, fleet total. Always shown, even on a clean week |
| :large_orange_circle: Heads up | New third-party OAuth app, new domain, no 2SV enforcement policy |
| :white_circle: Trends | Dormant and never-signed-in accounts |
If nothing is red, the first section becomes a green “Nothing needs attention now” rather than vanishing, so a clean week reads as clean instead of broken.
These are the things that will look arbitrary later and are not.
audit_events only sees a rolling
7-day window, so comparing one window to the next reported a long-standing
backup tool as newly authorized in six organizations the moment audit
ingestion lagged. The snapshot keeps every app ever seen, so each is
announced exactly once.agent.run_text_only and frames the findings explicitly as data rather than
instructions. A test asserts it never calls the tool-enabled agent.num_users_2sv_enforced
parameter went to zero in all 13 enforcing orgs at once while every admin
policy was untouched. Policies are the configuration itself: an ADMIN
policy with a real enforcedFrom means enforced, the Unix epoch means that
OU is exempt, and SYSTEM entries (Google’s own defaults and its 2026
mandatory-2SV rollout) are ignored. The report still supplies enrollment
counts, which stayed sane throughout.isEnrolledIn2Sv == False counts as unprotected. A
missing field means unknown, and a security alert that cries wolf gets muted.num_retries on the Reports and
Directory calls). On 2026-10-05 a single 500 each cost three orgs their
whole section.Non-secret config lives in digest.env.yaml; secrets come from Secret Manager.
| Variable | Purpose |
|---|---|
DIGEST_SLACK_CHANNEL |
Channel id to post into (required unless dry run). |
DIGEST_MENTION_USER_IDS |
Space/comma separated member ids, @-mentioned only on red/amber. |
DIGEST_STATE_BUCKET / DIGEST_STATE_OBJECT |
GCS location of the snapshot. |
DIGEST_LICENSE_WARN_FREE |
Free seats at or below this is “nearly out”. Default 3. |
DIGEST_DORMANT_DAYS |
Dormancy threshold in days. Default 90. Set 0 to skip. |
DIGEST_DRY_RUN |
Print instead of posting; writes no snapshot. |
DIGEST_HEADLINE |
Set false to skip the model-written headline. |
GW_EXCLUDED_WORKSPACES |
Shared with the service; excluded tenants are not reported. |
gcloud storage buckets create gs://gw-admin-assistant-digest-state `
--project gw-admin-assistant --location us-central1 `
--uniform-bucket-level-access
gcloud storage buckets add-iam-policy-binding gs://gw-admin-assistant-digest-state `
--project gw-admin-assistant `
--member serviceAccount:1039144753775-compute@developer.gserviceaccount.com `
--role roles/storage.objectAdmin
Then invite Greg to the private channel and put its id in digest.env.yaml.
gcloud run jobs deploy gw-weekly-digest `
--project gw-admin-assistant --region us-central1 `
--source . `
--command python --args digest_job.py `
--task-timeout 30m --max-retries 1 `
--service-account 1039144753775-compute@developer.gserviceaccount.com `
--set-secrets SLACK_BOT_TOKEN=gw-assistant-prod-slack-bot-token:latest,ANTHROPIC_API_KEY=gw-assistant-anthropic-api-key:latest `
--env-vars-file digest.env.yaml
Dry run first — prints the digest, posts nothing, writes no snapshot:
gcloud run jobs execute gw-weekly-digest --project gw-admin-assistant `
--region us-central1 --wait --update-env-vars DIGEST_DRY_RUN=true
Read it back out of the logs:
gcloud logging read 'resource.type="cloud_run_job" labels."run.googleapis.com/execution_name"="<execution>"' `
--project gw-admin-assistant --limit 300 --format "value(textPayload)"
Then a real run with DIGEST_DRY_RUN=false.
Monday 10:00 Pacific, so it lands while the IT director is at his desk. Cloud Scheduler handles PST/PDT itself, so it does not drift an hour in summer.
gcloud scheduler jobs create http gw-weekly-digest-monday `
--project gw-admin-assistant --location us-central1 `
--schedule "0 10 * * 1" --time-zone "America/Los_Angeles" `
--uri "https://us-central1-run.googleapis.com/apis/run.googleapis.com/v1/namespaces/gw-admin-assistant/jobs/gw-weekly-digest:run" `
--http-method POST `
--oauth-service-account-email 1039144753775-compute@developer.gserviceaccount.com
.\.venv\Scripts\python.exe -m unittest test_digest_job -v
36 tests: license parsing across SKUs, every finding kind, the grouping guarantees, chronic-unchanged marking, headline grounding, OAuth accumulation, mention-only-on-red/amber, the green all-clear, snapshot round-tripping, the usage-report walk-back past unpublished days, and the compactness guard.
DIGEST_DORMANT_DAYS counts what Google reports as lastLoginTime.
Shared mailboxes, service accounts and delegated-only users can look dormant
without being abandoned. Verify a sample before acting on a large number
(Hillspire reported 339 on the first run).