Skip to content

ci(guard): fail PRs that roll a vendor/* submodule pointer backwards (B44 prevention) - #5133

Closed
graycyrus wants to merge 1 commit into
tinyhumansai:mainfrom
graycyrus:ci/vendor-gitlink-guard
Closed

ci(guard): fail PRs that roll a vendor/* submodule pointer backwards (B44 prevention)#5133
graycyrus wants to merge 1 commit into
tinyhumansai:mainfrom
graycyrus:ci/vendor-gitlink-guard

Conversation

@graycyrus

Copy link
Copy Markdown
Contributor

Summary

  • Adds a Vendor Gitlink Guard CI lane that fails any PR which moves a vendor/* git-submodule pointer backwards.
  • Prevention half of B44 (see my_docs/flows_workflow_bugs.md): stale-tree submodule-pointer drift that repeatedly broke main. The compile break itself was already fixed in fix(flows): repair main compile break from crossed PRs #5128 — this PR stops it recurring silently.
  • Guard is a small Node script + one blocking job wired into the existing PR CI Gate; no Rust/product code changes.

Problem

The superproject records each vendor/* submodule as a gitlink SHA. A PR branched off an old main — from before a dependency bump advanced a gitlink — carries the stale SHA in its tree. On merge that stale tree rolls the gitlink backwards while the new Cargo.toml version requirement stays put → an unsatisfiable version skew → main stops compiling and every open flows PR goes red.

Concretely (B44): PR #5114 rolled vendor/tinyagents back from a 2.1.0-compatible commit (2583fcc) to a 1.9.0 commit (19dc2c4) while Cargo.toml still required tinyagents = "2.1" → the SqliteCheckpointer: Checkpointer trait mismatch → broken build. It recurred when re-merging main into two PRs the same day. Nothing in CI flagged the backward roll.

Solution

How the guard detects a backward move. Per vendor/* submodule it reads the gitlink SHA recorded on the PR head (git rev-parse HEAD:<path>) and on the base branch (git rev-parse origin/<base>:<path>), then classifies:

  • equal → pass; base is an ancestor of head (forward move) → pass;
  • head is an ancestor of base (backward) → fail;
  • neither ancestor (diverged, head not descending from base) → fail;
  • new submodule (absent on base) → pass.

How it gets ancestry in CI. merge-base --is-ancestor needs both commits in the submodule's object store. On a backward roll the base commit is a descendant of the checked-out head, so it isn't present — the script runs git submodule update --init for each vendor/* submodule (per-vendor, so the large tauri-cef fork is never cloned) and then git -C vendor/<name> fetch origin (plus a targeted per-SHA fetch fallback) to bring the base commit in. If ancestry still can't be resolved it fails closed — a guard that shrugs is worse than none.

Failure output names the submodule, both SHAs, whether it's a backward vs diverged move, and the exact remediation: git checkout origin/main -- vendor/<name> && git commit --amend + git submodule update --init --recursive.

Wiring. New vendor-gitlink-guard job in ci-lite.yml (runs on PRs to main/release), added to pr-ci-gate.needs and its results map so the existing required PR CI Gate check blocks the merge — mirroring the feature-forwarding-gate pattern. Not filtered on changes: a stale tree can roll a gitlink without touching any watched path, and a skipped job counts as a pass.

Verification (local, before push):

  • --self-test truth table pins all five classifier branches (also runs as a CI step).
  • Simulated the real B44 roll (tinyagents 2583fcc → 19dc2c4) through the full git-ancestry path → guard exits 1 with the backward-move message.
  • This PR moves no vendor pointer, so the guard passes on itself; git diff origin/main -- vendor/ is clean.

Submission Checklist

If a section does not apply to this change, mark the item as N/A with a one-line reason. Do not delete items.

  • Tests added or updated (happy path + at least one failure / edge case) — --self-test truth table covers pass (equal/forward/new) and fail (backward/diverged); runs as a CI step
  • Diff coverage ≥ 80%N/A: CI-config + a Node CI script only; no Vitest/cargo-llvm-cov instrumented source changed (no lcov-covered lines)
  • Coverage matrix updated — N/A: CI-infrastructure change, no user-facing feature row
  • All affected feature IDs from the matrix are listed under ## RelatedN/A: no matrix feature affected
  • No new external network dependencies introduced — guard only fetches the existing vendor/* submodule origins already fetched by other CI lanes
  • Manual smoke checklist updated if this touches release-cut surfaces — N/A: does not touch release-cut runtime surfaces
  • Linked issue closed via Closes #NNNN/A: tracked in my_docs/flows_workflow_bugs.md (B44), no GitHub issue

Impact

  • CI-only. No runtime/product behavior change on any platform. Adds one fast (~git-fetch-bound) PR lane.
  • Prevents a recurring main-breaking class of failure (backward vendor submodule rolls).

Related


AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: ci/vendor-gitlink-guard
  • Commit SHA: cd38b529c8f09a2b48219608ce809d02c47a132c

Validation Run

  • pnpm --filter openhuman-app format:check — N/A: no app/src files changed
  • pnpm typecheck — N/A: no TypeScript changed
  • Focused tests: node scripts/ci/check-vendor-gitlink.mjs --self-test (all pass) + simulated B44 backward roll → guard fails as expected
  • Rust fmt/check (if changed): N/A — no Rust changed
  • Tauri fmt/check (if changed): N/A — no Rust changed

Validation Blocked

  • command: N/A
  • error: N/A
  • impact: N/A

Behavior Changes

  • Intended behavior change: PRs that roll a vendor/* submodule pointer backwards now fail CI.
  • User-visible effect: contributor-facing CI failure with exact remediation; no product change.

Parity Contract

  • Legacy behavior preserved: existing lanes unchanged; guard is additive and skips (passes) on non-PR events.
  • Guard/fallback/dispatch parity checks: fails closed when ancestry can't be resolved; new/removed submodules handled explicitly.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): none
  • Canonical PR: this
  • Resolution: N/A

…(B44 prevention)

Add a Vendor Gitlink Guard CI lane that fails any PR moving a vendor/*
git-submodule gitlink BACKWARDS (head SHA is an ancestor of the base SHA,
or the two diverge with head not descending from base). Forward moves and
unchanged pointers pass.

This is the prevention half of B44: PR tinyhumansai#5114 branched off a stale main and
rolled vendor/tinyagents 2.1.0 -> 1.9.0 while Cargo.toml still required
'tinyagents = "2.1"', an unsatisfiable skew that broke main's compile.
The compile break was fixed in tinyhumansai#5128; this guard stops it recurring silently.

- scripts/ci/check-vendor-gitlink.mjs: pure move classifier + git ancestry
  resolution (submodule update + origin fetch + merge-base --is-ancestor),
  crystal-clear failure message with exact remediation, and a --self-test.
- .github/workflows/ci-lite.yml: new blocking 'Vendor Gitlink Guard' job,
  wired into pr-ci-gate.needs and its results map.
- CONTRIBUTING.md: note to re-sync submodules after merging main.

See my_docs/flows_workflow_bugs.md (B44).
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e8eb0df2-ca5b-472b-8a52-25033d138176

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

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