From fa511e65b5a46964b4ef45f5063a5ae6c7b361d2 Mon Sep 17 00:00:00 2001 From: heaven Date: Tue, 25 Aug 2026 12:38:06 +0300 Subject: [PATCH] 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 --- docs/BACKEND_SEAM_FIX_SESSION_PROMPT.md | 488 ++++++++++++++++++++++++ docs/README.md | 2 +- 2 files changed, 489 insertions(+), 1 deletion(-) create mode 100644 docs/BACKEND_SEAM_FIX_SESSION_PROMPT.md diff --git a/docs/BACKEND_SEAM_FIX_SESSION_PROMPT.md b/docs/BACKEND_SEAM_FIX_SESSION_PROMPT.md new file mode 100644 index 00000000..e262f269 --- /dev/null +++ b/docs/BACKEND_SEAM_FIX_SESSION_PROMPT.md @@ -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.73–0.80 ГБ RSS, 40 000 → 1.42 ГБ при документе всего в 2 МБ, то есть ~36 КБ +памяти на решение. Разброс на 20 000 — два прогона документов слегка разной формы (с `note` и без), +а не две разные оси. + +⚠ **И вторая половина, которой в §9 не было:** движок при нехватке памяти выходит **кодом 2** +(`fatal error: ... out of memory` — Go так завершается), а платформа читает 2 как +`CompletedWithFlags` — «команда СДЕЛАЛА свою работу, часть юнитов на человека» +(`platform/internal/ingest/exit.go`, читать, не править). То есть смерть движка от памяти +прочитывается как успешный прогон. Класс пред-существующий и общий для всех глаголов — но +`bank-apply` первый, чей размер входа задаёт ПОЛЬЗОВАТЕЛЬ. + +**Заказ:** кап на размер документа решений плюс внятная строка отказа полосой. Число выбираешь сам +и аргументируешь; ориентир — платформа капирует свои чтения от движка (16 МБ статус / 256 МБ банк). +Хвост про exit 2 — **пингом**, не тихой правкой: он общедвижковый и не твой скоуп. + +### 4.6 `artifacts{}` не доезжает до своего потребителя — РЕШИ САМ форму + +Проверено приёмкой грепом: `Artifacts` существует **только** в `internal/pipeline/status.go`, а +единственный код платформы, выводящий движковый путь (`runner.ReadBank` → `projectDB` → +`filepath.Join(workdir, book_id+".db")` + суффикс `.bank.json`), зовётся из её read-model, чей +интерфейс движка — `{Manifest, Export}`, без `Status` (`platform/internal/readmodel/`, читать, не править). + +Буква §4.5 прежнего заказа исполнена, **цель строки бэклога 213 — нет**: платформа не может выкинуть +свой `projectDB()`, не заведя третий вызов движка на каждую материализацию. + +**Заказ:** тот же версионированный конверт путей отдать на поверхности, которую потребитель уже +читает. `manifest --json` — самый дешёвый кандидат (read-model уже его гоняет), но форму и состав +выбираешь ты и аргументируешь. ⚠ Пере-именование банк-экспорта в фикс-имя по-прежнему ЛОМАЮЩЕЕ, +его окно — строка 161, сюда не тащить. + +⚠ **Твой же пинг §10 п.5** («опубликованный путь читается как приглашение его парсить; единственная +изгородь — комментарий») этим углубляется, и это осознанно: закон п.1 разрешает платформе читать по +путям, СООБЩЁННЫМ движком, а п.3 требует версионированного конверта на каждом выходе. Изгородью +становится конверт, а не комментарий. Если считаешь, что этого мало, — скажи аргументом, это законный +ответ по §11. + +### 4.7 Отказ по алиасу терма, одобренного в ТОМ ЖЕ вызове, принимается инертным — ДЕЛАЙ РОВНО ТАК + +Проверка `aliasOwner` смотрит в `res.Delta` ДО цикла применения, поэтому терм, одобряемый решением +`i`, ещё не лежит в дельте, когда судится решение `j`. + +**Репро:** один документ = `approve 方源` + `decline 方小子`, где `方小子` — алиас кластера майнера у +`方源`. Оба отчитываются `applied`, `rejected: []`. В дельте `方小子` остаётся ФАЙРЯЩИМ алиасом +одобренного терма, а reject — только фильтр предложений. Прозвище продолжает подставляться в +переводе, владелец уверен, что снял его. Те же два решения ДВУМЯ вызовами — корректно отвергаются +по имени. + +**Заказ:** правило судит НАБОР, как и заявлено в шапке `ApplyDecisions`, а не только состояние файла +до вызова. + +### 4.8 Перевёрнутое окно глав принимается — ДЕЛАЙ РОВНО ТАК + +`resolveDecision` проверяет только `< 0`. **Воспроизведено приёмкой:** решение с +`since_chapter: 90, until_chapter: 3` принимается и записывает в дельту строку, которая не сработает +ни в одной главе. Отказывать. + +### 4.9 Мина 3 строки 199: «применено» ≠ «стоп снимется» — РЕШИ САМ форму + +`reverseSectionTerms` режет кап 200 ПОСЛЕ фильтров, поэтому на книге с >200 banknote-only +поверхностями подпись всей карты стоп НЕ снимет: приедет свежая двухсотка, и актов подписи +понадобится `ceil(N/200)`. + +Механизм против этого паком не заказан и сейчас не заказывается. **Заказывается ЧЕСТНОСТЬ отчёта:** +вызов, после которого стоп заведомо не погаснет, не должен читаться как «готово». Форму (поле в +отчёте, строка предупреждения, что-то ещё) решаешь сам и аргументируешь; если считаешь, что дверь +этого знать не может — скажи это аргументом, и это будет принято. + +### 4.10 SIGTERM игнорируется, лок держится — РЕШИ САМ форму + +Проверено приёмкой: на длинном документе процесс переживает `SIGTERM` и продолжает держать +эксклюзивный лок проекта; снимается только `SIGKILL`. Платформа останавливает движок именно +`SIGTERM` (`platform/internal/runner/`, читать, не править), а лок блокирует прогон книги. + +Закон п.2 отводит `SIGTERM` роль «дочти и отпусти» для ПРОГОНА; для $0-глагола аналог — «брось +работу и отпусти лок, ничего не записав»: у всё-или-ничего нет частичного результата, который стоило +бы дописывать. Форму решаешь сам. + +### 4.11 Восемь слепых пятен гейта, десять мутаций — ДЕЛАЙ РОВНО ТАК + +Каждое ниже — место, где приёмка посадила мутацию и **батарея осталась зелёной**. Живого дефекта в +них нет; отсутствует пин. ⚠ Считай по МУТАЦИЯМ, а не по строкам таблицы: строка (д) несёт три +независимые посадки, и пин нужен на каждую. Каждый пин обязан краснеть на своей мутации — покажи это +в отчёте прогоном. + +| № | что не сторожится | мутация, которую пин обязан поймать | +|---|---|---| +| а | **арбитраж лока против ПРОГОНА.** `TestBankApplyRefusesABusyProject` держит лок тем же `store.LockProject`, поэтому проверяет «LockProject конфликтует с LockProject». Инвариант ратифицирован (закон п.2, «тот же flock проекта») | сменить файл лока в `LockProject` (`dbPath + ".lock"` → любой другой) — сегодня батарея зелёная. Пин обязан держать лок так, как его держит ПРОГОН (`store.Open`) | +| б | **`canonical_rewrite` и `preexisting_problems`** — два опубликованных поля отчёта, на которых пак держит смягчение двух СВОИХ ЖЕ названных рисков (§9 необратимый пере-рендер, R7 проглоченная поломка) | занулить оба (`rep.CanonicalRewrite = false`, `PreexistingProblems: []string{}`) — сегодня батарея зелёная | +| в | **гейт тотальности таблицы отказов слеп к сценарию, который сам называет.** `TestTheRefusalTableIsTotal` держит РУЧНОЙ список классов и сверяет его длину с длиной `refusalExit` | добавить шестой класс в `pipeline.RefusalClass`, не внося его НИ в `refusalExit`, НИ в список теста — сегодня тест ПРОХОДИТ, а класс уехал бы в catch-all 19. Перечисляй классы из ИСХОДНИКА. ⚠ `TestDispatchCommandsCoversTheSwitch` — прецедент ТЕХНИКИ, но не реализации: он ключён на `*ast.BasicLit` и потому слеп к `case` с константой (это его собственный пинг, §10 п.2 отчёта), а значения `RefusalClass` объявлены именно КОНСТАНТАМИ — копирование один-в-один воспроизведёт слепоту. Нужен обход `GenDecl`/`ValueSpec` | +| г | **нормализация в REPLACEMENT-половине отказа.** Тесты используют поверхности, на которых `NormalizeSourceKey` — тождество | `nk := text.NormalizeSourceKey(src)` → `nk := src` в `dropTerms`. Пин: одобрен 虫, отказ приходит по 蟲 (традиционная форма) — отказ обязан снять одобрение | +| д | **глубокое копирование алиасов в `withRubyAliases`** — ровно класс R4/R5, ради которого поле `Ruby` заведено; **перенос `note` при промоушене**; **рантайм-половина гарда ОБЪЯВЛЕННОГО пути** (`decisionFilePresent(path, declared=true)`) | снять глубокую копию · `if false { t.Note = d.Note }` · `case !declared && ...` → `case ...` — все три сегодня зелены | +| е | **абсолютность путей в `artifacts{}` запинена ВАКУУМНО.** Проверка `filepath.IsAbs` в `statusartifacts_test.go` есть, но фикстура даёт уже абсолютный `--config`, поэтому `absPath` в ней ничего не делает и его удаление не краснит. Замерено: с ОТНОСИТЕЛЬНЫМ `--config` `absPath` — несущий, и без него потребитель в другом рабочем каталоге прочитает не тот файл | убрать `absPath` у любого из четырёх путей — сегодня батарея зелёная. Пин обязан звать глагол с относительным `--config` | +| ж | **байтовый гейт `changedDoc` не запинен**: мутация `deltaChanged := res.DeltaTouched` (байтовый гейт снят целиком) оставляет и `TestBankApplyRepeatIsAByteNoOp`, и `TestBankApplyWritesTheConventionalFiles` зелёными. Контракт «байтовый no-op» держится сегодня только на `*Touched` из чистого слоя — второй рубеж не защищён | снять байтовое сравнение в `changedDoc` | +| з | **правило «файлы решений едут С КНИГОЙ, а не с `project_db`»** не запинено: конвенционный дефолт можно перевесить на каталог `project_db`, и батарея зелёная — во всех тестах два каталога совпадают. Правило несущее: `project_db` может указывать куда угодно, а решения владельца обязаны ехать с книгой в бэкапе и экспорте каталога | перевесить дефолт на `filepath.Dir(b.ProjectDB)`; пин обязан развести два каталога | + +⚠ Уже существующие пины, которые тоже придётся починить, вынесены отдельным номером — **§4.15**. + +### 4.12 Цепь §8 — ЗАКРЫТА ПРИЁМКОЙ, забери её в репозиторий — ДЕЛАЙ РОВНО ТАК + +Твой §8 просил приёмку прогнать цепь целиком и объяснял, что своими силами это невозможно: «до стопа +надо дойти ОПЛАЧЕННОЙ черновой волной». **Довод оказался шире правды.** В репозитории уже есть +харнесс, гоняющий тот же банк-стоп против ФЕЙКОВОГО провайдера за $0 — +`internal/pipeline/miningstop_join_test.go`: `setupMiningStopProject` + `newJSONProvider` + +`newVerifyRunner` + `runToSignatureStop` + `readSignatureMap`, на них стоит ~30 живых тестов. + +Приёмка написала и прогнала цепь целиком, и она **работает**: стоп на 2 термах → `bank-apply` +промотит dst-несущий и отклоняет WHICH-only → резюм → «empty delta, auto-continuing to the edit wave», +редакторская волна отработала. Мина 1 строки 199 (ливлок на `status: auto`) не сработала. + +**Заказ: этот тест обязан жить в репозитории**, потому что он — единственный, кто видит ливлок +уровня ЦЕПИ, которого частичные тесты по построению не видят. Собери его сам по описанию выше: +дойти до стопа → разобрать карту подписи `seed.DecodeFile` → построить документ, где термы с `dst` +идут в `approve`, а WHICH-only в `decline` → `ApplyBankDecisions` → пере-запустить прогон → убедиться, +что стоп ПОГАС и редакторская волна пошла. + +**Как понять, что собрал верно.** На фикстуре без опций стоп приходит на ДВУХ термах — один с +`dst`, один WHICH-only, — то есть документ решений выходит «1 approve + 1 decline», `bank-apply` +отвечает `changed=true`, `accepted=2`, а файлы у него КОНВЕНЦИОННЫЕ (ключей фикстура не объявляет). +На резюме движок пишет в журнал `skipped_as_declined=1 emitted_by_miner=0` и «empty delta, +auto-continuing to the edit wave», после чего редакторская волна отрабатывает. Если у тебя иначе — +собрано не то. + +⚠ **Ограничение назови в комментарии теста сам:** синтетический контраст меняет, КАКИЕ кандидаты +предложены, но не то, проходит ли подписанная строка фильтр подписи. Для вопроса «гаснет ли стоп» +это корректно; для замера качества майнера — нет. + +⚠ Соседний факт, поймай его при написании: `TestMiningStopRejectClearsDelta` заканчивается +проверкой «погасший стоп не оставляет карту подписи», но использует ВТОРОЙ, свежий проект, где карта +никогда не писалась, — проверка почти вакуумна. На ОДНОЙ книге карта остаётся на диске: движок её +нигде не удаляет (`os.Remove` в `mining.go` не встречается). Поведение пред-существующее, паком не +введено — **пинг, не правка**, но пусть твой тест это зафиксирует явно. + +### 4.13 Мелочи — РЕШИ САМ, чинить или отклонить с аргументом + +- **`canonical_rewrite` — ИЛИ по двум файлам**, поэтому предупреждает про файл, который вызов не + откроет (замерено: на книге, чьи оба файла записал движок, флаг корректно `false`; горит, когда + комментарии оператора несёт reject-лист, а решение трогает только дельту). Потребитель рисует + предупреждение не про то. +- **`note` нельзя ОЧИСТИТЬ**: повторное решение без `note` сохраняет прежнюю заметку. Заказом не + оговорено ни в ту, ни в другую сторону — назови поведение решением, каким бы оно ни было. +- **`book_id` не проверяется на путевую безопасность**, а путь строится ДО валидации: `book_id: ../x` + уводит оба файла решений выше каталога книги. Класс пред-существующий (`.db`), пак + расширил его с одного файла до трёх. На SaaS `book_id` генерируется платформой, то есть + пользователю недостижим. +- **Каталог книги без права записи → `exit 1`, ВНЕ полосы отказов** (`store: ... attempt to write a + readonly database`). Платформа спишет проблему прав ХОСТА в бюджет попыток КНИГИ. Класс + пред-существующий (`store.Open` ведёт себя так же), новый глагол его унаследовал. +- **Проекция создаёт `.lock`** в каталоге книги. Требование §4.2 «проекция БЕЗ мутации» + держится (файлы решений не тронуты), но «не трогает ничего» буквально неверно. +- **Порядок двух записей пессимален для `decline`.** Комментарий обосновывает «дельта первой» тем, + что крах между записями оставит промоушен без снятого отказа — верно для `approve`. Для `decline` + тот же порядок теряет решение ЦЕЛИКОМ: дельта переписана (одобрение снято), reject не записан. + ⚠ **Приёмка это НЕ воспроизвела** — атомарная замена обходит права на файл, а настоящее окно + микросекундное; повтор вызова при этом сходится. Утверждение остаётся рассуждением, не замером: + реши сам, стоит ли оно правки, и аргументируй. +- **`artifacts.bank_export` печатается безусловно**, в том числе для книги, где файла ещё нет + (проверено на копии стенда). Читатель платформы это переживает (обычная ошибка открытия), но поле + именует МЕСТО, а не наличие — реши, достаточно ли этого, и назови решение. +- **`aliasOwner` возвращается на ПЕРВОМ совпадении**, поэтому исход `decline` в теории зависит от + порядка строк дельты, если поверхность одновременно алиас одной строки и `src` другой. + ⚠ Такой документ уже ловится `ApprovedSharedKeyCollisions`, и приёмка значимого расхождения + **воспроизвести не смогла** — это гипотеза, а не находка. Отклонить с аргументом законно. +- **Проекция отвечает «что применится», а не «почём».** Закон п.7 звучит как «почём ДО + подтверждения», прежний заказ денег в проекции не требовал, а смета уже едет в `status --json`. + Это вопрос ДИСПОЗИЦИИ, а не дефект исполнения: **не чини, а вынеси пингом** со своей рекомендацией — + решать оркестратору. + +### 4.14 Довод отказа `--keys-file` не покрывает всех, кого отвергает — ДЕЛАЙ РОВНО ТАК + +Текст отказа (`cmd/tmctl/invocation.go`) звучит так: «`--keys-file` принимает только `translate`, не +%q: **$0-читающие команды (report/status/export/manifest/seed-lint)** не должны требовать ключей вовсе +(D20.4)». В ту же ветку попадают `redrive` (**платный** путь: «only a real redrive re-attacks and +re-bills»), `backup` и `migrate` (деплой-шаги). Довод их не описывает, и оператор, получивший этот +отказ на `redrive`, читает про себя неправду. + +**Заказ:** довод обязан покрывать всех, кого ветка отвергает. Одна строка. +⚠ Саму развилку «нужен ли `redrive` канал ключей аргументом» **НЕ решай** — прежний промт сказал +«флаг только у `translate`», и это исполнено верно; развилка остаётся пингом. + +### 4.15 Три уже существующих пина тавтологичны или узки — ДЕЛАЙ РОВНО ТАК + +Вынесено отдельным номером, чтобы механическая сверка §7 их видела. + +- **Вердикт `Preexisting` исполняется одной проверкой из четырёх:** + `TestAPreExistingCollisionDoesNotBlockAnUnrelatedDecision` гоняет только + `ApprovedSharedKeyCollisions`; `MinedDeltaSeedCollisions`, `GenderVocabViolations` и + `UnknownVoiceCharacters` не исполнены ни одним тестом. +- **Четвёртый оракул `FuzzDecisionDocument`** пере-выполняет ровно то, что `documentProblems` уже + выполнила внутри `ApplyDecisions`, и заперт под `len(res.Rejected) == 0` — тавтология. +- **`TestDecisionByBankID`** вычисляет ожидаемый id той же функцией, которую проверяет, поэтому не + видит потери инъективности — свойства, ради которого в `TermID` и сделана преамбула про + length-prefix. Сверяйся с независимо посчитанным значением, а не с самой функцией. + +### 4.16 Деньги: платных вызовов в паке НОЛЬ — ДЕЛАЙ РОВНО ТАК + +Санкции на платные вызовы нет и не запрашивается. Всё выше проверяется юнит-тестами, фейковым +провайдером и пробой на копии книги стенда. Упёрся в место, где без денег не доказать, — **пинг с +названной суммой, не трата**. Рантайм-вердикт по платному пути остаётся PLAUSIBLE — это ожидаемый +исход, а не дефект отчёта; так и напиши. + +## 5. Мандат самопроверки ИСПОЛНЕНИЕМ + +Не «перечитал» — **исполнил**. Минимум: + +- **Батарея целиком, ИЗ КАТАЛОГА `backend/`**, после каждой содержательной правки: `go build ./...`, + `go vet ./...`, `go test ./... -count=1`, **`make lint`**. ⚠ `go.mod` в корне репозитория НЕТ — модули раздельные. + ⚠ **И `make battery-stand` — обязательно, минимум один раз в конце.** Пак владеет `internal/standdata` + (тест-резолв корпуса), а регрессию в резолвере обычная батарея не ловит по построению: без корпуса + эти тесты просто скипаются. Ожидаемая картина — всё зелено, кроме `TestMinerFullBookParity`, красного + из-за отсутствующего jieba-контраста (он лежит по внутрирепозиторному пути, но вне git по построению — + клон его не получает; это строка бэклога 123, не твой дефект). + Красный тест не «подгоняется»: править или удалять тест, голден или гейт ради зелени — + НЕДОПУСТИМО; несогласие с тестом — пинг оркестратору, а не правка. + ⚠ **В `internal/store` живут два пред-существующих `-race`-флейка** — + `TestTheSeamIsNotChargedToTheStoreOperationBudget` и `TestKillMinus9LosesAtMostOneCall`. Приёмка + доказала тремя независимыми путями, что они не от пака: правка `store.go` чисто аддитивна, покрытие + новых функций под обоими тестами ноль, и чередующийся A/B против дерева без пака даёт ту же + частоту. Оба меряют ВРЕМЯ и чувствительны к нагрузке. Красное ИМЕННО в них — шум; красное в любом + другом тесте — регрессия. +- **Собственные адверсариальные посадки ВНЕ списка §4.11.** Десять названных там — обязательный + минимум, а не потолок: посади свои в новый код и покажи, что батарея краснеет. + ⚠ **Посадка обязана называть ПАКЕТ, в котором ищется пин** — не тот, где лежит правка: ложное + «мутация выжила» опаснее пропущенной, потому что выглядит как результат. + ⚠ **Харнесс мутаций обязан снимать отпечаток целей до и после** — твой же §11 п.1 объясняет цену. +- **Живая проба на копии книги стенда** (`<репозиторий>/books/gu-zhenren/*`). Работать в КОПИИ вне + рабочего дерева; правки в `books/` не делать вовсе — это отдельный git-репозиторий и не твоя зона. +- **Субагенты РАЗРЕШЕНЫ явно** — на ревью своего кода, на поиск дефектов вне твоей карты, на + опровержение твоих же выводов. Дефолт-запрет харнесса иначе тихо победит. ⚠ Соразмеряй параллельность с ресурсами: + субагенты, одновременно гоняющие полную батарею, топят друг друга, и упавший от нагрузки агент + выглядит как отсутствие находок. +- **Артефакт-находки обязателен:** дописывай `backend/docs/SEAM_PACK_FINDINGS.md` новой секцией + (не переписывай старые — они улика приёмки), что посадил, что краснело, что выжило. + +### 5.1 Обязательное для кодового пака + +- **Последний абзац отчёта — это план или обещание? Значит сделай его СЕЙЧАС.** +- **Дифф `^func Test` — исполнением, а не по памяти:** покажи командой, какие тесты добавлены и тронуты. +- **Интервальная самоверификация субагентом** против ЯВНЫХ критериев в середине работы. +- **Перед отчётом сверь каждый клейм с результатом инструмента ЭТОЙ сессии.** + +## 6. Оси ревью (три, по характеру работы) + +1. **Round-trip формата** — главная ось этого дофикса: всё, что дверь ЗАПИСЫВАЕТ, она обязана + ПРОЧИТАТЬ обратно, на обоих документах и на всех текстовых полях. Фаззер здесь сильнее примера. +2. **Слепота гейта** — для каждой правки: какой мутацией она сторожится и краснеет ли пин НА САМОМ + ДЕЛЕ. Восемь мест §4.11 — это там, где гейт уже оказался слепым; не заведи девятое. +3. **Общность** — заработает ли на паре, которой в репо нет, без правки Go. Приёмка проверила дверь + на `en→de` и `ja→en` и нашла её чистой; твои правки не должны это сломать. + +Ты вправе ЗАМЕНИТЬ любую ось своей, если аргументируешь, чем твоя лучше ловит дефекты этой работы. + +## 7. Записка-план + +До кода — короткая записка `backend/docs/SEAM_FIX_PLAN.md`: что делаешь, в каком порядке, какие +развилки видишь. Комплектность против §4 сверяется МЕХАНИЧЕСКИ (таблицей или грепом), а не глазами: +**пунктов шестнадцать** (`grep -cE '^### 4\.' `), и «кажется, всё» здесь не работает. Заказов ВНЕ §4 +нет — всё, что раньше жило в приложениях и в хвостах, вынесено в §4 отдельными номерами +(§4.14, §4.15); приложения несут только справку и пинги. + +## 8. Заявление = команда + +Каждое число и каждая категорика в отчёте — **с командой, которой получены, прямо рядом**. Не по +памяти о том, как ты это выяснял. Приёмка пере-ранит выборочно; расхождение отчёта с пере-раном +дороже, чем честное «не проверял». + +⚠ Прошлый отчёт дал повод: его замер цены глагола (`5000 → 1.60с/133МБ · 20000 → 9.08с/350МБ`) при +пере-ране приёмки сошёлся по времени на 5k/10k и разошёлся вдвое по ПАМЯТИ (199/395/798 МБ), а +выведенный из него потолок «до ~80k термов в 60-секундный бюджет платформы» оказался оптимистичен +примерно вдвое. Числа, которые поедут в чужой пак как датум, стоят отдельной аккуратности. + +## 9. Эхо-протокол старта + +ДО работы — не больше десяти строк: скоуп как ты его понял · инварианты, которые не тронешь · что +делать НЕ будешь. Расхождение с этим промтом видно сразу и стоит дёшево. + +## 10. Что НЕ удалось + +Обязательная секция отчёта. «Не проверено» ≠ «работает». Вердикт о рантайме без живого прогона — +максимум PLAUSIBLE, и так и пиши. + +## 11. Канал вопросов + +Конфликт промта с кодом или доками — **пинг через владельца, не интерпретация**. Ты вправе сказать +«этого делать не надо» с аргументом: пак, принёсший развилку вместо тихой девиации, — хороший пак. +Четыре пункта выше (§4.6, §4.9, §4.10, §4.13) прямо приглашают такой ответ. + +--- + +## Приложение: что приёмка ПОДТВЕРДИЛА — не чини это заново + +Чтобы дофикс не тратил время на уже доказанное: + +- **Цепь работает** (§4.12) · **мины 1 и 2 строки 199 обезврежены** (явный `status: approved` пишется; + approve без `dst` отвергается). +- **§4.1–§4.7 прежнего заказа исполнены полностью**, все девять семантик §4.2 — тоже, семь пунктов + закона — тоже; скоуп-крипа нет. Пять вещей сверх заказа (класс 14, `export_version`, пакет + `standdata`, строгость reject-читателя, два поля отчёта) — аргументированы и безопасны. +- **Ни один платформенный канал не ломается**: аддитивные поля прошли через её собственные декодеры + на реальных документах; строгих декодеров у неё нет ни одного на пути движковых документов; + id термов банка не сдвинулись; `Refused(14)` истинно, `DeploymentFault` 14 не покрывает, а интейк + отправляет нераспознанный код полосы в ветку, которая файл пользователя НЕ удаляет. +- **Строгость не сломала ни один живой документ корпуса** — 44 из 44 (29 сид-схемы + 15 reject-схемы). +- **Критерий §4.6 ПРЕЖНЕГО заказа (дефолт корпуса через `standdata`) выполнен буквально** — не путать + с §4.6 ЭТОГО промта, который про другое; **§13 сходится точно** (59 тест-функций + 2 фаззера, + удалено 0); все 28 пинов, названных отчётом, существуют ровно по одному разу. +- **$0 доказан** структурно и исполнением; ледджер и база не сдвинулись ни на байт. +- **Три заявки ревьюеров приёмка ОПРОВЕРГЛА, не поднимай их заново:** src, нормализующийся в пустой + ключ, ловится дедупом поверхностей · повторный `decline` корректно отчитывается `already_applied` · + отказ по алиасу подписанного сид-терма отвергается по имени. + +## Приложение: пинги, которые дофикс НЕ исполняет + +Названы, чтобы ты их не «допилил попутно»: + +- **exit 2 при OOM читается платформой как успех** (§4.5) — общедвижковый класс, не твой скоуп. +- **`readEngine` платформы выбрасывает stdout при любом ненулевом коде, кроме 2** — а отчёт + `bank-apply` при `exit 14` и есть продукт вызова. Требование к ПЛАТФОРМЕННОМУ паку. +- **Развилка «нужен ли `redrive` канал ключей аргументом»** — остаётся пингом. Заказом стала только + вторая половина этого пинга — неполный довод в тексте отказа, §4.14. +- **`rubyToCandidates` мёртв в проде** · **гейт `TestDispatchCommandsCoversTheSwitch` слеп к `case` с + константой** (⚠ эта слепота названа в §4.11 (в) как ловушка при копировании техники — не унаследуй её) · + **схема сида без `omitempty`** — три пинга твоего же §10, остаются пингами. +- **Проекция берёт ТОТ ЖЕ лок, что и применение** (§10 п.4) ⇒ «поправить термин при живом прогоне» + даёт `12` даже на ПРЕВЬЮ, на всю длину прогона. Проектируется ПЛАТФОРМЕННЫМ паком (его окно — + состояние между прогонами, когда движок вышел и лок отпущен), не движковым. +- **Опубликованные пути двух файлов решений читаются как приглашение их парсить** (§10 п.5) — + ⚠ см. §4.6: изгородь снимается сознательно и по ратифицированному направлению закона. +- **Карта подписи не удаляется после погасшего стопа** (§4.12) — пред-существующее, пинг. diff --git a/docs/README.md b/docs/README.md index feb85c28..64f044c7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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); перечень первого касания — в зонном журнале |