diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md index 11d950b..48e3ba9 100644 --- a/docs/HANDOFF.md +++ b/docs/HANDOFF.md @@ -23,16 +23,24 @@ machine (or a new LLM) picks up the work cold. it to the definition of done, and open a PR. Do not merge your own work; do not start a second story until yours is merged or handed back. -## Current state of the code (2026-07-28) +## Current state of the code (2026-07-30) - **Phases 0–2 complete.** The daemon boots gRPC-only on loopback `127.0.0.1:9420` behind a keyring bearer token, serving six services (System, Accounts, Mail, Filters, Export, Audit), each behind the auth interceptor. The `IMAP → store → read → SMTP send` spine works end-to-end over gRPC via the `nuncio-cli` reference client, tested offline. `nuncio.v1` is published with a contract-stability golden. -- **Phase 3 in progress:** filter correctness (3.A) and the calendar **engine** - layer (3.B.1: real CalDAV transport, TZID fix, `CalendarBackend`, store CRUD) - are done and on `dev`. Everything else is the M1–M7 backlog. +- **Phase 3 (feature completeness) is now COMPLETE — M1–M4 all merged to `dev`.** + Filter correctness (3.A), Calendar (M1: CalDAV + RRULE recurrence, API vertical), + Contacts (M2: real CardDAV engine + API vertical), JMAP (M3: real client wired + into Mail sync), and Filters made live (M4: engine wired into sync with a + fire-once guard, the `account`-field bug fixed, edit/export/import/logs and + `Filters.Triage` bulk rescan on the gRPC service) are all done. +- **Next milestone: M5 — Mail lifecycle + incremental sync** (account + edit/delete/test-connection + persisted TLS mode, incremental IMAP sync with + progress + per-item FETCH timeout, explicit `SendMessage` account selection, + keyring rollback on failed `AddAccount`). Then M6 (security hardening), then + M7 (API freeze / client-readiness). - Full detail and the path to a client-ready API: `docs/ROADMAP.md`. ## Branches & preserved WIP @@ -86,24 +94,26 @@ GitHub milestones **M1–M7** mirror `docs/ROADMAP.md`. Each milestone holds `story`-labelled issues with a full, self-contained spec. Order: M1 → M7 (M1–M5 independent-ish feature work; M6 security; M7 the client-readiness gate). -**M1 — Finish Calendar (CalDAV)** · milestone #23 -- #185 — Add Calendar API vertical (proto, daemon, CLI, E2E) · **[PROTO]** -- #186 — Wire RRULE recurrence expansion into calendar event queries +**M1–M4 are DELIVERED — all stories merged to `dev`.** M5 is next. -**M2 — Contacts (CardDAV)** · milestone #24 -- #187 — Build a real Contacts (CardDAV) engine, replacing the fabricated client -- #188 — Add Contacts API vertical (proto, daemon, CLI, E2E) · **[PROTO]** +**M1 — Finish Calendar (CalDAV)** · milestone #23 · **DONE** +- #185 — Add Calendar API vertical (proto, daemon, CLI, E2E) · **[PROTO]** · merged +- #186 — Wire RRULE recurrence expansion into calendar event queries · merged -**M3 — JMAP** · milestone #25 -- #189 — Real JMAP client wired into the existing Mail sync path +**M2 — Contacts (CardDAV)** · milestone #24 · **DONE** +- #187 — Build a real Contacts (CardDAV) engine, replacing the fabricated client · merged +- #188 — Add Contacts API vertical (proto, daemon, CLI, E2E) · **[PROTO]** · merged -**M4 — Filters made live** · milestone #26 -- #190 — Wire FilterEngine into live sync so matched rules actually fire *(headline; #193 depends on its helper)* -- #191 — Fix filter header-field and account-field evaluation bugs -- #192 — Move filter edit/export/import/logs onto the Filters gRPC service · **[PROTO]** -- #193 — Re-expose bulk filter triage as a streaming Filters RPC · **[PROTO]** *(depends on #190)* +**M3 — JMAP** · milestone #25 · **DONE** +- #189 — Real JMAP client wired into the existing Mail sync path · merged -**M5 — Mail lifecycle + incremental sync** · milestone #27 +**M4 — Filters made live** · milestone #26 · **DONE** +- #190 — Wire FilterEngine into live sync so matched rules actually fire *(headline; #193 depends on its helper)* · merged +- #191 — Fix filter header-field and account-field evaluation bugs · merged +- #192 — Move filter edit/export/import/logs onto the Filters gRPC service · **[PROTO]** · merged +- #193 — Re-expose bulk filter triage as a streaming Filters RPC · **[PROTO]** *(depends on #190)* · merged + +**M5 — Mail lifecycle + incremental sync** · milestone #27 ← **next** - #194 — Account lifecycle over gRPC (edit/delete/test) + persist TLS mode · **[PROTO]** *(prior art: `wip/unreviewed-account-tls-sync`, reference-not-trust)* - #195 — Incremental IMAP sync with progress reporting and per-item FETCH timeout - #196 — SendMessage selects an explicit account instead of the first configured · **[PROTO]** @@ -135,3 +145,7 @@ independent-ish feature work; M6 security; M7 the client-readiness gate). those two issues if you consider that sensitive. - Decide the eventual fate of `wip/unreviewed-account-tls-sync` (review-and-adopt vs discard) once M5 lands a clean implementation. +- Follow-ups filed during M1–M4 that are open but not blocking (not scheduled + into a milestone yet): #216 (calendar windowing/predicate refinement), #219 + (contacts notes handling), #225 (`filter edit` silently re-enables a disabled + rule and resets `created_at`). diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 352dc0f..1eed593 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -41,41 +41,49 @@ These are non-negotiable and apply to every item below: --- -## Where we are today (2026-07-28) +## Where we are today (2026-07-30) **Phases 0–2 are complete and on `dev`.** The daemon boots gRPC-only on loopback -`127.0.0.1:9420` behind a keyring-minted bearer token, and serves **six services, -each mounted behind the auth interceptor**: `System`, `Accounts`, `Mail`, -`Filters`, `Export`, `Audit`. The IMAP→store→read→SMTP-send spine works +`127.0.0.1:9420` behind a keyring-minted bearer token, and serves **eight +services, each mounted behind the auth interceptor**: `System`, `Accounts`, +`Mail`, `Filters`, `Calendar`, `Contacts`, `Export`, `Audit` (the `Calendar` +and `Contacts` verticals landed in M1/M2). The IMAP→store→read→SMTP-send spine works end-to-end over gRPC, driven by the `nuncio-cli` reference client, tested offline. The legacy JSON-RPC IPC is removed. The `nuncio.v1` contract is published with a byte-deterministic `FileDescriptorSet` golden guarding it, plus external-codegen docs for Swift/C#/TypeScript. -- **Phase 0 — Honesty & foundation:** done. Fabricated docs archived; workspace - shrunk to engine crates + CLI (`_reference/` holds the old shells); real OS - keyring; fail-open auto-updater disabled; releases marked pre-alpha. -- **Phase 1 — gRPC skeleton + the spine:** done. Authenticated loopback gRPC; - CLI as gRPC client; real `IMAP fetch → store → read → SMTP send` against mock - servers; store correctness fixes (FTS-vs-encryption, backfill, salvage schema, - transient-error ≠ corruption). -- **Phase 2 — API completeness for the spine:** done. Full proto surface for the - spine; contract-stability test; codegen docs; IPC removed. - -**Phase 3 — Feature completeness — is in progress:** +**Phase 3 — Feature completeness — is now COMPLETE**, via milestones M1–M4, all +merged to `dev`: - **3.A Filter correctness** — **done** (account scoping, `ON ACCOUNT`, non-ASCII offsets, `NOT IN`). -- **3.B Calendar** — engine layer **done** (real CalDAV `REPORT` transport, - `TZID` fix, `CalendarBackend` trait, `calendar_events` store CRUD); the API - vertical + recurrence wiring remain (see M1). -- **3.C Contacts (CardDAV)** and **3.D JMAP** — not started. +- **3.B Calendar** — **done** (M1). Real CalDAV `REPORT` transport, `TZID` fix, + `CalendarBackend` trait, `calendar_events` store CRUD, the `Calendar` gRPC + service + CLI commands, and RRULE recurrence wired into the event-query path. +- **3.C Contacts (CardDAV)** — **done** (M2). A real, wiremock-tested CardDAV + client replaced the fabricated one; contacts persist through the daemon's + store and are exposed over a `Contacts` gRPC service + CLI. +- **3.D JMAP** — **done** (M3). A real JMAP HTTP client is wired into the same + `MailBackend` fetch→store path as IMAP. +- **Filters made live** — **done** (M4). `FilterEngine::evaluate` is wired into + the live sync path with a fire-once-per-new-message guard, the `account` + condition-field bug is fixed, unsupported `header[...]` conditions are + honestly rejected, filter edit/export/import/logs moved onto the `Filters` + gRPC service, and a `Filters.Triage` server-streaming RPC does retroactive + bulk rescan. -Dogfooding against a real mailbox and a fabrication audit surfaced additional -**real gaps** now folded into the milestones below: the filter match-and-act -engine is not yet wired into the live sync path; sync is not incremental; -account edit/delete/test-connection and TLS-mode selection are incomplete; and a -set of correctness/security items (see M5/M6). +**Next up: M5 — Mail lifecycle & incremental sync** (account edit/delete/test + +persisted TLS mode, incremental IMAP sync with progress + per-item FETCH +timeout, explicit `SendMessage` account selection, keyring rollback on failed +`AddAccount`). Then M6 (security hardening), then M7 (API freeze / client +readiness). + +A handful of follow-ups were filed during M1–M4 and remain open, tracked as +their own backlog items rather than blocking M1–M4's completion: calendar +windowing/predicate refinement (#216), contacts notes handling (#219), and a +`filter edit` bug where re-saving a rule silently re-enables it and resets +`created_at` (#225). **Explicitly deferred** (out of scope until the backend is client-ready and each is re-justified as a genuine engine capability): OpenPGP/S-MIME E2EE (#79), diff --git a/docs/roadmap/index.html b/docs/roadmap/index.html index d6f3146..c09c6fb 100644 --- a/docs/roadmap/index.html +++ b/docs/roadmap/index.html @@ -76,7 +76,7 @@ /* milestone rail */ .miles{position:relative;margin-top:8px} .miles::before{content:"";position:absolute;left:19px;top:12px;bottom:34px;width:2px; - background:linear-gradient(var(--done),var(--prog) 16%,var(--line) 22%);border-radius:2px} + background:linear-gradient(var(--done),var(--done) 57%,var(--prog) 57%,var(--prog) 71%,var(--line) 71%);border-radius:2px} @media (max-width:640px){.miles::before{display:none}} .m{position:relative;padding-left:52px;margin-bottom:16px} @media (max-width:640px){.m{padding-left:0}} @@ -133,24 +133,31 @@
The daemon boots gRPC-only on loopback 127.0.0.1:9420 behind a keyring-minted
bearer token. IMAP → store → read → SMTP send runs end-to-end over gRPC, driven by the
nuncio-cli reference client, tested fully offline. The legacy JSON-RPC IPC is gone,
- and nuncio.v1 is published with a byte-deterministic contract-stability golden.
Publish the calendar engine over the API: a Calendar gRPC service
+
The calendar engine is published over the API: a Calendar gRPC service
(Sync / ListEvents / GetEvent — honest surface only) behind the auth interceptor, daemon wiring, CLI
- commands replacing the stub, and recurrence (rrule) wired into the fetch/store path (today it's dead code).
Replace the fake CardDAV client (returns one hardcoded contact) with a real, wiremock-tested
- client + vCard round-trip; persist through the daemon's store (the CLI currently writes a throwaway
- in-memory DB); add a Contacts service, CLI, and offline E2E.
The fake CardDAV client (returned one hardcoded contact) is replaced with a real, wiremock-tested
+ client + vCard round-trip; contacts persist through the daemon's store (the CLI previously wrote a throwaway
+ in-memory DB); a Contacts service, CLI, and offline E2E are in place.
A real JMAP HTTP client (session, Email/query/get/set) with the camelCase wire fix, wired into +
A real JMAP HTTP client (session, Email/query/get/set) with the camelCase wire fix is wired into
the same MailBackend fetch→store path as IMAP; wiremock-tested.
Today rules can be created, validated, and previewed as “MATCH” but never fire. Wire
- FilterEngine::evaluate into the real sync path so matches trigger their action
- (move/flag/delete/forward/webhook), fix the header + account-field bugs, and re-expose filter
- edit/export/import/logs and bulk triage over the service.
Rules could previously be created, validated, and previewed as “MATCH” but never fired.
+ FilterEngine::evaluate is now wired into the real sync path so matches trigger their action
+ (move/flag/delete/forward/webhook), the header + account-field bugs are fixed, and filter
+ edit/export/import/logs plus bulk triage are re-exposed over the service.
Round out real account management: edit / delete / test-connection with TLS modes (implicit / STARTTLS / plain) actually wired; explicit send-account selection; and incremental sync