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.
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/Syncproduction 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.
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)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes the M6.5 gap where
Calendar/Synconly worked through a test-injectedCalendarEngineOverrides::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
ACCOUNT_PROTOCOL_CALDAV = 3and a genericAccountConfig.collection_url(field 14, a full DAV collection URL). Additivewithin
nuncio.v1; descriptor golden regenerated andcontract_stabilitypasses.
AccountProtocol::CalDav+collection_url+is_dav().Validation is protocol-aware: a DAV account requires a non-empty http(s)
collection_urland skips the IMAP/SMTP endpoint checks; mail accountsunchanged.
collection_urlcolumn via a backfill-safeensure_accounts_dav_columnsmigration (mirrors the SMTP/TLS-mode backfills);round-trips through save/list/get; salvage preserves it.
AddAccountvalidates CalDAV accounts (http(s) URL + password).Calendar/Syncproduction path (no injected override) resolves configuredCalDAV accounts (filtered by protocol, honoring a targeted
account_id),reads each keyring credential, builds a real
nuncio_cal::CalDavClientfrom the persisted
collection_url, and syncs viacalendar_sync::sync_caldav_account→sync_with_backend. The honestFailedPreconditionerror now fires only when no CalDAV account isconfigured — never fabricated data. Mail sync/outbox skip DAV accounts. The
CalendarEngineOverridesseam is retained for tests.account add --protocol caldav --collection-url <url>(prompts forthe password, rejects a missing URL before dialing);
account listrendersCalDAV accounts with their collection URL.
caldav_production_e2e_test.rsconfigures a CalDAV account over thereal
AccountsAPI and drivesCalendar/Syncwith no injected backend,so the daemon builds a real
CalDavClientagainst a WireMock CalDAVserver and persists fetched events (visible via
ListEvents). Also assertsthe honest error for an unconfigured account and that Calendar stays behind
its
BearerAuthInterceptor. Fully offline (WireMock + mock keyring).collection_urlis intentionally generic (a DAV collection URL, notcalendar-specific) so #255 can reuse it for
ACCOUNT_PROTOCOL_CARDDAV = 4.Gate (green)
cargo fmt --all -- --check— cleancargo check-all(clippy--all-targets --workspace -D warnings) — 0 warningscargo test-all— 473 passed, 0 failed, 0 ignored🤖 Generated with Claude Code