From 7b4daf2c64c2faa4727dced0e4d196a26d85be50 Mon Sep 17 00:00:00 2001 From: Brad Anderson Date: Wed, 15 Apr 2026 21:02:33 -0400 Subject: [PATCH 1/5] feat: add GitHub Actions workflows and Claude Code commands Add per-environment deploy workflows using EnforceAuth/deploy-action@v1: - dev/stage trigger on path changes, test, then deploy directly - prod adds dry-run validation, concurrency lock, and environment protection - PR check workflow runs OPA tests only for changed environments Add Claude Code slash commands (commit, pr, review, address-pr-feedback, handoff) adapted from ea-financial for this OPA policy repo. --- .claude/commands/address-pr-feedback.md | 98 +++++++++++++++++++++++++ .claude/commands/commit.md | 41 +++++++++++ .claude/commands/handoff.md | 91 +++++++++++++++++++++++ .claude/commands/pr.md | 38 ++++++++++ .claude/commands/review.md | 70 ++++++++++++++++++ .github/workflows/deploy-dev.yml | 42 +++++++++++ .github/workflows/deploy-prod.yml | 81 ++++++++++++++++++++ .github/workflows/deploy-stage.yml | 42 +++++++++++ .github/workflows/pr-check.yml | 66 +++++++++++++++++ 9 files changed, 569 insertions(+) create mode 100644 .claude/commands/address-pr-feedback.md create mode 100644 .claude/commands/commit.md create mode 100644 .claude/commands/handoff.md create mode 100644 .claude/commands/pr.md create mode 100644 .claude/commands/review.md create mode 100644 .github/workflows/deploy-dev.yml create mode 100644 .github/workflows/deploy-prod.yml create mode 100644 .github/workflows/deploy-stage.yml create mode 100644 .github/workflows/pr-check.yml diff --git a/.claude/commands/address-pr-feedback.md b/.claude/commands/address-pr-feedback.md new file mode 100644 index 0000000..a5bc3a7 --- /dev/null +++ b/.claude/commands/address-pr-feedback.md @@ -0,0 +1,98 @@ +# Address PR Feedback + +Fetch and address all review feedback on the current PR. + +## Instructions + +### 1. Identify the PR + +```bash +gh pr view --json number,url,state --jq '{number, url, state}' +``` + +If no PR exists for the current branch, abort with a message. + +### 2. Fetch review comments + +```bash +# Get all review comments (inline) +gh api "repos/{owner}/{repo}/pulls/{pr}/comments" --paginate \ + --jq '.[] | select(.in_reply_to_id == null) | {id, author: .user.login, path, line, body: (.body[:300])}' + +# Get IDs already replied to +gh api "repos/{owner}/{repo}/pulls/{pr}/comments" --paginate \ + --jq '[.[] | select(.in_reply_to_id != null) | .in_reply_to_id] | unique' + +# Get general PR comments +gh pr view --json comments --jq '.comments[] | {author: .author.login, bodyPreview: (.body[:300])}' + +# Get reviews +gh api "repos/{owner}/{repo}/pulls/{pr}/reviews" --paginate \ + --jq '.[] | select(.body | length > 0) | {id, author: .user.login, state, body: (.body[:500])}' +``` + +Cross-reference to find **unreplied** comments. + +### 3. Identify actionable feedback + +For each comment, determine: +1. **Valid concern** — fix it +2. **False positive** — reply explaining why +3. **Stale** — code was already changed/removed since the comment was posted +4. **Ambiguous** — ask the user which direction to take + +### 4. Present decisions for approval + +**STOP and present a table** before making any changes: + +| # | Source | File | Line | Comment Summary | Decision | Rationale | +|---|--------|------|------|-----------------|----------|-----------| +| 1 | reviewer | `path/file.rego` | 42 | Brief summary | Fix / Dismiss / Stale | Why | + +**Wait for the user to approve** before proceeding. + +### 5. Address each item + +For valid concerns: +1. Read the file and understand the context +2. Apply the fix +3. Reply to the review comment thread: + ```bash + gh api "repos/{owner}/{repo}/pulls/{pr}/comments/{comment_id}/replies" \ + -X POST -f body="Fixed — " + ``` + +For false positives: +1. Reply explaining why: + ```bash + gh api "repos/{owner}/{repo}/pulls/{pr}/comments/{comment_id}/replies" \ + -X POST -f body="" + ``` + +### 6. Run tests + +After all fixes are applied, run OPA tests for affected environments: + +```bash +opa test dev/ -v # if dev/ was changed +opa test stage/ -v # if stage/ was changed +opa test prod/ -v # if prod/ was changed +``` + +### 7. Commit and push + +If any code changes were made: + +```bash +git add -A +git commit -m "fix: address PR review feedback" +git push +``` + +### 8. Report summary + +- **Fixed**: List of issues fixed +- **Dismissed**: False positives with reasoning +- **Stale**: Comments on already-changed code +- **Needs input**: Ambiguous items requiring user decision +- **Tests**: Pass/fail status diff --git a/.claude/commands/commit.md b/.claude/commands/commit.md new file mode 100644 index 0000000..9717ca6 --- /dev/null +++ b/.claude/commands/commit.md @@ -0,0 +1,41 @@ +# Commit Changes + +Stage and commit the current changes with a well-crafted message. + +## Instructions + +When activated, commit the current working tree changes: + +1. **Sync with remote**: + - Run `git fetch origin main` to get latest upstream + - Run `git log HEAD..origin/main --oneline` to check if main has moved ahead + - If it has, warn the user but don't rebase automatically + +2. **Ensure we're not on main**: + - Run `git branch --show-current` + - If on `main`, create a new feature branch: + - Look at the staged/unstaged changes to infer a branch name + - Run `git checkout -b feat/` + - Inform the user of the new branch name + +3. **Review changes**: + - Run `git diff --stat` and `git diff --staged --stat` to see what's changed + - If nothing is staged, run `git add -A` to stage everything + - Run `git diff --staged --stat` to confirm what will be committed + +4. **Generate commit message**: + - Use conventional commit format: `type: short description` + - Types: `feat`, `fix`, `refactor`, `chore`, `docs`, `test` + - Scope is optional but useful for env-specific changes: `feat(dev): ...`, `fix(prod): ...` + - If the change is substantial, add a body paragraph separated by a blank line + - Body should explain **what** changed and **why**, not how (the diff shows how) + - Keep the subject line under 72 characters + +5. **Commit**: + ```bash + git commit -m "" + ``` + +6. **Report** the commit hash and summary to the user + +If the user provides arguments (e.g., `/commit "fix(prod): tighten bot threshold"`), use that as the commit message instead of generating one. diff --git a/.claude/commands/handoff.md b/.claude/commands/handoff.md new file mode 100644 index 0000000..2f5d17f --- /dev/null +++ b/.claude/commands/handoff.md @@ -0,0 +1,91 @@ +--- +name: handoff +description: Structured handoff between agents +--- + +# Session Handoff + +Generate a structured handoff for the next session. Output must be self-contained — the next agent should be able to resume without reading this session's history. + +## Instructions + +### 1. Gather current state + +Run these in parallel: + +```bash +git branch --show-current +git log --oneline -5 +git status --short +gh pr view --json number,url,state,title 2>/dev/null || echo "No PR for this branch" +``` + +### 2. Produce the handoff document + +Output the following sections in order: + +--- + +## Handoff — {feature or change description} + +**Date:** {today} +**Branch:** `{current branch}` +**PR:** {PR URL and state, or "none"} + +--- + +### Completed this session + +Table format. Be specific — what was done, where, and what artifact proves it. + +| Item | Status | Artifact | +|------|--------|----------| +| ... | Done | PR #NNN / commit SHA | + +--- + +### Current state + +- What branch is active and whether it's clean or has uncommitted work +- Open PRs and their review status +- Which environments have been tested + +--- + +### Next session: what to do + +Be concrete. Name the files, the policies, the commands. The next agent should be able to start without asking clarifying questions. + +**Immediate (< 30 min):** +- [ ] {specific action} + +**Main work:** +- Files: {list the key files to read first} +- Environments affected: {dev, stage, prod} +- Test command: {e.g., `opa test dev/ -v`} + +--- + +### Decisions made this session + +Any non-obvious choices that the next agent needs to know about. Keep it brief — one bullet per decision, include the rationale. + +- {decision}: {why} + +--- + +### Open questions / blockers + +What is unresolved that may block next session. If none, omit this section. + +--- + +### Key files for next session + +List the files the next agent should read first, in priority order. Use relative paths. + +## Notes + +- Do NOT summarize the whole session history — focus on what the next session needs +- If the branch has uncommitted changes, say so explicitly with what they are +- If tests were left failing, say so and why — do not hide failures diff --git a/.claude/commands/pr.md b/.claude/commands/pr.md new file mode 100644 index 0000000..42c1947 --- /dev/null +++ b/.claude/commands/pr.md @@ -0,0 +1,38 @@ +# Create Pull Request + +Create a pull request for the current branch. + +## Instructions + +When activated, create a pull request for the current branch: + +1. **Verify branch state**: + - Run `git branch --show-current` to get the current branch name + - Ensure we're not on `main` (abort if so) + - Run `git log main..HEAD --oneline` to see commits to include + +2. **Push the branch** (if not already pushed): + - Run `git push -u origin ` + +3. **Determine which environments are affected**: + - Run `git diff main..HEAD --stat` to see changed files + - Identify which env folders (`dev/`, `stage/`, `prod/`) are touched + - This informs the PR title and description + +4. **Generate PR title and body**: + - Title: Conventional commit format (e.g., `feat(dev): add loyalty tier rate limiting`) + - Use env scope when changes target specific environments: `(dev)`, `(stage)`, `(prod)`, or omit for cross-env changes + - Body should include: + - **Summary**: Brief description of what this PR does + - **Environments affected**: Which env folders have changes + - **Changes**: Bullet list of key policy changes + - **Testing**: OPA test commands to verify (e.g., `opa test dev/ -v`) + +5. **Create the PR**: + ```bash + gh pr create --title "" --body "<body>" --base main --assignee @me + ``` + +6. **Report the PR URL** to the user + +If the user provides arguments (e.g., `/pr "Custom title"`), use that as the PR title instead of generating one. diff --git a/.claude/commands/review.md b/.claude/commands/review.md new file mode 100644 index 0000000..c24d62e --- /dev/null +++ b/.claude/commands/review.md @@ -0,0 +1,70 @@ +# Code Review Branch Commits + +Review all commits on the current branch since diverging from main. + +## Prerequisites + +**IMPORTANT**: Before starting the review, check if this is a fresh context/session: +- If there is prior conversation history in this session (e.g., you helped write the code being reviewed), STOP immediately +- Inform the user: "Code reviews should be done in a fresh context to avoid bias. Please start a new Claude Code session and run /review there." +- A reviewer should not be the same "person" who wrote the code + +## Instructions + +When activated, perform a thorough review of OPA/Rego policy changes: + +1. **Gather changes**: + - Run `git log main..HEAD --oneline` to see all commits on this branch + - Run `git diff main..HEAD` to see all changes + - For each file changed, read enough context to understand the changes + +2. **Run OPA tests**: + - Determine which environments were modified from the diff + - Run `opa test <env>/ -v` for each affected environment + - All tests must pass before proceeding + +3. **Review the changes** across these dimensions: + + **Policy correctness**: + - Are allow/deny rules logically correct? + - Are there unintended overlaps or gaps in rules? + - Could any rule combination produce unexpected results? + - Are default deny semantics preserved? + + **Environment consistency**: + - If a policy was changed in one env, should it also change in others? + - Are intentional env differences documented (see README table)? + - Do data.json files match the expected schema for their env? + + **Test coverage**: + - Do new/changed rules have corresponding test cases? + - Are edge cases covered (empty input, missing fields, boundary values)? + - Are both allow and deny paths tested? + + **Security**: + - Are authorization boundaries correct (role escalation, cross-tenant access)? + - Are there overly permissive rules? + - Is input validation sufficient? + + **Code quality**: + - Are package names and rule names consistent with conventions? + - Is there duplicated logic that should be in `shared/`? + - Are helper rules used appropriately? + +4. **Present the review** with severity-rated findings: + + | # | Severity | File | Finding | Recommendation | + |---|----------|------|---------|----------------| + | 1 | 🔴 Critical | path/to/file.rego | ... | ... | + | 2 | 🟡 Warning | path/to/file.rego | ... | ... | + | 3 | 🔵 Suggestion | path/to/file.rego | ... | ... | + +## Follow-up + +After presenting the review, present a **fix plan table** for the user to approve before making any changes: + +| # | File | Issue | Proposed Action | +|---|------|-------|-----------------| +| 1 | path/to/file.rego | Brief description | Fix / Skip / Ask | + +**Wait for the user to approve the plan** before applying fixes. Run `opa test` for affected environments after all fixes are applied. diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml new file mode 100644 index 0000000..8204858 --- /dev/null +++ b/.github/workflows/deploy-dev.yml @@ -0,0 +1,42 @@ +# Deploys dev policies to EnforceAuth when dev/ changes on main. +# Entity ID configured in EA_ENTITY_ID secret (dev environment). +name: "Deploy: dev" + +on: + workflow_dispatch: + push: + branches: [main] + paths: + - "dev/**" + +permissions: + id-token: write + contents: read + +jobs: + test: + name: Test dev policies + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: open-policy-agent/setup-opa@v2 + with: + version: latest + - run: opa test dev/ -v + + deploy: + name: Deploy via EnforceAuth + needs: test + runs-on: ubuntu-latest + environment: dev + steps: + - uses: actions/checkout@v4 + + - name: Deploy Policy Bundle + uses: EnforceAuth/deploy-action@v1 + with: + entity-id: ${{ secrets.EA_ENTITY_ID }} + api-url: ${{ vars.EA_API_URL }} + environment: dev + wait-for-completion: true + timeout-minutes: 10 diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml new file mode 100644 index 0000000..af87276 --- /dev/null +++ b/.github/workflows/deploy-prod.yml @@ -0,0 +1,81 @@ +# Deploys prod policies to EnforceAuth when prod/ changes on main. +# Entity ID configured in EA_ENTITY_ID secret (production environment). +# +# Safety controls: +# - Dry-run validation before deploy +# - GitHub environment protection rules (configure required reviewers +# on the "production" environment in repo settings) +# - Concurrency lock prevents parallel production deploys +name: "Deploy: prod" + +on: + workflow_dispatch: + push: + branches: [main] + paths: + - "prod/**" + +concurrency: + group: deploy-production + cancel-in-progress: false + +permissions: + id-token: write + contents: read + +jobs: + test: + name: Test prod policies + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: open-policy-agent/setup-opa@v2 + with: + version: latest + - run: opa test prod/ -v + + dry-run: + name: Validate (Dry Run) + needs: test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Dry-run deployment + id: dry-run + uses: EnforceAuth/deploy-action@v1 + with: + entity-id: ${{ secrets.EA_ENTITY_ID }} + api-url: ${{ vars.EA_API_URL }} + environment: production + dry-run: true + + - name: Print dry-run summary + if: always() + run: | + echo "### Dry-Run Summary" >> "$GITHUB_STEP_SUMMARY" + if [ "${{ steps.dry-run.outcome }}" = "success" ]; then + echo "- **Status:** ${{ steps.dry-run.outputs.status }}" >> "$GITHUB_STEP_SUMMARY" + echo "- **Bundle Version:** ${{ steps.dry-run.outputs.bundle-version }}" >> "$GITHUB_STEP_SUMMARY" + else + echo "- **Status:** unknown (step failed)" >> "$GITHUB_STEP_SUMMARY" + echo "- **Bundle Version:** N/A" >> "$GITHUB_STEP_SUMMARY" + fi + + deploy: + name: Deploy via EnforceAuth + needs: dry-run + if: needs.dry-run.result == 'success' + runs-on: ubuntu-latest + environment: production + steps: + - uses: actions/checkout@v4 + + - name: Deploy Policy Bundle + uses: EnforceAuth/deploy-action@v1 + with: + entity-id: ${{ secrets.EA_ENTITY_ID }} + api-url: ${{ vars.EA_API_URL }} + environment: production + wait-for-completion: true + timeout-minutes: 10 diff --git a/.github/workflows/deploy-stage.yml b/.github/workflows/deploy-stage.yml new file mode 100644 index 0000000..59cd26a --- /dev/null +++ b/.github/workflows/deploy-stage.yml @@ -0,0 +1,42 @@ +# Deploys stage policies to EnforceAuth when stage/ changes on main. +# Entity ID configured in EA_ENTITY_ID secret (stage environment). +name: "Deploy: stage" + +on: + workflow_dispatch: + push: + branches: [main] + paths: + - "stage/**" + +permissions: + id-token: write + contents: read + +jobs: + test: + name: Test stage policies + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: open-policy-agent/setup-opa@v2 + with: + version: latest + - run: opa test stage/ -v + + deploy: + name: Deploy via EnforceAuth + needs: test + runs-on: ubuntu-latest + environment: stage + steps: + - uses: actions/checkout@v4 + + - name: Deploy Policy Bundle + uses: EnforceAuth/deploy-action@v1 + with: + entity-id: ${{ secrets.EA_ENTITY_ID }} + api-url: ${{ vars.EA_API_URL }} + environment: stage + wait-for-completion: true + timeout-minutes: 10 diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml new file mode 100644 index 0000000..c1b230f --- /dev/null +++ b/.github/workflows/pr-check.yml @@ -0,0 +1,66 @@ +# On pull requests, test only the environments whose policies were changed. + +name: PR Check + +on: + pull_request: + branches: [main] + +jobs: + # ----- Detect which environment folders were touched ----- + changes: + name: Detect changed environments + runs-on: ubuntu-latest + outputs: + dev: ${{ steps.filter.outputs.dev }} + stage: ${{ steps.filter.outputs.stage }} + prod: ${{ steps.filter.outputs.prod }} + steps: + - uses: actions/checkout@v4 + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + dev: + - 'dev/**' + stage: + - 'stage/**' + prod: + - 'prod/**' + + # ----- Conditional test jobs — only run for changed envs ----- + test-dev: + name: Test dev policies + needs: changes + if: needs.changes.outputs.dev == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: open-policy-agent/setup-opa@v2 + with: + version: latest + - run: opa test dev/ -v + + test-stage: + name: Test stage policies + needs: changes + if: needs.changes.outputs.stage == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: open-policy-agent/setup-opa@v2 + with: + version: latest + - run: opa test stage/ -v + + test-prod: + name: Test prod policies + needs: changes + if: needs.changes.outputs.prod == 'true' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: open-policy-agent/setup-opa@v2 + with: + version: latest + - run: opa test prod/ -v From d60ed00e381c6944d6c2aa39cafc15c9b57b9546 Mon Sep 17 00:00:00 2001 From: Brad Anderson <brad@enforceauth.com> Date: Wed, 15 Apr 2026 21:06:00 -0400 Subject: [PATCH 2/5] feat: add Regal lint as local composite action Add .github/actions/rego-lint composite action that lints only changed Rego files in a PR. Wire it into all deploy workflows (lint + test run in parallel before deploy) and the PR check workflow. --- .github/actions/rego-lint/action.yml | 33 ++++++++++++++++++++++++++++ .github/workflows/deploy-dev.yml | 11 +++++++++- .github/workflows/deploy-prod.yml | 11 +++++++++- .github/workflows/deploy-stage.yml | 11 +++++++++- .github/workflows/pr-check.yml | 10 +++++++++ 5 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 .github/actions/rego-lint/action.yml diff --git a/.github/actions/rego-lint/action.yml b/.github/actions/rego-lint/action.yml new file mode 100644 index 0000000..8dfaa24 --- /dev/null +++ b/.github/actions/rego-lint/action.yml @@ -0,0 +1,33 @@ +name: Rego Lint (Regal) +description: Lint changed Rego files using Regal + +inputs: + regal-version: + description: Regal version to install + required: false + default: v0.39.0 + +runs: + using: composite + steps: + - uses: open-policy-agent/setup-regal@v1 + with: + version: ${{ inputs.regal-version }} + + - name: Get changed Rego files + id: changed + shell: bash + run: | + FILES=$(git diff --name-only --diff-filter=ACMR origin/${{ github.base_ref }}...HEAD -- '*.rego') + if [ -z "$FILES" ]; then + echo "has_files=false" >> "$GITHUB_OUTPUT" + else + DIRS=$(echo "$FILES" | xargs -I{} dirname {} | sort -u | tr '\n' ' ') + echo "has_files=true" >> "$GITHUB_OUTPUT" + echo "dirs=$DIRS" >> "$GITHUB_OUTPUT" + fi + + - name: Lint changed Rego files + if: steps.changed.outputs.has_files == 'true' + shell: bash + run: regal lint ${{ steps.changed.outputs.dirs }} diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index 8204858..4f5e48f 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -14,6 +14,15 @@ permissions: contents: read jobs: + lint: + name: Lint dev policies + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: ./.github/actions/rego-lint + test: name: Test dev policies runs-on: ubuntu-latest @@ -26,7 +35,7 @@ jobs: deploy: name: Deploy via EnforceAuth - needs: test + needs: [lint, test] runs-on: ubuntu-latest environment: dev steps: diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml index af87276..c58ec37 100644 --- a/.github/workflows/deploy-prod.yml +++ b/.github/workflows/deploy-prod.yml @@ -24,6 +24,15 @@ permissions: contents: read jobs: + lint: + name: Lint prod policies + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: ./.github/actions/rego-lint + test: name: Test prod policies runs-on: ubuntu-latest @@ -36,7 +45,7 @@ jobs: dry-run: name: Validate (Dry Run) - needs: test + needs: [lint, test] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/deploy-stage.yml b/.github/workflows/deploy-stage.yml index 59cd26a..ab760ad 100644 --- a/.github/workflows/deploy-stage.yml +++ b/.github/workflows/deploy-stage.yml @@ -14,6 +14,15 @@ permissions: contents: read jobs: + lint: + name: Lint stage policies + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: ./.github/actions/rego-lint + test: name: Test stage policies runs-on: ubuntu-latest @@ -26,7 +35,7 @@ jobs: deploy: name: Deploy via EnforceAuth - needs: test + needs: [lint, test] runs-on: ubuntu-latest environment: stage steps: diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index c1b230f..3695a46 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -7,6 +7,16 @@ on: branches: [main] jobs: + # ----- Lint changed Rego files with Regal ----- + lint: + name: Rego Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: ./.github/actions/rego-lint + # ----- Detect which environment folders were touched ----- changes: name: Detect changed environments From a9019604aaf8fffef20d1571d988c5401ebe5ebe Mon Sep 17 00:00:00 2001 From: Brad Anderson <brad@enforceauth.com> Date: Wed, 15 Apr 2026 21:41:16 -0400 Subject: [PATCH 3/5] fix: address PR review feedback --- .claude/commands/address-pr-feedback.md | 18 ++++++++++++++++-- .github/actions/rego-lint/action.yml | 11 +++++++++-- .github/workflows/deploy-dev.yml | 6 ++++-- .github/workflows/deploy-prod.yml | 9 +++++++-- .github/workflows/deploy-stage.yml | 6 ++++-- .github/workflows/pr-check.yml | 6 +++--- 6 files changed, 43 insertions(+), 13 deletions(-) diff --git a/.claude/commands/address-pr-feedback.md b/.claude/commands/address-pr-feedback.md index a5bc3a7..9403f6b 100644 --- a/.claude/commands/address-pr-feedback.md +++ b/.claude/commands/address-pr-feedback.md @@ -56,18 +56,32 @@ For each comment, determine: For valid concerns: 1. Read the file and understand the context 2. Apply the fix -3. Reply to the review comment thread: +3. Reply using the correct channel by source type: ```bash + # Inline review comment gh api "repos/{owner}/{repo}/pulls/{pr}/comments/{comment_id}/replies" \ -X POST -f body="Fixed — <brief explanation>" ``` + ```bash + # General PR comment (issue comment on PR) + gh pr comment {pr} --body "Addressed: <brief explanation>" + ``` + ```bash + # Review-body level feedback + gh pr review {pr} --comment --body "Addressed review feedback: <brief explanation>" + ``` For false positives: -1. Reply explaining why: +1. Reply using the matching channel (inline, general, or review): ```bash + # Inline review comment gh api "repos/{owner}/{repo}/pulls/{pr}/comments/{comment_id}/replies" \ -X POST -f body="<explanation of why this is safe>" ``` + ```bash + # General PR comment + gh pr comment {pr} --body "<explanation of why this is safe>" + ``` ### 6. Run tests diff --git a/.github/actions/rego-lint/action.yml b/.github/actions/rego-lint/action.yml index 8dfaa24..dcca8d3 100644 --- a/.github/actions/rego-lint/action.yml +++ b/.github/actions/rego-lint/action.yml @@ -18,7 +18,14 @@ runs: id: changed shell: bash run: | - FILES=$(git diff --name-only --diff-filter=ACMR origin/${{ github.base_ref }}...HEAD -- '*.rego') + if [[ "${{ github.event_name }}" == "pull_request" && -n "${{ github.base_ref }}" ]]; then + FILES=$(git diff --name-only --diff-filter=ACMR "origin/${{ github.base_ref }}...HEAD" -- '*.rego') + elif [[ -n "${{ github.event.before }}" && "${{ github.event.before }}" != "0000000000000000000000000000000000000000" ]]; then + FILES=$(git diff --name-only --diff-filter=ACMR "${{ github.event.before }}...HEAD" -- '*.rego') + else + # Fallback for manual/initial runs — lint all Rego files + FILES=$(git ls-files '*.rego') + fi if [ -z "$FILES" ]; then echo "has_files=false" >> "$GITHUB_OUTPUT" else @@ -30,4 +37,4 @@ runs: - name: Lint changed Rego files if: steps.changed.outputs.has_files == 'true' shell: bash - run: regal lint ${{ steps.changed.outputs.dirs }} + run: regal lint "${{ steps.changed.outputs.dirs }}" diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index 4f5e48f..6c1077a 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -10,7 +10,6 @@ on: - "dev/**" permissions: - id-token: write contents: read jobs: @@ -30,7 +29,7 @@ jobs: - uses: actions/checkout@v4 - uses: open-policy-agent/setup-opa@v2 with: - version: latest + version: "1.15" - run: opa test dev/ -v deploy: @@ -38,6 +37,9 @@ jobs: needs: [lint, test] runs-on: ubuntu-latest environment: dev + permissions: + id-token: write + contents: read steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml index c58ec37..c0b881b 100644 --- a/.github/workflows/deploy-prod.yml +++ b/.github/workflows/deploy-prod.yml @@ -20,7 +20,6 @@ concurrency: cancel-in-progress: false permissions: - id-token: write contents: read jobs: @@ -40,13 +39,16 @@ jobs: - uses: actions/checkout@v4 - uses: open-policy-agent/setup-opa@v2 with: - version: latest + version: "1.15" - run: opa test prod/ -v dry-run: name: Validate (Dry Run) needs: [lint, test] runs-on: ubuntu-latest + permissions: + id-token: write + contents: read steps: - uses: actions/checkout@v4 @@ -77,6 +79,9 @@ jobs: if: needs.dry-run.result == 'success' runs-on: ubuntu-latest environment: production + permissions: + id-token: write + contents: read steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/deploy-stage.yml b/.github/workflows/deploy-stage.yml index ab760ad..61e7306 100644 --- a/.github/workflows/deploy-stage.yml +++ b/.github/workflows/deploy-stage.yml @@ -10,7 +10,6 @@ on: - "stage/**" permissions: - id-token: write contents: read jobs: @@ -30,7 +29,7 @@ jobs: - uses: actions/checkout@v4 - uses: open-policy-agent/setup-opa@v2 with: - version: latest + version: "1.15" - run: opa test stage/ -v deploy: @@ -38,6 +37,9 @@ jobs: needs: [lint, test] runs-on: ubuntu-latest environment: stage + permissions: + id-token: write + contents: read steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 3695a46..20b664a 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -48,7 +48,7 @@ jobs: - uses: actions/checkout@v4 - uses: open-policy-agent/setup-opa@v2 with: - version: latest + version: "1.15" - run: opa test dev/ -v test-stage: @@ -60,7 +60,7 @@ jobs: - uses: actions/checkout@v4 - uses: open-policy-agent/setup-opa@v2 with: - version: latest + version: "1.15" - run: opa test stage/ -v test-prod: @@ -72,5 +72,5 @@ jobs: - uses: actions/checkout@v4 - uses: open-policy-agent/setup-opa@v2 with: - version: latest + version: "1.15" - run: opa test prod/ -v From 26683ef6ec511414c03be7de2024daf5a6d14a93 Mon Sep 17 00:00:00 2001 From: Brad Anderson <brad@enforceauth.com> Date: Wed, 15 Apr 2026 21:47:37 -0400 Subject: [PATCH 4/5] =?UTF-8?q?fix:=20remove=20OPA=20test=20jobs=20?= =?UTF-8?q?=E2=80=94=20EA=20platform=20handles=20testing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy-dev.yml | 12 +----- .github/workflows/deploy-prod.yml | 12 +----- .github/workflows/deploy-stage.yml | 12 +----- .github/workflows/pr-check.yml | 61 +----------------------------- 4 files changed, 4 insertions(+), 93 deletions(-) diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index 6c1077a..c5d45bd 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -22,19 +22,9 @@ jobs: fetch-depth: 0 - uses: ./.github/actions/rego-lint - test: - name: Test dev policies - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: open-policy-agent/setup-opa@v2 - with: - version: "1.15" - - run: opa test dev/ -v - deploy: name: Deploy via EnforceAuth - needs: [lint, test] + needs: lint runs-on: ubuntu-latest environment: dev permissions: diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml index c0b881b..9b6ebaf 100644 --- a/.github/workflows/deploy-prod.yml +++ b/.github/workflows/deploy-prod.yml @@ -32,19 +32,9 @@ jobs: fetch-depth: 0 - uses: ./.github/actions/rego-lint - test: - name: Test prod policies - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: open-policy-agent/setup-opa@v2 - with: - version: "1.15" - - run: opa test prod/ -v - dry-run: name: Validate (Dry Run) - needs: [lint, test] + needs: lint runs-on: ubuntu-latest permissions: id-token: write diff --git a/.github/workflows/deploy-stage.yml b/.github/workflows/deploy-stage.yml index 61e7306..2db17b7 100644 --- a/.github/workflows/deploy-stage.yml +++ b/.github/workflows/deploy-stage.yml @@ -22,19 +22,9 @@ jobs: fetch-depth: 0 - uses: ./.github/actions/rego-lint - test: - name: Test stage policies - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: open-policy-agent/setup-opa@v2 - with: - version: "1.15" - - run: opa test stage/ -v - deploy: name: Deploy via EnforceAuth - needs: [lint, test] + needs: lint runs-on: ubuntu-latest environment: stage permissions: diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 20b664a..dc61a01 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -1,4 +1,4 @@ -# On pull requests, test only the environments whose policies were changed. +# On pull requests, lint changed Rego files. name: PR Check @@ -7,7 +7,6 @@ on: branches: [main] jobs: - # ----- Lint changed Rego files with Regal ----- lint: name: Rego Lint runs-on: ubuntu-latest @@ -16,61 +15,3 @@ jobs: with: fetch-depth: 0 - uses: ./.github/actions/rego-lint - - # ----- Detect which environment folders were touched ----- - changes: - name: Detect changed environments - runs-on: ubuntu-latest - outputs: - dev: ${{ steps.filter.outputs.dev }} - stage: ${{ steps.filter.outputs.stage }} - prod: ${{ steps.filter.outputs.prod }} - steps: - - uses: actions/checkout@v4 - - uses: dorny/paths-filter@v3 - id: filter - with: - filters: | - dev: - - 'dev/**' - stage: - - 'stage/**' - prod: - - 'prod/**' - - # ----- Conditional test jobs — only run for changed envs ----- - test-dev: - name: Test dev policies - needs: changes - if: needs.changes.outputs.dev == 'true' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: open-policy-agent/setup-opa@v2 - with: - version: "1.15" - - run: opa test dev/ -v - - test-stage: - name: Test stage policies - needs: changes - if: needs.changes.outputs.stage == 'true' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: open-policy-agent/setup-opa@v2 - with: - version: "1.15" - - run: opa test stage/ -v - - test-prod: - name: Test prod policies - needs: changes - if: needs.changes.outputs.prod == 'true' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: open-policy-agent/setup-opa@v2 - with: - version: "1.15" - - run: opa test prod/ -v From 385ebeec13dfcf04df73284954b857d55f86e8bc Mon Sep 17 00:00:00 2001 From: Brad Anderson <brad@enforceauth.com> Date: Wed, 15 Apr 2026 21:57:51 -0400 Subject: [PATCH 5/5] fix: address PR review feedback (round 2) --- .claude/commands/address-pr-feedback.md | 6 +++++- .github/actions/rego-lint/action.yml | 2 +- .github/workflows/deploy-prod.yml | 9 +++++---- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.claude/commands/address-pr-feedback.md b/.claude/commands/address-pr-feedback.md index 9403f6b..cbe8ed6 100644 --- a/.claude/commands/address-pr-feedback.md +++ b/.claude/commands/address-pr-feedback.md @@ -82,6 +82,10 @@ For false positives: # General PR comment gh pr comment {pr} --body "<explanation of why this is safe>" ``` + ```bash + # Review-body level feedback + gh pr review {pr} --comment --body "<explanation of why this is safe>" + ``` ### 6. Run tests @@ -108,5 +112,5 @@ git push - **Fixed**: List of issues fixed - **Dismissed**: False positives with reasoning - **Stale**: Comments on already-changed code -- **Needs input**: Ambiguous items requiring user decision +- **Needs user input**: Ambiguous items requiring user decision - **Tests**: Pass/fail status diff --git a/.github/actions/rego-lint/action.yml b/.github/actions/rego-lint/action.yml index dcca8d3..020e18c 100644 --- a/.github/actions/rego-lint/action.yml +++ b/.github/actions/rego-lint/action.yml @@ -29,7 +29,7 @@ runs: if [ -z "$FILES" ]; then echo "has_files=false" >> "$GITHUB_OUTPUT" else - DIRS=$(echo "$FILES" | xargs -I{} dirname {} | sort -u | tr '\n' ' ') + DIRS=$(printf '%s\n' "$FILES" | while IFS= read -r f; do dirname "$f"; done | sort -u | tr '\n' ' ') echo "has_files=true" >> "$GITHUB_OUTPUT" echo "dirs=$DIRS" >> "$GITHUB_OUTPUT" fi diff --git a/.github/workflows/deploy-prod.yml b/.github/workflows/deploy-prod.yml index 9b6ebaf..ba28ca9 100644 --- a/.github/workflows/deploy-prod.yml +++ b/.github/workflows/deploy-prod.yml @@ -27,7 +27,7 @@ jobs: name: Lint prod policies runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: fetch-depth: 0 - uses: ./.github/actions/rego-lint @@ -35,12 +35,13 @@ jobs: dry-run: name: Validate (Dry Run) needs: lint + if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest permissions: id-token: write contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Dry-run deployment id: dry-run @@ -66,14 +67,14 @@ jobs: deploy: name: Deploy via EnforceAuth needs: dry-run - if: needs.dry-run.result == 'success' + if: github.ref == 'refs/heads/main' && needs.dry-run.result == 'success' runs-on: ubuntu-latest environment: production permissions: id-token: write contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Deploy Policy Bundle uses: EnforceAuth/deploy-action@v1