headroom/.github/workflows/rust.yml
JD Davis 2c9eb7c5f1
feat(simulators): add provider simulator service (#2014)
## Description  
Adds a Rust-only `headroom-simulators` workspace crate: a deterministic
local upstream simulator service for Headroom proxy and pipeline
validation. It supplies configurable stubs plus bottled provider-shaped
responses for supported provider/path surfaces without calling real
LLMs.

## Type of Change  
- [x] Bug fix (non-breaking change that fixes an issue)  
- [x] New feature (non-breaking change that adds functionality)  
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [x] Documentation update  
- [ ] Performance improvement  
- [ ] Code refactoring (no functional changes)

## Changes Made  
- Added `crates/headroom-simulators` Rust crate with library and
`headroom-simulators` binary.
- Added clean domain classification for supported surfaces: Anthropic
`/v1/messages`, OpenAI chat/responses/conversations, Bedrock
invoke/stream routes, Vertex raw/stream predict, health, and generic
fallback.
- Added JSON-configured stub matching by method, path, body substring,
and JSON pointer.
- Added bottled provider-shaped JSON, SSE, and Bedrock EventStream
responses for unconfigured requests.
- Added a container `Dockerfile` and README for local/GitHub Actions
usage.
- Added unit and HTTP integration tests for defaults, configured stubs,
SSE, Vertex, and Bedrock EventStream behavior.
- Added proxy-level simulator-backed E2E tests that run Headroom against
the simulator across Anthropic, OpenAI Chat, OpenAI Responses, OpenAI
Conversations, Bedrock invoke/converse/streaming, Vertex raw/stream
predict, and upstream health.
- Added simulator-backed provider error-path E2E coverage for OpenAI
429, Anthropic 529, Bedrock 502, and Vertex 503 responses flowing
through Headroom unchanged.
- Added Headroom-owned preflight error E2E coverage proving Bedrock
missing credentials and invalid Vertex envelopes stop inside the proxy
instead of silently falling through to the simulator/provider.
- Fixed direct Rust `headroom-core` binaries/tests on Windows so Magika
initializes ONNX Runtime via `ort::init_from` from an explicit pip
`onnxruntime` library path, with fail-fast fallback only when no safe
runtime is discoverable.
- Added a Rust CI `simulator-e2e` matrix for `ubuntu-latest`,
`macos-latest`, and `windows-latest` that runs `cargo test -p
headroom-proxy --test e2e_simulators`.
- Gated dynamic Magika `Path`/`PathBuf` imports to Windows and x86_64
macOS so Linux clippy does not see unused dynamic-ORT-only imports.

## Testing  
- [ ] Unit tests pass (`pytest`)  
- [ ] Linting passes (`ruff check .`)  
- [ ] Type checking passes (`mypy headroom`)  
- [x] New tests added for new functionality  
- [x] Manual testing performed

### Test Output  
cargo fmt --all -- --check  
# passed  

cargo clippy --workspace -- -D warnings  
# passed  

$env:ORT_DYLIB_PATH=$null  
cargo test -p headroom-core transforms::magika_detector::tests:: --lib  
# 17 passed, 0 failed; Magika initialized from discovered pip
onnxruntime DLL

$env:ORT_DYLIB_PATH=$null  
cargo test --workspace  
# passed  

gitleaks protect --staged --no-banner --redact  
# no leaks found  

gitleaks git --log-opts="headroomlabs/main..HEAD" --no-banner --redact  
# 5 commits scanned; no leaks found

## Real Behavior Proof  
- **Environment:** Windows PowerShell, Rust toolchain `1.95.0`, clean
worktree from `headroomlabs/main` at `9bacf481`.
- **Exact simulator command / steps:**  
  - `cargo run -p headroom-simulators -- --listen 127.0.0.1:8789`  
- Point Headroom proxy upstream at `http://127.0.0.1:8789` for local
deterministic provider responses.
- Use optional `--config path/to/simulator.json` to bind exact request
fixtures.
- **Observed simulator result:**  
  - OpenAI chat default returns `chat.completion` shape.  
  - OpenAI Responses stream returns named SSE events.  
  - Vertex raw predict returns Anthropic message shape.  
- Bedrock stream can return binary `application/vnd.amazon.eventstream`
bytes.
  - Configured stubs override bottled defaults.  
- **Observed Magika result:**  
- Direct Rust `headroom-core` tests pass with `ORT_DYLIB_PATH` unset.
- Magika discovers the installed pip `onnxruntime.dll`, loads it via
`ort::init_from`, and only falls back if no safe runtime is available.
- **Not tested:**  
- No live provider calls; simulator behavior is intentionally offline
and deterministic.

## Review Readiness  
- [x] I have performed a self-review  
- [x] This PR is ready for human review

## Checklist  
- [x] My code follows the project's style guidelines  
- [x] I have performed a self-review of my code  
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation  
- [x] My changes generate no new warnings  
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes  
- [ ] I have updated the CHANGELOG.md if applicable

## Screenshots (if applicable)  
N/A

## Additional Notes  
No CHANGELOG entry was added because this introduces a developer/CI
simulator crate plus a Windows direct-Rust Magika runtime fix, without
changing shipped Python package behavior. The simulator intentionally
does not include a lightweight fallback LLM in this slice; unbound
inputs receive deterministic bottled responses so tests stay
reproducible and offline.
2026-07-11 09:41:49 -07:00

168 lines
5.2 KiB
YAML

name: rust
on:
push:
branches: [ main, rust-rewrite ]
paths:
- 'crates/**'
- 'Cargo.toml'
- 'Cargo.lock'
- 'rust-toolchain.toml'
- 'tests/parity/**'
- 'Makefile'
- '.github/workflows/rust.yml'
pull_request:
paths:
- 'crates/**'
- 'Cargo.toml'
- 'Cargo.lock'
- 'rust-toolchain.toml'
- 'tests/parity/**'
- 'Makefile'
- '.github/workflows/rust.yml'
schedule:
# Nightly parity run at 07:17 UTC (weekdays only). Phase 0 allows failure.
- cron: '17 7 * * 1-5'
concurrency:
group: rust-${{ github.ref }}
cancel-in-progress: true
# Default permissions: read-only. Individual jobs override only what they need.
# Mitigates CodeQL/CWE-275 (missing-workflow-permissions): the GITHUB_TOKEN
# defaults to whatever the repo policy is, which can be read-write. Pinning
# this here means even if the repo default changes, this workflow stays safe.
permissions:
contents: read
jobs:
test:
name: test (ubuntu)
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
- name: Install stable toolchain
# Pin action code to @stable (latest fixes), toolchain version
# via input. The @1.95.0 ref shipped action code that errors on
# ubuntu-latest with `detected conflict: 'bin/cargo-clippy'`
# because the pre-installed runner Rust collides with the
# clippy-preview component install.
uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.95.0
components: rustfmt, clippy
- name: Cache cargo registry + build
uses: Swatinem/rust-cache@v2
- name: cargo fmt --check
run: cargo fmt --all -- --check
- name: cargo clippy
run: cargo clippy --workspace -- -D warnings
- name: cargo test
run: cargo test --workspace
simulator-e2e:
name: simulator e2e (${{ matrix.os }})
runs-on: ${{ matrix.os }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v7
- name: Install stable toolchain
uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.95.0
- name: Cache cargo registry + build
uses: Swatinem/rust-cache@v2
- name: cargo test simulator-backed proxy e2e
run: cargo test -p headroom-proxy --test e2e_simulators
wheels:
name: wheels (${{ matrix.target }})
runs-on: ${{ matrix.os }}
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
maturin-target: x86_64
- os: macos-14
target: aarch64-apple-darwin
maturin-target: aarch64-apple-darwin
- os: macos-15-intel
target: x86_64-apple-darwin
maturin-target: x86_64-apple-darwin
# Intel macOS uses `ort-load-dynamic` (no prebuilt ORT from ort-sys);
# Apple Silicon bundles ORT via `ort-download-binaries-rustls-tls`.
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: "Build wheel (single-wheel architecture builds headroom-ai)"
uses: PyO3/maturin-action@v1
# Maturin reads `[tool.maturin]` from the root `pyproject.toml`
# which points at `crates/headroom-py/Cargo.toml` for the cdylib.
# Output is `headroom_ai-<ver>-<py>-<py>-<platform>.whl` containing
# both Python source and the compiled `headroom/_core.so`.
with:
command: build
args: --release --out dist
target: ${{ matrix.maturin-target }}
- name: Upload wheel artifact
uses: actions/upload-artifact@v7
with:
name: wheels-${{ matrix.target }}
path: dist/*.whl
audit:
name: audit
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.95.0
- uses: Swatinem/rust-cache@v2
- name: Install cargo-audit + cargo-deny
uses: taiki-e/install-action@v2
with:
tool: cargo-audit,cargo-deny
- name: cargo audit (soft-fail)
continue-on-error: true
run: cargo audit
- name: cargo deny check licenses
continue-on-error: true
run: cargo deny check licenses
parity-nightly:
name: parity (nightly, allowed to fail during Phase 0)
if: github.event_name == 'schedule'
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.95.0
- uses: actions/setup-python@v6
with:
python-version: '3.11'
- uses: Swatinem/rust-cache@v2
- name: Install deps
run: |
python -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install maturin
pip install -e .
- name: Run parity harness
run: |
source .venv/bin/activate
make test-parity