stateforward-sml 1.0.0

sml.rs: a no-std Rust implementation of the sml.cpp transition-table DSL
Documentation
name: Release

on:
  workflow_dispatch:
    inputs:
      version:
        description: Version from Cargo.toml, without a v prefix
        required: true
        type: string

concurrency:
  group: crates-io-release
  cancel-in-progress: false

permissions:
  contents: write
  id-token: write
  attestations: write

jobs:
  release:
    name: Publish and attest release
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment: release
    steps:
      - uses: actions/checkout@v7
        with:
          fetch-depth: 0
          persist-credentials: true

      - uses: actions/setup-python@v6
        with:
          python-version: "3.13"

      - uses: dtolnay/rust-toolchain@stable

      - name: Verify release version and tag
        env:
          VERSION: ${{ inputs.version }}
          GH_TOKEN: ${{ github.token }}
        run: |
          python3 - <<'PY'
          import os
          import pathlib
          import tomllib

          expected = os.environ["VERSION"]
          versions = {
              pathlib.Path("Cargo.toml"): tomllib.loads(pathlib.Path("Cargo.toml").read_text())["package"]["version"],
              pathlib.Path("macros/Cargo.toml"): tomllib.loads(pathlib.Path("macros/Cargo.toml").read_text())["package"]["version"],
          }
          mismatched = {str(path): version for path, version in versions.items() if version != expected}
          if mismatched:
              raise SystemExit(f"version mismatch: expected {expected}, found {mismatched}")
          PY
          git diff --exit-code
          if gh release view "v${VERSION}" >/dev/null 2>&1; then
            echo "release v${VERSION} already exists" >&2
            exit 1
          fi

      - name: Prepare API baseline
        env:
          BASELINE_REF: HEAD^
        run: |
          mkdir .semver-baseline
          git archive "${BASELINE_REF}" | tar -x -C .semver-baseline
          python3 scripts/prepare_semver_baseline.py .semver-baseline

      - name: Enforce public API compatibility
        uses: obi1kenobi/cargo-semver-checks-action@v2
        with:
          package: stateforward-sml
          feature-group: all-features
          baseline-root: .semver-baseline

      - name: Check crates.io release state
        id: registry
        env:
          VERSION: ${{ inputs.version }}
        run: |
          if cargo info "stateforward-sml-macros@${VERSION}" >/dev/null 2>&1; then
            echo "macros_exists=true" >> "${GITHUB_OUTPUT}"
          else
            echo "macros_exists=false" >> "${GITHUB_OUTPUT}"
          fi
          if cargo info "stateforward-sml@${VERSION}" >/dev/null 2>&1; then
            echo "runtime_exists=true" >> "${GITHUB_OUTPUT}"
          else
            echo "runtime_exists=false" >> "${GITHUB_OUTPUT}"
          fi

      - name: Validate crates.io release state
        env:
          VERSION: ${{ inputs.version }}
          MACROS_EXISTS: ${{ steps.registry.outputs.macros_exists }}
          RUNTIME_EXISTS: ${{ steps.registry.outputs.runtime_exists }}
        run: |
          if [ "${MACROS_EXISTS}" != "${RUNTIME_EXISTS}" ]; then
            echo "Only one crate exists at ${VERSION}; finish the interrupted publication manually." >&2
            exit 1
          fi
          if [ "${VERSION}" = "1.0.0" ] && [ "${MACROS_EXISTS}" = "false" ]; then
            echo "Bootstrap 1.0.0 with an API token as documented in docs/releasing.md." >&2
            exit 1
          fi

      - name: Package macro crate
        run: cargo package -p stateforward-sml-macros

      - name: Authenticate to crates.io
        if: steps.registry.outputs.macros_exists == 'false'
        uses: rust-lang/crates-io-auth-action@v1
        id: auth

      - name: Publish macro crate first
        if: steps.registry.outputs.macros_exists == 'false'
        env:
          CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
        run: cargo publish -p stateforward-sml-macros

      - name: Wait for macro crate index propagation
        if: steps.registry.outputs.macros_exists == 'false'
        env:
          VERSION: ${{ inputs.version }}
        run: |
          for _ in $(seq 1 30); do
            if cargo info "stateforward-sml-macros@${VERSION}" >/dev/null 2>&1; then
              exit 0
            fi
            sleep 10
          done
          echo "stateforward-sml-macros ${VERSION} did not reach the crates.io index" >&2
          exit 1

      - name: Package runtime crate
        run: cargo package -p stateforward-sml

      - name: Publish runtime crate
        if: steps.registry.outputs.runtime_exists == 'false'
        env:
          CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
        run: cargo publish -p stateforward-sml

      - name: Attest published crate archives
        uses: actions/attest-build-provenance@v4
        with:
          subject-path: target/package/*.crate

      - name: Create annotated source tag and GitHub release
        env:
          GH_TOKEN: ${{ github.token }}
          VERSION: ${{ inputs.version }}
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
          git tag -a "v${VERSION}" -m "sml.rs ${VERSION}"
          git push origin "v${VERSION}"
          gh release create "v${VERSION}" \
            target/package/*.crate \
            --target "${GITHUB_SHA}" \
            --title "sml.rs ${VERSION}" \
            --generate-notes \
            --verify-tag