Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
0f9b3c9
runner_design
epompeii Feb 1, 2026
039f1c4
runner_migration
epompeii Feb 2, 2026
08601aa
model
epompeii Feb 2, 2026
f1de61e
runners
epompeii Feb 2, 2026
1a4cf3d
job_update
epompeii Feb 2, 2026
8c0320e
api_runners
epompeii Feb 2, 2026
6df7eda
otel
epompeii Feb 2, 2026
b3acf38
clippy
epompeii Feb 2, 2026
9f721f9
runner_tokern
epompeii Feb 2, 2026
4e4759a
jobs_api
epompeii Feb 2, 2026
da80e3b
gen_types
epompeii Feb 2, 2026
2577f52
from_request
epompeii Feb 2, 2026
3d36863
claude_review
epompeii Feb 2, 2026
8c16100
long_poll
epompeii Feb 2, 2026
c47dd9c
claude_review
epompeii Feb 2, 2026
bf72cb6
dockerfile
epompeii Feb 2, 2026
6db6303
rate_limiting
epompeii Feb 2, 2026
d565ec2
ws
epompeii Feb 2, 2026
8c0b538
tests
epompeii Feb 3, 2026
3721703
claude_review
epompeii Feb 3, 2026
2b9ceb4
claude_md_update
epompeii Feb 3, 2026
8f46e22
claude_review
epompeii Feb 3, 2026
138dffe
rate_limiting
epompeii Feb 4, 2026
f028f28
job_cleanup
epompeii Feb 4, 2026
24064b1
json_job
epompeii Feb 5, 2026
d6bda61
event_cancelled
epompeii Feb 5, 2026
372b4bb
job_spec
epompeii Feb 5, 2026
452cb75
job_spec_impl
epompeii Feb 5, 2026
faf01bd
concurrenct_plan
epompeii Feb 6, 2026
75c5139
concurrent_limit_impl
epompeii Feb 6, 2026
12baa1d
review
epompeii Feb 7, 2026
3bed644
claude
epompeii Feb 7, 2026
63fdd6a
claude_2
epompeii Feb 7, 2026
135f421
claude_3
epompeii Feb 7, 2026
cb43167
file_paths
epompeii Feb 9, 2026
467af0f
rebase_fixes
epompeii Feb 10, 2026
176f371
runner_plan
epompeii Feb 10, 2026
83a4e91
remove_locked
epompeii Feb 10, 2026
e1af5a9
claude
epompeii Feb 10, 2026
8eb6895
no_scale
epompeii Feb 11, 2026
1831b1a
claude
epompeii Feb 11, 2026
f50720a
api_specs
epompeii Feb 11, 2026
5f2d5cb
api_specs_tests
epompeii Feb 11, 2026
e9a4601
claude_2
epompeii Feb 11, 2026
69ba4ff
JsonOpenApiSpec
epompeii Feb 12, 2026
901f835
claude
epompeii Feb 12, 2026
b5b8f67
oci_step_summary
epompeii Feb 12, 2026
f5c6519
claude_2
epompeii Feb 12, 2026
1371d79
self
epompeii Feb 12, 2026
995ef5a
claude_3
epompeii Feb 12, 2026
8723776
claude_4
epompeii Feb 12, 2026
ff11039
claimed_jobs
epompeii Feb 12, 2026
0a45e46
spec_slug
epompeii Feb 12, 2026
6448844
fn_from_uuid
epompeii Feb 12, 2026
924d415
cleanup
epompeii Feb 12, 2026
2aeeede
claude_5
epompeii Feb 12, 2026
5998a3b
claude_6
epompeii Feb 13, 2026
bf5f84f
claude_7
epompeii Feb 13, 2026
491f4fe
claude_8
epompeii Feb 13, 2026
0df6a3d
claude_9
epompeii Feb 13, 2026
a6c5d44
claude_10
epompeii Feb 13, 2026
674d362
scripts_test
epompeii Feb 13, 2026
1b78653
security_review
epompeii Feb 14, 2026
835f1bb
claude_11
epompeii Feb 14, 2026
096ab3a
archived
epompeii Feb 14, 2026
985302d
json_job_config
epompeii Feb 14, 2026
f1c7a3a
self_review
epompeii Feb 14, 2026
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
21 changes: 15 additions & 6 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,6 @@ jobs:
- name: Deploy Local API to Fly.io
working-directory: ./services/api
run: flyctl deploy --local-only --config fly/fly.dev.toml --wait-timeout 300
- name: Scale up Dev Machines
working-directory: ./services/api
run: flyctl scale vm performance-8x --app bencher-api-dev
- name: Run Smoke Test
id: smoke-test
continue-on-error: true
Expand All @@ -89,10 +86,22 @@ jobs:
--api-url https://bencher-api-dev.fly.dev \
--username eustace.bagge@nowhere.com \
--password "${{ secrets.TEST_BENCHER_API_TOKEN_ADMIN }}"
- name: Scale down Dev Machines
- name: OCI Conformance Test Summary
if: always()
working-directory: ./services/api
run: flyctl scale vm shared-cpu-1x --app bencher-api-dev
run: |
echo "## OCI Conformance Test Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ -f ./oci-conformance-results/test-output.log ]; then
echo "<details>" >> $GITHUB_STEP_SUMMARY
echo "<summary>Test Output</summary>" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
cat ./oci-conformance-results/test-output.log >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "</details>" >> $GITHUB_STEP_SUMMARY
else
echo "No test output found." >> $GITHUB_STEP_SUMMARY
fi
- name: Check Smoke Test Result
if: steps.smoke-test.outcome == 'failure'
run: exit 1
Expand Down
12 changes: 8 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,16 @@ jobs:
env:
TEST_BILLING_KEY: ${{ secrets.TEST_BILLING_KEY }}
steps:
- uses: jlumbroso/free-disk-space@main
with:
large-packages: false
- uses: actions/checkout@v6
- uses: ./.github/actions/setup-rust
with:
cache-key: all-features
mold-version: ${{ inputs.mold-version }}
- name: cargo nextest run
run: RUST_BACKTRACE=1 cargo nextest run --all-features --no-capture --profile ci
- name: cargo test --doc
run: RUST_BACKTRACE=1 cargo test --doc --all-features
- name: Run Tests
run: ./scripts/test.sh
- name: Upload Perf JPEG
uses: actions/upload-artifact@v4
with:
Expand All @@ -50,6 +51,9 @@ jobs:
if: ${{ inputs.is-fork }}
runs-on: ubuntu-22.04
steps:
- uses: jlumbroso/free-disk-space@main
with:
large-packages: false
- uses: actions/checkout@v6
- uses: rui314/setup-mold@v1
with:
Expand Down
28 changes: 27 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ Running the seed tests:

