Skip to content

ROADMAP

github-actions[bot] edited this page Jul 30, 2026 · 6 revisions

Nuncio Roadmap (authoritative)

Purpose of this document: the single source of truth for where Nuncio is and the path from here to a frozen, client-ready backend — a solid engine, a reference CLI, and a published, versioned gRPC API that front-end repos (TUI, native macOS/Windows, MCP bridge) can begin consuming with contract-guaranteed parity.

It supersedes the Antigravity-era PLAN-*, TODO.md, TICKET-*, EXECUTIVE-* documents (systematic fabrication — removed 2026-07-26, in git history only; do not trust them). Ground truth for the starting state: the 2026-07-26 forensic assessment. Governing decision: docs/adr/0001-engine-first-grpc-architecture.md. Phase 0/1 story-level detail lives in docs/BACKLOG.md.

North star

Nuncio is a local-first (sovereign) mail, calendar, and contacts engine: a background daemon (nunciod) that owns all state, credentials, and protocol logic, and publishes a versioned gRPC API over loopback. User interfaces are separate native client projects that consume that API — they hold no business logic and no data of their own. Feature parity across clients is a property of the contract, not of anyone's discipline.

Definition of "done" (anti-theater guardrails)

These are non-negotiable and apply to every item below:

  1. A capability is done only when engine + proto + CLI command + offline E2E test all exist together, and the local gate (cargo fmt --all -- --check, cargo check-all (clippy -D warnings all-targets), cargo test-all) is green.
  2. No fabricated success. A path that cannot do the real thing returns an honest Unimplemented/error — never canned data, never a fake "sent".
  3. The .proto files are the contract. The nuncio-proto contract-stability test fails on any accidental wire change; breaking changes go to a new package version, never a silent mutation of nuncio.v1.
  4. All external protocols are mocked in tests (wiremock / Mock*Backend); live network calls in the test suite are forbidden.
  5. "Done" is re-derived from tested behavior, never inherited from a label.

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

The feature surface — Phase 3 through M5 — is now COMPLETE, via milestones M1–M5, all merged to dev (M1–M4 are already promoted to main; M5 promotes next):

  • 3.A Filter correctness — done (account scoping, ON ACCOUNT, non-ASCII offsets, NOT IN).
  • 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.
  • Mail lifecycle & incremental sync — done (M5). Account lifecycle (UpdateAccount/RemoveAccount/TestAccountConnection) is real over gRPC, with imap_tls_mode/smtp_tls_mode persisted and actually driving the real IMAP (implicit/STARTTLS/plain) and SMTP connection. Sync is genuinely incremental: a real per-folder {uidvalidity}:{uidnext} checkpoint narrows the FETCH instead of re-fetching everything, a UIDVALIDITY change forces a safe full re-fetch (no silent mail loss), a per-item FETCH timeout replaces the indefinite hang, and a SyncProgress event is emitted. SendMessage takes an explicit account_id (honest AccountNotFound, no silent wrong-account fallback), and AddAccount rolls back an orphaned keyring secret if persistence fails.

Next up: M6 — Security & release hardening (fail-closed encryption, zeroized key material, WORM/ledger key-length validation, a rejection of non-loopback gRPC binds plus an auth-coverage canary, a fail-closed updater on missing SHA256SUMS, real CIDR/DNS SSRF checks for webhooks, inbound HTML email sanitization). Then M7 (API freeze / client-readiness).

