docs: mark M1-M4 complete, set M5 as next milestone #227

Merged
KofTwentyTwo merged 2 commits from docs/m4-milestone-status into dev 2026-07-30 01:02:59 +00:00
3 changed files with 102 additions and 73 deletions

No files matched your search

+32 -18
View File
@@ -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
View File
@@ -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
View File
@@ -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
&amp; 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 &amp; 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>