# Tasks ## 1. Модель индекса и провенанс - [x] 1.1 Добавить в `DeviceAssetEntity` признак происхождения (`OWN` | `MIRROR`) и Room-миграцию с `OWN` для существующих строк; проверить миграционным тестом `android/app/src/test` (или instrumented), что старая база открывается без потери строк - [x] 1.2 Добавить таблицу надгробий (photoId, момент, состояние) и watermark первого прохода в Room с миграцией; проверить тестом DAO на вставку/чтение/снятие надгробия - [x] 1.3 Расширить `ProvenanceStamp` признаком происхождения; проверить юнит-тестом encode/decode и тестом `SyncRecovery`, что скачанный элемент восстанавливается как `MIRROR`, а не как `OWN` ## 2. Конвейер материализации - [x] 2.1 Реализовать загрузчик объекта по подписанному URL (`original_url` для B, `preview_url`/`medium_url` для C, постер для видео); проверить юнит-тестом на выбор URL и обработку ошибок сети - [x] 2.2 Реализовать запись файла в бакет альбома устройства (уникальное имя, `DATE_TAKEN` из `taken_at`, метка происхождения); проверить instrumented-тестом, что файл появляется в MediaStore нужного бакета с датой - [x] 2.3 Реализовать дедуп перед вставкой: если содержимое уже на устройстве — связать элемент, не создавая файл; проверить тестом планировщика, что второго файла нет - [x] 2.4 Реализовать правило n:1 — материализация ровно в один выбранный бакет; проверить тестом, что при двух связанных бакетах файл создаётся в одном ## 3. Двусторонний планировщик - [x] 3.1 Реализовать дельту серверных фото (`?since=`/курсор) с учётом watermark и ограничения «только Wi-Fi»; проверить тестом, что при «только новое» существующий состав не скачивается, а новое — скачивается - [x] 3.2 Реализовать планирование материализации по режиму (B — оригинал, C — превью); проверить тестами `SyncPlanner`: режим копий → оригинал, режим переноса → превью - [x] 3.3 Реализовать двустороннее удаление с учётом прав и надгробий (локальное → в корзину сервера, серверное → снятие локального, 403 не роняет проход); проверить тестами: идемпотентность, эхо, отказ прав - [x] 3.4 Реализовать рематериализацию при восстановлении серверного фото из корзины; проверить тестом, что элемент возвращается в альбом устройства - [x] 3.5 Реализовать отвязку и выключение синхронизации так, чтобы серверные фото и скачанные файлы сохранялись; проверить тестом, что после отвязки файлы остаются и сервер не затронут ## 4. Интерфейс - [x] 4.1 Включить скачанные элементы в сетку альбома устройства и обновлять её по завершении прохода; проверить UI-тестом, что новое серверное фото появляется, а удалённое исчезает без ручного действия - [x] 4.2 Добавить при включении синхронизации выбор «только новое» / «скачать всё» с сохранением выбора; проверить UI-тестом, что выбор влияет на первый проход ## 5. Интеграция и документация - [x] 5.1 Проверить сквозной сценарий: фото, добавленное в веб-интерфейсе, материализуется на устройстве; удаление с устройства убирает его на сервере; удаление на сервере убирает локальный элемент; проверить e2e-тестом на эмуляторе - [x] 5.2 Обновить Purpose спеки `phone-album-sync` (двусторонний миррор вместо односторонней синхронизации) и убедиться, что `openspec validate phone-album-two-way-sync --strict` проходит ## 6. Устойчивость выключения синхронизации - [x] 6.1 Записать явный признак «синхронизация выключена» при отвязке/выключении и блокировать им восстановление происхождения; повторное включение снимает признак; проверить unit-тестами `SyncRepositoryTest` и `SyncRecoveryTest` - [x] 6.2 Добавить instrumented-тест всех режимов и переключений (A/B/C, B→C, C→B, C→A, B→A, A→B, восстановление) и прогнать на эмуляторе - [x] 6.3 Вынести объёмную нагрузку (100 фото + 20 видео на тест) в отдельный `SyncVolumeE2ETest`, а функциональные тесты держать малыми; проверить на эмуляторе - [x] 6.4 Очередь загрузки: одно дренирующее WorkManager-задание вместо задания на каждый файл (иначе JobScheduler перестаёт запускать оставшиеся задания); проверить заливкой 120 файлов на эмуляторе - [x] 6.5 Производительность: дебаунс `DeviceMediaObserver` и кэш sha256 по `(size, dateModified)`; проверить unit-набором и малыми наборами - [x] 6.6 Корректность: неудачное чтение серверного списка не считается пустым альбомом (иначе удалялись локальные зеркала); проверить unit-тестом `a failed album read does not delete local mirrors` - [x] 6.7 Удаление зеркал: намерением считается только явное удаление в приложении (надгробие → удаление на сервере); пропавшая локальная копия материализуется заново; проверить unit-тестами и instrumented-тестом `explicitDeleteOfAMirrorRemovesTheServerPhoto` ## Workflow follow-up - Archive the change after the project's review requirements are satisfied.