Skip to content

Strategy Optimization Watcher #43

Strategy Optimization Watcher

Strategy Optimization Watcher #43

name: Strategy Optimization Watcher
on:
workflow_dispatch:
inputs:
source_repo:
description: "Repository that owns strategy metrics and receives optimization issues"
required: true
default: "QuantStrategyLab/CryptoLivePoolPipelines"
source_ref:
description: "Source repository ref to inspect"
required: false
default: "main"
metrics_path:
description: "JSON metrics payload path inside the source repository"
required: false
default: "data/output/strategy_metrics.json"
dry_run:
description: "Do not create GitHub issues"
required: false
type: boolean
default: true
schedule:
- cron: "17 3 * * *"
permissions:
contents: read
issues: write
concurrency:
group: strategy-optimization-watcher-${{ github.event.inputs.source_repo || vars.STRATEGY_WATCH_SOURCE_REPO || 'QuantStrategyLab/CryptoLivePoolPipelines' }}
cancel-in-progress: false
jobs:
strategy-optimization-watcher:
runs-on: ubuntu-latest
timeout-minutes: 15
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
SOURCE_REPO: ${{ github.event.inputs.source_repo || vars.STRATEGY_WATCH_SOURCE_REPO || 'QuantStrategyLab/CryptoLivePoolPipelines' }}
SOURCE_REF: ${{ github.event.inputs.source_ref || vars.STRATEGY_WATCH_SOURCE_REF || 'main' }}
METRICS_PATH: ${{ github.event.inputs.metrics_path || vars.STRATEGY_WATCH_METRICS_PATH || 'data/output/strategy_metrics.json' }}
STRATEGY_WATCH_DRY_RUN: ${{ github.event_name == 'workflow_dispatch' && format('{0}', inputs.dry_run) || vars.STRATEGY_WATCH_DRY_RUN || 'true' }}
ALLOWED_SOURCE_REPOS: ${{ vars.STRATEGY_WATCH_ALLOWED_SOURCE_REPOS || 'QuantStrategyLab/CryptoLivePoolPipelines' }}
ALLOWED_SOURCE_REFS: ${{ vars.STRATEGY_WATCH_ALLOWED_SOURCE_REFS || 'main' }}
steps:
- name: Checkout Bridge
uses: actions/checkout@v6.0.3
with:
path: bridge
persist-credentials: false
- name: Detect GitHub App Credentials
id: app_credentials
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: Resolve Source Repository Name
id: source_repo
run: |
set -euo pipefail
if [[ ! "${SOURCE_REPO}" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]]; then
echo "Invalid SOURCE_REPO: ${SOURCE_REPO}. Expected owner/name." >&2
exit 1
fi
allowed_match=false
IFS=',' read -ra allowed_repos <<< "${ALLOWED_SOURCE_REPOS}"
for allowed_repo in "${allowed_repos[@]}"; do
allowed_repo="${allowed_repo//[[:space:]]/}"
if [ "${allowed_repo}" = "${SOURCE_REPO}" ]; then
allowed_match=true
break
fi
done
if [ "${allowed_match}" != "true" ]; then
echo "SOURCE_REPO is not allowed for strategy watcher: ${SOURCE_REPO}" >&2
exit 1
fi
ref_allowed=false
IFS=',' read -ra allowed_refs <<< "${ALLOWED_SOURCE_REFS}"
for allowed_ref in "${allowed_refs[@]}"; do
allowed_ref="${allowed_ref//[[:space:]]/}"
if [ "${allowed_ref}" = "${SOURCE_REF}" ]; then
ref_allowed=true
break
fi
done
if [ "${ref_allowed}" != "true" ]; then
echo "SOURCE_REF is not allowed for strategy watcher: ${SOURCE_REF}" >&2
exit 1
fi
owner="${SOURCE_REPO%%/*}"
repository="${SOURCE_REPO#*/}"
echo "owner=${owner}" >> "$GITHUB_OUTPUT"
echo "repository=${repository}" >> "$GITHUB_OUTPUT"
- name: Create GitHub App Token For Source Repository
id: source_app_token
if: steps.app_credentials.outputs.available == 'true'
continue-on-error: true
uses: actions/create-github-app-token@v3.2.0
with:
app-id: ${{ vars.CROSS_REPO_GITHUB_APP_ID }}
private-key: ${{ secrets.CROSS_REPO_GITHUB_APP_PRIVATE_KEY }}
owner: ${{ steps.source_repo.outputs.owner }}
repositories: ${{ steps.source_repo.outputs.repository }}
permission-contents: read
permission-issues: write
- name: Verify Source Repository Token
env:
SOURCE_APP_TOKEN: ${{ steps.source_app_token.outputs.token }}
run: |
set -euo pipefail
if [ "${SOURCE_REPO}" != "${GITHUB_REPOSITORY}" ] && [ -z "${SOURCE_APP_TOKEN:-}" ]; then
echo "Cross-repository strategy watcher requires CROSS_REPO_GITHUB_APP_ID and CROSS_REPO_GITHUB_APP_PRIVATE_KEY." >&2
exit 1
fi
- name: Checkout Source Metrics
uses: actions/checkout@v6.0.3
with:
repository: ${{ env.SOURCE_REPO }}
ref: ${{ env.SOURCE_REF }}
path: source
token: ${{ steps.source_app_token.outputs.token || github.token }}
persist-credentials: false
- name: Fetch strategy metrics from latest Monthly Publish artifact
id: fetch-metrics
env:
GH_TOKEN: ${{ steps.source_app_token.outputs.token || github.token }}
run: |
set -euo pipefail
echo "Looking for latest successful Monthly Publish run on ${SOURCE_REF} in ${SOURCE_REPO}..."
WORKFLOW_FILE="monthly_publish.yml"
# Restrict to the canonical branch so we never consume metrics from a
# feature-branch or PR workflow run.
RUN_ID=$(gh run list --repo "${SOURCE_REPO}" --workflow "${WORKFLOW_FILE}" --branch "${SOURCE_REF}" --status success --limit 1 --json databaseId --jq '.[0].databaseId // ""')
if [ -z "${RUN_ID}" ]; then
echo "No successful Monthly Publish run on ${SOURCE_REF} in ${SOURCE_REPO} — metrics not available yet"
echo "downloaded=false" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "Found run ${RUN_ID}, downloading artifacts..."
mkdir -p source/data/output
# Artifact download failures (expired artifacts, missing bundles) are
# expected when the monthly publish hasn't run recently or doesn't
# produce the file yet. Let the watcher script skip gracefully.
if ! gh run download "${RUN_ID}" --repo "${SOURCE_REPO}" --dir source/data/output/_artifacts 2>/dev/null; then
echo "Artifact download from run ${RUN_ID} failed (may have expired or not contain the expected bundle)"
echo "downloaded=false" >> "$GITHUB_OUTPUT"
exit 0
fi
# The artifact name is dynamic; search for strategy_metrics.json in all downloaded artifacts
METRICS_FILE=$(find source/data/output/_artifacts -name "strategy_metrics.json" -type f 2>/dev/null | head -1)
if [ -n "${METRICS_FILE}" ]; then
cp "${METRICS_FILE}" source/data/output/strategy_metrics.json
echo "Downloaded strategy_metrics.json from run ${RUN_ID}"
echo "downloaded=true" >> "$GITHUB_OUTPUT"
else
echo "strategy_metrics.json not found in Monthly Publish artifacts — source repo may not generate it yet"
echo "downloaded=false" >> "$GITHUB_OUTPUT"
fi
- name: Run Strategy Optimization Watcher
env:
GH_TOKEN: ${{ steps.source_app_token.outputs.token || github.token }}
STRATEGY_WATCH_SOURCE_ROOT: ${{ github.workspace }}/source
STRATEGY_WATCH_METRICS_PATH: ${{ env.METRICS_PATH }}
STRATEGY_WATCH_SOURCE_REPO: ${{ env.SOURCE_REPO }}
working-directory: bridge
run: |
set -euo pipefail
mkdir -p data/output/strategy_optimization_watcher
python scripts/run_strategy_optimization_watcher.py | tee data/output/strategy_optimization_watcher/result.json
- name: Upload watcher diagnostics
if: always()
uses: actions/upload-artifact@v7
with:
name: strategy-optimization-watcher-${{ github.run_id }}
path: bridge/data/output/strategy_optimization_watcher/
if-no-files-found: warn