# Промт: бэкенд, малое касание — `tmctl migrate` (строка 174: деадлок деплоя v15) > Выдан оркестратором №16 14.08.2026. Запуск — по слову владельца (запуск = ратификация > варианта А строки 174). Лёгкий класс: короткий промт, инлайн-приёмка — но самопроверка > исполнением обязательна, это путь у денег и данных. ## 0. Какую проблему решаем Движок TextMachine — CLI-процесс (`tmctl`) со своим SQLite на книгу. Read-only команды (`status`, `report`) требуют ТОЧНОГО совпадения версии схемы («schema vN … expects vM», `internal/store/store.go:135-143`) и мигрировать не вправе; мигрирует схему только write-команда. Платформа (`platform/`) зовёт `tmctl status --json` ПЕРЕД каждым спавном прогона и для расчёта денег. Следствие — **деадлок деплоя**: апгрейд бинаря движка запирает ВСЕ существующие книги — status отказывает старой схеме, а write-команда, которая мигрировала бы, не наступает никогда (строка 174 единого бэклога, найдена приёмкой D39.131). Прямо сейчас это блокирует деплой свежезаленденного эмиттер-бинаря (D39.131) на стенд платформы. Твой результат: **$0-команда `tmctl migrate`** — явный write-open книги без прогона. Платформа вставит её деплой-шагом «стоп прогонов → migrate по каждой книге → новый бинарь в работу». ## 1. Онбординг `CLAUDE.md` (корень; гардрейлы: зона записи ТОЛЬКО `backend/`, ты НЕ коммитишь, `.env` не читать, чужие незакоммиченные файлы не трогать) → `backend/README.md` → `docs/architecture/12-go-style-notes.md` (норматив общности §0). Entry-points (код первичен): `cmd/tmctl/main.go` (диспетчер команд), `internal/store/store.go` (`Open` — write-путь: миграции + `recoverReservations`; `OpenReadOnly` — отказ по версии). **Эхо-протокол:** ≤10 строк «что я понял» до работы. ## 2. Задача (делай РОВНО так по границам; форму внутри — реши сам и аргументируй) `tmctl migrate --config ` (алиасы/форма флагов — как у соседних команд): - Открывает проект write-путём (`store.Open`) — миграции применяются, `recoverReservations` проходит, — печатает `schema vN -> vM` (или «already at vM») и выходит. Никаких LLM-вызовов, сети, трат: $0-команда, как `manifest`/`status`. - **Идемпотентна**: повторный вызов на свежей схеме — успех и no-op. - Exit-коды — по свежей конвенции полосы отказов (D39.131: 10 конфиг · 12 лок · 19 безымянный); успех 0. - **Типизированный отказ read-only путей при несовпадении схемы** (вторая половина задачи, ресёрч 14.08): `status`/`report` на файле чужой версии сегодня падают текстом — сделай исход МАШИНОРАЗЛИЧИМЫМ: свой exit-код (выбери место в конвенции, занятые классы не переиспользуй) + `found`/`expected` машиночитаемо (JSON/стандартная строка — реши форму). Цель: платформа сможет самолечиться «поймала → migrate → повтор» вместо стоп-мира. Поведение на совпадающей схеме не меняется ни на байт. - Тесты: фикстура/БД старой версии → `migrate` → `OpenReadOnly`/`status` работает; лок занят → exit 12; повторный вызов — no-op; version-mismatch у `status` даёт новый различимый исход. **Обязательный money-тест:** фикстура старой схемы с ЖИВЫМИ резервациями → `migrate` → `committed` не сдвинулся ни на цент, `reserved` обнулён ровно по правилам recovery, схема = head; повторный вызов денег не трогает. Плюс дифф `^func Test` исполнением, не памятью. ## 3. Границы и известные мины (приоры — опровергаются замером) - **Деньги:** `recoverReservations` обнуляет остаточный `reserved_usd` — на этом свойстве стоит ратифицированная формула потолка платформы (PD-158: аргумент = committed + прирост, БЕЗ reserved). `migrate` перед спавном делает `status` честнее, формулу не ломает — ПРОВЕРЬ это утверждение по коду и НАЗОВИ в отчёте явно; расхождение = пинг, не тихий фикс. - **Гейт `prices_checked` не должен убивать миграцию.** `LoadModels` жёстко отказывает конфигу с ценами старше 120 дней (`internal/config/models.go:209-214`), и строка 146 единого бэклога уже фиксирует: этот гейт стреляет на read-only/$0-путях, где перекупки нет. Если `migrate` соберёшь поверх полного конфиг-стека, протухшие цены превратят $0-миграцию в отказ — и деплой-деадлок вернётся ровно в той форме, которую команда лечит. Открывай store/книгу БЕЗ валидации свежести цен (или обоснуй, почему она тут уместна) — и назови выбор в отчёте. - **Строка 49а** (ALTER-шаги миграций v8–v14 не идемпотентны на полу-применённой БД) — знать, НЕ чинить этим касанием (отдельная строка); твоя команда не должна усугубить (никаких новых ALTER, только вызов существующего механизма). Опция «реши сам»: проверь, транзакционно ли ПРИМЕНЕНИЕ шагов миграции (DDL в SQLite транзакционен) — если нет, обёртка применения (тела миграций не трогать!) закрыла бы класс «крэш посреди migrate = полу-применённая БД с деньгами» вперёд; бери только если ложится чисто, иначе — строкой. - **Бэкап:** write-open уже делает бэкап файла (`backupStamp`) — проверь, что путь `migrate` его наследует; помни коллизию секундной метки (строка 173) — не наступи, чинить не обязан. - **Settle-инвариант:** платформа считает деньги из `committed`/леджера; `reserved` легитимно равен нулю в любой момент (advisory, восстановим). Назови в отчёте явно, что `migrate`, случившийся между выходом процесса и расчётом платформы, денег расчёта не искажает. - Снапшоты/голдены/RequestHash не двигаются — команда не трогает промпты и провод; голден-батарея обязана остаться бит-в-бит. - Общность §0: ничего пар-/книго-специфичного. ## 4. Самопроверка и сдача `cd backend && make battery` EXIT=0; самопроверка исполнением (живой прогон команды на копии проектной БД двух версий — не чтением диффа); адверсариальная вычитка собственного диффа с установкой опровергать (субагент разрешён явно); «заявление = команда». Отчёт короткий: что построено · команды проверок · взаимодействие с PD-158 · чего не делал. Итог — пингом в `docs/PROGRESS.md` секция «Бэкенд» — ТОЛЬКО append своей записи (файл держат параллельные сессии: CURRENT-STATE и чужие секции не трогать, перед правкой `git status`); дерево НЕ коммитить — лендит оркестратор. ## 5. Канал вопросов Непонятно / конфликт промта с кодом → пинг оркестратору через владельца, НЕ интерпретация.