Skip to content

test: base-image pin drift gate — tests/unit/containerfile-base-pin_test.bats (Containerfile FROM vs BASE_IMAGE_NAME/FEDORA_MAJOR_VERSION) - #371

Open
kubestellar-hive[bot] wants to merge 1 commit into
mainfrom
arch/containerfile-base-pin-gate
Open

kubestellar-hive[bot] wants to merge 1 commit into
mainfrom
arch/containerfile-base-pin-gate

Conversation

@kubestellar-hive

Copy link
Copy Markdown
Contributor

Refactor

Claimed ground — files: tests/unit/containerfile-base-pin_test.bats (new, only file in this PR). Cluster: the base-image pin axis of the Containerfile — the base FROM line, ARG BASE_IMAGE_NAME, ARG FEDORA_MAJOR_VERSION, the Justfile:build extraction pipeline, and the literal base-image restatements in .agents/skills/**. No workflow, build script, Justfile, Containerfile, README.md or image content is modified.

Explicitly not the image-identity rename cluster (IMAGE_NAME / IMAGE_VENDOR / UBLUE_IMAGE_TAG across README.md, artifacthub-repo.yml, iso/iso.toml, clean.yml) — that is #291/#367 and is claimed by the open PR #368, which touches different files (tests/unit/image-identity_test.bats, README.md). This PR asserts nothing about ARG IMAGE_NAME.

Why

"Which base image this repo builds on, and which Fedora major release that is" is stated five times, and only the FROM line is maintained automatically (.github/renovate.json pins its digest and disables major updates for quay.io/fedora-ostree-desktops/silverblue, so the Fedora bump is a hand edit). ARG BASE_IMAGE_NAME and ARG FEDORA_MAJOR_VERSION are not documentation — build/00-image-info.sh:54-55 writes them into /usr/share/ublue-os/image-info.json, and Justfile:122 reads the Fedora version back out to compose the image tag and the org.opencontainers.image.version label. A FROM-only bump ships an image built on Fedora 45 that reports "fedora-version": "44" and tags itself stable-44.<date>. bootc container lint --fatal-warnings does not catch this; nothing does.

What this adds

tests/unit/containerfile-base-pin_test.bats — 13 assertions that treat the base FROM line (the single FROM with no AS alias) as canonical:

  • the base FROM is unique, digest-pinned, and carries an explicit tag;
  • ARG BASE_IMAGE_NAME / ARG FEDORA_MAJOR_VERSION are each declared exactly once and equal the FROM image name / FROM tag;
  • Justfile:build still greps ^ARG FEDORA_MAJOR_VERSION= out of the Containerfile, and running that exact pipeline yields the FROM tag — so the extraction regex cannot drift away from the ARG format;
  • build/00-image-info.sh still consumes both ARGs;
  • no file under .agents/skills/ restates a stale FEDORA_MAJOR_VERSION, BASE_IMAGE_NAME, or fedora-ostree-desktops/<name>:<tag> reference.

Verification

All 13 assertions pass on main (be4b0eb) as-is. Simulating the real failure mode — bumping only the FROM tag silverblue:44silverblue:45 — fails 4 of them (ARG FEDORA_MAJOR_VERSION matches the base FROM tag, the Justfile extraction pipeline yields the base FROM tag, and both skills-doc restatement checks). The gate runs inside the existing unit-tests.yml BATS job, so it adds no CI surface.

Known limitation, deliberately not fixed here

unit-tests.yml triggers only on build/**, tests/**, Justfile and its own file, so a Containerfile-only Fedora bump will not run this gate on that PR (it will run on the next PR that touches a watched path, and on push to main/stable). Adding Containerfile and .agents/skills/** to that paths: filter needs workflows permission this agent does not have; it is written up as the maintainer follow-up in #370.

Closes #370


Filed by architect agent (ACMM L5 — hold-gated mode). Hold-gated: human review required.

— hive: agent=architect backend=copilot model=claude-opus-5

…e-base-pin_test.bats

The base image and its Fedora major release are restated in five places
that cannot read each other: the Containerfile FROM line, ARG
BASE_IMAGE_NAME, ARG FEDORA_MAJOR_VERSION, literal restatements in
.agents/skills/**, and the Justfile:build extraction pipeline. Only the
FROM line is maintained by Renovate.

ARG BASE_IMAGE_NAME and ARG FEDORA_MAJOR_VERSION are not documentation:
build/00-image-info.sh writes them into image-info.json and os-release,
and Justfile:build stamps the Fedora version into the image tag and the
OCI version label. A FROM-only Fedora bump therefore ships an image that
reports the wrong base image and the wrong Fedora release, with no
build-time signal.

This gate treats the base FROM line as canonical and fails when any
restatement drifts from it.

Closes #370

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: sec-check[bot] <sec-check[bot]@users.noreply.github.com>
@kubestellar-hive kubestellar-hive Bot added the hold Work is intentionally paused. label Sep 16, 2026
@github-actions github-actions Bot added the 4-review A pull request is awaiting review. label Sep 16, 2026
@kubestellar-hive kubestellar-hive Bot added architecture Approved by a Hive merger/owner for auto-merge on green CI agent/architect Approved by a Hive merger/owner for auto-merge on green CI hive/hosted-projectbluefin-knuckle-gjvq Approved by a Hive merger/owner for auto-merge on green CI labels Sep 16, 2026
@github-actions github-actions Bot removed hive/hosted-projectbluefin-knuckle-gjvq Approved by a Hive merger/owner for auto-merge on green CI architecture Approved by a Hive merger/owner for auto-merge on green CI agent/architect Approved by a Hive merger/owner for auto-merge on green CI labels Sep 16, 2026

@hanthor hanthor left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The core of this is right, and I want to say so first because I tried hard to break it and mostly could not. The five-site drift you describe is real, and the gate catches it. I reproduced your headline claim exactly — bumping only the FROM tag fails four assertions, no more and no fewer:

$ cd <worktree at bf0329d>
$ sed -i '53s/silverblue:44/silverblue:45/' Containerfile
$ bats tests/unit/containerfile-base-pin_test.bats
not ok 7  ARG FEDORA_MAJOR_VERSION matches the base FROM tag
not ok 9  the Justfile extraction pipeline yields the base FROM tag
not ok 11 skills docs do not restate a stale FEDORA_MAJOR_VERSION
not ok 13 skills docs do not restate a stale base image reference

Three more mutants, all killed:

ARG FEDORA_MAJOR_VERSION 44 -> 45 (reverse drift) -> not ok 7, 9
FROM .../silverblue -> .../kinoite                -> not ok 6, 12, 13
strip @sha256:... from the FROM line              -> not ok 2

And the skills-doc checks are not vacuous — there is genuine input for them to find: .agents/skills/finpilot-build/SKILL.md:41 restates ARG FEDORA_MAJOR_VERSION="44", :42 and :100 restate the full fedora-ostree-desktops/silverblue:44 ref, finpilot-overview/SKILL.md:44 restates it again, and finpilot-templates/SKILL.md:86 restates ARG BASE_IMAGE_NAME="silverblue". That is a real five-site problem and you have wired all five together.

The blocking issue is test 9, the Justfile extraction pipeline yields the base FROM tag. Its name and your header comment both promise that "the extraction regex cannot drift away from the ARG format" — but the test does not run the Justfile's pipeline. It reimplements it inline:

extracted=$(grep -E '^ARG FEDORA_MAJOR_VERSION=' "${CONTAINERFILE}" | head -n1 |
    sed -E 's/^ARG FEDORA_MAJOR_VERSION="?([^"]+)"?/\1/')

That is a copy of Justfile:122, not a reference to it. So when the real pipeline drifts, the gate keeps a copy of the old one and reports green. I broke only the sed half of Justfile:122 (kept the grep so test 8 stays satisfied):

$ sed -n '122p' Justfile   # after mutation
    fedora_version=$(grep -E '^ARG FEDORA_MAJOR_VERSION=' Containerfile | head -n1 | sed -E 's/^ARG FEDORA_MAJOR_VERSION=//')

$ grep -E '^ARG FEDORA_MAJOR_VERSION=' Containerfile | head -n1 | sed -E 's/^ARG FEDORA_MAJOR_VERSION=//'
"44"

$ bats tests/unit/containerfile-base-pin_test.bats
1..13
ok 1 .. ok 13

Thirteen green, and just build now composes stable-"44".<date> with the quotes embedded — into the image tag and into org.opencontainers.image.version. That is precisely the class of silent mis-stamping this gate was written to prevent, and it walks straight through. Test 8's grep -qF pins only the grep -E '^ARG FEDORA_MAJOR_VERSION=' Containerfile substring, so it does not cover the sed at all.

The fix is to execute the real line rather than restate it. Something like lifting the command substitution out of the Justfile and evaluating it:

@test "the Justfile extraction pipeline yields the base FROM tag" {
    local pipeline extracted
    pipeline=$(sed -nE 's/^[[:space:]]*fedora_version=\$\((.*)\)[[:space:]]*$/\1/p' "${JUSTFILE}")
    [ -n "${pipeline}" ] || { echo "Justfile no longer assigns fedora_version from a pipeline"; return 1; }
    extracted=$(cd "${REPO_ROOT}" && eval "${pipeline}")
    [ "${extracted}" = "$(base_image_tag)" ]
}

That kills the mutant above and makes test 8 redundant in a good way — the pipeline either produces the right value or it does not, regardless of how it is spelled. As written, test 8 is also over-pinned in the other direction: it is an exact grep -qF of one substring, so a purely cosmetic requoting of Justfile:122 reds the gate while the semantics are unchanged.

Two smaller notes, neither blocking. base_from_ref's grep -viE '[[:space:]]AS[[:space:]]' correctly excludes the three aliased stages (common, brew, ctx) and test 1 pins the count at one, so a new unaliased FROM is caught — good. And your "known limitation" about unit-tests.yml paths: not including Containerfile is the sharpest thing in the PR body: a Containerfile-only Fedora bump is exactly the change this gate exists for, and exactly the change that will not run it. #370 tracking that as a maintainer follow-up is the right call, but it does mean the gate's practical coverage today is lower than its assertion count suggests. Worth landing the paths: change close behind this.

For the record, disjointness holds: I merged #366, #368, #371, #373 and #377 onto be4b0eb sequentially, all clean, and the combined suite shows no failure attributable to this file. Your 13 assertions pass unmodified on the merge-base as you said.

Make test 9 execute the pipeline it names and this is a clear approve.


Generated by Claude Code

@kubestellar-hive kubestellar-hive Bot changed the title [architect] test: base-image pin drift gate — tests/unit/containerfile-base-pin_test.bats (Containerfile FROM vs BASE_IMAGE_NAME/FEDORA_MAJOR_VERSION) test: base-image pin drift gate — tests/unit/containerfile-base-pin_test.bats (Containerfile FROM vs BASE_IMAGE_NAME/FEDORA_MAJOR_VERSION) Sep 17, 2026
@kubestellar-hive kubestellar-hive Bot added agent/scanner Approved by a Hive merger/owner for auto-merge on green CI hive/hosted-projectbluefin-knuckle-gjvq Approved by a Hive merger/owner for auto-merge on green CI labels Sep 17, 2026
@github-actions github-actions Bot removed agent/scanner Approved by a Hive merger/owner for auto-merge on green CI hive/hosted-projectbluefin-knuckle-gjvq Approved by a Hive merger/owner for auto-merge on green CI labels Sep 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

4-review A pull request is awaiting review. hold Work is intentionally paused.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[architect] base-image pin restated 5x with no gate — a FROM-only Fedora bump ships image-info.json and the version tag with the wrong fedora-version

1 participant