Safe provider write-back
Duckbill™ writes mailbox state only after a durable MailCommand / ProviderMutation exists. UI and rules never import @inbox-agent/providers.
Source: MAIL-66, MAIL-95, MAIL-65 and ADR 0025. Migration 0017_provider_mutations.sql.
Lifecycle
submitCommand
→ evaluatePolicy(actor: USER)
→ persist MailCommand + ProviderMutation + audit (PENDING)
→ adapter safe write (Graph markRead / setFlag / archive / move)
→ ACCEPTED (API success, not healthy)
→ incremental sync observes instance
→ SUCCEEDED or RECONCILE_NEEDED
SUCCEEDED is the only healthy status. Optimistic clients may show pending from command_id.
Allowed MVP commands
| Command | Policy action | Capability | Notes |
|---|---|---|---|
| MarkRead / MarkUnread | markRead | markRead | User-requested only |
| SetFlag | setFlag | setFlag | User-requested only |
| Archive | archive | archive | If the adapter can archive |
| Move | move | move | If the adapter can move |
| Send | send | — | Always POLICY_DENIED |
| Delete | delete | — | Always POLICY_DENIED |
ENABLE_MICROSOFT_WRITES must be exactly true for Graph writes. ENABLE_SEND and ENABLE_DELETE stay false. Flipping ENABLE_SEND does not authorize send. Mail.Send is not in Graph scopes.
Errors
POLICY_DENIED— AI actor, send/delete, or gateway deny. Adapter is not called.CAPABILITY_NOT_SUPPORTED— adapter flag off or command type unsupported.FEATURE_FLAG_OFF—ENABLE_MICROSOFT_WRITESis nottrue.- Partial bulk returns
requested,accepted,failed,unsupported, andskippedcounts.
Convergence
Sync applies observed provider state to MessageInstance even when the change is an echo of our write. Echoes do not enqueue classify or fetch-body work. If later observation mismatches the desired state, the mutation stays RECONCILE_NEEDED and an audit/health alert is recorded.
