diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 79d94426..73be20ed 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -3,7 +3,7 @@ > **Единственный трекер проекта.** Здесь живут строки, на которые доки, промты и D-ноты ссылаются словами «строка N» / «строка бэклога N»: **ID строки стабилен навсегда**, не перенумеровывается и не переиспользуется (D39.80). Правки — только через оркестратора; каждая петля обязана иметь диспозицию: решено / отложено-с-записью / отклонено. > ⚠ **Это НЕ бэклог зоны `docs/`, а бэклог ПРОЕКТА.** У зон свои, с другими неймспейсами ID, и единый их строк не принимает (D39.84): платформа — [../platform/BACKLOG.md](../platform/BACKLOG.md) (`П-N`) и её регистр дефектов `platform/docs/DEFECT_REGISTER.md` (`PD-N`); фронт — [../frontend/docs/BACKLOG.md](../frontend/docs/BACKLOG.md) (`Ф-N`). > ⚠ **Состояние проекта — не здесь.** Очередь, курс, CURRENT-STATE, состояние паков и живая хроника — шапка [PROGRESS.md](PROGRESS.md). Здесь только долг и его диспозиции. -> - **СЧЁТ ОЧЕРЕДИ на 16.09 (скриптом по таблице — `python3 docs/scripts/counts.py`; обновлять при каждом лендинге):** всего **359** строк · зона бэкенд **189** строго / **242** широко (⚠ колонки счётчик читает С КОНЦА — испр. D39.116) · **блокеров очереди 0** (236 закрыта D39.175; **371** — актом D39.233, **360** — актом D39.232, обе 10.09), платные прогоны разблокированы · «скоро» **188** (перечень — грепом по таблице, рукописный список снят D39.126) · гейт-строка эксп-22: 153 (плюс 150 — руки владельца; ⚠ 55 из гейтов СНЯТА — D39.198 п.5); строка 5 — остаток гейчен ре-пробой 74, носитель события теперь строка 188 (аудит D39.140); остальное «когда-нибудь». ⚠ Счёт — НИЖНЯЯ граница долга, не потолок (разбор — легенда таблицы ниже). +> - **СЧЁТ ОЧЕРЕДИ на 16.09 (скриптом по таблице — `python3 docs/scripts/counts.py`; обновлять при каждом лендинге):** всего **360** строк · зона бэкенд **190** строго / **243** широко (⚠ колонки счётчик читает С КОНЦА — испр. D39.116) · **блокеров очереди 0** (236 закрыта D39.175; **371** — актом D39.233, **360** — актом D39.232, обе 10.09), платные прогоны разблокированы · «скоро» **189** (перечень — грепом по таблице, рукописный список снят D39.126) · гейт-строка эксп-22: 153 (плюс 150 — руки владельца; ⚠ 55 из гейтов СНЯТА — D39.198 п.5); строка 5 — остаток гейчен ре-пробой 74, носитель события теперь строка 188 (аудит D39.140); остальное «когда-нибудь». ⚠ Счёт — НИЖНЯЯ граница долга, не потолок (разбор — легенда таблицы ниже). > - ⚠ **Переезд 06.09 (D39.218):** таблица вынесена из `PROGRESS.md` ДОСЛОВНО и ни один ID не сдвинут — **с единственным исключением: две ячейки (строки 220 и 306) тронуты РОВНО в номере якоря `docs/scripts/counts.py:566`→`:569` и `:592`→`:595`**, потому что цель сдвинул этот же коммит; текст ячеек не менялся, длины равны. В `PROGRESS.md` на прежнем месте оставлен заголовок-указатель, поэтому ссылки «секция „Бэклог“» доезжают в один хоп. ## Бэклог (ЕДИНЫЙ, собран 26.07, актуализация 04.08 D39.99/D39.101; правки — только через оркестратора) @@ -395,3 +395,4 @@ | 478 | **ОРДИНАЛ БАТЧА ВХОДИТ В КЛЮЧ ПОКУПКИ ⇒ СНЯТИЕ ОДНОЙ РЕШЁННОЙ СТРОКИ ПЕРЕ-ПОКУПАЕТ ВСЕ ПОСЛЕДУЮЩИЕ БАТЧИ.** Ключ берёт номер куска (`backend/internal/pipeline/render.go:366`=`strconv.Itoa(req.ChunkIdx)`), а терминолог нумерует батчи порядком (`backend/internal/pipeline/terminologist.go:1003`=`Chapter: 0, ChunkIdx: i`). ⇒ когда фильтр решённого убирает кандидата из раннего батча, ординалы ВСЕХ следующих сдвигаются, хеши меняются, чекпойнт не находится — и они покупаются заново, ДАЖЕ когда их сообщения не изменились ни на байт. Замерено советчиком на купленном сырье B (батчи покупки 1 с номерами 1 и 2 стали в покупке 2 номерами 0 и 1), оба адреса пере-сняты оркестратором. ⛔ Бьёт прямо по экономике `D39.256`: чем лучше работает «решённое остаётся решённым», тем больше сдвиг ординалов и тем дороже он обходится. Комментарий у фильтра называет только смену СОСТАВА батча и про ординал молчит. ⛔ **ПОСЫЛКА ОПРОВЕРГНУТА ЗАМЕРОМ ЗОНЫ 17.09 — ряд остаётся ЛАТЕНТНЫМ.** Пере-мерено на НАСТОЯЩЕМ батчере, а не выведено: снятие одного решённого кандидата даёт **0 сдвигов из 246 снятий**, снятие блока при боевом размере батча — **0** и на 66, и на 300 кандидатах. ⭐ Ноль не слепота прибора: положительный контроль (убрать батч целиком) даёт **18 из 19**. Причина — боевые конфиги размер батча не задают, работает умолчание, и снятие строки перекладывает кандидатов ВНУТРИ батча, не меняя их числа. ⇒ ключ покупки не трогать, пере-чеканка не нужна. ⚠ Условие, при котором вывод перестаёт держаться (назвала зона): МЕЛКИЙ размер батча оживляет класс — при 400 рунах замер даёт 475 сдвигов. ⚠ Моя ошибка названа прямо: я пере-снял два адреса и НЕ проверил саму посылку на боевом батчере, вывод советчика поехал в трекер непроверенным | бэкенд | когда-нибудь (гейт: мелкий batch_runes) | посылка пере-мерена на боевом размере батча либо класс закрыт как латентный | `D39.257` п.7, замер сессии 17.09 | | 479 | **ВАРИАНТЫ ЧЕРНОВИКОВ УХОДЯТ НАРУЖУ СКЛЕЕННЫМ ЯРЛЫКОМ, И КЛИЕНТ НЕ МОЖЕТ ПОКАЗАТЬ ИХ НА ЯЗЫКЕ ЧИТАТЕЛЯ.** В проекции банка вариант едет одной строкой вида «передача ×N», а при алиасе — с англоязычным хвостом про то, для какой поверхности он предложен: части (сама передача · число чанков · алиас) НЕ опубликованы отдельными полями. ⇒ платформа вынуждена везти это как непрозрачный текст, а разобрать ярлык у себя не вправе — движок держит парсер рядом с писателем именно потому, что второй, пере-выведенный, «продолжит работать до дня, когда у ярлыка появится поле, и тогда вернёт числа вместо ошибки». ⚠ Пара-специфики в форме нет, но **английский фрагмент в данных ломает общность по языку интерфейса**: ревью-вопрос «заработает ли пара, которой в репозитории ещё нет, без правки Go» здесь отвечается «нет на уровне ПОКАЗА». Лечение движковое: публиковать части ярлыка полями. Найдено платформенной сессией 17.09 на купленном сырье при исполнении п.4 аддендума владельца | бэкенд | когда-нибудь | части ярлыка опубликованы полями либо решено, что показ вариантов читателю не нужен | зона платформы 17.09, `D39.258` п.9 | | 480 | **В КОНТРАКТЕ ЖИВУТ НАДГРОБИЯ: 121 СТРОКА, КОТОРУЮ НЕ ОТДАЁТ НИКТО — и у одного куска нет носителя решения ВООБЩЕ.** Аудит 17.09 (сплошное чтение спеки и компаньона): мусора 3.5 % при 3497 строках, и это не забытый хлам — у каждого куска свой баннер «ретировано», то есть история, которой не хватило последнего шага. Состав: схема потолка в главах (46 строк, НОЛЬ ссылок во всём документе — единственный орфан из 68 схем, ретирована ордер-формой `0.11.0`, держится только генерённым кодом ЗАМОРОЖЕННОГО фронта) · локализованное сообщение и его поле в конверте ошибки (23 строки; ⛔ **пере-снято мной 17.09: ноль упоминаний в не-тестовом коде платформы** — и, по аудиту, ноль хитов по журналу решений и обоим бэклогам, то есть ЕДИНСТВЕННЫЙ кусок контракта без носителя решения) · поле потолка в заявке прогона (16 строк, ⚠ УДАЛЯТЬ НЕЛЬЗЯ: единственное `deprecated` в файле, оно РАБОТАЕТ отказом на входе, чтобы старый клиент не купил молча не то). ⚠ Снимать разумно ТЕМ ЖЕ актом, что синк зеркала фронта, иначе фронт при разморозке сгенерит их заново. ⚠ Локализованное сообщение либо обосновать нотой, либо снять: сегодня его не защищает ничто | платформа + оркестратор | когда-нибудь (вместе с синком зеркала при разморозке фронта) | мёртвые схемы сняты либо у каждой назван носитель решения | аудит контракта 17.09 | +| 481 | **ОТКАЗ КНИЖНОГО ПОТОЛКА В БАНК-КОНТУРЕ ТЕРЯЕТ БАТЧИ ПОЗАДИ ОТКАЗАННОГО — включая УЖЕ ОПЛАЧЕННЫЕ, чей повтор стоил бы $0.** Цикл покупки банк-батчей на `errReserveCeiling` делает выход, и всё, что стояло в очереди после отказанного, не попадает НИ в счётчик выброшенных, ни в счётчик прошедших: единственный след — предупреждение посреди прохода. ⚠ Это противоречит правилу, которое пре-флайт того же контура объявляет у себя («деньги дороже проекции, но молчать о потере нельзя»), и **дефект ПРЕД-СУЩЕСТВУЕТ паку лестницы**: до неё потерянный батч и не покупался, после неё позади отказанного могут стоять оплаченные. Названо зоной в приёмочном круге 17.09 и сознательно НЕ чинено: смена поведения, которой пак не заказывал. Адрес — ветвь отказа резервации в цикле банк-ролей. ⛔ Цена молчания растёт вместе с механизмом «решённое остаётся решённым»: чем больше батчей переигрывается за ноль, тем дороже потерять их из-за отказа на соседнем | бэкенд | скоро | потерянные батчи попадают в счётчики либо выход заменён продолжением с честным учётом | зона `textmachine-main-12`, приёмочный круг 17.09 | diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index deea9bbd..1c930601 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -205,6 +205,362 @@ ⚠ Ниже точки реза якорей не было ни одного (пере-снято прибором перед выносом), так что больше у вас ничего не сдвинулось. +#### ОТЧЁТ пака «ЛЕСТНИЦА ПОПЫТКИ ДЛЯ БАНК-РОЛЕЙ» (17.09, сессия `textmachine-main-12`). НЕ КОММИЧУ — дерево передаю оркестратору №23 + +**Деньги пака: $0.** Платных вызовов моделей не было ни одного. Все числа сняты на фикстурах и на уже +купленном сырье прогона B (только чтение). + +##### Что построено — по пунктам заказа + +| пункт | исход | чем предъявлено | +|---|---|---| +| §4.1 вынос лестницы в общий контур | **сделано** | `backend/internal/pipeline/attemptladder.go`; `runStage` и банк-роли — два вызывающих одной петли; пин «третьей копии нет» перечисляет вызовы `runAttempt` поимённо | +| §4.1 ключ ступени — число удвоений | **сделано** | голден ЗНАЧЕНИЯ хеша (построен ДО выноса и прошёл через него зелёным — это и есть доказательство, что ключ не сдвинулся); шесть ручных посадок при постройке + три записи `BANKKEY-*`, введённые в гейт навсегда | +| §4.1 параметр «допуск ступени» | **сделано** | `ladderCall.afford`; у стадии `nil` (правило — резервация), у банк-роли — ролевой суб-бюджет | +| §4.1 параметр «судья ступени» | **НЕ ДЕЛАЮ, довод ниже** | — | +| §4.2 ступень мимо бюджета и пробы | **сделано** | `roleBudget` — один объект на пре-флайт и на ступени, и `nil` = одиночный выстрел по построению; четыре посадки RED (допуск снят · два счёта · ступень уже оплачена · книжный потолок на ступени ≥ 1) | +| §4.3 эскалация банк-ролей | **НЕ ДЕЛАЮ, довод и спецификация ниже** | — | +| §4.4 тождество банк-батча | **НЕ ДЕЛАЮ, посылка ряда 478 опровергнута замером** | таблица ниже + прибор в дереве (`TestProbeBankBatchOrdinalShift`); оркестратор отказ принял и снял пункт | +| §4.5 счёт ответов в итог | **сделано** | `classify_asked`/`classify_answered` + отдельное предупреждение; посадка RED | +| §4.6 чистый код, рефакторинг | **сделано** | список «что отрефакторено и почему» ниже | + +##### Разрез: что переехало и почему + +**Переехала ОДНА вещь — петля попытки.** Из `runStage` в `walkAttemptLadder` (`attemptladder.go`, 264 +строки). `stagerun.go`: −89 / +27 строк. Всё, что вокруг петли, осталось на месте: резюм по `chunk_status`, +эскалация, диспозиция, запись строки статуса — у стадии; сборка батча и разбор таблицы — у банк-роли. +`runStage` из терминолога не зовётся. + +**Почему рефактор, а не заплатка.** Заплаткой было бы `if isBankRole { ... }` внутри `runStage` или вторая +петля рядом. Обе оставляют ДВА ответа на вопрос «под каким бюджетом покупается ретрай», а бюджет входит в +хеш запроса — то есть две цены на один вызов, и ни одна не краснеет. + +**Что стало параметром и почему именно это:** + +- **допуск ступени** (`afford`) — у стадии деньги стережёт книжная резервация ВНУТРИ попытки, у банк-роли + фазовый суб-бюджет, решённый ДО прохода. Ступень — покупка, которой в том плане не было. +- **число удвоений** — ключ ступени, общий перечислитель, параметром идёт `baseMaxTokens`. +- **`stageAtEffort`** — одно написание «стадия при таком-то усилии». Появилось не для красоты: усилие + входит в ключ покупки, и проба «оплачено ли» обязана спрашивать про ключ, который попытка РЕАЛЬНО + возьмёт (см. находку 2 самопроверки). +- **политика допуска фазы — ОДИН объект `roleBudget` (потолок · израсходованное · число ре-спросов).** + Дошла до этой формы не сразу: сперва число ре-спросов было отдельным параметром `runBankAttempt`, и + вынудил его живой стенд — `live_reprobe` меряет, что вернул ОДИН вызов классификатора, и все его + опубликованные числа («4 прогона из 5 дают 6/6 при усилии low») про вызовы, а не про проходы; читай + функция политику прохода сама, стенд начал бы молча пере-спрашивать усечённый ответ и продолжил бы + печатать по-вызовные цифры. ⛔ Но два параметра оставляли инвариант «нет бюджета ⇒ нет ступени» на + КОММЕНТАРИИ: вызывающий с парой `(nil, 1)` получил бы допуск `nil`, то есть все ступени без пробы и без + потолка фазы, молча. Слитые в один объект, деньги и счёт разойтись не могут: **`nil` = одиночный выстрел + по построению**, и стенд передаёт именно `nil`, говоря почему. + +##### §4.3. Эскалация банк-ролей — не строю; вот что защищает нулевой хоп + +Довод отказа **не денежный**: измеренная болезнь прогона B — размышление, съевшее ПОЛНЫЙ потолок +(8496/8496 ×3, 8000/8000 у всех семи выброшенных вызовов; у DeepSeek размышление считается внутри +`completion_tokens`). Другая модель потолка не лечит; лечит удвоение бюджета, и на черновой стадии того же +прогона оно вылечило 5 единиц из 5. Класс, для которого хоп осмыслен (`refusal` — тот же вызов на той же +модели пере-откажет, D2.2), на банк-ролях имеет замеренную популяцию ноль. + +**Пять причин нулевого хопа — это спецификация, если владелец хоп закажет.** Прочитано в +`internal_call.go` и `escalation.go`, каждая пере-снята по коду: + +1. **Чужой пул.** `escalationBudgetRemains` читает `Pipeline.Escal.BudgetUSD` и `EscalationSpentUSD(книга)` — + пул КНИГО-ШИРОКИЙ, принадлежит волне. Банк-хоп ел бы его молча. +2. **Способ деградации.** «these calls degrade by leaving their work unchanged rather than by paying a + second model»: неотвеченный терм остаётся `auto` и ничего не портит, в отличие от чанка, который + отгрузил бы брак. +3. **Предварительная сборка клиентов.** `gateModels()` кладёт в достижимые модели только модели ВКЛЮЧЁННЫХ + гейтов; модель хопа туда не попадёт — и `buildClients`/`CheckKeys` её пропустят, а прогон умрёт на + банк-стопе «no pre-built client» ПОСЛЕ оплаченной волны. Это уже случалось (комментарий `gateModels`). +4. **Аддитивный буфер.** `InternalCall` держит `ReasoningMaxTokens: 0`, и это безопасно лишь потому, что + загрузчик ОТКАЗЫВАЕТ аддитивному провайдеру для моделей гейтов. Модель хопа потребовала бы того же отказа. +5. **Имя отказа в стоп-марке.** `ceilingStopDetail` (`cutcall.go`) утверждает, что отказ потолка называет + РЕ-АТАКУ, и это верно, пока каждый прочий платный шаг ловит `errReserveCeiling` и деградирует. + ⚠ Моя лестница — новый платный шаг на банк-пути, и она это условие ДЕРЖИТ: `mandatory: false`, ошибка + потолка ловится вызывающим и гасится в «остальные термы без изменений». + +##### §4.4. Ключ не трогаю: посылка ряда 478 не подтверждается + +Ряд 478 утверждает: снятие решённой строки сдвигает номера всех последующих батчей, и они покупаются +заново при неизменном тексте. Пере-мерил на настоящем `terminology.Batch`. + +**Механика.** Упаковка жадная. Снятие кандидата из батча k либо ПОДТЯГИВАЕТ содержимое из k+1 (у всех +последующих меняется ТЕКСТ, и ординал ни при чём), либо не подтягивает (не меняется ничего). Сдвиг номера +при неизменном тексте требует, чтобы батч исчез ЦЕЛИКОМ из середины. + +Прибор лежит В ДЕРЕВЕ и повторяется одной командой (по умолчанию скипается): +`TM_PROBE_478=1 go test ./internal/pipeline/ -run TestProbeBankBatchOrdinalShift -v`. + +| замер | батчей | untouched | content-changed | сдвинуто с тем же текстом | +|---|---|---|---|---| +| снятие ОДНОГО кандидата, 186 снятий в 3 режимах | 2…20 | 1494 | 958 | **0** | +| снятие БЛОКА, 200 случайных, `batch_runes=400`, 60 кандидатов | 19 | 1141 | 1899 | **490** | +| снятие БЛОКА, 200 случайных, `batch_runes=6000`, 66 кандидатов (боевой) | 2 | 4 | 354 | **0** | +| снятие БЛОКА, 200 случайных, `batch_runes=6000`, 300 кандидатов (боевой) | 6 | 65 | 1055 | **0** | +| ⭐ положительный контроль: убрать средний батч целиком | 20→19 | — | — | **18** | + +`batch_runes` не задан ни в одном из трёх боевых конфигов ⇒ работает движковый умолчательный 6000; на +прогоне B это дало 3 батча на 66 термов (22 · 19 · 24), то есть боевой режим — строки 3–4. + +⇒ **Класс ЛАТЕНТЕН, а не отсутствует. Условие, при котором вывод перестаёт держаться, называю прямо:** +мелкий `batch_runes` (сотни рун) делает его живым — 475 сдвигов на 200 снятий. Доминирующий драйвер +пере-покупки — пере-упаковка СОДЕРЖИМОГО — ни одной из двух ветвей §4.4 не лечится и назван ограничением в +самом `dropBankSettled`. + +##### Судья ступени (§4.1, третий параметр) — не строю, и это не экономия + +Промт даёт судью ради хопа («без него хоп по «упёрся в потолок» бессмыслен»). Хопа нет ⇒ обоснование +отпадает. Но есть и самостоятельный довод, и он сильнее: **у лестницы ровно одна ремедия — те же сообщения +с бо́льшим бюджетом.** Таблица, пришедшая ЦЕЛОЙ при `finish=stop`, но с малой долей ответов, в бюджет не +упиралась — больший бюджет ей не покупает ничего. Её лекарство — пере-спрос ОСТАТКА, то есть другой запрос, +другой ключ покупки и отдельное решение владельца (`D39.254` п.2, ряд 451). + +⇒ Хук `judge` в дереве НЕ оставлен: поле, у которого нет вызывающего, — обещание, которого не стережёт ни +один пин. Вместо него в `ladderCall` стоит абзац, называющий, почему форменный вердикт на эту лестницу не +заводится. + +**Что надо измерить, чтобы решение пересмотреть** (в бэклог): распределение `answered/asked` при +`finish=stop` по батчам на уже купленных прогонах — данные лежат в журнале запросов, пары печатаются на +каждый батч. Если доля заметно ниже 1 на нетривиальной части — строится пере-спрос остатка, не судья. + +##### Ручка лендится ВКЛЮЧЁННОЙ — решение СВЕРХ заказа, РАТИФИЦИРОВАНО `D39.259` + +`gates.terminology.regenerate: 1` в трёх боевых конфигах (c1 · arm-glm · arm-mistral; в c2 гейта нет), +пин `TestShippingPipelinesRegenerateBankRoles` стережёт значение РОВНО 1 и печатает знаменатель +(«3 из 4»). + +Норма проекта — не «новые ручки выключены», а «НЕИЗМЕРЕННОЕ денежное решение выключено, и пин называет, +что измерить». `lower_effort_on_empty` лендился выключенным по названной причине: стадия, которую он +двигает, — редактор, чья эхо-безопасность на пониженном усилии НЕ измерена. Здесь измерение есть, и оно +снято В ТОЙ ЖЕ КЛЕТКЕ: боевой черновик c1 и банк-роли идут на ОДНОЙ модели (`deepseek-v4-flash`) при ОДНОМ +усилии (`low`) — `classify_model` не задан ни в одном боевом конфиге, а `ClassifyModel == "" → Model`. +Обратный прецедент тоже есть: `regenerate_echo_before_escalate` лендился = 1, и его пин стережёт единицу. + +**Цена включения — числами, считанными по хранилищу прогона B** (ценовая модель сверена с леджером до +цента: 8000 completion + 4741 prompt по пинам `deepseek-v4-flash` дают ровно записанные $0.012646): + +| величина | число | +|---|---| +| реальная цена успешной ступени | **$0.005134** (среднее по 4 чистым вызовам классификатора, 2939 completion-токенов) | +| худший случай одной ступени (снова выест удвоенный потолок 16000) | **$0.0232** | +| что лестница купила бы на прогоне B (2 флагнутых батча) | реально ≈ **$0.010**, абсолютный худший ≈ **$0.046** | +| структурный потолок на книгу (≤ 1 лишний вызов на батч; у B батчей 7) | ≤ **$0.162**, и сверху ролевой суб-бюджет $1.00 на фазу | +| для сравнения: банковый контур прогона B / весь прогон | $0.083339 / $0.436 | + +⛔ **Это решение СВЕРХ заказа, и я назвала его таковым до того, как меня спросили.** Промт заказывал +лестницу и подключение вердикта; включение денежной ручки в боевых конфигах — отдельное денежное решение. +Довод «за»: с ключом на нуле пак для боевых книг ИНЕРТЕН — движок продолжит вычислять вердикт и продолжит +его выбрасывать. **Ратифицировано оркестратором актом `D39.259`** по этим числам. + +⚠ И что этот пин НЕ утверждает: что ре-спрос работает, и что единица — верное число. Он утверждает ровно +одно — что боевые ДАННЫЕ продолжают его просить. Работу механизма стерегут пины в пакете движка, а +значение выше единицы отвергается не как опасное, а как **неизмеренное**. + +**Трата разовая на книгу, а не на прогон**, потому что +ступень пишет СВОЙ чекпойнт и на всех последующих резюме переигрывается за $0. Это не довод, а пин: +`TestTheExtraRungIsBoughtOnceForTheLifeOfTheBook`. Снапшот при этом не двигается — гейт терминологии в +него не складывается (`snapshot.go`: упоминаний `Terminology` 0 при 11 упоминаниях `Gates.`). + +##### Улика, пере-снятая мной с КУПЛЕННОГО прогона B (а не взятая из промта) + +Промт велит пере-снимать числа, на которых строишь решение. Основание пака — «движок вычислил вердикт и +выбросил его» — я пере-сняла сама, по хранилищу прогона B, **с копии, оригинал не тронут** (mtime до и +после копирования совпадает; `sqlite3` как CLI на машине нет — читала `python3`). + +**Контрольные величины напечатаны рядом с ответом:** строк в `request_log` — **35**, из них +`stage='terminology'` — **14**; чекпойнтов терминологии — **13**. То есть ноль ниже был бы ответом, а не +отсутствием предмета. + +| роль | исход | вызовов | $ | +|---|---|---|---| +| classifier | чисто | 4 | 0.020534 | +| classifier | **`length`** | 1 | **0.012646** | +| classifier | **`empty`** | 1 | **0.012133** | +| terminologist | чисто | 8 | 0.038026 | + +Два вызова, которые движок САМ признал негодными, и чей вердикт выбросил: + +``` +role=classifier batch=0 degraded=length finish=length completion_tokens=8000 cost_usd=0.012646 tm_hit=0 +role=classifier batch=2 degraded=empty finish=length completion_tokens=8000 cost_usd=0.012133 tm_hit=0 +``` + +**Три вещи, которые это даёт сверх промта:** + +1. **`completion_tokens = 8000` у ОБОИХ** — ровно `min_max_tokens` модели `deepseek-v4-flash`. Потолок + выеден до токена; это не «модель отказалась», это «размышление съело бюджет», то есть болезнь, которую + лечит именно удвоение. ⭐ И удвоение даёт 16000 — то же число, которое `models.yaml` уже пинит для + СОСЕДНЕЙ модели по замеру «8000 → 16000 вылечило 2 единицы из 2». +2. **`max(attempt) = 0` по ВСЕМУ банковому контуру** (все 7 позиций) ⇒ попытки с индексом 1 не существует + ни у одного батча: **регенерации не было ни одной**, как и утверждалось. Это тот самый индекс, который + лестница теперь покупает. +3. **$0.024779 оплачено за ничто** из $0.083339 банкового контура прогона — 29.7%. + +⚠ И честная оговорка о том, чего эти числа НЕ говорят: у каждого батча в хранилище по ДВА чекпойнта при +`attempt = 0`, потому что store держит и первый прогон, и резюм, купившее проход заново под другим ключом. +Это ДРУГОЙ дефект (пере-покупка прохода при смене состава/снапшота), он в мой пак не входит и моей +лестницей не лечится. + +##### Рефакторинг без регресса — чем предъявлен + +Ось «что переехало и чем доказано, что поведение прежнего вызывающего не изменилось» закрывается тем, что +у СТАДИИ такой прибор уже был, и он побитовый: `TestGoldenDeterminism` (`golden_test.go`) пинит на +статичной книге снапшот каждой волны, **хеш запроса и тело провода каждой стадии**, строки `chunk_status` +и `retrieval_state` и итоговые тексты — на свежем прогоне И на резюме. Он прошёл через вынос зелёным. +Именно его отсутствие на БАНКОВОЙ стороне промт и назвал, и именно поэтому голден банк-ключа строился +первым делом. + +Сверх него зелёными под `-race` прошли одиннадцать пре-существующих пинов ровно того механизма, который +переехал: `TestABurnFollowedByARegenerationDoesNotOverBuy` · `TestAStopOnAStagesFirstCallStillReportsAnAttempt` · +`TestEchoRegenReplacesTheHop` · `TestEchoRegenFiresONLYForEcho` · `TestEchoRegenBudgetMovesNoSnapshot` · +`TestTheRetryForAnEmptyReplyBuysLessThinkingNotMoreBudget` · `TestRetryableSubset` · +`TestLabelledRefusalEscalatesThroughChainAndRegates` · `TestRunnerEscalationBudgetExhaustionDeniesLaterHop` · +`TestABurnedHopKeyDoesNotBuyAHopOutsideTheEscalationBudget` · `TestAStopOverAnEscalationHopLeavesAMarkAndIsRedone`. + +##### Самопроверка исполнением: круги и что они нашли + +Кругов было три: постройка прибора → вынос и банковая половина → приёмочный адверсариальный проход +(свой плюс старший советчик). Найдено у СЕБЯ, и часть — уже в готовой работе: + +| находка | что сделано | чем предъявлено | +|---|---|---| +| **Голден хеша был ложно-зелёным**: сообщения короткие ⇒ потолок упирался в пол `MinMaxTokens`, вся арифметическая ветвь формулы в ключ не входила; посадка `terminologyReplyFloor` 256→257 оставляла файл ЗЕЛЁНЫМ | батч 0 сидит В полу, батч 1 его перерастает; ПОСЫЛКА утверждается внутри теста, иначе фикстура тихо съедет в один режим | запись каталога `BANKKEY-bank-reply-floor-moves-the-budget` — RED | +| **Проба оплаты и цена ступени считались по ИСХОДНОМУ усилию, а покупка шла по пониженному** (усилие входит в ключ ⇒ ложное «оплачено» проносит ступень мимо суб-бюджета, ложное «не оплачено» выбрасывает уже купленную) | `admitLadderStep` получает стадию попытки; одно написание `stageAtEffort` | нашёл советчик-фабл; сегодня недостижимо у обоих вызывающих, и это НАЗВАНО (см. «где прибор слеп») | +| **Мой литерал базового бюджета фикстуры был неверен** (512 против реальных 564), а «проверявший» его тест мерил ПУСТОЙ батч — то есть согласился со мной про другой объект | литерала нет вовсе: фикстуры учат базу у ПЕРВОГО вызова и утверждают ОТНОШЕНИЕ (удвоение) | `askedBudgets.doubledOnce` | +| **Две посадки СДОХЛИ, ничего не измерив**: снятие `if cls.ok() { break }` — no-op (ok-ответ и так падает в финальный `break`), снятие `retryable()` — тоже no-op (ok-break срабатывает раньше) | посадка сделана СОСТАВНОЙ из обеих правок; в `why` записано, что ни один из двух гардов не является несущим ПООДИНОЧКЕ | `LADDER-healthy-reply-keeps-climbing` — RED | +| **Фикстура суб-бюджета не различала «общий счёт» и «два счёта»**: потолок был равен цене ступени 0, и ступень 1 отвергалась при любой реализации | потолок берётся равным цене СТУПЕНИ 1, прочитанной из строки лога самой лестницы | `LADDER-pre-flight-and-rungs-count-different-money` — RED | +| **Мёртвое поле** `roleBudget.role` (писалось, не читалось) | удалено | — | +| **Смена сигнатуры сломала стенд под тегом `live`**, а мой `go vet ./internal/pipeline/` этого НЕ ВИДИТ: он не компилирует файлы за билд-тегом. Поймал гейт зоны (`go vet -tags live`, `MAKE_EXIT=2` при НУЛЕ строк `FAIL`) | стенд переведён на новый шов и получил `maxRegens: 0` с объяснением, почему у него другая граница измерения | `go vet ./...` и `go vet -tags live ./...` зелёные; батарея ниже | +| **`nil`-бюджет превращался в колбэк, который его же разыменует** | сперва nil-проверка, затем — по существу (строка ниже) | — | +| ⛔ **РЕГРЕСС, КОТОРЫЙ ОТКРЫЛА САМА ЛЕСТНИЦА: отказ КНИЖНОГО потолка на ступени ≥ 1 выбрасывал ОПЛАЧЕННЫЙ ответ ступени 0.** До пака ошибка на этом пути означала «ничего не куплено», и `break` без записи текста был всей правдой. С лестницей ступень 0 может быть куплена, классифицирована и держать частичные строки — ровно те «4 из 22», ради которых пак и заказан, — а батч читался как невызванный | на `errReserveCeiling` при `judged > 0` текст сохраняется, `ran[i]=true`, `unusable++`; WARN называет, сколько ступеней этот батч успел оплатить, и что это КНИЖНЫЙ потолок, а не фазовый | нашёл советчик, подтверждено чтением; **запинено** `TestABookCeilingOnALaterRungKeepsWhatTheEarlierOneBought` (потолок меряется двумя пробами: расход по ступень 0 включительно + цена ступени 1 из строки покупки) и записью каталога — RED | +| **Инвариант «нет бюджета ⇒ нет ступени» держался КОММЕНТАРИЕМ**: вызывающий с парой `(nil, 1)` получил бы `afford == nil`, то есть все ступени без пробы и без потолка фазы — молча | политика фазы стала ОДНИМ объектом: `regens` живёт в `roleBudget`, `nil` = одиночный выстрел ПО ПОСТРОЕНИЮ; параметр `maxRegens` убран | — | +| **Потолок в фикстуре суб-бюджета стоял РОВНО на цене ступени 1**, а тест читает её из строки лога, отрендеренной `%.6f` ⇒ поимка мутанта решалась последним битом округления | потолок `est1 + est0/2` — запас на порядки выше шага округления, обе стороны различения целы | `LADDER-pre-flight-and-rungs-count-different-money` | +| **Посылка «фаза уже перерасходовала» строилась из КНИЖНОГО расхода**, а `roleBudget` стартует с РОЛЕВОГО ⇒ фикстура утверждала соотношение, которого не проверяла | берётся `RoleSpentUSD(роль)`, и посылка утверждает именно её | `TestAnAlreadyBoughtRungIsNeverLostToTheBudget` | +| **Имя теста обещало «ОДИН раз за жизнь книги», а фикстура держала постоянным СОСТАВ БАТЧА** — а состав в бою меняется обычным порядком (вырос набор черновиков · владелец подписал терм) | тест переименован в `…OnceForABatchComposition`; комментарий и абзац в `pipeline-c1.yaml` называют условие прямо, а не в скобках | — | +| **Ложное утверждение в боевом конфиге**: «считается против суб-бюджета фазы, НЕ против книжного потолка». Резервация внутри попытки судит книжным потолком КАЖДУЮ ступень | «поверх книжного потолка, а не вместо него» | — | +| ⛔ **Ложное утверждение в `why` записи каталога**: я написала, что снятие `retryable()` поодиночке — no-op. **Проверила исполнением: это неверно.** Посадила одну эту правку и прогнала ВЕСЬ пакет — красное, `TestRunnerCoverageGateFlagsExcision`: детерминированный флаг (excision) при `regenerate: 1` покупает лишний вызов на той же модели до хопа, то есть ровно D2.2 | `why` исправлен; заведена ОТДЕЛЬНАЯ запись `LADDER-retryable-gate-lets-a-deterministic-flag-re-buy` со своим катчером | RED | + +##### Что советчик утверждал, а дерево опровергло + +Советчик — не источник истины, и две его посылки я проверила и ОТКЛОНИЛА: + +1. **«Одиночное снятие `retryable()` не ловит ни один тест дерева».** Неверно: пакет краснеет на + `TestRunnerCoverageGateFlagsExcision` (`regenerate: 1` + детерминированный excision-флаг). Проверено + посадкой и прогоном ВСЕГО пакета, а не рассуждением. ⚠ Почему ошибка вышла у нас обоих: мутационный + харнесс гоняет каждую запись с `-run <тест>`, поэтому моя одиночная проба показала SURVIVED — она + спрашивала ОДИН тест. Сам класс советчик назвал верно, и запись каталога он заслужил. +2. **«Значение `regenerate_before_escalate` в боевых конфигах не запинено».** Неверно: + `internal/config/echoregen_shipping_test.go:49` утверждает `!= 1` по четырём боевым файлам. + +##### Унаследованное, названное и НЕ чинимое этим паком + +На отказе книжного потолка цикл покупки делает `break`, и вместе с отказанным батчем теряются батчи +ПОЗАДИ него — включая уже оплаченные, чей повтор стоил бы $0. Это противоречит правилу, которое сам +пре-флайт объявляет у себя («ADMISSION IS PER BATCH, NOT A PREFIX»), но существовало до пака и моей +лестницей не создано: адрес — ветвь `errReserveCeiling` в `runBankRoleBatches`. ⚠ И вторая половина, чтобы +следующая смена не искала её сама: такие батчи не попадают НИ в `dropped`, ни в `ran` — единственный их +след это WARN. Не чиню, потому что это смена поведения, которой пак не заказывал; называю, чтобы +следующая смена нашла решение, а не щель. **Оркестратор завёл это рядом 481** — то есть находка пережила +мой контекст, чего от «названного, но не заведённого» ждать нельзя. + +##### Мутации + +Каталог: было 478 записей / 267 в гейте, стало **491 / 280**. + +- **7 записей пере-нацелено** — их цели переехали вместе с петлёй из `stagerun.go` в `attemptladder.go`. + Каждая сверена ПО СМЫСЛУ с новым текстом, а не подогнана под совпадающую строку. +- **13 записей заведено** под новый код, **все 13 помечены `battery`** — разовым прогоном смены они не + остаются. По предметам: лестница банк-роли (есть/нет · воздержанность на здоровом ответе · D2.2-гард) · + деньги ступени (допуск снят · два счёта · ступень уже оплачена · книжный потолок на ступени ≥ 1) · + ключ покупки (ординал · индекс попытки · пол ответа) · счётчики (доля ответов · негодный батч · + отказанная ступень). +- **Контроль целостности:** все **502** правки каталога разрешаются в дереве уникально; + `anchors swept: 0 of 491 entr(ies) rotten`. + +##### Где мой прибор слеп, и я это знаю + +1. **Одиночная проба посадки спрашивает ОДИН тест.** Харнесс гоняет запись каталога с `-run <тест>`, + поэтому «SURVIVED» при одиночной проверке значит «выжила против ЭТОГО теста», а не «дыра в пинах». Так + я и получила ложное «no-op» про `retryable()`. ⇒ выжившую при одиночной пробе перепроверяю прогоном + ВСЕГО пакета, прежде чем писать о ней что-либо в `why`. +2. **`go vet ./<пакет>` не компилирует файлы за билд-тегом.** Я меняла сигнатуру и проверяла пакет + обычным `vet` — он был зелёным, пока гейт зоны не собрал тот же пакет с тегом `live` и не упал. + ⚠ И форма падения ровно та, о которой предупреждает канон: `MAKE_EXIT=2` при НУЛЕ строк `FAIL` — + «все тесты зелёные» было бы правдой, которая не значит ничего, потому что до тестов гейт не дошёл. + Закрывается тем, что теперь я гоняю ОБА набора тегов; в отчёте это названо, а не спрятано. +3. **Пин шва видит только обычное написание вызова.** Обход ищет `CallExpr` с селектором `runAttempt`; + метод-значение (`f := r.runAttempt; f(…)`) или вызов через интерфейс он пропустит. Денежный путь так + случайно не пишут, поэтому обход всё равно стоит держать — но «нового вызывающего нет» здесь значит + «нет вызывающего, написанного обычным способом». Названо в самом файле. +4. **Шов «усилие в допуске» почищен, но не запинен.** Ветвь понижения усилия недостижима на боевых + настройках дважды: нужен `retries.lower_effort_on_empty` (выключен во всех боевых, пин есть) И ступень + ниже настроенного усилия, а банк-роли настроены на `low` — дно лестницы `Models.ReducedEffort`. Посадка + под этот шов сегодня ВЫЖИЛА БЫ, ничего не измерив, — поэтому записи каталога под него НЕ завожу + (посадка обязана атаковать то, что стережёт пин). Условие достижимости названо в коде. +5. **Живой прогон не гонялся.** Всё снято на фикстурах; платных вызовов пак не делал. +6. **Эффект на КАЧЕСТВО перевода не измерен** — измерима только механика. Мерило эффекта, как и велит + промт, журнал запросов боевого прогона (`asked`/`answered` по батчам), не проекция: у неотвеченного + кандидата остаётся эвристический тип, и проекция покажет 66 из 66. + +##### Адреса, которые съехали (оркестратору — сэкономить проход) + +Строки сдвинулись в `backend/internal/pipeline/stagerun.go` (тело цикла вынесено) и +`backend/internal/pipeline/terminologist.go`. Живых якорей, целящих в эти файлы, в доках 115 и 62 +соответственно — какие из них реально уехали, судит гейт якорей, я в `docs/` за пределами этой секции не +лезу. + +##### Аддендум владельца 17.09 — исполнение по пунктам + +1. **Тщательно проектируй.** Записка-план легла в этот журнал ДО первой правки движка. +2. **Комментарии без гарантий без пинов; вывод называет условие отмены.** Исполнено; мой собственный + случай этого класса (ложно-зелёный голден) — в таблице находок. +3. **Прозы в коде нет** — комментарии объясняют «почему так», а не «что делает строка». +4. **Общность.** В лестнице нет ни одной ветки по паре или книге; порогов-констант в Go не заведено — + единственная новая ручка (`regenerate`) это ДАННЫЕ конфига. +5. **Советчик.** Поднят один фабл с постоянным контекстом, на ПРОЕКТИРОВАНИИ (до банковой половины). + Нашёл денежный дефект в моём же шве (находка 2) и отговорил от судьи аргументом, который я приняла. +6. **Греп по докам и полигону** — пользовалась: §4.3 и §4.4 закрыты чтением `25-door-to-file-b.md` и пина + в `configs/models.yaml`. + +##### Гейт зоны — зелёный, и это сверено СПИСКОМ, а не отсутствием слова FAIL + +`MAKE_EXIT=0`. Цели прошли все и в порядке: `go build` · `go vet` · `go vet -tags live` · сборка `tmvet` · +`go vet -vettool` · `go vet -tags live -vettool` · `golangci-lint` (0 issues) · `go test ./... -race`. + +Полнота: `go list ./...` даёт **24** пакета, вердиктов в логе **24**, разность `comm -23` (список против +вердиктов) — **пусто**. ⚠ Контроль порядка операндов напечатан рядом: перевёрнутая разность даёт 0 ВСЕГДА +и не значит ничего, поэтому сверка сделана в правильную сторону и обе величины показаны. + +⚠ Один раз по дороге этот же гейт меня и поймал: `MAKE_EXIT=2` при НУЛЕ строк `FAIL` — падение на +`go vet -tags live`, до тестов дело не дошло. Именно поэтому вердикт читается строкой-итогом и списком +целей, а не хвостом лога. + +##### Числа и как их повторить + +Все числа сняты ПОСЛЕ последней правки дерева. + +| что | команда | +|---|---| +| гейт зоны целиком | `cd backend && make battery` (вердикт — строка `MAKE_EXIT`, полнота — сверка `go list ./...` со списком вердиктов, а не отсутствие слова FAIL) | +| новые пины пака | `cd backend && go test ./internal/pipeline/ ./internal/config/ -count=1 -run 'TestBankBatchPurchaseKeyValueIsPinned\|TestTheBankPassBuysAtThePinnedKey\|TestATruncatedBankBatch\|TestAHealthyBankTable\|TestARungTheRoleBudget\|TestTheExtraRungIsBought\|TestAnAlreadyBoughtRung\|TestTheBankPassNamesItsAnswerShare\|TestOnlyTheLadder\|TestShippingPipelinesRegenerateBankRoles'` | +| пре-существующие пины переехавшего механизма, под `-race` | `cd backend && go test ./internal/pipeline/ -race -count=1 -run 'TestABurnFollowedByARegenerationDoesNotOverBuy\|TestEchoRegen\|TestTheRetryForAnEmptyReplyBuysLessThinkingNotMoreBudget\|TestGoldenDeterminism'` | +| каталог мутаций, батарейное подмножество | `cd backend && make mutations` | +| целостность каталога (все правки разрешаются уникально) | `python3 -c "import json;ms=json.load(open('backend/cmd/tmmutate/mutations.json'));print(sum(len(m['edits']) for m in ms), sum(1 for m in ms for e in m['edits'] if open('backend/'+e['file']).read().count(e['find'])!=1))"` | +| замер §4.4 (сдвиг ординала) | `cd backend && TM_PROBE_478=1 go test ./internal/pipeline/ -run TestProbeBankBatchOrdinalShift -v` (по умолчанию скипается; печатает знаменатели и положительный контроль) | + +##### Завершённость + +- у каждого пункта заказа есть исход (таблица в начале): сделано · не делаю с доводом · снято оркестратором; +- круги сошлись — последний адверсариальный проход не дал новых находок, прежние закрыты таблицей + «находка → что сделано → чем предъявлено»; +- таблица мутаций полная, выжившие названы поимённо и разобраны (обе выжившие были МОИ мис-посадки, а не + дыры в пинах); ⏳ **полный прогон батарейного подмножества каталога (280 записей) ИДЁТ и не закончен** — + «каталог зелёный» НЕ заявляю, число впишу сюда, когда придёт; +- список «что отрефакторено и почему» — в разделе «Разрез»; +- всё живое — в дереве, ничего не осталось в скретчпаде; +- ⏳ **дерево ЗАМОРОЖЕНО, править не планирую** — правки прекращены намеренно, чтобы кампания мерила ровно + то, что сдаётся. Фраза «работа завершена» будет здесь, когда закроется последняя строка выше. + #### ЗАПИСКА-ПЛАН пака «ЛЕСТНИЦА ПОПЫТКИ ДЛЯ БАНК-РОЛЕЙ» (17.09, сессия `textmachine-main-12`). До первой правки движка. НЕ КОММИЧУ **Предмет, пере-снятый по дереву, а не по промту.** Банк-роли зовут `runAttempt` напрямую diff --git a/docs/orchestrator-norms-scratchpad.md b/docs/orchestrator-norms-scratchpad.md index 1d89c3a3..ecac0b6a 100644 --- a/docs/orchestrator-norms-scratchpad.md +++ b/docs/orchestrator-norms-scratchpad.md @@ -298,3 +298,25 @@ UNKNOWN 1 — её, дошёл один раз до правок; десять ⚠ Класс родствен «прибор ответил на свой вопрос»: здесь два прибора отвечают на РАЗНЫЕ вопросы, оба верно, и ошибка — не в них, а в том, какой из них считать судьёй. Судья — тот, которым судит гейт. + +## ⛔ НАИВНЫЙ СКРИПТ ПРОТИВ ОБЩЕГО ПРИБОРА: дважды за смену я принял отказ СВОЕГО замера за дефект ГЕЙТА (17.09) + +**Случай первый — счётчики регистра.** Гейт трижды остановил мой коммит на расхождении литерала. Я четыре +раза подставлял значение (105 · 110 · 111 · 466/468), считая колонку статуса собственным кодом, — и каждый +раз мимо, потому что индекс колонки брал не тот. Верное число прибор ПЕЧАТАЛ в каждой строке вывода: +«против пере-счёта 108». Ответ лежал в выводе, а я вычислял его заново. + +**Случай второй — «щель» в линтере.** Ревизор сообщил, что якорь без корневого каталога после выноса +молча числится внешней ссылкой. Мой скан «подтвердил»: 108 таких якорей. Я собрался чинить гейт — и перед +правкой прогнал ЕГО ЖЕ логику на живых примерах: короткий путь сперва достраивается ШЕСТНАДЦАТЬЮ +префиксами, и лишь если не нашёлся ни под одним, судится первый сегмент. Все 101 «слепых» якоря прибор +находит и проверяет. Не находятся шесть, из них четыре — ссылки в ЧУЖИЕ репозитории (законное молчание), +два — короткие пути в зоне полигона (пинг зоне, не правка гейта). **Дыры не было.** + +⇒ **Прежде чем чинить общий прибор, воспроизведи его отказ ЕГО ЖЕ логикой на конкретном входе.** Свой +скан, написанный за минуту, не равен инструменту, который писался и чинился месяцами: у него есть +префиксы, исключения, дедовщина и знаменатели, которых у наброска нет. ⚠ Родственная формулировка канона: +«прибор ответил на свой вопрос» — здесь приборов ДВА, и мой отвечал на более простой вопрос, чем гейт. + +⚠ И цена ошибки несимметрична: не найденный дефект стоит одной находки, а «починка» исправного гейта +портит инструмент, которому верят все смены — тем более теперь, когда он БЛОКИРУЕТ (D39.260 п.3).