9.7 KiB
Промт: бэкенд, малое касание — 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 <book.yaml> (алиасы/форма флагов — как у соседних команд):
- Открывает проект 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. Канал вопросов
Непонятно / конфликт промта с кодом → пинг оркестратору через владельца, НЕ интерпретация.