diff --git a/scripts/compliance-audit.sh b/scripts/compliance-audit.sh index 7b7cab0a1..ea01f9f86 100755 --- a/scripts/compliance-audit.sh +++ b/scripts/compliance-audit.sh @@ -1563,23 +1563,28 @@ check_centralized_workflow_stubs() { decoded=$(echo "$content" | base64 -d 2>/dev/null || echo "") [ -z "$decoded" ] && continue - # Compliant if a non-comment `uses:` line pins the reusable at the canonical - # channel or — transitionally, during the #482 migration — an accepted legacy - # @vN ref. stub_pin_acceptable anchors to start-of-line so a `# uses: …` - # comment never satisfies the check. For RING reusables, also accept the - # major-scoped `/v-` form (major-scoped-channels epic #657 F3) - # for any major M whose tier matches the repo — backward-compatible with the - # bare-tier pins the fleet still carries; the wrong tier stays drift. - if stub_pin_acceptable "$decoded" "$reusable" "$canonical" "$legacy" \ - || { [ "$is_ring" = 1 ] && ring_major_form_acceptable "$decoded" "$reusable" "$chan" "$repo"; }; then + # Compliance rule (major-scoped-channels epic #657 F5, #861): + # RING reusables MUST pin the major-scoped `/v-` form — any + # major M whose tier matches the repo (ring_major_form_acceptable). A bare + # `/` pin is now drift: the fleet has migrated onto the v-form, + # so the bare grace (stub_pin_acceptable) no longer applies to the rings. + # Non-RING reusables (fixed-pin entries) keep the exact/legacy match via + # stub_pin_acceptable. + # Both helpers anchor to start-of-line so a `# uses: …` comment never counts; + # a v-form pinned to the WRONG tier stays drift. + if { [ "$is_ring" = 1 ] && ring_major_form_acceptable "$decoded" "$reusable" "$chan" "$repo"; } \ + || { [ "$is_ring" != 1 ] && stub_pin_acceptable "$decoded" "$reusable" "$canonical" "$legacy"; }; then continue fi - # Determine why it's non-compliant for a more actionable message. - local esc_reusable why + # Determine why it's non-compliant for a more actionable message. For RING + # reusables the expected pin is the major-scoped v-form `/v-` + # (major-agnostic on tier, #657 F5); non-ring keeps the fixed canonical ref. + local esc_reusable why expected_pin="$canonical" esc_reusable=$(escape_ere "$reusable") + [ "$is_ring" = 1 ] && expected_pin="${chan}/v-$(ring_tier_for_repo "$repo")" if echo "$decoded" | grep -qE "^[[:space:]]*uses:[[:space:]]*petry-projects/\\.github/\\.github/workflows/${esc_reusable}\\.yml@"; then - why="references the reusable but is not pinned to \`@${canonical}\` (org standard)" + why="references the reusable but is not pinned to the major-scoped channel \`@${expected_pin}\` (org standard — a bare \`@${canonical}\` tier pin is drift)" elif echo "$decoded" | grep -qF "petry-projects/.github/.github/workflows/${reusable}"; then why="references the reusable but the \`uses:\` line does not match the canonical stub" else @@ -1587,7 +1592,7 @@ check_centralized_workflow_stubs() { fi add_finding "$repo" "ci-workflows" "non-stub-$wf" "error" \ - "Centralized workflow \`$wf\` $why. Replace with the canonical stub from \`standards/workflows/${wf}\` which delegates to \`petry-projects/.github/.github/workflows/${reusable}.yml@${canonical}\`." \ + "Centralized workflow \`$wf\` $why. Replace with the canonical stub from \`standards/workflows/${wf}\` which delegates to \`petry-projects/.github/.github/workflows/${reusable}.yml@${expected_pin}\`." \ "standards/ci-standards.md#centralization-tiers" done } @@ -1627,16 +1632,21 @@ check_dev_lead_stub() { # channel `next` and per-ring channels `ring0`, `ring1`, … A frozen `@vX.Y.Z` # or `@` is NOT a channel and is intentionally rejected — callers pin the # moving channel so rollout/rollback is a single central tag move. - if ! printf '%s\n' "$decoded" | grep -qE "^[[:space:]]*uses:[[:space:]]*petry-projects/\\.github-private/\\.github/workflows/dev-lead-reusable\\.yml@dev-lead/(stable|next|ring[0-9]+)([[:space:]]|$)"; then + # Major-scoped channels (#657 F5, #861): the channel MUST carry the `v-` + # major prefix — a bare `dev-lead/stable` is now drift; only `dev-lead/v-` + # (any major) is accepted, mirroring the RING reusables above. + if ! printf '%s\n' "$decoded" | grep -qE "^[[:space:]]*uses:[[:space:]]*petry-projects/\\.github-private/\\.github/workflows/dev-lead-reusable\\.yml@dev-lead/v[0-9]+-(stable|next|ring[0-9]+)([[:space:]]|$)"; then add_finding "$repo" "ci-workflows" "dev-lead-stub-pin" "error" \ - "The \`dev-lead.yml\` caller stub must pin a \`dev-lead\` channel tag — \`petry-projects/.github-private/.github/workflows/dev-lead-reusable.yml@dev-lead/\` where is \`stable\` (default), \`next\`, or \`ring\`. Re-sync from \`standards/workflows/dev-lead.yml\`." \ + "The \`dev-lead.yml\` caller stub must pin a major-scoped \`dev-lead\` channel tag — \`petry-projects/.github-private/.github/workflows/dev-lead-reusable.yml@dev-lead/v-\` where is \`stable\` (default), \`next\`, or \`ring\` (a bare \`dev-lead/\` tier pin is drift). Re-sync from \`standards/workflows/dev-lead.yml\`." \ "standards/ci-standards.md#dev-lead-agent" fi # 2) agent_ref must be threaded through to pin the same channel inside the # reusable's own script/prompt checkout (prevents split-brain on promotion). - # Same channel set as the uses: pin above; should match the uses: channel. - uses_channel=$(printf '%s\n' "$decoded" | sed -nE 's#^[[:space:]]*uses:[[:space:]]*petry-projects/\.github-private/\.github/workflows/dev-lead-reusable\.yml@dev-lead/(stable|next|ring[0-9]+)([[:space:]]|$).*#\1#p') + # Same major-scoped channel form as the uses: pin above; must match the uses: + # channel exactly — including its major, so a `v3-stable` uses pin with a + # `v2-stable` agent_ref is caught. + uses_channel=$(printf '%s\n' "$decoded" | sed -nE 's#^[[:space:]]*uses:[[:space:]]*petry-projects/\.github-private/\.github/workflows/dev-lead-reusable\.yml@dev-lead/(v[0-9]+-(stable|next|ring[0-9]+))([[:space:]]|$).*#\1#p') if [ -n "$uses_channel" ]; then if ! printf '%s\n' "$decoded" | grep -qE "^[[:space:]]*agent_ref:[[:space:]]*dev-lead/$uses_channel([[:space:]]|$)"; then add_finding "$repo" "ci-workflows" "dev-lead-stub-agent-ref" "error" \ @@ -1644,9 +1654,9 @@ check_dev_lead_stub() { "standards/ci-standards.md#dev-lead-agent" fi else - if ! printf '%s\n' "$decoded" | grep -qE "^[[:space:]]*agent_ref:[[:space:]]*dev-lead/(stable|next|ring[0-9]+)([[:space:]]|$)"; then + if ! printf '%s\n' "$decoded" | grep -qE "^[[:space:]]*agent_ref:[[:space:]]*dev-lead/v[0-9]+-(stable|next|ring[0-9]+)([[:space:]]|$)"; then add_finding "$repo" "ci-workflows" "dev-lead-stub-agent-ref" "error" \ - "The \`dev-lead.yml\` caller stub must pass \`with: agent_ref: dev-lead/\` (\`stable\`, \`next\`, or \`ring\`) so the reusable checks out its own scripts/prompts from the same channel. Re-sync from \`standards/workflows/dev-lead.yml\`." \ + "The \`dev-lead.yml\` caller stub must pass \`with: agent_ref: dev-lead/v-\` (\`stable\`, \`next\`, or \`ring\`) so the reusable checks out its own scripts/prompts from the same major-scoped channel. Re-sync from \`standards/workflows/dev-lead.yml\`." \ "standards/ci-standards.md#dev-lead-agent" fi fi diff --git a/scripts/deploy-standard-workflows.sh b/scripts/deploy-standard-workflows.sh index b9eff80b6..2c4ebdb74 100755 --- a/scripts/deploy-standard-workflows.sh +++ b/scripts/deploy-standard-workflows.sh @@ -302,16 +302,27 @@ is_already_compliant() { if [[ "$prefix" =~ /([a-z0-9-]+)-reusable\.yml$ ]]; then base="${BASH_REMATCH[1]}" # Only treat it as ring-managed if the reusable is on the ring model AND the - # template pins a channel tag (not a frozen @vX / SHA). - if ring_is_ring_reusable "$base" && [[ "$ref_after" =~ ^${base}/(stable|next|ring[0-9]+)$ ]]; then - local ref - while IFS= read -r ref; do - [[ -n "$ref" ]] && grep -qF "${prefix}@${ref}" <<< "$existing_content" && return 0 - done < <(ring_accepted_refs "$base" "$repo") - # Transition to major-scoped channels (#657 F5): a stub already pinned to the - # repo's tier-correct `v-` form (any major) is compliant too, so the - # sweep accepts both the bare and the v-form during migration. A WRONG-tier - # v-form is not accepted here and stays drift. + # template pins a channel tag (not a frozen @vX / SHA). The template ref may be + # the bare `/` OR the major-scoped `/v-` form (#657 + # F5) — post-migration the templates pin the v-form, so both must enter here. + if ring_is_ring_reusable "$base" && [[ "$ref_after" =~ ^${base}/(v[0-9]+-)?(stable|next|ring[0-9]+)$ ]]; then + # Major-scoped channels (#657 F5, #861): the bare-tier grace is now + # major-AWARE. A bare `/` stub stays compliant only while the + # agent has NO release (no current major line) — the pre-release fleet has + # nothing to major-scope to yet. Once the agent has a release, a bare stub is + # drift and must migrate to the tier's `v-` form. + local host major + host="$(cut -d/ -f1-2 <<< "$prefix")" + major="$(ring_host_current_major "$host" "$base")" + if [[ -z "$major" ]]; then + local ref + while IFS= read -r ref; do + [[ -n "$ref" ]] && grep -qF "${prefix}@${ref}" <<< "$existing_content" && return 0 + done < <(ring_accepted_refs "$base" "$repo") + fi + # A stub already pinned to the repo's tier-correct `v-` form (any + # major) is compliant regardless of release state. A WRONG-tier v-form — or a + # bare stub once the agent has a release — is not accepted here and stays drift. local existing_ref existing_ref=$(grep -oE "@${base}/[^[:space:]\"']+" <<< "$existing_content" | head -1) existing_ref="${existing_ref#@}" diff --git a/test/scripts/compliance-audit/centralized-stub-pins.bats b/test/scripts/compliance-audit/centralized-stub-pins.bats index f1724760e..2b54e579f 100644 --- a/test/scripts/compliance-audit/centralized-stub-pins.bats +++ b/test/scripts/compliance-audit/centralized-stub-pins.bats @@ -7,6 +7,13 @@ # @vN ref — so the audit neither flags nor reverts a stub mid-migration. A SHA # pin, a commented-out line, or the wrong reusable must NOT satisfy the check. # +# #657 F5 (#861): the RING acceptance in check_centralized_workflow_stubs now +# REQUIRES the major-scoped v- form — a bare / pin on a +# stable-tier repo is drift, and only a tier-correct v- (any major) is +# accepted. The pure helpers stub_pin_acceptable (bare) / ring_major_form_acceptable +# (v-form) are unchanged and still unit-tested below; the integration section at +# the end exercises the whole-check decision through a mocked gh_api. +# # The script is sourced in an isolated subshell (its `main` is guarded, so # sourcing only defines functions) and the real helper is exercised directly. @@ -150,16 +157,55 @@ maj_accept() { [ "$status" -ne 0 ] } -@test "transition: bare stays clean while v-form is added (#657 F3)" { - # The two acceptance paths together: a stable-tier repo is clean whether it - # pins the bare tier (legacy) OR the v-form of its tier, but a v-form on the - # wrong tier is drift. Mirrors the issue's audit acceptance cases. - accept " uses: $R@agent-shield/stable" "$C" "$L" # bare → clean +# --------------------------------------------------------------------------- +# #657 F5 (#861) — integration: check_centralized_workflow_stubs now REQUIRES the +# v- form for RING reusables. A bare / pin on a stable-tier +# repo is drift (FLAGGED); a tier-correct v- (any major) is accepted; a +# wrong-tier v-form is still drift. These exercise the whole check by sourcing the +# script and mocking gh_api so the pin decision surfaces as a FINDING, not just a +# pure-helper return. +# --------------------------------------------------------------------------- +stub_check() { # — run the check for a stable-tier repo (markets) + FIXTURE_B64=$(printf '%s' "$1" | base64 | tr -d '\n'); export FIXTURE_B64 + run bash -c ' + source "$1" >/dev/null 2>&1 + ORG=petry-projects + gh_api() { + case "$1" in + */contents/.github/workflows) printf "agent-shield.yml\n" ;; + */contents/.github/workflows/agent-shield.yml) printf "%s" "$FIXTURE_B64" ;; + esac + } + add_finding() { printf "FLAGGED:%s\n" "$3"; } + check_centralized_workflow_stubs markets + ' _ "$SCRIPT" +} + +@test "F5 integration: a bare / RING pin is FLAGGED on a stable repo (#861)" { + stub_check "jobs: + agent-shield: + uses: $R@agent-shield/stable + secrets: inherit" [ "$status" -eq 0 ] - maj_accept "agent-shield-reusable" " uses: $R@agent-shield/v1-stable" "markets" # v-form → clean + echo "$output" | grep -q 'FLAGGED:non-stub-agent-shield.yml' +} + +@test "F5 integration: a tier-correct v- RING pin is accepted (no finding) (#861)" { + stub_check "jobs: + agent-shield: + uses: $R@agent-shield/v3-stable + secrets: inherit" [ "$status" -eq 0 ] - maj_accept "agent-shield-reusable" " uses: $R@agent-shield/v1-ring0" "markets" # wrong tier → drift - [ "$status" -ne 0 ] + [ -z "$output" ] +} + +@test "F5 integration: a wrong-tier v RING pin is FLAGGED (#861)" { + stub_check "jobs: + agent-shield: + uses: $R@agent-shield/v3-ring0 + secrets: inherit" + [ "$status" -eq 0 ] + echo "$output" | grep -q 'FLAGGED:non-stub-agent-shield.yml' } # --------------------------------------------------------------------------- diff --git a/test/scripts/compliance-audit/dev-lead-stub-channel-sync.bats b/test/scripts/compliance-audit/dev-lead-stub-channel-sync.bats index 36da20ee4..e5b589dcb 100644 --- a/test/scripts/compliance-audit/dev-lead-stub-channel-sync.bats +++ b/test/scripts/compliance-audit/dev-lead-stub-channel-sync.bats @@ -8,6 +8,11 @@ # # The helpers below mirror the extraction and matching logic in the script so # the check can be exercised without needing live gh API calls. +# +# #657 F5 (#861): the dev-lead stub must now pin the major-scoped v-form +# `dev-lead/v-` — a bare `dev-lead/` pin is drift. So the mirror +# helpers extract the FULL `v-` channel, return empty for a bare pin, +# and match agent_ref against the full v-form channel. bats_require_minimum_version 1.5.0 @@ -15,8 +20,9 @@ bats_require_minimum_version 1.5.0 # Pure helpers that mirror the script's logic # # _extract_uses_channel: pulls the channel name out of the uses: pin line. -# Returns the channel string (e.g. "stable", "ring0") on stdout, or empty -# string when no matching pin is found. +# Returns the full v-form channel string (e.g. "v3-stable", "v1-ring0") on +# stdout, or empty string when no matching v-form pin is found (a bare +# `dev-lead/stable` pin yields empty — it is drift under F5). # # _agent_ref_matches_channel: returns 0 when agent_ref pins exactly $channel. # --------------------------------------------------------------------------- @@ -24,7 +30,7 @@ bats_require_minimum_version 1.5.0 _extract_uses_channel() { local decoded="$1" printf '%s\n' "$decoded" | \ - sed -nE 's#^[[:space:]]*uses:[[:space:]]*petry-projects/\.github-private/\.github/workflows/dev-lead-reusable\.yml@dev-lead/(stable|next|ring[0-9]+)([[:space:]]|$).*#\1#p' + sed -nE 's#^[[:space:]]*uses:[[:space:]]*petry-projects/\.github-private/\.github/workflows/dev-lead-reusable\.yml@dev-lead/(v[0-9]+-(stable|next|ring[0-9]+))([[:space:]]|$).*#\1#p' } _agent_ref_matches_channel() { @@ -36,36 +42,42 @@ _agent_ref_matches_channel() { # Channel extraction from uses: pin # --------------------------------------------------------------------------- -@test "extracts 'stable' from a stable pin" { - decoded=" uses: petry-projects/.github-private/.github/workflows/dev-lead-reusable.yml@dev-lead/stable" +@test "extracts 'v3-stable' from a v-form stable pin" { + decoded=" uses: petry-projects/.github-private/.github/workflows/dev-lead-reusable.yml@dev-lead/v3-stable" run _extract_uses_channel "$decoded" [ "$status" -eq 0 ] - [ "$output" = "stable" ] + [ "$output" = "v3-stable" ] } -@test "extracts 'next' from a next pin" { - decoded=" uses: petry-projects/.github-private/.github/workflows/dev-lead-reusable.yml@dev-lead/next" +@test "extracts 'v2-next' from a v-form next pin" { + decoded=" uses: petry-projects/.github-private/.github/workflows/dev-lead-reusable.yml@dev-lead/v2-next" run _extract_uses_channel "$decoded" [ "$status" -eq 0 ] - [ "$output" = "next" ] + [ "$output" = "v2-next" ] } -@test "extracts 'ring0' from a ring0 pin" { - decoded=" uses: petry-projects/.github-private/.github/workflows/dev-lead-reusable.yml@dev-lead/ring0" +@test "extracts 'v1-ring0' from a v-form ring0 pin" { + decoded=" uses: petry-projects/.github-private/.github/workflows/dev-lead-reusable.yml@dev-lead/v1-ring0" run _extract_uses_channel "$decoded" [ "$status" -eq 0 ] - [ "$output" = "ring0" ] + [ "$output" = "v1-ring0" ] } -@test "extracts 'ring1' from a ring1 pin" { - decoded=" uses: petry-projects/.github-private/.github/workflows/dev-lead-reusable.yml@dev-lead/ring1" +@test "extracts 'v4-ring1' from a v-form ring1 pin" { + decoded=" uses: petry-projects/.github-private/.github/workflows/dev-lead-reusable.yml@dev-lead/v4-ring1" run _extract_uses_channel "$decoded" [ "$status" -eq 0 ] - [ "$output" = "ring1" ] + [ "$output" = "v4-ring1" ] +} + +@test "extraction returns empty for a bare (non-v-form) pin — it is drift under F5" { + decoded=" uses: petry-projects/.github-private/.github/workflows/dev-lead-reusable.yml@dev-lead/stable" + run _extract_uses_channel "$decoded" + [ "$output" = "" ] } @test "extraction ignores a commented-out uses line" { - decoded=" # uses: petry-projects/.github-private/.github/workflows/dev-lead-reusable.yml@dev-lead/stable" + decoded=" # uses: petry-projects/.github-private/.github/workflows/dev-lead-reusable.yml@dev-lead/v3-stable" run _extract_uses_channel "$decoded" [ "$output" = "" ] } @@ -86,49 +98,50 @@ _agent_ref_matches_channel() { # agent_ref channel matching — happy path (no split-brain) # --------------------------------------------------------------------------- -@test "agent_ref stable matches channel stable" { - decoded=" agent_ref: dev-lead/stable" - run _agent_ref_matches_channel "$decoded" "stable" +@test "agent_ref v3-stable matches channel v3-stable" { + decoded=" agent_ref: dev-lead/v3-stable" + run _agent_ref_matches_channel "$decoded" "v3-stable" [ "$status" -eq 0 ] } -@test "agent_ref next matches channel next" { - decoded=" agent_ref: dev-lead/next" - run _agent_ref_matches_channel "$decoded" "next" +@test "agent_ref v2-next matches channel v2-next" { + decoded=" agent_ref: dev-lead/v2-next" + run _agent_ref_matches_channel "$decoded" "v2-next" [ "$status" -eq 0 ] } -@test "agent_ref ring0 matches channel ring0" { - decoded=" agent_ref: dev-lead/ring0" - run _agent_ref_matches_channel "$decoded" "ring0" +@test "agent_ref v1-ring0 matches channel v1-ring0" { + decoded=" agent_ref: dev-lead/v1-ring0" + run _agent_ref_matches_channel "$decoded" "v1-ring0" [ "$status" -eq 0 ] } # --------------------------------------------------------------------------- -# Split-brain detection — uses: and agent_ref on different channels +# Split-brain detection — uses: and agent_ref on different channels. Under F5 +# this includes a major mismatch on the same tier (v3-stable vs v2-stable). # --------------------------------------------------------------------------- -@test "agent_ref stable does NOT match channel ring0 (split-brain)" { - decoded=" agent_ref: dev-lead/stable" - run _agent_ref_matches_channel "$decoded" "ring0" +@test "agent_ref v3-stable does NOT match channel v3-ring0 (split-brain)" { + decoded=" agent_ref: dev-lead/v3-stable" + run _agent_ref_matches_channel "$decoded" "v3-ring0" [ "$status" -ne 0 ] } -@test "agent_ref next does NOT match channel stable (split-brain)" { - decoded=" agent_ref: dev-lead/next" - run _agent_ref_matches_channel "$decoded" "stable" +@test "agent_ref v2-next does NOT match channel v2-stable (split-brain)" { + decoded=" agent_ref: dev-lead/v2-next" + run _agent_ref_matches_channel "$decoded" "v2-stable" [ "$status" -ne 0 ] } -@test "agent_ref ring0 does NOT match channel ring1 (split-brain)" { - decoded=" agent_ref: dev-lead/ring0" - run _agent_ref_matches_channel "$decoded" "ring1" +@test "agent_ref v3-stable does NOT match channel v2-stable (major split-brain)" { + decoded=" agent_ref: dev-lead/v3-stable" + run _agent_ref_matches_channel "$decoded" "v2-stable" [ "$status" -ne 0 ] } -@test "agent_ref ring1 does NOT match channel ring0 (split-brain)" { - decoded=" agent_ref: dev-lead/ring1" - run _agent_ref_matches_channel "$decoded" "ring0" +@test "agent_ref bare dev-lead/stable does NOT match a v-form channel" { + decoded=" agent_ref: dev-lead/stable" + run _agent_ref_matches_channel "$decoded" "v3-stable" [ "$status" -ne 0 ] } @@ -143,28 +156,28 @@ on: branches: [main] jobs: dev-lead: - uses: petry-projects/.github-private/.github/workflows/dev-lead-reusable.yml@dev-lead/ring0 + uses: petry-projects/.github-private/.github/workflows/dev-lead-reusable.yml@dev-lead/v1-ring0 with: - agent_ref: dev-lead/ring0 + agent_ref: dev-lead/v1-ring0 secrets: inherit EOF ) run _extract_uses_channel "$decoded" [ "$status" -eq 0 ] - [ "$output" = "ring0" ] + [ "$output" = "v1-ring0" ] } @test "matching agent_ref in a full stub passes when channels agree" { decoded=$(cat <<'EOF' jobs: dev-lead: - uses: petry-projects/.github-private/.github/workflows/dev-lead-reusable.yml@dev-lead/ring0 + uses: petry-projects/.github-private/.github/workflows/dev-lead-reusable.yml@dev-lead/v1-ring0 with: - agent_ref: dev-lead/ring0 + agent_ref: dev-lead/v1-ring0 EOF ) channel=$(_extract_uses_channel "$decoded") - [ "$channel" = "ring0" ] + [ "$channel" = "v1-ring0" ] run _agent_ref_matches_channel "$decoded" "$channel" [ "$status" -eq 0 ] } @@ -173,13 +186,13 @@ EOF decoded=$(cat <<'EOF' jobs: dev-lead: - uses: petry-projects/.github-private/.github/workflows/dev-lead-reusable.yml@dev-lead/ring0 + uses: petry-projects/.github-private/.github/workflows/dev-lead-reusable.yml@dev-lead/v1-ring0 with: - agent_ref: dev-lead/stable + agent_ref: dev-lead/v1-stable EOF ) channel=$(_extract_uses_channel "$decoded") - [ "$channel" = "ring0" ] + [ "$channel" = "v1-ring0" ] run _agent_ref_matches_channel "$decoded" "$channel" [ "$status" -ne 0 ] } diff --git a/test/scripts/deploy-standard-workflows/emit-vform.bats b/test/scripts/deploy-standard-workflows/emit-vform.bats index 0bd915745..e5e590f74 100644 --- a/test/scripts/deploy-standard-workflows/emit-vform.bats +++ b/test/scripts/deploy-standard-workflows/emit-vform.bats @@ -102,6 +102,29 @@ refs/tags/auto-rebase/v1.0.0" ! echo "$output" | grep -q 'Would open PR' } +@test "drift check flags a BARE-tier stub as drift when the agent has a release (#861)" { + export GH_RELEASE_REFS="refs/tags/auto-rebase/v2.0.0" + # markets (stable tier) still pins the bare @auto-rebase/stable; with a v2 release + # the bare form is drift → deploy re-pins to the tier v-form. + GH_CONTENT_B64="$(stub_pinning auto-rebase/stable)"; export GH_CONTENT_B64 + install_gh_stub + run env GH_TOKEN=x bash "$SCRIPT" --dry-run --repo markets --workflow auto-rebase.yml + [ "$status" -eq 0 ] + ! echo "$output" | grep -q 'already compliant' + echo "$output" | grep -qF '@auto-rebase/v2-stable' + echo "$output" | grep -qE 'Would open PR for markets .* auto-rebase.yml' +} + +@test "drift check keeps a BARE-tier stub compliant when the agent has NO release (#861)" { + unset GH_RELEASE_REFS # no release → no current major → bare tier stays compliant + GH_CONTENT_B64="$(stub_pinning auto-rebase/stable)"; export GH_CONTENT_B64 + install_gh_stub + run env GH_TOKEN=x bash "$SCRIPT" --dry-run --repo markets --workflow auto-rebase.yml + [ "$status" -eq 0 ] + echo "$output" | grep -q 'already compliant' + ! echo "$output" | grep -q 'Would open PR' +} + @test "drift check still flags a WRONG-tier v-tier stub" { export GH_RELEASE_REFS="refs/tags/auto-rebase/v2.0.0" # v2-ring0 on TalkTerm (a ring1 repo) is the wrong tier → drift.