Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 97 additions & 77 deletions .github/workflows/monthly_publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -199,106 +199,126 @@ jobs:
CryptoCodexAuditBridge
permission-contents: write

- name: Trigger Self-hosted Codex Monthly Review
if: success() && env.PUBLISH_ENABLED != 'false' && env.SELFHOSTED_CODEX_REVIEW_ENABLED != 'false'
- name: Trigger Monthly Review Automation
if: success() && env.PUBLISH_ENABLED != 'false'
env:
APP_TOKEN: ${{ steps.codex_review_app_token.outputs.token }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
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 }}
ISSUE_URL: ${{ steps.review_issue.outputs.issue_url }}
TARGET_REPOSITORY: ${{ env.SELFHOSTED_CODEX_REVIEW_REPOSITORY }}
REVIEW_MODE: ${{ env.SELFHOSTED_CODEX_REVIEW_MODE }}
AUTO_MERGE: ${{ env.SELFHOSTED_CODEX_REVIEW_AUTO_MERGE }}
CODEX_REVIEW_ENABLED: ${{ env.SELFHOSTED_CODEX_REVIEW_ENABLED }}
LEGACY_API_REVIEW_ENABLED: ${{ env.LEGACY_AI_REVIEW_ENABLED }}
run: |
set -euo pipefail
python - <<'PY'
import json
import os
import re
import urllib.error
import urllib.request

token = os.environ.get("APP_TOKEN", "").strip() or os.environ.get("CODEX_AUDIT_DISPATCH_TOKEN", "").strip()
if not token:
raise RuntimeError(
"Self-hosted Codex review dispatch requires either a GitHub App token "
"or CODEX_AUDIT_DISPATCH_TOKEN"
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": "crypto-leader-rotation-monthly-publish",
},
)
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 self-hosted review 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 self-hosted review mode: {mode}")
with urllib.request.urlopen(request) as response:
return response.status

payload = {
"event_type": "monthly-review-created",
"client_payload": {
"source_repo": os.environ["GITHUB_REPOSITORY"],
"source_ref": os.environ["GITHUB_REF_NAME"],
"issue_number": os.environ["ISSUE_NUMBER"],
"issue_url": os.environ["ISSUE_URL"],
"mode": mode,
"auto_merge": os.environ["AUTO_MERGE"].strip().lower() == "true",
},
}
request = urllib.request.Request(
f"https://api.github.com/repos/{target_repository}/dispatches",
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": "crypto-leader-rotation-monthly-publish",
},
)
with urllib.request.urlopen(request) as response:
if response.status not in (201, 204):
raise RuntimeError(f"Unexpected dispatch status: {response.status}")
print(
f"Dispatched self-hosted Codex review for issue #{os.environ['ISSUE_NUMBER']} "
f"to {target_repository}"
)
PY
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 review 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 review 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 review mode: {mode}")
payload = {
"event_type": "monthly-review-created",
"client_payload": {
"source_repo": os.environ["GITHUB_REPOSITORY"],
"source_ref": os.environ["GITHUB_REF_NAME"],
"issue_number": os.environ["ISSUE_NUMBER"],
"issue_url": os.environ["ISSUE_URL"],
"mode": mode,
"auto_merge": os.environ["AUTO_MERGE"].strip().lower() == "true",
},
}
status = dispatch(token, f"https://api.github.com/repos/{target_repository}/dispatches", payload)
if status not in (201, 204):
raise RuntimeError(f"Unexpected Codex dispatch status: {status}")
print(
f"Dispatched CryptoCodexAuditBridge review for issue #{os.environ['ISSUE_NUMBER']} "
f"to {target_repository}"
)

- name: Trigger Legacy AI Monthly Review
if: success() && env.PUBLISH_ENABLED != 'false' && env.LEGACY_AI_REVIEW_ENABLED == 'true'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}
GITHUB_REF_NAME: ${{ github.ref_name }}
ISSUE_NUMBER: ${{ steps.review_issue.outputs.issue_number }}
run: |
set -euo pipefail
python - <<'PY'
import json
import os
import urllib.request
def dispatch_legacy_api() -> None:
if not os.environ.get("ANTHROPIC_API_KEY", "").strip():
raise RuntimeError("Legacy API review fallback is enabled but ANTHROPIC_API_KEY is not configured")
if not os.environ.get("OPENAI_API_KEY", "").strip():
raise RuntimeError("Legacy API review fallback is enabled but OPENAI_API_KEY is not configured")
repository = os.environ["GITHUB_REPOSITORY"]
ref_name = os.environ["GITHUB_REF_NAME"]
issue_number = os.environ["ISSUE_NUMBER"]
payload = {"ref": ref_name, "inputs": {"issue_number": issue_number}}
status = dispatch(
os.environ["GITHUB_TOKEN"],
f"https://api.github.com/repos/{repository}/actions/workflows/ai_review.yml/dispatches",
payload,
)
if status not in (201, 204):
raise RuntimeError(f"Unexpected legacy AI dispatch status: {status}")
print(f"Dispatched legacy API AI monthly review for issue #{issue_number} on ref {ref_name}")

