{ "schema": "1.4", "catalogue": "ArtVsMark/Engineering-Incidents-Playbook", "generated_at": "2026-09-24T14:30:46+00:00", "consumers": [ { "repo": "ArtVsMark/Engineering-Incidents-Playbook", "role": "каталог", "schema": "1.5", "answers_to": "1.7", "state": "подключён", "read_at": "2026-09-24", "answered": 207, "by_status": { "active": 176, "not-applicable": 25, "rejected": 6 }, "by_mechanism": { "gate": 125, "document": 31, "pipeline": 9, "none": 11 }, "rules": { "001": "active", "002": "active", "003": "active", "004": "active", "005": "active", "006": "active", "007": "not-applicable", "008": "active", "009": "active", "010": "active", "011": "active", "012": "active", "013": "active", "014": "active", "015": "not-applicable", "016": "active", "017": "active", "018": "active", "019": "not-applicable", "020": "rejected", "021": "active", "022": "active", "023": "active", "024": "active", "025": "active", "026": "active", "027": "active", "028": "active", "029": "active", "030": "active", "031": "rejected", "032": "not-applicable", "033": "active", "034": "rejected", "035": "active", "036": "not-applicable", "037": "active", "038": "not-applicable", "039": "active", "040": "active", "041": "active", "042": "active", "043": "active", "044": "active", "045": "active", "046": "active", "047": "active", "048": "not-applicable", "049": "active", "050": "active", "051": "active", "052": "active", "053": "active", "054": "active", "055": "active", "056": "active", "057": "active", "058": "active", "059": "not-applicable", "060": "rejected", "061": "rejected", "062": "active", "063": "active", "064": "active", "065": "active", "066": "not-applicable", "067": "active", "068": "active", "069": "not-applicable", "070": "active", "071": "active", "072": "active", "073": "active", "074": "active", "075": "active", "076": "not-applicable", "077": "active", "078": "not-applicable", "079": "active", "080": "active", "081": "not-applicable", "082": "active", "083": "not-applicable", "084": "active", "085": "active", "086": "active", "087": "not-applicable", "088": "active", "089": "active", "090": "active", "091": "active", "092": "active", "093": "active", "094": "active", "095": "not-applicable", "096": "active", "097": "active", "098": "active", "099": "active", "100": "active", "101": "active", "102": "active", "103": "not-applicable", "104": "active", "105": "active", "106": "active", "107": "active", "108": "active", "109": "active", "110": "active", "111": "active", "112": "not-applicable", "113": "active", "114": "active", "115": "not-applicable", "116": "active", "117": "rejected", "118": "active", "119": "active", "120": "active", "121": "active", "122": "active", "123": "active", "124": "active", "125": "active", "126": "active", "127": "active", "128": "active", "129": "active", "130": "active", "131": "active", "132": "active", "133": "active", "134": "active", "135": "active", "136": "active", "137": "active", "138": "active", "139": "active", "140": "active", "141": "active", "142": "active", "144": "active", "145": "active", "146": "active", "147": "active", "148": "active", "149": "active", "150": "active", "151": "active", "152": "active", "153": "active", "154": "active", "155": "active", "156": "active", "157": "active", "158": "active", "159": "active", "160": "active", "161": "active", "162": "active", "163": "active", "164": "active", "165": "active", "166": "active", "167": "active", "168": "active", "169": "active", "170": "active", "171": "active", "172": "active", "173": "active", "174": "active", "175": "active", "176": "active", "177": "active", "178": "active", "179": "active", "180": "active", "181": "active", "182": "active", "183": "active", "184": "active", "185": "active", "186": "active", "187": "active", "188": "not-applicable", "189": "active", "190": "active", "191": "not-applicable", "192": "active", "193": "not-applicable", "194": "not-applicable", "195": "active", "196": "active", "197": "active", "198": "not-applicable", "199": "active", "200": "active", "201": "active", "202": "active", "203": "not-applicable", "204": "active", "205": "active", "206": "active", "207": "active", "208": "active" }, "holds": { "001": { "mechanism": "gate", "where": "scripts/check_transport.py — вызовы семейств `gh issue`/`gh pr`/`gh project`/`gh search` в scripts/*.py и .github/workflows/*.yml находятся разбором (в питоне по дереву вызовов, в оболочке по строке вызова, комментарии не предмет), и законен только тот, что назван поимённо в .rules/transport.json. Сейчас там один: включение автомержа, у которого REST-адреса нет вовсе", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "002": { "mechanism": "gate", "where": ".github/workflows/ci.yml — гейты на каждый PR; scripts/preflight.py — чек-лист свода переписан в команду, а шаги она читает из того же конвейера", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "003": { "mechanism": "gate", "where": ".github/workflows/agent-pr.yml реагирует на префикс `agent/**`, и свод называет это переключателем первой строкой — как правило и требует", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "004": { "mechanism": "gate", "where": "scripts/check_bindings.py и scripts/aggregate_bindings.py собирают ВСЕ находки и не падают на первой; недоступный потребитель помечается состоянием и не роняет сборку", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "005": { "mechanism": "gate", "where": "scripts/build_rules_index.py — числа каталога стоят в охраняемых маркерах и переписываются сборкой из самих записей (127); scripts/history_metrics.py — единственное место, где число в прозе законно вписано рукой, это снимок на момент выпуска, и он сверяется с деревом самого тега: разошлось — красное. Строку выпуску ставит .github/workflows/release.yml, а не договорённость: договорённость уже сломалась, и два выпуска ушли без строки. ГРАНИЦА: произвольное число в произвольной фразе гейтом не ловится — отличить число-факт от числа в названии машинно нечем, а ложные отказы на прозе приучают пропускать (051). Это остаётся приёмке", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "006": { "mechanism": "document", "where": "CLAUDE.md § Когда окно перезапускают — срок назван числом (три–пять дней) и к нему даны признаки, по которым не ждут календаря: окно перечитывает прочитанное, противоречит себе внутри смены, пересказ прошлого длиннее сегодняшней работы. ГРАНИЦА: проверки на срок нет, и держится он чтением при старте, а не гейтом. Прежняя формулировка границы — «возраст окна каталогу не виден» — снята как неверная: возраст в дереве есть, его датирует трейлер `Claude-Session`", "holdable": "not-yet", "awaiting": "", "analysed": "2026-09-16", "decided": "2026-09-16" }, "008": { "mechanism": "gate", "where": "scripts/check_prose.py — сворачиваемых блоков нет в отслеживаемых документах. Цитата внутри блока кода и упоминание в обратных кавычках нарушением не считаются: иначе первым нарушителем стала бы сама запись, объясняющая, чем спойлер плох", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "009": { "mechanism": "gate", "where": "scripts/check_charter.py и scripts/check_showcase.py считают через множество, а не по вхождениям: гейт, названный в конвейере дважды, считается одним", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "010": { "mechanism": "gate", "where": "scripts/merge_ready.py — пустой список проверок отвергается как «не стартовало», а обязательная проверка спрашивается поимённо. До этого .github/workflows/automerge.yml проверял только отсутствие красных, и условие было истинно на пустом множестве", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "011": { "mechanism": "gate", "where": ".github/workflows/consumers-sync.yml ходит наружу по расписанию и на слияние, а не циклом опроса; обязательная проверка изменения наружу не ходит вовсе", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "012": { "mechanism": "gate", "where": ".claude/hooks/push_guard.py — хук PreToolUse, объявленный в .claude/settings.json: толчок в ветку, отличную от текущей головы, отвергается ДО вызова git. Конвейер такого не ловит — он видит артефакт, а не действие, и к его запуску ветка уже сдвинута. Приём взят у грейдера — у него это тоже хук перед вызовом инструмента, — а файл нет: его запрет шире и собран под его конвейер. Разрешены толчок без имени ветки, толчок текущей и явная ссылка HEAD:<ветка>. Набор — tests/test_push_guard.py; живая проба состоялась при постройке: хук отверг толчок в несуществующую ветку и на ней же нашлась своя поломка — `2>&1` считалось именем ветки. ГРАНИЦА: ЧЬЯ ветка, сторож по-прежнему не знает — он знает только, что она не та, на которой стоит окно", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "013": { "mechanism": "pipeline", "where": ".github/workflows/agent-pr.yml передаёт заголовок и тело файлами (`--body-file`), а не строкой в оболочке; то же в .github/workflows/automerge.yml", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "014": { "mechanism": "document", "where": "CONTRIBUTING.md § Когда дефект считается исправленным — краснота доказывается ПОЛУ-откатом: убрать поведение, оставить имена; откат целиком даёт ImportError, и такое красное не доказывает ничего. Меняется ровно одна переменная. ГРАНИЦА: годная мутация ломает предмет правила, а не его границу, и отличить одно от другого может только понимающий предмет — гейт здесь не судья. ВТОРАЯ ПОЛОВИНА — принесла ли починка проверку вообще — машинная, и у нас она не построена", "holdable": "not-yet", "awaiting": "", "analysed": "2026-09-16", "decided": "2026-09-16" }, "016": { "mechanism": "gate", "where": "scripts/aggregate_bindings.py и scripts/collect_proposals.py печатают «и ещё N» вместо тихого урезания списка находок", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "017": { "mechanism": "gate", "where": "scripts/ghcli.py::остаток — при ПЕРВОМ отказе площадки запрашивается gh api rate_limit (сам запрос квоту не расходует) и факты приписываются к сообщению; scripts/check_subprocess.py — звать gh мимо этой двери нельзя, иначе мерить будет некому", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "018": { "mechanism": "gate", "where": "tests/ проверяют узлы — функции скриптов на подделанном дереве; .github/workflows/ci.yml прогоняет цепочку целиком. Разные окружения, разные предметы", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "021": { "mechanism": "gate", "where": "scripts/check_readers.py — у каждого документа (файл в корне либо README папки) объявлен читатель строкой «> **Читатель:** кто — на какой вопрос отвечает»; значение берётся из закрытого словаря из пяти, и у КАЖДОГО читателя обязан быть хотя бы один документ. Раскладка по каталогам docs/use|dev|agent|archive, как у грейдера, не взята намеренно: README, CONTRIBUTING и AGENTS имеют для площадки своё значение в корне, и переезд спрятал бы их — взят приём, а не файл (162). Указатель rules/README.md получает строку от сборки scripts/build_rules_index.py, а не от руки. ГРАНИЦА: тому ли читателю адресован текст на самом деле и не отвечает ли документ на два вопроса сразу, гейт не судит — это приёмка", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "022": { "mechanism": "gate", "where": "scripts/check_charter.py — свод и CONTRIBUTING.md сверяются с шагами .github/workflows/ci.yml scripts/check_own_name.py — то же правило о СОБСТВЕННОМ имени каталога: оно берётся у адреса origin, а не из реестра, и сверяется с ним; любая ссылка на репозиторий того же владельца обязана быть либо самим каталогом, либо записью реестра, либо объявленным исключением с причиной (068). Замер 2 сентября: репозиторий переименован, в дереве осталось 245 упоминаний старого имени, и перенаправление площадки скрыло это полностью — оба адреса отвечали 200, узналось из предупреждения git push. Имя приходит СНАРУЖИ намеренно: возьми гейт его из реестра, старое имя совпало бы с собой и он промолчал бы ровно там, ради чего построен (146).", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "023": { "mechanism": "gate", "where": "scripts/check_prose.py — витрина держится в пределе 1000 слов прозы: «первые пять минут» при 200 словах в минуту, то есть число переведено из самого правила, а не взято из достигнутого. Счёт в СЛОВАХ, а не в строках: перенос — форматирование, и гейт по строкам краснел бы от переливки абзацев (051); адрес ссылки словами не считается. Держат tests/test_check_prose.py — отвергает разросшуюся витрину и пропускает законную. ЗАМЕР 17.09 по всей истории README.md: 269 → 833 слова с 21 августа, правок, менявших длину, двадцать, и убыль среди них ровно одна — регресс, который правило само называет неизбежным, подтверждён рядом. Запас к пределу 167 слов. ГРАНИЦА: что ИМЕННО нужно человеку в первые пять минут, машина не решает и не станет — это суждение об аудитории, и оно остаётся автору с приёмкой (182). Гейт держит объём, то есть регресс; связность и уместность разделов — человеческая половина. ГДЕ ПОТОЛКА НЕТ — названо поимённо в самом скрипте: канон, записи правил, журнал и заготовки читают по надобности и частями, рост там не означает поломки. Набор держит форму этого списка — что пути существуют и витрина в них не входит; что предела там нет, он не держит и не может: предел в дереве один. Своду агента потолок нужен по другому доводу — он налог на любой старт, — но это предмет 029 и отдельное решение: замер даёт 3911 слов", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "2026-09-17" }, "024": { "mechanism": "gate", "where": "scripts/collect_changelog.py требует, чтобы «что сделали» приезжало отдельным фрагментом; «как работает» живёт в README и своде, «почему решили» — в docs/HISTORY.md", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "025": { "mechanism": "gate", "where": "scripts/check_prose.py — номер задачи в разделах объяснения («Почему», «Применимость», «Практические границы») отвергается; в «Инциденте» и «Следе» он законен, там он и есть датировка. ГРАНИЦА: список разделов запретительный, а не разрешительный — новый раздел объяснения гейт пропустит, и это выбор: ложный отказ на верной записи стоит дороже пропуска (051)", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "026": { "mechanism": "document", "where": "отвергнутые варианты записаны в export/README.md", "holdable": "not-yet", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "027": { "mechanism": "document", "where": "пустой реестр потребителей объявлен в export/README.md", "holdable": "not-yet", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "028": { "mechanism": "none", "where": "чек-лист в комплексной задаче — галочками, а не прозой. Машинного нет, и причина названа в why", "holdable": "conditional", "awaiting": "первая задача каталога с тремя и более НАХОДКАМИ в теле. Пока таких ноль, и гейт спрашивал бы галочки с любого длинного списка: отличить перечень находок от перечня фактов машинно нечем. Форма готова и взята у соседа — метка объявляет задачу комплексной, и тогда первая половина становится объявлением, а не суждением", "analysed": "2026-09-17", "decided": "" }, "029": { "mechanism": "gate", "where": "scripts/check_charter.py сверяет свод с конвейером: свод держит триггеры и ссылки, детали живут в записях каталога", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "030": { "mechanism": "gate", "where": "scripts/collect_changelog.py --check", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "033": { "mechanism": "gate", "where": "scripts/check_schedules.py + .rules/schedules.json — цена прогона объявляется числом, а гейт СЧИТАЕТ: сколько вызовов наружу расписания просят в худший час и укладывается ли сумма по всем расписаниям этого часа в объявленную долю часового лимита", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "035": { "mechanism": "gate", "where": "scripts/check_prose.py — поле версии в pyproject.toml обязано быть заглушкой: источник один, git-тег, и его читает scripts/version.py. ГРАНИЦА: проза не проверяется — число-факт от числа в рассказе машинно неотличимо (005), и замер по дереву дал шестнадцать законных файлов на ноль настоящих", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "037": { "mechanism": "gate", "where": "scripts/check_python_version.py — сверяет планку в манифесте, версию каждого прогона и версию окна, запустившего проверку перед толчком: зелёное, снятое версией ниже прогонной, доказательством не является", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "039": { "mechanism": "gate", "where": "scripts/check_gates.py прогоняет объявленные исходы по подделке: у каждого проверяемого им гейта есть предмет, который обязан пройти, и предмет, который обязан быть отвергнут; у scripts/main_red.py, scripts/link_trails.py и scripts/refresh_derived.py свои --selftest на все три исхода. ГРАНИЦА: «у каждого скрипта» машинно не проверяется — остальные держатся разбором при приёмке", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "040": { "mechanism": "gate", "where": "scripts/check_skips.py — шаг .github/workflows/ci.yml: пропуск без непустой причины (reason= у метки либо строка доводом вызова) даёт отказ. Пропусков сегодня ноль, и это состояние, а не пустота: предмет проверки — набор тестов, а он не пуст. ГРАНИЦА: «временно» и «потом» причиной считаются — гейт держит форму, содержание судит приёмка", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "041": { "mechanism": "gate", "where": "scripts/check_charter.py печатает ДВА числа — подделкой в check_gates и набором в tests, — а не одно усреднённое «покрыто гейтов»", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "042": { "mechanism": "document", "where": "export/README.md § Отвергнутые варианты", "holdable": "no", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "043": { "mechanism": "gate", "where": "scripts/build_rules_index.py — check_superseded: пометка «Заменено» разрешается в существующую запись, не зацикливается и стоит в обоих деревьях; отвергаемый предмет прогоняется в scripts/check_gates.py. ГРАНИЦА: ЗАМЕНЯЕТ ли одна запись другую — суждение автора, гейт держит форму уже принятого решения", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "044": { "mechanism": "gate", "where": "scripts/check_bindings.py — блок ревизии: каждое «чем держится» и каждый названный адрес есть УТВЕРЖДЕНИЕ о текущем коде, и оно сверяется с файлом прежде, чем вердикту поверят; несуществующий путь — отказ, «нет такого документа» при существующем — предупреждение. Тот же приём держит 044 у витрины и у собственного набора вопросов: scripts/check_showcase.py сверяет с деревом каждый путь, названный в `where` вопроса сопровождающего — адрес источника принимается не на слово. ГРАНИЦА: премиса находки В ПРОЗЕ — аудита, разбора, обсуждения — машинно не выделяется; здесь проверяется премиса ДЕКЛАРАЦИИ; scripts/check_python_version.py — объявленная планка requires-python есть утверждение о текущем дереве, и оно сверяется с ТРЕМЯ фактами: версией каждого прогона и версией того, кто запустил прогон перед толчком", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "045": { "mechanism": "gate", "where": "scripts/check_gates.py прогоняет объявленные исходы по подделке: у каждого проверяемого им гейта есть предмет, который обязан пройти, и предмет, который обязан быть отвергнут; у scripts/main_red.py, scripts/link_trails.py и scripts/refresh_derived.py свои --selftest на все три исхода. ГРАНИЦА: «у каждого скрипта» машинно не проверяется — остальные держатся разбором при приёмке", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "046": { "mechanism": "document", "where": "пробелы названы в export/README.md и в описаниях PR", "holdable": "no", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "047": { "mechanism": "document", "where": "CLAUDE.md § Когда окно перезапускают — сменились правила работы, окна перезапускают, а не рассылают им письма: свод и ядро читаются ОДИН раз, при старте, и правка живому окну не видна вовсе. ГРАНИЦА: реестра живых окон нет и не будет — рассылка потребовала бы знать, кто сейчас открыт", "holdable": "not-yet", "awaiting": "", "analysed": "2026-09-16", "decided": "2026-09-16" }, "049": { "mechanism": "gate", "where": "scripts/build_rules_index.py — области, даты и следы из файлов", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "050": { "mechanism": "document", "where": "scripts/check_schedules.py и .rules/schedules.json — доля лимита объявлена числом и снижается правкой файла; арифметика по худшему часу держится гейтом, а направление движения числа проверяется чтением изменения", "holdable": "no", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "051": { "mechanism": "gate", "where": "scripts/sync_labels.py предупреждает о неучтённой метке и НЕ удаляет её; значки выведены из-под гейта на ветку badges по той же причине", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "052": { "mechanism": "document", "where": "AGENTS.md § «Из общей ветки обновляется только голова очереди» — свод читает окно при старте, а исполнитель здесь именно окно", "holdable": "not-yet", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "053": { "mechanism": "gate", "where": "scripts/merge_ready.py::frozen, подключён шагом «красная общая ветка замораживает очередь» в .github/workflows/automerge.yml: пока последний прогон хоть одной работы общей ветки не зелёный, изменение не получает ни автомержа, ни слияния. Размораживает метка `blocker` на самом изменении — объявлена в .github/labels.yml; вычислить «а это ли починка» механизм не может, и признак ставит человек. Заморозка спрашивается ПЕРВОЙ, до проверок самого изменения: это условие входа, а не сортировка, и случай на это есть. .github/workflows/main-red.yml заводит задачу, пока ветка красная, — вторая половина: заморозка останавливает очередь, дежурный зовёт человека. ИЗМЕРЕНО: до 2 сентября 2026 ответ называл механизмом только дежурного, а тот никого не останавливает — ветка покраснела в 08:55, и за полчаса в неё уехали два изменения с зелёными проверками на сломанном основании (146). Тем же заходом снято `--exclude attribution-history` у дежурного: причина исключения давно выражена ключом --since, а само оно пережило её молча (068). Приём взят у грейдера целиком — метка та же и значит то же (162); набор — tests/test_merge_ready.py, одиннадцать случаев с обеих сторон. ГРАНИЦА: ПОРЯДОК внутри очереди — кто голова среди незамороженных — не держится ничем: у соседа он вычисляется своей шкалой приоритетов, здесь изменения идут по готовности, и это отдельный предмет (052)", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "054": { "mechanism": "document", "where": "AGENTS.md § Границы прохода — на шаге сбора ничего не разбирается, и три прохода каталога разведены поимённо таблицей: ночная сверка ответов потребителей печатает расхождения, а разбирает их задача У НИХ; опрос предложений тянет их из проектов, а номер присуждает каталог отдельным заходом; метрика «ничем» печатает очередь, а разбирает её окно по одному тиру. Названа и цена смешения: контекст первой находки съедает окно до последней, вторая половина прохода идёт хуже первой, решение по первому случаю принимается до того, как виден второй. У соседа это тоже документ — глава обхода курса (162). ГРАНИЦА: что окно ДЕЙСТВИТЕЛЬНО не разбирало на сборе, машинно не видно — разбор оставляет след только в тексте отчёта, и судит это приёмка", "holdable": "no", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "055": { "mechanism": "document", "where": "CONTRIBUTING.md § Своё ожидание — тоже гипотеза: порог, словарь и «так быть не может» берутся у замера, а не у автора, и названы три доступных внешних источника с живыми примерами — корпус (пороги языкового гейта замерены на 162 парах записей, между худшим настоящим значением и порогом разы), сосед (проверка «раздел решений называет отказ» НЕ заведена именно по его истории: шесть разделов из двенадцати не содержат ни одного слова, по которому отказ узнаётся машинно) и живая проба (139). Когда внешнего источника нет, остаётся мутация — соседний раздел того же документа. У соседа это тоже документ, и внешним судьёй там выступает сама площадка курса (162). ГРАНИЦА: что автор ДЕЙСТВИТЕЛЬНО сверился с внешним источником, а не подогнал замер под ожидание, гейт не видит — предмет тот же, что у 146, и судит его приёмка", "holdable": "no", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "056": { "mechanism": "gate", "where": "scripts/audit_catalogue.py требует у каждой записи границу «НЕ работает» — ту самую вторую половину, без которой описание неполно", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "057": { "mechanism": "gate", "where": "scripts/check_bindings.py требует поле mechanism, и значение `none` печатается метрикой поимённо: правило без механизма названо, а не забыто", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "058": { "mechanism": "gate", "where": "scripts/ghcli.py — у исчерпания свой терминальный код QUOTA_OUT, вне набора кодов gh и отличный от NO_GH; ghcli.failed его знает, поэтому вызывающий останавливается, а не идёт дальше. scripts/check_subprocess.py держит дверь единственной", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "062": { "mechanism": "gate", "where": "scripts/check_roles.py — у каждой строки карты направлений в AGENTS.md заполнены все колонки, а возражение НАЗЫВАЕТ АДРЕСАТА, и адресат — другая роль этой же карты: возражение в пустоту не останавливает никого, а спор с собой проходит всегда. Возражения сверяются на различие: два направления с одним доводом — одно направление, записанное дважды (009). Форма «Роль — «довод»» выбрана под посимвольную сверку: разбирать падежи ради одной таблицы значило бы ошибаться на каждой новой роли (013). Приём взят у грейдера — у него та же колонка и свой такой же гейт над отдельным документом ролей; файл не взят: там роли живут отдельным документом, здесь карта стоит в своде рядом с тем, что она объясняет (162). Набор — tests/test_check_roles.py, двенадцать случаев с обеих сторон. ГРАНИЦА: содержателен ли довод, гейт не судит — отписку нужной формы он пропустит, и отличить довод от отписки машинно нечем; приёмку проводит человек, а гейт держит полноту перебора", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "063": { "mechanism": "gate", "where": ".github/workflows/automerge.yml включается по всем условиям сразу: не черновик, открыто, проверки не красные, ветка защищена", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "064": { "mechanism": "gate", "where": ".github/labels.yml — набор меток и соответствие «путь → зона»; scripts/sync_labels.py применяет объявленное к репозиторию и называет неучтённое; scripts/check_labels.py выводит зону изменения из тронутых путей и сверяет с тем, что на нём стоит, а .github/workflows/agent-pr.yml её ставит — метит машина, потому что инцидент самого правила измерен так: «машина метила исправнее человека»; scripts/pr_body.py держит вторую половину, строку связи с задачей. ЦЕНА ПРЕЖНЕГО ОТВЕТА: он объявлял правило обеспеченным гейтом, называя адреса, которые держат ДРУГИЕ его половины. Замер 28 августа: у последних десяти изменений метки не было ни у одного, и нашёл это владелец глазами, а не механизм", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "065": { "mechanism": "document", "where": "CONTRIBUTING.md, раздел «Путь новичка»: обе метки входа объявлены в .github/labels.yml, тело задачи ведётся на двух языках, и правило действует в момент навешивания метки. Двуязычие тела не проверяется ничем — метку вешает человек", "holdable": "not-yet", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "067": { "mechanism": "gate", "where": "scripts/check_gates.py прогоняет объявленные исходы по подделке: у каждого проверяемого им гейта есть предмет, который обязан пройти, и предмет, который обязан быть отвергнут; у scripts/main_red.py, scripts/link_trails.py и scripts/refresh_derived.py свои --selftest на все три исхода. ГРАНИЦА: «у каждого скрипта» машинно не проверяется — остальные держатся разбором при приёмке", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "068": { "mechanism": "gate", "where": "scripts/check_attribution.py --require-declared-author — автор коммита сверяется с разрешительным списком «[авторы]», а не с запретом на одно написание. До задачи #79 запрет ловил `Claude ` и молчал на `claude[bot]`; изменение #78 уехало в общую ветку авторства бота", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "070": { "mechanism": "document", "where": "scripts/check_locale.py — докстрока § ПОРОГИ НЕ ВЫДУМАНЫ, А ЗАМЕРЕНЫ: два порога (0.15 сверху для английского дерева, 0.50 снизу для русского), и остаточный риск назван там же: «нарушение даёт значение у другого края, а не рядом с порогом, поэтому пороги стоят с запасом в разы». Это и есть требование 070, исполненное.", "holdable": "no", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "071": { "mechanism": "pipeline", "where": "намеренный повтор подписывается комментарием с причиной — так помечено двойное построение тела коммита в .github/workflows/automerge.yml", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "072": { "mechanism": "gate", "where": "scripts/check_gates.py ловит причину подделанным предметом, tests/ ловят факт фикстурой; обе нужны и обе стоят", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "073": { "mechanism": "document", "where": "requirements-test.txt — ОДИН источник версий на две работы — обязательную и значковую — и это половина правила, которая держится: до файла список был вписан в обе работы, и два списка одного набора разошлись бы молча (022). ВТОРАЯ ПОЛОВИНА НЕ ДЕРЖИТСЯ: верхней границы нет ни у одного из трёх — pytest, coverage, pyyaml стоят голыми именами.", "holdable": "not-yet", "awaiting": "верхняя граница у трёх имён в requirements-test.txt: сегодня их 0 из 3. Граница ставится отдельным изменением — эта запись даёт вердикт, а не правит зависимости (132)", "analysed": "2026-09-17", "decided": "" }, "074": { "mechanism": "gate", "where": "scripts/version.py считает версию из истории и тегов и отказывается при отсутствии тега схемы (исход 2) — до того, как выпуск состоялся", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "075": { "mechanism": "gate", "where": "scripts/ghcli.py и tests/test_ghcli.py — механизм, не нашедший инструмента, отдаёт третий исход, а не зеленеет. У потребителя код 1 означает «очередь, всё в порядке», и скрипт без gh рапортовал успех, не работая вовсе", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "077": { "mechanism": "gate", "where": "scripts/check_locale.py — запись, положенная в rules/en/ по-русски, ловится языком: доля кириллицы в английском дереве не выше 15%, в русском не ниже 50%. Пороги замерены на корпусе из 162 пар (en от 0.000 до 0.022, ru от 0.814 до 0.946) — нарушение даёт значение у другого края, а не рядом с порогом. Структурный паритет держит scripts/build_rules_index.py, и он этого случая не видит: разделы на месте, имена совпадают. ГРАНИЦА: качество перевода гейт не судит — подстрочник и калька остаются предметом чтения на обоих языках, и это граница самого правила", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "079": { "mechanism": "gate", "where": "scripts/aggregate_bindings.py — возраст ответа считается от МОМЕНТА ЧТЕНИЯ, а срок «объявлен, но не подключился» — от даты объявления в .rules/consumers.json, поле since. Отсчёт от постановки в очередь и от завершения здесь разведены нарочно", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "080": { "mechanism": "gate", "where": "scripts/collect_proposals.py — опрос предложений потребителей и одна задача-адресат; --check держит вердикт каталога", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "082": { "mechanism": "document", "where": "карта направлений в AGENTS.md; полнота проверена обходом артефактов, часть направлений закрыта гейтами, непокрытые названы непокрытыми", "holdable": "not-yet", "awaiting": "", "analysed": "2026-09-16", "decided": "2026-09-16" }, "084": { "mechanism": "gate", "where": "scripts/check_workflows.py — прогон, не объявленный основной работой, не просыпается на pull_request: его отказ не должен ни задерживать слияние, ни ронять его. Список тех, кому положено, разрешительный и короткий: .github/workflows/ci.yml и .github/workflows/automerge.yml", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "085": { "mechanism": "gate", "where": "scripts/check_untrusted_prompt.py — шаг, зовущий модель, обязан назвать чужой текст данными, иначе находка; .github/workflows/review.yml — пометка стоит первым абзацем промпта; scripts/collect_proposals.py — ответ чужого проекта кладётся как данные", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "086": { "mechanism": "gate", "where": "scripts/collect_proposals.py::check_verdicts — оценку находке ставит НЕ нашедший: проект присылает предложение БЕЗ номера (форма запрещает поле прямо), а вердикт выносит принимающая сторона, и гейт требует у каждого предложения статус из закрытого набора, у принятого — существующий номер правила, у отклонённого — причину. Номер не переиспользуется: два предложения под одним отвергаются. ГРАНИЦА, И ОНА ПОЛОВИНА ПРАВИЛА: вторая половина — «шкала опровергателя калибруется примерами» — не держится ничем, и предмета для неё пока нет: вердиктов семь, все «принято», ни одного отказа. Гейт на калибровку отказа мерил бы пустоту (075), и заводить его стоит с первым же отклонённым предложением. У соседа калибровка держится документом — шкалой на примерах в своде (162)", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "088": { "mechanism": "document", "where": "AGENTS.md § Границы прохода — на границе фазы спрашивают о МЕТОДЕ, а не о продукте, и обе границы каталога названы с их вопросами: «построил гейт → приёмка» (отвергает ли он предмет правила, а не свою границу; прогонялся ли объявленный третий исход; не подтверждает ли он сам себя) и «закрыл тир очереди → замер» (сколько закрыто работой, а сколько переклассификацией). Там же сказано, что вопрос задаёт не тот, кто делал фазу, и что у окна это означает отдельный заход после отчёта, а не абзац в конце того же. Роль «критик метода» стоит в карте направлений со своим возражением, и полноту карты держит гейт ролей. У соседа это тоже документ — роль отдельным разделом (162). ГРАНИЦА: что вопрос был ЗАДАН, машинно не видно; карта держит существование роли, а не её работу", "holdable": "no", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "089": { "mechanism": "gate", "where": "scripts/check_prose.py — запись каталога не ссылается в производные: rules/README.md, export/**, .github/badges/**. ГРАНИЦА: проверяются только записи. Свод ссылается на указатель законно — он не оригинал для него, а вход читателя в каталог", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "090": { "mechanism": "gate", "where": "scripts/ghcli.py — вызов gh живёт уровнем выше всех своих потребителей, а не копируется вбок. До этого копий было четыре, и все четыре разошлись в поведении при отсутствии самого gh; tests/test_ghcli.py спрашивает у каждого скрипта отдельно", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "091": { "mechanism": "gate", "where": "свод перечисляет источники работы по порядку, нулевым — красное на общей ветке; статусы живут только в трекере, и .github/workflows/main-red.yml заводит задачу туда же", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "092": { "mechanism": "document", "where": "AGENTS.md § Открытая работа — порядок разбора: источники работ перечислены и упорядочены, первый непустой и есть план (091). Находки живут отдельно и в другом месте: задачами в трекере, по одной на предмет. Два вопроса — два артефакта, ровно как требует правило; совпадает лишь то, что ни один из них не называется «документом аудита»", "holdable": "no", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "093": { "mechanism": "document", "where": "AGENTS.md § Шов и обобщение — разведены дешёвое и дорогое: шов это минимальный обратимый рефактор, обобщение это абстракция, которая застывает. Названы поимённо ТРИ введённых шва (общий вызов площадки, ключ --root у гейтов, общее определение ссылки) и ТРИ обобщения, которых не завели, с причиной у каждого — второй уровень группировки областей, общий разбор таблиц у двух гейтов, общий сборщик снимков. Там же признак нарушения: абстракция с ровно двумя реализациями, вторая из которых вчерашняя. У соседа это тоже документ — решение о шве отдельной запиской (162). ГРАНИЦА: СЧИТАТЬ случаи машинно нечем — «третий случай» опознаёт человек по употреблению, а не по числу файлов, и судит это приёмка", "holdable": "no", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "094": { "mechanism": "none", "where": "механизма нет, предмет в scripts/check_bindings.py: ЛЕГАСИ_ПОЛЕ = \"document_reason\" и ЛЕГАСИ_СЛОВА = {\"impossible\": \"no\", \"not-yet\": \"not-yet\"}, плюс by_mech[\"process-step\"] под именем legacy. Это переходный слой со старого словаря реестра на новый.", "holdable": "not-yet", "awaiting": "условие снятия у переходного слоя: сегодня записей со старым полем `document_reason` в реестре 0, то есть слой уже пуст и держится только тем, что его никто не убрал", "analysed": "2026-09-17", "decided": "" }, "096": { "mechanism": "gate", "where": ".rules разведён по жизненному циклу, и у каждого файла свой сторож: .rules/bindings.json — scripts/check_bindings.py, .rules/neighbours.json — scripts/check_duplicates.py, .rules/proposals.json — scripts/collect_proposals.py, .rules/consumers.json — scripts/aggregate_bindings.py, .rules/showcase.json — scripts/check_showcase.py. Общего файла нет намеренно: слить их значило бы оставить все эти вопросы одному сторожу", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "097": { "mechanism": "gate", "where": "tests/test_coverage_badge.py — пороги цвета проверяются с ОБЕИХ сторон границы, и ложный отказ ловится тем же прогоном, что и ложное «прошло»; tests/test_aggregate_bindings.py — отсутствие связи и нечитаемый ответ разведены нарочно, чтобы состояние не стало находкой", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "098": { "mechanism": "document", "where": "CONTRIBUTING.md § Как устроена запись — мерка одна: записывается то, что цитируют целиком. Запись, на которую ссылаются половиной, дробится; второй инцидент к существующему правилу нового номера НЕ заводит, а дописывается второй историей; «длинно» и «плохо ищется» лечится метаданными — областью, указателем, строкой «Смежное», — а не дроблением; намеренный бандл называется бандлом вслух, иначе следующий заход разобьёт его как недосмотр. У соседа это тоже документ — глава словаря «одна концепция — одна карточка» (162). ГРАНИЦА: цитируемость записи целиком оценивает читатель, и машинного признака у неё нет", "holdable": "no", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "099": { "mechanism": "gate", "where": "словарь областей в scripts/build_rules_index.py закрытый: область, которой в нём нет, сборку не проходит, и спор решается пополнением словаря осознанно", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "100": { "mechanism": "gate", "where": "scripts/check_workflows.py — предел времени обязателен у каждой работы конвейера. ГРАНИЦА: дедлайна на СТАРТ площадка не даёт, timeout-minutes идёт с момента, когда работа получила машину, — вторая половина правила на этой поверхности не обеспечена", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "101": { "mechanism": "none", "where": "scripts/ghcli.py — исчерпание квоты отвечает своим терминальным кодом, и повтор на нём невозможен по построению: постоянный отказ повтором не лечится. Повторяется только толчок ветки, и только на сетевом отказе. Машинного сторожа над этим нет, причина в why", "holdable": "conditional", "awaiting": "первый повтор, добавленный вокруг вызова наружу. Сейчас их ноль: замер 8 сентября по scripts/ — циклов, обёрнутых вокруг обращения к площадке, нет ни одного, и гейт держал бы пустоту. Форма готова: повтор вокруг вызова, чей отказ объявлен терминальным, — находка", "analysed": "2026-09-17", "decided": "" }, "102": { "mechanism": "gate", "where": "scripts/check_transport.py + .rules/transport.json — послабления перечислены закрытым списком, у каждого назван адрес и причина; адрес, которого в списке нет, становится находкой. Тем же устройством вынесены .rules/exclusives.json и .rules/schedules.json: послабление у каталога живёт таблицей, а не разлито по коду", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "104": { "mechanism": "gate", "where": ".github/workflows/ci.yml — workflow_dispatch", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "105": { "mechanism": "pipeline", "where": ".github/workflows/review.yml — ревью ведёт ОТДЕЛЬНЫЙ прогон со своей учётной записью, а не то окно, что писало изменение. Форма взята у соседа ArtVsMark/Stepik-Python-Grader, чей ответ по 105 лежит в export/where.json (правило 162); расхождения с ним названы в шапке нашего файла. И вторая половина, заведённая 8 сентября: работа внешнего взгляда УЧИТЫВАЕТСЯ, а не пропадает вместе с окном — scripts/review_findings.py разбирает вердикт ревьюера строками «НАХОДКА: …» и «ВЕРДИКТ: находок N», кладёт заметки в живую задачу и уносит названные разобранными", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "106": { "mechanism": "gate", "where": "scripts/check_showcase.py — у вопроса набора НАЗВАН АДРЕСАТ (`for`), и ответ ему разный. Посетителю — живой значок, названный в витрине, либо строка `absent` с причиной: витрина не показывает то, на что ответить нечем, а пробел называется — значка нет и значок застыл с витрины неотличимы. Сопровождающему — `where`, разрешимый адрес живого источника; значок ему запрещён отдельно: число, дёргающееся от каждого изменения, шумит там, куда смотрят один раз. ИЗМЕРЕНО: до схемы 1.1 у трёх вопросов из десяти стояло «значка нет; получается прогоном pytest -q» — источник, названный прозой и не проверяемый ничем; теперь путь сверяется с деревом (044). Числа тестов и тестовых модулей живут колонками снимка выпуска в docs/HISTORY.md, считает их scripts/history_metrics.py по дереву тега. ГРАНИЦА: верно ли ЧИСЛО в значке и свеж ли он, гейт не судит — значки живут на ветке `badges`, и сверять их в дереве общей ветки не с чем", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "107": { "mechanism": "gate", "where": "scripts/check_test_deps.py — каждый импорт в tests/ объявлен в requirements-test.txt, а не просто установлен у автора. Имя модуля объявляется рядом с пакетом («pyyaml # модуль: yaml»), а не выясняется у метаданных: первая версия гейта спрашивала установленное и упала в конвейере ровно той болезнью, которую ловит", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "108": { "mechanism": "gate", "where": "scripts/audit_catalogue.py — у растущей части docs/HISTORY.md есть предел, выраженный числом, и вышедшее за окно гейт называет поимённо, старшими вперёд. Дословность переноса в архив машинно не проверяется, и это сказано в самом скрипте", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "109": { "mechanism": "gate", "where": "scripts/check_gates.py прогоняет объявленные исходы по подделке: у каждого проверяемого им гейта есть предмет, который обязан пройти, и предмет, который обязан быть отвергнут; у scripts/main_red.py, scripts/link_trails.py и scripts/refresh_derived.py свои --selftest на все три исхода. ГРАНИЦА: «у каждого скрипта» машинно не проверяется — остальные держатся разбором при приёмке", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "110": { "mechanism": "document", "where": "scripts/link_trails.py — образец исполнения правила: временный каталог берётся ДО подмены (`with tempfile.TemporaryDirectory() as empty`), прежний PATH сохраняется, подмена идёт следом, восстановление стоит в `finally`. Всё, что может отказать, сделано до подмены — ровно требование 110.", "holdable": "not-yet", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "111": { "mechanism": "gate", "where": "конвейер делает сам, а не советует: .github/workflows/agent-pr.yml открывает изменение, .github/workflows/automerge.yml включает слияние, .github/workflows/labels-sync.yml расставляет метки", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "113": { "mechanism": "document", "where": "export/README.md § «Как схема меняется» — правила эволюции лежат рядом со схемой; § «На каких условиях» — что каталог делает с присланным и под какой лицензией публикует. Второе добавлено 31 августа: контракт год описывал формат и молчал об условиях, при том что поля потребителей цитируются в сводке дословно", "holdable": "not-yet", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "114": { "mechanism": "gate", "where": "scripts/check_attribution.py — подрезка до объявленного начала", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "116": { "mechanism": "gate", "where": "scripts/build_rules_index.py — сверка «сколько записей на входе / сколько в указателе на выходе»: расхождение останавливает сборку", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "118": { "mechanism": "gate", "where": "scripts/build_rules_index.py — экспорт рядом с источником", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "119": { "mechanism": "gate", "where": "scripts/check_candidates.py исключает README.md из отбора кандидатов, scripts/check_links.py — свои производные; инструмент не обрабатывает собственный вывод", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "120": { "mechanism": "gate", "where": "scripts/build_rules_index.py --check — указатель СОБИРАЕТСЯ из самих записей и сверяется на каждом изменении: области, словарь, следы, паритет деревьев, экспорт и значок числа правил. Обратный указатель «где правила действуют» строится из следов, а не ведётся руками: scripts/link_trails.py дописывает след в задачу потребителя, scripts/aggregate_bindings.py собирает сводку из ответов проектов. Единицу, номер и обязательные разделы держат scripts/audit_catalogue.py и scripts/check_bindings.py — у каждого правила есть ответ, и он полон. ПРЕЖНИЙ ОТВЕТ БЫЛ УКЛОНЕНИЕМ: «правило сквозное, отдельного гейта у него быть не может» — а проверяемые утверждения 120 разобраны поимённо, и у каждого свой гейт. Сквозным правило от этого быть не перестало: сквозное — не значит непроверяемое. У соседа то же самое и тем же приёмом: указатель генерируется, а проверка не даёт ему разойтись с ответом проекта (162). ГРАНИЦА: ПОЛЕЗЕН ли каталог тому, кто его взял, гейты не судят — на это отвечает сводка «где действует», и отвечает данными, а не вердиктом", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "121": { "mechanism": "none", "where": "контейнеров у каталога нет; критерий закрытия у единицы работы — задача, а не изменение. Причина в why", "holdable": "conditional", "awaiting": "первый заведённый контейнер — эпик, веха или спринт. Сейчас их ноль, открытых задач три, дочерних нет ни у одной. Обе половины машинные целиком, площадка отдаёт их числами, и у соседа это держится ночным обходом — строить с первым же контейнером", "analysed": "2026-09-17", "decided": "" }, "122": { "mechanism": "document", "where": "export/rules.json отдаёт следы структурой рядом с текстом", "holdable": "not-yet", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "123": { "mechanism": "gate", "where": ".github/workflows/ci.yml — коммиты ветки на pull_request; .github/workflows/attribution-history.yml — первопредки main на push; долг прошлой истории виден числом, а не подрезан", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "124": { "mechanism": "document", "where": "AGENTS.md § Перезапуск — это диагностика. Перезапускается ОДНА упавшая работа, а не весь прогон; попытка ровно одна — второй перезапуск того же места это попытка получить нужный ответ повторением вопроса. Три случая разрешённого перезапуска разведены таблицей, и каждый называет, ЧТО записывается: причина найдена и устранена — ничего, это обычная починка; работа умерла до первого теста — фрагмент журнала; та же работа зеленела на этом же коммите раньше — фрагмент журнала как НЕСТАБИЛЬНОСТЬ, с номером прогона. Отдельного реестра нестабильностей нет намеренно: запись едет тем же фрагментом в changelog.d/, что и всякая починка, и там же отвечает, тянет ли она на правило — второй реестр разошёлся бы с первым (022). Сказано и то, что «флаки» причиной не является. У соседа это прогон с закрытым списком разрешённых к автоперезапуску и своим файлом находок; приём взят, файл нет — здесь перезапускает человек кнопкой площадки, и автоматического перезапуска нет вовсе (162). ГРАНИЦА: что перезапуск СОСТОЯЛСЯ, машина здесь не видит — кнопку жмёт человек, и запись остаётся на нём", "holdable": "not-yet", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "125": { "mechanism": "gate", "where": "scripts/build_rules_index.py — область и даты из источников", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "126": { "mechanism": "pipeline", "where": ".github/workflows/thaw.yml — позеленевший прогон общей ветки сам перевзводит автомерж на стоящих изменениях; scripts/check_workflows.py держит вторую половину — ручную кнопку у каждого прогона", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "127": { "mechanism": "gate", "where": "scripts/build_rules_index.py — все три условия правила сразу: число стоит в именованном маркере, сборка переписывает его из самих записей, и пропажа маркера роняет сборку. Третье и есть то, без чего механизма нет: подделка на пропавший маркер прогоняется в scripts/check_gates.py", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "128": { "mechanism": "gate", "where": "scripts/build_rules_index.py — check_vocabulary", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "129": { "mechanism": "document", "where": "export/rules.json, export/README.md, этот файл", "holdable": "no", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "130": { "mechanism": "pipeline", "where": "scripts/sync_inbox.py::candidates_here — во «входящие» потребителя приезжает раздел «Возможно, про это ваши задачи»: правила, ещё не разобранные у него, рядом с ЕГО ОТКРЫТЫМИ ЗАДАЧАМИ, чьи заголовки говорят о том же. Доставить правило мало: без предмета в своём проекте оно остаётся абстракцией, которую откладывают — «понятно, но не про нас». Связывают значимые слова: короче пяти знаков в счёт не идут (в русском это почти всегда служебное), общие слова каталога — «правило», «механизм», «проект» — исключены поимённо, потому что стоят и в правилах и в задачах и связывают всё со всем. Порог: два общих слова либо одно длиной от девяти. Показывается не больше двух задач на правило: список-отчёт не читают (016). ГРАНИЦА НАЗВАНА В САМОМ РАЗДЕЛЕ, а не только здесь: совпадение по словам — ПОДСКАЗКА, а не вердикт; ни принять правило, ни отклонить его список не может. Трекер не ответил — раздела просто нет: 130 говорит «приходит вместе с», а не «не приходит без», и ронять доставку правил из-за подсказки значило бы менять предмет на украшение (084). Приём взят у грейдера — у него это свой прогон связи правил с задачами (162). Набор — tests/test_sync_inbox.py, семь случаев с обеих сторон", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "131": { "mechanism": "gate", "where": ".github/workflows/agent-pr.yml — изменение открывает конвейер токеном владельца, окно только толкает ветку agent/**", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "132": { "mechanism": "document", "where": "AGENTS.md § Критические запреты — «не везти в одном изменении две темы» стоит запретом наравне с остальными, и там же сказано, ПОЧЕМУ у него нет гейта: число тронутых зон сборности не доказывает (правка одного правила законно трогает пять зон и остаётся одной темой), а ложный отказ на широкой теме дороже пропуска и приучает читать красное как фон (051). Само правило требует предупреждения, а не отказа — и предупреждение есть: шаг «кто ещё правит те же файлы» в .github/workflows/agent-pr.yml пишет пересечения в сводку работы, не блокируя (133). Признак нарушения назван и читается, а не считается: заголовок изменения не описывается одной строкой без союза «и». У соседа ровно то же и по той же причине: запрет документом, пересечение неблокирующим шагом, гейта нет намеренно (162). ГРАНИЦА: одна тема в изменении или две — из диффа не следует, и судит это приёмка", "holdable": "no", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "133": { "mechanism": "pipeline", "where": "scripts/check_overlap.py — шаг «кто ещё правит те же файлы» в .github/workflows/agent-pr.yml: пересечение файлов ветки с чужими ОТКРЫТЫМИ изменениями печатается и пишется в сводку работы, где его видит толкнувший. Не отказ: пересечение бывает законным — производные трогает почти каждая правка, и красное на законном приучало бы пропускать красное (051). Живость берётся у площадки, а не из формы истории: слияние идёт squash, и git branch --no-merged считает живыми все 23 ветки из двух. ГРАНИЦА: шаг стоит на стороне площадки, потому что в агентском окне gh нет — там проверка отвечает третьим исходом", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "134": { "mechanism": "document", "where": "CLAUDE.md — свод проекта со ссылкой на каталог; окно перезапускается после наполнения, гейта на это нет", "holdable": "not-yet", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "135": { "mechanism": "gate", "where": "scripts/check_attribution.py — личность окна узнаётся ЗАПИСЬЮ и проверяется по ней: гейт сверяет поле автора коммитов ветки (при слиянии объединяющим коммитом в общую ветку едет именно оно) и трейлеры соавторства против разрешительного списка .github/authors.txt, а .github/workflows/attribution-history.yml спрашивает то же у первопредков main — там, где подмена и проявляется. ПРЕЖНИЙ ОТВЕТ БЫЛ НЕВЕРЕН: «проба делается ДО работы, а гейт стоит после неё» — но 135 и говорит, что личность узнаётся записью, а не опросом, и проверка записи это и есть проба. Живой случай в тот же корпус: 2 сентября окружение окна сменило подпись соавтора с «Claude» на «Claude Opus 5» молча, ни о чём не объявив; нашла это проверка по первопредкам, а не опрос токена и не память окна. ГРАНИЦА: ДО первой записи личность по-прежнему неизвестна — гейт узнаёт её постфактум, и пробным изменением остаётся первое же", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "136": { "mechanism": "document", "where": ".rules/bindings.json — вердикт о себе пишется после перебора предметов, и перебор называется в причине; гейта на полноту перебора нет", "holdable": "no", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "137": { "mechanism": "gate", "where": "scripts/check_locale.py (доля считается по буквам файла, а не по готовой сводке), scripts/check_skips.py (пропуск опознаётся разбором, а не совпадением текста), scripts/check_verdict_order.py (предмет — дерево разбора, а не текст)", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "138": { "mechanism": "gate", "where": "scripts/collect_changelog.py::entry_required, шаг «запись журнала едет вместе с изменением» в .github/workflows/ci.yml — изменение, тронувшее scripts/, .github/workflows/ или .github/actions/, обязано нести фрагмент в changelog.d/ ТЕМ ЖЕ заходом. Решение, оставленное «на потом», существует только в контексте окна и умирает вместе с ним; отложенная запись означает, что следующее окно разбирает тот же вопрос заново. ЗАМЕР, А НЕ ОПАСЕНИЕ: из 34 изменений общей ветки, тронувших поведение, запись несут 34 — ноль нарушений. Гейт не чинит поломку, он снимает правило с ДИСЦИПЛИНЫ (002): дисциплина, о которой известно, что она стопроцентна, ломается ровно один раз и незаметно, потому что смотреть на неё уже перестали. У отказа есть названный выход — строка «Журнал: не требуется — причина» в теле изменения; без выхода гейт обходили бы пустым фрагментом, а пустой фрагмент хуже отсутствующего: он выглядит памятью (126). Освобождение ищется СТРОКОЙ, а не вхождением: те же слова в прозе освобождением не являются (141). Приём взят у витрины — у неё это тот же гейт над своим документом истории (162). ГРАНИЦА: СОДЕРЖАНИЕ записи гейт не судит — отличить решение от отписки машина не может, и вопрос «тянет ли это на правило» задаётся соседним шагом человеку, а не проверяется", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "139": { "mechanism": "gate", "where": "scripts/check_bindings.py — ответ `mechanism: gate` обязан назвать в where адрес исполняемого: scripts/*.py, .github/workflows/*.yml, tests/*.py, action.yml или хук в .claude/. Список видов закрытый: «похоже на путь» приняло бы docs/*.md — законный адрес для document и незаконный для gate", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "140": { "mechanism": "gate", "where": "scripts/check_gates.py прогоняет объявленные исходы по подделке; scripts/check_charter.py ТРЕБУЕТ отвергаемый предмет у каждого гейта конвейера — подделкой, набором со случаем `main(...) == 1` либо собственной самопроверкой, которую зовёт .github/workflows/ci.yml. До 31 августа это была метрика: числа печатались и ни к чему не вели, а два гейта стояли без предмета вовсе. Список освобождённых разрешительный и пуст", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "141": { "mechanism": "gate", "where": "scripts/check_gates.py — набор «сборка указателя»: маркер и его расширение прогоняются как отдельный случай", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "142": { "mechanism": "gate", "where": ".github/workflows/consumers-sync.yml — шаг «у находки есть адресат» заводит задачу при отказе; scripts/review_findings.py — заметки внешнего взгляда переживают слияние: они записываются в живую задачу с отпечатком и снимаются строкой «Разобрано: <отпечаток>» в теле изменения-починки. Замер 8 сентября: из 29 слитых изменений с прогоном ревью вердикт успел к слиянию у 10, и находки #392 не увидел никто", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "144": { "mechanism": "gate", "where": "scripts/check_text_cuts.py — у каждого разреза строки по точке (split, rsplit, partition, find, index; включая регулярку [.!?]) обязана стоять рядом пометка «не проза: <что именно>». Разбор дерева, а не поиск подстроки", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "145": { "mechanism": "gate", "where": "scripts/check_gates.py — каждый набор двусторонний, у каждого есть предмет, который гейт обязан пропустить, и предмет, который обязан отвергнуть; scripts/check_workflows.py::dead_exit_codes держит вторую половину — оболочку шага: `имя=$?` законно только в форме `|| имя=$?`, иначе присвоение стоит за строкой, до которой под `bash -e` не доходит управление. Проверяются прогоны И составные действия: инцидент правила назвал три места, и одно из них корневой action.yml", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "146": { "mechanism": "gate", "where": "scripts/aggregate_bindings.py — обязательная проверка сверяет сводку с ОТВЕТОМ на диске, а не только саму с собой; до #122 она подтверждала своё основание тем же зелёным, каким подтверждала себя. Остальное правило держится разбором при приёмке: замер живого предмета машинно не отличить от рассуждения", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "147": { "mechanism": "pipeline", "where": ".github/workflows/off-prefix.yml — толчок в ветку МИМО префикса `agent/` поднимает прогон, единственная работа которого сказать, что изменение по этой ветке не откроется, и назвать, чего именно не произойдёт: не запустится открывающий прогон, не проставится зона, не посчитаются пересечения. Предмет правила ровно этот: отмена по несовпадению происходила МОЛЧА — «не совпало» и «совпало, но делать нечего» давали один наблюдаемый результат, тишину. Цена измерена: готовое правило пролежало сутки на ветке мимо префикса, и нашёл его случайный взгляд — прогона не было вовсе, а значит не было ни красного, ни лога, ни кода возврата. Прогон НЕ отказывает: ветка мимо префикса это штатное состояние, и красное на ней приучало бы читать красное как фон (051); он даёт отмене место, где её видно (142). Ручная кнопка на месте (104, 126). ГРАНИЦА: адресат появился у ОДНОЙ отмены — у той, что решается именем ветки. Прочие пропуски конвейера печатают ::warning:: в своём прогоне, и это уже другой предмет", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "148": { "mechanism": "pipeline", "where": ".github/workflows/automerge.yml ждёт появления коммита в общей ветке, а не поля merged записи изменения; разбор отказа начинается со сверки метки времени записи с проверками на голове", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "149": { "mechanism": "none", "where": "tests/conftest.py — фикстура repo(tmp_path): набор берёт площадку у pytest и владеет ею, вместо того чтобы делить общий временный каталог и вносить себя в исключения. Машинного сторожа над этим нет, причина в why", "holdable": "conditional", "awaiting": "первое обращение набора к общему пути — tempfile.gettempdir(), /tmp или Path.home() в tests/. Сейчас их ноль: замер 8 сентября по всему набору, вся площадка берётся фикстурой repo(tmp_path). Форма готова: разбор исходника набора, не прогон", "analysed": "2026-09-17", "decided": "" }, "150": { "mechanism": "gate", "where": "scripts/check_gates.py — каждый гейт проверяется своим вердиктом на подделанном предмете; scripts/check_charter.py — набор засчитывается только со случаем, где гейт отвечает ОТКАЗОМ через main(). Второе добавлено 31 августа по замеру: два набора того дня звали внутреннюю функцию, файл существовал, и мутация «главный ход её не зовёт» их переживала", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "151": { "mechanism": "document", "where": "прогон гейтов идёт локально теми же командами, что и в конвейере (CONTRIBUTING.md § Прежде чем открывать PR), и каждый печатает находки в вывод — читателю не нужны права на артефакты", "holdable": "not-yet", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "152": { "mechanism": "gate", "where": "scripts/check_workflows.py — на pull_request_target и workflow_run файл прогона берётся с общей ветки, и там закрепление вызываемого обязательно. ГРАНИЦА и она же инцидент правила: на pull_request требовать его нельзя — сам файл прогона приходит из изменения, и защита выходит нулевой. Сегодня таких событий у каталога нет, гейт сторожит их появление", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "153": { "mechanism": "document", "where": "export/README.md § контракт — чужие решения описаны ссылкой на репозиторий потребителя, а не пересказом их устройства; .rules/consumers.json — про потребителя хранится адрес и роль, но не объяснение, почему у него так. Держится чтением при приёмке: отличить ссылку от пересказа машинно нечем", "holdable": "not-yet", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "154": { "mechanism": "gate", "where": "scripts/check_bindings.py — ответ `active` + `none` без поля `why` отвергается до слияния, симметрично тому, как `why` давно обязателен при `rejected` и `not-applicable`. Гейт держит форму: что причина есть. Того, что она не пересказ самого правила, он не держит — это чтение при приёмке, и граница названа в самой записи", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "155": { "mechanism": "gate", "where": "scripts/check_templates.py — таблица в templates/README.md обязана назвать каждую заготовку и ответить, чем она применяется у самого каталога: адресом артефакта либо «нет: причина». ГРАНИЦА: что применение совпадает ПО СУЩЕСТВУ, гейт не судит — это приёмка", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "156": { "mechanism": "gate", "where": "scripts/check_attribution.py — трейлеры читаются функцией tail(): последний абзац принимается, только если ВСЕ его непустые строки имеют форму «Ключ: значение». Отвергаемый предмет прогоняется живым коммитом в tests/test_check_attribution.py, где тело объясняет атрибуцию словами", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "157": { "mechanism": "gate", "where": "scripts/aggregate_bindings.py — schema_lag(): версия ответа каждого потребителя сверяется с версией ОТВЕТА КАТАЛОГА, а не с константой в коде; отставание печатается разделом «правится не здесь». Там же сверяется templates/bindings.json: заготовка, отстав, рассылает устаревшую версию всем, кто подключается. И вторая половина, заведённая 7 сентября: scripts/aggregate_bindings.py::export_lag() сверяет ключ answers_to каждого потребителя с версией ВЫГРУЗКИ — той, которую он читает. До этого сверялся только номер самого ответа, а перечитывать ответы заставляет подъём другого контракта; два номера двигаются порознь. Замер: у грейдера прозой стояло 1.2 при нашей 1.5, и отставания не видела ни одна сторона. И ТРЕТЬЯ ПОЛОВИНА, ЗАВЕДЁННАЯ 8 СЕНТЯБРЯ: scripts/check_reread.py — сдвинулась подпись под перечитыванием, тело изменения обязано назвать размер обхода ЧИСЛОМ. Ответ этот гейт не называл, хотя AGENTS.md приписывает его правилу 157 прямо: связь была односторонней ровно у той записи, которая про одностороннюю сверку номеров и написана. Поймано ревью", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "158": { "mechanism": "gate", "where": "scripts/check_third_outcome.py — разбор исходников scripts/: у каждого оператора, отдающего код 2, сообщение обязано называть предмет подстановкой либо буквальным адресом (путь, имя файла, номер задачи, ссылка, длинный ключ, имя в верхнем регистре)", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "159": { "mechanism": "gate", "where": "scripts/check_verdict_order.py — разбором СИНТАКСИСА, а не поиском по тексту: в функции ищется накопитель (имя, которому присвоен список), затем цикл, который в него добавляет, затем `return` внутри этого цикла, ЧИТАЮЩИЙ накопитель. Возврат третьего исхода (`return 2` либо кортеж с двойкой первой) находкой не является и обязан быть ранним: продолжать перебор, не прочитав источник, значит копить находки о состоянии, которого не знаешь (039). Замер, на котором различие и построено: в scripts/ таких возвратов внутри накопительных циклов два, и оба именно третий исход — сборщика нет, сборщик отказал; поиск по слову «return» отверг бы оба. Просматриваются scripts/ и tests/: набор копит находки так же, как гейт. Приём взят у витрины — у неё это разбор кода с тем же вопросом (162). Набор — tests/test_check_verdict_order.py, одиннадцать случаев с обеих сторон. ГРАНИЦА: накопитель узнаётся по УПОТРЕБЛЕНИЮ — присвоен список, в него добавляют в цикле. Накопитель, собранный иначе (словарь, множество, счётчик строк), гейт не увидит, и это сказано вслух", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "160": { "mechanism": "gate", "where": "scripts/check_showcase.py — badges_in_tree(): артефакт витрины, вернувшийся в дерево общей ветки, отвергается; .gitignore накрывает .github/badges/*.json и *.svg, а при слиянии его не спрашивают — сторож стоит именно поэтому. Свежесть держит .github/workflows/badges.yml на каждый толчок в main. scripts/check_derived.py — вторая половина: сводка export/where.* в изменении не едет, у неё один писатель", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "161": { "mechanism": "gate", "where": "scripts/audit_catalogue.py — единица истории ВЫПУСК, а не отдельная запись о повороте: у каждого раздела «## vX.Y.0 · дата · чем он был» проверены четыре части (Контекст · Что вошло · Решения · Итог) и РАЗРЕШИМАЯ ссылка в «Что вошло»; там же окно живого документа и переезд вышедшего за него в архив. scripts/history_metrics.py — у каждого тега есть свой раздел, а `--add` переименовывает «## Не выпущено · X» в номер и дату при выпуске: иначе у раздела появилась бы та же договорённость «не забыть», на которой сломалась строка метрик. Первая редакция гейта держала свой жанр «## Поворотный момент», взятый у соседа выводом, а не формой, и цена измерена: из трёх выпусков раздел был у одного (162). ГРАНИЦА: отвергнутую альтернативу гейт не ищет, и это замер, а не лень — в истории соседа шесть разделов «Решения» из двенадцати не содержат ни одного слова, по которому отказ узнаётся машинно; словарь дал бы красное на верной работе (051), и вопрос остаётся приёмке", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "162": { "mechanism": "gate", "where": "scripts/check_bindings.py — метрика «НИЧЕМ» тут же называет, сколько из очереди уже решено у соседа, у кого именно и где смотреть адрес; вопрос задаётся в тот момент, когда выбирают, что строить, а не отдельной командой, которую надо помнить. Свёртка одна на весь каталог: ею же собирается раздел «У соседей это уже решено» во входящие потребителю (scripts/sync_inbox.py). ГРАНИЦА: подошёл ли чужой приём, гейт не судит — стеки разные, и это метрика с адресами, а не отказ; красное на «у соседа есть, а у тебя нет» приучало бы пропускать красное (051)", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "163": { "mechanism": "gate", "where": "CONNECT.md — единственный список того, что каталог отдаёт наружу: команда подключения, действие, скрипты потребителя; README.md и START.md ведут туда, а не заводят второй. scripts/check_connect.py держит связь в обе стороны: скрипт с маркером «ОТДАЁТСЯ ПОТРЕБИТЕЛЮ» обязан быть назван на входе, а названный инструмент — существовать (действие проверяется своим action.yml). scripts/version.py --pin закрепляет тег в примере подключения, чтобы вход не учил устаревшему (035). ГРАНИЦА: что скрипт вообще отдаётся наружу, гейт не судит — это решение автора, объявленное маркером; догадка по словам понижена до предупреждения, потому что ложные отказы приучают пропускать красное", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "164": { "mechanism": "gate", "where": "docs/VERSIONING.md § Номеров здесь несколько, и они двигаются врозь — таблица называет КАЖДЫЙ номер, что он версионирует, кто на него смотрит и когда он двигается; в самом контракте export/README.md оговорка стоит В ТОЧКЕ ЧТЕНИЯ, строкой рядом с числом, а не в отдельном документе. Держат это два гейта. scripts/check_prose.py::schema_unnamed — номер формата, объявленный в файле и НЕ названный в политике версий, это находка: читатель прочтёт его как версию каталога, и ровно так и был прочитан. scripts/aggregate_bindings.py::schema_findings — расхождение версий у потребителей и у собственной заготовки называется на КАЖДОМ изменении, а не только в ночном прогоне: данных хватает на диске, а собранное и никому не доставленное адресата не имеет (142). Чужое печатается без отказа — файл чужой (051); своё, заготовка, отказ. ИЗМЕРЕНО: из трёх подключённых ответов с контрактом совпал один; у одного стоял номер схемы ВЫГРУЗКИ в поле схемы ОТВЕТА, у другого — на версию ниже, и ни то ни другое ни один прогон на изменении не называл. ГРАНИЦА: переименование самого ключа (`export_schema` вместо `schema`) правило допускает как лучший ответ, но здесь не сделано — ключ читают четыре потребителя, и смена имени это подъём версии контракта, то есть отдельная работа с их стороны (157)", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "165": { "mechanism": "gate", "where": "scripts/check_subprocess.py — разбор дерева: вызов git, отдающий список путей, обязан нести -z либо -c core.quotePath=false; охват печатают сами гейты числом просмотренного (check_own_name: 566 файлов, check_prose: 458 документов)", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "166": { "mechanism": "gate", "where": "scripts/check_links.py::LINK_RE — ссылка ищется разметкой, а не подстрокой адреса, и другие скрипты берут это выражение, а не переписывают своё", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "167": { "mechanism": "gate", "where": "scripts/check_workflows.py — имена ключей env: и присваиваний в шагах прогонов обязаны быть ASCII; проза, имена шагов и сообщения не трогаются", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "168": { "mechanism": "gate", "where": ".github/workflows/ci.yml — работа catalogue, единственное обязательное имя; scripts/merge_ready.py --required catalogue спрашивает её ПОИМЁННО, а не «что-то зелёное»", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "169": { "mechanism": "gate", "where": "scripts/check_schedules.py — роль расписания объявлена в .rules/schedules.json: main либо safety-net. У страховки обязателен замер срабатываний, а страховка со сроком короче промежутка расписания отвергается сразу", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "170": { "mechanism": "gate", "where": "scripts/check_forgeries.py — у набора, подменяющего шов наружу (ghcli.run, gh, gh_json, fetch_*, urlopen, остаток), обязано стоять объявление источника: слово-маркер И АДРЕС в одной строке — команда площадки в обратных кавычках, ссылка, путь или номер задачи. Список швов разрешительный, с причиной у каждой строки ДОПОЛНЕНО 17.09 ПРИ ВЛИВАНИИ ПРЕДЛОЖЕНИЯ СОСЕДА ПРО ОТРАВЛЕННЫЙ ВХОД. Гейт держит ПРЕДПОСЫЛКУ (у подделки назван источник) и не держит ПОЛНОТУ перечня подделок — а перечень швов у него рукописный, одиннадцать имён, и его собственная докстрока это признаёт: «запретительный завтра пропустит новый». ЗАМЕР ОТРАВОЙ, С ОБОИХ КОНЦОВ (140): сокет отравлен и в прогоне, и в каждом дочернем процессе через sitecustomize — наружу не ходит ни один тест; канал площадки проверен и падающей заглушкой gh, и успешной, отдающей пустой ответ, — ни одна не меняет ни одного теста, то есть зелёное не держится ни на пропущенной подделке, ни на отсутствии gh в окне. Перечень сегодня сходится с деревом (subprocess.run, urlopen, check_output покрыты) — и сходится потому, что ПРОВЕРЕН, а не потому, что перечню можно верить. МЕХАНИЗМ ПОЛНОТЫ НЕ ЗАВОДИТСЯ: отрава как постоянный шаг прогона меняет устройство набора, и здесь такое предлагают владельцу прежде, чем делать. ИНСТРУМЕНТ ЗАМЕРА ЛЕЖИТ В ДЕРЕВЕ: scripts/measure_fake_coverage.py ставит все три захода (отравленный сокет с sitecustomize в дочерних процессах, падающая заглушка gh, успешная заглушка gh) и печатает вердикт. В .github/workflows/ci.yml НЕ введён намеренно, и потому прогоном не держится — это одноразовая проба по требованию, а не шаг. Краснеть умеет, проверено полу-откатом: без исключения петли заход сокета отказывает на tests/test_ghcli.py и tests/test_sync_inbox.py. Находка внешнего взгляда на #551: без такого артефакта числа в записи были подтверждены пересказом, а не прогоном (139).", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "171": { "mechanism": "gate", "where": "scripts/check_charter.py — объявленные гейты сверяются с шагами .github/workflows/ci.yml ТОГО ЖЕ дерева, а не с последним прогоном общей ветки", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "172": { "mechanism": "gate", "where": "scripts/check_own_name.py — своё имя берётся у адреса origin, а не из реестра, и старое имя, оставшееся в дереве, краснеет", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "173": { "mechanism": "gate", "where": "scripts/pr_body.py — три ответа различаются машинно, «Part of #NNN» требует названного остатка; scripts/check_task_state.py плюс .github/workflows/task-state.yml — состояние задачи спрашивается ПОСЛЕ слияния, замечание пишется в саму задачу", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "174": { "mechanism": "gate", "where": "scripts/build_facts.py — сводные факты каталога о себе публикуются в .github/badges/facts.json на ветке `badges`, по контракту витрины: schema строкой, generated_at обязателен, неизмеренный раздел ОТСУТСТВУЕТ, а не выставлен в ноль; scripts/build_rules_index.py — выгрузка export/rules.json собирается издателем и читается потребителями (витрина берёт числа оттуда, а не считает их клоном); scripts/aggregate_bindings.py — то же для сводки export/where.json", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "175": { "mechanism": "gate", "where": "scripts/check_showcase.py — ответ витрины «предмета нет» сверяется с деревом; scripts/check_bindings.py — то же для ответа «не применимо» о самом правиле: реестр проб плюс печать числа непроверенных утверждений", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "176": { "mechanism": "gate", "where": "scripts/check_subprocess.py — разбор дерева исходников: текстовый вызов subprocess без encoding= отвергается; включают текстовый режим любые из text, universal_newlines, errors", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "177": { "mechanism": "gate", "where": "scripts/check_bindings.py::tier_zero — каталог спрашивает ступень 0 с СЕБЯ: три числа печатаются всегда (без ответа, не рассмотрено, действует но держится ничем) плюс сверка своей схемы ответа с contracts.bindings собственной выгрузки. Потребителю то же отдаётся разделом «Ступень 0» в scripts/sync_inbox.py. Печать, а не отказ: правило прямо говорит, что заслон называет долг и работу не запрещает — остановить её механизм не может. Четвёртое число — «отложено до появления предмета»: правило без механизма, у которого названо СОБЫТИЕ постройки и ИЗМЕРЕНО отсутствие предмета (поле awaiting), долгом не считается, но и не исчезает из виду. Без этого различия 177 неисполнимо: 146 запрещает строить гейт на нулевом предмете, и три таких правила держали бы счётчик ненулевым вечно — очередь не открылась бы никогда. Поле держится тем же гейтом: awaiting без числа отвергается, иначе припиской «построим потом» долг обнулялся бы даром", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "178": { "mechanism": "gate", "where": "scripts/check_bindings.py::solved_next_door — единственное место, где каталог сводит два числа об одной величине (свои ответы и сводку соседей), печатает их ПОРОЗНЬ и НАЗВАННЫМИ: «правил N» и «записей M», когда M ≠ N", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "179": { "mechanism": "gate", "where": "scripts/check_workflows.py::cancelling_groups — у всякой группы concurrency с cancel-in-progress: true в имени обязана стоять голова; разбор по отступу, а не только по верхнему уровню, потому что группу объявляют и внутри работы. Разрешение объявлено списком CANCELS_BY_STATE с причиной у каждой строки, и стоит в нём один .github/workflows/off-prefix.yml: у него предмет — состояние, а не коммит. СОСТАВ СПИСКА ДЕРЖИТ СЛУЧАЙ tests/test_check_workflows.py::test_zapis_o_spiske_sovpadaet_so_spiskom — прежде запись о списке пережила сам список и осталась зелёной: сверка адреса видит существование файла, а не строку в словаре", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "180": { "mechanism": "gate", "where": "scripts/check_subprocess.py::resolve_calls — предмет разрешается по импортам разбираемого файла: имена, под которыми доступен модуль, и отображение «имя в файле → исходное имя функции». Обе стороны стоят случаями в tests/test_check_subprocess.py", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "181": { "mechanism": "gate", "where": "scripts/check_exclusive.py + .rules/exclusives.json — исключительные утверждения объявленных документов ранжируются по близости предмета, и у каждой из верхних пар обязан быть вердикт: разные предметы · согласны · уточняет · ПРОТИВОРЕЧИЕ", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "182": { "mechanism": "gate", "where": "scripts/check_bindings.py — у каждого ответа `mechanism: none` обязано стоять поле `machine_half`: что следует из данных целиком и почему оно не построено", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "183": { "mechanism": "gate", "where": "scripts/check_transport.py — утверждение «ходим по REST» сверяется с тем, каким API идёт вызов, и законное исключение называется поимённо в .rules/transport.json; scripts/check_bindings.py — поле where сверяется с деревом (несуществующий путь — отказ) и скрипт, названный механизмом, обязан объявить это правило своим; scripts/check_gates.py — свод и документ участника называют те же гейты, что стоят в конвейере", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "184": { "mechanism": "gate", "where": "scripts/check_bindings.py — скрипт, объявивший правило своим, при ответе «неприменимо» или «отвергнуто» по этому правилу даёт находку: механизм не может держать то, чего у нас нет. Это не всё требование, а его машинная половина — та, где у ответа есть свидетель в дереве", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "185": { "mechanism": "gate", "where": "scripts/build_rules_index.py — следы обеих законных форм собираются в export.rules[].trails и сверяются между русским и английским деревом: запись, где раздел назван по-разному, сборку не проходит. Именно этой сверкой найдены 68 переведённых адресов", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "186": { "mechanism": "gate", "where": "tests/test_link_trails.py::test_kod_ne_menyaet_smysla_mezhdu_rezhimami — один вход, два режима, разные ответы: сухой отвечает «есть находки», записывающий «чисто». Держит это scripts/link_trails.py, где код считается по ОСТАТКУ после записи, а исходы объявлены порознь для двух режимов", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "187": { "mechanism": "none", "where": ".github/workflows/ci.yml — у работы `catalogue`, единственной обязательной, условия нет вовсе, и запись она оставляет одну. Условие есть у .github/workflows/review.yml, но этот канал необязателен и слияние не держит (084). Машинного сторожа над этим нет, причина в why", "holdable": "conditional", "awaiting": "вторая обязательная проверка либо превращение review в обязательную. Замер 8 сентября: обязательная проверка 1 (catalogue), условий у работ в 13 прогонах — 2, и оба у необязательных каналов; записей с именем catalogue на голове по 1 у каждого из 4 последних изменений. Предмета сейчас нет, и гейт был бы зелёным с первого дня. Строить тем же изменением, которым меняется список обязательных проверок", "analysed": "2026-09-17", "decided": "" }, "189": { "mechanism": "gate", "where": ".claude/hooks/push_guard.py — сторож толчка сверяет имя ветки в команде с текущей, и ветка с чужим именем не уезжает; приставку `agent/` требует .github/workflows/agent-pr.yml, где она переключатель: без неё изменение не откроется вовсе (003). Слаг машинно не судится — половина названа в why", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "190": { "mechanism": "gate", "where": ".rules/consumers.json — хранится АДРЕС ответа потребителя, а не его форма: где у него лежит показатель, каталог не знает и знать не должен. scripts/aggregate_bindings.py при недоступном ответе даёт находку с адресом и досчитывает остальных — один молчащий потребитель не роняет сводку по пятерым. Замер 8 сентября: 2 ответа из 5 не прочитались (429 от raw), сводка собралась по трём и назвала двоих", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "192": { "mechanism": "document", "where": "CLAUDE.md § «Подпись и открытие изменения» — там записаны ДВА замера о поведении площадки вместе с условиями, при которых они верны: «решает учётная запись, ОТКРЫВШАЯ изменение» (замер по 6 изменениям, переподписка коммитов не помогла) и «actions/checkout прописывает extraheader, и он идёт с каждым запросом» (замер 28 августа: толчок ушёл от имени бота при полностью верном URL). Оба названы вместе с исполнителем действия, а не как свойство площадки вообще — именно этого требует правило", "holdable": "no", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "195": { "mechanism": "gate", "where": "scripts/check_narrowing.py — машинная половина: убыль символов символьного класса и элементов литерального множества по изменению ЦЕЛИКОМ, и при ней требуется строка «Соседи:» во фрагменте журнала. Гейт проверен на СВОЁМ инциденте, а не на пустоте (146): на диапазоне изменения #415 он краснеет — «символьный класс перестал принимать: '*' '_'», то есть ровно на той правке, которая уронила буллет. Суждение остаётся человеку: держится ЗАДАННЫЙ вопрос, а не ответ (182)", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "196": { "mechanism": "pipeline", "where": ".github/workflows/badges.yml — шаг «ссылки витрины ведут в файлы, которые на ветке есть», и его скрипт scripts/check_badges_published.py. Проверка стоит ПОСЛЕ отрисовки и после публикации: оба предмета лежат на разных ветках, и прогон снимает `badges` в отдельный каталог, а документы читает в общей. Предмет живой и измерен по всему дереву С ДЕКОДИРОВАНИЕМ АДРЕСОВ: ДВАДЦАТЬ ОДНА ссылка на ветку badges в четырёх документах — README.md (9), README.en.md (9), export/README.md (2), docs/VERSIONING.md (1); столько же насчитывает и сам гейт. Число уточнялось ДВАЖДЫ, и оба раза внешним взглядом: сперва считали только README.md (5), затем четыре документа сырым поиском (13), и лишь третий счёт увидел бейджи `img.shields.io/endpoint?url=…`, где адрес URL-КОДИРОВАН и подстрокой не находится вовсе", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "197": { "mechanism": "gate", "where": "tests/test_sync_inbox.py — каждый опубликованный номер контракта отнесён либо к сверяемым у потребителя (СВОИ_КОНТРАКТЫ в scripts/sync_inbox.py), либо к описывающим файлы каталога (ЧУЖОМУ_НЕ_НУЖНЫ); scripts/build_rules_index.py — номера контрактов в документе переписывает сборка, а не рука", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "199": { "mechanism": "none", "where": ".github/workflows/automerge.yml — шаг «состояние изменения» перечитывает состояние опросом (`gh api .../pulls/$PR` и `.../check-runs`), а не верит разбудившему событию; группа `automerge-<номер>` стоит с `cancel-in-progress: false`. Машинного сторожа над этим нет, причина в why", "holdable": "conditional", "awaiting": "второй прогон, который будится событием и стоит в группе с `cancel-in-progress: false`. Замер 10 сентября: таких прогонов у каталога 1 — automerge.yml, — и чтение состояния внутри работы в нём уже есть (2 опроса: состояние изменения и записи проверок на голове). Гейт был бы зелёным с первого дня и держал бы единственный случай, который и так верен (146). Строить тем же изменением, которым появится второй такой прогон", "analysed": "2026-09-17", "decided": "" }, "200": { "mechanism": "none", "where": ".github/badges/ — четыре значка, и каждый двигается: `rules-ru`/`rules-en` растут с каждой записью (196 на 10 сентября), `coverage` ходит в обе стороны (66%), `.github/badges/facts.json` несёт числа тестов и связей. Машинного сторожа над подписью значка нет, причина в why", "holdable": "conditional", "awaiting": "первый значок, у которого предмет движения назвать не удастся. Замер 10 сентября: значков 4, у всех четырёх событие движения называется вслух — rules-ru и rules-en двигает новая запись (196 на сегодня, +2 за смену), coverage двигает прогон тестов (66%), `.github/badges/facts.json` — числа тестов и связей. Дефекта 0 из 4, и гейт держал бы пустоту (146). Строить тем же изменением, которым заводится пятый значок", "analysed": "2026-09-17", "decided": "" }, "201": { "mechanism": "gate", "where": "scripts/build_rules_index.py — `claim_of` и `applies_of` кладут в выгрузку букву требования и раздел о границе целиком; tests/test_build_rules_index.py требует оба поля у КАЖДОЙ записи, иначе потребитель гадает, где повезло", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "202": { "mechanism": "gate", "where": ".claude/hooks/push_guard.py — `ветка_воскресает`: перед толчком сверяет свою ссылку `refs/remotes/origin/<ветка>` с ответом площадки, и толчок в удалённую при слиянии ветку не уезжает вовсе. Предмет проверки задаёт `везёт_содержимое`: чистое удаление ссылки (`--delete`, `-d`, `:имя`) коммитов не переносит и пропускается, смешанная форма `git push origin своя :чужая` остаётся толчком содержимого, а адресат явной ссылки разворачивается через `цель` — `HEAD:другая` текущей ветки не касается и отказа не получает (051). Строку разбирает одна функция `толчки`, её ответу задаются все три вопроса файла", "holdable": "", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "204": { "mechanism": "document", "where": "AGENTS.md § Ссылка на правило — порядок назван прямо: открыть цитируемое правило, прочитать «Не работает», и только потом ставить номер; там же три законных ответа — основание, аналогия, «держится ничем»", "holdable": "no", "awaiting": "", "analysed": "2026-09-17", "decided": "" }, "205": { "mechanism": "none", "where": "ответы «неприменимо» в этом же файле — поле why. Машинного нет, и причина названа в why", "holdable": "conditional", "awaiting": "24 записи «неприменимо» без предиката РАЗОБРАНЫ НАДВОЕ чтением, и обещание сужено по собственной границе правила. ОДИННАДЦАТЬ — про СОСТОЯНИЕ дерева, и они получат предикат: 015, 066, 069, 070, 073, 081, 094, 103, 110, 115, 203 (у 203 замер уже стоит числами, не хватает адреса). ТРИНАДЦАТЬ — про ПРИРОДУ проекта, и у них предикат вырождается в событие законно: 007, 019, 032, 036, 038, 048, 059, 076, 078, 083, 087, 095, 112 — каталог не запускаемый продукт, окнами управляет хост, пользователя с установленным пакетом нет. Гейт после этого принимает ДВЕ формы — предикат либо НАЗВАННОЕ событие с причиной — и краснеет на записи, не несущей ни одной", "analysed": "2026-09-17", "decided": "2026-09-17" }, "206": { "mechanism": "gate", "where": ".claude/hooks/push_guard.py — ПЕРВЫЙ ПРЕДМЕТ ПРАВИЛА В НАШЕМ ДЕРЕВЕ, И ОН ПОЧИНЕН ДВАЖДЫ (#533, затем находки обзора на #553). Разбор строки передан библиотеке: `shlex` с punctuation_chars знает кавычки и экранирование и отделяет операторы словами; своим остался только перечень ведущих слов оболочки (`без_обёртки`). ЗАМЕР 17.09: проверено 13 форм, слепых 0 — семь обёрнутых конструкций, две с экранированной кавычкой, ветка `case`, многострочная команда, перенаправление, кавычки вокруг имени ветки. ПОЛНОТА ПЕРЕЧНЯ — ЗАМЕР С ДАТОЙ, А НЕ СВОЙСТВО, и это предмет самого 206: первая починка объявила «слепых форм из семи ноль», и обзор тут же нашёл восьмую и девятую. Обратная сторона держится тем же набором: текст в кавычках, `echo do git push`, `git stash push <пути>`, тело heredoc остаются текстом.", "holdable": "no", "awaiting": "", "analysed": "2026-09-17", "decided": "2026-09-17" }, "207": { "mechanism": "none", "where": "заходы перечитывания этого же реестра — поле analysed у каждой записи. Машинного нет, и причина названа в why", "holdable": "not-yet", "awaiting": "заход перечитывания станет объектом с границами: сегодня их ноль, есть только 206 дат analysed у отдельных записей", "analysed": "2026-09-17", "decided": "2026-09-17" }, "208": { "mechanism": "none", "where": "механизма нет, и предмет для него назван точно: у scripts/preflight.py должен появиться заход, который сам толкает ветку и зовётся ТОЛЬКО после зелёного. Сегодня `grep -c push scripts/preflight.py` даёт 0 — вердикт печатается, толчок набирается отдельной командой тем же окном. Причина отсутствия названа в why", "holdable": "not-yet", "awaiting": "заход `--push` у scripts/preflight.py: сегодня упоминаний толчка в нём 0, у соседа механизм стоит и проверен откатом с 2 концов", "analysed": "2026-09-17", "decided": "2026-09-17" } }, "trails": 55, "born": 34 }, { "repo": "ArtVsMark/Stepik-Python-Grader", "role": "потребитель", "schema": "1.2", "answers_to": "1.5", "state": "подключён", "read_at": "2026-09-24", "answered": 195, "by_status": { "active": 192, "not-applicable": 3 }, "by_mechanism": { "gate": 86, "pipeline": 39, "document": 67 }, "rules": { "001": "active", "002": "active", "003": "active", "004": "active", "005": "active", "006": "active", "007": "active", "008": "not-applicable", "009": "active", "010": "active", "011": "active", "012": "active", "013": "active", "014": "active", "015": "active", "016": "active", "017": "active", "018": "active", "019": "active", "020": "active", "021": "active", "022": "active", "023": "active", "024": "active", "025": "active", "026": "active", "027": "active", "028": "active", "029": "active", "030": "active", "031": "active", "032": "active", "033": "active", "034": "active", "035": "active", "036": "active", "037": "active", "038": "active", "039": "active", "040": "active", "041": "active", "042": "active", "043": "active", "044": "active", "045": "active", "046": "active", "047": "active", "048": "not-applicable", "049": "active", "050": "active", "051": "active", "052": "active", "053": "active", "054": "active", "055": "active", "056": "active", "057": "active", "058": "active", "059": "active", "060": "active", "061": "active", "062": "active", "063": "active", "064": "active", "065": "active", "066": "active", "067": "active", "068": "active", "069": "active", "070": "active", "071": "active", "072": "active", "073": "active", "074": "active", "075": "active", "076": "active", "077": "active", "078": "active", "079": "not-applicable", "080": "active", "081": "active", "082": "active", "083": "active", "084": "active", "085": "active", "086": "active", "087": "active", "088": "active", "089": "active", "090": "active", "091": "active", "092": "active", "093": "active", "094": "active", "095": "active", "096": "active", "097": "active", "098": "active", "099": "active", "100": "active", "101": "active", "102": "active", "103": "active", "104": "active", "105": "active", "106": "active", "107": "active", "108": "active", "109": "active", "110": "active", "111": "active", "112": "active", "113": "active", "114": "active", "115": "active", "116": "active", "117": "active", "118": "active", "119": "active", "120": "active", "121": "active", "122": "active", "123": "active", "124": "active", "125": "active", "126": "active", "127": "active", "128": "active", "129": "active", "130": "active", "131": "active", "132": "active", "133": "active", "134": "active", "135": "active", "136": "active", "137": "active", "138": "active", "139": "active", "140": "active", "141": "active", "142": "active", "144": "active", "145": "active", "146": "active", "147": "active", "148": "active", "149": "active", "150": "active", "151": "active", "152": "active", "153": "active", "154": "active", "155": "active", "156": "active", "157": "active", "158": "active", "159": "active", "160": "active", "161": "active", "162": "active", "163": "active", "164": "active", "165": "active", "166": "active", "167": "active", "168": "active", "169": "active", "170": "active", "171": "active", "172": "active", "173": "active", "174": "active", "175": "active", "176": "active", "177": "active", "178": "active", "179": "active", "180": "active", "181": "active", "182": "active", "183": "active", "184": "active", "185": "active", "186": "active", "187": "active", "188": "active", "189": "active", "190": "active", "191": "active", "192": "active", "193": "active", "194": "active", "195": "active", "196": "active" }, "holds": { "001": { "mechanism": "gate", "where": ".claude/settings.json запрещает GitHub-сервер MCP целиком, форму запрета стережёт scripts/check_mcp_permissions.py (шаг CI и pre-commit); весь транспорт идёт подкомандами scripts/gh_rest.py по REST", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "002": { "mechanism": "gate", "where": "scripts/check_rule_bindings.py — формат ответа на каждый PR, полнота против каталога и метрика «не обеспечено ничем» ночным прогоном; сам этот файл и есть ответ", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "003": { "mechanism": "pipeline", "where": "CLAUDE.md § Рабочая ветка: префикс agent/ — переключатель, а не стиль (его подхватывает .github/workflows/agent-pr.yml и открывает PR от имени владельца); проверяется при создании ветки, гейта на имя нет", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "004": { "mechanism": "gate", "where": "scripts/move_merge_queue.py помечает конфликтный PR меткой needs-rebase и идёт дальше, оставляя прогон зелёным; закреплено tests/test_move_merge_queue.py", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "005": { "mechanism": "gate", "where": "scripts/check_docs_guardrails.py — числа тестов, покрытия и глоссария живут бейджами, а не текстом; CLAUDE.md § Метрики: «числом здесь не фиксируется»", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "006": { "mechanism": "document", "where": "CLAUDE.md § Два окна: «Окно живёт 3–5 дней, дальше перезапуск — обязательно»; замер 764 против 81 прочитанного токена на токен выхода и шаблон эстафеты — docs/agent/environments.md", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "007": { "mechanism": "document", "where": "docs/agent/dispatcher.md — окно, ждущее разрешения, узнаётся по pending_action в реестре сессий; порог получаса, решение принимает диспетчер", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "009": { "mechanism": "gate", "where": "scripts/version.py считает PATCH по номерам PR и уникализирует их множеством (issue #1042), сверяет scripts/check_version_consistency.py", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "010": { "mechanism": "document", "where": "CLAUDE.md § Гейты: пустой список проверок — «CI не стартовал», а не «зелено»; проверяет scripts/check_pr_ready.py, который запускает окно перед мержем", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "011": { "mechanism": "document", "where": "CLAUDE.md § Гейты: авто-мерж вместо опроса статусов, «цикл опроса на GraphQL запрещён как класс», интервал опроса 45–60 с; разбор — docs/agent/preflight.md", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "012": { "mechanism": "gate", "where": ".claude/hooks/pre_tool_use.py — пуш в ветку, отличную от текущей, отвергается до вызова git; CI такое поймать не может, он видит артефакт, а не действие", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "013": { "mechanism": "gate", "where": ".claude/hooks/pre_tool_use.py — heredoc без кавычек в делимитере с экранированием в теле отвергается до записи файла", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "014": { "mechanism": "document", "where": "CONTRIBUTING.md § Когда дефект считается исправленным: краснота доказывается полу-откатом — новые имена оставить, убрать только поведение; откат всего src/ даёт ImportError", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "015": { "mechanism": "document", "where": "CLAUDE.md § Мультиагентный режим и docs/agent/multiagent.md § Агенты возвращают данные — файлы правит хост (agent(schema=...) → детерминированный applier)", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "016": { "mechanism": "gate", "where": "scripts/check_truncation_marks.py — функция, режущая по пределу-константе, обязана оставить признак обрыва (флаг, многоточие, полную длину рядом); предел, заданный параметром, объявлен в контракте и молчанием не является", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "017": { "mechanism": "pipeline", "where": "scripts/gh_rest.py — остаток квоты читается из заголовков каждого ответа (бесплатно) и печатается подкомандой rate, которая сама квоту не тратит; разбор начинается с факта, а не с гипотезы", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "018": { "mechanism": "document", "where": "CLAUDE.md § Два окна и docs/agent/environments.md § Облако проверяет узлы, локальное окно — цепочку; планируя срез аудита, окно проставляется сразу", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "019": { "mechanism": "document", "where": "docs/agent/multiagent.md § Аудит планируется от поверхностей продукта, а не от файлов: первый шаг планирования — инвентарь поверхностей", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "020": { "mechanism": "document", "where": "docs/agent/multiagent.md § Рестарт только дельты: после сбоя добирается недостающее, а не повторяется вся волна", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "021": { "mechanism": "gate", "where": "docs/ разложена по направлениям use/dev/agent/archive, полноту индексов и разбиение проверяет scripts/check_docs_guardrails.py; две документные роли разделены по аудитории — docs/agent/roles.md", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "022": { "mechanism": "document", "where": "CLAUDE.md § Источники истины: канонический документ на тему один, остальные ссылаются; компактные триггеры не дублируют канон", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "023": { "mechanism": "gate", "where": "scripts/check_docs_guardrails.py — README держится в бюджете строк, остальное по ссылке", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "024": { "mechanism": "document", "where": "CLAUDE.md § Обновление CHANGELOG.md / HISTORY.md — журнал работ живёт в HISTORY.md и docs/archive/, действующие документы описывают настоящее; CLAUDE.md § Открытая работа — «Чего там не бывает» про очередь работ", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "025": { "mechanism": "gate", "where": "scripts/check_docs_guardrails.py — issue-tail policy: номер задачи допустим в журнале и запрещён в объясняющих документах", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "026": { "mechanism": "pipeline", "where": "CLAUDE.md § Открытая работа: отклонённая находка фиксируется с причиной — молча удалённая вернётся следующим аудитом; реестр сверяет scripts/check_audit_registry.py", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "027": { "mechanism": "pipeline", "where": "scripts/nightly_checks.py — чистый обход пишет в задачу «Находок нет» с датой и числом проверок, а не оставляет пустое тело; docs/audit/README.md и docs/agent/claude-handoff.md объявляют пустоту словами", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "028": { "mechanism": "pipeline", "where": "scripts/check_issue_checklists.py — комплексный issue от трёх находок ведёт чек-лист с исходом каждой", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "029": { "mechanism": "document", "where": "CLAUDE.md: блок в главном файле — компактный триггер, детали канонически живут в docs/ и не дублируются", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "030": { "mechanism": "gate", "where": "changelog.d/ — запись приезжает файлом вместе с изменением, сборка при релизе (scripts/collect_changelog.py); наличие записи требует scripts/preflight.py", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "031": { "mechanism": "document", "where": "CLAUDE.md § Мультиагентный режим (волнами по 5) и docs/agent/multiagent.md; прецеденты залпов записаны там же числами", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "032": { "mechanism": "document", "where": "docs/agent/roles.md: если предмет роли наблюдаем в работающем продукте, роль обязана его запустить — вывод чтением кода считается неполным", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "033": { "mechanism": "document", "where": "docs/agent/multiagent.md § арифметика интервала между стартами; CLAUDE.md § Гейты — порог остатка квоты и интервал опроса статусов", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "034": { "mechanism": "document", "where": "docs/agent/multiagent.md § Роль дробится на 2–3 агента; CLAUDE.md § Мультиагентный режим — явный список файлов и лимиты объёма ответа", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "035": { "mechanism": "gate", "where": "версия динамическая из git-тегов (setuptools-scm), дрейф ловит scripts/check_version_consistency.py", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "036": { "mechanism": "document", "where": "docs/agent/environments.md § Фаза 0 — слепок реальности до облачных волн и подтверждение в конце; docs/agent/multiagent.md § фазы аудита", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "037": { "mechanism": "document", "where": "docs/agent/environments.md: находка с фикстуры — гипотеза, уходит в список фазы L; то же в multiagent.md и roles.md", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "038": { "mechanism": "document", "where": "CLAUDE.md § Два окна: имя окна начинается с окружения ([WEB]/[LOCAL]/[CLI]), метка ставится при открытии; канон — docs/agent/environments.md", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "039": { "mechanism": "gate", "where": "scripts/check_three_outcomes.py — скрипт, ходящий в GitHub, обязан отличать отказ источника от результата (RateLimited → EXIT_WAIT, GitHubError → код 2); объявленный долг перечислен в KNOWN_DEBT с причиной", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "040": { "mechanism": "gate", "where": ".github/workflows/ci.yml (job docs-guardrails) и .pre-commit-config.yaml зовут scripts/skip_inventory.py --summary: пропуск без названной причины даёт ненулевой выход. Скрипт был написан гейтом и полтора месяца не запускался ничем (#1400)", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "041": { "mechanism": "document", "where": "два бейджа покрытия — single-OS и cross-OS combined: одного правдивого числа не существует, потому что три backend'а песочницы ОС-специфичны (CLAUDE.md § Метрики)", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "042": { "mechanism": "gate", "where": "scripts/check_adr_records.py", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "043": { "mechanism": "gate", "where": "scripts/check_adr_records.py", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "044": { "mechanism": "document", "where": "docs/agent/roles.md: оценка 🔍 Аудитора едет как заявка и проходит через ⚖️ Верификатора с установкой «опровергай по умолчанию» — по находке не работают, пока её премиса не проверена", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "045": { "mechanism": "gate", "where": "недоступный backend песочницы — parser.error, а не молчаливый откат на LocalRunner (CLAUDE.md § Архитектурные инварианты, п. 4); проверяют tests/test_sandbox_runner.py", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "046": { "mechanism": "document", "where": "SECURITY.md § Гарантии по ОС: таблица асимметрии, а не выравнивание на бумаге — что даёт bwrap, seatbelt и Job Object, названо порознь", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "047": { "mechanism": "document", "where": "CLAUDE.md § Гейты: смена правил транспорта требует перезапуска активных окон — настройки читаются при старте сессии", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "049": { "mechanism": "pipeline", "where": "scripts/gh_rest.py queue вычисляет очередь из состояния API и печатает обоснование; реестра нет намеренно — хранимое состояние разъезжается между окнами", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "050": { "mechanism": "gate", "where": "храповики BUDGET в scripts/check_declared_outcomes.py и UNHELD_BUDGET в scripts/check_rule_bindings.py: число опускается починкой, тест сверяет его с реальностью", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "051": { "mechanism": "pipeline", "where": "scripts/preflight.py — неблокирующий шаг «кто ещё трогает те же файлы» зовёт scripts/check_work_overlap.py: пересечение считается по git diff --name-only и показывается само. Блокировать нельзя — пересечение штатно, а не нарушение (docs/agent/preflight.md)", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "052": { "mechanism": "pipeline", "where": "scripts/move_merge_queue.py обновляет только голову очереди — из main остальные не двигаются (CLAUDE.md § Гейты: N против N(N−1)/2)", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "053": { "mechanism": "pipeline", "where": "scripts/gh_rest.py queue вычисляет очередь из состояния API и печатает обоснование (приоритет метки, общий файл, готовность), а не берёт из хранимого реестра", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "054": { "mechanism": "document", "where": "docs/agent/course-walkthrough.md: на шаге сбора ничего не анализируется — прогнать, записать сырое; разбор на границе главы", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "055": { "mechanism": "document", "where": "docs/agent/course-walkthrough.md § Внешний судья вердикта: свои ожидания в каталоге мутаций не доказывают ничего, сверка идёт со Stepik; каталог мутаций — docs/dev/corpus.md", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "056": { "mechanism": "document", "where": "docs/dev/result-contract.md § Вердикты (семантика) и docs/agent/preflight.md: пустой список проверок означает «CI не стартовал», а не «зелено» — у сигнала записано и то, чего он не означает", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "057": { "mechanism": "gate", "where": ".rules/bindings.json — правило без механизма записано со статусом active и mechanism none, а не выброшено и не объявлено очевидным; поле where объясняет, почему гейта нет, а scripts/check_rule_bindings.py считает такие правила метрикой, которая обязана уменьшаться", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "058": { "mechanism": "pipeline", "where": "scripts/gh_rest.py — стоп-кран по остатку: ниже DEFAULT_QUOTA_FLOOR (600, порог меняется GH_REST_QUOTA_FLOOR) операция не начинается, код возврата EXIT_WAIT «ждать», а не повтор; исчерпанный лимит поднимает RateLimited со временем сброса", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "059": { "mechanism": "document", "where": "docs/agent/preflight.md § Маршрут при исчерпании лимитов: что недоступно, что работает и чем занять время до сброса квоты", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "060": { "mechanism": "document", "where": "docs/agent/multiagent.md § Разбор после КАЖДОЙ волны — по двум осям, и качество важнее механики", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "061": { "mechanism": "document", "where": "docs/agent/multiagent.md § Прогонным агентам — запреты окружения прямо в промпте", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "062": { "mechanism": "document", "where": "docs/agent/roles.md § критерий приёмки новой роли: свой вопрос, свой артефакт, своё возражение конкретной роли — иначе это раздел документации", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "063": { "mechanism": "pipeline", "where": "scripts/rerun_flaky_checks.py", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "064": { "mechanism": "document", "where": "CLAUDE.md § Метки при заведении issue: на PR обязательны area/*, тип работы и связь с задачей; отказывает scripts/check_pr_ready.py, который запускает окно перед мержем", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "065": { "mechanism": "pipeline", "where": "scripts/check_good_first_issues_bilingual.py — good first issue и help wanted ведутся на двух языках, и меток именно две: путь участника не кончается первым вкладом", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "066": { "mechanism": "gate", "where": "tests/test_atomic_io.py — блокировка берётся на спутника, а не на файл, заменяемый целиком; предмет — src/stepik_grader/atomic_io.py", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "067": { "mechanism": "gate", "where": "src/stepik_grader/core/runner.py и src/stepik_grader/web/playground.py убирают приватный каталог в finally, но исход прогона возвращают неизменным; tests/test_runner.py проверяет и уборку, и то, что вердикт не подменён", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "068": { "mechanism": "pipeline", "where": "закрытые списки разрешённого: _FIELDS в src/stepik_grader/core/usage_export.py (что покидает журнал) и AUTO_RERUN в scripts/rerun_flaky_checks.py (что вообще можно перезапускать); оба закреплены тестами", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "069": { "mechanism": "gate", "where": "tests/test_user_settings.py — пишется поле, а не снимок; предмет — src/stepik_grader/core/user_settings.py", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "070": { "mechanism": "gate", "where": "scripts/check_glossary_examples.py — храповик с числом (BUDGET), опускается починкой, а не правкой числа; tests/test_check_glossary_examples.py не даёт бюджету молча вырасти", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "071": { "mechanism": "document", "where": "намеренный дубль подписывается комментарием с причиной: src/stepik_grader/launcher.py — имя дублирует downloader.CONFIG_FILE намеренно (issue #823), scripts/preflight.py::_trailer_block повторяет разбор из scripts/check_attribution.py, чтобы предпушевой гейт не импортировал соседние. Приём подсказан ответом соседа по своду (правило 162)", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "072": { "mechanism": "gate", "where": "scripts/check_test_isolation.py", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "073": { "mechanism": "gate", "where": "scripts/check_ruff_pin.py", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "074": { "mechanism": "gate", "where": "scripts/check_workflow_guardrails.py", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "075": { "mechanism": "gate", "where": "scripts/check_workflow_guardrails.py", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "076": { "mechanism": "gate", "where": "scripts/check_locale_guardrails.py", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "077": { "mechanism": "gate", "where": "scripts/check_locale_guardrails.py", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "078": { "mechanism": "gate", "where": "отмена — отдельный статус во всех слоях: CANCELLED в src/stepik_grader/web/playground.py и src/stepik_grader/web/runs.py, cancelled в реестре job'ов; закреплено tests/test_web_playground.py и tests/test_runs.py", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "080": { "mechanism": "pipeline", "where": "scripts/check_proposal_verdicts.py сверяет .rules/proposals.json с вердиктами каталога ночным обходом (.github/workflows/tracker-guardrails.yml): принятое предложение перестаёт быть предложением и требует ответа по номеру. Что правило вообще предложено — не проверяется ничем, это строка чек-листа CLAUDE.md § Новое правило — в общий каталог", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "081": { "mechanism": "gate", "where": "src/stepik_grader/core/runner.py и src/stepik_grader/web/playground.py материализуют чужой код в приватном каталоге 0700 (issue #799), а не в общем temp; tests/test_runner.py проверяет это прогоном — решение печатает свой sys.path[0]", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "082": { "mechanism": "document", "where": "CLAUDE.md § Режим ответов: пласт без владельца — слепая зона, покрытие не равно отдельной роли; разбор пластов — docs/agent/roles.md", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "083": { "mechanism": "document", "where": "docs/dev/corpus.md — прогонный корпус проверяет вердикты детерминированными мутациями эталона, а не сверкой с «правильным ответом» LLM", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "084": { "mechanism": "document", "where": "CLAUDE.md § Архитектурные инварианты, п. 3: rich опционален, весь вывод через _console с graceful fallback; AI-подсказка отказывает молча и не задерживает проверку", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "085": { "mechanism": "gate", "where": "tests/test_ai_grounding.py — вывод проверяемого решения уходит в промпт как недоверенный вход", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "086": { "mechanism": "document", "where": "CLAUDE.md § Режим ответов (🔍 не ставит окончательную тяжесть — его оценка заявка; ⚖️ доказывает) и docs/agent/multiagent.md § адверсариальный верификатор со шкалой на примерах", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "087": { "mechanism": "document", "where": "docs/agent/multiagent.md: второй аудит получает на вход накопитель первого и правило новизны — иначе на 60% переоткрывает его", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "088": { "mechanism": "document", "where": "docs/agent/roles.md § Роль 27 — Критик метода: вход — след фазы, вопрос о методе, а не о продукте", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "089": { "mechanism": "gate", "where": "scripts/check_showcase_links.py — данные карточек, локали и разметка веб-слоя не содержат ссылки на внешнюю витрину; объяснения в комментариях и docstring разрешены", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "090": { "mechanism": "gate", "where": "ADR-0011: atomic_io.py и db.py подняты в top-level leaf вместо ребра glossary → core; отсутствие такого ребра держит tests/test_import_dag.py", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "091": { "mechanism": "document", "where": "CLAUDE.md § Открытая работа: порядок обращения сверху вниз — issue, docs/audit/, docs/agent/claude-handoff.md, CHANGELOG.md; первый непустой источник и есть план", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "092": { "mechanism": "document", "where": "CLAUDE.md § Открытая работа: аудит даёт находки (docs/audit/), очередь задаёт порядок (docs/agent/claude-handoff.md) — два документа, смешивать нельзя", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "093": { "mechanism": "document", "where": "docs/dev/adr/0006-runner-abstraction.md — протокол Runner введён швом до server mode: минимальная обратимая точка расширения вместо обобщения по первому случаю", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "094": { "mechanism": "document", "where": "docs/dev/adr/0004-src-layout.md § Альтернативы, вариант B: src-layout плюс тонкие root-shim'ы отклонён явно — два пути импорта и их дрейф", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "095": { "mechanism": "document", "where": "docs/dev/adr/0002-history-opt-in.md: сбор и отдача данных выключены по умолчанию, решение принимает человек; consent-гейт AI-подсказки и флаг --expose-usage", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "096": { "mechanism": "document", "where": "docs/dev/adr/0011-local-persistence.md: SQLite для того, что пишется конкурентно и переживает прогон, JSON для настроек — хранилище выбрано по жизненному циклу данных", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "097": { "mechanism": "gate", "where": "scripts/check_gate_tests.py — у каждого гейта в тестах есть обе стороны: отвергающий случай (ложное «прошло») и пропускающий (ложное «не прошло»); набор из одних отказов зеленеет у гейта, который краснеет на всём подряд", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "098": { "mechanism": "document", "where": "docs/dev/glossary.md § Одна концепция — одна карточка: парные протоколы намеренно остаются бандлами, а поиск чинится keywords, а не разбиением", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "099": { "mechanism": "document", "where": "docs/dev/glossary.md § Дубли двух поколений импорта: при конфликте выигрывает тематика — иначе «Типы данных» осталась бы без int/str/float", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "100": { "mechanism": "gate", "where": "scripts/check_step_deadlines.py — у шага, который ходит в сеть (установка, скачивание артефакта, клон), свой timeout-minutes: общий предел job'а старт не покрывает, и зависшая установка съедала бы его целиком", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "101": { "mechanism": "pipeline", "where": "scripts/gh_rest.py — временный отказ отличается от постоянного по ответу сервера: 403/429 с исчерпанным остатком или Retry-After поднимают RateLimited («ждать»), а 401/403 без них — GitHubError («повтор не поможет»); названная сервером пауза важнее расчётной", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "102": { "mechanism": "gate", "where": "src/stepik_grader/core/normalizers.py — каждое послабление сравнения названо и объяснено (хвостовые пробелы, пустые строки в конце), а режим strict сверяет побайтово; закреплено tests/test_output_comparison.py", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "103": { "mechanism": "gate", "where": "tests/conftest.py — сторож _no_writes_outside_tmp с исключениями по ПРЕФИКСУ (_RUN_ARTEFACT_PREFIXES, _FOREIGN_TOOL_PREFIXES); закреплено tests/test_write_guard.py вложенной pytest-сессией", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "104": { "mechanism": "pipeline", "where": ".github/workflows/ci.yml", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "105": { "mechanism": "pipeline", "where": ".github/workflows/claude-code-review.yml — ревью ведёт отдельный прогон, не то окно, что писало код; claude-review в списке обязательных проверок защиты main", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "106": { "mechanism": "document", "where": "HISTORY.md: решение владельца — не показывать широко, пока не пройден настоящий курс и вердикты не сошлись с платформой; протокол прогона — docs/agent/course-walkthrough.md", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "107": { "mechanism": "document", "where": "матрица CI (три ОС × 3.12/3.13): вторая и третья ОС не проверяются руками ни в одном окне — только матрицей, потому что выборка окна это выборка его автора (CLAUDE.md § Два окна)", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "108": { "mechanism": "gate", "where": "scripts/check_docs_guardrails.py не даёт CHANGELOG.md держать больше трёх версионных заголовков: старое переезжает дословно в docs/archive/changelog-archive.md", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "109": { "mechanism": "gate", "where": "src/stepik_grader/web/runs.py — _STATUSES перечисляет два переходных состояния (queued, running) и три терминальных (done, error, cancelled); выход из переходного всегда в терминальное, закреплено tests/test_runs.py", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "110": { "mechanism": "gate", "where": "src/stepik_grader/core/tracer.py компилирует код до подмены stdout и settrace; регрессию держит tests/test_tracer.py — SyntaxError оставлял stdout подменённым", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "111": { "mechanism": "document", "where": "CLAUDE.md § Гейты: может сделать сам — делает, а не советует; прецедент — gh_rest.py edit-pr вместо совета проставить Closes руками", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "112": { "mechanism": "gate", "where": "флаг --clear-cache чистит оба кэша, заведённых продуктом (issue #816); tests/test_cli.py держит охват", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "113": { "mechanism": "gate", "where": "scripts/check_contract_evolution.py", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "114": { "mechanism": "gate", "where": "tests/test_db.py — общий коннектор SQLite (connect/user_version/apply_schema); предмет — src/stepik_grader/db.py", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "115": { "mechanism": "gate", "where": "src/stepik_grader/config.py — _dirs_up_to_boundary поднимается только до границы проекта или домашнего каталога (issue #993), якорь один на все поверхности; закреплено tests/test_config.py", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "116": { "mechanism": "gate", "where": "scripts/combine_coverage.py — сборщик сверяет пришедшие источники с названными (--require) и при недостаче включает degraded вместо молчаливого недосчёта; закреплено tests/test_combine_coverage.py", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "117": { "mechanism": "document", "where": "docs/agent/multiagent.md: лимит объёма ответа прямо в схеме (minItems/maxItems, maxLength на каждом строковом поле) и бюджет обращений; то же в CLAUDE.md § Мультиагентный режим", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "118": { "mechanism": "gate", "where": "scripts/check_generated_sources.py — файл с шапкой «СГЕНЕРИРОВАНО» называет генератор, и тот существует", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "119": { "mechanism": "gate", "where": "src/stepik_grader/core/test_loader.py — обход берёт только .py и не заходит в скрытые каталоги (_is_hidden_or_service_dir), поэтому .grader_cache/ и .grader_stats.jsonl под него не попадают; шаблон task*.py — приоритет с откатом (_solution_files_in возвращает by_pattern or files), а не жёсткий фильтр; закреплено tests/test_loader.py", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "120": { "mechanism": "gate", "where": "указатель правил и дайджест генерируются (scripts/generate_rules_index.py, scripts/generate_rules_digest.py), а scripts/check_rules_digest.py не даёт им разойтись с ответом проекта", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "121": { "mechanism": "pipeline", "where": "scripts/check_container_closure.py — ночной обход сверяет состояние эпика со счётчиком незакрытых дочерних задач: закрытый контейнер с открытой работой становится находкой с адресатом", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "122": { "mechanism": "gate", "where": "scripts/check_raw_values.py — поле ответа веб-слоя не отдаёт отформатированное число (`f\"{x:.1f} с\"`); человеческие поля (message, hint, title) под запрет не подпадают, там строка и есть содержимое", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "123": { "mechanism": "document", "where": "CLAUDE.md § Формат коммитов: идентичность окна согласована до слияния, после squash подпись не переписать; смотрит scripts/check_attribution.py --check-branch, который не зовёт ни прогон, ни preflight", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "124": { "mechanism": "pipeline", "where": "scripts/rerun_flaky_checks.py: список разрешённых к автоперезапуску закрыт, попытка ровно одна, а зелёное со второго раза записывается находкой в docs/agent/flaky-runs.md", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "125": { "mechanism": "gate", "where": "scripts/check_generated_sources.py — у производного файла назван живой исходник; DIGEST.md и указатель правил пересобираются генератором, а не правятся", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "126": { "mechanism": "pipeline", "where": "красная main замораживает очередь мержа, и выход из заморозки — метка blocker на чинящем PR, то есть путь мимо замороженного действия; вычисляет scripts/gh_rest.py queue", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "127": { "mechanism": "gate", "where": "scripts/check_docs_guardrails.py — число в прозе живёт только внутри генерируемого блока с маркером; вписанное руками отклоняется", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "128": { "mechanism": "document", "where": "CLAUDE.md § Метки при заведении issue: связь проверяется на полноту — номера во фрагментах changelog.d против тела PR; сверяет scripts/check_pr_ready.py, запускаемый окном перед мержем", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "129": { "mechanism": "gate", "where": ".rules/bindings.json и .rules/proposals.json — обе стороны контракта потребления; держат scripts/check_rule_bindings.py и scripts/check_rules_digest.py, канал подключён .github/workflows/rules-inbox.yml", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "130": { "mechanism": "pipeline", "where": "scripts/link_rules_to_issues.py — новое правило приходит вместе со списком кандидатов из нашего трекера", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "131": { "mechanism": "document", "where": "CLAUDE.md § Формат коммитов: PR, открытый ботом, пересоздаётся, а не мержится — squash записал бы бота автором в main; отказывает scripts/check_pr_ready.py при запуске окном", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "132": { "mechanism": "pipeline", "where": "scripts/check_work_overlap.py показывает пересечение неблокирующим шагом scripts/preflight.py, а CLAUDE.md § Метки при заведении issue требует объявить все задачи сборного изменения (Closes #N либо «Часть #N»)", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "133": { "mechanism": "pipeline", "where": "граница PR считается пересечением файлов, а не числом задач: scripts/check_work_overlap.py зовётся неблокирующим шагом scripts/preflight.py", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "134": { "mechanism": "gate", "where": ".claude/hooks/session_start.py печатает дайджест правил при старте окна, а docs/agent/environments.md задаёт эстафету ссылками, а не пересказом", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "135": { "mechanism": "document", "where": "CLAUDE.md § Формат коммитов: автор PR — человек, соавторство — место вклада Claude; scripts/check_pr_ready.py и scripts/check_attribution.py --check-branch запускаются окном, а не прогоном", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "136": { "mechanism": "gate", "where": "ответ по внешнему правилу пишется в .rules/bindings.json одним заходом: сначала перечисляются все свои предметы правила, потом вердикт; сверяется на ревью PR — гейта на полноту перечисления нет", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "137": { "mechanism": "gate", "where": "scripts/check_raw_values.py", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "138": { "mechanism": "document", "where": "docs/agent/environments.md — решение оседает задачей или комментарием сразу, эстафета передаётся ссылками; шаблон стартового сообщения окна там же", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "139": { "mechanism": "document", "where": "CONTRIBUTING.md § Когда дефект считается исправленным и чек-лист CLAUDE.md: дефект закрыт прогоном той поверхности, где найден, — браузер браузером, CLI командой", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "140": { "mechanism": "gate", "where": "scripts/check_gate_tests.py — у каждого scripts/check_*.py есть тест с прогоном отвергаемого предмета; объявленный долг перечислен в KNOWN_DEBT с причиной", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "141": { "mechanism": "gate", "where": "scripts/check_marker_matching.py — константа-маркер не подставляется в startswith/removeprefix; префикс от маркера отличается именем, и это названо в самих константах", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "142": { "mechanism": "pipeline", "where": "scripts/nightly_checks.py — ночной обход ведёт одну задачу с меткой «ночной обход»: пока находки есть, она открыта и обновляется, стало чисто — закрывается; красным прогон при этом не становится, потому что красное на вкладке прогонов адресатом не является", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "144": { "mechanism": "pipeline", "where": "scripts/check_audit_registry.py — mention_verdict берёт окно контекста абзацем, а заголовок раздела перевешивает форму строки; закреплено тестами test_check_audit_registry.py", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "145": { "mechanism": "gate", "where": "scripts/check_declared_outcomes.py — у каждого НЕуспешного кода возврата есть тест, ожидающий именно его; непрогнанные ветки видны числом (BUDGET), и оно опускается починкой", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "146": { "mechanism": "document", "where": "docs/agent/preflight.md § Что гейты не ловят: зелёный гейт подтверждает себя, утверждение проверяется замером на живом предмете, замер пишется рядом с механизмом", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "147": { "mechanism": "pipeline", "where": "scripts/check_orphan_branches.py", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "148": { "mechanism": "document", "where": "CLAUDE.md § Гейты: проверки читаются по sha головы PR, отдельно смотрится прогон main; scripts/check_pr_ready.py запускает окно перед мержем", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "149": { "mechanism": "gate", "where": "tests/conftest.py — фикстура _system_temp_inside_basetemp уводит системный temp внутрь pytest-basetemp на время прогона; правило родилось здесь, из ложного обвинения стороннего теста", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "150": { "mechanism": "document", "where": "docs/agent/preflight.md § Что гейты не ловят: тест проверки спрашивает её вердикт, а не пересказывает условие; механизмом не ловится — тот же признак законен там, где предмет теста и есть константа", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "151": { "mechanism": "pipeline", "where": "scripts/report_failed_tests.py и job report-failures в .github/workflows/ci.yml — сводка упавших тестов ложится комментарием в PR, потому что логи и артефакты Actions облачной сессии недоступны (403); правило родилось здесь", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "152": { "mechanism": "gate", "where": "scripts/check_workflow_guardrails.py", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "153": { "mechanism": "pipeline", "where": "docs/agent/rules/DIGEST.md собирается из каталога генератором (scripts/generate_rules_digest.py), а не переписывается руками: чужой текст здесь производное с живым исходником, и расхождение ловит check_rules_digest.py", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "154": { "mechanism": "gate", "where": "scripts/check_rule_bindings.py — храповик UNHELD_BUDGET = 0: запись active + mechanism: none сюда не мержится вовсе, поэтому состояние, у которого 154 требует причину, здесь недостижимо, а не молчаливо; у отрицательных статусов (rejected, not-applicable) тот же гейт отказывает при пустом why", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "155": { "mechanism": "gate", "where": "tests/test_shipped_examples.py — secrets.json.example и stepik_config.json.example проверяются НАБОРОМ ключей, который читает реальный код (core/stepik_client.py, downloader_config.normalize_config_paths), а не фактом своего существования", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "156": { "mechanism": "gate", "where": "scripts/check_attribution.py::trailer_block и scripts/preflight.py::_trailer_block читают трейлер только из хвостового блока — последнего абзаца целиком из строк «Ключ: значение»; закреплено tests/test_check_attribution.py", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "157": { "mechanism": "pipeline", "where": "scripts/check_rule_bindings.py::contract_drift сверяет нашу schema с той, что публикует каталог (templates/bindings.json); ходит ночным обходом .github/workflows/tracker-guardrails.yml — прогон замечает, слияние не держит", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "158": { "mechanism": "document", "where": "docs/agent/preflight.md § Что гейты не ловят: scripts/check_three_outcomes.py требует наличия третьего исхода, но не адреса отказа; признак «в сообщении есть адрес» от «есть любая подстановка» машинно не отличить", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "159": { "mechanism": "document", "where": "docs/agent/preflight.md § Что гейты не ловят: ранний return посреди перебора ловится грепом и почти всегда законен — отличает их только то, копится ли что-то в список, который читают позже", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "160": { "mechanism": "pipeline", "where": ".github/workflows/ci.yml — бейджи собираются в .github/badges/ и публикуются ОТДЕЛЬНОЙ веткой badges, а не в main (issue #1235): производное обновляется чаще изменений и в общей ветке давало бы конфликт на каждое слияние", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "161": { "mechanism": "document", "where": "docs/dev/adr/README.md — поворот с отвергнутой альтернативой пишется записью ADR, и полноту такой записи держит гейт scripts/check_adr_records.py; HISTORY.md по CLAUDE.md § Обновление CHANGELOG.md / HISTORY.md ведётся релизными сводками. Механизма, требующего завести ADR на состоявшийся поворот, нет", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "162": { "mechanism": "pipeline", "where": "scripts/check_rule_bindings.py::neighbour_holds печатает, чем правило без механизма держится у соседей по своду — собирает из export/where.json каталога, а не походом по чужим репозиториям; ходит ночным обходом", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "163": { "mechanism": "document", "where": "docs/use/installation.md и README.md — вход описан командами (pip install -e, stepik-grader, stepik-grader-gui), список команд один и живёт в CLAUDE.md § Команды; копирование образцов названо там же", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "164": { "mechanism": "gate", "where": ".rules/bindings.json и .rules/proposals.json — рядом с каждым `schema` стоит ключ `schema_of`, называющий предмет в точке чтения (сам `schema` переименованию не подлежит: имя задаёт контракт каталога). Наличие обоих ключей и значение каждого номера проверяет scripts/check_rule_bindings.py::version_subjects — шагом «Rule bindings answer the catalogue» обязательного docs-guardrails в .github/workflows/ci.yml и хуком pre-commit, то есть на каждый PR; дрейф от templates/bindings.json и templates/proposals.json каталога — contract_drift и proposal_drift ночным обходом .github/workflows/tracker-guardrails.yml", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "165": { "mechanism": "gate", "where": "scripts/check_hidden_defaults.py — разбор исходника отвергает список путей от git без -z; шаг «Умолчания заданы явно» обязательного docs-guardrails в .github/workflows/ci.yml и хук pre-commit. Сами списки читаются по NUL: scripts/preflight.py (git_paths), scripts/check_work_overlap.py, scripts/check_adr_records.py; охват называет числом scripts/preflight.py (fingerprint_coverage). Подтверждено живой пробой: отпечаток не замечал правок в файле с кириллическим именем, то есть pre-push хук принимал состояние, которого гейт не проверял (issue #1417)", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "166": { "mechanism": "gate", "where": "scripts/check_docs_guardrails.py ищет разметку ссылки `](адрес`, а не подстроку адреса, — шаг обязательного docs-guardrails в .github/workflows/ci.yml; регрессии в tests/test_check_docs_guardrails.py. Подстрока зеленела бы на подписи ссылки при подменённой цели", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "167": { "mechanism": "gate", "where": "scripts/check_workflow_guardrails.py (non_ascii_shell_names) — имена переменных оболочки и ключей env: в .github/workflows/*.yml; шаг обязательного docs-guardrails в .github/workflows/ci.yml. Тихая форма опаснее громкой: переменная с не-ASCII именем создаётся, но $ИМЯ не раскрывается, и шаг зеленеет, не проверив ничего (issue #1421)", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "168": { "mechanism": "pipeline", "where": "scripts/check_branch_protection.py сверяет список обязательных проверок с ruleset дословно — ночным обходом .github/workflows/tracker-guardrails.yml. Обязательных проверок у нас одиннадцать, шесть из них — имена ячеек матрицы, то есть ровно названная правилом ловушка; агрегирующей проверки с постоянным именем нет, это issue #1420, а до неё риск назван в CLAUDE.md § Гейты", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "169": { "mechanism": "pipeline", "where": "scripts/measure_queue_wakeups.py — замер источников пробуждения очереди выводится из живых прогонов, ночным обходом .github/workflows/tracker-guardrails.yml; отказ односторонний: страховка объявлена и по расписанию не пришло ни одного прогона. Шапка .github/workflows/merge-queue.yml называет наблюдаемое (42 срабатывания вместо ~201 за 201 час), а не заказанное", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "170": { "mechanism": "pipeline", "where": "scripts/capture_github_fixtures.py --check — у каждой подделки чужого интерфейса есть снятый источник с блоком происхождения (tests/fixtures/github/), сверка ночным обходом .github/workflows/tracker-guardrails.yml; разбор гоняется на снятых ответах в tests/test_fixture_sources.py. Ответы Stepik API из облака снять нечем — эти подделки названы несверенными, и это остаток задачи #1422", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "171": { "mechanism": "document", "where": "docs/agent/preflight.md § Что гейты НЕ ловят — эталон обязательных проверок в scripts/check_branch_protection.py (EXPECTED_CHECKS) является КОПИЕЙ имён из .github/workflows/ci.yml, а не производным от него: переименование ячейки матрицы оставит ruleset и константу согласными друг с другом, а работы — с другими именами. Самоблокировка и починка — issue #1420", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "172": { "mechanism": "pipeline", "where": "scripts/check_stale_repo_names.py — перепись имён репозиториев в действующих файлах против канона от площадки, ночным обходом .github/workflows/tracker-guardrails.yml; scripts/check_catalogue_name.py сверяет объявленное имя каталога с каноном. История отделена от адреса явно: журналы и архив в перепись не входят (issue #1421)", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "173": { "mechanism": "pipeline", "where": "scripts/check_issue_state_after_merge.py спрашивает у трекера судьбу задачи ПОСЛЕ слияния — закрыта ли закрытая, открыта ли частичная, назван ли остаток галочками; ночным обходом .github/workflows/tracker-guardrails.yml. Первая половина — три ответа (Closes #N · «Часть #N — что именно» · «Без issue:») — в scripts/check_pr_ready.py, но он числится в GATE_DEBT: запускает его окно руками, поэтому уровень взят по слабейшему звену (issue #1419)", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "174": { "mechanism": "pipeline", "where": "scripts/generate_facts.py публикует факты о проекте машиночитаемо — шаг в .github/workflows/ci.yml (под `|| true`, слияние не держит). Правило родилось здесь: след ведёт на #1411, соседи перестали считать наши числа сами", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "175": { "mechanism": "gate", "where": ".rules/bindings.json — ответ «предмета нет» несёт СВОЙ рецепт опровержения полем `absent`; проверяет scripts/check_rule_bindings.py (absence_claims) шагом «Rule bindings answer the catalogue» обязательного docs-guardrails в .github/workflows/ci.yml и хуком pre-commit. Отказ односторонний: нашли опровержение — красное, не нашли — молчим (issue #1421)", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "176": { "mechanism": "gate", "where": "scripts/check_hidden_defaults.py (encoding_findings) — текстовый subprocess без encoding= отвергается разбором исходника, а не прогоном; шаг обязательного docs-guardrails в .github/workflows/ci.yml и хук pre-commit. Прогон отвечает «сегодня не совпало», разбор — «умолчание не задано» (issue #1417)", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "177": { "mechanism": "gate", "where": "scripts/check_rule_bindings.py (unfinished_rule_work) — три числа «без ответа · не рассмотрено · держится ничем» печатаются ПЕРВЫМ, до находок и до списка новых правил, и печатаются всегда: «0 · 0 · 0» это состояние, а не пустота. Шаг обязательного docs-guardrails в .github/workflows/ci.yml; полное число «без ответа» требует клона каталога и потому даётся ночным обходом, а без него честно печатается «не спрошено», а не ноль", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "178": { "mechanism": "document", "where": "CLAUDE.md § Открытая работа — разбор расхождения начинается с двух гипотез о своём чтении. Механизма нет и не предлагается: по правилу 182 требование разложено, и машинная половина не нашлась — у нас ровно одно место, где два числа расходятся законно (два бейджа покрытия, причина названа рядом), а остальные расхождения единичны и разбираются по одному. Живой разбор: расхождение колонки состояния и реестра на 493 записи объяснено первопричиной, а не «разными величинами» (issue #1438)", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "179": { "mechanism": "gate", "where": "scripts/check_workflow_guardrails.py (check_cancelling_groups_name_the_head) — отменяющая группа обязана называть голову; исключения объявлены поимённо с причиной и краснеют, когда отмена из них ушла. Сам дефект исправлен в .github/workflows/ci.yml: группа PR стояла на github.ref, то есть на refs/pull/N/merge — одном ref для всех коммитов", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "180": { "mechanism": "gate", "where": "scripts/check_hidden_defaults.py (subprocess_names) — вызов разрешается по импортам разбираемого файла, включая псевдонимы модуля и функции. Обе половины подтверждены пробой и закреплены в tests/test_check_hidden_defaults.py: псевдоним прятал вызов от проверки, чужой метод run с теми же ключами объявлялся нарушением", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "181": { "mechanism": "gate", "where": "scripts/check_sources_of_truth.py — заявления об исключительности объявляются в .rules/sources_of_truth.json парой «предмет → адрес»; незаявленное краснеет, объявление, пережившее текст, краснеет тоже, два адреса на один предмет отвергаются. Правило родилось здесь: контракт называл источник состояния трижды и в двух местах (issue #1438)", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "182": { "mechanism": "gate", "where": "scripts/check_rule_bindings.py (UNHELD_BUDGET = 0) — ответ «действует, но ничем» отвергается: правило либо держится механизмом, либо записывается документом с разрешимым адресом. Бюджет опускают починкой, а не правкой числа, поэтому отказ целиком невозможен по построению — половина, которую держит машина, обязана быть названа", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "183": { "mechanism": "gate", "where": "scripts/check_rule_bindings.py — адрес в каждом ответе этого файла обязан разрешаться, проверка на каждый PR; scripts/check_sources_of_truth.py — заявления об исключительности объявлены в .rules/sources_of_truth.json и сверяются с текстом (PR и pre-commit). Не покрыта проза о механизме в docstring и комментариях кода: её не перечитывает никто, и это остаток", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "184": { "mechanism": "gate", "where": "scripts/check_rule_bindings.py требует причину у статусов not-applicable и rejected — ответ без неё отвергается до слияния. Разбор надвое (буква против обобщения) остаётся дисциплиной: CLAUDE.md § Новое правило — в общий каталог", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "185": { "mechanism": "pipeline", "where": "scripts/check_catalogue_trails.py — ночным обходом сверяет следы каталога в наше дерево: документ существует, названный раздел в нём есть. Предупреждение, а не отказ: предмет живёт в чужом репозитории, чинится предложением туда. Замер на выгрузке 1.5 — следов 105, оборванных 15", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "186": { "mechanism": "gate", "where": "scripts/check_declared_outcomes.py — у каждого объявленного НЕуспешного кода возврата обязан быть тест, ожидающий именно его; коды разведены по смыслу в scripts/gh_rest.py (EXIT_OK · EXIT_FAIL · EXIT_WAIT) и означают одно и то же в сухом и записывающем режимах — scripts/link_rules_to_issues.py возвращает EXIT_FAIL по числу НЕобновлённых задач, а не по числу найденных", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "187": { "mechanism": "gate", "where": "scripts/ci_aggregate.py и .github/workflows/ci-complete.yml — один вердикт с постоянным именем, собранный ЧЕРЕЗ API, а не через needs: джоб на зависимостях при падении соседа пропускается, а пропущенное защита ветки засчитывает как пройденное; закреплено в CLAUDE.md § Гейты", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "188": { "mechanism": "pipeline", "where": "scripts/gh_rest.py edit-issue считает галочки по телу, ВЕРНУВШЕМУСЯ от платформы, а не по отправленному, и печатает число — «поставил галочку» и «стёр чек-лист» иначе выглядят одинаково; ночной обход .github/workflows/tracker-guardrails.yml печатает исход по каждой записи. Полной сверки отправленного с сохранённым нет ни у одной публикации — это остаток", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "189": { "mechanism": "gate", "where": "приставка agent/ — переключатель конвейера, её отсутствие ловит scripts/check_orphan_branches.py (ветка без прикреплённого изменения); занятость имени — scripts/preflight.py --branch-only. Что слаг называет задачу, а не окно, разбором не отличить — эта половина в CLAUDE.md § Рабочая ветка", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "190": { "mechanism": "gate", "where": "scripts/check_rule_bindings.py спрашивает форму ответа у клона каталога (templates/bindings.json, функция catalogue_schema) вместо хранения копии — поэтому подъём чужого контракта виден как расхождение номеров, а не как молчаливое расхождение полей", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "191": { "mechanism": "gate", "where": "заготовку тела PR создаёт .github/workflows/agent-pr.yml из сообщения коммита, стоп-кран держит scripts/check_pr_ready.py — без Closes #N и меток PR не мержится; правка тела задачи машинно перечитывает записанное (gh_rest.py edit-issue --expect-file)", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "192": { "mechanism": "gate", "where": "scripts/check_workflow_guardrails.py — подмена токена в очереди мержа не проходит: замер «пуш запускает прогон на PR» снят на PAT владельца, и пуш штатным GITHUB_TOKEN его не воспроизводит (CLAUDE.md § Гейты, «отдельным токеном»)", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "193": { "mechanism": "gate", "where": "scripts/check_glossary_examples.py — приёмка примеров карточек строже прежнего храповика исполнения: тот помечал некомпилируемую карточку как unverifiable и проходил мимо, гейт требует компилируемости", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "194": { "mechanism": "gate", "where": "scripts/check_stale_repo_names.py спрашивает каноническое full_name у API по каждому адресу из своего дерева, вместо того чтобы верить имени в ссылке: переименованный репозиторий отвечает редиректом, пока старое имя свободно", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "195": { "mechanism": "document", "where": "CLAUDE.md § Чеклист перед PR — «покрыты ВСЕ вызовы класса (grep)»; CONTRIBUTING.md § Когда дефект считается исправленным. Механизма, требующего назвать соседние случаи, нет: их перечисление проверить нечем", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "196": { "mechanism": "document", "where": "CLAUDE.md § Метрики — бейджи публикуются в ветку badges отдельным прогоном, README ссылается на raw. Порядок «сначала артефакт, потом ссылка» держится текстом: гейта, сверяющего очерёдность, нет", "holdable": "", "awaiting": "", "analysed": "", "decided": "" } }, "trails": 187, "born": 132, "findings": [ "ответ по схеме 1.2, у контракта 1.5 — записи остаются валидными, означая уже другое", "ответ построен на выгрузке 1.5, у нас 1.7 — записи валидны, а составленные по ним ответы отвечают на другой вопрос (157)" ] }, { "repo": "ArtVsMark/ArtVsMark", "role": "потребитель", "schema": "1.5", "answers_to": "1.7", "state": "подключён", "read_at": "2026-09-24", "answered": 203, "by_status": { "active": 146, "not-applicable": 56, "rejected": 1 }, "by_mechanism": { "gate": 95, "code": 13, "document": 25, "pipeline": 12, "none": 1 }, "rules": { "001": "active", "002": "active", "003": "active", "004": "active", "005": "active", "006": "active", "007": "not-applicable", "008": "active", "009": "active", "010": "active", "011": "active", "012": "active", "013": "not-applicable", "014": "active", "015": "not-applicable", "016": "active", "017": "active", "018": "not-applicable", "019": "not-applicable", "020": "not-applicable", "021": "active", "022": "active", "023": "active", "024": "rejected", "025": "active", "026": "active", "027": "active", "028": "not-applicable", "029": "active", "030": "active", "031": "not-applicable", "032": "active", "033": "not-applicable", "034": "not-applicable", "035": "not-applicable", "036": "not-applicable", "037": "not-applicable", "038": "active", "039": "active", "040": "not-applicable", "041": "active", "042": "not-applicable", "043": "not-applicable", "044": "active", "045": "active", "046": "active", "047": "active", "048": "not-applicable", "049": "active", "050": "active", "051": "active", "052": "not-applicable", "053": "not-applicable", "054": "not-applicable", "055": "not-applicable", "056": "active", "057": "active", "058": "active", "059": "not-applicable", "060": "not-applicable", "061": "not-applicable", "062": "active", "063": "active", "064": "active", "065": "not-applicable", "066": "not-applicable", "067": "active", "068": "active", "069": "active", "070": "active", "071": "active", "072": "active", "073": "active", "074": "active", "075": "active", "076": "not-applicable", "077": "not-applicable", "078": "active", "079": "not-applicable", "080": "active", "081": "not-applicable", "082": "active", "083": "active", "084": "active", "085": "not-applicable", "086": "not-applicable", "087": "not-applicable", "088": "not-applicable", "089": "active", "090": "active", "091": "active", "092": "not-applicable", "093": "active", "094": "active", "095": "not-applicable", "096": "not-applicable", "097": "active", "098": "not-applicable", "099": "not-applicable", "100": "active", "101": "not-applicable", "102": "not-applicable", "103": "not-applicable", "104": "active", "105": "active", "106": "not-applicable", "107": "active", "108": "active", "109": "active", "110": "not-applicable", "111": "active", "112": "not-applicable", "113": "not-applicable", "114": "active", "115": "not-applicable", "116": "not-applicable", "117": "not-applicable", "118": "active", "119": "active", "120": "not-applicable", "121": "not-applicable", "122": "not-applicable", "123": "active", "124": "active", "125": "active", "126": "active", "127": "active", "128": "active", "129": "active", "130": "not-applicable", "131": "active", "132": "active", "133": "active", "134": "active", "135": "active", "136": "active", "137": "active", "138": "active", "139": "active", "140": "active", "141": "active", "142": "active", "144": "active", "145": "active", "146": "active", "147": "active", "148": "active", "149": "active", "150": "active", "151": "active", "152": "active", "153": "active", "154": "active", "155": "not-applicable", "156": "active", "157": "active", "158": "active", "159": "active", "160": "active", "161": "active", "162": "active", "163": "not-applicable", "164": "active", "165": "active", "166": "active", "167": "active", "168": "active", "169": "active", "170": "active", "171": "active", "172": "active", "173": "active", "174": "active", "175": "active", "176": "active", "177": "active", "178": "active", "179": "active", "180": "active", "181": "active", "182": "active", "183": "active", "184": "active", "185": "active", "186": "active", "187": "active", "188": "active", "189": "active", "190": "active", "191": "active", "192": "active", "193": "active", "194": "active", "195": "active", "196": "active", "197": "active", "198": "active", "199": "active", "200": "active", "201": "not-applicable", "202": "active", "203": "active", "204": "active" }, "holds": { "001": { "mechanism": "gate", "where": "scripts/build_metrics.py — все обращения к GitHub идут через _api() по REST; scripts/check_mechanisms.py::audit_calls держит это гейтом: обращение к площадке мимо _api() отвергается. Разбирается ВЫЗОВ, а не текст — первый черновик искал строкой и поймал сам себя, потому что образец с адресом внутри регулярного выражения неотличим от обращения, если смотреть на буквы", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "002": { "mechanism": "gate", "where": "README.md § Engineering approach и .github/workflows/pr-check.yml — утверждения проверяемы, а обязательная проверка в защите main держит их машинно; scripts/check_author.py — соглашение об авторе коммита перестало держаться памятью окна: оно попадало в main пять раз, пока механизма не было. ССЫЛКА ПОПРАВЛЕНА 8 СЕНТЯБРЯ: раздел назывался «What holds the quality» и был переименован своей же правкой #158; вердикт остался показывать в пустоту, и заметил это гейт, а не глаза", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "003": { "mechanism": "gate", "where": "ВЕРДИКТ СМЕНИЛСЯ ТЕМ ЖЕ ЗАХОДОМ, ЧТО ПОЯВИЛСЯ ПРЕДМЕТ. Прежде здесь стояло «предмета нет: конвейер витрины не смотрит на имена веток» — и это было верно, пока конвейер не начал смотреть. scripts/check_branch.py требует приставку agent/ и зовётся в .github/workflows/pr-check.yml, то есть имя ветки стало входом обязательной проверки. Правило требует двух вещей, и обе сделаны: приставка названа в CLAUDE.md § Ветки ПЕРВОЙ строкой, и там же сказано, ЧТО СЛУЧИТСЯ при другом имени — «PR check» краснеет, изменение не сливается, пока ветку не переименуют. МОЛЧАЛИВОЙ ОТМЕНЫ НЕТ: открытие изменения по-прежнему идёт с любой рабочей ветки (branches-ignore в agent-pr.yml), а имя вызывает громкий отказ с причиной — именно этим переключатель отличается от проверки (147). Ветки, которые заводит не человек, исключены — chore/metrics и dependabot/**, список общий с гейтом журнала в scripts/checks.py::MACHINE_BRANCHES. Предмет был живой: изменение #134 от dependabot встало намертво, и первым его отверг гейт ЖУРНАЛА, а этот отверг бы следом; у машины нет ни задачи, ни решения, о котором пишут в журнал (051).", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "004": { "mechanism": "code", "where": "ВЕРДИКТ ИСПРАВЛЕН 8 СЕНТЯБРЯ: стояло «обхода объектов не осталось», и это было неверно уже месяц. Сборка обходит четыре проекта из projects.json и по каждому ходит в чужой репозиторий за значками, фактами и выпуском. scripts/build_metrics.py::project_facts — отказ соседа возвращает пустые факты и жалобу, а не роняет прогон; жалобы копятся и печатаются в конце, пересчёт идёт дальше. То же у следа технологий и профильных чисел: отказ печатается командой площадки, картинка не перерисовывается, прежняя остаётся. ПРЕДМЕТ ОПЛАЧЕН ТРЕМЯ СУТКАМИ: 7 сентября файл соседа сменил предмет, витрина ответила «значка нет» и сборка встала красной на трое суток — падение целиком там, где правило требует пропустить и продолжить. Починка и есть этот вердикт. ГРАНИЦА ПРАВИЛА НАЗВАНА: пропавший маркер чисел роняет сборку намеренно — там элементы связаны, и продолжать с половиной страницы значило бы показать читателю смесь свежего и вчерашнего. НАБОР ДЕРЖИТ НЕ ВСЁ, и это названо: подделкой проверяется, что третий исход НАЗЫВАЕТ предмет и адрес, — а что пересчёт после него продолжился, держит код (scripts/build_metrics.py::neighbour_facts возвращает факты и находку, а не бросает), и проверено это живым прогоном 7 сентября, а не набором", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "005": { "mechanism": "gate", "where": "scripts/build_metrics.py::patch_readme — числа только между маркерами m:ключ; scripts/build_metrics.py::render_featured — числа баннера тоже измерены, а не вписаны: они приходят из project_stats и попадают в подпись картинки той же сборкой; scripts/build_metrics.py::project_badges — версия, состояние CI, покрытие и версия пакета измеряются сборкой и рисуются ею же: чужих бейджей на витрине не осталось, а значит не осталось и второго источника тех же чисел; подпись «data as of» под плитками пишется сборкой, а не рукой: scripts/build_metrics.py::render_projects. Это день, когда числа последний раз ИЗМЕНИЛИСЬ, и .github/workflows/metrics.yml исключает саму подпись из сравнения — иначе прогон открывал бы изменение ежедневно, и «числа изменились» перестало бы что-либо значить", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "006": { "mechanism": "document", "where": "CLAUDE.md § Окно — срок жизни три–пять дней, дальше эстафета ссылками. РАЗЛОЖЕНО НАДВОЕ по 182, и МЕХАНИЗМА НЕТ ни у одной половины: и возраст окна, и число переданных эстафет живут в реестре сессий, то есть вне репозитория. Проверить отсюда можно лишь наличие абзаца о сроке — то есть что правило ОБЪЯВЛЕНО, а не что соблюдается; такой гейт зеленел бы вокруг объявления и держал бы не то (146). Перезапуск делает владелец, и это остаётся его решением, а не механизмом. ДЕРЖИТСЯ ЗАПИСЬЮ: CLAUDE.md § Окно. ПЕРЕВЕДЁН ИЗ «ДЕРЖИТСЯ НИЧЕМ» 7 СЕНТЯБРЯ, И ЭТО НЕ ПОЯВЛЕНИЕ МЕХАНИЗМА, А ИСПРАВЛЕНИЕ ВЕРДИКТА: требование записано в читаемом месте и читается ПЕРЕД делом, а вердикт говорил «ничем» — то есть занижал существующее. Разница не косметическая: «ничем» значит, что никто не напомнит, «документом» — что напомнит текст, который окно обязано прочесть. Гейта по-прежнему нет, и половина, которой он невозможен, названа выше.", "holdable": "no", "awaiting": "", "analysed": "", "decided": "" }, "008": { "mechanism": "gate", "where": "scripts/check_page.py::audit_page — сворачиваемый блок на витрине отвергается дословным вхождением тега: он либо есть, либо нет, гадать не о чем. До 27 августа запрет держался абзацем в CLAUDE.md, хотя раздел под ним четыре обзора подряд назвали «обещанием без продолжения». README.md — блоков нет", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "009": { "mechanism": "gate", "where": "scripts/checks.py — свёртка записей по имени одна на весь репозиторий; scripts/check_mechanisms.py::check_run_readers отвергает скрипт, который тянется к записям проверок, не взяв этот модуль. РАЗОБРАНО НАДВОЕ по 182: судить, правильно ли свёрнуты записи, машина не может — это смысл выражения; увидеть, что файл читает записи проверок мимо общего модуля, может, и это ровно тот признак, по которому второй потребитель отличался от первого. ЦЕНА ПЛАЧЕНА ДВАЖДЫ: «32 проверки на PR» вместо шестнадцати прожили сутки и пять сторонних обзоров, а во второй раз правило было известно и помечено действующим — и всё равно нарушено конвейером слияния, потому что свёртка жила выражением внутри функции (090). ЧИТАТЕЛЕЙ СЕЙЧАС НЕТ ВОВСЕ: слияние отдано автомержу площадки. Гейт стоит против завтрашнего кода, и это названо, а не выдано за сегодняшнюю находку.", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "010": { "mechanism": "gate", "where": ".github/workflows/automerge.yml — автомерж не включается, пока в защите main нет обязательной проверки: без неё изменение «чисто» сразу и сливается на пустом списке", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "011": { "mechanism": "gate", "where": ".github/workflows/metrics.yml — суточное расписание вместо цикла опроса API; .github/workflows/automerge.yml — подписка на события изменения вместо ежечасного обхода. Прежний ответ называл прогон слияния по расписанию, который и был тем самым ежечасным опросом; вместе с ним ушёл и предмет; scripts/check_mechanisms.py::audit_workflows отвергает цикл ожидания в шаге: sleep в цикле означает, что прогон держит исполнителя и опрашивает чужой сервер вместо того, чтобы дождаться события или расписания", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "012": { "mechanism": "document", "where": "ВЕРДИКТ ИСПРАВЛЕН 8 СЕНТЯБРЯ: стояло «участник один: чужих веток, в которые можно было бы вписаться, здесь не бывает». Они бывают, и перечислены поимённо в scripts/checks.py::MACHINE_BRANCHES — dependabot/** и chore/metrics: их ведут обновлятель зависимостей и суточный прогон, а не окно. ПРЕДМЕТ ОПЛАЧЕН: 7 сентября окно нажало «обновить ветку» на изменении #134 от dependabot — то есть внесло правку в чужую ветку, — и созданный коммит слияния с автором-окном заставил односторонний довод гейта журнала потребовать запись о решении у изменения, где правку сделал бот. Чинилось это на нашей стороне (scripts/check_journal.py::audit перестал считать слияние авторством), но причина была в том, что тронули не свою ветку. ДЕРЖИТСЯ ЗАПИСЬЮ, И ГЕЙТА НЕТ: CLAUDE.md § Ветки называет машинные приставки и то, что задачи и решения у них нет. Запретить окну пуш в чужую ветку отсюда нечем — это право площадки, а не содержимое репозитория; часть чужих веток закрыта правами (правку прогонов окну не дают, 403), и это защита площадки, а не наша. Половина, которой механизма нет, названа, а не выдана за покрытую", "holdable": "conditional", "awaiting": "первый хук окна у витрины: каталога хуков в дереве нет вовсе", "analysed": "", "decided": "" }, "014": { "mechanism": "gate", "where": "HISTORY.md § Август 2026 · страница стала измеряемой — каждый гейт нарочно ломался обратно в свой исходный дефект при сохранённых именах, и самопроверка обязана была покраснеть. Меняется ровно одна переменная: поведение убрано, имена на месте. ПОДНЯТО ДО ГЕЙТА 28 августа: scripts/check_mechanisms.py::audit_voice требует отрицательный набор у КАЖДОГО скрипта, способного отвергнуть, а ::audit_harness — чтобы .github/workflows/pr-check.yml этот набор ЗВАЛ. Раньше здесь стояло «мутации прогоняются рукой при заведении гейта, в конвейере их нет» — и цена этого измерена в тот же день: scripts/checks.py получил самопроверку и в прогон вписан не был, что заметилось глазами. Мутация по-прежнему рукой: подменить поведение при сохранённых именах машина не умеет, и это остаток правила, а не всё оно", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "016": { "mechanism": "gate", "where": "scripts/check_mechanisms.py::silent_truncation — срез с числовой границей ВНУТРИ f-строки отвергается: урезанный вывод обязан говорить, что он урезан. РАЗОБРАНО НАДВОЕ по 182: судить, «сказано ли о непоказанном» по смыслу фразы, машина не может, но увидеть обрезку на пути к читателю — может. ПРЕДМЕТ БЫЛ ЖИВОЙ И БОЛЬШОЙ: восемь мест резали голым срезом — scripts/check_author.py (тема коммита, дважды), scripts/check_bindings.py (причина вердикта на 96 знаках в --queue и перечень найденного), scripts/check_labels.py (заголовок изменения и две колонки набора), scripts/gh_outcome.py (текст чужого отказа). Читатель видел законченную фразу, не зная, что у неё есть продолжение. Все переведены на общий помощник scripts/checks.py::clip и ::tail — знак обрыва один на дерево (090), у перечня называется ЧИСЛО непоказанного, а не многоточие. Граница гейта названа: срез вне вывода (короткий хеш, разбор записи) не предмет, срез переменной длины не виден и молчится, а не гадается.", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "017": { "mechanism": "code", "where": "ВЕРДИКТ ИСПРАВЛЕН 8 СЕНТЯБРЯ: стояло «своей квоты витрина не расходует», и это верно как факт, но отвечает не на тот вопрос. Правило про ДИАГНОСТИКУ: она начинается с фактов о квоте, а не с гипотез о причинах. МАШИННАЯ ПОЛОВИНА: scripts/gh_outcome.py::classify узнаёт исчерпанный бюджет по сообщению самой площадки и относит его к отказу ИСТОЧНИКА, а не к поломке изменения. Догадок здесь нет — ответ площадки читается как есть, и набор прогоняет настоящие её ответы, а не пересказ. ЧЕЛОВЕЧЕСКАЯ ПОЛОВИНА ОПЛАЧЕНА ЗАМЕРОМ, и он записан рядом с кодом: задача #64 предлагала кэшировать запросы сборки, считая её причиной исчерпанного бюджета. Замер премису ОТВЕРГ — расход сборки единицы запросов за прогон, а бюджет тратили прогоны конвейера на личном токене владельца; это разные бюджеты. Замечание живёт в scripts/build_metrics.py у кода, который считали виновным. Пробел назван: пересчитывать остаток никто не ходит, потому что запросов десяток за прогон и упереться в лимит нечем; появится нужда — появится и замер", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "021": { "mechanism": "gate", "where": "CLAUDE.md § Язык артефактов делит по читателю: README.md посетителю профиля, .rules/README.md, .rules/roles.md, HISTORY.md и сам CLAUDE.md — владельцу и окну. Прежнее «делить нечего» считало три файла там, где их восемь; scripts/check_page.py::audit_service и ::audit_page — язык проверяется долей кириллицы, порог 50% и он не выдуман: замер 27 августа дал 6% у витрины и 70–94% у служебных файлов, то есть нарушение даёт значение у другого края, а не рядом с порогом. Отдельно проверяется наличие русского раздела: без него страница перестаёт быть двуязычной, а это обещание свода", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "022": { "mechanism": "gate", "where": "CLAUDE.md § Источники истины — таблица «тема → канонический документ», по одному месту на тему; README.md § По-русски — пересказ, а не вторая редакция; scripts/check_mechanisms.py::audit_runners — версия языка задана в ОДНОМ месте на все прогоны: две разные означали бы, что проверки и сборка бегут на разных языках, и разошлись бы они молча. Раньше держалось чтением восьми файлов глазами", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "023": { "mechanism": "document", "where": "README.md — витрина держится в пределах страницы, подробности по ссылкам в проекты", "holdable": "no", "awaiting": "", "analysed": "", "decided": "" }, "025": { "mechanism": "gate", "where": "scripts/check_page.py::NUMBERED_TRACKER — ссылка на НОМЕРНУЮ задачу или изменение в тексте витрины отвергается: номер ничего не говорит постороннему читателю и устаревает вместе с задачей. Адрес трекера целиком законен и проверяется отдельным случаем набора — запрещён номер, а не адрес. README.md — номерных ссылок нет", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "026": { "mechanism": "gate", "where": "ВЕРДИКТ ИСПРАВЛЕН 8 СЕНТЯБРЯ: стояло «находок как учитываемых единиц нет», и это было ложно с 3 сентября. Единица здесь — ОТВЕТ ПО ПРАВИЛУ, и отклонение у него записывается: статус rejected несёт причину, а ответ «предмета нет» обязан назвать своё опровержение полем refuted_by — образец пути, чьё появление делает ответ ложным. Держит scripts/check_bindings.py::refuted: ответ без опровержения попадает в очередь на перечитывание, а опровержение, которое СБЫЛОСЬ, краснеет. Вторая половина — отклонённая находка внешнего разбора — держится записью, а не гейтом, и это названо: тела изменений #147 и #174 перечисляют, чего из чужого разбора не взято и почему, чтобы отвергнутое не переоткрывали. Судить полноту такого перечня машине нечем (правило 057)", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "027": { "mechanism": "code", "where": "ВЕРДИКТ ПЕРЕПИСАН 8 СЕНТЯБРЯ: он держался разделом страницы, который убрала своя же правка #158, — то есть разделом, которого нет. Предмет при этом никуда не делся, и мест у него теперь больше: scripts/build_metrics.py::render_profile_cards на пустом наборе печатает «Profile cards are not available: every source declined», а не пустоту между маркерами; ::project_badges отвечает по показателю «нет» С ПРИЧИНОЙ, и гейт ::check_badges отвергает отказ без причины; подпись «data as of <дата>» под плитками — это и есть «здесь сейчас пусто, дата»; в таблице снимков HISTORY.md прочерк объявлен словами как «не измеряли, а не ноль». СИЛЬНЕЕ ВСЕГО ЭТО ДЕРЖИТ scripts/build_metrics.py::unanswered: пустая метрика роняет сборку, потому что «ноль» и «источник промолчал» — разные вещи, и показать второе как первое значит соврать молча", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "029": { "mechanism": "document", "where": "CLAUDE.md — свод устроен ровно по этому правилу и говорит об этом во врезке под заголовком: «здесь компактные триггеры и ссылки на канон, а не пересказ», со ссылкой на 029. АДРЕС — ЦИТАТА, А НЕ НОМЕР СТРОКИ: здесь стояло «третьей строкой», и к 8 сентября это была уже четвёртая. Позиционная ссылка стареет от любой правки выше себя и не опровергается ничем — ни гейтом, ни глазами. Семнадцать ссылок в каталог, таблица «Источники истины» отправляет за деталями в README.md, .rules/README.md, .rules/roles.md и HISTORY.md, а не пересказывает их. ВЕРДИКТ ИСПРАВЛЕН 3 СЕНТЯБРЯ: стояло «входного свода у витрины нет: единственный файл-вход — README», и это устарело молча — свод появился с #22, а ответ остался прежним. Опровергалось одной командой (правило 175): файл CLAUDE.md лежит в корне.", "holdable": "no", "awaiting": "", "analysed": "", "decided": "" }, "030": { "mechanism": "gate", "where": "changelog.d/ — файл на изменение вместо строки в общем журнале; scripts/collect_changelog.py собирает их в CHANGELOG.md и проверяет формат (--check в pr-check.yml, --selftest там же); scripts/check_journal.py принимает фрагмент как запись о правке поведения. ПРЕЖНИЙ ОТВЕТ БЫЛ «предмета нет»: считалось, что фрагменты окупаются лишь при параллельных ветках, «а тут один автор и одна ветка». 8 сентября это опровергли дважды — две ветки подряд встали с конфликтом в HISTORY.md, потому что обе дописывали свой раздел в конец. Довод перестал быть верным раньше, чем его перечитали, и нашёл это гейт по полю refuted_by, а не глаза", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "032": { "mechanism": "gate", "where": ".github/workflows/pr-check.yml — вывод о работоспособности делается прогоном вхолостую, а не чтением кода", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "038": { "mechanism": "document", "where": "CLAUDE.md § Окно — имя начинается с окружения, витрину ведут только облачные окна: [WEB]. РАЗЛОЖЕНО НАДВОЕ по 182, МЕХАНИЗМА НЕТ ни у одной половины: признак окружения лежит в реестре сессий, а имя окна не попадает в репозиторий ни одним следом — ни коммитом, ни телом изменения. Сверять нечего и не с чем, и это не «дорого», а буквально нет данных. ДЕРЖИТСЯ ЗАПИСЬЮ: CLAUDE.md § Окно. ПЕРЕВЕДЁН ИЗ «ДЕРЖИТСЯ НИЧЕМ» 7 СЕНТЯБРЯ, И ЭТО НЕ ПОЯВЛЕНИЕ МЕХАНИЗМА, А ИСПРАВЛЕНИЕ ВЕРДИКТА: требование записано в читаемом месте и читается ПЕРЕД делом, а вердикт говорил «ничем» — то есть занижал существующее. Разница не косметическая: «ничем» значит, что никто не напомнит, «документом» — что напомнит текст, который окно обязано прочесть. Гейта по-прежнему нет, и половина, которой он невозможен, названа выше.", "holdable": "no", "awaiting": "", "analysed": "", "decided": "" }, "039": { "mechanism": "gate", "where": "scripts/build_metrics.py::main — «числа не изменились» отличается от отказа источника; scripts/build_metrics.py::_count — курсорная разбивка это третий исход, а не единица: посчитать нельзя, и сборка падает вместо правдоподобного числа; .github/workflows/rules-inbox.yml — очередь, чистота и неотработавший механизм разведены кодами; scripts/gh_outcome.py::classify — у обращения к площадке три исхода: ответил, не ответил (бюджет запросов, сеть, пятисотка), ответил отказом. Второй это предупреждение, третий красное, и неразобранный отказ тоже красное — молчаливый пропуск на незнакомом делает сломанный конвейер похожим на работающий; .github/workflows/automerge.yml — каждый шаг с `gh` разбирает исход этим разбором, а не считает любой отказ поломкой изменения; scripts/check_mechanisms.py::audit_scripts держит это гейтом у КАЖДОГО скрипта: докстрока обязана объявить все три исхода. Спрашивается только у тех, чей вход способен вернуть ненулевой код — модуль без входа исходов не имеет вовсе, а разбор, всегда возвращающий ноль, объявляет ровно один, и требовать от них трёх значило бы требовать неправды. Первый черновик отверг оба, и это был ложный отказ; третьего исхода у четырёх проверок НЕ БЫЛО ВОВСЕ — гейт нашёл это первым же прогоном: scripts/build_metrics.py, scripts/check_bindings.py, scripts/check_labels.py и scripts/check_roles.py возвращали только 0 и 1, а отказ источника поднимался трассировкой и выходил тем же кодом 1, каким сообщают о своей находке. Ровно это случилось 28 августа, когда каталог унёс значки на отдельную ветку: HTTP 404 прочиталось как «метрика не собралась». Теперь у каждой есть код 2 с названной причиной, и у сборки он подтверждён живым отказом — оборванная сеть даёт 2, а не трассировку Отказ несёт АДРЕС источника, а не только код: scripts/build_metrics.py::naming прикрепляет его в точке обращения, и подставной отказ прогоняется через guarded целиком — вместе с кодом возврата и напечатанным. До 31 августа третий исход называл причину без предмета, и окно дважды искало отказавший источник подменой urlopen. scripts/hold.py::checks_state — состояние проверок у изменения тоже три исхода, а не два: «провалов нет» и «зелено» разошлись бы на изменении, где все прогоны отменены (первый head #103), и ложное «зелено» слило бы непроверенное. ТРЕТИЙ ПРЕДМЕТ ДОБАВЛЕН 7 СЕНТЯБРЯ И ОПЛАЧЕН ТРЕМЯ СУТКАМИ ПРОСТОЯ: расхождение ответа витрины с живым источником соседа было четвёртым исходом, склеенным со вторым — «данные разошлись» роняло сборку так же, как своя поломка. Глоссарий вернул значок покрытия на место, наш ответ «значка нет» устарел, и 5–7 сентября не пересчитывались ВСЕ числа витрины, не публиковались картинки и краснел сторож свежести — из-за правки, верной на той стороне. Теперь scripts/build_metrics.py::project_badges копит находки о соседях: суточный пересчёт печатает их предупреждением площадки и досчитывает, --check краснеет и возвращает 1.", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "041": { "mechanism": "gate", "where": "scripts/build_metrics.py::main — метрика без данных не публикуется, сборка падает; на плитке подписано, какое покрытие показано", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "044": { "mechanism": "gate", "where": "scripts/check_bindings.py — премиса вердикта проверяется до того, как ему поверят: каждое «чем держится» и каждое «этого у нас нет» есть утверждение о текущем коде, и каждая ссылка в них сверяется с файлом и объявленным именем; отказ от чужих бейджей — та же премиса: утверждение, которое из окна не проверить, витрина на себя не берёт (прокси не пускает img.shields.io, и живой бейдж от опечатки в ссылке неотличим); scripts/build_metrics.py::verify_absence — премиса отказа по показателю проверяется до того, как её покажут: «релизов нет» у каталога правил оказалось неверным, релиз был помечен предварительным и не попадал в latest", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "045": { "mechanism": "gate", "where": "scripts/build_metrics.py::rules_export и .github/workflows/metrics.yml — отказ источника роняет сборку, отсутствие токена владельца тоже отказ, а не тихий переход на штатный", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "046": { "mechanism": "gate", "where": "README.md — песочница названа частичной на Windows со ссылкой на таблицу асимметрии; пробелы конвейера названы в .rules/README.md; projects.json — у каждого проекта пробел по показателю назван поимённо и с причиной, а не оставлен пустым местом; scripts/build_metrics.py::check_badges держит это гейтом; scripts/build_metrics.py::verify_absence проверяет однозначное — выпуски, прогоны, ветку badges и значок версии. НАЗВАННЫЙ ПРОБЕЛ ЗАКРЫЛСЯ 27 августа: непроверяемым был «пакет», у которого при отказе не объявлено имя, и спросить PyPI было не о чем. Пакет перестал быть отдельным показателем — он вошёл в «release», а отсутствие выпуска проверяется площадкой. Пробел ушёл вместе с предметом, а не с формулировкой; форма отказа по «ci» уточнена 28 августа: причина обязана назвать прогоны-исключения ПОИМЁННО, и scripts/build_metrics.py::verify_absence сверяет, что других нет. Прежний критерий «прогонов нет вовсе» сломался от чужой правки — сосед Claude-Code_Usage-Token подключился к каталогу правил, и у него завёлся rules-inbox: утверждение осталось верным по сути и стало ложным по букве. Судить, что делает чужой прогон, машина не берётся — это чтение смысла; она сверяет имена. ВТОРОЙ ПРЕДМЕТ ПОЯВИЛСЯ 10 СЕНТЯБРЯ, И ОН ПРО САМ СОСТАВ: projects.json — репозиторий владельца, который на витрину не берут, называется в поле not_shown с причиной, а пустая причина ответом не считается. Держит scripts/build_metrics.py::unlisted_repos, и отказ у него двусторонний — незаявленный репозиторий и отговорка, пережившая предмет. Оплачено пятым проектом: Engineering-Pipeline-Mechanisms открылся 9 сентября, к вечеру отвечал каталогу на все 195 правил, и на витрине его не было — заметил человек, а не механизм", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "047": { "mechanism": "document", "where": "CLAUDE.md § Окно — прямой цитатой: сменились правила работы, окна перезапускаются. Этот файл читается один раз, при старте", "holdable": "no", "awaiting": "", "analysed": "", "decided": "" }, "049": { "mechanism": "pipeline", "where": ".github/workflows/automerge.yml — что готово к слиянию, решает площадка по живому состоянию изменения и обязательным проверкам, а не отдельный реестр; scripts/build_metrics.py::stamp_assets — адрес картинки несёт отпечаток её содержимого. Прокси площадки кэширует по адресу, и без отпечатка страница показывала вчерашнюю картинку при верном репозитории: состояние выводилось не из живого артефакта, а из кэша", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "050": { "mechanism": "gate", "where": "scripts/build_metrics.py — FOCUS_LIMIT с пометкой «двигается только вниз», scripts/build_metrics.py::FEATURED_ACCENTS с той же пометкой и projects.json:featured_limit", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "051": { "mechanism": "gate", "where": "scripts/check_labels.py::outcome — внешнему участнику предупреждение, владельцу отказ; до 27 августа это утверждение держалось ЧТЕНИЕМ кода: ветка жила в main рядом с обращением к площадке и не выполнялась ни разу. Теперь прогоняется самопроверкой по каждому исходу, включая регистр логина. Мягко к внешнему потому, что прав ставить метки в чужом репозитории у него нет, и отказ требовал бы невозможного; .github/workflows/automerge.yml — ненастроенная защита это предупреждение, а не красный прогон; исчерпанный бюджет запросов тоже: он состояние источника, а не поломка изменения, и красное на нём запирало изменение в `unstable`, откуда автомерж уже не включить (scripts/gh_outcome.py::SOURCE_SILENT)", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "056": { "mechanism": "document", "where": ".rules/README.md § Что означают статусы и чего не означают — статусы этого файла читает не только витрина: они и есть её ответ по контракту потребления каталога, и вторая половина у них теперь написана", "holdable": "no", "awaiting": "", "analysed": "", "decided": "" }, "057": { "mechanism": "gate", "where": "scripts/build_metrics.py::check_focus_limit — «Current focus» назван единственным ручным блоком; CLAUDE.md § Критические запреты — из восьми шесть держат гейты, два названы непроверяемыми поимённо: «несколько тем в одном PR» и «утверждение, которого читатель не может проверить». 27 августа список сократился с четырёх непроверяемых до двух — scripts/check_page.py забрал спойлер, чужие картинки и гифки. Названное непокрытие уменьшается работой, а не переформулировкой; scripts/check_mechanisms.py::audit_gaps — правило впервые применено к САМОМУ ответу каталогу, а не к прозе витрины: вердикт со статусом active и механизмом none обязан сказать, ПОЧЕМУ механизма нет. Гейт при этом не судит, верна ли причина, — только что она названа: отличить «нечем проверить» от отговорки машина не может, и делать вид, что может, значило бы получить зелёное вокруг ложного основания; .rules/roles.md § Что спрашивает машина — у профиля «вторая витрина» критерий назван непроверяемым поимённо: «пересказ или вторая редакция» это суждение о смысле, и гейт вокруг него зеленел бы на объявлении (146); .rules/README.md § Чем держится правило — остаток назван поимённо: машина видит, что шаг блокирующий, но не то, на чём именно он краснеет, и «падает ли прогон ИМЕННО при нарушении» проверяется чтением", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "058": { "mechanism": "code", "where": "ВЕРДИКТ ИСПРАВЛЕН 8 СЕНТЯБРЯ: стояло «счётчиков квоты витрина не трогает». Трогает — и ведёт себя ровно так, как требует правило. scripts/gh_outcome.py::classify относит исчерпанный бюджет к отказу источника, а .github/workflows/automerge.yml делает из этого ПРЕДУПРЕЖДЕНИЕ и останавливается, а не повторяет попытку (#67: исчерпанный бюджет — предупреждение, а не тупик). ПОВТОРОВ У СБОРКИ НЕТ ВООБЩЕ: отказ источника роняет прогон или копится находкой, но второй попытки не делает никогда — проверено обходом всех обращений через scripts/build_metrics.py::_api. Полусделанных состояний за отбитым вызовом тоже нет: сборка ничего не публикует до конца пересчёта", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "062": { "mechanism": "gate", "where": ".rules/roles.md § Кто за что — у каждой роли свой вопрос, свой артефакт и своё возражение; профили заведены профилями именно потому, что своего возражения у них нет; scripts/check_roles.py держит это гейтом: у файла есть ведущий, а у строки — артефакт, и обе стороны проверяются перебором", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "063": { "mechanism": "gate", "where": ".github/workflows/automerge.yml — автомерж включается по конъюнкции: не черновик И открыт И без метки hold И защита с обязательной проверкой есть; каждый пропуск назван причиной", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "064": { "mechanism": "gate", "where": "scripts/check_labels.py в pr-check.yml — PR без метки содержания не проходит; метки конвейера (hold) классификацией не считаются; scripts/check_labels.py::label_for_subject — метку содержания выводит таблица по типу коммита, а не догадка в шаге прогона; неразобранный тип даёт пустую метку и предупреждение, потому что неверная метка хуже отсутствующей. .github/workflows/agent-pr.yml ставит hold сразу: стоп-кран принадлежит конвейеру, а не автору, и без него изменение, открытое пушем, поехало бы в main раньше срока; scripts/check_labels.py::derive_zones через `--zones-for-diff` — метку зоны прогон `agent-pr` выводит из списка изменённых файлов ТЕМ ЖЕ разбором, которым гейт потом проверяет полноту (правило 090: одна таблица на обе стороны). Замер, из-за которого это появилось: четыре изменения подряд краснели на недостающей `github_actions`, притом что она выводится из путей механически — метку требовал механизм, а ставил человек, то есть ровно признак 064 на самом гейте 064; .github/workflows/release-hold.yml снимает ТОЛЬКО метку конвейера hold и только по сигналу из тела изменения. Метки содержания принадлежат автору и этим прогоном не трогаются; решение читается из тела, которое правит человек, а не из метки, которую правят обе стороны — иначе механизм спорил бы с человеком; таблица типов знает и `chore`: вывод сделан ЗАМЕРОМ — все три chore-изменения витрины (#79, #42, #39) помечены documentation рукой, а четвёртое (#87) открылось без метки вовсе, потому что тип не разбирался. Вывод опровержим, и это сказано у таблицы: появится chore, не трогающий документацию, — поймает полнота по зонам", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "067": { "mechanism": "code", "where": "ВЕРДИКТ ИСПРАВЛЕН 8 СЕНТЯБРЯ: стояло «ресурсов, за которыми нужно убирать, скрипт не захватывает». Предмет есть, и он именно тот, о котором правило: scripts/collect_changelog.py пишет CHANGELOG.md, а потом удаляет фрагменты — между двумя действиями возможно ПОЛУСДЕЛАННОЕ состояние (журнал пополнен, часть файлов ещё на месте). ПРАВИЛО СОБЛЮДЕНО ГЛАВНЫМ: отказ ПРОБРАСЫВАЕТСЯ и не превращается в успех — оба действия стоят в одном try, а except печатает причину командой площадки и возвращает 2, то есть третий исход, а не ноль. Так же устроен scripts/build_logo.py: каждый его перехват называет предмет и возвращает 2. Узкого перехвата, пропускающего прерывание, нет — ловятся OSError и ValueError, а KeyboardInterrupt и SystemExit проходят насквозь. ОСТАТОК НАЗВАН: отката полусделанного состояния нет, есть только отчёт о нём. Заводить откат ради двух файлов дороже, чем перезапустить сборку фрагментов, — и это решение, а не недосмотр", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "068": { "mechanism": "gate", "where": "scripts/check_labels.py::CONTENT — классификацией считается только перечисленное; запретительный «всё, кроме hold» пропускал семь меток из двенадцати; scripts/build_metrics.py::BADGE_KINDS — обязательные показатели проекта тоже список разрешённого: показатель вне его называется в отказе, а опечатка в required_badges не снимает требование тихо", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "069": { "mechanism": "pipeline", "where": "ВЕРДИКТ ИСПРАВЛЕН 8 СЕНТЯБРЯ: стояло «общего хранилища с параллельной правкой нет». Оно есть — .rules/bindings.json, — и писателей у него ДВОЕ: суточная сборка дописывает новые правила статусом unreviewed и поднимает answers_to (scripts/build_metrics.py::sync_bindings), окно правит вердикты. И сборка пишет именно СНИМОК: читает файл целиком, меняет поля, кладёт целиком обратно — ровно то, от чего правило предостерегает. ЗАЩИТА ЗДЕСЬ ДРУГАЯ, ЧЕМ ПРЕДПИСЫВАЕТ ПРАВИЛО, И ЭТО НАЗВАНО: писателей разводит не запись по полю, а очередь изменений — сборка не пушит в общую ветку, она ОТКРЫВАЕТ изменение (.github/workflows/metrics.yml), и устаревший снимок сталкивается с чужой правкой как конфликт слияния, то есть громко. Правило запрещает молчаливую потерю чужих правок; конфликт молчаливым не бывает. Цена решения названа: конфликт стоит окну разбора, а запись по полю не стоила бы ничего. Менять устройство ради этого не стали — конфликтов на этом файле за три недели не было ни одного", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "070": { "mechanism": "code", "where": "ВЕРДИКТ ИСПРАВЛЕН 8 СЕНТЯБРЯ: стояло «эвристик в проверках нет: всё, на чём падает сборка, достоверно» — и это опровергается собственным гейтом. scripts/check_page.py::cyrillic_share судит язык файла ДОЛЕЙ КИРИЛЛИЦЫ, порог CYRILLIC_SHARE = 50 — это догадка по природе, и она сделана ровно так, как требует правило: порог не выдуман, а взят замером 27 августа (6% у витрины против 70–94% у служебных файлов), то есть нарушение даёт значение у другого края, а не рядом с порогом; остаточный риск назван письменно в докстроке. Вторая эвристика — scripts/build_metrics.py::signed_commits: машинные пересборки отделяются по заголовку и автору, и ошибка здесь занизила бы долю, а не завысила. Под обеими стоят настоящие барьеры: язык файла судит ещё и наличие русского раздела, подпись коммита — гейт scripts/check_author.py, который догадок не строит", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "071": { "mechanism": "gate", "where": ".github/workflows/pr-check.yml — единственный сознательный дубль витрины подписан прямо в коде: список конвейерных меток повторён в условии шага, потому что условие не умеет звать питон, и подпись называет и с кем дублируется, и чем оплачено — расхождение с PIPELINE в scripts/check_labels.py отвергает scripts/check_mechanisms.py::audit_pipeline_labels. ВЕРДИКТ ИСПРАВЛЕН 3 СЕНТЯБРЯ: стояло «сознательных дублей в коде нет: скрипт один», и обе половины устарели — скриптов тринадцать, а дубль появился вместе с гейтом меток. Опровергалось одной командой (правило 175): ls scripts/*.py.", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "072": { "mechanism": "gate", "where": "ВЕРДИКТ ИСПРАВЛЕН 8 СЕНТЯБРЯ, И ИСПРАВЛЕН ИНЦИДЕНТОМ. Прежде здесь стояло «пары «до и после» нет: витрина ничего не запускает и следа в среде не оставляет, её единственный артефакт — переписанная страница, и его сверяет тот же гейт до записи». Вывод был верен наполовину. ПРИЧИНУ ЛОВИТ ГЕЙТ: scripts/build_metrics.py --check собирает страницу в память и проверяет НАМЕРЕНИЕ — маркеры чисел на месте, источники живы, alt проставлен каждому; scripts/check_bindings.py, ::check_page, ::check_mechanisms и остальные делают то же со своими предметами. Всё это до прогона. ФАКТ НЕ ПРОВЕРЯЕТ НИКТО, и это доказано изменением #185: область m:profile-cards уехала в общую ветку ПУСТОЙ, витрина осталась без блока статистики целиком, а гейт был зелёным — он смотрел на генератор, а страницу в этот момент определял коммит. Ровно тот случай, о котором правило: гейт слеп там, где видит фикстура. Фикстуры нет — пробел назван, механизм для него заводится отдельной работой", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "073": { "mechanism": "gate", "where": ".github/dependabot.yml — SHA-пины действий двигает он; версия Python задана в одном месте; scripts/check_mechanisms.py::audit_runners — единственность версии языка проверяется перебором прогонов, а не чтением. ВТОРОЙ ГЕЙТ ПОЯВИЛСЯ 8 СЕНТЯБРЯ: scripts/check_mechanisms.py::pinned_actions требует SHA у каждой ссылки `uses:` — dependabot двигает пины, но не мешает прийти новой строке по метке, а метка переезжает на другой код молча и у чужого владельца. Критерий «действия прибиты по SHA» объявлен профилем «зависимости и внешний артефакт» в .rules/roles.md и до того держался чтением: двадцать пять ссылок сверены разово и вручную", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "074": { "mechanism": "gate", "where": ".github/workflows/pr-check.yml — обязательная проверка, которую защита main требует зелёной; правила ветки читаются по адресу https://api.github.com/repos/ArtVsMark/ArtVsMark/rules/branches/main и там же видно, что force-push и удаление запрещены. защита main: обязательная проверка PR check плюс запрет force-push и удаления — слияние необратимо, историю main не переписываем", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "075": { "mechanism": "gate", "where": "scripts/build_metrics.py::patch_readme — пропавший маркер и недопроставленный alt роняют сборку; scripts/build_metrics.py::unanswered — молчание источника роняет её же, и проверяются сырые значения: сторож, стоявший после форматирования, пропускал «0000+ tests»; scripts/check_roles.py::audit — строка покрытия, под которую не нашлось ни одного файла, роняет проверку: пласт назван, а предмета у него нет", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "078": { "mechanism": "gate", "where": "ВЕРДИКТ СМЕНИЛСЯ ПРИ ПЕРЕЧИТЫВАНИИ ПО КОНТРАКТУ 1.2, и сменил его гейт, а не глаза: scripts/check_mechanisms.py::claimed_rules нашёл, что scripts/hold.py объявляет это правило своим при ответе «предмета нет». Прежний ответ смотрел на БУКВУ — «терминальных статусов витрина не публикует», и это верно, — и не заметил ОБОБЩЕНИЯ, которым механизм уже пользовался (184). Держится scripts/hold.py::NOT_A_FAILURE: отменённое не провал, пропущенное тоже, но и успехом ни то ни другое не считается — для «зелено» нужен хотя бы один настоящий успех. Разница не косметическая: отменённая запись слияние ДЕРЖИТ, пропущенная его отпускает (187).", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "080": { "mechanism": "pipeline", "where": ".rules/proposals.json — инцидент уезжает в каталог ФАЙЛОМ, который каталог опрашивает сам, а не памятью окна: до 27 августа правило держалось шагом процесса, то есть ничем машинным, и цена этого измерена в каталоге — из 143 записей 129 ссылаются на грейдер, 11 на витрину, то есть большинство правил переносило руками то окно, которое случайно оказалось открыто. .rules/README.md § Канал с каталогом — обе половины названы; так уехало 127; .github/workflows/rules-inbox.yml — обратная ссылка достраивает вторую сторону: правило уезжает наверх файлом предложений, а след возвращается в задачу, из которой вырос", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "082": { "mechanism": "gate", "where": "scripts/check_roles.py — таблица покрытия в .rules/roles.md сверяется с деревом в обе стороны: файл без строки и строка без файлов одинаково роняют проверку. Полнота проверяется обходом артефактов, а не чтением списка ролей: обход нашёл пять пластов сверх четырёх записанных по памяти. КРИТЕРИИ ПРОФИЛЕЙ ПЕРЕСТАЛИ ДЕРЖАТЬСЯ ПАМЯТЬЮ 8 СЕНТЯБРЯ: у таблицы профилей появилась колонка «чем держится», и три критерия из четырёх стали механизмами — scripts/build_metrics.py::unreadable (контраст текста в обеих темах), scripts/check_page.py::audit_alt (подпись страницы и подпись внутри картинки), scripts/check_mechanisms.py::pinned_actions (действия по SHA). Пласт с ведущим, но без спрашиваемого критерия — половина покрытия, и вторая половина держалась разовым замером", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "083": { "mechanism": "gate", "where": "ВЕРДИКТ СМЕНИЛСЯ ПРИ ПЕРЕЧИТЫВАНИИ ПО 184. Буква чужая: недетерминированного компонента — запроса к модели — у витрины нет. Обобщение работает целиком: у страницы, которую сборка перерисовывает каждые сутки, нет эталонного ответа, с которым её можно сравнить, — числа меняются законно. Приёмка идёт по СВОЙСТВАМ: scripts/check_page.py::audit_page проверяет то, что обязано выполняться всегда (маркеры на месте, чужих картинок нет, русский пересказ есть), а scripts/build_metrics.py::unanswered — что ни одна метрика не собралась пустой. Эталонный файл здесь краснел бы ежедневно и был бы отключён в первую неделю — ровно тот инцидент, из которого правило выросло. ЗАОДНО ПОЧИНЕНЫ ДЕВЯТЬ ОБЪЯВЛЕНИЙ: они ссылались на 083, имея в виду 158 («третий исход называет предмет отказа»), — гейт нашёл противоречие, а починка легла на сторону кода, а не вердикта.", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "084": { "mechanism": "code", "where": "ВЕРДИКТ ИСПРАВЛЕН 8 СЕНТЯБРЯ: стояло «необязательных каналов нет: всё, что сборка делает, обязательно». Их два, и оба отказывают молча и сразу, не задерживая основную работу. scripts/build_metrics.py::snake_layer — прогон змейки идёт своим расписанием, его ветки может не быть вовсе: тогда полотно рисует собственный календарь, а не пустоту. Профильные числа приходят из /users/* и GraphQL, окну они закрыты (403): карточка не перерисовывается, прежняя остаётся, и отпечаток против кэша для неё не считается. Повторов ни у одного нет — отказ не пробуется второй раз", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "089": { "mechanism": "document", "where": "README.md — ссылки ведут только в источники, обратных из проектов сюда нет", "holdable": "no", "awaiting": "", "analysed": "", "decided": "" }, "090": { "mechanism": "gate", "where": "scripts/build_metrics.py::rules_export — число правил берётся из экспорта каталога, а не из своей копии чужого определения; scripts/checks.py — свёртка по имени вынесена выше подсистем, хотя после ухода конвейера слияния потребитель у неё остался один; .github/workflows/rules-inbox.yml — синхронизация подключена ИНСТРУМЕНТОМ каталога на закреплённом коммите, а не своей копией: копия разъехалась бы с первой правкой на той стороне. ФОРМУЛИРОВКА ИСПРАВЛЕНА 8 СЕНТЯБРЯ: стояло «действием каталога», а зовётся его скрипт — действие закрывает только «входящие», а обратным ссылкам нужен тот же чекаут, и переезд означал бы два чекаута одного репозитория ради одного шага. Прежняя причина отказа от действия (два исхода вместо трёх) на той стороне починена, и это проверено чтением разметки его действия на той стороне — там теперь стоит `|| rc=$?` с объяснением того же дефекта, — а не предположено; scripts/check_labels.py::SUBJECT_LABEL — таблица «тип коммита — метка» лежит рядом с гейтом, который эти же метки проверяет, а не второй копией в .github/workflows/agent-pr.yml; самопроверка сверяет вывод с CONTENT, чтобы прогон не ставил метку, которую гейт отвергает; .github/workflows/main-red.yml — дежурный по общей ветке подключён скриптом каталога на закреплённом SHA, а не скопирован: витрина вынесла механизм НАВЕРХ (playbook#139), вместо того чтобы завести второй такой же у себя; scripts/check_mechanisms.py::audit_sparse — прогон, берущий скрипт точечно (sparse-checkout), обязан взять и то, что этот скрипт зовёт. ИНЦИДЕНТ ЗДЕСЬ ЖЕ, 28 августа: общий помощник разметки был вынесен выше гейтов — верный ход по этому же правилу, — и scripts/hold.py начал его звать; .github/workflows/release-hold.yml забирал ровно один файл, и механизм снятия стоп-крана упал с ModuleNotFoundError на первом же изменении. Правка вызываемого без взгляда на вызывающего — то же рассуждение, что в 152, только в обратную сторону. Причина найдена не чтением: аннотация упавшего шага назвала скрипт, а догадка подтверждена запуском в изоляции; scripts/hold.py::marker_needed — решение о маркере живёт рядом с тем, кто этот маркер читает, а не в шаге прогона: у скрипта есть набор, у шага его не бывает. Заведено 31 августа, когда agent-pr перестал ставить маркер всем подряд.; scripts/check_mechanisms.py::audit_pipeline_labels — копия списка конвейерных меток в условии pr-check.yml сверяется с PIPELINE в scripts/check_labels.py. Копия заведена сознательно: условие работы не умеет звать питон, — и допустима ровно потому, что разойтись молча не может.; scripts/build_metrics.py::grader_facts — тесты, тест-модули, экспериментальные версии Python и проверки на PR читаются из файла фактов, который грейдер публикует о себе сам в ветке badges, а не считаются здесь. До 3 сентября витрина держала копию ЧУЖОГО определения: где у грейдера лежат тесты, как назван его каталог, как устроена матрица в его описании CI. Копия совпадала ровно до первого переноса на той стороне и разошлась бы молча — оба числа правдоподобны. Четвёртое число, проверки на PR, вычислялось медианой по семи последним PR, потому что снаружи точного ответа не видно; у издателя он есть, тем же набором, каким он держит собственный мерж-гейт. Замер перехода: 19 (медиана) против 16 (точно), тест-модулей 246 против 247.. ИНЦИДЕНТ 8 СЕНТЯБРЯ И ГЕЙТ НА НЕГО: адрес сводки каталога был записан ДВАЖДЫ — в сборке метрик и в подсказке соседей, — и копии разъехались на чужой правке: 4 сентября каталог перенёс сводку на ветку badges, сборка узнала об этом 8 сентября, а подсказка осталась с main, где сводки больше нет. Адрес переехал в scripts/checks.py::CATALOGUE_WHERE, третью копию отвергает scripts/check_mechanisms.py::single_catalogue_address — по присвоенной строке со схемой, а не по вхождению имени файла в текст: имя законно стоит в докстроках обоих потребителей", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "091": { "mechanism": "document", "where": "CLAUDE.md § Открытая работа — три источника по порядку, первый непустой и есть план; пустой трекер объявлен нормальным состоянием, иначе порядок ломается на первом шаге", "holdable": "no", "awaiting": "", "analysed": "", "decided": "" }, "093": { "mechanism": "code", "where": "ВЕРДИКТ ИСПРАВЛЕН 8 СЕНТЯБРЯ, И ИСПРАВЛЕН ПОЧИНКОЙ, А НЕ ФОРМУЛИРОВКОЙ. Стояло «известных развилок в одном скрипте нет» — и это отвечало на первую половину правила, умалчивая о второй. Обобщение по третьему случаю предмет имело, и случаев было ПЯТЬ: баннер акцентов, плитка проекта, карточка профиля, след технологий и полотно активности открывались одной и той же строкой, скопированной пятикратно. Третий случай пропустили дважды. ШОВ ВЫНЕСЕН: scripts/build_metrics.py::svg_open — одно определение открывающего тега, и он же ФИКСИРУЕТ КОНТРАКТ, а не просто убирает повтор: подпись обязательна, картинка без aria-label отвергается. Довод не педантский — alt на странице берётся из aria-label самой картинки (::sync_alt), другого источника у него нет, и безымянная картинка оставила бы alt пустым молча. ПЕРВАЯ ПОЛОВИНА ПРАВИЛА ПО-ПРЕЖНЕМУ БЕЗ ПРЕДМЕТА, и теперь по названной причине: шов вводят под ИЗВЕСТНУЮ будущую развилку, а у витрины таких нет — развилка «ещё одна картинка» уже закрыта этим же швом. Появится известная — появится и шов, гадать наперёд правило прямо запрещает. ЧТО ДЕРЖИТ ОБОБЩЕНИЕ ОТ РАСПОЛЗАНИЯ: набор считает копии открывающего тега в рисующей части файла — их обязана быть одна. Первая редакция этой проверки нашла две и была права: второй оказался её собственный образец, и она перестала искать текстом там, где текст не предмет", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "094": { "mechanism": "gate", "where": "ВЕРДИКТ ИСПРАВЛЕН 8 СЕНТЯБРЯ: стояло «слоёв совместимости нет: у витрины нет ни версий, ни потребителей», и это ложно дважды. Версия у витрины есть — schema её ответа каталогу, — и потребитель есть: сам каталог читает .rules/bindings.json и публикует сводку. Совместимость держится ГРАНИЦЕЙ, а не заглушкой: scripts/build_metrics.py::RULES_SCHEMA и ::FACTS_SCHEMA задают мажор, который сборка умеет читать, и чужой мажор её РОНЯЕТ, а не переводится молча. Минор разбирает scripts/build_metrics.py::answered_version, и он же держит правило: номер поднимает перечитывание, а не сборка. Переходного слоя «пока поддержим оба» нет ни у одного контракта — это и есть чистая миграция, которой требует правило", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "097": { "mechanism": "gate", "where": "scripts/check_labels.py::selftest, scripts/check_bindings.py::selftest, scripts/check_roles.py::selftest, scripts/build_metrics.py::selftest — у каждого гейта прогоняются оба класса ошибок: предмет, который он обязан отвергнуть, и предмет, который обязан пропустить. Ложное «прошло» не приходит жаловаться никогда, ложный отказ в конвейере без человека — тоже", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "100": { "mechanism": "gate", "where": ".github/workflows/*.yml — у всех четырёх прогонов timeout-minutes вместо шестичасового умолчания; scripts/check_mechanisms.py::audit_runners — таймаут спрашивается у КАЖДОЙ работы, разбором YAML, а не грепом: `timeout-minutes` встречается и у шага, и грепом одно от другого не отличить. Утверждение «у всех четырёх» устарело в тот же месяц — прогонов стало восемь, и именно поэтому перебор здесь дешевле счёта", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "104": { "mechanism": "gate", "where": ".github/workflows/*.yml — workflow_dispatch у каждого прогона, включая agent-pr.yml; числа прогонов здесь намеренно нет: оно устаревало бы от каждого нового файла; scripts/check_mechanisms.py::audit_runners — ручная кнопка спрашивается у каждого прогона перебором. Числа прогонов в вердикте нет намеренно, и теперь его нет и у механизма: он входит от списка файлов. КНОПКА ЕСТЬ И КНОПКА РАБОТАЕТ — РАЗНЫЕ УТВЕРЖДЕНИЯ, и второе оплачено 10 сентября: agent-pr.yml объявлял вход branch, подставлял его в gh pr list и СЧИТАЛ ГОЛОВУ у $GITHUB_SHA — ref, на котором запущен прогон. От пуша это одна ветка, и десятки прогонов сходились; от кнопки с main выходило «коммитов впереди main: 0 — открывать нечего» на любой ветке. Нашлось в первый же раз, когда кнопка понадобилась по-настоящему: у токена владельца исчерпался бюджет запросов площадки, и пуш изменение не открыл. Починено головой названной ветки; scripts/check_mechanisms.py::dispatch_head отвергает признак — прогон, принимающий ветку входом, не берёт голову у своего ref. ОСТАТОК НАЗВАН: что кнопка действительно открывает изменение, доказывает только запуск, и он остаётся за человеком (146)", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "105": { "mechanism": "document", "where": ".rules/roles.md § Почему ревизор — роль, а не «повнимательнее» — второй проход своим же инструментом повторяет собственные слепые пятна, поэтому вопрос «сходится ли объявленное с фактическим» вынесен в отдельную роль; живого второго участника нет, часть работы переведена в гейты, остальное играет владелец. РАЗЛОЖЕНО НАДВОЕ по 182: половина «роль объявлена и у неё есть артефакт» УЖЕ держится — scripts/check_roles.py сверяет таблицу покрытия пластов с деревом в обе стороны. У половины «аудит выполнен ЧУЖИМИ глазами» МЕХАНИЗМА НЕТ: кто смотрел, в репозитории не записано, а проверять наличие строки о ревизоре значило бы зеленеть вокруг объявления (146). ДЕРЖИТСЯ ЗАПИСЬЮ: .rules/roles.md § Почему ревизор — роль, а не «повнимательнее». ПЕРЕВЕДЁН ИЗ «ДЕРЖИТСЯ НИЧЕМ» 7 СЕНТЯБРЯ, И ЭТО НЕ ПОЯВЛЕНИЕ МЕХАНИЗМА, А ИСПРАВЛЕНИЕ ВЕРДИКТА: требование записано в читаемом месте и читается ПЕРЕД делом, а вердикт говорил «ничем» — то есть занижал существующее. Разница не косметическая: «ничем» значит, что никто не напомнит, «документом» — что напомнит текст, который окно обязано прочесть. Гейта по-прежнему нет, и половина, которой он невозможен, названа выше.", "holdable": "no", "awaiting": "", "analysed": "", "decided": "" }, "107": { "mechanism": "gate", "where": "scripts/build_metrics.py::_get — заголовок авторизации не уходит на raw: чужой хост ведёт себя не как API; scripts/check_mechanisms.py::audit_calls отвергает чтение raw.githubusercontent.com без authenticated=False: чужой хост отвечает на заголовок авторизации 404 вместо содержимого, и отладка этого стоит дорого — адрес в браузере открывается, а сборка падает", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "108": { "mechanism": "gate", "where": "scripts/build_metrics.py::check_focus_limit — потолок «Current focus» в пять строк", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "109": { "mechanism": "gate", "where": "ВЕРДИКТ ИСПРАВЛЕН 8 СЕНТЯБРЯ: стояло «переходных состояний в скрипте нет». Переходное состояние у витрины одно и главное — стоп-кран hold на изменении: открыто, тело пустое, автомерж придержан. scripts/hold.py::decide, который зовёт .github/workflows/release-hold.yml, обязан вернуть КОНЕЧНОЕ решение из каждой ветки — снять, придержать или оставить как есть, — и набор перебирает выходы поимённо, включая ранние. ЦЕНА ПЛАЧЕНА ДВАЖДЫ, и оба раза выход был не терминальным: 28 августа механизм снял метку, отчитался успехом и остановился на полпути — автомерж включается отдельным действием, которого никто не делал; 31 августа снятие будилось событием «проверка досчиталась», а тело заполняли позже, и изменение стояло готовым с меткой, которую некому снять. За 7 сентября так простояли три изменения подряд. ПРОГОН НАЗВАН 8 СЕНТЯБРЯ ТОЙ ЖЕ ПРОВЕРКОЙ, ЧТО И У 191: вердикт называл скрипт без того, кто его запускает, — по тексту «отказывает изменению» было неотличимо от «где-то бежит»", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "111": { "mechanism": "pipeline", "where": ".github/workflows/agent-pr.yml открывает изменение, .github/workflows/release-hold.yml снимает стоп-кран и вооружает автомерж, .github/workflows/automerge.yml сливает — конвейер делает сам, а не советует сделать. Тот же приём у соседа: каталог держит это тремя прогонами (agent-pr, automerge, labels-sync). .github/workflows/automerge.yml — совет только там, где нужна настройка владельца (секрет, защита ветки, разрешение squash); остальное конвейер делает сам. МЕХАНИЗМА НЕТ: правило о СОДЕРЖАНИИ сообщения — совет уместен там, где от человека нужна настройка, и неуместен там, где конвейер справляется сам. Отличить одно от другого можно только прочитав, что именно советуют; счётчик советов или список разрешённых фраз считали бы форму вместо смысла", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "114": { "mechanism": "gate", "where": "ВЕРДИКТ СМЕНИЛСЯ ПРИ ПЕРЕЧИТЫВАНИИ ПО 184: буква — про ступени схемы хранилища, и её у витрины нет; обобщение — «применяют к состоянию, которое ступень не проходило» — работает в scripts/check_author.py и .github/workflows/attribution-history.yml. Планка не поднята, она ПЕРЕНЕСЕНА РАНЬШЕ: то, что общая ветка требует после слияния, изменение обязано нести до него; прошлое при этом не переписывается — 31 августа #97 уехало в main без единого трейлера, и красным стал прогон по истории, где чинить уже нечем.", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "118": { "mechanism": "gate", "where": "scripts/check_mechanisms.py::source_next_to_derived — производное и его источник обязаны лежать в одном каталоге; пара README.md ← projects.json объявлена входом гейта. РАЗОБРАНО НАДВОЕ по 182: «источник ли это по замыслу» машина не решит, но СОСЕДСТВО путей проверяется буквально, а именно оно и есть требование правила. Источник, унесённый в подкаталог, правят реже производного — и тогда правят производное, то есть пишут в файл, который следующий прогон перезапишет. Гейт сломается ровно в тот день, когда источник унесут, оставив производное на месте.", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "119": { "mechanism": "gate", "where": "scripts/check_mechanisms.py::own_output_readers — функция сборки, читающая с диска её же вывод и не названная в OWN_OUTPUT_READERS, отвергается. Прежде довод «подпись берётся из памяти, с диска только рукодельные SVG» жил комментарием внутри scripts/build_metrics.py::sync_alt, то есть до первой правки. РАЗОБРАНО НАДВОЕ по 182: отличить чтение «ради свежести» от чтения ради рукодельного файла машина не может — это замысел вызова; но проверить СПИСОК может, и список печатается в отказе вместе с доводом каждого. Читателей трое: aria_label и asset_version (для рукодельных картинок файл и есть источник), patch_readme (проза README — источник, числа между маркерами обратно не читаются). Четвёртый придётся объявить вслух — ровно в тот момент, когда решение принимают, а не наследуют.", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "123": { "mechanism": "gate", "where": "предметов три, перебраны все. Автор коммита — scripts/check_author.py: контейнерное умолчание облачного окна отвергается поимённо, и отказ печатает команду целиком. Трейлеры коммитов изменения — действие каталога шагом в .github/workflows/pr-check.yml, со своим списком имён в .github/authors.txt. Итоговая история — .github/workflows/attribution-history.yml: первопредки main с объявленного начала долга. Коммит вовсе без трейлеров отвергается НА ИЗМЕНЕНИИ — scripts/check_author.py::unattributed: у гейта каталога это устройство (на диапазоне он их считает числом, требует только на первопредках), и здесь живёт недостающая половина, а не вторая копия списка имён. Заведено 31 августа по живому долгу: ca0d920 уехал в main без единого трейлера, зелёный на всех проверках изменения; тот же гейт краснеет на нём задним числом. Что остаётся непокрытым: слово «Co-Authored-By:» в прозе, ставшее первым в строке после переноса, образец примет за трейлер — ложный пропуск, названный в наборе и отправленный в каталог обратным каналом. Оговорка по 143: на автора коммита в общей ветке ни один из трёх не влияет — его задаёт учётная запись, открывшая изменение; .github/workflows/agent-pr.yml — изменение открывает токен владельца, а не окно: автором squash-коммита площадка ставит автора ИЗМЕНЕНИЯ, и соглашение держалось тем, что изменения открывал владелец. В окне, где на запись отвечает бот, оно перевернулось молча — коммит ca7dfcd в main подписан claude[bot], а владелец уехал в Co-authored-by мимо .github/authors.txt; .github/workflows/metrics.yml подписывает исполнителя суточной пересборки трейлером, а .github/authors.txt принимает его имя осознанно и с границей. ЗАМЕР 30 августа: три пересборки уехали в общую ветку вовсе без атрибуции, и создало это изменение #83 — убрав бота из авторов коммитов, оно убрало и расхождение, ради которого площадка дописывала Co-authored-by. Каждое изменение было зелёным на своём PR; дефект виден только в итоговой истории, ради чего гейт первопредков и заведён. Изменение, открытое ботом, переоформляется .github/workflows/deps-reauthor.yml (scripts/reauthor_deps.py): автор — владелец, dependabot — трейлером; без этого правка бота легла бы в main за его подписью, и гейт каталога держал #209 с 14 сентября. Решение владельца 24 сентября — переоформлять, а не разрешать боту авторство в списке имён", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "124": { "mechanism": "document", "where": "ВЕРДИКТ ИСПРАВЛЕН 8 СЕНТЯБРЯ: стояло «набор проверок — одна: выбирать, что перезапускать, не из чего». РАЗЛОЖЕНО НАДВОЕ по 182. ПЕРВАЯ ПОЛОВИНА — «перезапускать минимум» — предмета не имеет, и теперь по названной причине, а не по счёту работ: работ на изменении четыре (agent-pr, PR check, automerge, release-hold), но набор тестов один, а автоматического перезапуска нет нигде — группа одновременности в .github/workflows/pr-check.yml ОТМЕНЯЕТ прогон на устаревшей голове, а не запускает заново. ВТОРАЯ ПОЛОВИНА — «зелёное со второго раза — находка» — предмет имеет, и он проявился в тот же день: изменение #183 стояло готовым при зелёных проверках девять с половиной минут, и его расклинило внешним толчком, после которого проверка прошла заново. Находка записана в задачу #148 замером — три случая, их длительность и порог, — а не списана на случайность. ДЕРЖИТСЯ ЗАПИСЬЮ: сторожа застрявших изменений пока нет, он и есть предмет #148", "holdable": "no", "awaiting": "", "analysed": "", "decided": "" }, "125": { "mechanism": "gate", "where": "scripts/check_mechanisms.py::own_output_readers — тот же гейт, что у 119, и предмет тот же с другой стороны: генератор, читающий собственный вывод, делает единственным источником значения его же прошлую копию. Прежний вердикт утверждал «генератор читает источники и ничего из собственного вывода» — это было НЕТОЧНО: scripts/build_metrics.py::patch_readme читает README.md. Чтение законно и теперь названо: README смешанный — проза принадлежит человеку и служит источником, числа между маркерами пишет сборка и обратно не читает. Разница между «не читает вовсе» и «читает названное с доводом» и есть то, что вердикт скрывал.", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "126": { "mechanism": "pipeline", "where": "метка hold останавливает автомерж, и снимается она не через слияние; .github/workflows/automerge.yml; .github/workflows/release-hold.yml после снятия метки САМ вооружает автомерж методом squash. Снятая метка ничего не двигает: площадка не будит прогоны событиями от GITHUB_TOKEN, и automerge на такое снятие не просыпается. Замер 28 августа на #87: release-hold отработал дважды с исходом success, метка исчезла, а последний прогон automerge остался тем, что бежал ДО снятия — механизм сработал наполовину и отчитался успехом (правило 146); вооружает автомерж он ТОКЕНОМ ВЛАДЕЛЬЦА, а не штатным: замер 28 августа на #89 дал дословно «GraphQL: Resource not accessible by integration (mergePullRequest)». Второе следствие важнее первого — события от GITHUB_TOKEN площадка прогонами не считает, и даже успешное снятие метки штатным токеном никого не будит", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "127": { "mechanism": "gate", "where": "scripts/build_metrics.py::patch_readme — маркер, сборка и падение при пропаже маркера", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "128": { "mechanism": "gate", "where": "scripts/check_labels.py::derive_zones — зона, выведенная из диффа, требует своей метки: проверяется полнота, а не непустота; scripts/build_metrics.py::check_badges — обязательный показатель проверяется на ПОЛНОТУ ответа: у каждого проекта по каждому из четырёх есть бейдж или названная причина, а пустая причина ответом не считается", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "129": { "mechanism": "pipeline", "where": "предметов у правила четыре, и перебраны все. Экспорт каталога читается scripts/build_metrics.py::rules_export; ответ по каждому правилу — .rules/bindings.json, и его живость держит гейт scripts/check_bindings.py; синхронизация подключена .github/workflows/rules-inbox.yml — инструментом каталога на закреплённом коммите, а не копией скрипта; версия схемы лежит полем schema там же. ОБРАТНОЙ ССЫЛКИ НЕТ: правило, чей след ведёт на задачу этого репозитория, в самой задаче не отражается ничем. Пробел закрыт .github/workflows/rules-inbox.yml — обратная ссылка достраивает вторую сторону, и mechanism поэтому pipeline: прогон отработает, но изменению не откажет; .github/workflows/main-red.yml — второй механизм каталога, подключённый по тому же контракту: тянет потребитель своим токеном и в свой трекер, каталог не рассылает ничего сам; .rules/proposals.json — обратная половина контракта: каталог ТЯНЕТ предложения обычным HTTPS, витрина кладёт файл у себя. Ни токена в чужой проект, ни клона, ни прав — та же форма, что и у прямой половины. Номера у предложения нет и быть не может: его присваивает каталог при приёме; .github/workflows/rules-inbox.yml — третье звено того же контракта: обратная ссылка. У правила есть номер задачи витрины, а у задачи о правиле не было ни слова, и следующий разбор того же места начинался с нуля. Инструмент чужой и берётся ссылкой на закреплённую версию — витрина вынесла его в каталог сама (playbook#152), потому что у грейдера такой уже написан свой, и третья реализация разъехалась бы с первой правки. Предмет измерен: 8 задач витрины несут следы 12 правил ПИН ЭТОГО ИНСТРУМЕНТА ДВИГАЕТСЯ РУКОЙ, И ЦЕНА ЗАМЕРЕНА: простояв с 27 августа, он отстал на 239 коммитов, знал старое имя каталога и показывал в задаче-«входящих» ссылку на репозиторий, которого под этим именем нет, вместе с числом правил 193 при 195 живых. Сдвинут 8 сентября после сухого прогона на .rules/bindings.json.", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "131": { "mechanism": "pipeline", "where": "CLAUDE.md § Критические запреты — из окна не сливают руками. .github/workflows/agent-pr.yml — учётные данные на записи снова имеют предмет, и это исправление прежнего вердикта, а не добавление к нему: здесь было записано, что при squash подмена учётных данных предмета не имеет. Неверно. Автором squash площадка ставит автора ИЗМЕНЕНИЯ, то есть того, чьими учётными данными изменение открыто, — и коммит ca7dfcd приехал в main за подписью бота именно поэтому. Гейт по окружению по-прежнему не заводится: предмет держится тем, что изменение открывает прогон токеном владельца", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "132": { "mechanism": "document", "where": "CLAUDE.md § Критические запреты — не везти в одном PR несколько тем; .github/pull_request_template.md — тот же вопрос критику. Гейта нет намеренно: число затронутых зон сборности не доказывает, а ложный отказ на широкой теме дороже пропуска. Правило само требует предупреждения, а не отказа, — а предупреждать здесь некому", "holdable": "no", "awaiting": "", "analysed": "", "decided": "" }, "133": { "mechanism": "document", "where": "CLAUDE.md § Критические запреты — оговорка к запрету на несколько тем: пересечение файлов сильнее темы. У витрины общие файлы почти у каждой правки — HISTORY.md и .rules/bindings.json, — поэтому изменения идут по одному, и встречи на общем файле не случается", "holdable": "no", "awaiting": "", "analysed": "", "decided": "" }, "134": { "mechanism": "gate", "where": "CLAUDE.md — сам факт его существования и есть механизм: свод лежит в репозитории и читается при старте каждого окна, вместе со ссылкой на каталог. До PR #22 его не было, и окно витрины стартовало вслепую — это инцидент самого правила; scripts/check_mechanisms.py::audit_charter держит предмет чтения: CLAUDE.md существует и ведёт к каталогу и к .rules/. Ловится не «прочитало ли окно» — этого машине не видно, — а исчезновение того, что читают: файл удалён, переименован или потерял ссылку на каталог, из которого правила приходят. Ровно это и было инцидентом: свода не существовало, и витрину вело вслепую", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "135": { "mechanism": "gate", "where": ".rules/README.md § Из окна не пишут — личность этого окна установлена пробой того же класса, а не опросом токена, и результат годен только для него: следующее окно проверяет заново. scripts/check_author.py — то, что окно подписалось не тем именем, теперь выясняется записью и проверкой, а не памятью", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "136": { "mechanism": "document", "where": ".rules/bindings.json — вердикт пишется после перебора ВСЕХ предметов правила, а не первого; инцидент правила — наш же вердикт по 004, отклонённый на втором непросмотренном предмете. РАЗЛОЖЕНО НАДВОЕ по 182. Половина «перечисленное существует» держится гейтом: scripts/check_bindings.py::audit проверяет каждый путь и якорь, а ::refuted — что ответ «предмета нет» не опровергается деревом. У половины «перечислено ВСЁ» МЕХАНИЗМА НЕТ: полнота перебора — это знание о правиле, которого у машины нет; она не может знать, сколько предметов у чужого требования. Названо прямо, потому что это и есть цена правила. ДЕРЖИТСЯ ЗАПИСЬЮ: .rules/roles.md § Когда подключать — «перечислены ли ВСЕ предметы, а не первый». ПЕРЕВЕДЁН ИЗ «ДЕРЖИТСЯ НИЧЕМ» 7 СЕНТЯБРЯ, И ЭТО НЕ ПОЯВЛЕНИЕ МЕХАНИЗМА, А ИСПРАВЛЕНИЕ ВЕРДИКТА: требование записано в читаемом месте и читается ПЕРЕД делом, а вердикт говорил «ничем» — то есть занижал существующее. Разница не косметическая: «ничем» значит, что никто не напомнит, «документом» — что напомнит текст, который окно обязано прочесть. Гейта по-прежнему нет, и половина, которой он невозможен, названа выше.", "holdable": "no", "awaiting": "", "analysed": "", "decided": "" }, "137": { "mechanism": "gate", "where": "scripts/build_metrics.py::unanswered — сторож стоит до подстановки в README и смотрит на измеренное, а не на строку для показа. Инцидент правила — эта самая сборка: «0000+ tests» проходило мимо сравнения с «0». Форматирующая половина примера (order_of) ушла вместе с плиткой флагмана, когда витрина перестала показывать числа одного проекта; порядок «сторож, потом запись» от этого не изменился", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "138": { "mechanism": "gate", "where": "CLAUDE.md § Журнал изменений — журнал пополняется тем же заходом, что и правка, а не перед выпуском; решение оседает записью в HISTORY.md сразу; scripts/check_journal.py держит это гейтом: изменение, правящее scripts/ или .github/workflows/, обязано нести строку в HISTORY.md. Замер, из-за которого гейт завёлся, а не опасение: по 71 первопредку общей ветки 30 изменений правили поведение, и 3 приехали без записи — одно из них настоящее решение на 48 строк в прогоне очереди правил (#53), где отложенная запись означает, что следующее окно разбирает тот же вопрос заново. У отказа есть НАЗВАННЫЙ выход: строка «Журнал: не требуется — причина» в сообщении коммита снимает требование и остаётся в истории. Без выхода гейт обходили бы пустой записью, а пустая запись хуже отсутствующей — она выглядит как память. Содержание записи гейт не судит: отличить решение от опечатки машина не может, и обе стороны границы названы", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "139": { "mechanism": "document", "where": ".rules/README.md § Конвейер — каждое звено названо вместе с изменением, на котором оно отработало: механизм считается подтверждённым прогоном, а не чтением", "holdable": "no", "awaiting": "", "analysed": "", "decided": "" }, "140": { "mechanism": "gate", "where": "scripts/check_bindings.py::selftest, scripts/check_roles.py::selftest, scripts/check_labels.py::selftest — включая вывод метки по заголовку, где ошибка ставит НЕВЕРНУЮ метку, то есть врёт о зоне работы, scripts/build_metrics.py::selftest, scripts/gh_outcome.py::selftest — через каждый гейт нарочно прогоняется то, что он обязан отвергнуть, и то, что обязан пропустить: набор из одних отказов не видит ложного отказа. Прогоны стоят шагами в .github/workflows/pr-check.yml, а не запускаются руками scripts/check_mechanisms.py::audit_verdicts — сам набор теперь проверяется тем, что он обязан отвергнуть: находка, дописанная ЗА вердиктом, отвергается на PR. Замер 31 августа: в scripts/build_metrics.py::selftest за вердиктом стояли три группы случаев, и подставной провал в последней давал код 0 и «самопроверка пройдена».", "holdable": "", "awaiting": "", "analysed": "", "decided": "" }, "141": { "mechanism": "gate", "where": "scripts/check_mechanisms.py::prefix_matched_markers — образец, ищущий маркер витрины `