Tags

Tags give the ability to mark specific points in history as being important
  • v6.1.0

    protected Release: pipeline v6.1.0
    Catalog v6.1.0
    
    ci-go toolchain refresh: Go 1.26.4 -> 1.26.5, clearing GO-2026-5856
    (CVE-2026-42505, a crypto/tls Encrypted Client Hello pre-shared-key
    identity disclosure), and golangci-lint v2.4.0 -> v2.12.2, whose
    predecessor panicked analyzing Go 1.26 code and kept consumer CI lint
    gates disabled. Consumers pinning ci-go can restore blocking audit
    gates and re-enable golangci-lint in CI at this tag.
    
    Instance fix behind work item #5; the Go-patch release cadence it
    proposes remains open.
    
  • v6.0.1

    protected Release: pipeline v6.0.1
    Catalog v6.0.1
    
    Fail-loud sweep across every template whose script block could mask a
    failure behind a trailing echo. binary's publish-to-package-registry
    and sign-blob loops, container's :latest/:TAG push blocks, and
    verify's :latest/:TAG verification blocks -- the highest-stakes
    instance, since verify exists solely to assert signature integrity --
    now run under `set -euo pipefail`. A failed upload, sign, push, or
    verification now fails the job instead of shipping a green pipeline
    with a missing or unverified artifact.
    
    Also carries the Renovate delivery work merged since v6.0.0: automerge
    for lockfile/pin/digest updates, catalog bumps exempt from the
    concurrent-MR cap, the renovate-fleet-audit delivery gate, lab
    projects moved to dashboard approval, and token-rotation survival.
    
  • v6.0.0

    protected Release: pipeline v6.0.0
    Catalog v6.0.0
    
    BREAKING: automerge-gate's `required-checks` input is renamed
    `required_checks`. A consumer passing the old name gets `unknown input
    arguments` at include resolution. The component has no consumers in this
    estate, so the blast radius is zero today and grows with every consumer it
    gains. The hyphen was never a design decision: `required_checks` was the
    original name and a July commit renamed it with no rationale, so this
    reverts to the convention.
    
    FIXED, and the reason this release exists now rather than later:
    `container` and `registry-cleanup` shipped v5.0.0 with an `image:` that
    resolves to nothing. The job fell back to the runner's default image, which
    carries no buildah, and died on `buildah: command not found` (exit 127)
    after a clean pipeline start. v4.0.2 hardcoded the image and worked, so a
    bump to v5.0.0 is what broke it. Any consumer on v5.0.0 that builds
    containers should move to v6.0.0.
    
    The cause was a duplicate YAML key: the v5.0.0 cut added a second
    `variables:` block to a job that already had one, so the block defining the
    image name was silently discarded. Three layers missed it independently.
    The linter used yaml.safe_load, which applies last-key-wins in silence.
    GitLab's own /ci/lint reports such a config valid and returns merged YAML
    with the block already gone. And no sim covers `container`, the
    most-included component in the catalog.
    
    Two blocking lint rules close that class: 13 rejects duplicate mapping keys
    via a strict loader, and 14 requires an `image:` naming a variable to
    define that variable in the same job. Both were verified able to fail
    against the shape that shipped.
    
    Also in this release: 28 README rows and worked-example lines that
    documented inputs which do not exist, fixed across nine components, with
    five further blocking lint rules and an advisory tier so the class cannot
    return. verify's `needs_job` removal is now recorded as breaking in the
    5.0.0 notes, where a consumer bumping from v4 will look.
    
    KNOWN LIMITATION: `container` still has no sim. Its only coverage is
    consumer pipelines, which is how the v5.0.0 regression reached a tag. The
    new rules catch the static shape; nothing yet builds an image and asserts
    the job ran on a buildah image.
    
  • v5.0.0

    protected Release: pipeline v5.0.0
    pipeline v5.0.0
    
    **Major because the release is lockstep and one component's contract broke.**
    `automerge-gate` no longer exits nonzero for a customer-shipping class whose
    checks are green (see BREAKING below). That component has no consumers today,
    so the practical blast radius is nil -- but semver is a promise about the
    contract, not a function of how many people currently hold it, and every
    template ships at the same catalog tag. Cutting this as a minor would have
    put a breaking change behind Renovate's non-major grouped MR, which is
    automerge-eligible for some classes. A major routes it to its own MR with a
    human, which is what a contract change should get.
    
    Everything else here is a fix, and the vale work in particular changes
    behaviour without changing any input contract.
    
    - **Six defects found by a systematic audit, not incidentally.** Every earlier
      fix in this release surfaced because something broke or a consumer had patched
      around it. This pass instead checked all 21 templates mechanically against the
      failure shapes this estate has actually hit: runtime network fetches,
      floating refs, `component.version` in a spec default, inputs declared but
      unused, inputs used but undeclared, README/template input drift, jobs that
      cannot gate, missing `interruptible`, `cosign` without `id_tokens`, and
      ordering that rests on an `optional` `needs:`.
    
      - `container` and `registry-cleanup` **documented an `image` input neither
        template declared.** A consumer following the README got
        `unknown input arguments: image` -- the same failure the `vale` sim hit.
        Both now declare it, using the variable-name idiom this release
        established.
      - `verify` declared **`needs_job` and referenced it nowhere.** A consumer
        setting it expecting ordering got nothing. Removed; removing an input is
        breaking, which is what this major is for.
      - `release` ran a job on **`curlimages/curl:latest`**, an unpinned
        third-party image in the release path, the least reproducible place in the
        catalog to float. `ci-tools` already carries curl and ships in lockstep.
      - `binary` and `paper` **defaulted their image to `:latest`** instead of
        pairing with the component version. `pipeline.just`'s header claims version
        skew is "unrepresentable"; these two made it representable.
      - `vale`'s README still documented **fetching styles from `master` on every
        run**, which this release removed.
    
      The audit's other 50 findings were triaged as false positives and are
      recorded rather than silently dropped: `:latest` discussed in prose rather
      than used, SLSA `buildType` spec URLs matching a `/main/` pattern, `options:`
      values documented in a README table and read as input rows, and `curl`
      invocations whose pinned version sits on the next line of a shell
      continuation. Six components do order on an `optional` `needs:`, which is the
      same shape as the `container-sbom`/`container-attest` attestation race; that
      needs a sim reproducing the race and is deliberately not in this release.
    
    - **A vale that could not run was indistinguishable from a clean one, because
      `allow_failure: true` swallowed exit 2 along with exit 1.** v5.0.0 already
      removes the runtime failure mode by baking vale and its styles into
      `ci-tools`, but the signal itself was still unreadable by the pipeline. The
      job now emits `allow_failure: { exit_codes: $[[ inputs.tolerated_exit_codes ]] }`,
      defaulting to `[1]`: prose findings advise, a check that could not run
      blocks. This is the estate's exit-code taxonomy finally enforced rather than
      merely documented -- 0 pass, 1 the-checked-thing-failed, 2
      the-check-could-not-run, and only the middle one is a matter of taste.
    
      **The input contract is unchanged, so this is a minor.** `allow_failure` is
      still a boolean; `tolerated_exit_codes` derives its default from it via
      `spec:inputs:rules:` and resolves to `[0]` when `allow_failure` is false.
      `[0]` expresses "tolerate nothing" because exit 0 never fails, chosen over an
      empty array so the behaviour does not rest on GitLab's handling of
      `exit_codes: []`. Behaviour DOES change: a vale job that silently passed while
      linting nothing will now go red. That is the fix, not a regression.
    
      `CONTRIBUTING.md` rule 9 also documented an input shape that does not
      compose: it showed a top-level `default:` alongside `rules:`, and GitLab
      rejects that outright (`config these keys cannot be used together: default,
      rules`). The rule was written from upstream documentation rather than from a
      pipeline that ran, and the first component to actually use the idiom failed
      to create a pipeline because of it. Corrected, with the requirement that the
      rules be exhaustive over the deciding input, since an input with rules and no
      matching branch becomes required and breaks consumers.
    
      Proven by `sim-vale-exit-codes`, two arms covering both branches of the rule.
      `exit_codes` appeared nowhere in this catalog before now, and deriving an
      array default from another input is likewise new here; both failure modes are
      config failures, so an invalid shape means the pipeline does not create.
    
    - **`mode: project` did not disable the rules its own description claimed, and
      three separate consumers had patched around it.** The input said project mode
      disables `InternalLink*`; the emitted `.vale.ini` never disabled
      `InternalLinkFormat`. Sweeping every catalog consumer's `.gitlab-ci.yml`
      found `gitlab_docs.InternalLinkFormat` hand-disabled in **3 of 3** vale
      consumers, `gitlab_base.Offerings` in 3, and `gitlab_base.LatinTerms` in 3.
    
      All three are now off by default in project mode. A rule every consumer
      disables is not a standard, it is a papercut with a default:
      `InternalLinkFormat` enforces docs.gitlab.com link shape and means nothing in
      a README, and `Offerings` and `LatinTerms` enforce docs product voice on
      prose that is not product docs. A project that wants the full ruleset still
      has `mode: docs`. `Substitutions` and `SubstitutionWarning` deliberately stay
      on, because the mechanical discipline they carry is correct everywhere.
    
      After this ships, `reference`, `gitlab-contribution` and `gitlab-simulation`
      can each drop those entries from their `disable_rules`.
    
    - **`templates/paper/README.md` taught `@~latest` in three places** -- a
      floating ref in the one document a new consumer copies from. Now pinned.
      `lab/project-lattice/paper` pins `@main`, which components cannot resolve at
      all since they resolve from tags only, and is almost certainly downstream of
      this.
    
    - **`validate:templates:syntax` broke every MR in the catalog, from an
      unpinned `pip3 install yq`.** python-yq depends on `argcomplete`; a new
      `argcomplete` release annotates `Final[Mapping[str, str | bytes]]` at class
      creation, and PEP 604 unions need Python 3.10 while the job's pinned UBI9
      image ships 3.9. Nothing in this repository changed. Someone else's release
      did, and three unrelated MRs went red at once.
    
      The job now uses `python3-pyyaml` from the distro, so the version is
      whatever the pinned base image carries and there is no network install in
      the job at all. `yq` was never needed here: the job asserts each template
      parses as a two-document YAML stream, which PyYAML does directly. It also
      now checks the document count rather than only that parsing succeeds.
    
      This is the defect `mise.toml` already records for the template linters --
      "imported PyYAML from wherever it happened to be: present on one machine,
      absent on another, and pip-installed inline and unpinned by one CI job".
      This was that job.
    - **`vale` had stopped linting anything, and the job's own red square hid it.**
      On 2026-08-04 the job failed three pipelines in a row on `reference` with
      HTTP 429, exhausting its retries while asking gitlab.com to archive the
      `gitlab-org/gitlab` monorepo at `master` for `doc/.vale`. It examined zero
      files each time. Because the component is normally included with
      `allow_failure: true` so prose findings advise rather than block, "the
      styles download was rate-limited so nothing ran" was indistinguishable from
      "the prose is clean" and from "there are 313 findings" -- one ignored red job
      standing for three different states. That is why finding counts on this
      estate kept moving between sessions.
    
      `vale` and a **pinned** snapshot of the GitLab docs styles now ship inside
      `ci-tools`, at `/opt/vale-styles` with `VALE_STYLES_PATH` set. The default
      job path makes **no network call at all**: the binary is no longer pulled
      from a GitHub release per run, and the styles are no longer archived out of
      a 3 GB monorepo per run per consumer. A styles failure is now a loud
      `ci-tools` **build** failure that blocks, instead of a job nobody reads.
      This also makes the component offline-capable, which the airgapped
      consumers this catalog exists for actually need.
    
    - **The vale ruleset drifted under consumers with no diff in their
      repository.** `styles_archive` defaulted to `master`, and `master` is not a
      version. Diffing the pinned tag against `master` on the same day showed a
      new rule (`BoldEmphasis`), two new style packages, and four changed rule
      files. A consumer's green pipeline could go red because someone else merged
      documentation upstream. `VALE_STYLES_REF` pins it (`v18.11.8-ee`) and
      carries a Renovate annotation, so the ruleset now moves when a maintainer
      chooses and arrives as a reviewable MR.
    
      `vale_version` and `styles_archive` keep working as runtime **overrides**,
      and both now default to empty (use the image). A failed override fetch, or
      an image with no styles, exits **2** -- check could not run -- and never
      falls through to a vale invocation that would report zero findings against
      zero rules and read as a pass. Verified against all three branches: baked
      styles present, baked styles missing, and an override URL that 404s.
    
      **Not fixed here, and it needs a major.** `allow_failure: true` still
      swallows exit 2 along with exit 1, so a consumer who wants prose findings
      advisory cannot yet make could-not-run blocking. The mechanism is
      `allow_failure: { exit_codes: [1] }`, which means changing the
      `allow_failure` input's shape: a breaking contract change for the six
      current consumers, and one whose empty-array semantics should be proven on
      a lab before shipping. The measured defect above is fixed regardless,
      because the runtime failure mode is now gone rather than relabelled.
    - **`$[[ component.version ]]` inside a `spec.inputs` default never
      interpolated, so two components handed the literal string to the runner.**
      GitLab does not re-interpolate a default's contents when substituting them
      into the body; body-level `variables:` does interpolate. `automerge-gate`
      and `osv-verdict` both defaulted their `image` input to
      `ci-tools:$[[ component.version ]]`, so any consumer taking the default got
      an invalid image reference. `reference` carries a hand-written `image:`
      override and a comment explaining the workaround, which is how this
      surfaced: a consumer had to discover it the hard way.
    
      Both now use the idiom `audit` always used -- the input defaults to a
      variable NAME (`$GATE_IMAGE`, `$VERDICT_IMAGE`) and the job's `variables:`
      block builds it, where `component.version` resolves. `audit`'s own
      description claimed "Same idiom as osv-verdict and automerge-gate", which
      was not true when written; it is now.
    
      **The shape is also linted, so it cannot come back.**
      `scripts/lint-templates.py` fails any template whose `spec.inputs.*.default`
      mentions `component.version`, and points at `audit` for the working pattern.
      Verified by reintroducing the shape and confirming the linter exits
      non-zero. This is what unblocks wiring `automerge-gate`, which has zero
      consumers today partly because its default did not work.
    - **The shared `prConcurrentLimit` was starving the catalog bump, which is the
      one update class every other conformance property is delivered through.**
      Measured across the estate on 2026-08-04: 62 catalog includes and **not one
      on the current tag**, with 20 of them two majors behind on `v3.0.0`.
      Renovate was not the problem -- it had 22 open catalog MRs across 15 of 17
      consuming projects. The queue was. Four consumers (`assay`, `manifold`,
      `postern`, `reference`) sat at exactly 10 open MRs, the configured limit, and
      `manifold`'s ten contained **no catalog bump at all** while its
      `.gitlab-ci.yml` ran two different catalog versions: routine dependency
      updates had taken every slot, so the bump that would have made it coherent
      could not be proposed.
    
      The catalog `packageRule` now carries `prPriority: 10`, so it is created
      ahead of every other class regardless of queue depth. Raising the cap was
      the other option and the wrong one -- it enlarges the review pile without
      changing what gets proposed first. `automerge` stays `false`: a catalog bump
      changes consumer CI behaviour and still gets a human.
    
    - **The shared Renovate preset stripped the leading `v` from every version it
      wrote.** `extractVersionTemplate: "^v?(?<version>.+)$"` on the Containerfile
      ARG manager rewrote `v2.6.3` as `2.6.4`, but the ARG is used as the release
      TAG in the download URL (the filename gets a separately-stripped copy). Six
      merge requests across `ci-tools`, `ci-buildah` and `ci-buildkit` proposed
      URLs that 404: verified against the live API, `gitlab-org/cli` release
      `1.109.0` returns 404 and `v1.109.0` returns 200. Replaced with
      `versioningTemplate: "semver-coerced"`.
      The same line sat on the catalog component-pin manager, where it is worse:
      that manager's `matchStrings` requires the `v`, so the stripped value fails
      `confirmIfDepUpdated`, the file reverts, and Renovate throws
      `update-failure`. That is the errored branch updates, and why no consumer
      was on the current catalog version. Same root cause, two failure shapes:
      one writes silently wrong, the other cannot write at all.
    - **`validate:containers` built nothing and passed.** Its `while read` loop had
      no input redirect, so it read stdin, which is empty in a CI job. It printed a
      correct build plan and then `built 0 image(s); Job succeeded`. No pipeline had
      ever run it, because its rules require a merge request that changes a
      Containerfile. It now redirects from the plan and asserts built == planned.
    
    - **`osv-verdict` was fail-open in merge-request diff mode. Consumers may see
      MRs newly go red, correctly.** The head scan wrote its findings inside the
      working tree and `git stash --include-untracked` deleted them; the stash is
      never popped. `sort` on the missing file exited 2 to stderr, `comm` read an
      empty stream and exited 0, and the verdict was `pass` no matter what the
      head introduced. There is no `set -e` in that script, so nothing surfaced.
      Scan output now lives outside the working tree, the declared `osv-head.json`
      artifact is restored through an `EXIT` trap covering all five exit paths,
      and a guard fail-closes when either side of the diff is absent.
    - **`osv-verdict` treated an unparseable scan as a clean one.** The jq parse
      ended in `|| : > "$ids_file"`, so any schema change, truncated write or
      missing file became an empty findings list and a pass. Since
      `osv_scanner_version` is Renovate-managed, a major bump reshaping the JSON
      would have silently disarmed the check estate-wide. The parse now fails
      closed, and a scanner exit of 1 (findings present) that parses to zero
      findings is treated as schema drift rather than as clean.
    
    - **`audit` set `LANG`**, the POSIX locale variable, to the language name. On
      the vanilla bases every current consumer overrides to, `LANG` is already
      exported, so the assignment propagated `LANG=go` to every child process
      (`locale: Cannot set LC_CTYPE to default locale`, collation flipping to C
      ordering). Renamed to `AUDIT_LANG`.
    
    - **`audit`'s `image` input is now optional, derived from `language`.** It had
      no default, which made it required: the component could not be included
      without naming an image, and its own error text coached a vanilla base
      ("add 'image: rust:1.88.0'"). Across all 57 group projects every one of
      audit's six consumers passes `image:` and five pass a vanilla base, four of
      them naming a language `ci-tools` already bakes. Conformity was zero by
      construction. Existing overrides keep working; migration is deleting the
      line. `rust` resolves to `ci-rust`, `go` to `ci-go`, `node` and `python` to
      `ci-tools`. Removing the input entirely remains the eventual major.
    - **`audit` no longer installs audit tooling at job runtime.** The rust arm
      ran `cargo install --locked cargo-deny --version 0.19.2 2>/dev/null || true`,
      shadowing the image's Renovate-tracked pin with an unpinned second source of
      truth and swallowing its own failure. The go arm ran
      `go install golang.org/x/vuln/cmd/govulncheck@v1.1.4`, which was OLDER than
      the baked version, so the audit ran an older scanner than the image shipped.
      The python arm ran an unpinned `pip install pip-audit`, which on this image's
      pip backtracks to an older release rather than failing. All three now check
      the baked tool is present and fail pointing at a stale image.
    - **`ci-tools` bakes `pip-audit`** at a Renovate-tracked `PIP_AUDIT_VERSION`.
    - **`.image-build` runs on the catalog's own `ci-buildah`** and no longer
      curl-installs cosign at runtime, which happened on every image build, against
      this repo's own no-runtime-curl-installs rule, without checksum verification,
      in the job that signs every catalog image.
    
    - **Digest pinning is off.** `pinDigests: false` for the docker datasource, and
      invariant 1 in `ADOPTION.md` rewritten to match: the semver tag is the pin.
      Digests rot faster than a review-gated MR can land.
    - **Containerfile changes are built on the merge request.** No `build:ci-*` job
      ran on `merge_request_event`, so a Containerfile change was never exercised
      until after merge, which is why six MRs proposing 404 URLs were all green.
      `validate:containers` builds the changed images, and their dependents,
      without pushing or signing.
    
    - **BREAKING: `automerge-gate` no longer fails the pipeline for a
      customer-shipping class whose checks are green.** It reports
      `GATE_DECISION=human-review` and exits zero. Consumers on the default
      (`customer_shipping: true`) will see this job go from permanently red to
      green. That default previously exited nonzero unconditionally, which put
      "not automerge-eligible" on the same pipeline red/green channel
      `only_allow_merge_if_pipeline_succeeds` reads to gate *human* merge. The
      documented adoption shape therefore blocked the human lane rather than
      routing to it, and the catalog's own sim had to wrap that shape in
      `allow_failure: true` to stay green. A failing or absent check still exits
      nonzero on the shipping path. The automerge block itself was never carried
      by this exit code; it lives in `renovate-config.json`
      (`platformAutomerge: false` plus `automerge: false` on every rule).
    - **`automerge-gate` emits a `GATE_DECISION` dotenv** (`automerge-eligible`,
      `human-review`, `blocked`, `tampered`) with `when: always`, so the decision
      is auditable and assertable rather than only a log line.
    - **`automerge-gate` refuses to decide against a tampered floor.**
      `CUSTOMER_SHIPPING` is a job-level variable and GitLab ranks project and
      group CI/CD variables above job-level ones, so a Maintainer on a consuming
      project could set it in Settings and silently send the job down the
      non-shipping path with no diff in any file. The gate now compares the
      runtime value against the literal it was compiled with and emits
      `GATE_DECISION=tampered` with a nonzero exit if they differ. Documentation
      claiming the floor was "enforced at two layers" has been corrected in
      `renovate-config.json`, `README.md`, `templates/automerge-gate/README.md`
      and `ADOPTION.md`; it was one layer plus a report.
    
    
  • v4.2.0

    protected Release: pipeline v4.2.0
    pipeline v4.2.0
    
    Added
    
    - site-check component. Runs a project's own check command -- type checking,
      framework diagnostics -- on merge requests. A build is not a check: `astro
      build` and its equivalents transform sources without type-checking them, so
      a TypeScript or framework major goes green in CI while breaking types. Four
      projects in this group each defined `"check": "astro check"` and no pipeline
      ran it. allow_failure defaults to false, because a check that cannot fail
      the pipeline is not a gate.
    - ADOPTION.md -- the adoption profile every consumer of this catalog conforms
      to, so "deliberately exempt" and "nobody got round to it" stop being
      indistinguishable.
    - Inherited Renovate enrollment: the runner injects this catalog's preset as
      base config for every autodiscovered project, making enrollment opt-out
      rather than opt-in.
    - RENOVATE_FORCE cadence normalizer, so the CI cron is the single source of
      cadence and an inherited schedule cannot silently park updates.
    - validate:renovate-config job, pinned to the Renovate major the runner runs.
    - needs: [] on the renovate job, breaking a deadlock where a rotated
      base-image digest could skip the bumper that would have fixed it.
    
    Changed
    
    - Evidence is frozen, by path. Files under bench/, archive/, evidence/,
      experiments/, rig/, receipts/ or results/ are experimental conditions some
      captured result describes, not dependency choices; bumping them voids the
      receipt. Keyed on path rather than repository because only 7 of 18 lab
      projects hold receipts, and a repository-wide freeze both over-freezes the
      rest and would rot the receipt-bearing labs' own site builds.
    - TypeScript ceilinged below v7 by upstream limitation, not preference.
      @astrojs/check peers ^5.0.0 || ^6.0.0 and has no v7-capable release. The
      ceiling still modernizes: 6.0.3 is stable and compatible, so Renovate
      proposes the v6 major instead of a v7 that cannot merge.
    - Renovate scan timeout raised to 3h, sized for a cold run.
    - Autodiscovery skips purge-pending projects.
    - Runner preset documents the cadence and enrollment doctrines in its header.
    
    Fixed
    
    - ci-tools: glab is looked up on the right forge (gitlab-releases, not
      github-releases). The pin had sat at v1.92.1 while v1.108.0 shipped.
    - Scheduled runs actually open merge requests. The preset scheduled updates
      for a window that closed before the cron fired, so every update parked in
      Awaiting Schedule while the job stayed green.
    - UBI grouping covers UBI10.
    - Catalog component pins no longer emit a spurious lookup-failure warning.
      Closes #2.
    
  • v4.1.2

    protected Release: pipeline v4.1.2
    pipeline v4.1.2
    
    Fixed
    
    - pages template: default-branch deploys actually fire. The 4.1.1 fix
      repaired explicit literal branch inputs but not the default: nested
      variables inside a variable value do not expand during rules:if
      evaluation. The branch input default is now the empty string and the
      rule splits in two: empty input deploys from the project default
      branch (direct variable-to-variable), an explicit input deploys from
      exactly that branch. Both paths verified on a live consumer before
      release. (!82)
  • v4.1.1

    protected Release: pipeline v4.1.1
    pipeline v4.1.1
    
    Fixed
    
    - pages template: the deploy rule now fires with the default branch
      input. The rule compared the branch name against the quoted literal
      $CI_DEFAULT_BRANCH and never matched, silently dropping the pages
      job from consumer pipelines using defaults. The input now lands in
      a job variable and the rule compares variable to variable.
      Consumers that pinned a literal branch as a workaround can drop it.
      (!81)
  • v4.1.0

    protected Release: pipeline v4.1.0
    v4.1.0
    
    Added
    - automerge-gate template: the terminal, fail-closed Renovate automerge
      decision job, with a customer-shipping human-signoff floor. Consumes per-check
      dotenv verdicts over needs: optional and blocks unless every required-check is
      present and == pass (and blocks if zero checks were evaluated).
    - osv-verdict template: emits an OSV-scan verdict for automerge-gate. On a
      merge-request pipeline it fails only on newly-introduced findings, fail-closing
      if the merge-base is unreachable.
    - ci-tools ships diffutils (diff + cmp). ubi9-minimal ships neither; a consumer
      no-op/idempotency check built on them failed command-not-found silently, hiding
      a real downstream bug.
    
    Fixed
    - automerge-gate fail-open guard: block (exit 1) when zero required-checks were
      evaluated (empty or typo'd input), ahead of both the customer-shipping floor
      and the non-shipping path.
    - osv-verdict shared diff-mode: fail-close on an unreachable merge-base rather
      than full-scanning and blocking on pre-existing findings; consistent with the
      sibling estate catalogs.
    - release reflows the annotated tag body before publishing, so the release page
      renders wrapped prose as paragraphs.
    
    Changed
    - Standardize the gate input to required-checks (hyphen), matching the sibling
      catalogs; sim consumers and READMEs updated.
    - Dependency bumps: UBI9 base image, golang 1.26.4, gitlab-org/cli (glab)
      1.102.0, and renovate shared-preset convergence onto the root config.
    
    Consumers pin @v4.1.0.
  • v4.0.2

    protected Release: pipeline v4.0.2
    v4.0.2 -- fix: verify runs after container-sign (stage-gated) + guards :latest
    
    Patch over v4.0.1. verify only needs:ed the build job, so it raced the
    separate container-sign job -> 'no signatures found'. verify is now
    stage-gated and guards the :latest digest. Pin @v4.0.2.
    
    Full changelog: CHANGELOG.md
  • v4.0.1

    protected Release: pipeline v4.0.1
    v4.0.1 -- fix: catalog images resolve for default consumers
    
    Patch over v4.0.0. The image input added in v4.0.0 defaulted to
    ci-*:$[[ component.version ]], which GitLab does not interpolate inside a
    resolved input default -- so consumers using the default pulled the
    literal string and failed with "invalid reference format". Reverted to a
    body-hardcoded image (which interpolates) and dropped the image input on
    container, container-sign, vale, and registry-cleanup. No consumer change
    beyond pinning @v4.0.1.
    
    Full changelog: CHANGELOG.md
  • v4.0.0

    protected Release: pipeline v4.0.0
    v4.0.0 -- supply-chain decoupling: build/sign split + :latest gating
    
    BREAKING. The container supply chain is now composable: build, sign,
    sbom, attest, and verify are independent components, and :latest tracks
    the default branch only.
    
    Breaking (consumers must act):
    - container no longer signs -- it builds, pushes, writes container.env.
      Add a container-sign job per build job to keep signatures:
        - component: .../pipeline/container-sign@v4.0.0
          inputs: { image_name: $CI_REGISTRY_IMAGE, container_job: container-build }
    - :latest is pushed/signed on the default branch + tags only, never on
      MR pipelines (:SHA always; :TAG on tags).
    - build-container-buildah removed -- use container + container-sign.
    
    Added:
    - container-sign -- cosign-signs pushed digests from container.env.
    - registry-cleanup -- container_expiration_policy with a cosign .sig
      carve-out (protects latest/semver/signatures, prunes stale SHA tags).
    - image input on container / container-sign / vale.
    
    Changed:
    - container-sbom drops artifacts:reports:cyclonedx -- Ultimate Container
      Scanning owns GitLab-side SBOM ingestion; component keeps its
      cosign-attested external SBOM.
    - ci-tools expanded (make/node/npm/python3/yq); vale gains disable_rules.
    - shape linter conforms to GitLab input-name convention.
    - reference-check relocated to public-sector/reference; reference examples
      moved to examples/.
    
    Full changelog: CHANGELOG.md
  • v3.3.2

    protected Release: pipeline v3.3.2
    **Patch release.** Includes the backtick fix from v3.3.1 plus the `GOVULNCHECK_VERSION` pin fix that was blocking the v3.3.1 tag pipeline from completing.
    
    - **Backtick fix (was: v3.3.1).** Dropped the GitLab `release:` keyword in favor of an explicit `glab release create --notes-file` invocation. The `release:` keyword routed `${CI_COMMIT_TAG_MESSAGE}` through shell `eval`, treating backticks as command substitution and stripping every `code span` from the release page. The replacement reads the tag annotation directly from `git tag -l --format='%(contents)'` into a file, then passes that file to `glab` — no shell expansion of the description body. Re-runs are idempotent: `glab release create` updates an existing release in place rather than failing.
    - **`GOVULNCHECK_VERSION` pin fix.** `containers/ci-go.Containerfile` had `ARG GOVULNCHECK_VERSION=1.3.0` (no `v` prefix); Go's module loader rejects bare semver: `invalid version: unknown revision 1.3.0`. Fixed to `v1.3.0` to match the sibling `GOLANGCI_LINT_VERSION=v2.4.0` shape. This was silently blocking every tag pipeline since the pin drifted; surfaced when v3.3.1's tag pipeline couldn't reach its own release stage.
    
    - [MR !57](https://gitlab.com/gitlab-com/public-sector/pipeline/-/merge_requests/57) — backtick fix
    - [MR !58](https://gitlab.com/gitlab-com/public-sector/pipeline/-/merge_requests/58) — pin fix
    - Lab evidence: [kaniko test-catalog-rc1](https://gitlab.com/gitlab-com/public-sector/kaniko/-/releases/test-catalog-rc1) — pre-merge validation of the backtick fix against a real consumer; 32-backtick parity from source tag to rendered release page
    - [CHANGELOG](https://gitlab.com/gitlab-com/public-sector/pipeline/-/blob/main/CHANGELOG.md)
  • v3.3.1

    protected
    **Patch release.** Fixes the `templates/release` catalog component so backtick-delimited content in tag annotations renders correctly on the release page, and makes tag-pipeline re-runs idempotent.
    
    - **Backtick fix.** Dropped the GitLab `release:` keyword in favor of an explicit `glab release create --notes-file` invocation. The `release:` keyword routed `${CI_COMMIT_TAG_MESSAGE}` through shell `eval`, treating backticks as command substitution and silently stripping every `code span` from the release page. The replacement reads the tag annotation directly from `git tag -l --format='%(contents)'` into a file, then passes that file to `glab` — no shell expansion of the description body.
    - **Re-run idempotency.** `glab release create` updates an existing release in place rather than failing with `--no-update flag was specified`. Force-pushed tags, Renovate re-runs, and operator-initiated re-tags all succeed.
    
    - [CHANGELOG anchor](https://gitlab.com/gitlab-com/public-sector/pipeline/-/blob/main/CHANGELOG.md#v331)
    - [MR !57](https://gitlab.com/gitlab-com/public-sector/pipeline/-/merge_requests/57)
    - Lab evidence: [kaniko test-catalog-rc1](https://gitlab.com/gitlab-com/public-sector/kaniko/-/releases/test-catalog-rc1) — pre-merge validation against the branch SHA, 32 backticks in source tag, 32 backticks in rendered release page (parity)
  • v3.3.0

    protected
    v3.3.0 - paper component + ci-latex image, runner_tag inputs, OCI metadata, dependency freshness, Fedora 44 base
  • v3.2.0

    protected Release: pipeline v3.2.0
    v3.2.0 -- expose runner_tag input on compliance/container-attest/container-sbom/vale templates. Default saas-linux-large-amd64 (one tier up from previous implicit small). Backward-compatible; consumers that don't set the input get the new default.
  • v3.1.0

    protected Release: pipeline v3.1.0
    v3.1.0 -- container component auto-injects OCI image-metadata build-args (OCI_VERSION / OCI_REVISION / OCI_CREATED) sourced from CI predefined variables. Additive, backward-compatible with v3.0.0 consumers. Pairs with the forthcoming container-image standard in reference.
  • v3.0.0

    protected Release: pipeline v3.0.0
    v3.0.0 — namespace migration to gitlab-com/public-sector
    
    First release at the new path gitlab-com/public-sector/pipeline.
    Every catalog image, cosign OIDC identity, and SLSA provenance
    subject moves to the new namespace.
    
    Hard break from v2.x. Consumers must update include lines:
      $CI_SERVER_FQDN/gitlab-com/public-sector/pipeline/<name>@v3.0.0
    
    Removed: posture-verify component (divested to GitLab compliance
    product). Deferred: ci-kaniko image (returns when kaniko ships
    at the new path).
    
    Bootstrap: image-build runs under quay.io/buildah/stable:v1.43.0
    with runtime cosign install. v3.1.0+ uses the just-published
    ci-buildah image.
  • v2.7.0

    protected Release: pipeline v2.7.0
    v2.7.0 - validation cleanup + pipeline-self-validates roll-up
  • v2.6.0

    protected Release: pipeline v2.6.0
    v2.6.0 - catalog IS the Renovate runner (presets/renovate-runner.gitlab-ci.yml + self-include)
  • v2.5.0

    protected Release: pipeline v2.5.0
    v2.5.0 - declare artifacts.reports.cyclonedx for GitLab Dependency List ingestion