- шаг go-version в task gate сверяет объявленную версию в go.mod, Dockerfile, CLAUDE.md и README.md; судит по репозиторию, go не зовёт, docker и сети не требует - заведена capability toolchain: до сих пор спеки нормировали только поведение сервиса, теперь и инструмент сборки. Причина и цена — в двух ADR - закрыт дефект 2026-08-12: образ на golang:1.24-alpine разошёлся с go.mod и перестал собираться, а восемь шагов гейта и шесть проходов ревью были зелёными
57 KiB
Ревью кода: go-1-26-upgrade — триаж
База диффа: HEAD (коммит aa20b22), изменение целиком в рабочем дереве. Дата
прогона: 2026-08-12.
Сводка
Размер, сложность, метка. Размер — среднее: tasks.md 20 шагов в 4 разделах,
8 файлов; internal/ не тронут ни строкой. Сложность — знакомое: все узлы
названы поимённо до работы, шагов формы «разобраться/выяснить» нет. Метка
medium (максимум по осям), режим — по графу. Триггеры docs/review.md
проверены все три группы, ни один пункт не совпал.
Состояние гейта: ЗЕЛЁНЫЙ. Подтверждено собственным прогоном триажа, а не
только отчётом прохода: task gate BASE=HEAD → exit 0. Все девять шагов зелёные,
включая новый go-version. Одно унаследованное замечание tasks.py
(any-audio-source: цель без задач и без тега decomposed) шаг не роняет и к
диффу отношения не имеет.
План разметки задачи с исходом по каждой теме
| тема | дом | глубина | кто закрывает | исход |
|---|---|---|---|---|
| requirements | openspec/specs/ + дельта specs/toolchain/spec.md |
разбор | specs | закрыта, 4 находки (S1–S4) + 2 блока наблюдений |
| autotests | CLAUDE.md, раздел «Гейт» |
— | autotests | закрыта, 3 находки (A1–A3) + отчёт гейта |
| conventions | docs/conventions/ |
разбор | code | закрыта, 3 находки (C1–C3) + 3 наблюдения ниже порога |
| architecture | docs/architecture.md + источник docs/passport.md |
разбор | basics | закрыта, 1 находка (B1) + 4 пункта «дешевле переделать» |
| security | docs/security.md |
разбор | basics | закрыта, находок нет: тема неприменима к диффу целиком |
| operations | docs/architecture.md §Эксплуатация + источник docs/database.md |
разбор | basics | закрыта, 1 находка (B3); 4 вопроса из 6 неприменимы |
Тем без отчёта нет. Все шесть тем ядра вернули отчёты; своих тем сверх ядра у проекта нет. Ни одна тема не осталась непроверенной по причине «проход не запускался».
Находок на входе: 13 нумерованных (A1–A3, S1–S4, C1–C3, B1–B3) плюс 15 ненумерованных содержательных пунктов (3 «поведение вне спеки», 5 «границы спеки», 3 наблюдения ниже порога, 4 «дешевле переделать до мерджа») = 28 позиций. Осталось в основных секциях: 6 — 2 блокирующие и 4 «стоит исправить сейчас». Слито по причине 4 пары, понижено до гипотез 3, уехало в promote 5, выброшено 6 (все названы поимённо).
Сигнал о заниженной метке
Сигнал подан двумя проходами независимо — code и basics. Оба назвали одно
и то же: изменение вводит новое понятие (capability toolchain — первая, что
описывает не поведение сервиса, а инструмент разработки), новый каталог верхнего
уровня scripts/ и новый шаг гейта; design.md сам записывает нерешённое
натяжение в размещении capability. Оба сказали, что метка large дала бы
отдельный проход review-architecture, и оба отказались решать за конвейер.
Провенанс один, приоритет два. Согласие двух проходов — это одна модель,
высказавшаяся дважды: confidence оно не повышает, приоритет повышает. Одна
строка с двумя провенансами, а не два пункта.
Суждение триажа — факт для человека, не команда конвейеру:
- По записанному правилу разметка верна.
docs/review.md, «Триггеры метки»: в группе «Крупное здесь» ближайший пункт — «каркас приложения: сборка фронтенда, раздача статики и шаг гейта разом» — требует трёх вещей сразу, здесь только шаг гейта. В «Незнакомое здесь» не совпал ни один из шести: форма решения (сравнение строкsed/awk, безgoи без docker) была названа до работы. Отрицательный тест пройден: миграции, формата файла, контракта API и имени ключа конфига изменение не трогает.review-scopeне ошибся против правила, которое у него было. - Ось, на которую указали проходы, в правиле отсутствует. Ни один триггер не говорит о заведении новой capability и о новом каталоге верхнего уровня. А именно эта ось дала обе блокирующие находки прогона — обе про канон, а не про код. Сигнал верен по существу: правило разметки не видит того, что в этом изменении оказалось самым дорогим.
- Перезапуска это не требует, метку задним числом не пересматривают. Тема
architectureдома не лишилась и без отчёта не осталась — её закрылbasicsна глубине «разбор». Честный остаток: тему смотрел проход широкого профиля, а не специализированный, и вопрос «правильно ли выбрано имя и дом capability» остался без независимого разбора (H-1). - Что с этим делать — не здесь. Кандидат в правило вынесен в promote (P-5).
1. Блокирует мердж (2 из 3)
B-1. Требование «ровно одно вхождение» станет нормой в форме, которая про Dockerfile уже неверна, а на CLAUDE.md уронит гейт на правдивой строке
- Файл:
openspec/changes/go-1-26-upgrade/specs/toolchain/spec.md:16-18противscripts/check-go-version.sh:127-131и:147-153; правильные слова уже лежат вopenspec/changes/go-1-26-upgrade/design.md:158-163 - Severity: major | Confidence: high
- Действие: развилка
- Найдено проходами:
specs(S1 и пункт «Границы спеки»),basics(пункт «Рамка правила „ровно одно вхождение“»). Слито триажем по причине: причина одна — требование написано пофайлово единым правилом, а четыре места устроены по-разному. Правится одним абзацем. - Оракул — три прогона на копии дерева (копии в scratchpad; рабочее дерево не
тронуто,
git status --porcelainдо и после совпадает):- второй сборочный слой
FROM docker.io/library/golang:1.26-alpine AS second→ exit 0. Норма гласит: «Каждое место MUST называть версию ровно один раз. Второе вхождение числа в том же месте MUST считаться отказом», и перечень мест включаетDockerfile. Код нормы не исполняет и исполнять не должен:collect Dockerfile "$(read_dockerfile)" 0передаётstrict=0намеренно. Контроль: тот же второй слой сgolang:1.25-alpine→ exit 1, «Dockerfile называет несколько разных версий» — то есть совпадение слоёв проверяется, единственность нет; - в
CLAUDE.mdдописана правдивая строка- прежде собирались на Go 1.25; долг закрыт задачей go-1-26-upgrade→ exit 1, «CLAUDE.md называет несколько разных версий»; - в
README.mdдописан блок кода с# нужен Go 1.26→ exit 1, «README.md называет версию больше одного раза».
- второй сборочный слой
- Последствие. Со стороны
Dockerfile— молчаливое расхождение нормы и кода: после архивации нормой станет спека, а неdesign.md. Многослойная сборка с двумяFROM golang:— законная форма. Ревьюер следующего изменения увидитstrict=0, прочтёт MUST и «починит» скрипт, уронив гейт на рабочемDockerfile; обратный исход не лучше — норма останется ложью, на которую сошлются. Со стороны документов — гарантированный ложный красный:CLAUDE.mdпо устройству ведёт историю (раздел «Гейт» прямо говорит «Два прежних долга закрыты и здесь названы»), и первая же правдивая запись о прошлой версии роняет шаг. По правилу проекта «Что считается сломанным — новый красный шаг гейта… чинится прежде любой другой работы» это остановит работу, а сообщение «называет несколько разных версий» толкает чинить не скрипт, а исторический документ, то есть подделывать запись.README.mdловится тем же на любом блоке кода с командой установки. - Почему до мерджа: спека замерзает архивацией, после неё правка MUST — отдельное изменение. Сегодня это один абзац.
- Вопрос человеку:
- Вариант А (дешёвый, ожидаемый). Развести правило по местам прямо в
требовании, дословно как уже написано в
design.md:158-163: единственности требовать отgo.mod,CLAUDE.mdиREADME.md, а отDockerfile— совпадения всех вхожденийFROM golang:. Плюс сузить рамку для документов: правило считает не файл целиком, а помеченную строку стека (или раздел «Стек»/«Технологии»). Цена: абзац спеки +read_docначинает читать раздел, а не файл — несколько строк скрипта. Сценарий «Одно место называет два разных числа» остаётся верным и правки не требует. - Вариант Б (дешевле сейчас, дороже потом). Развести только
Dockerfile(правка чисто текстовая, кода не трогает), а цену «файл целиком» для документов принять осознанно и записать остатком в спеке: «CLAUDE.mdне ведёт истории версий Go; запись о прошлой версии живёт вdocs/review.md». Тогда красный на истории — не сюрприз, а объявленный запрет.
- Вариант А (дешёвый, ожидаемый). Развести правило по местам прямо в
требовании, дословно как уже написано в
B-2. Канонический перечень capability назовёт три из четырёх ровно в момент архивации, и промолчит именно о новой
- Файл:
docs/architecture.md:11-26;openspec/changes/go-1-26-upgrade/tasks.md, раздел «3. Документы» (в нёмdocs/architecture.mdнет) - Severity: minor | Confidence: high
- Действие: инлайн
- Найдено проходами:
specs(S3),basics(B1). Дубль по причине, слит; предложение взято более широкое, отbasics. - Оракул — поимённые положения, все перепроверены триажем:
openspec/config.yaml:25-26дословно: «Состояние спек и правило „первая задача, трогающая поведение, заводит спеку своей capability“ — docs/architecture.md, преамбула». Дом назначен, и он один;docs/architecture.md:11дословно: «Заведены три capability:»;proposal.mdзаводит четвёртую;- прецедент: прошлое изменение правило этот список тем же коммитом, что и
реализацию —
git show 01cc31d -- docs/architecture.mdдаёт-Заведены две capability, и каждая описана частично:/+Заведены три capability:; tasks.md, раздел 3, содержит ровно два пункта —CLAUDE.mdиdocs/review.md; обзора архитектуры в нём нет;- машинного оракула нет и быть не может:
CLAUDE.md, «Гейт» — «согласованность документов между собой и с кодом — её судят агенты, зовёт их скиллav-dev-docs:healthcheck, и звать его надо руками».
- Последствие. После архивации в
openspec/specs/появится четвёртая capability, о которой единственный назначенный обзор молчит. Следующий, кто возьмётся за версию инструмента сборки, пойдёт по указанному дому, четвёртой строки не найдёт и либо заведёт вторую спеку на ту же тему, либо припишет требование вpipeline— ровно то, от чегоdesign.md:79-82отказался. Отказ молчаливый: гейт этого класса не ловит по устройству. - Предложение (инлайн, три правки):
- четвёртая строка перечня в
docs/architecture.md— проtoolchain, со ссылкой на спеку и задачу-источник; - оговорка к преамбуле: сегодня она читается «поведение системы здесь не
описывается — нормативно оно живёт в
openspec/specs/», аtoolchainописывает не поведение сервиса; без оговорки преамбула становится неверной в момент архивации; - пункт в
tasks.md, раздел «3. Документы», чтобы правка не потерялась.
- четвёртая строка перечня в
Третий слот блокирующих не занят — кандидатов нет: всё прочее либо не замерзает мерджем, либо не имеет оракула.
2. Стоит исправить сейчас (4 из 4)
N-1. Единственный новый страж проекта не покрыт ничем: следующая правка его регулярных выражений перестанет ловить случай молча
- Файл:
scripts/check-go-version.sh(весь);tasks.md:96-111 - Severity: major | Confidence: high
- Действие: развилка (новая работа, за границей объявленного scope)
- Найдено проходами:
autotests(A1),specs(S4). Дубль по причине, слит. - Оракул (перепроверено триажем):
find . -iname '*check-go-version*'→ ровно один файл, сам скрипт; тестов нет;grep -rln 'check-go-version' --include='*_test.go' --include='*.bats' --include='*test*.sh' .→ пусто;task gateпрогоняет скрипт ровно на согласованном дереве, то есть проверяет один сценарий дельты из четырнадцати — «Версии совпадают»;- раздел «4. Проверка» в
tasks.mdперечисляет 11 сценариев, все[x], но ни один не зафиксирован ничем, кроме прозы: это разовый ручной прогон, а не оракул; - положение проекта, которое здесь нарушено, записано: `docs/review.md», «Типовые узлы» → «Любой узел»: «изменённое место покрыто хоть одним проходящим тестом».
- Последствие. POSIX-sh с разбором четырёх разных форм через
sed/awk— класс кода, где правка одного образца ломает смежный случай беззвучно.go vet,golangci-lintиgofmtshell не видят;shellcheckв гейт сознательно не введён. Правка третьего аргументаcollectили образцаread_docснимает проверку молча, и заметят это на следующем подъёме версии — примерно через год, и ровно тем способом, каким был найден дефект 2026-08-12: образ перестал собираться, и этого не увидел никто. Класс — «молчание»: страж перестаёт стеречь, не сообщая об этом. - Вопрос человеку:
- А. Сейчас, в этом изменении. Тест-скрипт рядом
(
scripts/check-go-version.test.sh) и отдельный шаг гейта: десяток мутационных прогонов на временной копии дерева — по одному на сценарий дельты. Цена ~100 строк shell плюс шаг Taskfile. Плюс: страж проверен ровно тем способом, какимdocs/review.mdвелит проверять оракулы. - Б. Задачей урожая, вместе с
shellcheck(P-1). Один шаг гейта, гоняющий и линтер оболочки, и мутационные прогоны. Плюс: не раздувает изменение, scope остаётся заявленным. Минус: между мерджем и задачей страж не проверен ничем, и правки в этот промежуток пройдут вслепую. - В. Принять остаток осознанно и записать строкой в
docs/review.md, «Недоступно проверке» → «Перестали проверять сознательно», с ценой. Плюс: честно и бесплатно. Минус: следующий промах этого класса будет уже вторым.
- А. Сейчас, в этом изменении. Тест-скрипт рядом
(
N-2. Новое машинное правило не попало в единственный индекс механизированного, и следующий проход конвенций будет сверять версии руками
- Файл:
docs/conventions/README.md:50-65 - Severity: minor | Confidence: medium
- Действие: инлайн
- Найдено проходом:
code(C3) - Оракул — поимённое положение конвенций:
docs/conventions/README.md:52-53(«Проверяется командами из CLAUDE.md; прозой не дублируется») и:64-65(«Не названное здесь место механизации означает, что проход по конвенциям будет добросовестно проверять уже проверенное»). Строка проdocs.py checkв той же таблице — прямой прецедент внесения шагов гейта. - Последствие. Дифф заводит новое машинное правило и не вносит его в индекс,
объявленный исчерпывающим. Следующий проход по конвенциям и следующий человек
будут считать согласованность версий непроверенной и сверять её руками. Заодно
это единственное место в
docs/conventions/, откуда новый скрипт вообще был бы виден: сегодня из дома конвенций он не виден никак. - Предложение: строка в таблицу —
| Одно число версии Go в go.mod, Dockerfile, CLAUDE.md и README.md | Taskfile.yml → шаг go-version (scripts/check-go-version.sh) |.
N-3. Сломанное окружение шаг объявит расхождением версий и назовёт невиновный файл
-
Файл:
scripts/check-go-version.sh:105-119,:147-153 -
Severity: minor | Confidence: high (проход давал
medium; поднято прогоном) -
Действие: инлайн
-
Найдено проходом:
code(C1) -
Оракул — два прогона на копии дерева:
chmod a-r README.md && sh scripts/check-go-version.sh→sed: can't read .../README.md: Permission denied check-go-version: README.md не называет версию Go Объявленная версия Go по местам: go.mod 1.26 Dockerfile 1.26 CLAUDE.md 1.26 README.md версия не названа exit=1- прогон с
PATH, где нетawk→ exit 127, код вне словаря вовсе.
Для сравнения, штатные пути проверены и корректны: нет файла места → 3, лишний аргумент → 2, директива
toolchain→ 1, согласованное дерево → 0. -
Последствие. Значения добываются подстановкой команд в аргументе (
collect README.md "$(read_doc "$readmemd")" 1), а POSIX теряет код возврата подстановки, стоящей в аргументе простой команды. Любой отказ чтения — файл есть, но нечитаем; урезанныйPATH; сломанный апплет busybox — даёт пустой вход,collectвидитtotal -eq 0и печатает утверждение о содержимом файла там, где сломалось окружение. По словарю, который этот же дифф и расширил, 1 значит «дрейф», 3 — «окружение», 4 — «внутренний сбой»; кода 4 скрипт не возвращает ни на одном пути. -
Честная оценка веса. Ущерб невелик:
sedпечатает свою причину строкой выше, так что человек у терминала подсказку видит, аtask gateразличает только ноль и не-ноль. Вероятность низкая — CI у проекта нет, гейт гоняет владелец на своей машине. Находка остаётся потому, что шаг гейта — источник, на который смотрят как на истину, и ложное утверждение о конкретном файле из такого источника дороже своей вероятности. -
Предложение: добывать значения через промежуточную переменную с проверкой кода, а не в аргументе — четыре места, по одному на источник.
N-4. Цена отказа от shellcheck названа в памятке вчетверо, и вопрос от этого отложится снова
- Файл:
CLAUDE.md:122-123; то же вdesign.md:37-38 - Severity: minor | Confidence: high
- Действие: инлайн
- Найдено проходом:
code(C2); сюда же ушла находкаautotestsA3 как замер цены. - Оракул — замер, снятый триажем на этом прогоне:
find . -path ./.git -prune -o -name '*.sh' -print→ ровно два файла:./scripts/check-go-version.shи./docker/entrypoint.sh; в гейте из них один. Остальные три шага гейта —docs.py,tasks.py,openspec.py— Python и лежат в плагинах вне репозитория;shellcheck scripts/check-go-version.sh→ одно замечание, SC1007 наroot=$(CDPATH= cd -- ... ), и оно ложное:CDPATH=— идиома очистки переменной передcd. Значит заведение линтера стоит одной строки шага плюс одной директивы подавления.
- Последствие. Строка «
shellcheckдля скриптов гейта — их четыре, и заводить им линтер надо разом» смешивает «скриптов в гейте четыре» с «shellcheckприменим к четырём». Применим он к одному. Следующий прочтёт в памятке цену «надо разом, четыре штуки» и отложит вопрос снова — при том что нелинтуемым остаётся ровно тот файл, который проходcodeбыл вынужден разбирать глазами построчно, а триаж — прогонять руками. - Предложение: переписать пункт по факту — «shell-скрипт в гейте один; три
соседних шага — Python в плагинах. Линтер не заведён; цена ему одна строка шага
и одно подавление SC1007». Ту же правку в
design.md.
3. Гипотезы без доказательства
H-1. Имя и дом capability toolchain замерзают мерджем (понижено: оракула нет)
Из прохода basics, «Дешевле переделать до мерджа». После архивации спека уезжает
в openspec/specs/ насовсем; design.md сам признаёт, что при одном требовании
переименовать дешевле, чем расщепить. Вопрос дома шире: четыре однородных шага
набора проверок живут в двух разных домах — у трёх плагинных дома нет вовсе, у
четвёртого есть нормативная спека, и эта асимметрия становится постоянной.
Почему понижено. Оракула нет и построить его нечем: это суждение о
правильности имени, а не о поведении. Плюс два смягчающих факта: стадия ревью
дизайна (specs + rubric) прошла до кода и её замечания отработаны — вопрос уже
был на столе; CLAUDE.md, «Необратимое», имени capability не перечисляет.
Severity снята.
Остаток честный: независимого архитектурного разбора у этого вопроса не было —
на метке medium отдельный проход не запускается, тему закрывал basics широким
профилем. Это и есть содержание сигнала о заниженной метке.
H-2. Пересборка того же коммита через месяц даст другой ffmpeg (понижено: замера нет, строка не из этого диффа)
Из прохода basics (B3). Dockerfile:26 — рантайм-слой alpine:latest, а
Taskfile.yml:93 собирает с --pull: два образа из одного коммита с разницей в
неделю несут разные ffmpeg. Регрессия конвертации после такой пересборки
выглядит как задачи в failed при пустом диффе репозитория, и откат на прежний
коммит её не чинит. Класс тот же, ради которого написан весь новый шаг:
объявленное и собранное расходятся, и сверять некому.
Почему понижено. Замера нет — ни одного числа о том, как часто и насколько
меняется ffmpeg в alpine:latest; снять на этом прогоне нечем. Строка внесена
не этим изменением, только активирована им. Confidence: medium.
H-3. «Два дома у семантики шага» — проверено и не подтвердилось
Из прохода basics. Утверждение: семантика шага описана и в CLAUDE.md «Гейт», и
в спеке toolchain, а openspec/config.yaml предупреждает, что «второй дом факта
расходится с первым молча».
Проверено триажем: спека spec.md:93-96 не пересказывает словарь, а
ссылается на него — «раздел „Гейт“ в CLAUDE.md объявляет словарь общим». Это
уже правильная форма: один дом факта, вторая точка — ссылка. Последствия не
построено, находкой не выводится.
4. Promote candidates
- P-1.
shellcheckшагом набора проверок. Цена замерена на этом прогоне: файлов.shдва, в гейте один, единственное сегодняшнее замечание — ложный SC1007 на идиомуCDPATH= cd. Шаг стоит одной строки плюс одной директивы подавления. - P-2. Род узла «скрипт набора проверок» в
docs/review.md, «Типовые узлы». Сегодня перечень родов покрывает только рантайм. Скрипт гейта — новый род с собственными проверяемыми свойствами: отличает «расхождение» от «сломанного окружения», исход есть функция коммита, покрыт мутационным прогоном. Без этого рода свойство «изменённое место покрыто хоть одним проходящим тестом» к shell не приложено ничем, и находка N-1 в следующий раз опять будет добываться с нуля. - P-3. Обёртки
Taskfileотдают 1, когда скрипта нет, а словарь велит 3. Из S2/B2, слитых по причине. Но так делают все четыре обёртки —docs,tasks,openspecи новаяgo-version: новый шаг лишь повторил существующий рисунок, и дефектом этого диффа это не является. Сам скрипт при отсутствующем месте выходит корректно — 3 (проверено). Правило, а не правка: привести все четыре обёртки к 3 разом либо убрать «или файл не найден» из описания кода 3 и объявитьexit 1нормой для «скрипта шага нет». - P-4. Норму «версия внешнего инструмента объявлена числом и сверяется» распространить на рантайм-базу образа. Из H-2. Non-Goal этого изменения записан; кандидат в отдельную задачу.
- P-5. Триггер метки: изменение, заводящее новую capability или новый каталог верхнего уровня. Из сигнала о заниженной метке. Сегодня «Триггеры метки» видят только объём и незнакомость формы решения; ось «изменение трогает канон» в них отсутствует, а на этом прогоне именно она дала обе блокирующие находки.
5. Границы покрытия
Секция не сокращается. Без неё формулировка «критичных проблем не обнаружено» запрещена — и здесь она не употребляется.
План: темы, дома, глубины
Все шесть тем ядра размечены, у всех есть дом, все закрыты — таблица в сводке.
Тем без дома нет. Тем без отчёта нет. Своих тем сверх ядра проект не
объявляет. Глубина «разбор» у пяти тем, у autotests глубина не назначалась.
Какие проходы запускались
На метке medium, в режиме «по графу», запускались четыре: autotests, specs,
code, basics. Стадия ревью дизайна (specs + rubric) прошла раньше, до
кода, и её замечания отработаны — на этом прогоне она не повторялась.
Какие проходы не запускались и почему
review-architecture— не запускается на меткеmediumпо устройству графа; темаarchitectureотданаbasics. Именно об этом сигнал двух проходов.- Проход независимой реализации — снят из конвейера по стоимости.
- Проход про идиоматичность языка — упразднён.
Что каждый запущенный проход не мог проверить в принципе
Оговорка о происхождении: сырые выводы, поданные триажу, несут границы прозой, а
не блоком Coverage of this pass из контракта. Перечень ниже восстановлен по
тому, что проходы написали, а не по их charter'ам, — и может быть неполон. Это
отдельная строка деградации.
autotests— не судит содержание кода; видит зелёное/красное и наличие тестов. Прогон гейта не собирает образ (намеренно) и не считает покрытие изменённых строк.specs— судит соответствие кода дельта-спеке и обратно; не судит качество кода вне нормы и не проверяет, нужна ли норма вообще.code— темыconventionsплюс технический разбор; Go-кода дифф не содержит, поэтомуlogging.md,errors.md,config.mdнеприменимы поимённо, и разбор свёлся к shell, который ни один линтер проекта не видит.basics— темыsecurity,operations,architectureшироким профилем; на этой метке заменяет специализированные проходы, а не дополняет их.- Триаж не находит ничего нового по определению: работает с чужими выводами и своими прогонами-оракулами. Пропуск любого прохода — его пропуск тоже.
Неприменимые темы и вопросы — названы, а не пропущены
security: тема неприменима к диффу целиком.internal/не тронут ни строкой. Три вопроса темы изdocs/review.mdадресованыinternal/service/transcribe.goиinternal/metrics— они не менялись, вопросы остаются открытыми и после этого прогона. Периметр сборки дом объявляет вне модели:docs/security.md:233-235.operations: применимы 2 вопроса из 6. Отказ соседа, повтор и одновременность, остановка на середине, наблюдаемость, рост объёма — неприменимы: рантайм не меняется. Три вопроса темы изdocs/review.mdк диффу неприменимы и остаются открытыми.autotests: вопрос темы изdocs/review.md:133-134адресован job-конвейеру вinternal/, которого дифф не трогает.conventions: вопрос темы («новая колонка правится во всех четырёх местах») — колонок изменение не трогает вовсе;internal/adapter/repo/pocketbaseне изменён ни строкой.
Что осталось целиком на человеке
Не проверит ни один проход:
operations: поведение внешних сервисов под нагрузкой и на границах;operations: реальный профиль нагрузки;security: стойкостьffmpegк вредоносному входу.
Перестали проверять сознательно:
autotests: разбор вывода настоящегоffprobe— решение и цена вdocs/adr/ADR-2026-08-11-stub-adapters-in-tests.md.
Своё, для этого изменения: собираемость образа на объявленной версии —
требование «Объявленное число — то, на котором проект собирается» прямо оставляет
проверку человеку и запрещает вводить её в набор проверок. Косвенные свидетельства
положительные (go version → go1.26.5; гейт зелёный; образ transcriber:dev в
наличии), но самой сборки триаж не запускал — она объявлена сделанной пунктом
tasks.md 4.2.
Каких документов проекта не хватило
docs/review.md, «Типовые ложноположительные» — есть и непуст (4 пункта), но все четыре про рантаймinternal/. Дифф его не трогает, поэтому проектных ложноположительных для него не существует вовсе: отсев вкусовщины шёл по общим критериям устава, без проектного входа.CLAUDE.md, «Инварианты» — раздел есть, 9 пунктов, и ни один не применим к диффу. Следствие названо прямо: ни одна находка этого прогона не поднята доcriticalпо основанию «нарушен инвариант проекта» — сослаться не на что. Ранжирование велось по обратимости, выведенной из механики openspec, и это предположение триажа, а не записанное правило проекта.CLAUDE.md, «Необратимое» — имени capability, составаopenspec/specs/и раскладкиscripts/в нём нет. Поэтому «спека замерзает мерджем» — вывод из механики openspec, а не проектная норма.CLAUDE.md, «Ориентир по размеру порции: не замерялся» — дословно. Значит суждение «объём right-size» опирается на оценку триажа, а не на проектное число.
Сработавшие потолки
basics— сообщил сам: 3 находки при потолке 4, за срезом ничего.code— сообщил частично: «Потолок конвенций 1/4 — срез не сработал». Про потолок технической половины не сказал ничего.specs— не сообщил потолок вовсе. 4 находки. Это находка о прогоне: проход обязан сообщать потолок сам.autotests— не сообщил потолок вовсе. 3 находки. То же.- Триаж: 2 из 3 блокирующих, 4 из 4 «стоит исправить сейчас». Секция «сейчас» заполнена под завязку; ничего не выброшено молча.
Выброшено — поимённо
- A2: вопрос темы про шаг конвейера — к диффу не относится.
- A3:
shellcheckSC1007 — ложное срабатывание, действующего последствия нет; содержание уехало замером в N-4 и P-1. - Порядок шагов в
gate: самый дешёвый шаг стоит пятым. Вкусовщина по всем трём условиям: поведения не меняет, стоимости следующего изменения не меняет, записанной конвенции о порядке шагов в проекте нет. proposal.md: «Внешних зависимостей изменение не трогает» против переездаsmithy-goиз// indirectв прямые требования. Версияv1.27.7не менялась, пакет импортируется вinternal/adapter/recognizer/yandex/s3.go:15— штатный результат заказанногоgo mod tidy. Прозаическая неточность без последствий.CLAUDE.md:114-115«краснеет с именем недостающего плагина» — новый шаг плагином не является. Последствия не построено; чинится вместе с P-3.docs/review.md:194-198: преамбула журнала не сходится с верхней записью. Расхождение приехало предыдущим коммитом, вне диффа.
Четыре строки, которые не принесёт ни один проход
- Решения проекта не сверялись.
docs/adr/— процессный документ, прогон его не открывает. Для этого изменения это особенно весомо:design.mdссылается на решения, но ни один проход не открывалdocs/adr/и не проверял, не противоречит ли новая норма уже принятому. - Записанные наблюдения проекта не использовались.
docs/research/— тоже процессный. Всякое число в этом отчёте снято на этом прогоне и сопровождается командой замера. - Поимённая сверка с руководствами по стилю языка не задавалась ни одним проходом. Для этого изменения дыра шире обычного: основной артефакт — POSIX-shell, у которого в проекте нет ни конвенции, ни линтера, ни руководства.
- Альтернативной реализации, с которой можно сдиффить решения, у конвейера
нет. Вопрос «а можно ли было решить это принципиально иначе — например, одним
go.modкак источником истины и генерацией остальных трёх мест» никто не задавал.
Среда прогона
- Рабочее дерево не тронуто. Все оракулы добыты на копиях в scratchpad.
git status --porcelainдо и после прогона совпадает символ в символ. - Запреты
CLAUDE.mdсоблюдены: боевой каталог данных не трогался, боевой токен не запускался, в Yandex Cloud не ходили, выкладка не запускалась,testdataне заводилась, временное — только в scratchpad. - Отказов доступа не было.
deny: Read(./build)на путях этого изменения не сработал (capability названаtoolchainименно поэтому). - Ограничение среды: подагентам запрещено писать файлы отчётов. Отчёт возвращён триажем текстом и записан сюда оркестратором.
Дополнение: перепроверка после отработки B-1
Записано оркестратором после того, как находки триажа были отработаны. Отчёт без этого раздела сообщал бы о составе прогона неверно.
Что было сделано по находкам
| Находка | Действие | Исход |
|---|---|---|
| B-1 | развилка → человеку | Выбран вариант А: правило множественности разведено по местам, рамка в документах сужена до раздела стека. Изменило требование и код |
| B-2 | инлайн | Четвёртая capability и оговорка о её природе — в преамбуле docs/architecture.md. Ссылка на спеку поставлена шагом синка: до архивации файла нет и docs.py check краснеет битой ссылкой |
| N-1 | развилка → человеку | Выбран вариант Б: задачей урожая, вместе с shellcheck. Между мерджем и той задачей страж не проверен ничем — названо остатком |
| N-2 | инлайн | Строка в таблице «Механизировано» docs/conventions/README.md |
| N-3 | инлайн | Проверка читаемости места: код 3 и сообщение о нечитаемости вместо «версия не названа» с кодом 1 |
| N-4 | инлайн | Цена отказа от shellcheck названа по замеру в CLAUDE.md и design.md; ложный SC1007 подавлен в скрипте |
Второй прогон: только проход specs
Полный прогон ревью кода не повторялся. Правка по B-1 изменила дельта-спеку и
код, поэтому перепрогнан целенаправленно один проход — specs, владеющий
темой requirements, чей дом и изменился. autotests заменён собственным
прогоном гейта оркестратором; code и basics не перезапускались.
Чем это ограничено, прямо: технический разбор новых функций section,
has_section и collect_doc независимым проходом не выполнялся — их читал
только specs в своей оптике (соответствие норме) и оркестратор. Проход code
видел прежнюю редакцию скрипта. Триаж второй раз не запускался, поэтому находки
ниже не проходили дедупликации и добычи оракула независимым агентом — оракулы у
них свои, прогонами.
Разметка не повторялась, хотя дельта-спека менялась. Причина названа: правка сузила формулировку одного требования внутри уже размеченной capability, не меняя ни набора capability, ни периметра узлов, ни списка тем — план тем остался бы тем же. Это осознанное отступление от правила «дельта-спеки изменились — повтори разметку», а не пропуск.
Находки перепроверки — три, все minor, все отработаны
- S1 закрыта по существу, а не переформулировкой. Проверено обеими сторонами: норма больше не требует единственности от сборочного образа, и код ровно это и делает; правдивая историческая строка о прошлой версии в памятке даёт зелёное, а второе число внутри раздела стека — красное.
- F-1. Таблица
design.mdпродолжала велеть «ровно одно вхождение на файл» — правило, обратное принятой норме, — и переживала бы мердж как единственное описание того, как машина ищет число. Абзац-мотивировка вдобавок стал фактически неверен. Переписаны оба. - F-2, дороже прочих. Норма называла начало раздела и молчала о конце. Раздел закрывался только заголовком того же уровня, а строка, похожая на заголовок, внутри блока кода читалась как настоящий заголовок. Худший исход — ложное зелёное: если строку версии из раздела убрать, а ниже по файлу появится заголовок первого уровня и любое «Go 1.26», шаг добрал бы число из чужого места и промолчал. Граница определена требованием и исполнена кодом; контрольный прогон подтверждает, что ложное зелёное исчезло — шаг теперь честно говорит «версия не названа».
- F-3. Отличие «сломанного окружения» от «пропавшей строки» держалось только на коде: ни один сценарий его не требовал, автотеста нет, гейт гоняет скрипт ровно на согласованном дереве. Записано требованием и сценарием.
Оракулы перепроверки
Регрессионная батарея — 21 прогон, все совпали с ожиданием: 4 мутации по
минору и 4 удаления строки версии (по одной на место), директива toolchain,
патч в теге, смена базы образа, два слоя одной версии, два слоя разных версий,
дубль внутри раздела, история вне раздела, пропавший раздел, PATH без go,
запуск из подкаталога, коды выхода 0/1/2/3. Отдельно проверено, что при
нечитаемом файле строка «не называет версию» не печатается ни разу.
Сверх того: openspec validate --strict — valid; task gate — exit 0;
task image пересобран на golang:1.26-alpine — exit 0; shellcheck на скрипте
чист.
Что осталось открытым после отработки
- N-1: страж не покрыт ничем. Решением человека уехало задачей урожая. Из девятнадцати сценариев дельты машина гоняет один — тот, где всё сошлось; остальные восемнадцать подтверждены разовыми прогонами. До закрытия той задачи правки скрипта идут вслепую.
- H-2: рантайм-база образа берётся «последней доступной». Два образа из
одного коммита с разницей в неделю несут разные
ffmpeg. Вне границ задачи, уезжает урожаем. - Сигнал о заниженной метке остаётся фактом для человека: ось «изменение трогает канон» в правиле выбора метки отсутствует, а на этом прогоне именно она дала обе блокирующие находки. Кандидат в правило — P-5.