Skip to content

feat(rust-ci): add gate jobs whose names cannot move - #145

Merged
Jaro-c merged 2 commits into
mainfrom
ci/1552-stable-gate-names
Aug 24, 2026
Merged

Jaro-c merged 2 commits into
mainfrom
ci/1552-stable-gate-names

Conversation

@Jaro-c

@Jaro-c Jaro-c commented Aug 24, 2026

Copy link
Copy Markdown
Member

First step of Glyndor/podup#1552, and the one that has to land before anything else can move.

The problem

A required status check is matched by its name string. Two jobs here build their name by interpolating a value:

line 136:  name: Test (${{ matrix.os }})
line 240:  name: MSRV (${{ inputs.msrv }})

When a consumer changes that value, the job emits a different name, the consumer's ruleset still requires the old one, nothing emits it, and the pull request waits forever — and it is the pull request carrying the change, so the fix cannot be merged from inside it.

MSRV is the worse of the two: not a matrix, but a single job interpolating an input into its own name. The number is informative in the UI and poisonous as a gate name, and it is in the job log either way.

Why not just rename the jobs

Dropping the interpolation would emit MSRV, which no existing ruleset requires — blocking every pull request in every consumer at once. A check-name change is at least as breaking as renaming an input and harder to diagnose: the input is in a file, the name is in a settings page nobody opens.

So this is additive. Two new jobs carry names that cannot move, and consumers migrate their rulesets at their own pace. Test (${{ matrix.os }}) and MSRV (${{ inputs.msrv }}) keep emitting; nothing is removed here.

The one detail that makes them gates rather than decorations

needs: msrv
if: always()

needs: on its own skips the dependent job when its dependency fails, and a skipped required check does not block a merge — the exact failure these jobs exist to prevent. if: always() plus an explicit needs.<job>.result check is what makes them gate. skipped counts as a pass, because both upstream jobs are conditional on caller inputs (inputs.msrv != '', inputs.extra-test-os != '[]'), so a caller that asks for neither has nothing to fail.

The reasoning is in the YAML as comments on purpose. It is what stops someone deleting the if: always() in six months for looking redundant.

Second commit: the generated docs were about to lie

docs/reusables/rust-ci.md tells a consumer which checks are safe to require. Its rule for "conditional" was job.if is not None, so the two new gates — which carry if: always() precisely so they always emit — came out documented as conditional, with always() printed in the "Emitted when" column.

That inverts the table's meaning on exactly the rows a ruleset should be pointed at: it warned readers away from the only two checks in this workflow that cannot become phantoms. The page now reads 6 of 10 conditional instead of 8 of 10. No other reusable page changes — none of them spells out always().

Verification

The gate logic was executed against all four values needs.<job>.result can take, rather than reasoned about:

needs.result exit output
success 0 MSRV: success
skipped 0 MSRV: skipped
failure 1 ::error::MSRV job did not pass (result: failure)
cancelled 1 ::error::MSRV job did not pass (result: cancelled)

actionlint .github/workflows/rust-ci.yml clean. The docs renderer is idempotent — regenerating into a copy and diffing produces no change.

What happens next, and in this order

  1. this merges and gets tagged (MINOR — additive)
  2. podup bumps its pin, adds an equivalent summary job over its own podman-vm matrix, and lands that
  3. only then do podup's two rulesets move to the stable names — new names added before the old ones are removed, or every pull request there blocks
  4. later, once no ruleset requires a value-carrying name, this workflow can be simplified

Consumers other than podup will see two new checks appear on their pull requests. They are not required anywhere, so nothing blocks.

Jaro-c added 2 commits August 24, 2026 16:16
A required status check is matched by its name string, and two jobs here build
their name by interpolating a value: `Test (${{ matrix.os }})` and
`MSRV (${{ inputs.msrv }})`. When a consumer changes that value the job emits a
different name, the consumer's ruleset still requires the old one, nothing emits
it, and the pull request waits forever — and it is the pull request carrying the
change, so the fix cannot be merged from inside it.

`MSRV` is the worse of the two: not a matrix, but a single job interpolating an
input into its own name.

Renaming the existing jobs would be the obvious fix and is the wrong one: it
would break every ruleset that requires the current names, with a failure that
is harder to diagnose than a renamed input, because the input is in a file and
the check name is in a settings page nobody opens.

So this is additive. Two new jobs carry names that cannot move, `Extra platforms`
and `MSRV`, and consumers migrate their rulesets to those at their own pace. The
value-carrying names keep emitting; nothing is removed here.

Both gates use `if: always()` and inspect `needs.<job>.result` rather than
relying on `needs:` alone, because `needs:` alone SKIPS the dependent job when
its dependency fails, and a skipped required check does not block a merge — the
exact failure these jobs exist to prevent. `skipped` counts as a pass, since
both upstream jobs are conditional on caller inputs.

Refs Glyndor/podup#1552

Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
The generated page tells a consumer which checks are safe to require, since a
required check whose name nothing emits blocks every pull request. Its rule for
"conditional" was `job.if is not None`, so the two gate jobs added in the
previous commit — which carry `if: always()` precisely so they keep emitting
when the job they report on failed or was skipped — were documented as
conditional, with `always()` printed in the "Emitted when" column.

That inverts the meaning of the table on exactly the rows a ruleset should be
pointed at: it warned readers away from the only two checks in this workflow
that cannot become phantoms.

`rust-ci` now reads 6 of 10 conditional rather than 8 of 10, and both gates read
`always`. No other reusable page changes: none of them spells out `always()`.

Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
@Jaro-c
Jaro-c merged commit 76f8bc6 into main Aug 24, 2026
9 checks passed
@Jaro-c
Jaro-c deleted the ci/1552-stable-gate-names branch August 24, 2026 22:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant