Tags give the ability to mark specific points in history as being important
-
v10.5.0
protectedRelease: v10.5.044f6a5aa · ·`instance` takes three new inputs so one project can include the component once per build lane. `job_name_prefix` renames that lane's four job keys (lint-containerfile, detect-changes, build, promote) and the three `needs:` edges between them; `containerfile` is hadolint's target and buildah's `-f`; `context` is buildah's trailing context argument, so a lane builds from its own subtree and anchors `build_change_paths` there. A second inclusion used to deep-merge silently onto the first's four job keys, later include winning, and every image built from the repo root. All three default to the previous behaviour: existing consumers get byte-for-byte identical merged jobs. `consumer-shape-test` runs two lanes: `instance` included twice, the second prefixed with its own containerfile and context, so child-pipeline creation reconciles eight job keys and two `needs:` chains. A child pipeline's source is `parent_pipeline`, matching neither build's nor promote's rules, so those two resolve structurally and never instantiate; their prefixed names and edges are graded by a real multi-lane consumer's pipeline, not here. `check-change-paths` reads every instance include, not only the first, diffs each against the Containerfile its own include names, and fails a repo whose includes share a prefix. Coverage now spans the Containerfile path and the context subtree as well as the COPY/ADD sources, so a lane that moves its Containerfile under `lanes/` while leaving the anchor at the template default reads DRIFT, not OK; a `containerfile` outside its own include's `context` is DRIFT too. An ancestor anchor reaches in, so one `lanes/**/*` covers every lane, and a single-mapping `include:` is checked, not skipped. Every command-level registry and network call retries. skopeo copy/inspect/delete and buildah push/pull against docker:// carry --retry-times/--retry-delay or --retry/--retry-delay (verified against skopeo v1.22.2, buildah v1.43.2). Every curl call gains --retry-connrefused and --connect-timeout 15 alongside its existing --retry flavor: the five binary downloads keep --retry-all-errors/--retry-max-time, API reads use --retry 3 --retry-delay 5 --max-time 120, release-create's release-metadata calls use --max-time 60. kickstart.yml and summary.yml's apt-get calls gain -o Acquire::Retries=3 on update and install. check-change-paths.py's fetch_url wraps urllib in a 3-attempt loop: a transport error (URLError, including a bare read timeout) retries after 5s; an HTTPError re-raises immediately as a verdict, not a flake. Job-level retry adds api_failure everywhere an outbound call is made, and script_failure only where a job's body is idempotent: a digest-compare guarded copy/promote, a read-only lint, a deterministic drift assertion. Four untimed jobs gain a timeout: seal 45m, image-verify 20m, installer-anaconda-iso and qcow2-bake 60m each, and the two long bakes gain `interruptible: true`, so a second push to the same branch cancels a running bake. `instance`'s build and promote gain 30m each; detect-changes stays untimed by design. base-build-scratch's `promote` no longer treats a failed `buildah pull` as a uniform no-op: only a captured stderr matching manifest-unknown/404 (no build at this sha) logs and exits 0, and every other pull failure, a registry flake included, exits 1 instead of silently skipping :latest/:stable promotion on a pipeline that stays green. installer-anaconda-iso and qcow2-bake's `--upload-file` calls raise `--max-time` from 900 to 1800 and add `--speed-limit 1024 --speed-time 120 --retry-max-time 3000`, so a transfer stalled below 1 KB/s for two minutes aborts and retries. `--max-time` bounds one attempt, `--retry-max-time` bounds how long the loop keeps starting them, and the loop closes at 3600s: the job's own 60m ceiling, not merely inside it. `consumers-aligned` and `release-check` accept a major-version range pin (`@10`) alongside an exact tag. `consumers-aligned` takes the literal after `@` verbatim instead of demanding a full `vX.Y.Z`, and compares it against the major of the catalog's newest release. `release-check` requires every catalog component pin in README.md to read `@<major-of-the-tag-being-checked>`; an exact `vX.Y.Z` pin, or a bare pin count of zero, fails. README.md's own pins move to `@10`: a range resolves at pipeline-parse time to the newest published release on that major, so a patch or minor reaches a consumer with no pin bump, leaving a major bump as the one fan-out step. `create-release` now carries `needs: [publish-images]`. Without the edge the two `release`-stage jobs ran in parallel, so a red `publish-images` did not stop the GitLab Release (and the Catalog release riding on it) being created. Survivable under an exact pin, but a range pin makes the Release itself the propagation event, so it must not register while the images it names are failing to publish. `seal`'s `buildah build` gains `--label catalog-ref=<catalog_ref>` on downstream runs, where the sealing recipe is cloned at the pinned `catalog_ref`. The label is conditional: a catalog self-test build uses the checked-out recipe and stamps none. Once a consumer cites `@10` instead of an exact tag, that label is the only place the resolved catalog release survives on a downstream image, readable with `skopeo inspect`. Minor bump: every new input keeps its previous default, and every other edit is additive (the label, the `needs:` edge) or a check accepting a second, already-valid pin shape.
-
v10.4.1
protectedRelease: v10.4.1a0bcc4a3 · ·seal pushes plain zstd instead of zstd:chunked, reverting v10.4.0's format: chunked images are unpullable by the composefs-native bootc backend this estate boots (composefs-rs decompresses layers with a plain zstd decoder that truncates a chunked stream at its first frame boundary; unimplemented upstream, composefs/composefs-rs#137; proven on metal 2026-08-24 against bootc 1.16.9). The post-push gate inverts with the format: layer mediaType must be tar+zstd AND the chunked TOC annotation must be ABSENT. Flip back only when composefs-rs#137 lands in a bootc the hosts run. Patch bump: same interface, unbreaks every sealed-image consumer. Also riding from main: consumer-change-paths-aligned catalog-CI job, fail on reintroduced catalog_ref overrides, fedora-release invariant (catalog-CI internal).
-
v10.4.0
protectedRelease: v10.4.083845072 · ·`seal` pushes sealed images as `zstd:chunked` instead of the previous default `gzip`, for faster decompression on install/upgrade -- unconditionally, not opt-in. Partial/delta pulls are NOT claimed: composefs, the backend this estate boots (per docs/decisions.md), has no confirmed chunked-aware partial-pull support upstream as of bootc 1.16.x (containers/storage#2095 still open). A post-push gate -- `skopeo inspect --raw` against the just-pushed manifest -- asserts the top layer's mediaType is `tar+zstd` AND carries the `io.github.containers.zstd-chunked.manifest-checksum` annotation, guarding against skopeo#2627's known behavior of mutating the compression annotation without real chunked TOC data. Driven by `cairn/base#14`. No new input; every consumer picks up the new push format by moving the component pin. Minor bump: new capability, no interface change. Also lands a quay pin-rot cleanup: `chunkah` (base-build-scratch's splitter) and `bootc-image-builder` convert from digest to tag-track, same class as buildah/skopeo in v10.1.1 -- a live digest at audit time doesn't refute the GC risk. `chunkah` tracks its resolved tag (`v0.6.0`); `bootc-image-builder` tracks `latest` by design, matching this repo's own v3.0.4 precedent for the same image (upstream ships no content tag at all). `hadolint` (ghcr.io) and `gitlab-org/cli` (registry.gitlab.com) were audited against the same question and stay digest-pinned: neither registry garbage-collects referenced manifests the way quay does. `renovate.json`'s `templates/*.yml` customManager gained a fourth matchString so it tracks arbitrary CI/CD variable-key pins, not just `image:`/`name:` (issue #7). - `seal`: pushes with `buildah push --compression-format zstd:chunked`; a post-push gate asserts the pushed manifest's top layer is `tar+zstd` with a real chunked TOC annotation, not just a mutated media type (cairn/base#14). - `templates/base-build-scratch.yml` (`CHUNKAH_REF`) and `installer-anaconda-iso/template.yml`/`containers/bib.Containerfile` (`bootc-image-builder`): digest pins converted to tag-track (`v0.6.0`, `latest` respectively); both get `pinDigests: false` in `renovate.json` so Renovate never re-pins a digest onto either. - `renovate.json`: `templates/*.yml` customManager's fourth matchString tracks arbitrary CI/CD variable-key pins (e.g. `CHUNKAH_REF`), not just `image:`/`name:`. - `hadolint` and `gitlab-org/cli` were audited under the same digest-vs-tag question and left digest-pinned: both registries retain referenced manifests, unlike quay. - Consumer pin bumps (base, lodestone) and promote-side idempotency under the new compression format ride the upcoming fleet transaction, not this tag -- `zstd:chunked`'s encoder determinism across pushes of unchanged content is undocumented upstream, so promote's digest-compare fast path may stop firing (an extra `skopeo copy --all`, not a false promotion). - `.gitlab-ci.yml`'s `consumer-change-paths-aligned` job (catalog CI, not shipped to consumers) lands ahead of this tag: enforces example-edge's D3 (an instance consumer's `build_change_paths` anchor must mirror its Containerfile's COPY/ADD sources) across all eight real instance consumers, real PyYAML + Containerfile parsing rather than a grep/awk approximation (issue #5).
-
v10.3.1
protectedRelease: v10.3.134108cf2 · ·Fixes a broken release. v10.3.0's tag pipeline failed in `publish-images`: `publish-one`'s digest-echo called `skopeo inspect`, and the publish image (`quay.io/buildah/stable:v1.43.2`) carries no skopeo. `buildah-fedora:10.3.0` was pushed before the job died; `skopeo:10.3.0` and `bib:10.3.0` were never published. v10.3.0's GitLab release is marked superseded -- do not pin it. - `justfile` `publish-one`'s digest-echo now reads the digest from `buildah push --digestfile` on the push it already performs -- buildah-native truth, no second binary, no post-hoc registry query that could in principle disagree with what was actually pushed. - `.gitlab-ci.yml`'s `verify-release-image-toolchain` job asserted the wrong image's toolchain (`registry.gitlab.com/gitlab-org/cli`, create-release's image, not publish-images'), so it stayed green while `publish-one`'s real skopeo dependency went completely unverified. Realigned onto the same image `publish-images` uses (single source via a shared `&publish-image` YAML anchor), asserting every external binary the publish recipe chain invokes (`buildah`, `just`) -- a missing tool now REDs every push pipeline instead of only the next tag. - Supersedes v10.3.0. That release's tag pipeline never completed `publish-images`; `buildah-fedora` is the only one of the three builder images actually published at that tag.
-
v10.3.0
protected98901c41 · ·`instance` gains opt-in verification of a signed/attested promote digest against a consumer-committed cosign public key. When a consumer sets `verify_pubkey_path` to a repo-relative path and `sign_on_promote`/`attest_on_promote` ran, promote runs `cosign verify` and/or `cosign verify-attestation` against that key immediately after signing/attesting, and fails the job on mismatch -- turning signing-key vs committed-pubkey drift into a red job instead of a silently unverifiable artifact (the incident class `cairn/example-evidence`'s D6 documents). Off by default; no behavior change for existing consumers. Also lands a loudness/verbosity hardening pass across `promote`, `detect-changes`, and the release tooling, closing several silent-skip gaps a gate-vacuity audit found: a masked-vs-genuine registry failure in `promote`'s digest resolution, an unparseable-vs-absent SBOM in the same job, undocumented decision inputs in `detect-changes`, and pass-path silence in `release-check`/`release-create`/`publish-one` that made a green run harder to audit than a red one. Additive, opt-in, no behavior change for existing consumers. Minor bump. - `instance` input `verify_pubkey_path` (default `""`). Fails closed if set to a path that doesn't exist. - `promote`'s `SRC_DIGEST` resolution now distinguishes a benign manifest-unknown/404 (documented "nothing to promote" skip) from any other registry/auth failure, which now fails loud with the captured stderr instead of silently resolving to "no digest". - `promote`'s SBOM component count is now a checked assignment; an unparseable (but present) `sbom.json` fails the job instead of surfacing as an empty count inline in the echo. - `promote`'s attest block echoes a `cosign verify-attestation` reproduction line per predicate type after each successful attest. - `detect-changes` echoes `BASE_CONFIG`/`PREVIOUS_BASE_CONFIG`/ `BASE_IMAGE_CHANGED` before writing `changes.env`, and notes when an unreadable `:latest` is being treated as changed. - `justfile` `release-check`'s pass path now names each matched file:line (kickstart/summary clone refs, seal `catalog_ref`, README pin count), matching the STALE path's existing offender-naming. - `justfile` `release-create` echoes its notes source (tag message vs fallback), the previous tag, and the commit count before calling the Releases API. - `justfile` `publish-one` resolves and echoes the pushed digest after push, so the log states what `:vX.Y.Z`/`:latest` now resolve to. - `.gitlab-ci.yml`'s `consumers-aligned` job (repo-CI, not shipped to consumers) got the same hardening ahead of this tag: the group-listing API call and both per-consumer file-read calls now fail loud instead of silently reading as "no refs", and the per-ref loop prints every consumer's actual pinned ref. - `templates/summary.yml`'s `allow_failure: true` now carries its rationale comment (summary is reporting-only and must never block the lane it reports on), matching the documentation discipline elsewhere in the catalog.
-
v10.2.0
protectedRelease: v10.2.029abaa02 · ·`instance` gains opt-in cosign attestation at promote, alongside the existing opt-in signing. When a consumer sets `attest_on_promote: "true"`, the promote job attests the promoted image digest with the estate cosign key: a CycloneDX SBOM predicate (reuses `sbom.json`, the same build artifact promote's SBOM-reuse check already validates) and a minimal SLSA v1 provenance predicate built inline from CI env vars (ref, sha, project, builder id, pipeline URL). Same own-key, no-Rekor posture as `sign_on_promote` (`--tlog-upload=false`, cosign v2 pinned), and shares its cosign install + decoded key -- the binary installs once when either input is opted in. Off by default; no behavior change for existing consumers. Additive, opt-in, no behavior change for existing consumers. Minor bump. - `instance` input `attest_on_promote` (default `"false"`). - Promote attests the digest once per predicate type; `:latest` and `:stable` both point at it, so one SBOM attestation and one provenance attestation cover every tag in the repo. - The attester fails closed if opted in but the protected group vars (`COSIGN_PRIVATE_KEY_B64`/`COSIGN_PASSWORD`) are absent, same as `sign_on_promote`. - `cosign attest --type slsaprovenance1` confirmed accepted by cosign 2.6.3. - `container-build.yml`'s header comment claimed build→push→sign→SBOM→attest; corrected to build→push→SBOM, matching what the script actually does (it has neither a sign nor an attest step). - README.md: dropped the stale "Current series: `@v9.x`" line and updated the Related section to point at `cairn/base` and the four `example-*` vignettes instead of the retired `immutable/basef` and `immutable/instance`. - Template doc-comment version literals in `supply-chain.yml`, `base-build-scratch.yml`, `seal/template.yml`, `installer-anaconda-iso/template.yml`, and `qcow2-bake/template.yml` replaced with a `@vX.Y.Z` placeholder plus a pointer to this CHANGELOG, so a usage-example pin can no longer go stale in place. `instance.yml`'s dated `immutable/pipeline` postmortem citation is a historical reference, not a usage pin, and was left as-is. - `justfile`'s `release-check` recipe now also fails on series-style literals (`@v9.x`) in README.md, the class that let the stale "Current series" line above survive three releases unnoticed.
-
v10.1.1
protectedRelease: v10.1.194ff9d10 · ·Unpin quay.io buildah/skopeo images to version tags quay rebuilds its rolling tags (including plain vX.Y.Z) and garbage-collects the superseded manifests, so any digest pin on these images rots within days. The v10.1.0 catalog baked such a pin into supply-chain's detect-changes, which took every consumer's daily cascade red from Aug 19 (lodestone, hagstone). All buildah/skopeo refs now track version tags only, and the catalog's renovate config carries pinDigests:false for both images so the pins cannot regrow. See CHANGELOG v10.1.1.
-
v10.1.0
protectedRelease: v10.1.083396eee · ·v10.1.0 — stop lint-containerfile + detect-changes contending for anvil MINOR: no input added, removed, or renamed. Two instance jobs move off the runner_tag input onto a hardcoded SaaS tag; every consumer's own .gitlab-ci.yml needs no change to pick this up on its next pin bump. instance's lint-containerfile and detect-changes jobs shared the runner_tag input with the hardware-bound build job. All 8 instance consumers set runner_tag: anvil (the estate's single self-hosted qemu host), so both lightweight jobs queued behind real image builds on the same single machine. Measured over 30 days: lint-containerfile + detect-changes were 2.5% of anvil's compute but 49% of its total queue wait across those 8 consumers. Worst case: roon-bootc's lint queued 52.3 minutes for 1 minute of work, and because stages run sequentially that dragged build/detect-changes behind it too. Neither job needs anvil: hadolint is a static lint and detect-changes is a skopeo inspect registry digest check -- no privilege, no host state, no credential beyond the job token. Hardcoded both jobs' tags to saas-linux-small-amd64 rather than dropping tags entirely or adding a new spec:inputs override. Confirmed via the runners API that the self-hosted storr runner (id 50689664) registers itself under every GitLab SaaS size tag AND has run_untagged: true, so a bare/untagged job is not guaranteed to land on genuine SaaS either -- an explicit tag is no more or less ambiguous on that axis, but it is self-documenting and (also runners-API-confirmed) structurally guaranteed to never touch anvil: anvil's tag_list is [lodestone, qemu, anvil] with run_untagged: false. small, not runner_tag's large default: both jobs are sub-minute work, right-sized the way vm_size is for build. No new input added: neither job has a real per-consumer reason to diverge, so a lint_runner_tag-style knob would be a moving part no consumer would ever set. build and promote are untouched. Verified via POST ci/lint against a real consumer's content (example-plain), before vs. after: only lint-containerfile and detect-changes tags changed (anvil -> saas-linux-small-amd64); build stayed anvil, promote stayed crucible. This catalog's own consumer-shape-gate child pipeline -- a real pipeline-creation + detect-changes execution -- passed against the fix branch, and both the branch pipeline and post-merge main pipeline (seal-self-test, consumer-test-build/verify/cleanup, release-check, verify-release-image-toolchain, consumer-shape-gate) were job-level green. Also folds in the prior Unreleased content: verify-release-image-toolchain (always-on toolchain check against create-release's image, closing the gap that let a bad gitlab-org/cli digest bump land unverified until the next tag), the .release-image/&release-image pin refactor, and the qcow2-bake/installer-anaconda-iso rootfs default change (xfs -> ext4, fs-verity requirement). See CHANGELOG.md for the full entry.
-
v10.0.1
protectedRelease: v10.0.1c3085470 · ·v10.0.1 — fix the stage v10.0.0 shipped broken, close the gap that let it through PATCH: v10.0.0's public contract (inputs, job names, the intended default-behavior change) is unchanged. Only the broken implementation is fixed. v10.0.0 as tagged was unusable by all seven of its declared consumers -- none had merged a pin bump to it. instance's lint-containerfile job (added v10.0.0) landed in a lint stage declared only in the component's own stages:. A consumer's own top-level stages: REPLACES the component's wholesale rather than merging with it, and every real instance consumer declares its own stages: [detect-changes, build, promote] with no lint -- so lint vanished from all seven and lint-containerfile referenced a stage that did not exist. Pipeline CREATION failed outright: 0 jobs, started_at: null, nothing resembling a lint failure or a YAML error on the pipeline object. Fixed by moving the job to .pre rather than adding "- lint" to all seven consumers' stages:, which would have recreated exactly the seven near-identical deviations this component exists to eliminate. GitLab injects .pre/.post into the merged stage list unconditionally regardless of what any consumer's stages: declares, so no consumer override can make it disappear -- confirmed by ci/lint against a consumer-shaped synthetic config both before the fix (invalid: chosen stage lint does not exist) and after (valid, all jobs present in the merged result). instance's own stages: no longer lists lint. Three separate pre-landing checks all passed without exercising the actual failure mode: the hadolint pre-audit validated hadolint's verdict on real Containerfiles, never the component's own resolvability; a synthetic-consumer ci/lint run passed because that synthetic consumer did not declare its own stages: unlike every real one; and this repo's own consumer-test-build/consumer-test-verify never instantiate a consumer .gitlab-ci.yml that includes the component alongside its own stages:. A POST ci/lint-based regression test was built and abandoned before landing -- it needs the api OAuth scope, and the estate's existing read_api-scoped token 403s insufficient_scope on that call. No token was minted or widened to route around this. Closed instead with consumer-shape-gate: a GitLab child pipeline for a synthetic instance consumer shaped exactly like the seven real ones, including instance/supply-chain at $CI_COMMIT_SHA, with strategy: depend propagating a failed pipeline CREATION back as a red job. Needs no API call or credential of any kind. Mutation-probed both directions on a real pipeline: pointed at the broken commit the child pipeline came back failed/0 jobs with GitLab's own stage-does-not-exist diagnostic; pointed at the fixed commit it came back success. Runs on every push and tag pipeline. release-check -- the guard for kickstart/summary/seal clone- and ref-literals plus README's component-pin examples -- was manual-only and CI-unwired; it failed when finally run by hand ahead of the v10.0.0 tag (all three template literals and four of five README pins were still v9.4.0, three releases stale) and one README pin stayed stale even in the v10.0.0 fix commit because the recipe never checked README.md at all. Wired into every pipeline now: tag pipelines check pins against CI_COMMIT_TAG exactly, push pipelines check self-referential coherence against kickstart.yml since there is no tag yet to compare against. Also fixes two qcow2-bake defects found on the lane's first real CI run (it had never executed before this release): the bib_image input default could not resolve the component.version directive inside a default value (GitLab does not re-interpolate there), now resolved in the job body; and BIB's devtmpfs mount failed EPERM under SaaS nested DinD, now bound to the dind daemon's already-populated /dev instead. Release-prep: bumped the kickstart/summary catalog clone literals, the seal catalog_ref default, and the README component usage pins v10.0.0 -> v10.0.1.
-
v10.0.0
protectedRelease: v10.0.0a3c7a9e0 · ·v10.0.0 — instance gains an unconditional lint-containerfile job MAJOR: every instance consumer's pipeline gains a new, always-run, merge-gating job with no input or config change on their part -- a default-behavior change per this catalog's own versioning policy, not an additive no-op. A vacuity audit found six cairn instance consumers (example-plain, example-attended, example-edge, example-evidence, corbel, whetstone -- lodestone is a seventh) whose sole merge-gating job was detect-changes, which asserts nothing: build/promote both gate on build_change_paths, so a docs/CI-only MR matches none of them and, with only_allow_merge_if_pipeline_succeeds=true, an empty-and-failed pipeline permanently blocks the merge on nothing being asserted at all. roon-bootc hit this exact failure mode and hand-carried a lint-containerfile job directly in its own .gitlab-ci.yml as the fix. Ported here once instead of copying it into six more repos: hadolint against the Containerfile, no changes: filter so it runs on every MR/main push regardless of path, --failure-threshold error. Inert until a consumer moves its instance pin past v9.6.x. Also carries everything merged since v9.6.1: the instance promote SBOM-reuse + missing-SBOM-fail fix (MR !8), retry coverage for the buildah login auth-flake class across base-build-scratch, image-verify, and seal, and the create-release job's move off GitLab's release: keyword (which shells to glab release create and is shadowable by an ambient GITLAB_TOKEN) to an explicit curl + JOB-TOKEN recipe -- this tag is that recipe's first real exercise. Release-prep: bumped the kickstart/summary catalog clone literals and the seal catalog_ref default v9.4.0 -> v10.0.0 (the release-check guard, stale since v9.4.0 across three releases), and the README component usage pins to match.
-
v9.6.1
protectedRelease: v9.6.134b7051b · ·v9.6.1 — digest-pin floating buildah/skopeo tags quay.io/buildah/stable and quay.io/skopeo/stable rebuild their vX, vX.Y, vX.Y.Z, and `latest` tags IN PLACE (proven 2026-08-03: v1.43.1, v1.43, v1, and `latest` on buildah/stable all resolved to one digest pushed 31 May). Every job in this catalog that pinned one of those tags was silently floating -- including `seal`, which signs boot artifacts. Digest-pinned all six occurrences to what each named tag currently resolves to, matching the `tag@sha256` style this repo's own `.gitlab-ci.yml` already uses for the same two images: - `templates/seal/template.yml` (highest consequence: signs boot artifacts) - `templates/base-build-scratch.yml` - `templates/container-build.yml` - `templates/image-verify/template.yml` (kept at its existing v1.41.5 -- only the floating-pin defect was in scope here) - `templates/supply-chain.yml` (buildah + skopeo) skopeo/stable's `-immutable` variant tag is distinct content (sha256:4a16d57b...) from what v1.22.2/`latest` currently resolve to (sha256:c7d3c512...) -- not a drop-in swap, so pinned to the latter to match this catalog's own existing usage rather than silently substituting different image content into a supply-chain component. renovate.json's `templates/*.yml` customManager now also matches a job's `image:`/`image.name:` key (previously it only saw `spec.inputs.*.default` values, and only when quoted), so none of these six pins were tracked going forward before this release. Added `versioningTemplate: docker` for correct tag-with-suffix ordering. Two of the six floating pins (container-build.yml, image-verify/template.yml) were found via a full-catalog grep beyond the four originally reported, and fixed the same way. Also carries two routine Renovate bumps merged since v9.6.0: registry.gitlab.com/gitlab-org/cli:latest docker digest, and anchore/syft to v1.49.0. Patch bump: fixes a floating-pin defect (jobs resolving a mutable upstream tag at parse time instead of a fixed digest). No input or interface change; consumers pick it up by moving the component pin. -
v9.6.0
protectedRelease: v9.6.08e0d29b2 · ·v9.6.0 — the OCI media-type gate now covers the instance lane v9.4.0 added a permanent OCI creep-detector for bootc-dev/bootc#1703 at two image-promotion points: base-build-scratch's promote (each of base, base-zfs, base-zfs-nvidia, at the exact digest the boot smoke tested) and seal (the local artifact, before push). The plain instance lane was the one remaining promotion point with no assertion — promote moved :latest/:stable without checking the candidate's media types. instance's promote now asserts the candidate is OCI immediately after SRC_DIGEST resolves and before either skopeo copy, using base-build-scratch's condition verbatim: .config.mediaType must be the OCI config type (the load-bearing signal — composefs re-reads the config blob, so that is the field ABSENT (chunkah-rechunked images omit it) but never a present non-OCI type. .install_skopeo already provides skopeo and jq, so the job gains no new install step. This closes a gap rather than fixing a live defect: .buildah_job pins BUILDAH_FORMAT=oci and a job-level variables: deep-merges rather than replaces it, so instance builds already emit OCI. The gate is what keeps a future edit that drops or overrides that variable from promoting a v2s2 image to a tag a host performs a day-2 bootc upgrade from — the failure mode with no workaround, since bootc upgrade/switch has no --source-imgref escape. Verified before tagging: the condition was probed over a seven-case truth table (OCI, absent-top-level, v2s2, the three mixed forms, and empty inspect output) and run against the live registry — base-zfs-nvidia:stable is chunkah-rechunked and really does report an absent top-level .mediaType with an OCI config, so the absent-tolerance is load-bearing on a real artifact rather than hypothetical; base:stable reports both OCI. Both pass; every non-OCI config fails. Minor bump: a consumer whose candidate is somehow not OCI now fails promote instead of publishing. No input or interface change; consumers pick it up by moving the instance component pin.
-
v9.5.0
protectedRelease: v9.5.017fdf27b · ·v9.5.0 — chunkah rechunk on base-zfs-nvidia + reproducible input base-build-scratch rechunks the final base-zfs-nvidia candidate through coreos/chunkah and republishes it to the same sha tag, so the boot-gated and promoted artifact is content-addressed by rpmdb component and a day-2 update moves only the changed layers. SOURCE_DATE_EPOCH is pinned on the compose and the rechunk, and (with the base treefile's __pycache__ postprocess strip) two identical-source composes chunk to byte-identical layers and seal to an identical composefs digest. promote's OCI assertion tolerates chunkah's absent top-level mediaType while still rejecting present-but-non-OCI. New catalog_ref input (default v9.5.0) fetches the Containerfile.splitter recipe. First chunked release is a one-time full re-pull per host, then smaller pulls.
-
v9.4.0
protectedRelease: v9.4.0e2c0d604 · ·v9.4.0 — OCI media types end to end (composefs/bootc#1703 fix) Every image the catalog builds is now OCI (application/vnd.oci.image.manifest.v1+json + application/vnd.oci.image.config.v1+json) instead of Docker schema2 (v2s2). bootc's composefs backend re-reads the image config blob at `bootc install` and again on day-2 `bootc upgrade`/`switch`; a v2s2 image fails that read (bootc-dev/bootc#1703, and the adjacent #2318) — "GetBlob ... file does not exist" at install, "blob unknown to registry" on day-2. Install had a local-OCI-staging workaround (--source-imgref oci:...); day-2 had none, so registry-direct upgrades were blocked outright. Native OCI media types remove the failing read at the source: the config blob is a proper OCI config the composefs backend can materialize, so install and day-2 both work registry-direct with a bare image ref — no local OCI staging, no --source-imgref. Changed: - base-build-scratch, seal, container-build, and supply-chain's shared .buildah_job template (which instance's build extends) set BUILDAH_FORMAT=oci (was docker). buildah's own upstream default is OCI. The dialect is set at BUILD and never converted after gating, so the digest the gates test is the digest promoted. Copies (promote, skopeo copy) preserve. Added: - Permanent OCI media-type assertion (creep-detector) so a v2s2 image can never regress into a promoted tag. base-build-scratch promote asserts each candidate layer is OCI (manifest + config) before moving :stable/:latest, on the exact digest smoke/smoke-sb tested; seal asserts the sealed artifact is OCI, read locally from containers-storage before push. Minor bump: published images change media type v2s2 -> OCI; layer contents and layer digests are unchanged.
-
v9.3.1
protectedRelease: v9.3.163daebac · ·v9.3.1 -- fix installer-anaconda-iso/qcow2-bake digest-pinning bug Both components could not actually carry a digest-qualified `image` input (repo@sha256:...): the docker pull + docker-daemon skopeo bridge failed closed on every digest-qualified reference (Docker Engine's classic local image store does not preserve original registry manifest bytes). Fixed by fetching directly via skopeo's docker:// transport into containers-storage, bypassing docker pull and the docker-daemon bridge entirely. Also corrects both components' header comments (and the catalog README) about triggering a standalone config file: a CI_CONFIG_PATH pipeline variable does not work, per confirmed GitLab limitations gitlab-org/gitlab-foss#60331 and gitlab-org/gitlab#27987. Patch bump: no existing consumer passes a digest-qualified image today, so nothing working changes behavior.
-
v9.3.0
protectedRelease: v9.3.022ee08f2 · ·v9.3.0 — opt-in own-key cosign sign-at-promote (instance) The `instance` promote job can now sign the promoted image digest with an own cosign key when a consumer sets `sign_on_promote: "true"` (cosign v2, `--tlog-upload=false`, key from protected+masked group vars). Own-key signing verifies at pull time against a committed public key, unlike the keyless flow dropped in v4.3.0, so a reject-by-default `sigstoreSigned` policy.json (cairn/example-evidence) can finally enforce. Off by default; existing instance consumers are unaffected. - inputs `sign_on_promote` (default "false") and `cosign_version` ("2.6.3"). - Digest signed once; matchRepository covers :latest and :stable. - cosign pinned to v2.x (v3 refuses --tlog-upload=false; example-evidence D4). - Validated sim-first: sign + verify + reject-by-default policy matrix against an ephemeral registry with cosign v2. -
v9.2.0
protectedRelease: v9.2.0005fc433 · ·v9.2.0 — qcow2-bake component + supply-chain/brand housekeeping New `qcow2-bake` component: converts a promoted bootc image to a qcow2 disk image via bootc-image-builder, in the same standalone-invocation shape as `installer-anaconda-iso` (separate CI config, web/API/manual trigger, Package Registry publish). Produces the disk image only; boot-verifying it needs /dev/kvm and stays a consumer-owned gate on a self-managed nested-KVM lane. Additive, no consumer behavior change. Housekeeping in the same release: - Dropped the dead cosign install (and COSIGN_VERSION/COSIGN_YES) from the buildah-fedora builder image; signing left the catalog in v4.3.0 and cairn's real signing path is the seal component. - Pinned syft via a syft_version component input instead of fetching releases/latest at CI runtime, with a matching Renovate customManager. - Pinned the remaining floating third-party :latest image refs (buildah, skopeo, docker/dind, bootc-image-builder) to version/digest. - Dropped two unreferenced pre-cairn brand PNGs.
-
v9.1.0
protectedRelease: v9.1.06c133f43 · ·feat: seal component for the sealed UKI rung Adds `seal`: convert a built bootc base image into the sealed stack. It produces db-signed systemd-boot (bootupd removed), a db-signed Unified Kernel Image carrying the computed composefs= digest on its cmdline, and a signed PCR11 policy embedded in the UKI, so a TPM2 LUKS slot keeps auto-unlocking across OS updates. Minor bump: purely additive, with no change to existing components or their include paths. - templates/seal/ ships the sealing Containerfile plus the component. The job runs `bootc container ukify` with systemd-sbsign (Secure Boot) and systemd-measure sign (PCR11), decoding two group-level masked and protected variables (UKI_SIGNING_KEY_B64, PCR11_PRIVATE_PEM_B64) to build secrets. It fails closed if either is absent and, after the build, verifies composefs= present, .pcrsig present, and the UKI verifies against the db certificate. - Self-test seals quay.io/fedora/fedora-bootc:44 at the commit under test on saas-linux-xlarge-amd64 (push disabled). - release-check now guards the seal catalog_ref pin; the kickstart and summary clone literals and the README pins bump to v9.1.0. - Boot-gating (enforcing-Secure-Boot KVM boot and install-time composefs digest match) is the planned next rung. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
-
v9.0.1
protectedRelease: v9.0.168a901f6 · ·fix: invoke cairn-verify in the promote re-verify Patch fix for a v9.0.0 carry-forward rename miss. The base-build-scratch promote lane re-verifies the image after its registry round-trip by running the verify binary baked into the final layer. It invoked the immutable-era `/usr/bin/basef-verify`, which cairn base images do not ship — they bake `/usr/bin/cairn-verify`. Cairn base builds passed boot-smoke, then failed at promote with `fork/exec /usr/bin/basef-verify: no such file or directory`. - `base-build-scratch` promote now runs `/usr/bin/cairn-verify`; the two describing comments updated to match. No other component affected.
-
v9.0.0
protectedRelease: v9.0.0d5fb5d5e · ·feat: carry the catalog forward into the cairn estate First release under dunn.dev/cairn/pipeline. Carried forward from dunn.dev/immutable/pipeline at v8.4.0 with full git history and every release tag intact — the tags below v9 document the design arc that produced this catalog. No component was redesigned; this is a home move plus an identity repoint. The major bump reflects the one breaking change: component include paths move namespace. Consumers pinning `dunn.dev/immutable/pipeline/<component>@<tag>` must repoint to `dunn.dev/cairn/pipeline/<component>@<tag>`. - Registry image references, component include examples, pipeline and release badges, the kickstart/summary clone-URL literals, CI comments, and container LABELs repointed to the cairn namespace. - Estate-identity prose (README, CONTRIBUTING, the builder-image header) and the brand SVGs now name the cairn estate. - README records the carry-forward lineage from immutable/pipeline. - All dated CHANGELOG entries and annotated-tag release notes below v9. - Cross-estate references to the still-live immutable basef/instance projects and the immutable.dunn.dev estate site. - The just-recipe (catalog CI) / inline-YAML (component templates) / POSIX-sh (release scripts) boundaries.