From 0fea8edafebd16bca830ef6cb05344a5edf93a56 Mon Sep 17 00:00:00 2001 From: James Maes Date: Wed, 29 Jul 2026 19:59:39 -0500 Subject: [PATCH 1/2] docs: mark M1-M4 complete, set M5 as next milestone Phase 3 feature completeness (Calendar, Contacts, JMAP, live Filters) is done and merged to dev; update ROADMAP.md, the rendered roadmap page, and HANDOFF.md to reflect it and point at M5 next. --- docs/HANDOFF.md | 50 ++++++++++++++++++----------- docs/ROADMAP.md | 49 ++++++++++++++++------------- docs/roadmap/index.html | 69 +++++++++++++++++++++++------------------ 3 files changed, 98 insertions(+), 70 deletions(-) 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..4ecab09 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -41,7 +41,7 @@ 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, @@ -52,30 +52,37 @@ 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 @@
-

Where we are · 2026-07-28

+

Where we are · 2026-07-30

-

Phases 0–2 complete — the spine works end to end

+

Phases 0–2 complete, Phase 3 feature completeness done — M1–M4 all merged

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.

+ and nuncio.v1 is published with a byte-deterministic contract-stability golden. + Milestones M1–M4 are now complete on dev: Calendar, Contacts, JMAP, and live + Filters are all real, wired, and API-complete. Next up: M5 (mail lifecycle + & incremental sync).

SystemAccountsMail - FiltersExportAudit - — 6 services, each behind the auth interceptor + FiltersCalendarContacts + ExportAudit + — 8 services, each behind the auth interceptor
Phase 0 · Honesty & foundation Phase 1 · gRPC skeleton + spine Phase 2 · API completeness (spine) 3.A · Filter correctness - 3.B.1 · Calendar engine (CalDAV + TZID) + M1 · Calendar (CalDAV + recurrence) + M2 · Contacts (CardDAV) + M3 · JMAP + M4 · Filters made live
@@ -159,72 +166,72 @@

The path · seven milestones to the goal line

-
+
M1

M1Finish Calendar

- In progress + Done
-

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).

+ commands replacing the stub, and recurrence (rrule) wired into the fetch/store path.

Exit: a Calendar view over gRPC, offline-tested sync→list→get, recurring events materialize.
-
#1783.B.1 done → 3.B.2 next
+
#178done — #185, #186 merged
-
+
M2

M2Contacts · CardDAV

- Planned + Done
-

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.

Exit: contacts sync and persist across restarts, driven over gRPC.
- +
#179done — #187, #188 merged
-
+
M3

M3JMAP

- Planned + Done
-

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.

Exit: a JMAP account fetches → stores → lists offline, via the existing Mail service.
- +
#180done — #189 merged
-
+
M4

M4Filters made live

- Planned + Done
-

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.

Exit: an E2E proves a matching rule fires an action against synced mail — no fabricated preview.
- +
#175#176#181done — #190–#193 merged
-
+
M5

M5Mail lifecycle + incremental sync

- Planned + Next

Round out real account management: edit / delete / test-connection with TLS modes (implicit / STARTTLS / plain) actually wired; explicit send-account selection; and incremental sync -- 2.54.0 From af690941bbbb897960eb62369850203205a822b7 Mon Sep 17 00:00:00 2001 From: James Maes Date: Wed, 29 Jul 2026 20:02:02 -0500 Subject: [PATCH 2/2] docs: correct service count to eight (Calendar+Contacts added) --- docs/ROADMAP.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 4ecab09..1eed593 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -44,9 +44,10 @@ These are non-negotiable and apply to every item below: ## 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 -- 2.54.0