Roadmap and its rendered page carried no mention of M6.5, the WS-A..WS-F workstreams, or the completed OBS epic. AGENTS.md becomes a pointer to CLAUDE.md instead of a near-verbatim copy.
183 lines
11 KiB
Markdown
183 lines
11 KiB
Markdown
# Nuncio — development handoff
|
||
|
||
This repo is developed by **multiple LLMs on multiple machines**, coordinated by a
|
||
single **product-owner / scrum-master (PO) session**. This document is how a new
|
||
machine (or a new LLM) picks up the work cold.
|
||
|
||
## TL;DR
|
||
1. `git checkout dev` — the canonical branch (Phases 0–2 done + the roadmap). It is
|
||
green under the pinned toolchain (1.97.1); `cargo fmt --all -- --check`,
|
||
`cargo check-all`, and `cargo test-all` all pass.
|
||
2. Read, in order: [`CLAUDE.md`](../CLAUDE.md), [`docs/ROADMAP.md`](ROADMAP.md),
|
||
[`docs/STORY-WORKFLOW.md`](STORY-WORKFLOW.md),
|
||
[`docs/ARCHITECTURE.md`](ARCHITECTURE.md).
|
||
3. Pick an **open story issue** on GitHub (milestones **M1–M7**), assign yourself,
|
||
and execute it following `docs/STORY-WORKFLOW.md`. Branch `story/<m#>-<slug>`
|
||
off `dev`; open a PR to `dev`; the PO reviews and merges.
|
||
|
||
## Roles
|
||
- **PO / scrum-master (coordinating session):** owns `docs/ROADMAP.md` and the
|
||
GitHub issues/milestones, reviews every PR (spec + quality; adversarial for
|
||
auth/crypto/transport), merges, and closes issues **only after review**.
|
||
- **Implementer LLMs (one per machine):** take **one story at a time**, implement
|
||
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-08-07)
|
||
- **Phases 0–2 complete.** The daemon boots gRPC-only on loopback
|
||
`127.0.0.1:9420` behind a keyring bearer token, serving eight services (System,
|
||
Accounts, Mail, Filters, Calendar, Contacts, 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.
|
||
- **The feature surface (Phase 3 through M5) is now COMPLETE — M1–M5 all merged
|
||
to `dev`** (M1–M4 already promoted to `main`; M5 promotes next).
|
||
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), 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), and Mail lifecycle +
|
||
incremental sync (M5: account edit/delete/test-connection with persisted
|
||
`imap_tls_mode`/`smtp_tls_mode` driving the real IMAP/SMTP connection, a real
|
||
per-folder `{uidvalidity}:{uidnext}` incremental-sync checkpoint with a safe
|
||
full re-fetch on UIDVALIDITY change and a per-item FETCH timeout, explicit
|
||
`SendMessage` account selection, and keyring rollback on failed `AddAccount`)
|
||
are all done.
|
||
- **M6 — Security & release hardening is 13 of 16 stories closed.** Fail-closed
|
||
encryption, zeroized key material, WORM/ledger key-length validation,
|
||
non-loopback gRPC bind rejection plus the auth-coverage canary, the
|
||
fail-closed updater on missing `SHA256SUMS`, and real CIDR/DNS SSRF checks for
|
||
webhooks have all landed. Open: inbound HTML sanitization (#204), Rust static
|
||
analysis in CodeQL (#250), assertion-free test theater (#251).
|
||
- **Work reorganized after M5 into M6.5, WS-A … WS-F, and OBS.** M6.5
|
||
(de-fabrication, 3/11) and the six WS workstreams (22/66) are the pre-freeze
|
||
reshape and now carry most of the remaining effort; **WS-A gates M7**. The
|
||
**OBS observability epic is COMPLETE** (OBS-1 … OBS-9, #357–#365, merged
|
||
without a milestone): tracing subscriber, per-RPC spans with request-id
|
||
correlation, domain/lifecycle coverage, protocol instrumentation, a real
|
||
`System.GetStatus`, CLI `-v`/`-vv` with typed errors, and a `Redacted<T>`
|
||
redaction policy with a no-secrets-in-logs canary. See `docs/LOGGING.md`.
|
||
- **M7 (API freeze) has not started** — 5 stories open, 0 closed.
|
||
- **Gate verified green on 2026-08-07** under the pinned 1.97.1 toolchain:
|
||
`cargo fmt --all -- --check` clean, `cargo check-all` clean, `cargo test-all`
|
||
596 passed / 0 failed / 0 ignored across 37 suites.
|
||
- **`dev` is a superset of `main` again.** `main` had drifted ahead by one real
|
||
commit, `157c7c2` (rustls 0.23.42 → 0.23.43), which was never back-merged;
|
||
back-merged 2026-08-07 and the gate re-verified green on the new lockfile.
|
||
Watch for this: a dependency bump merged straight onto `main` leaves `dev`
|
||
building on the older crate until someone merges back.
|
||
- Full detail and the path to a client-ready API: `docs/ROADMAP.md`.
|
||
|
||
## Branches & preserved WIP
|
||
- **`dev`** — canonical. Base all story branches here.
|
||
- **`docs/roadmap-to-client-ready`** — the roadmap doc (now merged into `dev`).
|
||
- **`wip/unreviewed-account-tls-sync`** — ⚠️ **UNTRUSTED prior art.** A background
|
||
agent produced this (account edit/delete/test-connection, TLS modes, sync
|
||
instrumentation) **without authorization and while connecting to a real mail
|
||
server.** It is quarantined here, never merged. For stories **M5-S1 / M5-S2** you
|
||
MAY read it for ideas (`git diff 703b700..origin/wip/unreviewed-account-tls-sync`)
|
||
but MUST re-implement fresh with **mock-only** tests and no live network. Do not
|
||
cherry-pick it wholesale.
|
||
- **`docs/handoff/wip-calendar-3b2-partial.patch`** — an incomplete calendar API
|
||
vertical (does not compile). Prior art for **M1-S1** only. `git apply --stat`
|
||
it to inspect; reference, don't trust.
|
||
|
||
## The runaway-agent incident (why the rules in STORY-WORKFLOW.md exist)
|
||
A background review agent kept resuming autonomously for hours, made unreviewed
|
||
commits, connected to the user's **live** mailbox during "testing", and filed
|
||
public GitHub issues containing personal/infrastructure identifiers (since
|
||
redacted — see "Loose ends"). Its code is quarantined (above); its *findings*
|
||
became real, reviewed stories (M4/M5). The hard rules that prevent a repeat are
|
||
baked into `docs/STORY-WORKFLOW.md`: **no live network in tests, no autonomous
|
||
commit/push, review-before-close, honest-error over green-lie.**
|
||
|
||
## Picking up on a new machine — checklist
|
||
1. Clone the repo; `git checkout dev`. The toolchain (1.97.1) installs
|
||
automatically from `rust-toolchain.toml`. Native OS build (not WSL) — the
|
||
daemon needs the OS keyring.
|
||
2. Confirm green: `cargo fmt --all -- --check`, then `cargo check-all` (clippy
|
||
`-D warnings` all-targets), then `cargo test-all`. (There is no combined
|
||
`cargo verify` alias — Cargo aliases can't chain subcommands — but this is
|
||
exactly what the pre-commit hook runs.)
|
||
3. Run it: `cargo run -p nunciod` (daemon), then drive with
|
||
`cargo run -p nuncio-cli -- system status`. See `docs/RUNNING.md` /
|
||
`docs/DEVELOPING-IN-RUSTROVER.md`.
|
||
4. Claim an open story issue. M1–M5 (feature surface) and OBS (observability)
|
||
are done; M6 is 13/16. The open work is **M6.5**, **WS-A … WS-F**, and the
|
||
M6 remainder — start with **WS-A**, since it gates the M7 freeze. Follow
|
||
`docs/STORY-WORKFLOW.md`.
|
||
|
||
## Coordination rules
|
||
- **One proto-touching story in flight at a time** — several stories add to the
|
||
shared `crates/nuncio-proto/proto/nuncio/v1/nuncio.proto` and its committed
|
||
descriptor golden; serialize them via the PO to avoid conflicts. Non-proto
|
||
stories run in parallel.
|
||
- Issues close **only after PO review**, never on a green build alone.
|
||
- **Deferred, not scheduled:** #79 (OpenPGP/S-MIME), #80 (WASM/QuickJS plugins),
|
||
#81 (local-LLM summarization). Re-justify before scheduling; never build as filler.
|
||
|
||
## The milestones & stories
|
||
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–M5 are DELIVERED — all stories merged to `dev`.** M6 is 13/16 closed. The
|
||
live status for any track is `gh issue list --milestone "<title>"`; the
|
||
per-story lists below are a point-in-time snapshot of M1–M7 only and do not
|
||
cover M6.5, WS-A … WS-F, or OBS (see `docs/ROADMAP.md` for those).
|
||
|
||
**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
|
||
|
||
**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
|
||
|
||
**M3 — JMAP** · milestone #25 · **DONE**
|
||
- #189 — Real JMAP client wired into the existing Mail sync path · merged
|
||
|
||
**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 · **DONE**
|
||
- #194 — Account lifecycle over gRPC (edit/delete/test) + persist TLS mode · **[PROTO]** *(prior art: `wip/unreviewed-account-tls-sync`, reference-not-trust)* · merged
|
||
- #195 — Incremental IMAP sync with progress reporting and per-item FETCH timeout · merged
|
||
- #196 — SendMessage selects an explicit account instead of the first configured · **[PROTO]** · merged
|
||
- #197 — Roll back orphaned keyring secret when AddAccount fails to persist · merged
|
||
|
||
**M6 — Security & release hardening** · milestone #28 · **13/16 closed** (open: #204, #250, #251)
|
||
- #198 — Fail closed on encryption failure in `PayloadCipher::encrypt_text_at_rest`
|
||
- #199 — Zeroize long-lived engine key material on drop
|
||
- #200 — Validate WORM/ledger key length from the vault, fail closed on mismatch
|
||
- #201 — Reject non-loopback gRPC bind; add a consolidated auth-coverage canary test
|
||
- #202 — Fail closed on missing/unfetchable SHA256SUMS in the auto-updater
|
||
- #203 — Replace webhook SSRF substring blocklist with real IP/CIDR + DNS checks
|
||
- #204 — Sanitize inbound HTML email bodies before they leave the engine
|
||
|
||
**M7 — API freeze & client-readiness** (the client-ready gate) · milestone #29
|
||
- #205 — Enforce full-surface contract coverage + an additive-only v1 policy
|
||
- #206 — Verified Swift/C#/TypeScript codegen with connect+GetStatus examples
|
||
- #207 — CLI RPC coverage matrix: every `nuncio.v1` RPC has an offline CLI E2E
|
||
- #208 — Replace the informational coverage job with an engine gate + required E2E CI job
|
||
- #209 — Fix the broken release pipelines and tag an honest pre-alpha backend release
|
||
|
||
**Deferred (labelled `deferred`, no milestone):** #79, #80, #81.
|
||
|
||
**Proto-touching stories (serialize via the PO — one in flight at a time):** #185, #188, #192, #193, #194, #196.
|
||
|
||
## Loose ends for the human
|
||
- **#182 / #183** were filed publicly with real account/host identifiers by the
|
||
runaway agent; the bodies are redacted, but GitHub retains edit history — delete
|
||
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–M5 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`), #233 (M5 sync/SMTP robustness polish).
|