- шаг 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 и перестал собираться, а восемь шагов гейта и шесть проходов ревью были зелёными
15 KiB
15 KiB
Критерии приёмки
От постановки
Перенесены дословно из записи задачи tasks/items/go-1-26-upgrade.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.
От рубрики ревью дизайна
Проход review-rubric, стадия ревью дизайна. Рубрика на род узла «проверочный
шаг набора проверок, читающий разнородные источники».
- Мутационный оракул на каждый источник. Четыре прогона: по очереди понизить
минор в
go.mod,Dockerfile,CLAUDE.md,README.md. В каждом шаг краснеет и называет именно изменённый источник. Оракул — четыре прогона, четыре красных, четыре разных сообщения. - Пустая выборка — отказ, а не согласие. Четыре прогона: по очереди убрать строку версии из каждого источника. Каждый даёт отказ с именем этого источника.
- Исход — функция коммита, а не машины. Оракул — прогон с
PATH, из которого убранgo: тот же код выхода и тот же вывод, что и при установленномgo. - Второе вхождение числа не проходит молча. Оракул — дописать в
CLAUDE.mdвторое «Go 1.25» и прогнать шаг: он краснеет, а не судит по первому совпадению. - Патч и база образа свободны. Оракул — два прогона:
golang:1.26.5-alpineиgolang:1.26-bookwormвDockerfile, оба зелёные. - Сообщение масштабируется на четыре места. Оракул — прогон на дереве, где один источник разошёлся с тремя: вывод содержит четыре пары «место: число».
- Словарь кодов выхода объявлен. Оракул — четыре прогона: сошлось, расхождение, лишний аргумент, файл источника убран — дают 0, 1, 2 и 3.
- Исход не зависит от рабочего каталога. Оракул — прогон из корня и из подкаталога дают один код выхода.
- Шаг только читает. Оракул — контрольная сумма дерева до и после прогона совпадает, два прогона подряд дают одинаковый вывод.
- Шаг не ходит в сеть. Оракул — прогон под
strace -f -e trace=socket,connect,sendto,recvfrom: ни одного сетевого вызова. - Граница «чего шаг не проверяет» записана. Оракул — раздел «Гейт» в
CLAUDE.mdсодержит строку о том, что сборка образа в набор проверок по-прежнему не входит. - Директива
toolchainне проходит незамеченной. Оракул — дописатьtoolchain go1.27.0вgo.modи прогнать шаг: он краснеет.
1. Шаг сверки версий
- 1.1 Написать
scripts/check-go-version.shна POSIXsh: достать мажор и минор из директивыgoвgo.mod, из тегаFROM golang:вDockerfile, из строки стека вCLAUDE.mdи из строки стека вREADME.md - 1.2 Пути строить от корня репозитория, а не от текущего каталога
- 1.3 Не звать
goни в каком виде: ниgo mod edit, ниgo list, ниgo env. Исход обязан быть функцией содержимого файлов - 1.4 Тег образа разбирать по форме
golang:<мажор>.<минор>[.<патч>][-<база>]— патч и база отбрасываются - 1.5 Запретить директиву
toolchainвgo.mod— отказ с названием причины - 1.6 Второе вхождение числа в одном источнике — отказ; для
DockerfileвсеFROM golang:обязаны давать одно число - 1.7 Отказ при расхождении: сообщение печатает все четыре места и число каждого, а не одну пару
- 1.8 Отказ при ненайденном числе: место названо поимённо, «нечего сравнивать» исходом не считается
- 1.9 Коды выхода по словарю проекта: 0 сошлось, 1 расхождение, 2 ошибка употребления, 3 окружение
- 1.10 Не использовать GNU-only флаги (
grep -P,sed -Eс расширениями,mapfile); сделать скрипт исполняемым - 1.11 Завести шаг
go-versionвTaskfile.ymlпо образцу соседних шагов, включая внятный отказ при отсутствии скрипта - 1.12 Включить шаг в
gate
2. Подъём версии до 1.26
- 2.1
go.mod: директиваgo 1.26.0 - 2.2
Dockerfile: сборочный слой наgolang:1.26-alpine - 2.3
CLAUDE.md, раздел «Стек»: «Go 1.26» — ровно одно вхождение числа на файл, включая абзац из шага 3.1 - 2.4
README.md, раздел «Технологии»: завести строку версии Go в форме, которую находит скрипт - 2.5
go mod tidyи проверка, чтоgo.sumне разъехался и директиваtoolchainне появилась
3. Документы
- 3.1
CLAUDE.md, раздел «Гейт»: новый шаг в перечне того, что красит безусловно; строка о том, что сборка образа в гейт по-прежнему не входит; словарь кодов выхода распространён на четвёртый проверочный шаг - 3.2
docs/review.md: запись за 2026-08-12 получает строку о том, чем дефект закрыт - 3.3
docs/architecture.md, преамбула: четвёртая capability в перечне и оговорка о том, что она нормирует не поведение сервиса (находка ревью B-2). Ссылка на спеку ставится шагом синка, после архивации: до неё файла нет иdocs.py checkкраснеет битой ссылкой - 3.4
docs/conventions/README.md, таблица «Механизировано»: строка про новое машинное правило (находка ревью N-2)
5. Отработка находок ревью кода
Отчёт триажа — openspec/changes/go-1-26-upgrade/review/report.md.
- 5.1 N-3: нечитаемое место даёт код 3 с честным сообщением, а не «версия не
названа» с кодом 1. Проверка читаемости стоит рядом с проверкой существования:
подстановка команд в аргументе теряет код возврата, а в
read_docстатус конвейера берётся от последней команды - 5.2 N-4: цена отказа от
shellcheckназвана по замеру — один shell-скрипт, а не четыре. Правка вCLAUDE.mdи вdesign.md - 5.3 Подавление ложного
SC1007в скрипте, чтобы заведение линтера потом стоило ровно одной строки шага.shellcheckна скрипте чист
6. B-1: правило множественности разведено по местам
Находка B-1 меняла требование, поэтому прошла через чекпоинт заново. Решение человека: развести правило по местам и сузить рамку до раздела.
- 6.1 Требование: единственность — от раздела стека в документах, от
сборочного образа — совпадение всех вхождений
FROM golang:, требование модуля единственно по устройству файла - 6.2 Пять новых сценариев: дубль в разделе, число вне раздела, два слоя с одной версией, два слоя с разными, пропавший раздел
- 6.3 Скрипт читает раздел (
## Стекв памятке,## Технологиив README), а не файл целиком - 6.4 Пропавший раздел — свой исход с именем раздела, а не «версия не названа»
- 6.5 Прогоны: история о прошлой версии вне раздела — зелено; пример команды в README вне раздела — зелено; дубль внутри раздела — красно; два слоя одной версии — зелено; два слоя разных — красно; раздела нет — красно с его именем
- 6.6 Регрессия прежней батареи: 8 мутаций по местам,
toolchain, патч и база тега,PATHбезgo, подкаталог, коды 0/1/2/3 — все как прежде - 6.7
openspec validate --strictиtask gateзелёные
7. Отработка перепроверки спек после правки B-1
Целевой перепрогон прохода specs: правка коснулась ровно его темы. Прежняя
находка S1 подтверждена закрытой по существу; три новые, все minor.
- 7.1 F-2, ложное зелёное: раздел кончался только заголовком того же уровня, а заголовок внутри блока кода читался как настоящий — число доставалось из-за границы раздела. Граница определена в требовании и исполнена в коде: следующий заголовок того же или более высокого уровня, блок кода заголовков не даёт, хвостовые пробелы в заголовке не значат ничего
- 7.2 F-2: два сценария — заголовок раздела внутри блока кода, раздел закрыт заголовком верхнего уровня
- 7.3 F-3: отличие «сломанного окружения» от «пропавшей строки» записано требованием и сценарием, а не только кодом
- 7.4 F-1: таблица
design.mdвелела правило, обратное принятой норме («одно вхождение на файл»), и абзац-мотивировка стал фактически неверен — переписаны оба - 7.5 Регрессия 21 прогоном: 8 мутаций по местам,
toolchain, патч и база тега, два слоя одной и разных версий, дубль в разделе, история вне раздела, пропавший раздел,PATHбезgo, подкаталог, коды 0/1/2/3 — все совпали с ожиданием - 7.6
shellcheckна скрипте чист
4. Проверка
- 4.1
CGO_ENABLED=0 go build ./...иgo test ./...на go1.26.5 - 4.2
task imageсобирает образ наgolang:1.26-alpine - 4.3 Четыре мутации по минору — по одной на источник; в каждой шаг краснеет и называет изменённый источник; дерево возвращается как было
- 4.4 Четыре мутации удалением строки версии — по одной на источник; в каждой отказ называет место
- 4.5 Прогон с
PATHбезgo, прогон сDOCKER_HOST=/dev/null, прогон подstraceбез сетевых вызовов — исход тот же - 4.6 Прогон из подкаталога — тот же код выхода
- 4.7 Прогоны на
golang:1.26.5-alpineиgolang:1.26-bookworm— зелёные - 4.8 Прогон с дописанным
toolchain go1.27.0— красный - 4.9 Четыре прогона на коды выхода: 0, 1, 2, 3
- 4.10 Контрольная сумма дерева до и после прогона совпадает
- 4.11
task gateцеликом зелёный