The gate job now runs in gitea.home.keithjackson.dev/home-network/toolbox@sha256:83a3501e... (the image images.yaml publishes from infra/devcontainer/Containerfile) instead of catthehacker/ubuntu:act-22.04 plus a per-run download of yamllint, check-jsonschema, kyverno, promtool and chainsaw. Local (dev.sh) and CI now gate on the identical toolchain. - actions/checkout (Node) replaced by a plain-git step with fetch-depth-0 semantics; the toolbox has no Node. The job token is not persisted into .git/config. - The install step is replaced by a check that every ci/versions.env pin equals the image's /etc/dev-toolchain-versions, so the pins stay enforced. - Renovate follows the digest through a `# renovate-digest:` marker; github-actions manager disabled for own images. - install-tools.sh kept (merge-gate.yaml blueprint + alert-rules-unit-tested check use it). Co-Authored-By: Claude Opus 5.5 <[email protected]>
5.0 KiB
date, branch, title
| date | branch | title |
|---|---|---|
| 2026-11-08 | feat/gate-toolbox-container | The merge gate runs inside the CI-built toolbox image, pinned by digest — no per-run tool install |
What changed
.gitea/workflows/gate.yml, job gate (job name, the gate step, its env and its
|| [ "$?" -eq 3 ] tolerance are unchanged, so the required context gate / gate (pull_request)
is unchanged):
container:gitea.home.keithjackson.dev/home-network/toolbox@sha256:83a3501e…ef81— the imageimages.yamlpublished fromdd94101(run 789; it is today'smainandsha-dd941016478ctag). The same imageinfra/devcontainer/dev.shruns, so local and CI gate on the identical toolchain. A# renovate-digest: image=… tag=mainmarker sits on the line above, the repo's existing convention;infra/gitea/registry-retention.shreads the same marker, so this digest is protected from pruning.actions/checkoutreplaced by a plain-git step. The toolbox has no Node (measured:command -v node→ missing), andactions/checkoutis a Node action. Chosen over adding Node to the toolbox because that would need two sequential PRs (Containerfile, then a publish, then the pin) and would put a JS runtime into the operator toolbox only to run one action. The step reproduces what checkout@v7fetch-depth: 0actually ran here (read from runs 490 and 495): every branch torefs/remotes/origin/*, tags, plus the event SHA; detached for a PR,-B <branch>for a push; assertsHEAD == github.sha. The job token goes to that one fetch viaGIT_CONFIG_*env and is not persisted in.git/config(nothing later needs network git).- "Install pinned CI toolchain" step removed, replaced by "Toolchain is the one
ci/versions.env pins": every
*_VERSIONinci/versions.envmust equal the image's/etc/dev-toolchain-versions, else::error::DRIFTand the job fails. Without this, dropping the installer would have madeci/versions.envan unenforced claim for everything except promtool (whichalert-rules-unit-testedalready asserts). - Header comments rewritten for the new job image (pull auth, cold-pull size, no-Node, why
install-tools.shstays).
renovate.json5: the renovate-digest custom manager now also scans .gitea/workflows/gate.yml;
the github-actions manager is disabled for this repo's own images (same reason the kubernetes
manager already was: it would see a digest-only image and look up latest).
Comment/message wording only: ci/gate.sh (header + the strict-skip error text), ci/lib.sh,
ci/README.md, .gitea/workflows/README.md — they said the CI toolchain comes from
install-tools.sh.
.gitea/workflows/scripts/install-tools.sh is kept: merge-gate.yaml (dormant blueprint)
calls it, and conformance/tests/l0-static/alert-rules-unit-tested/check.sh asserts its promtool
wiring (not touched — another agent owns conformance/).
Consequence to know: every toolbox digest bump is now a Renovate PR touching .gitea/**,
i.e. a sensitive path needing an operator /approve-override. That is arguably correct (a new
toolchain changes what green means — and ci/versions.env was already sensitive), but it is a
new recurring operator touch.
What I verified
- Registry pull auth: the Home-Network org is
public; anonymous/v2/token→ manifest200,docker-content-digest: sha256:83a3501e…ef81for bothmainandsha-dd941016478c. So nocontainer: credentials:is needed. (The Actions token could not have been used anyway — no package scope, per images.yaml's header.) - Image: 466,912,274 bytes compressed across 6 layers (manifest sum), 1,541,728,125 bytes
unpacked (
podman image inspect). Local cold pull 10.4s on the workstation. - Image tool versions equal
ci/versions.envfor all 8 pins (kyverno 1.19.0, kyverno-json 0.0.3, chainsaw 0.2.12, yamllint 1.35.1, kubectl 1.36.4, yq 4.44.6, promtool 3.14.0, check-jsonschema 0.29.4) — both via/etc/dev-toolchain-versionsand by running each binary's--version.sleep,tail,git2.56.0,bash,python3,jq,curlpresent (act's entrypoint is/bin/sleep 10800). - The new checkout script, run in the pinned image against the public repo (auth header disabled
for the test only): PR ref
refs/pull/143/head→ detached at60e3a24, 1018 commits; push refrefs/heads/main→On branch mainate8d2666;origin/mainpresent in both. The toolchain-drift step passes on the realci/versions.envand exits 1 naming both tools when yamllint/promtool pins are altered. - Renovate marker regex (copied from renovate.json5) matches gate.yml exactly once, capturing depName/tag/digest.
- LOCAL GATE: see below (filled after the run).
What I did NOT verify
- Renovate actually opening a digest-bump PR for gate.yml — only the regex was tested, not a Renovate run.
- The authenticated fetch with the real job token — only CI can do that; the PR's own gate run is that test.
- Push-to-main behaviour in CI (only the PR event runs before merge).