Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 36 additions & 21 deletions ci/sections.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1147,18 +1147,34 @@ jobs:
# never as "no growth", so the mistake would have been loud rather than
# silent. It is still a mistake. One fetch, before all of them.
#
# fetch-depth is 1 here, so the merge-base is usually unavailable and the
# ratchets fall back to the origin/main TIP. That is the STRICTER
# comparand, and it means the CI path is the tip path: a local run that
# passes on the merge-base can still red here, and CI is authoritative.
# #4861 + #4983: origin/main is pinned to the commit this tree was BUILT
# on, not fetched at its tip. fetch-depth is 1 here, so the merge-base is
# usually unavailable and the ratchets read origin/main as it stands.
# Fetched at the tip, a run that started after main shrank a baseline or
# fixed a function judged a merge commit built before that change against
# it: #4813 went red (pipe_grep_q_baseline.txt 62 vs 60, a file it never
# touched), and #4970's re-run went red on m17 complexity (checkout at
# 60a59031, compared against acd599e52), while a re-run rebuilds the SAME
# merge commit. Pinned per event (scripts/ci/pin_build_base.sh):
# pull_request the merge commit's first parent, checked against the PR head
# merge_group merge_group.base_sha, as the refinement-gate pin does
# otherwise the tip, deepened by one on push (resolve_base.sh judges a
# push against HEAD's first parent: HEAD vs HEAD would pass
# vacuously, the G-10 quorum's finding)
# The shared ratchet library takes the same first parent on its own
# (scripts/lib_baseline_ratchet.sh FIRSTPARENT), so a lib ratchet and a
# script that reads origin/main directly cannot disagree. A PR that adds
# back an entry main has since deleted is still refused by its
# merge_group run, which builds on main as it merges. The pin refuses,
# and every guard below is skipped red, when the event names no build base.
- id: guard-setup
name: Fetch origin/main as the comparand for every ratchet in this job
name: Pin origin/main to this run's build base, the comparand for every ratchet in this job (#4861)
env:
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
MG_BASE_SHA: ${{ github.event.merge_group.base_sha }}
run: |
git fetch --no-tags --depth=1 origin +refs/heads/main:refs/remotes/origin/main
# push shape: HEAD IS the origin/main tip, so every differential ratchet judges it against
# its FIRST PARENT (scripts/lib/resolve_base.sh: HEAD vs HEAD would pass vacuously, the
# G-10 quorum's finding), and a depth-1 checkout has not fetched that parent. Deepen by one.
if [ "${GITHUB_EVENT_NAME:-}" = push ]; then git fetch --no-tags --deepen=1 origin +refs/heads/main:refs/remotes/origin/main; fi
bash scripts/ci/pin_build_base.sh --self-test
bash scripts/ci/pin_build_base.sh
# #4415 (operator A4, 2026-09-25): ONE step runs every guard step of guard-tree-steps
# below, keeps going past a red one, and reports every failure. GitHub would
# stop this job at its first red step, and the others would never be seen.
Expand Down Expand Up @@ -1685,17 +1701,16 @@ jobs:
# never as "no growth", so the mistake would have been loud rather than
# silent. It is still a mistake. One fetch, before all of them.
#
# fetch-depth is 1 here, so the merge-base is usually unavailable and the
# ratchets fall back to the origin/main TIP. That is the STRICTER
# comparand, and it means the CI path is the tip path: a local run that
# passes on the merge-base can still red here, and CI is authoritative.
- name: Fetch origin/main as the comparand for every ratchet in this job
run: |
git fetch --no-tags --depth=1 origin +refs/heads/main:refs/remotes/origin/main
# push shape: HEAD IS the origin/main tip, so every differential ratchet judges it against
# its FIRST PARENT (scripts/lib/resolve_base.sh: HEAD vs HEAD would pass vacuously, the
# G-10 quorum's finding), and a depth-1 checkout has not fetched that parent. Deepen by one.
if [ "${GITHUB_EVENT_NAME:-}" = push ]; then git fetch --no-tags --deepen=1 origin +refs/heads/main:refs/remotes/origin/main; fi
# #4861 + #4983: origin/main is pinned to the commit this tree was BUILT
# on, not fetched at its tip -- the same pin, for the same reason, as
# guard-tree's guard-setup (which also runs the pin's self-test; this job
# only pins). m17's complexity ratchet is the #4983 case: a re-run of an
# unchanged PR judged against main's moved tip went red.
- name: Pin origin/main to this run's build base, the comparand for every ratchet in this job (#4861)
env:
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
MG_BASE_SHA: ${{ github.event.merge_group.base_sha }}
run: bash scripts/ci/pin_build_base.sh
# DOCKER CREATES A MISSING BIND SOURCE AS root:root, AND `--user` ALONE
# THEN EACCESs ON IT. The per-RUN target path is fresh every run, so a
# container that mounts it and drops to the runner's uid cannot write
Expand Down
101 changes: 101 additions & 0 deletions scripts/check_baseline_ratchets.sh
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,107 @@ if [ "${1:-}" = "--self-test" ] || [ "${1:-}" = "--selftest" ]; then
bad=1
fi

