diff --git a/changelog.d/file-length-enforce.changed.md b/changelog.d/file-length-enforce.changed.md new file mode 100644 index 000000000..5ab871fa5 --- /dev/null +++ b/changelog.d/file-length-enforce.changed.md @@ -0,0 +1 @@ +Enforced the file-length rule now that no Python file is over its limit, deleted its empty baseline, and registered it in the metric tier, the tier for a number crossing a threshold. diff --git a/docs/engineering/skills/file-length.md b/docs/engineering/skills/file-length.md index f7bdab303..d8b242e43 100644 --- a/docs/engineering/skills/file-length.md +++ b/docs/engineering/skills/file-length.md @@ -47,12 +47,8 @@ where that is natural, and keep every one under the limit. no longer reaches that call, and the test silently stops using its fake. Patch the module whose code calls the name. -## The baseline - -The rule is ratcheted: `rules/baselines/file-length.txt` lists the files that -were over their limit when the rule was added, and the guard tolerates only -those. It fails on an oversized file the baseline doesn't list, and on a listed -file that is now within its limit or no longer exists, so the list only -shrinks. When a split brings a file under its limit, remove its entry and lower -the rule's `baseline.count` in the same pull request. Never add an entry: split -the file instead. +## Enforcement + +The rule is enforced, with no baseline of tolerated files: the guard fails on +any measured file over its limit. When a change would take a file over, split +the file by responsibility, as above, in the same pull request. diff --git a/docs/generated/rules.md b/docs/generated/rules.md index 1d52745a2..5acfc789d 100644 --- a/docs/generated/rules.md +++ b/docs/generated/rules.md @@ -8,8 +8,8 @@ Every rule that governs `policyengine-api` is listed here with how it is enforce | Tier | enforced | ratcheted | advisory | doc-only | Total | | --- | ---: | ---: | ---: | ---: | ---: | -| mechanical | 59 | 1 | 1 | 13 | 74 | -| metric | 0 | 0 | 0 | 0 | 0 | +| mechanical | 59 | 0 | 1 | 13 | 73 | +| metric | 1 | 0 | 0 | 0 | 1 | | judgment | 0 | 0 | 0 | 1 | 1 | **Blocking:** 60 of 75 rules. @@ -66,7 +66,7 @@ Every rule that governs `policyengine-api` is listed here with how it is enforce ### Ratcheted (existing violations still tolerated) -- `api.quality.file-length`: 0 remaining in `rules/baselines/file-length.txt` +- None. ### Exceptions and review dates @@ -333,7 +333,7 @@ flowchart LR | `api.quality.command-line-modules-ruff-exemptions` | Application modules that also run as command-line programs (if __name__ == "__main__") may print their output (T201); every other ruff rule applies to them. | mechanical | enforced | yes | ruff per-file ignore of T201 for `policyengine_api/{data/v2/catalog/initialization,data/v2/household_migration_qualification,data/v2/policy_migration_qualification,data/v2/privilege_reconciliation,data/v2/user_association_migration_qualification,worker_spm_release}.py` | `pr.yml:lint` | — | | `api.quality.command-line-scripts-ruff-exemptions` | Command-line scripts under scripts/ may print their output, run other programs, fetch fixed URLs, stamp today's date and pick random samples (T201, S603, S607, S310, DTZ011, S311); every other ruff rule applies to them. | mechanical | enforced | yes | ruff per-file ignore of DTZ011, S310, S311, S603, S607, T201 for `scripts/**` | `pr.yml:lint` | — | | `api.quality.environment-variables-load` | Required environment variables and credentials load in a deployed-like environment. | mechanical | enforced | yes | CI job `test_env_vars` | `pr.yml:test_env_vars` | — | -| `api.quality.file-length` | A tracked Python file has at most 800 physical lines, or at most 1,500 if it is test code (a file under a tests or fixtures directory, or a conftest.py); a longer file is split by responsibility into modules, and keeps its public import path. | mechanical | ratcheted | yes | quality guard `file-length` | `pr.yml:quality-guards` | yes | +| `api.quality.file-length` | A tracked Python file has at most 800 physical lines, or at most 1,500 if it is test code (a file under a tests or fixtures directory, or a conftest.py); a longer file is split by responsibility into modules, and keeps its public import path. | metric | enforced | yes | quality guard `file-length` | `pr.yml:quality-guards` | yes | | `api.quality.mypy-v2` | Shared query models, FastAPI dependencies, v2 routes and services, and the user sign-in and account-deletion libraries pass mypy with untyped definitions disallowed. | mechanical | enforced | yes | mypy | `pr.yml:typecheck-v2` | — | | `api.quality.ruff-format` | All Python files are formatted with ruff format. | mechanical | enforced | yes | ruff format | `pr.yml:lint` | — | | `api.quality.ruff-lint` | All Python code passes ruff check with every ruff rule selected (ALL), with no ratchet, except the ignores in pyproject.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` | — | @@ -356,7 +356,7 @@ flowchart LR r_api_quality_environment_variables_load("api.quality.environment-variables-load (enforced)") e_CI_job_test_env_vars["CI job test_env_vars"] j_CI_pr_yml_test_env_vars["CI pr.yml:test_env_vars"] - r_api_quality_file_length("api.quality.file-length (ratcheted)") + r_api_quality_file_length("api.quality.file-length (enforced)") e_quality_guard_file_length["quality guard file-length"] j_CI_pr_yml_quality_guards["CI pr.yml:quality-guards"] r_api_quality_mypy_v2("api.quality.mypy-v2 (enforced)") diff --git a/rules/baselines/file-length.txt b/rules/baselines/file-length.txt deleted file mode 100644 index 3eb243549..000000000 --- a/rules/baselines/file-length.txt +++ /dev/null @@ -1,6 +0,0 @@ -# Python files tolerated over their api.quality.file-length limit (800 lines, -# or 1,500 for test code). This list may only shrink: split a file, then remove -# it here and lower the rule's baseline count in rules/registry.toml. -# -# Anthony Volk authorized this starting baseline on 2026-10-27. When it is -# empty, #4053 makes the rule enforced and deletes this file. diff --git a/rules/registry.toml b/rules/registry.toml index d2a50d7a6..a10ccd0ba 100644 --- a/rules/registry.toml +++ b/rules/registry.toml @@ -1096,10 +1096,8 @@ category = "quality" statement = "A tracked Python file has at most 800 physical lines, or at most 1,500 if it is test code (a file under a tests or fixtures directory, or a conftest.py); a longer file is split by responsibility into modules, and keeps its public import path." rationale = "docs/engineering/skills/file-length.md" scope = ["**/*.py"] -tier = "mechanical" -# Ratcheted with the files over their limit on 2026-10-27, as Anthony Volk -# decided that day; #4053 enforces the rule once the baseline is empty. -status = "ratcheted" +tier = "metric" +status = "enforced" autofix = false [rule.enforcer] kind = "quality-guard" @@ -1108,12 +1106,10 @@ name = "file-length" workflow = ".github/workflows/pr.yml" job = "quality-guards" invocation = "run_quality_guards.py" -[rule.baseline] -file = "rules/baselines/file-length.txt" -count = 0 [rule.proof] -bad_fixture = "rules/fixtures/api.quality.file-length/bad_tree.toml" -good_fixture = "rules/fixtures/api.quality.file-length/good_tree.toml" +below_fixture = "rules/fixtures/api.quality.file-length/good_tree.toml" +above_fixture = "rules/fixtures/api.quality.file-length/bad_tree.toml" +threshold = 800 test = "scripts/rules/tests/test_file_length.py::TestFileLengthProof::test_rule_proof_file_length" workflow = ".github/workflows/pr.yml" job = "quality-guards"