Skip to content

AI gate

AI gate #4

Workflow file for this run

name: AI gate
on:
pull_request:
branches: [main]
paths:
- 'crates/phase-ai/**'
- 'data/card-data.json'
- '.cargo/config.toml'
- '.github/workflows/ai-gate.yml'
schedule:
- cron: "0 9 * * *"
workflow_dispatch:
permissions:
contents: read
issues: write
jobs:
ai-gate:
name: Paired-seed AI gate
if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache-shared-key: rust-ai-gate
- name: Restore generated card data
id: cardgen-cache
uses: actions/cache@v4
with:
path: |
data/card-data.json
data/card-names.json
key: cardgen-${{ hashFiles('data/mtgjson/AtomicCards.json', 'crates/engine/src/**/*.rs', 'crates/engine/Cargo.toml', 'Cargo.lock') }}
- name: Generate card data
if: steps.cardgen-cache.outputs.cache-hit != 'true'
run: |
mkdir -p data/mtgjson
source scripts/lib/mtgjson-fetch.sh
mtgjson_download AtomicCards.json data/mtgjson/AtomicCards.json
cargo run --profile tool --features cli --bin oracle-gen -- data/ --stats --names-out data/card-names.json > data/card-data.json
- name: Run quick AI gate
run: cargo ai-gate --games 10
ai-gate-nightly:
name: Nightly AI gate drift monitor
if: github.event_name == 'schedule'
runs-on: ubuntu-latest
# `--full-suite --games 100` consistently exceeded the prior 90m ceiling
# (never completed since at least 2026-06-13). Raised to 300m — generous
# headroom under GitHub's 360m hosted-runner cap — to get a first completion
# and a real-duration baseline to right-size from. The ceiling only bills the
# failure case; a job that finishes in 2h stops at 2h.
timeout-minutes: 300
steps:
- uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache-shared-key: rust-ai-gate
- name: Restore generated card data
id: cardgen-cache
uses: actions/cache@v4
with:
path: |
data/card-data.json
data/card-names.json
key: cardgen-${{ hashFiles('data/mtgjson/AtomicCards.json', 'crates/engine/src/**/*.rs', 'crates/engine/Cargo.toml', 'Cargo.lock') }}
- name: Generate card data
if: steps.cardgen-cache.outputs.cache-hit != 'true'
run: |
mkdir -p data/mtgjson
source scripts/lib/mtgjson-fetch.sh
mtgjson_download AtomicCards.json data/mtgjson/AtomicCards.json
cargo run --profile tool --features cli --bin oracle-gen -- data/ --stats --names-out data/card-names.json > data/card-data.json
- name: Run full AI gate
id: gate
continue-on-error: true
run: |
cargo ai-gate --full-suite --games 100 > target/ai-gate-report.md
- name: Open or update drift issue
if: steps.gate.outcome == 'failure'
env:
GH_TOKEN: ${{ github.token }}
run: |
title="Nightly AI gate drift"
body="$(cat target/ai-gate-report.md)"
existing="$(gh issue list --label ai-gate-drift --state open --json number --jq '.[0].number')"
if [ -n "$existing" ]; then
gh issue comment "$existing" --body "$body"
else
gh issue create --title "$title" --label ai-gate-drift --body "$body"
fi
- name: Fail only on infrastructure errors
if: steps.gate.outcome == 'cancelled'
run: exit 1
ai-perf-gate:
name: Decision-cost perf gate
if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache-shared-key: rust-ai-gate
- name: Restore generated card data
id: cardgen-cache
uses: actions/cache@v4
with:
path: |
data/card-data.json
data/card-names.json
key: cardgen-${{ hashFiles('data/mtgjson/AtomicCards.json', 'crates/engine/src/**/*.rs', 'crates/engine/Cargo.toml', 'Cargo.lock') }}
- name: Generate card data
if: steps.cardgen-cache.outputs.cache-hit != 'true'
run: |
mkdir -p data/mtgjson
source scripts/lib/mtgjson-fetch.sh
mtgjson_download AtomicCards.json data/mtgjson/AtomicCards.json
cargo run --profile tool --features cli --bin oracle-gen -- data/ --stats --names-out data/card-names.json > data/card-data.json
# debug profile (authoritative): counter VALUES are profile-independent and the
# shared rust-ai-gate cache is debug-warm (win-rate jobs populate it). Runs
# PERF_SAMPLE_COUNT independent sample processes; compares the per-counter median (#4878).
- name: Run decision-cost perf gate
run: cargo ai-perf-gate
ai-perf-gate-nightly:
name: Nightly decision-cost perf drift monitor
if: github.event_name == 'schedule'
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache-shared-key: rust-ai-gate
- name: Restore generated card data
id: cardgen-cache
uses: actions/cache@v4
with:
path: |
data/card-data.json
data/card-names.json
key: cardgen-${{ hashFiles('data/mtgjson/AtomicCards.json', 'crates/engine/src/**/*.rs', 'crates/engine/Cargo.toml', 'Cargo.lock') }}
- name: Generate card data
if: steps.cardgen-cache.outputs.cache-hit != 'true'
run: |
mkdir -p data/mtgjson
source scripts/lib/mtgjson-fetch.sh
mtgjson_download AtomicCards.json data/mtgjson/AtomicCards.json
cargo run --profile tool --features cli --bin oracle-gen -- data/ --stats --names-out data/card-names.json > data/card-data.json
# debug profile (authoritative): counter VALUES are profile-independent and the
# shared rust-ai-gate cache is debug-warm (win-rate jobs populate it). Runs
# PERF_SAMPLE_COUNT independent sample processes; compares the per-counter median (#4878).
# `cargo` build progress goes to stderr and spawned children are Stdio::null on
# stdout, so the redirect captures only the binary's clean markdown table.
- name: Run decision-cost perf gate
id: gate
continue-on-error: true
run: |
cargo ai-perf-gate > target/ai-perf-gate-report.md
- name: Open or update drift issue
if: steps.gate.outcome == 'failure'
env:
GH_TOKEN: ${{ github.token }}
run: |
title="Nightly decision-cost perf drift"
body="$(cat target/ai-perf-gate-report.md)"
existing="$(gh issue list --label ai-perf-drift --state open --json number --jq '.[0].number')"
if [ -n "$existing" ]; then
gh issue comment "$existing" --body "$body"
else
gh issue create --title "$title" --label ai-perf-drift --body "$body"
fi
- name: Fail only on infrastructure errors
if: steps.gate.outcome == 'cancelled'
run: exit 1