feat(contacts): drive contacts sync from stored CardDAV accounts #431

Merged
KofTwentyTwo merged 1 commits from story/wire-contacts-sync into dev 2026-08-15 02:48:47 +00:00
1 Commits
Author SHA1 Message Date
james.maes e444ffd0c0 feat(contacts): drive contacts sync from stored CardDAV accounts
Contacts sync had a real, wiremock-tested CardDAV client that production
could never reach: `AccountConfig` had no way to express a CardDAV
address book, so `Contacts/Sync` always fell to its honest-error branch
unless a test double was injected.

Add CardDAV as a first-class account protocol, mirroring how CalDAV is
already resolved and driven:

- `AccountProtocol::CardDav` and `Transport::CardDav(DavTransport)`, plus
  `is_caldav()`/`is_carddav()` so callers pick a specific DAV flavour
  rather than the ambiguous `is_dav()`. Calendar sync now filters on
  `is_caldav()` so a CardDAV account can never be mistaken for a calendar.
- Persistence reuses the existing `collection_url` column, distinguished
  by the `protocol` discriminator; no schema change.
- Proto gains `ACCOUNT_PROTOCOL_CARDDAV` and a `carddav` transport case
  (both additive), with the committed descriptor golden regenerated.
- `contacts_sync::sync_carddav_account` builds a real `CardDavClient` from
  the persisted collection URL and a credential read just-in-time from the
  OS keyring, zeroized on drop.
- `Contacts/Sync` resolves configured CardDAV accounts and returns a typed
  `NotConfigured` (FAILED_PRECONDITION) when none matches -- including
  when the named account exists but speaks another protocol -- instead of
  an untyped internal error or a fabricated zero count.
- `account add --protocol carddav --collection-url ...` in the CLI.

Offline E2E proves the production path: a CardDAV account added over the
real Accounts API, synced through a real client against wiremock, with
credentials resolved from a mocked vault.

Refs #255
2026-08-14 21:41:15 -05:00