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
- Resolve
MailMessageidentity 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. RecordidentityMatchMethodon the instance for diagnostics.
- existing
- Canonicalize threads from
In-Reply-To,References, and normalized subject as supporting evidence. Provider thread IDs stay onMessageInstanceas evidence only. They are never theMailThreadprimary key and never equate conversations across accounts by themselves. Thread repair does not rewritemail_message_id. A new message on aWAITINGconversation re-evaluates attention toRESPOND. - 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 marksclean. One bad attachment does not block the mailbox. Broad document-management features stay out of scope.
Alternatives
- Merge on Message-ID or provider conversation ID alone — rejected; false merges are worse than duplicates.
- Download every attachment during sync — rejected; metadata is enough for triage.
- Fail-open
cleanwhen the scanner is down — rejected; Pipeline §115.
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.
