docs: mark M1-M4 complete, set M5 as next milestone #227
No files matched your search
+32
-18
@@ -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`).
|
||||
+32
-24
@@ -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),
|
||||
|
||||
+38
-31
@@ -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 @@
|
||||
|
||||
<hr class="rule">
|
||||
|
||||
<h2>Where we are · 2026-07-28</h2>
|
||||
<h2>Where we are · 2026-07-30</h2>
|
||||
<div class="band">
|
||||
<h3>Phases 0–2 complete — the spine works end to end</h3>
|
||||
<h3>Phases 0–2 complete, Phase 3 feature completeness done — M1–M4 all merged</h3>
|
||||
<p>The daemon boots gRPC-only on loopback <span class="chip">127.0.0.1:9420</span> behind a keyring-minted
|
||||
bearer token. <code>IMAP → store → read → SMTP send</code> runs end-to-end over gRPC, driven by the
|
||||
<span class="chip">nuncio-cli</span> reference client, tested fully offline. The legacy JSON-RPC IPC is gone,
|
||||
and <span class="chip">nuncio.v1</span> is published with a byte-deterministic contract-stability golden.</p>
|
||||
and <span class="chip">nuncio.v1</span> is published with a byte-deterministic contract-stability golden.
|
||||
Milestones M1–M4 are now complete on <span class="chip">dev</span>: Calendar, Contacts, JMAP, and live
|
||||
Filters are all real, wired, and API-complete. <b style="color:var(--ink)">Next up: M5</b> (mail lifecycle
|
||||
& incremental sync).</p>
|
||||
<div class="chips">
|
||||
<span class="chip svc">System</span><span class="chip svc">Accounts</span><span class="chip svc">Mail</span>
|
||||
<span class="chip svc">Filters</span><span class="chip svc">Export</span><span class="chip svc">Audit</span>
|
||||
<span class="chip" style="color:var(--muted)">— 6 services, each behind the auth interceptor</span>
|
||||
<span class="chip svc">Filters</span><span class="chip svc">Calendar</span><span class="chip svc">Contacts</span>
|
||||
<span class="chip svc">Export</span><span class="chip svc">Audit</span>
|
||||
<span class="chip" style="color:var(--muted)">— 8 services, each behind the auth interceptor</span>
|
||||
</div>
|
||||
<div class="donerow">
|
||||
<span class="donetag">Phase 0 · Honesty & foundation</span>
|
||||
<span class="donetag">Phase 1 · gRPC skeleton + spine</span>
|
||||
<span class="donetag">Phase 2 · API completeness (spine)</span>
|
||||
<span class="donetag">3.A · Filter correctness</span>
|
||||
<span class="donetag">3.B.1 · Calendar engine (CalDAV + TZID)</span>
|
||||
<span class="donetag">M1 · Calendar (CalDAV + recurrence)</span>
|
||||
<span class="donetag">M2 · Contacts (CardDAV)</span>
|
||||
<span class="donetag">M3 · JMAP</span>
|
||||
<span class="donetag">M4 · Filters made live</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -159,72 +166,72 @@
|
||||
<h2>The path · seven milestones to the goal line</h2>
|
||||
<div class="miles">
|
||||
|
||||
<div class="m prog">
|
||||
<div class="m done">
|
||||
<div class="node">M1</div>
|
||||
<div class="card">
|
||||
<div class="chead">
|
||||
<h3 class="ctitle"><span class="code">M1</span>Finish Calendar</h3>
|
||||
<span class="stat prog">In progress</span>
|
||||
<span class="stat done">Done</span>
|
||||
</div>
|
||||
<p class="cbody">Publish the calendar engine over the API: a <code>Calendar</code> gRPC service
|
||||
<p class="cbody">The calendar engine is published over the API: a <code>Calendar</code> gRPC service
|
||||
(Sync / ListEvents / GetEvent — honest surface only) behind the auth interceptor, daemon wiring, CLI
|
||||
commands replacing the stub, and <b>recurrence (rrule) wired into the fetch/store path</b> (today it's dead code).</p>
|
||||
commands replacing the stub, and <b>recurrence (rrule) wired into the fetch/store path</b>.</p>
|
||||
<div class="exit"><b>Exit:</b> a Calendar view over gRPC, offline-tested sync→list→get, recurring events materialize.</div>
|
||||
<div class="issues"><a class="iss">#178</a><span class="iss">3.B.1 done → 3.B.2 next</span></div>
|
||||
<div class="issues"><a class="iss">#178</a><span class="iss">done — #185, #186 merged</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="m plan">
|
||||
<div class="m done">
|
||||
<div class="node">M2</div>
|
||||
<div class="card">
|
||||
<div class="chead">
|
||||
<h3 class="ctitle"><span class="code">M2</span>Contacts · CardDAV</h3>
|
||||
<span class="stat plan">Planned</span>
|
||||
<span class="stat done">Done</span>
|
||||
</div>
|
||||
<p class="cbody">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 <code>Contacts</code> service, CLI, and offline E2E.</p>
|
||||
<p class="cbody">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 <code>Contacts</code> service, CLI, and offline E2E are in place.</p>
|
||||
<div class="exit"><b>Exit:</b> contacts sync and persist across restarts, driven over gRPC.</div>
|
||||
<div class="issues"><a class="iss">#179</a></div>
|
||||
<div class="issues"><a class="iss">#179</a><span class="iss">done — #187, #188 merged</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="m plan">
|
||||
<div class="m done">
|
||||
<div class="node">M3</div>
|
||||
<div class="card">
|
||||
<div class="chead">
|
||||
<h3 class="ctitle"><span class="code">M3</span>JMAP</h3>
|
||||
<span class="stat plan">Planned</span>
|
||||
<span class="stat done">Done</span>
|
||||
</div>
|
||||
<p class="cbody">A real JMAP HTTP client (session, Email/query/get/set) with the camelCase wire fix, wired into
|
||||
<p class="cbody">A real JMAP HTTP client (session, Email/query/get/set) with the camelCase wire fix is wired into
|
||||
the same <code>MailBackend</code> fetch→store path as IMAP; wiremock-tested.</p>
|
||||
<div class="exit"><b>Exit:</b> a JMAP account fetches → stores → lists offline, via the existing Mail service.</div>
|
||||
<div class="issues"><a class="iss">#180</a></div>
|
||||
<div class="issues"><a class="iss">#180</a><span class="iss">done — #189 merged</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="m plan">
|
||||
<div class="m done">
|
||||
<div class="node">M4</div>
|
||||
<div class="card">
|
||||
<div class="chead">
|
||||
<h3 class="ctitle"><span class="code">M4</span>Filters made live</h3>
|
||||
<span class="stat plan">Planned</span>
|
||||
<span class="stat done">Done</span>
|
||||
</div>
|
||||
<p class="cbody">Today rules can be created, validated, and previewed as “MATCH” but <b>never fire</b>. Wire
|
||||
<code>FilterEngine::evaluate</code> 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.</p>
|
||||
<p class="cbody">Rules could previously be created, validated, and previewed as “MATCH” but never fired.
|
||||
<code>FilterEngine::evaluate</code> 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.</p>
|
||||
<div class="exit"><b>Exit:</b> an E2E proves a matching rule fires an action against synced mail — no fabricated preview.</div>
|
||||
<div class="issues"><a class="iss">#175</a><a class="iss">#176</a><a class="iss">#181</a></div>
|
||||
<div class="issues"><a class="iss">#175</a><a class="iss">#176</a><a class="iss">#181</a><span class="iss">done — #190–#193 merged</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="m plan">
|
||||
<div class="m prog">
|
||||
<div class="node">M5</div>
|
||||
<div class="card">
|
||||
<div class="chead">
|
||||
<h3 class="ctitle"><span class="code">M5</span>Mail lifecycle + incremental sync</h3>
|
||||
<span class="stat plan">Planned</span>
|
||||
<span class="stat prog">Next</span>
|
||||
</div>
|
||||
<p class="cbody">Round out real account management: edit / delete / test-connection with TLS modes
|
||||
(implicit / STARTTLS / plain) actually wired; explicit send-account selection; and <b>incremental sync</b>
|
||||
|
||||
Reference in new issue
Block a user