ci: migrate the gate off trailer-only onto the fleet hybrid gate - #20
Conversation
The org-default reusable this replaces verifies a Gate-Passed trailer and nothing else. That trailer is minted by a full local gate, which needs a build box the fleet does not currently declare -- so a PR without one could never produce a passing required check. The gate was unsatisfiable rather than strict, and pinax's own ci.yml already carried a comment saying so and naming this migration as the fix. hybrid-gate keeps the trailer as a fast path and otherwise runs a real fmt/check/clippy/nextest build on a hosted runner, mirroring ci.yml's existing jobs. doctest_cmd is set because nextest does not execute doctests. rust_toolchain is set explicitly to "stable" rather than left empty, which is the fleet convention elsewhere: hybrid-gate auto-detects from the caller's rust-toolchain.toml, and pinax has none -- unlike koinon and sphragis, which leave it empty because they do. Leaving it empty here would hand toolchain selection to whatever the action defaults to absent any file. Adding a rust-toolchain.toml (MSRV 1.94 is already declared) would let this drop back to empty, and is worth doing separately. needs_fleet_repo_token is false: every workspace dependency is a crates.io crate and every cross-crate edge is a local path pin -- no forkwright git dependency in any of the four manifests. WARNING: the job id is `gate`, not `call`. Branch protection pins required contexts by name, so `call / gate-attestation` can never report again and `gate / gate` had never reported before. Both were swapped in required_status_checks as part of this change. Renaming the job without that swap strands every future PR against a context that cannot exist, which is how politeia's main became unmergeable.
WHY an empty commit: PR #20 was created with zero workflow runs -- not held at action_required, never created at all -- and a close/reopen raised nothing either. A push from an authenticated user is the trigger path that does not depend on how the PR was opened.
Correction — merge-order conflict with #16Verified against a fresh clone of #16 only floats this file's #20 is the complete structural fix (it also carries the required-check swap this repo needs, already operator-approved per the WARNING above), so the clean order is: land #20 first, then rebase #16 to drop its now-superseded Left uncommented on #16 pointing back here. Not actioned directly — I don't hold merge authority on this repo's lane and won't push to another open PR's branch without the |
…a startup_failure The 2026-08-26 retrigger commit landed during the Actions billing outage; the run died with startup_failure before gate / gate could report. Billing is restored, so merge main and let the hybrid gate run for real.
Main replaced the unsatisfiable trailer-only gate with the fleet hybrid gate and branch protection now requires gate / gate. Merging main so this PR can report the new required context.
… gate (#20) Main replaced the unsatisfiable trailer-only gate with the fleet hybrid gate and branch protection now requires gate / gate. Merging main so this PR can report the new required context.
Finding
pinax's required gate is the org-default trailer-only reusable: it verifies a
Gate-Passedtrailer and nothing else. That trailer is minted by a full local gate, which needs a build box the
fleet does not currently declare — so a PR without one can never produce a passing required
check. The gate is unsatisfiable, not strict.
Evidence
required_status_checks.contextsonmainnamescall / gate-attestation, produced by theprior file's
jobs: call: uses: …/gate-attestation.yml..github/workflows/ci.ymlalready carries the diagnosis: "gate-attestation.yml hereis still the org-default trailer-only reusable … unlike sphragis and heurema, which moved to
forkwright/.github's hybrid-gate.yml (kanon#2522) … Until pinax makes that same move, this
workflow … does not substitute for the Gate-Passed trailer branch protection requires."
This is that move.
exactly this reason.
Desired correction
Call
hybrid-gate.yml@main, matching koinon and sphragis. The trailer remains a fast path; atrailer-less PR falls through to a real
fmt/check/clippy/nextestbuild on a hosted runner,mirroring
ci.yml's existing jobs.Input choices, each verified rather than copied:
doctest_cmdset — nextest does not execute doctests;ci.yml's own NOTE says so.rust_toolchain: "stable"explicitly — hybrid-gate auto-detects from the caller'srust-toolchain.toml, and pinax has none (404), unlike koinon/sphragis which leave it emptybecause they do. Empty here would hand toolchain choice to whatever the action defaults to absent
any file. Separate follow-up: add a
rust-toolchain.toml(MSRV1.94is already declared) sothis can drop back to empty and the pin lives in one place.
needs_fleet_repo_token: false— every workspace dependency is a crates.io crate(
compact_str,proptest,snafu,xxhash-rust,tempfile) and every cross-crate edge is alocal
{ path = … }pin, verified across all four manifests. No forkwright git dependency.concurrency:block —hybrid-gate.ymldeclares${{ github.workflow }}-${{ github.ref }}itself and its comment states callers must not repeatthat key or the shared group self-cancels. The prior file set exactly that key; dropped
deliberately, not carried forward.
@main, not a SHA —forkwright/.githubpublishes no tags, so a SHA pin has no dependabotbump path and simply freezes. First-party, same trust boundary; not the third-party
supply-chain case a pin defends against.
Done when:
gate / gatereports green here and pinax#19 can pass without a hand-minted trailer.WARNING — branch protection changes with this, deliberately
The job id is
gate, notcall. Branch protection pins required contexts by name, socall / gate-attestationcan never report again once this merges, andgate / gatehas neverreported before it.
required_status_checksis being swapped as part of this change — addgate / gate, removecall / gate-attestation— after this PR's owngate / gatereports green and before itmerges. Not earlier (adding a context that cannot yet report strands every open PR) and not later
(leaving the old name strands every PR the moment this merges).
Renaming a gate job without that swap is exactly how politeia's
mainbecame unmergeable. Operatorapproved the protection change explicitly.