Skip to content

Reconciliation and cursor recovery

Duckbill™ treats reconciliation as a required correctness mechanism. Incremental APIs and notifications are hints. Canonical MailMessage / MessageInstance rows are the source of truth after repair.

See ADR 0017 and the governing synchronization specification §§13–14 and §17. Incremental cursors use scope incremental-delta (ADR 0016).

Levels

LevelWhat it comparesTypical use
LightweightRecently changed instances plus incremental cursor healthContinuous catch-up
PeriodicProvider vs local identity and state over a bounded received windowScheduled mailbox check
DeepFull provider listing vs local instancesDrift, manual request, post-recovery

Each run inserts and completes a reconciliation_runs row. Diagnostics include finding kinds. Initial sync stays awaiting_reconciliation until a successful run marks the account healthy.

Drift and repairs

Repairs emit audit_events (reconciliation.repair.*, reconciliation.flag.duplicate_mapping). A second run against the same mailbox is a no-op for already-repaired drift.

Sent mail and read-state

READ ≠ COMPLETE. Provider mark-read updates MessageInstance.is_read only.

An external Outlook (or other client) sent reply can be associated to the existing thread via providerThreadId. If conversation attention is already RESPOND, reconciliation moves it to WAITING. If attention does not exist yet, sent-mail association does not invent RESPOND.

Cursor recovery

If the incremental checkpoint is expired or invalid:

  1. Mark the checkpoint degraded and account sync error, then syncing.
  2. Preserve existing canonical rows.
  3. Bounded list/upsert from the provider.
  4. Run reconciliation.
  5. Establish a new incremental cursor and return the account to idle.

Cursor loss is degraded sync, not a database reset.