Tags

Tags give the ability to mark specific points in history as being important
  • 4.6.0

    protected
    03c0206b · release: 4.6.0 ·
    Release: pipeline 4.6.0
    dunn.dev/pipeline 4.6.0
    
    `ci-runtime-go` now builds on UBI10 (`ubi10/ubi:10.2` builder,
    `ubi10/ubi-micro:10.2` final), completing the estate's UBI10 train:
    consumers pin this catalog at fixed tags, so this release is what
    actually moves them off ubi9. A **minor**: no template input added,
    removed, or renamed.
    
    - **`ci-runtime-go`: base moved ubi9 → ubi10.** Builder `ubi10/ubi:10.2`,
      final `ubi10/ubi-micro:10.2`; the OCI `base.name` label now names the
      real base (!211). Zero ubi9 references remain in any Containerfile.
    - **`renovate-config.json`: UBI9→UBI10 `replacement` rules added; the
      cairn `platformAutomerge: false` interim override removed** (!240) —
      its premise died twice: the branch-head starvation was fixed in the
      cairn repos themselves, and Renovate's own merge `PUT` cannot satisfy
      this estate's sha-required merges.
    - Routine tracking: golang 1.26, `buildah:stable`, renovate 44,
      `anchore/syft`, ubi10 digests.
    
    - **`signal-digest`: `_request()` retries transient read timeouts**
      (3 attempts, short backoff; !251) — a single slow gitlab.com answer no
      longer kills the digest job. Real HTTP status answers propagate
      unchanged.
  • 4.5.1

    protected
    9328a177 · release: 4.5.1 ·
    Release: pipeline 4.5.1
    pipeline 4.5.1
    
    `ci-go` now builds on go1.26.6, closing the five stdlib advisories below.
    Consumers pin `ci-go` at a fixed catalog tag rather than `:latest`, so this
    tag is what actually delivers the fix — each consumer still has to bump its
    pinned ref and re-run `audit` before govulncheck goes quiet. A **patch**: no
    template input added, removed, or renamed.
    
    - **`ci-go`: Go toolchain digest bumped past go1.26.5 to close five stdlib
      CVEs.** `containers/ci-go.Containerfile`'s `mirror.gcr.io/library/golang:1.26`
      digest pin landed on go1.26.6-equivalent content via the routine digest-track
      lane (renovate!213, merged 2026-08-15; folded into !219). Fixes, as reported
      by `go-audit`'s govulncheck against consumers on the affected toolchain:
      - **GO-2026-5972** / CVE-2026-33818 — `encoding/asn1` `Unmarshal` lacked a
        recursion limit, allowing stack exhaustion on deeply-nested input.
      - **GO-2026-5026** / CVE-2026-39821 — `net/http` (via `golang.org/x/net/idna`)
        accepted ASCII-only Punycode labels it should have rejected, a hostname
        validation bypass.
      - **GO-2026-6218** (`net/url` quadratic complexity), **GO-2026-6090**
        (`crypto/tls` post-handshake message limit), **GO-2026-6088**
        (`encoding/xml` recursion depth) — same go1.26.6 fix, caught in the same
        scan.
    
      The fix reached `origin/main` before this entry did: the `ci-go`
      Containerfile is pinned by digest, tracks the upstream `golang:1.26` tag,
      and Renovate's digest-update lane (`matchUpdateTypes: [digest, pinDigest]`,
      `minimumReleaseAge: null`) auto-merges those on green CI regardless of
      cause — it is not the `vulnerabilityAlerts`-specific security lane, which
      targets manifest-declared package advisories, not a Containerfile `FROM`
      digest. The gap was cadence, not policy: the fixed image published upstream
      2026-08-13T20:27Z; the estate's next scheduled Renovate scan didn't run
      against it until 2026-08-15T03:00Z, roughly a day and a half later.
    
      This CHANGELOG entry is the only thing this fix needed that automerge
      couldn't supply. **Not yet propagated**: consumers pin `ci-go` at a fixed
      catalog tag (`bairn@4.4.1`, `tutti@2.1.2`, `qobuz@2.4.0`, all pre-dating
      this fix) — none will see it until a new catalog tag ships and each
      consumer bumps its pinned ref. The `go-audit` job is `allow_failure: true`,
      so none of this blocked anything; it has been failing silently since well
      before this CVE pair (bairn's `audit` job has red history back to at least
      2026-05-18).
  • 4.5.0

    protected
    2213983b · release: 4.5.0 ·
    Release: pipeline 4.5.0
    pipeline 4.5.0
    
    Two threads. The `validate-*` self-validation harness had never run — every
    component was included at `@$CI_COMMIT_SHA`, `$[[ component.version ]]`
    resolves empty for a SHA ref, so each job pulled `ci-rust:` and died at
    container attach, masked by `when: manual` + `allow_failure: true`. It runs
    now, and immediately caught a stale Claude model id, a `container-image`
    publishing path bug, and an unbound-variable class in `pipeline.just`. It is
    deliberately still not gating: the repo has no language fixtures, so every
    go/rust/node validate job would fail on that alone, and a permanently-red
    gate on the L1 catalog is worse than none.
    
    The second thread is supply-chain hygiene: Fedora and UBI10 repoints, and an
    end to the recurring class where GitLab garbage-collects its own published
    analyzer digest and reddens a pipeline we never touched.
    
    A **minor**: no template input added, removed, or renamed.
    
    - **Renovate: UBI9 to UBI10 replacement rules.** `replacement` is its own
      updateType, separate from `major` — the human-review lane never saw these.
    - **`validate-*` harness is playable.** Image-tag interpolation fixed and
      wiring corrected. Stays `manual` + `allow_failure` by deliberate decision
      until language fixtures exist.
    
    - **`ci-runtime-node` on UBI10** (`ubi10/nodejs-22-minimal`), removing ten
      unfixable HIGH curl/libcurl CVEs — 0 of 107 measured findings had a Red Hat
      fix, so the base moved rather than the finding being waived.
    - **Fedora containers repoint** `quay.io` to `registry.fedoraproject.org`.
    - **hadolint checksum** reads the combined `checksums.sha256` (upstream 2.15.0+).
    - **Rust jobs build, test and clippy `--locked`.**
    - **`secret-verdict` no longer pins a digest.** GitLab GC'd its own published
      `security-products/secrets` digest, which reddened this pipeline twice on an
      image we never changed; it now tracks the immutable version tag.
    - **`claude-drift-triage`** default model id corrected.
    - **`lint-includes`** tolerates an `image:` override on suppressed jobs.
    - **Eleven `pipeline.just` recipes guarded against unbound `CI_COMMIT_TAG`**
      under `set -u`. Release paths hard-fail with a clear message; artifact paths
      skip cleanly. A silent `exit 0` in a release path would report green while
      publishing nothing.
    - **`signal-digest.py`** discovers group runners dynamically instead of
      checking a hardcoded id pair, and its wedged-MR predicate now catches
      conflict-plus-armed. The old check reported a permanent false WARN on a dead
      runner while covering neither runner that mattered.
    
    - **`platformAutomerge: false` scoped to `dunn.dev/cairn/**`** (was
      estate-wide), moving the decision into Renovate's own retry loop so it
      self-heals.
    - **Stop digest-pinning `registry.gitlab.com/security-products/**`.** Pinning a
      digest we do not control against a registry that GCs it converts vendor
      housekeeping into a hard red pipeline.
  • 4.4.1

    protected
    7fe4c129 · chore(release): 4.4.1 ·
    Release: pipeline 4.4.1
    4.4.1 (retag after rebase)
  • 4.4.0

    protected Release: pipeline 4.4.0
    dunn.dev/pipeline 4.4.0
    
    Minor: secret detection that can actually fail a pipeline. One new
    component (secret-verdict) and one new optional input on automerge-gate
    (secret_job). Nothing removed or renamed, and "secret" is deliberately
    absent from the required-checks default, so an existing consumer that
    upgrades renders an identical materialized job set and an identical gate
    verdict. Adoption is opt-in, per repo.
    
    Added
    - secret-verdict: GitLab's own Security/Secret-Detection template cannot
      block a leak. Its analyzer shells out to gitleaks with --exit-code 0,
      so the job exits clean even when it finds a credential -- the findings
      go into gl-secret-detection-report.json and nowhere else. Flipping that
      template's allow_failure to false does not fix it; it only makes
      operational faults blocking. This component runs the same analyzer over
      the same scan surface, so migration is a swap of the include rather
      than a change in what gets flagged, then parses the report itself and
      derives its own verdict. It emits CHECK_SECRET=pass|fail as a dotenv
      for automerge-gate and also exits nonzero on a finding -- unlike
      osv-verdict, which always exits 0 and leaves blocking to the gate. That
      divergence is deliberate: a verdict-only component would leave a repo
      with no automerge-gate still unable to fail, reproducing the exact bug.
      Fail-closed throughout: a missing, zero-length, schema-foreign, or
      non-array report, and any nonzero analyzer exit, all emit fail and exit
      1 -- absence is never read as cleanliness. Scope control is
      excluded_paths, visible in the diff. There is deliberately no severity
      knob and no allow_failure input. Proven with a planted-secret probe.
    
    Changed
    - automerge-gate: new secret_job input plus a matching needs: entry,
      handled exactly like osv_job. Consumers opt in by adding "secret" to
      required-checks once they include the component. Because secret-verdict
      reddens the pipeline itself, the gate is skipped rather than
      run-and-blocking when a leak is found -- GitLab skips the dependents of
      a failed needs: job. The pipeline is red, and automerge blocked, either
      way.
    - The catalog meta-pipeline dogfoods the new slot: secret-verdict runs
      for real alongside osv-verdict, and this repo's own automerge-gate sets
      required-checks "osv,secret".
    - check-principle-image-tracking-version gains a second documented
      exception for the GitLab secret-detection analyzer image, pinned
      tag@digest and Renovate-tracked.
    
    Fixed
    - Renovate: security-fix automerge now requires a real pipeline rather
      than a green dashboard park. vulnerabilityAlerts keeps automerge true
      but now also sets platformAutomerge false, so the merge runs through
      Renovate's own checkAutoMerge instead of GitLab native automerge, which
      registers a branch carrying zero status checks as mergeable
      immediately. Scoped to vulnerabilityAlerts only; every other automerge
      lane is untouched.
    
    No consumer migration ships with this tag. patinate, kits, iac and qobuz
    still include the fail-open upstream template; moving them is a separate
    decision.
    
    Tag convention unchanged since 2.0.0: bare semver, no v-prefix.
    See CHANGELOG.md for the full entry.
  • 4.3.1

    protected Release: pipeline 4.3.1
    dunn.dev/pipeline 4.3.1
    
    Patch: reference-check migrates off the magic-name GITLAB_TOKEN group
    variable to ESTATE_READ_API_TOKEN, with a fallback during the dual-variable
    migration window. No new inputs, no removed/renamed inputs.
    
    Fixed
    - reference-check: GITLAB_TOKEN (provisioned in 4.2.0) is a magic name glab
      prioritizes over CI_JOB_TOKEN autologin for every glab call across every
      job in the dunn.dev group -- not just this recipe's own reads. A
      read_api-only token squatting on that name silently broke
      dunn.dev/cairn/pipeline's create-release job (403 insufficient_scope:
      glab picked up the read-only group token instead of the write-capable
      CI_JOB_TOKEN). Migrated the recipe to read ESTATE_READ_API_TOKEN instead,
      falling back to GITLAB_TOKEN during the migration window, scoped inside
      the recipe's own subshell only -- nothing exported process-wide.
    - reference-check: no longer exports an empty GITLAB_TOKEN. A naive
      GITLAB_TOKEN="${ESTATE_READ_API_TOKEN:-${GITLAB_TOKEN:-}}" would export
      an empty GITLAB_TOKEN when neither variable is set, and an empty
      magic-name variable shadows CI_JOB_TOKEN autologin exactly like a
      wrong-scoped one -- the identical failure mode this release exists to
      fix, reintroduced one layer down. Now unsets GITLAB_TOKEN when the
      resolved value is empty, so autologin sees a genuinely clean
      environment.
    
    The GITLAB_TOKEN fallback stays in place only for the dual-variable
    migration window and should be dropped once the group GITLAB_TOKEN
    variable is deleted.
    
    Tag convention unchanged since 2.0.0: bare semver, no v-prefix. See
    CHANGELOG.md for the full entry.
  • 4.3.0

    protected Release: pipeline 4.3.0
    4.3.0 — cloudflare-worker: deploy-time script deps + worker_secret_variables
    
    Graduated from dunn.dev/gals after a live deploy failure: the deploy job
    now honors needs_npm_install (wrangler bundles the worker script at
    deploy time), and an opt-in worker_secret_variables input populates
    Worker secrets from CI variables via wrangler secret bulk.
  • 4.2.0

    protected Release: pipeline 4.2.0
    dunn.dev/pipeline 4.2.0
    
    Four correctness fixes, no new inputs, no removed/renamed inputs -- follows
    4.1.0's minor-not-patch precedent.
    
    Fixed
    - container-image: builder image (quay.io/buildah/stable) repinned off a
      :latest that quay.io proved (2026-08) rebuilds in place across v1.43.1,
      v1.43, v1, and latest alike -- untracked drift, not "always current."
      Repinned to v1.43.1-immutable@sha256:fc649e18..., a real non-rebuilt tag
      Renovate can track and bump.
    - renovate.json templates/*.yml customManager: the sole matchString
      required a quoted default: value, so container-image.yml's bare
      image: quay.io/buildah/stable:latest was invisible to it. Added an
      image:/name: key variant (quoted or bare, digest optional).
    - reference-check: every API-backed assertion in dunn.dev/reference's
      primitives (check_gitlab_setting, check_registry_cleanup_coverage) was a
      silent no-op in CI since introduction -- ci never installed glab, the
      recipe never set PROJECT_ID. Fixed: ci now bakes glab (checksum-verified,
      version-pinned to harness/mise.toml's gitlab-org/cli pin); the recipe
      exports PROJECT_ID from CI_PROJECT_ID plus GLAB_ENABLE_CI_AUTOLOGIN=true.
      That alone still 403'd on the general Projects API (CI_JOB_TOKEN's
      job-token-access table doesn't cover GET /projects/:id). Closed by a
      read_api group access token, wired as group CI variable GITLAB_TOKEN
      (masked, unprotected, glab's own higher-precedence-than-CI_JOB_TOKEN env
      var -- confirmed via `glab auth login --help`, and empirically: a
      CI_JOB_TOKEN-authenticated call would 403 on this endpoint rather than
      return real settings). Proven end-to-end against a real job
      (dunn.dev/reference, throwaway branch, this catalog's own ci image):
      check_registry_cleanup_coverage reads and reports a real live value
      (PASS ... cleanup disabled (enabled=false), job 15676948764), and a
      throwaway deliberate-violation assertion reports a real FAIL against
      real data (FAIL ... default_branch = main (expected
      /this-branch-does-not-exist/), job 15677067064). fail_on_violation stays
      false estate-wide: all 9 current consumers were audited against live
      state and every one fails at least one contract today (pre-existing,
      unrelated gaps). Fix violations first, then enforce.
    - renovate-config preset: the cloudflare workers ecosystem group matched
      almost nothing -- matchUpdateTypes:["major"] excluded wrangler's peer
      bump to @cloudflare/workers-types ^5 at 4.108.0, a minor, so the group
      MR only ever carried the types half (the exact ERESOLVE the rule existed
      to prevent). Dropped matchUpdateTypes entirely.
    
    Tag convention unchanged since 2.0.0: bare semver, no v-prefix. See
    CHANGELOG.md for the full entry.
  • 4.1.0

    protected Release: pipeline 4.1.0
    dunn.dev/pipeline 4.1.0
    
    Minor: additive plus fixes. No input removed or renamed, no existing default
    behaviour changed. Consumers pick this up by bumping their component pins.
    
    Added
    - model-id-drift (new, opt-in): fails when a consumer's tracked working tree
      carries a hardcoded claude-(opus|sonnet|haiku) major below the declared
      current_ga_major (default "5"). No network access; pure regex over the
      already-checked-out tree. No repo is auto-enrolled. There is no Renovate
      datasource for Claude model ids, so a hardcoded id rots invisibly --
      confirmed stale on dunn.dev/pi and dunn.dev/skald.md while Opus 5 was GA.
    - ci-go bakes osv-scanner, ci bakes hadolint (both checksum-verified release
      binaries). osv-verdict now runs on ci-go, so the gate binary is no longer
      fetched fresh on every MR across the estate.
    
    Changed -- renovate-config preset
    - go.mod indirect dependencies now propose an MR only when they carry an
      active OSV/GHSA alert (security:gomodIndirectSecurityUpdates). Surfaced by
      bairn's kin-openapi indirect dep needing a hand-diagnosed fix.
    - Internal-artifact MAJORs exempt from minimumReleaseAge; automerge stays
      false (age delay and human review are separate knobs). Closed two coverage
      gaps while widening: the bare-catalog depName form was never matched, and
      the gitlab-releases exemption is now scoped by package name so it no longer
      silently covered upstream gitlab-org/cli.
    - mergeConfidence:all-badges, display-only, for the human majors-review queue.
    - minimumReleaseAge:null for four evidenced timestamp-less packages (envoy via
      mirror.gcr.io, two immich images, @dunn.dev/reference on GitLab's npm
      registry) -- a strand fix by exact package name, with explicit
      automerge:false, not a registry-wide trust exemption.
    
    Fixed -- osv-verdict
    - Go call analysis runs for real now that the job is on ci-go. The gate blocks
      on called=true or called=unknown; a finding call analysis proves unreachable
      is still logged and kept in the artifact, just not gate-blocking.
    - Default-branch pushes diff against CI_COMMIT_BEFORE_SHA instead of
      full-scanning, closing the green-MR-then-red-main asymmetry.
    - The MR diff base was a silent no-op: git stash swept up the untracked
      head-scan outputs and nothing ever popped it, so the verdict was pass
      regardless of whether a new finding existed. Fail-open under a fail-closed
      gate, on every diff-mode run. Head scan outputs now survive outside the tree.
    
    Fixed -- automerge-gate, osv-verdict
    - Both jobs now instantiate on a bare branch pipeline with no open MR yet. The
      prior rules matched only merge_request_event or the default branch; audited
      across 97 consumer projects, ~25 had zero gate coverage in exactly that gap,
      where the job never emitted and needs:optional read absent rather than
      failed.
    - The clause shipped in a two-rule form and was then consolidated to one
      (CI_COMMIT_BRANCH && CI_OPEN_MERGE_REQUESTS == null). That is a
      simplification, not a behaviour change -- both forms render identically.
    
    Verified pre-tag: a synthetic consumer POSTed to ci/lint at three refs with
    components pinned by SHA, carrying probes that prove the rendered variable
    context. Gate jobs instantiate on a no-MR branch, are correctly suppressed on
    a branch with an open MR, and main is unchanged. A grandparent control at the
    commit before the branch clause existed confirms the harness discriminates:
    the gate jobs are absent there on the same ref and present after.
    
    Also: baked tool versions advanced (cosign 3.1.2, syft 1.49.0, just 1.57.0;
    ci-iac uv 0.11.32, sops 3.13.3), image ARG and template spec-input default
    moving in lockstep.
  • 4.0.1

    protected Release: pipeline 4.0.1
    dunn.dev/pipeline 4.0.1
    
    Fix: ci-runtime-go ships the CA trust bundle + tzdata.
    
    The UBI-micro base shipped neither, so a static (CGO-off) Go binary doing
    outbound TLS failed x509 verification on its first call -- caddy's Let's
    Encrypt ACME + Cloudflare DNS-01, and bairn's Famly API (total-failure for
    both). Every other ci-* image already installed ca-certificates on purpose;
    ci-runtime-go was the outlier. Staged from a throwaway full-UBI builder;
    final image stays package-manager-free (+3.5MB). ci-runtime-node unaffected
    (Node uses its own compiled-in root store).
    
    Adversarially verified pre-tag: the built image carries 146 valid PEM certs
    (incl. ISRG Root X1/X2) at Go's x509 paths.
  • 4.0.0

    protected Release: pipeline 4.0.0
    dunn.dev/pipeline 4.0.0
    
    Breaking major (dead-surface removal + supply-chain hardening).
    
    Removed (zero-consumer, estate-wide verified):
    - cloudflare-pages component + recipes/validate slots (use cloudflare-worker)
    - release-create `extra_sbom_jobs` input + upload-extra-sbom job/recipe
    
    Supply chain:
    - ci image bakes osv-scanner + cosign (checksum-verified) → osv-verdict
      gate and node-publish signing install nothing at runtime
    - syft installer pinned to a release tag (was mutable main)
    - template tool-version defaults are Renovate-tracked (customManager)
    
    Lint: lint-includes (supersedes lint-suppression) — structural YAML
    bijection + suppression coverage. cosign/syft fallback defaults aligned
    to baked versions; go-sbom skips .sha256 sidecars.
    
    Merged via !96 (rebased onto current main; pipeline green incl. osv-verdict).
  • 3.1.0

    protected Release: pipeline 3.1.0
    3.1.0 - pin-audit + preset improvements
    
    Adds the pin-audit component (I1 enforcement: fails the build on an image pin
    Renovate cannot track forward -- bare @sha256 / commit-SHA tag) + the ADOPTION.md
    adoption profile, now adoptable by consumers (pin-audit postdated the 3.0.0 tag,
    so pin-audit@3.0.0 never resolved). Plus renovate-config preset improvements
    accumulated since 3.0.0 (abandonment signal, automerge-pin, astro/vitest ecosystem
    grouping, rebaseWhen=behind-base-branch, lockFileMaintenance release-age exemption,
    trusted-upstream fast-track, mise-gitlab manager de-dup) and ci/ci-iac fixes.
    
    No breaking change: every component's inputs and behavior are unchanged from 3.0.0.
    Minor bump.
  • 3.0.0

    protected Release: pipeline 3.0.0
    3.0.0 - baked-recipe component model
    
    BREAKING: every component's logic now lives in pipeline.just, baked into
    the ci-* images at /opt/dunn-pipeline/justfile. Templates are thin `just`
    wrappers and inputs pass via variables:. The `image:`-override escape
    hatch (run a component on a vanilla base and install tools on-demand) is
    removed: overriding `image:` to a base without the baked justfile will
    fail. Pin a ci-* image (the default) or fork the recipe.
    
    Migrated families: go, rust, node, the L0 gate (osv-verdict +
    automerge-gate), the ops/deploy family (cloudflare-pages/worker/r2,
    reference-check, slsa-attest, release-create, claude-drift-triage), and
    container-sbom. container-image stays inline by design (foreign buildah
    image, no baked justfile).
    
    No consumer-facing contract change: every component's inputs, artifacts,
    and behavior are identical to 2.7.0. Only the internal execution model
    changed.
  • 2.7.0

    protected Release: pipeline 2.7.0
    2.7.0: ship osv-verdict + automerge-gate with fail-open guard
    
    First tagged release of the osv-verdict and automerge-gate components
    (previously in the Unreleased section). The gate blocks (exit 1) when
    zero required checks were evaluated, so an empty or typo'd required-checks
    no longer fail-OPENs to GATE GREEN.
  • 2.6.0

    protected Release: pipeline 2.6.0
    pipeline 2.6.0
    
    Image-only release; templates unchanged since 2.5.1.
    
    ci image:
    - ship just alongside make (estate repos converging on justfile;
      consumers can drop per-job dnf installs)
    - sigstore/cosign 3.x
    
    Repo CI:
    - containers rebuild on MRs that touch their Containerfile
  • 2.5.1

    protected Release: pipeline 2.5.1
    renovate preset: lift UBI X.Y allowedVersions estate-wide
  • 2.5.0

    protected Release: pipeline 2.5.0
    2.5.0 - extra_sbom_jobs array on release-create
    
    release-create now accepts an `extra_sbom_jobs:` type-array input
    backing a new `upload-extra-sbom-assets` job, so consumers shipping
    N SBOMs per release can link all of them to the GitLab Release page.
    
    Backward compat: omitting `extra_sbom_jobs` keeps the prior single-
    SBOM behavior; the new job runs tag-only as a cheap no-op when the
    input is defaulted.
    
    GitLab CI components only support whole-value array substitution,
    so the multi-SBOM path lives in a second job rather than expanding
    the existing upload-release-assets needs: block.
  • 2.3.3

    protected Release: pipeline 2.3.3
    2.3.3
    
    fix(container-sbom): sbom_filename input. Multi-image consumers
    that collocate SBOMs under one package_name can now give each job
    a distinct output filename so they don't overwrite each other.
    Default behaviour unchanged.
  • 2.4.0

    protected Release: pipeline 2.4.0
    2.4.0 — Renovate preset, validator gate, daily cadence
    
    This release introduces the estate-wide Renovate preset as a public
    catalog surface, alongside the per-tag rebuild of ci-* images and
    the component versions that have always shipped on tag.
    
    What consumers gain
    -------------------
    
    A central preset published from this repo. Every consumer renovate.json
    can shrink to a single line:
    
        { "extends": ["gitlab>dunn.dev/pipeline:renovate-config"] }
    
    The preset carries: platformAutomerge: true (GitLab merges minor+patch
    on green CI without waiting for the next Renovate run), prHourlyLimit: 0
    (no per-hour cap; daily run drains the queue), assignees set to the
    maintainer accounts (so manual-review MRs land in the inbox), the
    minor+patch automerge + major manual-review packageRules, and the
    shared Containerfile ARG customManager used by every Pattern A
    repackaging consumer (caddy-estate, pipeline's own ci-* images,
    future auth upstream-software containers).
    
    No `schedule` field in the preset. Cadence is owned by the runner's
    CI cron schedule alone, with RENOVATE_FORCE overriding any stale
    per-repo schedule at scan time.
    
    Operational changes in this repo
    --------------------------------
    
    - New `validate-renovate` job runs `renovate-config-validator --strict`
      on every MR touching renovate.json or renovate-config.json. A
      malformed preset cannot reach consumers.
    - Renovate runner job drops $CI_PIPELINE_SOURCE == "web". Entry
      points are `schedule` (daily cron) and `api` only.
    - Pipeline's own renovate.json now extends the preset and keeps only
      the two catalog-specific managers: UBI date-stamped-tag filter
      and the templates/*.yml Docker pin regex.
    - README gains an Architecture section with the end-to-end Mermaid
      diagram of the single-maintainer estate.
    
    Container images
    ----------------
    
    ci, ci-go, ci-rust, ci-runtime-go, ci-runtime-node all published at
    :2.4.0 alongside this tag, matching every consumer's @2.4.0 pin
    under the v2.0.0 triple-coupling contract.
    
    Migration
    ---------
    
    No breaking changes for existing consumers. Per-repo renovate.json
    files continue to work; migration to the central preset is hygiene
    and can be done one repo at a time. The fan-out lands as a sequence
    of small per-repo MRs starting with dunn.dev/reference.
  • 2.3.2

    protected Release: pipeline 2.3.2
    2.3.2
    
    chore: UBI runtime bases on clean X.Y tags.
    
    - ci-runtime-go: ubi9/ubi-micro:9.8 (was 9.7-1778461406)
    - ci-runtime-node: ubi9/nodejs-22-minimal:9.8 (was :1)
    
    Plus renovate.json package rule constraining UBI image tag matching
    to /^[0-9]+\.[0-9]+$/, preventing drift back to date-stamped tags.
    
    Same UBI under the hood; cleaner pin, less PR churn for consumers.