peteksim 0.1.3

petekSim — the SIMULATION layer: dynamic/engineering appraisal (recoverable/forecast) + PVT + the appraisal facade over the petek subsurface-modelling stack, consolidated into one crate.
Documentation
name: Release

# Tag-triggered crates.io + PyPI publish (v* tags). PyPI uses OIDC trusted
# publishing; crates.io uses the CARGO_REGISTRY_TOKEN repo secret.
# The `peteksim` wheel is built from crates/srs-py (its pyproject.toml). The
#
# Configure a pending publisher on PyPI: project `peteksim`, owner kkollsga, repo
# peteksim, workflow release.yml, environment pypi.
#
# NOTE: before pushing a v* tag, the workspace family deps in Cargo.toml must
# resolve from crates.io. Release-train floors are updated after upstream family
# crates publish, so the CI runner uses registry crates rather than sibling paths.
on:
  push:
    tags: ["v*"]
  workflow_dispatch:

permissions:
  contents: read

jobs:
  # Release gate: the same fmt/clippy/test bar as CI must be green before any
  # build or publish job runs. CI failures block releases — by construction.
  gates:
    name: gates (fmt / clippy / test)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy, rustfmt
      - uses: Swatinem/rust-cache@v2
      - name: require v* tag
        run: |
          case "$GITHUB_REF" in
            refs/tags/v*) ;;
            *) echo "release workflow must run from a v* tag, got $GITHUB_REF" >&2; exit 1 ;;
          esac
      - name: verify tag matches Cargo.toml
        run: |
          VER=$(cargo metadata --no-deps --format-version 1 \
            | jq -r '.packages[] | select(.name=="peteksim") | .version')
          TAG="${GITHUB_REF_NAME#v}"
          if [ "$VER" != "$TAG" ]; then
            echo "tag ${GITHUB_REF_NAME} does not match Cargo version ${VER}" >&2
            exit 1
          fi
      - name: fmt
        run: cargo fmt --all --check
      - name: clippy (warnings = errors)
        run: cargo clippy --workspace --all-targets -- -D warnings
      - name: test
        run: cargo test --workspace

  publish-crate:
    name: crates.io
    needs: gates
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - name: Publish to crates.io (skip if this version is already published)
        run: |
          VER=$(cargo metadata --no-deps --format-version 1 \
            | jq -r '.packages[] | select(.name=="peteksim") | .version')
          if curl -fsS -H "User-Agent: peteksim-ci" \
               "https://crates.io/api/v1/crates/peteksim/$VER" \
               | jq -e '.version.num' >/dev/null 2>&1; then
            echo "peteksim $VER already on crates.io; skipping publish"
          else
            cargo publish -p peteksim --token ${{ secrets.CARGO_REGISTRY_TOKEN }}
          fi

  sdist:
    name: build sdist
    needs: gates
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: PyO3/maturin-action@v1
        with:
          command: sdist
          args: --out dist
          working-directory: crates/srs-py
      - uses: actions/upload-artifact@v4
        with:
          name: wheels-sdist
          path: crates/srs-py/dist

  wheels:
    name: build wheels ${{ matrix.runner }} ${{ matrix.target }}
    needs: gates
    runs-on: ${{ matrix.runner }}
    strategy:
      fail-fast: false
      matrix:
        include:
          - { runner: ubuntu-latest, target: x86_64 }
          - { runner: ubuntu-latest, target: aarch64 }
          - { runner: macos-14, target: universal2-apple-darwin }
          - { runner: windows-latest, target: x64 }
    steps:
      - uses: actions/checkout@v4
      - uses: PyO3/maturin-action@v1
        with:
          target: ${{ matrix.target }}
          args: --release --out dist
          sccache: "true"
          manylinux: auto
          working-directory: crates/srs-py
      - uses: actions/upload-artifact@v4
        with:
          name: wheels-${{ matrix.runner }}-${{ matrix.target }}
          path: crates/srs-py/dist

  publish-pypi:
    name: PyPI (trusted publishing)
    needs: [gates, sdist, wheels]
    runs-on: ubuntu-latest
    environment: pypi
    permissions:
      id-token: write
    steps:
      - uses: actions/download-artifact@v4
        with:
          pattern: wheels-*
          path: dist
          merge-multiple: true
      - uses: pypa/gh-action-pypi-publish@release/v1

  github-release:
    name: GitHub Release
    needs: [publish-crate, publish-pypi]
    if: startsWith(github.ref, 'refs/tags/')
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: softprops/action-gh-release@v2
        with:
          generate_release_notes: true