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.
**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)
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.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Prerequisite for the identity split. Ships standalone with zero semantic change.
MimeParserAdapter::parse_mimenever callsmessage_id(),Emailhas no such field, themessagestable has no column for it, andbuild_email_from_fetchdiscards theBODY[]bytes after parsing. Identity therefore cannot be derived for existing rows, which is why this must ship early and accumulate.Scope
Message-IDand a content hash of the full RFC822 octets, as nullable columns.parser.rs,imap.rsfull-body,imap.rsheaders-only,jmap.rs.BODY.PEEK[HEADER.FIELDS (...)]) so identity is always computable.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)
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.
ENVELOPEcarriesmessage-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.