sync: capture Message-ID and a content hash at ingest #386

Open
opened 2026-08-10 19:27:47 +00:00 by KofTwentyTwo · 1 comment
KofTwentyTwo commented 2026-08-10 19:27:47 +00:00 (Migrated from github.com)

Prerequisite for the identity split. Ships standalone with zero semantic change.

MimeParserAdapter::parse_mime never calls message_id(), Email has no such field, the messages table has no column for it, and build_email_from_fetch discards the BODY[] bytes after parsing. Identity therefore cannot be derived for existing rows, which is why this must ship early and accumulate.

Scope

  • Parse and persist a normalized Message-ID and a content hash of the full RFC822 octets, as nullable columns.
  • Four ingest points: parser.rs, imap.rs full-body, imap.rs headers-only, jmap.rs.
  • Eliminate the envelope-only fetch path (always request BODY.PEEK[HEADER.FIELDS (...)]) so identity is always computable.
  • Specify normalization exactly — angle brackets, folding, case, non-ASCII. Every engine must normalize byte-identically or convergence goes flaky for reasons nobody will find.

Not in scope

Placements, dedup, any change to how ids are used. Columns are written and not yet read.

Design: ADR 0002 · review evidence (PR #385)

**Prerequisite for the identity split. Ships standalone with zero semantic change.** `MimeParserAdapter::parse_mime` never calls `message_id()`, `Email` has no such field, the `messages` table has no column for it, and `build_email_from_fetch` discards the `BODY[]` bytes after parsing. Identity therefore **cannot be derived for existing rows**, which is why this must ship early and accumulate. ### Scope - Parse and persist a normalized `Message-ID` and a content hash of the full RFC822 octets, as **nullable** columns. - Four ingest points: `parser.rs`, `imap.rs` full-body, `imap.rs` headers-only, `jmap.rs`. - Eliminate the envelope-only fetch path (always request `BODY.PEEK[HEADER.FIELDS (...)]`) so identity is always computable. - Specify normalization exactly — angle brackets, folding, case, non-ASCII. Every engine must normalize byte-identically or convergence goes flaky for reasons nobody will find. ### Not in scope Placements, dedup, any change to how ids are used. Columns are written and not yet read. Design: [ADR 0002](https://github.com/KofTwentyTwo/nuncio/blob/dev/docs/adr/0002-multi-engine-sync-model.md) · [review evidence](https://github.com/KofTwentyTwo/nuncio/blob/dev/docs/reviews/2026-08-10-multi-engine-design-review.md) (PR #385)
KofTwentyTwo commented 2026-08-10 20:30:09 +00:00 (Migrated from github.com)

Note on framing: the "must accumulate before #394 is possible" rationale is superseded by ADR 0002's pre-release posture (PR #385). With a resettable store, capture only needs to land one merge ahead of the placements work rather than soak.

Implemented in PR #398. One deviation from this issue's scope, called out there: the envelope-only fetch path was not eliminated. ENVELOPE carries message-id (RFC 3501 §7.4.2), so identity is computable on that path after all — capture was added instead of reshaping the FETCH, which keeps the change off the batching logic.

Note on framing: the "must accumulate before #394 is possible" rationale is superseded by ADR 0002's pre-release posture (PR #385). With a resettable store, capture only needs to land one merge ahead of the placements work rather than soak. Implemented in PR #398. One deviation from this issue's scope, called out there: the envelope-only fetch path was **not** eliminated. `ENVELOPE` carries `message-id` (RFC 3501 §7.4.2), so identity is computable on that path after all — capture was added instead of reshaping the FETCH, which keeps the change off the batching logic.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: KofTwentyTwo/nuncio#386