The EmailPlatform SPI had no search method. Every other platform that needed search — DocumentPlatform, ContactsPlatform, ProjectPlatform — already had one. Email was the holdout, identified during the simulation integration audit back in #139.
What made this interesting wasn’t the implementation. It was how little
design work remained by the time we got here. The #138 design spec had
already classified email search as “interpretive” and a search-corpus.yaml
had shipped with #143’s seed data work — sitting in email-spi/src/main/resources/simulation/email/,
wired to nothing. The interface gap was the only thing missing.
The method signature follows the cross-SPI pattern: Page<EmailSummary> search(String query, PageRequest pagination).
No userId parameter — EmailPlatform is flat, unlike the capability-sub-interface
SPIs that scope operations per user. The ref implementation does naive
case-insensitive substring matching across subject, from, and body text.
Nothing clever, but consistent with how every other ref does search.
The Gmail implementation was the satisfying part. Gmail’s messages.list
accepts a q parameter that supports the full Gmail search syntax natively —
from:alice, has:attachment, after:2026/01/01. We pass the query straight
through. The only wrinkle: search results span all mailboxes, so the
mailboxId on each result needs deriving from the message’s labels rather than
being passed in as a parameter. A primaryLabel() helper on GmailMessageMapper
handles that — filtering out metadata labels (UNREAD, STARRED, IMPORTANT,
category labels) and taking the first remaining label, defaulting to INBOX.
Wiring the corpus into simulation.yaml was a single YAML block — seq strategy
with WRAP exhaustion, matching the document-spi pattern exactly.
The session that planted the corpus file and classified the search method did the harder work. This session just filled the slot.