diff --git a/.gitignore b/.gitignore index 03d337db..dc478472 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,10 @@ backend/dist/ *.db-wal *.db-shm *.db.lock +# Сайдкары движка рядом с БД (манифест, банк, стоп-таблицы — состояние книги, вне git; D39.122) +*.db.*.json +*.db.*.txt +*.db.*.yaml # Редакторы/ОС .vscode/ diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index eb3c2cdd..4cd2384b 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -1,15 +1,16 @@ # Журнал прогресса -> **⟶ ТЕКУЩЕЕ СОСТОЯНИЕ** (на 2026-08-08, голова D39.119; хендофф №14→№15 — D39.107). ⚠⚠ **ИНЦИДЕНТ 08.08 00:13 (D39.113): агент моей панели откатил незакоммиченные правки СЕМИ файлов чужих сессий** — `git checkout -- .` в реальном дереве вместо песочницы. Пинг полигона в этом файле ВОССТАНОВЛЕН из бэкапа 00:08; безвозвратно потеряны правки отчёта эксп-21, промта полигона, Приложения А контракта и трёх скриптов `eval/role_topology/`. Коммиты целы. Инвентарь и норма изоляции панелей — D39.113. ⚠ **Оркестраторов сейчас ДВА, зоны разведены явно (решение владельца 07.08):** №15 — приёмка ПЛАТФОРМЫ (исполнена, D39.109–D39.114: P1+P2 приняты, фикс-пак P3 принят и заленден `99f049c` — девять строк регистра закрыты, 8/8 моих посадок поймано, PD-80 и PD-91 пере-проверены исполнением; записи верифицированы независимым агентом, поправки — D39.112) · ВТОРОЙ, параллельный — приёмка ПОЛИГОНА (эксп-21 ПРИНЯТ — D39.117; промт эксп-22 выдан). **Номер второму НЕ выдан намеренно:** канонический счётчик один и живёт в `ORCHESTRATOR_SESSION_PROMPT.md`, а выдавать номер сессии, которой я не писал промт, — второй носитель состояния. Одновременно не запускаются; CURRENT-STATE ведут оба, каждый пишет про свою зону и чужую строку не трогает. +> **⟶ ТЕКУЩЕЕ СОСТОЯНИЕ** (на 2026-08-09, голова D39.122; хендофф №14→№15 — D39.107). ⚠⚠ **ИНЦИДЕНТ 08.08 00:13 (D39.113): агент моей панели откатил незакоммиченные правки СЕМИ файлов чужих сессий** — `git checkout -- .` в реальном дереве вместо песочницы. Пинг полигона в этом файле ВОССТАНОВЛЕН из бэкапа 00:08; безвозвратно потеряны правки отчёта эксп-21, промта полигона, Приложения А контракта и трёх скриптов `eval/role_topology/`. Коммиты целы. Инвентарь и норма изоляции панелей — D39.113. ⚠ **Оркестраторов сейчас ДВА, зоны разведены явно (решение владельца 07.08):** №15 — приёмка ПЛАТФОРМЫ (исполнена, D39.109–D39.114: P1+P2 приняты, фикс-пак P3 принят и заленден `99f049c` — девять строк регистра закрыты, 8/8 моих посадок поймано, PD-80 и PD-91 пере-проверены исполнением; записи верифицированы независимым агентом, поправки — D39.112) · ВТОРОЙ, параллельный — приёмка ПОЛИГОНА (эксп-21 ПРИНЯТ — D39.117; промт эксп-22 выдан). **Номер второму НЕ выдан намеренно:** канонический счётчик один и живёт в `ORCHESTRATOR_SESSION_PROMPT.md`, а выдавать номер сессии, которой я не писал промт, — второй носитель состояния. Одновременно не запускаются; CURRENT-STATE ведут оба, каждый пишет про свою зону и чужую строку не трогает. > - **Сессия №15 (07.08, ИСПОЛНЕНА):** **приёмка платформы P1+P2 — ПРИНЯТО и ЗАЛЕНДЕНО `fcdab81`** (62 файла, +7066/−252; D39.109). Адверсариально: своя батарея с живым PostgreSQL 18.4 (скипов ноль) · 45 СВОИХ мутаций (33 поймано, 8 выжило, 4 моих посадки негодны и разобраны) · живые пробы на боевом бинаре (PD-2 отпускает на 30.0 с; RFC 9207 сверен у Google) · панель 7 линз с опровергателем на каждую находку. Ратифицировано 6 решений (срок сессии 30 суток — **CONFIRM владельца 07.08 получен** · `iss` авторизационного ответа + миграция 00008 · §13 политики сессий · ломающие изменения зоны · `MemoryMax=80%`+`OOMPolicy=continue` · PD-71 риском). Регистр зоны доведён до **PD-107**. На момент приёмки открытыми были тридцать семь строк, весом major — три: PD-80 (вход выключается ~3 rps, и 429 колбэка добивает начатые входы) плюс PD-95 и PD-105, поднятые эрратой как задание эмиттеру. ⚠ **Числа здесь ИСТОРИЧЕСКИЕ — это то, что нашла приёмка;** живой счёт регистра меняется с каждым фиксом зоны и берётся командой `python3 docs/scripts/counts.py`, а не из этой строки (норма D39.112: один носитель на факт). **Живой уязвимости нет.** ⚠ **ЭРРАТА 07.08 (поймано вопросом владельца, не процессом):** приёмка пропустила, что **D39.106 п.3 снял пайп-транспорт** («PD-59 superseded; пайп-путь `supervisor.go` P1 = дев-режим»), и приняла зонную секцию «канал остаётся stdout» как живое решение. Ратифицированная форма — транзиентный systemd-юнит на прогон, платформа НЕ родитель, события = `events.jsonl` каталога книги, платформа ТЕЙЛИТ с курсором (`research/25` §Форма; родитель+пайп 0/15 голосов). Исправлено: секция журнала помечена superseded, PD-95 переписан и поднят в вес, PD-92 понижен до info, PD-105 повышен, PD-60 пере-диспозиционирован; вердикт лендинга не меняется (код и был дев-путём по D39.107 п.3). ⚠ Пять строк реестра — движковые по существу и должны быть разрулены ДО промта эмиттера (строка 103): PD-95 (транспортная история зоны устарела против D39.106 в двух местах — эмиттер-сессия прочтёт её как задание) · PD-105 (дубль `seq` = убитый платный прогон против норматива «дубль не ошибка»; при тейле файла повторная доставка — норма) · PD-79 (строковый `"null"` в цене = ноль денег) · PD-99 (argv на INFO — деньги/book-id при воркере) · PD-107 (удаление аккаунта обходит гарантию PD-25) · **PD-60 и PD-61** (обратное давление и сброс буфера — свойства эмиттера, задаются дешевле ДО постройки, чем миграцией после; PD-60 переформулируется под файл-транспорт). **Потолок прогона РЕШЁН владельцем 07.08 и ратифицирован формой (D39.110):** шкала в интерфейсе от минимума (ноль нельзя) до баланса аккаунта ⇒ Р7 не амендируется, «выключено» не существует как состояние. Добор: единица шкалы — ГЛАВЫ, не доллары (иначе амендмент D39.84) · потолок в `book.yaml` НЕ пишется, движку нужен приём на прогон — НОВАЯ строка **145** · максимум шкалы = баланс минус открытые холды. **Фри-тир:** ответ дан нормой (автогрант без якоря — не стандарт; `email_verified` Google якорем не является), предложение оркестратора — на бете дефолт в НОЛЬ и начисление руками, при платежах $5 вместе с суточным потолком; **ждёт слова владельца** (PD-104, там же расхождение док↔код: реестр обещает ноль, код начисляет $5) · **приёмка экспов 18/19/20 ИСПОЛНЕНА и залендена** (D39.108: приняты с ревью-шапками; §5.2 экспа 20 — «со слов»; деньги сессии полигона $2.0720; квирк §3б отозван, §3а в силе; D21.4/D21.10 закрыты) · **эксп-21 «топология ролей» — промт выдан** (`POLYGON_ROLE_TOPOLOGY_SESSION_PROMPT.md`: скрин моделей по 7 ключам → бейк-офф топологий на невиданном срезе; потолки ПРЕДВАРИТЕЛЬНЫЕ ≈$7.3 стадиями; **ЗАПУСК = слово владельца**) · строки 135–144 заведены, 103/106/134/65/55/12 пере-диспозиционированы · дальше по D39.107 п.3: промты эмиттера (бэкенд) и денег шва (платформа) — по команде · **S3 «слой данных» ПРИНЯТ и заленден `60595f6` (D39.115): контракт 0.2.0 в каноне, копии байт-равны, блоки компаньона 2.14–2.16 внесены; сессия нашла ТРИ ошибки оркестратора — «баланс минус холды» (вычитание дважды, пере-мерено), `nullable: true` невалиден в 3.1, инструкция внутри нормативного дока протухла от исполнения; норма «не ходить в чужую зону» амендирована: не ПИСАТЬ, а читать код для сверки — можно и нужно** · вахта deepseek-v4-pro — пере-проба маппинга вшита в Ф1 эксп-21 · мини-проба флора 44 — одобрена, промт не выдан. **Открыто на владельце:** прогон Sol-пакетов руками (строка 150) · кредиты xAI (арбитр grok + судья 18+) · запуск эксп-22 (промт выдан, D39.117) · передача платформы · Приложение А `14-api-contract` (незакоммичено) · **НОВОЕ 08.08 (D39.115, фронт):** продуктовое слово «остановлена: лимиты» (сжатие вашей фразы сделала сессия, В-3) · **К-13/Ф-31:** упереться в СВОЙ потолок прогона и остаться без кредита сегодня неразличимо — `paused_reason` имеет одно значение, и первому фраза врёт · **В-4/Ф-30:** глава без заголовка показана голым номером, синтез «Глава N» запрещён К-3 · **фри-тир (PD-104):** дефолт в ноль и начисление руками против автогранта $5 с суточным потолком · **Приложение А (строка 148):** брать реконструкцию с баннером или заполнять заново · 116 (порог подан) · мини-проба 44 · подпись денежного шага 46 · лист В-3+К-6 · вопрос платформы (авто-резюме `paused`). > - **Сессия №15 (08.08, приёмка бэкенда): фикс-пак банка (128+129+130-минимум) ПРИНЯТ и ЗАЛЕНДЕН `bccf2d8` (D39.118).** Приёмка четырёхконтурным воркфлоу (слепой дифф · пере-ран заявлений · 8 посадок в копии зоны · охота вне карты) + дофикс-раунд: блокер lint (клейм «батарея зелёная» был ложным — признано) · мёртвый WARN 130 (само-исключение сырого seed) · 2 дыры пинов space-run · инертный `bankParseVersion` · 3 тихих отказа канала семей — ВСЕ закрыты; ре-чек: battery EXIT=0 целиком, 685→717 тестов, пере-посадки 3/3 CAUGHT. Ратифицировано D39.118: сдвиг edit-волны bank-only принят (слово владельца) · bankdata-плоскость вне волнового фолда · лог вместо миграции для эвиктнутых. Строки 128/129 закрыты, 130 пере-скоуплена, заведены 154–159. Верификация эффекта — §6 отчёта, гейт: первый холодный прогон. **АКТИВНОГО промта бэкенда нет** — следующий (эмиттер 103 + деньги шва 135/145) по слову владельца (D39.107 п.3). -> - **Сессия №15 (08.08, курс D39.119): эмиттер ОТЛОЖЕН словом владельца — выданы ДВА промта:** бэкенд «блокеры контракта» (`BACKEND_CONTRACT_BLOCKERS_SESSION_PROMPT.md`: строки 99+100+101+125+145 — движковые поверхности для платформы/фронта; 49 — следующим) · платформа «раннер» (`platform/docs/PLATFORM_RUNNER_SESSION_PROMPT.md`: юниты D39.106 · River+воркер+холд-до-спавна · реконсилятор · тейлер наготове · ручки /v0 П-1/П-5; **PD-105 ратифицирован**: at-least-once, дубль seq = норма). Запуск обоих = владелец. **Фронт: S4 ГЕЙЧЕН** — слово владельца о качестве оболочки (вкладки = логика VS Code; полный список претензий следует); форма: «S3.5 фикс-пак оболочки» → S4 (D39.119 п.4, запись в зонном журнале). Эмиттер (103) — бэкенд-промтом после блокеров. +> - **Сессия №15 (08.08, курс D39.119): эмиттер ОТЛОЖЕН словом владельца — выданы ДВА промта:** бэкенд «блокеры контракта» (`BACKEND_CONTRACT_BLOCKERS_SESSION_PROMPT.md`: строки 99+100+101+125+145 — движковые поверхности для платформы/фронта; 49 — следующим) · платформа «раннер» (`platform/docs/PLATFORM_RUNNER_SESSION_PROMPT.md`: юниты D39.106 · River+воркер+холд-до-спавна · реконсилятор · тейлер наготове · ручки /v0 П-1/П-5; **PD-105 ратифицирован**: at-least-once, дубль seq = норма). Запуск обоих = владелец. **Фронт: S4 ГЕЙЧЕН** — слово владельца о качестве оболочки (вкладки = логика VS Code; полный список претензий следует); форма: «S3.5 фикс-пак оболочки» → S4 (D39.119 п.4, запись в зонном журнале). Эмиттер (103) — бэкенд-промтом после блокеров. **+ D39.120:** кросс-модельные контуры ревью (опровергатель другой моделью в панелях · параллельная сессия-ревьюер на тяжёлых ратификациях) и предметные оси самопроверки в кодовых промтах — нормой-НАПРАВЛЕНИЕМ с явной оговоркой свободы сессий. **+ D39.121 (09.08):** записка-план после тяжёлого ревью (структурированный файл ID→статус→улика, сдача = все диспозиции) + официальные практики Anthropic абсорбированы (research/26: сниппеты хендоффов · компакция-инструкция и запрет подгонки тестов в CLAUDE.md · obstacle reporting и анти-оверфлаг в панелях). +> - **Сессия №15 (09.08, D39.122): пак «блокеры контракта» ПРИНЯТ и заленден `0e69bc1`** — приёмка тремя раундами (7-агентный воркфлоу с кросс-модельным опровергателем-Opus по D39.120 · фикс-лист F1–F6 с MAJOR «unit.id переживал разрез» · V2-раунд · сессия сверх заказа провела третий слепой аудит своих клеймов и запинила 4 непокрытые оси; тестов 717→749); ратификации и формы — D39.122, ревью-шапка отчёта первична; строки 99·100·101·125·145 закрыты, заведены 163·164. **Формы артефактов платформе (финальные): `unit.id = ::` · `tm-manifest-v2` · id банка длино-префиксированы** — пинг в зонном журнале платформы. **+ research/27 «Определение глав» (D39.122 п.4, фактурой):** веб-ресёрч + панель двух моделей + карта по коду; жизненный цикл дерева (§3а, решение владельца) · мини-сессия названий (§5а) · этапность со строками 160–162; дизайн стройки НЕ ратифицирован. Следующее: приёмка платформы-раннера (сессия жива) · лендинг эксп-22 (CONFIRM/DENY владельца) · промт эмиттера. > - **Сессия параллельного оркестратора (приёмка полигона, 08.08, D39.117):** **эксп-21 ПРИНЯТ** — гейты пере-гнаны исполнением (`verify_report.py` 90/90 код 0 · `itog.py` код 0); подтверждения владельца по тратам и агент-судейству получены, риг-девиация ратифицирована (девиация от промта оркестратора, не решение владельца). Несущее (числа — ИТОГ отчёта 21 и D39.117, здесь не дублируются): **работает ТОПОЛОГИЯ, а не жилец** (нога 2×2) · **черновик — главный рычаг качества финала**, самая недоинвестированная роль · закон-блок ОБЯЗАТЕЛЕН (двойное подтверждение) · роутинг/обратная связка/ремонт-как-замена — отклонены. Диспозиции: 65 ЗАКРЫТА (жилец = цена) · 142 принята формой «пре-гейт перед редактором» · 106 не закрыта (довод, не опровержение) · 55 частично (остаток = чистый zh-срез, заказ повторён). **Промт эксп-22 «панель жильцов» выдан** (149) · Sol-арбитраж вместо слепого чтения (150) · строки 151–153 заведены · три провод-факта в quirks 00. ⚠ Величины эксп-21 предварительны до чистого zh-среза. > - **Сделано (сводно; детали — D-лог и архив-слайсы):** Ф0 ✅ · Ф1-инфра ✅ · арх-ресет D39 (7 слоёв, паки 11–16) ✅ · паки 17 «канал B» · 18 «долги» · 19 «голос/состояние» · 20 «банк+терминолог» ✅ (D39.26–28/31/41–56) · мини-прогон (D39.37) и ХОЛОДНЫЙ прогон (D39.58: recall банка 0.918/0.980, банк приходит переведённым) приняты · полигон-пакеты 5–8, ToS-речек (D39.57), Р6+P4 (D39.61) закрыты · **карта языковой привязки движка (D39.60): 149 сайтов/45 файлов, книго-ось чиста (4), ja→ru безопасно без правки Go, en→ru — нет** · Р1–Р4 закрыты целиком (D39.63: Р2 = ЗНАЧЕНИЕ, Р4 = норма потолка recall) · **ФАЗА 2 ОБЩНОСТИ ✅ (D39.64: П0 эмбед-хеш · П1 цель-шов · П2 скрипт-шов, ko-баг закрыт · П3 нарезка · П4 манифест-по-каналам; голден вердикт-нейтрален 0/172, майнер-парити EXACT)** · жанровый словарь отменён как класс (D39.47) · петля ремонта построена и НЕ включена (D39.38). > - **Курс:** ОБЩНОСТЬ ✅ → КАЧЕСТВО БАНКА ✅ → ПАКЕТ-ЧЕКЕРОВ ✅ (D39.59–78) → **ФРОНТ-ЭРА открыта** (D39.81–100: зоны `frontend/`+`platform/` живые, контракт API ратифицирован). Развилка блокера 0731 решена вариантом (б) и ИСПОЛНЕНА (D39.87/91: `reasoning_effort:"low"` черновику + ручка эффорта бэнк-ролям, код `553f1a3`); отладочный прогон C не стартовал ⇒ **строка 16 жива и оси голоса 13б без данных**; пак экрана 46 — дизайн заморожен (D39.92 п.1 + D39.93), промт ждёт выдачи; coldrun-b фаза C заморожена чекпойнтом — легитимный исход (D39.86); эталон денег/поведения — coldrun-a (read-only). Числа и хроника закрытых паков — D-лог D39.79–97, сюда НЕ дублируются. > - **Горизонт (D39.62/67, освежён D39.95):** ре-проба flash ИСПОЛНЕНА и строка 74 закрыта (D39.86/91; порог 6/6 → пере-ратификация выборочным, строка 116; проба Q2 ИСПОЛНЕНА — research/24, D39.102: семейства не окупаются, остаток = рецензент спорных кластеров при ре-пробе 74; эмиссия Q7 — по слову) → **ДОБОР ИДЕАЛА** (первым прогоном: оси голоса 24 · авто-режим · цена 16; жильцы ролей решаются ДО прогона отдельным эксп-22 — строка 149 · веса K1–K12 13а · вне-претрейн чекпоинт 55) → ВТОРАЯ ПАРА живьём (ja→ru; преп 81) → МАСШТАБ → пилот Ф2.5 (гейт резюме-строки 80; строки 62–68, 85) → Ф3 ридер-IDE (69–71). **Стоячие:** ToS-триггер 25.10 · Ш-2 до go1.27 (⚠ + x/text-таблицы Unicode 17 тем же тулчейном — строка 119/реестр 108) · проба провода исполнена и 117 закрыта (D39.97), конфиги 112 залендены — платные прогоны разблокированы. > - **Стек:** draft deepseek-v4-flash thinking-ON (банкнота: ⚠ канал в shipping-c1 НЕ включён — жив ран-локальным конфигом книги, строка 140) **⚠0731** → терминолог (та же модель, батчи, экран `target_script`) → editor deepseek-v4-pro БИЛИНГВ ИНТЕРИМ (⚠ эксп-21/D39.117: топология ПОДТВЕРЖДЕНА — второй проход покупает +2.5 ошибки/ед. при неразличимости жильцов dspro/glm-5, прежний вывод exp20 «на dspro отрицателен» опровергнут; закон-блок ОБЯЗАТЕЛЕН — строка 134; величины предварительны до чистого zh-среза; ⚠ вендор АНОНСИРОВАЛ смену маппинга эффорта pro «early August 2026» — вахта в реестре 108; glm-5 резерв) → судья gemini (Ф2, полигон); канал B Mistral+grok; ~$0.85/ранобэ (D30.4) — 112 залендена (D39.97), пере-калибровка цены при следующем платном прогоне. -> - **ЕДИНЫЙ БЭКЛОГ — секция «Бэклог» ниже** (одна таблица, единственный трекер; каждая петля обязана иметь диспозицию: решено / отложено-с-записью / отклонено; ведёт оркестратор). **СЧЁТ ОЧЕРЕДИ на 08.08 (D39.118, скриптом по таблице; обновлять при каждом лендинге):** всего **131** строка · зона бэкенд **67** строго (ячейка ровно «бэкенд») / **88** широко (ячейка содержит «бэкенд»; ⚠ колонки счётчик читает С КОНЦА — испр. D39.116) · **блокеров очереди 0**, платные прогоны разблокированы · «скоро» **35** (2·4·18·46·49·55·99·100·101·103·108·109·113·114·116·118·119·120·121·125·126·134·135·136·138·139·140·**142**·143·145·148·**149·150**·154·157) · гейт-строки эксп-22: 55·153 (плюс 150 — руки владельца); строка 5 — остаток гейчен ре-пробой 74 (D39.102 п.2); остальное «когда-нибудь». ⚠ **Счёт — НИЖНЯЯ граница долга, а не потолок:** трекер содержит только замеченное, и 02.08 дважды за $0 свежие глаза вытащили из кода дефекты, которых в нём не было (релей фронта → строки 99/100/102; приёмочное ревью → третий шов `repair.go:384` в 104 и честность `quality.go:436` в 105). Лента ✅ выше — про ЗАКРЫТЫЕ паки, читать её как «долга нет» неверно (разбор — вопрос владельца 02.08, D39.89). +> - **ЕДИНЫЙ БЭКЛОГ — секция «Бэклог» ниже** (одна таблица, единственный трекер; каждая петля обязана иметь диспозицию: решено / отложено-с-записью / отклонено; ведёт оркестратор). **СЧЁТ ОЧЕРЕДИ на 09.08 (D39.122: строки 99·100·101·125·145 закрыты лендингом пака блокеров; 160–162 заведены по research/27, 163–164 — приёмкой; скриптом по таблице; обновлять при каждом лендинге):** всего **131** строка · зона бэкенд **65** строго (ячейка ровно «бэкенд») / **88** широко (ячейка содержит «бэкенд»; ⚠ колонки счётчик читает С КОНЦА — испр. D39.116) · **блокеров очереди 0**, платные прогоны разблокированы · «скоро» **32** (2·4·18·46·49·55·103·108·109·113·114·116·118·119·120·121·126·134·135·136·138·139·140·**142**·143·148·**149·150**·154·157·**160·161**) · гейт-строки эксп-22: 55·153 (плюс 150 — руки владельца); строка 5 — остаток гейчен ре-пробой 74 (D39.102 п.2); остальное «когда-нибудь». ⚠ **Счёт — НИЖНЯЯ граница долга, а не потолок:** трекер содержит только замеченное, и 02.08 дважды за $0 свежие глаза вытащили из кода дефекты, которых в нём не было (релей фронта → строки 99/100/102; приёмочное ревью → третий шов `repair.go:384` в 104 и честность `quality.go:436` в 105). Лента ✅ выше — про ЗАКРЫТЫЕ паки, читать её как «долга нет» неверно (разбор — вопрос владельца 02.08, D39.89). > - Архивы хроники: `archive/PROGRESS-2026-07-04-10.md` (D31) · `-10-13` (D39.6-гигиена) · `-13-25` (стройка паков 11–16, rerun2) · **`-25-31` (паки 17–20 · мини-прогон · полигон-пакеты 5–8 · ToS · холодный прогон; D39.26–58)** · **`-08-01-02` (сессии №9/№10: общность · качество банка · coldrun-b · открытие фронта/платформы; D39.59–90, срез D39.105)**. Записи ниже — живой хвост (№11+, эра D39.91+). ## Бэклог (ЕДИНЫЙ, собран 26.07, актуализация 04.08 D39.99/D39.101; правки — только через оркестратора) @@ -93,7 +94,6 @@ | 142 | **Механизм «гейты флагают → точечная починка по флагу» как прод-путь**: exp20 §3.3 — $0.0002/дефект, 12/12 (при ИДЕАЛЬНОЙ детекции); механизм конкурентов (research/22:179), у нас петля пака-16 за `enabled:false` (строка 13); детекторы «выдуманное слово»/«инверсия» прототипированы эксп-21. **ДИСПОЗИЦИЯ D39.117:** ОТКЛОНЁН как замена редактора (`C2/A_law` −3.75 Holm 0.0006; «$0.0002 чинит всё» мерилось при идеальных флагах и при реальной детекции пало), ПРИНЯТ формой «механический ПРЕ-ГЕЙТ перед редактором» — брак 5 клеток против 10/23 при половинной цене ($0.00298/$0.00602); ремонт ТОЛЬКО с банком (без банка ломает канон 0.917→0.895) | владелец → бэкенд | скоро (форма решена D39.117; стройка — бэкенд-промтом по команде владельца) | бэкенд-промт | D39.108, **D39.117** | | 143 | **Абсорбция норматива судейства в `09-pilot-protocol.md`**: разброс инструмента ДО замера · объявленный порог различимости · MQM-lite/error-span первичен, pairwise только для стиля · декой обязателен · запрет судьи семейства жильца арма | полигон/оркестратор | скоро ($0) | правка дока | D39.108, exp20 §7 | | 144 | **Вахта: grok `reasoning_tokens` ВНЕ `completion_tokens`** — формула цены харнессов может недосчитывать (до $0.0325 на exp20); вендор-сверка биллинга xAI при следующем прогоне с grok | полигон | когда-нибудь (следующий xAI-прогон) | вендор-чек | D39.108 (приёмка) | -| 145 | **Движок обязан принимать потолок НА ПРОГОН, а не только из `book.yaml`** — сегодня флага/ENV нет вовсе (проверено: `tmctl` таких флагов не имеет, значение приходит только через `Ceilings.BookUSD`, `stagerun.go:480,505`). Пока так, платформа для управляемого потолка (строка 126) вынуждена ПРАВИТЬ конфиг книги — то есть пользовательское число становится постоянной записью в данных движка, что смешивает зоны против D39.81/D39.85. Форма: аргумент прогона (флаг или ENV), потолок остаётся состоянием ПРОГОНА. Смежное: `Ceilings` в `BriefHash` не входит (`book.go:280-297`) ⇒ ни снапшот, ни ре-билл не двигаются — цена правки нулевая | бэкенд | скоро (денежным промтом, со строками 135–139) | малое касание CLI + проводка | D39.110, приёмка платформы D39.109 | | 146 | **Два техдолга движка без строки трекера** (свип 08.08): `prices_checked` (120 д) гейтит read-only `report` и $0-resume — свежесть цен проверять только на ПЛАТНОМ пути (`03-implementation-notes:244`) · `cache_ttl` ↔ `cache_write_per_m`: цена записи в кэш задана под ОДИН TTL (5 м), при другом TTL расчёт врёт (`:246`). Оба жили только в справочнике якорей, то есть вне единственного трекера | бэкенд | когда-нибудь | малое касание | свип доков 08.08 | | 147 | **Транспортные частичные из research/21 без носителя** (свип 08.08): п.16 HTTP/1.1-escape на последней попытке как ДИАГНОСТИКА тихого усечения 2–8k [PARTIAL, GATED] · п.17 assistant-prefill continuation на `finish=length` вместо ре-рана всего вызова [PARTIAL, DEFER]. Оба помечены в ресёрче как отложенные и ни в одной строке трекера не появились | бэкенд/полигон | когда-нибудь | замер → решение | свип доков 08.08, research/21 §16–17 | | 148 | **Приложение А контракта потеряно инцидентом 08.08 и подлежит восстановлению** (D39.113): карта «вердикт движка → продуктовая фраза → ступень» была заполнена №14, откатилась к ЗАГОТОВКЕ с плейсхолдерами. Текст реконструкции держит оркестратор №15; решение владельца — брать реконструкцию черновиком С БАННЕРОМ «требует сверки» либо оставить заготовку и заполнять заново. Карта нужна фронту для замечаний (Ф-14) | владелец → оркестратор | скоро | решение владельца | D39.113, свип доков 08.08 | @@ -109,6 +109,11 @@ | 157 | **Пере-подтвердить `family_max_members=24` и `families_held`** на длинной книге (24 калиброван на дистилляции в 150 поверхностей; кап эмиссии 200 + реверс-секция дают другой список); если `families_held` велик — ослабить гард до «родство по любому члену юнита» вместо «по корню серии» | бэкенд | скоро (гейт: первый холодный прогон, §6 отчёта) | замер → решение | **D39.118**, отчёт §7 п.7 | | 158 | **Уверенность не гейтится данными пары**: канал снимает третью колонку у любой пары, чей промпт её попросит; у пары с ДВУХпольным промптом узкий риск «Пик␣␣99» (число ≤100 за двойным пробелом уедет в conf); при нужде — дата-декларация пары | бэкенд | когда-нибудь | дата-декларация | **D39.118**, отчёт §4 п.6/§7 п.8 | | 159 | **Durable-имена эвиктнутых** (`retrieval_state.evicted_detail` = миграция; сегодня лог, и приёмка ратифицировала «этого достаточно») — заводить только при доказанной потребности, одним касанием с идемпотентностью миграций 49а | бэкенд | когда-нибудь (гейт: доказанная потребность) | миграция с 49а | **D39.118**, отчёт §7 п.2 | +| 160 | **Структура глав, Этап 0** ($0, wire-нейтрально): `titleRaw` в манифест (subtitle уже добывается `matchHeaderLine` и выбрасывается) + provenance у heading + тип chapter/fragment + вердикт структуры (бамп manifestVersion) · **дыра Р6**: projectRebill слеп к контент/позиционной оси (rebill.go:32-35) — ревизия структуры перекупила бы хвост МОЛЧА, закрыть read-path-расширением · слой пользовательских правок отдельным сайдкаром | бэкенд | скоро (кандидат в ближайший бэкенд-пак; Р6 — приоритет пачки) | пак | research/27 §6 Этап 0 | +| 161 | **Структура глав, Этап 1 «большой перекрой»** (единственное дешёвое resnapshot-окно — пока платная книга одна, ДО интейка): IR + формато-адаптеры (epub nav/NCX, fb2) + индуктор с валидаторами и вердиктами + третий версионный план паттернов (не в снапшот волн) + развязка чанкера от глав + банк-окна на chapter-ID + контент-адресуемый resume (долг D15.2) + формула Chapter.ID «окно» + роль title (мини-сессия названий: редакторский слот · инъекция банка · двухфазно вокруг подписи) + аддитивное расширение контракта 14; жизненный цикл дерева — research/27 §3а (решение владельца 09.08) | бэкенд (+фронт/платформа аддитивно) | скоро (после эмиттера; дизайн-ратификация владельца ДО стройки) | ратификация → пак(и) | research/27 §3/§5/§6 | +| 162 | **Структура глав, Этап 2**: in-band метки глав в потоке перевода (третий эшелон детекта + коррекция структуры по ходу черновой волны, §3а п.3) + канон-гейт названий ($0) + миграционные карты якорей (same/moved/split/merged/gone/new) + калибровочный корпус детекта в CI | бэкенд + полигон | когда-нибудь (после Этапа 1) | пак + корпус | research/27 §5/§6 Этап 2 | +| 163 | **Версии модулей x/text|x/net двигают нарезку МИМО всех ключей** (NFC в NormalizeSource — таблицы модуля, не тулчейна; epub-токенайзер): бамп `go get -u` тихо меняет нарезку при свежих на вид манифесте/чекпойнтах/снапшоте — класс всей чекпойнт-плоскости, унаследован манифестом; вплести версии модулей (debug.ReadBuildInfo) в NormVersion/chunkerVersion либо явный запрет бампа без `--resnapshot` | бэкенд | когда-нибудь (до второго прогона книги) | малое касание | **D39.122**, приёмка (охотник №1) | +| 164 | **Rebill-проекция `status` с nil baseMemory завышает RebillUSD**: read-путь не материализует base-банк → renderedContentHashes даёт пустую инъекцию → repin недоопознан, строка считается re-pay (консервативная сторона, но противоречит комменту «status can never quote a different number than translate»); материализовать base-банк на read-пути либо смягчить клейм | бэкенд | когда-нибудь | малое касание | **D39.122**, приёмка (охотник №1); preexisting | | 49 | **Этапы Б+В спеки D15.2** (content-addressed resume / `guard_hash` — D39.31 сознательно не строил) ; этап В = tmctl export-контракт D29.1 (annot-v1 · цвет-мап+rollup · политика красных fail-closed) + операторский протокол-документ релиза гендер-твиста (D5.1): сам механизм УЖЕ построен — until_ch-правка → точечный re-edit со сметой и $0-пере-пином (`repin.go`/`rebill.go`, D39.42 п.5/D39.45) | бэкенд | скоро (ПОДТЯНУТА D39.81: annot-v1 = источник замечаний для фронта — критический путь подключения) | отдельное решение (annot-половина — по контракту 14, D39.99) | D39.34(4), D33 п.5, D39.81 | | 49а | **ALTER-шаги миграций v8–v14 не идемпотентны вопреки шапке `migrate.go:9-11`** (полу-применённая БД не сходится; счётчик версий скрывает) — находка критика полноты пака-19 | бэкенд | когда-нибудь | отдельное решение | D39.56, PACK19_BUILD §6.9 | | 50 | F3-остаток идемпотентности | бэкенд | когда-нибудь | отдельное решение | D39.34(4) | @@ -137,12 +142,8 @@ | 94 | **Продукт-механика входа и прогона** (V2, ПТ-16..18): абьюз/misuse-прескрин дёшево и точно ДО трат токенов (H8) + UI-контракт отказа (проект-решение D39.80) · лимит размера входа настройкой (H7) · продукт-кнопки «стоп/продолжить» поверх построенных graceful stop и redrive, абьюзивный прогон НЕ продолжать (H10); API-форма — по контракту 14 (D39.99; статус отказа — открытый К-9) | бэкенд | к подключению фронта (ПОДТЯНУТА D39.81; лимит можно раньше, к пилоту) | отдельный пак при Ф3-препе (контракт 14 ратифицирован) | START_PROMT V2, H7/H8/H10, D39.80, D39.81 | | 96 | **→ переехала в `platform/BACKLOG.md` П-1** (зонные бэклоги, D39.84: HTTP/SSE-слой, обвязка, аутентификация, порядок деплоя); здесь остаётся ратифицированным сам ИНВАРИАНТ D39.81 — движок = процесс-на-прогон (EXCLUSIVE flock `store.go:43`), сервер в backend НЕ пишется | платформа | — | зонный бэклог | D39.81, D39.84 | | 97 | **→ переехала в `platform/BACKLOG.md` П-2** (глобальный брокер рейт-лимитов; гейт «до второго параллельного пользователя» зафиксирован там же) | платформа | — | зонный бэклог | D39.81, D39.84 | -| 99 | **Пофазный прогресс `draft N/M ∥ edit N/M`**: unit = done только при всех draft-строках членов + edit-строке (`status.go:328-331/365`) — индикатор «готово N/M» стоит 0% ВСЮ черновую волну; данные уже есть (`chunk_status` — строка-на-стадию, `migrate.go:111-126`; status уже делит стадии по волнам `status.go:332-334`) — вывод пофазных счётчиков в StatusReport/JSON без миграции схемы | бэкенд | скоро (БЛОКЕР контракта API, D39.99) | малое касание read-model | релей фронта 02.08, D39.84 | -| 100 | **Персист манифеста глав/чанков** (+ `chunker_version` из снапшота + хеш источника): каждый read-вызов заново ингестит и режет исходник (`status.go:177-195` bookChunks; redrive — дважды за вызов; замер фронта 1.42–1.51 с CPU на книге 23 МБ, умножается на число книг) — нужен под экран разбора и дерево глав; дизайн обязан пережить смену чанкера (`--resnapshot`) | бэкенд | скоро (БЛОКЕР контракта API — стабильный id главы, D39.99) | отдельное решение (дизайн по контракту 14, D39.99) | релей фронта 02.08, D39.84 | -| 101 | **Машиночитаемая таблица подписи банка**: кап 20 — только stdout (`render.go:98`), полная таблица — текстовый сайдкар `.bank-stop.txt`, JSON/структурного выхода нет (mined-signature.yaml — сид-дельта без freq/spread/evidence); нужна фронту S5; помнить ловушку «подпись ≠ UPDATE» (банк пересобирается: `seeding.go:18/110`, `glossary.go:158-162` — контракт подписи пишет в файлы-источники) | бэкенд | скоро (БЛОКЕР контракта API — экран подписи, D39.99) | малое касание (форма — по контракту 14, D39.99) | релей фронта 02.08, D39.84 | | 102 | **Приём внешнего trace-контекста в tmctl**: TraceID минтится заново каждым вызовом (`main.go:67-70`; внутри вызова трасса цельная, `request_log.trace_id` есть) — прогон, запущенный платформой, должен быть одной трассой | бэкенд | когда-нибудь (Ф3, с 96) | малое касание | релей фронта 02.08, D39.84 | | 103 | **NDJSON-эмиттер прогонных событий со стабильным словарём** (машинный канал живого статуса для платформы, `research/23` §2): NDJSON-логи уже есть (`LOG_FORMAT=json`, авто-оси trace/book/chapter/stage/role — `obs/logging.go:15-54`), но сообщения прозой, version-хендшейка нет, а **событие потолка отсутствует вовсе** (деньги только в тексте ошибки `stagerun.go:472-494`); нужен словарь-enum + version-хендшейк первой строкой (образец terraform -json: минор = ignore-unknown, мажор = reject) + событие потолка; все границы — единичные call-sites в pipeline рядом с готовыми slog; человеческие логи уже на stderr — stdout-дисциплина соблюдена. **Форма ратифицирована D39.106 (research/25 §эмиттер):** `events.jsonl` каталога книги = outbox-проекция коммитов SQLite (та же транзакция, что чекпойнт) · fsync/seq/hello-версия · события денег и потолка · развилка формата (JSONL против пофайловых/CRC-фрейминга) — решить дизайном · `status --json` отдельным каналом (ремонт, тот же flock) | бэкенд | скоро (промт бэкенду — по команде владельца, D39.107 п.3) | стройка по D39.106 + research/25 | research/23, D39.85, **D39.106** | -| 125 | **Артефакт экспорта БАНКА** (полный банк тремя статусами — канал чтения `/bank` контракта 14): строка 101 это таблица ПОДПИСИ (кап 20 stdout, `cmd/tmctl/render.go:98`), не экспорт; сам банк в приватном SQLite, платформе закрыт (D39.85) — нужен экспорт-артефакт движка на границе стадии/стопе | бэкенд | скоро (БЛОКЕР контракта API, D39.99) | дизайн + малое касание export | ревью контракта 04.08, D39.99 | | 126 | **Потолок прогона — УПРАВЛЯЕМЫЙ, решение владельца 07.08: настройка в интерфейсе фронта, шкала от минимума (ноль выбрать НЕЛЬЗЯ, подсвечен серым) до максимума баланса аккаунта.** Форма снимает конфликт с Р7 бесплатно: максимум = баланс ⇒ движок всегда получает положительное число и состояния «выключено» не существует, валидатор `book.go:250` не правится. **Три следствия, ратифицированы формой (D39.110):** (а) **единица — НЕ доллары**, а главы (или % баланса): тогда минимум объясняет себя сам («одна глава»), а D39.84 цел — читатель не видит структуру расходов; доллары на пользовательском экране = осознанный амендмент D39.84, не побочный эффект виджета; (б) это потолок ПРОГОНА ⇒ в `book.yaml` он не пишется, движку нужен приём потолка на прогон — строка 145; (в) **максимум шкалы = ДОСТУПНЫЙ ОСТАТОК, то есть `Balance` как есть** — ⚠ испр. 08.08 (D39.115): формулировка «баланс минус открытые холды» была ОШИБКОЙ приёмки, вычитанием дважды. Холд — это дебет в момент взятия (`credits.go:179`), поэтому баланс уже без него; замерено: $10 грант + $1 холд = `Balance` 9, `Reserved` 1, «минус Reserved» дало бы 8. Спека несёт верную форму: `max_chapters` приходит УЖЕ подрезанным и по остатку, и по непереведённому хвосту книги, клиенту подрезать второй раз ЗАПРЕЩЕНО. **НОСИТЕЛЬ ПЕРЕСЧЁТА НАЗВАН (добор после верификации 07.08):** «сколько глав влезает в доступный остаток» считает ПЛАТФОРМА (по оценке движка) и отдаёт фронту ГРАНИЦЫ шкалы и выбранное значение в главах; сама формула наружу не выходит. Ручки под это в контракте НЕТ — сегодняшний `GET /v0/usage` несёт только процент, поэтому поверхность объявляется при правке спеки вместе с созданием прогона (работа П-1 + S3), и до её объявления фронт максимум шкалы взять неоткуда. Прежнее тело строки (механизм поднятия потолка для resume после ceiling-стопа: движок продолжает «once the ceiling is raised», `stagerun.go:488-489`) — половина этой же работы. Продуктовое слово стопа — К-8 у владельца | платформа/фронт/бэкенд | скоро (форма решена, стройка — промтами) | дизайн-вход П-1 + правка спеки + строка 145 | ревью контракта 04.08, D39.99, **D39.110** | | 127 | **Устаревший DDL-коммент `glossary.source`** (`migrate.go:193`: «seed|ruby|auto», фактические пути записи — seed/ruby/mined, `auto` не пишет никто; фантом уже утёк во фронт-доки дважды) — поправить коммент | бэкенд | когда-нибудь (попутно любым паком store) | однострочная правка | ревью контракта 04.08, D39.99 | | 105 | **Роль `classifier` мис-вердиктится как эхо + честность двух новых строк наблюдаемости (D39.86).** (а) `classifyOutput` даёт поблажку `SourceEchoExpected` только `roleTerminologist` (`chunkrun.go:37`), а ответ классификатора по формату состоит из исходных ханьских термов ⇒ доля исходного письма 0.20–0.29 > порога 0.15 на каждом батче: живой артефакт пробы 6/6 несёт `verdict=cjk_artifact` при `bad_lines=0`. Ущерб узкий — `runBankRoleBatches` кладёт `run.texts[i] = att.text` без проверки диспозиции (`terminologist.go:603`), посчитанные эхо-ставки (`quality.go:313-321`) берутся из `chunk_status`, куда бэнк-путь не пишет ⇒ портятся только строки `tmctl report`/лога; **латентно** (`classify_types` выключен во всех шиппинг-конфигах). ⚠ Цена дисциплинированного фикса, которую надо знать заранее: бамп `classifierVersion` (литерал, сам не двигается) меняет payload снапшота → snapshot id → `RequestHash` ⇒ промах ВСЕХ чекпойнтов книги, то есть `--resnapshot` + пере-покупка. (б) тем же касанием — честность поверхностей: строка `VOICE` берёт `rules=` из конфига ВРЕМЕНИ ОТЧЁТА (`quality.go:436`), поэтому включение `gates.voice` после прогона печатает «измерено, чисто» для неизмеренной книги (воспроизведено); и она печатается при выключенном гейте, если `SpoilerLeaks>0` (дизъюнкция — поведение верное, формулировка отчёта/коммента неверна); знаменатель `BANKNOTE` = `len(states)` включая призрачные строки `retrieval_state`, тогда как соседний агрегатор их фильтрует (`quality.go:259-260`) ⇒ после пере-чанковки покрытие занижается | бэкенд | когда-нибудь (ближайшее касание банка/наблюдаемости; (б) — дешёвая часть, вердиктов не двигает) | малое касание + вердикт-двигающая половина (а) через СТОП-решение | D39.86 | @@ -220,12 +221,14 @@ S3 упёрлась в замок (контракта нет) — по слов ## Бэкенд -**⟶ Свежее состояние бэкенда: ФИКС-ПАК БАНКА принят и заленден (D39.118, `bccf2d8`; ревью-шапка отчёта первична); АКТИВНЫЙ промт — `docs/BACKEND_CONTRACT_BLOCKERS_SESSION_PROMPT.md` (блокеры контракта 99/100/101/125/145, D39.119; запуск = владелец); эмиттер 103 — следующим промтом; точка входа `make battery`; ниже — пинг пака банка и хроника до №11.** **⟶ Приёмка №11 (D39.91, 02.08): пак ниже ПРИНЯТ, код заленден `553f1a3`; поправки приёмки (метка flash+pro · хеш по эквивалентности · `capability.go:78`) — в ревью-шапке отчёта и D39.91.** +**⟶ Свежее состояние бэкенда: ПАК «БЛОКЕРЫ КОНТРАКТА» ПРИНЯТ и заленден (D39.122, `0e69bc1`; ревью-шапка отчёта первична; тестов 749); до него — ФИКС-ПАК БАНКА (D39.118, `bccf2d8`). АКТИВНОГО промта НЕТ — следующий: эмиттер 103 + деньги шва (не выдан; эмиттер-сессии перед стартом читать PD-95/105/79/99/107/60/61 и D39.106); точка входа `make battery`; ниже — пинги паков и хроника.** **⟶ Приёмка №11 (D39.91, 02.08): пак ниже ПРИНЯТ, код заленден `553f1a3`; поправки приёмки (метка flash+pro · хеш по эквивалентности · `capability.go:78`) — в ревью-шапке отчёта и D39.91.** **ФИКС-ПАК БАНКА «КЛАСТЕРЫ + НАБЛЮДАЕМОСТЬ» (строка 128 + хвост 129 + $0-минимум 130) — ИСПОЛНЕН 08.08, ждёт приёмки.** Отчёт: [archive/reports/BANK_CLUSTER_FIXPACK_2026-08-08.md](archive/reports/BANK_CLUSTER_FIXPACK_2026-08-08.md). Деньги $0 (платных вызовов ноль), дерево НЕ коммичено, чужая зона (`eval/`) не тронута. Сделано: **G1** — `DetectFamilies`+`MergeUnits` рядом с `DetectSeries`, батчи ЮНИТАМИ (серия ⊕ семья ⊕ containment-пара), параметры семей — данные скрипта; критерий промта выполнен (линейка `一转…十二转` держится одним юнитом, равнодлинные серии не регрессируют); на реальном корпусе банка coldrun-a — 150 поверхностей → 16 юнитов, наибольший 20, отказов 0 · **G2** `ConsolidationConflicts` (прогон против САМОГО СЕБЯ — класс, которого канон-проверка не видит) · **G3** стоп-таблица += сигналы/`invented`/уверенность/конвенций/противоречия, третье поле уверенности в промпт zh-ru, alias-джойн подписной карты (карта строится из тех же кандидатов, что таблица), имена эвиктнутых, per-batch «спросили/ответили» + WARN на пустой ответ рендера И классификатора, честный `TestTerminologistEmptyReplyIsLoud` · **G5** голос по фолду целевой формы при показе сырых форм + честная агрегация Via · **129** ре-джойн многословного dst в парсере банкноты · **130** дифф авто-банка с именами выпавших (без накопительного мерджа — граница владельца) · опции: `BanknoteTruncated` (колонка была структурно недостижима) и мятый `⟦TM-NO-DST⟧`. **Два адверсариальных ревью исполнены** по санкции владельца — одиночное и МНОГОАГЕНТНОЕ (13 опусов: шесть линз → опровергатели → синтез, $0). Второе нашло четыре major, которых не увидели ни соло-проход, ни первое: **измеренный регресс парсера банкноты на замороженном эталоне coldrun-a** (реплей 56 блоков: −6 строк, +0, при неизменном `parse_fail` — тихо; трактовка «неизвестное последнее поле = часть перевода» портила строки с типом вне словаря и роняла обратный порядок) · **перевод АЛИАСА становился dst терма и ехал на провод** на $0-пути при выключенном гейте — ровно тот вред, ради которого заводился `Via` · **третье поле не-число молча приклеивалось к переводу** («наставник 95%» как канон при `bad_lines=0`) · **`Held` считался по попытке**, и отчёт трижды называл ценой гарда несуществующий случай. Плюс: ordinal-очередь ревью (единственная разрешённая D39.102 форма уверенности) была обещана и не реализована; WARN строки 130 бил ложной тревогой по штатному циклу подписи; `bankParseVersion` не был поднят при изменённом правиле парса. Всё закрыто; одиннадцать мутаций пере-проверены исполнением и убиты. Первое ревью: нашло 1 major (гард «серия сильнее семьи» растворял семью ЦЕЛИКОМ, включая не-серийных членов — та же химера §B4 через гард, который её должен был предотвращать), переусердствовавший тест родства (5 ложных придержаний на корпусе), задвоенный счётчик, `Conf=0` неотличимый от «нет уверенности», мёртвый параметр парсера и 4 выжившие мутации; **всё закрыто, мутации пере-проверены исполнением и убиты**. **`make battery` зелёная ЦЕЛИКОМ** (build·vet·fmt·**lint**·test) + `-race` на затронутых пакетах. ⚠ Первая редакция заявляла «батарея зелёная», прогнав только build/vet/test — гейт lint был КРАСНЫЙ (3× errcheck), поймано приёмкой. Фикс-лист приёмки исполнен целиком: блокер lint · **MAJOR: WARN строки 130 был структурно МЁРТВ в проде** (`ownerHandled` получал сырой seed, а тот со второго прогона содержит собственные строки движка ⇒ дифф всегда пуст; risk-2 self-exclusion, который файл обходит в двух других местах; мой тест это маскировал рукодельной map) · две дыры пинов на space-run-форме · `bankParseVersion` теперь реально логируется с прогоном · три тихих отказа канала семей (опечатка в script-колонке, пол-таблица, рассинхрон словаря типов с `nickname`) · неназванная девиация ранжирования дописана в §4. тестов 685 → 717 (+33, удалён 1 вместе с недостижимой веткой, которую он пинил). **Фолд (ИСПРАВЛЕНО ревью):** ДРАФТ-волна не двинулась (голден прошёл без пере-захвата, `Source:"mined"` исключён из BASE), а **edit-волна двигается bank-only** — пак меняет поле dst дельты, значит `memory_version`; сдвиг громкий (`--resnapshot`) и репинится за $0 для единиц без терма, но для частотного терма это почти вся книга ⇒ **решение владельца, вопрос в §7 отчёта**. ⚠ **Девиация, требующая слова оркестратора:** данные семей положены НЕ в `internal/lang/data/script-series.txt`, как предлагал промт, а в отдельную bank-плоскость `internal/lang/bankdata/` — иначе `EmbeddedVersion` сдвинул бы снапшот ОБЕИХ волн (и без права на $0-репин), что прямо противоречит §3 промта. **§7 отчёта разделён на ДВА списка:** три пункта требуют решения (принимается ли сдвиг edit-волны bank-only · durable-хранение имён эвиктнутых = миграция · двойной пробел в `coldrun-a`), и пять предложены СТРОКАМИ БЭКЛОГА для оркестратора (одноруновая голова семьи — включается строкой данных, гейт замер · скрипт-слепой резолв семей/серий на кану и хангыль — долг общности к преп ja→ru · `banknote_truncated` как пол, а не счёт · пере-подтверждение `family_max_members=24` и `families_held` на длинной книге · уверенность не гейтится данными пары). В таблицу бэклога сам не писал — её ведёт оркестратор. **→ ПРИЁМКА №15 (08.08, D39.118): ПРИНЯТ и заленден `bccf2d8`.** Вопросы §7 закрыты все (сдвиг edit-волны принят словом владельца · миграция не заводится, строка 159 · двойной пробел закрыт замером 0/585 внутренних); девиация bankdata-плоскости и инверсия ранжирования ратифицированы; строки 128/129 закрыты, 130 пере-скоуплена, 154–159 заведены. Поправки приёмки — ревью-шапка отчёта (сдвиг и при выключенном гейте · classify-чекпойнты перекупаются · зазор зеркальной фикстуры принят риском). +**ПАК «ДВИЖКОВЫЕ БЛОКЕРЫ КОНТРАКТА» (строки 99 · 100 · 101 · 125 · 145) — ПРИНЯТ И ЗАЛЕНДЕН 09.08 (D39.122, код `0e69bc1`; приёмка тремя раундами, ревью-шапка отчёта ПЕРВИЧНА над телом пинга; финальные формы артефактов — в пинге платформе).** Отчёт: [archive/reports/CONTRACT_BLOCKERS_2026-08-08.md](archive/reports/CONTRACT_BLOCKERS_2026-08-08.md). **Деньги $0** (платных вызовов ноль: read-пути на стенде + мок-провайдер), дерево НЕ коммичено, чужие зоны (`eval/`, `platform/`, `frontend/`) не тронуты, эталон `coldrun-a` только читался (для замеров сделана копия в песочнице). **`make battery` EXIT=0 ЦЕЛИКОМ** (build·vet·fmt·**lint 0 issues**·test -race); тестов **717 → 749** (+32, удалённых 0, дифф `^func Test` посчитан исполнением); голден без пере-захвата, снапшот и `RequestHash` пак не двигает. **АДВЕРСАРИАЛЬНОЕ РЕВЬЮ ИСПОЛНЕНО (§9 отчёта): два независимых верификатора, ОБА СЛЕПЫЕ к отчёту** (один судил промт против диффа, второй охотился ВНЕ карты отчёта) — **оба независимо вынесли один и тот же несущий дефект, которого соло-проход не увидел.** Семь находок закрыто, каждая правка пере-проверена УБИЙСТВОМ МУТАЦИИ: **(А)** ключ годности манифеста снимался ВТОРЫМ чтением исходника уже после нарезки — файл, переписанный внутри окна ~1.4 с, давал документ со СТАРОЙ структурой под НОВЫМ хешем, проходил валидацию вечно и не опознавался как протухший (опровергало моё же заявление «состояния «подан устаревший знаменатель» не существует по построению»); теперь отпечаток снимается ДО чтения и пере-снимается ПОСЛЕ, расхождение = манифест не пишется · **(Б)** `units[]` есть проекция `outputUnits`, ветвящейся по форме пайплайна, а формы в ключе не было: снятие редакторской стадии не двигало ни одного входа ⇒ дерево единиц валидировалось, описывая группы, которых прогон не отгружает (добавлен `shipping_wave`) · **(В)** `variants[]` машинной таблицы уезжал готовой строкой «` × (proposed for )`» — нарушение правила, объявленного доккоммантом того же DTO двумя абзацами выше (теперь `{dst,chunks,via}`, метку человеческим таблицам собирает `Label()`, их байты не изменились) · **(Г)** машинная таблица писалась только при НЕПУСТОЙ дельте, то есть после подписи всех термов на диске оставался байт-равный файл прошлого прогона и экран подписи пере-предлагал бы подписанное (пустая дельта теперь переписывает её пустой; текстовый сайдкар намеренно не трогал — принят D39.118) · **(Д)** доккоммент `translate` заявлял «`--ceiling-usd` caps what THIS run may spend», тогда как леджер сравнивает потолок с НАКОПЛЕННЫМ committed+reserved книги — для платформы это разница между «дойти до $X» и «потратить $X» · **(Е)** возвращаемый `withText` оба вызывателя выбрасывали (гард из доккоммента в коде не существовал), плюс `Status` без манифеста резал книгу ДВАЖДЫ — вторым результатом стал провайдер чанков с текстом · **(Ж)** `loadManifest` не проверял внутреннюю согласованность, а счётчики идут прямо в `make(...)`: `chunks_total:-1` в отредактированном руками сайдкаре ронял `tmctl status` паникой. Плюс `f.Sync()` перед rename. ⚠ К утверждениям самого ревью применена та же дисциплина: два его пункта пере-проверены и НЕ приняты как дефекты (экспорт банка на exit-2 срабатывает — `TranslateBook` возвращает nil при флагах; «перф-клейм не воспроизводится» — верификатору было запрещено писать файлы, а замер требует записать манифест). **Формы артефактов (названы явно, приёмке пере-ранить):** **99** — поле `progress:{draft:{done,total},edit:{done,total}}` в `StatusReport` И в КАЖДОМ элементе `chapters` (цена К-10 контракта), плюс строка `Phases (units resolved by wave)` в человеческом выводе; единица обоих счётчиков — выходной ЮНИТ (общий знаменатель), `done` = РАЗРЕШЕНО волной (ok/flagged/skipped), иначе индикатор не дотягивает до собственного знаменателя на книге с одним флагом; волны, которой нет, `total:0`; ⚠ следствие: `edit.done ≥ done` ровно на гейт-флагнутых · **100** — сайдкар `.manifest.json` + новая $0-команда `tmctl manifest` (дерево нужно ДО прогона: `not_started`); **id главы = хеш ИНГЕСТИРОВАННОГО текста главы** — переживает пере-чанковку, правку правила заголовка и правку/вставку СОСЕДНЕЙ главы (номера сдвигаются, id нет); годность решает КЛЮЧ (исходник+чанкер+сегментация+форма пайплайна+пак+embed+norm), протухший = откат на полную нарезку, состояния «подан устаревший знаменатель» нет по построению · **101** — `.bank-stop.json`, ПОЛНАЯ таблица теми же полями, что текстовая (вкл. Signals/Invented/Conf/Conventions/Contradicts), `conf: null` для «роль промолчала» (≠ 0), списки списками; семантику подписи не трогал — это ЧТЕНИЕ · **125** — `.bank.json`, весь банк тремя статусами, `id` из ключа уникальности (НЕ автоинкремент `glossary.id` — он пере-указывает на каждой пересборке банка), пишется на банк-стопе (оба исхода), на завершении прогона и сразу после сида · **145** — флаг `--ceiling-usd` (и у `translate`, и у `redrive` — тот тоже тратит), только этот прогон, `book.yaml` не пишется, ноль/отрицательное/нефинитное = отказ ЗАПУСКА, валидатор Р7 не тронут. **ЗАМЕР строки 100 (стенд 23 МБ, 2283 раздела, 4276 юнитов, 5071 чанк; команда — в §2.4 отчёта; ПОСЛЕ фикс-раунда ревью):** `status` wall **1.42/1.49/1.36 c → 0.07/0.08/0.07 c**, `export` **1.39/1.40/1.40 c → 0.10/0.06/0.07 c**, `report` **не изменился** (граница, §2.4); проекции с манифестом и без него сверены `cmp` — байт-равны. **Цена строки 145 предъявлена ИСПОЛНЕНИЕМ:** тест рендерит payload обоих волновых снапшотов и требует отсутствия потолка, плюс двигает `Ceilings` и требует неизменности `BriefHash`; приор промта подтверждён — resume после ceiling-стопа с бо́льшим аргументом доводит книгу до конца ровно за 6 вызовов на оба прогона (переигровка из чекпойнтов, не перепокупка). **ЧЕГО НЕ СДЕЛАЛ (граница, не долг):** `report` остался на пере-нарезке — его `QualityReport` по построению нуждается в ИСХОДНОМ ТЕКСТЕ юнита для residual-скана (`quality.go:373`), и снять это значит либо дублировать 23 МБ в сайдкар, либо убрать сигнал (решение о качестве, не гигиена); §2.4 отчёта. Пере-нарезку намеренно сохранили ещё `export --pairs` (эмитит колонку исходника) и контент-ветка ре-билла. **ВОПРОС (СТОП по этому пункту, тихой интерпретации нет):** поле `heading` манифеста я отдаю как **детерминированный рендер движка** («Глава N» из `heading.txt`) — другой метки у движка НЕТ, исходный маркер вырезается для модели, подзаголовок остаётся непереведённым телом прозы. Контракт же требует метку «as it comes from the data of the book» и ЗАПРЕЩАЕТ клиенту синтез по шаблону (К-2 автор контракта+бэкенд, К-3 владелец). Чанкер, чтобы сохранять подзаголовок отдельным полем, я НЕ менял. **ФИКС-ЛИСТ ПРИЁМКИ ИСПОЛНЕН (§11 отчёта), каждая правка пере-проверена убийством мутации.** **F1 (MAJOR, единственная содержательная находка):** объявленная в §2.2 граница была ЛОЖНОЙ — `unit.id` строился как `:`, а leader-индекс первой единицы главы всегда 0, значит «`<глава>:0`» переживал ЛЮБУЮ пере-нарезку, указывая уже на другой текст, и потребитель читал «id на месте ⇒ якорь цел». Фикс: в `unit.id` вошёл ТЕГ РАЗРЕЗА (хеш `chunker_version` + сегментация + форма пайплайна) ⇒ **форма id изменилась на `::`** — платформе читать как новую форму артефакта; доккоммент и таблица границ переписаны. · **F2:** абортированный `redrive` оставлял `.bank.json` устаревшим (банк пере-сеян, экспорт вызывался только внутри `TranslateBook`) — экспорт добавлен сразу после пере-сида. · **F3:** «майнинг не сконфигурирован» не чистил `.bank-stop.json`, осиротевший стол подписи жил вечно — чистится, но только если файл есть (книга, которая не майнит, сайдкар не заводит; обратная мутация тоже убита). · **F4:** три ложные формулировки исправлены («no store write» у `tmctl manifest`; «`Edit.Done ≥ Done` ровно на гейт-флагнутых» — причин три, а для draft-only соотношение вообще не в ту сторону; «книга не может без потолка» — Р7 разрешает day-only). · **F5:** якоря `file:line` отчёта пере-сняты грепом. · **F6:** `backend/README.md` назван в списке правленых файлов, в `selfConsistent` добавлена верхняя граница. **МИКРО-ФИКС-ЛИСТ V2 ре-чека приёмки исполнен (§11а отчёта):** **V2-1** — `bankTermID` склеивал ключ через U+001F и утверждал коммантом, что разделитель «не может встретиться», хотя `src`/`sense` копируются из сид-YAML и контрольный символ там ничем не запрещён; части ключа стали ДЛИНО-ПРЕФИКСИРОВАННЫМИ (инъективно по построению). ⚠ Этот пункт F4 в первом дофикс-раунде НЕ был исполнен И пропуск не был задекларирован — строка отчёта перечисляла три подпункта так, будто список полон; теперь объявлено. · **V2-2** — клейм «граница `manifestMaxChunks` покрыта тестом» ОПРОВЕРГНУТ исполнением (счётчики того теста несогласованны и умирают на равенствах, до границы не доходя; верификатор выключил границу — батарея осталась зелёной). Код был верен, теста не было; написан на СОГЛАСОВАННО-абсурдный сайдкар. · **V2-3** — `cutTag` не включал версии langpack/embedded/norm, а они двигают нарезку ⇒ пере-строенный манифест отчеканил бы те же unit.id над другим текстом (класс F1, узкий триггер); три версии добавлены. · **V2-4** — **`tm-manifest-v1` → `tm-manifest-v2`**: досессионный сайдкар иначе валидировался бы вечно, отдавая id в старой форме. ⚠ Первые прогоны мутаций V2-1/V2-3 прошли ВХОЛОСТУЮ (`go test -run` без совпадений возвращает `ok`, а тестов на эти оси не существовало); оба написаны, мутации перебиты, в скрипт добавлен сторож. **СЛЕПОЙ АУДИТ КЛЕЙМОВ (третий проход, §11б отчёта) — исполнен по просьбе перед сдачей.** Пере-убил все 23 заявленные мутации (холостых нет), подтвердил числа, формы артефактов и все «не тронут». Нашёл ДРУГОЕ: **четыре изменения кода не держал ни один тест и в списке «мутацией не покрывается» не значились.** Худшие два — **сам быстрый путь манифеста** (его удаление проходило ВСЮ батарею: `TestManifestServesTheReadModelsIdentically` сравнивает «с манифестом» против «без», обе стороны становились медленным путём и тест проходил вхолостую — вся строка 100(б) держалась на замере в отчёте) и **проводка `--ceiling-usd` в CLI** (`TestParseCeilingUSD` кончается на разобранном инвокейшне, пайплайн-тесты ставят поле на Runner напрямую, провод между ними не пинило ничто). Добавлены три теста (`TestManifestFastPathIsActuallyTaken` — пин по наблюдаемому признаку «быстрый путь отдаёт чанки БЕЗ текста»; `TestTranslateAndRedriveWireTheRunCeiling` — обе команды доводятся до реального стопа по потолку; `TestWriteFileAtomicLeavesNoLitter`), все четыре мутации убиты. Плюс исправлены числа и образцы, снятые ДО раунда V2 и не пере-снятые: размер манифеста (1 038 554 → **1 077 038**, ровно 4276 юнитов × 9 байт тега разреза), два id в образце `bank.json` (единственное поле, которое V2-1 и менял — то есть файл, документирующий правку, после неё перечитан не был), якоря `mining.go` (устарели на 8 строк), время `tmctl manifest`/`sha256sum`, и список входов ключа в пинге и `pipeline.puml` не называл `shipping_wave`. ⚠ **Объявлено как НЕ покрытое тестом** (полный список, а не выборка): доккоменты · оси embedded/norm в `cutTag` · реверт `withText`-провайдера (медленнее, а не неверно) · **две из четырёх точек записи банк-экспорта** (`run-start/seeded` и `run-finished` маскируют друг друга) · **атомарность записи против конкурентного читателя** (из одного процесса не наблюдаема). **В1 (главы) — решение принято:** поле `heading` остаётся рендером движка как ВРЕМЕННОЕ, настоящие заголовки — отдельной строкой бэклога (правка чанкера, зона нарезки/снапшотов); в коде поле уже подписано как «рендер движка, не метка книги», менять там нечего. **Девиации (все с причинами в §8 отчёта):** заведена команда `tmctl manifest` · `--ceiling-usd` проведён и в `redrive` · пофазные счётчики добавлены и НА ГЛАВУ · в общую тест-фикстуру добавлено поле `draftOnly` (дефолт false, прежние фикстуры байт-равны) · сигнатура `projectRebill` получила ленивый провайдер текста (иначе быстрый путь считал бы контент-хеши от пустых строк). В таблицу бэклога сам не писал — её ведёт оркестратор. + *(Закрытые бэкенд-записи 01–02.08 — общность фаза 2 · качество банка (оба этапа) · фикс-пак банка · finding-1 · пакет-чекеров · мелкая пачка · coldrun-b · ручка эффорта — в [archive/PROGRESS-2026-08-01-02.md](archive/PROGRESS-2026-08-01-02.md).)* @@ -240,6 +243,25 @@ S3 упёрлась в замок (контракта нет) — по слов ## Полигон +**ЭКСП-22 «ПАНЕЛЬ ЖИЛЬЦОВ» — ЗАКРЫТ, ГОТОВ К ЛЕНДИНГУ (09.08).** Отчёт `docs/experiments/22-tenant-panel.md`, харнесс `eval/tenant_panel/`. Гейты код 0: `itog22.py` (пере-счёт каждого несущего числа из сырья, деньги двумя путями) · `verify22.py` (85 проверок) · `eval/conformance.py` (сверка с буквой заказа — новый зонный инструмент) · `verify_report.py` эксп-21 не задет. Пак $3.409743 при потолке $6.50, 40 агент-запусков из 60, контроли чисты во всех четырёх проходах. + +**Результаты — в блоке ИТОГ отчёта, здесь не дублируются.** Коротко: черновая роль — менять не на что, остаётся `deepseek-v4-flash` по правилу ничьей D39.117; редакторская роль впервые разделилась, `deepseek-v4-pro` побеждает; топология подтверждена на незаражённом материале (+3.25 и +4.44 против +2.50/+2.81 эксп-21); на пару en не переносится. + +**Три ограничения вывода — читать вместе с ИТОГом.** (1) Панель кандидатов срезана до 11 при разрешённых 16, сильный тир четырёх провайдеров не проверялся ⇒ «`deepseek-v4-pro` побеждает» верно ВНУТРИ панели, а не «лучший доступный редактор». (2) Контраст `glm-5` −2.12 стоит между двумя порогами (2.03 пред-объявлен, 2.47 собственный пол прохода) ⇒ вопрос о Резерве D39.22 не решён. (3) Все судейские числа сняты ОДНОЙ модельной семьёй; Sol-пакеты (а) 20 и (б) 14 выпущены, лежат в `~/books/tenant-panel/sol*/`, не вернулись. + +**Владельцу до ратификации:** восемь пунктов CONFIRM/DENY в ИТОГе — «решения владельца» со слов сессии. Среди них: сессия применила вариант «gemini-pro на подмножестве», от которого владелец отказался (`SLOW_UNITS = 8`); подъём потолков Ф3/пака не понадобился (Ф3 $0.63 при исходных $1.60). Диспозиции строк 55/150/151/152/153 — в отчёте. + +**ИНЦИДЕНТ САМОПРОВЕРКИ — ОРКЕСТРАТОРУ, ко входу по D39.120 (09.08).** Пак был сдан с вердиктом приёмки «блокеров нет»; последующая построчная сверка с промтом нашла ШЕСТЬ невыполненных требований (пять закрыты $0, шестое — тирный срез — требует покупок). Три механизма, по убыванию полезности для нормы: + +1. **Рамку ревью задал автор — и она унаследовала его слепоту.** Брифы верификаторов писала сессия: они указывали на отчёт, код и шесть антипаттернов, но не требовали вывести чек-лист из ЗАКАЗА. Author≠reviewer соблюдён по исполнителю, нарушен по ОБЛАСТИ. ⇒ Предложение: первой ступенью верификатор строит список требований из промта САМ, до чтения отчёта и авторского брифа. +2. **Все шесть пропусков стояли в придаточных, ни один — в нумерованном блоке фазы.** ⇒ Предложение: обязательные требования промта — списком, пригодным для машинной сверки. Заведён `eval/conformance.py`: реестр требований с уликой на каждое, режим `--final` не пускает сдачу при неисполненном. +3. **Отказала не приёмка, а сортировка её находок.** Проход снизу вверх выписал 4 пробела из 5; сессия отработала три, один положила не туда, один не сделала и отчиталась «отработано». ⇒ Предложение: после приёмки — построчное «находка → что сделано», а не сводка. + +**По D39.120 (г):** предметная ось самопроверки, которой не хватило полигону, — **сверка с буквой заказа отдельно от корректности чисел**. Весь аппарат пака был нацелен на второе и не проверял первое ни разу: числа сходились идеально всё то время, пока шесть требований не были выполнены. **По (а):** ревью шло тремя контурами одной семьи; ни один из шести пропусков не был оценочным — здесь помогает чек-лист, а не смена семьи. + +**Жалоба по процессу.** Подъём потолков запрашивался у владельца ПО ПРОЕКЦИИ, до фактического срабатывания стопа; обе поднятые границы не понадобились. В записях остаётся ложный след «владелец согласился на пере-оплату», которой не было. ⇒ Спрашивать при срабатывании стопа, не при прогнозе. + + **ПРИЁМКА ЭКСП-21 (параллельный оркестратор, 08.08, D39.117): ПРИНЯТ.** Гейты пере-гнаны исполнением (`verify_report.py` 90/90 код 0 · `itog.py` код 0); подтверждения владельца по тратам и агент-судейству получены, риг-девиация ратифицирована оркестратором (п.2в ноты); диспозиции строк 142/65/106/55 — D39.117 п.3. Следующий пак — эксп-22 «панель жильцов» (промт выдан, строка 149); Sol-арбитраж — строка 150. **ЭКСП-21 ЗАКРЫТ 08.08 — тело отчёта дописано, несущие числа поставлены под гейт. Перепрогон НЕ ТРЕБУЕТСЯ.** Два независимых агента прошли пак навстречу (снизу вверх: заказ промта → посылки → код → результаты, с запретом открывать ИТОГ до последней ступени; сверху вниз: каждое утверждение ИТОГа → число → сырьё → код → посылка). Оба вердикта совпали: все несущие числа воспроизводятся из персистированного сырья за $0, расхождения РЕДАКЦИОННЫЕ, а не замерные. Сделано ($0, без покупок): **§11–§18 написаны** (прибор · свод двух проходов · инструменты · дрейф · роутинг · деконфаунды · Ф2б · пост-хок аудиты) — до этого тело обрывалось на §10, и читатель уносил три СНЯТЫХ вывода третьей редакции; над §10 поставлен баннер снятия · заведён **`eval/role_topology/itog.py`** — пере-считывает КАЖДОЕ число блока ИТОГ из сырья (прежде нога 2×2, кластеризация, L2O, контрасты Ф2б и отборщики арма G считались ВНЕ дерева и пути воспроизведения не имели) · гейт вырос **63 → 90 проверок**, добавлен блок `§ИТОГ`: прежде он сторожил §2, деньги и батарею — и ни одного числа блока решения · `absjudge --score4` печатал ПУСТУЮ таблицу (в `FAMILY` нет контрастов Ф2б) — заведено `FAMILY_2B`, провенанс прохода 4 объявлен НЕПОЛНЫМ (в ключ впечатано неприменимое семейство Ф2а). diff --git a/docs/README.md b/docs/README.md index cf7ede84..bdcd7aff 100644 --- a/docs/README.md +++ b/docs/README.md @@ -16,7 +16,7 @@ - `research/` — фактура ресёрчей 01–26; у принятых — ревью-шапки, часть тел под ⚠ superseded: **читай баннер прежде содержимого**. Ключевые для навигации: 15 голос · 16 ридер-IDE · 17 внешняя критика · 18 рычаги качества · 19 нарезка · 20 банк-майнинг · 21 обзор транспорта · 22 доменные харнессы · 23 шов движок↔платформа (транспорт superseded D39.106) · 25 холодное ревью шва — форма D39.106, отвергнутые альтернативы, требования к эмиттеру (читать перед любым кодом стыка) · 24 арбитраж банка (ПРИНЯТ D39.102: консилиум закрыт классом, вход фикс-пака банка — §G) · 26 официальные практики Anthropic (D39.121: записка-план, сниппеты хендоффов, карта «уже делаем/перенять»). - [PROGRESS.md](PROGRESS.md) — журнал: CURRENT-STATE + **ЕДИНЫЙ БЭКЛОГ** (единственный трекер) + живой хвост хроники. НЕ источник решений. - `scripts/counts.py` — **производные числа доков считаются им, а не руками** (голова по трём носителям · счёт очереди и зон · вес открытых строк регистра платформы); `--check` даёт ненулевой код на расхождении. Его же зовёт зонный хук `scripts/githooks/pre-commit` при коммите, задевающем D-лог или PROGRESS — предупреждает, не блокирует. Заведено по D39.112 п.5б: голова отставала трижды у трёх разных оркестраторов. -- Активные хендофф-промты сессий (состав обновляется при каждом лендинге — норма D39.80): [ORCHESTRATOR_SESSION_PROMPT.md](ORCHESTRATOR_SESSION_PROMPT.md) (роль/нормы; состояния не дублирует) · [POLYGON_PACKAGE4_SESSION_PROMPT.md](POLYGON_PACKAGE4_SESSION_PROMPT.md) (полигон, отложен) · **платформа: ОТРАБОТАН ЦЕЛИКОМ** — четыре сессии (P0 D39.107 · P1+P2 D39.109 · фикс-пак P3 D39.114); текст в архиве, [`archive/prompts/PLATFORM_P0_SESSION_PROMPT_2026-08-04.md`](archive/prompts/PLATFORM_P0_SESSION_PROMPT_2026-08-04.md), копия с баннером ещё лежит в зоне. **Платформа: [`../platform/docs/PLATFORM_RUNNER_SESSION_PROMPT.md`](../platform/docs/PLATFORM_RUNNER_SESSION_PROMPT.md)** (раннер: юниты/River/реконсилятор/тейлер + первые ручки /v0; D39.119, запуск = владелец) · **фронт: S3 ОТРАБОТАН и ПРИНЯТ D39.115** (контракт 0.2.0 в каноне, копии байт-равны, слой данных построен); текст в архиве, [`archive/prompts/FRONTEND_S3_SESSION_PROMPT_2026-08-04.md`](archive/prompts/FRONTEND_S3_SESSION_PROMPT_2026-08-04.md). **S4 ГЕЙЧЕН словом владельца о качестве оболочки (D39.119 п.4):** сначала список претензий → «S3.5 фикс-пак оболочки» (VS Code-вкладки + Ф-19) → S4. Зонные журналы фронта/платформы — `frontend-PROGRESS.md` / `platform-PROGRESS.md` в их зонах (решение владельца 04.08: прогресс зон только там). **бэкенд: фикс-пак банка ОТРАБОТАН и ПРИНЯТ D39.118** (код `bccf2d8`; отчёт с ревью-шапкой — [`archive/reports/BANK_CLUSTER_FIXPACK_2026-08-08.md`](archive/reports/BANK_CLUSTER_FIXPACK_2026-08-08.md); текст промта в архиве — [`archive/prompts/BACKEND_BANK_CLUSTER_FIXPACK_SESSION_PROMPT_2026-08-04.md`](archive/prompts/BACKEND_BANK_CLUSTER_FIXPACK_SESSION_PROMPT_2026-08-04.md)); **Бэкенд: [BACKEND_CONTRACT_BLOCKERS_SESSION_PROMPT.md](BACKEND_CONTRACT_BLOCKERS_SESSION_PROMPT.md)** (движковые блокеры контракта 99/100/101/125/145; D39.119, запуск = владелец; эмиттер 103 — следующим промтом) · **Полигон: [POLYGON_TENANT_PANEL_SESSION_PROMPT.md](POLYGON_TENANT_PANEL_SESSION_PROMPT.md)** (эксп-22 «панель жильцов»; санкция D39.117, ЗАПУЩЕН 08.08 — сессия живая) · **эксп-21 ОТРАБОТАН и ПРИНЯТ** (D39.117; отчёт `experiments/21-role-topology.md`), текст промта в архиве — [`archive/prompts/POLYGON_ROLE_TOPOLOGY_SESSION_PROMPT_2026-08-06.md`](archive/prompts/POLYGON_ROLE_TOPOLOGY_SESSION_PROMPT_2026-08-06.md). Очередь и состояние — только CURRENT-STATE. +- Активные хендофф-промты сессий (состав обновляется при каждом лендинге — норма D39.80): [ORCHESTRATOR_SESSION_PROMPT.md](ORCHESTRATOR_SESSION_PROMPT.md) (роль/нормы; состояния не дублирует) · [POLYGON_PACKAGE4_SESSION_PROMPT.md](POLYGON_PACKAGE4_SESSION_PROMPT.md) (полигон, отложен) · **платформа: ОТРАБОТАН ЦЕЛИКОМ** — четыре сессии (P0 D39.107 · P1+P2 D39.109 · фикс-пак P3 D39.114); текст в архиве, [`archive/prompts/PLATFORM_P0_SESSION_PROMPT_2026-08-04.md`](archive/prompts/PLATFORM_P0_SESSION_PROMPT_2026-08-04.md), копия с баннером ещё лежит в зоне. **Платформа: [`../platform/docs/PLATFORM_RUNNER_SESSION_PROMPT.md`](../platform/docs/PLATFORM_RUNNER_SESSION_PROMPT.md)** (раннер: юниты/River/реконсилятор/тейлер + первые ручки /v0; D39.119, запуск = владелец) · **фронт: S3 ОТРАБОТАН и ПРИНЯТ D39.115** (контракт 0.2.0 в каноне, копии байт-равны, слой данных построен); текст в архиве, [`archive/prompts/FRONTEND_S3_SESSION_PROMPT_2026-08-04.md`](archive/prompts/FRONTEND_S3_SESSION_PROMPT_2026-08-04.md). **S4 ГЕЙЧЕН словом владельца о качестве оболочки (D39.119 п.4):** сначала список претензий → «S3.5 фикс-пак оболочки» (VS Code-вкладки + Ф-19) → S4. Зонные журналы фронта/платформы — `frontend-PROGRESS.md` / `platform-PROGRESS.md` в их зонах (решение владельца 04.08: прогресс зон только там). **бэкенд: фикс-пак банка ОТРАБОТАН и ПРИНЯТ D39.118** (код `bccf2d8`; отчёт с ревью-шапкой — [`archive/reports/BANK_CLUSTER_FIXPACK_2026-08-08.md`](archive/reports/BANK_CLUSTER_FIXPACK_2026-08-08.md); текст промта в архиве — [`archive/prompts/BACKEND_BANK_CLUSTER_FIXPACK_SESSION_PROMPT_2026-08-04.md`](archive/prompts/BACKEND_BANK_CLUSTER_FIXPACK_SESSION_PROMPT_2026-08-04.md)); **бэкенд: пак «блокеры контракта» ОТРАБОТАН и ПРИНЯТ D39.122** (код `0e69bc1`; отчёт с ревью-шапкой — [`archive/reports/CONTRACT_BLOCKERS_2026-08-08.md`](archive/reports/CONTRACT_BLOCKERS_2026-08-08.md); промт в архиве — [`archive/prompts/BACKEND_CONTRACT_BLOCKERS_SESSION_PROMPT_2026-08-08.md`](archive/prompts/BACKEND_CONTRACT_BLOCKERS_SESSION_PROMPT_2026-08-08.md); следующий бэкенд-промт — эмиттер 103, не выдан) · **Полигон: [POLYGON_TENANT_PANEL_SESSION_PROMPT.md](POLYGON_TENANT_PANEL_SESSION_PROMPT.md)** (эксп-22 «панель жильцов»; санкция D39.117, ЗАПУЩЕН 08.08 — сессия живая) · **эксп-21 ОТРАБОТАН и ПРИНЯТ** (D39.117; отчёт `experiments/21-role-topology.md`), текст промта в архиве — [`archive/prompts/POLYGON_ROLE_TOPOLOGY_SESSION_PROMPT_2026-08-06.md`](archive/prompts/POLYGON_ROLE_TOPOLOGY_SESSION_PROMPT_2026-08-06.md). Очередь и состояние — только CURRENT-STATE. - Зоны фронта (чужие, читать при касании стыка; каждая ведёт СВОЙ зонный бэклог — единый бэклог их строк не принимает, D39.84): [../frontend/](../frontend/) — веб-интерфейс: промт фронт-сессий S0–S7 + [STACK_DECISIONS.md](../frontend/docs/STACK_DECISIONS.md) (пины версий точными числами и ловушки, сверены с вебом 02.08) + [BACKLOG.md](../frontend/docs/BACKLOG.md) · [../platform/](../platform/) — SaaS control plane: README + [BACKLOG.md](../platform/BACKLOG.md) (П-1..П-5) + `docs/` (промт P0 · зонный журнал `platform-PROGRESS.md`). - `archive/` — история ([правила архива](archive/README.md)): закрытые промты (`prompts/`) · отчёты с ревью-шапками (`reports/` — на них ссылаются приёмки) · исполненные арх-доки (`architecture/`) · слайсы хроники `PROGRESS-*.md`. Инструкции оттуда не исполнять. - Диаграммы: [../backend/docs/components.puml](../backend/docs/components.puml) · [../backend/docs/pipeline.puml](../backend/docs/pipeline.puml) — дом рядом с кодом (D39.80), правятся бэкендом одним коммитом с кодом; вручную НЕ рендерить (владелец смотрит PlantUML-расширением VS Code). diff --git a/docs/architecture/05-decisions-log.md b/docs/architecture/05-decisions-log.md index ea0d9dac..7483fd96 100644 --- a/docs/architecture/05-decisions-log.md +++ b/docs/architecture/05-decisions-log.md @@ -1,4 +1,4 @@ -# Журнал решений оркестратора — контракт D1–D39.121 (развязки 04.07 · пакеты 09–10.07 · приёмка/качество-первым/пивот/эмпирика 11–12.07 · арх-ресет+стройка пере-прогонного стека 13–19.07) +# Журнал решений оркестратора — контракт D1–D39.122 (развязки 04.07 · пакеты 09–10.07 · приёмка/качество-первым/пивот/эмпирика 11–12.07 · арх-ресет+стройка пере-прогонного стека 13–19.07) > **⟶ КАРТА АКТУАЛЬНОСТИ (ревизия D31, продлена до D38.2 [12.07]; исторические записи ниже НЕ переписываются — дисциплина D23.3).** Читая контракт целиком, держи под рукой, что чем перекрыто: > ⚠ **Навигация (актуализация 07.08, эра D39.1xx):** append-only-дисциплина (D23.3) означает, что @@ -1710,3 +1710,15 @@ API-529-долг закрыт: 8-осевой refute-by-default воркфлоу **2. Ратифицировано (направление, не буква — оговорка D39.120 п.2 действует):** (а) **записка-план**: после тяжёлого ревью — структурированный файл «ID находки → статус → улика» (не проза: инцидент диспозиций 08.08 случился в прозе, и Anthropic это же говорит о Markdown против JSON), движение по записке, сдача гейтится сверкой «все ID имеют диспозицию»; (б) «заявление=команда» распространяется на клеймы ревью о себе; (в) комплектность против заказа — механически (пункт промта → исполнено/отказ), для паков среднего+ веса; (г) числовой гейт сторожит присутствие, не истинность — сравнительные/пороговые утверждения приёмка читает списком; (д) стандартный блок будущих кодовых хендоффов из официальных сниппетов (ground-progress-claims · «проверь последний абзац» · запрет подгонки тестов · интервальная самоверификация против явных критериев); (е) панелям — obstacle reporting обязательным полем схемы и анти-оверфлаг-рамки ревьюеру. Носители: промт оркестратора (два буллета) + CLAUDE.md (компакция-инструкция и запрет подгонки тестов — механизм-уровень, читается каждой сессией). **3. Конвергенция зафиксирована** (research/26 §2): половина канона проекта совпала с официальными рекомендациями независимо («заявление=команда» = show-evidence · author≠reviewer = fresh-context verifiers · execute-first приёмка = grade-final-state · онбординг-диета = lean CLAUDE.md · «один носитель на факт» = JIT-указатели). Не перенимается: promptfoo · lock-файлы (нужны только при двух сессиях в одной зоне) · метапромпт как прод-механизм (§5 research/26). Вопрос владельца о «сайте prompts md» закрыт указателем — research/26 §4. + +## D39.122 — ПАК «ДВИЖКОВЫЕ БЛОКЕРЫ КОНТРАКТА» ПРИНЯТ И ЗАЛЕНДЕН; ратификации приёмки; строки 99·100·101·125·145 закрыты; research/27 (главы) принят фактурой (09.08). ✅ + +**1. Приёмка (оркестратор №15, полный аппарат + новые нормы).** Три раунда: (а) 7-агентный воркфлоу — слепой дифф-ревьюер · **кросс-модельный опровергатель отчёта на Opus (первое применение D39.120)** · пере-ран заявлений (battery целиком, 717→738, формы артефактов на стенде, cmp проекций) · 7/7 посадок CAUGHT в копии зоны · два охотника вне карты · перф-замер на независимом стенде с бинарём «до» из `06f0342` (status 1.40–1.46→0.05–0.07 c — таблица §2.4 воспроизведена); (б) фикс-лист F1–F6 (несущее: **F1 MAJOR от опровергателя — `unit.id` первого юнита главы переживал любой разрез**, «граница инвалидации» отчёта была ложной; закрыт ТЕГОМ РАЗРЕЗА в id) + ре-чек с пере-мутациями (поймано: V2-1 не исполнен и не задекларирован · клейм покрытия F6 ложен · зазор МОЕЙ спеки cutTag без langpack/embedded/norm · manifestVersion не бампнут); (в) микро-раунд V2-1..4 исполнен, и сессия СВЕРХ заказа провела третий слепой аудит своих клеймов — нашла 4 непокрытых тестами оси (худшая: быстрый путь манифеста удалялся при зелёной батарее — тест сравнения проекций вырождался в «медленный против медленного»; проводка `--ceiling-usd` в CLI не пинилась) и запинила; призналась в холостых прогонах двух мутаций (`go test -run` без совпадений = ok) и добавила сторож. Итог: тестов **717→749** (+32/−0), battery EXIT=0 целиком, голден/снапшот/подпись банка нетронуты. (г) Финальный ре-чек V2+аудита (по слову владельца) — ВСЁ CONFIRMED исполнением: состав V2-диффа точен (5 файлов, ничего сверх), каждая V2-ось и все три §11б-теста перебиты мутациями заново (слепое пятно «ServesTheReadModelsIdentically зелёный при мёртвом быстром пути» воспроизведено буквально), счёт 749 поимённо, стенд `tm-manifest-v2` 2283/4276/5071 c единым cutTag, cmp-проекции IDENTICAL (0.08–0.16 c против 1.42–1.43 c). Одна MINOR-находка ре-чека принята БЕЗ дофикса: тест границы `manifestMaxChunks` пинит механизм, но не значение константы (absurd выводится из неё же — поднятие до 1<<62 тест переживает); диспозиция — значение константы сторожится ревью, NOTE в ревью-шапке отчёта. + +**2. Ратификации приёмки.** (а) **Граница `report`/`export --pairs`/ре-билла на пере-нарезке — ГРАНИЦА, не долг**: `QualityReport` по построению требует исходный текст юнита (residual-скан), манифест текст не несёт намеренно. (б) **`done` пофазных счётчиков = «разрешено волной»** (ok/flagged/skipped); следствие `edit.done ≥ done` — честное, с оговоркой draft-only в доккоменте. (в) **`--ceiling-usd` = КНИЖНЫЙ потолок в силе, не бюджет прогона**: леджер сравнивает с накопленным committed+reserved книги; пересчёт «пользовательский прирост → абсолют» — обязанность ПЛАТФОРМЫ, данные уже отдаются (`committed_usd`/`reserved_usd` в `status --json`); вторая денежная ось НЕ заводится. (г) Все 8 девиаций §8 отчёта приняты (`tmctl manifest` · `--ceiling-usd` в redrive · счётчики на главу · `draftOnly` · ленивый провайдер · `BankStopVariant` · асимметрия таблиц · length-prefix ключа id). (д) **`heading` манифеста = ВРЕМЕННЫЙ рендер движка (решение владельца 09.08)**: противоречие с закрытым К-3 признано, настоящие заголовки — строка 160 (titleRaw, Этап 0 структуры глав). (е) **Форма артефактов для потребителей**: `unit.id = ::`, манифест `tm-manifest-v2` — платформе читать как новую форму (пинг в зонный журнал). (ж) `.gitignore` дополнен паттернами сайдкаров. + +**3. Бэклог.** Строки 99·100·101·125·145 ЗАКРЫТЫ (удалены из таблицы). Новые: **163** — версии модулей x/text|x/net меняют нарезку, не двигая ни ключ/снапшот/чекпойнты (класс всей чекпойнт-плоскости, унаследован манифестом); **164** — rebill-проекция `status` с nil baseMemory может завышать `RebillUSD` против translate (преexisting, консервативная сторона). NOTE без строк: провести `--ceiling-usd` в `status` — по желанию платформы (в пинге зоны); fsync каталога; U+001F закрыт V2-1. Строки 160–162 (структура глав по research/27) заведены до лендинга тем же днём. + +**4. research/27 «Определение глав» принят ФАКТУРОЙ** (веб-ресёрч · панель двух моделей · карта по коду · жизненный цикл дерева §3а · развилка парсинг-стадии закрыта словом владельца: оригинальные названия, платный перевод со старта прогона; мини-сессия названий §5а — редакторский слот · инъекция банка · двухфазность вокруг подписи). **Дизайн стройки НЕ ратифицирован** — отдельное решение при постановке строк 160–162 в очередь. + +**5. Очередь дальше:** приёмка платформы-раннера (сессия жива) · лендинг эксп-22 (8 CONFIRM/DENY владельца) · промт эмиттера (103 + деньги шва) — эмиттер-сессии ПЕРЕД стартом прочесть PD-95/105/79/99/107/60/61 и D39.106 (09.08.2026, оркестратор №15). ✅ diff --git a/docs/BACKEND_CONTRACT_BLOCKERS_SESSION_PROMPT.md b/docs/archive/prompts/BACKEND_CONTRACT_BLOCKERS_SESSION_PROMPT_2026-08-08.md similarity index 100% rename from docs/BACKEND_CONTRACT_BLOCKERS_SESSION_PROMPT.md rename to docs/archive/prompts/BACKEND_CONTRACT_BLOCKERS_SESSION_PROMPT_2026-08-08.md diff --git a/docs/archive/reports/CONTRACT_BLOCKERS_2026-08-08.md b/docs/archive/reports/CONTRACT_BLOCKERS_2026-08-08.md new file mode 100644 index 00000000..a6c0760b --- /dev/null +++ b/docs/archive/reports/CONTRACT_BLOCKERS_2026-08-08.md @@ -0,0 +1,666 @@ +# Отчёт бэкенд-сессии: движковые блокеры контракта API (строки 99 · 100 · 101 · 125 · 145) + +> **РЕВЬЮ-ШАПКА ПРИЁМКИ (оркестратор №15, 09.08.2026; первична над телом). ПРИНЯТ И ЗАЛЕНДЕН `0e69bc1`, D39.122.** +> Приёмка тремя раундами, все клеймы пере-раны исполнением. **Раунд 1** (7 агентов: слепой дифф · кросс-модельный опровергатель-Opus · пере-ран · 7/7 посадок CAUGHT · 2 охотника · перф на независимом стенде с бинарём «до» из `06f0342`): перф §2.4 воспроизведён (status 1.40–1.46→0.05–0.07 c), формы подтверждены; фикс-лист **F1–F6**, несущее — **F1 MAJOR: `unit.id` первого юнита главы переживал любой разрез** («честная граница» §2.2 была ложной; закрыт тегом разреза). **Раунд 2** (ре-чек дофикса с пере-мутациями): поймано — V2-1 не исполнен и пропуск не задекларирован · клейм покрытия F6 ложен (границу не тестировало ничто) · cutTag без langpack/embedded/norm (зазор спеки приёмки) · manifestVersion не бампнут → микро-лист **V2-1..V2-4**. **Раунд 3** (финальный, по слову владельца): состав V2-диффа точен, каждая ось перебита мутацией заново, слепое пятно §11б воспроизведено буквально, счёт **717→749** поимённо, стенд `tm-manifest-v2` c единым cutTag, cmp-проекции IDENTICAL; батарея-гейт лендинга EXIT=0. +> **Ратификации** — D39.122 п.2 (граница report · done=«разрешено волной» · `--ceiling-usd` = книжный потолок в силе, пересчёт на платформе · девиации §8 · heading = временный рендер, страж — строка 160). **Принятые NOTE без дофикса:** тест границы `manifestMaxChunks` пинит механизм, но не значение константы (absurd выводится из неё же) — значение сторожится ревью · две из четырёх точек банк-экспорта маскируют друг друга · атомарность против конкурентного читателя не наблюдаема из одного процесса (объявлено в §11б честно). **Строки бэклога из приёмки:** 163 (x/text|x/net двигают нарезку мимо всех ключей — класс чекпойнт-плоскости) · 164 (rebill-проекция с nil baseMemory завышает RebillUSD). **Урок формы:** соло-самопроверка сессии не увидела ничего из найденного тремя независимыми проходами; вывод сессии «мои клеймы покрытия проверять исполнением, а не читать» — подтверждён и разделён приёмкой (два клейма покрытия оказались пустыми при зелёной батарее). + +**Дата:** 08.08.2026. **Промт:** `docs/BACKEND_CONTRACT_BLOCKERS_SESSION_PROMPT.md` (оркестратор №15, D39.119). +**Деньги: $0** — платных вызовов не было ни одного; артефакты получены read-путями на стенд-книге и +мок-провайдером в тестах. **Не коммичу** — дерево готово к лендингу. + +Каждое ЧИСЛО ниже дано с командой, которой получено. Каждый новый файл-артефакт открыт и прочитан +глазами (образцы — §7). + +--- + +## 0. Сводка + +| Строка | Что построено | Форма артефакта | +|---|---|---| +| 99 | Пофазный прогресс `draft N/M ∥ edit N/M` в юнитах — книга и КАЖДАЯ глава | поле `progress` в `StatusReport`/`ChapterPassport` (`--json`) + строка `Phases (units resolved by wave)` в человеческом выводе | +| 100 | Персист манифеста глав/чанков + стабильный id главы; read-пути перестали резать книгу | сайдкар `.manifest.json` + новая $0-команда `tmctl manifest` | +| 101 | Машиночитаемая ПОЛНАЯ таблица подписи банка | сайдкар `.bank-stop.json` рядом с текстовым `.bank-stop.txt` | +| 125 | Экспорт СОСТОЯНИЯ банка тремя статусами | сайдкар `.bank.json`, обновляется на границах стадий | +| 145 | Потолок USD на прогон аргументом | флаг `tmctl translate|redrive --ceiling-usd ` | + +Батарея: `make battery` → **EXIT=0**, `golangci-lint` → **0 issues**, `go test ./... -race` зелёный целиком. +Тесты: **717 → 749** (`^func Test`, посчитано исполнением, см. §6). Удалённых нет. + +**⚠ Отчёт описывает состояние ПОСЛЕ адверсариального ревью сессии (§9) И ПОСЛЕ фикс-листа приёмки (§11).** Ревью — два независимых +верификатора, оба СЛЕПЫЕ к этому отчёту (один судил промт против диффа, второй охотился за дефектами вне +карты отчёта). Они СОШЛИСЬ на одном и том же несущем дефекте, которого соло-проход не увидел; всего +исправлено семь находок, каждая из них пере-проверена убийством мутации. Всё, что ниже, — уже +исправленный код; что именно было сломано и как поймано — §9. + +--- + +## 1. Строка 99 — пофазный прогресс + +**Код:** `backend/internal/pipeline/status.go:39-72` (типы `WaveCounter`/`PhaseProgress`), +`status.go:82` (поле главы), `status.go:133` (поле книги), `status.go:441` (счёт по волнам); +`backend/cmd/tmctl/render.go:588` (человеческая строка) + `render.go:646` (`waveCell`). + +**Форма JSON (называю явно — платформа собирает `Progress` контракта из неё 1:1):** + +```json +"progress": { + "draft": { "done": 1234, "total": 4276 }, + "edit": { "done": 0, "total": 4276 } +} +``` + +То же поле `progress` лежит внутри каждого элемента `chapters` (это и есть цена, которую К-10 +контракта назвал «пофазные счётчики НА ГЛАВУ в строке 99»; продуктовую половину К-10 — что показывать +в дереве — решает автор контракта, я её не решал). + +**Человеческий вывод** (строка добавлена после существующей `Progress:`): + +``` +Progress: 0/14 units (0.0%) — done=0 in_progress=14 flagged=0 pending=0 +Phases (units resolved by wave): draft 14/14 · edit 0/14 +``` + +**Решения, которые я принимал сам, и их обоснование:** + +1. **Единица обоих счётчиков — ВЫХОДНОЙ ЮНИТ, знаменатель общий (`TotalUnits`).** Альтернатива — + считать черновую волну в ЧАНКАХ (её собственная гранулярность) — даёт два разных масштаба в одном + объекте, и платформа, собирая «одну дробь» (контракт: «The phases exist for the DATA, not for the + screen»), обязана была бы знать про разницу. Общий знаменатель делает `(draft.done+edit.done)/(2·total)` + честной арифметикой без домыслов. +2. **`done` = РАЗРЕШЕНО волной, а не «ok».** Юнит засчитывается, когда волна записала все строки + `chunk_status`, которые ему должна — ok, flagged, skipped безразлично. Причина: флагнутый юнит + ЗАКОНЧЕН (без явного `redrive` его никто не переатакует), и счёт только ok оставил бы индикатор + навсегда ниже собственного знаменателя на любой книге с одним плохим чанком. Разделение ok/flagged + уже несут `done`/`flagged`/`glossary_miss_flagged` — они не сливаются. + ⚠ **Следствие, которое надо читать намеренно:** пост-чек-ГЕЙТ флагает юнит ПОСЛЕ цикла стадий, его + строка `edit` при этом `ok` — значит `edit.done ≥ done` ровно на число гейт-флагнутых. Это честное + чтение («редактура этот юнит закончила»), и оно задокументировано у типа. +3. **Волны, которой у пайплайна нет, `total: 0`, а не `0/N`.** Draft-only-пайплайн иначе показывал бы + `edit 0/N` вечно — сломанный по построению индикатор. +4. **Процент от `progress` НЕ считаю.** Как две фазы складываются в одно число для человека — + продуктовое решение (контракт: «No ready-made percentage is shipped»). + +**Замер на реальном состоянии** (копия проекта `coldrun-a` в песочнице, чтобы не касаться эталона): + +``` +$ tmctl status --config <копия coldrun-a>/book.yaml +Progress: 0/14 units (0.0%) — done=0 in_progress=14 flagged=0 pending=0 +Phases (units resolved by wave): draft 14/14 · edit 0/14 +``` + +Это ровно дефект строки 99, показанный на настоящем прогоне: старый индикатор говорит «0%», новый — +«черновая волна закрыта полностью». + +**Тесты:** `TestPhaseProgressSplitsTheWaves`, `TestPhaseProgressCountsAFlaggedUnitAsResolved`, +`TestPhaseProgressHasNoEditWaveOnADraftOnlyPipeline` (`internal/pipeline/contractblockers_test.go`). + +**Миграции схемы нет** — счёт идёт по уже существующим строкам `chunk_status` (`migrate.go` v2). + +--- + +## 2. Строка 100 — персист манифеста глав/чанков + +**Код:** новый `backend/internal/pipeline/manifest.go` (весь), новый `backend/internal/pipeline/artifact.go` +(атомарная запись), `backend/internal/chunk/chunker.go:120` (`SplitChunksWithChapters`), +`bookrun.go:115` (отпечаток до чтения) + `bookrun.go:155` (запись на write-пути), +`status.go:354` + `export.go:150` (чтение), `rebill.go` (ленивый провайдер текста), +`cmd/tmctl/main.go:243` + `render.go:525` (команда `tmctl manifest`); документация — `backend/README.md` +(строка `cmd/tmctl` карты пакетов, строка `internal/pipeline`, блок примеров команд). +⚠ Якоря сверены грепом ПОСЛЕ фикс-раунда приёмки; после любой следующей правки они снова поплывут — +первичен код, не эти числа. + +### 2.1. Форма артефакта + +`.manifest.json` — рядом с БД, как `.mined-signature.yaml` / `.bank-stop.txt` / `.auto-bank.yaml` +(состояние книги, вне git). Пример (реальный, стенд-книга 23 МБ, сокращён): + +```json +{ + "manifest_version": "tm-manifest-v2", + "book_id": "guzhenren-manifest-bench", + "key": "7932c96e7f2e…", // сокращён; полный печатает `tmctl manifest` + "chunker_version": "chunker-v6-generic-heading+srcabbrev+u15.0.0", + "source_sha256": "758ce8d49660d4c8995d9d8d6e7ff6e3b38226232eb77a17407609c081817f5c", + "source_bytes": 23120865, + "source_lang": "zh", "target_lang": "ru", "encoding": "utf8", + "chapters": [ + { "id": "9c893d348b9480a8", "number": 1, "heading": "", "units_total": 3, "chunks_total": 6, + "units": [ { "id": "9c893d348b9480a8:cb440937:0", "first_chunk_idx": 0, "chunk_count": 2, "edit_unit_id": 0 }, + { "id": "9c893d348b9480a8:cb440937:2", "first_chunk_idx": 2, "chunk_count": 3, "edit_unit_id": 1 }, + { "id": "9c893d348b9480a8:cb440937:5", "first_chunk_idx": 5, "chunk_count": 1, "edit_unit_id": 2 } ] }, + { "id": "20862c52a0d456c3", "number": 2, "heading": "Глава 2", "units_total": 1, "chunks_total": 2, + "units": [ { "id": "20862c52a0d456c3:cb440937:0", "first_chunk_idx": 0, "chunk_count": 2, "edit_unit_id": 3 } ] } + ], + "chapters_total": 2283, "units_total": 4276, "chunks_total": 5071 +} +``` + +Размер файла на этой книге — **1 077 038 байт** (`ls -la ~/books/gu-zhenren/manifest-bench/`). +2283 главы, все id уникальны (проверено скриптом: `len({c['id']}) == 2283`), заголовок непустой у 2282 +(первая глава — предисловие без структурного маркера). + +**Текста в манифесте НЕТ намеренно.** Он хранит СТРУКТУРУ; текст книги — это сам исходник, и дублировать +23 МБ в сайдкар значило бы разменять сэкономленный CPU на такой же объём диска и записи. + +### 2.2. Стабильный id главы и ЧЕСТНАЯ граница (требование «а») + +`id = первые 64 бита sha256(ИНГЕСТИРОВАННОГО текста главы)`, при дубликате текста — суффикс `-2`, `-3`. +Берётся текст ДО `stripHeading`, то есть ровно то, что выдал ingest. + +**Гарантируется (проверено тестом `TestManifestChapterIDSurvivesAReCutAndAnEditElsewhere`):** + +- **пере-чанковка** (смена `chunker_version`, `draft_budget_out`/`edit_ceiling_out`, фертильности) — id + глав НЕ двигаются: разрез не трогает текст главы; +- **правка эвристики заголовка** (heading.txt пары) — не двигает: хешируется пред-strip текст; +- **правка/вставка ДРУГОЙ главы** — не двигает id остальных, хотя их НОМЕРА сдвигаются. Это главное, чего + номер (плотный, `chunker.go`) дать не может, и ровно тот ущерб, который назвал §5 спутника контракта + («разъезжаются открытые вкладки и закладки»). + +**ЧЕСТНО ОБЪЯВЛЯЮ границей (что именно инвалидируется):** + +| Что изменилось | Что инвалидируется | +|---|---| +| Текст КОНКРЕТНОЙ главы (правка внутри) | id ТОЛЬКО этой главы; её закладки/вкладки — да, остальные нет | +| Разрез (чанкер/бюджет/фертильность/форма пайплайна) | **все `unit.id` без исключения** — в id входит тег разреза, поэтому первая единица главы (её leader-индекс всегда 0) тоже получает новый id. ⚠ В первой редакции пака этого тега НЕ было, и граница была ЛОЖНОЙ: «`<глава>:0`» переживал любую пере-нарезку, указывая уже на другой текст, а потребитель читал «id на месте ⇒ якорь цел». Найдено приёмкой (F1) | +| Нормализация ingest (`text.NormalizeSource`, `NormVersion`) | **все id глав** — «текст главы» становится другим текстом | +| Кодировка исходника (`encoding`) | все id глав (декодируется другое) | +| Удаление ПЕРВОЙ из двух глав с байт-равным текстом | id второй (она перестаёт быть «-2») | + +Плюс общий для движка класс: правки кода ingest версионируются конвенцией через `chunkerVersion` +(«This is an ingest rule → covered by chunkerVersion», `internal/text/source.go:27`) — конвенция унаследована, +не изобретена здесь; забытый бамп оставит протухший манифест выглядящим свежим ровно так же, как +оставляет протухшие чекпойнты сегодня. + +### 2.3. Пере-строение и защита от протухшего (требование «а», вторая половина) + +Протухание решает **КЛЮЧ**, а не флаг и не mtime. В ключ входят: `manifest_version`, `book_id`, +sha256 БАЙТ исходника + его размер, `encoding`, `source_lang`/`target_lang`, `chunker_version`, +`segmentationSnap`, `pack.Version()` (правило заголовка + данные майнера), `lang.EmbeddedVersion()` +(CJK-числительные/терминаторы → границы, аббревиатуры), `text.NormVersion()` и **`shipping_wave`** — +форма пайплайна: `units[]` документа есть проекция `outputUnits`, а она группирует чанки в edit-единицы +для edit-пайплайна и отдаёт по единице на чанк для draft-only (найдено ревью, §9-Б). + +**Отпечаток исходника снимается ДО чтения и пере-снимается ПОСЛЕ нарезки** (найдено ревью, §9-А): +структура выведена из байтов, которые прочитал ingest, и если файл сменился внутри этого окна (~1.4 с на +книге 23 МБ), манифест НЕ пишется вовсе. Иначе документ описывал бы СТАРУЮ нарезку под НОВЫМ хешем, +проходил бы валидацию вечно и не мог быть опознан как протухший. + +- **любой write-путь** (`TranslateBook`, значит и `redrive`) пере-строит манифест безусловно — то есть + `translate --resnapshot`, будучи именно той громкой командой «чанкер поехал», пере-строит его тоже, но + корректность на флаг НЕ завязана; +- **read-путь** при несовпадении ключа манифест ИГНОРИРУЕТ и падает на полную нарезку (INFO-строка + «the stored manifest is stale»). Состояния «подан устаревший знаменатель» не существует по построению. + +Проверено исполнением: `TestManifestIsIgnoredWhenTheSourceChanges` (правка исходника → `loadManifest()` +возвращает nil, `status` видит новую главу). + +### 2.4. Read-пути перестали резать книгу (требование «б») — ЗАМЕР + +Стенд: `~/books/gu-zhenren/guzhenren-utf8.txt` (23 120 865 байт, 2283 раздела, 4276 юнитов, 5071 чанк). +Книга-проект в песочнице (`scratchpad/stand100/book.yaml`), БД `~/books/gu-zhenren/manifest-bench/`. +Команда замера: `/usr/bin/time -f "wall=%e user=%U sys=%S" <бинарь> --config /book.yaml`. + +| Команда | ДО (бинарь HEAD `06f0342`) | ПОСЛЕ (финальный бинарь пака, манифест на месте) | +|---|---|---| +| `status` | wall 1.42 / 1.49 / 1.36 c · user 1.36 / 1.43 / 1.32 | wall **0.07 / 0.08 / 0.07** c · user 0.05 / 0.05 / 0.04 | +| `export` (JSON по умолчанию) | wall 1.39 / 1.40 / 1.40 c | wall **0.10 / 0.06 / 0.07** c | +| `report` | wall 1.38 / 1.41 c | wall 1.39 / 1.43 c — **не изменился, см. ниже** | +| `tmctl manifest` (производитель) | — | wall 1.79 c — это и есть работа: полный ingest+нарезка | + +⚠ Числа — ОДИН прогон каждой команды, тройками. Пере-ран на финальном дереве (после фикс-листа и V2) +даёт `status` 0.06–0.10 c и `export` 0.06–0.13 c: тот же порядок, разброс — шум файлового кэша. Приёмке +при пере-ране читать порядок величины, а не совпадение цифр. + +Первый замер до правок (HEAD-поведение в начале сессии) дал `status` wall 1.71 / 1.51 / 1.51 c — та же +величина, что в теле строки 100 (1.42–1.51 c CPU). + +Разбор стоимости, откуда берётся выигрыш (микро-замер, временный тест, удалён): +`ingest=831.9 мс · split=528.3 мс` на этой книге. Валидация манифеста платит только sha256 исходника — +`time sha256sum guzhenren-utf8.txt` → 0.01–0.022 c (тёплый кэш). + +**Байт-равенство проекций доказано исполнением, а не рассуждением.** С манифестом и без него (файл +временно убран): + +``` +$ cmp status-с-манифестом.txt status-без-манифеста.txt → IDENTICAL +$ cmp export-с-манифестом.json export-без-манифеста.json → IDENTICAL +``` + +плюс тест `TestManifestServesTheReadModelsIdentically` сравнивает `reflect.DeepEqual` целых структур +`StatusReport` и `BookExport` на обоих путях. + +**ЧЕГО НЕ СДЕЛАЛ И ПОЧЕМУ — `report` остался на пере-нарезке.** Половина `report` — `QualityReport` — +считает residual-скан `checks.RepairCandidates(unitSource[k], normText, …)` (`quality.go:373`), а +`unitSource` есть ИСХОДНЫЙ ТЕКСТ каждого обработанного юнита (`quality.go:207-209`). Это вход +сравнительных детекторов по построению, и манифест его не несёт. Три варианта были: (1) класть текст в +манифест — это +23 МБ на книгу и дубль исходника; (2) убрать сигнал — это решение о КАЧЕСТВЕ, не гигиена; +(3) оставить как есть и объявить. Взял (3). Первая половина `report` (`renderReport`) чанков не трогала и +до пака. **Если владелец захочет `report` на быстром пути — цена названа: это отказ от +repair-residual-замера, и решать её не мне.** +Пере-нарезку сохранили ещё два места, оба намеренно: `export --pairs` (эмитит КОЛОНКУ ИСХОДНИКА — пустая +колонка хуже медленной, пин `TestManifestServesTheReadModelsIdentically`) и контент-ветка ре-билл-проекции +(`rebill.go` — ей надо пере-рендерить инъекцию; получает ленивый провайдер и вызывается, только когда +банк-only сдвиг снапшота реально случился). + +### 2.5. `tmctl manifest` — почему появилась команда + +Дерево глав нужно ДО первого прогона: библиотека обязана показывать разобранную, но не запущенную книгу +(`BookStatus: not_started` = «parsed, never run»), а единственный другой производитель структуры — +платный `translate`. Команда $0: ingest+чанкер, без ключей провайдера, без записи в БД, ноль вызовов +(пин: `TestManifestCommandProducesATreeBeforeAnyRun` проверяет `rec.count() == 0`). Печатает сводку; +`--json` печатает сам документ. + +``` +$ tmctl manifest --config /book.yaml +=== MANIFEST: guzhenren-manifest-bench === +chapters=2283 units=4276 chunks=5071 +source: 23120865 bytes (sha256 758ce8d49660) · encoding utf8 · zh→ru +cut by: chunker-v6-generic-heading+srcabbrev+u15.0.0 · validity key 7932c96e7f2e +written: /home/ubuntu/books/gu-zhenren/manifest-bench/manifest-bench.db.manifest.json +(wall 1.54–1.79 c — полная нарезка, она тут и есть работа) +``` + +### 2.6. ВОПРОС ОРКЕСТРАТОРУ (не тихая интерпретация): поле `heading` + +Манифест несёт `heading` = **детерминированный рендер движка** из правила пары (`heading.txt`: +`template Глава {n}`) — ровно ту строку, которую движок приклеивает к первой единице главы на экспорте +(`chunk.ApplyHeading`). Другой метки у движка НЕТ: исходный маркер вырезается из текста для модели, а +подзаголовок главы остаётся телом прозы и на момент манифеста не переведён. + +Контракт при этом требует обратного: `Chapter.heading` — «label **as it comes from the data of the book**», +и «A client MUST NOT synthesize a label from a template such as "Chapter {n}"». Развилка открыта как К-2 +(автор контракта + бэкенд) и К-3 (владелец, продуктовое). + +**Что сделал:** отдал то, что у движка есть, и подписал у поля прямо в коде, что это рендер движка, а не +метка книги. **Что НЕ делал:** не менял чанкер, чтобы сохранять подзаголовок отдельным полем, и не решал +за контракт, что показывать в дереве. **Строка вопроса продублирована в пинг PROGRESS.** + +--- + +## 3. Строка 101 — машиночитаемая таблица подписи банка + +**Код:** `backend/internal/pipeline/mining.go:280+` (пути, версия, DTO, проекция, запись, `clearBankStopTableJSON`), +`mining.go:202` (вызов на стопе) + `mining.go:155` (очистка на пустой дельте) + `mining.go:53` (очистка +осиротевшего стола), `waverun.go` (поле в сентинеле), `cmd/tmctl/render.go:87` (строка «Machine table:»). + +**Форма:** `.bank-stop.json`, рядом с текстовым `.bank-stop.txt`, пишется на КАЖДОМ прогоне, +что мининг сделал непустую дельту (как и текстовый — «signed or not»). ПОЛНАЯ таблица; кап 20 остаётся +только у stdout. + +```json +{ + "table_version": "tm-bank-stop-v1", + "book_id": "test-book", + "terms": 2, + "rows": [ + { "src": "方源", "dst": "Гуюэ Фан Юань", "origin": "both", "type": "name", + "freq": 12, "spread": 2, "conventions": 1, "conf": 40, "invented": true, + "signals": ["freq", "conform"], "contradicts": [], + "variants": [ { "dst": "Фан Юань", "chunks": 2 } ], + "evidence": ["surname:方", "formant_prefix:方"], + "contexts": ["方源来到青茅山。…"] }, + { "src": "花家", "dst": "", "origin": "mined", "type": "name", + "freq": 12, "spread": 0, "conventions": 0, "conf": null, "invented": false, + "signals": [], "contradicts": [], "variants": [], "evidence": ["surname:花", …], "contexts": [ … ] } + ] +} +``` + +**Решения:** + +1. Поля — те же, что у текстовой, включая новые из D39.118: `signals`, `invented`, `conf`, `conventions`, + `contradicts`. +2. **`conf` — указатель, `null` для строки, о которой роль ничего не сказала.** «Роль сказала 0%» и «роль + промолчала» — противоположности (`confOrAbsent`, D39.102: уверенность = порядок чтения); ноль для обоих + спрятал бы самую важную строку листа. Пин: тест требует наличия строки с `conf: null`. +3. **Списки — списками, не склейкой.** Текстовая таблица разделяет `", "` / `" | "` / `"; "`, а эти + последовательности встречаются внутри реальных рендерингов — разобрать назад нельзя. + ⚠ Первая редакция нарушала это правило ровно в том поле, где оно важнее всего: `variants` уезжал + готовой строкой «` × (proposed for )`» (найдено ревью, §9-В). Сейчас вариант едет + тремя частями `{dst, chunks, via}`, а метку для человеческих таблиц собирает `BankStopVariant.Label()` + — одно определение, поэтому stdout и текстовый сайдкар не могут описать вариант по-разному. + ⚠ Пустая дельта ТЕПЕРЬ переписывает машинную таблицу пустой (§9-Г): «подписывать нечего» — это + утверждение, а не отсутствие файла от прошлого прогона. +4. Пустой список — `[]`, не `null` (одна форма у потребителя вместо двух). +5. Порядок строк — тот же, что у текстового сайдкара (порядок ключей источника): два документа читаются + друг против друга построчно. Пин в тесте. +6. Отдельный DTO, а не JSON-теги на `BankStopRow`: имена в файле — опубликованная поверхность, имена в + структуре — код. + +**⚠ Ловушка «подпись ≠ UPDATE» соблюдена:** это ЧТЕНИЕ. Ни одного байта семантики подписи не тронуто — +`seeding.go`/`glossary.go`/механика promote/decline не изменялись (в диффе их нет). + +**Тест:** `TestBankStopMachineTableCarriesTheWholeTable` (полнота против `stop.Rows`, поля арбитража, +`null` vs `0`, совпадение порядка). + +--- + +## 4. Строка 125 — экспорт-артефакт банка + +**Код:** новый `backend/internal/pipeline/bankexport.go`; вызовы — `bookrun.go:146` (после сида), +`mining.go:226` (авто-континью), `mining.go:242` (стоп на подпись), `bookrun.go:193` (завершение +прогона), `status.go` (пере-сид внутри `Redrive` — добавлено фикс-листом приёмки, F2). + +**Форма:** `.bank.json` — ОДИН файл, весь банк, тремя статусами. + +```json +{ + "bank_version": "tm-bank-v1", "book_id": "test-book", "total": 2, "signed": 1, + "terms": [ + { "id": "e1e2b5625d031d33", "src": "方源", "dst": "Фан Юань", "kind": "name", + "status": "draft", "origin": "seed", "sense": "", "since_chapter": 0, "until_chapter": 0, "aliases": [] }, + { "id": "fbe2fa3b1a0329e9", "src": "花海", "dst": "Море цветов", "kind": "place", + "status": "approved", "origin": "seed", "sense": "", "since_chapter": 0, "until_chapter": 0, "aliases": ["花"] } + ] +} +``` + +**Решения:** + +1. **Один файл, а не три по статусу.** Канал `/bank` контракта — ОДНА коллекция со счётчиками + `total`/`signed` и полем `status` в строке; три файла заставили бы платформу сшивать их обратно. +2. **`id` — производный от ключа уникальности** `(src, sense, since_ch, until_ch)`, 64 бита sha256, + разделитель U+001F. **НЕ `glossary.id`:** тот — свежий автоинкремент на каждой пересборке банка + (`migrate.go` v5), а банк пересобирается каждый прогон, то есть экспортированный автоинкремент + пере-указывал бы под читателем между двумя чтениями НЕИЗМЕНИВШЕГОСЯ термина. Пин: тест гоняет два + прогона и требует байт-равенства файла. +3. **`kind` = колонка `type`, пустая строка легитимна** (ruby-кандидат не-имя/не-место не имеет типа, + `memseed.go`); проекцию `"" → null` делает платформа (это её работа по §2.8 спутника). +4. **`origin` = колонка `source`** (провенанс). Имя `source` в файле не используется вовсе — иначе между + схемами появляется ложный друг (то же решение, что в §2.8 контракта). +5. **Момент записи — минимум промта плюс два дешёвых.** Обязательные: банк-стоп (оба исхода — стоп на + подпись и авто-континью) и завершение прогона. Добавил: сразу после сида в начале прогона — тогда + книга, которая ничего не майнит, или прогон, умерший до первой волны, всё равно оставляют читаемый + банк. Цена: одно чтение glossary + marshal (миллисекунды на банке в тысячи строк), платных вызовов ноль. +6. **Порядок детерминирован** (по тому же ключу), алиасы сортируются: два прогона над неизменным банком + дают побайтно один файл, значит no-op видно диффом. +7. Пишется через `writeFileAtomic` (temp+rename): артефакт читают, ПОКА идёт прогон. + +**Отличие от строки 101 держу явным** (и в доккоменте файла): 101 — таблица РЕШЕНИЙ на одном стопе +(предложения, эвиденс, «почему такой рендеринг»), существует только у прогона, который что-то намайнил; +125 — СОСТОЯНИЕ банка целиком, включая сид-строки, о которых никто ничего не решал. + +**Тест:** `TestBankExportCarriesEveryStatusWithStableIDs` (все три статуса, поля, стабильность id через +пересборку банка) + проверка обновления на стопе внутри теста строки 101. + +--- + +## 5. Строка 145 — потолок на прогон аргументом + +**Код:** `cmd/tmctl/invocation.go:114` + `:129-145` (флаг и отказ), `cmd/tmctl/main.go` (проводка в +`translate` и `redrive`), `internal/pipeline/runner.go:78` (поле `CeilingUSD`), +`runner.go:356` (`bookCeilingUSD` — ЕДИНСТВЕННОЕ определение «потолок в силе»), +`stagerun.go:480` (`Reserve` — единственная точка допуска), `stagerun.go:507` (текст отказа), `status.go`. + +**Форма:** `--ceiling-usd `. + +``` +tmctl translate --config book.yaml --ceiling-usd 0.5 +``` + +**Решения:** + +1. **Флаг, не ENV.** Промт разрешал «флаг и/или ENV — реши сам» и назвал входом `parseInvocation` — + это путь флага. Один вход = одна валидация; форма совпадает с остальными операторскими осями прогона + (`--verify-bank`, `--accept-rebill`). **Названный альтернативный довод — PD-99 реестра платформы + («argv на INFO — деньги/book-id при воркере»): это свойство ЛОГИРОВАНИЯ платформы, а не форма входа + движка (движок и так получает пути книги через argv). Если владелец решит иначе, ENV добавляется + поверх одной строкой.** +2. **Применяю и к `redrive`.** Redrive переатакует флагнутые чанки настоящими вызовами; оставить его на + книжном потолке значило бы оставить дыру ровно в той поверхности, которую строка 145 закрывает. +3. **Семантика ровно как в промте:** только этот прогон · `book.yaml` не пишется · перекрывает `book_usd`. + Дневной потолок НЕ перекрывается — он аккаунт-широкий страж, а не свойство прогона. + ⚠ **ЭТО КНИЖНЫЙ ПОТОЛОК В СИЛЕ, А НЕ БЮДЖЕТ ПРОГОНА — платформе читать внимательно.** Леджер + сравнивает его с НАКОПЛЕННЫМ committed+reserved книги (`store/ledger.go:67`: + `bookTotal + estimate > BookUSD`), то есть аргумент говорит «книга вправе дойти до $X, пока я + работаю», а не «этот прогон вправе потратить $X». Кто хочет выдать ПРИРОСТ, передаёт + «уже потрачено + прирост»; значение ниже уже потраченного отказывает первой же резервации. Это та же + величина, что и `ceilings.book_usd`, — именно поэтому перекрытие когерентно; вторая, пер-процессная + ось бюджета была бы НОВЫМ денежным механизмом, а не этой строкой. Первая редакция доккоммента + `translate` формулировала это неверно («caps what THIS run may spend») — найдено ревью (§9-Д). +4. **Валидатор Р7 (`config/book.go:250`) не тронут.** Ноль/отрицательное/нефинитное аргументом = отказ + ЗАПУСКА (`fs.Visit` отличает «флаг не передан» от «передан 0»). Итог: «выключено» не выразимо ни с + одного конца — книга не может обойтись без потолка, аргумент не может обнулить его (D39.110). +5. **Текст отказа называет ТОТ потолок, который в силе:** передавшему `--ceiling-usd` больше не советуют + править `ceilings.book_usd` — файл, который не действует. + +**Цена нулевая — предъявлено ИСПОЛНЕНИЕМ, не грепом:** +`TestCeilingsAreAbsentFromEveryHashInput` рендерит payload обоих волновых снапшотов и требует отсутствия +`ceilings` / `book_usd` / `day_usd` / самого значения, плюс двигает `Ceilings` в памяти и требует +неизменности `BriefHash`. (Греп для протокола: `Ceilings` в каноне `BriefHash` нет — +`config/book.go:280-297`, комментарий там же: «Wiring fields (paths, ceilings, db) deliberately excluded»; +в `buildSnapshotID` (`snapshot.go:341-450`) поля потолка нет.) +⚠ Наивный греп по подстроке `ceiling` даёт ЛОЖНОЕ срабатывание: `edit_ceiling_out` — это бюджет +сегментации. Первая редакция теста на этом и упала; тест ищет имена, а не подстроку. + +**Приор промта проверен и подтверждён:** resume после ceiling-стопа с БОЛЬШИМ аргументом — штатное +поднятие потолка, отдельной правки не потребовалось. +`TestRunCeilingMovesNoHashAndResumesWhenRaised`: тесный потолок останавливает прогон; повторный запуск с +`--ceiling-usd 10` доводит книгу до конца, и **суммарно за оба прогона ровно 6 вызовов провайдера** +(3 юнита × 2 стадии) — оплаченное первым прогоном переиграно из чекпойнтов, не куплено заново. + +**Тесты:** `TestRunCeilingArgumentOverridesTheBookCeiling` (перекрытие, книжный потолок цел, book.yaml +байт-равен до и после), `TestRunCeilingMovesNoHashAndResumesWhenRaised`, +`TestCeilingsAreAbsentFromEveryHashInput`, `TestParseCeilingUSD` (CLI: отсутствие/значение/обе формы +записи/отказ на `0`, `-1`, `NaN`, `Inf`). + +--- + +## 6. Самопроверка + +**Батарея целиком, исполнением:** + +``` +$ make battery ; echo EXIT=$? +… +golangci-lint run --timeout=15m ./... +0 issues. +go test ./... -race -count=1 → все пакеты ok +--- did NOT run (no stand data; see battery-stand) --- +--- SKIP: TestHelperKillLoop (0.00s) +EXIT=0 +``` + +Единственный SKIP — `TestHelperKillLoop` (вспомогательный процесс kill9-теста, скипается всегда, до пака +тоже). Корпус-гейченные тесты (`TM_MINER_PARITY`, `TM_CHECKER_LABELS`) в голой батарее не идут — это +известное свойство `make battery`, а не следствие пака. + +**Дифф `^func Test` — ИСПОЛНЕНИЕМ** (`git grep -h "^func Test" HEAD -- 'backend/**_test.go'` против грепа +по рабочему дереву): **717 → 749**, добавлено 32, удалено 0. + +| Добавлено | +|---| +| `TestPhaseProgressSplitsTheWaves` · `TestPhaseProgressCountsAFlaggedUnitAsResolved` · `TestPhaseProgressHasNoEditWaveOnADraftOnlyPipeline` | +| `TestManifestServesTheReadModelsIdentically` · `TestManifestIsIgnoredWhenTheSourceChanges` · `TestManifestChapterIDSurvivesAReCutAndAnEditElsewhere` · `TestManifestChunksReproduceTheCut` · `TestManifestCommandProducesATreeBeforeAnyRun` | +| `TestSplitChunksWithChaptersLeavesTheSplitUnchanged` · `TestSplitChunksWithChaptersReturnsPreStripText` | +| `TestBankStopMachineTableCarriesTheWholeTable` | +| `TestBankExportCarriesEveryStatusWithStableIDs` | +| `TestRunCeilingArgumentOverridesTheBookCeiling` · `TestRunCeilingMovesNoHashAndResumesWhenRaised` · `TestCeilingsAreAbsentFromEveryHashInput` · `TestParseCeilingUSD` | +| `TestRunLeavesTheContractArtifacts` (страховка от артефакта, который пишется на пути, куда не ходит ни один тест) | +| **По находкам ревью (§9):** `TestManifestIsNotWrittenWhenTheSourceMovesUnderTheRead` · `TestManifestKeyFollowsThePipelineShape` · `TestManifestWithBrokenCountersIsRefused` · `TestBankStopMachineTableIsClearedWhenNothingAwaitsSignature` | +| **По фикс-листу приёмки (§11):** `TestManifestUnitIDDiesWithTheCut` · `TestBankExportIsRefreshedWhenARedriveReSeedsAndAborts` · `TestBankStopMachineTableIsClearedWhenMiningIsUnconfigured` · `TestBankStopMachineTableIsNotCreatedForABookThatNeverMines` | +| **По микро-фикс-листу V2 (§11а):** `TestBankTermIDIsUnambiguous` · `TestManifestWithAbsurdButConsistentCountersIsRefused` · `TestCutTagFollowsTheLangpack` · `TestManifestOfAnOlderDocumentVersionIsRefused` | +| **По слепому аудиту клеймов (§11б):** `TestManifestFastPathIsActuallyTaken` · `TestTranslateAndRedriveWireTheRunCeiling` · `TestWriteFileAtomicLeavesNoLitter` | + +**Голден не пере-захватывался** (`golden_test.go` зелёный без правок). Ни снапшот, ни `RequestHash` пак не +двигает: новые поля — read-модель и файлы-проекции, потолок в хеши не входит (§5), `SplitChunks` доказанно +байт-равен (`TestSplitChunksWithChaptersLeavesTheSplitUnchanged`). + +**Инварианты соблюдены:** сетевых поверхностей не появилось (новых слушателей/HTTP в диффе нет); +процесс-на-прогон и EXCLUSIVE flock не тронуты; read-пути по-прежнему открывают store `query_only`. + +**Гардрейлы:** `.env` не читал; PUML не рендерил (правил `backend/docs/pipeline.puml` текстом, проверил +две ловушки синтаксиса — внутренних `;` в многострочных метках нет, строк, начинающихся на +`/ | < > ] }`, внутри меток нет). + +**Стенд и чужие зоны:** эталон `~/books/gu-zhenren/coldrun-a/` только читался, и то один раз — для +замеров сделана КОПИЯ в песочнице, чтобы даже `-shm` эталона не трогать. Замерная БД лежит в +`~/books/gu-zhenren/manifest-bench/` (новый каталог, вне git). `eval/`, `platform/`, `frontend/`, +`docs/` (кроме этого отчёта и своего пинга) не трогал — параллельно живы полигон эксп-22 и сессия +раннера платформы. + +--- + +## 7. Прочитанные глазами артефакты + +Все пять открыты и прочитаны целиком (сгенерированы $0: read-путями на стенде и мок-провайдером через +временный дамп-тест, удалённый после чтения): + +1. `manifest.json` фикстуры (2 главы) и стенд-книги (2283 главы) — §2.1; +2. `bank.json` — §4; +3. `bank-stop.json` — §3; +4. `bank-stop.txt` — сверен со своим JSON построчно (те же строки, тот же порядок); +5. вывод `tmctl manifest` и `tmctl status`/`status --json` — §1, §2.5. + +Что нашёл чтением и починил по ходу: `conf` рендерился бы нулём для неупомянутой роли строки (стало +`null`); пустые списки рендерились `null` (стало `[]`); в манифесте юнит-id мог схлопнуться в `:0` при +теоретическом рассинхроне номера главы и текста (добавлен детерминированный fallback + WARN). + +⚠ **Оговорка, поставленная слепым аудитом (§11б):** утверждение «прочитано глазами» было верно для +ПЕРВОГО раунда, а после V2-1 образец `bank.json` в §4 остался с прежними id — то есть файл, документирующий +правку, после этой правки перечитан НЕ был. Артефакт пере-сгенерирован и перечитан; id в §4 теперь взяты +из реальных байт. Тот же класс — размер манифеста в §2.1 (был снят до тега разреза) и якоря `mining.go`. + +--- + +## 8. Девиации от промта и чего не сделал + +| # | Что | Почему | +|---|---|---| +| 1 | `report` НЕ переведён на манифест (строка 100, требование «б») | `QualityReport` по построению нуждается в исходном тексте юнита для residual-скана; варианты и цена — §2.4. Считаю это границей, а не долгом: закрывать её значит либо дублировать 23 МБ, либо снимать сигнал | +| 2 | Добавлена команда `tmctl manifest` (промт её не называл) | Дерево глав нужно ДО прогона, а всё остальное, что строит структуру, — платный путь; §2.5 | +| 3 | `--ceiling-usd` проведён и в `redrive` (промт называл `translate`) | Redrive тратит деньги теми же вызовами; §5 п.2 | +| 4 | Пофазные счётчики добавлены и НА ГЛАВУ (промт просил в StatusReport) | `ChapterPassport` — часть `StatusReport`, а К-10 контракта прямо называет это ценой строки 99. Продуктовую половину К-10 не решал | +| 5 | В общую тест-фикстуру `projectOpts` добавлено поле `draftOnly` | Иначе поведение «у пайплайна нет edit-волны» непроверяемо. Дефолт `false` — все прежние фикстуры байт-равны (батарея зелёная) | +| 6 | Сигнатура `projectRebill` получила ленивый провайдер текста | Иначе быстрый путь `status` пришлось бы кормить бестекстовыми чанками в ветку, которая рендерит контент-хеши, — то есть тихо считать хеши от пустых строк. Провайдер делает это невозможным | +| 7 | `BankStopRow.Variants` сменил тип `[]string` → `[]BankStopVariant` | Правка по находке ревью (§9-В). Человеческие таблицы рендерят метку через `Label()`, поэтому stdout и текстовый сайдкар байт-равны прежним; строковый тип оставался бы единственным полем, которое машинная поверхность не может отдать частями | +| 8 | Пустая дельта майнинга переписывает МАШИННУЮ таблицу пустой, текстовую — нет | Правка по находке ревью (§9-Г). Асимметрия намеренная и названа: машинную читает экран (ему нужно утверждение «подписывать нечего»), текстовую читает человек, который знает, на какой прогон смотрит. Менять поведение текстового сайдкара — трогать принятое D39.118 | + +**Открытый вопрос — один, продублирован в пинг:** поле `heading` манифеста против К-2/К-3 контракта (§2.6). +Остальные работы он не блокирует и не блокировал. + +--- + +## 9. Адверсариальное ревью: что оно нашло и что с этим сделано + +**Механика.** Два независимых верификатора, оба СЛЕПЫЕ к этому отчёту (им прямо запрещено было его +читать — иначе первый же абзац задаёт им карту и они проверяют её, а не работу). Первый выносил вердикт +по ПРОМТУ против ДИФФА, построчно с грунтом; второй охотился за дефектами ВНЕ карты отчёта по заданным +направлениям (бестекстовый манифест · протухание · конкурентность и крах · потолок · счётчики фаз · +экспорт банка · регрессии). Оба писали ноль файлов. Оба независимо вынесли ОДИН И ТОТ ЖЕ несущий дефект +(А ниже), которого соло-проход не увидел. + +**Каждая правка пере-проверена УБИЙСТВОМ МУТАЦИИ** — фикс временно ломался, тест обязан был падать: + +| # | Дефект | Как поймано | Правка | Мутация убита | +|---|---|---|---|---| +| **А** | Ключ годности снимался ВТОРЫМ чтением исходника, уже после ingest+нарезки. Файл, переписанный внутри этого окна (~1.4 c на 23 МБ; переzаливка, правка оператора), давал документ со СТАРОЙ структурой под НОВЫМ хешем — он проходил валидацию вечно и не мог быть опознан как протухший. Это опровергало заявление «состояния «подан устаревший знаменатель» не существует по построению» — моё собственное | оба верификатора, независимо, первым пунктом | отпечаток снимается ДО чтения и пере-снимается ПОСЛЕ нарезки; расхождение = манифест НЕ пишется | ✔ `TestManifestIsNotWrittenWhenTheSourceMovesUnderTheRead` | +| **Б** | `units[]` документа — проекция `outputUnits`, а она ветвится по форме пайплайна; формы в ключе не было. Снятие редакторской стадии не двигало ни одного входа ключа ⇒ сохранённое дерево единиц продолжало валидироваться, описывая группы, которых прогон больше не отгружает | оба, вторым пунктом | `shipping_wave` в ключе | ✔ `TestManifestKeyFollowsThePipelineShape` | +| **В** | `variants[]` машинной таблицы уезжал готовой строкой «` × (proposed for )`» — нарушение правила, которое доккоммент этого же DTO объявляет двумя абзацами выше: «×» и скобка встречаются внутри реальных рендерингов | оба | вариант едет `{dst, chunks, via}`; метку собирает `BankStopVariant.Label()` | ✔ `TestBankStopMachineTableCarriesTheWholeTable` (пин на отсутствие «×» в `dst`). Мутация (вернуть склеенную метку) убита | +| **Г** | Машинная таблица писалась только при НЕПУСТОЙ дельте: подписав все термы, владелец оставлял на диске байт-равный файл прошлого прогона, и экран подписи пере-предлагал бы уже подписанное | слепой верификатор | пустая дельта переписывает машинную таблицу ПУСТОЙ | ✔ `TestBankStopMachineTableIsClearedWhenNothingAwaitsSignature` | +| **Д** | Доккоммент `translate` заявлял «`--ceiling-usd` caps what THIS run may spend», тогда как леджер сравнивает потолок с НАКОПЛЕННЫМ committed+reserved книги. Для платформы, которая будет ставить потолок на прогон, это разница между «дойти до $X» и «потратить $X» | охотник вне карты | доккоммент и help-строка переписаны, инвариант вынесен в §5 п.3 | документация; кода не касается | +| **Е** | `readModelChunks` возвращал bool `withText`, который ОБА вызывателя выбрасывали — то есть гард, объявленный доккоммантом, в коде не существовал; плюс `Status` без манифеста резал книгу ДВАЖДЫ (падение на полную нарезку, а затем `r.bookChunks` провайдером в ре-билл) | охотник вне карты | вторым результатом стал ПРОВАЙДЕР тех же чанков с текстом: на быстром пути — полная нарезка по требованию, на медленном — уже материализованный срез | ⚠ Обе ветки `readModelChunks` исполняются `TestManifestServesTheReadModelsIdentically`, но САМ фикс мутацией НЕ покрывается: реверт дал бы медленнее, а не неверно (лишняя пере-нарезка) плюс вернул бы гард, объявленный доккоммантом и не существующий в коде. За проверенное исполнением не выдаю | +| **Ж** | `loadManifest` проверял ПРОВЕНАНС (версия+ключ), но не внутреннюю согласованность, а счётчики документа идут прямо в `make(...)` и в границу цикла: `chunks_total: -1` в отредактированном руками сайдкаре роняет `tmctl status` паникой | охотник вне карты | `selfConsistent()` перед ключом | ✔ `TestManifestWithBrokenCountersIsRefused` | + +Плюс `f.Sync()` перед rename в `artifact.go` (краш хоста мог оставить усечённый документ под настоящим +именем; манифест это переживает — падает на парсе, — а экспорт банка и стоп-таблица валидации не имеют). + +**Что верификаторы нашли и что я НЕ менял, с причиной:** + +- **`report` остаётся на пере-нарезке** — оба назвали это (слепой пометил строку 100 PARTIAL). Согласен с + квалификацией; причина — §2.4, и это решение о качестве, а не гигиена. +- **`--ceiling-usd` парсится всеми подкомандами, действует у двух** — свойство общего `FlagSet`, ровно + как `--verify-bank` на `status`. Трогать общий разбор ради этого — больше механизма, чем пользы. +- **Переименование стадии даёт трём поверхностям три ответа** — класс существовал до пака (`export` + показывает всё pending, `resolveChunkState` считает по диспозиции); мои счётчики дают третье чтение. + Условие ловит `ConfigDrift`. Дописал прямо в доккоммент `PhaseProgress`, чтобы читатель не выводил это + сам. +- **Новые сайдкары не в `.gitignore`** — верно, и это тот же класс, что у `.bank-stop.txt` / + `.mined-signature.yaml` / `.auto-bank.yaml`. `.gitignore` — КОРНЕВОЙ файл, зона оркестратора; сам не + правил. Предлагаю строку `*.db.*.json` + `*.db.*.txt` + `*.db.*.yaml`. +- **`kind` экспорта банка — свободная строка, а контракт держит enum и `null`** — движок не валидирует + `type` сида ни на одном пути, так что закрытый словарь в экспорте соврал бы о том, что там лежит. + Проекция `"" → null` — работа платформы (§2.8 спутника); словарь открыт — дописал в доккоммент поля. +- **`tmctl manifest` создаёт БД при первом касании проекта** (падение `OpenReadOnly` → `Open`) — верно, + доккоммент «no store write» был неточен и исправлен. Это ровно то поведение, которое `status` имеет + всегда; заводить ради команды второй путь открытия store я не стал. +- **`sourceSHA256` на каждом чтении** (~20 мс на 23 МБ) — цена валидации; названа в §2.4. +- **Контракт держит потолок в ГЛАВАХ (`ceiling_chapters`), а строка 145 и этот пак — в USD** — верно, + и это вопрос контрактной стороны (пересчёт «главы → деньги» живёт на платформе, D39.84), не девиация + реализации. Промт назвал USD прямо. + +**⚠ К утверждениям самого ревью — та же дисциплина.** Два пункта верификаторов я проверил и НЕ принял +как дефекты: (1) «`run-finished` экспорт банка не сработает на exit-2» — `TranslateBook` возвращает `nil` +при флагах (сентинел `CompletedWithFlags` минтится в CLI), охотник это сам пере-проверил и снял; +(2) «перф-клейм puml не воспроизводится» — верификатору было запрещено писать файлы, а замер требует +записать манифест; числа §2.4 сняты мной с командой в шапке таблицы и пере-ранятся приёмкой. + +## 10. Что оркестратору перепроверять в первую очередь + +1. **Ключ годности манифеста** — полон ли список входов (§2.3) ПОСЛЕ добавления `shipping_wave`. Ревью + нашло там два пропуска подряд; третьего искать стоит именно здесь, потому что забытый вход = тихо + устаревшее дерево, а не громкая ошибка. +2. **Семантика `--ceiling-usd` как КНИЖНОГО потолка, а не бюджета прогона** (§5 п.3). Это то, что + платформа будет ставить с экрана; если ратифицированная форма D39.110 подразумевает прирост, нужен + либо пересчёт на стороне платформы, либо вторая ось — и второе есть новый денежный механизм. +3. **Семантика `done` пофазных счётчиков** (§1 п.2) — решение моё, следствие `edit.done ≥ done` + намеренное; если приёмка считает иначе, это правка одной строки предиката. +4. **Граница `report`** (§2.4) — согласиться, что это граница, а не недоделка. +5. **Форма `heading`** (§2.6) — вопрос владельцу/автору контракта. +6. **Момент записи банк-экспорта** (§4 п.5) — достаточно ли трёх точек, или нужен ещё какой-то рубеж. +7. **Числа §2.4** — пере-ранятся командой из шапки таблицы; бинарь HEAD пересобирается из `06f0342`. +8. **`.gitignore` для новых сайдкаров** (§9) — корневой файл, зона оркестратора. + +--- + +## 11. Фикс-лист приёмки (исполнен) + +| # | Что | Правка | Проверка | +|---|---|---|---| +| **F1** MAJOR | Объявленная в §2.2 граница была ЛОЖНОЙ: `unit.id = :`, а leader-индекс первой единицы главы всегда 0 ⇒ «`<глава>:0`» переживал любую пере-нарезку, указывая на другой текст. Потребитель читает «id на месте ⇒ якорь цел» и ошибается | в `unit.id` вошёл ТЕГ РАЗРЕЗА (`cutTag` = хеш `chunker_version` + сегментация + форма пайплайна), `manifest.go:129-166`; доккоммент `ManifestUnit.ID` и таблица границ §2.2 переписаны | ✔ `TestManifestUnitIDDiesWithTheCut` — два бюджета над одним текстом: id глав равны, НИ ОДИН unit.id не выжил. Мутация (убрать тег) убита | +| **F2** minor | Абортированный `redrive` оставлял `.bank.json` устаревшим: банк уже пере-сеян, но экспорт вызывался только внутри `TranslateBook`, до которого команда не доходила | `r.exportBank(ctx, "redrive/re-seeded")` сразу после пере-сида в `Redrive` (`status.go`) | ✔ `TestBankExportIsRefreshedWhenARedriveReSeedsAndAborts` (редрайв падает на дрифт-гарде, экспорт описывает новый банк). Мутация убита | +| **F3** minor | «Майнинг не сконфигурирован» не чистил `.bank-stop.json` — осиротевший стол подписи жил вечно | `clearBankStopTableJSON` на раннем возврате (`mining.go:53`), с гардом «только если файл есть» — книга, которая никогда не майнит, сайдкар не заводит | ✔ два теста: очистка осиротевшего и НЕ-создание у не-майнящей книги. Обе мутации убиты (прямая и обратная) | +| **F4** | ЧЕТЫРЕ ложные формулировки, а не три: (а) «no store write» у `tmctl manifest` (первое касание проекта создаёт БД) · (б) «`Edit.Done ≥ Done` ровно на гейт-флагнутых» (причин три, а для draft-only соотношение вообще не в ту сторону) · (в) «книга не может без потолка» (Р7 разрешает day-only) · **(г) гарантия «U+001F не встречается» в `bankexport.go`** | (а)(б)(в) — доккомменты `manifest.go`, `status.go` (`PhaseProgress`), `invocation.go` + `runner.go`. **(г) в первом дофикс-раунде НЕ исполнен, и пропуск не был задекларирован — файл остался байт-идентичным, а строка отчёта перечисляла три пункта так, будто список полон. Поймано ре-чеком приёмки, закрыто как V2-1 (§11а)** | (а)(б)(в) — документация, мутацией не покрывается и за проверенное не выдаю; (г) — см. V2-1 | +| **F5** | Дрейф `file:line` ссылок отчёта после фикс-раунда | якоря §1/§2/§3/§4/§5 пере-сняты грепом по текущему дереву | ✔ греп, вывод в логе сессии | +| **F6** | `backend/README.md` не назван в списке правленых файлов; в `selfConsistent` не было верхней границы | README назван в §2; `manifestMaxChunks = 10 000 000` + проверка суммы и per-unit | ⚠ **Клейм «покрыто `TestManifestWithBrokenCountersIsRefused`» был ЛОЖЕН и опровергнут исполнением:** все счётчики в том тесте НЕсогласованны, они умирают на равенствах и до границы не доходят — верификатор выключил границу, батарея осталась зелёной. Код был верен, тест отсутствовал. Закрыто как V2-2 (§11а) | + +**В1 (главы) — решение оркестратора принято:** поле `heading` остаётся рендером движка как ВРЕМЕННОЕ, +пак принимается, настоящие заголовки уезжают отдельной строкой бэклога (правка чанкера — зона нарезки и +снапшотов, хвостом в этот пак не тащится; при её приезде все книги получают заголовки ретроактивно, без +пере-перевода). В коде поле уже подписано как «рендер движка, не метка книги» (`manifest.go:76-82`) — +менять там нечего; страж от «временное приживётся» — строка бэклога, её ведёт оркестратор. + +### 11а. Микро-фикс-лист V2 (хвосты ре-чека приёмки, исполнен) + +| # | Что | Правка | Проверка | +|---|---|---|---| +| **V2-1** (из F4г) | `bankTermID` склеивал ключ уникальности через U+001F и утверждал коммантом, что разделитель «не может встретиться» — предположение о данных, которые движок НИГДЕ не валидирует: `src`/`sense` копируются из сид-YAML, и контрольный символ там ничем не запрещён | части ключа **длино-префиксированы**, кодировка инъективна ПО ПОСТРОЕНИЮ — утверждению больше не нужно быть истинным (и не нужно пере-проверяться при появлении нового источника сида); коммент переписан | ✔ `TestBankTermIDIsUnambiguous` — два РАЗНЫХ ключа, чьи separator-склейки байт-равны, обязаны дать разные id. Мутация (вернуть склейку) убита | +| **V2-2** (из F6) | Границу `manifestMaxChunks` не тестировало ничто | тест на СОГЛАСОВАННО-абсурдный сайдкар: все равенства проходят, отказать может только граница; утверждение останавливается на `loadManifest`, потому что при снятой границе реконструкция аллоцировала бы заявленное число чанков и уронила бы машину, а не тест | ✔ `TestManifestWithAbsurdButConsistentCountersIsRefused`. Мутация (выключить границу) убита | +| **V2-3** (зазор спеки приёмки, не брак сессии) | `cutTag` не включал версии langpack/embedded/norm — а они двигают нарезку (правило заголовка решает, съедает ли номер глава-из-одного-хедера; embedded несёт CJK-числительные и терминаторы; нормализация решает, что вообще режется). Пере-строенный манифест отчеканил бы ТЕ ЖЕ unit.id над другим текстом — класс F1, узкий триггер | три версии добавлены в `cutTag` (в ключе они лежали рядом) | ✔ `TestCutTagFollowsTheLangpack` (единственная из трёх осей, варьируемая в рантайме; embedded/norm — константы бинаря, как и `chunkerVersion`, который тест сегментации тоже не варьирует — сказано прямо, за исполненное не выдаю). Мутация убита | +| **V2-4** | `tm-manifest-v1` не бампнут: сайдкар от до-дофиксного бинаря структурно валиден и валидировался бы вечно, отдавая читателю id в СТАРОЙ форме — ровно той, чей дефект закрывает тег | версия документа → **`tm-manifest-v2`** | ✔ `TestManifestOfAnOlderDocumentVersionIsRefused` (фикстура — настоящий v1-документ со старой формой id, не пере-клеенный v2). Мутация убита | + +⚠ **Про сам мутационный прогон.** Первые два прогона V2-1 и V2-3 «прошли» вхолостую: `go test -run` с +шаблоном, которому НИЧЕГО не соответствует, возвращает `ok`. Для V2-1 теста тогда не существовало вовсе, +для V2-3 существующий тест варьирует только сегментацию. Оба написаны, мутации перебиты, а в скрипт +добавлен сторож «ни один тест не выбран шаблоном = прогон вхолостую». + +### 11б. Слепой аудит клеймов (исполнен) + +После фикс-листа и V2 запущен ещё один независимый проход — СЛЕПОЙ и с одной задачей: искать ЛОЖНЫЕ +УТВЕРЖДЕНИЯ, потому что именно на них сессия срывалась дважды. Он пере-убил все 23 заявленные мутации +(ни одна не оказалась холостой) и подтвердил числа, формы артефактов и все «не тронут». Нашёл он другое: + +| # | Что | Правка | +|---|---|---| +| **A4** (несущее) | ЧЕТЫРЕ изменения кода не держал НИ ОДИН тест, и в списке «мутацией не покрывается» они не значились. Худшие два: **сам быстрый путь манифеста** (его удаление проходило всю батарею, потому что `TestManifestServesTheReadModelsIdentically` сравнивает «с манифестом» против «без манифеста» — обе стороны становились медленным путём и тест проходил ВХОЛОСТУЮ; вся строка 100(б) держалась на замере в отчёте) и **проводка `--ceiling-usd` в CLI** (`TestParseCeilingUSD` кончается на разобранном инвокейшне, а пайплайн-тесты ставят поле на Runner напрямую — провод между ними не пинило ничто) | добавлены `TestManifestFastPathIsActuallyTaken` (пин по НАБЛЮДАЕМОМУ признаку: быстрый путь отдаёт чанки БЕЗ текста, медленный — с текстом), `TestTranslateAndRedriveWireTheRunCeiling` (обе команды доводятся до реального стопа по потолку; для `redrive` книга сперва доводится до флагнутого чанка, иначе резервации нет и потолок недостижим), `TestWriteFileAtomicLeavesNoLitter`. Все четыре мутации убиты | +| **A1/A2/A5** | Числа и образцы, снятые ДО раунда V2 и не пере-снятые: размер манифеста (1 038 554 → **1 077 038**, ровно 4276 юнитов × 9 байт тега), два id в образце `bank.json` (это единственное поле, которое V2-1 и менял), время `tmctl manifest` и `sha256sum` | все пере-сняты с реальных байт; §7 получил оговорку | +| **A3** | Клейм F5 «якоря пере-сняты» ✔ был верен для §1/§2/§5 и устарел для §3/§4 на 8 строк | якоря `mining.go` исправлены и сверены | +| **A7** | Список входов ключа в пинге и в `pipeline.puml` не называл `shipping_wave` — тот самый вход, который добавила находка Б | дописан в обоих; в puml добавлена строка про смертность `unit.id` | + +**Не покрыто тестом и объявлено (полный список, а не выборка):** доккоменты Д и F4(абв) · оси +embedded/norm в `cutTag` · сам факт, что реверт `withText`-провайдера даёт медленнее, а не неверно · +**две из четырёх точек записи банк-экспорта** (`run-start/seeded` и `run-finished` маскируют друг друга: +удаление любой ОДНОЙ оставляет батарею зелёной, потому что артефакт всё равно пишется другой; отдельный +пин потребовал бы прогона, где банк меняется МЕЖДУ ними, а это только путь майнинга — и он пинится) · +**атомарность записи против конкурентного читателя** (из одного процесса не наблюдаема; тест пинит то, +что наблюдаемо: замену целиком, отсутствие temp-мусора и права 0644). diff --git a/platform/docs/platform-PROGRESS.md b/platform/docs/platform-PROGRESS.md index 01151183..d9670f6e 100644 --- a/platform/docs/platform-PROGRESS.md +++ b/platform/docs/platform-PROGRESS.md @@ -6,6 +6,22 @@ ## Текущее состояние +- **P4 (08–09.08) построила РАННЕР целиком** (раздел «Сессия P4» ниже): транзиентный systemd-юнит на + прогон · очередь River + воркер · реконсилятор (свип + бут) · тейлер `events.jsonl` с курсором · + первые пять ручек `/v0` по контракту 0.2.0 · дев-интейк книг. Батарея зелёная целиком с живым + PostgreSQL 18.4 под `-race`, линтер 0 issues, `make vuln` чист, **скипов ноль**. Тестов + **105 → 242** (удалённых ноль). **Дерево НЕ закоммичено — ждёт приёмки.** +- ⚠ **Адверсариальное ревью (четыре независимых верификатора) нашло ТРИ денежные ошибки и три + запирающие прогон — все закрыты в этом же дереве, с пинами и повторной живой пробой.** Раздел + «Адверсариальное ревью» ниже; главная — расчёт брал пожизненную трату КНИГИ вместо траты прогона + (PD-124), и её независимо воспроизвели два верификатора. +- **Регистр после P4 — 143 строки** (`python3 docs/scripts/counts.py`): 107 закрыто · 3 приняты риском · + 1 закрыт ратификацией · **32 открыто** (1 major — PD-113, 9 minor, 22 info). Закрыты в P4 шесть + СТАРЫХ строк: PD-43 · PD-81 · PD-82 · PD-97 · PD-99 · **PD-105** (ратификация D39.119 + + реализация). Заведены тридцать шесть новых, PD-108…PD-143: двадцать пять найдены И закрыты + внутри пака (пять — собственной самопроверкой, двадцать — адверсариальным ревью), десять + оставлены открытыми вопросами и гейтами. + - **P3 (08.08) отработала фикс-лист приёмки P2 ЦЕЛИКОМ — все восемь пунктов** (раздел «Сессия P3» ниже): PD-80 · PD-79 · PD-83/84/85 · PD-100 · PD-106 · PD-91 · PD-95 · PD-104 (часть док↔код). 22 посадки, 22 поймано; батарея зелёная офлайн и с живым PG 18.4 под `-race`, линтер 0 issues, @@ -50,6 +66,257 @@ - Дизайн-ответы К-4 · К-7 · К-12 · форма П-5 — ниже, ПРЕДЛОЖЕНИЯМИ на ратификацию. - Контрактных ручек нет намеренно: они ждут ратификации К-4/К-7 (форма ответов) — это П-1. +## Сессия P4 (08–09.08): раннер — юнит-на-прогон, очередь, реконсилятор, тейлер, первые ручки /v0 + +**Каждое число ниже — с командой, которой получено.** Дерево не коммичено; в индексе ничего не держу. + +### Работа 1 — транзиентный systemd-юнит на прогон + +`internal/runner/runner.go` (юнит), `marker.go` (маркер выхода), `engine.go` (инвокация движка). + +**Привилегия-модель решена ДО постройки, и второй вариант отвергнут по СВОЙСТВУ, а не по вкусу.** +Выбран linger-пользователь: юниты создаются в СОБСТВЕННОМ менеджере платформы, новых привилегий в +рантайме ноль. Polkit-вариант research/25 непригоден: **polkit авторизует ГЛАГОЛ, а не свойства** — +`StartTransientUnit` отдаёт выбор `ExecStart=`/`User=` вызывающему, а транзиентный СИСТЕМНЫЙ юнит без +`User=` идёт от root, то есть правило выдаёт сервису root и сузить это нечем. Плюс до systemd v257 в +запрос авторизации не попадает даже ИМЯ юнита (systemd issue #17224 → PR #34651, влит 09.10.2024), +так что на стенде (systemd 255, `systemctl --version`) «узкое правило» не узкое вовсе. Разбор — +`STACK_DECISIONS` §15. + +**Замерено на стенде, не выведено из доки** (все команды — `systemd-run --user`, стенд без root): + +| Свойство | Как замерено | Результат | +|---|---|---| +| Юнит переживает того, кто его создал | спавнер-скрипт стартует юнит и выходит | `ActiveState=active`, маркер `success` | +| `--collect` ⇒ состояние systemd не истина | `systemctl --user show` после выхода | `LoadState=not-found`, `ExecMainStatus=0` для прогона, вышедшего с кодом **3** | +| Маркер несёт исход | `ExecStopPost` на трёх исходах | `exit-code/exited/3` · `success/killed/TERM` · `oom-kill/killed/TERM` | +| **Лимиты в `app.slice` НЕ применяются** | `cat app.slice/cgroup.subtree_control` | **пусто**; лист без `memory.max`; процесс, потрогавший 400 МиБ, пережил `MemoryMax=64M` | +| Лимиты в своём срезе применяются | то же в `tm-runs.slice` | `memory.max=67108864`, `pids.max=32`, тот же процесс — `oom-kill`, `Memory peak: 64.0M` | +| `%`-спецификаторы в `--property=` не раскрываются | `ExecStopPost` с `100%_done`/`%n` | доехали буквально ⇒ экранировать `%` не нужно и было бы неверно | +| `ProtectHome=yes` прячет `/run/user/` | проба под юнитом | `Permission denied` ⇒ своя же шина недостижима; `ProtectHome=tmpfs`+`BindPaths` ⇒ сокет виден | +| Шине хватает `DBUS_SESSION_BUS_ADDRESS` | `env -u XDG_RUNTIME_DIR` | работает; без обоих — `Failed to connect to bus` | + +**Ответ PD-13 живёт в cgroup ПРОГОНА и измерен.** ⚠ И там же найдено собственным флейком: `MemoryMax` +БЕЗ `MemorySwapMax=0` не ограничивает прогон — ядро выдавливает страницы в своп (у среза +`swap peak: 692.5M`), процесс выживает и тормозит до скорости диска. Для перевода это хуже отказа: +тормозящий часами прогон продолжает платить за каждый прошедший вызов. Снято: `MemorySwapMax=0` идёт +вместе с `MemoryMax` (`runner.go`), три прогона пина подряд зелёные, три посадки «убрать своп-лимит» +подряд красные. + +Пиннинг версии (строка 139): `run_attempts.engine_binary` пишется ДО создания юнита, новая попытка +его НАСЛЕДУЕТ, резюм исполняет именно его, а переход на другую сборку требует явного +`TM_PLATFORM_RESUME_MAY_CHANGE_ENGINE`. ⚠ Первая редакция закрывала эту строку НАПОЛОВИНУ — путь +читался каналом ремонта и не исполнялся резюмом; поймано моей же пост-сверкой диффа с промтом, не +батареей (обе половины компилировались и всё было зелёным). PD-143. + +### Работа 2 — очередь River + воркер + +`go.mod`: `github.com/riverqueue/river v0.42.0` + `riverdriver/riverpgxv5` (пин из `STACK_DECISIONS` +подтверждён). `internal/runs/queue.go`. + +**Холд берётся ДО спавна и в ОДНОЙ транзакции с созданием прогона, попытки и записи очереди** +(`pgstore.StartRun`, `runs.go:66`): холд без прогона — деньги, зарезервированные ни за чем; прогон без +холда — трата незарезервированного; запись очереди без обоих — воркер, спавнящий движок против книги, +у которой нет бухгалтерии. Очередь вставляет своё задание через колбэк с той же `pgx.Tx`. + +**Гард «один живой прогон на книгу» — исполнением:** частичный уникальный индекс +`runs_one_live_per_book` мапится в доменную `ErrRunInFlight` (не сырой SQLSTATE), ручка отдаёт 409. +Живая проба: второй `POST .../runs` на ту же книгу → **409**, `Reserved` не сдвинулся. + +`MaxAttempts: 1` у задания — намеренно против рефлекса очередей: повтор здесь не доделывает работу, а +спавнит ВТОРОЙ движок; восстановление — работа реконсилятора. Живая проба: повторная доставка задания +на живой прогон не создала второго юнита (`len(started)` = 1). + +### Работа 3 — реконсилятор + +`internal/runs/reconcile.go`. На КАЖДЫЙ выход юнита — через **ExecStopPost-маркер**, не D-Bus: сигнал, +посланный в момент, когда платформа лежит (а она лежит несколько раз в неделю по замыслу), теряется; +файл — нет. На БУТЕ тот же свип перезапускает прерванные прогоны (строка 138): истина — каталоги книг +и Postgres, systemd спрашивается только «жив ли юнит», и это улика, не вердикт. + +Разграничитель «не стартовал ещё» / «умер без слова» — время (`spawnGrace` 60 с). Перезапуск даёт +новой попытке ОСТАТОК бюджета (`budget − RunSpent`), иначе один прогон потратил бы потолок дважды; +если остатка нет — `paused`/`credit_exhausted`, не `failed`. + +**Расчёт — из фигуры движка, прочитанной ПОСЛЕ выхода процесса** (`status --json`, ратифицированный +канал ремонта; лока нет, гадать не о чем). Не прочиталась — резервация остаётся ОТКРЫТОЙ и попадает в +`UnsettledRuns` для следующего свипа. ⚠ Эскроу-половина research/25 (write-ahead intent, `uncertain`, +`closing`) НЕ строилась: это строка 136 и её собственный промт. + +**Резюнк — честно редкий и честно грубый:** по умолчанию 5 минут (`TM_PLATFORM_RESYNC_EVERY`), и +только там, где чинить нечего — курсор не двигался ЛИБО материализация в карантине. Причина в цене: +каждый вызов заново ингестит и режет исходник (1.4–1.5 с CPU на книге 23 МБ, строка 100). + +### Работа 4 — тейлер `events.jsonl` + +`internal/ingest/tail.go`. Курсор `(engine_run_id, seq)` коммитится в ОДНОЙ транзакции с эффектом +(`pgstore.RunSink.Apply`), байтовое смещение — хинт. Файла ещё нет (эмиттер = строка 103) — тейлер +спокойно ждёт (`ErrNoJournal` не ошибка свипа). + +**PD-105 ратифицирован этим паком и реализован:** дубль `seq` — НОРМА, пропускается идемпотентно и +без фатала; тот же `seq` с ДРУГИМ payload — `ErrPayloadConflict` → карантин ПРОЕКЦИИ (не прогона: +движок тратит уже зарезервированные деньги, и наша неспособность читать его журнал — не повод это +выбросить). Пропасть остаётся ошибкой. Строка PD-105 реестра обновлена. + +Частичная последняя строка не читается как событие (писатель ещё пишет). Журнал пер-книжный и +append-only, поэтому резюм дописывает ВТОРОЙ `hello`: читатель ведёт область и не судит чужие строки. + +### Работа 5 — ручки `/v0` по контракту 0.2.0 + +`internal/httpapi/v0.go`: `GET /books` · `GET /books/{bookId}` · `GET /books/{bookId}/run-options` · +`POST /books/{bookId}/runs` · `GET /usage`. Прогресс/статус прогона отдаётся внутри карточки книги — +отдельной ручки прогона в спеке НЕТ, и выдумывать её я не стал. + +`CeilingBounds`: максимум = `Balance` КАК ЕСТЬ (холд — дебет в момент взятия), подрезан И остатком +книги; `max_chapters: 0` легален. `default_chapters` = верх шкалы — продуктовая политика платформы +(D39.115 §4), компромисс назван вслух в коде и вынесен вопросом ниже. + +**Интейк:** дев-инструмент `tmplatformctl book add` (Go-подкоманда, а не шелл-скрипт: тестируется). +`POST /books` (multipart) НЕ взят — обоснование строкой **П-9** зонного бэклога. + +**Пересчёт «главы→доллары»** — константа платформы `$0.03`, провенанс: exp08 v2 ($10.94 на 500 глав = +$0.0219) через ревизию D30.4 (+15–25% → $0.0252–0.0274), округление ВВЕРХ. Движковой поверхности +оценки не выдумывал; потребность — строка **П-10**. + +### Работа 6 — регистр + +Закрыты: **PD-43** (денежный контур получил вызывающих) · **PD-81** · **PD-82** · **PD-97** · +**PD-99** · **PD-105**. Заведены **PD-108…PD-116** (из них PD-108/110/111/116 — дефекты, найденные и +закрытые внутри этого же пака; PD-112…115 открыты вопросами). Строки эмиттер-стороны (PD-60/61) не тронуты. + +### Сквозная проба на боевом бинаре (не тестами) + +Поднят `tmplatformd` против живого PG, с фейковым `tmctl` ($0, говорит ровно две поверхности: +инвокацию с потолком и `status --json`). + +| Шаг | Наблюдение | +|---|---| +| `book add` + `GET /v0/books` | книга в библиотеке, `status: not_started` | +| `GET run-options` | `min 1 / max 333 / default 333` — $10 / $0.03 = 333, подрезано 500 главами книги | +| `POST runs` (100 глав) | `202`, форма `Run` по спеке; юнит `tm-run--1.service` **active running** | +| потолок, дошедший до движка | `--ceiling-usd 3.000000` = 100 × $0.03 | +| второй `POST` на ту же книгу | **409** | +| `/v0/usage` при открытом холде | `remaining_percent: 70` (холд — дебет) | +| прогресс из журнала | `draft 4/10, edit 0/10` — пофазно, без затирания резюнком | +| выход юнита | книга и прогон → `ready`, `finished_at` проставлен | +| расчёт | грант $10 → движок отчитался $0.42 → баланс **9.580000** (не потолок $3) | +| гигиена логов | `grep -c 'ceiling-usd\|3.000000\|bk_' daemon.log` = **0** | +| systemd после `--collect` | юнитов **0** | + +**Ратифицированное свойство D39.106 — прогон переживает деплой — проверено убийством платформы:** +`pkill -9` по имени процесса → API отвечает `000`, юнит `active running`, журнал за 6 с вырос +`10 → 13` строк. Платформа поднята заново → карточка показала `draft 21/300` (догнала всё, что было +записано, пока её не было), через 6 с — `24/300`, юнит ОДИН, в логе новой платформы +`grep -c 'run unit started'` = **0** (второго движка не создано). Остановка через systemd → маркер +`success/killed/TERM` → статус **`stopped`** (не `failed`). + +### Батарея и самопроверка + +- `make check` с `TM_PLATFORM_TEST_DSN` (живой PostgreSQL 18.4, `-race`): все 13 пакетов зелёные, + линтер **0 issues**. Прогнана дважды подряд. +- Скипы: `go test ./... -count=1 -v | grep -c -- '--- SKIP'` = **0**. +- `make vuln`: **No vulnerabilities found**. +- Дифф `^func Test` ИСПОЛНЕНИЕМ (`git grep '^func Test' HEAD -- 'platform/**/*.go'` против того же + грепа по дереву): **105 → 203**, удалённых **0**, добавленных **98**. + ⚠ Две мои промежуточные редакции этого счёта были неверны, и это записано, а не подчищено: + сначала pathspec не тот, потом грep без `*.go` — второй ловил КОПИЮ теста, вставленную в этот же + журнал приёмкой P1 (`platform-PROGRESS.md:683`), и рисовал ложное «удалён + `TestHoldAndSettleOnTheSameAttemptDoNotDeadlock`». Тест на месте: `credits_test.go:308`. +- **Посадки (мутации): 12 из 12** в самопроверке; **139 в аудите ревью** — 107 поймано, 32 пережили + (8 не ослабления), по ним написано **22 новых пина**, каждый проверен своей посадкой (сначала 9/10, + затем 9/9 — две мои мутации были сформулированы мимо и переделаны). Итого посадок в паке — **33 + моих, все пойманы**, плюс аудит ревью., каждая восстанавливалась после прогона: + холд вне транзакции допуска · мапинг `runs_one_live_per_book` · high-water mark в синке · + `greatest` у spend · payload-конфликт · детект пропасти · частичная строка как событие · + область чужого потока (обе стороны: `mine := true` и `mine := false`) · срез прогона · + половина шкалы (та самая ошибка «минус Reserved») · грация спавна · потолок как `failed` · + argv в INFO-логе · `MemorySwapMax` · compare-and-set права на спавн. + ⚠ **Одна посадка ПЕРЕЖИЛА первую редакцию пина** и это записано, а не подчищено: high-water mark + проверялся событием `progress`, а `progress` — присваивание, то есть идемпотентен сам по себе. + Настоящий пин — считающий эффект (`unit_done`): `TestARedeliveredCountingEventDoesNotCountTwice`. + +### Девиации и то, чего не сделал + +1. **`503` на старте прогона не входит в перечисленные спекой статусы операции** — PD-112, вопрос + владельцу контракта. Каждый разрешённый код в этой ситуации соврал бы. +2. **Стоп по потолку сегодня отдаётся как `failed`** — PD-113, контрактно видимо. Движок возвращает + потолок ошибкой (`errReserveCeiling`, сверено в HEAD), события потолка нет (строка 103). Ветка под + событие построена и запинена; выдумывать порог «сколько процентов потолка = стоп» я не стал. +3. **`POST /books` не взят** — П-9, с обоснованием. PD-72 закрывается вместе с ним. +4. **Эскроу/`uncertain`/`closing` не строились** — строка 136, следующий денежный промт. +5. **Стоп/резюм прогона как ручки контракта** не в списке работ промта — не строились; банк-стоп + заканчивает попытку и прогон, резюм = новый прогон. +6. **`Supervisor.Status` дев-пути починен** (PD-108) — он в моей зоне и это мой канал ремонта. + +### Адверсариальное ревью (author≠reviewer), и почему это главный результат пака + +Четыре независимых верификатора, ни один не читал отчёт (его на тот момент не существовало): +(1) промт+дифф вслепую · (2) охота за дефектами ВНЕ карты · (3) сверка провода с openapi 0.2.0 · +(4) сила пинов посадками. Каждому запрещено писать в репозиторий; эксперименты — в копиях. + +**Нашли три ДЕНЕЖНЫЕ ошибки и три запирающие прогон. Все закрыты в этом дереве, каждая с пином.** + +| Находка | Чем это было | Как закрыто | +|---|---|---| +| **PD-124** (major) | Расчёт брал `committed_usd` — ПОЖИЗНЕННУЮ сумму КНИГИ (`SUM(committed_usd) … WHERE book_id`) — как трату прогона. Второй прогон книги оплачивал первый заново; после того как сумма книги перерастает потолок, каждый прогон стоит ровно свой потолок. **Воспроизвели ДВА верификатора независимо** | Попытка пишет базовую линию книги ДО старта (миграция 00010) и платит разницу; линию не прочитать = не стартуем | +| **PD-125** (major) | Перезапуск при отложенном расчёте брал ВТОРОЙ холд и терял первый навсегда: списки его не находили | Перезапуск спрашивает `AttemptReservationOpen`; список несведённых ключуется на завершённости ПОПЫТКИ | +| **PD-126** (major) | Юнит, который не удалось создать, оставлял «право на спавн» — и каждый свип перезапускал прогон, съедая потолок по свипу за раз | Неудавшийся `Start` снимает право; повторяется та же попытка | +| **PD-127** (major) | Одна нечитаемая строка журнала возвращала ошибку ДО чтения маркера: прогон навсегда `translating` с висящим холдом | Любая ошибка журнала = карантин ПРОЕКЦИИ, жизненный цикл продолжается | +| **PD-128** | Грация спавна мерилась от старта ПРОГОНА ⇒ у перезапущенной попытки её не было | Мерится от старта попытки | +| **PD-129** | Инверсия порядка блокировок `books`↔`runs` между материализатором и финишером ⇒ взаимоблокировки | Книга блокируется первой везде | +| **PD-130/131** | Любая ошибка чтения журнала выглядела как «догнали»; хендшейк не обязан был нести `seq 1` | Только настоящий EOF; `seq != 1` = плохой хендшейк | +| **PD-132** | Холд прогона, который так и не стартовал, не возвращался | Нет линии и нет юнита ⇒ холд возвращается целиком | +| **PD-133/134/135** | Инстанс без движка не отдавал даже библиотеку; пиннинг версии писался и не читался; прерванный прогон вставал `paused` без причины | Чтения монтируются отдельно; `EngineBinary` читается и используется; `PauseRun` вместо `FinishRun` | +| **PD-117…121** | Потолок ниже минимума схемы отвечал 409 вместо 400 · `Usage.paused_reason` недостижим через путь реконсилятора · карточка несла ревизию ПРОГОНА (отстающую) · чужой курсор пагинации принимался · «exhausted» при остатке, на который прогон стартует | Все пять закрыты, каждая с пином | +| **PD-136/138** | Деплой-юнит нёс обе диспозиции сразу; `go.mod` не тидинут | Переписано; `go mod tidy` | +| **PD-142** | **Аудит силы пинов: 139 посадок, 107 поймано, 32 пережили** (8 из них — не ослабления: эквивалентный код или страховка DDL). Пережившие — не дефекты кода, а отсутствующие пины, часть на свойствах, объявленных закрытыми | 22 новых пина, каждый проверен своей посадкой. Самые весомые: блокировка строки попытки под КОНКУРЕНЦИЕЙ (восемь горутин на одно событие — последовательная доставка посадку не ловила) и **CSRF на контрактной поверхности** (снятие переживало всё, а это старт платного прогона с амбиентной кукой) | + +**Оставлено открытым и названо:** PD-122 (`Library.revision` не монотонна при удалении книги — сегодня +недостижимо, ручки удаления нет; правильное решение — свой счётчик области, это своя миграция и +несколько путей записи, поэтому НЕ сделано, а объявлено гейтом) · PD-137 (упорядочение на +пользовательский менеджер — UID site-specific, инструкция в шапке юнита) · PD-139 (пути книг в +ERROR-логах — диспозицию надо принять осознанно) · PD-140 (`Service.Stop` не подключён: ручек +стопа/резюма в скоупе промта не было) · PD-141 · PD-112/113/123. + +**Что ревью подтвердило, а не опровергло:** привилегия-модель (с одним уточнением, принятым: +«никакое правило не сузит» верно для ТРАНЗИЕНТНЫХ юнитов; шаблонный системный юнит с фиксированным +`User=`, запускаемый `StartUnit`, — вариант, которого мой разбор не касался) · арифметика +`CeilingBounds` (баланс КАК ЕСТЬ, без второго вычитания холдов — проверено на живом PG пятью +раскладами) · отсутствие кросс-аккаунтных чтений и стартов · инвариант `balance == SUM(ledger)` +(сломать не удалось) · правила тейлера по областям и курсору. + +**Возражение ревью, которое я принял только частично.** Верификатор предложил различать стоп по +потолку через `ceiling_pct` из `status --json`. Не взял: `ceiling_pct` считается против +`book_ceiling_usd`, то есть против потолка ИЗ `book.yaml`, а наш потолок приходит аргументом прогона +(строка 145), которого в HEAD нет — значит смысл поля зависит от ещё не залендённой формы. Взять его +сейчас значило бы построить дискриминатор на поведении, которого я не могу проверить. Записано в +PD-113 как названная и оценённая альтернатива. + +**Повторная сквозная проба на боевом бинаре после починок** (чистая база, фейковый движок с +НАКОПИТЕЛЬНЫМ счётчиком книги, как у настоящего): грант $10.00 → прогон 1 (движок насчитал $0.40) → +баланс **9.600000** → прогон 2 на ТОЙ ЖЕ книге (счётчик книги $0.40 → $0.80) → баланс **9.200000**, +резервов ноль. До починки второй прогон списал бы $0.80. + +### Вопросы (канал вопросов промта; НЕ тихая интерпретация) + +1. **`503` вне перечня спеки** (PD-112) — вносить в спеку или назвать другой код? +2. **`default_chapters` = верх шкалы.** Когда книга дороже баланса, верх шкалы резервирует ВЕСЬ + баланс под одну книгу, и вторую начать нельзя (D39.110 §2в). Альтернатива — доля от максимума; + какая именно — вопрос продуктовый, не инженерный. +3. **Конфигурация (вопрос ВЛАДЕЛЬЦА 08.08, PD-114).** Ответ по существу: единого stdlib-пути в Go + нет; мейнстрим — либо 12-factor «только окружение» (наш выбор, записан в доккомменте `config.go`, + деплой уже использует systemd-нативные `EnvironmentFile=`+`LoadCredential=`), либо слоёная + `флаги > env > файл > дефолты` (koanf/viper). Ниже нормы у нас другое: **27 переменных** + (грепнуто), **ноль флагов** у демона, **нет печати эффективной конфигурации** при старте. + Вопрос владельца при этом нашёл реальный дефект в этом же паке: дефолт «$/глава» лежал в двух + местах — исправлено, резолвится только в `config`. +4. **Абстрактный вопрос владельца, и он подтверждается фактом (PD-115).** `ENGINEERING_STANDARDS` §2 + называет внешнюю версионированную базовую линию ровно для ОДНОЙ оси — безопасности (ASVS 5.0 L2 + + API Top-10 2023). Всё остальное — собственная проза зоны. Разница видна эмпирически: PD-57/PD-58 + нашлись именно сверкой с RFC 9700/9207 и NIST SP 800-63B. Там, где эталона нет, сверять не с чем — + грепнуто на 08.08: метрик и трейсинга **ноль**, процедуры бэкапа/восстановления в `deploy/` нет, + SLO не заданы. Предложение: §2 получает по эталону на ось; ратификация — оркестратора. + ## Ратификация приёмкой P3 (оркестратор №15, 08.08) **Вердикт: фикс-пак ПРИНЯТ и заленден.** Восемь пунктов фикс-листа отработаны, девять строк @@ -1220,3 +1487,11 @@ floor остался 1.26.4, общий с движком). Плюс снят м не возвращает вовсе (падает), поэтому ветки её обработки убраны, а не оставлены изображать проверку. Дерево не коммичено — лендит оркестратор. + +--- + +**Пинг оркестратора №15 (09.08, D39.122): движковый пак «блокеры контракта» ПРИНЯТ и заленден `0e69bc1` — пять поверхностей для платформы существуют. ФИНАЛЬНЫЕ формы (менялись трижды за приёмку — старые в переписке игнорировать):** +- **Манифест глав:** `.manifest.json`, `manifest_version: "tm-manifest-v2"` (v1 движок сам отклоняет); id главы = 16 hex (стабилен через пере-нарезку); **`unit.id = ::`** — тег разреза 8 hex, при любой смене нарезки/данных пары unit.id УМИРАЮТ намеренно (id жив ⇒ якорь цел); поле `heading` — ВРЕМЕННЫЙ рендер движка «Глава N», НЕ метка книги (решение владельца 09.08; настоящие заголовки — строка 160 бэклога движка). $0-команда `tmctl manifest` строит дерево до первого прогона (первое касание создаёт БД проекта — то же поведение, что у status). +- **Прогресс:** `status --json` → `progress: {draft:{done,total}, edit:{done,total}}` на книге и в каждом элементе `chapters`; `done` = «разрешено волной» (ok/flagged/skipped); волна, которой нет, — `total: 0`; процент не отгружается — собирает клиент. +- **Банк:** `.bank.json` (весь банк тремя статусами; id термов — длино-префиксированный хеш ключа уникальности, стабилен через пересборку) и `.bank-stop.json` (полная таблица подписи; `conf: null` ≠ 0). Все сайдкары пишутся атомарно (temp+rename) — читать можно во время прогона. +- **Потолок (строка 145): `tmctl translate|redrive --ceiling-usd ` — это КНИЖНЫЙ ПОТОЛОК В СИЛЕ, не бюджет прогона** (ратифицировано D39.122): леджер сравнивает значение с накопленным committed+reserved книги. Пересчёт пользовательского «прирост в главах» (D39.110) в абсолют — ОБЯЗАННОСТЬ платформы: `аргумент = committed_usd + reserved_usd (из status --json) + прирост×оценка`. Значение ниже уже потраченного откажет первой же резервации. День-потолок не перекрывается. Опция по желанию зоны: движок готов провести `--ceiling-usd` и в `status`, чтобы мониторинг видел действующий потолок capped-прогона (сегодня status показывает книжный) — скажите, заведём строку.