# FIRSTPARENT (#4983): a pull_request re-run. CI checks out GitHub's
# merge commit at depth 1, so there is no merge-base, and the re-run
# fetches origin/main fresh. Here main moved on and SHRANK the
# baseline (b deleted). Against the tip, the PR "appends" b, an
# entry it never touched; against the first parent, it changed
# nothing. The shallow clone is its own repository under $TD.
SC="$TD/shallow"
if git -C "$SR" checkout -q -b fp-pr "$SR_BASE" >/dev/null 2>&1 \
&& printf 'pr edit\n' > "$SR/scripts/unrelated.txt" \
&& git -C "$SR" -c commit.gpgsign=false commit -qam 'pr: unrelated edit' >/dev/null 2>&1 \
&& SR_X=$(git -C "$SR" rev-parse HEAD) \
&& git -C "$SR" checkout -q --detach "$SR_BASE" >/dev/null 2>&1 \
&& git -C "$SR" -c commit.gpgsign=false merge -q --no-ff --no-edit "$SR_X" >/dev/null 2>&1 \
&& SR_M=$(git -C "$SR" rev-parse HEAD) \
&& git -C "$SR" update-ref refs/pull/1/merge "$SR_M" \
&& git -C "$SR" checkout -q --detach "$SR_NOBASE" >/dev/null 2>&1 \
&& git -C "$SR" -c commit.gpgsign=false merge -q --no-ff --no-edit "$SR_BASE" >/dev/null 2>&1 \
&& SR_M2=$(git -C "$SR" rev-parse HEAD) \
&& git -C "$SR" update-ref refs/pull/2/merge "$SR_M2" \
&& git -C "$SR" checkout -q --detach "$SR_BASE" >/dev/null 2>&1 \
&& printf '# header\na\n' > "$SR/$P" \
&& git -C "$SR" -c commit.gpgsign=false commit -qam 'main: shrink the baseline' >/dev/null 2>&1 \
&& git -C "$SR" update-ref refs/heads/fp-main "$(git -C "$SR" rev-parse HEAD)" \
&& git -C "$SR" checkout -q --detach "$SR_BASE" >/dev/null 2>&1 \
&& git -C "$SR" config uploadpack.allowAnySHA1InWant true \
&& git init -q "$SC" >/dev/null 2>&1 \
&& [ "$(git -C "$SC" rev-parse --absolute-git-dir 2>/dev/null)" = "$SC/.git" ] \
&& git -C "$SC" fetch -q --no-tags --depth=1 "file://$SR" '+refs/pull/1/merge:refs/remotes/pull/1/merge' >/dev/null 2>&1 \
&& git -C "$SC" checkout -q --detach refs/remotes/pull/1/merge >/dev/null 2>&1 \
&& git -C "$SC" fetch -q --no-tags --depth=1 "file://$SR" '+refs/heads/fp-main:refs/remotes/origin/main' >/dev/null 2>&1 \
&& [ -z "$(git -C "$SC" merge-base HEAD origin/main 2>/dev/null)" ] \
&& ! git -C "$SC" cat-file -e "${SR_BASE}^{commit}" 2>/dev/null; then

