chore(openspec): предложить phone-album-two-way-sync и обновить спеку phone-album-sync

Новое изменение: двусторонний миррор альбома устройства и серверного альбома
(материализация серверных фото, признак происхождения, двусторонние удаления с
явным намерением, устойчивое выключение, первый проход, n:1). Purpose основной
спеки phone-album-sync обновлён под двустороннюю синхронизацию.

Change проходит `openspec validate --strict`.
This commit is contained in:
iu
2026-10-16 23:19:08 +03:00
parent cd326c0ae6
commit d17f62f676
7 changed files with 643 additions and 3 deletions
@@ -0,0 +1,59 @@
# Proposal
## Why
Синхронизация альбома устройства сегодня односторонняя по содержимому: устройство отдаёт
байты, сервер возвращает только метаданные. Фото, добавленное в целевой серверный альбом из
веба или с другого устройства, на телефон не приезжает, а связанный серверный альбом ещё и
скрыт из списка — увидеть его в приложении нельзя. Пользователь ожидает, что
синхронизированный альбом — один и тот же набор медиа на телефоне и на сервере.
## What Changes
- Связанная пара «альбом устройства → серверный альбом» становится **двусторонним миррором**:
состав медиа совпадает, добавление и удаление ходят в обе стороны.
- **Материализация серверных фото на устройстве**: в режиме копий (B) скачивается оригинал, в
режиме переноса (C) — превью (для видео — постер). Файлы пишутся в галерею (MediaStore)
бакета альбома устройства и видны системной галерее.
- Вводится явный **признак происхождения** элемента (`own` — загружено этим клиентом,
`mirror` — скачано с сервера). Политика удалений, разгрузка и восстановление после
переустановки опираются на него, а не на наличие `serverPhotoId`.
- **Удаление в обе стороны**: удаление элемента на устройстве удаляет соответствующее фото на
сервере (мягко, в корзину); серверное удаление убирает локальный элемент. Удаление
принадлежащего иному источнику элемента на устройстве подчиняется правам сервера.
- **Журнал удалений (надгробия)**: локально удалённый элемент не скачивается заново, пока
серверное удаление не подтверждено; серверные события не дают эха.
- Восстановление серверного фото из корзины заново материализует элемент на устройстве.
- **Отвязка и выключение синхронизации** остаются неразрушающими: фото на сервере сохраняются,
уже скачанные файлы остаются в галерее как обычные.
- **Выключение синхронизации устойчиво**: восстановление происхождения по меткам превью не
возобновляет связь, пока пользователь не включит синхронизацию снова.
- **Первый проход**: выбор «скачать всё содержимое альбома» или «только новое с этого момента»;
ограничение «только Wi-Fi» распространяется и на скачивание.
- **BREAKING**: нет — поведение аддитивное, существующие связи продолжают работать.
## Capabilities
### New Capabilities
Нет — поведение относится к существующей способности синхронизации альбома устройства.
### Modified Capabilities
- `phone-album-sync`: двусторонний миррор — материализация серверных фото по режимам, признак
происхождения, надгробия, политика удалений в обе стороны, восстановление из корзины,
поведение при отвязке.
- `android-client`: сетка альбома устройства включает скачанные (серверные) элементы, а
связанный альбом читается как один двусторонний альбом.
## Impact
- `android/`: движок синхронизации (`data/sync/*` — `SyncPlan`, `SyncCoordinator`, `SyncCheck`,
`SyncRecovery`, `ProvenanceStamp`, `OffloadManager`, `RestoreManager`, `LocalAssetProvider`),
новый загрузчик серверных фото, запись в MediaStore без диалога согласия (файлы приложения),
Room-схема (признак происхождения, надгробия, watermark первого прохода), UI
(`DeviceAlbumScreen`/`DeviceAlbumViewModel`, `AlbumsScreen`/`AlbumListBuilder`,
`SyncRepository`).
- `backend/`: изменений API не требуется — `preview_url`/`medium_url`/`original_url`, `?since=`,
мягкое удаление и восстановление из корзины уже есть.
- Спеки: `phone-album-sync`, `android-client`.