From 55c9f56a2a545c51edb8ec497f2786d73e2a418e Mon Sep 17 00:00:00 2001 From: Alexandre <44178713+alexbelgium@users.noreply.github.com> Date: Thu, 23 Jul 2026 13:11:48 +0200 Subject: [PATCH] chore: apply PR 2897 review fixes --- .../workflows/pr2897_fix_review_comments.yml | 272 ++++++++++++++++++ 1 file changed, 272 insertions(+) create mode 100644 .github/workflows/pr2897_fix_review_comments.yml diff --git a/.github/workflows/pr2897_fix_review_comments.yml b/.github/workflows/pr2897_fix_review_comments.yml new file mode 100644 index 0000000000..930cd397c4 --- /dev/null +++ b/.github/workflows/pr2897_fix_review_comments.yml @@ -0,0 +1,272 @@ +# yamllint disable rule:line-length +--- +name: Apply PR 2897 review fixes + +on: + push: + branches: + - agent/ai-issue-triage-fixes + paths: + - .github/workflows/pr2897_fix_review_comments.yml + +permissions: + contents: write + +jobs: + apply: + if: github.actor != 'github-actions[bot]' + runs-on: ubuntu-latest + steps: + - name: Checkout PR branch + uses: actions/checkout@v7 + with: + fetch-depth: 0 + + - name: Apply reviewed fixes + run: | + set -euo pipefail + python3 <<'PY' + from pathlib import Path + + def replace_exact(text: str, old: str, new: str, *, count: int = 1) -> str: + actual = text.count(old) + if actual < count: + raise SystemExit(f"Expected at least {count} occurrence(s), found {actual}: {old[:80]!r}") + return text.replace(old, new, count) + + validator_path = Path('.github/scripts/validate_ai_patch.sh') + validator = validator_path.read_text() + validator = replace_exact( + validator, + '''} + + mapfile -t changed_files < <( + git diff --cached --name-only --diff-filter=ACMRDTUXB "$base_ref" -- | + sed '/^$/d' + ) + ''', + '''} + + version_is_greater() { + local old_version="$1" + local new_version="$2" + + ruby -e '\'' + require "rubygems" + old_version = Gem::Version.new(ARGV.fetch(0)) + new_version = Gem::Version.new(ARGV.fetch(1)) + exit(new_version > old_version ? 0 : 1) + '\'' "$old_version" "$new_version" + } + + mapfile -d '\''\0'\'' -t changed_files < <( + git diff --cached --no-renames --name-only -z \\ + --diff-filter=ACMRDTUXB "$base_ref" -- + ) + ''') + validator = replace_exact( + validator, + 'git diff --cached --numstat "$base_ref" -- |', + 'git diff --cached --no-renames --numstat "$base_ref" -- |') + validator = replace_exact( + validator, + ''' if ! printf '%s\\n' "${changed_files[@]}" | grep -Fxq "$addon/CHANGELOG.md"; then + echo "Changed add-on '$addon' must update CHANGELOG.md." >&2 + exit 1 + fi + ''', + ''' changelog_changed=false + for file in "${changed_files[@]}"; do + if [[ "$file" == "$addon/CHANGELOG.md" ]]; then + changelog_changed=true + break + fi + done + if [[ "$changelog_changed" != true ]]; then + echo "Changed add-on '$addon' must update CHANGELOG.md." >&2 + exit 1 + fi + ''') + validator = replace_exact( + validator, + ''' if [[ -z "$new_version" || "$old_version" == "$new_version" ]]; then + echo "Changed add-on '$addon' must change its version value." >&2 + exit 1 + fi + ''', + ''' if [[ -z "$old_version" || -z "$new_version" ]]; then + echo "Changed add-on '$addon' must have readable old and new version values." >&2 + exit 1 + fi + + if ! version_is_greater "$old_version" "$new_version"; then + echo "Changed add-on '$addon' must increase its version value ($old_version -> $new_version)." >&2 + exit 1 + fi + ''') + validator_path.write_text(validator) + + workflow_path = Path('.github/workflows/on_issues_ai.yml') + workflow = workflow_path.read_text() + workflow = replace_exact( + workflow, + ''' - name: Checkout repository + uses: actions/checkout@v7 + ''', + ''' - name: Checkout repository + uses: actions/checkout@v7 + with: + persist-credentials: false + ''', + count=2) + workflow = replace_exact( + workflow, + ''' category="$(jq -r '.category' "$RUNNER_TEMP/triage.json")" + addon="$(jq -r '.addon // ""' "$RUNNER_TEMP/triage.json")" + existing_addon=false + if [[ -n "$addon" && "$addon" != */* && "$addon" != "." && "$addon" != ".." ]] && + [[ -f "$addon/config.yaml" || -f "$addon/config.json" ]]; then + existing_addon=true + fi + + echo "addon=$addon" >> "$GITHUB_OUTPUT" + echo "category=$category" >> "$GITHUB_OUTPUT" + echo "confidence=$(jq -r '.confidence' "$RUNNER_TEMP/triage.json")" >> "$GITHUB_OUTPUT" + echo "existing_addon=$existing_addon" >> "$GITHUB_OUTPUT" + echo "risk=$(jq -r '.risk' "$RUNNER_TEMP/triage.json")" >> "$GITHUB_OUTPUT" + ''', + ''' category="$(jq -r '.category' "$RUNNER_TEMP/triage.json")" + addon="$(jq -r '.addon // ""' "$RUNNER_TEMP/triage.json")" + confidence="$(jq -r '.confidence' "$RUNNER_TEMP/triage.json")" + risk="$(jq -r '.risk' "$RUNNER_TEMP/triage.json")" + + case "$category" in + question | missing_information | bug | improvement | new_addon_request | unsupported | spam) ;; + *) echo "Invalid triage category: $category" >&2; exit 1 ;; + esac + case "$risk" in + low | medium | high) ;; + *) echo "Invalid triage risk: $risk" >&2; exit 1 ;; + esac + + existing_addon=false + if [[ -n "$addon" ]] && + jq -e --arg addon "$addon" 'index($addon) != null' <<< "$addon_catalog" > /dev/null; then + existing_addon=true + else + addon="" + fi + + write_output() { + local name="$1" + local value="$2" + if [[ "$value" == *$'\\n'* || "$value" == *$'\\r'* ]]; then + echo "Refusing multiline GitHub output '$name'." >&2 + exit 1 + fi + printf '%s=%s\\n' "$name" "$value" >> "$GITHUB_OUTPUT" + } + + write_output addon "$addon" + write_output category "$category" + write_output confidence "$confidence" + write_output existing_addon "$existing_addon" + write_output risk "$risk" + ''') + workflow = replace_exact(workflow, ' allow-users: "*"\n', '') + workflow_path.write_text(workflow) + + readme_path = Path('.github/ai/README.md') + readme = readme_path.read_text() + readme = replace_exact( + readme, + '''The validator independently rejects new top-level add-on directories and Codex + never merges pull requests. + ''', + '''The validator independently rejects new top-level add-on directories and Codex + never merges pull requests. The Codex Action keeps its default authorization, so only + users with repository write access can trigger its execution; external issue authors + cannot run it merely by opening an issue. + ''') + readme_path.write_text(readme) + PY + + - name: Validate scripts and workflow + run: | + set -euo pipefail + bash -n .github/scripts/validate_ai_patch.sh + jq empty .github/ai/triage-schema.json + ruby -e 'require "yaml"; YAML.safe_load(File.read(ARGV.fetch(0)), aliases: true)' .github/workflows/on_issues_ai.yml + ruby <<'RUBY' + require "yaml" + workflow = YAML.safe_load(File.read('.github/workflows/on_issues_ai.yml'), aliases: true) + workflow.fetch('jobs').each do |job_name, job| + job.fetch('steps', []).each_with_index do |step, index| + next unless step['run'] + path = "/tmp/#{job_name}-#{index}.sh" + File.write(path, step['run']) + abort "bash syntax failed for #{job_name} step #{index}" unless system('bash', '-n', path) + end + end + RUBY + + - name: Test validator security cases + run: | + set -euo pipefail + work="$RUNNER_TEMP/validator-tests" + mkdir -p "$work" + cp .github/scripts/validate_ai_patch.sh "$work/validator.sh" + cd "$work" + git init -q + git config user.name test + git config user.email test@example.com + mkdir -p .github/ai addon + printf 'protected\n' > .github/ai/README.md + printf 'version: 1.2.0\n' > addon/config.yaml + printf '# Changelog\n' > addon/CHANGELOG.md + printf '#!/bin/sh\necho old\n' > addon/file.sh + git add -A + git commit -qm base + base=HEAD + + printf '#!/bin/sh\necho new\n' > addon/file.sh + printf '# Changelog\n- change\n' > addon/CHANGELOG.md + printf 'version: 1.2.1\n' > addon/config.yaml + git add -A + AI_REQUEST_CATEGORY=bug bash ./validator.sh "$base" > /dev/null + git reset --hard -q HEAD + + printf '#!/bin/sh\necho new\n' > addon/file.sh + printf '# Changelog\n- change\n' > addon/CHANGELOG.md + printf 'version: 1.1.9\n' > addon/config.yaml + git add -A + ! AI_REQUEST_CATEGORY=bug bash ./validator.sh "$base" > downgrade.log 2>&1 + grep -q 'must increase' downgrade.log + git reset --hard -q HEAD + + git mv .github/ai/README.md addon/AI_README.md + printf '# Changelog\n- change\n' > addon/CHANGELOG.md + printf 'version: 1.2.1\n' > addon/config.yaml + git add -A + ! AI_REQUEST_CATEGORY=bug bash ./validator.sh "$base" > rename.log 2>&1 + grep -q 'Disallowed path changed by AI: .github/ai/README.md' rename.log + git reset --hard -q HEAD + + bad=$'.github/bad\nname' + printf 'x\n' > "$bad" + git add -A + ! AI_REQUEST_CATEGORY=bug bash ./validator.sh "$base" > newline.log 2>&1 + grep -q 'Disallowed path changed by AI: .github/bad' newline.log + + - name: Commit fixes and remove helper + run: | + set -euo pipefail + rm -f .github/workflows/pr2897_fix_review_comments.yml + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add .github/ai/README.md \ + .github/scripts/validate_ai_patch.sh \ + .github/workflows/on_issues_ai.yml \ + .github/workflows/pr2897_fix_review_comments.yml + git commit -m "fix: address AI workflow review findings" + git push origin HEAD:agent/ai-issue-triage-fixes