From 90f8220907aa6ba70ff30108c3470f9334edb8f9 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Wed, 17 Jun 2026 00:28:20 +0000 Subject: [PATCH] refactor(any-gate): widen per-file Any headroom to ~50% Bump HEADROOM_RATIO from 0.25 to 0.50 so a grandfathered file may hold up to baseline + ceil(baseline * 0.50) Any-tainted values before the gate trips, and update the docs and tests to match. The per-file baselines are raw counts, so only the ceiling moves. --- CLAUDE.md | 2 +- CONTRIBUTING.md | 2 +- Makefile | 2 +- scripts/check_any_discipline.py | 8 +++---- .../test_litellm/test_check_any_discipline.py | 22 +++++++++---------- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 21d052a55ad..865ec89fab4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -40,7 +40,7 @@ When you fix violations gated by `ruff-strict-budget.json`, `mypy-code-budget.js If you're trying to create a new function that relies on untyped stuff, instead of adding more Any's and bringing it closer to the max, just validate it in the caller with Pydantic (a model or `TypeAdapter` that returns the typed thing or raises will do) and then pass the now typed variable in -The Any-discipline gate (`make lint-any`, also a CI job) grandfathers each file under `litellm/` at its current count of values typed `Any` (including the `X | Any`) in `any-discipline-budget.json` and gives it ~25% headroom; a changed file fails once it exceeds that ceiling, so a brand new file must be `Any`-free while a legacy file can absorb a little drift before it has to be cleaned. Ideally `# any-ok: ` is never used; treat it as a last resort for a genuine typed/untyped boundary that Pydantic truly can't model +The Any-discipline gate (`make lint-any`, also a CI job) grandfathers each file under `litellm/` at its current count of values typed `Any` (including the `X | Any`) in `any-discipline-budget.json` and gives it ~50% headroom; a changed file fails once it exceeds that ceiling, so a brand new file must be `Any`-free while a legacy file can absorb a little drift before it has to be cleaned. Ideally `# any-ok: ` is never used; treat it as a last resort for a genuine typed/untyped boundary that Pydantic truly can't model Ask to commit and push your work when you're done (or if you're confident that your code is good and works, just do it) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e223feecf35..0189d13a04a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -155,7 +155,7 @@ Individual linting commands: make format-check # Check Black formatting make lint-ruff # Run Ruff linting make lint-mypy # Run MyPy type checking -make lint-any # Fail if a changed file exceeds its Any-typed value budget (baseline + ~25%) +make lint-any # Fail if a changed file exceeds its Any-typed value budget (baseline + ~50%) make check-circular-imports # Check for circular imports make check-import-safety # Check import safety ``` diff --git a/Makefile b/Makefile index 808d2d8b63d..c4f90bc06d1 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,7 @@ help: @echo " make lint-ruff-budget - Gate the codebase total of each strict ruff rule against its ceiling" @echo " make lint-ruff-budget-update - Re-capture per-rule baselines in ruff-strict-budget.json (ratchet)" @echo " make lint-budget-update - Re-capture all three ratchet budgets (ruff + mypy + basedpyright)" - @echo " make lint-any - Fail if a changed file exceeds its Any-typed value budget (baseline + ~25%)" + @echo " make lint-any - Fail if a changed file exceeds its Any-typed value budget (baseline + ~50%)" @echo " make lint-any-budget-update - Re-capture the per-file Any baseline (ratchet)" @echo " make check-circular-imports - Check for circular imports" @echo " make check-import-safety - Check import safety" diff --git a/scripts/check_any_discipline.py b/scripts/check_any_discipline.py index 3b3524934a3..4d1552d6014 100644 --- a/scripts/check_any_discipline.py +++ b/scripts/check_any_discipline.py @@ -13,8 +13,8 @@ litellm already contains a large amount of pre-existing `Any` (a single legacy file can have >100 findings), and a whole-tree scan would have to re-export types for litellm's entire import closure on every run (~2 min, ~3 GB). So the gate grandfathers each file at its current count in `any-discipline-budget.json` and -gives it ~25% headroom: a file fails once its `Any`-tainted values exceed -`baseline + ceil(baseline * 0.25)`. A file with no entry is budgeted at zero, so +gives it ~50% headroom: a file fails once its `Any`-tainted values exceed +`baseline + ceil(baseline * 0.50)`. A file with no entry is budgeted at zero, so a brand new file must be `Any`-free, and a legacy file may absorb a little drift before it has to be cleaned. Cold files the change doesn't touch are left to the ratchet (run `--update`); like the mypy/basedpyright budgets, the gate only @@ -113,7 +113,7 @@ PY_TAG = f"{sys.version_info.major}.{sys.version_info.minor}" DEFAULT_BASE = "origin/litellm_internal_staging" # Headroom each grandfathered file gets over its baseline before the gate trips. -HEADROOM_RATIO = 0.25 +HEADROOM_RATIO = 0.50 # `--update` re-counts every file. Type-checking each module also preserves its # AST and exports its types, so we re-check in bounded batches against a warmed @@ -161,7 +161,7 @@ class Violation(NamedTuple): def cap_for(baseline: int) -> int: - """The most `Any`-tainted values a file may hold: baseline plus ~25%.""" + """The most `Any`-tainted values a file may hold: baseline plus ~50%.""" return baseline + math.ceil(baseline * HEADROOM_RATIO) diff --git a/tests/test_litellm/test_check_any_discipline.py b/tests/test_litellm/test_check_any_discipline.py index b63823c1fa3..a8523104400 100644 --- a/tests/test_litellm/test_check_any_discipline.py +++ b/tests/test_litellm/test_check_any_discipline.py @@ -16,7 +16,7 @@ def _v(path="litellm/x.py", line=10, code="LIT009"): # --------------------------------------------------------------------------- # -# cap_for: ~25% headroom over the grandfathered baseline +# cap_for: ~50% headroom over the grandfathered baseline # --------------------------------------------------------------------------- # @@ -25,16 +25,16 @@ def test_zero_baseline_gets_no_headroom(): assert mod.cap_for(0) == 0 -def test_headroom_is_a_quarter_of_the_baseline(): - assert mod.cap_for(100) == 125 - assert mod.cap_for(40) == 50 +def test_headroom_is_half_of_the_baseline(): + assert mod.cap_for(100) == 150 + assert mod.cap_for(40) == 60 def test_headroom_rounds_up_so_small_baselines_get_at_least_one_slot(): - # ceil(1 * 0.25) == 1, ceil(4 * 0.25) == 1, ceil(8 * 0.25) == 2. + # ceil(1 * 0.5) == 1, ceil(3 * 0.5) == 2, ceil(5 * 0.5) == 3. assert mod.cap_for(1) == 2 - assert mod.cap_for(4) == 5 - assert mod.cap_for(8) == 10 + assert mod.cap_for(3) == 5 + assert mod.cap_for(5) == 8 # --------------------------------------------------------------------------- # @@ -56,17 +56,17 @@ def test_counts_only_lit009_per_file(): # --------------------------------------------------------------------------- # -# budget_breaches: a file fails only above baseline + ~25% +# budget_breaches: a file fails only above baseline + ~50% # --------------------------------------------------------------------------- # def test_file_at_its_ceiling_does_not_breach(): - assert mod.budget_breaches({"litellm/a.py": 125}, {"litellm/a.py": 100}) == [] + assert mod.budget_breaches({"litellm/a.py": 150}, {"litellm/a.py": 100}) == [] def test_file_one_over_its_ceiling_breaches(): - assert mod.budget_breaches({"litellm/a.py": 126}, {"litellm/a.py": 100}) == [ - ("litellm/a.py", 126, 125) + assert mod.budget_breaches({"litellm/a.py": 151}, {"litellm/a.py": 100}) == [ + ("litellm/a.py", 151, 150) ]