diff --git a/.github/workflows/monthly_snapshot_audit.yml b/.github/workflows/monthly_snapshot_audit.yml index ef5551b..f901b90 100644 --- a/.github/workflows/monthly_snapshot_audit.yml +++ b/.github/workflows/monthly_snapshot_audit.yml @@ -15,18 +15,11 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 permissions: - actions: write contents: read issues: write env: MONTHLY_SNAPSHOT_AUDIT_ENABLED: ${{ vars.MONTHLY_SNAPSHOT_AUDIT_ENABLED || 'true' }} 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' }} steps: - name: Checkout @@ -51,13 +44,11 @@ jobs: env: GITHUB_REPOSITORY: ${{ github.repository }} GITHUB_REF_NAME: ${{ github.ref_name }} - CODEX_AUDIT_REPOSITORY: ${{ env.CODEX_AUDIT_BRIDGE_REPOSITORY }} run: | set -euo pipefail python scripts/write_monthly_snapshot_audit_issue.py \ --source-repo "${GITHUB_REPOSITORY}" \ --source-ref "${GITHUB_REF_NAME}" \ - --codex-audit-repository "${CODEX_AUDIT_REPOSITORY}" python - <<'PY' >> "$GITHUB_OUTPUT" import json with open("data/output/monthly_snapshot_audit/monthly_snapshot_audit_issue.json", "r", encoding="utf-8") as handle: @@ -164,126 +155,3 @@ jobs: print(f"issue_number={issue['number']}", file=output) print(f"issue_url={issue['html_url']}", file=output) PY - - - name: Detect Codex Audit GitHub App Credentials - id: codex_review_app_credentials - if: success() && env.MONTHLY_SNAPSHOT_AUDIT_ENABLED != 'false' && env.CODEX_AUDIT_ENABLED != 'false' - 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@bcd2ba49218906704ab6c1aa796996da409d3eb1 # 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 Snapshot Review Automation - if: success() && env.MONTHLY_SNAPSHOT_AUDIT_ENABLED != 'false' && env.CODEX_AUDIT_ENABLED != 'false' - env: - APP_TOKEN: ${{ steps.codex_review_app_token.outputs.token }} - APP_TOKEN_CREATION_OUTCOME: ${{ steps.codex_review_app_token.outcome }} - CODEX_AUDIT_DISPATCH_TOKEN: ${{ secrets.CODEX_AUDIT_DISPATCH_TOKEN }} - GITHUB_REPOSITORY: ${{ github.repository }} - GITHUB_REF_NAME: ${{ github.ref_name }} - ISSUE_NUMBER: ${{ steps.audit_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: ${{ env.CODEX_AUDIT_AUTO_MERGE }} - run: | - set -euo pipefail - python - <<'PY' - import json - import os - import re - import time - import urllib.error - import urllib.request - - 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": "hk-equity-snapshot-monthly-audit", - }, - ) - with urllib.request.urlopen(request) as response: - return response.status - - 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; GitHub App token step outcome=" - f"{os.environ.get('APP_TOKEN_CREATION_OUTCOME', 'not_run')}" - ) - 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}") - 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, - "task": "monthly_snapshot_audit", - "auto_merge": str(os.environ["AUTO_MERGE"].strip().lower() == "true").lower(), - }, - } - dispatch_url = ( - f"https://api.github.com/repos/{target_repository}/actions/workflows/codex_audit.yml/dispatches" - ) - last_error: Exception | None = None - for attempt in range(1, 4): - try: - status = dispatch(token, dispatch_url, payload) - if status in (201, 204): - break - last_error = RuntimeError(f"Unexpected Codex dispatch status: {status}") - except urllib.error.HTTPError as exc: - if 400 <= exc.code < 500: - raise RuntimeError(f"Codex dispatch rejected with HTTP {exc.code}") from exc - last_error = exc - except urllib.error.URLError as exc: - last_error = exc - if attempt < 3: - time.sleep(attempt) - else: - raise RuntimeError( - "Codex audit dispatch failed after 3 attempts; " - "the monthly issue was preserved for safe retry" - ) from last_error - print( - f"Dispatched AIAuditBridge HK snapshot review for issue #{os.environ['ISSUE_NUMBER']} " - f"to {target_repository}" - ) - PY diff --git a/src/hk_equity_snapshot_pipelines/first_snapshot_evidence_bundle.py b/src/hk_equity_snapshot_pipelines/first_snapshot_evidence_bundle.py index a502438..de8147e 100644 --- a/src/hk_equity_snapshot_pipelines/first_snapshot_evidence_bundle.py +++ b/src/hk_equity_snapshot_pipelines/first_snapshot_evidence_bundle.py @@ -114,7 +114,7 @@ def build_first_snapshot_evidence_bundle( "Attach immutable gs://, s3://, or https:// evidence URIs without secret-like query parameters.", "Run survivorship-safe walk-forward backtests with at least three OOS folds and max drawdown <= 30%.", "Run LongBridge and IBKR dry-run order previews before any runtime promotion.", - "Submit the completed evidence through AIAuditBridge monthly audit before live-enable approval.", + "Submit the completed evidence with the monthly audit issue before live-enable approval.", ], "stop_conditions": [ "sample_artifacts_or_synthetic_data_used_as_evidence", diff --git a/src/hk_equity_snapshot_pipelines/low_vol_dividend_evidence_bundle.py b/src/hk_equity_snapshot_pipelines/low_vol_dividend_evidence_bundle.py index 36f41ae..fedf8f1 100644 --- a/src/hk_equity_snapshot_pipelines/low_vol_dividend_evidence_bundle.py +++ b/src/hk_equity_snapshot_pipelines/low_vol_dividend_evidence_bundle.py @@ -99,7 +99,7 @@ def build_low_vol_dividend_evidence_bundle( "Attach immutable gs://, s3://, or https:// evidence URIs without secret-like query parameters.", "Run survivorship-safe walk-forward backtests with at least three OOS folds and max drawdown <= 30%.", "Run LongBridge and IBKR dry-run order previews before any runtime promotion.", - "Submit the completed evidence through AIAuditBridge monthly audit before live-enable approval.", + "Submit the completed evidence with the monthly audit issue before live-enable approval.", ], "stop_conditions": [ "sample_artifacts_or_synthetic_data_used_as_evidence", diff --git a/src/hk_equity_snapshot_pipelines/monthly_snapshot_audit_issue.py b/src/hk_equity_snapshot_pipelines/monthly_snapshot_audit_issue.py index d2a0927..d528dc3 100644 --- a/src/hk_equity_snapshot_pipelines/monthly_snapshot_audit_issue.py +++ b/src/hk_equity_snapshot_pipelines/monthly_snapshot_audit_issue.py @@ -13,7 +13,6 @@ DEFAULT_SOURCE_REPO = "QuantStrategyLab/HkEquitySnapshotPipelines" DEFAULT_SOURCE_REF = "main" -DEFAULT_CODEX_AUDIT_REPOSITORY = "QuantStrategyLab/AIAuditBridge" MONTHLY_SNAPSHOT_AUDIT_TASK = "monthly_snapshot_audit" MONTHLY_SNAPSHOT_AUDIT_BUNDLE_VERSION = "hk_snapshot_monthly_audit_bundle.v1" DEFAULT_OUTPUT_DIR = Path("data/output/monthly_snapshot_audit") @@ -70,7 +69,6 @@ def build_monthly_snapshot_audit_issue( as_of_month: str | None = None, source_repo: str = DEFAULT_SOURCE_REPO, source_ref: str = DEFAULT_SOURCE_REF, - codex_audit_repository: str = DEFAULT_CODEX_AUDIT_REPOSITORY, ) -> dict[str, Any]: month = normalize_as_of_month(as_of_month) plan = build_first_snapshot_promotion_plan() @@ -82,15 +80,14 @@ def build_monthly_snapshot_audit_issue( [ f"# {title}", "", - "This issue is the monthly AI audit input for HK snapshot-backed strategy scaffolds.", - "It is intended for `AIAuditBridge` and does not publish artifacts, deploy Cloud Run, or place broker orders.", + "This issue is the monthly audit input for HK snapshot-backed strategy scaffolds.", + "It is a human-readable audit briefing only and does not publish artifacts, deploy Cloud Run, place broker orders, or dispatch retired AI review workflows.", "", "## Routing", "", f"- Source repo: `{source_repo}`", f"- Source ref: `{source_ref}`", - f"- Codex audit repo: `{codex_audit_repository}`", - f"- Codex task: `{MONTHLY_SNAPSHOT_AUDIT_TASK}`", + f"- Audit task: `{MONTHLY_SNAPSHOT_AUDIT_TASK}`", f"- Bundle version: `{MONTHLY_SNAPSHOT_AUDIT_BUNDLE_VERSION}`", "", "## Active snapshot candidate", @@ -140,7 +137,7 @@ def build_monthly_snapshot_audit_issue( f"- Active candidates: `{', '.join(plan['profiles_in_scope'])}`", f"- Live enablement without evidence: `{plan['live_enablement_allowed_without_evidence']}`", "", - "## Requested Codex output", + "## Requested human / AI review output", "", "Please provide a concise bilingual review covering release consistency, evidence gaps, downstream impact, " "and operator action items. Do not recommend production trading changes unless all evidence gates are present.", @@ -152,7 +149,6 @@ def build_monthly_snapshot_audit_issue( "issue_title": title, "source_repo": source_repo, "source_ref": source_ref, - "codex_audit_repository": codex_audit_repository, "codex_task": MONTHLY_SNAPSHOT_AUDIT_TASK, "profiles_in_scope": plan["profiles_in_scope"], "excluded_from_scope": plan["excluded_from_scope"], @@ -166,14 +162,12 @@ def write_monthly_snapshot_audit_issue( as_of_month: str | None = None, source_repo: str = DEFAULT_SOURCE_REPO, source_ref: str = DEFAULT_SOURCE_REF, - codex_audit_repository: str = DEFAULT_CODEX_AUDIT_REPOSITORY, ) -> dict[str, Any]: output_dir.mkdir(parents=True, exist_ok=True) payload = build_monthly_snapshot_audit_issue( as_of_month=as_of_month, source_repo=source_repo, source_ref=source_ref, - codex_audit_repository=codex_audit_repository, ) body_path = output_dir / "ai_review_input.md" summary_path = output_dir / "job_summary.md" @@ -185,8 +179,7 @@ def write_monthly_snapshot_audit_issue( f"## {payload['issue_title']}", "", f"- Source repo: `{payload['source_repo']}`", - f"- Codex bridge: `{payload['codex_audit_repository']}`", - f"- Codex task: `{payload['codex_task']}`", + f"- Audit task: `{payload['codex_task']}`", f"- Profiles in scope: `{', '.join(payload['profiles_in_scope'])}`", "- Scope: audit issue generation only; no publish, no deployment, no broker orders.", "", @@ -221,25 +214,18 @@ def main(argv: list[str] | None = None) -> int: parser.add_argument("--output-dir", default=str(DEFAULT_OUTPUT_DIR), help="Directory for the audit issue bundle.") parser.add_argument("--source-repo", default=DEFAULT_SOURCE_REPO, help="Source repository name.") parser.add_argument("--source-ref", default=DEFAULT_SOURCE_REF, help="Source branch or ref.") - parser.add_argument( - "--codex-audit-repository", - default=DEFAULT_CODEX_AUDIT_REPOSITORY, - help="Repository that owns the AIAuditBridge workflow.", - ) args = parser.parse_args(argv) payload = write_monthly_snapshot_audit_issue( output_dir=Path(args.output_dir), as_of_month=args.as_of_month, source_repo=args.source_repo, source_ref=args.source_ref, - codex_audit_repository=args.codex_audit_repository, ) print(json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True)) return 0 __all__ = [ - "DEFAULT_CODEX_AUDIT_REPOSITORY", "DEFAULT_OUTPUT_DIR", "DEFAULT_SOURCE_REF", "DEFAULT_SOURCE_REPO", diff --git a/tests/test_monthly_snapshot_audit_issue.py b/tests/test_monthly_snapshot_audit_issue.py index 44484ee..0a96b80 100644 --- a/tests/test_monthly_snapshot_audit_issue.py +++ b/tests/test_monthly_snapshot_audit_issue.py @@ -25,7 +25,7 @@ def test_build_monthly_snapshot_audit_issue_scopes_codex_review(): assert payload["issue_title"] == "HK Snapshot Monthly Audit: 2026-06" assert payload["codex_task"] == MONTHLY_SNAPSHOT_AUDIT_TASK assert "QuantStrategyLab/HkEquitySnapshotPipelines" in body - assert "AIAuditBridge" in body + assert "does not publish artifacts" in body assert "monthly_snapshot_audit" in body assert "hk_low_vol_dividend_quality_snapshot" in body assert "hk_shareholder_yield_quality" in body @@ -36,7 +36,7 @@ def test_build_monthly_snapshot_audit_issue_scopes_codex_review(): assert "bilingual notification evidence" in body assert "research-only / deprioritized" in body assert "no publish, no deployment, no broker orders" not in body.lower() - assert "deploy Cloud Run, or place broker orders" in body + assert "dispatch retired AI review workflows" in body assert "python scripts/print_snapshot_readiness.py --profile hk_low_vol_dividend_quality_snapshot --platform longbridge --json" in body assert "python scripts/print_snapshot_readiness.py --profile hk_low_vol_dividend_quality_snapshot --platform ibkr --json" in body assert "hkeq-validate-live-enable-evidence" in body diff --git a/tests/test_monthly_snapshot_audit_workflow.py b/tests/test_monthly_snapshot_audit_workflow.py index a72beac..7e5af5c 100644 --- a/tests/test_monthly_snapshot_audit_workflow.py +++ b/tests/test_monthly_snapshot_audit_workflow.py @@ -7,32 +7,28 @@ WORKFLOW_PATH = PROJECT_ROOT / ".github" / "workflows" / "monthly_snapshot_audit.yml" -def test_monthly_snapshot_audit_workflow_dispatches_codex_bridge(): +def test_monthly_snapshot_audit_workflow_keeps_issue_creation_without_bridge_dispatch(): workflow = WORKFLOW_PATH.read_text(encoding="utf-8") - assert "actions: write" in workflow assert "contents: read" in workflow assert "issues: write" in workflow + assert "actions: write" not in workflow assert "actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6" in workflow assert "actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6" in workflow assert "actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7" in workflow - assert "actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3" in workflow assert "write_monthly_snapshot_audit_issue.py" in workflow assert "monthly_snapshot_audit_issue.json" in workflow - assert "QuantStrategyLab/AIAuditBridge" in workflow - assert "CODEX_AUDIT_BRIDGE_REF" in workflow - assert '"ref": os.environ["CODEX_AUDIT_BRIDGE_REF"]' in workflow - assert "codex_audit.yml" in workflow - assert "/actions/workflows/codex_audit.yml/dispatches" in workflow - assert '"task": "monthly_snapshot_audit"' in workflow - assert "CODEX_AUDIT_DISPATCH_TOKEN" in workflow - assert "APP_TOKEN_CREATION_OUTCOME" in workflow assert "Reused issue" in workflow assert "existing_issues = request_json" in workflow - assert "for attempt in range(1, 4)" in workflow - assert "monthly issue was preserved for safe retry" in workflow - assert "CODEX_AUDIT_PROVIDER || 'auto'" in workflow - assert "CODEX_AUDIT_MODE || 'review_and_fix'" in workflow + for retired in ( + "AIAuditBridge", + "CODEX_AUDIT_", + "codex_audit.yml", + "create-github-app-token", + "Trigger Monthly Snapshot Review Automation", + "--codex-audit-repository", + ): + assert retired not in workflow def test_monthly_snapshot_audit_workflow_stays_source_safe():