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
19 changes: 17 additions & 2 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,23 @@ semantic-audit = "run --profile tool --features cli --bin oracle-gen -- semantic
combo-verify = "run --profile tool --features combo-verify --bin combo-verify -- data/"
scrape-feeds = "run --release -p feed-scraper --"
tune-ai = "run --release --features tune --bin ai-tune --"
ai-gate = "run --bin ai-gate --"
ai-perf-gate = "run --bin ai-perf-gate --"
# AI gates run `server-release`, NOT the default dev profile and NOT `--release`.
# * dev (opt-level 0) made the PR gates exceed their 60-minute timeout on every
# run; the suite is ~14x faster optimized.
# * `--release` in this workspace is the WASM-SIZE profile (opt-level 'z',
# lto = true, codegen-units = 1, panic = 'abort') — size-optimized, slow to
# build, and the wrong shape for a native wall-clock gate.
# `server-release` is the native speed profile (opt-level 2, thin LTO,
# codegen-units 16, panic = 'unwind').
#
# These two aliases, scripts/ai-gate.sh, scripts/ai-perf-gate.sh and
# scripts/validate-ai-perf-reproducibility.sh must ALL name the same profile.
# ai-perf-gate re-spawns itself via current_exe() for each cold-process trial, so
# a parent built under one profile would spawn children under it while a script
# reads a different target/<profile>/ path — profile skew there yields confidently
# wrong numbers rather than an error.
ai-gate = "run --profile server-release --bin ai-gate --"
ai-perf-gate = "run --profile server-release --bin ai-perf-gate --"
engine-inventory = "run --quiet -p engine-inventory-gen"

[env]
Expand Down
18 changes: 12 additions & 6 deletions .github/workflows/ai-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,12 @@ jobs:
run: |
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).
# server-release profile (authoritative, set by the `cargo ai-perf-gate` alias):
# counter VALUES are profile-independent, and the shared rust-ai-gate cache stays
# coherent because every job in this workflow builds the same profile — the
# win-rate jobs populate it and the perf jobs reuse it. Expect one cold build on
# the first run after the dev -> server-release move.
# Runs PERF_SAMPLE_COUNT independent sample processes; compares the per-counter median (#4878).
- name: Run decision-cost perf gate
run: cargo ai-perf-gate

Expand All @@ -162,9 +165,12 @@ jobs:
run: |
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).
# server-release profile (authoritative, set by the `cargo ai-perf-gate` alias):
# counter VALUES are profile-independent, and the shared rust-ai-gate cache stays
# coherent because every job in this workflow builds the same profile — the
# win-rate jobs populate it and the perf jobs reuse it. Expect one cold build on
# the first run after the dev -> server-release move.
# 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
Expand Down
31 changes: 14 additions & 17 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,23 +52,20 @@ split-debuginfo = "unpacked"
# Measured on the linked artifact:
# readelf --debug-dump=info target/debug/ai-gate | grep -A1 'GNU C23'
# reported `-O0` for exactly that file before this override and `-O2` after it.
# This is NOT a no-op on the gates' payloads, and the honest statement of why is worth
# more than a clean claim. The AI reads the wall clock on its live decision path:
# `phase-ai/src/projection.rs:110` is `TIME_CAP = 15ms` and `:139-142` bails on it, and
# unlike `search.rs`'s and `planner/mod.rs`'s deadlines it is NOT gated on measurement
# mode. It is reachable at the gate's default `AiDifficulty::Medium` — registry.rs
# registers `EvasionRemovalPriorityPolicy` unconditionally, and its `velocity_score`
# calls `AiSession::get_or_project` → `project_to`, with the `projection_min_budget_ms`
# guard bypassed because a measurement-mode `Deadline` reports no remaining budget to
# compare against. A bail scores 0.0 where a completed projection scores up to +3.0, and
# that term picks the removal target. So making allocation faster lets more projections
# finish, which can change a target, a board, a winner and every counter downstream.
# That hazard is pre-existing and profile-wide (it fires on any faster or slower host);
# this override does not create it and cannot avoid it. It is recorded here because the
# obvious "allocator changes are invisible" claim is false, and the fix — gating
# `TIME_CAP` on measurement mode the way `search.rs:2012` does — belongs in its own
# change with its own baseline sign-off. `RandomState` (#4878) is a separate,
# already-documented source and is seeded from OS randomness, not allocation addresses.
# This override is still not *provably* payload-neutral, but the one known
# mechanism is now closed. The AI used to read the wall clock on its live decision
# path: `phase-ai/src/projection.rs` bailed a projection after a 15 ms wall-clock
# budget, and — unlike `search.rs`'s and `planner/mod.rs`'s deadlines — that
# budget was the one still missing the measurement-mode carve-out, so a faster
# allocator let more projections finish and could change a target, a board, a
# winner and every counter downstream. That is fixed:
# `projection::projection_deadline` returns `Deadline::none()` under
# `ExecutionMode::Measurement`, mirroring the other two, so the gates' payloads no
# longer contain a decision-affecting wall-clock read that allocation speed can
# move. What remains host-variable on the measurement path is `RandomState`
# iteration order (#4878), which is seeded from OS randomness, not allocation
# addresses. This block is kept because the obvious "allocator changes are
# invisible" claim was false once and the record is worth more than a clean claim.
# Scoped to the one package so nothing else loses debug fidelity.
[profile.dev.package.libmimalloc-sys]
opt-level = 2
Expand Down
Loading
Loading