Thanks for your interest! This document covers the things you need to know to get a PR through the Solarix CI gate on the first try.
Solarix runs its CI on GitHub Actions (.github/workflows/ci.yml). Every job
in the pipeline has a local-cargo equivalent so you can verify before pushing.
| CI job | Local equivalent |
|---|---|
lint |
cargo fmt -- --check && cargo clippy --release --all-targets -- -D warnings |
unit |
cargo test --release --lib |
integration |
cargo test --release --tests --features integration (Story 6.5 harness — see Integration tests below) |
coverage |
cargo llvm-cov --release --lib --summary-only |
fuzz-smoke |
cargo +nightly fuzz run decode_instruction -- -max_total_time=60 (requires Story 6.4 fuzz target) |
security |
cargo audit && cargo deny check advisories bans sources && gitleaks detect --source . --config .gitleaks.toml |
docker-smoke |
docker compose down -v && docker compose up --build -d && curl --retry 12 --retry-delay 5 --retry-connrefused --fail http://localhost:3000/health |
msrv |
cargo +1.88 build --release |
toolchain-matrix |
cargo +stable build --release and cargo +beta build --release |
Most jobs only need cargo. A few jobs install extra tooling in CI that you may
want to pre-install locally:
# MSRV toolchain (matches `rust-toolchain.toml`)
rustup toolchain install 1.88
rustup component add --toolchain 1.88 rustfmt clippy
# Coverage
rustup component add llvm-tools-preview
cargo install cargo-llvm-cov --locked
# Security
cargo install cargo-audit cargo-deny --locked
# gitleaks: `brew install gitleaks` (macOS) or see https://github.com/gitleaks/gitleaks
# Fuzz
cargo install cargo-fuzz --locked # run fuzz targets with `+nightly`
# Docker smoke
# Requires Docker Desktop or docker engine with compose plugin v2.The repository pins MSRV via rust-toolchain.toml. When you cd into the repo
your cargo will use that channel automatically. If you want to test against
stable or beta (matching the toolchain-matrix job), use cargo +stable /
cargo +beta explicitly or set RUSTUP_TOOLCHAIN=stable.
Solarix's integration tests boot real postgres:16-alpine containers via
testcontainers-modules and
exercise the writer, schema generator, decoder, registration path, and filter
SQL builder against the live PostgreSQL catalog. They are gated behind the
integration cargo feature so the default cargo test stays fast and
docker-free.
Local run:
cargo test --release --tests --features integrationRequirements:
- Docker Desktop or docker engine running locally (the harness spawns one ephemeral container per test, ~2-3 s warm cache).
- The MSRV toolchain (1.88) — see the Required tools section.
The canonical pool fixture lives at tests/common/postgres.rs::with_postgres
and with_postgres_returning. New integration tests should reuse it instead
of rolling their own connection setup. The harness:
- Spawns a fresh container per call (per-test isolation, parallelism-safe).
- Calls
solarix::storage::bootstrap_system_tablesso every closure starts on a clean, bootstrapped DB. - Drops the container automatically when the closure returns or panics.
The non-mainnet integration suite is budgeted to run end-to-end in under 90 seconds on a developer laptop with the docker image cached.
tests/api_endpoints.rs covers all 12 router endpoints (happy path, error
path, pagination, filter validation) using tower::ServiceExt::oneshot as
the test transport. Each test calls build_test_router(pool) from
tests/common/api.rs to get a fresh router bound to a per-test DB container.
Note: axum-test 16.x targets axum 0.7 while this project is on axum 0.8 —
Router<S> from the newer axum does not implement axum-test's
IntoTransportLayer. The oneshot approach is the stable alternative; do
not add axum-test to [dev-dependencies].
Run the API test suite in isolation:
cargo test --release --tests --features integration --test api_endpointsMainnet smoke test (nightly-only):
tests/mainnet_smoke.rs is gated behind the separate mainnet-smoke feature
(declared in Cargo.toml as mainnet-smoke = ["integration"] so it pulls
in the testcontainers harness transitively). It is run only by
.github/workflows/nightly.yml against https://api.mainnet-beta.solana.com,
not by per-PR CI. Local invocation:
cargo test --release --features mainnet-smoke -- mainnet_smokeThe nightly workflow uses continue-on-error: true and posts a PR comment
on failure rather than turning the cron red.
Rust imports in this repo are grouped by source, with a blank line between groups:
// 1. std
use std::collections::HashMap;
use std::sync::Arc;
// 2. external crates
use axum::Router;
use sqlx::PgPool;
// 3. internal crate (use `crate::...`)
use crate::decoder::SolarixDecoder;
use crate::types::DecodedInstruction;This convention is NOT enforced by CI. The nightly rustfmt options
(group_imports = "StdExternalCrate", imports_granularity = "Crate") that
would enforce it automatically require a nightly toolchain, and per
ADR-0002 § D4 we decided not to add nightly to
the lint job just for this one check. Reviewer discipline catches drift.
Solarix uses tracing macros exclusively — never println! / eprintln!.
Every pub async fn in src/pipeline/, src/api/handlers.rs, src/idl/,
src/registry.rs, and src/storage/writer.rs carries #[tracing::instrument]
with required field names. Every warn! / error! in the pipeline modules
must carry program_id — enforced by tests/log_levels.rs.
See docs/operating-solarix.md
for the full field glossary, enforced rules, and jq recipes.
When adding a new async fn in the tracing scope: add an #[tracing::instrument]
attribute with an explicit name = "..." (dotted convention: module.function),
skip(...) for large structs, level = "debug" for hot paths or "info" for
lifecycle, and err(Display) on fallible functions. Run cargo test --test instrument_coverage and cargo test --test log_levels before opening a PR.
If a CI job fails on your PR and the failure message isn't obvious:
- Check the job log for the first
error:line. Most clippy and compile failures are self-explanatory. - Two jobs upload artifacts on failure for offline inspection:
docker-smoke-logs(thesolarixcontainer's log output, 7-day retention)lcov-info(always uploaded, 14-day retention; useful for spotting coverage drops even though we don't enforce a delta gate yet)
- Reproduce the failing command locally using the table above.
- Soft-gated jobs (
fuzz-smoke, the/readyand/metricschecks insidedocker-smoke, and the entirenightly-mainnet-smokeworkflow) become hard gates when their dependency story lands. If a newly landed story unexpectedly reds one of these, the fix is usually to open an issue tracking it rather than to revert the CI change. - If the failure is transient (e.g., a flaky third-party service),
re-run the failed job from the GitHub Actions UI. Do NOT paper over flake
by adding
continue-on-error: true— onlymainnet-smokeand thebetatoolchain matrix entry are allowed to flake, and those decisions are documented in ADR-0002.
Thanks for contributing!