Skip to content

ci: gate a release tag on a green dependency-floor run - #252

Merged
lesnik512 merged 1 commit into
mainfrom
fix/245-release-floor-gate
Sep 20, 2026
Merged

lesnik512 merged 1 commit into
mainfrom
fix/245-release-floor-gate

Conversation

@lesnik512

Copy link
Copy Markdown
Member

Closes #245. Option 4 from the issue, without the option 2 narrowing.

Why the literal proposal would not have worked

The issue proposes "do not tag unless the most recent scheduled run is green". Checked against the
release it was written about, that gate waves 1.8.0 straight through:

latest scheduled.yml run before the 1.8.0 tag: 2026-09-15 success 83730ca

It was green because it predated the commit that broke the floor. Recency of the run says nothing
about whether it covered the code being tagged.

So the gate asserts the stronger thing: a successful scheduled.yml run whose commit contains the
tagged commit
, i.e. the tag is an ancestor of (or equal to) the commit the floors ran on. That
makes "green" mean "green on this code" rather than "green on something".

Verified against real history

Three cases, run locally against the actual run list:

case run list result
HEAD = 1.9.2 commit, real run list includes e4c844e, dispatched before that tag pass
HEAD = 1.8.0 commit, run list as it stood when 1.8.0 was tagged 83730ca, dd03c41, 1694963 block
no successful runs at all empty block

The second case is the one that matters: the gate retroactively blocks the release that motivated the
issue, against exactly the data that existed at the time.

Shape

floors-gate runs before release, so a block costs nothing: PyPI has not been touched. The
existing ordering comment in release.yml (PyPI first, GitHub Release second) is unchanged, and its
now-false claim that there is "no in-workflow CI gate" is corrected.

A run dispatched by hand counts the same as a scheduled one. That is deliberate: it is the documented
escape hatch, and it is what I did before tagging 1.9.2. The error message spells it out, including
the re-push:

gh workflow run scheduled.yml --ref <sha>
gh run watch "$(...)" --exit-status
git push --delete origin <tag> && git push origin <tag>

There is no bypass flag. Dispatching the floors run is the bypass, and it takes about 13 minutes.

Permissions moved from one repo-wide block to per-job least privilege: the gate gets
contents: read + actions: read, the release job keeps contents: write + id-token: write.

What this does not do

Option 2 (narrowing the floors job so it can run on pull requests) is not included, per the
instruction to do option 4 only. The consequence is unchanged: a floor break still merges green and
is caught at tag time rather than at review time.

The gate also cannot prove the resolution is still green today, only that it was green on this
code. The floors job resolves direct dependencies at their floor and transitives at their newest, so
a sufficiently old covering run could pass the gate while today's resolution would fail. With the
daily cadence from #233 the covering run will normally be under a day old, so no max-age check was
added; if that assumption stops holding, this is where it would go.

Testing note

mapfile is bash 4+, absent on macOS's bash 3.2, so the loop uses while read instead. That is not
cosmetic: it is what made the three cases above runnable locally rather than only on a runner.

_checks.yml runs the floors only for scheduled.yml, never for pull requests, so
a floor break can merge with every check green and be tagged minutes later. That
is how the crash in 1.8.0 reached PyPI.

The gate requires a successful scheduled.yml run whose commit contains the tag,
rather than merely the most recent successful run: when 1.8.0 was tagged the most
recent run was green, having predated the commit that broke the floor.

Closes #245
@lesnik512
lesnik512 merged commit 2f0c9f4 into main Sep 20, 2026
13 checks passed
@lesnik512
lesnik512 deleted the fix/245-release-floor-gate branch September 20, 2026 17:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A floor break can merge with every check green: lowest-direct never runs on pull requests

1 participant