Skip to content

fix(gate): stop a stale trailer passing the gate on a push - #54

Merged
forkwright merged 1 commit into
mainfrom
fix/gate-event-shape-integrity
Aug 25, 2026
Merged

fix(gate): stop a stale trailer passing the gate on a push#54
forkwright merged 1 commit into
mainfrom
fix/gate-event-shape-integrity

Conversation

@forkwright

Copy link
Copy Markdown
Owner

Finding

A push whose tip carried a Gate-Passed trailer from an earlier commit reported the gate GREEN even when full-gate-build had failed or been skipped on that push. The trailer was accepted as evidence about work it did not describe — which happens routinely through a rebase or a squash-merge.

A gate that can be satisfied by a stale artifact is worse than no gate, because it is trusted.

Evidence — measured, not argued

This repo already ships a checker for exactly this. Run against current main:

check_gate_evaluation.py → 2 of 10 cases FAIL

With this change:

check_gate_evaluation.py → 10/10 cases behaved as specified. EXIT=0
check_event_shape_guards.py → OK: 16 pull_request-only values examined, none reaches git unguarded. EXIT=0
actionlint 1.7.7 (CI's pinned version) → clean, exit 0 across all workflow files

What changes

  • full-gate-build now runs on a push regardless of trailer, and the verdict step no longer accepts a trailer as a substitute for a build result on that event.
  • gate-attestation.yml carried a second instance of the same class: PR_HEAD_SHA reaches git log with no push-event fallback and dies with fatal: ambiguous argument ''. It now falls back to github.sha.
  • Both checkers are wired into actionlint so the class cannot return silently, and scripts/** joins the path filter so a change to either checker triggers them.
  • release-please.yml's own job gains timeout-minutes: 10. It was the only regular job in this repo with no bound, inheriting GitHub's 360-minute ceiling — and a caller cannot supply one, since timeout-minutes is invalid on a job that is a bare uses: call. 10 matches the two jobs of the same cost class here.

Two hunks from the original branch are deliberately not carried forward — both are already on main via #26 and #34.

Blast radius — read this before it lands

hybrid-gate.yml floats at @main in at least six repos (zetesis, harmonia, koinon, theatron, gnomon, logismos), through their local gate-attestation.yml wrapper. This reaches them on their very next default-branch push, with no per-repo review or bump PR in between.

This is a correctness tightening, not new behaviour. If your default branch shows a new red check the push after this merges, that is this fix surfacing a gap that already existed — the build result was already wrong; previously the gate's verdict was wrong too, in the same direction. It is not a regression in your repo.

The gate-attestation.yml fallback is lower-risk by contrast: every consumer found (mneme, pinax, and the wrapper repos above) pins it by SHA with a comment rather than @main, so that half reaches nobody until their own pin-bump merges.

Not verified

Whether any consumer's dependabot auto-bumps the gate-attestation.yml SHA pin — inferred from pin style, not from reading each config. The six-repo @main count is a verified floor, not an exhaustive total. And actionlint type-checks the new if: expression's syntax but never evaluates it; the executable proof of the logic is the checker's 10/10.

Closes the live half of #41.

A push whose tip carried a Gate-Passed trailer from an earlier commit -- through
a rebase or a squash-merge -- reported the gate GREEN even when full-gate-build
had failed or been skipped on that push. The trailer was accepted as evidence
about work it did not describe.
Verified live rather than reasoned about: the repo's own check_gate_evaluation.py
run against current main fails 2 of its 10 cases. With this change it passes
10 of 10.
full-gate-build now runs on a push regardless of trailer, and the verdict step no
longer accepts a trailer as a substitute for a build result on that event.
gate-attestation.yml carried a second instance of the same class: PR_HEAD_SHA
reaches git log with no push-event fallback and dies with `fatal: ambiguous
argument ''`. It now falls back to github.sha.
Both checkers are wired into actionlint so the class cannot return silently, and
scripts/** is added to the path filter so a change to either one triggers them.
release-please.yml's own job gains a 10-minute timeout, matching the two jobs of
the same cost class in this repo. It was the only regular job here with no bound,
inheriting the 360-minute ceiling, and a caller cannot supply one -- timeout-
minutes is invalid on a job that is a bare `uses:` call.
Two hunks from the original branch are deliberately not carried: both are already
on main via #26 and #34.
@forkwright
forkwright merged commit 0a4d5c9 into mainAug 25, 2026
2 checks passed
@forkwright
forkwright deleted the fix/gate-event-shape-integrity branch August 25, 2026 17:12
Sign up for freeto 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.

1 participant

@forkwright