chore(openspec): архивировать fix-album-sync-churn-and-degradation и синхронизировать спеки
В спеку phone-album-sync добавлены требования о роли локального файла, подтверждении новой версии до удаления устаревшей и разгрузке без незавершённой загрузки; уточнены синхронизация изменений, провенанс и возврат оригиналов.
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
# Proposal
|
||||
|
||||
## Why
|
||||
|
||||
Режим переноса (C) может залить на сервер **превью вместо оригинала** и тут же удалить
|
||||
устаревшую серверную копию, то есть молча уничтожить оригинал. Причина — планировщик не
|
||||
отличает содержимое от роли локального файла: когда состояние элемента в локальном индексе
|
||||
расходится с фактической ролью (в индексе «оригинал», на диске превью), смена роли читается
|
||||
как «содержимое изменилось» и запускается цикл «удалил → залил → удалил».
|
||||
|
||||
Реальный инцидент: альбом устройства U2, режим переноса, серверный альбом `N2`. Из 20 фото
|
||||
пять перезаливались по кругу (20:03 → 20:08 → 20:42 → 21:24), а два потеряли оригиналы —
|
||||
`IMG_20261004_163151.jpg` (3 437 694 Б, 3072x4096 → 259 282 Б, 1200x1600) и
|
||||
`IMG_20261009_153136.jpg` (2 226 043 Б → 192 651 Б). Лечиться это будет только сейчас:
|
||||
у первого уцелевший оригинал лежит в корзине сервера, у второго — в корзине и в альбоме
|
||||
«Первый». Побочно тот же цикл оставляет на телефоне элементы, которых на сервере уже нет:
|
||||
в приложении альбом `N2` показывает 26 плиток вместо 20, шесть из них пустые.
|
||||
|
||||
## What Changes
|
||||
|
||||
- **Роль не является содержимым.** Приложение SHALL отличать созданное им превью (в том числе
|
||||
по метке провенанса) от нового оригинала: превью никогда не загружается как новая версия и
|
||||
не влечёт удаления серверной копии, а расхождение состояния в индексе выправляется.
|
||||
Настоящее изменение содержимого, как и раньше, синхронизируется новой версией.
|
||||
- **Удаление только после подтверждения.** Устаревшая собственная загрузка удаляется только
|
||||
после того, как новая версия подтверждена сервером: сообщённая им контрольная сумма
|
||||
принятого объекта совпала с отправленной. При расхождении загрузка отклоняется вместе с
|
||||
принятой копией, прежняя серверная копия остаётся в альбоме, а замена повторяется при
|
||||
следующей синхронизации. Если сервер контрольную сумму не сообщил (например, старая версия
|
||||
инстанса), загрузка остаётся состоявшейся, но прежняя копия не удаляется.
|
||||
- **Контрольная сумма в ответе на загрузку.** Сервер SHALL отдавать контрольную сумму
|
||||
оригинала в данных созданного фото — как требует существующее требование способности
|
||||
`photos`; сегодня ответ на загрузку её не содержит (дефект сервера, а не изменение API).
|
||||
- **Разгрузка не гоняется с загрузкой.** Оригинал не заменяется превью, пока по этому
|
||||
элементу есть незавершённая загрузка.
|
||||
- **Возврат оригинала устойчив.** После возврата оригиналов на устройство элемент не
|
||||
разгружается превью повторно, пока альбом снова не переведён в режим переноса.
|
||||
- **Разовое восстановление двух пострадавших фото** из корзины сервера (и альбома «Первый»
|
||||
для второго) с удалением деградировавших копий.
|
||||
- **BREAKING**: нет. Успешные сценарии (загрузка нового, разгрузка, повторная синхронизация,
|
||||
настоящее изменение содержимого) сохраняют поведение; меняются только защитные условия.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
_Нет — поведение относится к существующей способности синхронизации альбома устройства._
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `phone-album-sync`: различение роли и содержимого (метка превью против нового оригинала),
|
||||
разгрузка только без незавершённой загрузки, удаление устаревшей загрузки только после
|
||||
подтверждения новой версии, устойчивый возврат оригиналов.
|
||||
|
||||
## Impact
|
||||
|
||||
- **Код (Android):** `data/sync/SyncPlan` (различение роли и содержимого, отложенная замена),
|
||||
`data/sync/SyncCoordinator` (выправление состояния, разгрузка без незавершённых загрузок,
|
||||
постановка замены), `data/sync/LocalAssetProvider` (метка превью рядом с хешем),
|
||||
`data/upload/UploadWorker` (сверка принятой контрольной суммы, удаление устаревшей копии
|
||||
после подтверждения, без разгрузки вне координатора), `data/sync/RestoreManager` (состояние
|
||||
после возврата оригинала), `data/db` (поле «заменяемое фото» в задании загрузки).
|
||||
- **Схема Room:** версия 8 → 9, `ALTER TABLE upload_jobs ADD COLUMN replacesPhotoId TEXT`.
|
||||
- **API и бэкенд:** не затрагиваются — используются существующие `DELETE /api/photos/{id}`
|
||||
(мягко, в корзину), `POST /api/photos/{id}/restore` и `original_checksum` в ответе загрузки.
|
||||
- **Данные инстанса:** разовое восстановление двух оригиналов и удаление деградировавших
|
||||
копий (операция, а не постоянное поведение).
|
||||
- **Не затрагивается:** серверная модель версий/идентичности, дедупликация вложения, чистка
|
||||
кэша альбома на телефоне (пустые плитки удалённых фото — отдельная задача), доверие к
|
||||
метаданным MediaStore при кэшировании хешей (сверка контрольной суммы закрывает риск).
|
||||
Reference in New Issue
Block a user