```bash
cargo fmt # Format Rust code
cargo clippy --no-deps --all-features -- -Dwarnings # Lint Rust code
cargo clippy --no-deps --all-targets --all-features -- -Dwarnings # Lint Rust code
cargo check --no-default-features # Verify build without Plus features

cd services/console
Expand Down Expand Up @@ -111,7 +111,9 @@ lib/
bencher_valid/ # Input validation (compiled to WASM for frontend)
plus/ # Bencher Plus (commercial) features
api_oci/ # OCI Distribution Spec registry endpoints
api_runners/ # Runner management and agent endpoints (Plus)
bencher_oci_storage/ # OCI blob/manifest storage (S3-backed)
bencher_otel/ # OpenTelemetry instrumentation (Plus)
tasks/ # Build tasks invoked via cargo aliases
xtask/ # Administrative tasks (local only, not CI)
```
Expand Down Expand Up @@ -170,8 +172,12 @@ The API server includes an OCI Distribution Spec compliant container registry, r
- `cargo check --no-default-features`
- `cargo gen-types` (if the API changed at all)
- Use idiomatic, strong types instead of `String` and `serde_json::Value` where possible
- Database model fields should use strong validated types (e.g., `ProjectId`, `ProjectUuid`, `ProjectName`, `DateTime`, `VersionNumber`) with Diesel `ToSql`/`FromSql` impls rather than raw primitives (`i32`, `i64`, `String`). All conversion happens inside the Diesel impls, not in the model layer.
- Avoid `select!` macros - use `futures_concurrency::stream::Merge::merge`
- All time-based tests should be deterministic and use time manipulation not real wall-clock time
- Use `bencher_json::Clock::Custom` (behind the `test-clock` feature) to inject a fake clock in tests instead of calling `DateTime::now()` directly. `Clock` is available on `ApiContext`.
- Most wire type definitions are in the `bencher_valid` or `bencher_json` crate
- Always pass strong types (`MyTypeId`, `MyTypeUuid`, etc) into a function instead of its stringly typed equivalent, even in tests

