Files
transcriber/openspec/changes/archive/2026-08-12-go-1-26-upgrade/review/report.md
T
av 09228f23d8 Go обновлён до 1.26, а расхождение версий теперь роняет гейт
- шаг 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 и
  перестал собираться, а восемь шагов гейта и шесть проходов ревью были зелёными
2026-08-12 10:57:50 +03:00

57 KiB
Raw Blame History

Ревью кода: 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 нумерованных (A1A3, S1S4, C1C3, B1B3) плюс 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 оно не повышает, приоритет повышает. Одна строка с двумя провенансами, а не два пункта.

Суждение триажа — факт для человека, не команда конвейеру:

  1. По записанному правилу разметка верна. docs/review.md, «Триггеры метки»: в группе «Крупное здесь» ближайший пункт — «каркас приложения: сборка фронтенда, раздача статики и шаг гейта разом» — требует трёх вещей сразу, здесь только шаг гейта. В «Незнакомое здесь» не совпал ни один из шести: форма решения (сравнение строк sed/awk, без go и без docker) была названа до работы. Отрицательный тест пройден: миграции, формата файла, контракта API и имени ключа конфига изменение не трогает. review-scope не ошибся против правила, которое у него было.
  2. Ось, на которую указали проходы, в правиле отсутствует. Ни один триггер не говорит о заведении новой capability и о новом каталоге верхнего уровня. А именно эта ось дала обе блокирующие находки прогона — обе про канон, а не про код. Сигнал верен по существу: правило разметки не видит того, что в этом изменении оказалось самым дорогим.
  3. Перезапуска это не требует, метку задним числом не пересматривают. Тема architecture дома не лишилась и без отчёта не осталась — её закрыл basics на глубине «разбор». Честный остаток: тему смотрел проход широкого профиля, а не специализированный, и вопрос «правильно ли выбрано имя и дом capability» остался без независимого разбора (H-1).
  4. Что с этим делать — не здесь. Кандидат в правило вынесен в 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 до и после совпадает):
    1. второй сборочный слой FROM docker.io/library/golang:1.26-alpine AS secondexit 0. Норма гласит: «Каждое место MUST называть версию ровно один раз. Второе вхождение числа в том же месте MUST считаться отказом», и перечень мест включает Dockerfile. Код нормы не исполняет и исполнять не должен: collect Dockerfile "$(read_dockerfile)" 0 передаёт strict=0 намеренно. Контроль: тот же второй слой с golang:1.25-alpine → exit 1, «Dockerfile называет несколько разных версий» — то есть совпадение слоёв проверяется, единственность нет;
    2. в CLAUDE.md дописана правдивая строка - прежде собирались на Go 1.25; долг закрыт задачей go-1-26-upgradeexit 1, «CLAUDE.md называет несколько разных версий»;
    3. в README.md дописан блок кода с # нужен Go 1.26exit 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 отказался. Отказ молчаливый: гейт этого класса не ловит по устройству.
  • Предложение (инлайн, три правки):
    1. четвёртая строка перечня в docs/architecture.md — про toolchain, со ссылкой на спеку и задачу-источник;
    2. оговорка к преамбуле: сегодня она читается «поведение системы здесь не описывается — нормативно оно живёт в openspec/specs/», а toolchain описывает не поведение сервиса; без оговорки преамбула становится неверной в момент архивации;
    3. пункт в 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 и gofmt shell не видят; 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)

  • Оракул — два прогона на копии дерева:

    1. 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
      
    2. прогон с PATH, где нет awkexit 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); сюда же ушла находка autotests A3 как замер цены.
  • Оракул — замер, снятый триажем на этом прогоне:
    • 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 versiongo1.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 «стоит исправить сейчас». Секция «сейчас» заполнена под завязку; ничего не выброшено молча.

Выброшено — поимённо

  1. A2: вопрос темы про шаг конвейера — к диффу не относится.
  2. A3: shellcheck SC1007 — ложное срабатывание, действующего последствия нет; содержание уехало замером в N-4 и P-1.
  3. Порядок шагов в gate: самый дешёвый шаг стоит пятым. Вкусовщина по всем трём условиям: поведения не меняет, стоимости следующего изменения не меняет, записанной конвенции о порядке шагов в проекте нет.
  4. proposal.md: «Внешних зависимостей изменение не трогает» против переезда smithy-go из // indirect в прямые требования. Версия v1.27.7 не менялась, пакет импортируется в internal/adapter/recognizer/yandex/s3.go:15 — штатный результат заказанного go mod tidy. Прозаическая неточность без последствий.
  5. CLAUDE.md:114-115 «краснеет с именем недостающего плагина» — новый шаг плагином не является. Последствия не построено; чинится вместе с P-3.
  6. docs/review.md:194-198: преамбула журнала не сходится с верхней записью. Расхождение приехало предыдущим коммитом, вне диффа.

Четыре строки, которые не принесёт ни один проход

  1. Решения проекта не сверялись. docs/adr/ — процессный документ, прогон его не открывает. Для этого изменения это особенно весомо: design.md ссылается на решения, но ни один проход не открывал docs/adr/ и не проверял, не противоречит ли новая норма уже принятому.
  2. Записанные наблюдения проекта не использовались. docs/research/ — тоже процессный. Всякое число в этом отчёте снято на этом прогоне и сопровождается командой замера.
  3. Поимённая сверка с руководствами по стилю языка не задавалась ни одним проходом. Для этого изменения дыра шире обычного: основной артефакт — POSIX-shell, у которого в проекте нет ни конвенции, ни линтера, ни руководства.
  4. Альтернативной реализации, с которой можно сдиффить решения, у конвейера нет. Вопрос «а можно ли было решить это принципиально иначе — например, одним 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.