Cargo aliases are a single command argv and cannot chain subcommands, so `cargo verify = ["fmt --all -- --check", "check-all", "test-all"]` tried to run a literal subcommand named "fmt --all -- --check" and always errored. The pre-commit hook already runs the three checks as separate commands, so it was unaffected. Drop the broken alias, drop the matching RustRover run configuration, and reword every doc that told readers to run `cargo verify` to instead run fmt/check-all/test-all as three commands.
7.5 KiB
Backlog — Phase 0 & Phase 1 (engineering-ready)
Status: Phase 0 and Phase 1 are both complete and on
dev— this document is now the historical story-level record of that work. For where the project stands today (Phase 3 in progress, milestones M1–M7), seeROADMAP.md.
Ordered, executable stories for the first two phases of ROADMAP.md.
Each story has acceptance criteria (AC) and a rough size (S ≤1d, M ≈2–4d, L ≈1wk).
Do them roughly top-to-bottom; within an epic, stories are mostly independent
unless noted. Definition of done for every story: engine + (where applicable)
proto + CLI + offline test, and the local gate green (cargo fmt --all -- --check,
cargo check-all, cargo test-all).
Phase 0 — Honesty & foundation
Epic 0.A — Documentation reset
- 0.A.1 Archive fabricated docs (S). Move the superseded
PLAN-*,TODO.md,TICKET-*, andEXECUTIVE-*files intodocs/_archive/with a README stating they are historical and unverified. AC:docs/top level contains onlyROADMAP.md,BACKLOG.md,adr/, a rewrittenARCHITECTURE.md, and_archive/. - 0.A.2 Rewrite
CLAUDE.mdto reality (M). Correct crate count (12), define the cargo aliases (check-all,test-all,cov,verify), document the gRPC/engine-first direction, and replace the "100% four-shell parity" mandate with "parity via the published contract." AC: every command and claim inCLAUDE.mdis locally verifiable. - 0.A.3 Rewrite
README.md(S). Remove fabricated claims and benchmarks; state honest pre-alpha status. AC: no unverifiable claim remains.
Epic 0.B — Release hygiene
- 0.B.1 Mark public releases pre-alpha (S). Convert v1.0.0 and rolling releases to prerelease with a NOTICE that they are non-functional and the updater is unsafe. AC: no GitHub release is presented as GA.
- 0.B.2 Disable the fail-open auto-updater (S). Guard the 24h update loop off until Phase 4. AC: the daemon never downloads/replaces a binary.
Epic 0.C — Green CI
- 0.C.1 Clear the clippy backlog (M). Commit or revert the ~1,600 lines of
abandoned edits so
cargo check-allpasses (76 current denials innuncio-core). AC:cargo check-allexits 0 locally and in CI. - 0.C.2 Align CI to the local gate (S). CI runs
cargo clippy --workspacewithout--all-targets; make CI runcargo check-allso CI and local are identical. AC: CI and the local gate enforce the same lints. - 0.C.3 Replace the coverage gate (M). Drop the gamed 100%-line gate (it
excludes all
main.rsand incentivized stub+matching-test theater) for a realistic threshold on engine crates plus a required mock-server E2E job. AC: coverage gate reflects real confidence; full CI green ondev/main.
Epic 0.D — Shrink to engine + CLI
- 0.D.1 Extract the API-surface draft (S, do before 0.D.2). Mine the MCP
shell's ~20 tool→engine mappings into
docs/api-surface-draft.mdas candidate RPCs. AC: every operation the MCP shell exposed is listed as a candidate RPC. - 0.D.2 Move UIs to
_reference/(M). Removenuncio-tui,nuncio-gui(+src-tauri,ui), andnuncio-mcpfrom the workspace members into_reference/. AC: workspace =core, mail, cal, contacts, store, filter, cli, nunciod;cargo build/CI green.
Epic 0.E — Kill hardcoded keys / real vault
- 0.E.1 Purge key literals (M). Remove
DEFAULT_STORAGE_KEY,DEFAULT_WORM_KEY,'secret_ledger_key', andb'nuncio_ledger_secret'; fail closed when no key is present. AC:grepfinds no crypto key literal insrc/; missing-key paths error rather than silently use a default. - 0.E.2 Real OS keyring vault (L). Replace
MockKeyringin production with thekeyringcrate; keys minted and read from the OS keyring;MockKeyringremains test-only. AC: an integration test (with a mock keyring provider) proves the daemon reads keys from the vault, not from source.
Phase 1 — gRPC skeleton + the spine
Epic 1.A — gRPC transport
- 1.A.1 Proto skeleton + build (M). Create
proto/nuncio/v1with aStatusRPC; wire tonic/prost codegen. AC: proto compiles; Rust server+client stubs generate in the build. - 1.A.2 Authenticated daemon server (M). Daemon serves gRPC over loopback;
bearer token from the keyring; unauthenticated calls rejected. AC: an
unauthenticated
Statuscall is refused; an authenticated one returns real daemon state. - 1.A.3 CLI becomes a gRPC client (M). Rewrite
nuncio statusas a tonic client; fence/retire the legacy JSON-RPC IPC. AC:nuncio statusround-trips daemon↔CLI over gRPC in an E2E test. - 1.A.4 Events stream skeleton (M).
Subscribeserver-streaming RPC emittingCoreEvents. AC: the CLI can subscribe and receive a test event; no response/notification interleaving bug (regression test).
Epic 1.B — Store correctness (prerequisite for real data)
- 1.B.1 FTS-vs-encryption strategy (L). Decide and implement: index plaintext before encryption, or drop body encryption for searchable fields — document the choice. AC: body search returns real hits on the production store path (not via raw-SQL plaintext injection); E2E proves it.
- 1.B.2 FTS backfill at migration (M). Backfill the FTS index so messages saved before first search are searchable. AC: a message saved before any search call is found by a later search.
- 1.B.3 Fix salvage schema mismatch (M). Recovery restores filter rules against the real schema. AC: a recovery test restores a nonzero rule count.
- 1.B.4 Transient error ≠ corruption (M). Distinguish
SQLITE_BUSY/pool timeouts from genuine corruption; never salvage/delete the live DB on a transient error. AC: an injected transient error does not trigger salvage/deletion; a genuinely corrupt DB still does.
Epic 1.C — The spine (one account, real mail)
- 1.C.1 Persistent DB path (M). Daemon opens a real store (e.g.
~/.nuncio/…); removeHeadlessRunner::ephemeral()from the CLI/production path. AC:account add→ restart daemon →account listshows the account. - 1.C.2 Account setup + credentials (M). Collect IMAP/SMTP config; store the password in the keyring only. AC: account config persists; the password is never written to SQLite or source; only a keyring reference is stored.
- 1.C.3 Real IMAP fetch → store loop (L). Replace the status-flag "sync" with a
real fetch loop in the daemon using
ImapEngine. AC: against a mock IMAP server, messages land in the store and sync status reflects reality (no stuckSyncing). - 1.C.4 Read path over gRPC (M). List folders/messages, read a body, mark read — via CLI over gRPC. AC: offline E2E does list → read → mark-read against the mock server.
- 1.C.5 Real SMTP send via outbox (L). Delete the "Simulate remote IMAP/JMAP mutation execution" worker; real send with retry/backoff. AC: against a mock SMTP server a message actually sends; a failure retries; there is no fabricated "Message sent" when it did not.
- 1.C.6 Mock protocol server harness (M, enables 1.C.3–1.C.5 E2E). Stand up
offline mock IMAP/SMTP (and wiremock scaffolding for later JMAP/CalDAV). AC:
the full
fetch → store → read → sendE2E runs offline in CI.
Phase 1 exit: a real account syncs and sends end-to-end through the CLI over gRPC, persisted across restarts, tested entirely offline, with CI green.