token = os.environ["GITHUB_TOKEN"]
repository = os.environ["GITHUB_REPOSITORY"]
ref_name = os.environ["GITHUB_REF_NAME"]
issue_number = os.environ["ISSUE_NUMBER"]
payload = {"ref": ref_name, "inputs": {"issue_number": issue_number}}
request = urllib.request.Request(
f"https://api.github.com/repos/{repository}/actions/workflows/ai_review.yml/dispatches",
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": "crypto-leader-rotation-monthly-publish",
},
codex_error = ""
if enabled("CODEX_REVIEW_ENABLED"):
try:
dispatch_codex()
raise SystemExit(0)
except (RuntimeError, urllib.error.URLError, urllib.error.HTTPError) as exc:
codex_error = str(exc)
print(f"Codex monthly review dispatch failed: {codex_error}")
else:
codex_error = "Codex monthly review is disabled"
print(codex_error)

if enabled("LEGACY_API_REVIEW_ENABLED"):
try:
dispatch_legacy_api()
raise SystemExit(0)
except (RuntimeError, urllib.error.URLError, urllib.error.HTTPError) as exc:
raise RuntimeError(
f"Codex monthly review failed ({codex_error}); legacy API review fallback also failed: {exc}"
) from exc

raise RuntimeError(
"Codex monthly review failed or was disabled, and legacy API fallback is disabled. "
"Set LEGACY_AI_REVIEW_ENABLED=true and configure ANTHROPIC_API_KEY plus OPENAI_API_KEY "
f"to enable the API fallback. Codex failure: {codex_error}"
)
with urllib.request.urlopen(request) as response:
if response.status not in (201, 204):
raise RuntimeError(f"Unexpected dispatch status: {response.status}")
print(f"Triggered ai_review.yml for issue #{issue_number} on ref {ref_name}")
PY

- name: Write Release Heartbeat
Expand Down
17 changes: 11 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -533,31 +533,36 @@ Behavior:

## Automated AI Monthly Review

After the monthly report bundle is assembled, the workflow automatically creates a GitHub Issue containing the full `ai_review_input.md` content. A separate workflow (`ai_review.yml`) listens for issues labeled `monthly-review` and triggers Claude Code Action (Anthropic API, Sonnet model) to analyze the report.
After the monthly report bundle is assembled, the workflow creates a GitHub Issue containing the full `ai_review_input.md` content. The default automated review route dispatches `QuantStrategyLab/CryptoCodexAuditBridge`, which runs Codex on a self-hosted VPS runner. Codex reads the monthly issue, posts the audit result back to the issue, and opens a PR directly for safe low-risk fixes.

The legacy API-based dual AI review remains available as a compatibility fallback. Set `LEGACY_AI_REVIEW_ENABLED=true` and configure both `ANTHROPIC_API_KEY` and `OPENAI_API_KEY` to allow the monthly workflow to dispatch `ai_review.yml` if the Codex bridge dispatch fails. When Codex dispatch fails and the legacy API fallback is not enabled or not configured, the monthly publish workflow fails loudly instead of silently skipping review.

The AI review covers:

- **Release consistency**: cross-checks `live_pool.json`, `release_manifest.json`, and `release_status_summary.json` for agreement on date, version, mode, pool size, and symbols
- **Anomaly detection**: flags unexpected warnings, stale artifacts, validation failures, or suspicious ranking scores
- **Downstream impact**: notes implications for BinancePlatform (the downstream execution engine), including pool changes and degradation risk
- **Operator action items**: summarizes the checklist and adds any AI-identified follow-up items
- **Code improvements**: structured review output feeds the monthly optimization planner; concrete low-risk `auto-pr-safe` tasks for `CryptoSnapshotPipelines` are queued to the self-hosted ccbot/Codex runner with `codex-bridge`, while sensitive selector changes remain manual-review work
- **Code improvements**: Codex can open focused PRs directly for low-risk reporting, validation, workflow, test, or documentation defects; sensitive selector changes remain manual-review work

All analysis is posted in both English and Chinese.
Review output is posted back to the monthly issue. The legacy API workflow still renders bilingual output when enabled.

### Required GitHub Secret
### Optional Legacy API Fallback Secrets

- `ANTHROPIC_API_KEY`: Anthropic API key for Claude Code Action
- `OPENAI_API_KEY`: OpenAI API key for the secondary monthly review

The default production configuration does not need these API secrets because it uses `CryptoCodexAuditBridge`. Configure them only if you want the legacy dual AI fallback.

