From 445408cf91d29c6bcea6aeb0225f570c31e13e4a Mon Sep 17 00:00:00 2001 From: Anthony Volk <14987227+anth-volk@users.noreply.github.com> Date: Tue, 27 Oct 2026 18:15:00 +0400 Subject: [PATCH] Enforce the file-length rule and delete its empty baseline Every tracked Python file is now within its limit, so sim.quality.python-file-length becomes enforced: the empty baseline, rules/baselines/python-file-length.txt, is deleted, and the rule's [rule.downgrade] section, which an enforced rule can't keep, is removed. Merging this closes its tracking issue. The rule's tier is corrected from mechanical to metric, because it measures a number, a file's line count, against a threshold, which is what the registry's tiers call a metric rule. Its proof uses the metric fields: below_fixture is the manifest of files at their limit, above_fixture the manifest of files one line over it, and threshold is the 800-line source limit; both fixtures also cover the 1,500-line limit for test code, and the proof test is unchanged. docs/engineering/skills/file-length.md says there is no baseline or exception, and the rendered rules are regenerated. Fixes #921 Co-Authored-By: Claude Opus 5.5 --- changelog_entry.yaml | 1 + docs/engineering/skills/file-length.md | 15 ++++++--------- docs/generated/rules.md | 9 ++++----- rules/baselines/python-file-length.txt | 6 ------ rules/registry.toml | 17 +++++++---------- 5 files changed, 18 insertions(+), 30 deletions(-) delete mode 100644 rules/baselines/python-file-length.txt diff --git a/changelog_entry.yaml b/changelog_entry.yaml index caad276de..c044351cc 100644 --- a/changelog_entry.yaml +++ b/changelog_entry.yaml @@ -16,6 +16,7 @@ - Ran Stage 12 US national simulations as 20 region-group calls each, and built an independent, immutable Stage 12 cache of three annual US datasets and 60 current-law baselines before each deployment, publishing it only after a recorded determinism check and recording its release in the v2 version manifest under a new key, while the existing key keeps a copy that earlier readers accept - Made the budget refuse a household count outside its country's plausible range (above 200 million in the US or 60 million in the UK, negative or not finite), raising ImplausibleEntityCountError instead of publishing a count like #767's changed: + - Enforced the file-length rule sim.quality.python-file-length, now that every tracked Python file is within its limit: its empty baseline and its downgrade section are gone, and its tier is metric rather than mechanical, because it measures a number against a threshold, with the at-the-limit and one-over fixtures as its below and above proof - Moved the entrypoint's backend calls, response telemetry and comparison dispatch out of app.py (925 lines, now 583) into entry_runtime.py, which app.py re-exports and which still logs under the policyengine_simulation_entry.app logger; the routes stay in app.py, so FastAPI and ruff read their annotations as before; the 2,251-line deployment asset test file is split into seven test files and a support module named test_deployment_assets_support.py, the rules registry names each moved test by its new file, and its guard-test discovery now covers tests/test_deployment_assets*.py - Moved the worker's credential setup, request year and policy parsing, and region resolution out of simulation_runtime.py (1,111 lines, now 651) into gcp_credentials.py, request_policy.py and region_resolution.py; simulation_runtime.py keeps dataset selection and loading, simulation building, local-area breakdowns and the run itself, which tests replace by name there, and re-exports the moved names, so every caller still imports them from simulation_runtime; the shared helpers _parse_year, _normalise_policy, _compile_interval_policy, _runtime_policy (now wrapper_policy), _resolve_region, _resolve_dataset_reference and _require_local_area_breakdown are now public, and the credential messages still log under the simulation_runtime logger; the 3,048-line output builder test file is split into seven test files and a module of shared fakes, all named test_simulation_output_*.py so the weighted-frame rule still checks them - Split the artifact precompute (precompute.py, 931 lines) and the Stage 12 cache precompute (stage12_cache/precompute.py, 844 lines) each into planning, dataset-building and baseline modules, with the manifest and run_precompute left in the original module, which re-exports every public name, so the Modal precompute apps and their tests import from the same paths; the cache precompute's _scoped_planned_simulation and _baseline_identity are now public, and the worker validation imports scoped_planned_simulation from its new module; the 1,835-line precompute test file is split into four, and the cache precompute tests patch each name in the module whose code reads it diff --git a/docs/engineering/skills/file-length.md b/docs/engineering/skills/file-length.md index 2e1af580d..a4f2f6a2d 100644 --- a/docs/engineering/skills/file-length.md +++ b/docs/engineering/skills/file-length.md @@ -17,17 +17,14 @@ complexity, branch, statement and argument rules each measure one function, so a module can keep growing while every function in it passes. A long module usually holds several responsibilities, and is hard to review. -## The baseline +## No exceptions -While the rule is ratcheted, `rules/baselines/python-file-length.txt` lists the -files that were already over their limit when the rule was added. The guard -fails on a file over its limit that the baseline doesn't list, and on a listed -file that is now within its limit or no longer tracked. The pull request that -splits a listed file therefore removes its entry and lowers the rule's baseline -`count`. +The rule is enforced: every tracked Python file is within its limit, and there +is no baseline of tolerated files. A file that grows past its limit fails the +`quality-guards` job, so split it in the same pull request. -Never add an entry, or move a file out of the rule's scope: that weakens the -rule, which only a person may decide (see +Never add a baseline or an exception, or move a file out of the rule's scope: +that weakens the rule, which only a person may decide (see [the registry's baselines](rules-registry.md#baselines)). Split the file instead. diff --git a/docs/generated/rules.md b/docs/generated/rules.md index 917f0df80..b2f783267 100644 --- a/docs/generated/rules.md +++ b/docs/generated/rules.md @@ -8,8 +8,8 @@ Every rule that governs `policyengine-sim-api` is listed here with how it is enf | Tier | enforced | ratcheted | advisory | doc-only | Total | | --- | ---: | ---: | ---: | ---: | ---: | -| mechanical | 53 | 3 | 0 | 14 | 70 | -| metric | 0 | 0 | 0 | 0 | 0 | +| mechanical | 53 | 2 | 0 | 14 | 69 | +| metric | 1 | 0 | 0 | 0 | 1 | | judgment | 0 | 0 | 0 | 0 | 0 | **Blocking:** 56 of 70 rules. @@ -66,7 +66,6 @@ Every rule that governs `policyengine-sim-api` is listed here with how it is enf - `sim.ci.multiline-run-is-script`: 13 remaining in `rules/baselines/ci-multiline-run.txt` - `sim.microdata.weight-stripping-confined`: 0 remaining in `rules/baselines/sim-microdata-weight-stripping.txt` -- `sim.quality.python-file-length`: 0 remaining in `rules/baselines/python-file-length.txt` ### Exceptions and review dates @@ -312,7 +311,7 @@ flowchart LR | `sim.quality.pyright-executor` | The simulation executor has no Pyright errors, checked with the pinned Pyright version in the executor's own locked environment. | mechanical | enforced | yes | pyright in `projects/policyengine-simulation-executor` | `pr.yml:pyright` | yes | | `sim.quality.pyright-gateway` | The simulation gateway has no Pyright errors, checked with the pinned Pyright version in the gateway's own locked environment. | mechanical | enforced | yes | pyright in `projects/policyengine-simulation-gateway` | `pr.yml:pyright` | yes | | `sim.quality.pyright-libraries-and-entry` | The contract, observability, Stage 12 persistence and fastapi libraries and the simulation entry have no Pyright errors, checked with the pinned Pyright version in each package's own locked environment. | mechanical | enforced | yes | pyright in `libs/policyengine-simulation-contract`, `libs/policyengine-simulation-observability`, `libs/policyengine-stage12-persistence`, `libs/policyengine-fastapi`, `projects/policyengine-simulation-entry` | `pr.yml:pyright` | yes | -| `sim.quality.python-file-length` | No tracked Python file has more than 800 lines, or more than 1,500 lines if it is test code: a file under a tests or fixtures directory, or a conftest.py. | mechanical | ratcheted | yes | quality guard `file-length` | `pr.yml:quality-guards` | yes | +| `sim.quality.python-file-length` | No tracked Python file has more than 800 lines, or more than 1,500 lines if it is test code: a file under a tests or fixtures directory, or a conftest.py. | metric | enforced | yes | quality guard `file-length` | `pr.yml:quality-guards` | yes | | `sim.quality.ruff-format-src` | Source code under projects/*/src and libs/*/src is formatted with ruff format. | mechanical | enforced | yes | ruff format | `pr.yml:lint` | — | | `sim.quality.ruff-format-tests` | Test code is formatted with ruff format too; CI currently checks only src directories. | mechanical | doc-only | no | nothing | — | — | | `sim.quality.ruff-lint` | All Python code passes ruff check with every ruff rule selected (ALL), with no ratchet, except the ignores in ruff.toml: COM812 and ISC001, which conflict with ruff format, and D203 and D213, which conflict with the kept D211 and D212. | mechanical | enforced | yes | ruff check (ALL) | `pr.yml:lint` | — | @@ -345,7 +344,7 @@ flowchart LR e_pyright_in_projects_policyengine_simulation_gateway["pyright in projects/policyengine-simulation-gateway"] r_sim_quality_pyright_libraries_and_entry("sim.quality.pyright-libraries-and-entry (enforced)") e_pyright_in_libs_policyengine_simulation_contract__libs_policyengine_simulation_observability__libs_policyengine_stage12_persistence__libs_policyengine_fastapi__projects_policyengine_simulation_entry["pyright in libs/policyengine-simulation-contract, libs/policyengine-simulation-observability, libs/policyengine-stage12-persistence, libs/policyengine-fastapi, projects/policyengine-simulation-entry"] - r_sim_quality_python_file_length("sim.quality.python-file-length (ratcheted)") + r_sim_quality_python_file_length("sim.quality.python-file-length (enforced)") e_quality_guard_file_length["quality guard file-length"] r_sim_quality_ruff_format_src("sim.quality.ruff-format-src (enforced)") e_ruff_format["ruff format"] diff --git a/rules/baselines/python-file-length.txt b/rules/baselines/python-file-length.txt deleted file mode 100644 index 47a14f702..000000000 --- a/rules/baselines/python-file-length.txt +++ /dev/null @@ -1,6 +0,0 @@ -# Tolerated existing violations. This list may only shrink. -# -# Rule sim.quality.python-file-length. Each entry is the path of a tracked -# Python file that was already over its line limit (800 lines, or 1,500 for -# test code) when the rule was added. Remove an entry, and lower the rule's -# baseline count, in the pull request that splits the file. diff --git a/rules/registry.toml b/rules/registry.toml index d1ddfcf08..f298eb988 100644 --- a/rules/registry.toml +++ b/rules/registry.toml @@ -1178,8 +1178,8 @@ category = "quality" statement = "No tracked Python file has more than 800 lines, or more than 1,500 lines if it is test code: a file under a tests or fixtures directory, or a conftest.py." rationale = "docs/engineering/skills/file-length.md#the-rule" scope = ["**/*.py"] -tier = "mechanical" -status = "ratcheted" +tier = "metric" +status = "enforced" autofix = false [rule.enforcer] kind = "quality-guard" @@ -1188,19 +1188,16 @@ name = "file-length" workflow = ".github/workflows/pr.yml" job = "quality-guards" invocation = "run_quality_guards.py" -[rule.baseline] -file = "rules/baselines/python-file-length.txt" -count = 0 [rule.proof] -bad_fixture = "rules/fixtures/sim.quality.python-file-length/bad_files.toml" -good_fixture = "rules/fixtures/sim.quality.python-file-length/good_files.toml" +# Files at their limit (below) pass, and files one line over it (above) fail. +below_fixture = "rules/fixtures/sim.quality.python-file-length/good_files.toml" +above_fixture = "rules/fixtures/sim.quality.python-file-length/bad_files.toml" +# The source limit; both fixtures also cover the 1,500-line limit for test code. +threshold = 800 test = "scripts/rules/tests/test_file_length.py::TestFileLengthProof::test_rule_proof_python_file_length" workflow = ".github/workflows/pr.yml" job = "quality-guards" invocation = "unittest discover -s scripts/rules/tests" -[rule.downgrade] -reason = "Authorized by Anthony (anth-volk) on 2026-10-27: the rule starts ratcheted with the 14 files that were over their limit on main when it was added, and the pull requests stacked on #920 split them and shrink the baseline to empty." -issue = 921 # --- Dependency locking -----------------------------------------------------