Repository navigation
fix(ci): read the contract gate's base from the PR, not the event - #2660
Merged
Merged
Conversation
For an upper layer of a native stack, `edited` events report the stack's base (`main`) as `github.base_ref`, so the contract gate diffed the layer against `main` and blamed it for its parent's contract-doc edit (#2644). The gate now reads the PR's declared base from `pulls/N` with the read-only token on every trigger and diffs against that remote-tracking ref. A missing PR number, a refused or failed lookup, a foreign or invalid base, or an unfetched base fails the gate (exit 2) instead of falling back to `main`. The event's value is logged only to show a disagreement. The workflow lint refuses a contract step that takes its base from event context, and a workflow-level harness executes the installed step against a loopback PR lookup for every trigger, stacked, ordinary and failure cases. Closes #2650 Refs #2141 Co-Authored-By: Claude Opus 5.5 <[email protected]>
This was referenced Oct 22, 2026
schlessera
marked this pull request as ready for review
October 22, 2026 14:54
5 of 8 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2650
Refs #2141
What changed
The contract gate now compares a PR with the base the PR itself declares, read from
GET /repos/{repo}/pulls/{number}with the workflow's read-only token on every trigger, instead ofgithub.base_ref. Before this, an upper layer of a native stack passed onopenedbut failed on everyedited/labeledrun. Those events reportedmain, so the inherited contract-doc edit from the parent layer was charged to the upper layer (#2644, runs 39777521171 and 39779073432). Now a missing PR number fails the gate with exit 2 and a named diagnostic, and so do a refused or failed lookup (5xx and network errors are retried twice), a response for another PR, a base in another repository, an invalid branch name, or a base the checkout has not fetched. It never falls back tomain. The event's value is logged next to the resolved base, so a disagreement shows in the run log.This PR also delivers #2141's criterion that "contract comparison for an upper native layer uses its own parent", with inherited and own-contract controls. Per the maintainer ruling of 2026-10-22, the rest of #2141 is parked rather than ported here: the frozen-prefix, synthetic-chain and ORT-tree candidate confirmation, and native
integrate.tssupport. The old PRs #2187, #2285 and #2286 are closed with a pointer here.Why this shape
github.base_refholds for upper layers, and on test: measure first-party egress paths and route Git over SSH through the turn proxy #2644 it changed betweenopenedandedited. The PR object is the authority the stack rules name ("the PR's declared base").ci.ymlwas not changed. Its draft runs on test: measure first-party egress paths and route Git over SSH through the turn proxy #2644 got the parent SHA (converted_to_draftrun 39784882558:BASE_SHA757c0d9, the parent head), and it does not trigger onedited.contents: readstays exact, and the workflow lint still enforces it.ci.ymlalready readspulls/Nwith the same token for its draft-gate readiness check.check-contract-pr.ts. It does not importproof-reuse.ts, whose import graph would needbun install.check-contract-pr.ts <base-ref> <head-sha>is unchanged.Proof
Receipts below; local logs are kept by the coordinator.
Failing-first. I ran the new workflow-level harness against the unchanged
contract.yml(BASE_REF: origin/${{ github.base_ref }}), with the event reportingmainfor a stacked upper layer whose parent changeddocs/integration-contract/http.md:That is the exact #2650 failure. The own-contract, ordinary and fail-closed tests also failed. Restored:
tests/ci-workflows.test.ts+tests/contract-gate.test.ts, 94 pass / 0 fail, 557 assertions.Workflow-level harness (
tests/ci-workflows.test.ts). It runs the installed step: env expressions are substituted from an event context, then the step runs throughbash -ein a fixture repo that hasorigin/mainandorigin/feat/1665-lower. The PR lookup is a loopback server, and the repository network guard is untouched. Cases:opened,edited,synchronize,reopened,labeled,unlabeled), whether the event namesmainor the parent;mainbehaves as before, in both directions;Mutations (each restored; named test fails on the expected assertion):
EVENT_BASE_REFagain: "every trigger of an upper layer…" fails with exit 1 and the inheritedhttp.mddiagnostic.origin/main: "an unreadable or unfetched declared base fails the gate instead of choosing main" fails,exit 2→1.Workflow lint.
bun scripts/check-ci-workflows.tspasses. The lint now refuses a contract step that gets its base fromgithub.base_reforpull_request.base, or that lacksPR_NUMBERorGH_TOKEN; the event value is allowed only asEVENT_BASE_REF.Result at head
91f2ce2ddon base2ba99c607: passed, 989 fast + 108 affected tests, 0 fail ("Local preflight passed").Hosted. This PR's own
contractrun 39798005870 passed at head91f2ce2ddand loggedContract base: origin/main, declared by PR #2660; the event agrees.That confirms thepulls/Nlookup works with thecontents: readtoken. Draft gates run 39798002286 passed. I did not record a run on a stacked fixture PR. The workflow-level harness covers that, as the issue's acceptance criteria allow.Checklist
CONTRIBUTING.md(contract gate) anddocs/process/github.md(stacked PRs).Anything left open
integrate.tslayers) is parked until serial confirmation returns.🤖 Generated with Claude Code