From 328c8707df72aa8c2b1b319501ccc7166737a6fc Mon Sep 17 00:00:00 2001 From: codex-bot-andresmgsl Date: Mon, 31 Aug 2026 21:04:41 +0000 Subject: [PATCH 1/5] test(labels): reproduce moving base-tip warning --- test/labels-reconcile.test.sh | 72 +++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/test/labels-reconcile.test.sh b/test/labels-reconcile.test.sh index 9dd791c..c90793f 100755 --- a/test/labels-reconcile.test.sh +++ b/test/labels-reconcile.test.sh @@ -175,6 +175,78 @@ expect "an unreadable head version is silent — never nag on a guess" "" \ expect "a bare head over an unreadable base still warns" yes \ "$(release_shape_warning 41 2.0.0 "" | grep -qF '::warning::' && echo yes || echo no)" +# The pure matrix above proves the warning predicate. These two fixtures drive +# the sweep boundary that chooses WHICH base tree feeds it (#275): Forgejo and +# GitHub both expose the PR's moving base tip beside its fixed merge base, and +# using the former manufactures a downgrade after an intervening release. +release_shape_ref_probe() { # $1 = phantom | bump + ( + REPO=owner/repo + LABELS_CONF="$FIXTURE_CONF" + CEREMONY_FORGE=github + mode="$1" + refs="$RTMP/release-shape-$mode-refs" + : >"$refs" + case "$mode" in + phantom) + head_ver=1.3.0 base_tip_ver=1.4.0 merge_base_ver=1.3.0 ;; + bump) + head_ver=1.4.0 base_tip_ver=1.3.0 merge_base_ver=1.3.0 ;; + *) return 2 ;; + esac + # shellcheck disable=SC2317 # reached through the GitHub backend selected above + gh() { + if [ "$1" = label ] && [ "$2" = list ]; then + core_label_rows | cut -d'|' -f1 + return 0 + fi + if [ "$1" = pr ] && [ "$2" = list ]; then + printf '701\n' + return 0 + fi + if [ "$1" = pr ] && [ "$2" = view ]; then + jq -n '{mergeable:"MERGEABLE",statusCheckRollup:[]}' + return 0 + fi + if [ "$1" = issue ] && [ "$2" = edit ]; then return 0; fi + case "$(forge_stub_path "$*")" in + *'repos/owner/repo/pulls/701 --jq .requested_reviewers'*) return 0 ;; + *repos/owner/repo/pulls/701/reviews*) return 0 ;; + *repos/owner/repo/pulls/701) + jq -n '{draft:false,user:{login:"fixture-builder"}, + head:{sha:"head"},base:{sha:"base-tip"},merge_base:"merge-base", + labels:[{name:"state:addressing"},{name:"blocked"}], + requested_reviewers:[],created_at:"2026-08-31T20:00:00Z"}' ;; + *repos/owner/repo/commits/head*) printf '2026-08-31T20:00:00Z\n' ;; + *repos/owner/repo/contents/VERSION\?ref=head*) + printf 'head\n' >>"$refs" + printf '%s' "$head_ver" | base64 ;; + *repos/owner/repo/contents/VERSION\?ref=base-tip*) + printf 'base-tip\n' >>"$refs" + printf '%s' "$base_tip_ver" | base64 ;; + *repos/owner/repo/contents/VERSION\?ref=merge-base*) + printf 'merge-base\n' >>"$refs" + printf '%s' "$merge_base_ver" | base64 ;; + *) printf '[]\n' ;; + esac + } + main + ) +} + +phantom_shape="$(release_shape_ref_probe phantom)" +expect "an unchanged branch cut before a later base release emits no release-shape warning" \ + no "$(grep -q 'release-shaped' <<<"$phantom_shape" && echo yes || echo no)" +expect "the unchanged-branch guard compares head with the PR merge base" \ + $'head\nmerge-base' "$(cat "$RTMP/release-shape-phantom-refs")" + +bump_shape="$(release_shape_ref_probe bump)" +expect "a genuine version bump relative to the merge base keeps the warning text" \ + yes "$(grep -qF '#701 is release-shaped (version 1.3.0 -> 1.4.0 at its head)' \ + <<<"$bump_shape" && echo yes || echo no)" +expect "the genuine-bump guard also compares head with the PR merge base" \ + $'head\nmerge-base' "$(cat "$RTMP/release-shape-bump-refs")" + # -- drafts are building, whoever is requested -------------------------------- DRAFT=true HEAD_SHA=head1 REQUESTED="" REVIEWS_JSON='[]' expect "draft PR is building" state:building "$(decide_state)" From dc29fdc84c30c65bcd42c76403191b3b75a129c4 Mon Sep 17 00:00:00 2001 From: codex-bot-andresmgsl Date: Mon, 31 Aug 2026 21:06:50 +0000 Subject: [PATCH 2/5] fix(labels): compare release shape to merge base --- actions/labels-reconcile/labels-reconcile.sh | 7 +++++-- changelog.d/275.md | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 changelog.d/275.md diff --git a/actions/labels-reconcile/labels-reconcile.sh b/actions/labels-reconcile/labels-reconcile.sh index ca11fa1..7b07a8b 100755 --- a/actions/labels-reconcile/labels-reconcile.sh +++ b/actions/labels-reconcile/labels-reconcile.sh @@ -270,7 +270,8 @@ set_required_bots() { # the PR author is recused by construction # The state machine. Pure functions over these globals, set per PR: # DRAFT true|false # HEAD_SHA the PR's current head commit -# BASE_SHA the PR's base branch head (the release-shape guard's ref) +# BASE_SHA the PR's base branch head +# MERGE_BASE_SHA the PR's merge base (the release-shape guard's ref) # REQUESTED newline-separated logins with a review currently requested # REVIEWS_JSON JSON array of submitted, gradeable reviews # MERGEABLE MERGEABLE | CONFLICTING | UNKNOWN (GitHub's own verdict) @@ -918,7 +919,8 @@ reconcile_pr() { # $1 = PR number; relies on the globals set from its fetch # Drafts are exempt (the build phase is the builder's); the version # reads cost two API calls and only on PRs missing the label. if [ "$DRAFT" != true ] && ! has_label release; then - release_shape_warning "$n" "$(tree_version "$HEAD_SHA")" "$(tree_version "$BASE_SHA")" + release_shape_warning "$n" "$(tree_version "$HEAD_SHA")" \ + "$(tree_version "${MERGE_BASE_SHA:-$BASE_SHA}")" fi # ---- merge-next: cleared, never set ---------------------------------- @@ -1031,6 +1033,7 @@ main() { set_required_bots "$AUTHOR" HEAD_SHA="$(jq -r '.head.sha' <<<"$PR_JSON")" BASE_SHA="$(jq -r '.base.sha' <<<"$PR_JSON")" + MERGE_BASE_SHA="$(jq -r '.merge_base // empty' <<<"$PR_JSON")" LABELS="$(jq -r '.labels[].name' <<<"$PR_JSON")" # This allow-list answers whether a row is a submitted, gradeable review; # bot_verdict separately answers what that submitted verdict says (#235). diff --git a/changelog.d/275.md b/changelog.d/275.md new file mode 100644 index 0000000..4cf6559 --- /dev/null +++ b/changelog.d/275.md @@ -0,0 +1 @@ +- Compare release-shaped pull requests with their merge base so later base-branch releases do not create phantom version-change warnings (#275). From 4ce43c4a3ba4e4d3e68c88c7d5ce9f805b1afa76 Mon Sep 17 00:00:00 2001 From: codex-bot-andresmgsl Date: Mon, 31 Aug 2026 21:11:30 +0000 Subject: [PATCH 3/5] test(labels): document isolated fixture state --- test/labels-reconcile.test.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/test/labels-reconcile.test.sh b/test/labels-reconcile.test.sh index c90793f..9dd71c3 100755 --- a/test/labels-reconcile.test.sh +++ b/test/labels-reconcile.test.sh @@ -181,6 +181,7 @@ expect "a bare head over an unreadable base still warns" yes \ # using the former manufactures a downgrade after an intervening release. release_shape_ref_probe() { # $1 = phantom | bump ( + # shellcheck disable=SC2030 # this probe intentionally isolates its repository fixture REPO=owner/repo LABELS_CONF="$FIXTURE_CONF" CEREMONY_FORGE=github From 4bce62e1fa80fe9921c8cc538889286c4df3b028 Mon Sep 17 00:00:00 2001 From: codex-bot-andresmgsl Date: Mon, 31 Aug 2026 21:20:35 +0000 Subject: [PATCH 4/5] test(labels): prove merge-base fallback --- test/labels-reconcile.test.sh | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/test/labels-reconcile.test.sh b/test/labels-reconcile.test.sh index 9dd71c3..14b7ebc 100755 --- a/test/labels-reconcile.test.sh +++ b/test/labels-reconcile.test.sh @@ -176,10 +176,12 @@ expect "a bare head over an unreadable base still warns" yes \ "$(release_shape_warning 41 2.0.0 "" | grep -qF '::warning::' && echo yes || echo no)" # The pure matrix above proves the warning predicate. These two fixtures drive -# the sweep boundary that chooses WHICH base tree feeds it (#275): Forgejo and -# GitHub both expose the PR's moving base tip beside its fixed merge base, and -# using the former manufactures a downgrade after an intervening release. -release_shape_ref_probe() { # $1 = phantom | bump +# the sweep boundary that chooses WHICH base tree feeds it (#275): the +# reporting Forgejo payload exposes the PR's moving base tip beside its fixed +# merge base, and using the former manufactures a downgrade after an +# intervening release. A missing merge-base field deliberately keeps the +# specified base-tip fallback for backends that do not supply that fact. +release_shape_ref_probe() { # $1 = phantom | bump | fallback ( # shellcheck disable=SC2030 # this probe intentionally isolates its repository fixture REPO=owner/repo @@ -190,9 +192,14 @@ release_shape_ref_probe() { # $1 = phantom | bump : >"$refs" case "$mode" in phantom) - head_ver=1.3.0 base_tip_ver=1.4.0 merge_base_ver=1.3.0 ;; + head_ver=1.3.0 base_tip_ver=1.4.0 merge_base_ver=1.3.0 + merge_base_json='"merge-base"' ;; bump) - head_ver=1.4.0 base_tip_ver=1.3.0 merge_base_ver=1.3.0 ;; + head_ver=1.4.0 base_tip_ver=1.3.0 merge_base_ver=1.3.0 + merge_base_json='"merge-base"' ;; + fallback) + head_ver=1.4.0 base_tip_ver=1.4.0 merge_base_ver=unused + merge_base_json=null ;; *) return 2 ;; esac # shellcheck disable=SC2317 # reached through the GitHub backend selected above @@ -214,8 +221,9 @@ release_shape_ref_probe() { # $1 = phantom | bump *'repos/owner/repo/pulls/701 --jq .requested_reviewers'*) return 0 ;; *repos/owner/repo/pulls/701/reviews*) return 0 ;; *repos/owner/repo/pulls/701) - jq -n '{draft:false,user:{login:"fixture-builder"}, - head:{sha:"head"},base:{sha:"base-tip"},merge_base:"merge-base", + jq -n --argjson merge_base "$merge_base_json" \ + '{draft:false,user:{login:"fixture-builder"}, + head:{sha:"head"},base:{sha:"base-tip"},merge_base:$merge_base, labels:[{name:"state:addressing"},{name:"blocked"}], requested_reviewers:[],created_at:"2026-08-31T20:00:00Z"}' ;; *repos/owner/repo/commits/head*) printf '2026-08-31T20:00:00Z\n' ;; @@ -248,6 +256,12 @@ expect "a genuine version bump relative to the merge base keeps the warning text expect "the genuine-bump guard also compares head with the PR merge base" \ $'head\nmerge-base' "$(cat "$RTMP/release-shape-bump-refs")" +fallback_shape="$(release_shape_ref_probe fallback)" +expect "a null merge base falls back to the base tip without guessing a warning" \ + no "$(grep -q 'release-shaped' <<<"$fallback_shape" && echo yes || echo no)" +expect "the null merge-base fallback compares head with the base tip" \ + $'head\nbase-tip' "$(cat "$RTMP/release-shape-fallback-refs")" + # -- drafts are building, whoever is requested -------------------------------- DRAFT=true HEAD_SHA=head1 REQUESTED="" REVIEWS_JSON='[]' expect "draft PR is building" state:building "$(decide_state)" From 0b1fa70d60b38e7346f9d912c74eb8e43ba7e98a Mon Sep 17 00:00:00 2001 From: codex-bot-andresmgsl Date: Mon, 31 Aug 2026 21:24:50 +0000 Subject: [PATCH 5/5] fix(changelog): match grouped fragment shape --- changelog.d/275.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog.d/275.md b/changelog.d/275.md index 4cf6559..c4c4118 100644 --- a/changelog.d/275.md +++ b/changelog.d/275.md @@ -1 +1,3 @@ +### Fixed + - Compare release-shaped pull requests with their merge base so later base-branch releases do not create phantom version-change warnings (#275).