Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 32 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,10 @@ jobs:
- name: Run zizmor (SARIF)
# zizmor exits non-zero on findings at/above its default (high) threshold,
# so this step fails the job — and thus checks-complete — on a finding.
run: uvx zizmor@latest --format=sarif .github/workflows/ > results.sarif
# Pinned (not @latest) so the gate is reproducible: a scanner release must
# never be able to flip a previously-green PR red on its own schedule.
# Bump deliberately, like every other pinned tool here.
run: uvx zizmor@1.28.0 --format=sarif .github/workflows/ > results.sarif
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload SARIF to GitHub code scanning
Expand All @@ -312,12 +315,39 @@ jobs:
sarif_file: results.sarif
category: zizmor

docs:
# Docs are a load-bearing product surface (the spec and the app-dev guides
# are the contract's public face), and doc/code drift has repeatedly lived
# under a green CI — mkdocstrings warnings and stale API examples are
# invisible to lint/test. A strict build turns that whole drift class into
# a failing PR check.
name: Docs build (strict)
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.13"
- name: Install uv
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
with:
version: ${{ env.UV_VERSION }}
enable-cache: true
cache-dependency-glob: uv.lock
- name: Install dependencies (locked)
run: uv sync --locked --all-extras --all-groups
- name: Build docs (strict)
run: uv run --no-sync mkdocs build --strict

checks-complete:
# Single aggregate status required by branch protection (ADR D8). Adding a
# new job above automatically tightens the gate via `needs`.
name: checks-complete
if: always()
needs: [lock-check, lint, test, lower-bounds, numpy-floor, build, actionlint, coverage, zizmor]
needs: [lock-check, lint, test, lower-bounds, numpy-floor, build, actionlint, coverage, zizmor, docs]
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
Expand Down
40 changes: 30 additions & 10 deletions .github/workflows/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ on:
- 'docs/**'
- 'mkdocs.yml'
- 'src/standard_asr/**'
# The deploy builds from the LOCKED environment, so its output depends on
# these too: a mkdocs-material/mkdocstrings bump in uv.lock (or a docs
# dependency change in pyproject.toml, or an edit to this workflow, e.g.
# the UV_VERSION pin) must redeploy, or the published site silently
# diverges from the environment the CI docs gate validated.
- 'uv.lock'
- 'pyproject.toml'
- '.github/workflows/mkdocs.yml'
workflow_dispatch:

# Least-privilege at the top level; the deploy job opts up to contents: write
Expand All @@ -19,6 +27,11 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
# Same pin as ci.yml so the deploy environment is byte-for-byte the one the
# PR-blocking "Docs build (strict)" gate validated. Bump alongside ci.yml.
UV_VERSION: "0.11.21"

jobs:
deploy:
name: Build & deploy docs
Expand All @@ -37,14 +50,21 @@ jobs:
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: 3.x
- run: echo "cache_id=$(date --utc '+%V')" >> "$GITHUB_ENV"
- uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
python-version: "3.13"
# Deploy from the SAME locked environment the CI docs gate builds with
# (uv.lock, all extras/groups, Python 3.13). Previously this job floated
# `pip install mkdocs-material mkdocstrings` on Python 3.x, so a green PR
# gate did not guarantee a green (or identically rendered) deploy —
# dependency drift could change the site or block publishing.
- name: Install uv
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
with:
key: mkdocs-material-${{ env.cache_id }}
path: ~/.cache
restore-keys: |
mkdocs-material-
- run: pip install mkdocs-material "mkdocstrings[python]>=0.29"
- run: pip install -e .
- run: mkdocs gh-deploy --force
version: ${{ env.UV_VERSION }}
enable-cache: true
cache-dependency-glob: uv.lock
- name: Install dependencies (locked)
run: uv sync --locked --all-extras --all-groups
# --strict mirrors the CI "Docs build (strict)" PR gate: main should
# always be strict-clean, so this is a backstop that refuses to publish a
# site with doc/code drift rather than publishing it with warnings.
- run: uv run --no-sync mkdocs gh-deploy --force --strict
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Standard ASR is a **Python library that defines and enforces a universal interfa
- **Explicit > implicit.** Silent wrong results are the cardinal sin. When in doubt, fail loudly or emit a structured diagnostic — never silently degrade. When DX convenience and explicitness conflict, **correctness wins** (a loud error the developer can fix beats a silent wrong transcript).
- **Standard-library rigor.** This is infrastructure others build on for 10 years. Types complete, boundaries sharp, error paths explicit, no implicit behavior.
- **Security by default.** Credentials use `SecretStr`. URLs validated (HTTPS, no SSRF). Unsafe options require explicit opt-in.
- **Trust model: plugins are trusted code. The security layer defends against ACCIDENTS, not adversaries.** An installed engine plugin runs arbitrary in-process code — it can already read `os.environ` and open sockets, so no in-library boundary can contain a malicious plugin, and we do not build one. What we DO defend against is the honest mistake: a mis-pasted credential echoed back by pydantic's `input_value=...`, a secret riding inside an exception message into a log or a wire response. The whole defense is three cheap rules — scrub validation-error echoes, wrap credentials in `SecretStr`, never format a raw exception chain into operator logs or client responses. **Hard budget:** no proofs about third-party code, no introspection of pydantic/CPython internals, no machinery against hostile metaclasses / forged markings / subverted C slots — those attackers don't need our log path. A defense that needs its own review rounds to stay correct is a defect source, not a defense. Reviewers: do not file findings that assume an adversarial plugin author; they are out of scope by this definition.

## Rules

Expand Down
Loading