Files
transcriber/openspec/changes/archive/2026-08-12-go-1-26-upgrade/tasks.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

15 KiB
Raw Blame History

Критерии приёмки

От постановки

Перенесены дословно из записи задачи 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 на POSIX sh: достать мажор и минор из директивы 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 целиком зелёный