diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 95cd70dc..c24da1e6 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -410,4 +410,4 @@ | 502 | ⚠ **ПОЛОВИНА ЗАКРЫТА 18.09 актом `D39.268`:** имя исчерпания средств провайдера печатается человеку — в последней строке прогона и в записи об ошибке. **ОСТАЁТСЯ ПРОГРАММНАЯ ПОЛОВИНА:** нового кода возврата и нового слова в словаре кадров НЕ добавлено намеренно — словарь есть замороженный шов, и его расширение является решением, а не правкой пака. ⛔ **УПОР В БАЛАНС ПРОВАЙДЕРА НЕ ИМЕЕТ СОБСТВЕННОГО ИСХОДА: ПРОВОД КЛАССИФИЦИРУЕТ ЕГО ВЕРНО, А ОПЕРАТОР ПОЛУЧАЕТ ОБЫЧНОЕ ТЕРМИНАЛЬНОЕ ПАДЕНИЕ СТАДИИ.** ⚠ Разметка честная: **снято ЧТЕНИЕМ КОДА, живого 402 не исполнял** — движок в этом состоянии не гонялся. **Что построено и работает:** `backend/internal/llm/httpllm.go:691`=`func retryableStatus(status int, body []byte) bool` есть ЕДИНСТВЕННЫЙ источник раздела «повторять/терминально» и стережётся дрейф-гардом; 402 терминален по умолчанию (`:698`=`terminal 4xx (incl. 402 Payment Required)`), а 429 с телом об исчерпанных кредитах тоже терминален — узкий набор маркеров `:710`=`insufficient_quota` / `quota_exceeded`, и довод стоит рядом: повтор под удержанной резервацией жжёт только время, потому что «the operator must top up». ⇒ **денег на пустые ретраи мы не тратим, и это важно сказать первым.** ⛔ **Дыра выше провода:** типизированный `HTTPStatusError` (`backend/internal/llm/httpllm.go:717`) не покидает пакет `llm` — 7 упоминаний, ВСЕ в `internal/llm`, в конвейере ноль; ошибка едет наверх обёрнутым текстом (`%w`), а перечислимых причин в `backend/internal` **43**, и **ни одна** не про кредиты провайдера (контроль: строк со словом `reason` — 943; единственный хит шаблона «причина + кредиты» — посторонний комментарий `backend/internal/pipeline/rebill.go:269`). ⇒ «у провайдера кончились деньги» и «стадия упала терминально» на уровне ИСХОДА неразличимы; тело ответа провайдера доезжает до человека только внутри текста обёрнутой ошибки. ⛔ **Почему это гейт готовности, а не гигиена:** предстоят ДВЕ книги по 20 глав ПАРАЛЛЕЛЬНО, и прогон, встретивший 402 на середине, останавливается наполовину купленным; оператору нужно за секунды отличить «пополни счёт» от «движок сломался», а сегодня он это читает из текста ошибки. ⚠ **Событие не гипотетическое:** на английской оси полигона 44 записи об ошибке из 52 — ровно `APIStatusError 402 Insufficient` (ряд **501**), то есть провайдер уже упирался в баланс, и заметили это постфактум, разбирая артефакты. | бэкенд | **скоро (кандидат в блокеры ратификации готовности к эксперименту двух переводов)** | у исчерпания кредитов провайдера есть НАЗВАННЫЙ исход, отличимый от обычного терминального падения стадии, и он виден оператору прогона; пин краснеет, когда тело с 402 превращается в родовую причину | замер оркестратора 18.09 чтением кода; повод — находка `textmachine-b5` о 44 отказах по балансу на английской оси | | 503 | ⛔ **ЗАБОР РЕ-ПИНА ПРОПУСКАЕТ ТОЛЬКО ОДНУ ОСЬ, ПОЭТОМУ КАЖДОЕ СЛЕДУЮЩЕЕ ОБЪЯВЛЕНИЕ ПАРЫ БУДЕТ ПЕРЕПОКУПАТЬ БАЙТ-ИДЕНТИЧНЫЕ КНИГИ.** Движок УЖЕ умеет исход «снапшот двинулся, а байты нет — $0»: это ре-пин (`backend/internal/pipeline/repin.go:61`=`func classifySnapshotMove`). Но забор пускает в него ровно ОДНУ ось — банк памяти; любое иное расхождение полей классифицируется как «прочее» и требует полной перепокупки. ⇒ когда пара объявит опциональный пар-файл (любой из контракта `ABSENT → inert, PRESENT → байты фолдятся`), версия её пакета сдвинется у ВСЕХ её книг, включая те, чей ТЕКСТ не изменился ни на байт — например текстовые книги, для которых новая возможность недостижима по построению. ⚠ **Это ЛОЖНАЯ перепокупка, то есть безопасная сторона ошибки: она видна в проекции согласия и не портит текст.** Но это деньги, и с каждой новой парой их будет больше. ⛔ **Направление лечения названо советчиком и НЕ спроектировано:** расширить забор на «wire-нейтральные оси» — ход, при котором отличаются только оси, чьё действие целиком лежит в отправляемых сообщениях (нарезка · встроенные данные · нормализация · пакет пары), И при этом контент-хеш совпал; вердикт-несущие оси (классификатор · дешёвые гейты · экран цели) остаются «прочим». ⚠ Граница «ось лежит целиком в сообщениях» доказывается ПО КАЖДОЙ оси отдельно — это дизайн, а не правка условия. ⛔ **И почему это НЕ делается перед прогоном:** `repin.go` есть денежно-критичный путь, а ошибка в заборе даёт исход, противоположный нынешнему — тихую НЕ-перепокупку там, где заплатить надо. Прогон идёт с нынешним поведением, цена названа вслух. | бэкенд | скоро (после прогона; до него — ТОЛЬКО назвать цену в акте, не трогать) | забор ре-пина различает wire-нейтральный ход от вердикт-несущего, граница каждой оси доказана отдельно, и пин краснеет на попытке пропустить вердикт-несущую ось как нейтральную | разбор советчика 18.09 при ратификации условного фолда маркера разметки; несущие утверждения пере-сняты оркестратором (контент-гард `backend/internal/pipeline/stagerun.go:92`=`cs.ContentHash == contentHash`; образец — коммит `2f91b04`, 207 строк в нарезчик при нетронутой константе версии) | | 504 | ⛔ **ТАЙМИНГОЗАВИСИМЫЙ ПИН В `internal/llm` КРАСИТ ЧУЖИЕ ЛЕНДИНГИ — ДВА НЕЗАВИСИМЫХ СВИДЕТЕЛЯ ЗА ОДНУ СМЕНУ.** Тест `TestARefusalIsNotAPurchaseEvenWhenTheReplyOutrunsTheWrite` (подтест «тело ещё писалось, когда пришёл отказ») упал у ДВУХ разных сессий, на ДВУХ разных деревьях. ⚠ **ИСПР. ТУТ ЖЕ, ДО ЛЕНДИНГА РЯДА: «ни одна сессия пакет не трогала» — НЕВЕРНО, и поймал это мой же прибор.** Файл упавшего теста (`backend/internal/llm/attemptcut_test.go`) действительно не тронут ни одним коммитом смены (строк диффа 0), но ПАКЕТ тронут: `9defda4` добавил **19 строк** в `backend/internal/llm/httpllm.go` — это работа второй сессии по имени для исчерпания средств провайдера. ⇒ у первого свидетеля дерево пакета не касалось вовсе, у второго — касалось соседнего файла того же пакета, и потому **«дефект не её» НЕ УСТАНОВЛЕНО**: гипотеза о взаимодействии её правки с таймингом остаётся открытой, пока не сойдётся замер частоты в двух деревьях вперемежку. **Первый свидетель 18.09:** батарея `final8` сессии английской пары — текст падения говорит не о предмете, а о своей ПОСЫЛКЕ: «premise: the provider was connected to 3 time(s) … write: connection reset by peer» и «the client came back with a STATUS, so this run never reached the delivery question». **Второй свидетель 18.09:** гейт сессии достижимости пар-данных, причём упал ВТОРОЙ проход цели — тот, что собирает скипы без гонок, — тогда как `build vet fmt lint test` прошли и полнота сверена списком. ⭐ **Конструкция теста ХОРОША и это надо сказать прежде дефекта:** он не позеленел молча, не дойдя до предмета, а объявил вслух, что до вопроса о доставке не дошёл. Именно поэтому его красное есть информация, а не шум. **Дефект в другом:** он чувствителен к нагрузке — локальной HTTP-фикстуре сбрасывают соединение, — и тогда он красит ЧУЖОЙ лендинг, заставляя зону доказывать невиновность. Изолированно зелен 8 из 8 у обеих сессий. ⚠ **Разделить «нагрузка» и «дефект дерева» можно только правильным прибором:** чередующиеся полные проходы в своём дереве и в развёрнутом срезе `HEAD`, счёт частоты с обеих сторон; одинаковая частота = не твоё. Вторая сессия этот замер ведёт, числа придут со сдачей. ⛔ **И родня, из-за которой ряд заводится отдельным:** это ВТОРОЙ такой пин за двое суток (первый — ряд **497**, квадратичность), то есть класс, а не случай: **пин, утверждающий отношение или порядок событий, на общей машине меряет нагрузку вместе с предметом**. | бэкенд | скоро (не блокер прогона: предмет пина к пути прогона не относится, но красит лендинги) | падение различает нагрузку от предмета САМО — либо фикстура перестаёт зависеть от того, кто ещё работает на машине, либо тест объявляет исход «не измерено» вместо красного; предъявить частотой падений в двух деревьях вперемежку, а не одиночным прогоном | два независимых свидетеля 18.09 — батареи `textmachine-main-c5` и `textmachine-main-c8`. ⭐ **ЗАКРЫТО ПО ПРИЧИНЕ 18.09 ЗАМЕРОМ ЗОНЫ, И КОНТРОЛЬ ВЗЯТ ПРАВИЛЬНЫЙ, А НЕ УДОБНЫЙ:** сравнение под РАВНОЙ нагрузкой против дерева `9defda4^`, где правки зоны нет вовсе, дало **2 падения из 30 в контрольном дереве против 0 из 30 в правленом** ⇒ моя гипотеза о взаимодействии её правки с таймингом ОПРОВЕРГНУТА, и опровергнута в сторону, невыгодную опровергающему. **Механизм понят:** тест шлёт 11 МБ тела, которого сервер не читает, отдаёт обрывок статус-строки и спит ФИКСИРОВАННЫЕ 200 мс перед сбросом соединения — под нагрузкой планировщик не успевает прочитать обрывок. Предикат, добавленный паком в тот же пакет, внутри него не зовётся НИ РАЗУ (0 вызовов; единственный вызов идёт из `backend/internal/pipeline/stagerun.go`). ⇒ предмет ряда — **фиксированный сон против планировщика**, а не чья-либо правка. | -| 505 | **РЕГИСТР КЛЮЧА ИСТОЧНИКА СКЛЕИВАЕТ ИМЯ С НАРИЦАТЕЛЬНЫМ — единственная языковая находка переписи 19.09, которая меняет ПЕРЕВОД английской книги холодного прогона.** Ключ источника сворачивает регистр безусловно (`backend/internal/text/norm.go:153`=`b.WriteRune(unicode.ToLower(r))`), совпадения матчер банка считает без учёта регистра ⇒ термин-имя стреляет по нарицательному слову и несёт закон банка туда, где он неверен. Замер переписи на книге прогона: из 260 слов, ≥10 раз встречающихся с заглавной не в начале фразы, у **86** есть строчный двойник ≥3 раз — Blood 66/752 · Dead 155/143 · Grace 33/17 · Faith 24/92 · Patience 54/6 (пере-снято оркестратором грубее, порядок тот же). У caseless-письменностей (zh, ja) явления нет по построению. ⚠ `D39.268` п.7(б) отказался строить Go-гард, потому что обе детерминированные дороги двигают ГЛОБАЛЬНУЮ ось — «перекупка китайской книги»; **в окне пере-кроя эта цена $0**, и довод отпадает (перепись: сдвиг `NormVersion` идёт в снапшот через `memory_version`, это ход ре-пина). Та же нота записала «судьбу коллизионного термина» за ВЛАДЕЛЬЦЕМ — вопрос задан 19.09. Форма решает окно: матчер/нормализатор ⇒ только до прогона; гард на сборке банка ⇒ можно после. Родня — ряд 81 (kana-омографы) | бэкенд | скоро (окно пере-кроя — до холодного прогона) | решение владельца по форме, затем второй пак окна | перепись языковых правил 19.09 (воркфлоу оркестратора), `D39.268` п.7(б) | +| 505 | **РЕГИСТР КЛЮЧА ИСТОЧНИКА СКЛЕИВАЕТ ИМЯ С НАРИЦАТЕЛЬНЫМ — единственная языковая находка переписи 19.09, которая меняет ПЕРЕВОД английской книги холодного прогона.** Ключ источника сворачивает регистр безусловно (`backend/internal/text/norm.go:153`=`b.WriteRune(unicode.ToLower(r))`), совпадения матчер банка считает без учёта регистра ⇒ термин-имя стреляет по нарицательному слову и несёт закон банка туда, где он неверен. Замер переписи на книге прогона: из 260 слов, ≥10 раз встречающихся с заглавной не в начале фразы, у **86** есть строчный двойник ≥3 раз — Blood 66/752 · Dead 155/143 · Grace 33/17 · Faith 24/92 · Patience 54/6 (пере-снято оркестратором грубее, порядок тот же). У caseless-письменностей (zh, ja) явления нет по построению. ⚠ `D39.268` п.7(б) отказался строить Go-гард, потому что обе детерминированные дороги двигают ГЛОБАЛЬНУЮ ось — «перекупка китайской книги»; **в окне пере-кроя эта цена $0**, и довод отпадает (перепись: сдвиг `NormVersion` идёт в снапшот через `memory_version`, это ход ре-пина). Та же нота записала «судьбу коллизионного термина» за ВЛАДЕЛЬЦЕМ. ⚠ **ИСПР. 19.09 (советчик, пере-снято оркестратором по HEAD): посылка «сдвиг `NormVersion` — ход ре-пина» НЕПОЛНА.** `NormVersion` сворачивается ещё и в крой (`backend/internal/pipeline/manifest.go:384`=`Norm: text.NormVersion()`), а ре-пином за $0 классификатор считает ТОЛЬКО сдвиг одного поля `memory_version` (`backend/internal/pipeline/repin.go:61`=`func classifySnapshotMove`) ⇒ правило регистра в ключе источника после окна = пере-крой ВСЕХ книг, и довод `D39.268` п.7(б) в этой форме верен. **Дом — МАТЧЕР** (бамп `backend/internal/membank/memory.go:61`=`const matchVersion`, где уже живёт класс «collision-ambiguous»): ре-пин $0 в любое время. ⛔ И асимметрия «ключ с заглавной требует заглавной» ломает ВТОРОЙ класс двойников — с ОДИНАКОВЫМ переводом: `Silversaint` 93 / `silversaint(s)` 188, `King`/`king`, `Prince`/`prince` — там закон банка нужен; а регистр английского ключа — случайность модели, ключи сливаются по нормализованной форме. ⇒ **рекомендованная форма:** регистр — ПЕР-ТЕРМОВЫЙ атрибут подписи (образец — `allow_short`) с умолчанием, выведенным из книги; совпадения не отбрасываются, а ГРАДУИРУЮТСЯ существующей диспозицией (свидетельство имени в тексте ⇒ CONFIRMED, иначе AMBIGUOUS); на сборке банка — предъявление подписанту счётов и соседей, без порога. Нормализатор не трогать. Для денег окно не нужно, но перевод прогона меняет ⇒ едет ДО прогона. Судьба ТЕРМИНА остаётся за подписью — это и есть владельческий рычаг. Родня — ряд 81 (kana-омографы) | бэкенд | скоро (до холодного прогона — ради качества перевода, не денег) | второй пак окна: матчер + атрибут подписи + предъявление на сборке | перепись языковых правил 19.09 (воркфлоу оркестратора), `D39.268` п.7(б) |