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
132 changes: 0 additions & 132 deletions .github/workflows/monthly_snapshot_audit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
24 changes: 5 additions & 19 deletions src/hk_equity_snapshot_pipelines/monthly_snapshot_audit_issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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()
Expand All @@ -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",
Expand Down Expand Up @@ -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.",
Expand All @@ -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"],
Expand All @@ -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"
Expand All @@ -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.",
"",
Expand Down Expand Up @@ -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",
Expand Down
4 changes: 2 additions & 2 deletions tests/test_monthly_snapshot_audit_issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
26 changes: 11 additions & 15 deletions tests/test_monthly_snapshot_audit_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down
Loading