5b23b36261
* ci: use npm ci instead of npm install for reproducible installs Pins the node binding dependency install to the committed package-lock.json integrity hashes (OpenSSF Scorecard PinnedDependencies). npm ci installs strictly from the lockfile; npm install could resolve newer patch versions. Covers ci.yml and both release.yml node steps. * ci: hash-pin Python dev tooling in ci.yml (Scorecard #19) Replaces the unpinned 'pip install maturin pytest numpy hypothesis' with a hash-locked '--require-hashes -r' install (OpenSSF Scorecard PinnedDependencies). Two lock files are needed because numpy publishes no single release with wheels for both cp39 and cp313 (<=2.0.2 has cp39 only, >=2.1 drops cp39): ci-dev-py39.txt numpy 2.0.2 (Python 3.9, + tomli/exceptiongroup) ci-dev-py3.txt numpy 2.4.6 (Python 3.10+) The step selects the file by matrix.python-version under shell: bash. Both are generated from ci-dev.in via uv (scripts/update-lockfiles.sh, added next). * ci: hash-pin Python deps in bench.yml (Scorecard #16) Replaces the unpinned 'pip install maturin numpy pandas talipp finta' with a hash-locked '--require-hashes -r .github/requirements/bench.txt' install. bench.yml runs on a single Python version (3.11), so one lock file (generated from bench.in via uv) is sufficient. * build: add scripts/update-lockfiles.sh to regenerate all lockfiles One command refreshes every committed lockfile across languages: Cargo.lock and fuzz/Cargo.lock (cargo update), the Node binding package-lock.json, and the hash-pinned Python requirements under .github/requirements/ (uv pip compile --generate-hashes). Uses uv for the Python locks so a target Python version's hashed transitive closure can be resolved without that interpreter installed (needed for the numpy cp39/cp313 split); bootstraps uv if absent. .gitattributes pins *.sh to LF so the script stays runnable on Linux/macOS. * ci: split ci-dev requirements per Python version + Dependabot rehash Splits the single ci-dev.in into ci-dev-py39.in (numpy <2.1, the last series with cp39 wheels) and ci-dev-py3.in (3.10+), giving a 1:1 .in->.txt layout. The cap keeps Python 3.9 permanently installable and stops Dependabot from proposing 3.9-breaking numpy bumps. Adds a Dependabot pip entry on /.github/requirements so the hash-locked tooling is kept current automatically; the canonical manual refresh stays scripts/update-lockfiles.sh. Only the '# via -r' provenance lines in the .txt change; no package versions or hashes move. * ci: cache pip and npm downloads in the PR-loop jobs Adds setup-python cache: pip (ci.yml python matrix + bench.yml, keyed on the hash-locked requirements) and setup-node cache: npm (ci.yml node job, keyed on bindings/node/package-lock.json), on both the primary and retry setup steps. Scoped to jobs that actually install dependencies; the examples-smoke and clippy-bindings jobs install nothing and are left uncached. release.yml is intentionally left out: it runs only on tag push (not the PR loop) and is the publish-critical path, so no caching is added there. * docs: document hash-pinned requirements and update-lockfiles.sh Updates the lockfile-policy table: the bindings/python row no longer claims CI installs tooling unpinned, and a new .github/requirements row documents the hash-locked CI/bench tooling and the per-Python-version ci-dev split. Adds a paragraph pointing contributors at scripts/update-lockfiles.sh (uv-based, self-bootstrapping) as the canonical lockfile refresh. * docs: changelog entry for hash-pinned CI dependency installs
118 lines
4.6 KiB
YAML
118 lines
4.6 KiB
YAML
name: Cross-library benchmark
|
||
|
||
# Audit finding R10: previously the cross-library benchmark ran on every push
|
||
# and every pull-request to `main`, adding 5–10 minutes of build + bench time
|
||
# per CI run with no consumer of the resulting artefact. The benchmark is now
|
||
# scheduled (nightly at 03:00 UTC) and on-demand via `workflow_dispatch`. The
|
||
# CI pipeline proper (.github/workflows/ci.yml) still verifies build / tests /
|
||
# lints on every push and pull-request.
|
||
on:
|
||
schedule:
|
||
# Nightly at 03:00 UTC. Pick a slot well away from common European /
|
||
# American working-hours pushes to keep this off the critical path.
|
||
- cron: "0 3 * * *"
|
||
workflow_dispatch:
|
||
inputs:
|
||
size:
|
||
description: "Number of bars per indicator (default 20000)"
|
||
required: false
|
||
default: "20000"
|
||
iterations:
|
||
description: "Batch iterations per indicator (default 10)"
|
||
required: false
|
||
default: "10"
|
||
|
||
# Least-privilege default for the auto-injected GITHUB_TOKEN. The single job
|
||
# only builds and uploads an artifact (upload-artifact uses the artifact
|
||
# storage API, not the contents scope), so it never needs repo write (OpenSSF
|
||
# Scorecard: Token-Permissions).
|
||
permissions:
|
||
contents: read
|
||
|
||
env:
|
||
CARGO_TERM_COLOR: always
|
||
# Network-flake resilience: retry transient registry/DNS failures at the tool
|
||
# level so a blip fetching crates.io / PyPI inside any build step (cargo,
|
||
# maturin, pip) retries automatically instead of failing the job. Cargo treats
|
||
# "couldn't resolve host" / connect / timeout as spurious and retries with
|
||
# backoff; 10 attempts ride out a transient DNS blip on a runner.
|
||
CARGO_NET_RETRY: "10"
|
||
CARGO_NET_GIT_FETCH_WITH_CLI: "true"
|
||
npm_config_fetch_retries: "5"
|
||
npm_config_fetch_retry_maxtimeout: "120000"
|
||
PIP_RETRIES: "5"
|
||
PIP_DEFAULT_TIMEOUT: "120"
|
||
|
||
jobs:
|
||
cross-library-bench:
|
||
name: Cross-library benchmark report
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||
|
||
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
||
|
||
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
||
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
|
||
timeout-minutes: 6
|
||
|
||
- name: Set up Python
|
||
id: setup_python
|
||
continue-on-error: true
|
||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||
with:
|
||
python-version: "3.11"
|
||
cache: pip
|
||
cache-dependency-path: .github/requirements/bench.txt
|
||
|
||
- 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: "3.11"
|
||
cache: pip
|
||
cache-dependency-path: .github/requirements/bench.txt
|
||
|
||
- name: Install Python deps + peer libs
|
||
run: |
|
||
python -m pip install --upgrade pip
|
||
# Hash-locked deps (OpenSSF Scorecard PinnedDependencies). bench.yml
|
||
# runs on a single Python version (3.11), so one lock file suffices.
|
||
python -m pip install --require-hashes -r .github/requirements/bench.txt
|
||
|
||
- name: Build Wickra wheel
|
||
working-directory: bindings/python
|
||
run: maturin build --release --out dist
|
||
|
||
- name: Install Wickra wheel
|
||
working-directory: bindings/python
|
||
run: python -m pip install --find-links dist --force-reinstall wickra
|
||
|
||
- name: Run cross-library benchmark
|
||
working-directory: bindings/python
|
||
# workflow_dispatch inputs are untrusted; pass them through the
|
||
# environment and quote them rather than interpolating into the shell
|
||
# command (OpenSSF Scorecard: Dangerous-Workflow).
|
||
env:
|
||
BENCH_SIZE: ${{ github.event.inputs.size || '20000' }}
|
||
BENCH_ITERATIONS: ${{ github.event.inputs.iterations || '10' }}
|
||
run: |
|
||
python -m benchmarks.compare_libraries \
|
||
--size "$BENCH_SIZE" \
|
||
--iterations "$BENCH_ITERATIONS" \
|
||
--streaming-window 5000 --streaming-iterations 2 \
|
||
| tee benchmark.txt
|
||
|
||
- name: Upload report
|
||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||
with:
|
||
name: cross-library-bench
|
||
path: bindings/python/benchmark.txt
|