textmachine/CLAUDE.md

371 lines
73 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# TextMachine — контекст для Claude-сессий
> ⚠ **В этом проекте нейросети делают ВСЁ: пишут код, ведут документацию, принимают решения и правят их.**
> Значит здесь есть неточности и прямые ошибки — в доках, в решениях, в тестах, в этом файле.
> **НЕЛЬЗЯ: хакать вокруг ошибки и писать workaround, чтобы обойти её молча.**
> Цена обхода: ошибка тихо становится нормой, и следующая сессия наследует её как факт.
Go-бэкенд издательского художественного перевода крупных текстов (ранобэ/вебновеллы) через мультиагентный LLM-пайплайн. **Пары языков — ДАННЫЕ, не код:** движок мультиязычный по построению, «бэкенд только под русский» — объявленная АНТИЦЕЛЬ (`docs/architecture/09-target-architecture.md` §0.1). Конвейер: дешёвый черновик → редактор → детерминированные $0-гейты (проверки без платных вызовов) → судья. Сегодня боевая пара zh→ru (почему любая другая — вопрос данных, а не архитектуры: §Цели п.2). ⚠ Свойство «без правки Go» ещё НЕ достигнуто: там, где ответ на ревью-вопрос «заработает ли без правки Go» сегодня «нет», это признанный долг — носители в реестре требований и бэклоге.
## Цели и мерило (канон владельца — каждое решение сверяется с этим)
1. **Продукт:** издательское художественное качество перевода больших текстов — победить translationese; консистентные термины/голоса на всю книгу; чувствительный контент с учётом цензуры провайдеров.
2. **Мультиязычность:** движок ОДИН и общий; языко-/пара-/книго-специфика легитимна, но живёт в ДАННЫХ и отдельных модулях, встраиваемых архитектурно чисто (langpacks · пар-промпты · сид/бриф), и разрабатывается отдельно от ядра. Ревью-вопрос по умолчанию: «заработает ли пара, которой в репо ещё НЕТ, без правки Go?»
3. **Чистая архитектура:** бэкенд легко писать и поддерживать; механизм строится только там, где несёт качество/деньги — не ради галочки трекера.
4. **Современные подходы и харнесс-превосходство:** пайплайн и транспорт на уровне или впереди конкурентных харнесс-решений (калибровка — research/2122); эмпирика решает — мерить, не верить.
5. **Экономика:** низкий COGS на дешёвых моделях с адресными дорогими вызовами; деньги видимы с первого вызова (телеметрия · потолки · явное согласие на пере-оплату).
6. **Детерминизм:** воспроизводимый прогон (снапшоты · банк памяти · golden) — повторение без сюрпризов и тихих перепокупок.
## Владелец — контекст для разговора
Язык бэкенда выбран по удобству агентной разработки, а не по опыту владельца. Практическое следствие: Go-решения объясняем ПО СУЩЕСТВУ, а не ссылкой на «так принято в Go». Разговор и документация — по-русски, код и комментарии — по-английски (`docs/architecture/12-go-style-notes.md` §1). Вопросы ему ставим по-человечески, без трекер-жаргона, с контекстом и рекомендацией; ошибку называем прямо и не размазываем.
**Долговечное знание живёт в репозитории, а не в авто-памяти:** память грузится в контекст КАЖДОЙ сессии и не видна в ревью.
## Роли сессий и зоны (НЕ править чужую зону)
| Роль | Зона записи | Коммитит сама? | Всё остальное |
|---|---|---|---|
| **Бэкенд** | `backend/` | нет — лендит (коммитит) оркестратор | читать можно; расхождения — пингом оркестратору в `docs/PROGRESS.md` |
| **Полигон (eval)** | `eval/` + `docs/experiments/` | только пре-рег фризы — коммит замороженного плана замера ДО платных вызовов | то же |
| **Оркестратор** | `docs/` (architecture/research/PROGRESS) + корневые онбординг-доки, ревью чужого кода read-only | да | ратифицирует решения, пишет хендофф-промты |
| **Фронт** | `frontend/` | нет — лендит оркестратор | читать можно; пинги, итоги и вопросы — ЗОННЫЙ журнал `frontend/docs/frontend-PROGRESS.md` (решение владельца; в `docs/PROGRESS.md` фронт НЕ пишет), туда же пишет и оркестратор; продуктовые требования — `docs/product-requirements.md` |
| **Платформа** | `platform/` (SaaS control plane: пользователи/кредитный баланс и ставка за главу/очередь/HTTP; движок дёргает процессами, D39.81; ⚠ продуктовых КВОТ и фри-тир-лимитов нет и не проектируется — D39.176 п.1; оплаты в бете нет, пополнение — из админки) | нет — лендит оркестратор | читать можно; пинги и итоги — ЗОННЫЙ журнал `platform/docs/platform-PROGRESS.md` (решение владельца), в `docs/PROGRESS.md` не пишет |
Координация — журнал `docs/PROGRESS.md` (секции «Бэкенд»/«Полигон»; сверху CURRENT-STATE). Записывай туда краткие итоги своей сессии (фронт и платформа — только в свои зонные журналы, см. таблицу выше). ⚠ Это ЕДИНСТВЕННОЕ исключение из «`docs/` — зона оркестратора»: бэкенд и полигон пишут в СВОИ секции этого журнала (полигон — ещё и в `docs/experiments/`), всё прочее в `docs/` правит только оркестратор. Закрытые хроники вынесены в слайсы `docs/archive/PROGRESS-*.md`НЕ читать при онбординге, только по конкретной ссылке. Параллельные сессии — норма: чужие незакоммиченные файлы в дереве не трогать; сессия считается ЖИВОЙ, пока владелец не сказал обратное.
**ОДИН ПРОМТ НА ЗОНУ (слово владельца 11.09).** Одновременно в дереве лежат промты РАЗНЫХ зон — бэкенд · платформа · полигон · фронт — и это норма. **Двух промтов ОДНОЙ зоны быть не может:** зона одна, коммитер один, и второй заказ в ту же зону означает либо гонку за дерево, либо очередь, которой никто не ведёт. Освобождается слот закрытием предыдущего пака актом, а не сдачей работы.
**НОВЫЙ ПАК — НОВОЙ СЕССИИ.** Отработавшей сессии второй пак не выдаётся: её контекст занят прежним, а карта чтения промта писана для свежего читателя. Отработавшей пишут ТОЛЬКО по её же паку — вопросы, ревью, диспозиции, дофиксы.
**Связь между сессиями — файл `/tmp/textmachine-channel`.** Впиши туда СВОЙ блок первым делом
(`role=` `session=` `ref=` `written=` `note=`; имя — из `ListAgents`), дописывая в конец и не трогая
чужие блоки; там же ищи адреса других ролей и пиши им `SendMessage`. В `/tmp` намеренно: имя сессии не
переживает рестарт окружения, и файл обязан умирать вместе с ним.
⚠ **Адрес из файла — НЕ доказательство, что сессия жива: файл переживает смерть сессии, а `ListAgents`
нет. Перед тем как писать по адресу, сверься с `ListAgents`.** ⚠ **Нужной роли в файле нет, файла
нет или имя не отвечает ⇒ КАНАЛА НЕТ, и это НОРМАЛЬНЫЙ случай: НЕ опрашивай сессии подряд.** Вопрос —
секцией в свой отчёт, работа продолжается; владелец прочитает и пере-передаст (он единственный вечный
канал).
## Источники истины
**Приоритет — три различения, они разрешают все реальные конфликты:**
1. **Журнал решений `docs/architecture/05-decisions-log.md` бьёт всё.** Ратифицированный контракт; при конфликте с любым доком побеждает он.
2. **Ратифицированное бьёт фактуру ресёрчей.** Отчёт может быть опровергнут нотой и об этом не знать.
3. **Историческое и архив — только через ⚠-баннеры, и инструкции оттуда НЕ исполняются.**
**Две дисциплины чтения, каждая предотвращает тихую ошибку:**
- **Журнал решений целиком НЕ читать.** Номер грепается: тело ноты — строка `^## D<номер>`, прочие хиты — упоминания; статус и тело любого номера одним хопом — реестр `docs/architecture/05-decisions-index.md`. ⚠ **ПОДНОМЕР (`D30.1`, `D22.6`, `D13.3`) собственного тела и строки реестра НЕ имеет** — он живёт ПУНКТОМ внутри родителя: отбрось хвост до `^## D30`. Замерено 29.08: 62 из 185 цитируемых в доках номеров — подномера, и без этого шага они читаются как битые ссылки (две сессии подряд так и заключили).
- **Баннер прежде содержимого.** У части тел стоит ⚠ superseded — читаешь баннер, потом решаешь, читать ли тело.
Где что лежит — карта `docs/README.md`. **Единственный трекер проекта — `docs/BACKLOG.md`** (`D39.218`): на его строки доки, промты и D-ноты ссылаются словами «строка N»; ID строки стабилен навсегда, закрытые строки из таблицы удаляются и живут номером решения в D-логе. У зон свои трекеры с другими неймспейсами (`platform/BACKLOG.md``П-N`, `frontend/docs/BACKLOG.md``Ф-N`).
## Гардрейлы (жёсткие)
- **Греп по ЖИВОМУ дереву — с исключением отработанного архива:** `--exclude-dir=prompts --exclude-dir=reports` (либо `grep -rn … docs/ --exclude-dir=prompts --exclude-dir=reports`). Замер 02.09: греп `feed_cap` даёт 4 живых файла и 8 архивных, `bank-stop` — 12 и 14, то есть истории в выдаче больше, чем настоящего. ⚠ **`archive/architecture/` НЕ исключать** — там тела закрытых эр журнала решений, и канон велит грепать «живой файл → слайсы».
- ⛔⛔ **«ДОПИСАТЬ ТОКЕН» И «ПРОВЕРИТЬ, ЧТО ЯКОРЬ УКАЗЫВАЕТ ТУДА, КУДА ГОВОРИТ ПРОЗА» — РАЗНЫЕ РАБОТЫ, и
первая без второй ЗАКРЕПЛЯЕТ расхождение** (11.09; предложил токен я, поймала зона). Голый якорь
`reconcile.go:1808` в ряду реестра описывался прозой как «вызов `sourceThere` в `Resume`, ПЕРЕД
`reopen`». Я прочитал строку 1808, увидел там `return … fmt.Errorf("%w: it is %s", ErrNotResumable…)`
ветку `default` совсем другого свитча — и предложил взять токен ИЗ НЕЁ. Линтер бы позеленел **на
неверной цели**, и расхождение прозы с кодом стало бы вечным. ⇒ **токен, подобранный под то, что в цели
ОКАЗАЛОСЬ, есть подгонка под зелень** (`D39.121`), даже когда подгоняешь не тест, а якорь.
Верное движение — найти, куда ЦЕЛЬ уехала: настоящий вызов был на `:1815`, а `reopen` на `:1818`, и
**порядок двух, который утверждает проза, стерегут ДВА адреса, а не один**.
⚠ И контроль здесь обязателен в обе стороны: зона, чиня это, сама завела голый якорь без пути — линтер
промолчал, потому что не читал его, — и увидела только прогнав контроль «нацелить каждый якорь на строку
1 и посмотреть, краснеет ли». **«Ноль жалоб» и «не разобран» выглядят одинаково.**
- ⛔ **АДРЕС ВЫЗОВА — О ЧЁМ спрашивают и спрашивают ли ВООБЩЕ — не пинится изнутри того же набора, если
сам вызывающий тест не бежит** (формулировка платформенной зоны 11.09; **третий случай одного класса за
смену**, первые два — «согласие снято у живого теста» и выжившая M21). Посадка «гард спрашивает ШАБЛОН
вместо РЕНДЕРА» выжила не потому, что гард плох, а потому, что на этом хосте живой тест скипается
ВНУТРИ своего хелпера — до того, как гард вообще спросят. ⇒ **пин, живущий в наборе, который на твоём
хосте не запускается, не стережёт ничего, и его зелень ничего не значит.** Лечение: либо условие
запуска выполняется на машине приёмки и это НАЗЫВАЕТСЯ, либо мис-аим ловится ТАМ, где вызов
происходит, — а не там, где он описан. ⚠ И чего делать НЕЛЬЗЯ: подделывать операторский вход чужой зоны,
чтобы тест побежал — это отладка не своего предмета, и зона отказалась от неё сама.
-**ВЗЯТЬ У ОБРАЗЦА ФОРМУ И НЕ ВЗЯТЬ ЕГО ГАРАНТИЮ** (формулировка зоны 11.09, точнее моей). Пак строил
ручку по образцу соседней (`regenerate_echo_before_escalate`), ссылался на неё как на образец ТРИЖДЫ — и
не перенёс её гейт. У соседки есть пин, утверждающий, что в БОЕВЫХ конфигах ключ выключен, и её
доккомментарий говорит зачем: «an un-pinned data decision is one a later config edit reverts silently,
**on the money path**, without anything going red». ⇒ отчёт назвал «лендинг инертен» ФАКТОМ, а в дереве
это было ОБЕЩАНИЕМ: включение ключа во все четыре боевых конфига оставляло батарею 23/23 зелёной.
Та же смена, тот же класс вторым лицом: **21 новая мутация не помечена `battery`** (в каталоге 401
запись, в гейте 169; предыдущий пак пометил 84 из 84, этот — 0 из 21) ⇒ «21/21 RED» был разовым
прогоном СМЕНЫ, а не рубежом ПРОЕКТА. **Мутации, предъявленные как рубеж, рубежом не являются, пока не
введены в гейт.** ⇒ копируя механизм, перечисли, ЧЕМ образец себя стережёт, и перенеси это тоже.
-**КОММЕНТАРИЙ, НЕСУЩИЙ ВЫВОД, ОБЯЗАН НАЗВАТЬ УСЛОВИЕ, ПРИ КОТОРОМ ВЫВОД ПЕРЕСТАЁТ ДЕРЖАТЬСЯ.**
Замер 11.09: абзац у формулы потолка гласил «both loops that regenerate increment the doubling count»
и на этом стояло решение «версию политики двигать не надо» (цена бампа — 989 единиц, $9.99). Пак завёл
ТРЕТИЙ цикл, намеренно не инкрементящий, и вывод уцелел лишь потому, что ручка лендится выключенной —
о чём абзац молчит. ⇒ у вывода в комментарии пишется не только «почему верно», но и **«при каком
условии перестанет»**; иначе следующая правка снимет условие, не заметив, что снимает вывод.
- ⛔⛔ **СУДИ ПРЕДМЕТ ИСПОЛНЕНИЕМ ТАМ, ГДЕ ИСПОЛНЕНИЕ ДОСТУПНО И БЕСПЛАТНО — иначе проверишь ДОКУМЕНТ вместо
предмета, и оба раза одним движением** (замер второго круга приёмки 11.09; порознь это были две ошибки,
вместе — правило). **(а)** Денежный гард судил пайплайн ИЗ ШАБЛОНА, а живая проба гоняет движок по
пайплайну, который тестовый хелпер ПЕРЕ-РЕНДЕРИВАЕТ строкой выше, — и рендер уже $0. Носитель зоны
утверждал «между тестом и тратой только ключи в `.env`, ⛔ ПРОВЕРЕНО ЧТЕНИЕМ, И ЭТО УЖЕ НЕ ВЫВОД»: чтение
остановилось на вызове процесса и не дошло до строки НАД ним. **(б)** Рецепт сборки $0-конфига был
«проверен гардом» — текстовым сканом имён — и производил файл, который ДВИЖОК отказывается грузить
(`escalate_to must differ from the primary model`, `EXIT=10`); загрузчик стоил одного $0-глагола.
⇒ **прежде чем писать «проверено чтением», спроси: какой предмет РЕАЛЬНО побежит, и нельзя ли его
запустить?** Один `go test` и один `$0`-глагол обе ошибки снимали. ⚠ И частный случай, который стоит
держать отдельно: **предикат, приложенный к ИСХОДНИКУ, не судит то, что получится после рендера**;
спрашивать надо о том артефакте, который уходит в исполнение.
-**КОММЕНТАРИЙ У ПИНА В ДАННЫХ — ТАКОЙ ЖЕ НОСИТЕЛЬ, КАК НОТА, И ЧИТАЕТСЯ ПЕРЕД ЗАМЕРОМ, А НЕ ПОСЛЕ.**
Замер 11.09: пак был написан вокруг находки «потолок съедает размышление, а не текст» — и ровно это
лежало в `backend/configs/models.yaml` (блок `deepseek-v4-pro`) с 25.07 замером мини-прогона: «Размер
текста тут ни при чём… разница в длине РАЗМЫШЛЕНИЯ… Оценочный множитель `max_output_ratio` этого не
знает и знать не может». ⇒ **ратифицированное в ДАННЫХ было открыто заново и подано как находка**;
настоящим новым оказалось лишь то, что класс верен и для СОСЕДНЕЙ модели, чей флор с тех пор не
пере-мерили. ⚠ Проверка перед паком дешёвая: грепнуть предмет не только по докам и нотам, но и по
`configs/*.yaml` — там живут пины, у которых комментарий несёт ЗАМЕР, а не пояснение.
- ⛔ **ОБХОД ПРЯЧЕТ СВОЮ ЦЕНУ, ПОКА НЕТ ПРАВИЛЬНОГО МЕХАНИЗМА; ПОЯВИТСЯ МЕХАНИЗМ — ЦЕНА СТАНОВИТСЯ ВИДНА
СРАЗУ** (замер платформы 11.09, и это лучший аргумент за запрет воркэраундов, какой у нас есть в числах).
Рецепт стенда предписывал $0-шаблон, чтобы тест не ушёл в платные вызовы. Настоящая задача была другая —
**спросить СОГЛАСИЕ** на трату; предикат вместо этого спрашивал, свободен ли порт, то есть отвечал на
соседний вопрос. Пока обход стоял, его цена была невидима и выглядела как чужая поломка: **два красных
теста в пакете, которые никто не мог объяснить**, и число «0 FAIL» в носителе, оказавшееся УСЛОВНЫМ.
Как только согласие построили по роду (обход шаблона → гард, читающий провайдера КАЖДОЙ модели и
считающий незнакомую ПЛАТНОЙ), пакет стал зелёным целиком, а обход остался без основания.
⇒ **встретив «у нас так сделано, чтобы не случилось X», спрашивай не «хорошо ли сделано», а «тот ли это
вопрос»** — и меряй, сколько стоит обход, когда правильный механизм появится.
-**ТЯЖЁЛЫЙ ПРОГОН УБИВАЕТ НЕ НЕХВАТКА ПАМЯТИ НА МАШИНЕ, А ПОТОЛОК ЕГО СОБСТВЕННОГО CGROUP** (замерено
11.09 после ЧЕТЫРЁХ убийств подряд у одной сессии). `dmesg` показывает **26** убийств, и все с
`constraint=CONSTRAINT_MEMCG`, каждое в СВОЁМ транзиентном юните
`tm.slice/tm-runs.slice/tm-test-<тег>-<pid>.service`. Сами слайсы при этом `memory.max = max` — лимита
на них НЕТ. ⇒ **гасить чужие процессы бесполезно: машина в этот момент имела 5.7 ГБ доступных.**
⚠ И потолок НЕ у всех одинаковый: прогон, запущенный одной сессией, попадал в capped-юнит, а тот же
прогон из другой шёл в `/init.scope` с `memory.max = max` и доходил до конца. ⇒ **прежде чем объяснять
падение нагрузкой, спроси `cat /proc/self/cgroup` и `memory.max` этого cgroup** — и если потолок есть,
перенеси прогон, а не режь его.
⛔ **И чего делать НЕЛЬЗЯ, даже когда убило четыре раза подряд: сужать мутационный прогон через `-run`,
чтобы влезть в память.** Подмножество `-run` не есть пакет: базовый прогон перестаёт быть базовым, и
вердикт «поймана» становится утверждением о другом предмете. **Лучше не измерить, чем измерить не то**
формулировка зоны, выбравшей честную сторону при максимальном соблазне.
- ⛔ **ФОНОВОЕ ГАСИТСЯ ПО СВОЕМУ PID, А НЕ ПО ИМЕНИ КОМАНДЫ, и норма эта — про ЧУЖУЮ ложную зелень, а не
про твою аккуратность.** `go test`, `tmctl`, `psql`, `/exe/` — имена, общие для всех зон, поэтому
`pkill -f 'go test'` есть шаблон «все, кто сейчас работает»: убитый чужой прогон мутации читается его
хозяином как «мутация выжила», то есть ложная НАХОДКА у соседа. Замерено 29.08: сессия гасила СВОЮ
мутационную кампанию (`pkill -9 -f 'go test'`, `pkill -9 -f '/exe/'`) и попала по процессам
ПАРАЛЛЕЛЬНОЙ — узнала об этом из `ps -eo pid,cmd` уже ПОСЛЕ убийства; в записи зоны платформы за тот
же день жертвы названы поимённо — шесть ревью-агентов чужой сессии в окне 01:36:2001:37:45, включая
линзу покрытия посадками (греп «Я убила чужие процессы» в архиве журнала платформы). ⚠ Второе лицо
того же шаблона зона платформы записала у себя (`platform/docs/STACK_DECISIONS.md`, греп «Демон нельзя
убивать»): `pkill -f <путь к бинарю>` совпадает с собственной командной строкой оболочки и убивает
вызвавшего (exit 144). ⇒ запуская фоновое, ЗАПОМИНАЙ его PID (`$!`, pid-файл) и останавливай только
его; перед любым сигналом по шаблону — сперва `ps -eo pid,cmd | grep`, глазами, чьё это, и если чужое,
не трогать. Убил чужое — сразу пинг той сессии с временным окном, чтобы она пере-прогнала посчитанное:
молчание здесь дороже признания, потому что чужие числа уже испорчены, а выглядят целыми.
-**ЗЕЛЁНАЯ БАТАРЕЯ — ЭТО ПОЛНЫЙ СПИСОК ПАКЕТОВ ПЛЮС ОТСУТСТВИЕ `FAIL`, а не отсутствие `FAIL`.** Дважды
за смену 29.08 за зелень принят прогон **20 пакетов из 21**: `FAIL` стоял в самом хвосте лога. Оборванный
прогон выглядит почти нормальным — по алфавиту дошло докуда дошло; а для мутационных посадок он вдобавок
НЕОТЛИЧИМ от не пойманной мутации, то есть обрыв читается как дыра в тестах, которой нет. ⇒ полнота
сверяется СПИСКОМ, а не чтением хвоста: `comm -23` СПИСКА `go list ./...` против вердиктов лога обязан
быть пуст. ⚠ Порядок операндов тут несущий, и перевёрнутый пуст ВСЕГДА: замер 11.09 на движке — обрыв
на 20 пакетах из 23 даёт три строки в правильном порядке и **ноль** в перевёрнутом, то есть перевёрнутый
неотличим от полного прогона. Падение в пакете, которого не трогал, проверяется развёрткой чистого `HEAD`
(`git archive`) и прогоном вперемежку в обоих деревьях: одинаковая частота падений = не твоё.
**И `go test` — НЕ приёмка: приёмка это ЦЕЛЬ ГЕЙТА ЗОНЫ** (`battery: build vet fmt lint test` у движка,
`check` у платформы). `lint` стоит ДО `test`, поэтому «все тесты зелёные» бывает правдой, которая не значит
ничего: пак с настоящими числами `go test -race` упал на лендинге на `errorlint` в своей же новой строке
(`%v` вместо `%w`) — до тестов гейт не доходил вовсе. **Цель, падающая первой, отменяет все последующие**,
и в отчёте называется, ЧТО именно прогнано.
-**КОД ОБЁРТКИ — НЕ КОД РАБОТЫ.** Уведомление «completed (exit code 0)» на фоновую команду вида
`(make battery > log 2>&1; echo "MAKE_EXIT=$?" >> log) & echo started` приходит через СЕКУНДУ: ноль — это
код внешней обёртки, а батарея в этот момент ещё идёт (трижды за смену 03.09; в логе стоял один линтер).
Родня — `make`, умерший на `tools-check` под `exit 0` завершающего `echo` (`D39.212` п.8): **код возврата
составной команды отвечает на свой вопрос, а не на твой.** ⇒ `&` внутри фоновой команды не ставить —
фоновость даёт сам харнесс; нужен всё же — ждать УСЛОВИЯ, а не уведомления
(`until grep -q MAKE_EXIT log; do sleep 10; done`), и вердикт читать строкой-итогом из лога.
-**ДВА СВОЙСТВА ЭТОЙ СРЕДЫ, каждое даёт ТИХИЙ ЛОЖНЫЙ НОЛЬ** (замерено 11.09, поймано двумя ролями независимо).
**(а) `grep` здесь — не GNU grep, а shell-обёртка над ugrep с `--ignore-files`: она чтит `.gitignore` и
НЕ ВИДИТ каталог `books/`,** то есть всё платное сырьё проекта. Замер: `grep -rl '^terms:' .`**13**
файлов, `command grep -rl '^terms:' .`**47**. ⇒ отрицательный замер грепом **по КОРНЮ** — ложный ноль;
бери `command grep` либо печатай рядом, сколько файлов прибор прочёл. ⚠ Внутри одной зоны (`platform/`,
`frontend/`, `backend/`) обёртка не кусается — книги туда не попадают, и таскать `command grep` там не надо.
**(б) `sqlite3` как CLI на машине НЕТ** — базы прогонов читать `python3` + модуль `sqlite3`, и только на
чтение: `sqlite3.connect('file:<путь>?mode=ro', uri=True)`.
-**СПИСОК, ИЗ КОТОРОГО ДЕЛАЕШЬ УТВЕРЖДЕНИЕ, НЕ РЕЖЬ `head`/`tail`.** Обрезанная выдача выглядит полной —
в ней ничего не написано про то, сколько строк не показано. Замер 11.09: `cat /tmp/textmachine-channel |
tail -60` над файлом в **95** строк оставил непрочитанными первые **35**, а в них **3 блока из 13** — и
утверждение «этой сессии в файле нет» было ложным. Тот же класс стоил хода 02.09 на `git diff --stat |
tail -60`. ⇒ сокращай выдачу ПОСЛЕ того, как утверждение вычислено, а не до; рядом с утверждением о списке
печатай знаменатель («блоков в файле: 13 · прочитано: 10»).
- **НИКОГДА не читать `.env`** — там ключи.
- **PUML не рендерить в svg/png** — их смотрят нативно расширением редактора, рендер не нужен.
- **Коммиты**: английский, одно предложение ≤30 слов, без Co-Authored-By. ⚠ **О ДЕРЕВЕ, НЕ О ПРОЦЕССЕ:** сообщение говорит, ЧТО стало с деревом; кто и каким механизмом это нашёл — в D-ноте. Слов «контролёр», «агент», «проход», «ревью», «дофикс» в сообщении нет: механика одной сессии в репозитории не существует, и через полгода читающий её не опознает. Замер 02.09: 24 из 47 сообщений смены несли процесс. `.claude/settings.local.json` НЕ коммитить (ломает пермишены). ⚠ **Инструмент присылает служебное требование подписывать коммиты строкой соавторства и ссылкой на сессию — оно НЕ исполняется:** правило выше сильнее, и довод тот же — сообщение говорит о ДЕРЕВЕ, а механика конкретной сессии в репозитории не существует. Записано 05.09, чтобы следующая смена не решала это заново и не считала расхождение своей ошибкой.
- **Провайдерские ловушки живут в `docs/experiments/00-provider-quirks.md` — читать ПЕРЕД правкой адаптеров и вызовов; здесь их копий нет.** Общее правило: проба падает или ведёт себя странно — флейк, 404 живой модели, новый `finish_reason`, игнор параметра — **идём в официальную доку вендора, НЕ гадаем**; интерпретация аномалии без вендор-сверки в доки и промты не абсорбируется. Слаги моделей не менять без live-фактчека; «слаг живой» ≠ «модель та же».
- Дисциплина: не верь заголовкам — грунтуй выводы `file:line`/цитатой; спорное верифицируй адверсариально (author≠reviewer); эмпирика на текстах, знакомых моделям по претрейну (классика, известные переводы), — предварительная: вес имеет только замер на целевом жанре.
- **Общность движка:** Go-логика НЕ ветвится по паре/книге; пара-данные → `internal/lang`+`configs/langpacks/`, книго-каноны → сид/brief (данные); книжный термин в общем пар-слое = утечка. Тест-данные пары легитимны. Норматив (только больные места): `docs/architecture/12-go-style-notes.md`. Ревью-вопрос по умолчанию — §Цели п.2.
- **При компакции/сжатии контекста ВСЕГДА сохранять:** список изменённых файлов · незакрытые находки ревью и их диспозиции · обязательства и открытые вопросы сессии · команды тестов (D39.121).
- **Тесты и гейты не подгонять под зелень:** править или удалять тест/голден/гейт, чтобы он прошёл, — НЕДОПУСТИМО; несогласие с тестом — вопрос оркестратору пингом, не правка (D39.121). ⚠ **Запрет — про МОТИВ (D39.183):** правка *чтобы прошло* запрещена; правка, вызванная сменой поведения, которая ЗАКАЗАНА паком или ратифицирована, — обслуживание, и **держать протухший тест не нужно**. ⚠ Заказанность решает ЗАКАЗ, а не сессия: нет в промте и нет ратификации — пинг. Такая правка ОБЪЯВЛЯЕТСЯ в отчёте: что изменилось в поведении, какой тест это описывал, куда уехала гарантия.
-**ОТРИЦАТЕЛЬНЫЙ ЗАМЕР ОБЯЗАН ДОКАЗАТЬ, ЧТО СПРОСИЛ СУЩЕСТВУЮЩЕЕ.** «Ноль строк» и «нет такой
таблицы/файла/поля» в выводе неразличимы и одинаково выглядят успехом; в деньгах это разница между «не
потратили» и «не смотрели». ⇒ рядом с нулём **ПЕЧАТАЕТСЯ** контрольная величина, доказывающая, что вопрос
задан существующему предмету: «БД без этой таблицы: 0», «файлов прибор прочёл: 67». ⛔ Именно печатается, а
не упоминается: «проверено с контрольной строкой» — утверждение О проверке, читатель видит слово вместо
числа, и норма ловит замер, но не отчёт о нём (`D39.202`, `D39.217` п.2б).
-**«НЕ ВОСПРОИЗВЕЛОСЬ» — ЭТО НЕ «НЕДОСТИЖИМО»: верный прогон при неверной гипотезе неотличим от
«дефекта нет»** (`D39.170`, `D39.174` п.в). Замер: ПЯТЬ попыток воспроизвести перерасход дали пять
«нет», и все пять прогонов были ВЕРНЫ — ошибочна была гипотеза о механизме; решало ВТОРОЕ поле, на
которое не смотрели, а условия достижимости тянули в РАЗНЫЕ стороны (естественный способ добиться
эффекта ровно им же и убивал его), поэтому перебором фикстур такое не нащупывается — только пониманием
механизма. ⇒ прежде чем писать «воспроизвести не удалось», назови ЦЕПЬ звеньев от входа к следствию и
проверь КАЖДОЕ отдельным замером, а не только то, которое кажется решающим; цепь, не проверенная
звеньями, даёт формулировку «я не нашёл условие», а НЕ «условие недостижимо». И лечить класс
структурно всё равно нужно: фикс, покрывающий класс, от того, нашлась ли фикстура, не зависит.
**И злее того — ВЕРНЫЙ РЕЗУЛЬТАТ ПРИ НЕВЕРНОМ МЕТОДЕ: он не краснеет нигде** (11.09, полигон; нашёл
критик чисел, не автор). Строки лога делились по двум попыткам сравнением времени. Первый раз метод
сломался грубо — разделитель в колонке `ts` ПРОБЕЛ, а сравнивали с `T`, и все 33 строки ушли в первую
попытку (33/0). Разделитель починили, числа стали ПРАВИЛЬНЫМИ (16 и 17 строк из 33) — и на этом
остановились, а метод остался негодным: `ts` секундной гранулярности против `started_at` с
микросекундами, то есть все шесть подстановок формально «раньше» старта своей же попытки, и верные
числа вышли по счастливому порядку строк. ⇒ **если в данных есть поле, прямо отвечающее на вопрос,
любой ВЫВОД этого ответа — по времени, по порядку, по соседству — уже дефект метода, даже когда
сходится** (здесь ответ нёс `trace_id`: в нём стоит номер попытки). Спрашивай «чем это НАЗВАНО в самой
строке?» прежде, чем вычислять: красное заставляет чинить способ, зелёное при правильном числе не
заставляет ничего.
- **Мандат самопроверки в промтах сессий (обязателен, решение владельца):** промты полигон-сессий — и вообще любых пишущих код / запросы к моделям — ДОЛЖНЫ явно требовать ревью ИСПОЛНЕНИЕМ: своего кода + сформированных запросов к моделям + полученных результатов. Сессии регулярно ошибаются и багуют, и это искажает результат; **самоотчёт «проверено» без исполнения регулярно оказывается ложным и стоит денег**. Бэкенд-промты — явный бэкенд-ревью после кода (обычно отрабатывает по опыту, но требовать явно). Пост-хок адверсариальная верификация оркестратора при лендинге — второй рубеж, НЕ замена самопроверки.
**Исполнения мало: промт заказывает адверсариальный проход сессии по СВОЕЙ готовой работе; глубину и веер сессия выбирает под предмет, промт даёт направление — что в этом паке уязвимо.**
**КОПИЯ ПОД МУТАЦИЮ ЗАЩИЩАЕТСЯ ПОСТРОЕНИЕМ, А НЕ `set -e`** (инцидент 10.09, зона назвала сама).
Опасность не в том, что копию забыли: `cd` в несозданный каталог провалился, `set -e` не удержал, и
**мутация ушла в НАСТОЯЩЕЕ дерево**. ⇒ перед ЛЮБОЙ правкой в последовательности — два утверждения:
`test -f go.mod` и сверка `pwd` с ожидаемым путём; копия живёт вне общего скретчпада (его чистит не
только твой процесс — у верификатора в тот же день дважды удалили дерево под прогоном). ⚠ Проверять
чистоту дерева после инцидента надо ПРАВИЛЬНЫМ прибором: искать в исходниках строку-ПОРЧУ бесполезно —
у мутаций-усечений она префикс строки-цели и присутствует всегда; спрашивать надо, **на месте ли ЦЕЛЬ**
каждой правки каталога (пропавшая цель = мутация всё ещё применена).
**ПРАВКА ПРАВИЛА МОЛЧА ОТПРАВЛЯЕТ В ОТСТАВКУ ФИКСТУРУ, КОТОРАЯ ЭТО ПРАВИЛО СТЕРЕГЛА** (11.09, зона
поймала полным гейтом у СЕБЯ, на пине, который сама уже проверяла поодиночке и видела красным).
Ужесточение `NearStems` до строгого префикса отняло различающую силу у фикстуры на парах ОДИНАКОВОЙ
длины (`cart`/`cars`): строгий префикс отвергает такие сам, независимо от гейта `Enabled()`, — и тест
стал спрашивать то, на что правило уже отвечало, а снятие гейта стало НЕВИДИМЫМ. Мутант выжил.
**меняя правило, спроси: какие пины его стерегли и осталась ли у их фикстур различающая сила?**
Лечение — явное утверждение ПОСЫЛКИ внутри теста («правило обязано принимать эти входы, иначе фикстура
не отличает гейт от правила»); оно же тут же поймало вторую попытку зоны (`cat`/`cart` — не префикс).
**И следствие про метод: ПООДИНОЧКЕ ПРОВЕРЕННЫЕ ПОСАДКИ НЕ ЗАМЕНЯЮТ ПРОГОНА ВСЕГО КАТАЛОГА.**
Шестнадцать посадок, проверенных по одной, дали зелёную картину; полный гейт на 169 записях нашёл дыру,
которой не увидел ни один способ дешевле. Полный прогон каталога — не ритуал, а единственный прибор,
который видит взаимодействие правок между собой.
**ОДНА КОПИЯ — ОДИН МУТАТОР** (11.09, зона поймала у себя и назвала сама). Прежняя норма выше держала
«копия защищается построением» про ИСХОДНОЕ дерево и молчала про саму копию. Инцидент: увидев запись
`UNKNOWN`, сессия пере-запустила её на ТОМ ЖЕ `-root`, по которому в этот момент шёл цикл гейта — одна
программа сажала правку, вторая гоняла базовый прогон. Пере-проверка вернула FAIL, и этот FAIL —
артефакт вмешательства, а не улика. ⇒ **вторая мутирующая программа на том же корне делает результаты
ОБЕИХ недействительными, и делает это МОЛЧА: каждая по отдельности выглядит работающей.**
⛔ **И вот что здесь ловушка: `diff -rq` показал копию байт-идентичной источнику — и это НЕ доказывало,
что результаты целы.** Восстановление отработало; измерения, снятые МЕЖДУ посадкой и восстановлением,
всё равно испорчены. Чистое дерево говорит о дереве, а не о числах. ⇒ записи, снятые в окно
вмешательства, пере-снимаются на СВЕЖЕЙ копии, а не признаются годными по чистоте дерева.
⛔ **И ВТОРОЕ ЛИЦО ТОЙ ЖЕ НОРМЫ, дороже первого: вмешательство не просто портит измерения — оно
ПРОИЗВОДИТ ЛОЖНЫЕ УЛИКИ, которые выглядят как находки о предмете.** Запись, помеченная `UNKNOWN`
(«базовый прогон пакета не сошёлся»), читалась как подозрение к КАТАЛОГУ; пере-снятая на чистой копии
из сессии без потолка cgroup, она дала честное RED с обоими подтестами. ⇒ `UNKNOWN` говорил о ЦИКЛЕ
того, кто мерил, а не о предмете, и здоровая запись едва не уехала в подозреваемые. **Спрашивай у
аномалии, о ЧЁМ она — о предмете или о твоём приборе, — прежде чем заводить по ней находку.**
**ГРЕП ПО КАТАЛОГУ МУТАЦИЙ ДОКАЗЫВАЕТ ОТСУТСТВИЕ ЦЕЛИ, НО НЕ ОТСУТСТВИЕ КАТЧЕРА.** Починив фикстуру,
соблазнительно проверить грепом, что её файл никем не мутируется, и счесть влияние закрытым. Это две
РАЗНЫЕ вещи: «мою фикстуру никто не мутирует» видно статически, «моя фикстура никого не ловит» — нет.
Тест из тронутого файла может быть ЕДИНСТВЕННЫМ катчером посадки в совсем другом пакете, и каталог
такой связи не хранит. ⇒ после правки любого теста вопрос закрывает только ПРОГОН КАТАЛОГА ЦЕЛИКОМ.
**НЕИЗМЕРЕННАЯ МУТАЦИЯ ХУЖЕ ВЫЖИВШЕЙ.** Запись, у которой не сошёлся БАЗОВЫЙ прогон пакета («NOT GREEN
before any mutation»), не «поймана» и не «выжила» — она НЕ ИЗМЕРЕНА, в число не кладётся и закрывается
отдельно. Выжившая говорит «здесь дыра»; неизмеренная не говорит ничего, а выглядит как строка отчёта.
**И ТОТ ЖЕ КЛАСС У ЦИТАТЫ, А НЕ ТОЛЬКО У ПИНА: утверждение может удовлетворяться ЧУЖИМ ОБЪЕКТОМ.**
Замер 11.09: в пак было вписано «эхо-риск — 1 чистый китайский выход из 16», число проверено по квиркам
и там действительно есть. ⛔ Но оно снято на модели в роли **ПЕРЕВОДЧИКА**, а единственная стадия,
которую заказанная ручка двигает, — **РЕДАКТОР**, чья эхо-безопасность при том же усилии в тех же
квирках объявлена НЕ МЕРЕНОЙ. ⇒ условие включения денежного механизма стояло бы на замере из ЧУЖОЙ
КЛЕТКИ. ⚠ И почему этого не увидели ни автор пака, ни исполнитель (формулировка зоны): **оба читали
ЧИСЛО и проверяли, что оно ЕСТЬ; читатель проверил другое — что оно снято на ТОЙ КЛЕТКЕ, о которой
речь.** Это разные вопросы, и второй не задаётся сам собой. ⇒ у каждого заимствованного числа
спрашивай не только «оно есть в носителе?», но и **«о ТОМ ЛИ оно предмете — та же роль, та же модель,
та же стадия, тот же режим?»**; фантом-гард по номеру ноты этого не ловит.
**ПИН МОЖЕТ УДОВЛЕТВОРЯТЬСЯ ЧУЖОЙ УЛИКОЙ — и это НЕ флейк, а детерминированная пустота** (10.09,
зона поймала у себя). Утверждение `errors.Is(err, context.Canceled)` про ОДИН выход держалось тем, что
фикстура гнала петлю через провод: ошибка попытки уже несла отмену в своём поле, и `errors.As` находил
обрыв ПЕРВОЙ попытки, а `errors.Is` — отмену внутри ВТОРОЙ. Мутант выживал **6 прогонов из 6**, то есть
повторный прогон такое не ловит. ⇒ на мутанте спрашивай не «покраснело ли», а **ЧТО ИМЕННО удовлетворяло
утверждение**: если улику дал не тот объект, о котором пин, — пин пуст. Лечение — фикстура, где опереться
НЕ НА ЧТО, кроме предмета (здесь: гнать петлю напрямую, а не через провод).
**ПИН, ФЛЕЙКОВЫЙ НА МУТАНТЕ, ИЗМЕРЯЕТ ПУСТОЙ СЦЕНАРИЙ.** Замер 10.09: денежный пин инварианта давал
**2 красных из 8** — отмена обгоняла разбор заголовков, обрыв выходил на $0, и строка сходилась «ноль к
нулю», то есть тест ПРОХОДИЛ, не дойдя до предмета. Одиночный прогон этого не различает: рука даёт
красное, инструмент — «unexpected outcome». ⇒ денежный пин гоняется НЕ ОДИН РАЗ, а фикстура строится так,
чтобы деньги были НЕИЗБЕЖНЫ, а не вероятны (после перестройки 8/8 зелёных на дереве и 8/8 красных на
мутанте). Нашла зона у СЕБЯ, инструментом, а не глазом.
⛔ **ЕСЛИ ФИКСТУРА ДЕРЖИТ ЧТО-ТО ПОСТОЯННЫМ, ПИН ОТЧИТЫВАЕТСЯ ЗА КЛАСС, А МЕРЯЕТ СРЕЗ — и молчит о том,
какой именно** (формулировка платформенной зоны 11.09, обобщившей находку шире своего случая).
У неё фоновой константой был `alive = false`, и пин «текст движка остаётся только там, где каталог на
месте» вышел ЛОЖНЫМ: он не доходил до повторяющегося `resync failed` у ЖИВОГО юнита, то есть нашёл
ОДИН носитель класса и отчитался за КЛАСС. Тот же дефект даёт любая незаваренная константа фикстуры:
замороженные часы (у неё они родили монетку `newestRun`, делавшую два других пина пустыми), пустой
воркдир, единственный экземпляр сервиса. ⇒ **у каждого пина, утверждающего КЛАСС, спрашивай: что эта
фикстура держит постоянным и о чём она поэтому молчит** — и либо варьируй, либо сузи утверждение
до среза, который она действительно меряет.
**И два утверждения о цвете ОДНОГО пина, снятые РАЗНЫМИ приборами, сравнивать нельзя.** Замер
11.09: «8 FAIL из 8» снято без `-race`, зелёная сторона — `-count=3`; пере-снято ОДНИМ прибором с
обеих сторон (`-race -count=8`) и дало 8/8 в обе стороны. Счёт вёлся по ПОЛНОМУ списку имён
(`grep -oE '^--- (PASS|FAIL): Test…' | sort | uniq -c`), а не по хвосту вывода. ⚠ И `-count=8` в одном
процессе — не восемь отдельных прогонов; если предмет чувствителен к состоянию процесса, это разные
замеры, и разницу называют.
**«SKIP ПРЕВРАТИЛСЯ В PASS» ДОКАЗЫВАЕТ, ЧТО ТЕСТ ИДЁТ, А НЕ ЧТО ОН ДЕРЖИТ** (формулировка зоны 11.09).
Введя корпусные переменные в гейт, легко счесть дело сделанным по тому, что скипы исчезли. Это про
ЗАПУСК. Что ступень НЕСУЩАЯ, доказывает только одно: посадка, сломавшая предмет, краснеет ИМЕННО ЕЮ.
⭐ Замер: `C-verdict-leaves-the-anchored-column` поймана ДВУМЯ тестами — синтетическим пином и оракулом
холодного прогона; значит сломать первичную колонку и получить зелёный гейт нельзя даже мимо
синтетики. ⇒ **после включения любой ступени в гейт спроси её посадкой, а не списком скипов.**
**И ТА ЖЕ НОРМА — ПРО САМ ХАРНЕСС, А НЕ ТОЛЬКО ПРО ЧИТАЮЩЕГО** (зона поймала у себя 11.09, приёмка
этого не видела). Её мутационный харнесс склеивал ДВА пакета в ОДИН аргумент `go test`; тот не мог его
разрешить, выходил ненулевым — и харнесс читал ненулевой код как «мутация поймана». **Три записи
получили ложное RED**, то есть прибор сам печатал поимки там, где их не было; нашлось руками на
четвёртой. ⇒ **харнесс, который судит по КОДУ ВОЗВРАТА, не отличает «тест упал на предмете» от «команда
не собралась»**; оба выглядят успехом мутационной кампании. Лечение: харнесс обязан требовать от
падения ТЕКСТ про предмет, а на неразрешённой команде — выдавать ОТДЕЛЬНЫЙ исход (неизмеренная), и под
сам этот дефект ставится пин. ⚠ Класс родствен `D39.212` п.8 («код возврата составной команды отвечает
на свой вопрос, а не на твой») и норме про `UNKNOWN`: **неизмеренное, прочитанное как пойманное, хуже
выжившего — оно закрывает вопрос, не задав его.**
⛔ **ПОСАДКА ОБЯЗАНА АТАКОВАТЬ ТО ЖЕ, ЧТО СТЕРЕЖЁТ ПИН: мутация КОДА против гейта ДАННЫХ измеряет
пустоту** (зона поймала у себя 11.09, на СВОЕЙ же новой посадке). Пин утверждал, что боевые конфиги не
включают ключ; мутацию под него сделали правкой Go — переименовали yaml-тег. Гейт стоит над ДАННЫМИ, а
боевые конфиги ключа не несут вовсе ⇒ переименование не меняло ничего, и посадка **выжила, ничего не
измерив**. Пере-посаженная в сам конфиг — краснеет по имени пина. ⇒ прежде чем засчитать выжившую,
спроси: **предмет посадки и предмет пина — один и тот же слой?** (код · данные · провод · схема).
Та же зона за один пак трижды получила мутацию «не про то»: недостижимое условие в фикстуре ·
не собирающаяся посадка · не тот слой. **Все три выглядели результатом**, и ни одна им не была.
**МУТАЦИЯ ЗАСЧИТЫВАЕТСЯ ПО ТЕКСТУ СООБЩЕНИЯ, А НЕ ПО ФАКТУ КРАСНОТЫ.** Читай ТЕКСТ падения: говорит ли
он про сломанное тобой. Правый вердикт по неправой причине — дыра, а не поимка, и от настоящей поимки
отличается только тем, прочёл ли кто-нибудь текст, а не цвет (`D39.217` п.2в).
⛔ **ФИКСТУРА ОБЯЗАНА НАЗВАТЬ ГРАНИЦУ, КОТОРУЮ ПРОШЛА: у сквозного теста граница — побочный эффект
того, как сконфигурирован прогон, а не то, что тест утверждает** (08.09, зона нашла у СЕБЯ и назвала
прямо, `D39.228` п.5). Проекция банка публикуется на ПЯТИ границах прогона, и каждая несёт свою метку
внутри самого документа; все три сквозных теста к ней открывали раннер так, что читали артефакт границ
«авто-продолжение» и «прогон закончен», а предметом была граница ПОДПИСИ — единственная, ради которой
проекция и существует. Порча «не публиковать секцию именно на границе подписи» шла ЗЕЛЁНОЙ по всей
батарее. ⇒ у артефакта больше одного момента записи — тест утверждает СНАЧАЛА, какой момент он прочёл
(сверка метки границы), и только потом содержимое; иначе зелёный тест описывает не тот момент и молчит
ровно там, где предмет. Вопрос себе: «сколько раз этот файл переписывается за прогон и какой из разов
я сейчас читаю?»
**Пришло письмо «всё закрыто» — иди перечитывать СВОИ утверждения о закрытом, а не чужие правки.**
**Fable 5 — ПОТОЛОК 12 АГЕНТА НА СЕССИЮ, слово владельца 11.09: «я не разрешаю больше 1-2 на сессию».** Прежняя редакция называла это рекомендацией — она отозвана. Модель задавай агенту ЯВНО и знай, сколько их у тебя работает. ⚠ И форма важнее числа: **одному агенту с ПОСТОЯННЫМ контекстом дослылают вопросы, а не поднимают второго** — контекст и есть ценность. Опус-субагенты потолком не ограничены, но тратятся так же. ⭐ **Дополнение владельца 11.09: потолок относится к КАЖДОЙ сессии, и зонная сессия тоже вправе держать СВОЕГО фабла-советчика — ОДНОГО.** Слово дословно: «разрешаю 1 фабла на 1 сессию, ну как у тебя». ⇒ промт зоны это НАЗЫВАЕТ: как поднять (`model: "fable"` явно), о чём спрашивать (сомнение · развилка · «не построено ли уже» · «не противоречит ли ратифицированному»), и что он СОВЕТЧИК, а не источник истины — его посылки проверяются деревом.
- **Git-координация мультисессий:**
- **Коммитит только оркестратор.** Сессии зон — бэкенд, полигон, фронт, платформа — не коммитят: своё дерево готовят и передают на лендинг (исключение: пре-рег фризы полигона). Чужую зону не трогает НИКТО.
- **Форма коммита — ТОЛЬКО с явным списком путей: `git commit -- <путь> <путь>`.** Причина механическая: голый `git commit` уносит **ВЕСЬ индекс**, включая чужие застейдженные файлы, и чужая работа уезжает под твоим сообщением. Pathspec-форма индекс не трогает.
- **НИКОГДА `git add -A` / `git add .` / `git commit -a`** — только явные пути.
- **Перед каждым коммитом:** `git status` (опознать ЧУЖОЕ) и `git diff --cached --name-only`. Увидел в индексе путь вне своей зоны — **не коммить его и не «прибирать»**: снимай не чужое из индекса, а свою задачу — коммить pathspec-формой.
- **Не оставляй свои файлы застейдженными** между операциями: пока они в индексе, их может унести чужой коммит. Стейдж и коммит — одной командой.
- **НИКАКИХ `reset --hard`, перезаписей истории (amend/rebase несвежих коммитов) и `checkout` поверх грязного дерева**, пока возможны незакоммиченные правки параллельных сессий — rewrite стирает их безвозвратно; history-rewrite — только по согласованию через оркестратора при чистом дереве.
- **Чужое уже уехало в твой коммит?** Историю НЕ переписывать — сообщить владельцу/оркестратору; содержимое при этом цело, теряется только атрибуция.
- Никогда не коммитить В ЭТОТ репозиторий: `START_PROMT.MD` (файл владельца) · `.claude/settings.local.json` · книгу и производные. ⚠ С 24.08 книги лежат в `<репозиторий>/books` и версионируются ОТДЕЛЬНЫМ git-репозиторием со своим origin; внешний держит `/books/` в `.gitignore`. Следствия: `git status` внешнего репо правки книг НЕ показывает (смотреть `git -C books status`), а `clean -xdf` снёс бы каталог вместе с его историей.
## Онбординг новой сессии (порядок чтения)
**Развилка ПЕРВАЯ, до всего остального — есть ли у тебя хендофф-промт (файл задания, выданный оркестратором).**
- **С промтом:** этот файл → СВОЙ промт; его карта чтения (≤5 позиций) — ЗАКОН, дальше только по её ссылкам. README, CURRENT-STATE и голову журнала решений читать НЕ нужно, ЕСЛИ карта промта их не называет (у ролевого промта оркестратора называет — там его номер и очередь): ратифицированное вложено в тело промта, остальное берётся грепом по мере вопросов.
- **Без промта (холодный вход): СНАЧАЛА таблица активных промтов в конце `docs/README.md`.** Назван промт твоей роли — он и есть твой: дальше действует ветка «С промтом», и его карта чтения ЗАКОН. Не назван («активного НЕТ») — полный маршрут ниже. ⚠ Порядок именно такой: слепой замер входа 24.08 показал, что послушная холодная сессия проходит весь маршрут (~300 тыс. знаков) и только потом узнаёт, что её работа уже заказана промтом.
1. Этот файл → `docs/README.md` (карта) → CURRENT-STATE в `docs/PROGRESS.md`. Непонятный жаргон/сокращения — `docs/glossary.md`.
2. `docs/architecture/05-decisions-log.md`: **шапка** (карта актуальности + эрраты) и **реестр** `docs/architecture/05-decisions-index.md` — одна строка на ноту. Из тел — только 23 последние. ⚠ «Живая голова» целиком в обязательное чтение НЕ входит: это ВСЯ голова D39.124+, и она растёт каждую смену (на 10.09 — больше сотни нот), и требование её прочесть противоречило бы дисциплине «целиком НЕ читать» выше; остальные номера грепаются по мере вопросов. Тела закрытых эр — в слайсах `docs/archive/architecture/` (греп: живой файл → слайсы).
3. По роли (пути — от КОРНЯ репозитория): Бэкенд — шапка-таблица `docs/architecture/09-target-architecture.md` (что построено) → `backend/README.md``docs/architecture/03-implementation-notes.md` (через баннер) → `docs/architecture/12-go-style-notes.md`; ⚠ порядок именно такой: сессии, впервые видящей движок, механика нужнее стиль-норм; Полигон — `eval/README.md` + `docs/experiments/00-provider-quirks.md` + `docs/experiments/09-pilot-protocol.md`; Фронт/продукт — `docs/product-requirements.md` + `docs/research/16-reader-ide-alignment.md` (через ревью-шапку) + контракт `docs/architecture/14-api-contract/`; Платформа — `platform/README.md` + зонный журнал + `docs/research/23-engine-platform-seam.md` + контракт 14; всем — шапка-таблица `docs/architecture/09-target-architecture.md` (статус слоёв). Тулчейн на чистой машине — строка «Тулчейн» в карте `docs/README.md`.