# ADR-0008 — Calendar & Outlook .pst integration - **Status:** Accepted - **Date:** 2026-06-30 - **Context source:** Design doc §"Calendar Integration", §"Outlook .pst Integration" ## Context WA enriches meetings with calendar context (title, organizer, attendees) and reads local Outlook `.pst` backups to build meeting history and suggest speaker names — all local, no cloud sync required. ## Decision - **`.pst` reading:** use the Rust **`outlook-pst`** crate (read-only, MS-PST-based) to extract calendar appointments and attendees. Fall back to **libpff** (`pffexport`/FFI or a small sidecar) for password-protected or edge-case files. PST is **read-only input**; WA never writes to it. - **Live calendar:** abstract behind a `calendar::CalendarSource` trait. Phase 6 ships the PST source. Microsoft Graph (with explicit user consent) and local ICS files are future sources behind the same trait; Graph is the _one_ allowed remote call and only for calendar metadata, never meeting content, and only with consent. - **Reminders:** action items become **local OS notifications** (Windows toast); optional calendar entry creation is deferred and, where added, stays local/consented. - **Participant-aware naming:** attendee names from the selected event populate the speaker-naming dropdown (FR-SPK-2), with an "add new name" escape hatch; mappings persist per meeting. ## Consequences - **Positive:** delivers Granola-style context fully offline from data the user already has; trait keeps Graph/ICS additions non-invasive; read-only PST avoids corrupting user mail stores. - **Negative:** PST parsing is fiddly (encryption, large files, format variance) → keep libpff as a fallback and treat parse failures as non-fatal; Graph path (future) introduces OAuth + the product's only remote dependency, so it must be strictly opt-in and clearly labeled. ## Update (2026-07-01) — `readpst` (libpst) instead of the `outlook-pst` crate T6.1 implementation revealed `outlook-pst` (the Rust crate this ADR originally named) is a byte-level, "clean room" implementation of the raw MS-PST binary format — B-tree pages, allocation maps, block trailers — with **no MAPI object-model layer**: no `folder.name()` or `message.subject()`, just numeric property IDs and hand-decoded `PropertyValue`s. Reaching "appointment with attendees" would mean hand-implementing named-property resolution (`PidLidAppointmentStartWhole` etc. aren't fixed low prop IDs) and per-message recipient-table parsing against the MS-PST/MS-OXPROPS spec directly, with no test `.pst` fixture available to verify correctness against. Decision: shell out to **`readpst`** (the **libpst** project, not libpff — the two are separate, similarly-purposed C libraries) instead. `readpst -S -e -t a -o ` dumps every appointment as its own `.ics` (iCalendar) file; WA parses those with a small hand-rolled RFC 5545 VEVENT reader (`calendar::parse_vevents` — SUMMARY/ORGANIZER/DTSTART/DTEND/ATTENDEE/UID only, no timezone database, floating and `Z` times both treated as UTC). This trades a pure-Rust dependency for an external-binary one (`readpst` must be on `PATH` — obtainable via MSYS2, Cygwin, or prebuilt Windows binaries; WA surfaces a clear `CalError::ToolMissing` if it isn't found, no different in kind from Ollama's guided-install treatment, T5.7) in exchange for a **far** smaller, better-tested WA-side surface. Most PST "password protection" is a UI-level gate over a fixed, keyless obfuscation that `readpst` already reverses transparently, not real encryption — the `password` field is accepted for forward-compat but currently unused; genuinely IRM-protected files surface as a non-fatal parse error rather than being supported. `CalendarSource::attendees(event_id)` (a second, separate trait method in the original design) was dropped: PST/ICS list attendees inline per-appointment, so there's no live "fetch attendees for event X" round trip against the source to make — `import()` now returns `Vec` (event + its attendees together), and storage persists both. ## Update (2026-07-01) — validated against a real 7.2GB mailbox Ran `readpst` (libpst v0.6.63, a ~2014 Windows build — an older release than current libpst master) against a real, in-use corporate `.pst` and fed every resulting `.ics` file through the real `parse_vevents`. Results: **1738/1738 files produced exactly one valid event each** — subjects, `DTSTART`/`DTEND` (including the `VALUE=DATE-TIME` parameter form, correctly ignored by the parser) all came through cleanly, zero parse failures. One real, confirmed gap: **this libpst v0.6.63 build never emits `ORGANIZER` or `ATTENDEE` lines at all** — 0/1738 events had either, including confirmed real meetings ("Denver", location "Microsoft Teams Meeting", `STATUS:TENTATIVE`). `write_schedule_part_data()`'s attendee-writing logic either doesn't exist in this old release or isn't reached; not something WA's parser can compensate for — the data simply never reaches WA. Practical effect: **T6.1/T6.2 deliver real event import (subject/organizer-as-None/start/end/location) today; T6.5's attendee-populated naming dropdown has nothing to populate from PST until this is resolved.** Also found and fixed: this readpst build doesn't support `-8` (force UTF-8 output) at all ("invalid option -- 8") — removed from the invocation; a non-UTF-8-encoded item's `.ics` file is skipped rather than misread (`collect_ics_files` already treats a non-UTF-8-readable file as skippable). Also confirmed empirically: `-t a` does mean **appointments**, not attachments, despite this build's own `-h` output literally printing `-t[eajc] ... a = attachment` — the printed usage string is wrong/stale in this build; the real behavior (verified by watching it skip-count non-appointment items per folder, then produce exactly the Calendar folder's real appointments) matches current libpst master's `OTMODE_APPOINTMENT` mapping for `'a'`. ## Revisit if A newer libpst build (or a different install path) turns out to emit `ORGANIZER`/`ATTENDEE` correctly — re-test T6.5 once one is available. Otherwise: `readpst` proves insufficient in practice in some other way, or users need live Exchange more than PST (prioritize the Graph source).