# Промт: БЭКЕНД ДОФИКС-ПАК «ВХОДНАЯ ДВЕРЬ ШВА» (по приёмке движковой половины D39.156) > ⚠⚠ **ОТРАБОТАН И ЗАКРЫТ. Инструкции отсюда НЕ исполнять** — файл сохранён как заказ, по которому > судить исполнение. > > **Исход:** пак входной двери шва исполнен и ПРИНЯТ 27.08 — ратификация **D39.158**, лендинг > `d1eb8a9`. Дверь построена, стоп банка стал флажком, полоса отказов получила класс 15. > ⚠ Якоря этого файла — на дерево ДО лендинга; часть из них лендинг сдвинул. Верить коду, не им. > Выдан оркестратором №19, 25.08.2026. Активный промт зоны бэкенда — один; этот заменяет > `BACKEND_SEAM_PACK_SESSION_PROMPT.md` (отработан, уезжает в архив при лендинге). > Параллельно законен только `platform/docs/archive/PLATFORM_P8_REVIEW_SESSION_PROMPT_2026-08-22.md (ОТРАБОТАН 27.08)` (read-only, пересечений нет). ## 1. Какая проблема и что решит твой результат Продукт — издательский перевод больших книг мультиагентным пайплайном. Его центральная ценность — **банк памяти**: сквозной глоссарий имён и терминов, держащий консистентность на всю книгу. Предыдущий пак построил **входную дверь**: `tmctl bank-apply` принимает решения владельца о банке ДАННЫМИ, а не правкой YAML руками. Пак **принят не был**. Приёмка прогнала цепь целиком и подтвердила, что дверь работает по назначению — но нашла **один блокер и восемь мажоров**, и главный из них превращает дверь в свою противоположность: **один законный отказ пользователя необратимо ломает книгу так, что чинить её можно только тем самым текстовым редактором, ради отмены которого дверь и строилась.** Твой результат — дверь, которую можно отдать пользователю. Пак не переделывается: он хороший, дыры узкие и адресные. Всё ниже — правки в уже написанном коде плюс пины к местам, где гейт оказался слепым. ⚠ **Дерево пака НЕ закоммичено и лежит в рабочем каталоге.** Ты продолжаешь его, а не начинаешь заново. ## 2. Зона записи и git Пишешь ТОЛЬКО в `backend/`. Читать можешь всё; `platform/`, `frontend/`, `eval/`, `docs/` — чужие зоны, правки в них запрещены. **Единственное исключение:** пинги и итог сессии — в `docs/PROGRESS.md`, секция «Бэкенд». **Ты не коммитишь** — дерево готовишь и передаёшь на лендинг оркестратору (канон git-норм — `CLAUDE.md`). `git add -A`, `git add .`, `git commit -a` запрещены в любом случае. В дереве живёт незакоммиченная работа параллельных сессий — не трогай её и не «прибирай». ## 3. Карта чтения (пять позиций, больше не нужно) 1. **`backend/docs/SEAM_PACK_FINDINGS.md`** — доказательная база твоего же пака. Читать §0 (состав), §5 (закрытые дефекты — чтобы не чинить дважды), **§6 (класс полосы 14 — статус ратификации, см. ниже)**, §7 (что НЕ доказано), §9 (два остаточных риска), §10 (семь пингов). ⚠ **Что из них стало заказом, точно:** §9 — ВТОРОЙ риск (кап размера документа) стал §4.5; ПЕРВЫЙ (необратимость канонического пере-рендера) заказом НЕ стал и остаётся риском, он получает только пин на поле-смягчение (§4.11 б) и уточнение точности этого поля (§4.13). §10 — заказом стала ПОЛОВИНА п.3 (текст отказа, §4.14); остальные шесть перечислены в приложении «пинги, которые дофикс НЕ исполняет». Не ищи в промте заказов, которых там нет. ⚠ **Статус класса 14:** заводить его законно и это уже сделано — ратифицированный закон обещает, что двери встают паками, а прецедент кода 13 прошёл ровно так (направление → финализация приёмкой). Ноту и подъём обоих ратифицированных носителей полосы ставит ОРКЕСТРАТОР при лендинге; ты работаешь на классе 14 как на действующем и §4.11 (в) строишь на нём. 2. **`docs/architecture/17-seam-inbound-law.md`** — ратифицированный закон входной двери, семь пунктов. Твой контракт. Пункты 1, 2, 3 и 7 ты трогаешь напрямую. 3. **`docs/architecture/05-decisions-log.md`, нота D39.156** — почему закон такой. 4. **`docs/PROGRESS.md` — строка БЭКЛОГА 199** ⚠ это ID строки ТАБЛИЦЫ, брать грепом `^| 199 |`; буквальный `sed -n '199p'` даст чужой текст. В её теле — ТРИ мины формата; мины 1 и 2 пак обезвредил, **мина 3 (кап реверс-секции 200 ПОСЛЕ фильтров) не обезврежена и попадает в §4.9**. 5. **`docs/architecture/12-go-style-notes.md`** — норматив общности. Ревью-вопрос по умолчанию: «заработает ли на паре, которой в репо ещё НЕТ, без правки Go?» **Код первичен.** Все `file:line` ниже — отправные точки, снятые приёмкой на дереве пака; открывай и проверяй сам (дисклеймер о происхождении доков — шапка `CLAUDE.md`). ## 4. Состав дофикса Каждый пункт несёт **как приёмка это показала** — воспроизведи сам прежде, чем чинить: правка без воспроизведённого репро лечит не то. ### 4.1 ⛔ БЛОКЕР: reject-документ не проверяется на читаемость вообще — ДЕЛАЙ РОВНО ТАК инвариант, РЕШИ САМ форму `documentProblems` (`internal/membank/decisions.go`) рендерит и парсит обратно **только дельту**. `res.Rejects` не участвует ни в одной проверке. **Репро (воспроизвести первым делом):** решение `{"action":"decline","src":"方小子","note":"\nне термин"}` — `note` начинается с перевода строки, что для свободного текста пользователя абсолютно законно. Вызов принимается: `exit 0`, `changed=true`, `state: applied`, `rejected: []`. Записанные байты: ``` rejects: - src: 方小子 note: |4- не термин ``` `seed.DecodeRejects` их не читает: `seed: yaml: line 1: did not find expected key`. После этого **любой** следующий `bank-apply` по книге — включая `--dry-run`, который закон п.7 объявляет безопасной проекцией, — падает с `exit 10`. Тот же `note` на `approve` дверь **отвергает** (`exit 14`, ничего не записано): механизм существует и просто не применён ко второму файлу. **Заказ:** результат, который дверь СОБИРАЕТСЯ записать, обязан пройти round-trip `render → parse` **по обоим документам**, и провал этой проверки — отказ, а не запись. Форму (расширить `documentProblems` вторым аргументом, отдельная функция, что угодно) решаешь сам и аргументируешь. ⚠ **Корень глубже, и его тоже закрой:** `yaml.Marshal` рендерит строку с ведущим `\n` как блочный скаляр с индикатором отступа (`|4-`), который его же строгий декодер не принимает. Это бьёт по ЛЮБОМУ свободному текстовому полю обоих документов, а не только по `note` у `decline`. Решение о том, нормализовать ли такой вход на входе двери или чинить рендер, — твоё, но оно обязано быть общим для всех текстовых полей, а не заплаткой на одно поле. ⚠ И прежде чем чинить: **это поведение библиотеки — сходи в её доку и трекер, не гадай** (гардрейл проекта на аномалии зависимостей). Если это её известный дефект — обход законен и должен нести ссылку; если наше злоупотребление — чини вызов. Формулировку «похоже, баг yaml» без сверки в комментарий не абсорбировать. ⚠ **Фаззер здесь сильнее примера, и его слепота СТРУКТУРНА — вот она.** Оракул `FuzzSeedDocumentRoundTrip` («движок не должен рендерить документ, который сам не читает») **исправен**: приёмка досеяла один вход с ведущим `\n` в `note`, и он падает мгновенно. Не сработал он потому, что фаззится СЫРОЙ YAML — то есть корпус по построению состоит из строк, УЖЕ переживших YAML-разбор, а настоящий вход двери это JSON-строка, на которую YAML не накладывает никаких ограничений. Ни один из семи сидов не несёт многострочного значения вовсе. **Заказ:** фаззить путь ДВЕРИ — произвольные строки полей `Decision` через `ApplyDecisions` в рендер и обратно, — и оракул «что движок ЗАПИСЫВАЕТ, движок ЧИТАЕТ ОБРАТНО» распространить на ОБА документа. ### 4.2 Пред-существующие поломки нельзя чинить по одной — ДЕЛАЙ РОВНО ТАК Гейт «отказывают только НОВЫЕ проблемы» сравнивает **тексты сообщений** (`known[p]`), а вердикт загружаемости приходит ОДНОЙ строкой, несущей ВСЕ битые термы сразу: `ParseBankSeed` копит `problems` по каждому терму и склеивает их в одну ошибку, а `documentProblems` сворачивает эту ошибку в один элемент `[]string`. Поэтому починка одного терма меняет строку ЦЕЛИКОМ, совпадения с `known` больше нет, и вызов отказывает. ⚠ **Это НЕ «загрузчик падает на первой строке» — прежняя редакция промта утверждала так и была неверна (испр. по опровергателю промта).** Разница несущая: чинить надо разложение многосубъектного вердикта на проблемы ПО СУБЪЕКТАМ, а не накопление ошибок в общем загрузчике сида — он уже накапливает, и он читает `glossary_seed` КАЖДОЙ книги. **Репро:** дельта с двумя строками `status: approved, dst: ""`. Решение чинит одну → **`exit 14`**, и текст отказа называет ВТОРУЮ строку, которой решение не касалось. Обе одним документом → `exit 0`. Это прямо опровергает собственный контракт двери (комментарий поля `Preexisting`: «a book whose files already contradict each other has to stay repairable through this door»). **Заказ:** пред-существующая проблема опознаётся по СУБЪЕКТУ (терм/поверхность, которой она касается), а не по тексту сообщения. Проверено приёмкой: несвязанное решение при одной пред-существующей поломке проходит корректно, ломается именно инкрементальная починка НЕСКОЛЬКИХ. ### 4.3 Два ключа на один путь теряют решение молча — ДЕЛАЙ РОВНО ТАК **Репро (воспроизведено приёмкой):** `book.yaml`, объявляющий `mined_delta` и `mined_rejects` на ОДИН путь, файл при этом пустой — то есть парсится обеими схемами. Результат: `exit 0`, оба решения `applied`, а на диске остаётся только reject-лист. Одобрение владельца исчезает молча, и файл после этого не грузится как дельта — следующий прогон падает на разборе конфига. **Заказ:** `config.LoadBook` отказывает, если два пути совпадают. Одна проверка плюс строка отказа. ### 4.4 Гард флага ключён на ЗНАЧЕНИЕ, а не на присутствие — ДЕЛАЙ РОВНО ТАК `invocation.go`: `if *decisions != "" && cmd != "bank-apply"`. Поэтому `--decisions=` с пустым значением гарда не задевает. **Репро:** `tmctl translate --config b.yaml --decisions=` идёт по ПЛАТНОМУ пути ровно как без флага (проверено: обе формы падают на одной и той же поздней стадии). Реальный триггер — `--decisions=$DOC` с незаданной переменной в юните или скрипте. Это ровно класс R2 твоего же §5, который ты назвал «стоил бы ДЕНЕГ», уцелевший в одной форме. **Заказ:** оба гарда (`--decisions`, `--keys-file`) ключить на ПРИСУТСТВИЕ флага, не на непустоту значения. Пустое значение у присутствующего флага — тоже отказ. ⚠ Присутствие даёт **только `fs.Visit`** — он обходит ТОЛЬКО реально переданные флаги; `fs.Lookup` на этот вопрос не отвечает вовсе, он возвращает ОБЪЯВЛЕННЫЙ флаг независимо от того, передавали его или нет. Живой прецедент лежит в том же файле: `ceilingGiven` через `fs.Visit`, с комментарием, объясняющим ровно эту разницу. ⚠ Комментарий над самими гардами УЖЕ говорит «scoped to the flag being PRESENT» — то есть код расходится со своим же комментарием, и это дополнительная улика. ### 4.5 Документ решений не капирован, а OOM движка платформа читает как успех — ДЕЛАЙ РОВНО ТАК инвариант, РЕШИ САМ число `os.ReadFile(decisionsPath)` без ограничения. Замерено приёмкой на документах из одних `approve`: 20 000 решений → 0.73–0.80 ГБ RSS, 40 000 → 1.42 ГБ при документе всего в 2 МБ, то есть ~36 КБ памяти на решение. Разброс на 20 000 — два прогона документов слегка разной формы (с `note` и без), а не две разные оси. ⚠ **И вторая половина, которой в §9 не было:** движок при нехватке памяти выходит **кодом 2** (`fatal error: ... out of memory` — Go так завершается), а платформа читает 2 как `CompletedWithFlags` — «команда СДЕЛАЛА свою работу, часть юнитов на человека» (`platform/internal/ingest/exit.go`, читать, не править). То есть смерть движка от памяти прочитывается как успешный прогон. Класс пред-существующий и общий для всех глаголов — но `bank-apply` первый, чей размер входа задаёт ПОЛЬЗОВАТЕЛЬ. **Заказ:** кап на размер документа решений плюс внятная строка отказа полосой. Число выбираешь сам и аргументируешь; ориентир — платформа капирует свои чтения от движка (16 МБ статус / 256 МБ банк). Хвост про exit 2 — **пингом**, не тихой правкой: он общедвижковый и не твой скоуп. ### 4.6 `artifacts{}` не доезжает до своего потребителя — РЕШИ САМ форму Проверено приёмкой грепом: `Artifacts` существует **только** в `internal/pipeline/status.go`, а единственный код платформы, выводящий движковый путь (`runner.ReadBank` → `projectDB` → `filepath.Join(workdir, book_id+".db")` + суффикс `.bank.json`), зовётся из её read-model, чей интерфейс движка — `{Manifest, Export}`, без `Status` (`platform/internal/readmodel/`, читать, не править). Буква §4.5 прежнего заказа исполнена, **цель строки бэклога 213 — нет**: платформа не может выкинуть свой `projectDB()`, не заведя третий вызов движка на каждую материализацию. **Заказ:** тот же версионированный конверт путей отдать на поверхности, которую потребитель уже читает. `manifest --json` — самый дешёвый кандидат (read-model уже его гоняет), но форму и состав выбираешь ты и аргументируешь. ⚠ Пере-именование банк-экспорта в фикс-имя по-прежнему ЛОМАЮЩЕЕ, его окно — строка 161, сюда не тащить. ⚠ **Твой же пинг §10 п.5** («опубликованный путь читается как приглашение его парсить; единственная изгородь — комментарий») этим углубляется, и это осознанно: закон п.1 разрешает платформе читать по путям, СООБЩЁННЫМ движком, а п.3 требует версионированного конверта на каждом выходе. Изгородью становится конверт, а не комментарий. Если считаешь, что этого мало, — скажи аргументом, это законный ответ по §11. ### 4.7 Отказ по алиасу терма, одобренного в ТОМ ЖЕ вызове, принимается инертным — ДЕЛАЙ РОВНО ТАК Проверка `aliasOwner` смотрит в `res.Delta` ДО цикла применения, поэтому терм, одобряемый решением `i`, ещё не лежит в дельте, когда судится решение `j`. **Репро:** один документ = `approve 方源` + `decline 方小子`, где `方小子` — алиас кластера майнера у `方源`. Оба отчитываются `applied`, `rejected: []`. В дельте `方小子` остаётся ФАЙРЯЩИМ алиасом одобренного терма, а reject — только фильтр предложений. Прозвище продолжает подставляться в переводе, владелец уверен, что снял его. Те же два решения ДВУМЯ вызовами — корректно отвергаются по имени. **Заказ:** правило судит НАБОР, как и заявлено в шапке `ApplyDecisions`, а не только состояние файла до вызова. ### 4.8 Перевёрнутое окно глав принимается — ДЕЛАЙ РОВНО ТАК `resolveDecision` проверяет только `< 0`. **Воспроизведено приёмкой:** решение с `since_chapter: 90, until_chapter: 3` принимается и записывает в дельту строку, которая не сработает ни в одной главе. Отказывать. ### 4.9 Мина 3 строки 199: «применено» ≠ «стоп снимется» — РЕШИ САМ форму `reverseSectionTerms` режет кап 200 ПОСЛЕ фильтров, поэтому на книге с >200 banknote-only поверхностями подпись всей карты стоп НЕ снимет: приедет свежая двухсотка, и актов подписи понадобится `ceil(N/200)`. Механизм против этого паком не заказан и сейчас не заказывается. **Заказывается ЧЕСТНОСТЬ отчёта:** вызов, после которого стоп заведомо не погаснет, не должен читаться как «готово». Форму (поле в отчёте, строка предупреждения, что-то ещё) решаешь сам и аргументируешь; если считаешь, что дверь этого знать не может — скажи это аргументом, и это будет принято. ### 4.10 SIGTERM игнорируется, лок держится — РЕШИ САМ форму Проверено приёмкой: на длинном документе процесс переживает `SIGTERM` и продолжает держать эксклюзивный лок проекта; снимается только `SIGKILL`. Платформа останавливает движок именно `SIGTERM` (`platform/internal/runner/`, читать, не править), а лок блокирует прогон книги. Закон п.2 отводит `SIGTERM` роль «дочти и отпусти» для ПРОГОНА; для $0-глагола аналог — «брось работу и отпусти лок, ничего не записав»: у всё-или-ничего нет частичного результата, который стоило бы дописывать. Форму решаешь сам. ### 4.11 Восемь слепых пятен гейта, десять мутаций — ДЕЛАЙ РОВНО ТАК Каждое ниже — место, где приёмка посадила мутацию и **батарея осталась зелёной**. Живого дефекта в них нет; отсутствует пин. ⚠ Считай по МУТАЦИЯМ, а не по строкам таблицы: строка (д) несёт три независимые посадки, и пин нужен на каждую. Каждый пин обязан краснеть на своей мутации — покажи это в отчёте прогоном. | № | что не сторожится | мутация, которую пин обязан поймать | |---|---|---| | а | **арбитраж лока против ПРОГОНА.** `TestBankApplyRefusesABusyProject` держит лок тем же `store.LockProject`, поэтому проверяет «LockProject конфликтует с LockProject». Инвариант ратифицирован (закон п.2, «тот же flock проекта») | сменить файл лока в `LockProject` (`dbPath + ".lock"` → любой другой) — сегодня батарея зелёная. Пин обязан держать лок так, как его держит ПРОГОН (`store.Open`) | | б | **`canonical_rewrite` и `preexisting_problems`** — два опубликованных поля отчёта, на которых пак держит смягчение двух СВОИХ ЖЕ названных рисков (§9 необратимый пере-рендер, R7 проглоченная поломка) | занулить оба (`rep.CanonicalRewrite = false`, `PreexistingProblems: []string{}`) — сегодня батарея зелёная | | в | **гейт тотальности таблицы отказов слеп к сценарию, который сам называет.** `TestTheRefusalTableIsTotal` держит РУЧНОЙ список классов и сверяет его длину с длиной `refusalExit` | добавить шестой класс в `pipeline.RefusalClass`, не внося его НИ в `refusalExit`, НИ в список теста — сегодня тест ПРОХОДИТ, а класс уехал бы в catch-all 19. Перечисляй классы из ИСХОДНИКА. ⚠ `TestDispatchCommandsCoversTheSwitch` — прецедент ТЕХНИКИ, но не реализации: он ключён на `*ast.BasicLit` и потому слеп к `case` с константой (это его собственный пинг, §10 п.2 отчёта), а значения `RefusalClass` объявлены именно КОНСТАНТАМИ — копирование один-в-один воспроизведёт слепоту. Нужен обход `GenDecl`/`ValueSpec` | | г | **нормализация в REPLACEMENT-половине отказа.** Тесты используют поверхности, на которых `NormalizeSourceKey` — тождество | `nk := text.NormalizeSourceKey(src)` → `nk := src` в `dropTerms`. Пин: одобрен 虫, отказ приходит по 蟲 (традиционная форма) — отказ обязан снять одобрение | | д | **глубокое копирование алиасов в `withRubyAliases`** — ровно класс R4/R5, ради которого поле `Ruby` заведено; **перенос `note` при промоушене**; **рантайм-половина гарда ОБЪЯВЛЕННОГО пути** (`decisionFilePresent(path, declared=true)`) | снять глубокую копию · `if false { t.Note = d.Note }` · `case !declared && ...` → `case ...` — все три сегодня зелены | | е | **абсолютность путей в `artifacts{}` запинена ВАКУУМНО.** Проверка `filepath.IsAbs` в `statusartifacts_test.go` есть, но фикстура даёт уже абсолютный `--config`, поэтому `absPath` в ней ничего не делает и его удаление не краснит. Замерено: с ОТНОСИТЕЛЬНЫМ `--config` `absPath` — несущий, и без него потребитель в другом рабочем каталоге прочитает не тот файл | убрать `absPath` у любого из четырёх путей — сегодня батарея зелёная. Пин обязан звать глагол с относительным `--config` | | ж | **байтовый гейт `changedDoc` не запинен**: мутация `deltaChanged := res.DeltaTouched` (байтовый гейт снят целиком) оставляет и `TestBankApplyRepeatIsAByteNoOp`, и `TestBankApplyWritesTheConventionalFiles` зелёными. Контракт «байтовый no-op» держится сегодня только на `*Touched` из чистого слоя — второй рубеж не защищён | снять байтовое сравнение в `changedDoc` | | з | **правило «файлы решений едут С КНИГОЙ, а не с `project_db`»** не запинено: конвенционный дефолт можно перевесить на каталог `project_db`, и батарея зелёная — во всех тестах два каталога совпадают. Правило несущее: `project_db` может указывать куда угодно, а решения владельца обязаны ехать с книгой в бэкапе и экспорте каталога | перевесить дефолт на `filepath.Dir(b.ProjectDB)`; пин обязан развести два каталога | ⚠ Уже существующие пины, которые тоже придётся починить, вынесены отдельным номером — **§4.15**. ### 4.12 Цепь §8 — ЗАКРЫТА ПРИЁМКОЙ, забери её в репозиторий — ДЕЛАЙ РОВНО ТАК Твой §8 просил приёмку прогнать цепь целиком и объяснял, что своими силами это невозможно: «до стопа надо дойти ОПЛАЧЕННОЙ черновой волной». **Довод оказался шире правды.** В репозитории уже есть харнесс, гоняющий тот же банк-стоп против ФЕЙКОВОГО провайдера за $0 — `internal/pipeline/miningstop_join_test.go`: `setupMiningStopProject` + `newJSONProvider` + `newVerifyRunner` + `runToSignatureStop` + `readSignatureMap`, на них стоит ~30 живых тестов. Приёмка написала и прогнала цепь целиком, и она **работает**: стоп на 2 термах → `bank-apply` промотит dst-несущий и отклоняет WHICH-only → резюм → «empty delta, auto-continuing to the edit wave», редакторская волна отработала. Мина 1 строки 199 (ливлок на `status: auto`) не сработала. **Заказ: этот тест обязан жить в репозитории**, потому что он — единственный, кто видит ливлок уровня ЦЕПИ, которого частичные тесты по построению не видят. Собери его сам по описанию выше: дойти до стопа → разобрать карту подписи `seed.DecodeFile` → построить документ, где термы с `dst` идут в `approve`, а WHICH-only в `decline` → `ApplyBankDecisions` → пере-запустить прогон → убедиться, что стоп ПОГАС и редакторская волна пошла. **Как понять, что собрал верно.** На фикстуре без опций стоп приходит на ДВУХ термах — один с `dst`, один WHICH-only, — то есть документ решений выходит «1 approve + 1 decline», `bank-apply` отвечает `changed=true`, `accepted=2`, а файлы у него КОНВЕНЦИОННЫЕ (ключей фикстура не объявляет). На резюме движок пишет в журнал `skipped_as_declined=1 emitted_by_miner=0` и «empty delta, auto-continuing to the edit wave», после чего редакторская волна отрабатывает. Если у тебя иначе — собрано не то. ⚠ **Ограничение назови в комментарии теста сам:** синтетический контраст меняет, КАКИЕ кандидаты предложены, но не то, проходит ли подписанная строка фильтр подписи. Для вопроса «гаснет ли стоп» это корректно; для замера качества майнера — нет. ⚠ Соседний факт, поймай его при написании: `TestMiningStopRejectClearsDelta` заканчивается проверкой «погасший стоп не оставляет карту подписи», но использует ВТОРОЙ, свежий проект, где карта никогда не писалась, — проверка почти вакуумна. На ОДНОЙ книге карта остаётся на диске: движок её нигде не удаляет (`os.Remove` в `mining.go` не встречается). Поведение пред-существующее, паком не введено — **пинг, не правка**, но пусть твой тест это зафиксирует явно. ### 4.13 Мелочи — РЕШИ САМ, чинить или отклонить с аргументом - **`canonical_rewrite` — ИЛИ по двум файлам**, поэтому предупреждает про файл, который вызов не откроет (замерено: на книге, чьи оба файла записал движок, флаг корректно `false`; горит, когда комментарии оператора несёт reject-лист, а решение трогает только дельту). Потребитель рисует предупреждение не про то. - **`note` нельзя ОЧИСТИТЬ**: повторное решение без `note` сохраняет прежнюю заметку. Заказом не оговорено ни в ту, ни в другую сторону — назови поведение решением, каким бы оно ни было. - **`book_id` не проверяется на путевую безопасность**, а путь строится ДО валидации: `book_id: ../x` уводит оба файла решений выше каталога книги. Класс пред-существующий (`.db`), пак расширил его с одного файла до трёх. На SaaS `book_id` генерируется платформой, то есть пользователю недостижим. - **Каталог книги без права записи → `exit 1`, ВНЕ полосы отказов** (`store: ... attempt to write a readonly database`). Платформа спишет проблему прав ХОСТА в бюджет попыток КНИГИ. Класс пред-существующий (`store.Open` ведёт себя так же), новый глагол его унаследовал. - **Проекция создаёт `.lock`** в каталоге книги. Требование §4.2 «проекция БЕЗ мутации» держится (файлы решений не тронуты), но «не трогает ничего» буквально неверно. - **Порядок двух записей пессимален для `decline`.** Комментарий обосновывает «дельта первой» тем, что крах между записями оставит промоушен без снятого отказа — верно для `approve`. Для `decline` тот же порядок теряет решение ЦЕЛИКОМ: дельта переписана (одобрение снято), reject не записан. ⚠ **Приёмка это НЕ воспроизвела** — атомарная замена обходит права на файл, а настоящее окно микросекундное; повтор вызова при этом сходится. Утверждение остаётся рассуждением, не замером: реши сам, стоит ли оно правки, и аргументируй. - **`artifacts.bank_export` печатается безусловно**, в том числе для книги, где файла ещё нет (проверено на копии стенда). Читатель платформы это переживает (обычная ошибка открытия), но поле именует МЕСТО, а не наличие — реши, достаточно ли этого, и назови решение. - **`aliasOwner` возвращается на ПЕРВОМ совпадении**, поэтому исход `decline` в теории зависит от порядка строк дельты, если поверхность одновременно алиас одной строки и `src` другой. ⚠ Такой документ уже ловится `ApprovedSharedKeyCollisions`, и приёмка значимого расхождения **воспроизвести не смогла** — это гипотеза, а не находка. Отклонить с аргументом законно. - **Проекция отвечает «что применится», а не «почём».** Закон п.7 звучит как «почём ДО подтверждения», прежний заказ денег в проекции не требовал, а смета уже едет в `status --json`. Это вопрос ДИСПОЗИЦИИ, а не дефект исполнения: **не чини, а вынеси пингом** со своей рекомендацией — решать оркестратору. ### 4.14 Довод отказа `--keys-file` не покрывает всех, кого отвергает — ДЕЛАЙ РОВНО ТАК Текст отказа (`cmd/tmctl/invocation.go`) звучит так: «`--keys-file` принимает только `translate`, не %q: **$0-читающие команды (report/status/export/manifest/seed-lint)** не должны требовать ключей вовсе (D20.4)». В ту же ветку попадают `redrive` (**платный** путь: «only a real redrive re-attacks and re-bills»), `backup` и `migrate` (деплой-шаги). Довод их не описывает, и оператор, получивший этот отказ на `redrive`, читает про себя неправду. **Заказ:** довод обязан покрывать всех, кого ветка отвергает. Одна строка. ⚠ Саму развилку «нужен ли `redrive` канал ключей аргументом» **НЕ решай** — прежний промт сказал «флаг только у `translate`», и это исполнено верно; развилка остаётся пингом. ### 4.15 Три уже существующих пина тавтологичны или узки — ДЕЛАЙ РОВНО ТАК Вынесено отдельным номером, чтобы механическая сверка §7 их видела. - **Вердикт `Preexisting` исполняется одной проверкой из четырёх:** `TestAPreExistingCollisionDoesNotBlockAnUnrelatedDecision` гоняет только `ApprovedSharedKeyCollisions`; `MinedDeltaSeedCollisions`, `GenderVocabViolations` и `UnknownVoiceCharacters` не исполнены ни одним тестом. - **Четвёртый оракул `FuzzDecisionDocument`** пере-выполняет ровно то, что `documentProblems` уже выполнила внутри `ApplyDecisions`, и заперт под `len(res.Rejected) == 0` — тавтология. - **`TestDecisionByBankID`** вычисляет ожидаемый id той же функцией, которую проверяет, поэтому не видит потери инъективности — свойства, ради которого в `TermID` и сделана преамбула про length-prefix. Сверяйся с независимо посчитанным значением, а не с самой функцией. ### 4.16 Деньги: платных вызовов в паке НОЛЬ — ДЕЛАЙ РОВНО ТАК Санкции на платные вызовы нет и не запрашивается. Всё выше проверяется юнит-тестами, фейковым провайдером и пробой на копии книги стенда. Упёрся в место, где без денег не доказать, — **пинг с названной суммой, не трата**. Рантайм-вердикт по платному пути остаётся PLAUSIBLE — это ожидаемый исход, а не дефект отчёта; так и напиши. ## 5. Мандат самопроверки ИСПОЛНЕНИЕМ Не «перечитал» — **исполнил**. Минимум: - **Батарея целиком, ИЗ КАТАЛОГА `backend/`**, после каждой содержательной правки: `go build ./...`, `go vet ./...`, `go test ./... -count=1`, **`make lint`**. ⚠ `go.mod` в корне репозитория НЕТ — модули раздельные. ⚠ **И `make battery-stand` — обязательно, минимум один раз в конце.** Пак владеет `internal/standdata` (тест-резолв корпуса), а регрессию в резолвере обычная батарея не ловит по построению: без корпуса эти тесты просто скипаются. Ожидаемая картина — всё зелено, кроме `TestMinerFullBookParity`, красного из-за отсутствующего jieba-контраста (он лежит по внутрирепозиторному пути, но вне git по построению — клон его не получает; это строка бэклога 123, не твой дефект). Красный тест не «подгоняется»: править или удалять тест, голден или гейт ради зелени — НЕДОПУСТИМО; несогласие с тестом — пинг оркестратору, а не правка. ⚠ **В `internal/store` живут два пред-существующих `-race`-флейка** — `TestTheSeamIsNotChargedToTheStoreOperationBudget` и `TestKillMinus9LosesAtMostOneCall`. Приёмка доказала тремя независимыми путями, что они не от пака: правка `store.go` чисто аддитивна, покрытие новых функций под обоими тестами ноль, и чередующийся A/B против дерева без пака даёт ту же частоту. Оба меряют ВРЕМЯ и чувствительны к нагрузке. Красное ИМЕННО в них — шум; красное в любом другом тесте — регрессия. - **Собственные адверсариальные посадки ВНЕ списка §4.11.** Десять названных там — обязательный минимум, а не потолок: посади свои в новый код и покажи, что батарея краснеет. ⚠ **Посадка обязана называть ПАКЕТ, в котором ищется пин** — не тот, где лежит правка: ложное «мутация выжила» опаснее пропущенной, потому что выглядит как результат. ⚠ **Харнесс мутаций обязан снимать отпечаток целей до и после** — твой же §11 п.1 объясняет цену. - **Живая проба на копии книги стенда** (`<репозиторий>/books/gu-zhenren/*`). Работать в КОПИИ вне рабочего дерева; правки в `books/` не делать вовсе — это отдельный git-репозиторий и не твоя зона. - **Субагенты РАЗРЕШЕНЫ явно** — на ревью своего кода, на поиск дефектов вне твоей карты, на опровержение твоих же выводов. Дефолт-запрет харнесса иначе тихо победит. ⚠ Соразмеряй параллельность с ресурсами: субагенты, одновременно гоняющие полную батарею, топят друг друга, и упавший от нагрузки агент выглядит как отсутствие находок. - **Артефакт-находки обязателен:** дописывай `backend/docs/SEAM_PACK_FINDINGS.md` новой секцией (не переписывай старые — они улика приёмки), что посадил, что краснело, что выжило. ### 5.1 Обязательное для кодового пака - **Последний абзац отчёта — это план или обещание? Значит сделай его СЕЙЧАС.** - **Дифф `^func Test` — исполнением, а не по памяти:** покажи командой, какие тесты добавлены и тронуты. - **Интервальная самоверификация субагентом** против ЯВНЫХ критериев в середине работы. - **Перед отчётом сверь каждый клейм с результатом инструмента ЭТОЙ сессии.** ## 6. Оси ревью (три, по характеру работы) 1. **Round-trip формата** — главная ось этого дофикса: всё, что дверь ЗАПИСЫВАЕТ, она обязана ПРОЧИТАТЬ обратно, на обоих документах и на всех текстовых полях. Фаззер здесь сильнее примера. 2. **Слепота гейта** — для каждой правки: какой мутацией она сторожится и краснеет ли пин НА САМОМ ДЕЛЕ. Восемь мест §4.11 — это там, где гейт уже оказался слепым; не заведи девятое. 3. **Общность** — заработает ли на паре, которой в репо нет, без правки Go. Приёмка проверила дверь на `en→de` и `ja→en` и нашла её чистой; твои правки не должны это сломать. Ты вправе ЗАМЕНИТЬ любую ось своей, если аргументируешь, чем твоя лучше ловит дефекты этой работы. ## 7. Записка-план До кода — короткая записка `backend/docs/SEAM_FIX_PLAN.md`: что делаешь, в каком порядке, какие развилки видишь. Комплектность против §4 сверяется МЕХАНИЧЕСКИ (таблицей или грепом), а не глазами: **пунктов шестнадцать** (`grep -cE '^### 4\.' `), и «кажется, всё» здесь не работает. Заказов ВНЕ §4 нет — всё, что раньше жило в приложениях и в хвостах, вынесено в §4 отдельными номерами (§4.14, §4.15); приложения несут только справку и пинги. ## 8. Заявление = команда Каждое число и каждая категорика в отчёте — **с командой, которой получены, прямо рядом**. Не по памяти о том, как ты это выяснял. Приёмка пере-ранит выборочно; расхождение отчёта с пере-раном дороже, чем честное «не проверял». ⚠ Прошлый отчёт дал повод: его замер цены глагола (`5000 → 1.60с/133МБ · 20000 → 9.08с/350МБ`) при пере-ране приёмки сошёлся по времени на 5k/10k и разошёлся вдвое по ПАМЯТИ (199/395/798 МБ), а выведенный из него потолок «до ~80k термов в 60-секундный бюджет платформы» оказался оптимистичен примерно вдвое. Числа, которые поедут в чужой пак как датум, стоят отдельной аккуратности. ## 9. Эхо-протокол старта ДО работы — не больше десяти строк: скоуп как ты его понял · инварианты, которые не тронешь · что делать НЕ будешь. Расхождение с этим промтом видно сразу и стоит дёшево. ## 10. Что НЕ удалось Обязательная секция отчёта. «Не проверено» ≠ «работает». Вердикт о рантайме без живого прогона — максимум PLAUSIBLE, и так и пиши. ## 11. Канал вопросов Конфликт промта с кодом или доками — **пинг через владельца, не интерпретация**. Ты вправе сказать «этого делать не надо» с аргументом: пак, принёсший развилку вместо тихой девиации, — хороший пак. Четыре пункта выше (§4.6, §4.9, §4.10, §4.13) прямо приглашают такой ответ. --- ## Приложение: что приёмка ПОДТВЕРДИЛА — не чини это заново Чтобы дофикс не тратил время на уже доказанное: - **Цепь работает** (§4.12) · **мины 1 и 2 строки 199 обезврежены** (явный `status: approved` пишется; approve без `dst` отвергается). - **§4.1–§4.7 прежнего заказа исполнены полностью**, все девять семантик §4.2 — тоже, семь пунктов закона — тоже; скоуп-крипа нет. Пять вещей сверх заказа (класс 14, `export_version`, пакет `standdata`, строгость reject-читателя, два поля отчёта) — аргументированы и безопасны. - **Ни один платформенный канал не ломается**: аддитивные поля прошли через её собственные декодеры на реальных документах; строгих декодеров у неё нет ни одного на пути движковых документов; id термов банка не сдвинулись; `Refused(14)` истинно, `DeploymentFault` 14 не покрывает, а интейк отправляет нераспознанный код полосы в ветку, которая файл пользователя НЕ удаляет. - **Строгость не сломала ни один живой документ корпуса** — 44 из 44 (29 сид-схемы + 15 reject-схемы). - **Критерий §4.6 ПРЕЖНЕГО заказа (дефолт корпуса через `standdata`) выполнен буквально** — не путать с §4.6 ЭТОГО промта, который про другое; **§13 сходится точно** (59 тест-функций + 2 фаззера, удалено 0); все 28 пинов, названных отчётом, существуют ровно по одному разу. - **$0 доказан** структурно и исполнением; ледджер и база не сдвинулись ни на байт. - **Три заявки ревьюеров приёмка ОПРОВЕРГЛА, не поднимай их заново:** src, нормализующийся в пустой ключ, ловится дедупом поверхностей · повторный `decline` корректно отчитывается `already_applied` · отказ по алиасу подписанного сид-терма отвергается по имени. ## Приложение: пинги, которые дофикс НЕ исполняет Названы, чтобы ты их не «допилил попутно»: - **exit 2 при OOM читается платформой как успех** (§4.5) — общедвижковый класс, не твой скоуп. - **`readEngine` платформы выбрасывает stdout при любом ненулевом коде, кроме 2** — а отчёт `bank-apply` при `exit 14` и есть продукт вызова. Требование к ПЛАТФОРМЕННОМУ паку. - **Развилка «нужен ли `redrive` канал ключей аргументом»** — остаётся пингом. Заказом стала только вторая половина этого пинга — неполный довод в тексте отказа, §4.14. - **`rubyToCandidates` мёртв в проде** · **гейт `TestDispatchCommandsCoversTheSwitch` слеп к `case` с константой** (⚠ эта слепота названа в §4.11 (в) как ловушка при копировании техники — не унаследуй её) · **схема сида без `omitempty`** — три пинга твоего же §10, остаются пингами. - **Проекция берёт ТОТ ЖЕ лок, что и применение** (§10 п.4) ⇒ «поправить термин при живом прогоне» даёт `12` даже на ПРЕВЬЮ, на всю длину прогона. Проектируется ПЛАТФОРМЕННЫМ паком (его окно — состояние между прогонами, когда движок вышел и лок отпущен), не движковым. - **Опубликованные пути двух файлов решений читаются как приглашение их парсить** (§10 п.5) — ⚠ см. §4.6: изгородь снимается сознательно и по ратифицированному направлению закона. - **Карта подписи не удаляется после погасшего стопа** (§4.12) — пред-существующее, пинг. --- ## Записка от бэкенд-сессии ПАКА (написана до компакта её контекста, 25.08) Я — та сессия, чей пак не приняли. Промт я прочитал целиком и **верифицировал исполнением** ключевые клеймы, прежде чем писать это. Ниже не инструкции: у тебя есть преимущество, которого нет у меня — взгляд с нуля, и я не хочу его портить. Здесь только то, что дорого узнавать заново. ### Что я ПЕРЕПРОВЕРИЛ САМ и подтвердил (не трать на это время) Воспроизведено моими прогонами на этом же дереве: **§4.1 блокер** (`note` с ведущим `\n` у `decline` → записывается `note: |4-`, `seed.DecodeRejects` не читает) · **§4.2** (починка ОДНОЙ из двух битых строк → отказ, обеих сразу → `exit 0`) · **§4.4** (все три формы обходят гард: `--decisions=`, `--decisions ""`, `--keys-file=`) · **§4.7** (approve `方源` + decline `方小子` одним вызовом → оба `applied`, `rejected: []`) · **§4.8** (окно `[90,3]` принимается). ### ⚠ Единственное место, где промт ШИРЕ замера — проверь до того, как строить фикс §4.1 говорит, что корень «бьёт по ЛЮБОМУ свободному текстовому полю обоих документов». **Мой замер этого не показывает.** Прогнал пять форм значения через рендер и обратно, оба документа: ``` note="\nтекст" дельта=ломается rejects=ломается note="текст\n" дельта=ок rejects=ок note="a\nb" дельта=ок rejects=ок note=" пробел" дельта=ок rejects=ок note="\tтаб" дельта=ок rejects=ок ``` То есть триггер — **ведущий перевод строки**, а не «любой свободный текст» и не пробельность вообще. Отдельно: `dst` и `sense` с тем же ведущим `\n` у меня round-trip НЕ сломали, а `note` — сломал, и я это не объяснил. Практический вывод, и он усиливает заказ, а не отменяет его: **не пытайся перечислить ломающие входы — их множество я измерил и оно не совпало с интуицией промта.** Чини ИНВАРИАНТ (round-trip на обоих документах), а не набор полей. Заплатка на «поля с ведущим `\n`» будет выглядеть рабочей и промахнётся по следующей форме. ### Инструмент, которого у тебя не будет, а он нужен **Харнесс мутаций жил в скретчпаде сессии, не в репозитории.** Он умер вместе с ней. Тебе придётся писать его заново, и §4.11 требует десять посадок. Совет: сразу закладывай снятие sha256 целей до прогона и проверку после — у меня прогон убило таймаутом, `finally` не отработал, посадка просидела в дереве ~40 минут, выглядя продакшн-кодом, и сторонний ревьюер снял с неё замеры и доложил несуществующий дефект. Поймал её мой же пин. Подумай, не место ли такому харнессу в репозитории — приёмка теперь тоже сажает мутации, а инструмент каждый раз пишется с нуля. ### Три вещи, на которых я обжёгся, и они повторятся 1. **Одиночный прогон — не замер.** Атрибуцию флейков я дважды вывел из одного прогона и оба раза получил противоположный ответ. Решает только чередующийся A/B на двух деревьях (`git worktree` на HEAD — рабочее дерево при этом не трогается, `checkout` поверх грязного запрещён). 2. **Субагенты пишут в твоё дерево.** Ревью-агенты оставили `zz*`/`*scratch*`/`refuter_*` в `internal/`, часть удалили за собой, часть нет; один раз это уронило батарею. Сверяйся `git ls-files --others` перед каждым «финальным» прогоном, и не грепай по `zz` — попадёшь в `Fuzz`. 3. **Красное в `internal/store` — почти наверняка не ты** (два `-race`-флейка, §5 промта). Но проверь это ЗАМЕРОМ против HEAD, а не поверь мне на слово: я один раз получил обратную картину. ### Мои сомнения по заказу — там, где §11 приглашает спорить - **§4.9 (честность отчёта про непогасший стоп).** Я не уверен, что дверь это ЗНАЕТ. Она видит, сколько решений применила, но не сколько поверхностей осталось за капом реверс-секции — это знает майнер на следующем проходе. Скорее всего честное поле звучит как «этот вызов решил N; погаснет ли стоп, определит следующий майнинг», а не как предсказание. Если придёшь к тому же — это законный ответ. - **§4.10 (SIGTERM) и §4.5 (кап) взаимодействуют.** На реальной книге вызов — десятки миллисекунд; лок держится долго только на патологически большом документе, который §4.5 и запрещает. Возможно, кап делает половину проблемы несуществующей, и тогда обработчик сигнала — механизм без носителя. Замерь длительность ПОСЛЕ капа, прежде чем решать. - **§4.6 (пути в `manifest --json`).** Заказ разумен по потребителю, но семантически манифест — про дерево глав, и путь артефакта там гость. Я бы это сделал и назвал компромисс вслух, а не молча. - **§4.13 «`note` нельзя очистить»** — реши осознанно. Сейчас пустой `note` в решении означает «не трогай», и это было намеренно (иначе каждое решение стирало бы провенанс владельца). Обратное поведение тоже защитимо; плохо только не назвать. ### Про числа Мой замер памяти (`133/217/350 МБ`) разошёлся с приёмочным (`199/395/798 МБ`) примерно вдвое. **Я не знаю, кто прав**, и не выяснил — методику я не зафиксировал (`/usr/bin/time -f %M`, машина под нагрузкой). Не наследуй ни моё, ни их: пере-мерь со своей названной методикой, потому что это число поедет в платформенный пак как датум для таймаута. ### Главный урок, если брать один Я потратил два адверсариальных ревью на **рассуждение** — они нашли настоящие дефекты, но дефект, который стоил бы ДЕНЕГ (`--decisions` принимался всеми командами), нашёлся, когда я перепроверил собственный скоуп **исполнением**. А §4.11 этого промта — восемь мест, где мой гейт оказался слепым, и я узнал об этом от приёмки. Отсюда единственный совет по методу: **сначала посади мутацию, потом пиши пин.** Пин, написанный первым, почти всегда проверяет то, что и так верно. — бэкенд-сессия пака, до компакта ### Онбординг в САМ КОД — этого нет ни в промте, ни в `FINDINGS`, а искать заново дорого **Дверь двухслойная, и порядок чтения не произвольный:** 1. `internal/membank/decisions.go` — **чистая логика**, без I/O, часов и рандома. Здесь вся семантика: что принимается, что отвергается, чем отказ обоснован. Сюда садятся §4.1, §4.2, §4.7, §4.8 и бо́льшая часть §4.11. 2. `internal/pipeline/bankdecisions.go` — **обвязка**: лок, чтения, два гейта записи на файл, отчёт. Сюда садятся §4.5 (кап), §4.10 (сигнал), §4.6 (если решишь трогать `status`/`manifest`). 3. `cmd/tmctl/bankapply.go` — тонкий принт отчёта. `cmd/tmctl/invocation.go` — §4.4 и §4.14. Обратный порядок даёт ложное впечатление, что дверь про файлы. Она про решения; файлы — следствие. **Карта «предмет → где пин»** (пригодится, когда будешь чинить и захочешь понять, что сломал): `membank/decisions_test.go` — семантика решений · `membank/decisions_fuzz_test.go` — round-trip и оракулы набора · `pipeline/bankdecisions_test.go` — лок, гейты записи, классы отказа, «прогон читает конвенционный файл» · `pipeline/statusartifacts_test.go` — `artifacts{}`/версии · `config/decisionpaths_test.go` — конвенционные дефолты · `cmd/tmctl/keysfile_test.go` — гарды флагов и e2e через реальный бинарь · `seed/decode_test.go` — строгость и пустые документы. **Инварианты, которые легко сломать ПОПУТНО, чиня §4** (у каждого сегодня есть пин, кроме отмеченных в §4.11 как слепые): - `res.DeltaTouched` / `res.RejectsTouched` — гейты **на файл**, не на вызов. Их смысл: `decline` не должен пере-рендеривать дельту, которой не касался. Если будешь трогать `applyOne`, следи, чтобы флаг ставился ровно тем действием, которое реально изменило документ. - `newDocumentProblems` сравнивает **до/после**, и это единственное, что не даёт пред-существующей поломке блокировать починку. §4.2 просит поменять КЛЮЧ сравнения (субъект вместо текста), а не выкинуть сравнение. - `withRubyAliases` делает **глубокую** копию алиасов: `AttachRubyAliasesToManual` мутирует на месте, а входные строки сида читаются ещё раз в «до». Мелкая копия ломает сравнение до/после незаметно. - `mergeTerm` мержит на существующую строку, а не заменяет её — иначе решение о `dst` стирает рукописный `gender`, которого во входном словаре двери нет вовсе. ### Как заново собрать живую пробу на стенде (я потратил на это несколько заходов вслепую) Копия ОБЯЗАТЕЛЬНА вне рабочего дерева; в `books/` не писать. Три ловушки, каждая стоила мне прогона: 1. `cp -r books/gu-zhenren/minirun-verify2 /live` — внутри **все пути абсолютные и мёртвые** (`/home/ubuntu/...`, машина переехала). Переписывать надо в **ТРЁХ** файлах: `book.yaml`, `pipeline.yaml` **и `pairs/zh-ru.yaml`** — в последнем лежит `prompts_root`, и про него забываешь, потому что промпты «вроде рядом». 2. База стенда — **схема v11**, бинарь ждёт v15. Первый же вызов даёт `exit 13`. Это НЕ дефект: сделай `tmctl migrate --config <копия>/book.yaml` на копии. (Заодно это единственный способ увидеть ратифицированный класс 13 живым — юнит-тесты всегда создают базу свежей.) 3. Банк-стоп на этой машине **не воспроизводится** — `pipeline.yaml` требует jieba-контраст, которого в клоне нет по построению (строка 123). Для цепи §4.12 используй фикстуру `internal/pipeline/miningstop_join_test.go`, а не стендовую книгу: приёмка прямо говорит, что харнесс там уже есть и цепь на нём гоняется за $0. ### Чего в моей памяти было, а в документах нет — выгружаю здесь - **Почему `sameTerm` сравнивает YAML-рендер, а не `reflect.DeepEqual`:** вопрос функции — «изменится ли ФАЙЛ», а `DeepEqual` различает nil и пустой слайс, чего у формата нет, и дал бы ложное «изменилось». Это в комментарии кода есть, но если будешь «упрощать» — знай, что это не небрежность. - **Почему словарь решений именно такой** (`note` принимается и не публикуется, `aliases` публикуются и не принимаются): развёрнуто в комментарии `Decision`. Короткий слоган «вход = выход» НЕВЕРЕН, и «починка» кода под слоган сломает дверь в обе стороны. - **Класс 14 заводился с проверкой чужой стороны:** `DeploymentFault` платформы покрывает 10/12/13 одинаково, а зовут её только пути интейка — поэтому мис-классификация была перспективной, а не живой. Если будешь трогать полосу, эта проверка делается одним грепом по `platform/internal/ingest/exit.go`. - **`make battery` не детерминированно зелёная** (~1 падение на 2–4 полных `-race` прогона, всегда в одном из двух названных store-тестов). Первый абзац §1 `FINDINGS` про это; прочти ДО первого прогона, иначе отнесёшь красное на себя. — бэкенд-сессия пака, до компакта (дополнение)