1+ name : Claude AI Rollback Safety Check
2+
3+ on :
4+ pull_request :
5+ types : [opened, synchronize]
6+
7+ jobs :
8+ # Security gate: Check if user is dotCMS organization member
9+ #
10+ # REQUIREMENTS FOR CLAUDE ACCESS:
11+ # 1. Must be a member of the dotCMS organization
12+ # 2. Membership must be set to PUBLIC visibility
13+ #
14+ # TROUBLESHOOTING: If blocked, visit https://github.com/orgs/dotCMS/people
15+ # and ensure your membership is public (click "Make public" if needed)
16+ security-check :
17+ runs-on : ubuntu-latest
18+ permissions :
19+ contents : read # Allow repository checkout
20+ # Note: Organization membership checking uses fine-grained token
21+ # so no additional GITHUB_TOKEN permissions needed for that API
22+ outputs :
23+ authorized : ${{ steps.membership-check.outputs.is_member }}
24+ steps :
25+ - name : Checkout repository
26+ uses : actions/checkout@v4
27+
28+ - name : Check organization membership
29+ id : membership-check
30+ uses : ./.github/actions/security/org-membership-check
31+ with :
32+ username : ${{ github.event.pull_request.user.login || github.actor }}
33+
34+ - name : Log security decision
35+ run : |
36+ if [ "${{ steps.membership-check.outputs.is_member }}" = "true" ]; then
37+ echo "✅ Access granted: User is a dotCMS organization member"
38+ else
39+ echo "❌ Access denied: User failed dotCMS organization membership check"
40+ echo ""
41+ echo "📋 TROUBLESHOOTING: If you are a dotCMS team member:"
42+ echo " 1. Visit https://github.com/orgs/dotCMS/people"
43+ echo " 2. Ensure your membership is set to 'Public'"
44+ echo " 3. If you're not listed, contact an organization owner"
45+ echo ""
46+ echo "::warning::Unauthorized user attempted to trigger Claude workflow: ${{ github.event.pull_request.user.login || github.actor }}"
47+ fi
48+
49+ # Preflight: clear stale AI rollback labels so each push is re-evaluated from scratch,
50+ # and skip the whole AI evaluation when the PR author has already classified the change
51+ # via a "Human: ..." label (skip cascades to claude-rollback-safety-check via needs/success()).
52+ preflight-clear-stale-labels :
53+ name : Clear stale AI rollback labels
54+ needs : security-check
55+ if : |
56+ needs.security-check.outputs.authorized == 'true' &&
57+ !contains(github.event.pull_request.labels.*.name, 'Human: Safe To Rollback') &&
58+ !contains(github.event.pull_request.labels.*.name, 'Human: Not Safe To Rollback')
59+ runs-on : ubuntu-latest
60+ permissions :
61+ pull-requests : write
62+ issues : write
63+ steps :
64+ - name : Remove stale AI rollback labels so this push is re-evaluated
65+ env :
66+ GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
67+ PR_NUMBER : ${{ github.event.pull_request.number }}
68+ REPO : ${{ github.repository }}
69+ run : |
70+ # `gh pr edit --remove-label` returns non-zero if the label is not on the PR.
71+ # Swallow that so we don't need a pre-check fetch; any other failure is cosmetic.
72+ for label in "AI: Not Safe To Rollback" "AI: Safe To Rollback"; do
73+ gh pr edit "$PR_NUMBER" --repo "$REPO" --remove-label "$label" || true
74+ done
75+
76+ # Rollback safety analysis — runs on every PR push
77+ claude-rollback-safety-check :
78+ needs : [security-check, preflight-clear-stale-labels]
79+ # Cancel in-progress check when a new push arrives — always analyze latest state
80+ concurrency :
81+ group : claude-rollback-${{ github.event.pull_request.number }}
82+ cancel-in-progress : true
83+ if : needs.security-check.outputs.authorized == 'true'
84+ permissions :
85+ contents : write
86+ id-token : write
87+ pull-requests : write
88+ issues : write
89+ uses : dotCMS/ai-workflows/.github/workflows/claude-orchestrator.yml@v2.1.0
90+ with :
91+ trigger_mode : automatic
92+ prompt : |
93+ You are a dotCMS rollback-safety analyst. Determine whether the changes in this PR are safe to roll back to the previous release.
94+
95+ STEP 1 — Read the rollback-unsafe categories reference:
96+ cat docs/core/ROLLBACK_UNSAFE_CATEGORIES.md
97+
98+ STEP 2 — Get the full PR diff:
99+ git diff ${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }}
100+
101+ STEP 3 — Analyze the diff against EVERY category in the reference document.
102+ Focus on: database migrations (runonce tasks), Elasticsearch mapping changes,
103+ data model changes, API contract changes, and any structural storage changes.
104+ Ignore pure UI, test-only, or documentation changes unless they touch an unsafe category.
105+
106+ STEP 4a — If the changes match one or more unsafe categories, post this comment on the PR
107+ using: gh pr comment ${{ github.event.pull_request.number }} --body "..."
108+
109+ Format:
110+ Pull Request Unsafe to Rollback!!!
111+ - Category: <category ID and name, e.g. "C-1 — Structural Data Model Change">
112+ - Risk Level: <🔴 CRITICAL / 🟠 HIGH / 🟡 MEDIUM / 🟢 LOW>
113+ - Why it's unsafe: <specific explanation tied to the actual code changed>
114+ - Code that makes it unsafe: <file path(s) and the specific lines or block>
115+ - Alternative (if possible): <the safer alternative from the reference, adapted to this change>
116+
117+ If multiple categories match, repeat the block for each one.
118+
119+ Then add the label: gh pr edit ${{ github.event.pull_request.number }} --add-label "AI: Not Safe To Rollback"
120+
121+ STEP 4b — If the changes do NOT match any unsafe category:
122+ Only add the label: gh pr edit ${{ github.event.pull_request.number }} --add-label "AI: Safe To Rollback"
123+ No comment needed.
124+
125+ Be specific: quote actual file names and code lines, not generic descriptions.
126+ claude_args : ' --allowedTools "Bash(git diff*),Bash(git log*),Bash(cat docs/core/ROLLBACK_UNSAFE_CATEGORIES.md),Bash(gh pr comment*),Bash(gh pr edit*)"'
127+ timeout_minutes : 15
128+ runner : ubuntu-latest
129+ enable_mention_detection : false
130+ secrets :
131+ ANTHROPIC_API_KEY : ${{ secrets.ANTHROPIC_API_KEY }}
0 commit comments