diff --git a/.github/workflows/auto_merge_codex_pr.yml b/.github/workflows/auto_merge_codex_pr.yml deleted file mode 100644 index 8feb378..0000000 --- a/.github/workflows/auto_merge_codex_pr.yml +++ /dev/null @@ -1,194 +0,0 @@ -name: Auto Merge Codex Remediation PR - -"on": - workflow_run: - workflows: ["CI"] - types: [completed] - -jobs: - auto-merge: - if: github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_repository.full_name == github.repository && startsWith(github.event.workflow_run.head_branch, 'codex/monthly-review-issue-') && contains(fromJSON('["true","True","TRUE"]'), vars.CODEX_AUDIT_AUTO_MERGE) - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - contents: write - issues: write - pull-requests: write - - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - ref: ${{ github.event.repository.default_branch }} - - - name: Resolve Codex remediation PR - id: pr - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -euo pipefail - mkdir -p data/output/codex_auto_merge - branch="${{ github.event.workflow_run.head_branch }}" - pr_payload=$(gh pr list --repo "${{ github.repository }}" --state open --head "${branch}" --json number,headRefOid,headRepository,isCrossRepository \ - --jq '[.[] | select(.isCrossRepository == false and .headRepository.nameWithOwner == "${{ github.repository }}")][0] // {}') - pr_number=$(python3 -c 'import json,sys; print(json.load(sys.stdin).get("number", ""))' <<<"${pr_payload}") - if [ -z "${pr_number}" ]; then - echo "No same-repository open PR found for ${branch}." >> "$GITHUB_STEP_SUMMARY" - exit 0 - fi - pr_head_sha=$(python3 -c 'import json,sys; print(json.load(sys.stdin).get("headRefOid", ""))' <<<"${pr_payload}") - if [ "${pr_head_sha}" != "${{ github.event.workflow_run.head_sha }}" ]; then - echo "Skipping auto-merge for stale CI success on ${branch}; workflow_run head SHA does not match the current PR head." >> "$GITHUB_STEP_SUMMARY" - exit 0 - fi - echo "pr_number=${pr_number}" >> "$GITHUB_OUTPUT" - - - name: Evaluate merge guard - id: merge_guard - if: steps.pr.outputs.pr_number != '' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -euo pipefail - gh pr view "${{ steps.pr.outputs.pr_number }}" --repo "${{ github.repository }}" \ - --json number,isDraft,body,url,files,changedFiles,additions,deletions,reviewDecision,labels,baseRefName,headRefName,headRepositoryOwner,headRepository,isCrossRepository > data/output/codex_auto_merge/pr.json - gh api --paginate --slurp \ - "/repos/${{ github.repository }}/pulls/${{ steps.pr.outputs.pr_number }}/files?per_page=100" \ - > data/output/codex_auto_merge/pr_files_pages.json - python3 - <<'PY' - import json - from pathlib import Path - - output_dir = Path("data/output/codex_auto_merge") - pr_path = output_dir / "pr.json" - pr = json.loads(pr_path.read_text(encoding="utf-8")) - pages = json.loads((output_dir / "pr_files_pages.json").read_text(encoding="utf-8")) - files = [] - for page in pages: - if not isinstance(page, list): - continue - for item in page: - if not isinstance(item, dict): - continue - files.append( - { - "path": item.get("filename", ""), - "status": item.get("status", ""), - "additions": item.get("additions"), - "deletions": item.get("deletions"), - "previous_filename": item.get("previous_filename", ""), - } - ) - pr["files"] = files - pr_path.write_text(json.dumps(pr, indent=2, sort_keys=True) + "\n", encoding="utf-8") - PY - python3 scripts/evaluate_codex_pr_merge.py \ - --pr-json data/output/codex_auto_merge/pr.json \ - --summary-file data/output/codex_auto_merge/summary.md \ - --decision-file data/output/codex_auto_merge/decision.json \ - --expected-base-ref "${{ github.event.repository.default_branch }}" \ - --expected-head-ref "${{ github.event.workflow_run.head_branch }}" \ - --expected-head-owner "${{ github.repository_owner }}" \ - --expected-head-repository "${{ github.repository }}" \ - --require-same-repository - python3 - <<'PY' >> "$GITHUB_OUTPUT" - import json - from pathlib import Path - - decision = json.loads(Path("data/output/codex_auto_merge/decision.json").read_text(encoding="utf-8")) - print(f"should_merge={str(bool(decision['should_merge'])).lower()}") - print(f"reason={decision['reason']}") - print(f"risk_level={decision['risk_level']}") - PY - - - name: Append merge guard summary - if: steps.pr.outputs.pr_number != '' - run: cat data/output/codex_auto_merge/summary.md >> "$GITHUB_STEP_SUMMARY" - - - name: Comment auto-merge guard decision - if: steps.pr.outputs.pr_number != '' && steps.merge_guard.outputs.should_merge != 'true' - continue-on-error: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -euo pipefail - python3 scripts/post_codex_auto_merge_decision_comment.py \ - --repo "${{ github.repository }}" \ - --pr-json data/output/codex_auto_merge/pr.json \ - --decision-json data/output/codex_auto_merge/decision.json \ - --output-file data/output/codex_auto_merge/guard_decision_comment.md \ - --sync-labels - - - name: Check guarded auto-merge readiness before merge - id: merge_readiness - if: steps.merge_guard.outputs.should_merge == 'true' - continue-on-error: true - env: - GITHUB_TOKEN: ${{ secrets.CODEX_AUDIT_READINESS_TOKEN || secrets.GITHUB_TOKEN }} - CODEX_AUDIT_REQUIRED_STATUS_CHECKS: ${{ vars.CODEX_AUDIT_REQUIRED_STATUS_CHECKS || 'test' }} - run: | - set -euo pipefail - python3 scripts/check_codex_auto_merge_readiness.py \ - --repo "${{ github.repository }}" \ - --branch "${{ github.event.repository.default_branch }}" \ - --auto-merge true \ - --required-status-checks "${CODEX_AUDIT_REQUIRED_STATUS_CHECKS}" \ - --summary-file data/output/codex_auto_merge/readiness.md - - - name: Comment merge-time readiness failure - if: steps.merge_guard.outputs.should_merge == 'true' && steps.merge_readiness.outcome != 'success' - continue-on-error: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -euo pipefail - python3 - <<'PY' - import json - from pathlib import Path - - decision_path = Path("data/output/codex_auto_merge/decision.json") - decision = json.loads(decision_path.read_text(encoding="utf-8")) - risk_reasons = list(decision.get("risk_reasons") or []) - risk_reasons.append("merge-time readiness check failed; see codex_auto_merge/readiness.md") - metadata_errors = list(decision.get("metadata_errors") or []) - metadata_errors.append("merge-time readiness check failed") - decision.update( - { - "should_merge": False, - "reason": "merge_readiness_failed", - "risk_level": "high", - "risk_reasons": risk_reasons, - "metadata_errors": metadata_errors, - } - ) - Path("data/output/codex_auto_merge/readiness_decision.json").write_text( - json.dumps(decision, indent=2, sort_keys=True) + "\n", - encoding="utf-8", - ) - PY - python3 scripts/post_codex_auto_merge_decision_comment.py \ - --repo "${{ github.repository }}" \ - --pr-json data/output/codex_auto_merge/pr.json \ - --decision-json data/output/codex_auto_merge/readiness_decision.json \ - --output-file data/output/codex_auto_merge/readiness_guard_decision_comment.md \ - --sync-labels - - - name: Fail on merge-time readiness failure - if: steps.merge_guard.outputs.should_merge == 'true' && steps.merge_readiness.outcome != 'success' - run: | - echo "Guarded auto-merge readiness failed at merge time; see readiness.md and readiness_guard_decision_comment.md." >&2 - exit 1 - - - name: Merge Codex remediation PR - if: steps.merge_guard.outputs.should_merge == 'true' && steps.merge_readiness.outcome == 'success' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: gh pr merge "${{ steps.pr.outputs.pr_number }}" --repo "${{ github.repository }}" --rebase --delete-branch --match-head-commit "${{ github.event.workflow_run.head_sha }}" - - - name: Upload Codex auto-merge diagnostics - if: always() - uses: actions/upload-artifact@v7 - with: - name: codex-auto-merge-${{ github.run_id }} - path: data/output/codex_auto_merge/ - if-no-files-found: warn diff --git a/.github/workflows/codex_pr_feedback.yml b/.github/workflows/codex_pr_feedback.yml deleted file mode 100644 index 05c1e58..0000000 --- a/.github/workflows/codex_pr_feedback.yml +++ /dev/null @@ -1,542 +0,0 @@ -name: Codex PR Feedback - -"on": - workflow_run: - workflows: ["CI"] - types: [completed] - pull_request_review: - types: [submitted] - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" - CODEX_AUDIT_ENABLED: ${{ vars.CODEX_AUDIT_ENABLED || 'true' }} - CODEX_AUDIT_BRIDGE_REPOSITORY: ${{ vars.CODEX_AUDIT_BRIDGE_REPOSITORY || 'QuantStrategyLab/AIAuditBridge' }} - CODEX_AUDIT_BRIDGE_REF: ${{ vars.CODEX_AUDIT_BRIDGE_REF || 'main' }} - CODEX_AUDIT_MODE: ${{ vars.CODEX_AUDIT_MODE || 'review_and_fix' }} - CODEX_AUDIT_PROVIDER: ${{ vars.CODEX_AUDIT_PROVIDER || 'auto' }} - CODEX_AUDIT_AUTO_MERGE: ${{ vars.CODEX_AUDIT_AUTO_MERGE || 'false' }} - CODEX_AUDIT_REQUIRED_STATUS_CHECKS: ${{ vars.CODEX_AUDIT_REQUIRED_STATUS_CHECKS || 'test' }} - -jobs: - ci-feedback: - if: github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'failure' && startsWith(github.event.workflow_run.head_branch, 'codex/monthly-review-issue-') && github.event.workflow_run.head_repository.full_name == github.repository - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - contents: read - issues: write - pull-requests: read - - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Post CI failure back to Codex issue - id: feedback - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BRANCH_NAME: ${{ github.event.workflow_run.head_branch }} - RUN_HEAD_SHA: ${{ github.event.workflow_run.head_sha }} - RUN_URL: ${{ github.event.workflow_run.html_url }} - RUN_NAME: ${{ github.event.workflow_run.name }} - MAX_CODEX_FEEDBACK_ROUNDS: ${{ vars.CODEX_AUDIT_MAX_FEEDBACK_ROUNDS || '3' }} - run: | - set -euo pipefail - mkdir -p data/output/codex_feedback - gh pr list --repo "${GITHUB_REPOSITORY}" --state open --head "${BRANCH_NAME}" --json number,title,url,body,headRefOid,headRepository,isCrossRepository \ - --jq '[.[] | select(.isCrossRepository == false and .headRepository.nameWithOwner == "'"${GITHUB_REPOSITORY}"'")]' > data/output/codex_feedback/pr.json - python3 - <<'PY' - import json - import os - import re - import textwrap - from pathlib import Path - - prs = json.loads(Path("data/output/codex_feedback/pr.json").read_text(encoding="utf-8")) - if not prs: - Path("data/output/codex_feedback/skip.txt").write_text("No open Codex PR found.\n", encoding="utf-8") - raise SystemExit(0) - - pr = prs[0] - if str(pr.get("headRefOid") or "") != os.environ["RUN_HEAD_SHA"]: - Path("data/output/codex_feedback/skip.txt").write_text( - "Skipping stale CI failure because the workflow_run head SHA no longer matches the current PR head.\n", - encoding="utf-8", - ) - raise SystemExit(0) - body = pr.get("body") or "" - match = re.search(r"", body) - if not match: - Path("data/output/codex_feedback/skip.txt").write_text("No source issue marker found.\n", encoding="utf-8") - raise SystemExit(0) - - issue_number = match.group(1) - comment = textwrap.dedent( - f"""\ - - ## Codex PR CI Feedback - - CI failed for the Codex remediation PR. - - - PR: {pr['url']} - - Branch: `{os.environ['BRANCH_NAME']}` - - Workflow: `{os.environ['RUN_NAME']}` - - Run: {os.environ['RUN_URL']} - - Codex should inspect the failing GitHub Actions logs, update the same PR branch, run targeted tests, and keep the PR draft until the fix is verified. - """ - ) - Path("data/output/codex_feedback/issue_number.txt").write_text(issue_number, encoding="utf-8") - Path("data/output/codex_feedback/pr_number.txt").write_text(str(pr["number"]), encoding="utf-8") - Path("data/output/codex_feedback/comment.md").write_text(comment.strip() + "\n", encoding="utf-8") - PY - if [ -f data/output/codex_feedback/issue_number.txt ]; then - issue_number="$(cat data/output/codex_feedback/issue_number.txt)" - pr_number="$(cat data/output/codex_feedback/pr_number.txt)" - policy_labels="$(python3 - <<'PY' - import sys - - from scripts.check_codex_auto_merge_readiness import DEFAULT_POLICY_PATH, ReadinessError, load_policy_labels - - try: - labels = load_policy_labels(DEFAULT_POLICY_PATH) - except ReadinessError as exc: - print( - f"::warning::Skipping stale guarded auto-merge label cleanup because auto-merge policy labels are invalid: {exc}", - file=sys.stderr, - ) - raise SystemExit(0) - print(labels["auto_merge_label"]) - print(labels["human_review_label"]) - PY - )" - guard_label="$(printf '%s\n' "${policy_labels}" | sed -n '1p')" - human_review_label="$(printf '%s\n' "${policy_labels}" | sed -n '2p')" - if [ -n "${guard_label}" ]; then - gh issue edit "${pr_number}" --repo "${GITHUB_REPOSITORY}" --remove-label "${guard_label}" || true - echo "Removed stale guarded auto-merge label ${guard_label} from PR #${pr_number} after CI failure if it existed." - else - echo "Skipped stale guarded auto-merge label cleanup after CI failure because policy labels are invalid." - fi - gh api --paginate --slurp \ - "/repos/${GITHUB_REPOSITORY}/issues/${issue_number}/comments?per_page=100" \ - > data/output/codex_feedback/comment_pages.json - python3 - <<'PY' - import json - import os - import textwrap - from pathlib import Path - - output_dir = Path("data/output/codex_feedback") - comment_pages = json.loads((output_dir / "comment_pages.json").read_text(encoding="utf-8")) - comments = [] - for page in comment_pages: - if isinstance(page, list): - comments.extend(comment.get("body") or "" for comment in page if isinstance(comment, dict)) - previous_rounds = sum(body.startswith(" - ## Codex PR Retry Limit Reached - - Automatic Codex feedback reached the retry limit. - - - Previous feedback rounds: `{previous_rounds}` - - Maximum automatic rounds: `{max_rounds}` - - The workflow removed `codex-bridge` from this issue and will try to mark the PR with the configured human-review label. Please inspect the PR and re-apply `codex-bridge` only if another automated Codex pass is still appropriate. - """ - ) - comment_path.write_text(comment.strip() + "\n", encoding="utf-8") - (output_dir / "limit_reached").write_text("true\n", encoding="utf-8") - else: - attempt = previous_rounds + 1 - comment = comment_path.read_text(encoding="utf-8").rstrip() - comment_path.write_text( - f"{comment}\n\n- Feedback round: `{attempt}` of `{max_rounds}`\n", - encoding="utf-8", - ) - PY - if [ -f data/output/codex_feedback/limit_reached ]; then - gh issue edit "${issue_number}" --repo "${GITHUB_REPOSITORY}" --remove-label codex-bridge || true - if [ -n "${human_review_label}" ]; then - gh label create "${human_review_label}" --repo "${GITHUB_REPOSITORY}" --color d93f0b --description "Codex remediation PR requires human review before merge." || true - gh issue edit "${pr_number}" --repo "${GITHUB_REPOSITORY}" --add-label "${human_review_label}" || true - echo "Marked PR #${pr_number} with human-review label ${human_review_label} after retry limit was reached." - else - echo "Skipped adding human-review label after retry limit because policy labels are invalid." - fi - echo "dispatch_feedback=false" >> "$GITHUB_OUTPUT" - else - echo "dispatch_feedback=true" >> "$GITHUB_OUTPUT" - fi - echo "issue_number=${issue_number}" >> "$GITHUB_OUTPUT" - gh issue comment "${issue_number}" --repo "${GITHUB_REPOSITORY}" --body-file data/output/codex_feedback/comment.md - else - echo "dispatch_feedback=false" >> "$GITHUB_OUTPUT" - cat data/output/codex_feedback/skip.txt >> "$GITHUB_STEP_SUMMARY" - fi - - - name: Detect Codex Audit GitHub App Credentials - id: codex_review_app_credentials - if: steps.feedback.outputs.dispatch_feedback == 'true' && contains(fromJSON('["true","True","TRUE"]'), env.CODEX_AUDIT_ENABLED) - env: - APP_ID: ${{ vars.CROSS_REPO_GITHUB_APP_ID }} - APP_PRIVATE_KEY: ${{ secrets.CROSS_REPO_GITHUB_APP_PRIVATE_KEY }} - run: | - set -euo pipefail - if [ -n "${APP_ID:-}" ] && [ -n "${APP_PRIVATE_KEY:-}" ]; then - echo "available=true" >> "$GITHUB_OUTPUT" - else - echo "available=false" >> "$GITHUB_OUTPUT" - fi - - - name: Create GitHub App Token For Codex Audit - id: codex_review_app_token - if: steps.codex_review_app_credentials.outputs.available == 'true' - continue-on-error: true - uses: actions/create-github-app-token@v3 - with: - app-id: ${{ vars.CROSS_REPO_GITHUB_APP_ID }} - private-key: ${{ secrets.CROSS_REPO_GITHUB_APP_PRIVATE_KEY }} - owner: ${{ github.repository_owner }} - repositories: | - AIAuditBridge - permission-actions: write - - - name: Check guarded auto-merge readiness - id: auto_merge_readiness - if: steps.feedback.outputs.dispatch_feedback == 'true' && contains(fromJSON('["true","True","TRUE"]'), env.CODEX_AUDIT_ENABLED) - continue-on-error: true - env: - GITHUB_TOKEN: ${{ secrets.CODEX_AUDIT_READINESS_TOKEN || secrets.GITHUB_TOKEN }} - run: | - set -euo pipefail - python scripts/check_codex_auto_merge_readiness.py \ - --repo "${GITHUB_REPOSITORY}" \ - --branch "${{ github.event.repository.default_branch || github.ref_name }}" \ - --auto-merge "${CODEX_AUDIT_AUTO_MERGE}" \ - --required-status-checks "${CODEX_AUDIT_REQUIRED_STATUS_CHECKS}" \ - --summary-file data/output/codex_feedback/codex_auto_merge_readiness.md - - - name: Dispatch Codex feedback retry - if: steps.feedback.outputs.dispatch_feedback == 'true' && contains(fromJSON('["true","True","TRUE"]'), env.CODEX_AUDIT_ENABLED) - env: - GH_TOKEN: ${{ steps.codex_review_app_token.outputs.token || secrets.CODEX_AUDIT_DISPATCH_TOKEN }} - ISSUE_NUMBER: ${{ steps.feedback.outputs.issue_number }} - SOURCE_REF: ${{ github.event.repository.default_branch || github.ref_name }} - TARGET_REPOSITORY: ${{ env.CODEX_AUDIT_BRIDGE_REPOSITORY }} - REVIEW_MODE: ${{ env.CODEX_AUDIT_MODE }} - REVIEW_PROVIDER: ${{ env.CODEX_AUDIT_PROVIDER }} - AUTO_MERGE_REQUESTED: ${{ env.CODEX_AUDIT_AUTO_MERGE }} - AUTO_MERGE_READINESS_OUTCOME: ${{ steps.auto_merge_readiness.outcome || 'skipped' }} - run: | - set -euo pipefail - if [ -z "${GH_TOKEN:-}" ]; then - echo "Codex audit feedback dispatch requires either a GitHub App token or CODEX_AUDIT_DISPATCH_TOKEN" >&2 - exit 1 - fi - if [[ ! "${TARGET_REPOSITORY}" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]]; then - echo "Invalid Codex audit repository: ${TARGET_REPOSITORY}" >&2 - exit 1 - fi - case "${REVIEW_MODE}" in - review_only|review_and_fix) ;; - *) echo "Unsupported Codex audit mode: ${REVIEW_MODE}" >&2; exit 1 ;; - esac - case "${REVIEW_PROVIDER}" in - auto|api|anthropic|codex|openai) ;; - *) echo "Unsupported Codex audit provider: ${REVIEW_PROVIDER}" >&2; exit 1 ;; - esac - auto_merge_requested="false" - if [ "${AUTO_MERGE_REQUESTED}" = "true" ] || [ "${AUTO_MERGE_REQUESTED}" = "True" ] || [ "${AUTO_MERGE_REQUESTED}" = "TRUE" ]; then - auto_merge_requested="true" - fi - auto_merge="false" - if [ "${auto_merge_requested}" = "true" ] && [ "${AUTO_MERGE_READINESS_OUTCOME}" = "success" ]; then - auto_merge="true" - fi - if [ "${auto_merge_requested}" = "true" ] && [ "${AUTO_MERGE_READINESS_OUTCOME}" != "success" ]; then - echo "Guarded auto-merge was requested, but readiness did not pass; dispatching Codex feedback retry with auto_merge=false." - fi - gh workflow run codex_audit.yml \ - --repo "${TARGET_REPOSITORY}" \ - --ref "${CODEX_AUDIT_BRIDGE_REF}" \ - --field source_repo="${GITHUB_REPOSITORY}" \ - --field source_ref="${SOURCE_REF}" \ - --field issue_number="${ISSUE_NUMBER}" \ - --field mode="${REVIEW_MODE}" \ - --field provider="${REVIEW_PROVIDER}" \ - --field auto_merge="${auto_merge}" \ - --field task="monthly_snapshot_audit" - echo "Dispatched AIAuditBridge feedback retry for issue #${ISSUE_NUMBER} to ${TARGET_REPOSITORY}" - - - name: Upload Codex feedback diagnostics - if: always() - uses: actions/upload-artifact@v7 - with: - name: codex-pr-feedback-ci-${{ github.run_id }} - path: data/output/codex_feedback/ - if-no-files-found: warn - - review-feedback: - if: github.event_name == 'pull_request_review' && github.event.review.state == 'changes_requested' && startsWith(github.event.pull_request.head.ref, 'codex/monthly-review-issue-') && github.event.pull_request.head.repo.full_name == github.repository - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - contents: read - issues: write - - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Post review feedback back to Codex issue - id: feedback - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PR_NUMBER: ${{ github.event.pull_request.number }} - PR_URL: ${{ github.event.pull_request.html_url }} - PR_BODY: ${{ github.event.pull_request.body }} - REVIEW_URL: ${{ github.event.review.html_url }} - REVIEW_AUTHOR: ${{ github.event.review.user.login }} - REVIEW_BODY: ${{ github.event.review.body }} - MAX_CODEX_FEEDBACK_ROUNDS: ${{ vars.CODEX_AUDIT_MAX_FEEDBACK_ROUNDS || '3' }} - run: | - set -euo pipefail - mkdir -p data/output/codex_feedback - python3 - <<'PY' - import os - import re - import textwrap - from pathlib import Path - - body = os.environ.get("PR_BODY") or "" - match = re.search(r"", body) - if not match: - Path("data/output/codex_feedback/skip.txt").write_text("No source issue marker found.\n", encoding="utf-8") - raise SystemExit(0) - - review_body = (os.environ.get("REVIEW_BODY") or "_No review body supplied._").strip() - comment = textwrap.dedent( - f"""\ - - ## Codex PR Review Feedback - - A review requested changes on the Codex remediation PR. - - - PR: {os.environ['PR_URL']} - - Reviewer: @{os.environ['REVIEW_AUTHOR']} - - Review: {os.environ['REVIEW_URL']} - - ### Review Body - - {review_body} - - Codex should update the same PR branch, address the requested changes, run targeted tests, and leave the PR draft until the fix is verified. - """ - ) - Path("data/output/codex_feedback/issue_number.txt").write_text(match.group(1), encoding="utf-8") - Path("data/output/codex_feedback/comment.md").write_text(comment.strip() + "\n", encoding="utf-8") - PY - if [ -f data/output/codex_feedback/issue_number.txt ]; then - issue_number="$(cat data/output/codex_feedback/issue_number.txt)" - policy_labels="$(python3 - <<'PY' - import sys - - from scripts.check_codex_auto_merge_readiness import DEFAULT_POLICY_PATH, ReadinessError, load_policy_labels - - try: - labels = load_policy_labels(DEFAULT_POLICY_PATH) - except ReadinessError as exc: - print( - f"::warning::Skipping stale guarded auto-merge label cleanup because auto-merge policy labels are invalid: {exc}", - file=sys.stderr, - ) - raise SystemExit(0) - print(labels["auto_merge_label"]) - print(labels["human_review_label"]) - PY - )" - guard_label="$(printf '%s\n' "${policy_labels}" | sed -n '1p')" - human_review_label="$(printf '%s\n' "${policy_labels}" | sed -n '2p')" - if [ -n "${guard_label}" ]; then - gh issue edit "${PR_NUMBER}" --repo "${GITHUB_REPOSITORY}" --remove-label "${guard_label}" || true - echo "Removed stale guarded auto-merge label ${guard_label} from PR #${PR_NUMBER} after requested changes if it existed." - else - echo "Skipped stale guarded auto-merge label cleanup after requested changes because policy labels are invalid." - fi - gh api --paginate --slurp \ - "/repos/${GITHUB_REPOSITORY}/issues/${issue_number}/comments?per_page=100" \ - > data/output/codex_feedback/comment_pages.json - python3 - <<'PY' - import json - import os - import textwrap - from pathlib import Path - - output_dir = Path("data/output/codex_feedback") - comment_pages = json.loads((output_dir / "comment_pages.json").read_text(encoding="utf-8")) - comments = [] - for page in comment_pages: - if isinstance(page, list): - comments.extend(comment.get("body") or "" for comment in page if isinstance(comment, dict)) - previous_rounds = sum(body.startswith(" - ## Codex PR Retry Limit Reached - - Automatic Codex feedback reached the retry limit. - - - Previous feedback rounds: `{previous_rounds}` - - Maximum automatic rounds: `{max_rounds}` - - The workflow removed `codex-bridge` from this issue and will try to mark the PR with the configured human-review label. Please inspect the PR and re-apply `codex-bridge` only if another automated Codex pass is still appropriate. - """ - ) - comment_path.write_text(comment.strip() + "\n", encoding="utf-8") - (output_dir / "limit_reached").write_text("true\n", encoding="utf-8") - else: - attempt = previous_rounds + 1 - comment = comment_path.read_text(encoding="utf-8").rstrip() - comment_path.write_text( - f"{comment}\n\n- Feedback round: `{attempt}` of `{max_rounds}`\n", - encoding="utf-8", - ) - PY - if [ -f data/output/codex_feedback/limit_reached ]; then - gh issue edit "${issue_number}" --repo "${GITHUB_REPOSITORY}" --remove-label codex-bridge || true - if [ -n "${human_review_label}" ]; then - gh label create "${human_review_label}" --repo "${GITHUB_REPOSITORY}" --color d93f0b --description "Codex remediation PR requires human review before merge." || true - gh issue edit "${PR_NUMBER}" --repo "${GITHUB_REPOSITORY}" --add-label "${human_review_label}" || true - echo "Marked PR #${PR_NUMBER} with human-review label ${human_review_label} after retry limit was reached." - else - echo "Skipped adding human-review label after retry limit because policy labels are invalid." - fi - echo "dispatch_feedback=false" >> "$GITHUB_OUTPUT" - else - echo "dispatch_feedback=true" >> "$GITHUB_OUTPUT" - fi - echo "issue_number=${issue_number}" >> "$GITHUB_OUTPUT" - gh issue comment "${issue_number}" --repo "${GITHUB_REPOSITORY}" --body-file data/output/codex_feedback/comment.md - else - echo "dispatch_feedback=false" >> "$GITHUB_OUTPUT" - cat data/output/codex_feedback/skip.txt >> "$GITHUB_STEP_SUMMARY" - fi - - - name: Detect Codex Audit GitHub App Credentials - id: codex_review_app_credentials - if: steps.feedback.outputs.dispatch_feedback == 'true' && contains(fromJSON('["true","True","TRUE"]'), env.CODEX_AUDIT_ENABLED) - env: - APP_ID: ${{ vars.CROSS_REPO_GITHUB_APP_ID }} - APP_PRIVATE_KEY: ${{ secrets.CROSS_REPO_GITHUB_APP_PRIVATE_KEY }} - run: | - set -euo pipefail - if [ -n "${APP_ID:-}" ] && [ -n "${APP_PRIVATE_KEY:-}" ]; then - echo "available=true" >> "$GITHUB_OUTPUT" - else - echo "available=false" >> "$GITHUB_OUTPUT" - fi - - - name: Create GitHub App Token For Codex Audit - id: codex_review_app_token - if: steps.codex_review_app_credentials.outputs.available == 'true' - continue-on-error: true - uses: actions/create-github-app-token@v3 - with: - app-id: ${{ vars.CROSS_REPO_GITHUB_APP_ID }} - private-key: ${{ secrets.CROSS_REPO_GITHUB_APP_PRIVATE_KEY }} - owner: ${{ github.repository_owner }} - repositories: | - AIAuditBridge - permission-actions: write - - - name: Check guarded auto-merge readiness - id: auto_merge_readiness - if: steps.feedback.outputs.dispatch_feedback == 'true' && contains(fromJSON('["true","True","TRUE"]'), env.CODEX_AUDIT_ENABLED) - continue-on-error: true - env: - GITHUB_TOKEN: ${{ secrets.CODEX_AUDIT_READINESS_TOKEN || secrets.GITHUB_TOKEN }} - run: | - set -euo pipefail - python scripts/check_codex_auto_merge_readiness.py \ - --repo "${GITHUB_REPOSITORY}" \ - --branch "${{ github.event.repository.default_branch || github.ref_name }}" \ - --auto-merge "${CODEX_AUDIT_AUTO_MERGE}" \ - --required-status-checks "${CODEX_AUDIT_REQUIRED_STATUS_CHECKS}" \ - --summary-file data/output/codex_feedback/codex_auto_merge_readiness.md - - - name: Dispatch Codex feedback retry - if: steps.feedback.outputs.dispatch_feedback == 'true' && contains(fromJSON('["true","True","TRUE"]'), env.CODEX_AUDIT_ENABLED) - env: - GH_TOKEN: ${{ steps.codex_review_app_token.outputs.token || secrets.CODEX_AUDIT_DISPATCH_TOKEN }} - ISSUE_NUMBER: ${{ steps.feedback.outputs.issue_number }} - SOURCE_REF: ${{ github.event.repository.default_branch || github.ref_name }} - TARGET_REPOSITORY: ${{ env.CODEX_AUDIT_BRIDGE_REPOSITORY }} - REVIEW_MODE: ${{ env.CODEX_AUDIT_MODE }} - REVIEW_PROVIDER: ${{ env.CODEX_AUDIT_PROVIDER }} - AUTO_MERGE_REQUESTED: ${{ env.CODEX_AUDIT_AUTO_MERGE }} - AUTO_MERGE_READINESS_OUTCOME: ${{ steps.auto_merge_readiness.outcome || 'skipped' }} - run: | - set -euo pipefail - if [ -z "${GH_TOKEN:-}" ]; then - echo "Codex audit feedback dispatch requires either a GitHub App token or CODEX_AUDIT_DISPATCH_TOKEN" >&2 - exit 1 - fi - if [[ ! "${TARGET_REPOSITORY}" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]]; then - echo "Invalid Codex audit repository: ${TARGET_REPOSITORY}" >&2 - exit 1 - fi - case "${REVIEW_MODE}" in - review_only|review_and_fix) ;; - *) echo "Unsupported Codex audit mode: ${REVIEW_MODE}" >&2; exit 1 ;; - esac - case "${REVIEW_PROVIDER}" in - auto|api|anthropic|codex|openai) ;; - *) echo "Unsupported Codex audit provider: ${REVIEW_PROVIDER}" >&2; exit 1 ;; - esac - auto_merge_requested="false" - if [ "${AUTO_MERGE_REQUESTED}" = "true" ] || [ "${AUTO_MERGE_REQUESTED}" = "True" ] || [ "${AUTO_MERGE_REQUESTED}" = "TRUE" ]; then - auto_merge_requested="true" - fi - auto_merge="false" - if [ "${auto_merge_requested}" = "true" ] && [ "${AUTO_MERGE_READINESS_OUTCOME}" = "success" ]; then - auto_merge="true" - fi - if [ "${auto_merge_requested}" = "true" ] && [ "${AUTO_MERGE_READINESS_OUTCOME}" != "success" ]; then - echo "Guarded auto-merge was requested, but readiness did not pass; dispatching Codex feedback retry with auto_merge=false." - fi - gh workflow run codex_audit.yml \ - --repo "${TARGET_REPOSITORY}" \ - --ref "${CODEX_AUDIT_BRIDGE_REF}" \ - --field source_repo="${GITHUB_REPOSITORY}" \ - --field source_ref="${SOURCE_REF}" \ - --field issue_number="${ISSUE_NUMBER}" \ - --field mode="${REVIEW_MODE}" \ - --field provider="${REVIEW_PROVIDER}" \ - --field auto_merge="${auto_merge}" \ - --field task="monthly_snapshot_audit" - echo "Dispatched AIAuditBridge feedback retry for issue #${ISSUE_NUMBER} to ${TARGET_REPOSITORY}" - - - name: Upload Codex feedback diagnostics - if: always() - uses: actions/upload-artifact@v7 - with: - name: codex-pr-feedback-review-${{ github.run_id }} - path: data/output/codex_feedback/ - if-no-files-found: warn diff --git a/.github/workflows/monthly_review.yml b/.github/workflows/monthly_review.yml index c519d62..6733ae5 100644 --- a/.github/workflows/monthly_review.yml +++ b/.github/workflows/monthly_review.yml @@ -28,20 +28,12 @@ jobs: group: monthly-snapshot-review-${{ github.ref_name }} cancel-in-progress: false permissions: - actions: write contents: read issues: write env: REVIEW_LABEL: monthly-review FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" - CODEX_AUDIT_ENABLED: ${{ vars.CODEX_AUDIT_ENABLED || 'true' }} - CODEX_AUDIT_BRIDGE_REPOSITORY: ${{ vars.CODEX_AUDIT_BRIDGE_REPOSITORY || 'QuantStrategyLab/AIAuditBridge' }} - CODEX_AUDIT_BRIDGE_REF: ${{ vars.CODEX_AUDIT_BRIDGE_REF || 'main' }} - CODEX_AUDIT_MODE: ${{ vars.CODEX_AUDIT_MODE || 'review_and_fix' }} - CODEX_AUDIT_PROVIDER: ${{ vars.CODEX_AUDIT_PROVIDER || 'auto' }} - CODEX_AUDIT_AUTO_MERGE: ${{ vars.CODEX_AUDIT_AUTO_MERGE || 'false' }} - CODEX_AUDIT_REQUIRED_STATUS_CHECKS: ${{ vars.CODEX_AUDIT_REQUIRED_STATUS_CHECKS || 'test' }} steps: - name: Checkout @@ -200,186 +192,6 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Ensure guarded auto-merge labels - if: success() && contains(fromJSON('["true","True","TRUE"]'), env.CODEX_AUDIT_ENABLED) && contains(fromJSON('["true","True","TRUE"]'), env.CODEX_AUDIT_AUTO_MERGE) - continue-on-error: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -euo pipefail - python scripts/sync_codex_auto_merge_labels.py \ - --repo "${GITHUB_REPOSITORY}" \ - --auto-merge "${CODEX_AUDIT_AUTO_MERGE}" \ - --summary-file data/output/monthly_report_bundle/codex_auto_merge_label_sync.md - - - name: Check guarded auto-merge readiness - id: auto_merge_readiness - if: success() && contains(fromJSON('["true","True","TRUE"]'), env.CODEX_AUDIT_ENABLED) - continue-on-error: true - env: - GITHUB_TOKEN: ${{ secrets.CODEX_AUDIT_READINESS_TOKEN || secrets.GITHUB_TOKEN }} - run: | - set -euo pipefail - python scripts/check_codex_auto_merge_readiness.py \ - --repo "${GITHUB_REPOSITORY}" \ - --branch "${GITHUB_REF_NAME}" \ - --auto-merge "${CODEX_AUDIT_AUTO_MERGE}" \ - --required-status-checks "${CODEX_AUDIT_REQUIRED_STATUS_CHECKS}" \ - --summary-file data/output/monthly_report_bundle/codex_auto_merge_readiness.md - - - name: Build guarded auto-merge enablement plan - if: success() && contains(fromJSON('["true","True","TRUE"]'), env.CODEX_AUDIT_ENABLED) - continue-on-error: true - env: - GITHUB_TOKEN: ${{ secrets.CODEX_AUDIT_READINESS_TOKEN || secrets.GITHUB_TOKEN }} - run: | - set -euo pipefail - python scripts/plan_codex_auto_merge_enablement.py \ - --repo "${GITHUB_REPOSITORY}" \ - --branch "${GITHUB_REF_NAME}" \ - --required-status-checks "${CODEX_AUDIT_REQUIRED_STATUS_CHECKS}" \ - --output-file data/output/monthly_report_bundle/codex_auto_merge_enablement_plan.md - - - name: Comment guarded auto-merge preflight summary - if: success() && contains(fromJSON('["true","True","TRUE"]'), env.CODEX_AUDIT_ENABLED) - continue-on-error: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -euo pipefail - python scripts/post_codex_auto_merge_preflight_comment.py \ - --repo "${GITHUB_REPOSITORY}" \ - --issue-number "${{ steps.review_issue.outputs.issue_number }}" \ - --report-month "${{ steps.bundle.outputs.report_month }}" \ - --readiness-file data/output/monthly_report_bundle/codex_auto_merge_readiness.md \ - --label-sync-file data/output/monthly_report_bundle/codex_auto_merge_label_sync.md \ - --enablement-plan-file data/output/monthly_report_bundle/codex_auto_merge_enablement_plan.md \ - --output-file data/output/monthly_report_bundle/codex_auto_merge_preflight_comment.md - - - name: Detect Codex Audit GitHub App Credentials - id: codex_review_app_credentials - if: success() && contains(fromJSON('["true","True","TRUE"]'), env.CODEX_AUDIT_ENABLED) - env: - APP_ID: ${{ vars.CROSS_REPO_GITHUB_APP_ID }} - APP_PRIVATE_KEY: ${{ secrets.CROSS_REPO_GITHUB_APP_PRIVATE_KEY }} - run: | - set -euo pipefail - if [ -n "${APP_ID:-}" ] && [ -n "${APP_PRIVATE_KEY:-}" ]; then - echo "available=true" >> "$GITHUB_OUTPUT" - else - echo "available=false" >> "$GITHUB_OUTPUT" - fi - - - name: Create GitHub App Token For Codex Audit - id: codex_review_app_token - if: steps.codex_review_app_credentials.outputs.available == 'true' - continue-on-error: true - uses: actions/create-github-app-token@v3 - with: - app-id: ${{ vars.CROSS_REPO_GITHUB_APP_ID }} - private-key: ${{ secrets.CROSS_REPO_GITHUB_APP_PRIVATE_KEY }} - owner: ${{ github.repository_owner }} - repositories: | - AIAuditBridge - permission-actions: write - - - name: Trigger Monthly Review Automation - if: success() && contains(fromJSON('["true","True","TRUE"]'), env.CODEX_AUDIT_ENABLED) - env: - APP_TOKEN: ${{ steps.codex_review_app_token.outputs.token }} - CODEX_AUDIT_DISPATCH_TOKEN: ${{ secrets.CODEX_AUDIT_DISPATCH_TOKEN }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITHUB_REPOSITORY: ${{ github.repository }} - GITHUB_REF_NAME: ${{ github.ref_name }} - ISSUE_NUMBER: ${{ steps.review_issue.outputs.issue_number }} - TARGET_REPOSITORY: ${{ env.CODEX_AUDIT_BRIDGE_REPOSITORY }} - REVIEW_MODE: ${{ env.CODEX_AUDIT_MODE }} - REVIEW_PROVIDER: ${{ env.CODEX_AUDIT_PROVIDER }} - AUTO_MERGE_REQUESTED: ${{ env.CODEX_AUDIT_AUTO_MERGE }} - AUTO_MERGE_READINESS_OUTCOME: ${{ steps.auto_merge_readiness.outcome || 'skipped' }} - CODEX_AUDIT_ENABLED_RUNTIME: ${{ env.CODEX_AUDIT_ENABLED }} - run: | - set -euo pipefail - python3 - <<'PY' - import json - import os - import re - import urllib.error - import urllib.request - - def enabled(name: str) -> bool: - return os.environ.get(name, "").strip().lower() == "true" - - def dispatch(token: str, url: str, payload: dict) -> int: - request = urllib.request.Request( - url, - data=json.dumps(payload).encode("utf-8"), - method="POST", - headers={ - "Authorization": f"Bearer {token}", - "Accept": "application/vnd.github+json", - "Content-Type": "application/json", - "X-GitHub-Api-Version": "2022-11-28", - "User-Agent": "us-equity-snapshot-monthly-review", - }, - ) - with urllib.request.urlopen(request, timeout=30) as response: - return response.status - - def dispatch_codex() -> None: - token = os.environ.get("APP_TOKEN", "").strip() or os.environ.get("CODEX_AUDIT_DISPATCH_TOKEN", "").strip() - if not token: - raise RuntimeError( - "Codex audit workflow dispatch requires either a GitHub App token or CODEX_AUDIT_DISPATCH_TOKEN" - ) - target_repository = os.environ["TARGET_REPOSITORY"].strip() - if not re.fullmatch(r"[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+", target_repository): - raise RuntimeError(f"Invalid Codex audit repository: {target_repository!r}") - mode = os.environ["REVIEW_MODE"].strip() or "review_and_fix" - if mode not in {"review_only", "review_and_fix"}: - raise RuntimeError(f"Unsupported Codex audit mode: {mode}") - provider = os.environ["REVIEW_PROVIDER"].strip() or "codex" - if provider not in {"api", "anthropic", "codex", "openai", "auto"}: - raise RuntimeError(f"Unsupported Codex audit provider: {provider}") - auto_merge_requested = enabled("AUTO_MERGE_REQUESTED") - auto_merge_ready = os.environ.get("AUTO_MERGE_READINESS_OUTCOME", "").strip() == "success" - auto_merge = auto_merge_requested and auto_merge_ready - if auto_merge_requested and not auto_merge_ready: - print( - "Guarded auto-merge was requested, but readiness did not pass; " - "dispatching Codex audit with auto_merge=false." - ) - payload = { - "ref": os.environ["CODEX_AUDIT_BRIDGE_REF"], - "inputs": { - "source_repo": os.environ["GITHUB_REPOSITORY"], - "source_ref": os.environ["GITHUB_REF_NAME"], - "issue_number": os.environ["ISSUE_NUMBER"], - "mode": mode, - "provider": provider, - "auto_merge": str(auto_merge).lower(), - "task": "monthly_snapshot_audit", - }, - } - status = dispatch( - token, - f"https://api.github.com/repos/{target_repository}/actions/workflows/codex_audit.yml/dispatches", - payload, - ) - if status not in (201, 204): - raise RuntimeError(f"Unexpected Codex dispatch status: {status}") - print( - f"Dispatched AIAuditBridge review for issue #{os.environ['ISSUE_NUMBER']} " - f"to {target_repository}" - ) - - if enabled("CODEX_AUDIT_ENABLED_RUNTIME"): - dispatch_codex() - raise SystemExit(0) - - print("Codex audit bridge dispatch is disabled by CODEX_AUDIT_ENABLED=false") - PY - - name: Upload monthly review bundle if: always() uses: actions/upload-artifact@v7 diff --git a/docs/operator_runbook.md b/docs/operator_runbook.md index 8a07582..fa3180a 100644 --- a/docs/operator_runbook.md +++ b/docs/operator_runbook.md @@ -201,28 +201,17 @@ The publish workflow keeps a defensive month-end trading-day guard: if the resol ## Monthly AI Review -After a successful scheduled `Publish Snapshot Artifacts` run, `Monthly Snapshot Review` downloads that run's artifacts and assembles: - -```text -data/output/monthly_report_bundle/monthly_report_bundle.json -data/output/monthly_report_bundle/ai_review_input.md -data/output/monthly_report_bundle/job_summary.md -``` - -The workflow creates or updates a GitHub issue labeled `monthly-review`. By default it dispatches `QuantStrategyLab/AIAuditBridge`, which calls the Quant HTTPS/443 service-backed Codex path and posts the audit result back to the issue. The review focuses on: - -- artifact completeness for the expected snapshot profile -- contract version, snapshot date, row count, and ranking-preview health -- missing or stale evidence that should block downstream confidence -- downstream impact for broker/runtime repositories - -The monthly review workflow dispatches `AIAuditBridge`; the bridge owns provider selection through `CODEX_AUDIT_PROVIDER`. `auto` is the default and calls the service-backed Codex path first, falls back to the configured API reviewers when Codex service execution fails, and fails loudly when no API fallback key is configured. Set `CODEX_AUDIT_BRIDGE_REF` to pin the bridge workflow ref; the default is `main`. `codex` uses only the service-backed Codex path and disables API fallback; `api` posts a combined API review; `openai` and `anthropic` post a single-provider API review only. - -Direct bridge PRs are not auto-merged by default. Set `CODEX_AUDIT_AUTO_MERGE=true` only after branch protection or repository rulesets, CI gates, the source `Auto Merge Codex Remediation PR` guard, and the `Codex PR Feedback` retry workflow are confirmed. When guarded auto-merge is requested, `Monthly Snapshot Review` first runs `scripts/sync_codex_auto_merge_labels.py` to create the configured `auto-merge-ok` / `human-review-required` labels if they are missing. Before flipping the variable, run `scripts/plan_codex_auto_merge_enablement.py` and complete its enablement preflight checklist, including review of any existing branch protection or ruleset settings. The source auto-merge workflow is hard-gated by `CODEX_AUDIT_AUTO_MERGE`, so it skips when the variable is unset or false even if a PR already carries `auto-merge-ok`. Required CI checks default to `test`; set repository variable `CODEX_AUDIT_REQUIRED_STATUS_CHECKS` to a comma- or newline-separated list before enablement if branch protection uses different check names. `Monthly Snapshot Review` still dispatches AIAuditBridge when readiness is incomplete, but downgrades that dispatch to `auto_merge=false` so audit and PR creation continue without requesting guarded auto-merge. If the default workflow token cannot read branch protection, rulesets, or label settings in the deployed repository, configure `CODEX_AUDIT_READINESS_TOKEN` as a source-repository secret for readiness checks only. The intended unattended path is: Codex opens a small remediation PR, CI passes, the PR carries the monthly remediation marker and `auto-merge-ok`, then the source merge guard confirms there is no active requested-changes review decision, classifies the changed-file surface, and enforces `max_changed_files` (`20` by default) and `max_changed_lines` (`1200` by default) before merging. The auto-merge workflow skips stale successful CI runs when the run head SHA no longer matches the current PR head, and the final merge is also pinned to the CI `workflow_run.head_sha`; a newer push to the same PR branch requires a fresh successful CI run. High-risk changes such as strategy logic, live profile contracts, dependencies, secrets, broker/runtime settings, live allocation, data artifacts, or strategy deletion/disablement must stay human-reviewed; AIAuditBridge labels those generated PRs `human-review-required` and records the risk files in the source issue. -Each monthly review bundle artifact also includes `codex_auto_merge_label_sync.md`, `codex_auto_merge_readiness.md`, `codex_auto_merge_enablement_plan.md`, and the rendered `codex_auto_merge_preflight_comment.md` when the audit path is enabled. The workflow also upserts that short guarded auto-merge preflight comment on the monthly review issue, including the label-sync, readiness, and enablement checklist snapshots. Operators can use those records to audit the preflight state that was used before any guarded auto-merge dispatch decision. - -If CI fails on a same-repository Codex remediation PR, or a reviewer requests changes on that PR, `Codex PR Feedback` first skips stale CI-failure runs whose head SHA no longer matches the current PR head. For current failures or requested changes, it removes any stale configured guarded auto-merge label from that PR on a best-effort basis after validating that the policy labels are readable and distinct; if the policy is invalid or the auto-merge and human-review labels collide, it skips label mutation instead of guessing. It then comments the failure or review summary back to the source `codex-bridge` issue and dispatches `AIAuditBridge` again for the same issue. Feedback retries run the same guarded auto-merge readiness check; when readiness is incomplete, the retry still runs but passes `auto_merge=false`. The bridge resolves the latest feedback marker and updates the existing PR branch when it is still open, same-repository, and tied to the same monthly issue. The workflow permits `CODEX_AUDIT_MAX_FEEDBACK_ROUNDS` automatic feedback rounds, defaulting to `3`; invalid values fall back to `3`, and values are clamped to `1` through `10`. Once that limit is reached, it removes `codex-bridge`, marks the PR with the configured human-review label on a best-effort basis, creating that label first if it is missing and permissions allow it, and leaves the issue for human review. The feedback workflow uploads `codex-pr-feedback-ci-` or `codex-pr-feedback-review-` diagnostics even when later steps fail; inspect those artifacts for `comment.md`, `comment_pages.json`, `pr.json`, and `codex_auto_merge_readiness.md` before rerunning manually. - -When guarded auto-merge is enabled, `Auto Merge Codex Remediation PR` uploads `codex-auto-merge-` diagnostics on every run. Use `decision.json`, `summary.md`, `pr.json`, `guard_decision_comment.md`, and `readiness.md` from that artifact to explain why a PR merged, skipped, failed readiness, or was blocked by the merge guard. If the merge guard skips a PR, or if the final merge-time readiness check fails after the merge guard was otherwise ready, the workflow also upserts a short decision comment on the monthly review issue so high-risk/manual-review cases are visible without opening artifacts first; it also tries to remove stale guarded auto-merge labels from the skipped PR and add the human-review label for high-risk decisions. The decision comment is written first; label hygiene action results are then appended to the same comment and artifact on a best-effort basis. If the follow-up comment update fails, the artifact records that failure, so a permission issue does not hide the reason unattended merge stopped. - -See [`snapshot-ai-audit-automation.md`](snapshot-ai-audit-automation.md) for the risk tiers and unattended-maintenance policy. +`Monthly Snapshot Review` is currently disabled. It is report-only: a future +independent activation decision may allow it to build the existing health, +promotion-readiness, and review evidence bundle, upload the bundle, and create +or update a `monthly-review` issue. + +AIAuditBridge review/fix/retry/merge automation has been retired. GitHub Codex +App is the sole AI reviewer. The monthly workflow does not dispatch an AI +reviewer, create a remediation PR, retry feedback, or prepare or perform +auto-merge. The evidence bundle is advisory and is not an approval or +automated merge signal. + +Inert AIAudit scripts, policy files, repository variables, and secret names are +tracked separately for Phase B. Do not remove or change them as part of +report-only operation. diff --git a/docs/operator_runbook.zh-CN.md b/docs/operator_runbook.zh-CN.md index 6162ab8..7ebe74b 100644 --- a/docs/operator_runbook.zh-CN.md +++ b/docs/operator_runbook.zh-CN.md @@ -95,28 +95,14 @@ workflow 仍保留月末交易日 guard:如果解析得到的 `snapshot_as_of` ## 月度 AI Review -定时 `Publish Snapshot Artifacts` 成功后,`Monthly Snapshot Review` 会下载该 run 的 artifacts,并组装: +`Monthly Snapshot Review` 当前保持 disabled。它仅生成报告:未来只有在独立 +activation 决策后,才可构建既有 health、promotion-readiness 和 review 证据包、 +上传 artifact,并创建或更新 `monthly-review` issue。 -```text -data/output/monthly_report_bundle/monthly_report_bundle.json -data/output/monthly_report_bundle/ai_review_input.md -data/output/monthly_report_bundle/job_summary.md -``` - -workflow 会创建或更新一个带 `monthly-review` label 的 GitHub issue。默认会 dispatch `QuantStrategyLab/AIAuditBridge`,由该桥接 workflow 调用 Quant HTTPS/443 服务端 Codex 路径,并把 audit 结果回写到 issue。review 重点包括: - -- 预期 snapshot profile 的 artifact 完整性; -- contract version、snapshot date、row count 和 ranking-preview 健康度; -- 会降低下游信心的缺失或陈旧证据; -- 对券商/runtime 下游仓库的影响。 - -`Monthly Snapshot Review` 通过 `CODEX_AUDIT_PROVIDER` 控制 reviewer provider。默认 `auto` 会优先调用 service-backed Codex,失败时回退到已配置的 API reviewer;没有 API fallback key 时会显式失败。可通过 `CODEX_AUDIT_BRIDGE_REF` pin 桥接 workflow ref,默认是 `main`。 - -桥接 PR 默认不自动合并。只有确认 branch protection 或 repository rulesets、CI、源仓库 `Auto Merge Codex Remediation PR` guard 以及 `Codex PR Feedback` 重试 workflow 后,才应设置 `CODEX_AUDIT_AUTO_MERGE=true`。当请求受控自动合并时,`Monthly Snapshot Review` 会先运行 `scripts/sync_codex_auto_merge_labels.py`,在缺失时创建配置的 `auto-merge-ok` / `human-review-required` label。真正打开变量前,应先运行 `scripts/plan_codex_auto_merge_enablement.py`,并逐项完成其中的 enablement preflight checklist,包括复核现有 branch protection 或 ruleset 设置,避免覆盖已有保护规则。源仓库 auto-merge workflow 自身也受 `CODEX_AUDIT_AUTO_MERGE` 硬开关控制;变量未设置或为 false 时,即使 PR 已带 `auto-merge-ok` 也会跳过。必需 CI check 默认是 `test`;如果 branch protection 使用不同 check 名称,应在启用前把仓库变量 `CODEX_AUDIT_REQUIRED_STATUS_CHECKS` 设置为逗号或换行分隔的列表。启用前可先运行 `scripts/plan_codex_auto_merge_enablement.py` 生成只读启用计划和 branch protection 命令;该脚本不会修改远端设置。`Monthly Snapshot Review` 会在 dispatch AIAuditBridge 前运行 `scripts/check_codex_auto_merge_readiness.py`;如果 auto-merge 已开启但本地 guard、反馈重试 workflow、远端 label、protected branch/ruleset 或必需 CI status check 任一条件缺失,本次 dispatch 会自动降级为 `auto_merge=false`,继续执行 AI 审计和 PR 创建,但不会请求受控自动合并。如果部署仓库中的默认 workflow token 不能读取 branch protection、rulesets 或 label 设置,可配置源仓库 secret `CODEX_AUDIT_READINESS_TOKEN`,仅供 readiness 检查使用。`auto-merge-ok` label 默认由 AIAuditBridge 在低/中风险 PR 打标前按需创建;如果 bridge token 权限不足,需要先手动创建该 label。无人值守路径应为:Codex 创建小范围修复 PR,CI 通过,PR 带 monthly remediation marker 和 `auto-merge-ok`,然后源仓库 merge guard 会确认当前没有 requested-changes review decision,再根据变更文件面、风险等级和 `max_changed_files`(默认 `20`)和 `max_changed_lines`(默认 `1200`)决定是否合并。auto-merge workflow 会跳过 run head SHA 已经不是当前 PR head 的过期成功 CI,最终 merge 也会绑定 CI 的 `workflow_run.head_sha`;同一 PR 分支如果之后又有新提交,必须重新通过 CI 才能合并。策略逻辑、live profile contract、依赖、密钥、broker/runtime 设置、live allocation、数据 artifact、策略删除或禁用等高风险变更必须保留人工复核;AIAuditBridge 会给这类生成 PR 添加 `human-review-required`,并在源 issue 中记录风险文件。 -月度 review bundle artifact 在 audit path 启用时也会包含 `codex_auto_merge_label_sync.md`、`codex_auto_merge_readiness.md`、`codex_auto_merge_enablement_plan.md` 以及渲染后的 `codex_auto_merge_preflight_comment.md`,workflow 还会在月度 review issue 上 upsert 这条简短的 guarded auto-merge preflight 评论,并包含 label sync、readiness 与 enablement checklist 摘要,用于留存本次受控自动合并 dispatch 决策前的 preflight 状态。 - -如果同仓 Codex remediation PR 的 CI 失败,或 reviewer 要求修改,`Codex PR Feedback` 会先跳过 run head SHA 已经不是当前 PR head 的过期 CI 失败事件。对当前失败或 requested changes,它会复用 readiness 的策略 label 校验,确认 policy 可读且 auto-merge / human-review label 不冲突后,再尽力移除该 PR 上旧的受控自动合并 label;如果 policy 无效或两个 label 冲突,则跳过 label mutation,避免误删人工复核 label。随后它会把失败或 review 摘要评论回源 `codex-bridge` issue,并再次 dispatch `AIAuditBridge` 处理同一个 issue。反馈重试会运行同一套受控 auto-merge readiness 检查;如果 readiness 不完整,重试仍会执行,但传入 `auto_merge=false`。Bridge 会读取最新 feedback marker;只要原 PR 仍然 open、同仓、且属于同一个 monthly issue,就更新现有 PR 分支。workflow 允许 `CODEX_AUDIT_MAX_FEEDBACK_ROUNDS` 轮自动反馈,默认 `3` 轮;无效值会回退到 `3`,并被限制在 `1` 到 `10` 之间。达到上限后会移除 `codex-bridge`,并尽力先创建缺失的人工复核 label、再给 PR 加上该 label,然后交给人工处理。反馈 workflow 即使后续步骤失败,也会上传 `codex-pr-feedback-ci-` 或 `codex-pr-feedback-review-` 诊断 artifact;手动重跑前应先检查其中的 `comment.md`、`comment_pages.json`、`pr.json` 和 `codex_auto_merge_readiness.md`。 - -启用受控自动合并后,`Auto Merge Codex Remediation PR` 每次运行都会上传 `codex-auto-merge-` 诊断 artifact。可用其中的 `decision.json`、`summary.md`、`pr.json`、`guard_decision_comment.md` 和 `readiness.md` 判断 PR 为什么被合并、跳过、readiness 失败或被 merge guard 阻止。如果 merge guard 跳过 PR,或 merge guard 已就绪但最终 merge-time readiness 检查失败,workflow 还会在月度 review issue 上 upsert 一条简短决策评论,让高风险或需人工复核的情况不必先打开 artifact 也能看到;同时会尽力从被跳过的 PR 上移除陈旧的受控自动合并 label,并在高风险决策时补上人工复核 label。决策评论会优先写入;label hygiene action 结果随后会 best-effort 追加到同一条评论和 artifact 中。如果后续评论更新失败,artifact 会记录该失败,避免权限问题掩盖无人值守合并停止的真实原因。 +AIAuditBridge 的 review/fix/retry/merge 自动化已经退役。GitHub Codex App +是唯一 AI reviewer。该月度 workflow 不 dispatch AI reviewer、不创建 remediation +PR、不重试反馈,也不准备或执行 auto-merge。证据包只作参考,不构成批准或自动 +merge 信号。 -风险分层和无人值守维护边界见 [`snapshot-ai-audit-automation.md`](snapshot-ai-audit-automation.md)。 +遗留的 AIAudit 脚本、policy、repository variables 和 secret 名称属于独立的 +Phase B 清理范围;report-only 操作不得删除或修改它们。 diff --git a/docs/snapshot-ai-audit-automation.md b/docs/snapshot-ai-audit-automation.md index d71b312..48f3a7b 100644 --- a/docs/snapshot-ai-audit-automation.md +++ b/docs/snapshot-ai-audit-automation.md @@ -1,173 +1,27 @@ -# Snapshot AI audit automation policy +# Snapshot AI review policy -This policy defines how the monthly snapshot AI audit can run mostly unattended -while keeping high-risk operations behind human review. +## Current boundary -## Target flow +GitHub Codex App is the repository's sole AI reviewer. The retired AIAuditBridge +review, retry, remediation, and merge automation is not an active repository +path and must not be re-enabled through repository variables, secrets, or +workflow dispatch. -1. `Publish Snapshot Artifacts` succeeds on a scheduled source-input refresh. -2. `Monthly Snapshot Review` downloads artifacts, builds live strategy health - evidence, creates/updates the monthly review issue, and dispatches - `AIAuditBridge`. -3. `AIAuditBridge` reviews the issue and may open a remediation PR for safe, - focused fixes. -4. Source-repository CI must pass. -5. `Auto Merge Codex Remediation PR` evaluates the PR marker, labels, file - surface, and risk level before merging. +## Monthly report generation -The merge gate is fail-closed: any file outside the monthly-review allowlist is -treated as high-risk and skipped. -The PR marker must also match the issue number encoded in the Codex branch name -(`codex/monthly-review-issue--...`); a generic or mismatched monthly -marker is not enough to merge. -The workflow resolves only same-repository PRs for the Codex branch before -running the merge guard, then the guard independently verifies the PR head owner -and repository again. -The auto-merge workflow also requires the successful CI `workflow_run` head -repository to match the source repository before the job starts. -It resolves the current same-repository PR head and skips stale successful CI -runs whose `workflow_run.head_sha` no longer matches that current PR head. -The merge command is pinned with `--match-head-commit` to the CI -`workflow_run.head_sha`, so a PR branch pushed after the successful CI run is not -merged by that older run. -The configured human-review label is a hard veto: a PR carrying -`human-review-required` will not be auto-merged even if it also carries -`auto-merge-ok`. -When the merge guard skips a PR, the auto-merge workflow upserts a concise -decision comment back to the monthly review issue so operators can see the -reason and risk level without opening workflow artifacts first. -The same skip path upserts the guard-decision comment first, then removes -any stale configured auto-merge label from that PR and adds the configured -human-review label when the guard classified the risk as high. Label hygiene is -best-effort after the comment is written; the action results are appended back -to the same guard-decision comment and written to the diagnostic artifact when -possible. If the follow-up comment update fails, the diagnostic artifact records -that failure, and a permission or transient API failure does not hide the guard -decision itself. If the merge guard is ready but the final merge-time readiness check fails, -the workflow converts that failure into the same visible guard-decision comment -and label-hygiene path before failing the job, so operators do not need to open -artifacts first to see why the unattended merge stopped. +`Monthly Snapshot Review` is a disabled report-only workflow pending a separate +activation decision. When enabled by that future decision, it may build the +existing monthly evidence bundle, publish the artifact, and create or update a +`monthly-review` issue. It does not dispatch an AI reviewer, retry review +feedback, create remediation pull requests, or prepare or perform auto-merge. -The changed-file allowlist is stored in -`.github/codex_auto_merge_policy.json`. The source merge guard reads this file, -and AIAuditBridge can read the same policy from its temporary source -checkout before adding `auto-merge-ok`. AIAuditBridge reads the baseline -policy before Codex edits run, so a remediation PR cannot grant itself a broader -auto-merge surface by editing the policy file. Keep this file in sync with any -new monthly-review automation surface. `blocked_path_patterns` are evaluated -before low-risk prefixes, so secret-like paths under `docs/` or `tests/` still -require human review. Invalid policy JSON or invalid blocked-path regular -expressions fail closed and require human review. -The same policy also defines `human_review_label` (`human-review-required` by -default), which is used for high-risk generated PRs. -`auto_merge_label` and `human_review_label` must be distinct; matching labels -make the policy invalid and fail closed. -Only `version: 1` is currently supported; missing or future policy versions are -also fail-closed until the guard code is intentionally updated. +The generated evidence is advisory. It is not an approval, trading, provider, +or automated merge signal. GitHub Codex App review and any merge settlement +remain independently bounded and manually controlled. -## Risk tiers +## Cleanup boundary -| Tier | Examples | Automation policy | -| --- | --- | --- | -| Low | `docs/`, `tests/`, `README.md`, `README.zh-CN.md` | May be remediated and auto-merged when the PR has the Codex monthly marker, `auto-merge-ok`, is not draft, CI passed, no PR review is currently requesting changes, and the changed-file count plus total additions/deletions stay within `max_changed_files` (`20` by default) and `max_changed_lines` (`1200` by default). | -| Medium | Monthly-review evidence/reporting helpers such as `scripts/build_monthly_live_strategy_health_reports.py`, `scripts/run_monthly_report_bundle.py`, `scripts/post_monthly_ai_review_issue.py`, and the read-only enablement planner | May be remediated when narrowly scoped. Auto-merge is still gated by `auto-merge-ok`, CI, no active requested-changes review decision, the source merge guard summary, and the same changed-file / changed-line caps. | -| High | `src/` strategy logic, profile contracts, `pyproject.toml`, dependencies, secrets, runtime/broker settings, live allocation, publish permissions, data artifacts, workflow files, auto-merge policy files, auto-merge/readiness/merge-guard code, file removals/renames/copies, or strategy deletion/disablement | Never auto-merge. Keep the generated PR for review, label it `human-review-required`, and include evidence plus recommended next checks in the source issue. | - -## Live strategy health evidence - -The live strategy health report is advisory evidence only. It tracks artifact and content health for review workflows, not AiGateway online service health, and it must not be treated as an automatic trading, auto-merge, or auto-approval basis. A -`review_for_retirement` state can justify a human review task, but it must not -automatically remove, disable, or reallocate a strategy. Strategy retirement -still requires a separate review of out-of-sample evidence, costs, runtime -impact, and downstream broker behavior. - -## Operational switches - -- Keep `CODEX_AUDIT_ENABLED=true` to run the monthly audit automatically. -- Set `CODEX_AUDIT_AUTO_MERGE=true` only after branch protection or repository - rulesets, CI, and the source merge guard are active. -- The source `Auto Merge Codex Remediation PR` workflow is also hard-gated by - `CODEX_AUDIT_AUTO_MERGE`; when the variable is unset or not exactly `true`, - `True`, or `TRUE`, the workflow skips even if a PR already has - `auto-merge-ok`. -- `Monthly Snapshot Review` runs `scripts/check_codex_auto_merge_readiness.py` - before dispatching AIAuditBridge. When `CODEX_AUDIT_AUTO_MERGE=true`, the - readiness gate requires the local auto-merge workflow/policy guard surface, - the Codex feedback retry workflow, the configured source labels (`auto-merge-ok` and `human-review-required` by - default), a protected source branch or active ruleset, and the required CI - status check context. The expected checks default to `test`; set repository - variable `CODEX_AUDIT_REQUIRED_STATUS_CHECKS` to a comma- or newline-separated - list if branch protection uses different check names. - If any check is missing or unreadable, the workflow fails closed for guarded - auto-merge by dispatching AIAuditBridge with `auto_merge=false`; the AI - audit and PR creation path still runs. -- When guarded auto-merge is requested, `Monthly Snapshot Review` first runs - `scripts/sync_codex_auto_merge_labels.py` to create the configured source - labels if they are missing. This step is soft-fail: a token or permission - problem is recorded in the monthly artifact bundle and the preflight issue - comment, and readiness still downgrades `auto_merge=false`. -- If the default workflow token cannot read the readiness inputs in a deployed - repository, configure `CODEX_AUDIT_READINESS_TOKEN` as a source-repository - secret with read access to labels, branch protection, and status-check - configuration. The token is used only by readiness checks; issue creation and - bridge dispatch keep using their existing tokens. -- Use `scripts/plan_codex_auto_merge_enablement.py` to generate a read-only - enablement plan and copyable branch-protection commands. The planner never - mutates GitHub settings; apply the generated commands only after human review. - It uses the same `CODEX_AUDIT_REQUIRED_STATUS_CHECKS` value as readiness when - rendering branch-protection and verification commands. -- Ensure the bridge token can create/apply the configured auto-merge label - (`auto-merge-ok` by default). AIAuditBridge creates the label on demand - before applying it; if token permissions block label creation, create the - label manually before enabling `CODEX_AUDIT_AUTO_MERGE=true`. -- Ensure the bridge token can also create/apply the high-risk review label - (`human-review-required`). If this label cannot be applied, the source issue - comment still records the failure and the PR remains without `auto-merge-ok`. -- `Codex PR Feedback` may dispatch a bounded retry only for same-repository - Codex remediation PRs with a valid monthly remediation marker. The retry - ignores stale CI-failure `workflow_run` events whose head SHA no longer - matches the current PR head. For current failures or requested changes, it - first removes any stale configured guarded auto-merge label from the PR on a - best-effort basis, but only after validating the policy labels with the same - distinct-label guard used by readiness. If the policy is invalid or the - auto-merge and human-review labels collide, feedback skips label mutation - rather than guessing. The retry then updates the existing PR branch when it is - still open and tied to the same monthly issue; after the feedback-round limit, - the workflow removes `codex-bridge`, marks the PR with the configured - human-review label on a best-effort basis, creating that label first if it is - missing and permissions allow it, and leaves the issue for manual review. - Feedback retries run - the same readiness gate as the monthly review; if guarded auto-merge is not - ready, the retry still dispatches AIAuditBridge with `auto_merge=false`. -- Automatic feedback retries default to `3` rounds. Set repository variable - `CODEX_AUDIT_MAX_FEEDBACK_ROUNDS` to adjust this without editing the workflow; - invalid values fall back to `3`, and values are clamped to the safe range - `1` to `10`. -- Treat workflow artifacts as the durable audit trail for unattended runs: - `monthly-snapshot-review-` keeps the review bundle, label-sync report, - readiness report, enablement plan, and rendered preflight comment; - `codex-pr-feedback-ci-` and `codex-pr-feedback-review-` keep retry - inputs and rendered comments; `codex-auto-merge-` keeps `pr.json`, - `decision.json`, `summary.md`, `guard_decision_comment.md`, and merge-time - `readiness.md`. Inspect these artifacts before manually rerunning a failed or - skipped unattended step. -- Keep high-risk follow-ups manual even when the monthly issue was generated by - an unattended run. - -## Guarded auto-merge canary - -- 2026-06-20: Ran a controlled same-repository canary through issue #117. - The canary changes this documentation file only and uses the normal - `codex/monthly-review-issue-*` branch, monthly remediation marker, - `auto-merge-ok` label, CI, and source merge guard path. - - -## Required guardrails - -- Do not expose or persist secrets in audit prompts, comments, logs, or PRs. -- Do not edit generated `data/` artifacts. -- Do not change live strategy behavior from a single monthly report. -- Do not remove, rename, or copy files through unattended remediation; create a review PR instead. -- Keep remediation PRs small and tied to concrete monthly-review evidence. -- If CI or review feedback repeats, stop after the configured retry limit and - leave the issue for human review. +Retired AIAuditBridge workflow files are removed in Phase A. Inert scripts, +policy files, repository variables, and secret names are a separately tracked +Phase B cleanup backlog; this policy does not authorize their deletion or +configuration changes. diff --git a/tests/test_monthly_review_workflow_config.py b/tests/test_monthly_review_workflow_config.py index d84b4ee..928b1ea 100644 --- a/tests/test_monthly_review_workflow_config.py +++ b/tests/test_monthly_review_workflow_config.py @@ -2,28 +2,25 @@ from pathlib import Path - MONTHLY_REVIEW = Path(".github/workflows/monthly_review.yml") -CODEX_FEEDBACK = Path(".github/workflows/codex_pr_feedback.yml") PUBLISH_SNAPSHOT_ARTIFACTS = Path(".github/workflows/publish-snapshot-artifacts.yml") UPDATE_SOURCE_INPUT_DATA = Path(".github/workflows/update-source-input-data.yml") -def test_monthly_review_workflow_creates_issue_and_triggers_codex_first() -> None: +def test_monthly_review_workflow_is_report_only_and_creates_issue() -> None: workflow = MONTHLY_REVIEW.read_text(encoding="utf-8") assert "Publish Snapshot Artifacts" in workflow assert "github.event.workflow_run.event == 'workflow_run'" in workflow - assert "contains(fromJSON('[\"schedule\",\"workflow_run\"]'), github.event.workflow_run.event)" not in workflow - assert "actions: write" in workflow + assert "contents: read" in workflow + assert "issues: write" in workflow + assert "actions: write" not in workflow assert "Install monthly review dependencies" in workflow assert 'python -m pip install "pandas>=2.0"' in workflow assert workflow.index("Install monthly review dependencies") < workflow.index("Build live strategy health reports") assert workflow.index("Build live strategy health reports") < workflow.index("Build live decay monitors") assert workflow.index("Build live decay monitors") < workflow.index("Build Russell crash-brake research artifacts") - assert workflow.index("Build Russell crash-brake research artifacts") < workflow.index( - "Build Russell crash-brake review chain" - ) + assert workflow.index("Build Russell crash-brake research artifacts") < workflow.index("Build Russell crash-brake review chain") assert workflow.index("Build Russell crash-brake review chain") < workflow.index("Build Global ETF promotion bundles") assert workflow.index("Build Global ETF promotion bundles") < workflow.index("Build plugin promotion reviews") assert workflow.index("Build plugin promotion reviews") < workflow.index("Build live replacement reviews") @@ -35,223 +32,23 @@ def test_monthly_review_workflow_creates_issue_and_triggers_codex_first() -> Non assert "scripts/build_monthly_plugin_promotion_reviews.py" in workflow assert "scripts/build_monthly_live_replacement_reviews.py" in workflow assert "scripts/build_promotion_readiness_report.py" in workflow - assert "--latest-only" in workflow - assert "gh run download" in workflow - assert "scripts/build_monthly_live_strategy_health_reports.py" in workflow - assert "scripts/build_monthly_live_decay_monitors.py" in workflow - assert "--output-root \"${artifact_root}\"" in workflow assert "scripts/run_monthly_report_bundle.py" in workflow assert "scripts/post_monthly_ai_review_issue.py" in workflow - assert "Ensure guarded auto-merge labels" in workflow - assert "scripts/sync_codex_auto_merge_labels.py" in workflow - assert "scripts/check_codex_auto_merge_readiness.py" in workflow - assert "scripts/plan_codex_auto_merge_enablement.py" in workflow - assert "scripts/post_codex_auto_merge_preflight_comment.py" in workflow - assert "--auto-merge \"${CODEX_AUDIT_AUTO_MERGE}\"" in workflow - assert "CODEX_AUDIT_REQUIRED_STATUS_CHECKS: ${{ vars.CODEX_AUDIT_REQUIRED_STATUS_CHECKS || 'test' }}" in workflow - assert '--required-status-checks "${CODEX_AUDIT_REQUIRED_STATUS_CHECKS}"' in workflow - assert "codex_auto_merge_readiness.md" in workflow - assert "codex_auto_merge_label_sync.md" in workflow - assert "codex_auto_merge_enablement_plan.md" in workflow - assert "--readiness-file data/output/monthly_report_bundle/codex_auto_merge_readiness.md" in workflow - assert "--label-sync-file data/output/monthly_report_bundle/codex_auto_merge_label_sync.md" in workflow - assert "--enablement-plan-file data/output/monthly_report_bundle/codex_auto_merge_enablement_plan.md" in workflow - assert "--output-file data/output/monthly_report_bundle/codex_auto_merge_preflight_comment.md" in workflow - assert "CODEX_AUDIT_READINESS_TOKEN || secrets.GITHUB_TOKEN" in workflow - assert "id: auto_merge_readiness" in workflow - assert "continue-on-error: true" in workflow - assert "AUTO_MERGE_REQUESTED: ${{ env.CODEX_AUDIT_AUTO_MERGE }}" in workflow - assert "AUTO_MERGE_READINESS_OUTCOME: ${{ steps.auto_merge_readiness.outcome || 'skipped' }}" in workflow - assert 'auto_merge_requested = enabled("AUTO_MERGE_REQUESTED")' in workflow - assert 'auto_merge_ready = os.environ.get("AUTO_MERGE_READINESS_OUTCOME", "").strip() == "success"' in workflow - assert "auto_merge = auto_merge_requested and auto_merge_ready" in workflow - assert "dispatching Codex audit with auto_merge=false" in workflow - assert '"auto_merge": str(auto_merge).lower()' in workflow - assert "AUTO_MERGE: ${{ env.CODEX_AUDIT_AUTO_MERGE }}" not in workflow - audit_enabled_condition = "if: success() && contains(fromJSON('[\"true\",\"True\",\"TRUE\"]'), env.CODEX_AUDIT_ENABLED)" - assert workflow.count(audit_enabled_condition) == 6 - assert "env.CODEX_AUDIT_ENABLED != 'false'" not in workflow - assert "monthly-review" in workflow - assert "CODEX_AUDIT_ENABLED" in workflow - assert "CODEX_AUDIT_BRIDGE_REF" in workflow - assert '"ref": os.environ["CODEX_AUDIT_BRIDGE_REF"]' in workflow - assert "AIAuditBridge" in workflow - assert "monthly-snapshot-review-${{ github.ref_name }}" in workflow - assert "cancel-in-progress: false" in workflow - assert "CODEX_AUDIT_DISPATCH_TOKEN" in workflow - assert "permission-actions: write" in workflow - assert "CODEX_AUDIT_PROVIDER" in workflow - assert "CODEX_AUDIT_PROVIDER || 'auto'" in workflow - assert '"provider": provider' in workflow - assert '"anthropic"' in workflow - assert '"api"' in workflow - assert "codex_audit.yml" in workflow - assert "actions/workflows/codex_audit.yml/dispatches" in workflow - assert workflow.index("Ensure guarded auto-merge labels") < workflow.index("Check guarded auto-merge readiness") - assert workflow.index("Check guarded auto-merge readiness") < workflow.index("Trigger Monthly Review Automation") - assert workflow.index("Build guarded auto-merge enablement plan") < workflow.index("Trigger Monthly Review Automation") - assert workflow.index("Comment guarded auto-merge preflight summary") < workflow.index("Trigger Monthly Review Automation") - assert workflow.index("codex_auto_merge_enablement_plan.md") < workflow.index("Upload monthly review bundle") - assert workflow.index("codex_auto_merge_label_sync.md") < workflow.index("Upload monthly review bundle") - assert workflow.index("codex_auto_merge_preflight_comment.md") < workflow.index("Upload monthly review bundle") - assert workflow.index("Trigger Monthly Review Automation") < workflow.index("Upload monthly review bundle") - assert "if: always()" in workflow + assert "Upload monthly review bundle" in workflow assert "monthly-snapshot-review-${{ steps.bundle.outputs.report_month || 'unknown' }}" in workflow - assert "if-no-files-found: warn" in workflow - assert "Codex audit bridge dispatch is disabled by CODEX_AUDIT_ENABLED=false" in workflow - assert 'raise RuntimeError("Codex audit bridge dispatch is disabled' not in workflow - assert "LEGACY_AI_REVIEW_ENABLED" not in workflow - assert "actions/workflows/ai_review.yml/dispatches" not in workflow - assert "/repos/{target_repository}/dispatches" not in workflow - assert "gh workflow run ai_review.yml" not in workflow - assert "auto_optimization" not in workflow + assert "AIAuditBridge" not in workflow + assert "CODEX_AUDIT_" not in workflow + assert "auto-merge" not in workflow.lower() + assert "actions/create-github-app-token" not in workflow + assert "actions/workflows/codex_audit.yml/dispatches" not in workflow -def test_source_local_legacy_ai_review_workflow_is_removed() -> None: +def test_retired_aiaudit_workflows_are_removed() -> None: + assert not Path(".github/workflows/codex_pr_feedback.yml").exists() + assert not Path(".github/workflows/auto_merge_codex_pr.yml").exists() assert not Path(".github/workflows/ai_review.yml").exists() -def test_auto_merge_workflow_requires_codex_branch_and_guarded_label() -> None: - workflow = Path(".github/workflows/auto_merge_codex_pr.yml").read_text(encoding="utf-8") - - assert "Auto Merge Codex Remediation PR" in workflow - assert "contents: write" in workflow - assert "issues: write" in workflow - assert "pull-requests: write" in workflow - assert "codex/monthly-review-issue-" in workflow - assert "vars.CODEX_AUDIT_AUTO_MERGE" in workflow - assert "vars.CODEX_AUDIT_REQUIRED_STATUS_CHECKS" in workflow - assert '["true","True","TRUE"]' in workflow - assert "github.event.workflow_run.head_repository.full_name == github.repository" in workflow - assert workflow.count('--repo "${{ github.repository }}"') == 6 - assert "--json number,headRefOid,headRepository,isCrossRepository" in workflow - assert ".headRepository.nameWithOwner" in workflow - assert "No same-repository open PR found" in workflow - assert "pr_head_sha" in workflow - assert "github.event.workflow_run.head_sha" in workflow - assert "Skipping auto-merge for stale CI success" in workflow - assert "ref: ${{ github.event.repository.default_branch }}" in workflow - assert ( - "files,changedFiles,additions,deletions,reviewDecision,labels,baseRefName,headRefName," - "headRepositoryOwner,headRepository,isCrossRepository" - ) in workflow - assert '--expected-base-ref "${{ github.event.repository.default_branch }}"' in workflow - assert '--expected-head-ref "${{ github.event.workflow_run.head_branch }}"' in workflow - assert '--expected-head-owner "${{ github.repository_owner }}"' in workflow - assert '--expected-head-repository "${{ github.repository }}"' in workflow - assert "--require-same-repository" in workflow - assert "scripts/evaluate_codex_pr_merge.py" in workflow - assert "Comment auto-merge guard decision" in workflow - assert "scripts/post_codex_auto_merge_decision_comment.py" in workflow - assert "--decision-json data/output/codex_auto_merge/decision.json" in workflow - assert "--output-file data/output/codex_auto_merge/guard_decision_comment.md" in workflow - assert "--sync-labels" in workflow - assert "Check guarded auto-merge readiness before merge" in workflow - assert "id: merge_readiness" in workflow - assert "scripts/check_codex_auto_merge_readiness.py" in workflow - assert "CODEX_AUDIT_READINESS_TOKEN || secrets.GITHUB_TOKEN" in workflow - assert "CODEX_AUDIT_REQUIRED_STATUS_CHECKS: ${{ vars.CODEX_AUDIT_REQUIRED_STATUS_CHECKS || 'test' }}" in workflow - assert '--required-status-checks "${CODEX_AUDIT_REQUIRED_STATUS_CHECKS}"' in workflow - assert "--auto-merge true" in workflow - assert "--summary-file data/output/codex_auto_merge/readiness.md" in workflow - assert "Comment merge-time readiness failure" in workflow - assert "readiness_decision.json" in workflow - assert "readiness_guard_decision_comment.md" in workflow - assert "merge_readiness_failed" in workflow - assert "Fail on merge-time readiness failure" in workflow - assert "steps.merge_readiness.outcome == 'success'" in workflow - assert workflow.index("Comment auto-merge guard decision") < workflow.index("Check guarded auto-merge readiness before merge") - assert workflow.index("Check guarded auto-merge readiness before merge") < workflow.index("Merge Codex remediation PR") - assert workflow.index("Comment merge-time readiness failure") < workflow.index("Fail on merge-time readiness failure") - assert workflow.index("Fail on merge-time readiness failure") < workflow.index("Merge Codex remediation PR") - assert "gh pr merge" in workflow - assert "--match-head-commit" in workflow - assert "github.event.workflow_run.head_sha" in workflow - assert "Upload Codex auto-merge diagnostics" in workflow - assert "uses: actions/upload-artifact@v7" in workflow - assert "codex-auto-merge-${{ github.run_id }}" in workflow - assert "path: data/output/codex_auto_merge/" in workflow - assert "if-no-files-found: warn" in workflow - assert "auto-merge-ok" not in workflow # label check lives in evaluate_codex_pr_merge.py - - -def test_codex_feedback_workflow_requeues_failed_ci_and_review_feedback() -> None: - workflow = CODEX_FEEDBACK.read_text(encoding="utf-8") - - assert "workflow_run:" in workflow - assert "pull_request_review:" in workflow - assert "contents: read" in workflow - assert workflow.count("uses: actions/checkout@v6") == 2 - assert "codex/monthly-review-issue-" in workflow - assert "CODEX_AUDIT_BRIDGE_REPOSITORY" in workflow - assert "CODEX_AUDIT_DISPATCH_TOKEN" in workflow - assert "github.event.workflow_run.head_repository.full_name == github.repository" in workflow - assert "github.event.pull_request.head.repo.full_name == github.repository" in workflow - assert 'gh pr list --repo "${GITHUB_REPOSITORY}"' in workflow - assert "--json number,title,url,body,headRefOid,headRepository,isCrossRepository" in workflow - assert ".headRepository.nameWithOwner" in workflow - assert "Skipping stale CI failure because the workflow_run head SHA no longer matches the current PR head" in workflow - assert 'gh api --paginate --slurp' in workflow - assert '"/repos/${GITHUB_REPOSITORY}/issues/${issue_number}/comments?per_page=100"' in workflow - assert "data/output/codex_feedback/comment_pages.json" in workflow - assert 'gh issue view "${issue_number}" --repo "${GITHUB_REPOSITORY}" --comments' not in workflow - assert 'gh issue edit "${issue_number}" --repo "${GITHUB_REPOSITORY}"' in workflow - assert 'gh issue comment "${issue_number}" --repo "${GITHUB_REPOSITORY}"' in workflow - assert "Dispatch Codex feedback retry" in workflow - assert "gh workflow run codex_audit.yml" in workflow - assert workflow.count("Check guarded auto-merge readiness") == 2 - assert workflow.count("id: auto_merge_readiness") == 2 - assert workflow.count("CODEX_AUDIT_READINESS_TOKEN || secrets.GITHUB_TOKEN") == 2 - assert "CODEX_AUDIT_REQUIRED_STATUS_CHECKS: ${{ vars.CODEX_AUDIT_REQUIRED_STATUS_CHECKS || 'test' }}" in workflow - assert workflow.count('--required-status-checks "${CODEX_AUDIT_REQUIRED_STATUS_CHECKS}"') == 2 - assert workflow.count("continue-on-error: true") >= 2 - assert "scripts/check_codex_auto_merge_readiness.py" in workflow - assert "--summary-file data/output/codex_feedback/codex_auto_merge_readiness.md" in workflow - assert "AUTO_MERGE_REQUESTED: ${{ env.CODEX_AUDIT_AUTO_MERGE }}" in workflow - assert "AUTO_MERGE_READINESS_OUTCOME: ${{ steps.auto_merge_readiness.outcome || 'skipped' }}" in workflow - assert "auto_merge_requested=\"false\"" in workflow - assert "dispatching Codex feedback retry with auto_merge=false" in workflow - assert "AUTO_MERGE: ${{ env.CODEX_AUDIT_AUTO_MERGE }}" not in workflow - assert '--field issue_number="${ISSUE_NUMBER}"' in workflow - assert '--field task="monthly_snapshot_audit"' in workflow - assert "steps.feedback.outputs.dispatch_feedback == 'true'" in workflow - assert "codex-monthly-remediation:issue-" in workflow - assert "gh issue comment" in workflow - assert "CODEX_AUDIT_MAX_FEEDBACK_ROUNDS" in workflow - assert workflow.count("MAX_CODEX_FEEDBACK_ROUNDS: ${{ vars.CODEX_AUDIT_MAX_FEEDBACK_ROUNDS || '3' }}") == 2 - assert workflow.count('configured_max_rounds = int(os.environ.get("MAX_CODEX_FEEDBACK_ROUNDS", "3"))') == 2 - assert workflow.count("configured_max_rounds = 3") == 2 - assert workflow.count("max_rounds = min(max(configured_max_rounds, 1), 10)") == 2 - assert "gh issue edit" in workflow - assert 'auto_merge_label' in workflow - assert 'human_review_label' in workflow - assert workflow.count("load_policy_labels(DEFAULT_POLICY_PATH)") == 2 - assert workflow.count('if [ -n "${guard_label}" ]; then') == 2 - assert workflow.count('if [ -n "${human_review_label}" ]; then') == 2 - assert workflow.count("Skipping stale guarded auto-merge label cleanup") == 2 - assert workflow.count("Skipped stale guarded auto-merge label cleanup") == 2 - assert '--remove-label "${guard_label}"' in workflow - assert workflow.count('gh label create "${human_review_label}"') == 2 - assert "--color d93f0b" in workflow - assert "Codex remediation PR requires human review before merge." in workflow - assert '--add-label "${human_review_label}"' in workflow - assert workflow.count("Removed stale guarded auto-merge label") == 2 - assert workflow.count("Marked PR #") == 2 - assert workflow.count("Skipped adding human-review label after retry limit") == 2 - assert "--remove-label codex-bridge" in workflow - assert workflow.count("Upload Codex feedback diagnostics") == 2 - assert workflow.count("uses: actions/upload-artifact@v7") == 2 - assert "codex-pr-feedback-ci-${{ github.run_id }}" in workflow - assert "codex-pr-feedback-review-${{ github.run_id }}" in workflow - assert workflow.count("path: data/output/codex_feedback/") == 2 - assert workflow.count("if-no-files-found: warn") == 2 - assert "Codex PR Retry Limit Reached" in workflow - assert workflow.count("will try to mark the PR with the configured human-review label") == 2 - assert "re-apply `codex-bridge` only if another automated Codex pass is still appropriate" in workflow - assert "Codex PR CI Feedback" in workflow - assert "Codex PR Review Feedback" in workflow - - def test_automated_snapshot_publish_runs_after_source_input_refresh() -> None: workflow = PUBLISH_SNAPSHOT_ARTIFACTS.read_text(encoding="utf-8")