ADR 0178 — Тег стороннего образа живёт ВНУТРИ ссылки, перед digest
Статус
Accepted. 2026-09-29 (AT-139, история HS-024, эпик EP-016). Поверх ADR 0159, который продолжает действовать во всём, что не является ФОРМОЙ ссылки: digest индекса, три дерева, то, что check не покрывает и почему. Решение сопровождающего от 2026-09-27, записанное в заметке AT-139, которая называла его «ADR 0171» — номер, оставшийся зарезервированным и так и не написанный; этот ADR и есть тот, а 0171 остаётся свободным.
У решения было две половины. Этот ADR фиксирует вошедшую — форму и lint, — и объявляет, почему другая, включение Dependabot для образов, не вошла (см. Чего этот ADR НЕ делает).
Контекст
ADR 0159 закрепил 39 ссылок на сторонние образы по digest, с тегом
в комментарии рядом (neo4j@sha256:… # 5.26-community; в Dockerfile — на
строке выше), повторяя форму actions (uses: x@<sha> # v4). И
объявил цену: digest замораживает, и «включение экосистемы docker Dependabot
— отдельное решение».
Решение пришло 2026-09-27 — Dependabot docker — с условием: сначала измерить,
проходят ли его PR lint закрепления. Измерение (lane раунда 15, которая
остановилась на шлюзе, без PR) опрокинуло форму ADR 0159:
- Dependabot не читает тег в комментарии ни в каком формате. Комментарий,
который читает его
github-actions, — соглашение ТОЙ экосистемы; уdockerнет аналога. - Закрепление только по digest обновляется до digest
latest(update_checker.rb). pgvector, neo4j и node ушли бы на другую major-версию, а комментарий по-прежнему утверждал бы старый тег, и lint был бы ЗЕЛЁНЫМ — check проверял наличие комментария, а не его правдивость. - Дубликаты compose × workflow (pgvector и ollama в
services:ci.yml/golden-set-*.yml) заставили бы каждый PR бота рождаться красным по правилу «один тег, два digest» самого ADR 0159: ни одна экосистема не читаетservices:workflow. imageNameCloudNativePG тоже остаётся вне какой-либо экосистемы.
А в репозитории была ссылка, у которой уже была другая форма, по другой
причине: imageName CNPG — это postgresql:16.10@sha256:…, потому что webhook
оператора ОТКЛОНЯЕТ ссылку только с digest — он читает из тега версию Postgres. То
есть форма imagem:tag@sha256: уже была доказана в репозитории
Kubernetes, kubeconform и bootstrap k3d.
Решение
1. Ссылка на сторонний образ становится imagem:tag@sha256:<índice>.
image: neo4j:5.26-community@sha256:22ec5cd0…
FROM node:24.11.1-alpine3.21@sha256:b8f7c905… AS deps
При наличии обоих Docker скачивает по digest: тег — это информация для читающего, а digest решает байты — неизменяемость ADR 0159 остаётся нетронутой. Меняется то, что теперь тег читается ОДНИМ И ТЕМ ЖЕ синтаксисом тем, кто исполняет (Docker), тем, кто обновляет (Dependabot), и тем, кто ревьюит. Digest по-прежнему — digest ИНДЕКСА, никогда не платформы.
39 ссылок трёх деревьев мигрировали без замены единого digest — 29 в
docker/ (16 в compose, 13 FROM), 4 в deploy/k8s/ и 6 в
.github/workflows/. Комментарии с тегами ушли: ссылка — источник, а
одинаковый комментарий был бы второй копией, которая стареет.
2. Lint выводит тег из ссылки и отклоняет то, что ему противоречит.
scripts/ci/imagens-pinadas.ts отклоняет, помимо изменяемой ссылки:
- digest без встроенного тега — включая форму ADR 0159, с тегом только в
комментарии, потому что именно её бот обновил бы до
latest; - комментарий, утверждающий другой тег — комментарий, который является ОДНИМ токеном с цифрой
(
# 5.26-community,# pg16) и расходится со встроенным тегом. Комментарий по-прежнему разрешён (решение гласит «комментарий вне или равный»); отклоняется то, что остаётся после повышения версии. Проза и отдельное слово без цифры не утверждают версию и не оцениваются; - комментарий в конце строки
FROM— по-прежнему отклоняется, теперь с собственной причиной: парсер Docker его не принимает (ADR 0159); - один и тот же тег с двумя digest — ключом становится
nome:tag, прочитанный из ссылки.
Каждое правило доказано мутацией в imagens-pinadas.spec.ts: отключение
любого из пяти роняет хотя бы один тест.
Последствия
Форма ADR 0159 начинает отклоняться. Тот, кто скопирует старый пример (из ADR
0159, из старого PR), получит сообщение с требованием поставить тег inline, с
объяснением. Runbook (Bumping a third-party image), страница цепочки
поставок и CLAUDE.md начинают показывать только новую форму.
Комментарий перестаёт быть обязательным, и это инверсия аргумента ADR 0159, а не ослабление: там он был обязательным, потому что был ЕДИНСТВЕННЫМ местом тега; здесь тег находится в ссылке, и check требует, чтобы он существовал и чтобы ничто рядом ему не противоречило.
Ни одна версия не меняется. Каждый digest тот же, что и раньше; docker build --check
проходит на девяти Dockerfile, настоящая сборка (docker/backup/Dockerfile.prod)
завершается, docker compose config проходит на затронутых compose, а
kubectl kustomize + kubeconform — на overlays.
Чего этот ADR НЕ делает
Не включает Dependabot для образов, и причина — блокировка, которую решение от
27.09 не покрывало. Решение требовало «шаг, который выравнивает services:
workflows в PR бота», по образцу dependabot-justifica-pin.ts. Этому шагу
пришлось бы ПУШИТЬ commit, изменяющий .github/workflows/*.yml, в ветку
бота, а GITHUB_TOKEN не может этого сделать: у него нет и не может быть через
permissions: разрешения workflows, и GitHub отклоняет push
(«refusing to allow a GitHub App to create or update workflow … without
workflows permission»). Задокументировано сообществом GitHub и не измерено
здесь запуском. Есть вторая цена, уже известная в репозитории: push,
сделанный через GITHUB_TOKEN, не запускает workflow, поэтому commit выравнивания
остался бы без checks, пока человек не перезапустит триггер.
Включение docker/docker-compose без этого шага заставило бы рождаться красным каждый PR
бота, затрагивающий pgvector или ollama. Существующие пути — и они являются решением
сопровождающего, а не этого ADR — перечислены в отчёте AT-139: учётные данные с
workflows (GitHub App или токен) только для этого шага; убрать литерал из
workflows (image: в services: принимает контекст needs, и предыдущий job
читал бы его из compose — несуществующий дубликат не расходится); шаг лишь
ДИАГНОСТИРУЕТ, а человек применяет; или ignore бота для двух
дублирующихся образов. До тех пор повышение digest — процедура runbook.
Не распространяет dependabot-justifica-pin.ts на закрепление образов. PR
бота, затрагивающий docker/**, упёрся бы в drift (config-e-inferencia, block), а
решение AT-094 ограничило строку, записываемую ботом, заменой закрепления ACTION.
Расширение этого класса — отдельное решение.
Не покрывает того, что уже было вне: собственные образы продукта,
интерполированную ссылку, стадию multi-stage и otel-collector-values.yaml Helm
(ADR 0159). И остаются на РУЧНОЙ процедуре, с Dependabot или без, два
образа, живущих вне трёх деревьев или в ключе, который не читает ни одна экосистема:
imageName CNPG и образ случаев golden-set QA
(IMAGEM_DO_GOLDEN_SET_QA, константа TypeScript — уже во встроенной форме).
Не создаёт RN. Правило — родственник правила actions и живёт, как и оно, в
CLAUDE.md и в docs/explanation/cadeia-de-suprimentos-do-ci.md.