From 1b8aa86cce7cecfbe8c8193b9ea93b7d74d90292 Mon Sep 17 00:00:00 2001 From: heaven Date: Fri, 4 Sep 2026 21:05:50 +0300 Subject: [PATCH] Re-point the anchors the engine's loop moved, mark one dead from its cure, and lengthen four tokens too short to identify a target: the anchor lint reads zero --- docs/PROGRESS.md | 8 ++++---- docs/architecture/15-money-path.md | 6 +++--- docs/architecture/17-seam-inbound-law.md | 4 ++-- docs/experiments/00-provider-quirks.md | 4 ++-- docs/experiments/23-editor-tier.md | 10 +++++----- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index 6cbcbc46..64ed9a3d 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -75,7 +75,7 @@ | 90 | **Ф2-кандидаты 02-mvp-плана без носителя** (свип-хвост, D39.73): CometKiwi/T-index метрики судьи · Batch API судьи (экономия) · llama-server-интеграция локали · стриминг — взять/закрыть при пилот-препе; + H16 самоулучшение (V3-п.2, ПТ-26): дизайн петли по данным судьи — тем же препом | полигон/бэкенд | когда-нибудь (Ф2.5) | пилот-преп-промт (с 85) | 02-mvp-plan, D39.66, D39.73, D39.80 | | **— НАХОДКИ СВИПА ПОЛНОТЫ (D39.66: 951 обязательство проверено, потери возвращены в трекер) —** | | | | | | | 80 | **Резюме-слой памяти** — проза-суммарайзер ЗАКРЫТ (D39.69); строка = ГЕЙТ ПИЛОТА Ф2.5: первый деливерабл пилота — «допускает ли автономная нарратив-состояние-строка ДЕТЕРМИНИРОВАННЫЙ верификатор (source-anchored) — или это D1-компаундинг со схемой»; не-покрытые классы (source-anchored reveal · арк-колбэки без ключа) реальны, но не измерены как дефект | Ф2.5 (Q3 подтверждён D39.70) | когда-нибудь (пилот) | Ф2.5 пре-рег | D25 п.8–9, D39.66, D39.69 | -| 81 | **ja-преп B6-ja (сужено пост-сверкой D39.66)**: Поливанов-валидатор (`translit_policy` = «Phase 2»-заглушка, `backend/internal/store/migrate.go:187`=`translit_policy TEXT` и `backend/internal/store/migrate.go:195`=`ruby_reading`) + kana-омограф POS/known-word гейтинг / B6-токенизатор (`backend/internal/membank/memory.go:1077`=`KANA and HAN are deliberately NOT boundary-checked`); чек-лист kana-алиасов сида УЖЕ построен (backend/README §ja + `AttachRubyAliasesToManual`) — остаток в нём только дизамбигуация двойных чтений (тот же B6) | бэкенд | когда-нибудь (перед ja→ru) | ja→ru-пак (рядом 35) | D16.4, D17.1, D18, D39.66 | +| 81 | **ja-преп B6-ja (сужено пост-сверкой D39.66)**: Поливанов-валидатор (`translit_policy` = «Phase 2»-заглушка, `backend/internal/store/migrate.go:187`=`translit_policy TEXT` и `backend/internal/store/migrate.go:195`=`ruby_reading`) + kana-омограф POS/known-word гейтинг / B6-токенизатор (`backend/internal/membank/memory.go:1155`=`KANA and HAN are deliberately NOT boundary-checked`); чек-лист kana-алиасов сида УЖЕ построен (backend/README §ja + `AttachRubyAliasesToManual`) — остаток в нём только дизамбигуация двойных чтений (тот же B6) | бэкенд | когда-нибудь (перед ja→ru) | ja→ru-пак (рядом 35) | D16.4, D17.1, D18, D39.66 | | 82 | **Морфо-гейт РОДА** (C3: русский глагол прош. вр. при gender=hidden = механический спойлер-канал; «жалоба №1 читателей MTL»; python-сайдкар/pymorphy, связка с морфопроходом Decl из 73). **$0-прототип получен эксп-21** (`battery.check_gender`: карточки из подписанного сида, фильтр по именительному падежу, 86–112 сверок/арм — D39.117); движковый гейт не построен | бэкенд | когда-нибудь (Ф2-гейты) | отдельный пак (с 52) | 06-реестр C3, research/12, D39.66, **D39.117** | | 85 | **Пилот-преп добор** (к переупаковке PACKAGE4): Bertalign GOLD-пары + zh→ru spot-check (D29.2в; GPL-тулинг отдельно) · fidelity-каскад R2 shadow с корпус-подготовкой (D25.2) · exp08 v3 пере-съём COGS до фиксации budget_usd (D21.8) | полигон | когда-нибудь (Ф2.5) | пилот-преп-промт | D25, D29, D21.8, D39.66 | | **— ПАК-21 «чекеры»: РАСТВОРЁН (D39.62); шестёрка исполнена 2/6 фазой 2 (D39.64: #11 · строка 26), остаток — ниже —** | | | | | | @@ -173,17 +173,17 @@ | 205 | **Гейт якорей ИСПОЛНЕН 21.08** — `docs/scripts/counts.py --lint`, область `docs/` + `platform/docs` + `frontend/docs`, сверка по СОДЕРЖИМОМУ, двусторонняя дельта. ⚠ Быстрый выход по «коммит не трогает docs/» из хука убран НАМЕРЕННО: цели якорей — в основном КОД, и главный двигатель протухания приезжал ровно тем коммитом, на котором хук молчал. Форма якоря: ОПТ-ИН токен ожидания `` `путь:12-14`=`подстрока` ``, сверка ПРОБЕЛЬНО-нормализованная. ⚠ **Слепота печатается САМИМ гейтом каждый прогон — читать оттуда, копии здесь нет.** Одна её грань в вывод не попала: в ручном прогоне «дельта» = всё незакоммиченное кем угодно, поэтому ТРЕБОВАНИЕ токена живёт только под `--from-index` (ПРОВЕРКА уже написанных токенов дельтой не ограничивается вовсе — токен был истинным при записи, значит упавший есть гниль по построению). **ОСТАТОК:** миграция корпуса на токен-форму органическая — токен требуется на ЗАТРОНУТЫХ строках, счёт растёт сам с каждым касанием. ⚠ Числа здесь НЕ держим (прежняя редакция держала «4 из ~580» и протухла за сутки): текущий счёт токенов, якорей и доков гейт печатает КАЖДЫЙ прогон| оркестратор/доки | сделано 21.08, миграция органическая | исполнено; остаток гасится касаниями | аудит работы №18 + разбор с внешним ревьюером, 21.08 | | 207 | **Пол самосогласованности манифеста стоит только у материализатора, интейк тот же документ ПРИНИМАЕТ — и по такой книге можно ОПЛАТИТЬ прогон** (`PD-367`, вторая волна ревью P8-FIX). Манифест `{ChaptersTotal: 120, UnitsTotal: 400}` с пустым списком глав `Whole()` отвергает, а `books.Parse` заводит книгу `not_started` с `chapter_count=120` и пустым деревом; потолок считается ОТ `chapter_count`. **Очевидное лекарство опровергнуто исполнением самой зоной:** применить `Whole()` на интейке нельзя — вся батарея интейка ездит на манифестах без списка глав, контракт интейка это только счётчики. Значит решение не зонное: либо контракт интейка меняется, либо пол ставится в другом месте цепи | оркестратор → платформа | скоро | нужно решение (лекарство ломает запиненный контракт интейка) | D39.154 п.10, `PD-367` | | 209 | **Риг живых проб не может залогировать тела запросов ни при каких env** (находка бэкенд-пака честности 17.08, оставшаяся без носителя до аудита доков 22.08): `LOG_LLM_BODIES=1`+`LOG_LEVEL=debug` объявлены носителем тел, но ключ читается из `obs.ReqInfo` контекста, а `live_reprobe_test.go` строит ctx БЕЗ `WithReqInfo` — гейт `obs/logging.go` не пропускает. Обход, которым пользовались: ручной дамп сообщений в файл. Цена — каждая живая проба, которой нужно прочитать провод глазами, платит за обход заново | бэкенд | когда-нибудь (следующее касание рига живых проб) | малое касание (`WithReqInfo` в риге) + пин | пак честности 17.08, вынесено аудитом доков 22.08 | -| 210 | **Род НЕ ПРОИЗВОДИТСЯ авто-путём вовсе — механизм доставки построен, а данных для него нет** (найдено трассировкой цепи банка 22.08, сверено кодом оркестратором; полигон вскрыл то же независимо: «в банке НЕТ ПОЛЕЙ ПОЛА»). Доставка рода на провод ПОСТРОЕНА и работает: `membank/memory.go` кладёт `genderConstraintNote` в черновую инъекцию — но ТОЛЬКО для строк со статусом `Confirmed`, по прямой директиве владельца D39.21 «род должен доезжать». Данных ей никто не даёт: промпт терминолога возвращает РОВНО ТРИ поля (`backend/prompts/zh-ru/terminologist.md`: термин · перевод · уверенность 0–100) — поля рода в ответе НЕТ; майнер берёт `gender` исключительно из СИДА (`internal/miner/miner_emit.go` `seedMeta`, `meta.gender = e.Gender`), эмитируемые кандидаты его не несут. ⇒ **на любой книге без ручного сида ось рода мертва по построению**, и «плывущий род» — это не дефект рендера, а отсутствие входных данных. Прямо бьёт по цели №1: реестр памяти C3 называет русский глагол прош. вр. при скрытом роде «механическим спойлер-каналом» и «жалобой №1 читателей MTL» (строка 82). **Развилка, которую надо назвать до стройки:** добавлять ли поле рода в ответ терминолога (двигает провод ⇒ ТОЛЬКО в вайр-батч 182) ЛИБО выводить род отдельным дешёвым проходом, ЛИБО признать род сид-только свойством и гейтить им качество явно. Родня 82 (морфо-гейт рода) и 178 (калибровка `sense`). ⚠ Замка ДВА и они независимы — род рендерится только при `Disp == Confirmed` (`membank/memory.go:705-713,823-827`=`Mirrors the editor block's confirmed-only gender`), а `mining.go:676` запрещает авто-банку статус `approved`. Род НЕ единственное осиротевшее поле: `Sense`/`Speech`/`Decl` тоже приходят только из сида, авто-продюсер есть лишь у `Dst` (терминолог) и `Type` (классификатор). ⚠ Поле `Gender` в схеме сида УЖЕ ЕСТЬ (`internal/seed/seed.go:71`=`Gender string`) — ось мертва в ПИСАТЕЛЯХ, не в формате. ⚠ **И тесты слепы ПО ПОСТРОЕНИЮ** (пере-проверено 23.08): все gender-кейсы конструируют род РУКАМИ, то есть зелёная батарея не отличает «производитель работает» от «производителя нет»; гейта на дрейф рода в движке нет | бэкенд (+полигон на замер) | **скоро (цель №1; правка провода — вайр-батчем 182)** ⚠ **ЗАМОК №0, найденный бэкенд-сессией 04.09 и НЕ снимаемый: произведённый род не доезжает до ТРАНСЛЯТОРНОГО провода вообще — там нет самой строки.** Черновая волна выбирает по `baseMemory`, из которого строки `Source:"mined"` вычеркнуты по построению (`backend/internal/pipeline/bankmaterialize.go`, греп `Source:mined excluded`), а авто-банк и подписанная дельта пишутся именно как `mined` (`mining.go`, греп `base-excluded`). ⛔ Это не дефект, а денежный контракт `D39.42` п.3: инъекция черновой волны обязана быть байт-стабильной поперёк обогащения банка, иначе пере-снапшот платит ДВЕ волны вместо одной. ⇒ **честная граница построенного: род производится и доезжает до РЕДАКТОРСКОГО провода, на черновой не едет и не поедет, пока цена двух волн не решена отдельно.** Замер этого состояния прибит тестом `backend/internal/pipeline/gender_wire_test.go` — он покраснеет, если следующий пак откроет любую дверь, и заставит автора назвать какую. | развилка → решение → правка промпта/прохода + замер на прогоне с редактурой | трассировка цепи банка 22.08, D39.21, C3 реестра памяти | +| 210 | **Род НЕ ПРОИЗВОДИТСЯ авто-путём вовсе — механизм доставки построен, а данных для него нет** (найдено трассировкой цепи банка 22.08, сверено кодом оркестратором; полигон вскрыл то же независимо: «в банке НЕТ ПОЛЕЙ ПОЛА»). Доставка рода на провод ПОСТРОЕНА и работает: `membank/memory.go` кладёт `genderConstraintNote` в черновую инъекцию — но ТОЛЬКО для строк со статусом `Confirmed`, по прямой директиве владельца D39.21 «род должен доезжать». Данных ей никто не даёт: промпт терминолога возвращает РОВНО ТРИ поля (`backend/prompts/zh-ru/terminologist.md`: термин · перевод · уверенность 0–100) — поля рода в ответе НЕТ; майнер берёт `gender` исключительно из СИДА (`internal/miner/miner_emit.go` `seedMeta`, `meta.gender = e.Gender`), эмитируемые кандидаты его не несут. ⇒ **на любой книге без ручного сида ось рода мертва по построению**, и «плывущий род» — это не дефект рендера, а отсутствие входных данных. Прямо бьёт по цели №1: реестр памяти C3 называет русский глагол прош. вр. при скрытом роде «механическим спойлер-каналом» и «жалобой №1 читателей MTL» (строка 82). **Развилка, которую надо назвать до стройки:** добавлять ли поле рода в ответ терминолога (двигает провод ⇒ ТОЛЬКО в вайр-батч 182) ЛИБО выводить род отдельным дешёвым проходом, ЛИБО признать род сид-только свойством и гейтить им качество явно. Родня 82 (морфо-гейт рода) и 178 (калибровка `sense`). ⚠ Замка ДВА и они независимы — род рендерится только при `Disp == Confirmed` (⚠ якорь МЁРТВ ОТ ЛЕЧЕНИЯ, испр. оркестратором 04.09: замок расщеплён на отдельное поле `KeyTrusted` паком `b8154cd` по D39.193 — род гейтится МАТЧЕМ, а не статусом, и прежней фразы в коде нет ни на одной строке; пере-указывать по сходству значило бы воскресить снятое утверждение. Строка закрывается актом приёмки движкового пака), а `mining.go:676` запрещает авто-банку статус `approved`. Род НЕ единственное осиротевшее поле: `Sense`/`Speech`/`Decl` тоже приходят только из сида, авто-продюсер есть лишь у `Dst` (терминолог) и `Type` (классификатор). ⚠ Поле `Gender` в схеме сида УЖЕ ЕСТЬ (`internal/seed/seed.go:71`=`Gender string`) — ось мертва в ПИСАТЕЛЯХ, не в формате. ⚠ **И тесты слепы ПО ПОСТРОЕНИЮ** (пере-проверено 23.08): все gender-кейсы конструируют род РУКАМИ, то есть зелёная батарея не отличает «производитель работает» от «производителя нет»; гейта на дрейф рода в движке нет | бэкенд (+полигон на замер) | **скоро (цель №1; правка провода — вайр-батчем 182)** ⚠ **ЗАМОК №0, найденный бэкенд-сессией 04.09 и НЕ снимаемый: произведённый род не доезжает до ТРАНСЛЯТОРНОГО провода вообще — там нет самой строки.** Черновая волна выбирает по `baseMemory`, из которого строки `Source:"mined"` вычеркнуты по построению (`backend/internal/pipeline/bankmaterialize.go`, греп `Source:mined excluded`), а авто-банк и подписанная дельта пишутся именно как `mined` (`mining.go`, греп `base-excluded`). ⛔ Это не дефект, а денежный контракт `D39.42` п.3: инъекция черновой волны обязана быть байт-стабильной поперёк обогащения банка, иначе пере-снапшот платит ДВЕ волны вместо одной. ⇒ **честная граница построенного: род производится и доезжает до РЕДАКТОРСКОГО провода, на черновой не едет и не поедет, пока цена двух волн не решена отдельно.** Замер этого состояния прибит тестом `backend/internal/pipeline/gender_wire_test.go` — он покраснеет, если следующий пак откроет любую дверь, и заставит автора назвать какую. | развилка → решение → правка промпта/прохода + замер на прогоне с редактурой | трассировка цепи банка 22.08, D39.21, C3 реестра памяти | | 213 | **ОСТАТОК — одна строка в ДЕВ-пути: супервизор зашивает `"book.yaml"` мимо константы `ConfigFile`** (`platform/internal/ingest/supervisor.go:142`=`"status", "--config"`). ⚠ **ГЛАВНАЯ половина ЗАКРЫТА лендингом P9 (D39.162):** путь банк-экспорта берётся из конверта артефактов движка (`platform/internal/runner/artifacts.go:28-35`; пустой путь — ГРОМКИЙ отказ), нестрогий парс `book.yaml` и собственный `projectDB()` снесены. Осталась ровно та тривиальность, которую строка называла рядом с дефектом. ⚠ Пере-именование банк-экспорта в фикс-имя рядом с `events.jsonl` — ЛОМАЮЩЕЕ, его место в окне строки **161**, НЕ здесь | платформа | когда-нибудь (одна строка, следующим касанием зоны) | правка дев-супервизора | консилиум шва 22.08; главная половина — D39.162 | | 214 | **Подпись банка не оставляет на банке НИ ОДНОГО следа.** Единственная долговечная запись акта подписи — булев `bank_released` на прогоне (`platform/internal/pgstore/runs.go`, греп `bank_released` (номер строки двигает живая сессия зоны)): кто и когда подписал, что именно вошло в подписанный набор — не хранится нигде. Следствие: пост-фактум ответить «почему в книге этот перевод термина» нельзя, а при пере-нарезке или пере-прогоне восстановить намерение человека не из чего. ⚠ **ЧАСТИЧНЫЙ ответ появился с дверью правок (D39.162):** её документы решений — долговечная запись того, ЧТО владелец решил, и они переживают прогон. Не отвечено по-прежнему: КТО и КОГДА подписал, и что именно вошло в подписанный набор. Всплыло трассировкой цепи банка 22.08, подтверждено пере-проверкой | платформа + бэкенд | когда-нибудь (с трубой доставки правок, D39.156) | отдельное решение о провенансе подписи | трассировка цепи банка 22.08, пере-проверено №19 | | 216 | **Подпись банка, halt на потолке и резюм после пополнения не проверялись против ЖИВОГО движка ни разу.** Проверки этих путей в батарее платформы стоят на фейках; единственный живой прогон движка в батарее — не по этим маршрутам. То есть три несущих сценария продукта держатся на договорённости о поведении чужой стороны, а не на замере. ⚠ Гейтится тем же, чем и живой прогон книги (строка 202): холодный прогон движка | платформа | когда-нибудь (с 202) | живой прогон по трём маршрутам | трассировка цепи банка 22.08, пере-проверено №19 | -| 227 | **`signature` в квитанции двери правок считается от карты, которую переписывает ЛЮБАЯ граница майнинга** (находка воркфлоу-ревью P9, 28.08): запись карты стоит ВЫШЕ решения о стопе (`backend/internal/pipeline/mining.go:191`=`writeFileAtomic(r.signatureMapPath()`), поэтому `signature != null` НЕ означает состоявшегося стопа подписи; `surfaces`/`undecided` дрейфуют между двумя вызовами владельца, а `undecided: 0` достижим при НЕпредъявленных решениях (кап top-200 вытесняет). ⚠ Починка аддитивна и носитель для неё уже есть: карта несёт СВОЙ идентификатор (`backend/internal/membank/decisions.go:955`=`id, err := seed.SignatureMapID(content)`), а шов его не читает — отдать идентификатор в квитанцию и судить по нему. Цена сегодня мала (поле информационное, гейтом не служит) и растёт вместе с экраном подписи: именно он будет решать «что я подписываю» по этому числу | бэкенд | когда-нибудь (гейт: заказ экрана подписи) | аддитивная правка квитанции | воркфлоу-ревью P9, D39.162 | +| 227 | **`signature` в квитанции двери правок считается от карты, которую переписывает ЛЮБАЯ граница майнинга** (находка воркфлоу-ревью P9, 28.08): запись карты стоит ВЫШЕ решения о стопе (`backend/internal/pipeline/mining.go:192`=`writeFileAtomic(r.signatureMapPath()`), поэтому `signature != null` НЕ означает состоявшегося стопа подписи; `surfaces`/`undecided` дрейфуют между двумя вызовами владельца, а `undecided: 0` достижим при НЕпредъявленных решениях (кап top-200 вытесняет). ⚠ Починка аддитивна и носитель для неё уже есть: карта несёт СВОЙ идентификатор (`backend/internal/membank/decisions.go:971`=`id, err := seed.SignatureMapID(content)`), а шов его не читает — отдать идентификатор в квитанцию и судить по нему. Цена сегодня мала (поле информационное, гейтом не служит) и растёт вместе с экраном подписи: именно он будет решать «что я подписываю» по этому числу | бэкенд | когда-нибудь (гейт: заказ экрана подписи) | аддитивная правка квитанции | воркфлоу-ревью P9, D39.162 | | 228 | **Отклонённая поверхность возвращается АЛИАСОМ уцелевшей строки — движок не держит того, что канон уже обещает** (находка воркфлоу-ревью P9 в форме Д1, УЗКО пере-сформулирована бэкенд-сессией 28.08 и принята приёмкой): канон говорит дословно «declining a surface removes EVERY window of that surface» (`docs/architecture/14-api-contract/openapi.yaml:2124`=`declining a surface removes EVERY window`; адрес пере-нацелен 02.09 — минор 0.9.0 сдвинул прежний 2051), а эмиссия майнера энтити-широка (`backend/internal/pipeline/miner_emit.go`, `clusterTouches`), тогда как фильтр авто-банка ключуется только по собственному `src` строки (`backend/internal/pipeline/mining.go:662`). ⚠ **Правильная форма — снять АЛИАС со строки, а не снести строку:** расширение `decline` до энтити противоречило бы ратифицированному контракту, и именно поэтому бэкенд-сессия применила право §9 и НЕ чинила это попутно. Предмет — банковая онтология (`18-bank-ontology.md`), не тихая порча | бэкенд | скоро (гейт: заказ по читающей стороне банка) | отдельный заказ узкой формы | воркфлоу-ревью P9; форма — бэкенд-сессия 28.08, D39.164 | | 229 | **Снапшот не фолдит модель ВНУТРЕННИХ гейтов — флип провода под неизменным `request_hash`** (самонаходка бэкенд-сессии 28.08, подтверждена приёмкой): снапшот фолдит `Capability` СТАДИЙНЫХ моделей и их эскалации (`backend/internal/pipeline/snapshot.go:316-340`), но модель `gates.terminology.model` / `gates.repair.model` (`backend/internal/config/internal_call.go:72`) не фолдится сознательно — а терминолог шлёт ДВА системных сообщения, так что смена оси `capabilities.system_messages` у провайдера, которым пользуется только гейт, меняет байты запроса при неизменном хеше: тихий false-hit класса D5.2. ⚠ **Сегодня ЛАТЕНТНА и денег не стоит — проверено приёмкой: гейта `terminology` нет НИ В ОДНОМ конфиге репозитория** (`grep -c terminology configs/pipeline-c1.yaml` = 0). Триггер починки — день, когда гейт включат с провайдером, объявляющим НЕдефолтную возможность. ⚠ Цена лечения — денежная: фолд гейт-моделей сдвигает хеши и обесценивает чекпойнты; дешёвая форма — фолдить ТОЛЬКО недефолтное (приём `omitempty`, прецедент `MinMaxTokens`), тогда сегодняшние снапшоты остаются байт-равными | бэкенд | когда-нибудь (гейт: включение внутреннего гейта либо следующее касание снапшот-контракта) | правка снапшот-контракта | самонаходка бэкенд-сессии, D39.164 | | 230 | **Инертный `decline` подписанного сид-терма отвечает `already_applied` вместо единственной работающей инструкции** (названный размен пака «тихая порча», D39.164): сузив отказ по поверхности ради СХОДИМОСТИ повтора, движок потерял поучение в одном углу — когда отказ и записан, и по-прежнему инертен против `glossary_seed`, пользователь получает «уже применено» вместо «убери терм из сида». Сходимость сочтена более тяжёлой обязанностью (на ней стоит вся раскладка класса 15 и синхронная дверь платформы), но размен РЕАЛЕН. **Форма закрытия — поле отчёта со стоячим фактом**, то есть аддитивная правка формы шва: платформенный `BankReport` — аллоулист, лишнее поле на провод не уедет само | бэкенд + контракт | скоро (с ближайшим касанием отчёта двери) | аддитивное поле отчёта | размен пака «тихая порча», D39.164 | | 232 | **Ось «свежий/пере-делка» выведена из ПОЛНОТЫ СТРОК, а не из факта отгрузки** (D39.170, находки охотника 3 и 4). Следствия ДЕНЕЖНЫЕ на слух покупателя: добавление стадии в пайплайн превращает ДОЧИТАННУЮ книгу в «3 unit(s) NEVER delivered» и приглашает купить её снова; юнит, прерванный между волнами (signature stop, денежный потолок, Ctrl-C), второй раз считается свежим и тратит слот гранта повторно — замерено 4 купленных юнита → 2 главы. Носитель у движка УЖЕ есть: реестр анонсов `events_outbox.once_key` (`backend/internal/pipeline/events.go:396`=`unitOnceKey is the identity of one announcement`), ключ `unit:<книга>:<волна>:<глава>:<юнит>`, монотонный на всю жизнь книги и переживающий и добавление стадии, и обрыв между волнами. ⚠ **ПЕРВАЯ ПОЛОВИНА ИСПОЛНЕНА 31.08** (`bb541a8`, экземпляр A11; испр. 02.09): читающий метод `store.AnnouncedOnceKeys()` построен, его докстринг называет эту строку по номеру, предикат — `delivered[key] && unitShipped(rows)`. **ЖИВОЙ ОСТАТОК — ВТОРАЯ ПОЛОВИНА:** юнит, прерванный МЕЖДУ ВОЛНАМИ, повторно тратит слот гранта (разбор — `backend/docs/MONEY_HONESTY_REPORT.md` §4.5). ⚠ РАЗВИЛКА, которую надо назвать в промте: ключ несёт ВОЛНУ, значит «юнит отгружен» — факт per-wave. **Ответ есть и он не новый механизм:** отгрузкой считается волна, владеющая ОТГРУЖАЮЩЕЙ (последней) стадией — `backend/internal/pipeline/snapshot.go:243`=`finalStageWave is the wave that owns the SHIPPING (last) stage`; на редакторском конвейере это edit, на черновом-только — draft. Проверять надо ИМ, иначе черновой-только конвейер получит ось, которая никогда ничего не считает отгруженным (предложено движковой сессией при сдаче, сверено мной по коду) | бэкенд | скоро | Читающий метод стора + перевод оси на факт отгрузки; отдельный пак | приёмка D39.170 | | 233 | **Трата терминолога вне объёмного потолка масштабируется КНИГОЙ, а не грантом** (D39.170). Замер: три последовательные покупки по одному юниту на четырёхглавной книге дали три полнокнижных консолидации по $0.005460 каждая — покупка одного юнита обходится в ~1.5× стоимости самого юнита. Книга на 500 юнитов, проданная по одному, оплатит 500 полнокнижных проходов. Место траты — в ЦЕНЕ, а не в потолке (решение подтверждено), но при мелкой нарезке продажи она перестаёт быть накладной и становится основной статьёй: это ВХОД В КАЛИБРОВКУ ЦЕНЫ, а не сноска | бэкенд | скоро | Учесть в модели цены при следующей калибровке; либо чекпойнт консолидации, переживающий покупку | приёмка D39.170 | -| 234 | **Основание платформы «не брать `rebill_*` через шов» СНЯТО лендингом D39.170 — комментарий чужой зоны несёт мёртвый довод.** Довод объяснял отказ ТАЙМИНГОМ («status проецирует СОХРАНЁННУЮ память, сразу после `bank-apply` честно читает ноль»); движковый пак это починил — `foldMemoryForRead` стал ПЕРВЫМ ответом читающего пути, `projectStoredMemory` понижена до фолбэка (`backend/internal/pipeline/status.go:901`=`IT IS NO LONGER THE READ PATH'S FIRST ANSWER`). ⚠ Первый носитель (`platform/internal/ingest/resync.go`) уже пере-написан 30.08 и сам объявляет посылку снятой — строка регистра `PD-427`. **ЖИВОЙ ОСТАТОК:** та же снятая посылка стоит ВТОРЫМ носителем в `platform/internal/runs/bank.go` (греп `STORED memory`) — пинг зоне, не своя правка. ⚠ Проводка полей при этом НЕ разблокирована: она гейчена вместе с `--max-units`. | платформа | скоро | Пинг зоне платформы + строка её регистра; проводка — после снятия гейта `--max-units` | приёмка D39.170 | +| 234 | **Основание платформы «не брать `rebill_*` через шов» СНЯТО лендингом D39.170 — комментарий чужой зоны несёт мёртвый довод.** Довод объяснял отказ ТАЙМИНГОМ («status проецирует СОХРАНЁННУЮ память, сразу после `bank-apply` честно читает ноль»); движковый пак это починил — `foldMemoryForRead` стал ПЕРВЫМ ответом читающего пути, `projectStoredMemory` понижена до фолбэка (`backend/internal/pipeline/status.go:925`=`IT IS NO LONGER THE READ PATH'S FIRST ANSWER`). ⚠ Первый носитель (`platform/internal/ingest/resync.go`) уже пере-написан 30.08 и сам объявляет посылку снятой — строка регистра `PD-427`. **ЖИВОЙ ОСТАТОК:** та же снятая посылка стоит ВТОРЫМ носителем в `platform/internal/runs/bank.go` (греп `STORED memory`) — пинг зоне, не своя правка. ⚠ Проводка полей при этом НЕ разблокирована: она гейчена вместе с `--max-units`. | платформа | скоро | Пинг зоне платформы + строка её регистра; проводка — после снятия гейта `--max-units` | приёмка D39.170 | | 237 | **Два загейченных триггера НАЖАТЫ и не отработаны** (реестр `13-tech-debt-anchors.md` §Б-108, сверено 30.08). **(а) `go.work`:** условие «появление Go-кода платформы» выполнено давно — `platform/go.mod` есть, `ls go.work` в корне даёт «No such file or directory». Ратифицировано D39.85 §8 п.4 (`docs/research/23-engine-platform-seam.md:66` = `go.work — коммитить, когда у платформы появится Go-код`); цена — один файл, по тому же §6 он решает только dev-удобство и не меняет ни видимости, ни версий. **(б) вендор-сверка маппинга эффорта `deepseek-v4-pro`:** вахта D39.92 сработала 13.08 changelog'ом вендора, а носители до сих пор ПРОТИВОРЕЧАТ (квирк-канон и `STACK.md` говорят «ручки у pro НЕТ»), и по гардрейлу владельца это закрывается ТОЛЬКО вендор-докой, а не выбором стороны. Держит жильца Д5 и калибровку цены редакторского арма. ⚠ **Класс важнее двух пунктов: спроектированный механизм без носителя события тихо становится долгом, и никто не проверяет, не нажат ли спусковой крючок.** | оркестратор | скоро | (а) один файл в корень; (б) заказ полигону на вендор-сверку | аудит очереди 30.08 | | 241 | **Канон противоречит сам себе на ДОЧИТАННОЙ книге, и принятая правка банка умирает молча** (названо телом D39.165 §3 28.08, носителя не получило; заведено оркестратором №20 30.08 по находке сессии P12). Две фразы: правка банка «takes effect on the NEXT run» (`docs/architecture/14-api-contract/openapi.yaml:504`=`takes effect on the NEXT run`) против «finished work is not bought twice» + подъём потолка только у ПРИОСТАНОВЛЕННОЙ книги (`:590`=`finished work is not bought twice`). У дочитанной книги следующего прогона купить нечем: пользователь правит банк, получает квитанцию об успехе — и правка не доезжает ни до одной строки никогда. ⚠ Смежное лечение уже существует и не связано с этой дырой: пере-проход ПОКУПАЕТСЯ членом `RunRequest.re_pass` (канон 0.7.0, D39.166) — то есть механизм есть, а фраза канона его не признаёт для дочитанной книги. Решать формой: либо канон признаёт пере-проход входом для дочитанной книги, либо дверь правок отказывает такой книге ЧЕСТНО вместо ложной квитанции | контракт (+платформа) | скоро (следующее касание контракта) | контрактный минор + пин на дочитанной книге | D39.165 §3, эррата 30.08-б, находка сессии P12 | | 242 | **Реестр D-нот: колонки «статус» и «тело» разъехались на 74 строках из 224** (замер аудита доков 30.08). У свежих нот в «статусе» стоит заметка «ЖИВОЕ: …», а слово `жив` уехало в «тело»; шапка таблицы описывает другой порядок. Гейт `counts.py --check` этот класс НЕ ловит по устройству (сверяет полноту номеров, не форму ячеек). Лечение — не ручная перекладка (дороже пользы и легко внести новую ложь), а машинное: правило формы в `counts.py` + разовый прогон приведения. Пока не сделано — в легенде реестра стоит правило чтения (испр. 30.08) | оркестратор | когда-нибудь | правило в `counts.py` + разовое приведение | аудит доков 30.08 | diff --git a/docs/architecture/15-money-path.md b/docs/architecture/15-money-path.md index 4ea4e659..5222c865 100644 --- a/docs/architecture/15-money-path.md +++ b/docs/architecture/15-money-path.md @@ -11,7 +11,7 @@ ## 1. Канон одним абзацем -Деньги видимы с первого вызова: телеметрия, потолки, явное согласие на пере-оплату (CLAUDE.md, цель 5). Леджер без потолка запрещён — Р7: книга обязана нести хотя бы один из `book_usd`/`day_usd` (`backend/internal/config/book.go:321`=`a ledger with no ceiling is forbidden`, текст валидатора: «a ledger with no ceiling is forbidden, Р7»). Суммы на пользовательский экран НЕ выходят — запрет D39.84, подтверждён D39.100/К-8 (дословно: «страница лимитов/использования в настройках (СТАТУС, не суммы — запрет денег на экране D39.84 в силе и НЕ superseded)» — `docs/architecture/05-decisions-log.md`, греп `^## D39.100` → пункт К-8, дословная подстрока `СТАТУС, не суммы — запрет денег на экране`; ⚠ адресуем НОМЕРОМ ноты, а не строкой журнала) и держится в контракте (`docs/architecture/14-api-contract/openapi.yaml`, греп `State of the credit balance. No window` — ⚠ номер не ставим: контракт переверстывается минорами, греп-форма дешевле поддержки). Защита от перерасхода двойная и не зависит от событий: холд платформы делает кредит недоступным следующему прогону, а движок сам останавливается на потолке — «an overspend is impossible even while the platform is blind. The event stream is freshness only» (док-коммент `Hold` — `platform/internal/pgstore/credits.go`, греп `an overspend is impossible even`). +Деньги видимы с первого вызова: телеметрия, потолки, явное согласие на пере-оплату (CLAUDE.md, цель 5). Леджер без потолка запрещён — Р7: книга обязана нести хотя бы один из `book_usd`/`day_usd` (`backend/internal/config/book.go:332`=`a ledger with no ceiling is forbidden`, текст валидатора: «a ledger with no ceiling is forbidden, Р7»). Суммы на пользовательский экран НЕ выходят — запрет D39.84, подтверждён D39.100/К-8 (дословно: «страница лимитов/использования в настройках (СТАТУС, не суммы — запрет денег на экране D39.84 в силе и НЕ superseded)» — `docs/architecture/05-decisions-log.md`, греп `^## D39.100` → пункт К-8, дословная подстрока `СТАТУС, не суммы — запрет денег на экране`; ⚠ адресуем НОМЕРОМ ноты, а не строкой журнала) и держится в контракте (`docs/architecture/14-api-contract/openapi.yaml`, греп `State of the credit balance. No window` — ⚠ номер не ставим: контракт переверстывается минорами, греп-форма дешевле поддержки). Защита от перерасхода двойная и не зависит от событий: холд платформы делает кредит недоступным следующему прогону, а движок сам останавливается на потолке — «an overspend is impossible even while the platform is blind. The event stream is freshness only» (док-коммент `Hold` — `platform/internal/pgstore/credits.go`, греп `an overspend is impossible even`). ## 2. Деньги ДВИЖКА (зона `backend/`) @@ -20,8 +20,8 @@ - **leftover-reserved зануляется write-open.** `store.Open` (путь записи, каждый `translate`) выполняет `recoverReservations` (`backend/internal/store/store.go:110`=`s.recoverReservations(ctx)`; сама функция — `backend/internal/store/store.go:278-279`=`UPDATE spend SET reserved_usd = 0`) — файл владеется одним процессом, значит любой reserved на открытии принадлежит несеттлённому прогону. `OpenReadOnly` этого прохода намеренно НЕ делает (`backend/internal/store/store.go:124`=`does not run that pass`) ⇒ **reserved, увиденный read-only `status` В МОМЕНТ СПАВНА, — остаток мёртвого процесса** (несущий факт формулы PD-158, см. §3). ⚠ Но НЕ «всегда»: `OpenReadOnly` построен ровно затем, чтобы `status` работал ВО ВРЕМЯ живого прогона (`backend/internal/store/store.go:122`=`allows concurrent readers while a writer is live`), и конкурентный `status` покажет ЖИВУЮ резервацию между `Reserve` и settle. Узко формулирует и сам код: «after a run crashes, reserved_usd stays non-zero until the next WRITE command» (`backend/internal/store/store.go:130`=`after a run crashes, reserved_usd stays non-zero`), и платформа — «at spawn there is no other writer … so anything reserved is by construction a leftover, never a live promise» (`platform/internal/runs/spawn.go`, греп `never a live promise`). - **`--max-units` — ОБЪЁМНЫЙ потолок прогона, ортогональный денежному** (D39.165 §1б, принят D39.170). Ограничивает не деньги, а РАБОТУ: не больше N выходных ЮНИТОВ (гранулярность `units_total` манифеста — та же, в которой платформа продаёт главы) возьмут СЛОТ гранта в этом прогоне; юниты, отданные за $0 (резюм, ре-пин), ретраи и эскалации внутри юнита потолок не тратят. ⚠ **«Слот» ≠ «оплата», и с 03.09 это РАЗНЫЕ числа** (пак «число согласия», строка бэклога 232): юнит, который прежний прогон НАЧАЛ и не отгрузил, дописывается ВНЕ гранта, поэтому грант N оплачивает ДО 2N выходных юнитов — замерено приёмкой на живом раннере (грант 2 → `Paid()=4`, шесть вызовов провайдера). Единственный денежный бонд здесь — `--ceiling-usd`. ⛔ **Семантика переноса НЕ ратифицирована — слово владельца 03.09 «подумаем на этот счёт»**, и до его решения читать это как замеренный факт, а не как норму. Принимает только `translate`. **Остановка по объёму — ЗАВЕРШЕНИЕ (exit 0), не пауза:** словарь кодов выхода не расширялся и нового значения `Finished.Outcome` тоже нет — ⚠ **но с 31.08 признак едет ЧИСЛАМИ в кадре `finished` шва, а не только прозой** (D39.181 п.2, закон раскрытия: прозаическая строка отчёта до потребителя потока не доезжала): носитель — `Finished.Volume`, леджер доставки. Различение при этом живёт и в отчёте прогона, и в логе; отчёт разводит ДОСТАВКУ и ПЕРЕ-ДЕЛКУ. Носитель — `backend/internal/pipeline/volume.go:13`=`the VOLUME ceiling — the run's second stop`; словарь флага дословно — `backend/cmd/tmctl/invocation.go:143`=`Stopping on it is a COMPLETION (exit 0), not a pause`. ⚠ Проводка в платформу ГЕЙЧЕНА (`PD-422`): единственный писатель признака движения банка — дверь правок, рост АВТО-банка от майнинга флага не ставит. - **`--ceiling-usd` — КНИЖНЫЙ потолок, не бюджет прогона.** Дословно из флага: «the book USD ceiling in force for THIS RUN ONLY — it OVERRIDES book.yaml `ceilings.book_usd` and is never written back. It caps the book's CUMULATIVE committed+reserved spend, not this run's increment…» (`backend/cmd/tmctl/invocation.go:142`=`the book USD ceiling in force for THIS RUN ONLY`; ⚠-коммент `backend/cmd/tmctl/main.go:260`=`--ceiling-usd is NOT a per-run budget`; многоточие закрывает обрыв цитаты — во флаге дальше стоит «, and must be > 0»). Ратификация — D39.122 п.2(в): пересчёт «пользовательский прирост → абсолют» — обязанность ПЛАТФОРМЫ, вторая денежная ось не заводится. День-потолок флаг НЕ перекрывает (PD-157). Проводка внутри: `Ceilings.BookUSD` в `Reserve` и именование сработавшего потолка в ошибке — `backend/internal/pipeline/stagerun.go:496`=`BookUSD: r.bookCeilingUSD()` и `backend/internal/pipeline/stagerun.go:525`=`overrides the book's ceilings.book_usd` (стоп = `errReserveCeiling`, `backend/internal/pipeline/escalation.go:49`=`var errReserveCeiling`). -- **Потолки — wiring, не семантика:** `Ceilings` намеренно исключены из `BriefHash` («Wiring fields (paths, ceilings, db) deliberately excluded», `backend/internal/config/book.go:334`=`Wiring fields (paths, ceilings, db) deliberately excluded`; канон — `backend/internal/config/book.go:350`=`canon := struct {`) ⇒ смена ДЕНЕЖНОГО потолка не двигает ни снапшот, ни ре-билл (D39.110 п.2б). ⚠ Верно ровно про ДЕНЬГИ: потолок СЕГМЕНТАЦИИ `edit_ceiling_out` в снапшот ВХОДИТ и меняет границы чанков (`backend/internal/pipeline/snapshot.go:34`=`EditCeilingOut int`). -- **`status --json` отдаёт фигуры платформе:** `committed_usd`, `reserved_usd`, `book_ceiling_usd`, `ceiling_pct` = 100·(committed+reserved)/book_ceiling (`backend/internal/pipeline/status.go:311`=`book_ceiling_usd,omitempty`; арифметика `ceiling_pct` — `backend/internal/pipeline/status.go:811`=`100 * (committed + reserved)`). Дневной фигуры в status нет (PD-157 — `platform/docs/DEFECT_REGISTER.md`, греп `PD-157`). +- **Потолки — wiring, не семантика:** `Ceilings` намеренно исключены из `BriefHash` («Wiring fields (paths, ceilings, db) deliberately excluded», `backend/internal/config/book.go:345`=`Wiring fields (paths, ceilings, db) deliberately excluded`; канон — `backend/internal/config/book.go:369`=`canon := struct {`) ⇒ смена ДЕНЕЖНОГО потолка не двигает ни снапшот, ни ре-билл (D39.110 п.2б). ⚠ Верно ровно про ДЕНЬГИ: потолок СЕГМЕНТАЦИИ `edit_ceiling_out` в снапшот ВХОДИТ и меняет границы чанков (`backend/internal/pipeline/snapshot.go:34`=`EditCeilingOut int`). +- **`status --json` отдаёт фигуры платформе:** `committed_usd`, `reserved_usd`, `book_ceiling_usd`, `ceiling_pct` = 100·(committed+reserved)/book_ceiling (`backend/internal/pipeline/status.go:311`=`book_ceiling_usd,omitempty`; арифметика `ceiling_pct` — `backend/internal/pipeline/status.go:835`=`100 * (committed + reserved)`). Дневной фигуры в status нет (PD-157 — `platform/docs/DEFECT_REGISTER.md`, греп `PD-157`). - **Леджер = НИЖНЯЯ граница** (строка 78 бэклога, `docs/PROGRESS.md`, греп `Леджер денег = НИЖНЯЯ граница`): «2xx body decode failed (call IS billed)» — провайдер списал, попытка в `request_log` не попадает; живой замер D39.86 — 3 вызова из 14, неизвестность $0.015111 при леджере $0.114378; движок сеттлит оценку и печатает `estimated-cost rows: 3`. Канал виден, фикс — money-паком (дизайн GENERALITY_PHASE2 §5.6). - **Согласие на пере-оплату — ДВИЖКОВЫЙ гейт**. Прогон, который пере-покупает уже оплаченные юниты дороже порога, ОСТАНАВЛИВАЕТСЯ и требует явного согласия. Порог по умолчанию ратифицирован D20.2-Q2: `min($0.50, 5% × ProjectedBookUSD)`. Носитель — `backend/internal/pipeline/rebill.go`, греп `rebillConsentThreshold`; единственная ручка книги — `rebill_consent_usd` (валидатор `backend/internal/config/book.go`, греп `rebill_consent_usd`; 0 = ратифицированный дефолт). ⚠ Порог считается БЕЗ контура банк-ролей (D39.182 §3), то есть срабатывает позже, чем подсказывает полная проекция. diff --git a/docs/architecture/17-seam-inbound-law.md b/docs/architecture/17-seam-inbound-law.md index 590cbb77..a6f47e3c 100644 --- a/docs/architecture/17-seam-inbound-law.md +++ b/docs/architecture/17-seam-inbound-law.md @@ -43,8 +43,8 @@ (`platform/internal/ingest/tail.go:17`=`const JournalFile = "events.jsonl"`, D39.106); без этого выноса буква ломает главный канал самого шва. *Прецеденты:* потолок аргументом (D39.110 п.2б → `--ceiling-usd`, D39.122) · `--verify-bank` флагом (`platform/internal/runner/engine.go:99`=`func TranslateArgs(workdir string, verifyBank bool`) · дефолт `project_db` по конвенции каталога - (`backend/internal/config/book.go:189`=`b.ProjectDB = filepath.Join(dir, b.BookID+".db")`) · конвенционное опциональное чтение `.auto-bank.yaml` - (`backend/internal/pipeline/mining.go:617`=`.auto-bank.yaml`). + (`backend/internal/config/book.go:200`=`b.ProjectDB = filepath.Join(dir, b.BookID+".db")`) · конвенционное опциональное чтение `.auto-bank.yaml` + (`backend/internal/pipeline/mining.go:618`=`.auto-bank.yaml`). 2. **Посреди прогона внутрь идёт ТОЛЬКО сигнал.** `SIGTERM` = «дочти чанк и отпусти лок»; всё остальное — на границах процессов. Арбитр — эксклюзивный flock проекта на всю жизнь прогона diff --git a/docs/experiments/00-provider-quirks.md b/docs/experiments/00-provider-quirks.md index 4407032e..efa46587 100644 --- a/docs/experiments/00-provider-quirks.md +++ b/docs/experiments/00-provider-quirks.md @@ -80,7 +80,7 @@ ⛔⛔ **ВАХТА D39.92 ПЕРЕ-СНЯТА 2026-08-30 (полигон; `curl`, HTTP 200, страница 108 336 байт, sha256 `f28c4324…`) — ТАБЛИЦА У ВЕНДОРА ИЗМЕНИЛАСЬ, И ЗАПИСЬ НИЖЕ БОЛЬШЕ НЕ ВЕРНА.** Вендор исполнил обещанное сноской обновление маппинга. Живая редакция, дословно: *«(2) The mapping between the effort set by the user and the model's actual reasoning effort is as follows (**identical for `deepseek-v4-flash` and `deepseek-v4-pro`**)»* — и таблица теперь ОДНА на обе модели: `low→low · medium→high · high→high · xhigh→high · max→max`. Плюс *«(1) Thinking mode is enabled by default, with the default effort being `high`»*, а тумблер `{"thinking":{"type":"enabled/disabled"}}` подан как общий для модели DeepSeek, без оговорки «только flash». ⇒ **(i) на `deepseek-v4-pro` ручка `low` ТЕПЕРЬ РАБОТАЕТ и даёт `low`; (ii) рычаг бюджета размышления существует У ОБЕИХ моделей; (iii) размышление у `pro` в принципе ВЫКЛЮЧАЕМО тумблером.** ⚠ Это ДОКА, а не поведенческая проба: по норме проекта («слаг ≠ модель, при аномалии — поведенческая проба, не листинг») клейм действителен как ВЕНДОРСКОЕ ЗАЯВЛЕНИЕ и требует живой пробы перед денежным решением (смета — единицы центов). ⚠ И цена рычага уже замерена с другой стороны: `effort:"low"` **пере-вооружает эхо-мину** (п.4 ниже), а «reasoning-off + плотный CJK-вход = зона эха». ⚠ ЕЩЁ ОДИН ЖИВОЙ ФАКТ С ТОЙ ЖЕ СТРАНИЦЫ, которого у нас не записано нигде: *«Thinking mode does not support the `temperature`, `top_p`, `presence_penalty`, or `frequency_penalty` parameters… setting these parameters will not trigger an error but will also have no effect»* ⇒ **наш `temperature: 0.3`, который ростер шлёт на `deepseek-v4-pro`, МОЛЧА ИГНОРИРУЕТСЯ** — параметр в проводе есть, эффекта нет. ~~⇒ (i) **не слать `reasoning_effort` = ехать на `high`** — вот почему боевая форма упирается в стену (п.10); (ii) на `deepseek-v4-pro` ручка `low` НЕ РАБОТАЕТ (маппится в `high`); рычаг бюджета размышления существует ТОЛЬКО у flash~~ — **редакция до 30.08, оставлена под зачёркиванием: на ней стоят все денежные выводы фазы Д и вся её арифметика размышления. Дефолт `high` при не-присланной ручке — В СИЛЕ и в новой редакции** — **строка В СИЛЕ**: её отзыв от 05.08 сам ОТОЗВАН адверсариальным ревью того же дня (см. 3б), замер §3б признан недиагностичным; (iii) `none` в OpenAI-формате у DeepSeek не существует вовсе (колонка `reasoning.effort:"none"` — Anthropic-формат), запись 04.07 «`none` → 400» в силе. - **3г. ⭐ ПОВЕДЕНЧЕСКАЯ ПРОБА 2026-08-30 (полигон, фаза Д, $0.038276): ПАРАМЕТР `low` НА `pro` ТЕПЕРЬ ДОХОДИТ ДО МОДЕЛИ.** Дока 3а требовала живой пробы перед денежным решением — вот она. **Несущая улика — не размер размышления, а `prompt_tokens`:** при **побайтно одинаковых** `messages` (sha256 `51e7f427940cae15`, проверено исполнением) вызов без параметра дал **3270** входных токенов, вызов с `reasoning_effort:"low"` — **3191**, то есть **−79**. Тело вызова различалось РОВНО одним полем (`eval/dovodka/rol.py:668`=`def call_kwargs`), исходник главы приколот манифестом с гейтом остановки (`eval/role_topology/pair_en.py:132`=`def units`), шаблон промта не менялся с 20.08, кэш смещения не даёт (две клетки с `cached_tokens=0` дали остатки −31.8 и +10.4 при разбросе ±20–44). **ДВА контроля детерминизма.** (1) Замер 05.08 ниже: при побайтно одинаковых `messages` дефолт и `low` вернули ОДИНАКОВЫЕ 2124 — провайдер повторяет `prompt_tokens` в точности, когда параметр не действует; ⚠ но он снят ДО смены сервинга и на другом промте. (2) **Межэпохальный контроль — пред-полётные пробы того же рига:** один и тот же тривиальный запрос БЕЗ ручки дал `prompt_tokens` = **84 · 84 · 84 · 84**, причём четвёртая куплена 30.08 за две минуты до испытуемого вызова ⇒ **дефолтный счёт входа не сдвинулся ни на токен через смену вендорского сервинга**, и −79 принадлежит параметру, а не эпохе. ⚠ **Регулярность без объяснения:** 2203 − 2124 = **79** (05.08, `xhigh` над дефолтом) и 3270 − 3191 = **79** (30.08, дефолт над `low`) на совершенно разных промтах — похоже на серверную вставку фиксированного размера, но это догадка. ⇒ **Статус `low` на `pro` закрыт В ЧАСТИ «доходит ли»: ДОХОДИТ.** ⛔⛔ **НО ЭТО ПРО ПРОВОД ПОЛИГОННОГО РИГА, А НЕ ПРО ДВИЖОК, И РАЗНИЦА СТОИТ ЗАМЕРА.** Проверено исполнением 31.08: у `deepseek-v4-pro` в `backend/configs/models.yaml` блок `capabilities` несёт РОВНО `min_max_tokens` — **`reasoning`-capability отсутствует**, модель резолвится в `ReasoningNone`, и `reasoning: "off"` боевого `backend/configs/pipeline-c1.yaml` есть NO-OP (это и написано в самом конфиге, `pipeline-c1.yaml:69`=`ReasoningNone`). У `glm-5` capability есть (`control: extra_body_disable`), у `gemini-3.1-pro` — `mandatory`. ⛔⛔ **ПОПРАВКА 01.09, ТРЕТЬЯ РЕДАКЦИЯ ЭТОЙ СТРОКИ — ПРЕДЫДУЩАЯ («ДВИЖОК РУЧКУ НЕ ШЛЁТ ВООБЩЕ») БЫЛА НЕВЕРНА, И Я ЕЁ СНИМАЮ.** Я прочитал ОТСУТСТВИЕ `capabilities.reasoning` как «ручка не шлётся», не прочитав, ЧТО делает `ReasoningNone`. Дословно, `backend/internal/llm/capability.go:53`=`reasoning_effort`: **«ReasoningNone is the OpenAI-compat baseline: low|medium|high go out as reasoning_effort; off and "" emit nothing, leaving the provider's OWN default»**. И там же ниже: **«⚠ "Never turned off" is NOT "never configured" (D39.87): low|medium|high are a ratified way to size the thinking BUDGET on such a model»**. ⇒ **ДВИЖОК ШЛЁТ `low`/`medium`/`high` И ЭТО РАТИФИЦИРОВАННЫЙ СПОСОБ ЗАДАТЬ БЮДЖЕТ РАЗМЫШЛЕНИЯ.** Не шлёт он ровно два значения — `off` и пустое, и тогда едет вендор-дефолт `high`. **Именно `off` и стоит в боевом `pipeline-c1.yaml` у редактора** — поэтому редактор едет дефолтным `high`, но это ВЫБОР конфига, а не невозможность. ⇒ Ось «эффорт `off` против `low`» на движке ИЗМЕРИМА и требует правки ОДНОЙ строки конфига книги, без правки Go и без снятия защиты. Гейт `TestDeepSeekEchoMineRejected` запрещает ВЫКЛЮЧЕНИЕ размышления (`extra_body_disable`, `thinking` в `extra_body`, вложенный `chat_template_kwargs`, `effort`-capability) — `low` под запрет НЕ подпадает, потому что едет с размышлением ВКЛЮЧЁННЫМ. **Класс моей ошибки: отсутствие поля прочитано как отсутствие механизма, без чтения того, что делает дефолтная ветка.** Полигонный риг шлёт ручку своим проводом — это верно и остаётся, но движок ему в этом не уступает. Практические следствия: ⛔ **ЭТОТ АБЗАЦ СНЯТ 01.09 ЦЕЛИКОМ — он стоял на неверной посылке (см. поправку выше).** Он утверждал, что «любая движковая проба оси эффорта вернёт разницы нет» и что правка данных «уронила бы батарею». Оба ложны: движок шлёт `low`, а гейт, который я поминал, — `echo_mine_test.go:218` — лежит в `TestBoevoyConfigEditorDsproAndGrokReasoning` (⚠ я атрибутировал его `TestDeepSeekEchoMineRejected` — тоже ошибка) и требует `off` у редактора ТОЛЬКО в шиппинг-конфигах `pipeline-c1`/`c2`. Отдельный профиль книги с `reasoning: "low"` под него не подпадает. ⇒ **Ось «`off` против `low`» на движке измеряется отдельным конфигом книги — это ДАННЫЕ, ровно как записано в §14 отчёта холодного прогона; ни правки Go, ни снятия защиты не требуется.** ⚠ Класс ошибки, которого здесь чуть не случилось: **вендорский факт принят за свойство нашей системы** — между вендором и нами стоит реестр моделей, и он молчит. ⛔ **ВЕЛИЧИНА среза размышления ПО-ПРЕЖНЕМУ НЕ УСТАНОВЛЕНА:** наблюдённые ×2.52 (19 559 → 7 748 токенов) сняты БЛОКАМИ, разнесёнными на 9 дней. ⛔ **Несущий конфаунд здесь — НЕ дрейф, а сама смена сервинга, зафиксированная выше в 3а:** вендор переписал маппинг между двумя блоками, и сравнение «арм 21.08 против арма 30.08» приписывает ручке всё, что он поменял. Дрейф (строка в 3б) — второй и более слабый: у него есть ЗНАК (все шесть розыгрышей монотонно РАСТУТ), и при его продолжении ×2.52 было бы нижней границей среза. Чтобы величина стала известной, нужен интерливинг обоих армов в ОДНОМ блоке; пре-регистрация и цены — `docs/experiments/23-editor-tier.md` §Д46.3 и §Д47 (дорогая восьмёрка $0.565–0.620, дешёвая — $0.078–0.133). ⚠ **Эхо на `low` у `pro`: одна английская клетка дала кириллицу 1.000 и ноль латиницы — это НЕ снимает предупреждение «Не бесплатно» ниже**, эхо-мина живёт на плотном CJK, а проба была на английской паре. + **3г. ⭐ ПОВЕДЕНЧЕСКАЯ ПРОБА 2026-08-30 (полигон, фаза Д, $0.038276): ПАРАМЕТР `low` НА `pro` ТЕПЕРЬ ДОХОДИТ ДО МОДЕЛИ.** Дока 3а требовала живой пробы перед денежным решением — вот она. **Несущая улика — не размер размышления, а `prompt_tokens`:** при **побайтно одинаковых** `messages` (sha256 `51e7f427940cae15`, проверено исполнением) вызов без параметра дал **3270** входных токенов, вызов с `reasoning_effort:"low"` — **3191**, то есть **−79**. Тело вызова различалось РОВНО одним полем (`eval/dovodka/rol.py:668`=`def call_kwargs`), исходник главы приколот манифестом с гейтом остановки (`eval/role_topology/pair_en.py:132`=`def units(n_per_stratum: int | None = None`), шаблон промта не менялся с 20.08, кэш смещения не даёт (две клетки с `cached_tokens=0` дали остатки −31.8 и +10.4 при разбросе ±20–44). **ДВА контроля детерминизма.** (1) Замер 05.08 ниже: при побайтно одинаковых `messages` дефолт и `low` вернули ОДИНАКОВЫЕ 2124 — провайдер повторяет `prompt_tokens` в точности, когда параметр не действует; ⚠ но он снят ДО смены сервинга и на другом промте. (2) **Межэпохальный контроль — пред-полётные пробы того же рига:** один и тот же тривиальный запрос БЕЗ ручки дал `prompt_tokens` = **84 · 84 · 84 · 84**, причём четвёртая куплена 30.08 за две минуты до испытуемого вызова ⇒ **дефолтный счёт входа не сдвинулся ни на токен через смену вендорского сервинга**, и −79 принадлежит параметру, а не эпохе. ⚠ **Регулярность без объяснения:** 2203 − 2124 = **79** (05.08, `xhigh` над дефолтом) и 3270 − 3191 = **79** (30.08, дефолт над `low`) на совершенно разных промтах — похоже на серверную вставку фиксированного размера, но это догадка. ⇒ **Статус `low` на `pro` закрыт В ЧАСТИ «доходит ли»: ДОХОДИТ.** ⛔⛔ **НО ЭТО ПРО ПРОВОД ПОЛИГОННОГО РИГА, А НЕ ПРО ДВИЖОК, И РАЗНИЦА СТОИТ ЗАМЕРА.** Проверено исполнением 31.08: у `deepseek-v4-pro` в `backend/configs/models.yaml` блок `capabilities` несёт РОВНО `min_max_tokens` — **`reasoning`-capability отсутствует**, модель резолвится в `ReasoningNone`, и `reasoning: "off"` боевого `backend/configs/pipeline-c1.yaml` есть NO-OP (это и написано в самом конфиге, `pipeline-c1.yaml:69`=`ReasoningNone`). У `glm-5` capability есть (`control: extra_body_disable`), у `gemini-3.1-pro` — `mandatory`. ⛔⛔ **ПОПРАВКА 01.09, ТРЕТЬЯ РЕДАКЦИЯ ЭТОЙ СТРОКИ — ПРЕДЫДУЩАЯ («ДВИЖОК РУЧКУ НЕ ШЛЁТ ВООБЩЕ») БЫЛА НЕВЕРНА, И Я ЕЁ СНИМАЮ.** Я прочитал ОТСУТСТВИЕ `capabilities.reasoning` как «ручка не шлётся», не прочитав, ЧТО делает `ReasoningNone`. Дословно, `backend/internal/llm/capability.go:53`=`reasoning_effort`: **«ReasoningNone is the OpenAI-compat baseline: low|medium|high go out as reasoning_effort; off and "" emit nothing, leaving the provider's OWN default»**. И там же ниже: **«⚠ "Never turned off" is NOT "never configured" (D39.87): low|medium|high are a ratified way to size the thinking BUDGET on such a model»**. ⇒ **ДВИЖОК ШЛЁТ `low`/`medium`/`high` И ЭТО РАТИФИЦИРОВАННЫЙ СПОСОБ ЗАДАТЬ БЮДЖЕТ РАЗМЫШЛЕНИЯ.** Не шлёт он ровно два значения — `off` и пустое, и тогда едет вендор-дефолт `high`. **Именно `off` и стоит в боевом `pipeline-c1.yaml` у редактора** — поэтому редактор едет дефолтным `high`, но это ВЫБОР конфига, а не невозможность. ⇒ Ось «эффорт `off` против `low`» на движке ИЗМЕРИМА и требует правки ОДНОЙ строки конфига книги, без правки Go и без снятия защиты. Гейт `TestDeepSeekEchoMineRejected` запрещает ВЫКЛЮЧЕНИЕ размышления (`extra_body_disable`, `thinking` в `extra_body`, вложенный `chat_template_kwargs`, `effort`-capability) — `low` под запрет НЕ подпадает, потому что едет с размышлением ВКЛЮЧЁННЫМ. **Класс моей ошибки: отсутствие поля прочитано как отсутствие механизма, без чтения того, что делает дефолтная ветка.** Полигонный риг шлёт ручку своим проводом — это верно и остаётся, но движок ему в этом не уступает. Практические следствия: ⛔ **ЭТОТ АБЗАЦ СНЯТ 01.09 ЦЕЛИКОМ — он стоял на неверной посылке (см. поправку выше).** Он утверждал, что «любая движковая проба оси эффорта вернёт разницы нет» и что правка данных «уронила бы батарею». Оба ложны: движок шлёт `low`, а гейт, который я поминал, — `echo_mine_test.go:218` — лежит в `TestBoevoyConfigEditorDsproAndGrokReasoning` (⚠ я атрибутировал его `TestDeepSeekEchoMineRejected` — тоже ошибка) и требует `off` у редактора ТОЛЬКО в шиппинг-конфигах `pipeline-c1`/`c2`. Отдельный профиль книги с `reasoning: "low"` под него не подпадает. ⇒ **Ось «`off` против `low`» на движке измеряется отдельным конфигом книги — это ДАННЫЕ, ровно как записано в §14 отчёта холодного прогона; ни правки Go, ни снятия защиты не требуется.** ⚠ Класс ошибки, которого здесь чуть не случилось: **вендорский факт принят за свойство нашей системы** — между вендором и нами стоит реестр моделей, и он молчит. ⛔ **ВЕЛИЧИНА среза размышления ПО-ПРЕЖНЕМУ НЕ УСТАНОВЛЕНА:** наблюдённые ×2.52 (19 559 → 7 748 токенов) сняты БЛОКАМИ, разнесёнными на 9 дней. ⛔ **Несущий конфаунд здесь — НЕ дрейф, а сама смена сервинга, зафиксированная выше в 3а:** вендор переписал маппинг между двумя блоками, и сравнение «арм 21.08 против арма 30.08» приписывает ручке всё, что он поменял. Дрейф (строка в 3б) — второй и более слабый: у него есть ЗНАК (все шесть розыгрышей монотонно РАСТУТ), и при его продолжении ×2.52 было бы нижней границей среза. Чтобы величина стала известной, нужен интерливинг обоих армов в ОДНОМ блоке; пре-регистрация и цены — `docs/experiments/23-editor-tier.md` §Д46.3 и §Д47 (дорогая восьмёрка $0.565–0.620, дешёвая — $0.078–0.133). ⚠ **Эхо на `low` у `pro`: одна английская клетка дала кириллицу 1.000 и ноль латиницы — это НЕ снимает предупреждение «Не бесплатно» ниже**, эхо-мина живёт на плотном CJK, а проба была на английской паре. **3д. ⚠ 30.08 ($0.134804, интерливинг Д47): ДВА ОДИНОЧНЫХ РОЗЫГРЫША ДЕФОЛТНОГО ПУТИ `deepseek-v4-pro` РАЗОШЛИСЬ В 38 РАЗ. ⛔ ПРИЧИНА НЕ УСТАНОВЛЕНА — «модель сдвинулась» и «выпал хвост» ОБЕ ЖИВЫ.** (Первая редакция этой записи подавала ×38 как замеренный сдвиг во времени; поправлено в тот же день — по лучшей имеющейся оценке разброса это ≈3.1 sd, редкое событие, но не невозможное. Оценка разброса: sd логарифма 0.82, то есть одно sd = ×2.3, снята по шести группам «тот же тег, разные розыгрыши», 18 вызовов, СУДЕЙСКАЯ роль — на переводе чистых повторов в корпусе нет. Разбор — `docs/experiments/23-editor-tier.md` §Д47.7.) Побайтно один промт (sha256 сверен), одна и та же глава, `reasoning_effort` НЕ слался ни разу: 21.08 модель думала **466** токенов, 30.08 — **17 757**. Цена той же клетки $0.005034 → $0.076893. **Для практики обе гипотезы дают одно и то же предупреждение: любая смета DeepSeek, снятая с ОДИНОЧНОГО прошлого вызова, может ошибиться на порядок, и денежные планы на неё ставить нельзя.** ⭐ **Следствие, которое дороже самой записи: сметы и сравнения моделей по размышлению нужно строить на ПОВТОРАХ.** Считано: при sd логарифма 0.82 парный дизайн ловит эффект ×2.5 за 13 пар, ×2.0 за 22, ×1.4 за 93 (80% мощности, α=0.05). Проверено на себе: пре-регистрация оценила восемь пар в $0.078–0.133, одна пара стоила $0.1348. ⭐ **Зато счёт ВХОДА через тот же разрыв стабилен до токена:** `prompt_tokens` = 3 256 и 21.08, и 30.08 при не-присланной ручке, а `low` сдвигает его ровно на −79 (3 177) — то есть провенанс параметра читается по входу даже там, где выход уехал ×38. ⚠ Наблюдённый ВНУТРИ ОДНОГО БЛОКА эффект ручки — ×1.40 (17 757 → 12 676), заметно меньше ×2.52, полученных сравнением через девять дней: конфаунд эффект РАЗДУВАЛ. n=1, знаковый тест p=1.0 ⇒ величина по-прежнему **НЕ УСТАНОВЛЕНА**; разбор — `docs/experiments/23-editor-tier.md` §Д47.6. @@ -160,7 +160,7 @@ ## Контент-фильтры / safety - ⛔⛔ **Gemini, ДВА СИСТЕМНЫХ СООБЩЕНИЯ = ПРОМТ ТЕРЯЕТСЯ МОЛЧА (полигон 22.08, живая проба `eval/dovodka/proba_gemini.py`).** **СТРУКТУРНЫЙ ФАКТ ВЕНДОРА** (`ai.google.dev/api/generate-content`, страница помечена 2026-08-17, снято 2026-08-28): в нативном `GenerateContentRequest` поле `systemInstruction` — ОДИН `object (Content)` (в той же таблице `contents[]` и `tools[]` несут суффикс повторяемого поля), а `Content.role` документирован как «Must be either 'user' or 'model'» ⇒ второму системному ХОДУ в нативном запросе места нет. ⚠ **ПОПРАВКИ ПИНГА №19 (28.08, D39.164), внесены 29.08 — прежняя редакция была сильнее источника в трёх местах.** (а) ~~«слой ФИЗИЧЕСКИ не может пронести больше одного системного»~~ — это про НАТИВНЫЙ запрос; **что делает с двумя системными OpenAI-совместимый ШИМ, вендор не документирует НИГДЕ** (раздел «Current limitations» молчит и в живой странице, и в снимке Wayback 2026-08-21). (б) ~~«уход на нативный API проблемы НЕ решает»~~ верен по сути, но НЕ по причине: `Content.parts[]` — ПОВТОРЯЕМОЕ поле, и вендорская заметка на `systemInstruction` гласит «Only text should be used in parts and content in each part will be in a separate paragraph» ⇒ нативный API МОЖЕТ нести N системных текстов как N частей ОДНОГО `Content`; склеивать всё равно пришлось бы, но «физически не может» — неправда. (в) форум Google — **НЕ вендорский источник**: тред `discuss.ai.google.dev/t/86097` (30.05.2025) — два поста, оба от НЕ-сотрудников, ответа Google нет; ссылаться на него наравне с замером нельзя. При двух и более системных лишние выбрасываются без ошибки. ⛔ **УСТАНОВЛЕНО 29.08: ВЫЖИВАЕТ ПОСЛЕДНЕЕ СИСТЕМНОЕ, ВЫБРАСЫВАЕТСЯ ПЕРВОЕ** (полигон, Д35.3 отчёта 23; $0, детерминированно, на 16 уже купленных клетках). Доказательство арифметическое: у арма `RE` первое системное (ролевой промт) **побайтно одинаково на всех 16 главах**, второе (глоссарий) — **разное** (255–404 знака, термины из текста главы); разность `RE.prompt_tokens − RQ.prompt_tokens` равна **2436 РОВНО на каждой из 16 глав, sd = 0.00** ⇒ выброшено сообщение ПОСТОЯННОГО размера, а таково только первое; будь выброшены оба, разность росла бы вместе с глоссарием. Побочно: `prompt_tokens` карантинных клеток коррелируют с длиной глоссария +0.52 и с длиной исходника +0.04 — глоссарий внутри них физически есть. ~~⚠ КАКОЕ именно выживает — НЕ УСТАНОВЛЕНО~~ — вопрос ЗАКРЫТ этим замером; прежняя интерпретация «не выжило НИ ОДНО» неверна: она сравнила 535 токенов с «размером одного user-сообщения» НА ГЛАЗ, а при шкале 0.324 ток/знак русского глоссарий в 300 знаков весит ~100 токенов и в такой оценке теряется. ⚠ Форум Google (`discuss.ai.google.dev/t/86097`) говорит то же самое, но он **не источник** (не-сотрудники, ответа Google нет) — здесь он лишь СОВПАДАЕТ с замером, а не подпирает его. ⚠⚠ **ГРАНИЦА КЛЕЙМА, названная пингом №19 и подтверждённая здесь:** замер снят на `gemini-3.1-flash-lite` и на клетках, купленных 21.08.2026. Слой у `gemini-3.1-pro-preview` (тот, что стоит в цепочке эскалации) ТОТ ЖЕ, но слаг другой — клейм переносится на него как ПРАВДОПОДОБНЫЙ, не как замеренный. Для решения это безразлично: движок склеивает системные сам. Документированного вендором ограничения в разделе «Current limitations» НЕТ. Замер на одном и том же системном тексте в 7970 знаков: ОДНИМ сообщением → `prompt_tokens` 2994 и маркерная инструкция ИСПОЛНЕНА; ДВУМЯ → **535 токенов** ~~(это размер одного user-сообщения)~~ **и инструкция НЕ исполнена** — ⚠ оценка «размер одного user-сообщения» СНЯТА: 535 = user + глоссарий, см. закрытие вопроса выше. Ответ приходит с `finish=stop` и выглядит правильным переводом — отказа нет, есть тихая потеря инструкции, невидимая всем гейтам годности. ⚠ Так были куплены 16 клеток арма `RE` (промт-роль на gemini): ~~они переведены ВООБЩЕ БЕЗ ИНСТРУКЦИИ~~ — **уточнено 29.08 тем же замером, что закрыл вопрос выше: они переведены БЕЗ РОЛЕВОГО ПРОМТА, но С ГЛОССАРИЕМ** (до модели доехали ~130 токенов канонных форм из 2 566). Вердикт о переносимости из них был бы ложным в обоих чтениях; но арм, собранный из этих клеток, называется «без ролевого промта», а не «без инструкции» — иначе класс «банк» читается неверно. Лечение: склеивать системные в одно ДЛЯ ЭТОГО ВЕНДОРА (`rol.ONE_SYSTEM`/`fit_msgs`) — общая склейка сменила бы форму запроса у всех моделей и рассогласовала бы уже купленные клетки. ⚠ Проверено, что у `deepseek-v4-pro`, `gpt-5.6-luna`, `glm-5` и `grok-4.3` два системных доезжают целиком. -- ⛔⛔ **ТОТ ЖЕ ДЕФЕКТ ЖИВЁТ В ДВИЖКЕ, А НЕ ТОЛЬКО В ПОЛИГОНЕ (аудит 22.08).** `backend/internal/pipeline/render.go:256-258`=`Content: injection` штатно строит ВТОРОЕ системное сообщение — инъекцию банка памяти (глоссарий/STM), `backend/internal/llm/httpllm.go:517-522`=`SystemMessagesSingle` копирует сообщения дословно, склейки под Gemini нет нигде, а `gemini-3.1-pro-preview` стоит в ратифицированной цепочке эскалации во ВСЕХ арм-конфигах (`pipeline-c1.yaml:129`, `pipeline-arm-glm.yaml:61`, `pipeline-arm-deepseek-pro.yaml:62`, `pipeline-arm-mistral.yaml:61`). ⇒ **любой эскалационный хоп на Gemini при непустой инъекции ТИХО терял текст.** Оговорка D22.3 «экспозиция Ф1 нулевая» держалась только на том, что до Gemini в дефолте не доходят. +- ⛔⛔ **ТОТ ЖЕ ДЕФЕКТ ЖИВЁТ В ДВИЖКЕ, А НЕ ТОЛЬКО В ПОЛИГОНЕ (аудит 22.08).** `backend/internal/pipeline/render.go:312-314`=`Content: injection` штатно строит ВТОРОЕ системное сообщение — инъекцию банка памяти (глоссарий/STM), `backend/internal/llm/httpllm.go:517-522`=`SystemMessagesSingle` копирует сообщения дословно, склейки под Gemini нет нигде, а `gemini-3.1-pro-preview` стоит в ратифицированной цепочке эскалации во ВСЕХ арм-конфигах (`pipeline-c1.yaml:129`, `pipeline-arm-glm.yaml:61`, `pipeline-arm-deepseek-pro.yaml:62`, `pipeline-arm-mistral.yaml:61`). ⇒ **любой эскалационный хоп на Gemini при непустой инъекции ТИХО терял текст.** Оговорка D22.3 «экспозиция Ф1 нулевая» держалась только на том, что до Gemini в дефолте не доходят. ✅ **ДЕФЕКТ ДВИЖКА ЗАКРЫТ 28.08 лендингом `7d0c6f2`** — проверено кодом 29.08, а не пересказано: у провайдера `gemini` объявлено `system_messages: single` (`backend/configs/models.yaml:137`=`system_messages: single`), адаптер склеивает ВЕДУЩИЙ system-ран в одно сообщение и падает громко на системном ходе после не-системного (`backend/internal/llm/httpllm.go:528`=`func toOpenAIMessages`), инвариант запинен тестом (`backend/internal/llm/systemmessages_test.go:78`=`TestSystemMessagesSingleCarriesTheInjection`). ⇒ строки выше про движок — ИСТОРИЯ, а не открытый долг. ⛔ **НО ЦЕНА ДЕФЕКТА ДО ПОЧИНКИ ОЦЕНИВАЛАСЬ НЕВЕРНО, и это правится здесь (полигон 29.08, Д35.3).** По установленному выше правилу выживает ПОСЛЕДНЕЕ системное, а `render.go:247-258`=`Role: "system"` кладёт первым **сам промт стадии**, вторым — инъекцию банка. ⇒ до 28.08 хоп в Gemini терял **не глоссарий, а ВЕСЬ ПРОМТ СТАДИИ**, оставляя глоссарий: модель получала список канонных форм и текст — без единого слова о том, что с ними делать. Ровно так выглядят 16 клеток `RE` полигона: перевод есть, `finish=stop`, русский, правильной длины, инструкции за ним нет. **Тихая потеря промта не отличима от нормального ответа ничем, кроме счётчика входных токенов.** ⇒ решение «склеивать самим» было единственно верным и при неизвестном тогда чтении квирка; а любой ИСТОРИЧЕСКИЙ вывод, снятый на Gemini-хопе до 28.08, надо читать как «стадия работала без своего промта». ⚠ Класс в проекте уже был решён рядом: `backend/internal/llm/provider_anthropic.go:169-179`=`wire.System = append` сворачивает ведущий system-префикс в одно поле и падает громко на system посреди диалога — просто не обобщён на Gemini. **Зона бэкенда, пинг оркестратору отправлен.** - **Gemini**: `safety_settings` через OpenAI-совместимый слой **не передаются** (HTTP 400 «Unknown name safety_settings»). Дефолт фильтров у 2.5/3 — OFF (gap-2). Для явного контроля фильтров (роль судьи 18+) нужен **нативный** Gemini API, не OpenAI-слой. ⚠ **D22.6: на ГРАФИЧНОЙ эротике Gemini 3.x fail-closed `PROHIBITED_CONTENT` (8× wire-verified, exp11) — фильтр НЕконфигурируем, нативный API не спасает; судья эротики — только Grok. На violence/SFW Gemini-судья жив.** `finish_reason` приходит СОСТАВНОЙ строкой `'content_filter: PROHIBITED_CONTENT'` (точные матчеры мертвы — D22.8а). Биллинг Gemini: thinking ТОЛЬКО в `total_tokens` → `reasoning: additive_total`, иначе недоучёт 146×/вызов (D22.3). Нативное имя поля — `usageMetadata.thoughtsTokenCount`; слой AI-Studio `completion_tokens_details.reasoning_tokens` не отдаёт ВОВСЕ, тогда как Vertex AI то же поле отдаёт (форум 15.01.2026) — при переезде на Vertex учёт менять. ⚠⚠ **`reasoning_tokens=0` у Gemini — НОРМА, а не измерение: вывод «размышление выключено» из этого поля НЕДЕЙСТВИТЕЛЕН.** Полигон 22.08 на этом и поскользнулся, объявив «вендор-дефолт flash-lite = размышление ВЫКЛ (замерено)»; считать надо `total − prompt − completion`. ⚠ **В роли редактора Gemini течёт сервис-преамбулой в выход («Вот отредактированный…», 6/6 чанков — exp12) → за output-санитайзером D30.3.** ⚠ **Дополнение эксп-21 (08.08):** fail-closed ответ OpenAI-слоя приходит вовсе БЕЗ объекта `message` (не с пустым `content`) — парсер обязан переживать его отсутствие; на вебновелльном violence-материале срабатывания массовые (10 клеток судейского прогона с `content_filter: PROHIBITED_CONTENT`) ⇒ оговорка «на violence/SFW Gemini-судья жив» целиком НЕ держится — Gemini непригоден как ЕДИНСТВЕННЫЙ второй контур на этом материале (эксп-21 §1/§8). diff --git a/docs/experiments/23-editor-tier.md b/docs/experiments/23-editor-tier.md index 073374b2..dca158d8 100644 --- a/docs/experiments/23-editor-tier.md +++ b/docs/experiments/23-editor-tier.md @@ -5256,7 +5256,7 @@ user-сообщения» на глаз. 2 436 токенов на 7 525 зна шкале глоссарий в 300 знаков весит ~100 токенов и в оценке «на глаз» просто теряется. **⛔ И это правит записанную ЦЕНУ движкового дефекта — но не открывает его заново.** -`backend/internal/pipeline/render.go:247-258`=`Role: "system"` кладёт первым `system` — **сам промт +`backend/internal/pipeline/render.go:311-316`=`Role: "system"` кладёт первым `system` — **сам промт стадии**, вторым `system` — инъекцию банка (глоссарий/STM). Записанное следствие звучало как «эскалационный хоп на Gemini тихо теряет глоссарий» (`00-provider-quirks.md:158`). По установленному правилу всё наоборот: @@ -6877,7 +6877,7 @@ RL reasoning_tokens: 511 · 512 · 512 · 512 · 1005 · 1022 · 1023 · 1024 `temperature` — общие; 3. **исходник главы тот же** — манифест единиц приколот, и чтение никогда не переотбирает: при смене текста uid меняется, приколотый исчезает из пересчитанных кандидатов и **покупка - останавливается** (`eval/role_topology/pair_en.py:132`=`def units`). Покупка 30.08 прошла ⇒ текст не менялся. + останавливается** (`eval/role_topology/pair_en.py:132`=`def units(n_per_stratum: int | None = None`). Покупка 30.08 прошла ⇒ текст не менялся. ⚠ Это не рассуждение, а гейт, который обязан был сработать и не сработал; 4. **шаблон промта не менялся** — `onepass-core.md` последний раз тронут **20.08**, за день до покупки `RN`; пар-блоки и бриф с 20.08 не менялись вовсе (`git log --since=2026-08-20`, пусто); @@ -7085,7 +7085,7 @@ RN» переносила отношение с САМОЙ ДУМАЮЩЕЙ гл **$0.078 (кэш прогрет) … $0.133 (кэш холодный на каждой клетке)**. ⛔ ~~то есть запас ~2× даже по худшему концу~~ — **СНЯТО, разбор абзацем ниже: запаса не было вовсе.** **Как именно защищены деньги — по коду, а не по намерению.** Гард `money.Guarded.afford()` -(`eval/tenant_panel/money.py:149`=`def afford`) проверяется **перед КАЖДОЙ клеткой** и отказывает, когда +(`eval/tenant_panel/money.py:149`=`def afford(self) -> tuple[bool, str]`) проверяется **перед КАЖДОЙ клеткой** и отказывает, когда `потрачено + ожидание > потолок`. ⛔⛔ **И ЗДЕСЬ Я СОЛГАЛ САМ СЕБЕ ИМЕННО В ТОЙ ФРАЗЕ, ГДЕ ОБЕЩАЛ ЧИТАТЬ КОД (баннер консилиума 30.08).** @@ -7736,8 +7736,8 @@ $0.040093 — расхождение 0.0%). Значит прайс-таблиц `rerun2` ставил dspro первым (0.679 против 0.589) — приборы согласны лишь в том, что зазор мал; (2) пик у DeepSeek — только будни 01–04 и 06–10 UTC, то есть ответ зависит от РАСПИСАНИЯ прогона, а не только от модели; (3) выбор жильца — конфиг `backend/`, зона бэкенда и лендинг оркестратора. -* ⛔ **Носитель решения врёт прямо сейчас.** `backend/configs/pipeline-c1.yaml:65`=`×2 дешевле glm` и - `backend/configs/pipeline-arm-glm.yaml:6`=`×2 дешевле` до сих пор обосновывают выбор словами «×2 дешевле glm». +* ⛔ **Носитель решения врёт прямо сейчас.** `backend/configs/pipeline-c1.yaml:95`=`×2 дешевле glm` и + `backend/configs/pipeline-arm-glm.yaml:6`=`×2 дешевле). glm-5 держим ЖИВЫМ` до сих пор обосновывают выбор словами «×2 дешевле glm». Это устаревшее обоснование в БОЕВОМ конфиге. Правка — зона бэкенда; **пинг сдан этой секцией.** ### Д51.4. Дешёвый ход, который виден из этих же данных