The GraphQL layer should not hide or drop data JMAP already hands us. Two classes
of gap exist today: fields we deserialize and then discard, and fields we never
request even though adding them to an existing call costs no extra round trip.
Discarding them is worse than it looks for an LLM client — a field absent from the
schema isn't a field the model works around, it's a capability the model cannot
discover exists.
Already in src/models/mod.rs, dropped before reaching the schema
No API cost to expose; the data is already in memory.
EmailBodyValue.isTruncated / isEncodingProblem. Email.textBody and
Email.htmlBody resolve to a bare String, so a truncated body is
indistinguishable from a complete one. This is the one that produces
silently wrong answers rather than merely incomplete ones, and should lead.
EmailBodyPart.cid. The only way to match an inline image to
<img src="cid:…"> in htmlBody. Currently unreachable, so inline images
can't be placed.
EmailBodyPart.charset. Needed to decode text attachments correctly.
EmailBodyPart.partId. Part addressing.
- Body parts beyond the first.
Email::text_content / html_content take
.first() on the part list, so multipart bodies lose everything after part 1
with no indication that anything was omitted.
Never requested, free to add to an existing call
Extending the properties list on a call we already make is the same round trip.
Mailbox.isSubscribed, Mailbox.myRights (RFC 8621 §2) — absent from
list_mailboxes. myRights in particular governs whether a write will be
refused.
Email.sender — distinct from from; the meaningful one for list mail.
Email.headers / bodyStructure. Note the asymmetry introduced by the
filter work: a caller can filter on header: ["List-Id", "rust-lang"] but
cannot read List-Id back off a result.
Not gaps
Verified complete, so no work needed: Identity (all 8 model fields exposed),
MaskedEmail (all 9), Mailbox model → GraphQL (all 9), Thread, and every
field in EMAIL_SUMMARY_PROPERTIES.
Constraint
The lazy boundary must survive this. EMAIL_SUMMARY_PROPERTIES exists so lists
don't pay for bodies, and anything added has to land on the correct side of that
split — a new field that drags the full-record fetch into a cheap listing would
regress the batching this schema is built around. src/mcp/graphql/tests.rs
asserts on the actual HTTP calls made, so a regression there fails a test rather
than going unnoticed.
The GraphQL layer should not hide or drop data JMAP already hands us. Two classes
of gap exist today: fields we deserialize and then discard, and fields we never
request even though adding them to an existing call costs no extra round trip.
Discarding them is worse than it looks for an LLM client — a field absent from the
schema isn't a field the model works around, it's a capability the model cannot
discover exists.
Already in
src/models/mod.rs, dropped before reaching the schemaNo API cost to expose; the data is already in memory.
EmailBodyValue.isTruncated/isEncodingProblem.Email.textBodyandEmail.htmlBodyresolve to a bareString, so a truncated body isindistinguishable from a complete one. This is the one that produces
silently wrong answers rather than merely incomplete ones, and should lead.
EmailBodyPart.cid. The only way to match an inline image to<img src="cid:…">inhtmlBody. Currently unreachable, so inline imagescan't be placed.
EmailBodyPart.charset. Needed to decode text attachments correctly.EmailBodyPart.partId. Part addressing.Email::text_content/html_contenttake.first()on the part list, so multipart bodies lose everything after part 1with no indication that anything was omitted.
Never requested, free to add to an existing call
Extending the
propertieslist on a call we already make is the same round trip.Mailbox.isSubscribed,Mailbox.myRights(RFC 8621 §2) — absent fromlist_mailboxes.myRightsin particular governs whether a write will berefused.
Email.sender— distinct fromfrom; the meaningful one for list mail.Email.headers/bodyStructure. Note the asymmetry introduced by thefilter work: a caller can filter on
header: ["List-Id", "rust-lang"]butcannot read
List-Idback off a result.Not gaps
Verified complete, so no work needed:
Identity(all 8 model fields exposed),MaskedEmail(all 9),Mailboxmodel → GraphQL (all 9),Thread, and everyfield in
EMAIL_SUMMARY_PROPERTIES.Constraint
The lazy boundary must survive this.
EMAIL_SUMMARY_PROPERTIESexists so listsdon't pay for bodies, and anything added has to land on the correct side of that
split — a new field that drags the full-record fetch into a cheap listing would
regress the batching this schema is built around.
src/mcp/graphql/tests.rsasserts on the actual HTTP calls made, so a regression there fails a test rather
than going unnoticed.