- шаг 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 и перестал собираться, а восемь шагов гейта и шесть проходов ревью были зелёными
61 lines
5.1 KiB
Markdown
61 lines
5.1 KiB
Markdown
# ADR-2026-08-12. Объявленную версию Go шаг гейта читает из репозитория, а не спрашивает у инструмента
|
||
|
||
- **Дата:** 2026-08-12
|
||
- **Источник:** [openspec/changes/archive/2026-08-12-go-1-26-upgrade/design.md](../../openspec/changes/archive/2026-08-12-go-1-26-upgrade/design.md), раздел `Decisions`, Решения 1 и 6
|
||
|
||
## Решение
|
||
|
||
Шаг сверки версий добывает числа чтением файлов и **не зовёт `go` ни в каком
|
||
виде** — ни `go mod edit -json`, ни `go list -m`, ни `go env`. Директива
|
||
`toolchain` в `go.mod` при этом запрещена: её наличие роняет шаг.
|
||
|
||
Дословно из источника:
|
||
|
||
> Способ это не самый удобный: разбор директивы через `go mod edit -json` короче
|
||
> и надёжнее регулярного выражения. Он же и опасный: вызов `go` тянет за собой
|
||
> `GOTOOLCHAIN`, `$PATH` и установленный тулчейн, а при непустом `GOTOOLCHAIN`
|
||
> `go` вправе полезть в сеть за нужной версией — то есть требование «без сети»
|
||
> перестало бы выполняться. Хуже того, исход шага стал бы зависеть от машины, а
|
||
> не от коммита.
|
||
|
||
## Почему
|
||
|
||
Причина не в аккуратности, а в том, что **ровно этой подменой и держался дефект,
|
||
ради которого шаг заведён**. 2026-08-12 требование модуля уехало на 1.25,
|
||
сборочный образ остался на 1.24, образ перестал собираться — и восемь шагов гейта
|
||
с шестью проходами ревью показали зелёное, потому что `go build ./...` шёл на
|
||
хостовом Go. Проверка судила по тому, что стоит на машине, вместо того что
|
||
записано в коммите. Шаг, зовущий `go`, воспроизвёл бы ту же подмену внутри себя:
|
||
зелёный там, где стоит нужная версия, и другой ответ на другой машине.
|
||
|
||
Отсюда же запрет `toolchain`. Директива — штатный механизм Go и очевидное
|
||
решение задачи расхождения: она заставила бы Go скачать нужную версию самому, и
|
||
сверять стало бы нечего. Отвергнута намеренно:
|
||
|
||
> Директива `toolchain` заставила бы Go скачивать нужный тулчейн сам, и
|
||
> расхождение с образом перестало бы ломать сборку. Но она же превращает сборку
|
||
> образа в сетевую операцию, а сборочный слой качает тулчейн при каждой сборке.
|
||
> Дороже и менее предсказуемо, чем строка сравнения.
|
||
|
||
Вдобавок она вводит **пятое место**, называющее версию, — то, которого закрытый
|
||
перечень из четырёх мест не знает: при `toolchain go1.27.0` четыре объявленных
|
||
числа сойдутся, а собирать будет пятое.
|
||
|
||
## Последствия
|
||
|
||
- `+` исход шага есть функция коммита. Проверено прогоном: с `PATH`, где нет
|
||
`go`, шаг даёт тот же код выхода и тот же вывод.
|
||
- `+` требование «без сети» выполняется по построению, а не обещанием: под
|
||
`strace` шаг не делает ни одного сетевого вызова.
|
||
- `+` пятое место закрыто: `toolchain` в `go.mod` роняет шаг с названной
|
||
причиной.
|
||
- `−` разбор держится на регулярных выражениях `sed`/`awk` вместо готового
|
||
разбора, который дал бы сам `go`. Это дороже в сопровождении и хрупче: правка
|
||
образца ломает смежный случай беззвучно.
|
||
- `−` запрет `toolchain` придётся снять или пересмотреть, если зависимость
|
||
однажды потребует версию выше той, что стоит у нас. Тогда эта запись
|
||
пересматривается, а не обходится.
|
||
- `−` проверять сам скрипт нечем: `shellcheck` в гейт не заведён, тестов у него
|
||
нет. Из девятнадцати сценариев нормы машина гоняет один — тот, где всё
|
||
сошлось. Остаток объявлен и уехал отдельной задачей.
|