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.
11 KiB
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
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, andcargo test-allall pass.- Read, in order:
CLAUDE.md,docs/ROADMAP.md,docs/STORY-WORKFLOW.md,docs/ARCHITECTURE.md. - Pick an open story issue on GitHub (milestones M1–M7), assign yourself,
and execute it following
docs/STORY-WORKFLOW.md. Branchstory/<m#>-<slug>offdev; open a PR todev; the PO reviews and merges.
Roles
- PO / scrum-master (coordinating session): owns
docs/ROADMAP.mdand 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:9420behind a keyring bearer token, serving eight services (System, Accounts, Mail, Filters, Calendar, Contacts, Export, Audit), each behind the auth interceptor. TheIMAP → store → read → SMTP sendspine works end-to-end over gRPC via thenuncio-clireference client, tested offline.nuncio.v1is 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 tomain; 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, theaccount-field bug fixed, edit/export/import/logs andFilters.Triagebulk rescan on the gRPC service), and Mail lifecycle + incremental sync (M5: account edit/delete/test-connection with persistedimap_tls_mode/smtp_tls_modedriving 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, explicitSendMessageaccount selection, and keyring rollback on failedAddAccount) 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/-vvwith typed errors, and aRedacted<T>redaction policy with a no-secrets-in-logs canary. Seedocs/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 -- --checkclean,cargo check-allclean,cargo test-all596 passed / 0 failed / 0 ignored across 37 suites. devis a superset ofmainagain.mainhad 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 ontomainleavesdevbuilding 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 intodev).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 --statit 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
- Clone the repo;
git checkout dev. The toolchain (1.97.1) installs automatically fromrust-toolchain.toml. Native OS build (not WSL) — the daemon needs the OS keyring. - Confirm green:
cargo fmt --all -- --check, thencargo check-all(clippy-D warningsall-targets), thencargo test-all. (There is no combinedcargo verifyalias — Cargo aliases can't chain subcommands — but this is exactly what the pre-commit hook runs.) - Run it:
cargo run -p nunciod(daemon), then drive withcargo run -p nuncio-cli -- system status. Seedocs/RUNNING.md/docs/DEVELOPING-IN-RUSTROVER.md. - 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.protoand 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.v1RPC 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 editsilently re-enables a disabled rule and resetscreated_at), #233 (M5 sync/SMTP robustness polish).