From 308a4a132c129b03abf6fd4cf6344bb8fd4ea259 Mon Sep 17 00:00:00 2001 From: don-petry <36422719+don-petry@users.noreply.github.com> Date: Tue, 22 Sep 2026 17:22:15 +0000 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20implement=20issue=20#1866=20?= =?UTF-8?q?=E2=80=94=20standards-sync=20rewrites=20the=20per-repo=20channe?= =?UTF-8?q?l=20pin=20it=20is=20told=20to=20preserve=20=E2=80=94=20PR=20#18?= =?UTF-8?q?19=20would=20move=20dev-lead=20from=20stable=20to=20next=20in?= =?UTF-8?q?=20the=20repo=20that=20hosts=20it?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/lint.yml | 1 + .github/workflows/pr-review-mention.yml | 2 +- scripts/lib/channel-pin-consistency.sh | 130 +++++++++++++ .../channel-pin/stub-agent-ref-desync.yml | 12 ++ tests/fixtures/channel-pin/stub-aligned.yml | 11 ++ .../fixtures/channel-pin/stub-both-differ.yml | 8 + .../channel-pin/stub-comment-mismatch.yml | 10 + tests/fixtures/channel-pin/stub-contrast.yml | 11 ++ tests/fixtures/channel-pin/stub-no-pin.yml | 10 + .../fixtures/channel-pin/stub-other-agent.yml | 12 ++ tests/test_channel_pin_consistency.bats | 176 ++++++++++++++++++ 11 files changed, 382 insertions(+), 1 deletion(-) create mode 100644 scripts/lib/channel-pin-consistency.sh create mode 100644 tests/fixtures/channel-pin/stub-agent-ref-desync.yml create mode 100644 tests/fixtures/channel-pin/stub-aligned.yml create mode 100644 tests/fixtures/channel-pin/stub-both-differ.yml create mode 100644 tests/fixtures/channel-pin/stub-comment-mismatch.yml create mode 100644 tests/fixtures/channel-pin/stub-contrast.yml create mode 100644 tests/fixtures/channel-pin/stub-no-pin.yml create mode 100644 tests/fixtures/channel-pin/stub-other-agent.yml create mode 100644 tests/test_channel_pin_consistency.bats diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 05a6dd1c9..a83d17560 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -180,6 +180,7 @@ jobs: tests/test_lint_main_push_trigger.bats \ tests/caller_stub_freeze.bats \ tests/test_sc2_self_review_channel.bats \ + tests/test_channel_pin_consistency.bats \ tests/pr_review_canary.bats \ tests/dev-lead/unit/test_maintainer_review_thread_gate.bats \ tests/dev-lead/unit/test_maintainer_comment_gate.bats \ diff --git a/.github/workflows/pr-review-mention.yml b/.github/workflows/pr-review-mention.yml index da59c8f2a..7be10cf0b 100644 --- a/.github/workflows/pr-review-mention.yml +++ b/.github/workflows/pr-review-mention.yml @@ -7,7 +7,7 @@ # • This file is a THIN CALLER STUB. All review-dispatch logic lives in the # reusable workflow above. # • You MUST NOT change: the `uses:` ref — it is pinned to the -# `pr-review-mention/v2-stable` channel, a moving tag advanced centrally. +# `pr-review-mention/v2-next` channel, a moving tag advanced centrally. # Never repoint it to `@main`, a SHA, or a frozen `@vX` (see # ci-standards.md → Reusable workflow versioning). Also do not change the # trigger events or the job-level `permissions:` block — reusable workflows diff --git a/scripts/lib/channel-pin-consistency.sh b/scripts/lib/channel-pin-consistency.sh new file mode 100644 index 000000000..495aec65f --- /dev/null +++ b/scripts/lib/channel-pin-consistency.sh @@ -0,0 +1,130 @@ +#!/usr/bin/env bash +# channel-pin-consistency.sh — same-file channel-pin/comment consistency guard +# (#1866 AC #3; regression guard for #1819). +# +# The workflow-stub sync generator (standards-deploy.yml, in the PUBLIC +# petry-projects/.github repo) PRESERVES each destination repo's own per-repo +# `uses:` channel pin. #1819 exposed the receiving-side hazard: a guard COMMENT +# (or the `agent_ref:` input) that names a channel can drift out of step with the +# actual `uses:` pin in the SAME file — moving one without the other. This library +# provides the receiving-side property this repo can enforce for its own committed +# stubs: any channel a caller stub's comments / `agent_ref` name for its OWN +# reusable must equal the `uses:` pin in the same file. +# +# It is a member of the receiving-guard family (validate-caller-inputs #1253, +# caller-stub-freeze #1255, sync-scope-guard #1700, self-review-channel SC2 #1624). +# Intentional CONTRAST references (a promotion target, a legacy form, a different +# agent's duty) are excluded so they are not mistaken for a stale pin comment. +# +# Pure helpers, no side effects, no network. This file defines functions only (no +# top-level `set -euo pipefail`), matching the repo's sourceable-lib convention +# (e.g. scripts/lib/self-review-channel.sh, which it sources for the shared +# `src_stub_uses_ref` / `src_stub_agent_ref` parsers). +# +# Sourced by tests/test_channel_pin_consistency.bats. + +CPC_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=scripts/lib/self-review-channel.sh +source "${CPC_LIB_DIR}/self-review-channel.sh" + +# Contrast markers: words that flag a comment line as INTENTIONALLY naming a +# channel other than this file's own pin — a promotion target, a legacy/pre-#1184 +# form, a cross-agent duty note, or an explicit dogfood/canary contrast — so it is +# not mistaken for a stale/inconsistent pin comment. +CPC_CONTRAST_RE='legacy|promote|promoted|promotion|fleet|pre-#|independent|on purpose|instead|rather than|formerly|dogfood' + +# cpc_channel_refs +# Read text on stdin; emit each `/` channel ref it contains, one +# per line (deduped, in first-seen order). A channel ref is `/(vN-)?` +# with tier ∈ {stable, next, ring}; the agent token is lowercase/hyphenated. +# A full `…/.yml@/` token yields just `/` +# (the `.` in `.yml` bounds the agent). Bare tiers with no agent and ordinary +# paths yield nothing. Always returns 0. +cpc_channel_refs() { + grep -oE '[a-z][a-z0-9-]*/(v[0-9]+-)?(stable|next|ring[0-9]+)([^a-z0-9-]|$)' \ + | sed -E 's/[^a-z0-9-]$//' \ + | awk 'NF && !seen[$0]++' +} + +# cpc_line_is_contrast +# Return 0 iff carries a contrast marker (see CPC_CONTRAST_RE). +cpc_line_is_contrast() { + printf '%s' "$1" | grep -qiE "$CPC_CONTRAST_RE" +} + +# cpc_stub_conflicts +# Print each same-file channel-pin conflict in , one per line; return 1 if +# any are found, else 0. Two conflict classes: +# (a) a COMMENT names this file's OWN agent at a channel != the `uses:` pin, +# on a line that is not a contrast reference; +# (b) the `agent_ref:` input names a channel != the `uses:` pin. +# A file with no reusable `uses:` channel pin has nothing to check → returns 0 +# with no output (not-applicable). +cpc_stub_conflicts() { + local file="$1" pinref pinagent pinchan aref line comment ref agent chan found=0 + pinref="$(src_stub_uses_ref "$file")" || return 0 # no pin → N/A + case "$pinref" in + */*) ;; # need an / pin + *) return 0 ;; # e.g. @main / a SHA → N/A + esac + pinagent="${pinref%/*}" + pinchan="${pinref##*/}" + + # (a) comment/pin consistency. + while IFS= read -r line || [ -n "$line" ]; do + case "$line" in + *"#"*) comment="${line#*#}" ;; # text after the first '#' + *) continue ;; + esac + cpc_line_is_contrast "$comment" && continue + while IFS= read -r ref; do + agent="${ref%/*}" + chan="${ref##*/}" + if [ "$agent" = "$pinagent" ] && [ "$chan" != "$pinchan" ]; then + printf 'comment names channel `%s` but `uses:` pins `%s`\n' "$ref" "$pinref" + found=1 + fi + done < <(printf '%s\n' "$comment" | cpc_channel_refs) + done < "$file" + + # (b) agent_ref/uses consistency (only when an agent_ref input is present). + if aref="$(src_stub_agent_ref "$file")"; then + if [ "$aref" != "$pinref" ]; then + printf '`agent_ref: %s` does not match the `uses:` pin `%s`\n' "$aref" "$pinref" + found=1 + fi + fi + + [ "$found" -eq 0 ] +} + +# cpc_assert_consistent [