Skip to content

ci(openapi): require the pinned server commit to be reachable from server main - #61

Merged
tiankaima merged 1 commit into
mainfrom
chore/openapi-pin-reachability
Sep 20, 2026
Merged

tiankaima merged 1 commit into
mainfrom
chore/openapi-pin-reachability

Conversation

@tiankaima

@tiankaima tiankaima commented Sep 20, 2026

Copy link
Copy Markdown
Member

Problem

OpenAPI provenance accepted any fetchable server commit, including a pull-request head that never reached server main.

What changed

  • Added a reachability check requiring the pinned commit to be an ancestor of server main.
  • Made both source verification and synchronization use the same check.
  • Made CI fail closed when the checkout is shallow, main is unavailable, or the pinned commit is missing.

Result

The CLI cannot silently vendor a contract generated from an unmerged server branch.

`openapi-contract verify-source` only asserted that the server checkout
is *at* the pinned commit, and CI checks out exactly the pinned SHA, so
that assertion could never fail.

A pull-request head SHA stays fetchable from Life-USTC/server forever,
so the pin could point at a commit that was never on main while every
check passed: the provenance verified, the checkout matched, and the
vendored spec was byte-identical to the source.

This repository hit it. api/openapi.provenance.json was pinned at
fea7bb21ead65fa4da1d51d8ef36ef914a647783 ("fix(young): complete release
contracts", 2026-09-15) in #57, a pull-request head that is not an
ancestor of server main -- the server squash-merges, so PR heads never
land there. The generated client came from a discarded branch snapshot
and CI reported success until the contract was re-synced from 222eeb61.

Add `openapi-contract verify-reachable SERVER_DIR`, asserting
`git merge-base --is-ancestor <pin> <main>` against the server checkout.
`verify-source` and `sync` both call it, so CI rejects an unreachable
pin and the nightly sync refuses to write one.

Ancestry needs real history, so the server checkouts now use
`fetch-depth: 0` and fetch `refs/heads/main` explicitly. A shallow
checkout stays shallow even after fetching main, so the script refuses
to run there instead of guessing: a check that cannot be evaluated is
worse than no check. The same refusal covers a missing main ref and a
pin the checkout does not contain.

scripts/openapi-contract.test.sh builds synthetic server histories and
proves the check rejects an unmerged branch head -- reproducing the
incident where HEAD and the vendored spec both matched the pin -- and
accepts the main tip and older main commits. `make test` runs it before
the Go suite.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@tiankaima
tiankaima merged commit 3ca205d into main Sep 20, 2026
4 checks passed
@tiankaima
tiankaima deleted the chore/openapi-pin-reachability branch September 20, 2026 04:39
Sign up for free to 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