Add nautilus-event-store DataCommand encoder #15110
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" |