From 710375888e162d80186c1674bbb9d41387114551 Mon Sep 17 00:00:00 2001 From: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com> Date: Thu, 16 Jul 2026 10:31:00 +0000 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20implement=20issue=20#749=20?= =?UTF-8?q?=E2=80=94=20Canary=20403=20NOT=20fixed=20by=20#745/#746=20?= =?UTF-8?q?=E2=80=94=20release-manager=20App=20can't=20bypass=20tag=20rule?= =?UTF-8?q?set=20even=20repo-scoped;=20need=20effective-permission=20diagn?= =?UTF-8?q?ostic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/canary-rollout.sh | 50 ++++++++++++++++++++++++ tests/canary_rollout.bats | 80 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+) diff --git a/scripts/canary-rollout.sh b/scripts/canary-rollout.sh index 7b31427eb..dd942077b 100755 --- a/scripts/canary-rollout.sh +++ b/scripts/canary-rollout.sh @@ -148,6 +148,14 @@ _gh_move_tag() { # real rejection to surface, not to paper over with a create. local low="${out,,}" if [[ "$low" != *"not found"* && "$low" != *"http 404"* ]]; then + # #749: on a 403 "Resource not accessible by integration" — the failure #745/#746 did NOT + # fix — dump the write token's EFFECTIVE permissions + scope so the next step is decided + # from data, not another guess. Only runs on the 403 branch (a non-404 that is also a + # permission 403), before the ::error:: so the diagnostic and the surfaced rejection are + # co-located in the run log. Best-effort: never alters rc. + if [[ "$low" == *"403"* || "$low" == *"not accessible by integration"* ]]; then + _gh_403_diag "$repo" >&2 || true + fi echo "::error::_gh_move_tag: could not move refs/tags/$tag -> ${sha:0:12} on $repo (HTTP): ${out//$'\n'/ }" >&2 return "$rc" fi @@ -158,6 +166,48 @@ _gh_move_tag() { return "$rc" } +# _gh_403_diag — one-shot effective-permission diagnostic (#749), called only from the +# _gh_move_tag 403 failure path. #745/#746 (repo-scoped write token) did NOT resolve the 403 +# "Resource not accessible by integration" on a protected channel-tag PATCH, so before guessing +# again we DUMP DATA, using the SAME token the PATCH used (_gh_write → CANARY_WRITE_TOKEN when +# set). This distinguishes the two possible root causes: +# (a) the token LACKS effective contents:write → a minting bug (code-fixable), or +# (b) the token HAS contents:write but the release-channel-tags ruleset bypass lapsed → NOT a +# code bug (a maintainer must re-save bypass_actors / open GitHub support). +# Emits, to stderr, inside a foldable ::group::: +# - X-Accepted-GitHub-Permissions from `gh api -i repos/` (the perms the API advertises), +# - the installation's repository_selection ("all" = OWNER-WIDE, "selected" = REPO-SCOPED) and +# repo count, i.e. whether the token is owner- or repo-scoped. +# Best-effort: every probe is guarded, and the function always returns 0 — a diagnostic must +# never mask the 403 or change the mover's exit status. +_gh_403_diag() { + local repo="$1" hdr inst sel count + echo "::group::_gh_move_tag 403 effective-permission diagnostic (#749)" + if [ -n "${CANARY_WRITE_TOKEN:-}" ]; then + echo "diag: introspecting with CANARY_WRITE_TOKEN (repo-scoped write token)" + else + echo "diag: introspecting with ambient GH_TOKEN (no CANARY_WRITE_TOKEN set)" + fi + # (1) What permission does the API advertise for this repo endpoint? + hdr="$(_gh_write api -i "repos/$repo" 2>/dev/null | grep -i '^x-accepted-github-permissions:' || true)" + if [ -n "$hdr" ]; then + printf 'diag: %s\n' "${hdr%$'\r'}" + else + echo "diag: X-Accepted-GitHub-Permissions header not present on repos/$repo response" + fi + # (2) Is the token owner-wide or repo-scoped, and over how many repos? + inst="$(_gh_write api /installation/repositories 2>/dev/null || true)" + sel="$(jq -r '.repository_selection // empty' <<<"$inst" 2>/dev/null || true)" + count="$(jq -r '.total_count // empty' <<<"$inst" 2>/dev/null || true)" + case "$sel" in + all) echo "diag: token scope = OWNER-WIDE (repository_selection=all, repos=${count:-?})" ;; + selected) echo "diag: token scope = REPO-SCOPED (repository_selection=selected, repos=${count:-?})" ;; + *) echo "diag: token scope UNKNOWN (repository_selection='${sel:-}')" ;; + esac + echo "::endgroup::" + return 0 +} + # _gh_create_annotated_tag — create the immutable annotated # tag object pointing at commit on and publish its ref, via the GitHub # API with the release-manager App token (mirrors cut-release.sh's gh_create_annotated_tag). diff --git a/tests/canary_rollout.bats b/tests/canary_rollout.bats index 70395c50a..33cdac354 100644 --- a/tests/canary_rollout.bats +++ b/tests/canary_rollout.bats @@ -1184,6 +1184,86 @@ GHEOF ! grep -q 'owner-wide' "$TOKEN_LOG" } +# ── #749: one-shot effective-permission diagnostic on a 403 tag-move ──────────── +# #745/#746 (repo-scoped write token) did NOT resolve the 403 "Resource not accessible by +# integration" on a protected channel-tag PATCH. To decide the next step from DATA rather than +# guessing again, the _gh_move_tag 403 failure path (behind #744's un-suppressed error) dumps +# the write token's EFFECTIVE permissions + scope — using the SAME write token the PATCH used — +# so we can tell (a) a token that LACKS effective contents:write (a minting bug, code-fixable) +# from (b) a token that HAS it but is still blocked because the ruleset bypass lapsed (NOT a +# code bug). The diagnostic must run ONLY on a 403 (not on the #743 non-404 422 rejection), +# must still surface the ::error:: + return non-zero, and must not fall back to POST. +_diag_403_stub() { + local scope="${1:-selected}" # selected (repo-scoped) | all (owner-wide) + STUB_BIN="$(mktemp -d "$BATS_TEST_TMPDIR/stub.XXXXXX")"; export PATH="$STUB_BIN:$PATH" + export CALL_LOG="$STUB_BIN/calls.log"; : > "$CALL_LOG" + export TOKEN_LOG="$STUB_BIN/tokens.log"; : > "$TOKEN_LOG" + cat > "$STUB_BIN/gh" <}" >> "$TOKEN_LOG" +case "\$*" in + *"-X PATCH"*"git/refs/tags/"*) + echo "PATCH" >> "$CALL_LOG" + echo '{"message":"Resource not accessible by integration","status":"403"}' >&2 + exit 1 ;; + *"-X POST"*"git/refs"*) + echo "POST" >> "$CALL_LOG"; echo '{"ref":"refs/tags/x"}'; exit 0 ;; + *"-i "*"repos/"*) + echo "DIAG_HEADERS" >> "$CALL_LOG" + printf 'HTTP/2.0 403 Forbidden\r\n' + printf 'X-Accepted-GitHub-Permissions: contents=write; contents=read\r\n' + printf '\r\n' + echo '{"full_name":"petry-projects/.github"}' + exit 0 ;; + *"installation/repositories"*) + echo "DIAG_INSTALL" >> "$CALL_LOG" + echo '{"repository_selection":"$scope","total_count":2}' + exit 0 ;; + *) echo "{}" ;; +esac +GHEOF + chmod +x "$STUB_BIN/gh" +} + +@test "_gh_move_tag: on a 403 dumps the effective-permission diagnostic then surfaces the error (#749)" { + _diag_403_stub selected + run env GH_TOKEN=owner-wide CANARY_WRITE_TOKEN=repo-scoped bash -c \ + "source '$ORCH' && _gh_move_tag petry-projects/.github agent-shield/v2-ring0 12b0075a9c48000000000000000000000000000" + [ "$status" -ne 0 ] + [[ "$output" == *"effective-permission diagnostic"* ]] + [[ "$output" == *"X-Accepted-GitHub-Permissions: contents=write"* ]] + [[ "$output" == *"REPO-SCOPED"* ]] + [[ "$output" == *"::error::"* ]] + grep -q '^PATCH$' "$CALL_LOG" + grep -q '^DIAG_HEADERS$' "$CALL_LOG" + grep -q '^DIAG_INSTALL$' "$CALL_LOG" + ! grep -q '^POST$' "$CALL_LOG" +} + +@test "_gh_move_tag: the 403 diagnostic introspects through CANARY_WRITE_TOKEN (#749)" { + _diag_403_stub selected + run env GH_TOKEN=owner-wide CANARY_WRITE_TOKEN=repo-scoped bash -c \ + "source '$ORCH' && _gh_move_tag petry-projects/.github agent-shield/v2-ring0 12b0075a9c48000000000000000000000000000" + grep -q 'installation/repositories.*repo-scoped' "$TOKEN_LOG" + ! grep -q 'installation/repositories.*owner-wide' "$TOKEN_LOG" +} + +@test "_gh_move_tag: the 403 diagnostic reports OWNER-WIDE when repository_selection is all (#749)" { + _diag_403_stub all + run env GH_TOKEN=owner-wide CANARY_WRITE_TOKEN=repo-scoped bash -c \ + "source '$ORCH' && _gh_move_tag petry-projects/.github agent-shield/v2-ring0 12b0075a9c48000000000000000000000000000" + [[ "$output" == *"OWNER-WIDE"* ]] + [[ "$output" != *"REPO-SCOPED"* ]] +} + +@test "_gh_move_tag: a non-403 (422 ruleset) failure does NOT trigger the 403 diagnostic (#749)" { + _move_tag_stub reject + run bash -c "source '$ORCH' && _gh_move_tag petry-projects/.github agent-shield/v2-ring0 12b0075a9c48000000000000000000000000000" + [ "$status" -ne 0 ] + [[ "$output" != *"effective-permission diagnostic"* ]] + [[ "$output" == *"::error::"* ]] +} + # ── orchestrator: sync-issues — auto-triage held promotions into tracked issues ── # A held (BLOCKED) promotion files/updates ONE idempotent issue per agent with the failing-run # evidence; a cleared agent's issue auto-closes; the fleet-status table is rendered to the job From 37b90759e9b4c62b9a58f7ef6c273fd0abb453f1 Mon Sep 17 00:00:00 2001 From: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com> Date: Thu, 16 Jul 2026 10:39:31 +0000 Subject: [PATCH 2/3] fix(reviews): address review comments [skip ci-relay] --- scripts/canary-rollout.sh | 8 ++++--- tests/canary_rollout.bats | 49 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/scripts/canary-rollout.sh b/scripts/canary-rollout.sh index dd942077b..9f5f23f4f 100755 --- a/scripts/canary-rollout.sh +++ b/scripts/canary-rollout.sh @@ -181,7 +181,7 @@ _gh_move_tag() { # Best-effort: every probe is guarded, and the function always returns 0 — a diagnostic must # never mask the 403 or change the mover's exit status. _gh_403_diag() { - local repo="$1" hdr inst sel count + local repo="$1" hdr inst sel="" count="" echo "::group::_gh_move_tag 403 effective-permission diagnostic (#749)" if [ -n "${CANARY_WRITE_TOKEN:-}" ]; then echo "diag: introspecting with CANARY_WRITE_TOKEN (repo-scoped write token)" @@ -197,8 +197,10 @@ _gh_403_diag() { fi # (2) Is the token owner-wide or repo-scoped, and over how many repos? inst="$(_gh_write api /installation/repositories 2>/dev/null || true)" - sel="$(jq -r '.repository_selection // empty' <<<"$inst" 2>/dev/null || true)" - count="$(jq -r '.total_count // empty' <<<"$inst" 2>/dev/null || true)" + if [ -n "$inst" ]; then + sel="$(jq -r '.repository_selection? // empty' <<<"$inst" 2>/dev/null || true)" + count="$(jq -r '.total_count? // empty' <<<"$inst" 2>/dev/null || true)" + fi case "$sel" in all) echo "diag: token scope = OWNER-WIDE (repository_selection=all, repos=${count:-?})" ;; selected) echo "diag: token scope = REPO-SCOPED (repository_selection=selected, repos=${count:-?})" ;; diff --git a/tests/canary_rollout.bats b/tests/canary_rollout.bats index 33cdac354..586cb98a5 100644 --- a/tests/canary_rollout.bats +++ b/tests/canary_rollout.bats @@ -1264,6 +1264,55 @@ GHEOF [[ "$output" == *"::error::"* ]] } +@test "_gh_403_diag: empty installation/repositories response does not cause a jq parse error (#749)" { + # Stub: PATCH → 403; installation/repositories → empty (API failure) + STUB_BIN="$(mktemp -d "$BATS_TEST_TMPDIR/stub.XXXXXX")"; export PATH="$STUB_BIN:$PATH" + cat > "$STUB_BIN/gh" <<'GHEOF' +#!/usr/bin/env bash +case "$*" in + *"-X PATCH"*"git/refs/tags/"*) + echo '{"message":"Resource not accessible by integration","status":"403"}' >&2; exit 1 ;; + *"-i "*"repos/"*) + printf 'HTTP/2.0 403 Forbidden\r\nX-Accepted-GitHub-Permissions: contents=write\r\n\r\n{}' + exit 0 ;; + *"installation/repositories"*) exit 1 ;; # API failure → empty $inst + *) echo "{}" ;; +esac +GHEOF + chmod +x "$STUB_BIN/gh" + run env GH_TOKEN=tok bash -c \ + "source '$ORCH' && _gh_move_tag petry-projects/.github agent-shield/v2-ring0 12b0075a9c48000000000000000000000000000" + [ "$status" -ne 0 ] + [[ "$output" == *"effective-permission diagnostic"* ]] + # Must not print a jq error about parse failure + [[ "$output" != *"parse error"* ]] + [[ "$output" == *"token scope UNKNOWN"* ]] +} + +@test "_gh_403_diag: installation/repositories response missing keys does not error (#749)" { + # Stub: installation/repositories returns valid JSON but without the expected keys + STUB_BIN="$(mktemp -d "$BATS_TEST_TMPDIR/stub.XXXXXX")"; export PATH="$STUB_BIN:$PATH" + cat > "$STUB_BIN/gh" <<'GHEOF' +#!/usr/bin/env bash +case "$*" in + *"-X PATCH"*"git/refs/tags/"*) + echo '{"message":"Resource not accessible by integration","status":"403"}' >&2; exit 1 ;; + *"-i "*"repos/"*) + printf 'HTTP/2.0 403 Forbidden\r\nX-Accepted-GitHub-Permissions: contents=write\r\n\r\n{}' + exit 0 ;; + *"installation/repositories"*) echo '{}' ;; # valid JSON but no repository_selection or total_count + *) echo "{}" ;; +esac +GHEOF + chmod +x "$STUB_BIN/gh" + run env GH_TOKEN=tok bash -c \ + "source '$ORCH' && _gh_move_tag petry-projects/.github agent-shield/v2-ring0 12b0075a9c48000000000000000000000000000" + [ "$status" -ne 0 ] + [[ "$output" == *"effective-permission diagnostic"* ]] + [[ "$output" != *"parse error"* ]] + [[ "$output" == *"token scope UNKNOWN"* ]] +} + # ── orchestrator: sync-issues — auto-triage held promotions into tracked issues ── # A held (BLOCKED) promotion files/updates ONE idempotent issue per agent with the failing-run # evidence; a cleared agent's issue auto-closes; the fleet-status table is rendered to the job From 35ec7b864fe0333d013448464e355368e75c9f24 Mon Sep 17 00:00:00 2001 From: donpetry-bot <281750570+donpetry-bot@users.noreply.github.com> Date: Thu, 16 Jul 2026 10:51:00 +0000 Subject: [PATCH 3/3] chore: dev-lead update (review-changes) [skip ci-relay] --- scripts/canary-rollout.sh | 18 ++++++++---------- tests/canary_rollout.bats | 20 +++++++++++--------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/scripts/canary-rollout.sh b/scripts/canary-rollout.sh index 9f5f23f4f..f62a573e0 100755 --- a/scripts/canary-rollout.sh +++ b/scripts/canary-rollout.sh @@ -176,12 +176,11 @@ _gh_move_tag() { # code bug (a maintainer must re-save bypass_actors / open GitHub support). # Emits, to stderr, inside a foldable ::group::: # - X-Accepted-GitHub-Permissions from `gh api -i repos/` (the perms the API advertises), -# - the installation's repository_selection ("all" = OWNER-WIDE, "selected" = REPO-SCOPED) and -# repo count, i.e. whether the token is owner- or repo-scoped. +# - total_count from /installation/repositories (how many repos the installation token covers). # Best-effort: every probe is guarded, and the function always returns 0 — a diagnostic must # never mask the 403 or change the mover's exit status. _gh_403_diag() { - local repo="$1" hdr inst sel="" count="" + local repo="$1" hdr inst count="" echo "::group::_gh_move_tag 403 effective-permission diagnostic (#749)" if [ -n "${CANARY_WRITE_TOKEN:-}" ]; then echo "diag: introspecting with CANARY_WRITE_TOKEN (repo-scoped write token)" @@ -195,17 +194,16 @@ _gh_403_diag() { else echo "diag: X-Accepted-GitHub-Permissions header not present on repos/$repo response" fi - # (2) Is the token owner-wide or repo-scoped, and over how many repos? + # (2) How many repositories does the installation token cover? inst="$(_gh_write api /installation/repositories 2>/dev/null || true)" if [ -n "$inst" ]; then - sel="$(jq -r '.repository_selection? // empty' <<<"$inst" 2>/dev/null || true)" count="$(jq -r '.total_count? // empty' <<<"$inst" 2>/dev/null || true)" fi - case "$sel" in - all) echo "diag: token scope = OWNER-WIDE (repository_selection=all, repos=${count:-?})" ;; - selected) echo "diag: token scope = REPO-SCOPED (repository_selection=selected, repos=${count:-?})" ;; - *) echo "diag: token scope UNKNOWN (repository_selection='${sel:-}')" ;; - esac + if [ -n "$count" ]; then + echo "diag: installation/repositories total_count=${count}" + else + echo "diag: installation/repositories total_count=" + fi echo "::endgroup::" return 0 } diff --git a/tests/canary_rollout.bats b/tests/canary_rollout.bats index 586cb98a5..33d14a41d 100644 --- a/tests/canary_rollout.bats +++ b/tests/canary_rollout.bats @@ -1194,7 +1194,9 @@ GHEOF # code bug). The diagnostic must run ONLY on a 403 (not on the #743 non-404 422 rejection), # must still surface the ::error:: + return non-zero, and must not fall back to POST. _diag_403_stub() { - local scope="${1:-selected}" # selected (repo-scoped) | all (owner-wide) + local scope="${1:-selected}" # selected (repo-scoped, low count) | all (owner-wide, high count) + local install_count + if [ "$scope" = all ]; then install_count=50; else install_count=2; fi STUB_BIN="$(mktemp -d "$BATS_TEST_TMPDIR/stub.XXXXXX")"; export PATH="$STUB_BIN:$PATH" export CALL_LOG="$STUB_BIN/calls.log"; : > "$CALL_LOG" export TOKEN_LOG="$STUB_BIN/tokens.log"; : > "$TOKEN_LOG" @@ -1217,7 +1219,7 @@ case "\$*" in exit 0 ;; *"installation/repositories"*) echo "DIAG_INSTALL" >> "$CALL_LOG" - echo '{"repository_selection":"$scope","total_count":2}' + echo '{"total_count":$install_count}' exit 0 ;; *) echo "{}" ;; esac @@ -1232,7 +1234,7 @@ GHEOF [ "$status" -ne 0 ] [[ "$output" == *"effective-permission diagnostic"* ]] [[ "$output" == *"X-Accepted-GitHub-Permissions: contents=write"* ]] - [[ "$output" == *"REPO-SCOPED"* ]] + [[ "$output" == *"total_count=2"* ]] [[ "$output" == *"::error::"* ]] grep -q '^PATCH$' "$CALL_LOG" grep -q '^DIAG_HEADERS$' "$CALL_LOG" @@ -1248,12 +1250,12 @@ GHEOF ! grep -q 'installation/repositories.*owner-wide' "$TOKEN_LOG" } -@test "_gh_move_tag: the 403 diagnostic reports OWNER-WIDE when repository_selection is all (#749)" { +@test "_gh_move_tag: the 403 diagnostic reports the higher total_count for an owner-wide installation token (#749)" { _diag_403_stub all run env GH_TOKEN=owner-wide CANARY_WRITE_TOKEN=repo-scoped bash -c \ "source '$ORCH' && _gh_move_tag petry-projects/.github agent-shield/v2-ring0 12b0075a9c48000000000000000000000000000" - [[ "$output" == *"OWNER-WIDE"* ]] - [[ "$output" != *"REPO-SCOPED"* ]] + [[ "$output" == *"total_count=50"* ]] + [[ "$output" != *"total_count=2"* ]] } @test "_gh_move_tag: a non-403 (422 ruleset) failure does NOT trigger the 403 diagnostic (#749)" { @@ -1286,7 +1288,7 @@ GHEOF [[ "$output" == *"effective-permission diagnostic"* ]] # Must not print a jq error about parse failure [[ "$output" != *"parse error"* ]] - [[ "$output" == *"token scope UNKNOWN"* ]] + [[ "$output" == *"total_count="* ]] } @test "_gh_403_diag: installation/repositories response missing keys does not error (#749)" { @@ -1300,7 +1302,7 @@ case "$*" in *"-i "*"repos/"*) printf 'HTTP/2.0 403 Forbidden\r\nX-Accepted-GitHub-Permissions: contents=write\r\n\r\n{}' exit 0 ;; - *"installation/repositories"*) echo '{}' ;; # valid JSON but no repository_selection or total_count + *"installation/repositories"*) echo '{}' ;; # valid JSON but no total_count *) echo "{}" ;; esac GHEOF @@ -1310,7 +1312,7 @@ GHEOF [ "$status" -ne 0 ] [[ "$output" == *"effective-permission diagnostic"* ]] [[ "$output" != *"parse error"* ]] - [[ "$output" == *"token scope UNKNOWN"* ]] + [[ "$output" == *"total_count="* ]] } # ── orchestrator: sync-issues — auto-triage held promotions into tracked issues ──