textmachine/docs/archive/PROGRESS-2026-08-backend.md

623 lines
89 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.

# АРХИВ: секция «Бэкенд» журнала прогресса, эра 2428.08.2026
> ⚠ **ИСТОРИЧЕСКОЕ. Инструкции отсюда НЕ ИСПОЛНЯЮТСЯ.** Это вынесенная 28.08 прожитая проза зонной
> секции `docs/PROGRESS.md` — пять итогов бэкенд-сессий и их записки-планы. **ВСЕ описанные здесь паки
> ЗАЛЕНДЖЕНЫ**, и каждый «дерево готово, НЕ закоммичено» относится к состоянию, которого больше нет.
>
> **Куда что уехало:** входная дверь шва (2427.08, четыре сессии) — `D39.158`, лендинг `d1eb8a9`;
> пак «тихая порча» (28.08) — `D39.164`, лендинг `7d0c6f2`.
>
> ⚠ **Остатки, которые здесь названы, живут СТРОКАМИ единого бэклога, а не этим файлом:** **228**
> (алиас возвращает отклонённую поверхность), **229** (снапшот не фолдит модель внутренних гейтов),
> **230** (размен сходимости против поучения), **231** (смета пере-прохода), **141** (остаток
> детектора сносок), **131** (кап инъекции не считает маркер). Вендор-вердикт по Gemini живёт
> ПИНГОМ ПОЛИГОНУ в живом журнале — здесь его копия, не носитель.
>
> Вынесено аудитом документации 28.08 (D39.167) по слову владельца «пройденную прозу можно сносить».
**⟶ Состояние бэкенда — CURRENT-STATE (один носитель, D39.80); здесь только пинги живых сессий.**
**✅ ИТОГ БЭКЕНД-СЕССИИ 28.08 — ПАК «ТИХАЯ ПОРЧА» (`docs/BACKEND_SILENT_HARM_SESSION_PROMPT.md`), все три пункта. Дерево готово, НЕ закоммичено.**
**§6 КОМПЛЕКТНОСТЬ — пункт → что сделано → каким ИСПОЛНЕНИЕМ подтверждено.**
| Заказ | Что сделано | Чем подтверждено ИСПОЛНЕНИЕМ |
|---|---|---|
| **§3.1** инъекция банка доезжает на каждом провайдере либо громкий отказ | Новая ось `llm.Capability.SystemMessages` (`multi`\|`single`, `capability.go:87-127,158-165`); под `single` `toOpenAIMessages` СКЛЕИВАЕТ ведущий системный ряд в одно сообщение (`httpllm.go:513-559`) и падает громко на системном посреди диалога — зеркально Anthropic; YAML-ось `capabilities.system_messages` с валидацией enum (`config/models.go:101-106,566-578,646-650`); квирк объявлен у провайдера `gemini` с вендор-ссылкой и датой (`configs/models.yaml:120-137`) | 6 тестов `internal/llm/systemmessages_test.go` + сквозной на ОБОИХ волнах `internal/pipeline/systemmessages_wave_test.go`. Мутация «ось игнорируется» → падает `TestSingleSystemEndpointCarriesTheBankOnBothWaves` обеими под-ветками и `…CarriesTheInjection` с текстом `got 2 (roles [system system user])`. Мутация «ось теряется по дороге из YAML» → тот же тест. Каталог: `SH1`, `SH2` |
| **§3.2** дыра выдачи видна читателю файла | ⚠ **Заказ пере-формулирован по находке** (разрешено оркестратором 28.08): маркер уже существовал и ВРАЛ. `ChunkOutcome`/`ChunkExport` несут `DroppedMembers` (`bookrun.go:69-75`, `export.go:45-51`, `waverun.go:488`, `export.go:227-230`); `cmd/tmctl/render.go` печатает маркер В ПОТОКЕ ТЕКСТА (`gapMarker`, :26-63) в обоих рендерах и честные счётчики `withheld`/`incomplete` | Сквозной прогон БОЕВОГО бинаря `cmd/tmctl/exportgap_cli_test.go`: `tmctl translate``tmctl export --plaintext`, текст ДО и ПОСЛЕ на ОДНОМ состоянии (ниже). Матрица семи состояний. Мутации `SH3` (маркер пуст) и `SH4` (счёт выпавших не ведётся) |
| **§3.3** повтор принятого документа сходится | `refuseSeedConflicts` больше не срабатывает, когда отказ УЖЕ на записи (`membank/decisions.go:365-393`, новый `rejectsHoldSurface` :690-700); порядок записи в `writeDecisionFiles` изменён на REJECTS-FIRST с обоснованием (`bankdecisions.go:427-441,458-489`) | 5 тестов `membank/decisions_converge_test.go` + 3 дверных `pipeline/bankdecisions_converge_test.go` (реальные файлы, реальные классы отказа). Мутации `SH5` (сужение снято) и `SH6` (порядок возвращён) |
**РЕШЕНИЯ ПО ПРОПУСКАМ И РОДНЕ — строкой каждое, как просил оркестратор.**
- **Четвёртая дыра (строка 141) — ВЗЯТА БЕСПЛАТНО, работы не потребовала.** Проверено исполнением
(проба по `internal/checks`, файл удалён после замера): сноска `[1] 蛊真人 — название романа.`
мимо детектора примечаний идёт, но поднимает класс `CJKLeak`, а он `CosmeticOnly`
(`checks/sanitizer.go:161-163`) ⇒ `StripCosmetic` вырезает ханьцзы, юнит уходит
`sanitizer_stripped`, и баннер «leak cleaned, verify» для НЕГО правдив. Механизм этого же заказа
накрывает класс без правки детектора — граница промта соблюдена буквально.
⚠ Смежное, НЕ взятое и названное строкой: сноска БЕЗ ханьцзы (`[1] Название романа.`) не поднимает
ни одного класса (замер: `total=0`), едет как есть — но текста при этом НЕ теряет, это лишняя
добавка, а не дыра. Маркер на ней дезинформировал бы (⛔ анти-скоуп промта). Ловля таких сносок —
работа В ДЕТЕКТОРЕ, промт её прямо запретил.
- **Д1 — НЕ ВЗЯТ, отдельным предметом.** Право §9. Канон определяет decline ПОВЕРХНОСТНО
(«declining a surface removes EVERY window of that surface», `14-api-contract/openapi.yaml:2051`),
и расширять его до энтити значило бы менять ратифицированный контракт из бэкенд-сессии. Вторая
половина Д1 («память глушит стоп») — уже НАЗВАННЫЙ и принятый двусторонний остаток в комментарии
`mining.go:308-313`, а не необнаруженный дефект. ⚠ Но одна траектория Д1 реальна и требует
ЧУЖОГО решения: эмиссия майнера энтити-широка (`miner_emit.go` `clusterTouches`), а фильтр
авто-банка ключуется только по собственному src строки (`mining.go:662`), поэтому ОТКЛОНЁННАЯ
поверхность может вернуться алиасом уцелевшей строки. Это противоречит канону уже сегодня.
**Не чиню в этом паке** (правильная форма — снять алиас со строки, а не снести строку; это
предмет банковой онтологии, а не тихой порчи) — **но это заказ, который стоит выдать.**
- **Д3 — НЕ ВЗЯТ, вне моей зоны.** `LiftBankStop` в `backend/` не существует ни в каком написании;
символ живёт в `platform/internal/runs/spawn.go` и `reconcile.go`, то есть в зоне, запертой паком
P9. Движковая половина при этом здорова: порядок «карта → память» ратифицирован и исполнен
(`mining.go:191` пишет карту, `:274` метит память), отказ памяти оценён в комментарии `:275-276`
как «one extra stop, nothing lost». ⚠ Поправка к моему же раннему пингу: я сказал «гарда не
существует» — точнее «не существует В ДВИЖКЕ».
- **Сосед 131 — НАЗВАН, не чиню.** Кап инъекции считает только `src → dst`
(`membank/memory.go:517-521` `glossaryLineTokens`), а рендер дописывает к КАЖДОЙ строке
`tx.UnverifiedMarker` (`:706`) либо `genderConstraintNote(...)` (`:711`), а редакторский блок ещё и
префикс `- ` с кавычками-ёлочками (`:821`). То есть отгружаемая строка строго длиннее той, за
которую платит бюджет; на коротких именных строках перерасход велик. Тема денег и качества
инъекции — не тихий брак в тексте.
**НАХОДКИ, МЕНЯЮЩИЕ ПРОМТ (все три подписаны, ни одна не абсорбирована молча).**
1. **§3.2: маркер существовал и ВРАЛ.** Ветка `case ce.FinalText != ""` печатала «(leak cleaned,
verify)» ЛЮБОМУ флагнутому юниту с текстом, а c-lite member-drop (`export.go` ставит `DispFlagged`
при непустом тексте) попадал ровно в неё. Существующий тест не ловил: покрывал ветку только
причиной `sanitizer_stripped` (`export_cli_test.go:66`). Предмет заказа изменён с «завести маркер»
на «перестать врать и покрыть непокрытое» — разрешено оркестратором 28.08 при закрытом гейте.
2. **§3.2: подсказка промта про место правки была опасна.** Промт указывал `export.go:237` рядом с
`ApplyHeading`. Туда нельзя: `ChunkExport.FinalText` — поле артефактного контракта, а провод судит
`translated`/`withheld` предикатом «текст непуст» (`waverun.go:203-204` и `events.go:538-554`,
`runevents.go:130` `Shipped`). Маркер в пустом юните перевернул бы `withheld``translated`
ровно ⛔(а) промта. `ApplyHeading` этого не делает лишь потому, что на пустом тексте он no-op.
Маркер живёт в РЕНДЕР-слое. Проверено: `FlagReason`-константы до и после ИДЕНТИЧНЫ, `exportVersion`
не двигался, `dropped_members` аддитивно и `omitempty`.
3. **§3.1: посылка «Gemini принимает ровно одно» отозвана автором** (оркестратор, 28.08) — вендор-
вердикт со ссылками и датами отправлен ему отдельно, для `00-provider-quirks.md`. Кратко:
структурная посылка ПОДТВЕРЖДЕНА (`systemInstruction` — один `object (Content)`, `Content.role`
{user, model}), поведение шима вендором НЕ документировано нигде, наш замер даёт «не склеиваются»
и «инструкция первого не исполнена»; «выживает одно или ноль» остаётся ОТКРЫТЫМ и для решения
безразлично.
**ДЕФЕКТ, КОТОРЫЙ Я ВНЁС САМ И ЗАКРЫЛ (называю, потому что он показателен).** Первая редакция ветки
маркера срабатывала по `DroppedMembers > 0` БЕЗ проверки непустого текста. Юнит, у которого выпали ВСЕ
члены, ship-ит пустоту — и получил бы баннер «(INCOMPLETE)» с фразой «фрагмент отсутствует в тексте
НИЖЕ», указывающей на несуществующий текст. То есть я едва не заменил одну ложь читателю на другую.
Найдено собственным разбором ветвлений до опровергателей, закрыто гардом на обоих сайтах вызова,
покрыто состоянием «выпали все» в матрице и запинено мутацией (снятие гарда → тест падает).
**§5 ОСИ РЕВЮ.**
1. **Читатель книги.** Все четыре класса проверены на ОТГРУЖЕННОМ ТЕКСТЕ, не на структурах:
(а) c-lite — был помечен ложно, теперь помечен верно и в потоке текста; (б) юнит целиком withheld и
(в) `glossary_miss` — помечены верно и раньше, работы не требовали, теперь ещё и не попадают под
маркер по ошибке; (г) сноска с ханьцзы — накрыта существующим верным маркером.
2. **Пара, которой в репо нет.** Правка не ветвится по паре ни в одном месте: ось `system_messages`
свойство ЭНДПОЙНТА, маркер — английский операторский словарь той же поверхности, что и соседние
баннеры, поэтому у книги с любым `target_lang` и БЕЗ langpack вывод байт-идентичен. Специально НЕ
стал класть маркер в langpack: `InjectionTextsFor` для незнакомой цели возвращает нулевое значение
молча (`lang/embedded.go:411-423`) — маркер бы исчез, и дыра снова стала бы тихой ровно для той
пары, ради которой мандат общности и написан.
3. **Детерминизм.** Голден зелёный (`go test ./internal/pipeline/ -run TestGolden -count=1`), и это
сильная улика: голден пинит ПОЛНОЕ тело запроса с обоими системными сообщениями
(`testdata/golden/capture.golden:110`), то есть глобальная свёртка сделала бы его красным, а
квирк-свёртка не трогает никого, кто квирк не объявил. `omitempty` на новом поле запинен тестом
(`TestCapabilityWithoutTheAxisMarshalsUnchanged` сверяет БАЙТЫ). Снапшот фолдит Capability только
праймари и `escalate_to` (`snapshot.go:316-340`), а в `pipeline-c1.yaml` это `deepseek-v4-pro`
(`:60`) — gemini там лишь третий в цепочке при `budget_usd: 0` и SINGLE-HOP раннере ⇒ снапшот
боевого конфига не двигается, перекупки нет.
**§8 OBSTACLE — что НЕ удалось и что НЕ проверено.**
- **Живого вызова к Gemini НЕ было** (пак платных вызовов не требует, и я их не делал). Что склейка
ПОМОГАЕТ, доказано структурно вендор-докой и логикой «при любом чтении, кроме склеивания, два
системных теряют текст» — но НЕ замерено на проводе. Решающая проба стоит денег и принадлежит
полигону: маркер на ПОСЛЕДНЕЕ системное + user-only контроль закрыли бы открытый вопрос
«выживает одно или ноль». Также: проба полигона гонялась на `gemini-3.1-flash-lite`, а в цепочке
стоит `gemini-3.1-pro-preview` — слой тот же, слаг другой, перенос не замерен.
- **Достижимость §3.1 сегодня НУЛЕВАЯ, и это не смягчение дефекта, а его честная рамка.** Раннер
исполняет SINGLE-HOP, `escalate_to` боевого конфига — `deepseek-v4-pro`, `budget_usd: 0`. Gemini не
достигается ни одним шиппинг-конфигом; дефект ждал первого конфига, который назовёт его в
`escalate_to` или в `chain[0]` лейбла. Кривая пост-чек-промахов на эскалационном хопе, которую §4.2
просил как второй замер, СНЯТА БЫТЬ НЕ МОЖЕТ по той же причине — мерить нечего.
- **Ловушка, которую я НЕ чинил и обязан назвать: `capabilities` у провайдера с `kind: anthropic`
принимается валидацией и молча игнорируется** — `clients.go` не передаёт `Cap` в Anthropic-клиент
вовсе. Это ПРЕД-СУЩЕСТВУЮЩИЙ класс (так ведут себя ВСЕ оси: `budget_field`, `temperature`,
`reasoning`, `min_max_tokens`), моя ось лишь наследует его. Чинить правильно — тащить `kind` в
`validateCapabilities`, что трогает каждую ось; в этом паке это чужой предмет. Практический риск
сегодня нулевой: провайдера `anthropic` в `models.yaml` нет.
- **Изменён СУЩЕСТВУЮЩИЙ тест — заявляю прямо, не прячу в дифе.**
`internal/pipeline/bankreport_test.go` `TestHalfLandedIsNamedPerFile` кодировал ПОРЯДОК переименований
в своей фикстуре (занимал путь rejects, чтобы упал второй rename). Порядок изменён заказом §3.3, и
фикстура пере-нацелена на путь delta. Утверждение НЕ ослаблено: по-прежнему требуется, что ровно
одна половина легла, что это ПРОВЕРЕННЫЕ байты и что отчёт называет какая. Сверх того добавлен
`TestTheRenameOrderIsWhatMakesADeclineRecover`, который пинит САМ порядок вместе с его причиной,
чтобы его нельзя было вернуть как «уборку». **Если оркестратор считает это подгонкой — я готов
откатить и вынести §3.3-половину с полусостоянием отдельным заказом.**
- **Каталог мутаций: запись `AJ-seed-alias-decline` пере-заякорена.** Моя правка сдвинула строку, на
которую она указывала, и инструмент падал жёсткой ошибкой «the source moved under the catalogue» —
это был бы хард-блокер для следующей сессии. Атакуемое свойство сохранено дословно (снять
дельта-оговорку, оставить остальное); плюс добавлены шесть СВОИХ посадок `SH1``SH6`, по две на
пункт, чтобы фикс был постоянным гейтом, а не разовым замером.
- **Не проверено:** поведение под реальным параллелизмом двух `bank-apply` (лок это исключает, но я
не гонял); поведение склейки на провайдере, который принимает одно системное И имеет префиксный кеш
(у gemini кеш иной) — довод «склейка кеш не портит» опирается на комментарий самого кода и на то,
что префикс сохраняет байты и позицию, живьём не мерен.
**§4 ОПРОВЕРГАТЕЛЬ — ЗАКАЗ ИСПОЛНЕН, И ОН ОКУПИЛСЯ. Шесть линз, мандат «опровергни».**
Веер шесть, а не «24» из §4: численный потолок снят словом владельца 28.08, соразмеряю предмету.
Линзы: Anthropic-путь и кеш · читатель книги · провод `withheld`/`translated` · безопасность банковых
решений · пара, которой в репо нет + детерминизм · вырожденность собственных тестов. **Пять из шести
вернули вердикт REFUTED или PARTIAL, и это правильный исход: они нашли ШЕСТЬ дефектов, из них ЧЕТЫРЕ
я внёс сам этим паком.** Все воспроизведены ИСПОЛНЕНИЕМ на копиях дерева, не чтением.
| Находка | Кто/чем доказано | Что сделано |
|---|---|---|
| **MAJOR: маркер называл ЧУЖУЮ причину.** Юнит, где редактура флагнулась САМА (косм. чистка) И выпал член, печатал причину ЧИСТКИ как причину дыры: «фрагмент не переведён: sanitizer_stripped». Та же ложь, что убирал пак, перенесённая в поток текста | 2 линзы независимо, боевой бинарь | **ИСПРАВЛЕНО:** заведены `DroppedReason` на `ChunkOutcome`/`ChunkExport`, маркер несёт причину ЧЛЕНА, баннер — вердикт ЮНИТА. Пин `SH7`, состояние в матрице обоих рендеров |
| **MAJOR: анти-скоуп.** В черновично-однофазном конвейере юнит — одиночный чанк, и его СОБСТВЕННЫЙ флаг читался как «выпавший член» ⇒ косметически почищенный чанк, у которого ВЕСЬ текст на месте, объявлялся неполным | боевой бинарь, `tmctl translate` и `tmctl export` расходились на одном юните | **ИСПРАВЛЕНО:** учёт выпадений только когда финальная волна — редакторская (`export.go`). Тест `TestADraftOnlyStrippedChunkIsNotCalledIncomplete`, пин `SH8` |
| **MAJOR: перестановка порядка записи купила сходимость ЦЕНОЙ второго тихого вреда.** Rejects-first оставляет строку дельты на диске, а `seedGlossary` дельту отказами НЕ фильтровал ⇒ в окне между падением и ре-сендом ОПЛАЧЕННЫЙ прогон вливал в глоссарий редактора терм, который владелец отклонил | 2 линзы, исполнение на реальных файлах | **ИСПРАВЛЕНО:** `loadMinedDelta` отбрасывает поверхность, стоящую в списке отказов, и говорит об этом WARN'ом. Тест на реальном полусостоянии, пин `SH12`. Заодно исправлено утверждение в шапке `writeDecisionFiles`, которое после перестановки стало ложным |
| **MAJOR: сходимость останавливалась на ОДНОЙ из двух дверей.** `refuseInertDeclines` я решил не трогать — и ошибся: траектория из ОДНИХ ПРИНЯТЫХ вызовов (decline терма-со-своей-строкой → approve терма, который несёт его алиасом) делала повтор стоячего реестра решений отказным навсегда, а всё-или-ничего уносило с ним законный approve | исполнение, полная трасса | **ИСПРАВЛЕНО:** та же оговорка на второй двери (по `in.Rejects`, не `res.Rejects` — фолд уже отработал). Тест `TestTheStandingLedgerConvergesThroughBothDoors` + направление-гард на живом алиасе, пин `SH13` |
| **MAJOR: у боевого объявления в `configs/models.yaml` не было ГЕЙТА вовсе.** Две строки — единственное, что доводит фикс до реального эндпойнта, а удаление их оставляло ВЕСЬ модуль зелёным | опровергатель посадил свою мутацию и получил SURVIVED | **ИСПРАВЛЕНО:** `TestShippedGeminiDeclaresOneSystemMessage` над боевым конфигом, пин `SH9` (проверено: удаление строк → тест краснеет) |
| **MINOR: гарды в `renderTranslate` не были запинены** (близнецы в `renderExport` были), `case "multi"` не покрыт, счётчики читались как разбиение целого | исполнение | **ИСПРАВЛЕНО:** матрица шести состояний для `renderTranslate`, тест `TestExplicitMultiCostsNothing`, счётчик переписан как `exported=N (of them incomplete=M)`. Пины `SH10`, `SH11` |
**ЧТО ОПРОВЕРГАТЕЛИ АТАКОВАЛИ И НЕ СЛОМАЛИ** (чтобы следующий читатель знал, что уже покрыто):
Anthropic-путь (склейка до него не доходит структурно — `Cap` в него не передаётся; два блока и живой
`cache_control` на месте) · префиксный кеш (стабильный префикс сохраняет байты и позицию) · провод
`translated`/`withheld` (`UnitDone.Shipped` считается по пустоте текста; ни `DroppedMembers`, ни
маркер её не трогают) · отсутствие новых причин флага (константы `FlagReason` до и после ИДЕНТИЧНЫ) ·
`exportVersion` не двигался · снапшоты боевых конфигов не двигались · арм-конфиги грузятся.
**ЧТО НАЙДЕНО, НО СОЗНАТЕЛЬНО НЕ ЧИНИТСЯ — с аргументом.**
1. **Ловушка `capabilities` на `kind: anthropic`** — оказалась чинибельной дёшево, и я её ВЗЯЛ: ось
теперь отказывается на этом виде по образцу `cache_ttl` (`models.go`), с тестом. Пред-существующий
класс для ОСТАЛЬНЫХ осей (`budget_field`, `temperature`, `reasoning`, `min_max_tokens`) остаётся —
его правильная форма тащит `kind` в `validateCapabilities` и трогает каждую ось; это чужой предмет.
2. **Снапшот не покрывает модель ГЕЙТА ТЕРМИНОЛОГИИ** (`config/pipeline.go` — не фолдится сознательно,
ратифицированно), а терминолог шлёт ДВА системных. ⇒ флип оси у провайдера, которым пользуется
только этот гейт, меняет байты под НЕИЗМЕННЫМ `RequestHash`, и чекпойнты ложно попадают. Дыра НЕ
моей оси — по ней ездят все оси `capabilities` с D3.1 — но моя первая, чей смысл именно в смене
этих байтов. **Утверждение в комментарии я ослабил до правды и дыру НАЗВАЛ прямо в коде**
(`capability.go`); закрытие — фолд модели гейта, как это делает `repairSnapshot`, и это правка
снапшот-контракта, принадлежащая владельцу решения «не фолдить», а не провайдерскому квирку.
3. **Сузив отказ по поверхности, я потерял поучение в одном углу:** decline, который И записан, И
по-прежнему инертен против подписанного сид-терма, теперь отвечает `already_applied` вместо
единственной работающей инструкции «убери из glossary_seed». Сходимость сочтена более тяжёлой
обязанностью (у вызывающего, который следует опубликованному ретраю, другого хода нет), но размен
реальный: **закрывается полем отчёта, несущим стоячий факт**, и это форма шва.
**ХАРД-БЛОКЕР, НАЙДЕННЫЙ И СНЯТЫЙ (требование владельца «после тебя не осталось хард-блокеров»).**
`go test ./cmd/tmctl/` строил боевой бинарь в `os.MkdirTemp` и НИКОГДА не убирал каталог — по 20 МБ за
прогон. За этот пак накопилось **249 брошенных каталогов `tmctl-bin*`, ~5 ГБ**, и `/tmp` заполнился до
99%: батарея под `-race` начала падать с `link: mapping output file failed: no space left on device`,
что читается как сломанная сборка, а не как кончившееся место. Я на это и купился на один прогон —
сообщать «батарея красная» было бы ложью. **Причина найдена, место освобождено, харнесс починен:**
добавлен `TestMain`, снимающий каталог (`cmd/tmctl/exitcontract_test.go`); проверено исполнением —
два прогона подряд, прирост каталогов 0. Дефект пред-существующий, пак его лишь довёл до отказа.
**ЧИСЛА, С КОМАНДАМИ (§4.1).**
- `go build ./...` · `gofmt -l .` (пусто) · `go vet ./...` · `go vet -tags live ./...`**все 0**.
- `go test ./... -count=1 -timeout=20m`**EXIT 0**.
- `go test ./... -race -count=1 -timeout=20m` (= строка `make test`) → **EXIT 0, real 2m09s**.
- `golangci-lint run --timeout=15m ./...` (= `make lint`) → **0 issues**.
- `go test ./internal/pipeline/ -run TestGolden -count=1`**ok** (голден пинит ПОЛНОЕ тело запроса).
- `go run ./cmd/tmmutate -root <КОПИЯ>`**73 посадки, 0 неожиданных исходов, EXIT 0** (72 RED + один каталогизированный выживший `G-byte-gate`; все 13 моих красны); каталог вырос 60 → 73 (13 новых посадок:
`SH1``SH13`), у каждой из трёх заказанных тем ≥2 своих.
**Каталог гонять ТОЛЬКО на копии** — сам инструмент это требует, а я один раз запустил его на рабочем
дереве и словил таймаут; дерево спас обработчик сигнала самого инструмента (проверено: мутационного
текста не осталось, батарея зелена). Записываю как урок, а не как мелочь.
**ФАЙЛЫ (19 правленых + 5 новых; всё в `backend/`, плюс эта запись в журнале).**
`cmd/tmctl/render.go` · `cmd/tmctl/exitcontract_test.go` · `cmd/tmmutate/mutations.json` ·
`configs/models.yaml` · `internal/config/models.go` · `internal/config/models_catalog_test.go` ·
`internal/llm/capability.go` · `internal/llm/httpllm.go` · `internal/llm/provider_openai.go` ·
`internal/llm/provider_local.go` · `internal/membank/decisions.go` ·
`internal/membank/decisions_fuzz_test.go` · `internal/pipeline/bankdecisions.go` ·
`internal/pipeline/bankreport_test.go` · `internal/pipeline/bookrun.go` ·
`internal/pipeline/export.go` · `internal/pipeline/mining.go` · `internal/pipeline/runner_test.go` ·
`internal/pipeline/waverun.go`.
Новые: `cmd/tmctl/exportgap_cli_test.go` · `internal/llm/systemmessages_test.go` ·
`internal/membank/decisions_converge_test.go` · `internal/pipeline/bankdecisions_converge_test.go` ·
`internal/pipeline/systemmessages_wave_test.go`.
**⛔ ВОПРОСЫ И ПИНГИ ОРКЕСТРАТОРУ — четыре, каждый требует ЧУЖОГО решения.**
1. **Изменён СУЩЕСТВУЮЩИЙ тест, и я это заявляю, а не прячу.** `bankreport_test.go`
`TestHalfLandedIsNamedPerFile` кодировал в фикстуре ПОРЯДОК переименований, который заказ §3.3
изменил; фикстура пере-нацелена на второй rename, утверждение сохранено дословно и усилено новым
`TestTheRenameOrderIsWhatMakesADeclineRecover`, пинящим сам порядок с его причиной. **Считаешь
подгонкой — откачу, и тогда половину §3.3 с полусостоянием надо выносить отдельным заказом.**
2. **Вендор-вердикт по Gemini** — прислан отдельным сообщением, с URL и датами; три поправки к
`00-provider-quirks.md:157` (структурная посылка ≠ поведенческий вывод · `Content.parts[]`
повторяемо · «форум Google» — это два не-сотрудника из 2025). Пишешь ты, зона твоя.
3. **Дыра снапшота у модели гейта терминологии** (см. выше, п.2 «не чинится»). Закрытие — правка
снапшот-контракта; принадлежит владельцу решения «гейт не фолдится», не мне.
4. **Д1 достоин собственного заказа.** Не «расширить decline до энтити» (это против канона), а узко:
эмиссия майнера энтити-широка (`miner_emit.go` `clusterTouches`), а фильтр авто-банка ключуется
только по собственному src строки (`mining.go:662`), поэтому ОТКЛОНЁННАЯ поверхность возвращается
алиасом уцелевшей строки. Канон уже сегодня обещает «declining a surface removes EVERY window of
that surface» — то есть движок не держит обещание. Правильная форма — снять алиас со строки, а не
снести строку; это банковая онтология, и в «тихую порчу» её тянуть не следует.
**ПРАВО §9 ПРИМЕНЕНО ТРИЖДЫ, каждый раз с грунтом:** Д3 отклонён (символ живёт в `platform/`, зона
заперта; движковая половина здорова и порядок «карта → память» исполнен) · Д1 отклонён как отдельный
предмет (выше) · подсказка промта про место маркера отклонена (она перевернула бы `withheld`
`translated`). ⚠ И один раз право §9 я применил ОШИБОЧНО: решил не трогать вторую дверь
`refuseInertDeclines`, рассудив, что её случай — «устаревший документ, а не ретрай». Опровергатель
показал траекторию из ОДНИХ ПРИНЯТЫХ вызовов. Рассуждение было неверным, исполнение это доказало,
дверь починена.
**⚠ ЗАПИСКА НИЖЕ — ПЛАН ДО ПРАВОК (§6), она СОХРАНЕНА как след дисциплины, но её выводы местами ОПРОВЕРГНУТЫ итогом выше. Не наследовать из неё: «сломан только класс (а)» (сломан ещё и стык «редактура флагнулась сама + выпал член»), «фиксация порядка записи безопасна» (она купила второй тихий вред, закрытый фильтром дельты) и «Д3 не существует» (существует, в `platform/`).**
**📝 ЗАПИСКА-ПЛАН БЭКЕНД-СЕССИИ 28.08 — ПАК «ТИХАЯ ПОРЧА» (`docs/BACKEND_SILENT_HARM_SESSION_PROMPT.md`), §6. Правок кода на момент записи НЕТ.**
Три заказа: §3.1 инъекция банка не теряется · §3.2 дыра выдачи видна читателю файла · §3.3 повтор
принятого документа сходится. Ниже — что установлено ЧТЕНИЕМ до правок и как из этого следует план.
⚠ Всё, что помечено «проверить исполнением», в план входит как обязательство, а не как факт.
**Находка 1 (меняет §3.2). Маркер уже есть — и он ВРЁТ.** `cmd/tmctl/render.go:499` печатает для
флагнутого юнита с НЕПУСТЫМ текстом баннер «(leak cleaned, verify)», а `:38` — «[FLAG … — leak
cleaned, exported cleaned, verify]». Ветка выбирается по `ce.FinalText != ""`, и c-lite member-drop
(`export.go:226-230` ставит `DispFlagged` при непустом тексте) попадает ровно в неё. То есть юнит, у
которого ВЫПАЛ ЦЕЛЫЙ ЧЛЕН, объявляется читателю косметической чисткой санитайзера, которой не было.
Существующий тест это не ловит: `cmd/tmctl/export_cli_test.go:66` покрывает ветку только причиной
`sanitizer_stripped`. ⇒ Предмет §3.2 не «завести маркер», а «перестать врать и покрыть непокрытое»
(пере-формулировка разрешена оркестратором 28.08 при закрытом гейте «что читает человек»).
**Гейт §3.2 закрыт чтением: движок НЕ ПИШЕТ ФАЙЛОВ ВООБЩЕ.** Все команды рендерят в stdout
(`cmd/tmctl/main.go:227-228`, `:338-349`). Читаемых человеком поверхностей у движка две, обе
текстовые и обе уже несут пер-юнитный баннер: `tmctl translate` (`render.go:27-72`) и
`tmctl export --plaintext` (`render.go:478-511`). Дефолт `export` — JSON
(`export_cli_test.go:24-28`), и он же артефакт шва к платформе (`research/23` строка 76 —
«артефактный контракт (export-JSON · 101 · 100)»). Читательский файл (epub и пр.) собирает
ПЛАТФОРМА по `POST /books/{bookId}/exports` (`14-api-contract/openapi.yaml:824-870`) — чужая зона.
⇒ Адресат моего маркера — человек, читающий plaintext-выгрузку движка; платформенная половина
остаётся за платформой, и провод различение несёт (`14-api-contract/README.md:300`).
**Следствие для места правки, и оно расходится с подсказкой промта.** Промт указывал на
`export.go:237` (прецедент `ApplyHeading`). Туда маркер класть НЕЛЬЗЯ: `ChunkExport.FinalText` — это
поле `final_text` артефактного контракта, а `waverun.go:203-204` кормит провод предикатом
`oc.FinalText != ""` (`shipped`). Любой маркер, вклеенный в текст ПУСТОГО юнита, перевернул бы
`withheld``translated` — ровно ⛔(а) промта. `ApplyHeading` этого не делает только потому, что на
пустом тексте он no-op (`chunk/chunker.go:186-191`). Различение: титул — часть КНИГИ и обязан ехать
на провод; маркер дыры — метаданные О тексте, и в поле, по которому провод судит, ему нельзя. ⇒
маркер живёт в РЕНДЕР-слое (`cmd/tmctl/render.go`), на слой дальше проекции. Это ответ на требование
промта «либо тем же механизмом, либо объясни, почему другое».
**Классы дыр — ревизия по коду.** (а) c-lite member-drop — помечен, но ЛОЖНО (находка 1); (б) юнит
целиком withheld (`waverun.go:496-509`) и (в) gate-override `glossary_miss` (`export.go:319-323`)
дают пустой текст и попадают в ветку `render.go:502-504` «(not translated, flagged for a human)» —
помечены ВЕРНО, работы не требуют; (г) четвёртая, сноска «[1] …» мимо детектора примечаний: детектор
требует заголовочного слова (`lang/data/target-ru.txt`, `sanitizer_trailing_note`), но ханьцзы в
сноске поднимают класс `CJKLeak`, а он `CosmeticOnly` (`checks/sanitizer.go:161-163`) ⇒ юнит уходит
как `sanitizer_stripped`, и баннер «leak cleaned» для НЕГО правдив. То есть класс (г) накрыт
существующим механизмом БЕСПЛАТНО и работы в детекторе не требует — граница промта соблюдена.
**Проверить исполнением обязательно:** что сноска с ханьцзы действительно даёт `CosmeticOnly`, а не
подтягивает субстантивный класс.
**§3.1 — план и его цена.** Свёртки в `toOpenAIMessages` нет (`llm/httpllm.go:513-521`, копия
один-в-один); её комментарий `:511-513` говорит, что `CacheBoundary` на этом пути бессмысленна ⇒
свёртка кеш не портит. У Anthropic путь другой и трогать его нельзя
(`llm/provider_anthropic.go:169-179`: ведущий system-префикс → блоки, `CacheBoundary` → реальный
`cache_control`). Ось лечения — `llm.Capability` + блок `capabilities:` (провайдерный ИЛИ модельный,
`config/models.go:85`,`:146`,`:442-450`), прецедент безопасного расширения — `MinMaxTokens` с
`json:",omitempty"`. **Детерминизм:** снапшот фолдит РАЗРЕШЁННУЮ Capability только для праймари и
единственного `escalate_to` (`pipeline/snapshot.go:316-340`,`:479`,`:495`); в `pipeline-c1.yaml`
`escalate_to: deepseek-v4-pro` (`:60`), а gemini стоит лишь третьим в цепочке `default` (`:129`) при
`budget_usd: 0` (`:136`) и раннере SINGLE-HOP ⇒ объявление квирка у gemini снапшот боевого конфига
НЕ двигает. Проверить исполнением (голдены + снапшот-тесты).
**Честная граница достижимости:** при SINGLE-HOP и текущих конфигах gemini не достигается ВООБЩЕ;
дефект латентный и ждёт конфига, который назовёт gemini в `escalate_to` или в `chain[0]` лейбла.
**Развилка, которую выношу на опровергателя (§4 заказ):** дефолт оси. «Квирк объявляет тот, у кого
он есть» (нулевое значение = сегодняшнее поведение) не двигает ничего, но НОВЫЙ провайдер с той же
ловушкой снова теряет инъекцию молча — тот же класс тихого вреда. «Свёртка по умолчанию, исключение
объявляет проверенный» безопасна для незнакомого провайдера, но меняет провод боевому переводчику без
замера (запрет «эмпирика решает») и двигает снапшот. Решение — после опровергателя, с обоснованием.
**Посылка промта «Gemini принимает ровно одно системное» ОТОЗВАНА автором** (оркестратор, 28.08):
это было рассуждение; собственный замер полигона (`00-provider-quirks.md:157`) даёт 535 токенов ≈ не
выжило НИ ОДНО. Заказу это безразлично, тесту — нет: утверждается пост-фиксный инвариант «системное
РОВНО ОДНО и инъекция ВНУТРИ него», он верен при любом чтении. Вендор-вердикт со ссылкой уходит
оркестратору для `00-provider-quirks.md` (его зона).
**§3.3 — трасса контрактной сессии ПОДТВЕРЖДЕНА моим чтением, и подтверждено то, что она сама не
читала.** Отказ живёт в `membank/decisions.go:374-377`, бежит ДО фолда (`:296-300`), и
всё-или-ничего вытирает документ целиком (`:302-307`). **Порядок записи я прочитал сам:
`bankdecisions.go:458-473` коммитит ДЕЛЬТУ ПЕРВОЙ**, а собственный комментарий функции `:436-438`
говорит дословно: «delta-first is safe for an approve and NOT for a decline … convergence is what
actually carries both». ⇒ окно застревания реально, и код сам называет допущение, которое ложно.
Идемпотентная ветка уже есть (`already_applied`, `:221-224`,`:269-272`) — вводить понятие не надо.
План: сузить условие отказа так, чтобы оно не срабатывало, когда отказ УЖЕ записан в rejects, плюс
направление-гард (настояще-инертный decline обязан отказывать по-прежнему тем же текстом).
Идентичность документа НЕ вводить — сходимость по СОСТОЯНИЮ.
**Д1 и Д3 — беру право §9 и НЕ чиню, оба с грунтом.**
· **Д3 ОТКЛОНЁН как несуществующий:** гард `LiftBankStop` не найден в коде НИ В КАКОМ написании
(греп по всему `backend/`); реальный носитель — `mining.go:266-277`, и он уже реализует
ратифицированный порядок «карта → память»: `writeFileAtomic` карты на `:191`, `MarkStopPresented`
на `:274`, с комментарием `:270-273` «strictly after the map … the reverse order can mark surfaces
presented whose map never reached disk» и с явной оценкой отказа памяти как «one extra stop, nothing
lost» (`:275-276`). То есть механизм написан ВМЕСТЕ с памятью v16, а не до неё.
· **Д1 ОТКЛОНЁН как ратифицированное поведение + названный остаток:** канон определяет decline
ПОВЕРХНОСТНО («declining a surface removes EVERY window of that surface» —
`14-api-contract/openapi.yaml:2051`), и расширение до энтити противоречило бы контракту; а «память
глушит стоп» — уже НАЗВАННЫЙ и принятый двусторонний остаток в комментарии
`mining.go:308-313`. Оба — строка с причиной, не правка.
**Сосед 131 (называю, не чиню, как велит §3.4):** кап инъекции считает только «src → dst» и не
считает маркер `⟨проверить⟩` и родовые пометки (`lang/data/injection.txt` несёт их как отдельные
значения) ⇒ реальная инъекция шире бюджета. Проверю точный носитель при работе в этом коде и назову
`file:line` в итоге; чинить не буду — тема денег и качества инъекции, а не тихого брака в тексте.
**Порядок работы:** §3.1 → §3.2 → §3.3, каждый с тестом на ПОСТ-фиксный инвариант; батарея под
`-race` + линтер; посадки мутаций ≥1 на пункт с вердиктом по ДЕЛЬТЕ и ТОПИЧНОСТИ; опровергатель на
выбор места §3.1 (заказ §4). Веер агентов соразмеряю предмету: численный потолок «24» из §4 промта
снят словом владельца 28.08.
**✅ ИТОГ БЭКЕНД-СЕССИИ 27.08 (2) — ФИКС3 ПО ФИКС-ЛИСТУ ПРИЁМКИ (`docs/BACKEND_SEAM_FIX3_SESSION_PROMPT.md`), все 6 пунктов. Дерево готово, НЕ закоммичено; продолжает дерево трёх паков.**
Четыре пина, один снос, ноль новых механизмов: пин порядка «карта → память» (мутация приёмки
воспроизведена в трёх пакетах ДО пина; занятый путь карты → ошибка прогона + пустая память) · пин
«отчёт класса 15 мерит подпись по ДИСКУ, не по намерению» (утверждение панели воспроизведено) · оба
носителя старого пола полосы приведены к двух-ярусной букве · отменённая формула «стоп гаснет, когда
решён каждый терм» зачищена ГРЕПОМ — восемь носителей, включая два файла самого пака · снесена
машинная таблица `.bank-stop.json` вместе с писателем и четырьмя тестами (читателя нет ни в одной
зоне — свой греп; человеческая `.bank-stop.txt` не тронута) · банк-экспорт получил якорь свежести
`as_of`+`run_id` (аддитивно на `tm-bank-v1`; «может быть устаревшим» больше не живёт только в логе).
**Доказательная база — `FINDINGS` §46; каталог мутаций 58 → 60 (FX23, FX24).**
**⛔ Пинги:**
1. **Платформе/оркестратору:** `.bank-stop.json` СНЕСЁН (мёртвая публикация упразднённого D39.144
пер-термного экрана) — платформенные журналы (`platform-PROGRESS.md`, `STACK_DECISIONS.md`)
называют его атомарно-читаемым сайдкаром, упоминания устареют при лендинге.
2. **Потребителям банк-экспорта:** документ несёт `as_of` (граница прогона) и `run_id` (тот же id,
что стрим `events.jsonl`); аддитивно, старые читатели не ломаются; пустой `run_id` = «идентичности
нет», не «текущий».
**✅ ИТОГ БЭКЕНД-СЕССИИ 27.08 — ДОФИКС-2 «ФЛАЖОК СТАНОВИТСЯ ФЛАЖКОМ» (`docs/BACKEND_SEAM_FIX2_SESSION_PROMPT.md`). Дерево готово, НЕ закоммичено; продолжает дерево паков 2425.08.**
Все 13 пунктов §4 закрыты: движок сам реализует модель D39.144 — стоп банка срабатывает только на
кластер, которого ни один прежний стоп не предъявлял (персистентная память `bank_stop_presented`,
схема v16); авто-провод неподписанных строк работает и при ПОДНЯТОМ флаге; отчёт двери собирается
ОДНИМ конструктором по явному исходу (проекция · отказ · no-op · прерывание · записано · запись не
удалась), версия отчёта → v2 (`written_delta`/`written_rejects`, mode `refused`/`write_incomplete`);
полоса отказов получила два яруса и класс `write_incomplete` (exit 15); оба файла решений готовятся
целиком до первого rename + fsync каталога; ключи `mined_delta`/`mined_rejects` СНЯТЫ (пути только
конвенцией — МЕЖКНИЖНАЯ гонка недостижима, ⛔-пинг прошлого пака закрыт; воркфлоу-ревью нашло остаток
той же оси: второй конфиг ТОЙ ЖЕ книги с другим объявленным project_db пишет те же файлы под другим
локом, мимо flock живого прогона — пинг ниже, решение не паку); карта подписи
пишется атомарно и несёт конверт `map_version`+`map_id`; шесть слепых пятен гейта закрыты пинами,
проверенными посадкой. Поверх — воркфлоу-ревью (шесть полос × три скептика, `FINDINGS` §45): его две
кодовые находки закрыты правками с пинами — `--dry-run` теперь отказ для всех глаголов без проекции
(раньше `translate --dry-run` МОЛЧА запускал платный прогон), а повтор после отказа синка каталога
держит класс 15, пока долговечность не пере-доказана. **Доказательная база —
`backend/docs/SEAM_PACK_FINDINGS.md` §§36§45; каталог мутаций 36 → 58 записей, 0 неожиданных
исходов.** Здесь — только требующее действия.
**⛔ ПОРЯДОК ДЕПЛОЯ/ЛЕНДИНГА — зависимость НАПИСАНА, а не выведена (заказ §4.12):**
1. **Движок первым, платформа второй.** Сброс флага `--verify-bank` платформой (её персистентная
колонка + особый случай в спавне, `platform/internal/runs/spawn.go`) может умереть ТОЛЬКО ПОСЛЕ
лендинга этого пака: в обратном порядке каждая книга с поднятым флажком в день лендинга перестаёт
писать авто-банк и пере-сидироваться ровно в режиме, ради которого флажок существует.
2. **`tmctl migrate` до прогонов** (голова схемы v15 → v16): `OpenReadOnly` отказывает классом 13 всем
непере-мигрированным книгам — ратифицированный порядок D39.134 п.3 это покрывает, но назван явно.
3. **Миграционная нота:** после апгрейда память пуста → ОДИН доброкачественный стоп на первом
verify-прогоне каждой книги.
**⛔ ПИНГИ ЧУЖИМ ЗОНАМ (правок не делал):**
1. **Платформе:** у неё умирает сброс флага при resume (после лендинга движка — п.1 выше) · отчёт двери
теперь v2 (`tm-bank-decisions-report-v2`: mode `refused`/`stopped`/`write_incomplete`, пофайловые
`written_*`) — её `readEngine` выбрасывает stdout при ненулевых кодах, прежний пинг об этом стал
острее: класс 15 БЕЗ отчёта неотличим от «ничего не легло» · новый exit 15 в полосе 1019 читается
её ридером как refused по построению полосы — ратификация значения на оркестраторе при лендинге.
2. **Полигону:** `eval/bank_autonomy/parse_common.py` парсит `mined-signature.yaml` — карта теперь
несёт верхние ключи `map_version`/`map_id` (лежащие артефакты coldrun не тронуты; новые прогоны
будут в новом жанре).
3. **Фронту:** экран подписи, читающий `signature.undecided` из отчёта двери, обязан перестать трактовать
его как «стоп не погаснет» — поле информационное (D39.144), контракт поля пере-описан в коде.
4. **Майнерский хвост за потолком структурно недостижим** (§4.4, НЕ чинилось по заказу): потолок эмиссии
(200) режет ранжированный пул ДО фильтров пригодности/сида/отказов (`miner_emit.go`,
`ranked[:emitRankCap]` до `emissionEligible`). Пере-замерено своей командой (числа прежнего
сплошного читателя в дерево не попали): 2354 ранжированных · 209 годных во всём алфавите · 11 в окне
потолка · 198 невидимы постоянно; отклонение всех годных окна не подняло ни одного из хвоста
(`FINDINGS` §38). Первоисточник замера — постоянный якорь-пин `internal/miner/emissioncap_test.go`:
он утверждает ТЕКУЩЕЕ поведение и покраснеет в день сдвига потолка или порядка фильтров, приведя
двигающего к этому пингу. Двигать потолок — трогать фрозен-паритет; отдельное решение.
5. **Гипотеза «подписание промахивается мимо чекпоинтов терминолога и пере-оплачивает роль» механизмом
НЕ подтверждена** (§4.13): память предъявленного входов чекпоинта не трогает; если перерасход
существует — он существовал и до пака, отдельный предмет.
6. **Остаток оси арбитра (нашло воркфлоу-ревью, воспроизведено живьём):** второй конфиг ТОЙ ЖЕ книги
(один каталог, один book_id, другой объявленный `project_db`) пишет ту же пару файлов решений под
ДРУГИМ локом — в т.ч. мимо flock живого прогона. Межкнижная версия снята конвенцией; эта — второй
конфиг одной книги, т.е. рукотворная конфигурация оператора. Выбор лечения (второй арбитр на
идентичности файлов решений или отказ при загрузке) трогает закон п.2 — решение не паку, пинг.
Довод в коде сужен честно (`bankdecisions.go`, THE ARBITER).
**✅ ИТОГ БЭКЕНД-СЕССИИ 25.08 — ДОФИКС ТОЙ ЖЕ ДВЕРИ по приёмке (`docs/BACKEND_SEAM_FIX_SESSION_PROMPT.md`). Дерево готово, НЕ закоммичено; продолжает дерево пака 24.08, а не заменяет его.**
Все 16 пунктов §4 закрыты: блокер формата (дверь писала байты, которых её же парсер не читает, и любой
следующий вызов по книге — включая проекцию — падал) · пред-существующие поломки чинятся по одной ·
два ключа на один путь · гарды флагов на ПРИСУТСТВИЕ (`--decisions=` уходил на ПЛАТНЫЙ путь) · кап
документа решений · конверт путей на `manifest --json` · алиасы судятся по НАБОРУ · перевёрнутое окно
глав · честность отчёта о стопе · SIGTERM · десять слепых пятен гейта · **цепь целиком в репозитории**.
**Доказательная база, замеры, попунктный чек и обе итерации ревью — `backend/docs/SEAM_PACK_FINDINGS.md`
§§1430;** здесь только то, что требует ДЕЙСТВИЯ.
**✅ ⛔-ЗАКАЗ ПРИЁМКЕ ИЗ ПАКА СНЯТ.** Цепь «стоп → `bank-apply` → резюм → стоп ГАСНЕТ» теперь живёт в
репозитории как $0-тест на фейковом провайдере (`internal/pipeline/bankchain_test.go`), на ОДНОМ проекте
от стопа до погасшего стопа. Довод пака «своими силами невозможно, надо дойти оплаченной черновой волной»
был шире правды: харнесс для этого уже был в репозитории. Мина №1 строки **199** (ливлок на `status:
auto`) закрыта исполнением, а не рассуждением.
**⚠ РАТИФИКАЦИЯ КЛАССА 14 — прежнее требование в силе, объём немного шире.** Класс `decisions_rejected`
теперь несёт и отказ по КАПУ (документ больше, чем движок применяет за один акт): это факт о ЗАПРОСЕ, а
не о деплое, поэтому 10 был бы неверен. Носители полосы поднимает оркестратор при лендинге, как и
планировалось.
**⛔ ПИНГИ ДОФИКСА (правок не делал):**
1. **`exit 2` при OOM платформа читает как успех** — общедвижковый класс; кап удерживает `bank-apply` от
того, чтобы стать первым, кто до него доедет, но самого класса не снимает.
2. **Каталог книги без права записи даёт `exit 1`** (вне полосы). Класс движка корректен и одинаков у
всех глаголов — чинить надо чтение кода 1 на платформе, которая списывает проблему прав ХОСТА в
бюджет попыток КНИГИ.
3. **Проекция отвечает «что применится», а не «почём».** Рекомендация — НЕ строить: глагол $0 по
построению, поле «$0» было бы шумом. Решать оркестратору.
4. **Карта подписи не удаляется после погасшего стопа** — пред-существующее; зафиксировано тестом как
текущее поведение. Честный ответ на «есть ли что подписать» — новое поле отчёта `signature.undecided`.
5. **Цена глагола Θ(N²) по построению** — каждый пер-решенческий помощник сканирует весь документ.
Именно поэтому кап на КОЛИЧЕСТВЕ стоит на 5 000. Поднимать его без индексации этих сканов нельзя.
6. **Апстрим-дефект `gopkg.in/yaml.v3 v3.0.1`** (эмиттер пишет блочный скаляр, который его же парсер не
читает) вылечивается по-настоящему только скаляр-типом схемы с явным стилем или переездом на
`go.yaml.in/yaml/v4` (релиз-кандидат, смена module path в ДВУХ зонах). Мой обход несёт ссылку на
исходник библиотеки и характеризационный тест, который покраснеет в день починки апстрима.
7. **Фолд поверхностей Go-резидентный:** `internal/text/norm.go` держит `trad2simp` и катакана→хирагана
в Go, а не в langpack'е. Пара, которой в репо нет, деградирует до NFKC+lowercase — не блокирована, но
это ровно тот класс, о котором ревью-вопрос общности. Шире двери, поэтому пингом.
**⚠ ЧТО В ЗАПИСИ ПАКА 24.08 (ниже) УСТАРЕЛО — читать её через эту поправку.**
1. **Пинг 2, «таймаут: 1.6с на 5 000 термов, 9.1с на 20 000» — ЧИСЛА НЕВЕРНЫ, и это ровно тот датум,
которым платформенный пак будет мерить свой таймаут.** Мой замер той же командой: 5 000 `approve`
**5.2 с**, 20 000 → **50 с**; на минимальных `decline` (самая дешёвая форма, и потому та, которой
байтовый кап пускает больше всего) 5 000 → **11.3 с**, 12 000 → **58 с**, 28 900 → **308 с**. Брать
надо `FINDINGS` §18 и §29 п.1, не эти.
2. **Пинг 6 «однострочник при лендинге: документ решений не капирован» — СДЕЛАН.** Капов теперь два
(1 МиБ на чтение + 5 000 решений на применение), потому что байтовый не ограничивает время.
3. **Пинг 3 про флейки** пере-снят сильнее: изолированный A/B против дерева HEAD даёт побайтно
одинаковое падение с обеих сторон — `FINDINGS` §31.
**⛔ ПИНГИ, КОТОРЫЕ ЖИЛИ ТОЛЬКО В `FINDINGS` И НЕ ДОЕЗЖАЛИ ДО ЖУРНАЛА** (найдено проверкой согласованности):
карта подписи пишется обычным `os.WriteFile` мимо атомарной конвенции сайдкаров того же пакета
(пред-существующее) · цена доказывающего рендера ≈5 % времени, плюс `notCanonical` теперь тоже разбирает
документ · опубликованные пути двух файлов решений не несут версионного конверта (пинг пака §10.5) ·
схема сида без `omitempty` (пинг пака §10.6).
**⚠ Два пред-существующих `-race`-флейка в `internal/store` не тронуты** (`TestKillMinus9LosesAtMostOneCall`,
`TestTheSeamIsNotChargedToTheStoreOperationBudget`). База ДО правок, снятая на нетронутом дереве пака,
уже была красной по первому — это моя собственная атрибуция, а не наследованная.
**⛔ ПИНГ, КОТОРЫЙ ТРОГАЕТ РАТИФИЦИРОВАННЫЙ ЗАКОН (п.2) — решать не паку.** Арбитр двери — flock на
`<project_db>.lock`, а пишутся `mined_delta`/`mined_rejects`, которые `book.yaml` может направить куда
угодно. **Две книги с РАЗНЫМИ `project_db` и ОДНИМ delta друг друга не исключают:** воркфлоу-ревью
продемонстрировало гонку на 1500 одобрений с каждой стороны — одна сторона потеряла всё, `exit 0`, отчёт
«применено». Внутрикнижную версию этого §4.3 уже запрещает; межкнижная законна сегодня. Выбор — второй
арбитр или отказ при загрузке на путь вне каталога книги. В корпусе нарушителей нет. В коде стоит явное
«арбитр покрывает проект, а не пути», а не молчаливое допущение.
**🔧 В репозиторий добавлен инструмент: `backend/cmd/tmmutate`** — харнесс мутаций с каталогом из 36
посадок, у каждой названо СВОЙСТВО, за которое она отвечает, и записан ожидаемый исход. Три сессии подряд
писали такой инструмент в скретчпад и теряли его; приёмка при этом нашла восемь мест, где гейт был слеп.
**Он дважды подводил и оба раза был починен по следу:** сначала подсадил мутацию и прошёл собственную
sha256-самопроверку (эталон второй правки снимался с уже пропатченного файла), затем считал КРАСНЫМ провал
СБОРКИ — из-за чего две записи каталога, сломанные рефакторингом, целую итерацию числились пойманными
тестами, которые не могли отработать. Теперь: одно чтение и запись на файл, отпечаток всего каталога перед
каждой посадкой, зелёная базовая линия пакета ДО первой посадки и третий вердикт `NOTHING`, роняющий гейт.
`FINDINGS` §25 и §34.
**✅ ИТОГ БЭКЕНД-СЕССИИ 24.08 — ПАК «ВХОДНАЯ ДВЕРЬ ШВА», движковая половина D39.156. Дерево готово, НЕ закоммичено, передаётся на лендинг.**
Заказ §4.1§4.6 выполнен: конвенционные дефолты `mined_delta`/`mined_rejects` (ключей не объявляет и
файлов заранее не создаёт никто) · глагол `tmctl bank-apply --config <book.yaml> --decisions <doc.json>
[--dry-run]` ($0, идемпотентен побайтово, всё-или-ничего, занятый flock — ровно 12 неблокирующе, ответ
несёт версию и явное `depth: edit_wave`) · `--keys-file` у `translate` (строка **211**) · строгость
сид-загрузчика (**212**) · `status_version` + `artifacts{}` с абсолютными путями (**213**) · дефолт
стендового корпуса маркерным резолвом (**218**, критерий оркестратора выполнен буквально).
**Вся доказательная база, замеры и попунктный чек промта — `backend/docs/SEAM_PACK_FINDINGS.md`;**
здесь только то, что требует ДЕЙСТВИЯ.
**⚠ ТРЕБУЕТ РАТИФИКАЦИИ: заведён класс полосы отказов 14 `decisions_rejected.`** Отвергнутый по существу
набор решений ≠ сломанный деплой: `platform/internal/ingest/exit.go` `DeploymentFault` относит 10 к
«условию, общему для всех книг хоста», и по 10 набор решений одного пользователя списался бы на
оператора. Старый читатель деградирует безопасно (`Refused(14)``OutcomeRefused`). 10 сохранён для
документа, не прочитавшегося КАК документ решений. Гейт тотальности таблицы добавлен.
**ЗАКАЗ ПРИЁМКЕ — единственный незакрытый замер (согласован со сторонним архитектором).** Цепь целиком
не гонялась ни разу: стоп красный → `bank-apply` → резюм → **стоп гаснет**. Части доказаны порознь, но
мина №1 строки **199** — livelock уровня ЦЕПИ, которого частичные тесты по построению не видят. Снять не
могу, и это проверено: банк-стоп на машине не воспроизводится (`contrast_path` → отсутствующий
`eval/exp16/data/jieba_dict_general_zh.txt`, строка **123**), а до стопа надо дойти оплаченной черновой
волной. **Команда:** на КОПИИ книги на exit 3 скормить `bank-apply` документ на всю карту подписи, затем
`translate --verify-bank`. **ДЕФЕКТ** — стоп снова с ТЕМИ ЖЕ поверхностями; **не дефект** — свежая пачка
≤200 термов реверс-секции (мина №3 строки 199). До этого прогона вердикт по циклу — PLAUSIBLE.
**⛔ ПИНГИ ЗОНАМ И СЛЕДУЮЩИМ ПАКАМ:**
1. **`rubyToCandidates` мёртв в проде** — единственный вызов из теста (`memseed_test.go:204`);
`seeding.go:36-38` строит `manualSrcs` и никуда не передаёт (Go молчит: запись в map = использование).
Строк `Source:"ruby"` в банке не возникает, а платформа держит для них ветку словаря
(`ingest/bank.go` `termOrigin``OriginAnnotated`).
2. **Платформенному паку:** первый контакт с непере-мигрированной книгой даёт **exit 13** — воркер решений
обязан планировать `13 → migrate → повтор` · **проекция берёт тот же лок**, поэтому правка термина при
живом прогоне даёт 12 даже на превью, на всю длину прогона · **таймаут**: 1.6с на 5 000 термов банка,
9.1с на 20 000, бюджет платформы на вызов движка — 60с (`runs/reconcile.go:280`) · `bank-apply`
создаёт `<project_db>.lock` при первом касании никогда не запускавшегося проекта.
3. **⚠ БАТАРЕЯ НЕ ДЕТЕРМИНИРОВАННО ЗЕЛЕНА, и это НЕ пак — прочесть ДО пере-рана.** Два
пред-существующих `-race`-флейка в `internal/store` срабатывают примерно раз на 24 полных прогона
(`go test ./... -race -count=1` ×4 → 1 чист, 2-й красный). **Красное именно в этих двух тестах
ожидаемо; любое другое красное — регрессия.** Тесты НЕ трогал (D39.121).
`TestTheSeamIsNotChargedToTheStoreOperationBudget`, `TestKillMinus9LosesAtMostOneCall`. Чередующийся
A/B на `git worktree` HEAD: оба падают на HEAD **не реже** (`kill9` 2/30 против 0/30; `seam` 2/30
против 1/30; без `-race` 0/30). Покрытие: `LockProject`/`Release` этими тестами не исполняются вовсе.
⚠ 0/30 против 2/30 НЕ значит «пак улучшает» — на таком числе это шум (Фишер p≈0.5); достаточно того,
что флейк живёт на дереве, которого пак не касался.
4. **Гейт `TestDispatchCommandsCoversTheSwitch` слеп к `case` с константой** (считает только
`*ast.BasicLit`) — команда, объявленная константой, невидима гейту, чей смысл в дефекте строки **176**.
Я вернулся к литералу (конвенция файла), дыра не эксплуатируется.
5. **`redrive` — платный путь без канала ключей аргументом.** Промт: «флаг только у `translate`», сделано
ровно так; канал у него есть (конвенционный `.env`), платформа его не зовёт. Развилка следующему паку.
6. **Однострочник при лендинге (не блокер):** документ решений читается неограниченным `os.ReadFile`,
тогда как платформа свои чтения от движка капает. Документ на 10 000 решений ≈2МБ ⇒ кап 116МБ.
**⚠ ПРАВИЛА, ВЫНЕСЕННЫЕ ИНЦИДЕНТАМИ — для следующих сессий, не про этот пак.** Прогон харнесса мутаций
был убит таймаутом, `finally` не отработал, и посадка просидела в дереве ~40 минут, выглядя
продакшн-кодом; сторонний ревьюер снял с него замеры и доложил несуществующий дефект. Поймал посадку
собственный пин пака. Отсюда три правила: **харнесс мутаций обязан фингерпринтить цели** (починено) ·
**снимай отпечаток дерева перед сборкой бинаря замера** (на этом наступили обе стороны) · **одиночный
прогон — не замер** (атрибуцию флейков я дважды вывел неверно из одиночных прогонов).
**Что НЕ доказано:** рантайм-вердикт по платному пути — **PLAUSIBLE**. Доказано, что ключ из
`--keys-file` доезжает до заголовка и что без флага прогон отказывает конфигом; не доказано, что
реальный провайдер примет ключ и что SaaS положит файл по этому пути — платформенная половина строки
**211**, чужая зона. Живой `translate` не гонялся: санкции на платные вызовы у пака нет. (24.08)
**⛔ ПИНГ ОРКЕСТРАТОРА №19 — 23.08, ЗОНЕ БЭКЕНДА. Четыре движковых дефекта, все сверены моими командами.**
1. **ОТВЕТ НА ПИНГ ПОЛИГОНА: дефект ПОДТВЕРЖДЁН, он ваш.** Эскалация на Gemini теряет инъекцию
банка памяти (строка **208**). Механизм пере-проверен: `pipeline/render.go:256-258`=`Role: "system", Content: injection, CacheBoundary: false`
штатно строит ВТОРОЕ системное сообщение, а `llm/httpllm.go` (до-паковые строки 517-523, якорь ИСТОРИЧЕСКИЙ — сигнатура сменилась лендингом D39.164: функция приняла ось `mode SystemMessagesMode` и под квирком СКЛЕИВАЕТ системный ряд, так что описанного здесь «один-в-один» больше нет)
копирует сообщения дословно — склейки нет нигде. ⚠ Лекарство того же класса в проекте УЖЕ
написано и работает для другого провайдера: `llm/provider_anthropic.go:169-179`=`inSystemPrefix := true`
сворачивает ведущий system-префикс в одно поле. Не обобщено на Gemini — это и есть работа.
Полигон ждёт ответа с 22.08 и починил у себя по вендор-доке; их находка верна.
2. **Род не производится авто-путём вовсе** (строка **210**, дополнена 23.08): замка ДВА и они
независимы, а поле `Gender` в схеме сида УЖЕ есть — ось мертва в ПИСАТЕЛЯХ, не в формате.
3. **Выпавший член юнита исчезает из отгружаемых байт БЕЗ внутритекстового маркера**
`pipeline/waverun.go:487-490`=`the flagged member is DROPPED from the edit`. Родня строки **193**;
для читателя это молчаливая дыра в тексте.
4. **Загрузчик сида НЕстрогий** (строка **212**): голый разбор YAML без проверки имён ключей, и
`seed-lint` этого не видит. Опечатка в имени поля молча теряется — в файлах, которые владелец
правит руками чаще всего.
⚠ Плюс строки **211** (движок на SaaS не получает провайдерских ключей — блокер живого прогона) и
**213** (платформа дублирует вашу конвенцию пути): обе половинные, движковая часть ваша.
**⛔ ПИНГ ОРКЕСТРАТОРА №20 — 22.08, ЗОНЕ БЭКЕНДА: три ваших дока разошлись с деревом (аудит доков, сверено кодом; рукой не лезу — ваша зона).**
1. **`backend/README.md`, карта пакетов** объявлена сверенной по коду, а в ней НЕТ `internal/runevents` — эмиттера журнала прогона, то есть ВСЕГО шва движок↔платформа (D39.131), — и нет второго бинаря `cmd/tmvet`. Греп: `runevents` и `tmvet` в файле по нулю вхождений, при том что оба каталога в дереве.
2. **`backend/README.md`, «Известный техдолг»** держит живыми Cyrillic-хардкоды DC-чекеров, которые сам код называет ПОГАШЕННЫМ долгом (фаза 2 общности, D39.64: `isRuTarget` удалён, `TargetActive`/`TargetScriptNonLatin`), и оба его `file:line`-якоря указывают мимо.
4. **(дописано 24.08) `backend/README.md:47`=`/home/ubuntu/books/gu-zhenren/` зовёт книгу-стенд по МЁРТВОМУ пути** `/home/ubuntu/books/gu-zhenren/` — каталога нет ни там, ни в `$HOME`: книги переехали в `<репозиторий>/books` и версионируются своим git (D39.157 п.2). Там же фраза «тексты и производные ВНЕ git» стала неточной: вне ЭТОГО git. Тем же касанием — три тест-хелпера, резолвящие корпус от `$HOME/books` (строка 218); они уже в заказе движкового пака шва §4.6, так что дублировать правку не надо, но док обязан сойтись с ними.
3. **`backend/docs/components.puml`** — диаграмма, которую карта доков продаёт как правимую «одним коммитом с кодом», отстала примерно на месяц: старый exit-контракт (без 4/5 и полосы 1019), неполный список глаголов CLI, удалённый паком-17 `failover.go` и ни следа эмиттера. ⚠ Рендерить её в svg/png НЕ надо (гардрейл) — править исходник.
*(Пак «честные числа и статусы» — ИСПОЛНЕН, ПРИНЯТ С ФИКС-ЛИСТОМ и ЗАЛЕНДЕН `f840867` (17.08; D39.149, ФЧ-5 — D39.150). Отчёт, две волны адверсариального ревью, мутационная приёмка и тело фикс-листа ФЧ-1…ФЧ-8 — в [archive/PROGRESS-2026-08-17.md](archive/PROGRESS-2026-08-17.md); живой остаток — строка бэклога 197.)*
*(Пинги закрытых паков — фикс-пак банка D39.118, блокеры контракта D39.122 — в [archive/PROGRESS-2026-08-04-09.md](archive/PROGRESS-2026-08-04-09.md), D39.125.)*
*(Закрытые бэкенд-записи 0102.08 — общность фаза 2 · качество банка (оба этапа) · фикс-пак банка · finding-1 · пакет-чекеров · мелкая пачка · coldrun-b · ручка эффорта — в [archive/PROGRESS-2026-08-01-02.md](archive/PROGRESS-2026-08-01-02.md).)*
*(Закрытые бэкенд-записи 1415.08 — эмиттер шва (D39.131) · `tmctl migrate` (D39.134) · пере-пин цен DeepSeek (D39.137) — в [archive/PROGRESS-2026-08-14-15.md](archive/PROGRESS-2026-08-14-15.md); вынесено D39.139.)*
*(Закрытая хроника пакетов №0№4 (0410.07) перенесена в [archive/PROGRESS-2026-07-04-10.md](archive/PROGRESS-2026-07-04-10.md) — ревизия D31.)*
*(Хроника пакетов 1112.07 (пакет-5, D15.2-этап-А, санитайзер-фикс D33) — в [archive/PROGRESS-2026-07-10-13.md](archive/PROGRESS-2026-07-10-13.md).)*
*(Трек-A пак-1/1.5 (D39.2/D39.5) — см. записи оркестратора №6 выше.)*
*(Хроника бэкенд-паков 1116 (1925.07, вкл. отчёт-запись пака-15) — в [archive/PROGRESS-2026-07-13-25.md](archive/PROGRESS-2026-07-13-25.md); отчёты паков — `archive/reports/`.)*
*(Закрытые записи 2531.07 — пак-18 · мини-прогон · фикс-пак · сквозная проверка · хендофф 26.07 · ресёрч общности фаза 1 — в [archive/PROGRESS-2026-07-25-31.md](archive/PROGRESS-2026-07-25-31.md).)*