fp_row() { # fp_row <label> <want-mode> <want-ref|-> <GITHUB_EVENT_NAME> <GITHUB_SHA>
local got ref
got=$(GITHUB_EVENT_NAME="$4" GITHUB_SHA="$5" baseline_ratchet_resolve "$SC" origin/main "$P")
ref=${got##*$'\t'}
got=${got%%$'\t'*}
rows=$((rows + 1))
if [ "$got" != "$2" ] || { [ "$3" != - ] && [ "$ref" != "$3" ]; }; then
printf 'FAIL %-46s want %s %s got %s %s\n' "$1" "$2" "$3" "$got" "$ref"
bad=1
fi
}
fp_e2e() { # fp_e2e <label> <want-rc> <GITHUB_EVENT_NAME> <GITHUB_SHA>
local got
( GITHUB_EVENT_NAME="$3" GITHUB_SHA="$4" BASELINE_RATCHET_BASE_REF=origin/main \
baseline_ratchet_check "$SC" "$P" set ) >/dev/null 2>&1
got=$?
rows=$((rows + 1))
if [ "$got" != "$2" ]; then
printf 'FAIL %-46s want rc=%s got rc=%s\n' "$1" "$2" "$got"
bad=1
fi
}
# Controls: the tip path is unchanged off the event, and it
# carries the false red this fix removes.
fp_row 'firstparent: push event keeps TIP' TIP origin/main push "$SR_M"
fp_e2e 'firstparent: tip blames PR for main shrink' 1 push "$SR_M"
# A head that is not the event's own merge commit is never read.
fp_row 'firstparent: HEAD != GITHUB_SHA keeps TIP' TIP origin/main pull_request "$SR_X"
# Fail-closed: the parent not fetched is UNRESOLVABLE, never TIP.
fp_row 'firstparent: parent unfetched -> UNRESOLVABLE' UNRESOLVABLE "$SR_BASE" pull_request "$SR_M"
fp_e2e 'firstparent: parent unfetched -> rc 1' 1 pull_request "$SR_M"
if git -C "$SC" fetch -q --no-tags --depth=1 "file://$SR" "$SR_BASE" >/dev/null 2>&1 \
&& [ -z "$(git -C "$SC" merge-base HEAD origin/main 2>/dev/null)" ]; then
fp_row 'firstparent: parent fetched -> FIRSTPARENT' FIRSTPARENT "$SR_BASE" pull_request "$SR_M"
fp_e2e 'firstparent: re-run after main moved' 0 pull_request "$SR_M"
else
printf 'FAIL firstparent: the first parent could not be fetched, or it made a\n'
printf ' merge-base resolve. FIRSTPARENT is UNTESTED. Not a skip.\n'
bad=1
fi
# A single-parent head (a merge_group squash) has no first
# parent to trust: it keeps TIP.
if git -C "$SC" fetch -q --no-tags --depth=1 "file://$SR" "$SR_X" >/dev/null 2>&1 \
&& git -C "$SC" checkout -q --detach "$SR_X" >/dev/null 2>&1; then
fp_row 'firstparent: single-parent head keeps TIP' TIP origin/main pull_request "$SR_X"
else
printf 'FAIL firstparent: the single-parent head could not be checked out. UNTESTED.\n'
bad=1
fi
# The base the PR was merged onto has no baseline: the PR
# introduces it. The tip, which has one, is NOT consulted.
if git -C "$SC" fetch -q --no-tags --depth=1 "file://$SR" '+refs/pull/2/merge:refs/remotes/pull/2/merge' "$SR_NOBASE" >/dev/null 2>&1 \
&& git -C "$SC" checkout -q --detach refs/remotes/pull/2/merge >/dev/null 2>&1; then
fp_row 'firstparent: parent lacks file -> BOOTSTRAP' BOOTSTRAP "$SR_NOBASE" pull_request "$SR_M2"
mv "$SC/$P" "$SC/$P.hidden"
fp_row 'firstparent: not in tree either -> ABSENT' ABSENT "$SR_NOBASE" pull_request "$SR_M2"
mv "$SC/$P.hidden" "$SC/$P"
else
printf 'FAIL firstparent: the second merge commit could not be checked out. UNTESTED.\n'
bad=1
fi
else
printf 'FAIL resolve FIRSTPARENT UNTESTED — the shallow re-run clone could\n'
printf ' not be built, and a pull_request re-run is that shape. Not a skip.\n'
bad=1
fi
git -C "$SR" checkout -q --detach "$SR_BASE" >/dev/null 2>&1

# END TO END, through the public entry point, on a real repository:
# append -> RED, delete -> GREEN, unchanged -> GREEN.
e2e_row() { # e2e_row <label> <want-rc> <content>
Expand Down
4 changes: 2 additions & 2 deletions scripts/check_census_derived.sh
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,10 @@ touch_check() { # touch_check ROOT BRANCH -> 0 not touched (or train); 1 touched
res=$(baseline_ratchet_resolve "$root" "$BASELINE_RATCHET_BASE_REF" "$CENSUS_PATH")
mode=${res%%$'\t'*}; ref=${res##*$'\t'}
case "$mode" in
MERGEBASE | TIP) ;;
MERGEBASE | FIRSTPARENT | TIP) ;;
*)
printf 'FAIL comparand %s resolved %s for %s: whether this branch edits the census is UNMEASURED, and that is not "untouched".\n' "$ref" "$mode" "$CENSUS_PATH"
printf ' In CI: git fetch --no-tags --depth=1 origin +refs/heads/main:refs/remotes/origin/main\n'
printf ' In CI: %s\n' "$(baseline_ratchet_fetch_hint "$ref")"
return 1 ;;
esac
# Committed changes AND the working tree, so a local run sees an unstaged edit too.
Expand Down
2 changes: 1 addition & 1 deletion scripts/check_complexity_ratchet.sh
Original file line number Diff line number Diff line change
Expand Up @@ -675,7 +675,7 @@ REF=${RESOLUTION##*$'\t'}
case "$MODE" in
UNRESOLVABLE)
printf 'FAIL PREFLIGHT: cannot resolve the comparand ref <%s>, so complexity is UNMEASURED against anything. That is not "no growth", and it is not degraded to comparing this branch against itself.\n' "$REF" >&2
printf ' In CI, before this guard runs: git fetch --no-tags --depth=1 origin +refs/heads/main:refs/remotes/origin/main\n' >&2
printf ' In CI, before this guard runs: %s\n' "$(baseline_ratchet_fetch_hint "$REF")" >&2
exit 1 ;;
ABSENT | BOOTSTRAP)
printf 'FAIL PREFLIGHT: <%s> carries no %s/ tree, so there is no comparand to measure. A missing comparand is not "no growth".\n' "$REF" "$CX_COMPARAND_PATH" >&2
Expand Down
Loading
Loading