textmachine/backend/docs/SEAM_FIX_PLAN.md

11 KiB
Raw Blame History

План дофикса «входная дверь шва» (по промту docs/BACKEND_SEAM_FIX_SESSION_PROMPT.md)

Бэкенд-сессия 25.08.2026, продолжает незакоммиченное дерево пака. Заказ — 16 пунктов §4 (grep -cE '^### 4\.' → 16). Записка обязана быть комплектной механически, поэтому таблица ниже перечисляет ВСЕ шестнадцать, включая те, которые заканчиваются аргументированным отказом.

Базовая точка (снята ДО правок)

make battery на нетронутом дереве пака: EXIT=2, красен ровно TestKillMinus9LosesAtMostOneCall — один из двух названных промтом пред-существующих -race-флейков (internal/store). То есть флейк воспроизвёлся на дереве, к которому я ещё не прикасался; это моя собственная атрибуция, а не наследованная.

Что уже замерено до кода (§4.1, корень)

gopkg.in/yaml.v3 v3.0.1, проба вне рабочего дерева, матрица «значение → рендер → разбор» на обоих документах. Итог шире, чем говорит промт, и шире, чем говорила записка прошлой сессии:

значение поля что рендерится round-trip
"\nтекст" note: |4- + строка не разбирается (did not find expected key)
"\n\nтекст" |4- + пустая строка не разбирается
"\n\tx" |4- + таб не разбирается
"\n" |4+ разбирается, но значение "" — тихая потеря
"\n x" |4- + 10 пробелов разбирается, но значение "x" — тихая потеря
"текст\n", "a\nb", " x", "\tx", "\r\nx" скаляр/блок ок

Два вывода, оба несущие:

  1. Клейм промта «бьёт по ЛЮБОМУ свободному текстовому полю» ВЕРЕН, а поправка записки прошлой сессии («dst и sense не ломаются») — НЕТ: ломается любое строковое поле обоих документов (проверено на src/dst/sense/note и на rejects[].note). Триггер — ведущий \n, а не поле.
  2. «Документ разбирается» — недостаточный инвариант. Две формы разбираются и МЕНЯЮТ значение. Заказанная §4.1 проверка render → parse их пропустит: дверь отчитается applied, файл загрузится, а заметка владельца будет другой. Инвариант обязан быть «прочиталось И совпало».

Механика (тоже замерена): дефект — рассогласование индикатора отступа блочного скаляра с реальным отступом родителя внутри элемента последовательности; на глубине 1 ({note: "\nx"} без - ) тот же вход рендерится и читается корректно, а SetIndent(2) случайно попадает в согласие. Путь «починить рендер стилями yaml.Node» закрыт: (*yaml.Node).Encode внутри сам гоняет marshal→unmarshal и падает на том же входе — узел построить нельзя.

Порядок работ

Волнами, снизу вверх: формат → чистая семантика → конфиг/CLI → обвязка → пины. Батарея после каждой волны, make battery-stand в конце.

что делаю где
4.1 нормализация текстовых полей документа (TrimSpace, ОДНО правило на все поля обеих схем) + рендер, ДОКАЗЫВАЮЩИЙ себя: parse(render(A)) обязан совпасть с нормализованным A, иначе ошибка. Проверка распространяется на ОБА документа; байты, которые дверь проверила, она же и пишет (рендер один, не два). Фаззер пути ДВЕРИ: произвольные строки полей DecisionApplyDecisions → рендер → разбор seed, membank
4.2 пред-существующая проблема опознаётся по СУБЪЕКТУ: многосубъектный вердикт загружаемости раскладывается по термам до сравнения до/после membank
4.3 config.LoadBook отказывает при совпадении путей. Шире буквы заказа на одну строку: попарно различны должны быть четыре пути, которые движок пишет или читает как РАЗНЫЕ форматы (project_db, glossary_seed, mined_delta, mined_rejects) — project_db == mined_delta сегодня означало бы YAML поверх sqlite. Проверю на всех book.yaml репозитория и стенда, что живых нарушителей нет config
4.4 оба гарда — на ПРИСУТСТВИЕ флага через fs.Visit (прецедент ceilingGiven в том же файле) cmd/tmctl
4.5 кап на размер документа решений + строка отказа полосой. Число выбираю после СВОЕГО замера памяти (методика будет названа); хвост про exit 2 при OOM — пингом pipeline
4.6 конверт путей на поверхности, которую потребитель читает (manifest --json). Компромисс назову вслух: манифест — про дерево глав, путь артефакта там гость pipeline
4.7 правило алиасов судит НАБОР: одобряемые тем же вызовом термы участвуют в проверке aliasOwner membank
4.8 перевёрнутое окно глав (until < since, оба ненулевые) — отказ membank
4.9 вероятно отклоню как предсказание и заменю на факт. Дверь не знает, сколько поверхностей осталось за капом реверс-секции — это знает майнер на следующем проходе. Кандидат: отчёт называет, что решил ЭТОТ вызов, и не читается как «стоп погас». Решу замером, видит ли дверь остаток вообще pipeline
4.10 обработчик SIGTERM: бросить работу, отпустить лок, не записать ничего. Сначала замерю длительность вызова ПОСЛЕ капа §4.5 — если максимум по капу это десятки миллисекунд, скажу это аргументом pipeline/cmd
4.11 десять посадок (строка (д) — три), пин на каждую, каждый краснеет на своей мутации. Харнесс мутаций пишу заново с отпечатком целей до/после; кладу его в репозиторий — приёмка сажает мутации тоже, а инструмент каждый раз пишется с нуля все
4.12 тест цепи на фейковом провайдере (miningstop_join_test.go-харнесс): стоп → bank-apply → резюм → стоп погас, редакторская волна пошла. Плюс явная фиксация, что карта подписи после погасшего стопа остаётся на диске pipeline
4.13 восемь мелочей: по каждой решение и довод, чиню или отклоняю — списком в отчёте, без «кажется» разные
4.14 довод отказа --keys-file покрывает всех, кого ветка отвергает (redrive — платный, backup/migrate — деплой-шаги). Развилку про канал ключей redrive НЕ решаю cmd/tmctl
4.15 три существующих пина: вердикт Preexisting исполнить всеми четырьмя проверками · четвёртый оракул фаззера снять с тавтологии · TestDecisionByBankID сверять с НЕЗАВИСИМО посчитанным id тесты
4.16 платных вызовов ноль. Рантайм по платному пути остаётся PLAUSIBLE

Развилки, которые понесу владельцу/оркестратору (§11)

  1. §4.1 шире заказа. Заказанный round-trip render → parse не ловит две тихие потери значения; делаю строже («прочиталось И совпало») и говорю об этом прямо, потому что это меняет ответ двери на входы, которые заказ считал безопасными.
  2. §4.3 шире буквы на одну проверку — см. таблицу.
  3. §4.9 — скорее всего отказ от предсказания в пользу факта.
  4. §4.10 — возможен ответ «механизм без носителя после §4.5»; решит замер, не рассуждение.
  5. §4.6 — сделаю, компромисс назову.

Чего НЕ делаю

Пинги приложения «дофикс НЕ исполняет» (exit 2 при OOM, readEngine платформы, канал ключей redrive, rubyToCandidates, слепота TestDispatchCommandsCoversTheSwitch к константам, схема сида без omitempty, лок на проекции, карта подписи после стопа) — не «допиливаю попутно». Механизм против капа реверс-секции не строю. Пере-именование банк-экспорта не трогаю. Словарь решений не расширяю.