Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
4e26c9a
Fix ruff TRY, EM and BLE findings in the FastAPI library
anth-volk Oct 13, 2026
d891ddc
Fix ruff TRY and EM findings in the simulation contract library
anth-volk Oct 13, 2026
14e3c46
Fix ruff TRY, EM and BLE findings in the simulation observability lib…
anth-volk Oct 13, 2026
7f63ffa
Fix ruff EM findings in the Stage 12 persistence library
anth-volk Oct 13, 2026
561e61e
Fix ruff TRY, EM and BLE findings in the simulation entrypoint
anth-volk Oct 13, 2026
bc1f5a1
Fix ruff TRY, EM and BLE findings in the simulation gateway
anth-volk Oct 13, 2026
eeb000b
Fix ruff TRY, EM and BLE findings in the simulation executor
anth-volk Oct 13, 2026
88bc57f
Fix ruff EM findings in the integration tests and repo scripts
anth-volk Oct 13, 2026
d4126d8
Enable ruff group 5 (exceptions and messages)
anth-volk Oct 13, 2026
59848a5
Give every BLE001 suppression an inline reason
anth-volk Oct 13, 2026
284612c
Name hoisted SPM error codes error_code, not msg
anth-volk Oct 13, 2026
4fd5f7c
Register TRY, EM and BLE in the ruff-lint rule
anth-volk Oct 13, 2026
826b855
Name hoisted parity mismatch codes error_code, not msg
anth-volk Oct 13, 2026
f638fe8
Fix the group 5 changelog line's YAML and wording
anth-volk Oct 13, 2026
9117b15
Build the exception messages #743 and #744 added before raising
anth-volk Oct 14, 2026
3c8abf8
Build the exception messages #789's tests raise before raising
anth-volk Oct 14, 2026
9cfbf21
Build the exception messages #795 added before raising
anth-volk Oct 14, 2026
8e68894
Build the exception messages #791's tests raise before raising
anth-volk Oct 14, 2026
4c42815
Write the BLE001 suppressions #791 added with an inline reason
anth-volk Oct 14, 2026
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
63 changes: 38 additions & 25 deletions .github/scripts/update_policyengine_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,11 @@ def current_version(pyproject: Path, package: str) -> str:
if isinstance(dependency, str) and dependency.startswith(prefix)
]
if len(matches) != 1:
raise SystemExit(
msg = (
f"Expected one {package}[models] requirement in project.dependencies; "
f"found {matches!r}"
)
raise SystemExit(msg)
return matches[0]


Expand Down Expand Up @@ -86,9 +87,8 @@ def update_requirements(
and dependency.startswith(f"{package}[models]==")
]
if matches != [old_requirement]:
raise SystemExit(
f"Expected {old_requirement} in {location}; found {matches!r}"
)
msg = f"Expected {old_requirement} in {location}; found {matches!r}"
raise SystemExit(msg)

redundant_wrapper_requirements = [
dependency
Expand All @@ -98,10 +98,11 @@ def update_requirements(
and _requirement_name(dependency) == package
]
if redundant_wrapper_requirements:
raise SystemExit(
msg = (
f"Expected only {old_requirement} for {package} in {location}; "
f"found {redundant_wrapper_requirements!r}"
)
raise SystemExit(msg)

direct_component_requirements = [
dependency
Expand All @@ -110,18 +111,20 @@ def update_requirements(
and _requirement_name(dependency) in COMPONENT_PACKAGES
]
if direct_component_requirements:
raise SystemExit(
msg = (
f"Expected {location} to obtain component packages from "
f"{package}[models]; found {direct_component_requirements!r}"
)
raise SystemExit(msg)

old_pin = f'"{old_requirement}"'
new_pin = f'"{new_requirement}"'
if text.count(old_pin) != len(requirements):
raise SystemExit(
msg = (
f"Expected {old_pin} {len(requirements)} times in {pyproject}; "
f"found {text.count(old_pin)}"
)
raise SystemExit(msg)

with tempfile.NamedTemporaryFile(
mode="w",
Expand All @@ -139,7 +142,8 @@ def find_issue(title: str) -> None:
"""Print the lowest matching issue number from paginated GitHub output."""
pages = _json_from_stdin()
if not isinstance(pages, list):
raise SystemExit("GitHub issue response must be a list of pages")
msg = "GitHub issue response must be a list of pages"
raise SystemExit(msg)
matches = sorted(
item["number"]
for page in pages
Expand All @@ -158,31 +162,35 @@ def verify_issue(expected_number: int, expected_title: str) -> None:
"""Validate the issue selected for an automated update."""
issue = _mapping(_json_from_stdin())
if issue.get("number") != expected_number:
raise SystemExit("Resolved update issue has an unexpected number")
msg = "Resolved update issue has an unexpected number"
raise SystemExit(msg)
if issue.get("state") != "OPEN":
raise SystemExit("Resolved update issue is not open")
msg = "Resolved update issue is not open"
raise SystemExit(msg)
if issue.get("title") != expected_title:
raise SystemExit("Resolved update issue has an unexpected title")
msg = "Resolved update issue has an unexpected title"
raise SystemExit(msg)


def verify_pr(expected_repository: str) -> None:
"""Validate that an automated pull request is a canonical draft."""
pull_request = _mapping(_json_from_stdin())
if pull_request.get("isDraft") is not True:
raise SystemExit("Automated policyengine update PR is not a draft")
msg = "Automated policyengine update PR is not a draft"
raise SystemExit(msg)
head_repository = _mapping(pull_request.get("headRepository"))
if head_repository.get("nameWithOwner") != expected_repository:
raise SystemExit(
"Automated policyengine update PR is not from the canonical repository"
)
msg = "Automated policyengine update PR is not from the canonical repository"
raise SystemExit(msg)


def latest_version(package: str) -> None:
"""Print a package version from the PyPI JSON response."""
payload = _mapping(_json_from_stdin())
version = _mapping(payload.get("info")).get("version")
if not isinstance(version, str) or not version:
raise SystemExit(f"PyPI response has no latest version for {package}")
msg = f"PyPI response has no latest version for {package}"
raise SystemExit(msg)
print(version)


Expand All @@ -197,7 +205,8 @@ def bundle_versions() -> None:
def package_version(name: str) -> str:
version = _mapping(packages.get(name)).get("version")
if not isinstance(version, str) or not version:
raise SystemExit(f"Bundle has no version for {name}")
msg = f"Bundle has no version for {name}"
raise SystemExit(msg)
return version

def data_release_version(country: str) -> str:
Expand All @@ -206,7 +215,8 @@ def data_release_version(country: str) -> str:
"version"
)
if not isinstance(version, str) or not version:
raise SystemExit(f"Bundle has no data release version for {country}")
msg = f"Bundle has no data release version for {country}"
raise SystemExit(msg)
return version

def data_package_version(country: str) -> str:
Expand All @@ -225,7 +235,8 @@ def dataset_sha256(country: str) -> str:
value = reference.get("sha256") or ""
digest = str(value).strip().lower()
if digest and not re.fullmatch(r"[0-9a-f]{64}", digest):
raise SystemExit(f"Bundle records a malformed {country} dataset SHA-256")
msg = f"Bundle records a malformed {country} dataset SHA-256"
raise SystemExit(msg)
return digest

def certificate_sha256(country: str) -> str:
Expand Down Expand Up @@ -295,10 +306,11 @@ def verify_dispatch() -> None:
if payload.get("version") and payload["version"] != installed.get(
"policyengine_version"
):
raise SystemExit(
msg = (
f"Dispatch is for policyengine {payload['version']}, but "
f"{installed.get('policyengine_version')} is installed."
)
raise SystemExit(msg)
expected = _mapping(_mapping(payload.get("bundle")).get("uk"))
mismatches = []
compared = 0
Expand Down Expand Up @@ -329,17 +341,18 @@ def verify_dispatch() -> None:
verdict = qualification.get("verdict") or ""
receipt_sha256 = qualification.get("receipt_sha256") or ""
if not isinstance(verdict, str) or not re.fullmatch(r"[a-z_]*", verdict):
raise SystemExit("The release dispatch carries a malformed verdict")
msg = "The release dispatch carries a malformed verdict"
raise SystemExit(msg)
if not isinstance(receipt_sha256, str) or not re.fullmatch(
r"(?:[0-9a-f]{64})?", receipt_sha256
):
raise SystemExit("The release dispatch carries a malformed receipt digest")
msg = "The release dispatch carries a malformed receipt digest"
raise SystemExit(msg)
if qualification.get("promotable") is False or (
verdict and verdict not in PROMOTABLE_VERDICTS
):
raise SystemExit(
f"The release dispatch says the UK pair was not promotable ({verdict})"
)
msg = f"The release dispatch says the UK pair was not promotable ({verdict})"
raise SystemExit(msg)
print(f"dispatch_verified={'true' if compared else 'false'}")
print(f"uk_qualification_verdict={verdict}")
print(f"uk_qualification_receipt_sha256={receipt_sha256}")
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ To add or change a rule, read `docs/engineering/skills/rules-registry.md`.
- `sim.quality.every-project-tested`: Every project and library with unit tests runs them in the PR test matrix. (doc-only, not checked: follow it yourself)
- `sim.quality.ruff-format-src`: Source code under projects/*/src and libs/*/src is formatted with ruff format. (blocking: ruff format)
- `sim.quality.ruff-format-tests`: Test code is formatted with ruff format too; CI currently checks only src directories. (doc-only, not checked: follow it yourself)
- `sim.quality.ruff-lint`: All Python code passes ruff check with every rule family selected in ruff.toml, with no ratchet; families are added one themed group at a time until the selection is ALL. (blocking: ruff check (ANN, D, E4, E7, E9, F, FLY, FURB, I, PIE, PYI, RET, RSE, TC, UP, W))
- `sim.quality.ruff-lint`: All Python code passes ruff check with every rule family selected in ruff.toml, with no ratchet; families are added one themed group at a time until the selection is ALL. (blocking: ruff check (ANN, BLE, D, E4, E7, E9, EM, F, FLY, FURB, I, PIE, PYI, RET, RSE, TC, TRY, UP, W))
- `sim.quality.runtime-evaluated-models-resolve`: Every Pydantic model is runtime-evaluated for ruff: one of its bases, read through the imports of the model's own module, is listed in ruff.toml's runtime-evaluated-base-classes or is a runtime-evaluated class in that same module; and every Pydantic model resolves its annotations once its package is imported. (blocking: pytest `projects/policyengine-simulation-entry/tests/test_runtime_evaluated_annotations.py`, `projects/policyengine-simulation-executor/tests/test_runtime_evaluated_annotations.py`)
- `sim.quality.tests-pass`: Each project's unit tests pass in its own locked environment. (blocking: CI job `test`)
- `sim.quality.tests-ruff-exemptions`: Test files may use pytest's bare assert and need no docstrings, annotations or named constants for magic numbers (S101, D1, ANN, PLR2004); every other ruff rule applies to tests. (blocking: ruff per-file ignore of ANN, D1, PLR2004, S101 for `**/tests/**`)
Expand Down
1 change: 1 addition & 0 deletions changelog_entry.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
- Made CLAUDE.md and the Copilot instructions thin pointers to AGENTS.md, required workflow logic longer than one line to live in tested and shellchecked scripts, and registered the tracking-issue rule
- Added a rules registry (rules/registry.toml) with a standard-library tool that validates it, checks every rule is enforced where it claims, and renders docs/generated/rules.md and the Rules block in AGENTS.md
changed:
- Enabled ruff's exception rules (TRY, EM, BLE); exception messages are built before the raise, raises inside try blocks moved into helpers, and every deliberate catch-all or traceback-free error log carries a reasoned suppression; message text is unchanged, and error types change only in an operator script's two type checks
- Enabled ruff's docstring rules (D) and documented every module, class and function outside tests; published contract models and routes now carry these descriptions in their JSON schema and OpenAPI, with no structural schema change
- Enabled ruff's annotation rules (ANN) and annotated every function outside tests; test files are exempt from ANN, missing docstrings, magic numbers and bare-assert rules
- Enabled ruff's typing-only import rules (TC) and moved annotation-only imports under TYPE_CHECKING, keeping runtime imports for Pydantic models and FastAPI endpoints
Expand Down
8 changes: 4 additions & 4 deletions docs/generated/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ flowchart LR
| `sim.quality.every-project-tested` | Every project and library with unit tests runs them in the PR test matrix. | mechanical | doc-only | no | nothing | — | — |
| `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 rule family selected in ruff.toml, with no ratchet; families are added one themed group at a time until the selection is ALL. | mechanical | enforced | yes | ruff check (ANN, D, E4, E7, E9, F, FLY, FURB, I, PIE, PYI, RET, RSE, TC, UP, W) | `pr.yml:lint` | — |
| `sim.quality.ruff-lint` | All Python code passes ruff check with every rule family selected in ruff.toml, with no ratchet; families are added one themed group at a time until the selection is ALL. | mechanical | enforced | yes | ruff check (ANN, BLE, D, E4, E7, E9, EM, F, FLY, FURB, I, PIE, PYI, RET, RSE, TC, TRY, UP, W) | `pr.yml:lint` | — |
| `sim.quality.runtime-evaluated-models-resolve` | Every Pydantic model is runtime-evaluated for ruff: one of its bases, read through the imports of the model's own module, is listed in ruff.toml's runtime-evaluated-base-classes or is a runtime-evaluated class in that same module; and every Pydantic model resolves its annotations once its package is imported. | mechanical | enforced | yes | pytest `projects/policyengine-simulation-entry/tests/test_runtime_evaluated_annotations.py`, `projects/policyengine-simulation-executor/tests/test_runtime_evaluated_annotations.py` | `pr.yml:test` | yes |
| `sim.quality.tests-pass` | Each project's unit tests pass in its own locked environment. | mechanical | enforced | yes | CI job `test` | `pr.yml:test` | — |
| `sim.quality.tests-ruff-exemptions` | Test files may use pytest's bare assert and need no docstrings, annotations or named constants for magic numbers (S101, D1, ANN, PLR2004); every other ruff rule applies to tests. | mechanical | enforced | yes | ruff per-file ignore of ANN, D1, PLR2004, S101 for `**/tests/**` | `pr.yml:lint` | — |
Expand All @@ -259,7 +259,7 @@ flowchart LR
j_CI_pr_yml_lint["CI pr.yml:lint"]
r_sim_quality_ruff_format_tests("sim.quality.ruff-format-tests (doc-only)")
r_sim_quality_ruff_lint("sim.quality.ruff-lint (enforced)")
e_ruff_check__ANN__D__E4__E7__E9__F__FLY__FURB__I__PIE__PYI__RET__RSE__TC__UP__W_["ruff check (ANN, D, E4, E7, E9, F, FLY, FURB, I, PIE, PYI, RET, RSE, TC, UP, W)"]
e_ruff_check__ANN__BLE__D__E4__E7__E9__EM__F__FLY__FURB__I__PIE__PYI__RET__RSE__TC__TRY__UP__W_["ruff check (ANN, BLE, D, E4, E7, E9, EM, F, FLY, FURB, I, PIE, PYI, RET, RSE, TC, TRY, UP, W)"]
r_sim_quality_runtime_evaluated_models_resolve("sim.quality.runtime-evaluated-models-resolve (enforced)")
e_pytest_projects_policyengine_simulation_entry_tests_test_runtime_evaluated_annotations_py__projects_policyengine_simulation_executor_tests_test_runtime_evaluated_annotations_py["pytest projects/policyengine-simulation-entry/tests/test_runtime_evaluated_annotations.py, projects/policyengine-simulation-executor/tests/test_runtime_evaluated_annotations.py"]
j_CI_pr_yml_test["CI pr.yml:test"]
Expand All @@ -271,8 +271,8 @@ flowchart LR
r_sim_quality_ruff_format_src --> e_ruff_format
e_ruff_format --> j_CI_pr_yml_lint
r_sim_quality_ruff_format_tests -.-> gap_unenforced
r_sim_quality_ruff_lint --> e_ruff_check__ANN__D__E4__E7__E9__F__FLY__FURB__I__PIE__PYI__RET__RSE__TC__UP__W_
e_ruff_check__ANN__D__E4__E7__E9__F__FLY__FURB__I__PIE__PYI__RET__RSE__TC__UP__W_ --> j_CI_pr_yml_lint
r_sim_quality_ruff_lint --> e_ruff_check__ANN__BLE__D__E4__E7__E9__EM__F__FLY__FURB__I__PIE__PYI__RET__RSE__TC__TRY__UP__W_
e_ruff_check__ANN__BLE__D__E4__E7__E9__EM__F__FLY__FURB__I__PIE__PYI__RET__RSE__TC__TRY__UP__W_ --> j_CI_pr_yml_lint
r_sim_quality_runtime_evaluated_models_resolve --> e_pytest_projects_policyengine_simulation_entry_tests_test_runtime_evaluated_annotations_py__projects_policyengine_simulation_executor_tests_test_runtime_evaluated_annotations_py
e_pytest_projects_policyengine_simulation_entry_tests_test_runtime_evaluated_annotations_py__projects_policyengine_simulation_executor_tests_test_runtime_evaluated_annotations_py --> j_CI_pr_yml_test
r_sim_quality_tests_pass --> e_CI_job_test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ def __call__(
"""
try:
return self._validate(token)
except HTTPException as err:
except HTTPException:
if self.auto_error:
LOG.info("Invalid auth token: Throwing 4XX error")
raise err
raise
LOG.info("Ignoring invalid auth token")
return None

Expand All @@ -63,10 +63,10 @@ def _validate(self, token: HTTPAuthorizationCredentials | None) -> dict[str, str
token.credentials
).key
except jwt.exceptions.PyJWKClientError as error:
LOG.error(f"Unable to get public key for JWT validation: {error}")
LOG.error(f"Unable to get public key for JWT validation: {error}") # noqa: TRY400 # callers control the token, so a traceback per bad token would let them flood the logs
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
except jwt.exceptions.DecodeError as error:
LOG.error(f"Unable to get public key for JWT validation: {error}")
LOG.error(f"Unable to get public key for JWT validation: {error}") # noqa: TRY400 # callers control the token, so a traceback per bad token would let them flood the logs
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)

try:
Expand All @@ -77,7 +77,7 @@ def _validate(self, token: HTTPAuthorizationCredentials | None) -> dict[str, str
audience=self.audience,
issuer=self.issuer,
)
except Exception as error:
except Exception as error: # noqa: BLE001 # any decode or claim-validation failure must become a 403, never a 500
LOG.info(f"Invalid bearer token: {error}")
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
return payload
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,8 @@ def parse_header_value_pairs(raw: str | None) -> dict[str, str]:
continue
key, separator, value = candidate.partition("=")
if not separator:
raise ValueError(
"Expected OTLP headers in key=value format separated by commas"
)
msg = "Expected OTLP headers in key=value format separated by commas"
raise ValueError(msg)
headers[key.strip()] = value.strip()

return headers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def _get_project_id_from_metadata() -> str | None:
req.add_header("Metadata-Flavor", "Google")
try:
return request.urlopen(req).read().decode()
except Exception as err:
except Exception as err: # noqa: BLE001 # best-effort probe: any failure falls back to GOOGLE_CLOUD_PROJECT
log.info(
f"Unable to get the google project id from a local metadata service: {err}"
)
Expand Down
6 changes: 4 additions & 2 deletions libs/policyengine-fastapi/tests/test_observability.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ def test_parse_header_value_pairs__raises_for_invalid_pair():
except ValueError as error:
assert "key=value" in str(error)
else:
raise AssertionError("Expected invalid OTLP header parsing to fail")
msg = "Expected invalid OTLP header parsing to fail"
raise AssertionError(msg)


def test_observability_config_disabled__returns_disabled_defaults():
Expand Down Expand Up @@ -120,7 +121,8 @@ def test_contract_models__reject_extra_fields():
except ValidationError as error:
assert "unexpected" in str(error)
else:
raise AssertionError("Expected extra field validation to fail")
msg = "Expected extra field validation to fail"
raise AssertionError(msg)


def test_noop_observability__accepts_calls_without_side_effects():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ def split_dataset_revision(dataset_uri: str) -> tuple[str, str | None]:
return dataset_uri, None
dataset_without_revision, revision = dataset_uri.rsplit("@", maxsplit=1)
if not dataset_without_revision or not revision:
raise ValueError(f"Invalid dataset revision reference: {dataset_uri}")
msg = f"Invalid dataset revision reference: {dataset_uri}"
raise ValueError(msg)
return dataset_without_revision, revision


Expand All @@ -39,11 +40,12 @@ def select_dataset_revision(
and requested_data_version is not None
and requested_revision != requested_data_version
):
raise ValueError(
msg = (
"Conflicting dataset revisions: "
f"data requests {requested_revision!r} but data_version is "
f"{requested_data_version!r}"
)
raise ValueError(msg)
return requested_revision or requested_data_version


Expand Down
Loading
Loading