Type Checking
Spectral’s primary Python type checker is ty (Astral) running in strict-max mode (rules.all = "error"). Ruff’s ANN rule family backfills the missing-annotation coverage; mypy stays as an informational warning step in the pre-push runner during the transition window. Decision lineage in ADR-051.
Why ty
Section titled “Why ty”- Astral toolchain alignment. Same vendor as
uvandruff; one Python tool family, one upgrade cadence. - Speed. Rust-implemented; faster than mypy on the same source tree.
- Strict-max coverage.
rules.all = "error"matches the discipline the dev-tooling stack wanted from mypy.
Configuration
Section titled “Configuration”pyproject.toml:
[tool.ty.environment]python-version = "3.14"
[tool.ty.src]include = [ "src", "apps/api/src", "apps/workers/src",]
[tool.ty.rules]all = "error"Source roots cover the library + the deployable apps. A passing uv run ty check returns zero diagnostics — anything more than zero is a CI failure.
Pre-push tier
Section titled “Pre-push tier”Pre-push runs uv run ty check as the canonical type-check gate. Pre-commit (tier 1) stays fast (< 3 s) per ADR-012 — full ty runs at pre-push (tier 2) and in CI; tier 1 catches the missing-annotation family via ruff ANN.
CI (.github/workflows/ci.yml) invokes uv run ty check in the architecture job.
Ruff ANN family
Section titled “Ruff ANN family”Backfills the missing-annotation coverage that mypy --strict provided.
- All
ANNrules enabled ANN401(noAnyin public function signatures) is enforced — catches the worst missing-annotation class
Runs in ruff check at every tier (pre-commit, pre-push, CI).
Mypy as informational-transitional
Section titled “Mypy as informational-transitional”tools/dev/precheck.sh runs mypy as a non-blocking step that surfaces diagnostic classes ty does not yet cover. Mypy results print as warnings; they do not fail the precheck gate. Pre-commit and CI gates do not invoke mypy.
Removal trigger: when ty’s diagnostic coverage matches mypy’s for our codebase (verified by running both gates on a representative branch and observing parity), the precheck.sh mypy step is removed and the dev dependency is dropped.
Tripwires
Section titled “Tripwires”tests/_tooling/test_ty_tripwires.py plus fixtures in tests/_tooling/fixtures/ guard against ty misconfiguration:
ty_sanity.py— minimal annotated module that ty must acceptpydantic_ty_sanity.py— pydantic-specific cases verifyingdataclass_transformhandling (unknown kwargs + wrong field types are caught)
If a future ty upgrade silently regresses rules.all = "error" or breaks pydantic field-typing detection, the tripwires fail loudly.
Local commands
Section titled “Local commands”| Command | Purpose |
|---|---|
uv run ty check | Full type check (matches CI) |
uv run ruff check | Includes ANN family backfill |
bash tools/dev/precheck.sh | Pre-push gate (ruff + ty + arch + migrations + tests; mypy as informational warning) |