Skip to content

ADR 0018: Conservative canonicalization for Duckbill™

Status

Accepted

Context

Duckbill™ must treat MailMessage as logical RFC content and MessageInstance as a provider/account occurrence. Sync upserts previously merged on Internet Message-ID alone. That is unsafe: Message-ID can be missing, malformed, or colliding. Provider conversation IDs also leak across accounts and must not become canonical thread keys. Attachments need metadata during sync, but bytes and scanner results must not fail open.

Source: 05 CDM §5–§6, Pipeline §27–30 / §114–115 / §149, MAIL-32 / MAIL-30 / MAIL-31.

Decision

  1. Resolve MailMessage identity with a ranked, merge-conservative matcher:
    • existing (account, provider_message_id) mapping;
    • trustworthy normalized Message-ID plus corroboration;
    • unique strict content/header fingerprint;
    • otherwise create a new MailMessage. Subject-only and provider-thread-only merges are forbidden. Prefer duplicate canonical messages over a false merge. Missing or malformed Message-ID becomes null and does not crash sync. Record identityMatchMethod on the instance for diagnostics.
  2. Canonicalize threads from In-Reply-To, References, and normalized subject as supporting evidence. Provider thread IDs stay on MessageInstance as evidence only. They are never the MailThread primary key and never equate conversations across accounts by themselves. Thread repair does not rewrite mail_message_id. A new message on a WAITING conversation re-evaluates attention to RESPOND.
  3. Persist attachment metadata without treating files as always-local blobs. Fetch bytes on demand. Scanner failure or parser poison holds the attachment (held / unknown) and never marks clean. One bad attachment does not block the mailbox. Broad document-management features stay out of scope.

Alternatives

Consequences

Initial, incremental, and reconciliation upserts go through resolveMailMessageIdentity. Thread and attachment services live in @inbox-agent/application (message-identity.ts, thread-canonicalization.ts, attachments.ts). Scan-state check constraint includes unknown and held (migration 0014_attachment_hold_states.sql). ADR 0016 remains Stream A incremental sync. ADR 0017 remains Stream B reconciliation.