main's only unique commit was an outdated CLAUDE.md (pre-reconstruction
11-crate description). Keep dev's reconstructed tree wholesale; record main as
merged so dev -> main promotion is clean.
Set an explicit top-level permissions block (contents: read for CI;
contents: write for wiki-sync, which pushes to the wiki) so the workflows
follow least-privilege and clear the CodeQL 'workflow does not contain
permissions' findings.
Sweep every README/doc for accuracy and remove fabricated or stale content:
- README/CLAUDE: add the live roadmap-page link and a rule to keep the rendered
page in sync with docs/ROADMAP.md; fix the stale cov alias and the
already-moved-shells note.
- .github/CONTRIBUTING+SECURITY: drop the fabricated nuncio.mx site link and the
aspirational release/security claims; state honest pre-alpha status.
- docs/RUNNING, DEVELOPING, ARCHITECTURE, BACKLOG, Home, NSQL spec, corruption
notes, proto/README: correct crate count/list, service list, CLI flags, phase
status, and remove claims of unshipped/removed things (IPC-as-current,
hardcoded-keys, unwired engines); mark superseded api-surface-draft historical.
- Remove the fabricated benchmark baked into nsql_pipeline.svg and delete the
orphaned ipc_sequence.svg (depicted the removed IPC with a fake timing).
Add docs/STORY-WORKFLOW.md (the canonical per-story execution/gate/review
protocol every roadmap issue links to) and docs/HANDOFF.md (how a new machine or
LLM picks up the work: current state, branches, the M1-M7 story index, and
coordination rules). Include the incomplete calendar API-vertical WIP as a
reference patch under docs/handoff/ (prior art for the M1 calendar story;
does not compile, apply-to-inspect only).
Rewrite ROADMAP.md as the authoritative plan from today's state (Phases 0-2
complete; Phase 3 in progress) to a frozen, versioned gRPC API that front-end
repos can consume. Records true status, folds in gaps found via dogfooding and
audit, and defines seven milestones (M1 calendar, M2 contacts, M3 JMAP, M4 live
filters, M5 mail lifecycle + incremental sync, M6 security hardening, M7 API
freeze + client-readiness gate) each with concrete exit criteria.
Fix the TZID bug in the iCalendar parser: DTSTART/DTEND values carrying a TZID
parameter hit a catch-all that zeroed the timestamp. Resolve the zone via
chrono_tz and return an error on an unknown or DST-ambiguous zone rather than
fabricating a 1970 timestamp.
Add a CalendarBackend trait mirroring MailBackend, a real reqwest CalDAV REPORT
transport (CalDavClient + CalDavAccountConfig) that surfaces network/HTTP
failures as honest errors, and make MockCalendarBackend implement the trait.
wiremock integration tests cover the REPORT path and a transport failure.
Add save/list/get calendar_events CRUD to the store. Enable recursive_triggers
so INSERT OR REPLACE fires the FTS delete trigger, keeping events_fts in sync on
upsert; verified this only strengthens the WORM-audit guards and never
double-indexes the encrypted message FTS.
Refs #178
- Enforce a rule's account scope in evaluate, evaluate_with_timeout, and
preview via matches_account; previously a rule created ON ACCOUNT one account
would fire on any account whose message matched the WHERE clause.
- Return true byte offsets from find_keyword_outside_quotes (char_indices)
instead of a char count, so the WHERE/ACTION and ON ACCOUNT splits no longer
corrupt or panic on non-ASCII text preceding a keyword.
- Add FilterOperator::NotIn evaluated as the complement of IN. Negated IN lists
previously mapped to NotEquals over a List value, which no eval arm handled,
so NOT IN was silently always false.
Refs #177
Emit a FileDescriptorSet golden (proto/nuncio/v1/descriptor.bin) at build time
and assert the freshly-compiled descriptor matches it byte-for-byte, so any
breaking change to the nuncio.v1 wire contract fails the build until the golden
is deliberately regenerated. Source info is stripped so pure doc-comment edits
do not perturb the golden.
Add proto/README.md documenting Swift/C#/TypeScript codegen from the proto,
the loopback bearer-token auth model, the System.Subscribe push stream, and the
v1 versioning policy (additive-only within v1; breaking changes go to nuncio.v2).
Refs #174
Update CLAUDE.md, ARCHITECTURE.md, and the RustRover guide: the daemon serves
only the gRPC API on 127.0.0.1:9420; the old JSON-RPC IPC (:9422) is removed. Also
corrected the stale 'shells still in the workspace' note (they moved to _reference).
Delete nuncio-core::ipc (IpcDaemonServer, IpcClient, framing, protocol -- the
demux-race transport), the daemon's :9422 boot + JSON-RPC handler, the stubbed
'nuncio-cli daemon' subcommand, and the IPC-transport test (gRPC coverage
already exists). The daemon now awaits nunciod::grpc::serve as its foreground
task; DB/keyring/sync/outbox/update remain background tasks.
Refs #173
Earlier feature commits staged only crates/, leaving the root Cargo.lock behind
when nunciod gained bytes + a sqlx dev-dependency. Re-sync so a --locked build
matches the committed manifests.
Reword ~190 source comments and user-facing strings across crates/ to explain
the code's intent/constraints instead of citing story numbers, GH issues, or
roadmap phases (that traceability lives in git history + docs). Kept RFC and
technical rationale; changed no logic. Enforces the CLAUDE.md comment convention.
One-click 'Cargo Clean' RustRover config and a 'clean-all' cargo alias
(= cargo clean, whole workspace). Document in CLAUDE.md that source comments
explain intent/constraints only -- no backlog-story/GH-issue/phase references
(those live in git history + docs).
The plain-text (non --json) listing printed only 'N account(s) registered'; it
now lists each account's id, name, email, IMAP/SMTP endpoints, and TLS. The
--json output already carried the fields. Test asserts the details appear in
plain output.
Add authenticated Export (ExportMailbox -> mbox/eml-zip/json/jsonl written to a
local file, backed by the real ExportEngine + a scoped message query) and Audit
(ListRecords, VerifyChain over the WORM ledger with a structured report) gRPC
services, each mounted with the bearer interceptor like the others. Wire CLI
'mail export' and 'system audit list/verify'. Audit records expose the record
MAC, never the signing key. Offline tests cover export content/counts, chain
verify (intact + tampered), and auth rejection.
Refs #172
Add docs/DEVELOPING-IN-RUSTROVER.md (setup, running the daemon + CLI, running and
debugging tests, quality gates, the dev loop, gotchas) and link it from CLAUDE.md.
Add one-click run configs: Run nuncio-cli (mail sync / account list / mail list).
Remove buildTarget=REMOTE from all shared run configs. That flag (inherited from
the original Antigravity setup) told RustRover to build/run on the remote WSL
Ubuntu toolchain instead of native Windows -- wrong for dogfooding, and it also
breaks the daemon (no Secret Service keyring in a headless WSL). Configs now
default to the local machine.
Add run configs for the daemon (Run nunciod), the release build
(Cargo Build Release = cargo build-release), and Cargo Verify; fix Run nuncio-cli
to the current 'system status' command (was the stale '-- status'). Document the
IDE setup in CLAUDE.md (shared run configs, rustfmt-on-save, reload-cargo-project,
pinned toolchain, clippy-as-external-linter) and refresh the now-stale gate-status
note. The .idea shared/local .gitignore split, rustfmt-on-save code style, and
Cargo project registration were already correct.
docs/RUNNING.md: verified build/run/use steps for the daemon + CLI (real gRPC +
OS keyring confirmed working). Add a 'cargo build-release' alias for the two
runnable binaries.
nunciod opens ~/.nuncio/<db>, but SQLite create_if_missing creates only the
file, not its parent directory -- so on a fresh machine (no ~/.nuncio) the
daemon failed to start with SQLITE_CANTOPEN (code 14). connect_file now
create_dir_all's the parent first. Found via dogfooding (first real daemon run);
adds a regression test.
Delete the orphaned wiki/ dir (2nd copy of fabricated docs), dead top-level
tests/ (not compiled by the virtual workspace; stale signatures), excalidraw.log,
the fabricated GUI hero screenshot + unreferenced old-architecture SVGs
(c4_context, topology), stale RustRover run-configs for the moved GUI/TUI crates,
and committed Tauri-generated schemas under _reference/. Add .gitignore guards
(agent scratch, node_modules, dist, _reference gen) and align the local 'cargo
cov' alias to the de-gamed informational coverage (matches CI). Kept app_icon +
the nsql_pipeline/ipc_sequence SVGs still referenced by docs.
Add an authenticated Filters service backed by the NSQL engine + store:
CreateRule/DeleteRule persist and hot-reload the live FilterEngine (ArcSwap);
ValidateRule returns an honest valid/error verdict without failing the RPC;
PreviewRule dry-runs a candidate (unsaved) rule against stored mail. Mounted
with the bearer interceptor like the other services (auth-rejection tested).
Wire CLI filter create/list/delete/validate/test to it; the shared filter_engine
instance is reused by both the gRPC path and the legacy filter.* handler during
the transition. filter edit/export/import/logs stay local for now (tracked).
Refs #171
Add MailEngineOverrides so tests inject MockMailBackend/MockMessageSender into a
live daemon without production depending on mocks (default = real engines). Add
an authenticated Mail.Sync RPC (awaits completion) and wire CLI 'mail sync' to
it, off the ephemeral runner. New spine_e2e_test drives the whole spine over
gRPC offline: AddAccount -> Sync -> ListMessages/GetMessage -> MarkRead (with
MessageFlagsChanged on Subscribe) -> SendMessage (captured by the mock sender).
Refs #161
Add the outbound path: authenticated Mail.SendMessage builds an SMTP transport
from the account's SMTP endpoint + keyring password and sends via
SmtpTransportEngine -- honest success/failure, never fabricated. New
MessageSender trait (production SmtpTransportEngine; MockMessageSender for tests)
mirrors the sync MailBackend seam. #168: AccountConfig gains smtp_host/smtp_port
via a backfill-safe accounts migration (old rows fall back to the IMAP
host/port); proto + CLI now persist the SMTP endpoint instead of dropping it.
CLI 'mail send' is a real gRPC client; the fabricated 'Message sent' output is
gone. Tests prove the exact message reaches a mock transport and failures
surface honestly (no live network).
Refs #160#168
Add an authenticated Mail service (ListFolders, ListMessages, GetMessage,
MarkRead, SearchMessages), mounted with the bearer interceptor like
System/Accounts. Backed by DatabaseEngine read methods + a new set_message_read;
GetMessage returns the decrypted body; MarkRead persists then emits
MessageFlagsChanged (so it streams via Subscribe). Wire nuncio-cli
mail list/read/mark/search + folder list off the ephemeral runner onto the
daemon over gRPC, with honest errors when the daemon is unreachable. Offline
E2E round-trips all RPCs and proves mark-read persistence + streaming.
Refs #159
Add nunciod::sync: sync_with_backend(&dyn MailBackend) fetches folders +
messages and persists each via save_email, wrapping the status lifecycle so a
sync ALWAYS completes (Idle + SyncCompleted) and can never stick at Syncing;
failures surface as CoreEvent::Error. Production builds a real ImapEngine from
account config + the keyring password. The daemon now claims the EventBus
command receiver and runs real syncs on SyncAll/SyncAccount (previously those
commands -- including post-recovery resync -- were silently inert). Remove the
outbox worker's 'Simulate remote IMAP/JMAP mutation execution' fake; it no
longer fabricates 'completed' (outbound send is 1.C.5). Tests inject
MockMailBackend and assert persistence + the SyncStarted/SyncCompleted/Error
sequence, offline.
Refs #158
The three release workflows triggered on push to dev/rc/main (+ nightly crons),
so every push spawned release/desktop/nightly builds that failed: they build the
presentation GUI now archived under _reference/, and the release policy forbids
public releases until V3. Restrict release.yml and release-desktop.yml to v*
tags + manual dispatch; make release-nightly.yml manual-only.
Refs #140
CI installed the latest stable (dtolnay/rust-toolchain@stable), whose
rustfmt/clippy diverged from the local 1.97.1 toolchain -- so commits passed the
local pre-commit hook yet failed CI's fmt/clippy. Pin via rust-toolchain.toml so
local, the hook, and CI share one compiler. rustup auto-installs it + rustfmt +
clippy on first use.
Refs #142
Add an authenticated Accounts gRPC service (AddAccount/ListAccounts), mounted
with the same bearer interceptor as System (every service authenticated). The
daemon now uses a persistent DB (~/.nuncio/nuncio.db; was ephemeral temp_dir)
and stores account passwords ONLY in the OS keyring -- never in SQLite, logs,
Debug/Serialize output, or the ListAccounts response (CLI PasswordArg redacts;
rpassword prompt, no echo). Wire nuncio-cli account add/list to the daemon over
gRPC. Tests prove persistence across restart and credential secrecy.
Security-reviewed, APPROVED.
Refs #156#157
Data-loss fix (1.B.4): open_with_backup_dir no longer coerces transient errors
(SQLITE_BUSY/LOCKED/IOERR/CANTOPEN, pool timeouts) into 'corrupt' -- it now
propagates them without salvaging or deleting the live DB. is_sqlite_corruption_error
is a strict allowlist of SQLITE_CORRUPT/NOTADB (primary code & 0xff) plus a
deterministic raw-header-magic pre-flight check. Salvage SQL now matches the real
filter_rules/conditions/actions schema, so rules are actually restored (1.B.3).
De-flake #155: root cause was a deferred WAL checkpoint healing the test's header
corruption ~10-50ms after close; close() now runs wal_checkpoint(TRUNCATE) before
pool close. Test re-enabled and stable (130 runs, 0 failures).
Refs #154#155
The FTS index was built over the encrypted body column, so body search never
matched real terms; it was also created lazily with no backfill. Now migrate()
creates the FTS tables eagerly and save_email writes plaintext subject/sender/
body into a standalone messages_fts table in the same transaction (stored
columns stay AES-256-GCM ciphertext). migrate() backfills rows missing from the
index by decrypting with the engine key. Tests exercise the real save_email
path (no raw-SQL bypass) and backfill. Documented tradeoff: the FTS index holds
plaintext-derived trigrams, so body text is recoverable from the index while
search is enabled -- encrypted index / whole-DB encryption is future work.
Refs #152#153
Add System.Subscribe server-streaming RPC on the authenticated System service,
mapping every CoreEvent variant to a nuncio.v1.Event oneof. The daemon streams
live EventBus broadcasts (skip-on-lag with a warning; clean close when the bus
drops). Add a client subscribe helper in nuncio-proto. Regression test proves
20 concurrent unary GetStatus calls and 20 streamed events arrive uncorrupted
and in order -- the property the old hand-rolled JSON-RPC IPC demux lacked.
Refs #151
Move the gRPC address resolver and add an authenticated client helper
(connect_system + BearerTokenInterceptor) into the shared nuncio-proto crate so
client shells never depend on the nunciod binary. Wire nuncio-cli's system
status to query the live daemon over gRPC (token from an injectable OS keyring),
printing real status or an honest error when unreachable. Refactor nunciod to
reuse the shared addr items. Reconcile tests to assert the real gRPC round-trip
instead of local ephemeral state.
Refs #150
nunciod now serves the nuncio.v1.System gRPC service on loopback (default
127.0.0.1:9420, env NUNCIO_GRPC_ADDR), authenticated by a bearer token minted
into the OS keyring (SecretManager, CSPRNG, fail-closed). A tonic interceptor
requires 'authorization: Bearer <token>' and compares in constant time
(subtle::ConstantTimeEq); every missing/malformed/wrong-token path returns
Unauthenticated. GetStatus returns live engine status + version. Existing
JSON-RPC IPC (9422) unchanged. Security-reviewed, APPROVED.
Refs #149
New crate crates/nuncio-proto houses the versioned protobuf contract
(proto/nuncio/v1) and tonic-generated client+server stubs, starting with a
minimal System.GetStatus RPC. protoc is vendored via protoc-bin-vendored
(prebuilt binaries; no system protoc, cmake, or C++ toolchain) for reproducible
CI on Linux/macOS/Windows. Uses the tonic 0.14 split (tonic + tonic-prost
runtime, tonic-prost-build codegen). Generated code exempted from workspace
lints; hand-written code keeps them.
Refs #148
Move nuncio-tui, nuncio-gui (+src-tauri, ui), and nuncio-mcp out of the Cargo
workspace into _reference/ (git renames; history preserved), retained for the
Phase 5 native client-repo extraction. Remove the unused nuncio-mcp dev-dep
from nunciod and rework daemon_e2e_test to drive the real nuncio-cli
HeadlessRunner (more coverage, no archived-shell dependency). Workspace is now
engine + daemon + reference CLI (8 members); build/clippy/tests green.
Refs #145
Catalogue of 40 candidate RPCs across 8 services (25 real / 10 partial / 5 stub)
with reality tags, parity gaps, and open Phase 2 decisions. Input to the
proto/nuncio/v1 contract; sync RPCs to be drafted from Phase 1's real spine,
not today's status-flag stubs.
Refs #144
Clippy job now runs 'cargo check-all' (--all-targets), matching the local gate
so CI can no longer pass while the local gate fails. Coverage is now
informational (--summary-only): the former --fail-under-lines 100 excluded all
main.rs and rewarded stub+matching-test theater; a realistic threshold plus a
required mock-server E2E gate lands with Phase 1.C.6.
Refs #142#143
Gate the nunciod 24h auto-update-check loop behind NUNCIO_AUTO_UPDATE_ENABLED
(defaults off), so the daemon never autonomously checks/installs updates. Make
update.apply RPC return an explicit 'disabled pending Phase 4' error instead of
performing the fail-open install; update.check (read-only) is preserved.
UpdateEngine (update.rs) untouched for the Phase 4 checksum fix.
Refs #140
Remove the hardcoded DEFAULT_STORAGE_KEY, DEFAULT_WORM_KEY, and ledger secret
literals. Add an OsKeyring-backed SecretManager::production() with fail-closed,
CSPRNG-generated, per-key vault provisioning (get_or_create_key_bytes). Thread
storage/WORM/ledger keys through DatabaseEngine, recovery salvage, and the
daemon orchestrator; remove the per-call secret_key parameters on WORM/ledger
signing and verification (closing that bug class). Tests use MockKeyring only;
added cross-key-rejection tests. Security-reviewed and approved.
Refs #146#147
Adopt the previously-uncommitted working-tree edits (audited state) as the
baseline. Fix real clippy lints properly (manual_strip -> strip_prefix,
let-else for infallible hardcoded regex/HMAC constructors, type aliases for
complex tuples); convert user-reachable CLI/daemon unwrap/expect to graceful
errors; allow unwrap/expect/panic in test code per project convention.
Quarantine one flaky recovery test (corruption-detection/close race) with an
explicit #[ignore] + tracking (GH #155, Phase 1.B). fmt, clippy (-D warnings,
all targets), and the full suite pass.
Remove fabricated PLAN-*/EXECUTIVE-*/TODO/TICKET/spec docs; add authoritative
ROADMAP, ADR-0001 (engine-first gRPC), and Phase 0/1 BACKLOG; rewrite README,
CLAUDE.md, ARCHITECTURE, and wiki Home to honest reality; add status banners to
the NSQL and self-healing specs; make wiki-sync mirror-with-delete.