tasks: закрыта задача go-1-26-upgrade, заведён урожай ревью

- четыре задачи из находок ревью и заметки владельца: проверки shellcheck и
  hadolint с тестом скрипта сверки версий, закрепление рантайм-базы образа,
  коды выхода шагов гейта, настройка конвейера ревью
- INBOX разобран целиком и очищен
This commit is contained in:
av
2026-08-12 14:26:07 +03:00
parent 09228f23d8
commit d676df8a27
7 changed files with 190 additions and 54 deletions
+57
View File
@@ -0,0 +1,57 @@
# 🧹 Завести недостающие проверки: shellcheck, hadolint и тест скрипта сверки версий
- **Тип:** chore
- **Категория:** Очередь
- **Зачем:** Скрипты оболочки и Dockerfile машина не проверяет: shellcheck и hadolint не заведены, lefthook смотрит только gitleaks, а единственный исполняемый скрипт проекта на shell — scripts/check-go-version.sh — разбирали на ревью глазами построчно, потому что машине его показать нечем.
Собрана из двух источников: заметки владельца от 2026-08-12 про lefthook и
находки N-1 ревью `go-1-26-upgrade` про непроверенный скрипт сверки версий.
Собраны вместе, потому что правятся одним заходом: и то и другое — недостающие
проверки на файлы, которых сегодняшний гейт не видит.
Цена shellcheck замерена на прогоне 2026-08-12: shell-файлов в репозитории два,
в гейте из них один, единственное замечание — ложный `SC1007` на идиому
`CDPATH= cd`, и оно уже подавлено в скрипте директивой. То есть шаг стоит одной
строки.
Отдельная половина — **скрипт сверки версий не проверен ничем.** Его норма
(`openspec/specs/toolchain/spec.md`) описывает девятнадцать сценариев, а гейт
гоняет скрипт ровно на согласованном дереве, то есть проверяет один из них — тот,
где всё сошлось. Остальные восемнадцать подтверждены разовыми ручными прогонами
2026-08-12 и после первой же правки образца перестанут выполняться молча.
Проверки go (`go vet`, `gofmt`) в гейте уже есть; в заметке речь о том, чтобы
часть из них ловил и pre-commit, не дожидаясь полного прогона.
## Затрагивает
- `lefthook.yml` — набор предкоммитных проверок;
- набор шагов `task gate` в `Taskfile.yml`;
- новый тест-скрипт для `scripts/check-go-version.sh`;
- `Dockerfile` — как объект проверки hadolint, не правкой содержимого;
- семантика гейта в `CLAUDE.md`, раздел «Гейт», подраздел «чего в гейте
намеренно нет»;
- таблица «Механизировано» в `docs/conventions/README.md`.
## Критерии приёмки
- `shellcheck` краснеет на негодном скрипте оболочки. Оракул — внести в
`scripts/check-go-version.sh` неэкранированную переменную и прогнать шаг: он
краснеет; вернуть — зеленеет.
- `hadolint` краснеет на негодном `Dockerfile`. Оракул — тот же приём: снять
закрепление версии базового образа и прогнать шаг.
- Каждый сценарий нормы `toolchain` проверяется машиной, а не памятью. Оракул —
тест-скрипт мутирует дерево по одному сценарию за прогон; снять любую проверку
из `check-go-version.sh` — тест краснеет и называет сценарий.
- Предкоммитные проверки не дублируют гейт молча. Оракул — `CLAUDE.md`, раздел
«Гейт», называет, что именно ловит pre-commit и что остаётся только гейту.
- Гейт зелёный целиком. Оракул — `task gate`.
## Рамки
Мутационный тест работает на временной копии дерева: `t.TempDir()`-подход, в
`data/` не писать, рабочее дерево после прогона обязано остаться нетронутым.
Заметка владельца называет проверки go в lefthook, но полный набор `task gate` в
pre-commit не переносится: он идёт минуты, а pre-commit обязан быть быстрым.
Что именно из него дублировать — решение этой задачи, и его надо назвать.
+35
View File
@@ -0,0 +1,35 @@
# 🧹 Привести коды выхода шагов гейта к общему словарю
- **Тип:** chore
- **Категория:** Очередь
- **Зачем:** CLAUDE.md объявляет словарь кодов общим для четырёх проверочных шагов и описывает код 3 как «каталог или файл не найден», но все четыре обёртки в Taskfile.yml при отсутствующем скрипте отдают 1 («дрейф»), из-за чего читатель кода идёт искать разъехавшееся там, где просто неполно дерево.
Расхождение старое и повторено всеми четырьмя обёртками одинаково — дефектом
одного изменения оно не является. Сами скрипты словарю следуют: при пропавшем
источнике `scripts/check-go-version.sh` выходит с кодом 3, проверено прогоном
2026-08-12.
Развилка тут есть, и решать её придётся: либо обёртки приводятся к 3, либо из
описания кода 3 в памятке убирается «или файл не найден» и `exit 1` объявляется
нормой для «скрипта шага нет». Второй вариант меняет только текст памятки, а не
код всех четырёх обёрток, и тоже не расходится с фактами.
## Затрагивает
- шаги `docs`, `tasks`, `openspec` и `go-version` в `Taskfile.yml`;
- словарь кодов в `CLAUDE.md`, раздел «Гейт».
## Критерии приёмки
- Один и тот же исход даёт один и тот же код у всех четырёх шагов. Оракул —
убрать по очереди каждый из четырёх скриптов и прогнать его шаг: коды
совпадают между собой и с тем, что обещает памятка.
- Памятка и поведение сошлись. Оракул — прочитать словарь в `CLAUDE.md` и
сверить с четырьмя прогонами выше поимённо.
- Гейт зелёный целиком. Оракул — `task gate`.
## Рамки
Скрипты плагинов (`docs.py`, `tasks.py`, `openspec.py`) не правятся: они живут
вне репозитория, и их коды уже следуют словарю. Правятся только обёртки в
`Taskfile.yml` и текст памятки.
-53
View File
@@ -1,53 +0,0 @@
# 🧹 Обновить Go до 1.26 и сверять версию шагом гейта
- **Тип:** chore
- **Категория:** Очередь
- **Зачем:** Модуль объявляет go 1.25.0, образ собирается на golang:1.25-alpine, на машине разработки стоит 1.26.5, и расхождение этих чисел не ловит ни один шаг гейта: разъехавшийся Dockerfile прошёл весь конвейер зелёным.
Задача собрана из двух: заметки владельца от 2026-08-12 про обновление и находки
ревью `pocketbase-storage` про несверяемые версии. Собраны вместе, потому что
правятся одним заходом: поднимать число в трёх местах и не заводить сверку
значит завести то же расхождение заново на следующей зависимости.
Версия 1.25 в модуле появилась не выбором, а требованием PocketBase: `go mod
tidy` поднял директиву сам, а образ подтянули следом. То есть сегодняшнее число
никем не назначено.
Случай расхождения записан журналом дефектов `docs/review.md` за 2026-08-12:
`go mod tidy` поднял директиву до 1.25, `Dockerfile` остался на
`golang:1.24-alpine` с `GOTOOLCHAIN=local`, и шесть проходов ревью вместе с
восемью шагами гейта показали зелёное. Нашлось случайным свойством прогона, а не
устройством проверок.
Сборку образа в гейт не заводим — она дорога. Дешёвая замена: сравнение двух
строк.
## Затрагивает
- директива `go` в `go.mod`;
- сборочный слой `Dockerfile`;
- строка стека в `CLAUDE.md` и в `README.md`;
- набор шагов `task gate` в `Taskfile.yml`;
- семантика гейта в `CLAUDE.md`, раздел «Гейт».
## Критерии приёмки
- Модуль, образ и документы называют одну версию Go. Оракул — `grep` по четырём
местам: `go.mod`, `Dockerfile`, `CLAUDE.md`, `README.md`; все дают одно число.
- Сборка на объявленной версии проходит. Оракул — `task image` и
`CGO_ENABLED=0 go build ./...` на чистом дереве.
- Расхождение версий роняет гейт. Оракул — прогон `task gate` на дереве, где
версия в `Dockerfile` понижена на минор: шаг краснеет и называет оба числа.
- Совпадение гейт не роняет, а сам шаг не требует docker и работает без сети.
Оракул — `task gate` на неизменённом дереве и прогон с
`DOCKER_HOST=/dev/null`.
- Гейт зелёный целиком. Оракул — `task gate`.
## Рамки
Версию выбирает человек: обновление до неподдерживаемой ещё зависимостями версии
ломает сборку молча. Выкладку не запускаем.
Сравнение строковое: `Dockerfile` называет `golang:<major>.<minor>-alpine`, а
`go.mod``<major>.<minor>.<patch>`. Сверяются мажор и минор; патч сборочного
образа своей жизнью живёт законно.
+42
View File
@@ -0,0 +1,42 @@
# 🧹 Закрепить версию рантайм-базы образа
- **Тип:** chore
- **Категория:** Очередь
- **Зачем:** Финальный слой Dockerfile собирается на alpine:latest, а task image идёт с --pull, поэтому два образа из одного коммита с разницей в неделю несут разный ffmpeg — регрессия конвертации после такой пересборки выглядит как задачи в failed при пустом диффе репозитория, и откат на прежний коммит её не чинит.
Класс тот же, ради которого задача `go-1-26-upgrade` завела шаг сверки версий:
объявленное и собранное расходятся, а сверять некому. Разница в том, что там
расходились числа внутри репозитория, а здесь репозиторий вообще не называет
числа — версия рантайм-базы задана словом «последняя».
Цена названа условием, а не замером: сколько именно и как часто меняется
`ffmpeg` в `alpine:latest`, никто не мерил. Известно, что он в таблице отказов
`docs/architecture.md` стоит со столбцом «Отдаёт мусор»: «Выходной файл пуст,
отказ вылезет на распознавании».
Перечень мест, где объявлена версия Go, эта задача не расширяет: он закрыт
решением `go-1-26-upgrade`, и рантайм-база — другой предмет.
## Затрагивает
- финальный слой `Dockerfile` — тег базового образа;
- раздел «Деплой» в `docs/architecture.md`.
## Критерии приёмки
- Репозиторий называет версию рантайм-базы числом, а не словом «последняя».
Оракул — `grep` по `Dockerfile`: тег финального слоя содержит мажор и минор.
- Два образа, собранные из одного коммита, несут один `ffmpeg`. Оракул — собрать
`task image` дважды с интервалом и сравнить `ffmpeg -version` внутри обоих.
- Решение записано там, где его найдёт разбор отказа конвертации. Оракул —
раздел «Деплой» `docs/architecture.md` называет закреплённую базу и причину.
- Гейт зелёный целиком. Оракул — `task gate`.
## Рамки
Версию базы выбирает человек: перескок на минор новее может принести другой
`ffmpeg` ровно с тем же исходом, ради предотвращения которого задача и заведена.
Выкладку не запускаем.
Если решено оставить `latest`, задача кончается записью принятого остатка в
`docs/architecture.md`, а не правкой `Dockerfile`. Это законный исход.
@@ -0,0 +1,48 @@
# 🧹 Настроить конвейер ревью по итогам прогона go-1-26-upgrade
- **Тип:** chore
- **Категория:** Очередь
- **Зачем:** Прогон вскрыл две прорехи настройки: «Типовые узлы» знают только рантайм и не знают рода «проверочный шаг набора проверок», а «Триггеры метки» не видят оси «изменение трогает канон» — и именно она дала обе блокирующие находки.
Обе прорехи одного рода — настройка конвейера, живущая в `docs/review.md`, — и
правятся одним заходом.
**Род узла.** Сегодня перечень покрывает шаг конвейера, транспорт, клиент
внешнего сервиса, репозиторий и обёртку над внешним процессом. Скриптов гейта в
проекте четыре, и свойства у них свои: отличает ли шаг расхождение от сломанного
окружения, есть ли исход функция коммита, а не машины, покрыт ли шаг мутационным
прогоном. Без этого рода находка «шаг набора проверок не проверен ничем»
добывается заново каждый раз.
**Ось метки.** Два прохода независимо сказали, что метка `medium` занижена:
изменение заводило новую capability и новый каталог верхнего уровня. Триаж
проверил и подтвердил — по записанному правилу разметка была верна, потому что
такой оси в правиле нет вовсе, а обе блокирующие находки прогона пришли именно
по ней.
Провенанс обоих — отчёт
`openspec/changes/archive/2026-08-12-go-1-26-upgrade/review/report.md`,
кандидаты в правило P-2 и P-5.
## Затрагивает
- раздел «Типовые узлы» в `docs/review.md`;
- раздел «Триггеры метки» в `docs/review.md`.
## Критерии приёмки
- Проверочный шаг набора проверок описан родом со своими свойствами. Оракул —
открыть «Типовые узлы» и найти род; свойств не меньше трёх, и каждое
сформулировано проверяемо.
- Правило выбора метки видит заведение новой capability и нового каталога
верхнего уровня. Оракул — приложить правило к прогону `go-1-26-upgrade` задним
числом: метка выходит выше `medium`.
- Гейт зелёный целиком. Оракул — `task gate`.
## Рамки
Правится только настройка конвейера в `docs/review.md`. Устав самого конвейера
живёт в плагине `av-dev-code` и этой задачей не трогается: проект вправе
настраивать свои темы и триггеры, но не переписывать чужой скилл.
Журнал дефектов в том же файле не трогается — записи неизменяемы.