# План дофикса «входная дверь шва» (по промту `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`, иначе ошибка. Проверка распространяется на ОБА документа; байты, которые дверь проверила, она же и пишет (рендер один, не два). Фаззер пути ДВЕРИ: произвольные строки полей `Decision` → `ApplyDecisions` → рендер → разбор | `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`, лок на проекции, карта подписи после стопа) — не «допиливаю попутно». Механизм против капа реверс-секции не строю. Пере-именование банк-экспорта не трогаю. Словарь решений не расширяю.