rlsbl v0.113.0 /rlsbl.ci_checks
On this page

One predicate behind both the release CI gate and the publish gate: did this project's own CI check actually run, and pass, on a given commit?

#rlsbl.ci_checks

#rlsbl.ci_checks

One predicate for both CI gates: did THIS project's own CI RUN and PASS on a commit?

rlsbl gates a release twice on the same question, in two places:

  • the RELEASE gate (rlsbl release run / resume / the batch

orchestrator) asks it before tagging the candidate commit;

  • the PUBLISH gate (the generated workflow in :mod:rlsbl.publish_gate)

asks it again before pushing artifacts to a registry.

They used to answer it with different machinery. The release gate watched WORKFLOW RUNS (gh run list --commit) and accepted the run's own conclusion; the publish gate polls CHECK RUNS filtered by the releasing project's name regex and accepts only success. In a monorepo those two answers come apart exactly when the CI router's dorny/paths-filter sees no change under a project's paths: the project's job concludes skipped, the router's workflow run still concludes success, so the release gate tagged and the publish gate then refused the same commit -- a tag and a GitHub Release for a version that can never publish.

This module is the single predicate both gates now use:

  • the check-run NAME MATCHER is the publish gate's own

(:func:rlsbl.publish_gate.ci_check_regex_for_targets for standalone repositories, :func:rlsbl.ci_router._router_ci_check_regex for monorepo members), never a second matcher that can drift;

  • the CONCLUSION POLICY is :data:PASSING_CONCLUSION: only success

passes. skipped and cancelled prove nothing about the commit and are hard errors on both sides.

Three outcomes stay explicitly distinct, and the release gate reports each differently (see :func:verify_project_ci_ran):

  1. The repository declares no push-triggered workflow at all -- handled

upstream by :func:rlsbl.commands.watch.push_triggered_workflows; the release proceeds with a loud notice and this module is never consulted.

  1. A project in scope ships no CI workflow of its own (nothing was ever

inlined into the router for it) -- an observable fact about the project, reported as its own loud notice, and its checks are not required.

  1. Workflows exist and the project HAS CI, but its checks are absent or

concluded anything other than success on the candidate -- a hard error naming the filter and the remedy.

#CheckFilter

One project's check-run name filter for a single candidate commit.

regex is None when the project ships no CI workflow of its own -- outcome 2 in the module docstring, reported rather than enforced.

#ProjectCINotRunError

The releasing project's own CI did not RUN-and-PASS on the candidate.

Deliberately distinct from a red CI: the code at the candidate may be perfectly fine, and the remedy is not "fix the failure" but "make the candidate contain a commit this project's CI actually runs on".

#standalone_check_filter

python
def standalone_check_filter(config, registry, *, label=None) -> CheckFilter

The filter a standalone repository's own publish gate bakes in.

Built from the same target set (:func:gate_targets_from_config) and the same matcher (:func:ci_check_regex_for_targets) that rlsbl scaffold renders into publish.yml.

#monorepo_check_filters

python
def monorepo_check_filters(workspace_root, project_dirs) -> list[CheckFilter]

Filters for the workspace projects released under one tag.

project_dirs maps project name -> absolute project directory. Each filter is the monorepo publish router's own (:func:rlsbl.ci_router._router_ci_check_regex), recomputed from the project's CI files on disk -- the same discovery monorepo sync used to mint the router's job keys.

#workspace_check_filters

python
def workspace_check_filters(workspace_root, project_dirs) -> list[CheckFilter]

Filters for every workspace project covered by project_dirs.

Each directory is resolved to its workspace project; a project belonging to a releasable pulls in that releasable's other members, because one tag publishes them all. Used by the batch orchestrator, whose single CI gate stands in for every member's individual gate.

#release_check_filters

python
def release_check_filters(*, config, registry, project_dir, workspace_root=None, monorepo_name=None, releasable_name=None)

Resolve the check filters the release CI gate must satisfy.

Standalone repositories yield one filter. A monorepo yields one per project released under the tag: the releasing project alone in implicit mode, or every member of the releasable in explicit mode -- exactly the set whose publish jobs the tag will trigger.

The releasing project is ALWAYS in the result, keyed by its own name and directory. The workspace read only widens that set to the releasable's other members; it can never narrow it away, so no workspace state can leave the releasing project unverified.

#latest_check_runs

python
def latest_check_runs(check_runs, regex, *, exclude_run_id=None)

Matching check runs, collapsed to the latest verdict per job.

Mirrors the publish gate's jq pipeline: filter by name, drop this workflow's own runs, group by name and keep the newest (started_at, then numeric id) so a retried run supersedes the stale one it replaced.

One thing a per-name collapse cannot see on its own: GitHub does not expand a matrix for a job its if skipped. The whole job collapses to ONE check run under the unsuffixed name (cli-ci / test), while the run that actually executes it emits one per leg (cli-ci / test (3.12)). The two never share a name, so the skip would outlive the run that answered it -- which is exactly the state a run_all dispatch exists to leave behind (see :data:RUN_ALL_REMEDY): the push run skipped the job, the dispatched run ran every leg of it.

So a skipped check run is dropped when a STRICTLY LATER check run for the SAME job -- its matrix expansion, by name -- exists. The legs are then judged on their own conclusions like any other check, so nothing is waived: a red leg still fails the gate. Nothing else can cover a skip. Not a sibling job, not a merely prefix-sharing name, and not an earlier run: if the skip is the latest word about that job, it stands and the gate refuses.

#failing_check_runs

python
def failing_check_runs(runs)

(name, conclusion) for every run that did not conclude success.

An incomplete run counts as failing here: the release gate only asks after every workflow run for the commit has already concluded, so a check still in flight at that point is not a pass either.

#fetch_check_runs

python
def fetch_check_runs(sha, *, cwd=None, config=None)

Every check run GitHub recorded for sha.

#verify_project_ci_ran

python
def verify_project_ci_ran(sha, filters, *, cwd=None, config=None, log=None, fetch=None, attempts=CHECK_DISCOVERY_ATTEMPTS, interval=CHECK_DISCOVERY_INTERVAL, exclude_run_id=None)

Hard-error unless every project in filters ran and passed CI on sha.

Raises :class:ProjectCINotRunError otherwise. Projects with no CI workflow of their own (CheckFilter.regex is None) get a loud notice on stderr and are not enforced -- an absent workflow is an observable fact about the project, not a skipped job.

Search