diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index c1f8b681..3c25fb4a 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). Здесь только долг и его диспозиции. -> - **СЧЁТ ОЧЕРЕДИ на 08.09 (скриптом по таблице — `python3 docs/scripts/counts.py`; обновлять при каждом лендинге):** всего **270** строк · зона бэкенд **132** строго / **179** широко (⚠ колонки счётчик читает С КОНЦА — испр. D39.116) · **блокеров очереди 2** (236 закрыта D39.175), платные прогоны разблокированы · «скоро» **123** (перечень — грепом по таблице, рукописный список снят D39.126) · гейт-строка эксп-22: 153 (плюс 150 — руки владельца; ⚠ 55 из гейтов СНЯТА — D39.198 п.5); строка 5 — остаток гейчен ре-пробой 74, носитель события теперь строка 188 (аудит D39.140); остальное «когда-нибудь». ⚠ Счёт — НИЖНЯЯ граница долга, не потолок (разбор — легенда таблицы ниже). +> - **СЧЁТ ОЧЕРЕДИ на 10.09 (скриптом по таблице — `python3 docs/scripts/counts.py`; обновлять при каждом лендинге):** всего **273** строк · зона бэкенд **135** строго / **182** широко (⚠ колонки счётчик читает С КОНЦА — испр. D39.116) · **блокеров очереди 2** (236 закрыта D39.175), платные прогоны разблокированы · «скоро» **125** (перечень — грепом по таблице, рукописный список снят 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; правки — только через оркестратора) @@ -306,3 +306,6 @@ | 375 | **ВЫГРУЗКА ЧИТАЕТ ОТМЕНЁННУЮ ПОЗИЦИЮ КАК ВЫПАВШИЙ КУСОК ТЕКСТА.** `memberDrops` считает строку `cancelled` выпавшим членом редакторской единицы — то есть выносит КОНТЕНТ-вердикт по факту нажатия кнопки: читателю сообщается, что из единицы пропал текст, тогда как пропала лишь незаконченная работа. Семантика экспорта, не транспорт. ⚠ Исчезает само вместе с классом `cancelled`, если денежная половина строки **360** будет урезана. Найдено критиком полноты седьмого круга; диспозиция оркестратора жила письмом и носителя не имела до `D39.231` | бэкенд | скоро | отменённая позиция отличима от выпавшего контента | седьмой круг ревью пака 360, 08.09 | | 376 | **ДОСТАВЛЕННЫЙ ОБРЫВ ДО ЗАГОЛОВКОВ КНИЖИТСЯ НУЛЁМ — и мы НЕ ЗНАЕМ, у скольких провайдеров это срабатывает.** Деньги за оборванный нами вызов считаются по `answered && afterHeaders` (`backend/internal/llm/attemptcut.go`), то есть единственный оплачиваемый обрыв — пришедший через ошибку ЧТЕНИЯ ТЕЛА. Обрыв до первого байта ответа (наш дедлайн, стоп родителя, разрыв сокета) даёт **$0** и не виден даже в оценочной паре (`paidtail.go` пропускает `CostUSD <= 0`). ⚠ **Сужение СОЗНАТЕЛЬНОЕ и ратифицировано** (`D39.230`, разведение предикатов): довод замером — 3 из 25 отменённых в полёте сеттлили оценку за запрос, которого не видел ни один хендлер, то есть переплата ЧИТАТЕЛЯ, запрещённая `D39.196` п.2а. ⛔ **Открыт РАЗМЕР остатка, и он неизвестен по построению:** провайдеров **8** (замер по `configs/models.yaml`), ранний `200` задокументирован ровно у ОДНОГО (DeepSeek), а про поведение заголовков у остальных СЕМИ в репозитории не сказано НИЧЕГО — греп класса по странице квирков даёт 0 при 250 строках файла. ⇒ верное утверждение не «сломано у семи», а **«неизвестно для семи»**: если заголовки у них приходят рано, путь почти не срабатывает; если поздно — головной класс пака у них не закрыт вовсе. ⚠ Замерить без платных вызовов нельзя, и постфактум из телеметрии тоже: движок не пишет время до первого байта успешных вызовов. ⇒ первый шаг дешёвый и $0 — начать писать это время | бэкенд | скоро | время до первого байта пишется; размер остатка назван числом, а не «неизвестно» | приёмка пака «вызов, который оборвали мы» 10.09 | | 377 | **ОПЛАЧЕННЫЙ ОБРЫВ, ЗА КОТОРЫМ РЕТРАЙ УСПЕЛ, ИСЧЕЗАЕТ БЕССЛЕДНО: $0 и ни одного следа.** `backend/internal/llm/httpllm.go` — на успешном ретрае `if err == nil { return resp, nil }`, и накопленный `owedCut` ВЫБРАСЫВАЕТСЯ. Замер приёмки: сервер спрошен ДВАЖДЫ (первый вызов доставлен, подтверждён и оборван), вызывающий получает чистый успех и о первом не узнаёт ничего — ни строки леджера, ни оценки, ни пометки. ⚠ Это ровно предмет пака (платим дважды за одну генерацию), но пак его НЕ заказывал: §4.2 требовал деньги «на исчерпании», а здесь исчерпания нет — ретрай удался. Ни одна из девяти фикстур этого пути не покрывает: `TestABrokenConnectionAfterDeliveryIsRetriedOnceAndOnlyOnce` роняет ОБЕ попытки | бэкенд | скоро | оплаченный обрыв виден в леджере независимо от исхода ретрая | приёмка пака «вызов, который оборвали мы» 10.09 | +| 378 | **ПИН «НЕ-2XX — НЕ ПОКУПКА» ЛОВИТ МУТАНТА НЕ ТЕМ АССЕРТОМ: денежные проверки в нём не исполняются ни разу.** Каталожная мутация `CUTCALL-a-non-2xx-becomes-a-purchase` (батарейная, цель — `backend/internal/llm/httpllm.go`, ветвь статуса `if resp.StatusCode < 200 || resp.StatusCode >= 300` → `if false`). Пере-снято ОРКЕСТРАТОРОМ 10.09 на копии: тест краснеет, но ТЕКСТОМ `cutcall_test.go:584: a provider failing every attempt must surface as an infra failure` — ассертом об исходе ПРОГОНА. `t.Fatal` останавливает тест ДО трёх проверок, ради которых пин написан (`committed`, `reserved`, `checkpoints`), и они не исполняются. Под мутацией движок сеттлит **$0.001056 как ОЦЕНКУ за 500-ку** — ровно ту утечку, которую доккоммент теста называет, — и текст падения об этом молчит. ⚠ Класс ратифицирован (`D39.217` п.2в, `D39.232` п.6в) — здесь открыт НОСИТЕЛЬ | бэкенд | скоро | мутант краснеет ассертом О ДЕНЬГАХ: посылка «прогон упал» проверяется так, чтобы не глотать денежные проверки | приёмка пака «вызов, который оборвали мы» 10.09 (С8) + пере-замер оркестратора | +| 379 | **ДВА ПИНА ПАКА НЕ УТВЕРЖДАЮТ СВОЕЙ ПОСЫЛКИ — названный сценарий может не состояться, и они этого не скажут.** **(а)** `backend/internal/pipeline/cutcall_test.go:402` — две строки таблицы девяти («after 2xx · cancelled by the operator» и «before headers · cancelled by the operator») разводятся ОКНОМ СТЕННЫХ ЧАСОВ: `<-srv.arrived` + `time.Sleep(50 * time.Millisecond)` против `attempt_s: 1`. По какую сторону границы заголовков сел `cancel()`, не утверждается ничем. Замер приёмки на мутанте: **2 красных из 8** — отмена обгоняла разбор заголовков, и строка сходилась «ноль к нулю» (`D39.232` п.6а). **(б)** `backend/internal/llm/attemptcut_test.go` — `TestARefusalIsNotAPurchaseEvenWhenTheReplyOutrunsTheWrite` утверждает только `err != nil` и `!errors.As(err, &cut)`; что ответ ДЕЙСТВИТЕЛЬНО обогнал запись (шестимегабайтное тело не дописалось, `WroteRequest` не сработал), не проверяет ничто — тест зелен и на сценарии, где запись успела, то есть на другом предмете | бэкенд | скоро | каждый из двух пинов ПЕЧАТАЕТ величину, доказывающую пройденную границу, и падает, когда сценарий не состоялся | приёмка пака «вызов, который оборвали мы» 10.09 (С5, С6) | +| 380 | **У ДЕНЕЖНОГО УТВЕРЖДЕНИЯ «ПЕРЕ-ДЕЛКА НЕ УДВАИВАЕТ БЮДЖЕТ» ОДИН НОСИТЕЛЬ, А НЕ ПЯТЬ.** Колонка `wantSameBudget` стоит в четырёх строках таблицы девяти (`backend/internal/pipeline/cutcall_test.go:333,340,355,361`), но осей не разводит: обжог случается ВНУТРИ `runAttempt`, а `maxTokens` считается в `runStage` ДО вызова, на итерации `attempt == 0`, где номер попытки и число удвоений равны по причине, к предмету отношения не имеющей. Пере-снято ОРКЕСТРАТОРОМ 10.09 на копии под каталожной мутацией `CUTCALL-the-redo-doubles-the-budget` (`maxTokensForAttempt(baseMaxTokens, escalations)` → `..., attempt`): таблица девяти — **0 красных из 8** (контроль: прибор запустил 10 подтестов, то есть спрашивал существующее), весь пакет `internal/pipeline` — ровно **ОДИН** красный тест, `TestABurnFollowedByARegenerationDoesNotOverBuy` (`cutcall_test.go:1435`). ⚠ Сам носитель ПОЧИНЕН паком (посылку теперь утверждает: «run 1 must leave exactly ONE checkpoint», «…is three checkpoints»), поэтому дыры сегодня нет — есть ЕДИНСТВЕННОСТЬ | бэкенд | когда-нибудь | утверждение держат ≥2 независимых носителя ЛИБО колонка `wantSameBudget` снята как ничего не меряющая, и это записано | приёмка пака «вызов, который оборвали мы» 10.09 (С3) + пере-замер оркестратора | diff --git a/docs/architecture/05-decisions-log.md b/docs/architecture/05-decisions-log.md index 52173137..7da5f6b9 100644 --- a/docs/architecture/05-decisions-log.md +++ b/docs/architecture/05-decisions-log.md @@ -5,6 +5,7 @@ > ⚠ **Эррата 15.08 (D39.132):** D39.131 п.2(а) перечисляет полосу отказов «10 конфиг · 11 источник · 12 лок · 19 безымянный» — читать через D39.132 п.2(г): полоса ДОПОЛНЕНА **exit 13 = `schema_mismatch`** (~~обе зоны сошлись на числе независимо~~; ратифицирован направлением, ФИНАЛИЗИРОВАН приёмкой D39.134). > ⚠ **Эррата 15.08-б (D39.134 п.2а, вписана по аудиту корпуса):** клейм «зоны сошлись на 13 НЕЗАВИСИМО» (строкой выше и в D39.132 п.2г) СНЯТ — платформа прочитала число из незакоммиченного дерева движка и сама это записала (`platform/internal/ingest/exit.go:63-64`); на силу ратификации не влияет. > ⚠ **Эррата 15.08-в (D39.136 п.5):** форма контракт-ревью «фазный воркфлоу оркестратора» ОТМЕНЕНА словом владельца тем же днём — исполняет ОТДЕЛЬНАЯ СЕССИЯ со своим онбордингом (норма D39.120 п.2; оркестратор — автор части ратификаций 0.2.3, author≠reviewer); запущенная воркфлоу-фаза 1 остановлена, её результаты выброшены; промт — `docs/CONTRACT_REVIEW_SESSION_PROMPT.md`. +> ⚠ **Эррата 10.09-а (`D39.232` п.9) — ПЕРЕЧЕНЬ ОСТАТКОВ НЕПОЛОН: их не четыре, а семь.** Пункт 9 писал «остатки заведены строками **374**–**377**» — это было верно в момент акта и перестало быть в тот же день: три находки приёмки, не вошедшие в круг, получили строки **378** (пин «не-2xx — не покупка» ловит мутанта ассертом об исходе прогона, денежные проверки не исполняются), **379** (два пина не утверждают своей посылки) и **380** (у денежного утверждения про бюджет пере-делки один носитель, а не пять). Две из трёх пере-сняты моим прибором на копии — с контрольной величиной рядом с нулём. Тело ноты не переписывается (D23.3); читать п.9 как **374**–**380**. > ⛔⛔ **Эррата 08.09-д (`D39.229` п.2) — «ЧИСЛА ПЕРЕ-СНЯТЫ МНОЮ» ДОКАЗЫВАЛИ ПОЧТИ НИЧЕГО О ПРЕДМЕТЕ ПАКА, И ЭТО КЛАСС `D39.202`, ПРИМЕНЁННЫЙ К САМОЙ БАТАРЕЕ.** Пункт 2 записывает: «`go test ./... -count=1` → 20 пакетов `ok` · 0 FAIL · EXIT=0». Ровно эту картинку печатает машина БЕЗ Postgres, потому что `go test` без `-v` скипы не показывает вовсе. Замер в моём окружении: `TM_PLATFORM_TEST_DSN` пуст, прогон даёт **431 скип**, и среди них — **все десять тестов `internal/books/limit_test.go`**, то есть весь пин ЦЕНТРАЛЬНОГО механизма пака. `go test ./internal/books/ -run TestTheHostRunsNoMoreCutsAtOnceThanItsCapAllows -v` в том же окружении печатает `SKIP` и следом `PASS`. ⇒ мой приёмочный прогон потолка не касался ни разу. **Нашёл охотник вне карты, и доказал не рассуждением, а двусторонней посадкой:** ослабленный семафор (`NewWeighted(limit+3)`) без DSN даёт `ok 0.009s`, с DSN — `FAIL: TestTheHostRunsNoMoreCutsAtOnceThanItsCapAllows`. ⭐ **Пробел закрыт исполнением, а не оговоркой:** я подняла стенд (сокет `/tmp/.s.PGSQL.5432`) и прогнала оба флагманских теста с живой базой — **PASS, 0.81 с и 2.45 с**, а затем и всю платформенную батарею со стендом: **20 пакетов `ok` · 0 FAIL · скипов 9 вместо 431** (остаток гейтится движковым бинарём и `pg_restore`) — число независимо совпало со счётом слепого верификатора. Механизм ЗАПИНЕН по-настоящему; неверен был мой прибор, а не работа зоны. ⇒ **НОРМА: приёмочный прогон печатает счёт СКИПОВ рядом с числом `ok`, иначе «пере-снято мною» и «пере-снято на четырёхстах невыполненных тестах» неразличимы.** Гейт зоны (`make check`) скипы печатает и пустой лог объявляет красным — а голый `go test`, которым мерила приёмка, слеп по построению. ⚠ И `D39.228` п.2 (движковая батарея) той же поправке НЕ подлежит: там гейтов хоста нет, скипов 4 и они названы. > ⚠⚠ **Эррата 08.09-г (`D39.228` п.1 и п.3) — ТРИ ОШИБКИ ОДНОГО АКТА, НАЙДЕНЫ КРОСС-МОДЕЛЬНЫМ РЕВЬЮ РАТИФИКАЦИЙ, которое норма велела провести ДО ратификации, а я провела ПОСЛЕ.** **(а) ⛔ ЦЕНА БАМПА НАЗВАНА ДЕНЬГАМИ, КОТОРЫХ ТАМ НЕТ.** Пункт 3 пишет, что бамп `manifestVersion` «выбрасывает каждый сохранённый сайдкар и ПЕРЕКУПАЕТ каждую книгу». Код говорит другое и говорит это четырежды (`manifest.go:78`, `:99`, `:118`, `:729`): «discard every stored sidecar and **re-cut** / **re-chunk** every book». Ре-чанк — процессор, не деньги: единственный денежный читатель манифеста (`rebill.go`) на нулевом манифесте не объявляет ряд перепокупкой, а делает дорогую сверку контент-хешей и при совпадении продолжает как `$0 resume`. Настоящая цена бампа — пере-нарезка на каждом чтении, одна дорогая проверка в гейте согласия и **релиз платформы В НОГУ** (`KnownManifestVersion`), и последний довод СИЛЬНЕЕ денежного, но в ноте не назван. ⇒ **правило пункта 3 остаётся в силе, довод под ним заменён:** запрет бампа держится на двузонном релизе, а не на перепокупке. ⚠ Следующий, кто станет взвешивать бамп, взвесил бы не ту цену. **(б) ⛔ СТРОКА 312 ЗАКРЫТА НАПОЛОВИНУ И УДАЛЕНА ЦЕЛИКОМ — тот же класс, что 253, в том же акте решённый ПРОТИВОПОЛОЖНО.** Живым остатком 312 был Д-3 — «увидит ли ноту ЧЕЛОВЕК»; после лендинга поле `TOCUnreadable` пишется в манифест и не читается никем: **0 хитов `toc_unreadable` в `platform/` при 200 `.go`**. Факт переехал из лога в файл, которого никто не открывает, и для пользователя не изменилось ничего. Строка ВОЗВРАЩЕНА суженной до платформенной половины; класс A для этого поля объявлен там же (акт объявлял его только для `consolidation`). **(в) ⛔ ЭРРАТА 08.09-в ЗАКРЫЛА 1 НОСИТЕЛЬ ИЗ 6.** «253 закрыта» стояло ещё в `05-decisions-index.md`, `docs/README.md`, CURRENT-STATE `docs/PROGRESS.md` и баннере архивного промта — а следующая сессия онбордится с CURRENT-STATE, а не с шапки журнала. Приведены к правде вместе с 312. ⚠ Это МОЯ ЖЕ норма «класс закрыт только со ЗНАМЕНАТЕЛЕМ N из M», применённая к моей же эррате и не исполненная. **(г) Атрибуция коммита.** `f1404f0` (акт ПЛАТФОРМЫ) унёс закрытие ДВИЖКОВЫХ рядов 312/355/357/358 и сужение 253; движковый акт `e9a0f78` бэклога не трогал вовсе. Историю не переписываю — читателю `git log -- docs/BACKLOG.md` объясняет эта строка. > ⚠ **Эррата 08.09-в (`D39.228` п.1) — АКТ ОБЪЯВИЛ ЗАКРЫТОЙ СТРОКУ, У КОТОРОЙ ЗАКРЫТА ПОЛОВИНА.** Пункт 1 писал «закрыты строки 253 · 355 · 357 · 358». Для 253 это неверно: её собственное тело называет лечением (б) «полнота консолидации в сайдкар **+ поле на `BankPage`**», и второй половины нет — у секции `consolidation` ноль читателей, класс A заведён сознательно. ⇒ строка СУЖЕНА до платформенной половины, не удалена; 355 · 357 · 358 · 312 закрыты и удалены. ⛔ **Класс `D39.167` п.1 в чистом виде, и поймала его та же норма, что его называет:** «каждое утверждение акта о ряде сверяется его СТАТУСОМ в тот же заход» — сверка сделана через час после акта, а не до, и это разрыв в МОЁМ порядке: ратификация и сверка носителей обязаны идти ОДНИМ движением, иначе акт живёт с переоценкой ровно столько, сколько я собираюсь до неё дойти.