Files
email-assistant/openspec/changes/contacts-caldav-server/specs/contacts/carddav-sync/spec.md
T

108 lines
6.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# contacts/carddav-sync Specification
## Purpose
Односторонняя синхронизация контактов, извлечённых из подписей писем
(pipeline contacts_extractor), в CardDAV-сервер Radicale (:5232), чтобы
контакты были доступны клиентам (Android/DAVx5, десктопные клиенты).
## ADDED Requirements
### Requirement: REQ-CON-CARDDAV-001: Адресная книга в Radicale
**MUST** — в Radicale должна существовать адресная книга пользователя
`estorozhenko` (коллекция `Контакты`, доступ по CardDAV
`/estorozhenko/Контакты/`), созданная до начала синка.
#### Scenario: Проверка адресной книги
**GIVEN** Radicale запущен на :5232
**WHEN** выполняется `curl -X PROPFIND -u estorozhenko:<pass>
http://127.0.0.1:5232/estorozhenko/Контакты/`
**THEN** возвращается HTTP 207 (Multi-Status) и в ответе есть
`<D:resourcetype>` с `C:addressbook`.
### Requirement: REQ-CON-CARDDAV-002: vCard для каждого контакта
**MUST** — для каждого контакта из `contacts.json` (поле `contacts`),
имеющего поле `email`, в адресной книге лежит ровно одна vCard-карточка
(.vcf), идентифицируемая по UID, с полями: FN, EMAIL, TEL (если есть),
ORG (если есть), TITLE (если есть), ADR (если есть).
#### Scenario: Карточка создана
**GIVEN** в contacts.json есть контакт `{email: "a@b.ru", full_name: "Иван"}`
**WHEN** выполняется `curl -X PROPFIND .../Контакты/...a@b.ru.vcf`
**THEN** возвращается 200/207 и vCard содержит `FN:Иван`, `EMAIL:a@b.ru`.
### Requirement: REQ-CON-CARDDAV-003: Повторная синхронизация идемпотентна
**MUST** — повторный запуск синка с теми же данными не создаёт дублей
(vCard уже существует → только обновление по ETag, не новый ресурс).
#### Scenario: Двойной запуск
**GIVEN** синк выполнен один раз
**WHEN** синк выполняется второй раз без изменений данных
**THEN** количество ресурсов в адресной книге не изменяется.
### Requirement: REQ-CON-CARDDAV-004: Обновление существующей карточки
**MUST** — при изменении данных контакта в contacts.json (например,
у контакта появился телефон) карточка в Radicale обновляется (PUT с If-Match
по ETag). Если клиент на телефоне уже изменил карточку (ETag не совпал) —
изменения локальной базы не перезаписывают карточку молча; синк пропускает
обновление и логирует конфликт (см. также REQ-CON-CARDDAV-006).
#### Scenario: Контакт обновлён локально
**GIVEN** у контакта в базе появился `phone`
**WHEN** выполняется синк
**THEN** карточка в Radicale содержит новый `TEL`.
### Requirement: REQ-CON-CARDDAV-005: Удаление карточек
**MUST** — контакты, которых больше нет в `contacts.json` (поле `contacts`),
могут удаляться из адресной книги (DELETE). Удаление не выполняется по
умолчанию (флаг `--prune-caldav`); по умолчанию карточки без соответствующего
контакта остаются.
#### Scenario: Удаление по флагу
**GIVEN** контакт удалён из contacts.json
**WHEN** синк запущен с `--prune-caldav`
**THEN** соответствующая vCard удаляется из адресной книги.
### Requirement: REQ-CON-CARDDAV-006: Неконфликтная работа с клиентами
**MUST** — синк должен использовать ETag (If-Match/If-None-Match) при
PUT, чтобы не затирать изменения, сделанные клиентами на телефоне между
запусками синка. При конфликте ETag — пропустить и записать предупреждение
в лог (файл лога синка).
#### Scenario: Конфликт ETag
**GIVEN** клиент (DAVx5) изменил vCard на сервере после последнего синка
**WHEN** выполняется синк с изменёнными локальными данными этого контакта
**THEN** PUT возвращает 412, синк логирует конфликт и продолжает остальные
карточки.
### Requirement: REQ-CON-CARDDAV-007: Локальная база остаётся источником
**MUST** — contacts.json, contacts.vcf, index.json продолжают обновляться
как раньше (источник истины для дедупликации и трекинга). CardDAV — цель
синка, не замена локальной базе.
#### Scenario: Локальная база не затронута
**GIVEN** синк выполнен
**WHEN** проверяется содержимое /opt/hermes/email/contacts/contacts.json
**THEN** файл существует и содержит актуальную базу контактов.
## Non-Goals
- Двусторонняя синхронизация (правки на телефоне не пишутся обратно в
contacts.json).
- Миграция существующих .vcf из локальных файлов (contacts.vcf остаётся
как есть, синк идёт из contacts.json).
- Синхронизация с внешними CardDAV/Google/Cloud — только локальный Radicale.