Issue the backend seam fix prompt: the door bricks a book on a lawful decline, and eight majors more, each with the acceptance's own repro

This commit is contained in:
heaven 2026-08-25 12:38:06 +03:00
parent ff2e0061d5
commit fa511e65b5
2 changed files with 489 additions and 1 deletions

View file

@ -0,0 +1,488 @@
# Промт: БЭКЕНД ДОФИКС-ПАК «ВХОДНАЯ ДВЕРЬ ШВА» (по приёмке движковой половины D39.156)
> Выдан оркестратором №19, 25.08.2026. Активный промт зоны бэкенда — один; этот заменяет
> `BACKEND_SEAM_PACK_SESSION_PROMPT.md` (отработан, уезжает в архив при лендинге).
> Параллельно законен только `platform/docs/PLATFORM_P8_REVIEW_SESSION_PROMPT.md` (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.730.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`
уводит оба файла решений выше каталога книги. Класс пред-существующий (`<book_id>.db`), пак
расширил его с одного файла до трёх. На SaaS `book_id` генерируется платформой, то есть
пользователю недостижим.
- **Каталог книги без права записи → `exit 1`, ВНЕ полосы отказов** (`store: ... attempt to write a
readonly database`). Платформа спишет проблему прав ХОСТА в бюджет попыток КНИГИ. Класс
пред-существующий (`store.Open` ведёт себя так же), новый глагол его унаследовал.
- **Проекция создаёт `<project_db>.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) — пред-существующее, пинг.

View file

@ -22,7 +22,7 @@
| Роль | Активный промт | Статус |
|---|---|---|
| Оркестратор | [ORCHESTRATOR_SESSION_PROMPT.md](ORCHESTRATOR_SESSION_PROMPT.md) | роль и нормы; счётчик роли — CURRENT-STATE |
| Бэкенд | [BACKEND_SEAM_PACK_SESSION_PROMPT.md](BACKEND_SEAM_PACK_SESSION_PROMPT.md) | движковая половина закона входной двери шва (D39.156) + строка 218 (дефолт корпуса), дописана 24.08; запуск по слову владельца |
| Бэкенд | [BACKEND_SEAM_FIX_SESSION_PROMPT.md](BACKEND_SEAM_FIX_SESSION_PROMPT.md) | дофикс движковой половины входной двери шва по приёмке 25.08: блокер round-trip reject-документа + восемь мажоров + пять слепых пятен гейта; дерево пака НЕ закоммичено и продолжается, не начинается заново; запуск по слову владельца |
| Платформа | [PLATFORM_P8_REVIEW_SESSION_PROMPT.md](../platform/docs/PLATFORM_P8_REVIEW_SESSION_PROMPT.md) | read-only ревью четырёх осей; запуск по слову владельца |
| Полигон | [POLYGON_EXP2223_REDO_SESSION_PROMPT.md](POLYGON_EXP2223_REDO_SESSION_PROMPT.md) (отложенный — [POLYGON_PACKAGE4_SESSION_PROMPT.md](POLYGON_PACKAGE4_SESSION_PROMPT.md), строка 85) | фаза Д ИДЁТ; ⚠ живой носитель курса — в `eval/dovodka/`, какой именно называет зона (⚠ [POLYGON_PHASE_D_HANDOFF.md](POLYGON_PHASE_D_HANDOFF.md) — перекрытый снимок, читать не как курс) |
| Фронт | активного НЕТ | **ЗОНА ЗАМОРОЖЕНА** (D39.136 п.2 + D39.147: разморозка отдельным словом владельца, не привязана к P7); перечень первого касания — в зонном журнале |