textmachine/backend/docs/SEAM_FIX_PLAN.md

82 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# План дофикса «входная дверь шва» (по промту `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`, лок на проекции, карта подписи после стопа) — не «допиливаю попутно». Механизм против
капа реверс-секции не строю. Пере-именование банк-экспорта не трогаю. Словарь решений не расширяю.