Skip to content

Add nautilus-event-store DataCommand encoder #15110

Add nautilus-event-store DataCommand encoder

Add nautilus-event-store DataCommand encoder #15110

Workflow file for this run

name: build
permissions: # Principle of least privilege
contents: read
actions: read
on:
push:
branches: [master, nightly, develop, test-ci, test-pre-commit]
pull_request:
branches-ignore: [master, nightly]
concurrency:
# yamllint disable-line rule:line-length
group: ${{ github.workflow }}-${{ github.event.pull_request.number || format('{0}-{1}', github.ref_name, github.sha) }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
# Fork PRs lack repo/org vars, so audit instead of block.
EGRESS_POLICY: >-
${{ github.event.pull_request.head.repo.fork && 'audit'
|| vars.STEP_SECURITY_EGRESS_POLICY
|| 'block' }}
jobs:
plan:
runs-on: ubuntu-latest
outputs:
run-tests: ${{ steps.plan.outputs.run_tests }}
run-rust-tests: ${{ steps.plan.outputs.run_rust_tests }}
steps:
# https://github.com/step-security/harden-runner
- uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0
with:
egress-policy: ${{ env.EGRESS_POLICY }}
allowed-endpoints: >-
${{ vars.COMMON_ALLOWED_ENDPOINTS }}
${{ vars.CI_ALLOWED_ENDPOINTS }}
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
persist-credentials: false
- name: Plan
id: plan
shell: bash
env:
EVENT_NAME: ${{ github.event_name }}
BASE_REF: ${{ github.event.pull_request.base.ref }}
BEFORE_SHA: ${{ github.event.before }}
run: bash scripts/ci/plan.sh
pre-commit:
# Fork PRs stay GitHub-hosted.
runs-on: >-
${{ github.event.pull_request.head.repo.fork
&& 'ubuntu-22.04'
|| fromJson('["self-hosted", "Linux", "X64", "build"]') }}
env:
# Self-hosted keeps Rust artifacts outside the workspace.
CARGO_TARGET_DIR: >-
${{ github.event.pull_request.head.repo.fork
&& format('{0}/target', github.workspace)
|| '/home/runner/.cache/cargo-target/pre-commit' }}
CARGO_CI_PROFILE: >-
${{ github.event.pull_request.head.repo.fork && 'ci-pr' || 'nextest' }}
# Scopes incremental clippy/doc checks; empty for non-PR/push events.
CHANGED_BASE_SHA: >-
${{ github.event.pull_request.base.sha || github.event.before }}
steps:
# https://github.com/step-security/harden-runner
- uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0
with:
egress-policy: ${{ env.EGRESS_POLICY }}
allowed-endpoints: >-
${{ vars.COMMON_ALLOWED_ENDPOINTS }}
${{ vars.CI_ALLOWED_ENDPOINTS }}
- name: Checkout repository
# https://github.com/actions/checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
# Full history so changed-file scripts can resolve CHANGED_BASE_SHA.
fetch-depth: 0
# Temporary: fail fast on rare runner worktree corruption while we isolate the root cause
- name: Verify immutable CI inputs (post-checkout)
run: bash scripts/ci/verify-ci-inputs.sh post-checkout
- name: Common setup
uses: ./.github/actions/common-setup
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
python-version: "3.13"
free-disk-space: "true"
build-type: "pre-commit"
rust-cache-enabled: >-
${{ github.event.pull_request.head.repo.fork && 'true' || 'false' }}
- name: Limit Cargo build jobs on GitHub-hosted runners
if: github.event.pull_request.head.repo.fork
run: echo "CARGO_BUILD_JOBS=2" >> "$GITHUB_ENV"
- name: Run pre-commit
run: prek run --all-files
- name: Verify capnp schemas are up-to-date
run: make check-capnp-schemas
# Dependency license, advisory, and ban checks (master only - gates releases)
# https://embarkstudios.github.io/cargo-deny/
cargo-deny:
if: github.ref == 'refs/heads/master'
runs-on: ubuntu-22.04
steps:
# https://github.com/step-security/harden-runner
- uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0
with:
egress-policy: ${{ env.EGRESS_POLICY }}
allowed-endpoints: >-
${{ vars.COMMON_ALLOWED_ENDPOINTS }}
${{ vars.CI_ALLOWED_ENDPOINTS }}
- name: Checkout repository
# https://github.com/actions/checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Install cargo-deny
uses: ./.github/actions/cargo-tool-install
with:
tool-name: cargo-deny
- name: Run cargo-deny (advisories, licenses, sources, bans)
run: cargo deny --all-features check advisories licenses sources bans
# Supply chain security auditing (master only - gates releases)
# https://mozilla.github.io/cargo-vet/configuring-ci.html
cargo-vet:
if: github.ref == 'refs/heads/master'
runs-on: ubuntu-22.04
steps:
# https://github.com/step-security/harden-runner
- uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0
with:
egress-policy: ${{ env.EGRESS_POLICY }}
allowed-endpoints: >-
${{ vars.COMMON_ALLOWED_ENDPOINTS }}
${{ vars.CI_ALLOWED_ENDPOINTS }}
- name: Checkout repository
# https://github.com/actions/checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Install cargo-vet
uses: ./.github/actions/cargo-tool-install
with:
tool-name: cargo-vet
- name: Run cargo-vet
run: cargo vet --locked
build-linux-x86:
if: github.ref != 'refs/heads/test-pre-commit' && needs.plan.outputs.run-tests == 'true'
strategy:
fail-fast: false
matrix:
python-version:
- "3.12"
- "3.13"
- "3.14"
defaults:
run:
shell: bash
# Ubuntu 22.04 (glibc 2.35) keeps the wheel runtime range broad.
# Fork PRs stay GitHub-hosted.
name: build - python ${{ matrix.python-version }} (ubuntu-22.04)
runs-on: >-
${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork
&& 'ubuntu-22.04'
|| github.event_name == 'pull_request'
&& fromJson('["self-hosted", "Linux", "X64", "build"]')
|| github.event_name == 'push' && github.ref_name == 'develop'
&& fromJson('["self-hosted", "Linux", "X64", "build"]')
|| fromJson('["depot-ubuntu-22.04-8"]') }}
needs:
- plan
- pre-commit
env:
BUILD_MODE: >-
${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork
&& 'ci-pr' || 'release' }}
CARGO_CI_PROFILE: >-
${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork
&& 'ci-pr' || 'nextest' }}
PARALLEL_BUILD: >-
${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork
&& 'false' || 'true' }}
# Self-hosted develop pushes keep Rust artifacts outside the workspace so
# they survive actions/checkout's git clean -ffdx between runs.
CARGO_TARGET_DIR: >-
${{ github.event_name == 'push' && github.ref_name == 'develop'
&& format('/home/runner/.cache/cargo-target/build-linux-x86/py{0}', matrix.python-version)
|| format('{0}/target/build-linux-x86', github.workspace) }}
RUST_BACKTRACE: 1
# yamllint disable rule:line-length
services:
redis:
image: public.ecr.aws/docker/library/redis:7.4.5-alpine3.21@sha256:bb186d083732f669da90be8b0f975a37812b15e913465bb14d845db72a4e3e08
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
postgres:
image: public.ecr.aws/docker/library/postgres:16.4-alpine@sha256:5660c2cbfea50c7a9127d17dc4e48543eedd3d7a41a595a2dfa572471e37e64c
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: pass
POSTGRES_DB: nautilus
ports:
- 5432:5432
options: --health-cmd "pg_isready -U postgres -d nautilus" --health-interval 10s --health-timeout 5s --health-retries 5
# yamllint enable rule:line-length
steps:
# https://github.com/step-security/harden-runner
- uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0
with:
egress-policy: ${{ env.EGRESS_POLICY }}
allowed-endpoints: >-
${{ vars.COMMON_ALLOWED_ENDPOINTS }}
${{ vars.CI_ALLOWED_ENDPOINTS }}
- name: Checkout repository
# https://github.com/actions/checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
# Temporary: fail fast on rare runner worktree corruption while we isolate the root cause
- name: Verify immutable CI inputs (post-checkout)
run: bash scripts/ci/verify-ci-inputs.sh post-checkout
- name: Common setup
uses: ./.github/actions/common-setup
with:
python-version: ${{ matrix.python-version }}
free-disk-space: "true"
# Persistent CARGO_TARGET_DIR on the self-hosted build pool replaces
# Swatinem/rust-cache for develop push builds.
rust-cache-enabled: ${{ github.event_name == 'push' && github.ref_name == 'develop' && 'false' || 'true' }}
rust-cache-key: build-linux-x86
rust-cache-workspaces: . -> target/build-linux-x86
rust-cache-save-if: ${{ github.event_name == 'push' }}
- name: Limit Cargo build jobs on GitHub-hosted runners
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork
run: echo "CARGO_BUILD_JOBS=2" >> "$GITHUB_ENV"
- name: Install Nautilus CLI
env:
NAUTILUS_CLI_FORCE_SOURCE: ${{ github.ref == 'refs/heads/nightly' && '1' || '0' }}
run: bash scripts/ci/install-nautilus-cli.sh
- name: Init postgres schema
run: nautilus database init --schema ${{ github.workspace }}/schema/sql
env:
POSTGRES_HOST: localhost
POSTGRES_PORT: 5432
POSTGRES_USERNAME: postgres
POSTGRES_PASSWORD: pass
POSTGRES_DATABASE: nautilus
- name: Cached test data
uses: ./.github/actions/common-test-data
- name: Run Rust tests (core)
if: needs.plan.outputs.run-rust-tests == 'true'
run: make cargo-test-core EXTRA_FEATURES="capnp,hypersync" NEXTEST_PROFILE=ci
- name: Run Rust tests (adapters)
if: needs.plan.outputs.run-rust-tests == 'true'
run: make cargo-test-adapters EXTRA_FEATURES="capnp,hypersync" NEXTEST_PROFILE=ci
- name: Clean Rust test artifacts
if: needs.plan.outputs.run-rust-tests == 'true'
run: |
rm -rf "${CARGO_TARGET_DIR}/nextest"
rm -rf "${CARGO_TARGET_DIR}/${CARGO_CI_PROFILE:-nextest}"
df -h .
# Temporary: fail fast on rare runner worktree corruption while we isolate the root cause
- name: Verify immutable CI inputs (pre-wheel build)
run: bash scripts/ci/verify-ci-inputs.sh pre-wheel-build
- name: Build and install wheel
uses: ./.github/actions/common-wheel-build
with:
python-version: ${{ matrix.python-version }}
github_ref: ${{ github.ref }}
- name: Run tests
run: |
uv run --no-sync pytest --ignore=tests/performance_tests \
-n logical --dist=loadgroup --reruns 2 --reruns-delay 1
- name: Upload wheel artifact
uses: ./.github/actions/upload-artifact-wheel
build-linux-arm:
strategy:
fail-fast: false
matrix:
python-version:
- "3.12"
- "3.13"
- "3.14"
defaults:
run:
shell: bash
name: build - python ${{ matrix.python-version }} (ubuntu-22.04-arm)
runs-on: depot-ubuntu-22.04-arm-8
# ARM stays off PRs, develop pushes, and pre-commit-only test runs.
# yamllint disable-line rule:line-length
if: >
needs.plan.outputs.run-tests == 'true' &&
!(
(github.event_name == 'push' &&
(github.ref_name == 'develop' ||
github.ref_name == 'test-pre-commit'))
|| github.event_name == 'pull_request'
)
needs:
- plan
- pre-commit
env:
BUILD_MODE: release
CARGO_TARGET_DIR: ${{ github.workspace }}/target/build-linux-arm
RUST_BACKTRACE: 1
# yamllint disable rule:line-length
services:
redis:
image: public.ecr.aws/docker/library/redis:7.4.5-alpine3.21@sha256:bb186d083732f669da90be8b0f975a37812b15e913465bb14d845db72a4e3e08
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
postgres:
image: public.ecr.aws/docker/library/postgres:16.4-alpine@sha256:5660c2cbfea50c7a9127d17dc4e48543eedd3d7a41a595a2dfa572471e37e64c
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: pass
POSTGRES_DB: nautilus
ports:
- 5432:5432
options: --health-cmd "pg_isready -U postgres -d nautilus" --health-interval 10s --health-timeout 5s --health-retries 5
# yamllint enable rule:line-length
steps:
# https://github.com/step-security/harden-runner
- uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0
with:
egress-policy: ${{ env.EGRESS_POLICY }}
allowed-endpoints: >-
${{ vars.COMMON_ALLOWED_ENDPOINTS }}
${{ vars.CI_ALLOWED_ENDPOINTS }}
- name: Checkout repository
# https://github.com/actions/checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
# Temporary: fail fast on rare runner worktree corruption while we isolate the root cause
- name: Verify immutable CI inputs (post-checkout)
run: bash scripts/ci/verify-ci-inputs.sh post-checkout
- name: Common setup
uses: ./.github/actions/common-setup
with:
python-version: ${{ matrix.python-version }}
free-disk-space: "true"
rust-cache-key: build-linux-arm
rust-cache-workspaces: . -> target/build-linux-arm
rust-cache-save-if: ${{ github.event_name == 'push' }}
- name: Install Nautilus CLI
env:
NAUTILUS_CLI_FORCE_SOURCE: ${{ github.ref == 'refs/heads/nightly' && '1' || '0' }}
run: bash scripts/ci/install-nautilus-cli.sh
- name: Init postgres schema
run: nautilus database init --schema ${{ github.workspace }}/schema/sql
env:
POSTGRES_HOST: localhost
POSTGRES_PORT: 5432
POSTGRES_USERNAME: postgres
POSTGRES_PASSWORD: pass
POSTGRES_DATABASE: nautilus
- name: Cached test data
uses: ./.github/actions/common-test-data
- name: Run Rust tests (core)
if: needs.plan.outputs.run-rust-tests == 'true'
run: make cargo-test-core EXTRA_FEATURES="capnp" NEXTEST_PROFILE=ci
- name: Run Rust tests (adapters)
if: needs.plan.outputs.run-rust-tests == 'true'
run: make cargo-test-adapters EXTRA_FEATURES="capnp" NEXTEST_PROFILE=ci
- name: Clean Rust test artifacts
if: needs.plan.outputs.run-rust-tests == 'true'
run: |
rm -rf "${CARGO_TARGET_DIR}/nextest"
df -h .
# Temporary: fail fast on rare runner worktree corruption while we isolate the root cause
- name: Verify immutable CI inputs (pre-wheel build)
run: bash scripts/ci/verify-ci-inputs.sh pre-wheel-build
- name: Build and install wheel
uses: ./.github/actions/common-wheel-build
with:
python-version: ${{ matrix.python-version }}
github_ref: ${{ github.ref }}
- name: Run tests
run: |
uv run --no-sync pytest --ignore=tests/performance_tests --reruns 2 --reruns-delay 1
- name: Upload wheel artifact
uses: ./.github/actions/upload-artifact-wheel
build-macos:
# macOS stays off develop pushes and pre-commit-only test runs.
if: >
needs.plan.outputs.run-tests == 'true' &&
github.event_name == 'push' &&
github.ref != 'refs/heads/develop' &&
github.ref != 'refs/heads/test-pre-commit'
strategy:
fail-fast: false
matrix:
python-version:
- "3.12"
- "3.13"
- "3.14"
defaults:
run:
shell: bash
name: build - python ${{ matrix.python-version }} (macos)
runs-on: macos-latest
needs:
- plan
- pre-commit
env:
BUILD_MODE: release
CARGO_TARGET_DIR: ${{ github.workspace }}/target/build-macos
RUST_BACKTRACE: 1
steps:
# https://github.com/step-security/harden-runner
- uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0
with:
egress-policy: ${{ env.EGRESS_POLICY }}
allowed-endpoints: >-
${{ vars.COMMON_ALLOWED_ENDPOINTS }}
${{ vars.CI_ALLOWED_ENDPOINTS }}
- name: Checkout repository
# https://github.com/actions/checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
# Temporary: fail fast on rare runner worktree corruption while we isolate the root cause
- name: Verify immutable CI inputs (post-checkout)
run: bash scripts/ci/verify-ci-inputs.sh post-checkout
- name: Log macOS runner version
run: |
echo "::group::macOS runner info"
sw_vers
uname -a
echo "::endgroup::"
- name: Common setup
uses: ./.github/actions/common-setup
with:
python-version: ${{ matrix.python-version }}
free-disk-space: "true"
rust-cache-key: build-macos
rust-cache-workspaces: . -> target/build-macos
rust-cache-on-failure: "false"
rust-cache-save-if: ${{ github.event_name == 'push' }}
- name: Cached test data
uses: ./.github/actions/common-test-data
- name: Set compile jobs from cache state
run: bash scripts/ci/set-cargo-build-jobs.sh
- name: Run Rust tests (core)
if: needs.plan.outputs.run-rust-tests == 'true'
run: make cargo-test-core EXTRA_FEATURES="capnp,hypersync" NEXTEST_PROFILE=ci
- name: Run Rust tests (adapters)
if: needs.plan.outputs.run-rust-tests == 'true'
run: make cargo-test-adapters EXTRA_FEATURES="capnp,hypersync" NEXTEST_PROFILE=ci
# Temporary: fail fast on rare runner worktree corruption while we isolate the root cause
- name: Verify immutable CI inputs (pre-wheel build)
run: bash scripts/ci/verify-ci-inputs.sh pre-wheel-build
- name: Build and install wheel
id: build-wheel
uses: ./.github/actions/common-wheel-build
env:
CARGO_TARGET_DIR: ${{ github.workspace }}/target/build-macos
PARALLEL_BUILD: false
with:
python-version: ${{ matrix.python-version }}
github_ref: ${{ github.ref }}
- name: Diagnose failed macOS wheel build
if: failure() && steps.build-wheel.outcome == 'failure'
env:
# yamllint disable-line rule:line-length
MACOS_WHEEL_TARGET_DIR: ${{ github.workspace }}/target/build-macos/release
run: |
set +e
echo "::group::macOS runner and toolchain"
uname -a
sw_vers
rustc -Vv || true
cargo -Vv || true
python --version || true
which python || true
env | rg '^(ARCHFLAGS|CARGO|DYLD_LIBRARY_PATH|LD_LIBRARY_PATH|LIBRARY_PATH|PYO3|RUST)=' || true
echo "::endgroup::"
if [ ! -d "$MACOS_WHEEL_TARGET_DIR" ]; then
echo "Wheel target dir not found: $MACOS_WHEEL_TARGET_DIR"
exit 0
fi
build_scripts="$(find "$MACOS_WHEEL_TARGET_DIR/build" -type f -name build-script-build | sort)"
echo "::group::macOS build-script artifacts"
if [ -z "$build_scripts" ]; then
echo "No build-script-build artifacts found"
else
while IFS= read -r build_script; do
[ -z "$build_script" ] && continue
echo "--- $build_script"
ls -l "$build_script"
shasum -a 256 "$build_script" || true
file "$build_script" || true
otool -hv "$build_script" || true
done <<< "$build_scripts"
fi
echo "::endgroup::"
echo "::group::macOS Rust archives"
for archive in \
"$MACOS_WHEEL_TARGET_DIR/libnautilus_backtest.a" \
"$MACOS_WHEEL_TARGET_DIR/libnautilus_common.a" \
"$MACOS_WHEEL_TARGET_DIR/libnautilus_core.a" \
"$MACOS_WHEEL_TARGET_DIR/libnautilus_model.a" \
"$MACOS_WHEEL_TARGET_DIR/libnautilus_persistence.a"; do
if [ ! -f "$archive" ]; then
echo "Missing archive: $archive"
continue
fi
echo "--- $archive"
ls -l "$archive"
shasum -a 256 "$archive" || true
file "$archive" || true
ar -t "$archive" | head -n 20 || true
done
echo "::endgroup::"
- name: Run tests
run: |
uv run --no-sync pytest --ignore=tests/performance_tests --reruns 2 --reruns-delay 1
- name: Upload wheel artifact
uses: ./.github/actions/upload-artifact-wheel
build-windows:
# Windows stays off PRs, develop pushes, and pre-commit-only test runs.
# yamllint disable-line rule:line-length
if: >
needs.plan.outputs.run-tests == 'true' &&
!(
(github.event_name == 'push' &&
(github.ref_name == 'develop' ||
github.ref_name == 'test-pre-commit'))
|| github.event_name == 'pull_request'
)
strategy:
fail-fast: false
matrix:
python-version:
- "3.12"
- "3.13"
- "3.14"
defaults:
run:
shell: bash
name: build - python ${{ matrix.python-version }} (windows)
runs-on: depot-windows-2022-8
needs:
- plan
- pre-commit
env:
BUILD_MODE: release
CARGO_TARGET_DIR: ${{ github.workspace }}/target/build-windows
HIGH_PRECISION: false
PARALLEL_BUILD: false
RUST_BACKTRACE: 1
steps:
# https://github.com/step-security/harden-runner
- uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0
with:
egress-policy: ${{ env.EGRESS_POLICY }}
allowed-endpoints: >-
${{ vars.COMMON_ALLOWED_ENDPOINTS }}
${{ vars.CI_ALLOWED_ENDPOINTS }}
- name: Checkout repository
# https://github.com/actions/checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
# Temporary: fail fast on rare runner worktree corruption while we isolate the root cause
- name: Verify immutable CI inputs (post-checkout)
run: bash scripts/ci/verify-ci-inputs.sh post-checkout
- name: Common setup
uses: ./.github/actions/common-setup
with:
python-version: ${{ matrix.python-version }}
free-disk-space: "true"
rust-cache-key: build-windows
rust-cache-workspaces: . -> target/build-windows
rust-cache-save-if: ${{ github.event_name == 'push' }}
# Temporary: fail fast on rare runner worktree corruption while we isolate the root cause
- name: Verify immutable CI inputs (pre-wheel build)
run: bash scripts/ci/verify-ci-inputs.sh pre-wheel-build
- name: Build and install wheel
uses: ./.github/actions/common-wheel-build
with:
python-version: ${{ matrix.python-version }}
github_ref: ${{ github.ref }}
- name: Cached test data
uses: ./.github/actions/common-test-data
- name: Run tests
run: |
uv run --no-sync python -m pytest --ignore=tests/performance_tests --reruns 2 --reruns-delay 1
- name: Upload wheel artifact
uses: ./.github/actions/upload-artifact-wheel
publish-wheels-develop:
name: publish-wheels-develop
runs-on: ubuntu-latest
environment: r2-develop
permissions:
actions: write # Required for deleting artifacts
contents: read
id-token: write # Required for attestations
attestations: write # Required for attestations
needs:
- build-linux-x86
# - build-windows # Windows builds moved to nightly only
# - build-linux-arm # Keep for nightly only (slow build)
# - build-macos # macOS builds moved to nightly only
if: >
github.event_name == 'push' && github.ref == 'refs/heads/develop'
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
CLOUDFLARE_R2_URL: ${{ secrets.CLOUDFLARE_R2_URL }}
CLOUDFLARE_R2_REGION: "auto"
CLOUDFLARE_R2_BUCKET_NAME: "packages"
steps:
# https://github.com/step-security/harden-runner
- uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0
with:
egress-policy: ${{ env.EGRESS_POLICY }}
allowed-endpoints: >-
${{ vars.COMMON_ALLOWED_ENDPOINTS }}
${{ vars.CI_ALLOWED_ENDPOINTS }}
# Sigstore TUF trust root (for gh attestation verify)
tuf-repo-cdn.sigstore.dev:443
${{ secrets.CLOUDFLARE_R2_ALLOWED_HOST }}:443
- name: Checkout repository
# https://github.com/actions/checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Download built wheels
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: dist
pattern: "nautilus_trader-*.whl"
merge-multiple: true
# https://github.com/actions/attest-build-provenance
- name: Attest wheel provenance
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
with:
subject-path: 'dist/nautilus_trader-*.whl'
- name: Verify wheel attestations
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
IDENTITY: ${{ github.server_url }}/${{ github.workflow_ref }}
ISSUER: https://token.actions.githubusercontent.com
run: |
set -e
for whl in dist/nautilus_trader-*.whl; do
gh attestation verify "$whl" \
--repo "$GITHUB_REPOSITORY" \
--cert-identity "$IDENTITY" \
--cert-oidc-issuer "$ISSUER"
done
- name: Publish wheels to Cloudflare R2
uses: ./.github/actions/publish-wheels
- name: Fetch and delete artifacts for current run
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
bash ./scripts/ci/publish-wheels-delete-artifacts.sh
security-gate-nightly:
name: security-gate-nightly
runs-on: ubuntu-latest
needs:
- build-linux-x86
- build-linux-arm
- build-macos
- build-windows
if: >
github.event_name == 'push' && github.ref == 'refs/heads/nightly'
steps:
- uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0
with:
egress-policy: ${{ env.EGRESS_POLICY }}
allowed-endpoints: >-
${{ vars.COMMON_ALLOWED_ENDPOINTS }}
${{ vars.SECURITY_AUDIT_ALLOWED_ENDPOINTS }}
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
# Requires admin access to modify; does not widen attack surface.
# See .github/OVERVIEW.md "Security gate override" for rationale.
- name: Check security gate override
id: gate
run: |
override="${{ vars.SECURITY_GATE_OVERRIDE }}"
if [ -n "$override" ] && [ "$(date -u +%s)" -lt "$(date -u -d "$override" +%s 2>/dev/null || echo 0)" ]; then
echo "::warning::Security gate override active until $override"
echo "override_active=true" >> "$GITHUB_OUTPUT"
else
echo "override_active=false" >> "$GITHUB_OUTPUT"
fi
- name: Install cargo-audit
if: steps.gate.outputs.override_active != 'true'
uses: ./.github/actions/cargo-tool-install
with:
tool-name: cargo-audit
- name: Run cargo-audit (fail on vulnerabilities)
if: steps.gate.outputs.override_active != 'true'
run: cargo audit --deny unsound
- name: Run osv-scanner
id: osv-scan
if: steps.gate.outputs.override_active != 'true'
continue-on-error: true
uses: google/osv-scanner-action/osv-scanner-action@c51854704019a247608d928f370c98740469d4b5 # v2.3.5
with:
scan-args: |
--config=osv-scanner.toml
--format json
--output=/github/workspace/osv-results.json
--lockfile=Cargo.lock
--lockfile=uv.lock
--lockfile=python/uv.lock
- name: Check OSV severity (fail on critical/high only)
if: steps.osv-scan.outcome == 'failure' && steps.gate.outputs.override_active != 'true'
run: bash scripts/ci/osv-severity-gate.sh osv-results.json
publish-wheels-nightly:
name: publish-wheels-nightly
runs-on: ubuntu-latest
environment: r2-nightly
permissions:
actions: write # Required for deleting artifacts
contents: read
id-token: write # Required for attestations
attestations: write # Required for attestations
needs:
- security-gate-nightly
if: >
github.event_name == 'push' && github.ref == 'refs/heads/nightly'
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
CLOUDFLARE_R2_URL: ${{ secrets.CLOUDFLARE_R2_URL }}
CLOUDFLARE_R2_REGION: "auto"
CLOUDFLARE_R2_BUCKET_NAME: "packages"
steps:
# https://github.com/step-security/harden-runner
- uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0
with:
egress-policy: ${{ env.EGRESS_POLICY }}
allowed-endpoints: >-
${{ vars.COMMON_ALLOWED_ENDPOINTS }}
${{ vars.CI_ALLOWED_ENDPOINTS }}
# Sigstore TUF trust root (for gh attestation verify)
tuf-repo-cdn.sigstore.dev:443
${{ secrets.CLOUDFLARE_R2_ALLOWED_HOST }}:443
- name: Checkout repository
# https://github.com/actions/checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Download built wheels
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: dist
pattern: "nautilus_trader-*.whl"
merge-multiple: true
# https://github.com/actions/attest-build-provenance
- name: Attest wheel provenance
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
with:
subject-path: 'dist/nautilus_trader-*.whl'
- name: Verify wheel attestations
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
IDENTITY: ${{ github.server_url }}/${{ github.workflow_ref }}
ISSUER: https://token.actions.githubusercontent.com
run: |
set -e
for whl in dist/nautilus_trader-*.whl; do
gh attestation verify "$whl" \
--repo "$GITHUB_REPOSITORY" \
--cert-identity "$IDENTITY" \
--cert-oidc-issuer "$ISSUER"
done
- name: Publish wheels to Cloudflare R2
uses: ./.github/actions/publish-wheels
- name: Fetch and delete artifacts for current run
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
bash ./scripts/ci/publish-wheels-delete-artifacts.sh
publish-wheels-master:
runs-on: ubuntu-latest
environment: release
permissions:
contents: write # Required for uploading release assets
actions: write # Required for deleting artifacts
id-token: write # Required for attestations
attestations: write # Required for attestations
needs:
- build-linux-x86
- build-linux-arm
- build-macos
- build-windows
- tag-release
if: >
github.event_name == 'push' && github.ref == 'refs/heads/master'
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
CLOUDFLARE_R2_URL: ${{ secrets.CLOUDFLARE_R2_URL }}
CLOUDFLARE_R2_REGION: "auto"
CLOUDFLARE_R2_BUCKET_NAME: "packages"
steps:
# https://github.com/step-security/harden-runner
- uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0
with:
egress-policy: ${{ env.EGRESS_POLICY }}
allowed-endpoints: >-
${{ vars.COMMON_ALLOWED_ENDPOINTS }}
${{ vars.CI_ALLOWED_ENDPOINTS }}
# Sigstore TUF trust root (for gh attestation verify)
tuf-repo-cdn.sigstore.dev:443
pypi.org:443
files.pythonhosted.org:443
upload.pypi.org:443
${{ secrets.CLOUDFLARE_R2_ALLOWED_HOST }}:443
- name: Checkout repository
# https://github.com/actions/checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Get uv version from pyproject.toml
shell: bash
run: |
echo "UV_VERSION=$(bash scripts/uv-version.sh)" >> "$GITHUB_ENV"
- name: Install uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
version: ${{ env.UV_VERSION }}
- name: Download built wheels for PyPI and GitHub release
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: dist
pattern: "nautilus_trader-*.whl"
merge-multiple: true
# https://github.com/actions/attest-build-provenance
- name: Attest wheel provenance
id: attest-wheels
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
with:
subject-path: 'dist/nautilus_trader-*.whl'
- name: Verify wheel attestations
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
IDENTITY: ${{ github.server_url }}/${{ github.workflow_ref }}
ISSUER: https://token.actions.githubusercontent.com
run: |
set -e
for whl in dist/nautilus_trader-*.whl; do
gh attestation verify "$whl" \
--repo "$GITHUB_REPOSITORY" \
--cert-identity "$IDENTITY" \
--cert-oidc-issuer "$ISSUER"
done
- name: Publish wheels to Cloudflare R2
uses: ./.github/actions/publish-wheels
- name: Upload wheels to GitHub release
# https://cli.github.com/manual/gh_release_upload
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG_NAME: ${{ needs.tag-release.outputs.tag_name }}
run: |
set +e
success=false
for i in {1..5}; do
gh release upload "$TAG_NAME" dist/nautilus_trader-*.whl --clobber --repo "$GITHUB_REPOSITORY"
status=$?
if [ $status -eq 0 ]; then
success=true
break
else
echo "gh upload (wheels) failed (exit=$status), retry ($i/5)"
sleep $((2**i))
fi
done
set -e
if [ "$success" = false ]; then echo "Failed to upload wheels to release after retries"; exit 1; fi
- name: Publish to PyPI
if: success()
run: |
# Authenticates via PyPI Trusted Publishing (OIDC) - no long-lived credentials.
# Requires id-token: write permission and a Trusted Publisher configured on PyPI
# bound to: repo nautechsystems/nautilus_trader, workflow build.yml, environment release.
# Use --check-url to skip files that already exist on PyPI
set +e
success=false
for i in {1..5}; do
uv publish --trusted-publishing automatic --check-url https://pypi.org/simple/
status=$?
if [ $status -eq 0 ]; then
success=true
break
else
echo "uv publish failed (exit=$status), retry ($i/5)"
sleep $((2**i))
fi
done
set -e
if [ "$success" = false ]; then
echo "Failed to publish wheels to PyPI after retries"
exit 1
fi
# Scorecard's Signed-Releases check detects signing only by release-asset
# filenames (.sig/.sigstore/.intoto.jsonl). Copy the existing Sigstore
# bundle next to each wheel so Scorecard sees coverage. Runs after PyPI
# publish (uv publish reads dist/) and is non-blocking on failure.
- name: Attach attestation siblings to GitHub release
continue-on-error: true
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG_NAME: ${{ needs.tag-release.outputs.tag_name }}
BUNDLE_PATH: ${{ steps.attest-wheels.outputs.bundle-path }}
run: |
bash ./scripts/ci/publish-release-attestation-siblings.sh \
dist/nautilus_trader-*.whl
- name: Fetch and delete artifacts for current run
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
bash ./scripts/ci/publish-wheels-delete-artifacts.sh
tag-release:
needs:
- build-linux-x86
- build-linux-arm
- build-macos
- build-windows
- cargo-deny
- cargo-vet
permissions:
contents: write # Required for pushing tags and upload release assets
actions: write # Required for creating releases
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
outputs:
upload_url: ${{ steps.create-release.outputs.upload_url }}
tag_name: ${{ env.TAG_NAME }}
steps:
# Security hardening
- uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0
with:
egress-policy: ${{ env.EGRESS_POLICY }}
allowed-endpoints: >-
${{ vars.COMMON_ALLOWED_ENDPOINTS }}
${{ vars.CI_ALLOWED_ENDPOINTS }}
- name: Checkout repository
# https://github.com/actions/checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: true # Required for salsify action to push git tags
fetch-depth: 2
fetch-tags: true
- name: Set up Python environment
# https://github.com/actions/setup-python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.13"
- name: Create git tag
# https://github.com/salsify/action-detect-and-tag-new-version
uses: salsify/action-detect-and-tag-new-version@b1778166f13188a9d478e2d1198f993011ba9864 # v2.0.3
with:
version-command: ./scripts/package-version.sh
- name: Set output
id: vars
run: |
echo "TAG_NAME=v$(./scripts/package-version.sh)" >> "$GITHUB_ENV"
echo "RELEASE_NAME=NautilusTrader $(./scripts/package-version.sh) Beta" >> "$GITHUB_ENV"
sed -n '/^#/,$ {p;/^---/q}; w RELEASE.md' RELEASES.md
- name: Create GitHub release
id: create-release
# https://github.com/softprops/action-gh-release v3.0.0
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ env.TAG_NAME }}
name: ${{ env.RELEASE_NAME }}
draft: false
prerelease: false
body_path: RELEASE.md
publish-sdist:
needs: [tag-release]
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
environment: release
permissions:
contents: write # Required for uploading release assets
id-token: write # Required for attestations
attestations: write # Required for attestations
env:
COPY_TO_SOURCE: false # Do not copy built *.so files back into source tree
steps:
# https://github.com/step-security/harden-runner
- uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0
with:
egress-policy: ${{ env.EGRESS_POLICY }}
allowed-endpoints: >-
${{ vars.COMMON_ALLOWED_ENDPOINTS }}
${{ vars.CI_ALLOWED_ENDPOINTS }}
# Sigstore TUF trust root (for gh attestation verify)
tuf-repo-cdn.sigstore.dev:443
pypi.org:443
files.pythonhosted.org:443
upload.pypi.org:443
- name: Checkout repository
# https://github.com/actions/checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Common setup
uses: ./.github/actions/common-setup
with:
python-version: "3.13"
free-disk-space: "true"
- name: Build sdist
run: |
uv build --sdist
# https://github.com/actions/attest-build-provenance
- name: Attest sdist provenance
id: attest-sdist
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
with:
subject-path: 'dist/*.tar.gz'
- name: Verify sdist attestation
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
IDENTITY: ${{ github.server_url }}/${{ github.workflow_ref }}
ISSUER: https://token.actions.githubusercontent.com
run: |
gh attestation verify dist/*.tar.gz \
--repo "$GITHUB_REPOSITORY" \
--cert-identity "$IDENTITY" \
--cert-oidc-issuer "$ISSUER"
- name: Set release output
id: vars
run: |
if [ ! -d "./dist" ]; then
echo "Error: dist directory not found"
exit 1
fi
ASSET_PATH=$(find ./dist -name "*.tar.gz" -type f -print0 | xargs -0 ls -t 2>/dev/null | head -n 1)
if [ -z "$ASSET_PATH" ]; then
echo "Error: No .tar.gz files found in dist directory"
exit 1
fi
echo "ASSET_PATH=$ASSET_PATH" >> "$GITHUB_ENV"
echo "ASSET_NAME=$(basename "$ASSET_PATH")" >> "$GITHUB_ENV"
- name: Upload release asset
# https://cli.github.com/manual/gh_release_upload
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG_NAME: ${{ needs.tag-release.outputs.tag_name }}
ASSET_PATH: ${{ env.ASSET_PATH }}
run: |
set +e
success=false
for i in {1..5}; do
gh release upload "$TAG_NAME" "$ASSET_PATH" --clobber --repo "$GITHUB_REPOSITORY"
status=$?
if [ $status -eq 0 ]; then
success=true
break
else
echo "gh upload (sdist) failed (exit=$status), retry ($i/5)"
sleep $((2**i))
fi
done
set -e
if [ "$success" = false ]; then echo "Failed to upload sdist to release after retries"; exit 1; fi
- name: Publish to PyPI
if: success()
run: |
# Authenticates via PyPI Trusted Publishing (OIDC) - no long-lived credentials.
# Requires id-token: write permission and a Trusted Publisher configured on PyPI
# bound to: repo nautechsystems/nautilus_trader, workflow build.yml, environment release.
# Use --check-url to skip files that already exist on PyPI
set +e
success=false
for i in {1..5}; do
uv publish --trusted-publishing automatic --check-url https://pypi.org/simple/
status=$?
if [ $status -eq 0 ]; then
success=true
break
else
echo "uv publish failed (exit=$status), retry ($i/5)"
sleep $((2**i))
fi
done
set -e
if [ "$success" = false ]; then
echo "Failed to publish sdist to PyPI after retries"
exit 1
fi
# Mirrors the wheels job: copy the Sigstore bundle next to the sdist so
# Scorecard's filename-based Signed-Releases check sees coverage.
- name: Attach attestation sibling to GitHub release
continue-on-error: true
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG_NAME: ${{ needs.tag-release.outputs.tag_name }}
BUNDLE_PATH: ${{ steps.attest-sdist.outputs.bundle-path }}
run: |
bash ./scripts/ci/publish-release-attestation-siblings.sh \
"$ASSET_PATH"