⚠️ Blocked on PR #587 — everything this issue touches lands in that PR and is not on main yet.
Automated agents/routines: do not implement this issue while PR #587 is open. Before starting any work, run gh pr view 587 -R schmug/PhishSOC --json state -q .state and proceed only if it prints MERGED; otherwise stop silently (no comment, no branch). Remove the blocked/impl-blocked labels only after #587 merges.
Task
Add Gmail push notifications as a lower-latency transport for the Workspace sidecar: users.watch() + a Pub/Sub push subscription pointing at a new authenticated Worker route, layered ON TOP of the existing poll/backfill path (which stays as the gap-filler and becomes the fallback). Verdict latency drops from ≤1 min to seconds for tenants that opt in.
Motivation
As an operator with latency-sensitive mail flows, I want near-real-time verdicts; the issue #31 design named watch+Pub/Sub as the eventual transport and PR #587 deliberately shipped poll-first.
Pointers
workers/providers/workspace.ts — pollWorkspaceMailbox is the reuse seam: a push notification should just trigger the same poll step for that mailbox (the historyId cursor already dedupes/backfills)
workers/providers/gmail-client.ts — add watch()/stop() wrappers
workers/app.ts scheduled() — watch renewal job (7-day max expiry) joins the hourly branch
workers/routes/sidecar.ts — pattern for mounting; the push endpoint must validate Google's OIDC token on the Pub/Sub push (aud + issuer + email claim), NOT trust the payload
workers/durableObject/sidecar-state.ts — watch_expiration column (new migration)
docs/sidecar-credentials.md — Pub/Sub topic + gmail-api-push@system.gserviceaccount.com publish-grant runbook
Constraints
- Push is opt-in per mailbox (
sidecar.transport: "poll" | "push", default poll); poll cadence relaxes but never disappears for push mailboxes (gap insurance)
- The push endpoint is unauthenticated-by-CF-Access by necessity — it must verify the Pub/Sub OIDC JWT with a vetted library/
crypto.subtle, never hand-rolled parsing, and must not process message content from the notification body (fetch via API using the cursor, as today)
- Renewal failure surfaces in
sidecar_health within one cycle
Acceptance criteria
Out of scope
M365/Graph subscriptions (file under the M365 provider follow-up); onboarding wizard changes beyond documenting the GCP setup.
Cross-references
#31 (parent), PR #587 (poll-first transport decision)
Task
Add Gmail push notifications as a lower-latency transport for the Workspace sidecar:
users.watch()+ a Pub/Sub push subscription pointing at a new authenticated Worker route, layered ON TOP of the existing poll/backfill path (which stays as the gap-filler and becomes the fallback). Verdict latency drops from ≤1 min to seconds for tenants that opt in.Motivation
As an operator with latency-sensitive mail flows, I want near-real-time verdicts; the issue #31 design named watch+Pub/Sub as the eventual transport and PR #587 deliberately shipped poll-first.
Pointers
workers/providers/workspace.ts—pollWorkspaceMailboxis the reuse seam: a push notification should just trigger the same poll step for that mailbox (the historyId cursor already dedupes/backfills)workers/providers/gmail-client.ts— addwatch()/stop()wrappersworkers/app.tsscheduled()— watch renewal job (7-day max expiry) joins the hourly branchworkers/routes/sidecar.ts— pattern for mounting; the push endpoint must validate Google's OIDC token on the Pub/Sub push (aud + issuer + email claim), NOT trust the payloadworkers/durableObject/sidecar-state.ts—watch_expirationcolumn (new migration)docs/sidecar-credentials.md— Pub/Sub topic +gmail-api-push@system.gserviceaccount.compublish-grant runbookConstraints
sidecar.transport: "poll" | "push", default poll); poll cadence relaxes but never disappears for push mailboxes (gap insurance)crypto.subtle, never hand-rolled parsing, and must not process message content from the notification body (fetch via API using the cursor, as today)sidecar_healthwithin one cycleAcceptance criteria
Out of scope
M365/Graph subscriptions (file under the M365 provider follow-up); onboarding wizard changes beyond documenting the GCP setup.
Cross-references
#31 (parent), PR #587 (poll-first transport decision)