Setup:

```bash
gh variable set LEGACY_AI_REVIEW_ENABLED --body true
gh secret set ANTHROPIC_API_KEY --body "sk-ant-..."
gh secret set OPENAI_API_KEY --body "sk-..."
```

The AI review workflow runs on `ubuntu-latest` (no self-hosted runner required) and costs approximately $0.01-0.05 per monthly run. Code remediation is a separate phase: repo-scoped low-risk tasks are created as GitHub issues, and safe `CryptoSnapshotPipelines` tasks are handed to the VPS ccbot/Codex bridge instead of GitHub-hosted Claude Action.
The legacy AI review workflow runs on `ubuntu-latest` (no self-hosted runner required) and costs approximately $0.01-0.05 per monthly run. It is retained for open-source compatibility and emergency fallback, not as the normal production path.

### Codex Remediation and Auto-Merge Gate

Expand Down Expand Up @@ -1018,7 +1023,7 @@ Practical review file selection:

Automated AI handoff:

The workflow now automatically creates a GitHub Issue with the `monthly-review` label, which triggers Claude Code Action to analyze the report. See the "Automated AI Monthly Review" section for details.
The workflow automatically creates a GitHub Issue with the `monthly-review` label, then dispatches `CryptoCodexAuditBridge`. If the Codex bridge dispatch fails and `LEGACY_AI_REVIEW_ENABLED=true` with both API secrets configured, the workflow falls back to the legacy dual AI `ai_review.yml`; otherwise it fails loudly. See the "Automated AI Monthly Review" section for details.

Manual AI handoff (fallback):

Expand Down
6 changes: 5 additions & 1 deletion docs/operator_runbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,11 @@ Boundary rules:

## Monthly Codex Remediation

The monthly optimization planner may create repo-scoped follow-up issues after AI review. For `CryptoSnapshotPipelines`, low-risk non-experiment tasks marked `[auto-pr-safe]` are queued to the self-hosted VPS ccbot/Codex runner with the `codex-bridge` label. GitHub-hosted Claude Action remains only a manual-dispatch fallback; normal automated code remediation should run through ccbot/Codex.
The monthly publish workflow creates a `monthly-review` issue, then dispatches `CryptoCodexAuditBridge` as the primary automated review and remediation path. Codex posts its audit result back to the issue and may open a focused PR directly for low-risk reporting, validation, workflow, test, or documentation fixes.

The legacy Claude/OpenAI dual review workflow remains only as a compatibility fallback. Enable it with `LEGACY_AI_REVIEW_ENABLED=true` and configure both `ANTHROPIC_API_KEY` and `OPENAI_API_KEY`. If Codex dispatch fails and the legacy API fallback is disabled or missing credentials, the monthly publish workflow fails loudly.

The monthly optimization planner may still create repo-scoped follow-up issues after AI review. For `CryptoSnapshotPipelines`, low-risk non-experiment tasks marked `[auto-pr-safe]` are queued to the self-hosted VPS ccbot/Codex runner with the `codex-bridge` label. GitHub-hosted Claude Action remains only a manual-dispatch fallback; normal automated code remediation should run through ccbot/Codex.

Codex remediation PRs must use branch `codex/monthly-optimization-issue-<issue-number>`, include `<!-- auto-optimization-pr:issue-<issue-number> -->` in the PR body, and start as draft. The auto-merge workflow only merges after CI passes, the PR is ready for review, `auto-merge-ok` is present, task-level auto-merge eligibility is recorded, and changed files stay outside guarded selector/config paths.

Expand Down
8 changes: 7 additions & 1 deletion tests/test_monthly_publish_workflow_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,16 @@ def test_monthly_review_issue_creation_does_not_require_gh_cli(self) -> None:
self.assertIn("CryptoCodexAuditBridge", workflow)
self.assertIn("permission-contents: write", workflow)
self.assertIn("APP_TOKEN", workflow)
self.assertIn("Trigger Monthly Review Automation", workflow)
self.assertIn("CODEX_AUDIT_DISPATCH_TOKEN", workflow)
self.assertIn("ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}", workflow)
self.assertIn("OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}", workflow)
self.assertIn("Codex monthly review dispatch failed", workflow)
self.assertIn("legacy API review fallback", workflow)
self.assertIn("legacy API fallback is disabled", workflow)
self.assertIn("monthly-review-created", workflow)
self.assertIn("/repos/{target_repository}/dispatches", workflow)
self.assertIn("LEGACY_AI_REVIEW_ENABLED == 'true'", workflow)
self.assertIn("LEGACY_API_REVIEW_ENABLED", workflow)
self.assertIn("/actions/workflows/ai_review.yml/dispatches", workflow)

def test_ai_review_workflow_supports_dispatch_and_comment_posting(self) -> None:
Expand Down