Sprint 03 runtime: Graph connect, workers, initial sync
Duckbill can authorize a Microsoft mailbox, run restartable initial sync against the provider port (mock or Graph), and process background work with leases, retry, DLQ, and pause switches.
Graph connect
/connectis the product control. It issues the single-user owner session, then starts OAuth.GET /api/providers/microsoft/startissues a PKCE authorize URL. The JSON body contains onlyauthorizationUrl.GET /api/providers/microsoft/callbackexchanges the code, upsertsConnectedAccount, and stores encrypted tokens. The response is account health, never tokens.POST /api/providers/microsoft/revokedeletes ciphertext and setsauthorization_status = AUTH_REQUIRED.- Refresh is single-flight per account.
AUTHORIZATIONerrors stop retry and markAUTH_REQUIRED.
Initial sync
runInitialSyncPage enumerates folders, pages messages, upserts MailMessage / MessageInstance (unique on account + provider message id), writes an outbox event, and enqueues independent FETCH_BODY and CLASSIFY work items. AI enqueue failure cannot prevent the upsert. Classification UNPROCESSED is valid. initial_sync_status stays awaiting_reconciliation until runReconciliation (MAIL-28) marks healthy. See reconciliation.
MAIL-103 exposes a session-gated bounded page at POST /api/sync/initial (default account jason@jasonkoch.io) and count-only GET /api/sync/cardinality. Responses never include mail bodies. See MVP E2E evidence.
Ongoing mailbox changes use incremental sync (MAIL-24): webhook hints, getChanges delta, cursor advance after durable process, and polling fallback. See Incremental sync.
Workers
Leased sync_work_items plus transactional outbox (ADR 0010). Transient errors back off. Authorization and exhausted attempts go to dead_letter_items. DLQ replay is explicit and audited. Poison isolation is per message id so siblings continue.
Pause and circuit breaker
automation_pauses scopes: GLOBAL, MAILBOX, ACTION_TYPE, AI. Global and mailbox pauses block COMMAND work (provider mutations). Sync pages still run. The ai circuit breaker opens on an error budget and skips only CLASSIFY. Recovery mode (MAIL-56 / ADR 0026) inserts a global pause and refuses new mutations until operators clear it. See operational health and backup and restore.
CLASSIFY work is recommendation-only (MAIL-37 / ADR 0022). The worker must not call mail provider adapters. Inference timeouts, invalid JSON, and prompt injection leave the instance synced. See AI intelligence.
