Skip to content

fix: the upstream re-pin bypasses the pull-request gate #163

Closed

Description

@teemow

Problem

The fork line's upstream re-pin (#119) landed on the protected default branch as a force-push of its head (by a repository admin, the manual step FORK.md documented; the sync workflow's re-pin force-pushed the line the same way as the HeraldBot App). No required check ran on the commit that landed. Every other change to the default branch goes through a gated pull-request merge; the upstream sync is the exception, and it is the change most likely to break the line.

Root cause

A re-pin rewrites the line: the old line head is not an ancestor of the re-pinned one, so no pull-request merge method can land it (a rebase merge replays upstream's commits onto the old line and conflicts at the second one). The flow therefore skipped the gate entirely instead of gating the push.

Proposed solution

  1. The sync pushes the re-pin candidate to sync/<date>-<pin>. That push runs the line's required checks (run-tests, govulncheck) on its head. It opens a pull request against the line for review. A separate land dispatch pushes exactly that checked head to the line, onto the line head it was replayed from, and closes the pull request. No merge, no devctl pr merge for a re-pin. The same flow serves a maintenance line (release-1.3). FORK.md describes it.
  2. The line's rulesets (declared in giantswarm/github) refuse a push to the default branch whose head has not passed the required checks, with no bypass for the sync's actor or any person. Only the sync's App may replace the line (a non-fast-forward update), and only within that check gate.

Acceptance criteria

  • A push to giantswarm whose head has not passed run-tests and govulncheck is refused, for every actor.
  • The sync's re-pin candidate lands only as the exact SHA those checks passed on, through the workflow's land.
  • FORK.md's description of the sync flow matches.

Written by an agent.

Activity

  1. teemow commented on Oct 7, 2026

    @teemow
    MemberAuthor

    Step 1 (the sync flow) is in #185: a re-pin pushes its candidate to sync/<date>-<pin>, whose push runs run-tests and govulncheck on its head, and opens a pull request against the line. The workflow's land dispatch pushes exactly that checked head to the line, onto the line head the candidate was replayed from, and closes the pull request. No merge method lands a re-pin. The same flow serves release-1.3 (line input). FORK.md describes it. #185 also fixes the pin lookup, which skipped prerelease tags and so failed on the v0.3.0-alpha3 pin.

    Dry run of the new flow from #185's head (c4ef8980): https://github.com/giantswarm/substrate/actions/runs/37688180396, sync green in 4m56s, land skipped, nothing pushed (no sync/20261007-* branch exists). Re-pin onto 228790e9 (the v0.3.0-alpha3 commit) resolved the current pin v0.3.0-alpha3, rebased the carried patches, and passed go build ./... && go test ./.... Its summary shows the pull request it would open:

    • candidate sync/20261007-228790e92b59f0c3b3ea2e01a38fbecd92bc4d14 at e8e656ca, against giantswarm, title "sync: re-pin giantswarm onto 228790e…"
    • body: the replayed patches, the land instruction (land = the candidate, line = giantswarm) and Line head: e8e656ca29a475eb5b6ee5b0d70e3845a1ec6474.

    Step 2 is still open: the rulesets that refuse a push to giantswarm whose head has not passed the required checks.

    Written by an agent.

  2. teemow commented on Oct 7, 2026

    @teemow
    MemberAuthor

    Step 1 landed: #185 rebase-merged into giantswarm at 89c5830c through the gated merge (release v1.5.0-rc.7 cut by the auto-release). The next re-pin opens a candidate pull request and lands with the workflow's land. Step 2 (the rulesets) is open.

    Written by an agent.

  3. teemow commented on Oct 7, 2026

    @teemow
    MemberAuthor

    Step 2, the rulesets: in progress.

    • Found: the default branch's engine ruleset (devctl: default branch) has no bypass for the sync's App and requires the PR-only title check, so the land push is refused as configured; a bypass there would also skip the line's checks. release-1.3 carries no ruleset at all: any writer can push any commit to it.
    • Design: the repository's declaration gains lineGate (feat(repo reconcile): lineGate gates a fork line's land push devctl#2559, released in devctl v8.120.0). The sync's App joins devctl: default branch as an always bypass actor, so its land passes the pull-request and non-fast-forward rules. A second engine ruleset, devctl: line gate, on giantswarm and release-1.3 requires run-tests and govulncheck (pinned to GitHub Actions) for every push, forbids deletion and has no bypass actor, so no actor (the App, admins, the merge App) lands a commit those checks did not pass on. Pull requests merge as before.
    • Next: the reconciler release carrying devctl v8.120.0, then the declaration. The proof follows here: the rulesets read back and a refused push of an unchecked commit to release-1.3.

    Written by an agent.

  4. teemow commented on Oct 8, 2026

    @teemow
    MemberAuthor

    Step 2, the rulesets: done, declared in the team file and applied by the reconciler (devctl v8.120.0, lineGate).

    Read back from the API after the reconciler's run:

    • devctl: default branch (~DEFAULT_BRANCH): deletion, pull_request, non_fast_forward, semantic-pull-request / Validate PR title; bypass actors: repository admins, the merge App and the owning team for pull requests only, and the sync's App (Integration 414149) always. Its land push passes the pull-request and non-fast-forward rules.
    • devctl: line gate (~DEFAULT_BRANCH, refs/heads/release-1.3): deletion, required checks run-tests and govulncheck, both pinned to GitHub Actions (15368); no bypass actors. release-1.3 now carries deletion and required_status_checks (it carried no rule before).

    Refusal proof: an empty commit on top of release-1.3, pushed by a repository admin:

    remote: error: GH013: Repository rule violations found for refs/heads/release-1.3.
    remote: - 2 of 2 required status checks are expected.
     ! [remote rejected] HEAD -> release-1.3 (push declined due to repository rule violations)
    

    release-1.3 stayed at a4c27712. The same ruleset guards giantswarm, with no bypass actor, so no actor lands an unchecked commit on either line.

    Admission: the line's land pushes a commit whose run-tests and govulncheck passed on its candidate branch, so it meets the gate, and the App's bypass covers the rest. No candidate of the new flow exists yet: a land dry run on sync/20260924-v0.2.0-beta5 stops at the workflow's own check (no open pull request of that branch; it predates the new flow). The next re-pin's land is the first live admission.

    Acceptance criteria: an unchecked push is refused for every actor (above); the candidate lands only as the checked SHA, through land (step 1); FORK.md describes the flow (step 1). Closing.

    Written by an agent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    • Status
      Done ✅

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions