openspec: contacts-caldav-server — двусторонний синк (правки с телефона → в базу)

This commit is contained in:
2026-09-13 16:07:27 +00:00
parent de07fae246
commit 17252ebfa9
4 changed files with 120 additions and 36 deletions
@@ -99,10 +99,62 @@ PUT, чтобы не затирать изменения, сделанные к
**WHEN** проверяется содержимое /opt/hermes/email/contacts/contacts.json
**THEN** файл существует и содержит актуальную базу контактов.
### Requirement: REQ-CON-CARDDAV-008: Изменение карточки на телефоне
**MUST** — если vCard на сервере изменена клиентом (DAVx5) после последнего
синка (ETag изменился), синк должен обновить соответствующий контакт в
`contacts.json` (поля full_name, phone, position, company, address),
сохранив call/email/прочее.
#### Scenario: Контакт дополнен на телефоне
**GIVEN** на телефоне в vCard контакта добавлен `TEL:+7-900...`
**WHEN** выполняется синк
**THEN** контакт в contacts.json содержит этот телефон.
### Requirement: REQ-CON-CARDDAV-009: Новая карточка на сервере
**MUST** — если на сервере появилась новая vCard, не имеющая соответствия в
`contacts.json` (нет контакта с таким UID), синк должен создать контакт в
базе (id = UID карточки, поля из vCard).
#### Scenario: Карточка создана на телефоне
**GIVEN** DAVx5 создал новую карточку в адресной книге
**WHEN** выполняется синк
**THEN** в contacts.json появляется соответствующий контакт.
### Requirement: REQ-CON-CARDDAV-010: Удаление карточки на сервере
**MUST** — если vCard удалена с сервера клиентом (известный UID, карточки
больше нет), синк должен пометить контакт в базе как удалённый
(`deleted: true`), а не физически удалять (сохранение данных).
#### Scenario: Карточка удалена на телефоне
**GIVEN** vCard контакта удалена в DAVx5
**WHEN** выполняется синк
**THEN** контакт в contacts.json имеет `deleted: true`.
### Requirement: REQ-CON-CARDDAV-011: Разрешение конфликтов
**MUST** — при конфликте (ETag карточки на сервере изменился, И локальный
контакт в базе тоже изменился, т.е. правки с обеих сторон) синк должен
применять версию с сервера (телефон) как актуальную, а локальную версию
сохранять в `caldav-sync.log` (поле `conflict_local`) — данные не теряются.
#### Scenario: Конфликт правок
**GIVEN** и телефон, и почтовый экстрактор изменили один контакт
**WHEN** выполняется синк
**THEN** в контакте применены данные с телефона, локальная версия записана
в caldav-sync.log, следующий запуск не повторяет конфликт.
## Non-Goals
- Двусторонняя синхронизация (правки на телефоне не пишутся обратно в
contacts.json).
- Глубокая биография/полная модель контакта — синк обновляет стандартные
поля vCard (FN, EMAIL, TEL, ORG, TITLE, ADR); произвольные расширения
vCard (X-*, категории, фото) не переносятся в contacts.json.
- Миграция существующих .vcf из локальных файлов (contacts.vcf остаётся
как есть, синк идёт из contacts.json).
- Синхронизация с внешними CardDAV/Google/Cloud — только локальный Radicale.