Гейт ищет гонки, переписанные шаги схемы и девять новых классов дефектов

- включены noctx, contextcheck и bodyclose (отмена доходит до внешнего вызова,
  контекст приезжает сверху, тело ответа закрывается), nilerr, rowserrcheck и
  sqlclosecheck (отказ не теряется молча), testifylint и nolintlint (форма
  утверждения и форма подавления), плюс errcheck check-type-assertions:
  непроверенное приведение типа паникует, и check-blank его не видит
- заведён шаг tests: go test -race, потому что «результат пишет только держатель
  захвата» — утверждение об одновременности. Без компилятора C шаг гоняет тесты
  без детектора и краснеет кодом 3 после них: гонки не повод отнимать у гейта
  сами тесты
- заведён шаг migrations: у файла шага схемы допустим один статус — A. Баз диффа
  две, BASE и HEAD: первая отвечает на «уже уехал» настолько, насколько свежа
  origin/master, вторая ловит правку закоммиченного шага независимо от неё.
  Каталог берётся из docs/.docs.json, пустой каталог роняет шаг
- единственное подавление — noctx на httptest.NewRequest в проверках: за
  фикстурой запроса внешнего собеседника нет. Граница проверена мутацией —
  http.Get и exec.Command из проверки правилу по-прежнему подсудны
This commit is contained in:
av
2026-08-13 10:28:12 +03:00
parent f494dcb83e
commit bc5c35790e
3 changed files with 132 additions and 2 deletions
+78 -1
View File
@@ -33,17 +33,94 @@ tasks:
echo "gofmt: файлы выше не отформатированы"
exit 1
fi
- go test ./...
- task: tests
- golangci-lint run
- task: shell
- task: dockerfile
- task: go-version
- task: migrations
- task: docs
- task: tasks
- task: openspec
# Последним: единственный шаг, которому нужна сеть, и самый долгий.
- task: vulns
tests:
desc: 'Тесты с детектором гонок'
cmds:
# Гонки ищет детектор, а не чтение кода: у сервиса три воркера ходят в одну
# очередь, и «результат пишет только держатель захвата» — утверждение о
# одновременном доступе. Детектору нужен CGO и компилятор C; сборка
# приложения по-прежнему обходится без них (CLAUDE.md, «Стек»), поэтому их
# отсутствие — отказ окружения, код 3, а не отказ проверки.
# Окружение проверяется **после** обычного прогона, а не вместо него:
# отсутствие компилятора отнимает у гейта поиск гонок, но не должно
# отнимать сами тесты. Порядок проверок — сперва компилятор: без него
# совет «включи CGO_ENABLED=1» бесполезен.
- |
if ! command -v gcc >/dev/null 2>&1 && ! command -v clang >/dev/null 2>&1; then
go test ./... || exit 1
echo "тесты прошли, но гонки не искали: детектору нужен компилятор C"
echo "ни gcc, ни clang не найдены в PATH; поставь: apt install gcc"
exit 3
fi
if [ "$(go env CGO_ENABLED)" != "1" ]; then
go test ./... || exit 1
echo "тесты прошли, но гонки не искали: детектору нужен CGO"
echo "CGO_ENABLED=$(go env CGO_ENABLED); включи: CGO_ENABLED=1 task gate"
exit 3
fi
go test -race ./...
migrations:
desc: 'Применённый шаг схемы не переписывается'
cmds:
# Инвариант CLAUDE.md (critical): хранилище считает применённое по имени
# файла шага, поэтому изменить уехавший шаг нельзя — только добавить новый.
# Компилятор этого не держит, и до этого шага не держало ничто.
#
# Судится каталог шагов против базы диффа: у файла шага допустим один
# статус — `A`. Правка (`M`), удаление (`D`) и переименование (`R`) красят.
# `migrations.go` под правило не подпадает: строка `Register` у нового шага
# прибавляется именно там, и запрет на него запретил бы заведение шага.
- |
if ! git rev-parse --verify --quiet "{{.BASE}}" >/dev/null 2>&1; then
echo "база диффа не найдена: {{.BASE}}"
echo "задай свою: task migrations BASE=<rev>"
exit 3
fi
# Каталог шагов берётся из docs/.docs.json — там он уже записан ключом
# `migrations` для сверки документов. Свой литерал завёл бы факту второй
# дом: каталог переехал бы, а один из двух стражей молча позеленел.
dir=$(python3 -c 'import json,sys; print(json.load(open("docs/.docs.json"))["migrations"])' 2>/dev/null) || dir=""
if [ -z "$dir" ] || [ ! -d "$dir" ]; then
echo "каталог шагов схемы не найден: ключ migrations в docs/.docs.json → '$dir'"
exit 3
fi
# Страж предмета: правило, потерявшее файлы, стало бы вечно зелёным от
# одного переименования — тот же приём, что у правил `internal/archrules`.
if [ -z "$(ls "$dir" | grep -E '^[0-9]{12}_.*\.go$')" ]; then
echo "в $dir нет ни одного файла шага: правило потеряло предмет"
echo "поправь шаблон имени в этом шаге либо ключ migrations в docs/.docs.json"
exit 3
fi
# Баз две, и вторая обязательна. `{{.BASE}}` отвечает на «шаг уже уехал»
# ровно настолько, насколько свеж `origin/master`: отставшая ссылка
# читает весь каталог как добавленный, и правило молчит. `HEAD` ловит
# правку закоммиченного шага в рабочем дереве независимо от ссылки.
for base in {{.BASE}} HEAD; do
touched=$(git diff --name-status "$base" -- "$dir" \
| grep -E '[0-9]{12}_[^/]*\.go$' \
| grep -vE '^A[[:space:]]' || true)
if [ -n "$touched" ]; then
echo "база $base:"
echo "$touched"
echo "применённый шаг схемы переписан: изменение схемы — только новым файлом шага"
echo "(CLAUDE.md, «Инварианты», critical: хранилище считает применённое по имени файла)"
exit 1
fi
done
shell:
desc: 'shellcheck на скрипты оболочки'
cmds: