Skip to content

Add batch streaming mode: assembly stream --from-stdin #645

Add batch streaming mode: assembly stream --from-stdin

Add batch streaming mode: assembly stream --from-stdin #645

Workflow file for this run

name: CI
on:
pull_request:
branches: [main]
types: [opened, reopened, ready_for_review, synchronize]
merge_group: # Merge-queue runs: validate the queued merge result so two
# PRs that are each green against an older main can't land a
# semantic conflict together.
push:
branches: [main] # PRs are covered by pull_request (incl. synchronize);
# scoping push to main avoids double-running every PR commit.
# Least privilege: CI only needs to read the repo. Actions are pinned to commit
# SHAs (a moved tag can't silently change what runs); Dependabot keeps them current.
permissions:
contents: read
# Cancel superseded runs when new commits land on a PR/branch, but never cancel a
# main run (don't kill an in-flight merge build).
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
check:
name: lint + typecheck + tests (py${{ matrix.python-version }})
runs-on: ubuntu-latest
timeout-minutes: 15
# Test both ends of the supported range: 3.12 is the floor (requires-python),
# 3.13 is what the Homebrew formula ships. fail-fast off so one version's
# failure doesn't mask the other's.
strategy:
fail-fast: false
matrix:
python-version: ["3.12", "3.13"]
# Pin the interpreter every `uv run`/`uv build` in check.sh resolves to, so the
# matrix actually exercises each version rather than whatever uv would pick.
env:
UV_PYTHON: ${{ matrix.python-version }}
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false # no job pushes; don't leave the token in .git/config
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ matrix.python-version }}
cache: pip
# PortAudio backs sounddevice; ffmpeg decodes non-WAV/URL audio (the `--sample`
# stream tests build a FileSource for the hosted sample, which needs ffmpeg).
- name: System deps (PortAudio + ffmpeg)
run: sudo apt-get update && sudo apt-get install -y libportaudio2 ffmpeg
# check.sh lints Markdown and template JS/CSS via Node CLIs; versions are
# pinned in scripts/gate_tool_pins.sh (shared with the web session-start
# hook). The runner ships Node, so a global npm install suffices.
- name: Node lint CLIs
run: |
source scripts/gate_tool_pins.sh
npm install -g "markdownlint-cli@${MARKDOWNLINT_VERSION}" "prettier@${PRETTIER_VERSION}"
# check.sh runs every tool through `uv run` / `uv build` for a locked,
# reproducible env, so only uv must be on PATH (installed from PyPI to match
# the repo's pip-based, no-new-action posture). `uv run` itself syncs the
# project + dev group into .venv, so no `pip install -e .` is needed here.
- name: Install
run: python -m pip install uv
# actionlint and gitleaks are Go binaries (no PyPI wheel), so check.sh self-skips
# them locally like shellcheck. Build them here with the runner's preinstalled Go,
# pinned via scripts/gate_tool_pins.sh (shared with the web session-start hook),
# and put GOPATH/bin on PATH so check.sh enforces them.
# (gitleaks v8's Go module path is still github.com/zricethezav/gitleaks/v8.)
- name: Workflow + secret scanners (actionlint, gitleaks)
run: |
source scripts/gate_tool_pins.sh
go install "$ACTIONLINT_MODULE"
go install "$GITLEAKS_MODULE"
echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH"
- name: Lint, typecheck, test
run: ./scripts/check.sh
# Branch protection requires a check literally named "lint + typecheck + tests",
# but `check` is a matrix, so its contexts are suffixed "(py3.12)" / "(py3.13)".
# Re-publish the un-suffixed name here, green only when every matrix cell passed
# (if: always() + an explicit result check, so a failed/skipped/cancelled matrix
# can't satisfy the required check). Point branch protection at this one stable
# name and matrix changes never break the required check again.
check-result:
name: lint + typecheck + tests
needs: [check]
if: always()
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Require every py-version matrix cell to have passed
run: |
if [ "${{ needs.check.result }}" != "success" ]; then
echo "check matrix result: ${{ needs.check.result }}"
exit 1
fi
echo "all py-version matrix cells passed"
windows:
name: tests (windows, py${{ matrix.python-version }})
runs-on: windows-latest
timeout-minutes: 20
# Windows can't run scripts/check.sh (it's bash plus Go/Homebrew/shell tooling), so
# this job runs only the pytest suite — enough to catch Windows-specific regressions
# (path handling, subprocess/encoding, POSIX-only assumptions). The lint/type/security
# gates stay on the Linux `check` job. Same Python ends as that matrix: 3.12 floor,
# 3.13 shipped; fail-fast off so one version's failure doesn't mask the other's.
strategy:
fail-fast: false
matrix:
python-version: ["3.12", "3.13"]
# Pin the interpreter every `uv run` resolves to, so the matrix exercises each version.
env:
UV_PYTHON: ${{ matrix.python-version }}
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false # no job pushes; don't leave the token in .git/config
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ matrix.python-version }}
cache: pip
# ffmpeg must be on PATH: the `stream --sample`/`clip`/`caption` paths probe for it
# (require_ffmpeg) before doing their work, so without it those tests fail at the
# probe rather than exercising the mocked run. PortAudio needs no install — the
# sounddevice wheel bundles it on Windows. choco ships on the runner but its download
# occasionally flakes (one matrix cell got ffmpeg, the other didn't), so retry and
# verify ffmpeg is callable here — a real miss fails this step instead of surfacing as
# confusing "ffmpeg not on PATH" test failures. The shim lands in choco's bin dir,
# already on the runner PATH, so later steps pick it up.
- name: System deps (ffmpeg)
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
$env:PATH = "C:\ProgramData\chocolatey\bin;$env:PATH"
for ($i = 1; $i -le 3; $i++) {
choco install ffmpeg --no-progress -y
if (Get-Command ffmpeg -ErrorAction SilentlyContinue) { break }
Write-Host "ffmpeg not yet on PATH (attempt $i); retrying…"
Start-Sleep -Seconds 5
}
ffmpeg -version
- name: Install uv
run: python -m pip install uv
# `uv run` syncs the locked project + dev group into .venv, then runs the default
# suite (e2e/install excluded via addopts).
- name: Run test suite
run: uv run pytest -q
# Stable, un-suffixed name for branch protection, mirroring `check-result`: green only
# when every Windows matrix cell passed (a failed/skipped/cancelled matrix can't satisfy
# it). Point branch protection at this one name and matrix changes won't break it.
windows-result:
name: tests (windows)
needs: [windows]
if: always()
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Require every Windows matrix cell to have passed
run: |
if [ "${{ needs.windows.result }}" != "success" ]; then
echo "windows matrix result: ${{ needs.windows.result }}"
exit 1
fi
echo "all windows matrix cells passed"
lint-formula:
name: brew style (Homebrew formula)
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false # no job pushes; don't leave the token in .git/config
# Homebrew's formula linters live inside `brew`, so set it up on the runner.
# Homebrew/actions is a monorepo (setup-homebrew is a subpath); pin it to a
# commit SHA like every other action here — Dependabot keeps it current.
- uses: Homebrew/actions/setup-homebrew@2ebcf16054461267868620b1414507f3ccc765c1
# `brew style` is the RuboCop-based formula linter and runs fully offline, so
# it lints idioms (resource/depends_on ordering, on_linux scoping) on every PR.
# A real `brew install --build-bottle` of the formula runs in release.yml when
# a tag is cut; doing it per-PR was dropped — the from-source macOS build
# (rust + cryptography) took too long for PR feedback. The stricter
# `brew audit --strict --online` still belongs in a release job.
- name: Lint the formula
run: brew style ./Formula/assembly.rb
pre-commit:
name: pre-commit
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false # no job pushes; don't leave the token in .git/config
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.12"
cache: pip
# PortAudio backs sounddevice; ffmpeg decodes the `--sample` stream source.
- name: System deps (PortAudio + ffmpeg)
run: sudo apt-get update && sudo apt-get install -y libportaudio2 ffmpeg
# The local pytest hook runs `uv run --frozen python -m pytest`, so the tests
# resolve the LOCKED dependency versions (uv.lock) rather than the newest
# release `pip install` would pull — which is what keeps the byte-exact
# `--help` snapshots stable. Install uv and materialize the frozen env here.
- name: Install
run: |
python -m pip install --upgrade pip uv
uv sync --frozen
- uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
build:
name: build + twine check
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false # no job pushes; don't leave the token in .git/config
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.12"
cache: pip
- name: Build wheel + sdist
run: |
python -m pip install build twine
python -m build
- name: Validate metadata
run: twine check dist/*
audit:
name: pip-audit (dependency CVEs)
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false # no job pushes; don't leave the token in .git/config
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.12"
cache: pip
- name: Audit runtime dependencies for known CVEs
run: |
# Keep build tooling current first: pip-audit scans the whole environment,
# so a pip/setuptools advisory that a one-line upgrade fixes would otherwise
# fail the gate on something that isn't one of our runtime dependencies.
python -m pip install --upgrade pip setuptools
python -m pip install -e . pip-audit
# Append `--ignore-vuln <ID>` to accept an unfixable transitive advisory.
python -m pip_audit