From 3f4680c71a394b9ee39b1374f751b1417af1d29e Mon Sep 17 00:00:00 2001 From: heaven Date: Tue, 8 Sep 2026 12:35:24 +0300 Subject: [PATCH] Make the anchors introduced with these rows verifiable by giving each an expectation token --- docs/BACKEND_ENGINE_KNOWS_SESSION_PROMPT.md | 6 +++--- docs/BACKLOG.md | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/BACKEND_ENGINE_KNOWS_SESSION_PROMPT.md b/docs/BACKEND_ENGINE_KNOWS_SESSION_PROMPT.md index fa08427d..8b73b18a 100644 --- a/docs/BACKEND_ENGINE_KNOWS_SESSION_PROMPT.md +++ b/docs/BACKEND_ENGINE_KNOWS_SESSION_PROMPT.md @@ -6,14 +6,14 @@ владелец на экране подписи банка и оператор, читающий траты, — смотрит в другое место. Это один класс, а не три случая, и он сегодня стоит дороже всего остального в очереди. -- **Полнота банка.** `internal/pipeline/terminologist.go:99-103` считает `Consolidated` · `Declined` · +- **Полнота банка.** `internal/pipeline/terminologist.go:99-103`=`ClassifyBatchesDropped int` считает `Consolidated` · `Declined` · `Unanswered` · `BatchesDropped` · `ClassifyBatchesDropped`; `:582` предупреждает — «this bank is PARTIALLY consolidated — a budget cut a pass». В `Log`. А носитель, который читает платформа и с которого строится экран подписи, — `internal/pipeline/bankexport.go` (`BankExport`: `bank_version`, `book_id`, `total`, `signed`, `as_of`, `run_id`, `terms`, `proposed`) — полей полноты **не несёт вовсе**. Наблюдение платформы за живым потоком **04.09** (H10/H11, строка 199): консолидировано 38, **не спрошено ни разу 47**, срезано бюджетом 5. Владелец подписал бы такой банк как полный. -- **Деньги.** `internal/pipeline/waverun.go:218` складывает в итог волны только `CostUSD` рендер-паса; +- **Деньги.** `internal/pipeline/waverun.go:218`=`res.TotalUSD += r.lastTerminology.CostUSD` складывает в итог волны только `CostUSD` рендер-паса; `ClassifyCostUSD` (`terminologist.go:145`) в `TOTAL (this run)` не попадает. А разложение «за что заплачено» относит классификатор в `superseded`, потому что обе банк-роли адресуются ОДНИМ именем стадии и делят позицию `{глава, чанк, стадия}`. Замер прогона 08.09 по строкам леджера: терминологическая фаза @@ -132,7 +132,7 @@ ClassifyBatchesDropped > 0`. На прогоне 08.09 было `batches_dropped ### 4.2 Два числа о деньгах, не сходящиеся с леджером — **делай РОВНО так** -- **(а) Итог волны занижен.** `internal/pipeline/waverun.go:218` — `res.TotalUSD += r.lastTerminology.CostUSD`, +- **(а) Итог волны занижен.** `internal/pipeline/waverun.go:218`=`res.TotalUSD += r.lastTerminology.CostUSD` — а `CostUSD` по собственному докстрингу — «what THIS run's RENDER phase paid»; классификаторская стоимость лежит отдельным полем `ClassifyCostUSD` (`terminologist.go:145`, заполняется на `:406`) и в `TOTAL (this run)` не попадает вовсе. **Замер прогона 08.09 по строкам леджера:** терминологическая фаза diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 0ae9a7ab..eca8ed78 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -285,10 +285,10 @@ | 351 | **ЛИНТ СЛОВАРЯ КЛАССИФИКАТОРА ПИНИТ ОДНО НАПРАВЛЕНИЕ: слово, УБРАННОЕ из Go, не ловит ничто.** Гейт `TestEveryClassifierPromptSpeaksTheEnginesVocabulary` (лендинг `D39.225`) утверждает «каждое слово из `terminology.Types`/`Genders` названо в промте пары» — и потому сжимается вместе со своим предметом: выкинутый класс `title` оставляет батарею зелёной (замер 07.09 на копии: 19 ok / 0 FAIL). Вырожденный предел закрыт стражем `len(classifierVocabulary()) == 0`, общий случай — нет. Настоящий дом — пин САМОГО словаря, которого в репозитории нет (research/33 Д1: 0 тестов на `terminology.Types`). ⚠ Граница названа зоной самостоятельно, в заказ пака не входила | бэкенд | когда-нибудь | пин словаря (голден или рефлексивный тест) | `D39.225`, research/33 Д1 | | 352 | **ПЯТЬ РУКОПИСНЫХ СПИСКОВ АРМ-КОНФИГОВ ЖИВУТ РЯДОМ С ДВУМЯ ГЕЙТАМИ, КОТОРЫЕ ТЕПЕРЬ ГЛОБЯТ.** `pipeline/promptlabel_test.go:45` · `config/echoregen_shipping_test.go:35` · `config/content_labels_test.go:529` и `:575` · `config/prompt_pack_test.go:239` перечисляют армы поимённо; новый арм автоматически попадает в `TestAnArmIsTheProductionConfigWithADifferentEditor` и `TestSwapArmConfigs` и проходит мимо всех пяти. Найдено приёмкой `D39.225`, вне заказа пака; предсуществующее | бэкенд | когда-нибудь (со следующим касанием армов) | свести пять списков к глобу | `D39.225` | | 353 | **КОНТРАКТ СЕКЦИИ ПРЕДЛОЖЕНИЙ В САЙДКАРЕ БАНКА ЗАПИНЕН ПО ПОЛЯМ И ПОРЯДКУ, НО НЕ ИСЧЕРПЫВАЮЩЕ — строка заведена ПРАВИЛОМ ОСТАНОВКИ, а не находкой.** Три круга приёмки `D39.225` находили в одной новой секции всё более узкие дыры: сперва семь полей без утверждений, потом `Freq` и своп списков, потом ранжирование и класс пустого `Dst`. Каждая была настоящей и предъявлена посадкой; каждый следующий круг стоил больше и покупал меньше. Сегодня запинены: все решающие поля против стоп-строки · порядок против стоп-СТРОК на двух уровнях · рефлективный страж «ни одно поле проекции не нулевое» (ловит поле, добавленное завтра и не заполненное). ⚠ **Дописка 08.09 (приёмка `D39.226`):** у поля `SettledByBank` стоп-строки нет свидетеля в списке «полей, которых сайдкар намеренно НЕ несёт» (`bankexport_test.go`) — то есть его сознательное отсутствие в проекции не утверждено ничем; закрывать вместе с первым читателем секции. ⚠ Границу называет сам гейт: поле, которое отрастил `BankStopRow` и которое СЛЕДОВАЛО бы проецировать, механически не ловится — это суждение того, кто поле добавляет. ⇒ вернуться к исчерпывающему контракту, когда у секции появится ЧИТАТЕЛЬ (платформенная половина строки **224**): пока читателя нет, расхождение ничем не судится, а в день его появления разница станет семантикой задним числом — второй ярус класса A `18-bank-ontology.md` | бэкенд + платформа | когда-нибудь (гейт: появление читателя секции, строка 224) | исчерпывающий пин контракта секции вместе с её первым потребителем | `D39.225` п.10 | -| 354 | ⛔ **ОПЛАЧЕННЫЙ `2xx` С ОБОРВАННЫМ ТЕЛОМ — КЛАСС, СТОИВШИЙ 32 % ЖИВОГО ПРОГОНА, И СТРАНИЦА КВИРКОВ О НЁМ МОЛЧИТ.** Живой прогон 08.09 (10 глав, `committed=$1.259217`): два вызова редактора вернулись `2xx` с нечитаемым JSON и были ОПЛАЧЕНЫ — `$0.407606`, то есть **32 % бюджета прогона**. Движок класс ЗНАЕТ и обрабатывает штатно: `backend/internal/pipeline/disposition.go:79` `FlagDecodeError` — «2xx with an unreadable body — billed, conservatively settled, flagged». ⛔ Но `docs/experiments/00-provider-quirks.md` — страница, которую канон велит читать ПЕРЕД правкой адаптеров и вызовов, — о нём не несёт НИ СТРОКИ: греп по `decode`/`unexpected end of JSON`/«оборван» даёт **0 при 250 строках файла** (контроль: слово `provider` там встречается 3 раза, то есть спрошено существующее). ⇒ следующая сессия, читающая квирки перед платным прогоном, не узнает о поведении, которое съедает треть бюджета. ⛔ **испр. 08.09: МЕХАНИЗМ НАЗВАН ЗДЕСЬ НЕВЕРНО — это не свойство провайдера, а дефект движка, и носитель у него теперь строка 360.** Полигон замером exp-23 п.5 показал, что за подписью «нечитаемое тело» стоит ТАЙМАУТ ЧТЕНИЯ по `attempt_s`, который движок сам же выбрасывает в `_`. ⇒ эта строка остаётся ТОЛЬКО про пробел страницы квирков: она обязана предупреждать, ЧТО оператор увидит и почему, со ссылкой на 360, а не приписывать поведение вендору. ⚠ Зона — ПОЛИГОН (`docs/experiments/` по зонной таблице), живой сессии полигона нет; строка заведена оркестратором как носитель, писать в файл полигону | полигон | скоро (перед следующим платным прогоном любой зоны) | строка в `00-provider-quirks.md` с частотой, стоимостью и тем, что движок делает с классом | живой прогон 08.09, пак «правда оператору и деньги» | +| 354 | ⛔ **ОПЛАЧЕННЫЙ `2xx` С ОБОРВАННЫМ ТЕЛОМ — КЛАСС, СТОИВШИЙ 32 % ЖИВОГО ПРОГОНА, И СТРАНИЦА КВИРКОВ О НЁМ МОЛЧИТ.** Живой прогон 08.09 (10 глав, `committed=$1.259217`): два вызова редактора вернулись `2xx` с нечитаемым JSON и были ОПЛАЧЕНЫ — `$0.407606`, то есть **32 % бюджета прогона**. Движок класс ЗНАЕТ и обрабатывает штатно: `backend/internal/pipeline/disposition.go:79`=`FlagDecodeError FlagReason` `FlagDecodeError` — «2xx with an unreadable body — billed, conservatively settled, flagged». ⛔ Но `docs/experiments/00-provider-quirks.md` — страница, которую канон велит читать ПЕРЕД правкой адаптеров и вызовов, — о нём не несёт НИ СТРОКИ: греп по `decode`/`unexpected end of JSON`/«оборван» даёт **0 при 250 строках файла** (контроль: слово `provider` там встречается 3 раза, то есть спрошено существующее). ⇒ следующая сессия, читающая квирки перед платным прогоном, не узнает о поведении, которое съедает треть бюджета. ⛔ **испр. 08.09: МЕХАНИЗМ НАЗВАН ЗДЕСЬ НЕВЕРНО — это не свойство провайдера, а дефект движка, и носитель у него теперь строка 360.** Полигон замером exp-23 п.5 показал, что за подписью «нечитаемое тело» стоит ТАЙМАУТ ЧТЕНИЯ по `attempt_s`, который движок сам же выбрасывает в `_`. ⇒ эта строка остаётся ТОЛЬКО про пробел страницы квирков: она обязана предупреждать, ЧТО оператор увидит и почему, со ссылкой на 360, а не приписывать поведение вендору. ⚠ Зона — ПОЛИГОН (`docs/experiments/` по зонной таблице), живой сессии полигона нет; строка заведена оркестратором как носитель, писать в файл полигону | полигон | скоро (перед следующим платным прогоном любой зоны) | строка в `00-provider-quirks.md` с частотой, стоимостью и тем, что движок делает с классом | живой прогон 08.09, пак «правда оператору и деньги» | | 355 | **РАЗЛОЖЕНИЕ «ЗА ЧТО ЗАПЛАЧЕНО» ОТНОСИТ КЛАССИФИКАТОР В `superseded`, ПОТОМУ ЧТО БЭНК-РОЛИ ДЕЛЯТ ОДНУ ПОЗИЦИЮ.** Бэнк-роли адресуются синтетическим чанком (глава 0, `chunk_idx` = номер батча), а разложение ключуется `{глава, чанк, стадия}` (`backend/internal/pipeline/paidtail.go:120-123`: `p := pos{u.Chapter, u.ChunkIdx, u.Stage}`, затем `case lastPaid[p] != i` → `SupersededUSD`). Классификаторский батч 0 и терминологический батч 0 занимают ОДНУ позицию `{0,0,terminology}` ⇒ более ранний уезжает в «superseded», то есть в «купил ничего». Улика живого прогона 08.09 — три строки леджера (`0 0 terminology classifier $0.009013` · `0 0 terminology terminologist $0.002775` · `0 1 terminology terminologist $0.001820`) при печатном «the book's TERMINOLOGY $0.004595 (2)», где два — ровно последние. ⚠ На стенде цена копеечная, на боевом `classify_budget_usd: 1.00` доля может быть большой: оператор читает, что классификатор куплен зря, тогда как он куплен и использован. Найдено зонной сессией на прогоне, ею НЕ чинилось — вне её заказа | бэкенд | скоро (до следующего платного прогона, где смотрят разложение) | разведение позиций бэнк-ролей в ключе разложения + пин на три строки леджера | живой прогон 08.09 | | 356 | **КНИГА У СВОЕГО ПОЖИЗНЕННОГО ПОТОЛКА РОЛИ НАВСЕГДА ОСТАЁТСЯ БЕЗ КОНСОЛИДИРОВАННОГО БАНКА, и ни одно сообщение не говорит именно этого.** `store/ledger.go` `RoleSpentUSD` суммирует чекпойнты роли за ВСЮ жизнь книги, предполётный срез сеет `probe := spent` и обрывается на `probe+want > budgetUSD`; при `fits == 0` цикл попыток не запускается вовсе. ⚠ Оператор ПРИ ЭТОМ извещён — `terminologist.go:936` печатает `batches_planned=N batches_running=0 batches_dropped=N spent_usd budget_usd` ДО первого вызова (сверено оркестратором 08.09), поэтому «тихо» здесь неверно, и находка приёмки в этой части ОТКЛОНЕНА доводом зоны. Верен остаток: сообщение говорит про ЭТОТ прогон, а не про то, что книга исчерпала пожизненный бюджет роли и консолидации не получит НИКОГДА, сколько её ни перезапускай. Свойство `budget_usd`, а не фильтра пака «правда оператору и деньги»: тот на решение бюджета не влияет. ⇒ решить, что показывать оператору и владельцу: отдельное сообщение «книга у пожизненного потолка роли», подъём потолка как явный акт, или сознательное молчание с записью | бэкенд | когда-нибудь (гейт: первая книга, упёршаяся в пожизненный потолок роли) | сообщение об исчерпании пожизненного бюджета либо ратифицированное молчание | приёмка `D39.226`, линза «вне карты» | | 357 | **ПОМЕТКА «БАНК УЖЕ РЕНДЕРИТ ЭТУ ПОВЕРХНОСТЬ» ЕСТЬ ТОЛЬКО В ОДНОЙ ИЗ ТРЁХ ПОВЕРХНОСТЕЙ ПОДПИСИ — а первой владелец читает НЕ её.** Пак `D39.226` довёл пометку `NOT ASKED(the bank already renders this surface and every draft agreed — nothing to decide)` до ТЕКСТОВОГО сайдкара стоп-таблицы, и там она предъявлена исполнением. Остаются две: **(а)** экран стопа на stdout (`cmd/tmctl/render.go`, то, что печатает `renderSignatureStop` первым) о пропущенном кандидате не знает — 0 ссылок на признак, и приёмка предъявила контролем, что этот же рендерер УМЕЕТ печатать пер-строчные пометки (`invented`): пропущенная строка и неотвеченная выглядят одинаково (`方源 — … —` против `花家 — … —`). Это НЕ ложь о решении (решать там действительно нечего), это отсутствующее объяснение на экране, который читают первым. **(б)** машинный сайдкар `BankExportProposal` факта не несёт — но это ОБЪЯВЛЕНО (`mining.go:412`) и упирается в строку **353**, чью секцию заказ пака трогать запрещал. ⇒ (а) — дофикс одной пометки в рендерере; (б) — вместе с первым читателем секции, то есть со строкой 224 | бэкенд | скоро (до первой подписи банка внешним пользователем) | пометка в stdout-рендерере + решение по (б) вместе со строкой 353 | приёмка `D39.226`, линза «вне карты» | -| 358 | **ИТОГ ТРАТ ПРОГОНА ЗАНИЖЕН: `TOTAL (this run)` не включает стоимость классификатора.** `backend/internal/pipeline/waverun.go:218` — ⚠ **испр. 08.09: прежняя редакция несла числа `$0.007280`/`$0.005460` — они АВГУСТОВСКИЕ, из соседнего файла, к этому прогону отношения не имеют (моя ошибка при заведении ряда).** Замер прогона 08.09 по строкам леджера: терминологическая фаза стоила `$0.013608` = классификатор `$0.009013` + рендер-батчи `$0.002775` и `$0.001820`; в итог волны входит только рендерная сумма `$0.004595`, то есть две трети денег фазы до итога не доезжают. Предсуществующее, паком `D39.226` не вносилось и им не чинилось. ⚠ Класс тот же, что у строки 355: оператор читает о СВОИХ деньгах число, которое не сходится с леджером, и не может этого заметить без второго чтения. ⇒ либо включить, либо назвать в самой строке, что классификатор считается отдельно | бэкенд | скоро (до следующего платного прогона, где смотрят итог) | итог сходится с леджером либо объявляет своё исключение + пин | приёмка `D39.226`, живой прогон 08.09 | +| 358 | **ИТОГ ТРАТ ПРОГОНА ЗАНИЖЕН: `TOTAL (this run)` не включает стоимость классификатора.** `backend/internal/pipeline/waverun.go:218`=`res.TotalUSD += r.lastTerminology.CostUSD` — ⚠ **испр. 08.09: прежняя редакция несла числа `$0.007280`/`$0.005460` — они АВГУСТОВСКИЕ, из соседнего файла, к этому прогону отношения не имеют (моя ошибка при заведении ряда).** Замер прогона 08.09 по строкам леджера: терминологическая фаза стоила `$0.013608` = классификатор `$0.009013` + рендер-батчи `$0.002775` и `$0.001820`; в итог волны входит только рендерная сумма `$0.004595`, то есть две трети денег фазы до итога не доезжают. Предсуществующее, паком `D39.226` не вносилось и им не чинилось. ⚠ Класс тот же, что у строки 355: оператор читает о СВОИХ деньгах число, которое не сходится с леджером, и не может этого заметить без второго чтения. ⇒ либо включить, либо назвать в самой строке, что классификатор считается отдельно | бэкенд | скоро (до следующего платного прогона, где смотрят итог) | итог сходится с леджером либо объявляет своё исключение + пин | приёмка `D39.226`, живой прогон 08.09 | | 359 | **ТРИ ЯКОРЯ В ЧУЖИХ ЗОНАХ ПРОТУХЛИ ОТ ДВУХ ДВИЖКОВЫХ ЛЕНДИНГОВ — адреса уже найдены, править владельцам зон.** Линтер `counts.py --lint` даёт 6 проблемных якорей при 121 живом доке; три из них — фронтовые и пингнуты в `frontend/docs/frontend-PROGRESS.md` (зона ЗАМОРОЖЕНА). Остальные три, с новыми адресами, снятыми оркестратором 08.09: **(а) полигон** — `docs/experiments/23-editor-tier.md:7739` и `:7740` ссылаются на «×2 дешевле glm» в `configs/pipeline-c1.yaml:95` и `pipeline-arm-glm.yaml:6`; ⚠ цель ИСЧЕЗЛА, а не сдвинулась (греп даёт 0): долларовые числа сняты из шапки арма приёмкой `D39.226`, потому что стояли на опровергнутом ценовом базисе — значит якорь надо не пере-навести, а пере-написать по факту. **(б) бэкенд** — `backend/docs/DISCLOSURE_LAW_DESIGN.md:40` ссылается на `internal/pipeline/quality.go:233`, токен уехал на `:235`. ⚠ Оркестратор в чужие зоны не пишет; строка — носитель, чтобы адреса не искали заново | полигон + бэкенд | когда-нибудь (попутно с любым касанием этих файлов) | пере-наведённый якорь у бэкенда, пере-написанный по факту — у полигона | `counts.py --lint` 08.09 | | 360 | ⛔⛔ **ТАЙМАУТ ЧТЕНИЯ МАСКИРУЕТСЯ ПОД НЕЧИТАЕМОЕ ТЕЛО, РЕТРАИТСЯ И БИЛЛИТСЯ ПО ОЦЕНКЕ — 23–34 % ЦЕНЫ РЕДАКТОРСКОГО ПРОГОНА, И НОСИТЕЛЯ У КЛАССА НЕ БЫЛО.** Механизм доказан кодом: `backend/internal/llm/httpllm.go` в чтении ответа делает `data, _ := io.ReadAll(io.LimitReader(...))` — **ошибка чтения выбрасывается**, а обрыв распознаётся по РАЗМЕРУ (читают на байт дальше лимита), поэтому тело, оборванное дедлайном `attempt_s` посреди генерации, от целого НЕ ОТЛИЧИМО. Обрезок идёт в `json.Unmarshal` → `unexpected end of JSON input` → `BilledDecodeError` («2xx body decode failed (call IS billed)»), и возвращается он с флагом retryable **`true`** ⇒ вызов повторяется до `max_attempts`, и КАЖДАЯ попытка сеттлится ПО ОЦЕНКЕ. ⛔ Замер полигона (`docs/experiments/23-editor-tier.md`, п.5 «ЧЕТЫРЕ ТАЙМАУТА»): латентности `480390 / 480505 / 480577 / 480528` мс = **ровно 2 × `attempt_s: 240`**, класс назван `decode_error`, цена — **$0.475893 = 23.8 % руки строками, $0.689215 = 34.5 % юнитами**; полигон квалифицировал это как дефект ДВИЖКА и пингнул оркестратору («не наша зона»). ⚠ **Носителя не было: греп `attempt_s` и «таймаут чтения» по живому бэклогу — 0 хитов при 255 рядах** (контроль: `decode` — 3 хита), то есть спрошено существующее. ⛔ **И это ТА ЖЕ физика, что строка 331**, где вызов, оборванный потолком, уезжает в леджер НУЛЁМ: один предмет — «вызов оборвали МЫ, ответа нет» — движок сегодня закрывает ПРОТИВОПОЛОЖНО, оценкой в одном случае и нулём в другом. ⚠ **Граница честности:** что два вызова прогона 08.09 ($0.407606 = 32 % бюджета, «unexpected end of JSON input») были именно таймаутами — ВЫВЕДЕНО по совпадению подписи с замером полигона, а НЕ измерено: сырого леджера того прогона в дереве нет (`.db` новее 07.09 — 0 при 163). ⇒ инженерная половина (не выбрасывать ошибку чтения, отличать таймаут от нечитаемого тела, не ретраить вызов, оборванный СВОИМ дедлайном) — заказ без развилок; ДЕНЕЖНАЯ половина требует слова владельца: платит ли пользователь за вызов, который оборвали мы, и если да — по оценке или по нулю. Пока ответа нет, платформа биллит `committed_usd`, куда оценка уже попала | бэкенд | блокер-очереди для ПЛАТНЫХ прогонов (класс съедает четверть-треть бюджета; $0-работа им не блокируется) | ошибка чтения не выбрасывается · таймаут отличён от нечитаемого тела и НЕ ретраится · денежная диспозиция ратифицирована владельцем · строка 331 закрыта тем же решением | замер полигона exp-23 п.5 + разбор Fable 5 08.09, пере-проверено оркестратором по коду |