From e91e3c3ae5e1ae05d440dd43f78e7708a6515daa Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Sun, 27 Sep 2026 22:05:21 +0600 Subject: [PATCH] [2026-09-27] eagle: family/projects/watchlist-automation.md family/projects/watchlist-sync.md personal/plans/music-artist-albumartist-repair.md --- family/projects/watchlist-automation.md | 53 +++++++ family/projects/watchlist-sync.md | 81 ++++++++++ .../plans/music-artist-albumartist-repair.md | 141 +++++++++++++++--- 3 files changed, 251 insertions(+), 24 deletions(-) diff --git a/family/projects/watchlist-automation.md b/family/projects/watchlist-automation.md index 4cb755d5..55a7297e 100755 --- a/family/projects/watchlist-automation.md +++ b/family/projects/watchlist-automation.md @@ -12,6 +12,59 @@ tags: Полная цепочка: как `family/documents/movies-watchlist.md` превращается в скачанный контент. +## 🔴 2026-09-27: дефект позиционирования `[x]` — найден, НЕ исправлен + +**Симптом (Alex):** «Почему просмотренные типа Даун Хаус или Кокаиновый медведь не падают +вниз списка? Да и новинки как будто давно не обновлялись». + +**Корень — `sync_up.py:69` (`_mark_watched`):** +```python +insert_at = next((i for i, l in enumerate(lines) if re.match(r'\s*-\s*\[x\]', l)), None) +lines.insert(insert_at, raw) +``` +`next(...)` — **ПЕРВАЯ** `[x]`-строка **всего файла**, вставка идёт **выше** неё. +Докстринг: *«move to top of `[x]` section»*, код: *«top of file»*. Расхождение и есть баг. + +**Спецификация** ([[family/projects/watchlist-sync]] §1.4): строка встаёт **под последней +`[ ]` главного списка**, т.е. в **НАЧАЛО блока просмотренных** (он тянется вниз до конца +`## Фильмы`). НЕ «в начало файла», НЕ «в конец файла», НЕ в отдельную секцию. +Тот же смысл у слова Alex «вниз»: строка уезжает из зоны `[ ]` вниз, в блок `[x]`. + +**Замер (712 строк):** блок просмотренных — **421–674**, в нём 174 из 178 `[x]`. +Застряли выше — **4**: 106 `Кокаиновый медведь`, 109 `Проект конец света`, 111 `Неистовый`, +326 `Даун Хаус` (все — записи `sync-from-lampac` после 25.09). Дефект был незаметен, потому +что для 174 строк вставка «выше первой `[x]`» случайно совпадала с правильной позицией. + +⚠️ `## Мультфильмы` (676) / `## Аниме` (685) идут **после** блока → границу считать +**внутри `## Фильмы`**. Инверсия существующей `parser.find_main_last_watched()`. + +**Состояние:** коммит ДО правки — **`cf73666`** (зафиксировано чужое изменение +`sync_down.py`). **Правка `sync_up.py` НЕ сделана**, ждёт апрува. `sync_up.py` md5 +`45f2652cfc4c1c550588d52a7d55fd7b`, установка editable → правка подхватывается без +переустановки. +🔴 **Ветки разошлись:** Mac HEAD `0534952` vs Кра́кен `6c5dbe7` (поверх него `cf73666`). +Перекрываются по смыслу → **не пушить** до разбора. +Порядок работ: коммит ДО → правка → деплой → прогон. Бэкап в план не входил. + +**«Новинки не обновлялись» — две разные вещи:** +1. Каталог Lampa обновляется каждую ночь: `watchlist.json` 27.09 01:07, `count: 403` + (films 357 / new 32 / cartoons 7 / anime 7). +2. `## Новинки` в `.md` — последний `discover` 21.09. 27.09 (вс) скрипт залогировал только + sync-vault pull/push, без шага `discover`. **Причина не выяснена — открытый вопрос.** + Плюс `sync_down.py:70` держит секцию в `HIDE_SECTIONS` → новые «новинки» в фид не идут, + пока не нажата 👍. + +**Питфоллы jq по фиду:** +```bash +F=.../lampac/wwwroot/watchlist.json +jq -r '.channels | to_entries[] | "\(.key) (\(.value.title)): \(.value.items | length)"' $F +jq -r '.count' $F # 403 +``` +🔴 Канал — объект `{title, items}`, НЕ массив → `jq -c ".channels.films | .[0]"` даёт +`Cannot index object with number`. +🔴 `## Новинки` в HIDE_SECTIONS → `grep` по фиду НЕ найдёт позиции из Новинок. +Фид содержит только `Status.UNWATCHED` (`sync_down.py:326`) — ни одного `[x]`. + ## ✅ 2026-09-24: `sync-from-lampac` реализован — Lampa ставит галочки Отметки «просмотрено» из Lampa автоматически попадают в watchlist как `[x]`. diff --git a/family/projects/watchlist-sync.md b/family/projects/watchlist-sync.md index 5fdf48a4..2cd649ad 100755 --- a/family/projects/watchlist-sync.md +++ b/family/projects/watchlist-sync.md @@ -186,6 +186,87 @@ WATCHLIST_PATH=/vault/family/documents/movies-watchlist.md - Переместить строку на **первое место** среди `[x]` записей (под последней `[ ]`) 4. Обновить `.md`, git commit +--- + +## 🔴 2026-09-27: ДЕФЕКТ `[x]`-позиционирования — НАЙДЕН, НЕ ИСПРАВЛЕН + +**Симптом (Alex):** «Почему просмотренные типа Даун Хаус или Кокаиновый медведь не падают вниз списка?» + +**Корень — `sync_up.py:69`, `_mark_watched()`:** +```python +insert_at = next((i for i, l in enumerate(lines) if re.match(r'\s*-\s*\[x\]', l)), None) +lines.insert(insert_at, raw) +``` +`next(...)` = **ПЕРВАЯ** `[x]`-строка **ВСЕГО файла** → вставка идёт **выше** неё. +Комментарий в докстринге говорит *«move to top of `[x]` section»*, а код делает +*«top of file»*. Это и есть баг. + +**Спецификация из §1.4 выше:** строка должна вставать **под последней `[ ]`-строкой +главного списка** (первое место среди `[x]`-записей), т.е. в **НАЧАЛО блока +просмотренных**, который тянется вниз до конца `## Фильмы`. Не «в начало файла», +не «в конец файла», не в отдельную секцию. + +**Замер боевого файла (2026-09-27, 712 строк):** +| Что | Значение | +|---|---| +| Секции | `## Новинки - 👍/👎` 20, `## Фильмы` 55, `## Мультфильмы` 676, `## Аниме` 685, `## Новинки👎` 694, `## Связанные заметки` 709 | +| `[x]` в главных секциях | **178** | +| Реальный блок просмотренных | **421–674** | +| `[x]` ВЫШЕ блока (застряли) | **4**: 106 `Кокаиновый медведь`, 109 `Проект конец света`, 111 `Неистовый`, 326 `Даун Хаус` | + +Все 4 попали наверх от `sync-from-lampac` (записи после 25.09 — файл датирован 27.09). +Остальные 174 стояли в блоке, и вставка «выше первой `[x]`» для них работала по назначению — +поэтому дефект был незаметен. + +**Важно:** правит не только будущие `[x]`, но и накопленные. Разовая нормализация = +все 178 `[x]` главного списка пересобираются блоком от границы последней `[ ]`. + +**Готовая функция, которую надо переиспользовать:** `parser.find_main_last_watched(ls)` +(существует, работает только в `process_thumbs`). Требуется инверсия — «последняя `[ ]`». + +⚠️ `## Мультфильмы` (676) и `## Аниме` (685) идут **ПОСЛЕ** блока просмотренных → +границу считать **внутри `## Фильмы`**, а не по всему файлу, иначе `[x]` из мультфильмов +поедет не туда. + +**Состояние работ на конец сессии:** +- ✅ Коммит ДО правки сделан: **`cf73666`** `sync-down: fall back to name match when KP + card fails; fix question-mark section scoping` — зафиксировано ЧУЖОЕ незакоммиченное + изменение `sync_down.py` (два блока: фолбэк на матч по названию при неудаче + `_card_for()`, и `_sync_question_marks` по `HIDE_SECTIONS` вместо «всё кроме Новинок»). +- ⏸ **Правка `sync_up.py` НЕ сделана.** Требуется апрув. +- Код не тронут: `sync_up.py` md5 `45f2652cfc4c1c550588d52a7d55fd7b` (идентичен в git + HEAD, на Кра́кене и на Mac). +- Установка на Кра́кене — **editable** (`.venv/.../_editable_impl_watchlist_sync.pth`), + правка файла в репе подхватывается без переустановки. + +**🔴 Расхождение веток (разобрать до push):** на Mac HEAD = `0534952` +(«feed: fix ❓ marker scope»), на Кра́кене HEAD был `6c5dbe7`. `cf73666` сделан поверх +`6c5dbe7`. Эти два коммита по смыслу перекрываются → `cf73666` **не пушить**, пока не +выяснено, эквивалентен `0534952` или дополняет его. + +**Связанные факты сессии:** +- Крон `watchlist-nightly` (01:00) отработал: фид `wwwroot/watchlist.json` — 27.09 01:07, + `count: 403` (films 357 / new 32 / cartoons 7 / anime 7), `missing.json` 3 позиции. + Бэкап `TimeCode.sql` за 27.09 в 01:09 создан. +- `## Новинки` в `.md` не пополнялся с 21.09. Крон discover стоит (`0 9 * * 0`), но + 27.09 (воскресенье) `watchlist-discover.sh` залогировал только sync-vault pull/push, + без запуска `discover`. **Причина не выяснена — открытый вопрос.** +- Структура фида: `channels..items[]` (не массив верхнего уровня) — + `jq -r ".channels | to_entries[] | \"\(.key): \(.value.items | length)\""`. Поле + `count: 403` в корне. 🔴 `jq -c ".channels.films | .[0]"` даёт + `Cannot index object with number` — канал это объект `{title, items}`, не массив. +- В фиде нет ни одного `[x]` (проверено: `Даун Хаус`, `Кокаиновый медведь`, `Неистовый` + отсутствуют) — фид строится только из `Status.UNWATCHED` (`sync_down.py:326`). + +**Порядок работ (регламент Alex, повторён явно):** коммит ДО → правка → деплой → прогон. +Бэкап в план НЕ входил (агент добавил отсебятиной, Alex возразил) — не повторять. +Спроектированная, но НЕ выполненная папка `~/backup/reflow-watched-20260927-215836/` +на Кра́кене (3 файла: `sync_up.py`, `cli.py`, `movies-watchlist.md`) — можно снести. + +**Питфолл сессии (для будущих агентов):** Alex спросил «куда именно вниз», имея в виду +**документацию**. Ответ лежал в §1.4 этой же заметки. Правило: при вопросе «по +документации?» — читать доки, а не предлагать варианты на выбор. + **Jellyfin userId:** получать через `GET /Users` (admin user = alex) ### 1.5 Cron Jobs (Hermes Eagle) diff --git a/personal/plans/music-artist-albumartist-repair.md b/personal/plans/music-artist-albumartist-repair.md index 29aeeb5a..8f0b37b1 100755 --- a/personal/plans/music-artist-albumartist-repair.md +++ b/personal/plans/music-artist-albumartist-repair.md @@ -4,7 +4,7 @@ created: '2026-09-27' updated: '2026-09-27' type: plan namespace: personal -status: dry-run-done / boevoy-zapusk-pravilnyy-cmd-ne-podtverzhden +status: БОЕВОЙ-ПРОГОН-ВЫПОЛНЕН / TCON-4359-ЗАПИСАН / verify-0-mismatch tags: - music - metadata @@ -46,17 +46,16 @@ related: Канон: ~/Downloads/music-tags/APPLY-PLAN.csv (7861 строка — НЕ ПЕРЕЗАПИСЫВАТЬ) ``` -**Состояние (27 сен, сессия ~30):** ✅ **dry-run ВЫПОЛНЕН УСПЕШНО** — `7653 к правке`, -`mp3 на диске 7861`, `не найдено 0`. Требование Alex «совпасть по цифрам с -APPLY-PLAN-v3-changes» **снято самим Alex** («нихуя не понял… очевидно что полный набор»): -дифф считает группы, dry-run — файлы, совпадение недостижимо by design. Решение Alex: -**пишем полный набор полей** (не только `TCON`). +**Состояние (27 сен, сессия ~31):** ✅ **БОЕВОЙ ПРОГОН ВЫПОЛНЕН УСПЕШНО.** +`7653/7653 ok, err=0`; **`TCON` записан в 4359 файлов** — открытый дефект с 27 сен ЗАКРЫТ. +Проверка чтением файлов (не по логу): `7861/7861 имеют TCON`, `mismatches 0`, `read errors 0`. -🔴 **БОЕВОЙ ПРОГОН НЕ СОСТОЯЛСЯ — запущен с неверным аргументом.** См. § БОЕВОЙ ПРОГОН, -немедленно читать перед повтором. +Скрипт пишет **все 6 полей сразу** (`TIT2 TPE1 TPE2 TALB TCON TRCK`) — режима «только genre» нет. +Решение Alex: **полный набор**, дифф не пересобираем; требование «совпасть по цифрам с +APPLY-PLAN-v3-changes» снято самим Alex (дифф = ГРУППЫ, прогон = ФАЙЛЫ). -**Открытый дефект:** жанры (`TCON`) в тегах отсутствуют. Скрипт исправлен, dry-run доказал, -что `TCON` пишется. Боевой прогон — ждёт корректной команды. +⏳ **Осталось:** пересканить Navidrome (`stop → rm navidrome.db* → up -d`) и убедиться в UI, +что альбомы/жанры встали без дублей. **Кэш БД не чищен.** **Доступ к NAS:** `ssh -i ~/.ssh/id_rsa truenas_admin@mallexxx.duckdns.org` ⚠️ `192.168.1.100` — ЧУЖАЯ подсеть, не использовать. Подробнее → [[family/how-to/truenas-access]] @@ -77,7 +76,9 @@ APPLY-PLAN-v3-changes» **снято самим Alex** («нихуя не пон | 8 | 27 сен | **Канонный дифф v3 собран** — сортировка по `field`, схлопывание, шум убран | `DIFF-base-vs-applyplan-v3.csv` | | 9 | 27 сен | **Верификация артефактов v3 перед dry-run** — 3 расхождения вскрыты, см. § ВЕРИФИКАЦИЯ | `/tmp/verify_v3*.py` | | 10 | 27 сен | **Dry-run выполнен: 7653 к правке, не найдено 0** — `TCON` пишется | `/tmp/dryrun.log` | -| 11 | 27 сен | 🔴 **Боевой прогон не состоялся** — `CMD=--dry-run` образа подставился | `/tmp/tw306/run.log` | +| 11 | 27 сен | 🔴 **Прогон №1 сорвался** — `CMD=--dry-run` образа подставился | `/tmp/tw306/run.log` | +| 12 | 27 сен | ✅ **БОЕВОЙ ПРОГОН ВЫПОЛНЕН** — 7653/7653 ok, err=0, **`TCON` 4359** | `/tmp/tw306/apply_run.log` | +| 13 | 27 сен | ✅ **Верификация на диске** — 7861/7861 имеют `TCON`, mismatches 0 | `/tmp/tw306/verify_applied.py` | --- @@ -143,13 +144,20 @@ docker create --name twdry --entrypoint python3 \ tagwriter:latest /app/apply_tags_v2.py --dry-run docker start -a twdry -# 3. БОЕВОЙ ПРОГОН — CMD переопределить явно, иначе будет dry-run (см. ПИТФОЛЛ 13): +# 3. БОЕВОЙ ПРОГОН — 3 ОТДЕЛЬНЫЕ КОМАНДЫ, не одной строкой (см. ПИТФОЛЛ 17): docker rm -f twrun >/dev/null 2>&1 docker create --name twrun --entrypoint python3 \ -v /tmp/tw306:/app/data:ro -v /mnt/RED_2TB/storage/Music:/music:rw \ - tagwriter:latest /app/apply_tags_v2.py --apply # ← ФЛАГ ПОДТВЕРДИТЬ В СКРИПТЕ -docker start -a twrun + tagwriter:latest /app/apply_tags_v2.py --apply # ✅ ФЛАГ ПОДТВЕРЖДЁН РАБОЧИМ +docker inspect twrun --format 'CMD={{json .Config.Cmd}}' # должен быть --apply, БЕЗ --dry-run +docker start twrun # НЕ `start -a` — не держать терминал ``` +⚠️ `docker start twrun` **без `-a`**: прогон идёт ~7 минут, `-a` держит SSH-сессию открытой +и ход легко прерывается. Прогресс — `docker ps --filter name=twrun`, ожидание — +`docker wait twrun`, лог — `docker logs twrun > /tmp/tw306/apply_run.log`. +🔴 **Лог появляется только в КОНЦЕ** (скрипт буферизует): пустой `docker logs` при живом +процессе — это НЕ зависание. Проверять `docker top twrun` / `docker stats twrun`. + - Образ `tagwriter:latest` (НЕ `v2`), uid 950:950, amd64 · 25971 байт `apply_tags_v2.py` - **План читается ТОЛЬКО как `/app/data/apply-plan.csv`** → подкладывать `/tmp/apply-plan-v3.csv` под этим именем @@ -209,6 +217,7 @@ backup_raw /tmp/verify_v3c.py верификация: аномалии (genre-дубли, Sbornik, пропуск пути) /tmp/parse_dry.py разбор лога dry-run по полям (только показанные 40 файлов!) /tmp/dryrun.log вывод успешного dry-run (копия с NAS) +/tmp/verify_applied.py верификация ПОСЛЕ прогона: читает TCON/TPE2/TALB из файлов ~/Developer/personal-os/tools/music-tags/build_fix_plan2.py генератор плана (пофикшен) ``` @@ -279,11 +288,12 @@ Dry-run считает ФАЙЛЫ: album 28 · albumartist 6 · genre 4359 (в | Дефект | Факт | Статус | |--------|------|--------| -| **Жанры не в тегах** | В БД `genre` пусто у всех 7861; прогон 27 сен не писал `TCON` | Скрипт исправлен, dry-run доказал `TCON`. **Боевой прогон нужен** | -| 🔴 **Боевой прогон сорвался** | Запущен без переопределения `CMD` → подставился `--dry-run` образа | **Ничего не записано. Повтор с явным CMD** | +| **Жанры не в тегах** | В БД `genre` было пусто у всех 7861; прогон 27 сен не писал `TCON` | ✅ **ЗАКРЫТ 27 сен:** `TCON` записан в 4359 файлов, на диске 7861/7861, mismatches 0 | | Жанры в диффе | 427 строк / 911 групп, покрытие 7861 — не «правка», а повторная запись | Учтено | | Лог v3 устарел | 3 строки + 1 путь расходятся с планом после `fix_ruki`/`fix_rockopop` | Решено: план — эталон, лог не пересобираем | | Дифф: genre-группы | 211 ТРЕК-строк дублируют `new_genre`; 7 одиночек не добраны | **Alex снял вопрос:** пишем полный набор, дифф не пересобираем | +| `track` −4 от плана | 4 файла `changed=yes`, расхождений нет → скрипт не писал | **Не дефект**, ожидаемо | +| **Navidrome-кэш не чищен** | Теги на диске новые, БД Navidrome — старая (музыку не сканировали) | ⏳ **Ждёт перескана: `stop → rm navidrome.db* → up -d`** | --- @@ -327,7 +337,7 @@ genre 4359 · albumartist 7213 · album 2031 · track 441 · artist 381 · title plan.changed=yes 7653 ``` -### 🔴 БОЕВОЙ ПРОГОН — НЕ СОСТОЯЛСЯ (запущен с неверным аргументом) +### 🔴 ПРОГОН №1 — НЕ СОСТОЯЛСЯ (запущен с неверным аргументом) Запущено: ```bash @@ -348,8 +358,84 @@ CMD = ["--dry-run"] **✅ Ничего не записано, музыка не тронута, бэкап не потребовался.** -**Осталось перед повтором:** подтвердить в скрипте флаг боевого режима (в доках он -не зафиксирован — проверялись только `--dry-run`), затем `docker create` **с явным CMD**. +--- + +### ✅ ПРОГОН №2 — БОЕВОЙ, ВЫПОЛНЕН УСПЕШНО (27 сен, сессия ~31) + +Флаг боевого режима — **`--apply`** (подтверждён рабочим). CMD переопределён явно: +```bash +docker rm -f twrun >/dev/null 2>&1 +docker create --name twrun --entrypoint python3 \ + -v /tmp/tw306:/app/data:ro -v /mnt/RED_2TB/storage/Music:/music:rw \ + tagwriter:latest /app/apply_tags_v2.py --apply +docker inspect twrun --format 'CMD={{json .Config.Cmd}}' +# CMD=["/app/apply_tags_v2.py","--apply"] ENTRY=["python3"] ← проверено ДО старта +docker start twrun # без -a; затем docker wait twrun +``` + +**Результат** (`/tmp/tw306/apply_run.log`, `exit=0`): +``` +строк в плане: 7861 +mp3 на диске: 7861 пропущено не-mp3: 562 +пропущено changed=no: 208 +не найдено на диске: 0 +к обработке: 7653 + +=== APPLY: 7653 файлов === + ... 7653/7653 (100%) ok=7653 err=0 + +=== ИТОГ APPLY === + обработано: 7653 · успешно: 7653 · ошибок: 0 + изменения по полям: + TIT2 (title): 8 + TPE1 (artist): 381 + TPE2 (albumartist): 7213 + TALB (album): 2031 + TCON (genre): 4359 ← ЖАНРЫ ЗАПИСАНЫ + TRCK (track): 437 +``` + +**Сверка с планом** — все поля совпали, кроме `track`: + +| поле | записано | в плане | ✓ | +|---|---|---|---| +| `TCON` genre | **4359** | 4359 | ✓ | +| `TPE2` albumartist | 7213 | 7213 | ✓ | +| `TALB` album | 2031 | 2031 | ✓ | +| `TPE1` artist | 381 | 381 | ✓ | +| `TIT2` title | 8 | 8 | ✓ | +| `TRCK` track | 437 | 441 | −4 | + +Минус 4 по `track` = те самые 4 файла `changed=yes`, но без расхождений +(Infected Mushroom ×3, `Пушной/Город Омск`) — скрипт им ничего не писал. **Не дефект.** + +--- + +### ✅ ВЕРИФИКАЦИЯ ПОСЛЕ ПРОГОНА — ЧТЕНИЕМ ФАЙЛОВ (не по логу) + +Скрипт `/tmp/verify_applied.py` (на NAS `/tmp/tw306/verify_applied.py`), прогон: +```bash +docker create --name verapp --entrypoint python3 \ + -v /tmp/tw306:/app/data:ro -v /mnt/RED_2TB/storage/Music:/music:ro \ + tagwriter:latest /app/data/verify_applied.py +docker start verapp && docker wait verapp && docker logs verapp +``` +⚠️ Обход 7861 файла НЕ укладывается в 180-секундный форграунд-лимит → запускать +через `docker create` + `docker start` (без `-a`), результат забирать `docker logs`. + +Результат: +``` +files_read = 7861 +has TCON (genre) = 7861 ← жанр у КАЖДОГО файла +has TPE2 (albumart) = 7861 +has TALB (album) = 7861 +TCON mismatches = 0 +read errors = 0 +``` + +**Открытый дефект «жанры не в тегах» (с 27 сен) — ЗАКРЫТ.** + +**Проверяет Alex:** открыть Navidrome — альбомы должны показывать жанры. --- @@ -358,8 +444,8 @@ CMD = ["--dry-run"] ``` Библиотека: 7861 живых файлов = строкам плана Бэкапы: 535 файлов (6 папок, в .ndignore) -Боевой прогон: 27 сен, 7649/7649 ok, err=0, VERIFY 7649 -Поля прогона: TPE2 7213 · TCON 4359 · TALB 1659 · TPE1 381 · TRCK 344 · TIT2 8 +Прогон 27 сен №1: 7649/7649 ok, err=0, VERIFY 7649 (без TCON — старый скрипт) +Поля прогона №1: TPE2 7213 · TCON 4359 · TALB 1659 · TPE1 381 · TRCK 344 · TIT2 8 Поля плана (cur!=new): album 2031 · albumartist 7213 · genre 4359 · artist 381 · title 8 · track 441 ; plan.changed=yes 7653 Дифф v3: 945 строк (ПАПКА 711 · ТРЕК 234) @@ -369,17 +455,24 @@ CMD = ["--dry-run"] Dry-run 27 сен: план 7861 · mp3 на диске 7861 · не найдено 0 · к обработке 7653 (пропущено changed=no 208, не-mp3 562) · лог печатает 40 файлов changed=yes но расхождений нет: 4 файла (Infected Mushroom ×3, Пушной) -Боевой прогон: НЕ СОСТОЯЛСЯ — подставился `CMD=--dry-run` образа +БОЕВОЙ прогон №2: ✅ ВЫПОЛНЕН — 7653/7653 ok, err=0, exit=0 + TCON 4359 · TPE2 7213 · TALB 2031 · TPE1 381 · TRCK 437 · TIT2 8 + (= плану, кроме track −4: 4 файла без расхождений) +Верификация диска: files_read 7861 · has TCON 7861 · TCON mismatches 0 · read errors 0 +Navidrome: ⏳ кэш БД НЕ чищен, рескан не делался ``` --- ## 📁 ФАЙЛЫ НА NAS (рабочие, `/tmp/tw306/`) ``` -apply-plan.csv md5 6b9e49b2baa3f1d1708e4566972266a9 (= /tmp/apply-plan-v3.csv) +apply-plan.csv 2882525 B, md5 6b9e49b2baa3f1d1708e4566972266a9 (= /tmp/apply-plan-v3.csv) dryrun.log 21955 B — вывод успешного dry-run (есть копия на Mac /tmp/dryrun.log) -run.log вывод сорвавшегося боевого прогона (снова DRY-RUN) +run.log прогон №1: снова DRY-RUN, ничего не записано (CMD образа) +apply_run.log ✅ прогон №2 БОЕВОЙ: 7653/7653 ok, err=0, exit=0 +verify_applied.py верификация на диске: 7861/7861 TCON, mismatches 0 check_album_applied*.py / verify_306*.py вспомогательные проверки +``` ---