ADR 0025: Safe provider write-back through MailCommand / ProviderMutation
Status
Accepted
Context
Rules and UI must not call mail provider adapters. Sprint 06 Stream B (MAIL-44 / MAIL-45 / MAIL-43) requires user-requested read, flag, and archive write-back, but only after a durable command exists. Provider HTTP success is not canonical state. Send and delete stay off. AI cannot authorize side effects. Graph write methods already exist; the missing piece was the command outbox and sync convergence.
Decision
- Every write creates a
MailCommandandProviderMutationfirst (0017_provider_mutations.sql). The row has an idempotency key, actor, audit event, pre-execution snapshot, and statusPENDING. evaluatePolicy(ADR 0019) runs with actorUSERbefore any adapter call. AI actors are denied. Send and delete persist asFAILED/POLICY_DENIEDand never call an adapter.ENABLE_SENDis not authorization.- Graph-capable safe writes (
MarkRead,MarkUnread,SetFlag,Archive,Move) run only whenENABLE_MICROSOFT_WRITES=trueand the adapter advertises the capability. Unsupported work returnsCAPABILITY_NOT_SUPPORTED. Partial bulk reports explicit counts. - Adapter success moves the mutation to
ACCEPTED, notSUCCEEDED. Optimistic API responses may returncommand_id+pending. Healthy status requires normal sync to observe the desired state (MAIL-65). Echoes update canonical instance state and do not enqueue duplicate user workflows. Mismatch becomesRECONCILE_NEEDEDand is visible in health.
Alternatives
- Call Graph from the UI or a Next.js route — rejected; package boundaries and MAIL-66 forbid hidden write paths.
- Treat Graph
204as done — rejected; 02 Sync §11 and kickoff write-back guardrails require observation. - Honor
ENABLE_SENDas send authorization — rejected; MAIL-170 / this stream keep send flag-off until a future ticket.
Consequences
Application owns submitCommand and convergeWriteBacks. providers/graph owns capability mapping and Graph PATCH/move. Incremental sync remains the observation path. Gmail and iCloud stay out of scope.