A handful of follow-ups were filed during M1–M5 and remain open, tracked as their own backlog items rather than blocking completion: calendar windowing/predicate refinement (#216), contacts notes handling (#219), a filter edit bug where re-saving a rule silently re-enables it and resets created_at (#225), and further mail sync/SMTP robustness polish identified during M5 (#233).

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), WASM/QuickJS plugins (#80), local-LLM summarization (#81).


The path to client-ready

The remaining work is organized into seven milestones. M1–M5 complete the feature surface; M6 hardens security; M7 is the API-freeze gate that declares the backend ready for front-end repos. Milestones are ordered but M1–M4 are largely independent (they touch different engine crates); the only hard serialization is the shared nuncio.proto (and its golden), so proto additions land one at a time.

M1 — Finish Calendar (closes #178)

Complete the Calendar vertical behind the API.

  • Proto Calendar service (Sync/ListEvents/GetEvent — honest surface only, no fabricated create/delete), mounted behind the bearer-auth interceptor; regenerate the contract golden.
  • Daemon CalendarGrpcService + injection seam + calendar_sync module; wire the (currently orphaned) nuncio-cal crate into nunciod.
  • Wire RecurrenceEngine (rrule) into the fetch/store/query path so recurring events actually materialize (currently dead code).
  • CLI calendar commands replacing the hardcoded stub; offline sync → list → get E2E incl. an unauthenticated-rejection assertion.
  • Exit: create a rule-free Calendar view over gRPC end-to-end, offline-tested, recurrence expanded.

M2 — Contacts / CardDAV (closes #179)

Bring Contacts to the same bar as Calendar.

  • Replace the fake CardDavClient (returns a hardcoded contact) with a real wiremock-tested CardDAV client; vCard parse → model → regenerate round-trip.
  • Persist contacts through the daemon's store (today the CLI writes a fresh in-memory DB per invocation — nothing persists); add a Contacts proto service
    • daemon wiring + CLI + offline sync → list E2E.
  • Exit: contacts sync and persist across restarts, driven over gRPC.

M3 — JMAP (closes #180)

Make JMAP a real second mail backend alongside IMAP/SMTP.

  • Real JMAP HTTP client (session, Email/query/get/set), camelCase wire fix, wired into the same MailBackend fetch→store path; wiremock-tested.
  • Exit: a JMAP account fetches → stores → lists offline, via the existing Mail service.

M4 — Filters made live + complete (closes #175, #176, #181)

Today rules can be created, validated, and previewed as "MATCH" but never fire.

  • Wire FilterEngine::evaluate into the real sync/orchestrator path so a synced message that matches a rule actually triggers its action (move/flag/ delete/forward/webhook — the outbox is built but never invoked end-to-end).
  • Fix FilterField::Header (silently hardcoded false) and the eval_leaf account-field bug (#181, reads folder_id).
  • Re-expose filter edit/export/import/logs (#175) and bulk triage (#176) over the Filters service (lost with the IPC).
  • Exit: an offline E2E proves a matching rule fires an action against synced mail; no silent no-match, no fabricated preview.

M5 — Mail spine completeness & correctness (closes #170, #169, #182, #183, #184)

Round out real account and sync management.

  • Account lifecycle over gRPC: edit / delete / test-connection, with TLS-mode (implicit / STARTTLS / plain) actually persisted and wired (#182).
  • Mail.SendMessage explicit account selection (#170); avoid orphaned keyring entries on partial account add (#169).
  • Incremental sync: honor since_state so sync isn't a full historical re-fetch every time (#184); sync progress reporting + bounded per-item stall timeout (#183).
  • Exit: full account lifecycle + incremental sync, offline-E2E-proven, no fabricated fallbacks in the IMAP path.

M6 — Security & release hardening (Phase 4; closes #162, #163, #164, #165)

The engine becomes production-solid.

  • Fail-closed everywhere: fix encrypt_text_at_rest silent empty-ciphertext data loss (#162); a fail-closed, signature-verifying updater (no apply when SHA256SUMS is absent).
  • Key hygiene: zeroize engine key material on drop (#163); validate WORM/ledger key length from the vault (#164).
  • Transport/egress: gRPC bind hardening + a guard that every service is wrapped in the interceptor (#165); real CIDR-based SSRF checks on webhook egress (replace the substring blocklist); sandboxed HTML email rendering pipeline for clients.
  • Exit: a security review passes; no fail-open crypto, update, or egress path remains.

M7 — API freeze & client-readiness gate ← the goal line

Declare the backend ready for front-end repos.

  • Freeze nuncio.v1: every backend capability is represented; the contract-stability golden covers the full surface; the versioning policy (additive-only in v1) is enforced by test.
  • Generated SDK / codegen docs verified for Swift, C#, and TypeScript; a minimal "hello, daemon" example per language.
  • The CLI reference client exercises every RPC in offline E2E — the CLI is the proof the contract is complete and usable.
  • Replace the gamed 100%-line coverage gate with a realistic engine-crate threshold + a required mock-server E2E job (#143); CI green on dev/main.
  • Tag a pre-alpha backend release whose notes state exactly what is real.
  • Exit / definition of client-ready: the daemon publishes a frozen, documented, contract-tested gRPC API that the CLI fully exercises offline. At this point Phase 5 front-end repos may begin.

Phase 5 — Client projects (after M7)

Separate repositories consuming the frozen API — nuncio-tui (Rust), nuncio-gui-macos (Swift + grpc-swift), nuncio-gui-windows (WinUI/C# + Grpc.Net), nuncio-mcp (MCP↔gRPC bridge). Parity is guaranteed by the contract. The deferred V2/V3 features (E2EE, plugins, local AI) are re-justified and scheduled here or folded back into the engine as they earn their place.


Tracking

Each milestone maps to GitHub issues (numbers above). Every capability is built via one implementer + a spec/quality review gate + an offline E2E, committed only when the local gate (fmt + clippy -D warnings + tests) is green and the proto golden is consistent. Issues close only after the review step — never on a green build alone.

Clone this wiki locally