Skip to content

Phase 3 C6 / #200: Email connector — iMIP calendar invite extraction - #253

Merged
BhavsarDevansh merged 3 commits into
mainfrom
feat/email-structured-extraction
Aug 4, 2026
Merged

BhavsarDevansh merged 3 commits into
mainfrom
feat/email-structured-extraction

Conversation

@BhavsarDevansh

@BhavsarDevansh BhavsarDevansh commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Closes #200.

Summary

Implements the Email connector's structured fact extraction (Phase 3 C6 / #200) with a deliberately narrower scope than the original issue wording — see the implementation note on #200 and the discussion below.

Email is provenance, not the fact. EmailConnector::extract() now runs a deterministic extraction cascade over each staged RFC 822 message. Today the cascade has one layer — iMIP calendar invites: a MIME attachment with Content-Type: text/calendar; method=REQUEST|REPLY is parsed with mail-parser and the embedded VEVENT is turned into the same appointment fact cluster the Calendar connector emits — user has_event <event> (typed EventType::Appointment, recurrence from RRULE FREQ, temporal bounds from DTSTART/DTEND), plus <event> located_in <place> and <attendee> attending <event> — resolved via the full F5 entity chain in normalize_and_insert. PUBLISH/CANCEL are skipped (CANCEL lifecycle → #247).

  • No per-email communication facts (received_email_from / sent_email_to) and no Person entities auto-created from From/To headers, so marketing/spam produces no junk facts. A plain prose email with no text/calendar part produces nothing in C6.
  • DRY: the VEVENT parsing + fact cluster is extracted into a new shared mimir-connectors/src/ical.rs (parse_ical_to_vevents, vevent_to_facts, RawVEvent), gated any(feature = "calendar", feature = "gmail"). The Calendar connector (RawCalDavEvent now wraps RawVEvent; event_to_facts delegates) reuses it, eliminating the duplicated parsing + rrule_to_recurrence + calendar_fact helpers.
  • User identity: the Email connector now authors user-scoped facts against the injected ConnectorContext::user_identity (the config.toml [identity] name), matching the Calendar connector; from_config_with_http gains a user_identity parameter and the factory passes ctx.user_identity. Without an identity the primary has_event fact is skipped; location/attendee facts still emit.

Why the narrow scope (vs. the issue's "headers/dates/contacts")

A dentist's free-text "see you Tuesday 3pm" with no .ics, a flight boarding pass in prose, a bank statement, a job offer — none carry structured text/calendar, so C6 emits nothing for them. Those are the LLM layer (C7 / #201). Transactional emails embedding machine-readable schema.org JSON-LD (Order, FlightReservation, Ticket, …) are a deterministic layer to be added in #249, between invites and the LLM. Auto-creating a contact from every sender would fill the KB with marketing@… entities — exactly the "false facts" #200's discussion flagged — so contacts emerge only from invite attendees (structured) and C7's LLM extraction. The cascade is built so #249 and C7 slot in without restructuring extract().

Key changes

  • mimir-connectors/src/email/mod.rsextract() cascade + extract_invites(); user_identity wiring.
  • mimir-connectors/src/ical.rs (new) — shared RawVEvent, parse_ical_to_vevents, vevent_to_facts (+ moved parse_ical_datetime, participant_display, rrule_to_recurrence).
  • mimir-connectors/src/calendar/{caldav,mod}.rs — refactored to reuse ical (DRY).
  • mimir-connectors/Cargo.tomlmail-parser 0.11.5 under gmail; icalendar/chrono-tz shared with calendar.
  • Docs: docs/email-connector.md, docs/wiki/email-connector.md, README.md, docs/wiki/what-works-now.md, Mimir-Implementation-Context.md, CHANGELOG.md.
  • Semver: 0.84.20.85.0 (MINOR, new feature).

Tests

  • Unit: iMIP REQUEST/REPLY/PUBLISH/CANCEL gating, no-identity path, plain/marketing email → no facts.
  • KB integration: an invite staged through extract()normalize_and_insert asserts F5 entity resolution (user / event / place / attendees), the Appointment events-subsystem overlay, secondary facts carrying no overlay, and connector provenance (Connector / Gmail / StructuredParse / raw_reference = IMAP UID).
  • Fake-IMAP → extract() round-trip proving the transport and extraction compose end-to-end.
  • Calendar lib + integration tests updated for the shared RawVEvent and remain green (22 + 140 lib).

cargo test -p mimir-connectors, cargo clippy -p mimir-connectors --all-targets, and cargo fmt are clean.

Pre-existing failures (not introduced here; filed separately)

Related issues updated for context

#201 (C7 boundary sharpened), #234 (ExtractionMethod relevance), #249 (JSON-LD follow-up), #250 (Photos fallback).

Summary by CodeRabbit

  • New Features

    • Added structured extraction of iMIP REQUEST and REPLY calendar invites from email, including appointments, locations and attendees.
    • Added support for time zones, recurrence rules, organisers and attendee details.
    • Preserved event provenance with mailbox identity tracking and user-scoped authorship.
  • Bug Fixes

    • Improved MIME traversal, method fallback handling and case-insensitive calendar matching.
    • Unsupported or malformed messages now produce no facts.
  • Documentation

    • Updated connector documentation and release notes for version 0.85.1.

Add deterministic structured extraction to the IMAP Email connector:
`extract()` runs an extraction cascade over staged RFC 822 messages and,
today, turns iMIP calendar invites (text/calendar; method=REQUEST|REPLY)
into the same appointment fact cluster the Calendar connector emits
(user has_event <event> typed Appointment, plus located_in and attending),
reusing a new shared mimir-connectors::ical module (DRY). The email is
treated as provenance (IMAP UID is each fact's raw_reference), not the
fact: no per-email communication facts and no Person entities auto-created
from From/To headers, so marketing/spam produces no junk. The connector
now authors user-scoped facts against the injected ConnectorContext
user_identity, matching the Calendar connector. mail-parser 0.11.5 added
under the gmail feature; icalendar/chrono-tz now shared with calendar.

Calendar connector refactored to reuse the shared ical module (RawCalDavEvent
wraps RawVEvent; event_to_facts delegates); its tests updated and remain
green. Email gains unit tests (REQUEST/REPLY/PUBLISH/CANCEL gating,
no-identity, plain email), a KB integration test (F5 resolution,
Appointment overlay, connector provenance), and a fake-IMAP → extract
round-trip.

Follow-ups: schema.org JSON-LD extraction (#249), LLM prose extraction
(C7 / #201), Photos took_photo fallback (#250). Semver bumped to 0.85.0
(MINOR). Closes #200.
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@BhavsarDevansh, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 22 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: feb2e7aa-e1d8-432f-8e72-5072b212d755

📥 Commits

Reviewing files that changed from the base of the PR and between 18e4307 and c3f56e4.

📒 Files selected for processing (8)
  • CHANGELOG.md
  • Cargo.toml
  • README.md
  • docs/email-connector.md
  • docs/wiki/email-connector.md
  • mimir-connectors/src/email/imap.rs
  • mimir-connectors/src/email/mod.rs
  • mimir-connectors/src/ical.rs
📝 Walkthrough

Walkthrough

The release adds shared iCalendar VEVENT parsing and fact generation. CalDAV now uses the shared representation. The Email connector parses staged RFC 822 messages and extracts facts from iMIP REQUEST and REPLY invites. It preserves UIDVALIDITY-qualified IMAP provenance and applies optional user identity. Tests cover parsing, extraction, integration, identity, recurrence, time zones, and knowledge-graph resolution. Version metadata and connector documentation now describe the completed extraction scope.

Sequence Diagram(s)

sequenceDiagram
  participant IMAP
  participant EmailConnector
  participant mail_parser
  participant ical
  participant KnowledgeGraph
  IMAP->>EmailConnector: fetch and stage RFC 822 message
  EmailConnector->>mail_parser: parse MIME content
  mail_parser-->>EmailConnector: text/calendar part
  EmailConnector->>ical: parse REQUEST or REPLY VEVENT
  ical-->>EmailConnector: appointment, location, and attendee facts
  EmailConnector->>KnowledgeGraph: provide facts with UIDVALIDITY-qualified provenance
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: iMIP calendar invite extraction in the Email connector.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/email-structured-extraction
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feat/email-structured-extraction

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@BhavsarDevansh
BhavsarDevansh marked this pull request as ready for review July 31, 2026 16:31

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/email-connector.md`:
- Around line 86-90: Update the Dependencies paragraph to distinguish Gmail-only
extraction dependencies from shared calendar dependencies: identify mail-parser
as gated by the gmail feature, and state that icalendar, chrono-tz, and the
shared ical module are available under any(feature = "calendar", feature =
"gmail").

In `@docs/wiki/email-connector.md`:
- Around line 22-26: Update the “The email is the evidence, not the fact”
section to state that primary has_event extraction depends on
ConnectorContext::user_identity, that supported iMIP REQUEST and REPLY parts may
produce facts regardless of sender, and that provenance uses the IMAP UID rather
than the RFC Message-ID header.

In `@mimir-connectors/src/email/mod.rs`:
- Around line 993-1029: Update the invite_email fixture to construct the raw
message with CRLF separators throughout, matching the plain_email fixture and
real IMAP BODY.PEEK[] wire format; preserve the existing headers, MIME
boundaries, calendar content, and method interpolation.
- Around line 751-753: In the staging flow around the buffer lock and staged
messages, release the mutex guard immediately after draining the buffer, before
any MIME parsing or fact-building loop begins. Explicitly drop the guard after
std::mem::take, then continue parsing the owned staged messages without holding
the lock.
- Around line 754-761: Thread the mailbox UIDVALIDITY from run_sync through the
staging flow into each RawEmail, then update the raw_ref construction in the
invite-extraction loop to use the cursor-style "{uid_validity}:{uid}" format.
Preserve the existing parsing and fact extraction behavior while ensuring
references differ across UIDVALIDITY epochs.
- Around line 1338-1340: Replace the tautological dr_smith > 0 assertion in the
attendee-facts test with a check over the inserted attending facts, asserting
that at least one has subject_id equal to dr_smith. Preserve the existing
entity-resolution setup and verify the attending fact specifically represents Dr
Smith as a Person.
- Around line 606-615: Update the text/calendar handling around the method match
to inspect the iCalendar body’s METHOD property when the MIME method parameter
is absent. Prefer the normalized MIME parameter when present, otherwise parse
and use the body METHOD line, while continuing to accept only REQUEST or REPLY
and skip unsupported or missing methods.
- Around line 599-605: The attachment-only iteration in the iMIP calendar
extraction flow misses inline text/calendar parts within multipart/alternative
messages. Replace the attachments() traversal around the message attachment
handling with a full MIME or body/non-attachment calendar-part traversal, while
retaining multipart/mixed support, and add coverage for an inline
multipart/alternative calendar without a filename.

In `@mimir-connectors/src/ical.rs`:
- Around line 328-330: Update the attendee name normalization around the
`strip_prefix` call to remove the `mailto:` scheme regardless of letter casing,
while preserving the existing trimming of whitespace and surrounding quotes and
leaving other values unchanged.
- Around line 98-140: Update the VEVENT filter and all property lookup paths in
the calendar conversion to use case-insensitive iCalendar name matching,
including prop_str, tzid_of, DTSTART, DTEND, ORGANIZER, and attendee handling.
Replace case-sensitive find_prop usage or introduce a shared case-insensitive
lookup helper, while preserving the existing RawVEvent field mapping and
datetime parsing behavior.

In `@README.md`:
- Line 35: Update the README description for mimir-connectors so schema.org
JSON-LD extraction references `#249`, while free-text LLM extraction references C7
/ `#201`; keep the surrounding connector and roadmap text unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 559fce22-49db-4603-b560-659518af9366

📥 Commits

Reviewing files that changed from the base of the PR and between 4a20391 and 18e4307.

📒 Files selected for processing (13)
  • CHANGELOG.md
  • Cargo.toml
  • Mimir-Implementation-Context.md
  • README.md
  • docs/email-connector.md
  • docs/wiki/email-connector.md
  • docs/wiki/what-works-now.md
  • mimir-connectors/Cargo.toml
  • mimir-connectors/src/calendar/caldav.rs
  • mimir-connectors/src/calendar/mod.rs
  • mimir-connectors/src/email/mod.rs
  • mimir-connectors/src/ical.rs
  • mimir-connectors/src/lib.rs
📜 Review details
🧰 Additional context used
🪛 LanguageTool
Mimir-Implementation-Context.md

[style] ~146-~146: Would you like to use the Oxford spelling “centralizes”? The spelling ‘centralises’ is also correct.
Context: ... connector whose status is Active and centralises spawn-on-startup, restart with exponent...

(OXFORD_SPELLING_Z_NOT_S)


[uncategorized] ~146-~146: Use a comma before ‘so’ if it connects two independent clauses (unless they are closely connected and short).
Context: ...lize_and_insertin an isolated sub-task so a connector panic is caught viaJoinEr...

(COMMA_COMPOUND_SENTENCE_2)


[uncategorized] ~146-~146: Use a comma before ‘so’ if it connects two independent clauses (unless they are closely connected and short).
Context: ...oto degrades to the C1 coords-only shape so no data is lost. Knowledge-graph growth...

(COMMA_COMPOUND_SENTENCE_2)


[style] ~146-~146: Would you like to use the Oxford spelling “serializes”? The spelling ‘serialises’ is also correct.
Context: ...al pass, since is a relative window), serialises concurrent triggers per connector via a...

(OXFORD_SPELLING_Z_NOT_S)


[locale-violation] ~146-~146: Lower-case ‘Ok’ is American English. For British English use “OK”.
Context: ...d returns the cycle's TriggerOutcome (Ok { fetched, new_cursor }, AuthExpired...

(OK)


[locale-violation] ~146-~146: Lower-case ‘Ok’ is American English. For British English use “OK”.
Context: ...e); run_cycle takes SyncOptions and CycleOutcome::Ok carries the SyncOutcome. - mimir —...

(OK)

README.md

[typographical] ~44-~44: Conjunctions like ‘and’ should not follow semicolons. Consider using a comma, or removing the conjunction.
Context: ...ents surfacing in the “Upcoming” section; and the only connector write-back, act() crea...

(CONJUNCTION_AFTER_SEMICOLON)

docs/email-connector.md

[uncategorized] ~52-~52: Possible missing comma found.
Context: ...s on every fact as the raw_reference. Today the cascade has one layer — **iMIP cale...

(AI_HYDRA_LEO_MISSING_COMMA)


[style] ~58-~58: The noun “invitation” is usually used instead of ‘invite’ in formal writing.
Context: ...config.toml [identity] name), so an invite surfaces in the user's "Upcoming" memor...

(AN_INVITE)


[typographical] ~58-~58: It appears that a comma is missing.
Context: ... treats as user_entity_id. Without an identity the primary fact is skipped; the event ...

(DURING_THAT_TIME_COMMA)


[style] ~86-~86: The noun “invitation” is usually used instead of ‘invite’ in formal writing.
Context: ...wledge-graph integration test stages an invite, runs extract() → `normalize_and_inse...

(AN_INVITE)

🔇 Additional comments (21)
Cargo.toml (1)

10-10: LGTM!

CHANGELOG.md (1)

3-14: LGTM!

Mimir-Implementation-Context.md (1)

4-4: LGTM!

Also applies to: 146-146

README.md (1)

44-44: LGTM!

docs/email-connector.md (1)

5-12: LGTM!

Also applies to: 50-63

docs/wiki/email-connector.md (1)

4-8: LGTM!

docs/wiki/what-works-now.md (1)

3-6: LGTM!

mimir-connectors/src/ical.rs (5)

1-32: LGTM!


168-254: LGTM!


269-310: LGTM!


347-415: LGTM!


417-663: LGTM!

mimir-connectors/src/lib.rs (1)

67-71: LGTM!

mimir-connectors/src/calendar/caldav.rs (2)

468-483: LGTM!

Also applies to: 742-763, 791-801


449-466: 📐 Maintainability & Code Quality

No change needed.

RawVEvent is re-exported from mimir-connectors/src/ical.rs, so the bare intra-doc link is valid unless rustdoc broken intra-doc links are denied.

mimir-connectors/src/calendar/mod.rs (1)

43-43: LGTM!

Also applies to: 56-56, 414-426

mimir-connectors/Cargo.toml (2)

155-155: LGTM!


135-141: 📐 Maintainability & Code Quality

No action needed. The mail-parser dependency is on the latest stable release, the exact version keeps the MIT/Apache-2.0 licence, and its default feature set includes the decode support shown in the crate documentation.

mimir-connectors/src/email/mod.rs (3)

48-48: LGTM!

Also applies to: 235-242, 253-253, 262-262, 295-297, 807-813


1031-1041: LGTM!

Also applies to: 1043-1047, 1049-1184, 1186-1337, 1341-1360


1846-1898: LGTM!

Comment thread docs/email-connector.md Outdated
Comment thread docs/wiki/email-connector.md Outdated
Comment thread mimir-connectors/src/email/mod.rs Outdated
Comment thread mimir-connectors/src/email/mod.rs Outdated
Comment thread mimir-connectors/src/email/mod.rs Outdated
Comment thread mimir-connectors/src/email/mod.rs
Comment thread mimir-connectors/src/email/mod.rs Outdated
Comment thread mimir-connectors/src/ical.rs
Comment thread mimir-connectors/src/ical.rs
Comment thread README.md Outdated
Closes the 11 actionable CodeRabbit findings on the Email iMIP invite
extraction PR (#200):

- iMIP MIME walk: iterate message.parts (not attachments()) so a
  text/calendar part inside multipart/alternative with no
  Content-Disposition: attachment is no longer missed.
- METHOD fallback: resolve METHOD from the MIME Content-Type method
  parameter, falling back to the iCalendar body METHOD property (RFC
  6047 §2.4 makes the parameter optional).
- Globally-unique provenance: raw_reference is now {uid_validity}:{uid}
  (matching the persisted cursor format), not a bare IMAP UID unique
  only within one mailbox + UIDVALIDITY epoch. imap::RawEmail gains a
  uid_validity field populated by fetch_since.
- Buffer lock: drain the staged buffer and release the mutex guard
  before the CPU-bound MIME parse loop so a concurrent sync() cycle is
  not blocked from staging new mail.
- iCalendar name matching: parse_ical_to_vevents looks up VEVENT
  properties case-insensitively (icalendar 0.17.x find_prop matches
  case-sensitively, but RFC 5545 names are case-insensitive); the VEVENT
  component match is case-insensitive; participant_display strips the
  mailto: scheme case-insensitively (RFC 3986).
- Tests: invite fixture now uses CRLF (RFC 5322/MIME + IMAP BODY.PEEK[]
  wire format); the tautological assert!(dr_smith > 0) is replaced by a
  real check that the Dr Smith attending fact resolved to the right
  Person entity and points at the appointment Event; fake-IMAP
  provenance assertion updated for the UIDVALIDITY-qualified raw_reference.
- Docs: docs/email-connector.md, docs/wiki/email-connector.md, and
  README.md corrected — mail-parser is Gmail-specific while
  icalendar/chrono-tz are shared with calendar; the wiki states the
  has_event primary fact depends on a configured user_identity, that
  only supported iMIP REQUEST/REPLY parts produce facts, and that
  provenance is the IMAP UID; the README splits future-work references
  (schema.org JSON-LD = #249, LLM free-text extraction = C7 / #201).

Version bumped 0.85.0 -> 0.85.1 (PATCH: backwards-compatible fixes +
doc corrections); CHANGELOG updated.

All connectors tests (140 lib + integration suites), clippy, and fmt
are green; workspace check is clean.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Line 9: Update the provenance wording to use the UIDVALIDITY:UID form instead
of “IMAP UID” in README.md:44 and docs/wiki/email-connector.md:25;
CHANGELOG.md:9 and CHANGELOG.md:13 require no direct changes and provide context
for the qualified format.

In `@docs/email-connector.md`:
- Line 54: Update docs/email-connector.md at line 54 to describe all eligible
text/calendar MIME parts, including METHOD resolved from either the MIME
parameter or calendar body, and remove attachment-only and MIME-parameter-only
wording. Update README.md at line 44 to document the body METHOD fallback and
state that supported REQUEST/REPLY invitations can produce facts regardless of
sender.

In `@mimir-connectors/src/email/mod.rs`:
- Around line 617-633: Update the method extraction logic near the existing MIME
`method` lookup to independently normalize both the MIME parameter and calendar
`METHOD` property, then reject or skip the part whenever both values are present
and differ instead of preferring the MIME value. Preserve the existing handling
for matching values and single-source values, while ensuring `PUBLISH` and
`CANCEL` remain skipped. Add regression tests covering conflicting supported and
unsupported method pairs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 48c57c35-30cc-4e3f-b78b-b942b6cb7c43

📥 Commits

Reviewing files that changed from the base of the PR and between 18e4307 and e6c3432.

📒 Files selected for processing (8)
  • CHANGELOG.md
  • Cargo.toml
  • README.md
  • docs/email-connector.md
  • docs/wiki/email-connector.md
  • mimir-connectors/src/email/imap.rs
  • mimir-connectors/src/email/mod.rs
  • mimir-connectors/src/ical.rs
📜 Review details
🧰 Additional context used
🪛 LanguageTool
docs/email-connector.md

[style] ~86-~86: The noun “invitation” is usually used instead of ‘invite’ in formal writing.
Context: ...wledge-graph integration test stages an invite, runs extract() → `normalize_and_inse...

(AN_INVITE)

🔇 Additional comments (8)
Cargo.toml (1)

10-10: LGTM!

CHANGELOG.md (1)

3-8: LGTM!

Also applies to: 10-12, 14-26

README.md (1)

35-35: LGTM!

docs/email-connector.md (1)

5-12: LGTM!

Also applies to: 86-90

docs/wiki/email-connector.md (1)

4-8: LGTM!

mimir-connectors/src/ical.rs (1)

90-147: LGTM!

Also applies to: 338-343

mimir-connectors/src/email/mod.rs (1)

526-526: LGTM!

Also applies to: 584-616, 769-781, 1054-1058, 1192-1192, 1210-1210, 1266-1266, 1373-1393, 1402-1402, 1937-1944

mimir-connectors/src/email/imap.rs (1)

210-214: LGTM!

Also applies to: 291-291, 323-323

Comment thread CHANGELOG.md

- **Functional correctness (iMIP MIME walk):** `EmailConnector::extract_invites` (`mimir-connectors/src/email/mod.rs`) now walks every MIME part (`message.parts`) instead of `attachments()`, so a `text/calendar` part nested in `multipart/alternative` with no `Content-Disposition: attachment` header (classified as a body part by `mail-parser`) is no longer missed.
- **Functional correctness (METHOD fallback):** the iMIP `METHOD` is now resolved from the MIME `Content-Type` `method` parameter when present, falling back to the iCalendar body `METHOD` property (RFC 6047 §2.4 makes the parameter optional). Only `REQUEST`/`REPLY` are extracted; `PUBLISH`/`CANCEL` are still skipped.
- **Data integrity (globally-unique provenance):** the email provenance `raw_reference` is now `{uid_validity}:{uid}` (matching the persisted cursor format), not a bare IMAP UID that is unique only within one mailbox + `UIDVALIDITY` epoch. `imap::RawEmail` gains a `uid_validity` field populated by `fetch_since`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | rg '(^|/)(CHANGELOG\.md|README\.md|docs/email-connector\.md|docs/wiki/email-connector\.md)$' || true

echo "== CHANGELOG relevant =="
if [ -f CHANGELOG.md ]; then
  nl -ba CHANGELOG.md | sed -n '1,25p'
fi

echo "== README relevant =="
if [ -f README.md ]; then
  nl -ba README.md | sed -n '38,50p'
fi

echo "== docs/wiki/email-connector relevant =="
if [ -f docs/wiki/email-connector.md ]; then
  nl -ba docs/wiki/email-connector.md | sed -n '18,30p'
fi

echo "== docs/email-connector presence and relevant =="
if [ -f docs/email-connector.md ]; then
  nl -ba docs/email-connector.md | sed -n '1,120p'
fi

echo "== search raw_reference / provenance / UIDVALIDITY / raw_reference =="
rg -n "raw_reference|provenance|global|unique|UIDVALIDITY|UID-validity|uid_validity|IMAP UID|IMAP uid|bare IMAP UID" README.md CHANGELOG.md docs docs/wiki || true

Repository: BhavsarDevansh/Mimir

Length of output: 311


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== tool availability =="
command -v sed awk grep find python || true

echo "== top-of-docs excerpts =="
for f in CHANGELOG.md README.md docs/email-connector.md docs/wiki/email-connector.md; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    sed -n '1,60p' "$f"
  fi
done

echo "== relevant text search =="
grep -RinE "raw_reference|provenance|global|unique|UIDVALIDITY|UID-validity|uid_validity|IMAP UID|IMAP uid|bare IMAP UID" -f <(grep -RlnE "raw_reference|provenance|global|unique|UIDVALIDITY|UID-validity|uid_validity|IMAP UID|IMAP uid|bare IMAP UID" README.md CHANGELOG.md docs | sort -u) README.md CHANGELOG.md docs 2>/dev/null || true

Repository: BhavsarDevansh/Mimir

Length of output: 35925


Use UIDVALIDITY-qualified provenance wording in the mail docs.

The implementation uses the UIDVALIDITY:UID form for cursor and raw_reference, so these references should not say the provenance is just the IMAP UID.

  • README.md#L44-L44: use UIDVALIDITY:UID instead of “IMAP UID”.
  • docs/wiki/email-connector.md#L25-L25: use the same qualified provenance wording.
📍 Affects 3 files
  • CHANGELOG.md#L9-L9 (this comment)
  • CHANGELOG.md#L13-L13
  • README.md#L44-L44
  • docs/wiki/email-connector.md#L25-L25
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.md` at line 9, Update the provenance wording to use the
UIDVALIDITY:UID form instead of “IMAP UID” in README.md:44 and
docs/wiki/email-connector.md:25; CHANGELOG.md:9 and CHANGELOG.md:13 require no
direct changes and provide context for the qualified format.

Comment thread docs/email-connector.md

`extract()` drains the staged RFC 822 messages and runs a **deterministic (structured-parse) extraction cascade** over each. The email is treated as **provenance, not the fact**: the fact is about the real-world thing the email conveys (an appointment), and the email's `UIDVALIDITY`-qualified IMAP UID rides on every fact as the `raw_reference`. Today the cascade has one layer — **iMIP calendar invites**:

- A MIME attachment with `Content-Type: text/calendar; method=REQUEST | REPLY` is parsed with `mail-parser`, then the embedded `VEVENT` is parsed with the shared `mimir-connectors::ical` module (the same one the Calendar connector uses — DRY) and turned into the same appointment fact cluster the Calendar connector emits: a primary `user has_event <event>` (typed `EventType::Appointment`, recurrence from `RRULE` `FREQ`, temporal bounds from `DTSTART`/`DTEND`), `<event> located_in <place>`, and `<attendee> attending <event>`. Entities resolve via the full F5 chain in `normalize_and_insert`; facts carry `source_type = Connector`, `connector_type = Gmail`, `extraction_method = StructuredParse`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Keep the documented iMIP eligibility rules consistent.

The implementation accepts every text/calendar MIME part, resolves METHOD from the MIME parameter or body, and extracts only REQUEST/REPLY.

  • docs/email-connector.md#L54-L54: remove the attachment-only and MIME-parameter-only wording.
  • README.md#L44-L44: document body METHOD fallback and state that supported invitations can produce facts regardless of sender.
📍 Affects 2 files
  • docs/email-connector.md#L54-L54 (this comment)
  • README.md#L44-L44
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/email-connector.md` at line 54, Update docs/email-connector.md at line
54 to describe all eligible text/calendar MIME parts, including METHOD resolved
from either the MIME parameter or calendar body, and remove attachment-only and
MIME-parameter-only wording. Update README.md at line 44 to document the body
METHOD fallback and state that supported REQUEST/REPLY invitations can produce
facts regardless of sender.

Comment thread mimir-connectors/src/email/mod.rs Outdated
…THOD values

Reject a text/calendar part when the MIME method parameter and the iCalendar body METHOD property are both present and disagree (RFC 6047 §2.4), instead of silently preferring the MIME value. Add regression tests for conflicting supported/unsupported pairs, the body-only fallback, and the no-source case. Update docs/email-connector.md, docs/wiki/email-connector.md, and README.md to match the extraction contract (any text/calendar part, METHOD from MIME param or body, has_event depends on user_identity, supported REQUEST/REPLY produce facts regardless of sender, UIDVALIDITY-qualified provenance). Bump 0.85.1 → 0.85.2.
@BhavsarDevansh
BhavsarDevansh merged commit 08bf915 into main Aug 4, 2026
@BhavsarDevansh
BhavsarDevansh deleted the feat/email-structured-extraction branch August 4, 2026 08:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Phase 3: Email connector — mail parsing + structured fact extraction

1 participant