Tags

Tags give the ability to mark specific points in history as being important
  • v17.4.0

    protected Release: v17.4.0
    cairn/pipeline v17.4.0
    
    **Changed**: `kmod-zfs` and `kmod-nvidia` fall back to `LATEST_KERNEL` when `KVER` is unset; `build-base` fails on a kernel mismatch in `vars` mode.
    
    **Removed**: the unused `.configure_dnf` job.
    
    Migration: none; consumers on `@17` pick this up on their next pipeline.
  • v17.3.0

    protected Release: v17.3.0
    cairn/pipeline v17.3.0
    
    **Fixed**: the `seal` gate accepts `composefs.digest=` or a bare `composefs=` on the UKI cmdline.
    
    **Added**: promote also pushes a unique `<sha8>-p<pipeline_id>` tag, so a digest one rebuild old stays pullable.
    
    **Removed**: the catalog's `consumers-aligned` and `release-check` jobs.
    
    Migration: none; consumers on `@17` pick this up on their next pipeline.
  • v17.2.2

    protected Release: v17.2.2
    cairn/pipeline v17.2.2
    
    **Fixed**: with `rechunk: true`, files chunkah cannot attribute to a package (its bigfiles and xattr components: GPU userspace, firmware, out-of-tree kernel modules) kept each build's wall-clock mtime, so their layers got a new digest on every build even when the bytes were unchanged. chunkah v0.7.1 clamps them to SOURCE_DATE_EPOCH like every other file. This is the fix for #14.
    
    **Changed**: syft 1.52.0 to 1.54.1 in the buildah image and the supply-chain `syft` default.
    
    Migration: none; consumers on `@17` pick this up on their next pipeline.
  • v17.2.1

    protected Release: v17.2.1
    cairn/pipeline v17.2.1
    
    **Fixed**: with `rechunk: true`, a rebuild of an unchanged commit rechunked the registry's copy of `<image>:<sha>` instead of the image the job had just built, so the scheduled rebuild re-shipped old content and promoted it. The rechunk now reads its source by the image ID the build step recorded, never by tag.
    
    **Added**: the rechunk step fails unless the rechunked image's rpmdb hashes equal to the rpmdb of the image the job built. Two hashes in the existing job; no new jobs.
    
    Migration: none; consumers on `@17` pick this up on their next pipeline.
  • v17.2.0

    protected Release: v17.2.0
    cairn/pipeline v17.2.0
    
    **Added**: `sealed-instance` input `rehearsal_vm_size` (default `large`) sizes `install-rehearsal`, which was fixed at `large`. `xlarge` joins the `boot_vm_size` options.
    
    **Changed**: the `runner_tag` and `vm_size` descriptions say that a `saas-linux-*` tag runs on GitLab SaaS, where `vm_size` is ignored, and that `vm_size` sizes the self-hosted custom executor (`xlarge` only where a runner offers it). Defaults are unchanged.
    
    Migration: none; consumers on `@17` pick this up on their next pipeline.
  • v17.1.0

    protected Release: v17.1.0
    cairn/pipeline v17.1.0
    
    **Added**: `seal` inputs `base_digest_file` and `digest_file`. `layer` and `base-build-scratch` write `image-<image>.digest`; the kmod jobs write `image-kmod-<layer>.digest`; `seal` writes `sealed.digest`.
    
    **Changed**: `promote`, `instance` promote, `seal`, `sealed-boot`, `pcr11-anchor-gate` and `promote-sealed` take `<image>@<digest>` from the build artifact instead of `<image>:<sha>`, and read the served digest back against it. `layer` builds over the parent digest, and over the kmod digest when the kmod job emitted one.
    
    Behaviour changes:
    - `layer` and `promote` need their builds' digest artifacts: keep `build-<parent>` and `build-<image>` in `needs:` with `artifacts` on (the default). A missing artifact fails the job; `promote` skips only when no build ran and no tag exists at the sha.
    - `sealed-instance` no longer runs on `web` pipelines, which have no build digest. Re-seal by pushing or through the cascade.
    
    Migration: no input changes; consumers on `@17` pick this up on their next pipeline.
  • v17.0.1

    protected Release: v17.0.1
    cairn/pipeline v17.0.1
    
    ## v17.0.1 — 2026-10-05
    
    No interface change.
    
    **Fixed**: the `instance` build writes `image.digest` world-readable. It copied mktemp's 0600 file, so the runner could not upload the artifact and every v17.0.0 instance build failed after a successful push.
    
    Migration: none; consumers on `@17` pick this up on their next pipeline.
  • v17.0.0

    protected Release: v17.0.0
    cairn/pipeline v17.0.0
    
    ## v17.0.0 — 2026-10-05
    
    **BREAKING.** Every catalog push is plain zstd, and a push fails unless every layer of the pushed digest is. Move every cairn/pipeline ref to `@17` in one transaction.
    
    **Added**: `instance` input `rechunk` (boolean, default false) re-layers the built image with chunkah before the SBOM and the push, carrying its image config and labels over. It refuses an image labelled `ostree.commit`, and needs `--cap-add=all`, `/dev/fuse` and about twice the image size in free disk on the build runner.
    
    **Changed**: `base-build-scratch` (base and kmod builders), `layer` and `instance` push with `--compression-format zstd`. The base, layer, instance and seal pushes read back the manifest of the pushed digest and fail on any layer that is not `tar+zstd` or carries a zstd:chunked TOC, which replaces seal's last-layer check. `layer` rechunks before its single push, so no gzip image sits under the sha tag, and hands chunkah the full image config instead of a fixed label list; a label the rechunk drops or changes fails the job. `instance` promote copies the build's digest to `:latest` and `:stable` and fails unless both serve that digest; `sealed-instance` promote does the same for `:stable-sealed` and checks every layer. `instance` promote also fails when the sha tag no longer serves the digest its own build pushed, the one its SBOM and signatures describe.
    
    **Removed**: `templates/layer/Containerfile.splitter` and `layer`'s fetch of the catalog; the recipe is inline in `layer` and `instance`.
    
    Migration:
    - Bump every ref to `@17`; no inputs change. Hosts and tools that pull these images must decode zstd layers, as sealed hosts already do.
    - Every layer digest changes, so the first upgrade after the bump pulls the whole image once.
    - A consumer's own compression check on the last layer should check every layer.
    - To rechunk an instance, set `rechunk: true` on a runner that allows `--cap-add=all` and `/dev/fuse`.
  • v16.0.0

    protected Release: v16.0.0
    cairn/pipeline v16.0.0
    
    **BREAKING.** `base-build-scratch` builds the base image and the kmod builders only; layered images and promotion move to two new components. Move every cairn/pipeline ref to `@16` in one transaction.
    
    **Added**: `layer` builds one image as a layer over a parent and emits `build-<image>`: inputs `image`, `parent`, `layer`, `rechunk`, `runner_tag`, `vm_size`, `build_change_paths`. It builds `images/<layer>` over `<parent>:<sha>` with `kmod-<layer>:<sha>`, needs `build-<parent>` and optionally `kmod-<layer>` and `kmod-<layer>-mr`, and runs chunkah only when `rechunk` is true. `promote` verifies and promotes one image and emits `promote-<image>`: inputs `image`, `needs`, `runner_tag`, `build_change_paths`. Image names are relative to `${CI_REGISTRY_IMAGE}`.
    
    **Removed**: from `base-build-scratch`, the jobs `build-base-zfs`, `build-base-zfs-nvidia` and `promote`, and the inputs `base_zfs_image`, `base_zfs_nvidia_image`, `promote_needs` and `promote_runner_tag`. The chunkah recipe moved to `templates/layer/Containerfile.splitter`.
    
    **Security**: `layer` and `promote` write their inputs into the job script instead of passing them through job variables, so pipeline, project and group CI/CD variables cannot override an image, parent, layer or rechunk setting. Every default `promote` rule additionally requires `$CI_COMMIT_REF_PROTECTED == "true"`: a promote pipeline on an unprotected ref no longer runs. Consumers whose default branch is not protected must protect it.
    
    Migration:
    - Drop the removed inputs. Include `layer` once per layered image (`build-base-zfs` becomes `image: base-zfs, parent: base, layer: zfs`; `build-base-zfs-nvidia` becomes `image: base-zfs-nvidia, parent: base-zfs, layer: nvidia, rechunk: true`) and `promote` once per promoted image, with that image's gates in `needs`.
    - `layer` and `promote` address images at `${CI_REGISTRY_IMAGE}/<image>` and kmod builders at `${CI_REGISTRY_IMAGE}/kmod-<layer>`; keep `base_image`, `zfs_image` and `nvidia_image` on those paths.
    - The Containerfile directory is `images/<layer>` and its builder build-arg is `<LAYER>_BUILDER_IMAGE`; rename the directories if they differ.
    - Job names are `<job>-<image>`: update every `needs`, rule and cascade that names `promote`.
  • v15.0.1

    protected Release: v15.0.1
    cairn/pipeline v15.0.1
    
    No interface change.
    
    **Changed**: `bootc container lint` is fatal on every build and seal path, including the rechunked base image before it is pushed; a warning now fails the job. `seal` normalizes file modes across every top-level directory and logs what it changed, and writes the ext4 install drop-in only when the base image lacks it. Checkout modes are set in each build job's script. The instance build fails when `BASE_REF` is empty.
    
    **Removed**: `seal`'s post-normalize fail gate. Normalization already fails closed under `set -e`, so the gate could never fire. `promote`'s lint of the pulled image, since the exact artifact is now linted before it is pushed.
    
    Migration: consumers on `@15` pick this up on their next pipeline, so make the base image lint-clean first and promote it; a consumer whose base still warns will fail its build and seal.
  • v15.0.0

    protected Release: v15.0.0
    cairn/pipeline v15.0.0
    
    **BREAKING.** Move every cairn/pipeline ref to `@15` in one transaction.
    
    **Added**: `sealed-instance`, the protected sealed lane for an instance. Base and instance images carry `org.opencontainers.image.revision` and `.source` labels; instance builds `FROM` the base digest and adds `.base.name`, `.base.digest`. `install-rehearsal` runs on default-branch schedules with `INSTALL_REHEARSAL=true`, which skip `detect-changes`, `build` and `promote`.
    
    **Changed**: protected seal, gate and promote jobs are non-interruptible; `promote-sealed` serializes per `<prefix>stable-sealed` and skips when it already descends from the pipeline commit; `pcr11-anchor-gate` runs untagged; default-branch push rules compare `$CI_DEFAULT_BRANCH`; kmod jobs time out at 30m; base `rootfs` label follows the image (`ext4`); `sign_on_promote`, `attest_on_promote` and `push` are `type: boolean`; `builder_base` is required; builds strip group/world write from the checkout; `seal` normalizes modes and fails on group/world-writable files.
    
    **Removed**: `catalog_ref` and `extra_kargs` (`seal`, and the `EXTRA_KARGS` arg); `catalog_ref` and the five Containerfile-path inputs of `base-build-scratch`.
    
    Migration:
    - Replace the local `seal` include, `.sealed-lane-rules`, the `seal` needs stanza and the local sealed jobs with `sealed-instance@15`.
    - Add a first `workflow:rules` entry `- if: '$CI_COMMIT_BRANCH && $CI_COMMIT_REF_PROTECTED == "true"'`, limited to the pipeline sources your later rules run, with `auto_cancel: {on_new_commit: none}`.
    - Unquote the boolean inputs, drop removed inputs, set `builder_base`.
  • v14.0.1

    protected Release: v14.0.1
    cairn/pipeline v14.0.1
    
    **Fixed**: `instance`'s `promote` rules for acute, api, trigger and schedule
    pipelines now also require `$CI_COMMIT_REF_PROTECTED == "true"`, matching
    `base-build-scratch`'s protected-ref rules. On an unprotected ref the job
    used to sit pending forever, since `promote_runner_tag` names a runner that
    only takes protected refs, and it must never move `:stable`/`:latest` from
    one. Non-breaking: protected-ref pipelines and the main-push rule are
    unchanged, and no input or job name changes.
  • v14.0.0

    protected Release: v14.0.0
    cairn/pipeline v14.0.0
    
    **BREAKING.** `kmod-zfs` and `kmod-nvidia` now run on protected refs only, with `-mr` twins for merge-request pipelines.
    
    **Added**: `kmod-zfs`, `kmod-nvidia`, `seal` and `instance`'s `promote`
    declare `environment: {name: signing, action: access}`, so the four
    signing variables (`UKI_SIGNING_KEY_B64`, `PCR11_PRIVATE_PEM_B64`,
    `COSIGN_PRIVATE_KEY_B64`, `COSIGN_PASSWORD`) can be scoped to a `signing`
    environment. Inert while the variables carry environment scope `*`.
    
    **Added**: `base-build-scratch` input `signing_runner_tag` tags the
    protected-ref `kmod-zfs` and `kmod-nvidia` and is required (no default);
    new `kmod-zfs-mr` and `kmod-nvidia-mr` run on unprotected refs (merge-request
    pipelines, and acute/api/trigger/schedule runs) under `runner_tag`, the
    same canonical/`-mr` split cairn/base uses for `sealed-boot`.
    `build-base`, `build-base-zfs` and `build-base-zfs-nvidia` are unchanged
    and never see the signing variables. `seal` gains a `vm_size` input
    (default empty, exported as `VM_SIZE`).
    
    **Changed**: `build-base` no longer checks `UKI_SIGNING_KEY_B64` in mint
    mode; the check runs in the kmod jobs, which decode the key.
    
    **Migration:** every `base-build-scratch` consumer sets
    `signing_runner_tag` (the estate uses `crucible`). A hard `needs:` on `kmod-zfs` or
    `kmod-nvidia` from a job that also runs on merge-request pipelines needs
    the matching `-mr` job added with `optional: true`.
  • v13.0.0

    protected Release: v13.0.0
    cairn/pipeline v13.0.0
    
    ## v13.0.0 — 2026-09-21
    
    **BREAKING.** `image-verify` and its self-test scaffold are removed; it had
    zero consumers in the group. Consumers change only the major pin.
    
    **Changed**: `seal` and `base-build-scratch` fetch their recipe at the
    commit the include resolved to; `catalog_ref` is an optional override.
    `release-check` scans every component README pin.
    
    **Added**: a parse check composes `base-build-scratch` and `supply-chain`
    on push and tag pipelines.
  • v12.0.0

    protected Release: v12.0.0
    **MAJOR (v12.0.0) — input removed, default behavior change, migration
    notes below.**
    
    `base-build-scratch` drops the `signing_key_var` input. `vars` mode now
    reads the group protected variable `UKI_SIGNING_KEY_B64` directly --
    the same name `seal` already hardcodes -- and fails closed with a
    FATAL naming it when the ref does not carry it. There is no
    indirection and no fallback to any other variable name; a consumer
    that still sets `MOK_SIGNING_KEY_B64` and nothing else gets exactly
    that FATAL.
    
    Every internal name still carrying the retired module-key identity is
    renamed to the `kmod-signer`/`kmod-signing` role it actually plays, now
    that a `base` module is signed with the same key that signs the sealed
    UKI rather than a separate Machine Owner Key: the `.mok-material`
    anchor becomes `.kmod-signing`; its `MOK_ROLE` variable becomes
    `KMOD_SIGNING_ROLE`; the buildah secret id `mok` becomes
    `kmod-signing`; the ephemeral build-arg `MOK_CERT_SRC` becomes
    `KMOD_SIGNER_CERT_SRC`; the staged/artifact files `mok.pem`/`mok.der`
    become `kmod-signer.pem`/`kmod-signer.der`; and the catalog's own
    negative-control job `mok-fail-closed` becomes
    `kmod-signing-fail-closed`.
    
    `kmod-signing-fail-closed`'s negative control drops from five cases
    (plus the name-guard loop) to four: the `signing_key_var`-repoint case
    and the injected-name guard are gone with the input they tested. The remaining four are unset ->
    red naming `UKI_SIGNING_KEY_B64`, set -> green, the retired
    `MOK_SIGNING_KEY_B64` carrying a key while `UKI_SIGNING_KEY_B64` stays
    unset -> red still naming `UKI_SIGNING_KEY_B64` (no silent fallback to
    the retired name), and the ephemeral mint producing a CA:TRUE /
    no-keyUsage cert at `644`.
    
    - `base-build-scratch` input `signing_key_var` and its `SIGNING_KEY_VAR`
      wiring in `build-base`, `kmod-zfs`, and `kmod-nvidia`.
    - The `eval`-based indirect read, its name-format guard, and the
      `signing_key_var`/wiring assertions in `kmod-signing-fail-closed`.
    
    - `base-build-scratch` `vars` mode reads `UKI_SIGNING_KEY_B64` directly
      (was: whichever variable `signing_key_var` named, default
      `MOK_SIGNING_KEY_B64`).
    - `.mok-material` anchor -> `.kmod-signing`; `MOK_ROLE` ->
      `KMOD_SIGNING_ROLE`.
    - buildah secret id `mok` -> `kmod-signing`; ephemeral build-arg
      `MOK_CERT_SRC` -> `KMOD_SIGNER_CERT_SRC`.
    - Staged/artifact files `mok.pem`/`mok.der` ->
      `kmod-signer.pem`/`kmod-signer.der`.
    - Catalog job `mok-fail-closed` -> `kmod-signing-fail-closed`.
    
    1. **Drop `signing_key_var:` from every include.** An include still
       passing it fails pipeline creation with "unknown input"; a
       consumer must remove its line in the same MR that bumps to `@12`.
    2. **A consumer whose refs all carry `UKI_SIGNING_KEY_B64` needs no
       further change.** `vars` mode reads it directly now, with no
       variable name left to configure.
    3. **A consumer whose protected refs only ever set
       `MOK_SIGNING_KEY_B64` must set `UKI_SIGNING_KEY_B64` instead**, or
       the build goes red where it used to build signed, and the committed
       cert your Containerfile bakes (the `KMOD_SIGNER_CERT_SRC` default)
       must be that key's cert.
    4. **Update the build secret id and build-arg in every consumer
       Containerfile.** `RUN --mount=type=secret,id=mok` becomes
       `id=kmod-signing`; `ARG MOK_CERT_SRC=...` and its `COPY
       ${MOK_CERT_SRC}` become `ARG KMOD_SIGNER_CERT_SRC=...` / `COPY
       ${KMOD_SIGNER_CERT_SRC}`.
    5. **Update any script or path that names the committed cert file.**
       `mok.der`/`mok.pem` no longer exist under that name; the baked path
       and build artifacts are `kmod-signer.der`/`kmod-signer.pem`.
    6. **`mok-fail-closed` -> `kmod-signing-fail-closed`.** Update any
       required-check, badge, or API query that names the job.
  • v11.1.0

    protected Release: v11.1.0
    `base-build-scratch` gains a `signing_key_var` input: `.mok-material`'s
    "vars" mode now decodes whichever CI/CD variable the consumer names,
    rather than always `MOK_SIGNING_KEY_B64`. cairn/base can point kmod
    signing at `UKI_SIGNING_KEY_B64` -- the same key that signs the sealed
    UKI -- and sign kmods with it instead of minting a separate MOK. The
    name is read indirectly and validated first (empty or any character
    outside `A-Z0-9_` is fatal); there is no fallback to
    `MOK_SIGNING_KEY_B64` once a consumer names a different variable.
    
    `mok-fail-closed`'s negative control grows from three cases to six: the
    two existing (unset -> red naming the variable, set -> green) now pass
    `SIGNING_KEY_VAR=MOK_SIGNING_KEY_B64` explicitly, plus two new cases
    proving `signing_key_var=UKI_SIGNING_KEY_B64` goes green when only that
    variable carries the key, and stays red naming `UKI_SIGNING_KEY_B64` --
    never falling back to `MOK_SIGNING_KEY_B64` -- when only the old
    variable is set, and one more proving the name guard rejects an injected
    name and a positional-parameter name before the indirect read, without
    running the injection. A new assertion parses the template and checks
    `signing_key_var`'s default and that `build-base`, `kmod-zfs`, and
    `kmod-nvidia` all wire `SIGNING_KEY_VAR` from the input.
    
    Additive, backward compatible, default unchanged
    (`MOK_SIGNING_KEY_B64`). Minor bump.
    
    - `base-build-scratch` input `signing_key_var` (default
      `MOK_SIGNING_KEY_B64`, regex `^[A-Z_][A-Z0-9_]*$`). Wired into
      `build-base`, `kmod-zfs`, and `kmod-nvidia` as `SIGNING_KEY_VAR`.
  • v11.0.1

    protected Release: v11.0.1
    Fixes the ephemeral MOK artifact's mode. `base-build-scratch`'s
    `.mok-material` anchor minted the ephemeral key as root and wrote
    `.cairn-signing/` `700` and `mok.pem`/`mok.der` `600`; on the lodestone
    qemu-vm executor the artifact uploader runs as the unprivileged `build`
    user, so it saw an empty directory ("found 1 matching artifact files and
    directories") and both kmod jobs died on the anchor's own fail-closed
    guard because `.cairn-signing/mok.pem` was never fetched. SaaS docker
    executors upload as root, so the catalog's own self-test never saw it.
    `base` worked around it consumer-side with an `after_script` chmod; that
    workaround is no longer needed.
    
    ### Fixed
    - `base-build-scratch`'s ephemeral mint role (`MOK_ROLE=mint`,
      `signing_source=ephemeral`) now leaves `.cairn-signing/` `755` and
      `mok.pem`/`mok.der` `644` after minting -- world-readable is
      acceptable for a one-day do-not-trust throwaway with `access: none`.
      The canonical key (`vars` mode, and the ephemeral consume role's
      fetched copy) is unaffected and stays `700`/`600`.
  • v11.0.0

    protected Release: v11.0.0
    **MAJOR (v11.0.0) — default behavior change, migration notes below.**
    
    `base-build-scratch` gains a `signing_source` input and module signing
    fails closed.
    
    Before this release, kmod signing in `base-build-scratch` was a
    convention rather than an input: `kmod-zfs` and `kmod-nvidia` each
    carried a byte-identical block that signed when `MOK_SIGNING_KEY_B64`
    was present and, when it was absent, printed `building unsigned` and
    built green anyway. `build-base` baked the committed canonical signer
    cert into the image either way. A pipeline on a ref that does not carry
    the protected variable — every merge request, every fork, every
    unprotected branch — therefore produced an image asserting a MOK its
    modules were not signed with. Nothing in CI failed; the discovery point
    was a machine refusing to load the module under lockdown.
    
    `signing_source` mirrors seal's input of the same name and takes the
    same two values. `vars` (the default) decodes `MOK_SIGNING_KEY_B64` and
    **exits 1 naming the variable and the ref** when it is absent. There is
    no unsigned mode left. `ephemeral` mints one throwaway MOK per pipeline
    in `build-base` — RSA-2048, one day, `CN=cairn ephemeral module signing
    (do not trust)`, minted in the canonical cert's exact shape (CA:TRUE, no
    keyUsage, so it lands in `.platform` as the ceremony cert does) —
    publishes it on `build-base`'s artifacts as `.cairn-signing/`, and both
    kmod jobs sign against that one key. The build bakes THAT cert via a new
    `MOK_CERT_SRC` build-arg, so signer and baked cert always correspond.
    
    The two duplicated signing blocks collapse into one `.mok-material`
    anchor shared by all three jobs, so the mint and consume paths cannot
    drift apart. The component stays protection-blind: it reads
    `$SIGNING_SOURCE` and never `CI_COMMIT_REF_PROTECTED`, leaving the
    consumer to choose the mode from its own `workflow:rules`. The value is
    validated at runtime rather than by `options:`, so a consumer may pass a
    variable reference and decide per pipeline; empty or unknown is fatal.
    
    `build-base`'s artifacts drop from `expire_in: 1 week` to `1 day` with
    `access: none`, bounding the ephemeral key to its cert's lifetime. Cost:
    a manual `kmod-*` retry more than a day after the build loses the `KVER`
    dotenv and fails loudly instead of building against a stale kernel.
    
    A `mok-fail-closed` job in the catalog's own CI extracts the component's
    `.mok-material` preamble from the template and runs it three ways —
    unset must be red and must name the variable, set must be green,
    ephemeral must mint a CA:TRUE cert with no keyUsage. `base-build-scratch`
    cannot be included in this pipeline (canonical job names collide), so
    without it the guard would be a claim nothing in CI ever executes.
    
    ### Removed
    
    Six components retire. Each was surveyed by every consumption route —
    `component:`, `include: project:/remote:/local:`, `trigger: include:`,
    raw/archive URL, `git clone`, kickstart `%post`, cloud-init, systemd
    unit, justfile, Containerfile, `needs: project:` and jobs/artifacts API
    — across every dunn.dev and nomograph project, and each has no estate
    consumer by any of them. GitLab's own catalog telemetry agrees:
    `last30DayUsageCount` is 0 for all six on every published version.
    
    - **`installer-anaconda-iso`** — no estate consumer by any route; catalog
      usage 0. Its only consumer ever, the archived `immutable/carmine`,
      dropped it for an inline job before being archived. The estate has not
      cut an ISO in the cairn era: every install path is `bootc install
      to-disk` from an OCI source.
    - **`kickstart`** — no estate consumer by any route; catalog usage 0.
      Extracted from carmine in v4.6.0, never adopted by a cairn-era repo.
    - **`summary`** — no estate consumer by any route; catalog usage 0. Same
      v4.6.0 carmine extraction, same never-adopted history; its `--branch`
      literal was bumped every release since as pure upkeep.
    - **`qcow2-bake`** — no estate consumer by any route; catalog usage 0.
      whetstone's golden-qcow2 lane, its one consumer, replaced it with a
      consumer-owned `bootc install to-disk` job on 2026-08-06 and has been
      off the component since.
    - **`badges`** — no estate consumer by any route; catalog usage 0. Its one
      consumer, `immutable/basec`, was archived with the `immutable/*`
      generation; nothing in cairn publishes Pages badges.
    - **`container-build`** — no estate consumer by any route; catalog usage
      0, and never had one. The generic-container role it was written for is
      served estate-wide by the L1 catalog's `dunn.dev/pipeline/container-image`.
    
    Going with them: `scripts/kickstart.py`, `scripts/validate-kickstart.sh`,
    `scripts/summary.py` and the `validate-kickstart` justfile recipe (only
    those components ran them); `containers/bib.Containerfile` and `bib` from
    `publish-images` (the two BIB components were its only consumers — the one
    estate repo that still bakes with BIB, cairn/hagstone, pulls
    `quay.io/centos-bootc/bootc-image-builder` from upstream directly, not this
    catalog's re-tag); and the `quay.io/centos-bootc/bootc-image-builder` half
    of the `pinDigests: false` Renovate rule, which tracked only that
    Containerfile's pin. The `chunkah` half stays — `base-build-scratch`
    still consumes it.
    
    `release-check` loses its `kickstart.yml`/`summary.yml` `--branch`
    assertions; `templates/seal/template.yml`'s `catalog_ref` default is now
    the sole clone-literal anchor, and the pre-tag coherence branch of the CI
    job reads its declared version instead of `kickstart.yml`'s. README drops
    from seven component pins to five; `release-check` counts them, so the two
    stay consistent with no number to maintain.
    
    `image-verify` is NOT retired despite reading 0: the catalog self-includes
    it at `$CI_COMMIT_SHA` as its own end-to-end image self-test, so a 0 there
    means "no version-pinned external include", not "unused".
    
    ### Migration
    
    1. **A consumer whose refs all carry `MOK_SIGNING_KEY_B64` needs no
       change.** The default is `vars` and that is the behavior it already
       had on those refs.
    2. **A consumer that builds on refs without the protected variable must
       pass `signing_source: "ephemeral"` on those pipelines**, or the build
       turns red where it used to be green-and-unsigned. The intended shape
       is a consumer-side variable defaulted to `vars` and overridden to
       `ephemeral` in the consumer's own `workflow:rules` for merge-request
       pipelines, passed through as `signing_source: "$MOK_SIGNING_SOURCE"`.
    3. **A consumer using `ephemeral` must take the cert from the build-arg.**
       Add `ARG MOK_CERT_SRC=files/usr/share/cairn/mok.der` above the `COPY`
       that stages the signer cert and copy `${MOK_CERT_SRC}` instead of the
       literal path. `vars` mode passes no build-arg, so the committed cert
       is baked byte-for-byte and the image is unchanged.
    4. Images built under `ephemeral` carry a real signed chain that is not
       the canonical one. Tag them so they can never be mistaken for a
       canonical artifact, and keep boot gates that assert canonical-key
       provenance on protected refs.
    5. **A consumer of a removed component: none exist in the estate.**
       External consumers pin `@10` and are unaffected until they move —
       `@10` keeps resolving to the v10 series, which still ships all six.
       Moving to `@11` means the component is gone: `installer-anaconda-iso`
       and `qcow2-bake` are replaced by a consumer-owned `bootc install
       to-disk` job (whetstone's `.gitlab-ci-qcow2.yml` is the worked
       example), `container-build` by `dunn.dev/pipeline/container-image`,
       and `kickstart`, `summary` and `badges` have no successor here.
  • v10.5.0

    protected
    44f6a5aa · release: prepare v10.5.0 ·
    Release: v10.5.0
    `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

    protected
    a0bcc4a3 · release: prepare v10.4.1 ·
    Release: v10.4.1
    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).