asry 0.1.0

Sans-I/O cut/batch/whisper/align state machine for speech-to-text indexing pipelines
name: CI

on:
  push:
    branches:
      - main
    paths-ignore:
      - 'README'
      - 'COPYRIGHT'
      - 'LICENSE-*'
      - '**.md'
      - '**.txt'
  pull_request:
    paths-ignore:
      - 'README'
      - 'COPYRIGHT'
      - 'LICENSE-*'
      - '**.md'
      - '**.txt'
  workflow_dispatch:
  schedule: 
    - cron: "0 1 1 * *"

env:
  CARGO_TERM_COLOR: always
  RUSTFLAGS: -Dwarnings
  RUST_BACKTRACE: 1
  ASRY_FETCH_FIXTURES: '1'

jobs:
  # Check formatting (platform-independent, one OS is enough)
  rustfmt:
    name: rustfmt
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v7
    # Nightly toolchain — `rustfmt.toml` uses
    # `imports_granularity = "Crate"`, an unstable feature only
    # available on nightly. Without this the stable rustfmt
    # silently drops the setting and reports diffs vs the
    # nightly-formatted tree.
    - name: Install Rust (nightly)
      run: rustup update nightly --no-self-update && rustup default nightly && rustup component add rustfmt --toolchain nightly
    - name: Check formatting
      run: cargo fmt --all -- --check

  # Apply clippy lints
  clippy:
    name: clippy
    strategy:
      matrix:
        os:
          - ubuntu-latest
          - macos-latest
          - windows-latest
    runs-on: ${{ matrix.os }}
    steps:
    - uses: actions/checkout@v7
    - name: Install Rust
      # --no-self-update is necessary because the windows environment cannot self-update rustup.exe.
      run: rustup update stable --no-self-update && rustup default stable && rustup component add clippy
    - name: Apply clippy lints
      # Explicit feature combos — see build job for rationale.
      run: |
        set -e
        cargo clippy -- -D warnings
        cargo clippy --features alignment -- -D warnings
        cargo clippy --features alignment,serde -- -D warnings
        cargo clippy --features alignment,bench-internals -- -D warnings
        cargo clippy --no-default-features --features emissions -- -D warnings
        cargo clippy --no-default-features --features emissions,serde -- -D warnings
        if [ "$RUNNER_OS" = "macOS" ]; then
          cargo clippy --features alignment,metal,coreml -- -D warnings
        fi
      shell: bash

  # Run tests on some extra platforms
  # `cross`, `sanitizer`, `miri-sb`, `miri-tb` removed: they are
  # template defaults that don't fit asry's architecture.
  # `Lang` is asry's own native pure-Rust enum (`src/types/lang.rs`),
  # not re-exported from `whispercpp`, so the `emissions`-only combo
  # (no `runner`, no `alignment`) compiles and clippy-passes without
  # whisper.cpp or ONNX Runtime — see the `clippy`/`build`/`test`
  # jobs' `--no-default-features --features emissions[,serde]` lines
  # below. The default (`runner`) and `alignment` combos still
  # require linking whisper.cpp (cmake + C++) and, for `alignment`,
  # ONNX Runtime via load-dynamic. Neither dependency:
  #   * cross-compiles cleanly to the cross-job target list
  #     (wasm, i686, android, etc.) — `cmake + Foundation`
  #     framework lookups fail before we ever see asry
  #     code,
  #   * is meaningfully sanitizable / miri-evaluable from
  #     downstream — any UB those tools would find lives in
  #     whisper.cpp / ORT, which test themselves upstream.
  # `emissions` is exactly the meaningful ORT/whisper.cpp-free subset
  # this comment used to say would justify a scoped job (the trellis/
  # beam/tokenize/normalize/compose algorithm factored out of
  # `runner` — see Cargo.toml's `emissions` feature doc); it now has
  # build/test/clippy coverage below.
  #
  # ORT/whisper.cpp-free is NOT pure-Rust, and the distinction is
  # exactly the one this comment turns on. `emissions` enables
  # `tokenizers` with `features = ["onig"]`, which pulls
  # `onig v6.5.3` -> `onig_sys v69.9.3`; `onig_sys` declares
  # `links = "onig"` and compiles ~57 C files of Oniguruma through
  # `cc` in its build script. Verify with:
  #   cargo tree --no-default-features --features emissions -i onig_sys
  #
  # Do NOT read that as "Oniguruma is the only thing keeping cross /
  # sanitizer / Miri out of scope" — an earlier version of this comment
  # did, and it was overbroad. asry ALREADY compiles C under every
  # feature combo, `--no-default-features` included, because
  # `[build-dependencies] ureq` pulls `rustls` -> `ring` -> `cc`:
  #   cargo tree --no-default-features -e normal,build -i cc
  #
  # The two are not interchangeable. `ring` is a BUILD-dependency: it is
  # host-compiled, never linked into the library, and invisible to Miri
  # (which interprets the target crate graph, not build scripts) and to
  # the target's sanitizers. `onig_sys` is a NORMAL dependency with a
  # `links` key that rides into the built artifact. So `emissions` is the
  # combo that adds native code to the LIBRARY; it is not the combo that
  # first introduces a C compiler to the BUILD.
  #
  # Net: `cross`/`sanitizer`/`miri` stay out of scope for every feature
  # combo — but for two independent reasons, and dropping the `onig`
  # tokenizer feature would only remove one of them. Keep in sync with
  # the `tokenizers` comment in Cargo.toml.

  build:
    name: build
    strategy:
      matrix:
        os:
          - ubuntu-latest
          - macos-latest
          - windows-latest
    runs-on: ${{ matrix.os }}
    steps:
    - uses: actions/checkout@v7
    - name: Cache cargo build and registry
      uses: actions/cache@v6
      with:
        path: |
          ~/.cargo/registry
          ~/.cargo/git
          target
        key: ${{ runner.os }}-build-${{ hashFiles('**/Cargo.lock') }}
        restore-keys: |
          ${{ runner.os }}-build-
    - name: Install Rust
      # --no-self-update is necessary because the windows environment cannot self-update rustup.exe.
      run: rustup update stable --no-self-update && rustup default stable
    - name: Run build
      # Explicit feature combos rather than `cargo hack
      # --feature-powerset`: asry's features aren't fully
      # orthogonal (`alignment` implies `runner` + `emissions`), and
      # a full powerset would waste CI time on combos that can't
      # build on some runners or are redundant with `alignment`.
      # `Lang` is asry's own native pure-Rust enum
      # (`src/types/lang.rs`), not re-exported from `whispercpp`, so
      # the `emissions`-only combo below compiles without `runner`
      # (no whisper.cpp, no ONNX Runtime). Apple-only `metal` /
      # `coreml` only build on macOS; diagnostic toggles
      # (`parity-dump-emission`, `bench-internals`) ship
      # internals, never on the production surface.
      run: |
        set -e
        cargo build
        cargo build --features alignment
        cargo build --features alignment,serde
        cargo build --features alignment,bench-internals
        cargo build --no-default-features --features emissions
        cargo build --no-default-features --features emissions,serde
        if [ "$RUNNER_OS" = "macOS" ]; then
          cargo build --features alignment,metal,coreml
        fi
      shell: bash

  test:
    name: test
    strategy:
      matrix:
        os:
          - ubuntu-latest
          - macos-latest
          - windows-latest
    runs-on: ${{ matrix.os }}
    steps:
    - uses: actions/checkout@v7
    - name: Cache cargo build and registry
      uses: actions/cache@v6
      with:
        path: |
          ~/.cargo/registry
          ~/.cargo/git
          target
        key: ${{ runner.os }}-test-${{ hashFiles('**/Cargo.lock') }}
        restore-keys: |
          ${{ runner.os }}-test-
    - name: Install Rust
      # --no-self-update is necessary because the windows environment cannot self-update rustup.exe.
      run: rustup update stable --no-self-update && rustup default stable
    # Audio parity fixtures (~283 MB of WAV + RTTM) live in the
    # sibling repo `Findit-AI/audio-fixtures`, NOT in asry's
    # git history. The fetch is opt-in via
    # `ASRY_FETCH_FIXTURES=1` so the default `cargo test`
    # path stays network-free and fast. Tests that need the
    # fixtures gate themselves on the populated path
    # (`tests/parity/fixtures/<name>/clip_16k.wav`) and
    # short-circuit when absent.
    - name: Fetch audio parity fixtures
      if: env.ASRY_FETCH_FIXTURES == '1'
      shell: bash
      run: bash scripts/fetch_fixtures.sh
    - name: Run test
      # Explicit feature combos — see build job for rationale.
      run: |
        set -e
        cargo test
        cargo test --features alignment
        cargo test --features alignment,serde
        cargo test --features alignment,bench-internals
        cargo test --no-default-features --features emissions
        cargo test --no-default-features --features emissions,serde
      shell: bash

  coverage:
    name: coverage
    runs-on: ubuntu-latest
    needs:
      - rustfmt
      - clippy
      - build
      - test
    steps:
      - uses: actions/checkout@v7
      - name: Install Rust
        run: rustup update nightly && rustup default nightly
      - name: Install cargo-tarpaulin
        run: cargo install cargo-tarpaulin
      - name: Cache cargo build and registry
        uses: actions/cache@v6
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-coverage-${{ hashFiles('**/Cargo.lock') }}
          restore-keys: |
            ${{ runner.os }}-coverage-
      - name: Fetch audio parity fixtures
        if: env.ASRY_FETCH_FIXTURES == '1'
        shell: bash
        run: bash scripts/fetch_fixtures.sh
      - name: Run tarpaulin
        env:
          RUSTFLAGS: "--cfg tarpaulin"
        # Coverage runs Linux. `metal` / `coreml` need
        # Apple frameworks; diagnostic toggles ship internals.
        # Cover the full default-runner-and-alignment surface.
        run: cargo tarpaulin --features alignment,serde,bench-internals --run-types tests --run-types doctests --workspace --out xml
      - name: Upload to codecov.io
        uses: codecov/codecov-action@v7
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          slug: ${{ github.repository }}
          fail_ci_if_error: true