diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index b524b241..ed1541c9 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). Здесь только долг и его диспозиции. -> - **СЧЁТ ОЧЕРЕДИ на 10.09 (скриптом по таблице — `python3 docs/scripts/counts.py`; обновлять при каждом лендинге):** всего **268** строк · зона бэкенд **130** строго / **177** широко (⚠ колонки счётчик читает С КОНЦА — испр. D39.116) · **блокеров очереди 0** (236 закрыта D39.175; **371** — актом D39.233, **360** — актом D39.232, обе 10.09), платные прогоны разблокированы · «скоро» **124** (перечень — грепом по таблице, рукописный список снят D39.126) · гейт-строка эксп-22: 153 (плюс 150 — руки владельца; ⚠ 55 из гейтов СНЯТА — D39.198 п.5); строка 5 — остаток гейчен ре-пробой 74, носитель события теперь строка 188 (аудит D39.140); остальное «когда-нибудь». ⚠ Счёт — НИЖНЯЯ граница долга, не потолок (разбор — легенда таблицы ниже). +> - **СЧЁТ ОЧЕРЕДИ на 10.09 (скриптом по таблице — `python3 docs/scripts/counts.py`; обновлять при каждом лендинге):** всего **269** строк · зона бэкенд **131** строго / **178** широко (⚠ колонки счётчик читает С КОНЦА — испр. D39.116) · **блокеров очереди 0** (236 закрыта D39.175; **371** — актом D39.233, **360** — актом D39.232, обе 10.09), платные прогоны разблокированы · «скоро» **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; правки — только через оркестратора) @@ -304,3 +304,4 @@ | 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) + пере-замер оркестратора | | 381 | ⛔ **ДВУХ ОСТАНОВОК НЕТ: у человека одна кнопка, и она ЖЁСТКАЯ.** Слово владельца 09–10.09 («должно быть два стопа, один жёсткий… и второй мягкий, который не обрывает всё сразу, чтоб деньги зазря не списывались») носителя не получило — ни ноты, ни строки, ни заказа в паке; жило перепиской и умерло вместе с сессией (тот же класс, что `D39.231` п.1). **Замерено 10.09 оркестратором и подтверждено движковой зоной:** единственная регистрация сигнала — `backend/cmd/tmctl/main.go:207` `signal.NotifyContext`, она отменяет контекст прогона ⇒ вызовы в полёте рвутся, доставленные книжатся по оценке и метятся `cancelled`. **Мягкий ГЛАГОЛ уже построен** — `latched` (`backend/internal/pipeline/waverun.go:353-363`, дословно «a different verb from cancel()»: фидер перестаёт раздавать индексы, начатое доигрывается под неотменённым контекстом), но дёргает его только денежный потолок (строка 277), и он ЛОКАЛЕН для одного `runWave` (живых вызовов два: `waverun.go:140`, `:285`) ⇒ кнопке нужен ПРОГОННЫЙ источник, который волна консультирует. **Что ещё названо замером зоны:** (а) мягкий стоп контекст не отменяет ⇒ `errors.Is(err, context.Canceled)` ложно и `exitCode` (`cmd/tmctl/main.go:117`) отдаёт 0/2 — «я остановил» машинно неотличимо от «книга кончилась»; форма для своего кода и поля есть у потолка (`CeilingHalt` → 4 → `events.go:423`), но прецедент против отдельного `outcome` тоже есть (`runevents.go:261-264`); (б) банковые проходы идут ВНЕ волны (`terminologist.go:348`, `:907`, зовутся из `mining.go:166`) и латчем не останавливаются — их естественная точка попартийный приём `terminologist.go:944-970`; (в) резюм уже верен и правки не требует (`waverun.go:449-452`); (г) `--max-units` слотов не тратит, но недоигранный грант должен публиковаться в `Finished.Volume`; (д) ⛔ **второй Ctrl-C сегодня НЕ ДЕЛАЕТ НИЧЕГО** — `stop()` стоит на `defer` (`main.go:208`), горутина `NotifyContext` после первого сигнала вышла, второй сигнал попадает в буфер и никем не читается ⇒ «второе нажатие ужесточает» — НОВОЕ поведение, а не восстановление дефолта. ⚠ **Продуктовая цена, которую владелец должен знать ДО решения:** мягкий стоп доигрывает начатое, а один вызов может держаться до `attempt_max_s` (~20 мин на deepseek) — оператор, не знающий этого, нажмёт второй раз и потеряет ровно те деньги, ради которых мягкий стоп заводился ⛔ **ДОБАВКА 10.09: два носителя УЖЕ обещают мягкий стоп, которого нет.** Контракт — `docs/architecture/14-api-contract/openapi.yaml:762` «The product "stop" action; finished work is kept and not paid for again»: жёсткий стоп это нарушает, летящая единица книжится по оценке и пере-делается на резюме (`backend/internal/pipeline/cutcall.go:208`), то есть покупается ДВАЖДЫ. Платформа — `platform/internal/runner/runner.go:56-58` («the engine … finishes the in-flight chunk before exiting») и `:163-164`: оба утверждения о движке ложны. И числовой конфликт: `stopGrace = 10 * time.Minute` (`runner.go:59`) МЕНЬШЕ потолка ожидания вызова (`attempt_max_s` 1240 с) ⇒ как только SIGTERM станет мягким, systemd будет убивать законное ожидание, а SIGKILL не оставляет ни пометки, ни сеттла. ⇒ пак движка ПАРНЫЙ платформенному, порознь не лендятся | бэкенд | скоро | у человека ДВЕ различимые остановки: мягкая (новое не начинаем, начатое доигрываем, пометок `cancelled` не возникает) и жёсткая; обе различимы машинно — кодом выхода и признаком в кадре `finished`, чтобы платформа не читала остановку как конец книги | слово владельца 09–10.09 + замер оркестратора и движковой зоны 10.09 | | 382 | ⛔ **ПОМЕТКА «ЭТО ОЦЕНКА» — УСЛОВИЕ СЛОВА ВЛАДЕЛЬЦА (`D39.230` п.1) — ПО ШВУ НЕ ЕДЕТ И ПОТРЕБИТЕЛЯ НЕ ИМЕЕТ.** Владелец разрешил списывать с читателя по ОЦЕНКЕ при условии, что пометка стоит. В движке она есть: `backend/internal/pipeline/status.go:321` — `estimated_rows` / `estimated_usd` в `status --json`. Дальше она обрывается. Замер 10.09 с контролем: в кадрах событий шва (`backend/internal/runevents/runevents.go`) слова `estimated` **0 хитов** (контроль: `committed` в том же файле — **6**; денежный кадр несёт один `committed_micro_usd`, `runevents.go:244`); в `platform/internal` вне тестов — **2 хита, оба КОММЕНТАРИИ**, причём `pgstore/credits.go:235` прямо просит эту величину: «publish the count and sum of estimated-price rows beside committed_usd, which is what would let this side say "at most Y"» (контроль: `committed_micro` — 1 хит; go-файлов вне тестов прибор прочёл **81**). ⇒ условие исполнено на ОДНОМ канале из двух, и читателя, ради которого пометка ставилась, у неё нет: платформа не может сказать пользователю «не больше Y». ⚠ Пара к строке **381**: и то и другое — признак, который обязан ехать в кадре `finished`/`spend`, а не только в `status --json` | бэкенд | скоро | платформа ЧИТАЕТ счёт и сумму оценочных строк из потока и может назвать пользователю верхнюю границу списания | акт `D39.233` п.7, замер оркестратора 10.09 по разбору чистого Fable 5 | +| 383 | **ПОТОЛОК ОЖИДАНИЯ ЖИВЁТ КОНСТАНТОЙ В ОДНОЙ ЗОНЕ, А ВЫВОДИТСЯ ИЗ КОНФИГА ДРУГОЙ — ФОРМА, КОТОРАЯ ТУХНЕТ МОЛЧА.** Платформенный грейс остановки обязан покрывать законное ожидание движка. Сегодня это число платформа ВЫВОДИТ из движкового каталога и кладёт себе константой (пак «две остановки», `D39.236` п.6): любая правка `backend/configs/models.yaml` — новый провайдер, поднятый `edit_ceiling_out`, изменённый `tok_s_floor` — делает константу ниже правды, и никто об этом не узнает: превышение грейса даёт SIGKILL, а SIGKILL не оставляет ни пометки `cancelled`, ни сеттла оценки. ⛔ **И ловушка уже названа числом, а не гипотезой (замер платформенной сессии 10.09):** `attempt_max_s` инертен у ПЯТИ провайдеров из восьми, и у беcключевых `DeadlineFor` сверху не ограничена вовсе — она растёт с бюджетом линейно. Беcключевой `zai` перерастает сегодняшний максимум 1240 с (deepseek) при бюджете ≥ **43 400** токенов, то есть при `edit_ceiling_out` ≈ **9 900** против сегодняшних **8 000**. ⇒ запас по конфигу — меньше четверти. **Нетухнущая форма, которую надо построить:** движок ПЕЧАТАЕТ свой потолок ожидания сам (он и так умеет считать его `DeadlineFor` — функция экспортирована ровно для вопроса о конфигурации), платформа читает его НА БУТЕ и отказывается стартовать с грейсом ниже. ⚠ В паке «две остановки» этого НЕТ ни в одной половине — там принята сегодняшняя форма (пин на арифметику + строка реестра + комментарий с адресом), и она честно названа тухнущей | бэкенд | скоро | грейс платформы не может молча оказаться ниже законного ожидания движка: число выводится ИЗ ДВИЖКА в рантайме, а расхождение ловится на буте, а не SIGKILL-ом | эхо-протокол платформенной сессии по паку «две остановки», 10.09 (`D39.237` п.5) | diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index dd25cfbd..66c94dbc 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -1,6 +1,6 @@ # Журнал прогресса -> **⟶ ТЕКУЩЕЕ СОСТОЯНИЕ** (на 2026-09-10, голова D39.236 — КУРС: движок и платформа до «работает и отдаёт результат», фронт ЗАМОРОЖЕН и P7 его НЕ размораживает (D39.147). **ОЧЕРЕДЬ №23** (единственный носитель — здесь; роль передана 06.09, №22 закрыт нотой передачи D39.217; ⚠ испр. 06.09: коммит передачи `c992ee5` бампнул голову и НЕ тронул номер — гейта на номер очереди нет вовсе, `counts.py` сверяет только голову, поэтому носитель разошёлся молча). ⚠ **ЗАКАЗ ВЛАДЕЛЬЦА 01.09 — ИСПОЛНЕН, испр. 08.09 (висел как «первым» неделю после исполнения):** (0а) ревизия документации на протухшее ОТРАБОТАНА 01.09 воркфлоу `docs-staleness-revision-A` (15 срезов), провенанс находок — `D39.185`; (0б) планы доработок в бэкенд и платформу — исполняются ПАКАМИ, за 07–08.09 закрыты два движковых (`D39.225`, `D39.226`); (0в) вынос неактуального в архив идёт батчами `DOC_CLEANUP_PLAN.md` (Б14/Б15/Б17 живы). ⇒ строка ниже — не заказ, а история: (0а) ревизия документации на ПРОТУХШЕЕ — по всем зонам; (0б) планы доработок в БЭКЕНД и ПЛАТФОРМУ; (0в) вынос неактуального в АРХИВ (`docs/archive/`, `platform/docs/archive/`) — за сутки 31.08 закрыто много, и часть носителей стала историей. ⚠ Трезвость по масштабу (пере-считано 02.09): **94** живых дока в `docs/` (пере-счёт 04.09: `find docs -name '*.md' -not -path 'docs/archive/*' | wc -l`), 8 в `platform/docs`, 9 в `backend/docs` — ⚠ из 94 ВРЕМЕННЫЙ остался ОДИН (`DOC_CLEANUP_PLAN`, живой до закрытия батчей Б14/Б15/Б17); два прежних временных уехали в `archive/reports/` 02.09; счёт бэклога — бюллетенем ниже, открытых рядов регистра платформы — 109 (major 1), всего рядов 465 (пере-счёт `python3 docs/scripts/counts.py`; с 04.09 оба числа под гардом `--check`, прежние 95/3 разошлись молча) ⚠ (ревизией 02.09 ряды **154** и **157** переведены из «скоро» в «когда-нибудь»: их гейтом стоял первый холодный прогон, он ОТРАБОТАЛ 31.08 и оба предусловия оказались другими — разбор в самих ячейках, ни одна НЕ закрыта). ⚠ Числа доков `counts.py` НЕ сторожит — при переносе файлов пере-считывать руками командой `find docs -name '*.md' -not -path 'docs/archive/*' | wc -l`. ⚠ И предупреждение о МЕТОДЕ, купленное сменой №21: «выглядит протухшим» ≠ «протухло». Три факта оркестратора опровергнуты ЗАМЕРОМ сессий, а якоря `15-money-path.md` в девяти случаях из двенадцати РОДИЛИСЬ верными и сгнили дрейфом — то есть ревизия обязана быть исполнением, а не чтением. ⚠ **ОЧЕРЕДЬ, унаследованная от №21** (три лендинга 31.08 — секция «СОСТОЯНИЕ ПАКОВ» ниже): **(1) ~~РАЗРЫВ ЦИКЛА~~ ЗАМКНУТ ЖИВЬЁМ 04.09** — пользователь получил EPUB настоящего ПЛАТНОГО перевода ЧЕРЕЗ API, `epubcheck` 5.3.0 на СКАЧАННОМ файле 0/0/0/0 (книга `bk_SS5VES2JELESJSTR`, потрачено $0.278319 из гранта $0.60 при потолке пака $1.5, санкция D39.189). Дверь выдачи построена и проверена исполнением: `202`+`Location`, поллинг с `Retry-After`, Range 206 · второй клиент 200 · аноним 401 · чужая книга 404, TTL с GC, идемпотентность третьего создающего вызова. Все ТРИ сценария строки 216 предъявлены живьём (подпись банка · halt на потолке с exit 4 · `409 run_not_resumable/ceiling_reached` и лечение новым прогоном). +> **⟶ ТЕКУЩЕЕ СОСТОЯНИЕ** (на 2026-09-10, голова D39.237 — КУРС: движок и платформа до «работает и отдаёт результат», фронт ЗАМОРОЖЕН и P7 его НЕ размораживает (D39.147). **ОЧЕРЕДЬ №23** (единственный носитель — здесь; роль передана 06.09, №22 закрыт нотой передачи D39.217; ⚠ испр. 06.09: коммит передачи `c992ee5` бампнул голову и НЕ тронул номер — гейта на номер очереди нет вовсе, `counts.py` сверяет только голову, поэтому носитель разошёлся молча). ⚠ **ЗАКАЗ ВЛАДЕЛЬЦА 01.09 — ИСПОЛНЕН, испр. 08.09 (висел как «первым» неделю после исполнения):** (0а) ревизия документации на протухшее ОТРАБОТАНА 01.09 воркфлоу `docs-staleness-revision-A` (15 срезов), провенанс находок — `D39.185`; (0б) планы доработок в бэкенд и платформу — исполняются ПАКАМИ, за 07–08.09 закрыты два движковых (`D39.225`, `D39.226`); (0в) вынос неактуального в архив идёт батчами `DOC_CLEANUP_PLAN.md` (Б14/Б15/Б17 живы). ⇒ строка ниже — не заказ, а история: (0а) ревизия документации на ПРОТУХШЕЕ — по всем зонам; (0б) планы доработок в БЭКЕНД и ПЛАТФОРМУ; (0в) вынос неактуального в АРХИВ (`docs/archive/`, `platform/docs/archive/`) — за сутки 31.08 закрыто много, и часть носителей стала историей. ⚠ Трезвость по масштабу (пере-считано 02.09): **94** живых дока в `docs/` (пере-счёт 04.09: `find docs -name '*.md' -not -path 'docs/archive/*' | wc -l`), 8 в `platform/docs`, 9 в `backend/docs` — ⚠ из 94 ВРЕМЕННЫЙ остался ОДИН (`DOC_CLEANUP_PLAN`, живой до закрытия батчей Б14/Б15/Б17); два прежних временных уехали в `archive/reports/` 02.09; счёт бэклога — бюллетенем ниже, открытых рядов регистра платформы — 109 (major 1), всего рядов 465 (пере-счёт `python3 docs/scripts/counts.py`; с 04.09 оба числа под гардом `--check`, прежние 95/3 разошлись молча) ⚠ (ревизией 02.09 ряды **154** и **157** переведены из «скоро» в «когда-нибудь»: их гейтом стоял первый холодный прогон, он ОТРАБОТАЛ 31.08 и оба предусловия оказались другими — разбор в самих ячейках, ни одна НЕ закрыта). ⚠ Числа доков `counts.py` НЕ сторожит — при переносе файлов пере-считывать руками командой `find docs -name '*.md' -not -path 'docs/archive/*' | wc -l`. ⚠ И предупреждение о МЕТОДЕ, купленное сменой №21: «выглядит протухшим» ≠ «протухло». Три факта оркестратора опровергнуты ЗАМЕРОМ сессий, а якоря `15-money-path.md` в девяти случаях из двенадцати РОДИЛИСЬ верными и сгнили дрейфом — то есть ревизия обязана быть исполнением, а не чтением. ⚠ **ОЧЕРЕДЬ, унаследованная от №21** (три лендинга 31.08 — секция «СОСТОЯНИЕ ПАКОВ» ниже): **(1) ~~РАЗРЫВ ЦИКЛА~~ ЗАМКНУТ ЖИВЬЁМ 04.09** — пользователь получил EPUB настоящего ПЛАТНОГО перевода ЧЕРЕЗ API, `epubcheck` 5.3.0 на СКАЧАННОМ файле 0/0/0/0 (книга `bk_SS5VES2JELESJSTR`, потрачено $0.278319 из гранта $0.60 при потолке пака $1.5, санкция D39.189). Дверь выдачи построена и проверена исполнением: `202`+`Location`, поллинг с `Retry-After`, Range 206 · второй клиент 200 · аноним 401 · чужая книга 404, TTL с GC, идемпотентность третьего создающего вызова. Все ТРИ сценария строки 216 предъявлены живьём (подпись банка · halt на потолке с exit 4 · `409 run_not_resumable/ceiling_reached` и лечение новым прогоном). > ⛔ **ПЯТЬ ПОТОКОВ РАБОТЫ — состояние на 07.09 (пере-снято лендингами смены №23).** > **(1) ПОЛИГОН — «ремонт прибора»** (`docs/POLYGON_INSTRUMENT_REPAIR_SESSION_PROMPT.md`, строки 319 · 300 · 265 · 143): > ⚠ **ЕДИНСТВЕННЫЙ ПОТОК, НЕ СДВИНУВШИЙСЯ ЗА СМЕНУ — сессия по промту так и не стартовала.** Блокирующая линза @@ -183,6 +183,120 @@ ## Бэкенд +#### Пак «ДВЕ ОСТАНОВКИ» (10.09, промт `docs/BACKEND_SOFT_STOP_SESSION_PROMPT.md`, вход HEAD `121c3c7`). ⏳ В РАБОТЕ — НЕ КОММИЧУ, жду лендинга оркестратором; пак ПАРНЫЙ и порознь не лендится (`D39.234` п.2) + +**BASELINE НА ВХОДЕ, снят СВОИМ прогоном ДО первой правки (§5.1–2).** +`python3 docs/scripts/counts.py --check` → «Литералы сходятся с пере-счётом (8 проверок)», exit 0. +`make battery` → **MAKE-EXIT=0** · **19 `ok`** · **0 FAIL** · **4 «no test files»** · **4 скипа**, и они +названы: `TestMinerFullBookParity` · `TestCorpusBankKeyConflicts` · `TestHelperEventsRun` · +`TestHelperKillLoop` (прогон с `-v`: без него скипы невидимы). Сходится со строкой предыдущей смены. +`make mutations` — отдельным заходом, число в итоговый отчёт. + +**ЗАПИСКА-ПЛАН (§7), написана ДО первой правки.** Порядок работ — рекомендованный паком, с одним +уточнением: реестр летящих вызовов строится ОДИН раз внутри §4.7, потому что его же читает §4.13 (строка +оператору) — это одна машина на двух потребителей, а не две. + +1. **§4.1 сентинел** — без типа ни один другой пункт не формулируется: «мягко» существует только как тип + ошибки, потому что контекст жив и `errors.Is(err, context.Canceled)` на нём ЛОЖЬ. +2. **§4.3 прогонный источник** — подъём существующего `latched` на этаж выше (вызовов `runWave` ровно два, + `waverun.go:140` и `:285`). +3. **§4.2 две точки отказа** — гейт (`stagerun.go:570`) и транспорт (`httpllm.go:177`). +4. **§4.4 + §4.15(а) + §4.5** — пост-обработка обеих волн, границы фаз, банковые проходы. +5. **§4.6 эскалация вторым сигналом** (`tmctl`), **§4.13** строка оператору + леджер на стоп-пути. +6. **§4.7 кадр + §4.11 оценочные строки** — форма из `D39.235` п.1, буква в букву. +7. **§4.10, §4.12, §4.15(б,в,г,д)** — мелкие механизмы и пины. +8. **ВТОРОЙ ЛЕНДИНГ: §4.8 (строка 377) и §4.9 (строка 375)** — линия объявлена паком заранее (§7). + +**РАЗМЕТКА КАЖДОГО ПОДПУНКТА §4 (§7: отчёт первого лендинга сверяется со СПИСКОМ, а не с памятью).** + +| § | что это | разметка | +|---|---|---| +| 4.1 | типизированный сентинел остановки + слот в `fail()` волны + ранжирование | **МЕХАНИЗМ** | +| 4.2 | точка отказа (1) пер-вызовный гейт · (2) транспортная петля ретраев | **МЕХАНИЗМ** (обе) | +| 4.3 | прогонный источник латча (подъём `latched` на прогон) | **МЕХАНИЗМ** | +| 4.4 | пост-обработка обеих волн по образцу потолка | **МЕХАНИЗМ** | +| 4.5 | банковые проходы: новых партий не начинаем, пас выходит ДО применения | **МЕХАНИЗМ** | +| 4.6 | второй сигнал → жёсткая, третий → дефолт процесса | **МЕХАНИЗМ** | +| 4.7 | кадр `finished`: `Stop{…}`, `Money` на `stopped`, минор `1.3→1.4` | **МЕХАНИЗМ** | +| 4.8 | строка **377**: оплаченный обрыв, за которым ретрай успел | **ВТОРОЙ ЛЕНДИНГ** | +| 4.9 | строка **375**: третье состояние единицы + запрет затирать флагнутую строку | **ВТОРОЙ ЛЕНДИНГ** | +| 4.10 | строка **372**: пятый исход `waitOutcome` + враньё комментария «THREE values» | **МЕХАНИЗМ** | +| 4.11 | строка **382**: счёт и сумма оценочных строк в кадрах + пин равенства с `status --json` | **МЕХАНИЗМ** | +| 4.12 | строка **379**(а): две строки таблицы девяти, разведённые стенными часами | **ПИН** | +| 4.13 | строка оператору при первом сигнале + леджер на стоп-пути | **МЕХАНИЗМ** | +| 4.14 | чего в паке НЕТ | **ни одно из трёх: работы здесь нет**, это объявленное сужение — отмечено, чтобы список сходился по числу | +| 4.15(а) | границы фаз, а не только волны | **МЕХАНИЗМ** | +| 4.15(б) | `res.Volume = nil` на мягкой остановке | **МЕХАНИЗМ** (однострочник) + **ПИН** | +| 4.15(в) | статус джоба при отказе гейта — не `failed` | **МЕХАНИЗМ** (однострочник) | +| 4.15(г) | столкновение с падением соседа: `failed` С пометками `cancelled` | **ПИН** (защиту не строю) | +| 4.15(д) | резюм после мягкой остановки — ноль платных вызовов | **ПИН** | + +Итого: механизмов 11, чистых пинов 3 (4.12 · 4.15г · 4.15д), второй лендинг 2, работы нет 1. + +**ЧТО СЧИТАЮ РИСКОВАННЫМ — названо ДО работы, чтобы потом сверить (и чтобы не жило только в переписке).** + +1. ⛔ **Транспорт — место, где мягкая тише всего становится жёсткой.** `retryLoop` (`httpllm.go:177`) — + ЕДИНСТВЕННАЯ петля ретраев обоих адаптеров (`httpllm.go:491`, `provider_anthropic.go:146`). Сигнал + обязан читаться РОВНО на границе попытки; прочитанный внутри `attempt()` или в `select` бэкоффа так, + что выходит ошибкой обрыва, он рвёт доставленную генерацию — ровно тот дефект, ради которого пак и + заведён. Провал НЕВИДИМ для батареи: «мягкий стоп, который на самом деле рвал», выглядит как успешная + остановка. ⇒ фикстура строится так, чтобы летящая попытка ОБЯЗАНА была дожить до ответа, и чтобы + вторая попытка была НЕВОЗМОЖНА, а не маловероятна (§5.6). +2. ⛔ **Тихий успех (§4.1).** `runWave` возвращает ошибку только если её вернул РАБОЧИЙ. Мягкая остановка, + пришедшая, когда ни один рабочий не был отказан гейтом (все дописывали своё), не рождает ни одной + ошибки: фидер просто перестал раздавать. ⇒ после дренажа `runWave` обязан сам спросить источник и + вернуть сентинел, если хоть один элемент не был начат. Без этого прогон с недоделанными единицами + уезжает с кодом 0 — дословно ловушка, названная в самом `waverun.go:340`. +3. ⛔ **Двойной учёт.** Мягкая остановка не платит ⇒ ни одной пометки `cancelled`, ни одного сеттла оценки + на ней возникать не должно. Механически это держится тем, что `recordCancelledStage` (`cutcall.go:200`) + спрашивает `errors.Is(err, context.Canceled)`, а на мягкой контекст жив. Значит пин обязан проверять + не «нет пометок», а «нет пометок ПРИ ТОМ, что вызов был доставлен и дожил» — иначе он зелен на пустом + сценарии. +4. ⚠ **Ранжирование сентинела в `runWave`.** Беру: паника > инфра-ошибка > потолок > остановка. Довод — + сегодняшний порядок в `waverun.go:457-497` ровно таков для ЖЁСТКОЙ (`parent.Err()` стоит последним, + после `haltErr`), и две остановки обязаны ранжироваться одинаково: иначе одно и то же столкновение + даёт разный код выхода в зависимости от того, какую кнопку нажали. Инфра-ошибка выше остановки — это + §4.15(г), решённое за меня. +5. ⚠ **`noteMoneyStop` говорит «the run stopped on a SPEND ceiling»** (`waverun.go:930`). §4.4 велит вести + мягкую остановку ПО ТОМУ ЖЕ пути (`moneyStoppedResult`), а этот путь печатает про потолок. Строка + станет враньём о причине (`D39.93` п.2) ⇒ развожу текст по причине, сохраняя одну функцию. + +**ВОПРОСЫ И РАСХОЖДЕНИЯ С ПАКОМ (посланы оркестратору эхо-протоколом §9; здесь — чтобы пережили сессию).** +⭐ **Первые три ПРИНЯТЫ оркестратором и ратифицированы `D39.236`** (п.1 · п.2 · п.3), пак исправлен +коммитом `ac9a24d` — оставлены здесь как запись о том, ЧТО было неверно и почему, а не как открытые +вопросы. Четвёртый — моя правка по месту, объявленная, но не ратифицированная. + +1. ⛔ **§4.11, скобка «эмиттер копит на лету», — как написано, даёт РАСХОЖДЕНИЕ ровно там, где §4.11 + требует равенства.** Соседнее число того же кадра, `committed_micro_usd`, берётся ИЗ СТОРА и является + ПОЖИЗНЕННЫМ для книги: `SELECT COALESCE(SUM(committed_usd),0) FROM spend WHERE book_id = ?` внутри + транзакции сеттла (`backend/internal/store/ledger.go:224`). Пара `estimated_rows`/`estimated_usd` в + `status --json` — тоже пожизненная: `estimatedSpend` (`backend/internal/pipeline/paidtail.go:231-258`) + считается по ВСЕМ чекпойнтам книги (`CheckpointUsageForBook`, `store/ledger.go:385`) плюс синтетическая + строка «committed без чекпойнта» (`paidtail.go:255`). Внутрипроцессный счётчик с нуля равен ей только + на СВЕЖЕМ одиночном прогоне; на любом резюме и после любого редрайва — нет, и пин §4.11 был бы зелёным + на пустом сценарии. ⇒ **делаю по прецеденту из того же файла:** сидирую пару из стора при старте (так + заведён `waveCounters.counted`, `backend/internal/pipeline/events.go:266-276`), дальше инкременты на + каждом сеттле по ТОМУ ЖЕ определению. Тогда равенство ПО ПОСТРОЕНИЮ, а не по совпадению фикстуры. + Форму кадра (`estimated{rows, micro_usd}` на `spend` и в `Money`) не трогаю — она из `D39.235` п.1(б). +2. ⚠ **§4.7 / `D39.235` п.1(в): расширение правила присутствия `Money`** («потолок сработал» → «потолок ИЛИ + остановка») — по ратифицированному правилу версий это смена СМЫСЛА существующего поля, то есть + формально МАЖОР, а не минор (`backend/internal/runevents/runevents.go:29-38`: «changing what an existing + field MEANS bumps the major»). Делаю минор `1.3→1.4`, как ратифицировано, потому что на практике + безопасно: ридер платформы сравнивает только мажор, его константа стоит на `1.1` + (`platform/internal/ingest/events.go:46`), и по `finished` он «does not ACT on this line» (`events.go:203`). + Называю, чтобы правило версий не сгнило молча; запись этого как осознанного исключения — нота + оркестратора, не моя правка. +3. ⚠ **§4.13 «опора готова» — готова наполовину.** Число летящих вызовов есть (`reserveGate.stateNow`, + `reservegate.go:176`), а ДЕДЛАЙН живёт только внутри горутины хартбита каждого вызова + (`stagerun.go:964-997`) — прогонного реестра «кто летит и до какого срока» нет. Строю маленький реестр + летящих вызовов; он же даёт числа кадра `Stop` (`in_flight_finished` / `in_flight_cut` — это счёт по тем + же объектам). Это работа, а не «уже есть». +4. ⚠ **§4.15(в) буквально указывает не на ту ветку.** «Отказ гейта даёт `failed`» верно для ветки + ПРЕРВАННОГО ОЖИДАНИЯ (`stagerun.go:625` и `:644`) — то есть для сегодняшней ЖЁСТКОЙ остановки; ЧИСТЫЙ + отказ потолка статус не трогает вовсе и оставляет джоб `pending` (`stagerun.go:648-657`). Мой отказ по + мягкой остановке встаёт ПЕРЕД `admit`, поэтому идёт по образцу потолка (статус не трогаю), а не по + образцу аборта. Расхождение с буквой пака объявлено здесь. + #### ⚠ ПИНГ ОРКЕСТРАТОРА №23 (10.09) — ТРИ ЯКОРЯ `backend/docs/` УКАЗЫВАЮТ НЕ ТУДА; ЧИНИТЬ ВАМ, Я В ВАШУ ЗОНУ НЕ ЛЕЗУ Линтер `python3 docs/scripts/counts.py --lint` краснеет на `backend/docs/DISCLOSURE_LAW_DESIGN.md`. diff --git a/docs/architecture/05-decisions-index.md b/docs/architecture/05-decisions-index.md index c5843676..891931e4 100644 --- a/docs/architecture/05-decisions-index.md +++ b/docs/architecture/05-decisions-index.md @@ -1,4 +1,4 @@ -# Реестр D-нот — карта актуальности v2 (D1–D39.236; титул — носитель головы, бампать при каждом аппенде) +# Реестр D-нот — карта актуальности v2 (D1–D39.237; титул — носитель головы, бампать при каждом аппенде) > ⚠ **Колонку «тело» `counts.py --check` НЕ сторожит по устройству:** он сверяет полноту НОМЕРОВ, а не > место тела, поэтому колонка держится дисциплиной лендинга. Не нашёл тело по колонке — иди в слайсы, @@ -295,3 +295,4 @@ | D39.234 | 10.09 | **СЛОВО ВЛАДЕЛЬЦА по двум остановкам + сверка носителей, которую `D39.232` объявил и не сделал.** Ратифицировано: форма «повтор ужесточает» (первое нажатие — мягкая, второе — жёсткая), потолок ожидания ~20 мин (одна летящая попытка, не цепочка), пак мягкой остановки и строка **377** — одним заходом; коды выхода не трогать. Пак движка ПАРНЫЙ платформенному (грейс 600 с < 1240 с). ⛔ Эррата 10.09-б: три объявленные закрытыми строки сутки стояли в таблице — **78** · **369** · **360** закрыты и убраны, плюс незамеченная **331** (с названным сужением, остаток — **376**). Инвентарь живых доков: `15-money-path.md` чинен в трёх местах и получил новую денежную границу; реестр требований против брифа владельца сходится (V0–V6) | стоп · деньги · слово владельца · сверка | | D39.235 | 10.09 | **ФОРМА КАДРА ОСТАНОВКИ ЗАФИКСИРОВАНА ДО РАБОТ; два парных промта выданы; сплошной инвентарь живых доков.** Кадр: `Finished.Stop{mode,…}` присутствием (прецедент `Volume`), `estimated{rows,micro_usd}` на ОБОИХ кадрах, `Money` на `stopped` всегда, запасной канал — обязанность платформы; минор потока `1.3→1.4`. ⛔ Эррата к `D39.234` п.1б: «отказ на гейте» ратифицированных ~20 мин НЕ даёт — резервация держит ВСЮ цепочку ретраев, нужен и `retryLoop`. ⛔ Замер исполнением: платформенный `Stop` без `--no-block` блокируется на весь грейс (15,21 с против 0,019 с) ⇒ сломал бы `202` контракта. Инвентарь: **19** мест починено, самый урожайный класс — УСЛОВНОЕ ЗАКРЫТИЕ. Контракт `0.13.0→0.13.1`: `Run.required` требовал отставленный член ⇒ каждый ответ был невалиден. Закрыта строка **166** | стоп · шов · контракт · инвентарь | | D39.236 | 10.09 | **ДИСПОЗИЦИИ ПО ЭХО-ПРОТОКОЛАМ обеих сессий пака «две остановки»: шесть возражений, шесть приняты, ТРИ — мои ошибки.** (1) счётчик оценки СИДИРУЕТСЯ из стора, а не копится с нуля — иначе пин равенства двух каналов зелен на пустом сценарии; (2) расширение правила присутствия `Money` формально МАЖОР, везём минором `1.3→1.4` как осознанное исключение (мажор читателем отвергается целиком); (3) «опора готова» для строки оператору — преувеличение, прогонного реестра летящих вызовов нет; (4) платформа поднимает `StreamVersion` и тем же движением пишет, каких полей 1.2/1.3 не читает; (5) «свип встанет на 20 минут» — неверный механизм, настоящий ущерб — ложная тревога `StalledAfter`; (6) 1240 с — свойство ПАРЫ, не константа платформы. ⭐ Все шесть пришли ПОСЛЕ двух кругов ревью: эхо-протокол — третий рубеж, а не формальность | стоп · шов · метод | +| D39.237 | 10.09 | **ЗАПИСКА-ПЛАН ПЛАТФОРМЫ.** ⛔ Мой корректирующий минор контракта (`D39.235` п.6) положил гейт ЧУЖОЙ зоны — базовая линия была красной на входе сессии; носитель один (`platform/internal/httpapi/capabilities.go`), правку берёт сессия. **Норма: правка ратифицированного носителя сверяется с ГЕЙТАМИ на него тем же движением** (второй раз за две недели, ср. эрратy 08.09-б). ⭐ Сессия нашла сверх заказа: повторный `stop --no-block` по `deactivating` НЕ шлёт второй SIGTERM — иначе свип давал бы АВТОМАТИЧЕСКУЮ эскалацию в жёсткую остановку; свойство держалось на удаче, теперь пинится. Потолок ожидания — свойство каталога И бюджета: беcключевой `zai` перерастает 1240 с при `edit_ceiling_out` ≈ 9 900 против 8 000 сегодня. Нетухнущая форма — строка **383** | стоп · шов · метод | diff --git a/docs/architecture/05-decisions-log.md b/docs/architecture/05-decisions-log.md index 359bede8..4c24db55 100644 --- a/docs/architecture/05-decisions-log.md +++ b/docs/architecture/05-decisions-log.md @@ -1,4 +1,4 @@ -# Журнал решений оркестратора — контракт D1–D39.236 (живой файл: карта · эрраты · живые тела · голова D39.124+ (подрезка D39.139); тела закрытых эр — в слайсах `docs/archive/architecture/`, указатель ниже; реестр всех нот — `05-decisions-index.md`) +# Журнал решений оркестратора — контракт D1–D39.237 (живой файл: карта · эрраты · живые тела · голова D39.124+ (подрезка D39.139); тела закрытых эр — в слайсах `docs/archive/architecture/`, указатель ниже; реестр всех нот — `05-decisions-index.md`) > **⟶ КАРТА АКТУАЛЬНОСТИ (ревизия D31, продлена до D38.2 [12.07]; исторические записи ниже НЕ переписываются — дисциплина D23.3).** Работая с контрактом (греп номера: живой файл → слайсы, целиком НЕ читать — D39.125), держи под рукой, что чем перекрыто: > ⚠ **Эррата 09.08 (D39.125):** D39.111 п.1 предписывал промту S3 «максимум = баланс МИНУС открытые холды» — формула ОШИБОЧНА (вычитание дважды), исправлена D39.115 п.2(а): максимум = Balance КАК ЕСТЬ; тело D39.111 — в слайсе `../archive/architecture/05-decisions-D39-106-123.md` (испр. 05.09: прежнее «живёт ниже в этом файле» протухло подрезкой D39.139) (голова D39.106+). @@ -3334,3 +3334,21 @@ bought NOTHING». ⇒ **`tmctl manifest` есть НИЖНЯЯ граница, **6. ПРИНЯТО: «законное ожидание 1240 с» — свойство ПАРЫ, а не константа платформы.** Оно равно ожиданию после SIGTERM только если движковая половина посадит «мягкая остановка видна МЕЖДУ попытками и в бэкоффе» (`D39.235` п.2). Не сядет — ожидание снова становится цепочкой (~3×1240 + 2×60 ≈ 64 мин), и любой грейс, выведенный из 1240, снова окажется ниже. ⇒ в отчёте платформы число пишется как ЗАВИСИМОСТЬ с названным носителем, а не как своя константа. ⚠ Заодно обе сессии независимо пере-считали каталог и подтвердили мою вчерашнюю поправку: `attempt_max_s` задан у ТРЁХ провайдеров из восьми, у пяти его нет и он инертен. **7. Границы.** Все шесть пере-сняты мной на дереве по адресам, которые дали сессии; ни одно не принято со слов. Пункты 1, 3 и 5 — ошибки в МОИХ паках (скобка про счётчик, «опора готова», механизм следствия), пункт 2 — правило, которое я нарушил бы молча. Пункты 4 и 6 — работа сессий сверх заказа. Живого прогона не было, $0. + +## D39.237 — ЗАПИСКА-ПЛАН ПЛАТФОРМЫ: мой бамп версии покраснил чужую зону, а сессия нашла свойство безопасности, которое держалось на удаче (10.09, оркестратор №23) ✅ + +**1. ⛔ МОЙ КОРРЕКТИРУЮЩИЙ МИНОР КОНТРАКТА ПОЛОЖИЛ ГЕЙТ ПЛАТФОРМЕННОЙ ЗОНЫ, и нашёл это не я.** `D39.235` п.6 увёл канон на `0.13.1`, а объявляемая билдом версия осталась `0.13.0` — гейт `TestTheAnnouncedContractVersionIsTheOneTheCanonRatified` (`platform/internal/gates/`) сверяет их и краснеет. То есть базовая линия зоны была КРАСНОЙ на входе сессии, и красное — моё. **Замер ущерба, пере-снят мной:** носитель в коде РОВНО ОДИН — `platform/internal/httpapi/capabilities.go`, греп `const ContractVersion`; в `backend/` хитов `0.13.` НОЛЬ (контроль: go-файлов вне тестов там 142); зеркало фронта на `0.2.3` и его равенство приостановлено ратификацией. ⇒ поражена одна зона и одна константа. **Диспозиция: платформенная сессия берёт правку** — она всё равно двигает эту константу своим минором (`mode` у `/stop`), и две правки одной константы назовёт в отчёте раздельно: чужой долг и своя. ⚠ **Норма, которую я нарушил и записываю здесь:** правка ратифицированного носителя обязана быть сверена с ГЕЙТАМИ, которые на него смотрят, тем же движением — иначе следующая смена входит в чужую красноту и тратит первый час на диагноз чужой ошибки. Это второй раз за две недели (эррата 08.09-б — тот же класс). + +**2. ⭐ СВОЙСТВО БЕЗОПАСНОСТИ, КОТОРОЕ ДЕРЖАЛОСЬ НА УДАЧЕ, — находка сессии сверх заказа.** Замерено ею на своём транзиентном юните: повторный `systemctl stop --no-block` по юниту, уже стоящему в `deactivating`, НЕ перезапускает `TimeoutStopSec` и НЕ шлёт второй SIGTERM (три вызова с шагом 5 с, юнит умер ровно на t+15,10 с от ПЕРВОГО стопа, в логе пробника ОДИН SIGTERM). Хорошая половина очевидна: идемпотентный пере-выпуск стопа реконсайлером грейс не продлевает. **Несущая половина в другом:** если бы пере-выпуск ДОХОДИЛ до процесса, каждый мягко останавливающийся прогон получал бы второй SIGTERM на следующем проходе свипа — то есть АВТОМАТИЧЕСКУЮ эскалацию в жёсткую остановку, ровно то, что пак запретил отдавать платформе («явное согласие на пере-оплату», канон владельца; `D39.236` п.6 контекстом). ⇒ **свойство пинится тестом и называется в комментарии**: сегодня оно держится на поведении systemd, о котором никто не договаривался. + +**3. Потолок ожидания — свойство каталога И НАИБОЛЬШЕГО БЮДЖЕТА, а не одного каталога.** Пере-считано сессией программой на `config.LoadModels` + `DeadlineFor` над снапшотом дерева: провайдеров 8, `attempt_max_s` задан у 3, инертен у 5; максимум при сегодняшнем наибольшем бюджете (35 200 токенов) — 1240 с, deepseek. ⛔ Но у беcключевых `DeadlineFor` сверху не ограничена и растёт линейно: беcключевой `zai` перерастает 1240 с при бюджете ≥ 43 400 токенов, то есть при `edit_ceiling_out` ≈ 9 900 против сегодняшних 8 000 — **запас по конфигу меньше четверти**. Форма грейса принята как СУММА с названными слагаемыми, а не литерал: `1240 (потолок) + 120 (свёртка движка — АЛЛОКАЦИЯ, не замер, направление ошибки названо) + 60 (жёсткая фаза)`. + +**4. Мой замер §4.7 воспроизведён ЧУЖОЙ рукой на чужом юните.** `stop` → 15,058 с (по SIGKILL) против `stop --no-block` → 0,014 с; второй сигнал по `deactivating` доходит (`kill --signal=SIGTERM --kill-whom=main`, rc=0, процесс получил), контроль — тот же вызов по несуществующему юниту даёт rc=1. ⇒ утверждение пака, взятое мной из чужого замера, теперь стоит на двух независимых приборах. + +**5. Нетухнущая форма удержания потолка — ЗАВЕДЕНА СТРОКОЙ 383, а не обещанием.** Сессия предложила три варианта и рекомендовала (а)+(б); принято ровно так. (а) пин на арифметику, строка реестра и комментарий с адресом — в паке, делается сейчас; (б) движок печатает свой потолок сам, платформа читает на буте и отказывается стартовать с грейсом ниже — **единственная форма, которая не тухнет**, но она не входит ни в одну из двух половин пака и потому получает СТРОКУ. ⚠ Сегодняшняя форма честно названа тухнущей и в паке, и в строке — это условие её принятия. + +**6. Контракт: режим остановки получает ДОБАВОЧНОЕ поле, `stop_requested` не трогается.** Он булев и `required`; смена типа — мажор для генерируемого клиента. Точную формулировку сессия пришлёт в отчёте, ратифицирую минором. Довод — тот же, что в `D39.235` п.6: проза контракта компилируется в исходник клиента, поэтому форма важнее удобства. + +**7. Перестановка порядка работ ПРИНЯТА и довод записан.** `--no-block` (§4.7) идёт ВТОРЫМ, до пути второго сигнала: до него любой живой сценарий на грейсе больше 600 с блокирует и пробник, и свип на весь грейс — то есть инструмент проверки грейса упирается в дефект, который тот же пак и чинит. ⇒ **правило шире случая: если пак чинит инструмент, которым сам же проверяется, эта починка идёт первой.** + +**8. Границы.** Пункт 1 пере-снят мной. Пункты 2–4 — замеры сессии на её юнитах и её программой; я их не воспроизводил и говорю это прямо, но пункт 4 совпал с независимым замером старшего коллеги, а пункт 3 совпал с моим счётом каталога (3 из 8 с ключом). Пункт 3 в части «120 с на свёртку» — АЛЛОКАЦИЯ, названная аллокацией самой сессией.