### Frontend (TypeScript)

Expand Down Expand Up @@ -211,13 +217,33 @@ When adding a new crate, update both Dockerfiles:
- `services/api/Dockerfile`
- `services/console/Dockerfile`

## Runner System (Plus Feature)

Bare metal benchmark runners that claim and execute jobs from the API. Server-scoped (runners serve all projects).

**Key concepts:**
- **Runner**: A registered agent authenticated via `bencher_runner_`-prefixed tokens (SHA-256 hashed in DB)
- **Job**: Linked to a report, follows state machine: `pending → claimed → running → completed/failed/canceled`
- **Claim endpoint**: Long-poll POST that atomically claims pending jobs (priority DESC, FIFO within tier)
- **WebSocket channel**: Persistent connection for heartbeat and status updates during job execution

**Key files:**
- `plus/api_runners/` - Runner CRUD, token rotation, job claiming, job updates, WebSocket channel
- `lib/bencher_json/src/runner/` - Shared JSON types (`JsonRunner`, `JsonJob`, `JobStatus`)
- `lib/bencher_schema/src/model/runner/` - Database models and queries
- `lib/api_projects/src/jobs.rs` - Project-scoped job listing (public API)
- `lib/bencher_schema/migrations/2026-02-02-120000_runner/` - Migration for `runner` and `job` tables

**Runner authentication** is separate from user auth — runner tokens use `Authorization: Bearer bencher_runner_<token>` and are validated by hashing and looking up `token_hash` in the `runner` table.

## Key Coordination Points

Changes in these areas often require updates across multiple files:
- **API endpoints** → run `cargo gen-types` → commit updated `openapi.json` + `bencher.ts`
- **Rust types with `#[typeshare]`** → run `npm run typeshare` in console
- **New crate** → update workspace `Cargo.toml` + both Dockerfiles
- **Validation types** → may need WASM rebuild (`npm run wasm` in console)
- **Runner endpoints** → behind `plus` feature flag; runner model in `bencher_schema` also gated on `plus`

## Documentation

Expand Down
100 changes: 99 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ api_users = { path = "lib/api_users" }
# plus API endpoints
api_oci = { path = "plus/api_oci" }
# plus
api_runners = { path = "plus/api_runners" }
api_specs = { path = "plus/api_specs" }
bencher_billing = { path = "plus/bencher_billing" }
bencher_bing_index = { path = "plus/bencher_bing_index" }
bencher_github_client = { path = "plus/bencher_github_client" }
Expand Down Expand Up @@ -87,6 +89,7 @@ dotenvy = "0.15"
email_address = "0.2"
fs-err = "3.2"
futures = "0.3"
futures-concurrency = "7"
futures-util = "0.3"
gix = { version = "0.75", default-features = false }
gix-hash = "0.20"
Expand Down Expand Up @@ -148,6 +151,7 @@ tempfile = "3"
thiserror = "2.0"
tokio = "1.48"
tokio-rustls = {version = "0.26", default-features = false, features = ["logging", "tls12", "ring"]}
tokio-tungstenite = "0.26"
tokio-util = { version = "0.7", features = ["io"] }
typeshare = "1.0"
url = "2.5"
Expand Down
2 changes: 1 addition & 1 deletion lib/api_auth/src/oauth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ async fn handle_oauth_user(
} else if let Some(organization_uuid) = oauth_state.claim() {
let query_organization =
QueryOrganization::from_uuid(auth_conn!(context), organization_uuid)?;
query_organization.claim(context, &query_user).await?;
query_organization.claim(log, context, &query_user).await?;
}
(query_user, AuthAction::Login)
} else {
Expand Down
Loading
Loading