pipeline v5.8.0 -- L0 gate branch-push coverage + catalog Renovate policy
Gate-coverage repair and catalog-side Renovate policy. The L0 gate now
instantiates on a bare branch push -- roughly a quarter of consumer repos
were reporting green having evaluated zero gate logic -- and
`automerge-gate` gained a second independent cargo-verdict slot. The
estate preset picked up an internal-trust exemption from
`minimumReleaseAge` and wasmtime-family grouping. Separately, this
catalog's own Renovate bot gained a signal digest, a token-expiry alarm,
CI validation of the preset, and stage decoupling (catalog-internal --
nothing a consumer pins or extends moves with those).
- **`automerge-gate`: `cargo_job_2` -- gate on a second independent
cargo-verdict job.** A repo with 2+ Cargo manifests and no root
manifest (nomograph/extension: `host/` + `crates/extension-host/`)
could not vouch itself through the L0 gate -- `cargo_job` had exactly
one needs: slot, so extension shipped hand-rolled CI instead (see its
`.gitlab-ci.yml` header). `cargo-verdict` gained a `check_name` input
(default `"cargo"`, unchanged) so a second instance can emit
`CHECK_CARGO_HOST`/`CHECK_CARGO_EXTENSION_HOST` instead of colliding on
`CHECK_CARGO`; `automerge-gate` gained `cargo_job_2` pointing at that
instance's `job_name`, plus a hyphen-to-underscore fold when resolving
`required-checks` names to `CHECK_<NAME>` variables (needed for
hyphenated multi-cargo check names; a no-op for the existing bare
`osv`/`cargo` names). `cargo_job_2` defaults to a sentinel job name no
consumer defines, so every existing single-cargo-job consumer is
unaffected (verified: `needs: job: <undefined-name> optional: true`
lints valid with no such job in the pipeline). A third manifest would
follow the identical pattern (`cargo_job_3`); not shipped preemptively.
- **Renovate: internal-trust exemption from `minimumReleaseAge`, all update
types including major.** `minimumReleaseAge: "7 days"` exists to give a
compromised third-party release time to be caught before we consume it --
there is no third party in the loop for artifacts we author, build, and
tag ourselves. Two new `packageRules` set `minimumReleaseAge: null` for
the nomograph/pipeline catalog itself (both the `component:` includes and
the `${NOMOGRAPH_REGISTRY}`-prefixed images collapse to
depName/packageName `nomograph/pipeline` on the `gitlab-tags` datasource
-- verified against Renovate's `gitlab-ci` manager source), our own
crates (`nomograph-claim`, `nomograph-workflow`, confirmed live on
crates.io), and the `gitlab-releases`-datasource mise tools
(`andunn/dkg`, `gapvec`, `glean`). Majors deliberately keep
`automerge: false` via the existing major-updates rule -- a major can
still break consumers and the releaser owns consumer green, so the age
gate is gone but human review is not. Mirrors the equivalent (narrower)
rule in `dunn.dev/pipeline`'s preset.
- **Renovate: group the wasmtime crate family into one MR.** `wasmtime` and
every `/^wasmtime-/` crate are pinned in lockstep by a shared
`wasmtime-environ` exact-pin, so Renovate splitting them into separate MRs
produced MRs that were each red in isolation -- including a security pair
that could not be landed incrementally (observed on `extension`: !14,
a security fix bumping `wasmtime` to 46.0.2, broke Cargo resolution
against `wasmtime-wasi` =46.0.1; !13 bumping `wasmtime-wasi` to v47 left
two `wasmtime` versions in one graph with cross-version type mismatches;
both sat red until landed together as `extension!15`). New `packageRules`
entry sets `groupName: "wasmtime family"` only -- majors keep
`automerge: false` from the existing major-updates rule, minor/patch keep
automerge from the minor+patch rule, and a mixed-type group will not
automerge because its updates disagree, which is the correct conservative
outcome.
- **Renovate bot: signal digest + token-expiry alarm** (catalog-internal;
no template or preset behavior moves). Parity port from
`dunn.dev/pipeline` so both estates run the same alerting logic. New
`scripts/signal-digest.py` and a scoped `justfile` (this repo had none;
other jobs call scripts directly) drive a `signal-digest` job that runs
after the scheduled scan with `when: always` and folds five cheap health
signals -- the scan's own trace and `artifactErrors`, every configured
group's Dependency Dashboard "Errored" section, every repo's
default-branch pipeline status, runner liveness, and wedged MRs -- into
one pinned GitLab issue, plus an optional webhook. `renovate-token-check`
reds within 30 days of `RENOVATE_TOKEN_EXPIRES` (a PAT cannot introspect
its own expiry, so the date is carried in that variable and updated on
rotation). No new secret: `GITLAB_TOKEN` maps from the `RENOVATE_TOKEN`
the scan already holds. Adapted rather than copied blind -- both jobs use
this repo's digest-pinned alpine and default `storr` tag, and
`signal-digest`'s rules carry the sibling `renovate` job's `$RENOVATE ==
"true"` guard so the weekly container-rebuild schedule (where `renovate`
does not run) does not fail pipeline creation on `needs: [renovate]`.
- **CI: `validate-renovate` gate on this repo's own preset**
(catalog-internal). Edits to `renovate.json` / `renovate-config.json` now
run `renovate-config-validator --strict` inside the same digest-pinned
`renovate/renovate` image the bot pulls, on MR and on main push -- so a
green pipeline means that exact bot build accepts the config, not that it
parsed on a laptop. Every preset edit before this landed unchecked.
- **`automerge-gate`, `osv-verdict`, `cargo-verdict`: gate never
instantiates on a bare branch push.** All three `rules:` blocks matched
only `$CI_PIPELINE_SOURCE == "merge_request_event"` or
`$CI_COMMIT_BRANCH == "main"` -- a Renovate bot branch pushed but not
yet carrying an open MR pipeline is neither, so the gate and its verdict
producers simply did not run: `needs: optional: true` swallowed the
absent job, and the pipeline reported green having evaluated zero gate
logic. Added a third clause, `if: $CI_COMMIT_BRANCH &&
$CI_OPEN_MERGE_REQUESTS == null`, scoped so it fires only when the
branch carries no open MR yet: once an MR opens, its
`merge_request_event` pipeline (clause above) independently covers the
same push, so the branch-pipeline copy stays out of the way instead of
duplicating a full OSV scan / `cargo build --all-features` + test run
on every push thereafter. The first push after MR creation still races
GitLab populating `$CI_OPEN_MERGE_REQUESTS`, so one duplicated run per
MR remains -- accepted, not engineered away. Verified via `ci/lint`
against a synthetic consumer carrying the refined 3-clause shape
(`valid: true`, no errors). Measured across the estate: roughly 25 of
97 consumer repos were losing gate coverage in branch context before
this fix.
- **Renovate bot: `needs: []` decouples the scan from every prior stage**
(catalog-internal). The `renovate` job sits in the last stage with no
`needs:`, so GitLab's stage-sequential default made it wait on all of
`lint` -- confirmed on scheduled pipeline 2724786185, where all seven
lint-stage jobs ran to completion before the scan started. A failing or
stuck earlier-stage job (a container build on a rotated base-image
digest, say) would then block or skip the one job positioned to fix it.
`needs: []` lets it run on schedule regardless, matching
`dunn.dev/pipeline` (05ce0f7c) and `yotoshelf/pipeline`'s existing
convention.