name: CI on: push: branches: [main] pull_request: branches: [main] env: CARGO_TERM_COLOR: always RUSTFLAGS: "-D warnings" jobs: rust: name: Rust ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27 with: components: rustfmt, clippy - name: Cache cargo uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 - name: Format check run: cargo fmt --all -- --check - name: Clippy (workspace, all targets) run: cargo clippy -p wickra-core -p wickra -p wickra-data -p wickra-wasm --all-targets -- -D warnings - name: Build run: cargo build -p wickra-core -p wickra -p wickra-data --verbose - name: Tests (default features) run: cargo test -p wickra-core -p wickra -p wickra-data --verbose - name: Tests (live-binance feature) run: cargo test -p wickra-data --features live-binance --verbose - name: Compile benches (smoke) run: cargo build -p wickra --benches --verbose - name: Compile examples # All runnable examples now live in the dedicated wickra-examples crate # (examples/rust/src/bin/*.rs) which enables the live-binance feature # on its wickra-data dep, so a single --bins build covers backtest, # live_binance, fetch_btcusdt, multi_timeframe, parallel_assets and # streaming. run: cargo build -p wickra-examples --bins # Clippy for the Python and Node bindings. These are kept out of the main # `rust` job because PyO3 / napi build scripts need a Python interpreter and # a Node toolchain on PATH, which the 3-OS matrix job does not provision. # Ubuntu-only is sufficient: the lints are platform-independent. clippy-bindings: name: Clippy bindings runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27 with: components: clippy - name: Set up Python uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: "3.12" - name: Set up Node uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: "20" - name: Cache cargo uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 - name: Clippy (bindings, all targets) run: cargo clippy -p wickra-node -p wickra-python --all-targets -- -D warnings # Verify the crates still build and test on their declared minimum supported # Rust version. The workspace pins rust-version = "1.86" — that floor is # set by criterion 0.8.2 (the bench dev-dep), which itself rolled past the # clap_lex 1.1.0 / edition2024 / Rust 1.85 floor and now needs 1.86; the # earlier rayon-core 1.13.0 (1.80) and clap_lex (1.85) requirements are # subsumed. bindings/node pins rust-version = "1.88" because napi-build # 2.3.2 requires it (and that subsumes the older 1.77 floor needed for # `cargo::` directives). Without this job an accidental use of a newer # API would only surface for downstream users. msrv: name: ${{ matrix.name }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: include: - name: MSRV workspace (Rust 1.86) toolchain: "1.86" packages: "-p wickra-core -p wickra -p wickra-data" - name: MSRV node binding (Rust 1.88) toolchain: "1.88" packages: "-p wickra-node" steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install Rust ${{ matrix.toolchain }} uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27 with: toolchain: ${{ matrix.toolchain }} - name: Cache cargo uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 - name: Build on MSRV run: cargo build ${{ matrix.packages }} --verbose - name: Test on MSRV run: cargo test ${{ matrix.packages }} --verbose # Code coverage for the pure-Rust crates, uploaded to Codecov. coverage: name: Coverage runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27 with: components: llvm-tools-preview - name: Cache cargo uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 - name: Install cargo-llvm-cov uses: taiki-e/install-action@0fd46367812ee04360509b4169d9f659d6892bb2 # v2.79.15 with: tool: cargo-llvm-cov - name: Generate coverage (lcov) run: > cargo llvm-cov -p wickra-core -p wickra -p wickra-data --features wickra-data/live-binance --lcov --output-path lcov.info - name: Upload to Codecov uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1 with: files: lcov.info fail_ci_if_error: false env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} # Supply-chain audit: security advisories, license policy, banned crates, # and source restrictions. Configured by deny.toml at the repo root. supply-chain: name: Supply-chain (cargo-deny) runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: cargo-deny uses: EmbarkStudios/cargo-deny-action@bb137d7af7e4fb67e5f82a49c4fce4fad40782fe # v2.0.20 with: command: check # Time-boxed fuzz smoke. Each target runs for ~30 s with libfuzzer; any panic # fails the job. The goal is to catch a regression in the harness (e.g. a # newly added indicator that panics on a particular input shape), not to # discover novel bugs — long fuzz campaigns should be run on dedicated # infrastructure with persistent corpora. fuzz-smoke: name: Fuzz (smoke) runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install nightly Rust uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27 with: toolchain: nightly - name: Cache cargo uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 with: workspaces: fuzz - name: Install cargo-fuzz # Use the prebuilt binary from taiki-e/install-action instead of # `cargo install cargo-fuzz --locked`. The latter resolves the # version graph from cargo-fuzz's own Cargo.lock, which pins to # rustix 0.36.5 — a version that still uses internal `rustc_*` # attributes the modern nightly compiler rejects, so the install # never gets off the ground. The prebuilt binary avoids the entire # transitive-dep compile. uses: taiki-e/install-action@0fd46367812ee04360509b4169d9f659d6892bb2 # v2.79.15 with: tool: cargo-fuzz - name: Fuzz csv_reader (30 s) run: cargo +nightly fuzz run --target x86_64-unknown-linux-gnu csv_reader -- -max_total_time=30 working-directory: fuzz - name: Fuzz binance_envelope (30 s) run: cargo +nightly fuzz run --target x86_64-unknown-linux-gnu binance_envelope -- -max_total_time=30 working-directory: fuzz - name: Fuzz indicator_update (30 s) run: cargo +nightly fuzz run --target x86_64-unknown-linux-gnu indicator_update -- -max_total_time=30 working-directory: fuzz - name: Fuzz indicator_update_candle (30 s) run: cargo +nightly fuzz run --target x86_64-unknown-linux-gnu indicator_update_candle -- -max_total_time=30 working-directory: fuzz - name: Fuzz tick_aggregator (30 s) run: cargo +nightly fuzz run --target x86_64-unknown-linux-gnu tick_aggregator -- -max_total_time=30 working-directory: fuzz python: name: Python ${{ matrix.python-version }} on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] python-version: ["3.9", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27 - name: Cache cargo uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 # setup-python downloads the interpreter from the Actions tool cache / # nodejs CDN and occasionally hangs or 5xx's on the Windows runners. # Run it with continue-on-error, then retry once after a backoff so a # single CDN flake does not fail the whole job (see also: GitHub # Actions runner-images#7061). - name: Set up Python id: setup_python continue-on-error: true uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ matrix.python-version }} - name: Wait before Python retry if: steps.setup_python.outcome == 'failure' shell: bash run: | echo "::warning::setup-python failed (likely CDN flake), waiting 30s before retry..." sleep 30 - name: Set up Python (retry) if: steps.setup_python.outcome == 'failure' uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ matrix.python-version }} - name: Install Python dev dependencies run: | python -m pip install --upgrade pip python -m pip install maturin pytest numpy hypothesis - name: Build wheel working-directory: bindings/python run: maturin build --release --out dist - name: Install wheel shell: bash working-directory: bindings/python # --no-index forces pip to ignore PyPI; --no-deps skips re-resolving # numpy (already installed in the previous step). Without --no-index # pip prefers the PyPI 0.2.x wheel over our freshly built one when # platform tags overlap (e.g. macOS arm64), so tests would run # against the released package and miss any new symbols the PR adds. run: python -m pip install --no-index --find-links dist --force-reinstall --no-deps wickra - name: Run Python tests working-directory: bindings/python run: pytest -v wasm: name: WASM build runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install Rust toolchain (with wasm target) uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27 with: targets: wasm32-unknown-unknown - name: Cache cargo uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 - name: Install wasm-pack # jetli/wasm-pack-action@v0.4.0 with no `version:` input installs an # old wasm-pack (~0.10.x) whose `build` subcommand does not yet accept # `--features`, so `wasm-pack build … --features panic-hook` fails # with "Found argument '--features' which wasn't expected". Use the # same taiki-e prebuilt-binary installer we already use for # cargo-llvm-cov and cargo-fuzz; it tracks the latest wasm-pack # release, which has `--features` as a top-level flag (since 0.12). uses: taiki-e/install-action@0fd46367812ee04360509b4169d9f659d6892bb2 # v2.79.15 with: tool: wasm-pack - name: Build WASM package run: wasm-pack build bindings/wasm --target web --release --features panic-hook - name: Run WASM tests run: wasm-pack test --node bindings/wasm - name: Verify generated artefacts run: | test -f bindings/wasm/pkg/wickra_wasm.js test -f bindings/wasm/pkg/wickra_wasm_bg.wasm test -f bindings/wasm/pkg/wickra_wasm.d.ts node: name: Node ${{ matrix.node-version }} on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] node-version: ["18", "20"] steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27 - name: Cache cargo uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 # setup-node downloads Node from nodejs.org and we've seen it fail on # Windows runners with "Attempting to download 18..." followed by a # silent hang or curl error. Retry once after a backoff so a single # CDN flake does not fail the whole job. - name: Set up Node id: setup_node continue-on-error: true uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: ${{ matrix.node-version }} - name: Wait before Node retry if: steps.setup_node.outcome == 'failure' shell: bash run: | echo "::warning::setup-node failed (likely CDN flake), waiting 30s before retry..." sleep 30 - name: Set up Node (retry) if: steps.setup_node.outcome == 'failure' uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: ${{ matrix.node-version }} - name: Install Node dependencies working-directory: bindings/node run: npm install - name: Build native module working-directory: bindings/node # --platform puts the target triple into the filename so the loader's # `wickra..node` lookup finds the freshly built binary instead # of falling back to the per-platform npm subpackage (which doesn't # exist yet for win32-x64-msvc). run: npx napi build --platform --release - name: Run Node tests working-directory: bindings/node run: node --test __tests__/ # The cross-library benchmark has moved to a dedicated scheduled workflow # (.github/workflows/bench.yml) — see audit finding R10. It runs nightly # at 03:00 UTC and on-demand via `workflow_dispatch`, and is no longer on # the every-push / every-PR critical path.