feat: production-wire Calendar sync via first-class CalDAV accounts #272

Merged
KofTwentyTwo merged 7 commits from story/m6.5-caldav-account into dev 2026-07-31 17:41:25 +00:00
KofTwentyTwo commented 2026-07-31 17:39:19 +00:00 (Migrated from github.com)

Closes the M6.5 gap where Calendar/Sync only worked through a test-injected
CalendarEngineOverrides::calendar_backend. Implements the owner-chosen model:
a CalDAV account is a first-class account entry (protocol CALDAV +
collection URL + its own keyring credential), decoupled from mail accounts.

Refs #254 #178

Layers

  • proto — ACCOUNT_PROTOCOL_CALDAV = 3 and a generic
    AccountConfig.collection_url (field 14, a full DAV collection URL). Additive
    within nuncio.v1; descriptor golden regenerated and contract_stability
    passes.
  • core — AccountProtocol::CalDav + collection_url + is_dav().
    Validation is protocol-aware: a DAV account requires a non-empty http(s)
    collection_url and skips the IMAP/SMTP endpoint checks; mail accounts
    unchanged.
  • store — nullable collection_url column via a backfill-safe
    ensure_accounts_dav_columns migration (mirrors the SMTP/TLS-mode backfills);
    round-trips through save/list/get; salvage preserves it.
  • daemon — AddAccount validates CalDAV accounts (http(s) URL + password).
    Calendar/Sync production path (no injected override) resolves configured
    CalDAV accounts (filtered by protocol, honoring a targeted account_id),
    reads each keyring credential, builds a real nuncio_cal::CalDavClient
    from the persisted collection_url, and syncs via
    calendar_sync::sync_caldav_account → sync_with_backend. The honest
    FailedPrecondition error now fires only when no CalDAV account is
    configured — never fabricated data. Mail sync/outbox skip DAV accounts. The
    CalendarEngineOverrides seam is retained for tests.
  • cli — account add --protocol caldav --collection-url <url> (prompts for
    the password, rejects a missing URL before dialing); account list renders
    CalDAV accounts with their collection URL.
  • e2e — caldav_production_e2e_test.rs configures a CalDAV account over the
    real Accounts API and drives Calendar/Sync with no injected backend,
    so the daemon builds a real CalDavClient against a WireMock CalDAV
    server and persists fetched events (visible via ListEvents). Also asserts
    the honest error for an unconfigured account and that Calendar stays behind
    its BearerAuthInterceptor. Fully offline (WireMock + mock keyring).

collection_url is intentionally generic (a DAV collection URL, not
calendar-specific) so #255 can reuse it for ACCOUNT_PROTOCOL_CARDDAV = 4.

Gate (green)

  • cargo fmt --all -- --check — clean
  • cargo check-all (clippy --all-targets --workspace -D warnings) — 0 warnings
  • cargo test-all — 473 passed, 0 failed, 0 ignored

🤖 Generated with Claude Code

Closes the M6.5 gap where `Calendar/Sync` only worked through a test-injected `CalendarEngineOverrides::calendar_backend`. Implements the owner-chosen model: a CalDAV account is a **first-class account entry** (protocol `CALDAV` + collection URL + its own keyring credential), decoupled from mail accounts. Refs #254 #178 ## Layers - **proto** — `ACCOUNT_PROTOCOL_CALDAV = 3` and a generic `AccountConfig.collection_url` (field 14, a full DAV collection URL). Additive within `nuncio.v1`; **descriptor golden regenerated** and `contract_stability` passes. - **core** — `AccountProtocol::CalDav` + `collection_url` + `is_dav()`. Validation is protocol-aware: a DAV account requires a non-empty http(s) `collection_url` and skips the IMAP/SMTP endpoint checks; mail accounts unchanged. - **store** — nullable `collection_url` column via a backfill-safe `ensure_accounts_dav_columns` migration (mirrors the SMTP/TLS-mode backfills); round-trips through save/list/get; salvage preserves it. - **daemon** — `AddAccount` validates CalDAV accounts (http(s) URL + password). `Calendar/Sync` **production path (no injected override)** resolves configured CalDAV accounts (filtered by protocol, honoring a targeted `account_id`), reads each keyring credential, builds a **real `nuncio_cal::CalDavClient`** from the persisted `collection_url`, and syncs via `calendar_sync::sync_caldav_account` → `sync_with_backend`. The honest `FailedPrecondition` error now fires **only** when no CalDAV account is configured — never fabricated data. Mail sync/outbox skip DAV accounts. The `CalendarEngineOverrides` seam is retained for tests. - **cli** — `account add --protocol caldav --collection-url <url>` (prompts for the password, rejects a missing URL before dialing); `account list` renders CalDAV accounts with their collection URL. - **e2e** — `caldav_production_e2e_test.rs` configures a CalDAV account over the real `Accounts` API and drives `Calendar/Sync` with **no injected backend**, so the daemon builds a real `CalDavClient` against a **WireMock** CalDAV server and persists fetched events (visible via `ListEvents`). Also asserts the honest error for an unconfigured account and that Calendar stays behind its `BearerAuthInterceptor`. Fully offline (WireMock + mock keyring). `collection_url` is intentionally **generic** (a DAV collection URL, not calendar-specific) so #255 can reuse it for `ACCOUNT_PROTOCOL_CARDDAV = 4`. ## Gate (green) - `cargo fmt --all -- --check` — clean - `cargo check-all` (clippy `--all-targets --workspace -D warnings`) — 0 warnings - `cargo test-all` — **473 passed, 0 failed, 0 ignored** 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.