sarj-python-lint 0.77.1__tar.gz → 0.78.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/PKG-INFO +1 -1
  2. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/pyproject.toml +1 -1
  3. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rule_base.py +1 -1
  4. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/created_at_order_requires_tiebreaker.py +1 -1
  5. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/get_delegates_to_get_many.py +1 -1
  6. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/negative_only_http_status_assertion.py +1 -1
  7. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/no_hidden_constructor_fallback.py +1 -1
  8. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/no_unique_violation_message_match.py +1 -1
  9. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/no_unnecessary_docstring.py +1 -1
  10. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/no_vague_suppression_description.py +1 -1
  11. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/prefer_immutable_module_constant.py +1 -1
  12. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/prefer_match_type_dispatch.py +7 -9
  13. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/prefer_non_nullable_collection.py +1 -1
  14. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/prefer_one_for_required_row.py +1 -1
  15. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/preserve_declared_nominal_id.py +1 -1
  16. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/preserve_enum_types.py +1 -1
  17. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/production_derived_test_cases.py +1 -1
  18. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/repeated_static_call_cases.py +1 -1
  19. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/require_pydantic_for_external_json.py +1 -1
  20. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/require_pydantic_ordinal_lower_bound.py +1 -1
  21. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/require_validated_row_factory.py +1 -1
  22. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/sql_requires_injected_pool_owner.py +1 -1
  23. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/.gitignore +0 -0
  24. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/LICENSE +0 -0
  25. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/README.md +0 -0
  26. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/__init__.py +0 -0
  27. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/__main__.py +0 -0
  28. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/_analysis_session.py +0 -0
  29. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/_filesystem.py +0 -0
  30. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/_ratchet_cli.py +0 -0
  31. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/_secret_names.py +0 -0
  32. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/_version.py +0 -0
  33. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/py.typed +0 -0
  34. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/ratchet.py +0 -0
  35. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/__init__.py +0 -0
  36. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/_ast_index.py +0 -0
  37. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/_comments.py +0 -0
  38. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/_docstrings.py +0 -0
  39. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/_fastapi.py +0 -0
  40. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/_first_party.py +0 -0
  41. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/_imports.py +0 -0
  42. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/_logging.py +0 -0
  43. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/_paths.py +0 -0
  44. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/_project_index.py +0 -0
  45. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/_prose_budget.py +0 -0
  46. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/_pytest.py +0 -0
  47. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/_registry.py +0 -0
  48. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/_sql.py +0 -0
  49. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/_suppression_comments.py +0 -0
  50. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/_test_assertions.py +0 -0
  51. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/defect_xfail_requires_strict.py +0 -0
  52. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/docstring_args_restate_signature.py +0 -0
  53. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/docstring_returns_restate_signature.py +0 -0
  54. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/duplicate_test_body.py +0 -0
  55. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/duplicated_override_docstring.py +0 -0
  56. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/fastapi_openapi_contract.py +0 -0
  57. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/fixture_returns_bare_tuple.py +0 -0
  58. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/iac_source_coupled_test.py +0 -0
  59. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/invalid_pydantic_field_default.py +0 -0
  60. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/kwarg_heavy_construction_in_test.py +0 -0
  61. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/mock_without_spec.py +0 -0
  62. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/no_aggregation_in_store_query.py +0 -0
  63. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/no_comment_cruft.py +0 -0
  64. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/no_conftest_test_module_import.py +0 -0
  65. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/no_cors_wildcard_with_credentials.py +0 -0
  66. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/no_duplicate_dunder_all_entry.py +0 -0
  67. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/no_fastapi_on_event.py +0 -0
  68. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/no_file_level_escape_hatch_noqa.py +0 -0
  69. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/no_first_party_private_import.py +0 -0
  70. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/no_frozen_after_validator_field_write.py +0 -0
  71. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/no_gen_random_uuid_in_sql.py +0 -0
  72. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/no_generic_single_export_module.py +0 -0
  73. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/no_isinstance_union_chain.py +0 -0
  74. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/no_long_comment.py +0 -0
  75. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/no_nested_pydantic_field_validator.py +0 -0
  76. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/no_offset_pagination.py +0 -0
  77. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/no_optional_tenant_predicate.py +0 -0
  78. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/no_query_with_many_joins.py +0 -0
  79. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/no_redundant_literal_description.py +0 -0
  80. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/no_repeated_string_literal.py +0 -0
  81. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/no_restated_comment.py +0 -0
  82. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/no_secret_in_log.py +0 -0
  83. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/no_select_star.py +0 -0
  84. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/no_sentinel_return_on_except.py +0 -0
  85. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/no_stdlib_logging.py +0 -0
  86. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/no_string_concat_in_loop.py +0 -0
  87. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/no_tautological_expect.py +0 -0
  88. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/no_typed_doc_sections.py +0 -0
  89. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/opaque_parametrize_case_needs_id.py +0 -0
  90. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/over_mocked_test.py +0 -0
  91. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/phase_label_comment.py +0 -0
  92. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/prefer_class_row.py +0 -0
  93. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/prefer_constant_time_secret_compare.py +0 -0
  94. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/prefer_fstring_over_concat.py +0 -0
  95. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/prefer_library_fake.py +0 -0
  96. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/prefer_match_assert_never.py +0 -0
  97. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/prefer_module_level_constant.py +0 -0
  98. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/prefer_namedtuple_over_tuple_return.py +0 -0
  99. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/prefer_nominal_id_types.py +0 -0
  100. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/prefer_or_pattern.py +0 -0
  101. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/prefer_self_documenting_constant.py +0 -0
  102. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/prefer_self_type_annotation.py +0 -0
  103. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/prefer_str_enum.py +0 -0
  104. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/prefer_struct_over_namedtuple.py +0 -0
  105. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/prefer_timedelta_for_durations.py +0 -0
  106. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/prefer_walrus_comprehension_filter.py +0 -0
  107. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/prefer_walrus_regex_match.py +0 -0
  108. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/prefer_walrus_stream_loop.py +0 -0
  109. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/pydantic_at_boundaries.py +0 -0
  110. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/redundant_class_docstring.py +0 -0
  111. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/redundant_docstring.py +0 -0
  112. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/redundant_module_docstring.py +0 -0
  113. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/require_keyword_only_swap_prone_params.py +0 -0
  114. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/require_nodecode_for_splitting_settings_field.py +0 -0
  115. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/require_port_for_service.py +0 -0
  116. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/restated_test_docstring.py +0 -0
  117. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/source_coupled_test.py +0 -0
  118. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/stepdown.py +0 -0
  119. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/store_insert_requires_on_conflict.py +0 -0
  120. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/trailing_value_narration.py +0 -0
  121. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/trivially_true_assertion.py +0 -0
  122. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/uncontrolled_randomness_in_test.py +0 -0
  123. {sarj_python_lint-0.77.1 → sarj_python_lint-0.78.0}/src/sarj_python_lint/rules/unused_mock_setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sarj-python-lint
3
- Version: 0.77.1
3
+ Version: 0.78.0
4
4
  Summary: Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults
5
5
  Project-URL: Homepage, https://code-standards.sarj.ai/rules/python/
6
6
  Project-URL: Documentation, https://code-standards.sarj.ai/rules/python/
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sarj-python-lint"
3
- version = "0.77.1"
3
+ version = "0.78.0"
4
4
  description = "Custom Python lint rules — AST-based, pre-commit-friendly, hypermodern defaults"
5
5
  readme = "README.md"
6
6
  authors = [{ name = "sarj-ai" }]
@@ -286,7 +286,7 @@ class Rule(ABC):
286
286
  return () if spec is None else spec.public_examples
287
287
 
288
288
 
289
- class ProjectRule(Rule):
289
+ class ProjectRule(Rule, ABC):
290
290
  _project_indexes: ProjectIndexSet | None = None
291
291
 
292
292
  def prepare(self, indexes: ProjectIndexSet) -> None:
@@ -127,7 +127,7 @@ class CreatedAtOrderRequiresTiebreaker(Rule):
127
127
  line=node.lineno,
128
128
  col=node.col_offset + 1,
129
129
  code=self.code,
130
- severity=Severity.WARNING,
130
+ severity=Severity.ERROR,
131
131
  message=(
132
132
  "Store query leaves `created_at` as its final ordering item; add a stable tie-break key "
133
133
  "after it, such as `id` (for example, `ORDER BY created_at DESC, id DESC`)."
@@ -130,7 +130,7 @@ class GetDelegatesToGetMany(Rule):
130
130
  f"`{bulk.name}([key])` so singleton and bulk reads share one contract, or document why "
131
131
  "their semantics differ."
132
132
  ),
133
- severity=Severity.WARNING,
133
+ severity=Severity.ERROR,
134
134
  )
135
135
  )
136
136
  return diagnostics
@@ -150,7 +150,7 @@ class NegativeOnlyHttpStatusAssertion(Rule):
150
150
  line=node.lineno,
151
151
  col=node.col_offset + 1,
152
152
  code=self.code,
153
- severity=Severity.WARNING,
153
+ severity=Severity.ERROR,
154
154
  message=(
155
155
  "this assertion proves only that the response avoided one server-error shape; assert the "
156
156
  "intended exact status and, when relevant, its domain payload or side effect."
@@ -209,7 +209,7 @@ class NoHiddenConstructorFallback(Rule):
209
209
  "make the argument required and resolve the fallback at the call site or composition root."
210
210
  f"{falsey_warning}"
211
211
  ),
212
- severity=Severity.WARNING,
212
+ severity=Severity.ERROR,
213
213
  )
214
214
  )
215
215
  return sorted(diagnostics, key=lambda diagnostic: (diagnostic.line, diagnostic.col))
@@ -105,7 +105,7 @@ class NoUniqueViolationMessageMatch(Rule):
105
105
  "UniqueViolation is classified by substring-matching its message; compare "
106
106
  "`exc.diag.constraint_name` with the expected constraint instead."
107
107
  ),
108
- severity=Severity.WARNING,
108
+ severity=Severity.ERROR,
109
109
  )
110
110
  for node in findings
111
111
  ]
@@ -191,7 +191,7 @@ class NoUnnecessaryDocstring(ProjectRule):
191
191
  col=expression.col_offset + 1,
192
192
  code=self.code,
193
193
  message=self.description,
194
- severity=Severity.WARNING,
194
+ severity=Severity.ERROR,
195
195
  )
196
196
  )
197
197
  return sorted(diagnostics, key=lambda diagnostic: (diagnostic.line, diagnostic.col))
@@ -98,7 +98,7 @@ class NoVagueSuppressionDescription(Rule):
98
98
  col=comment.col,
99
99
  code=self.code,
100
100
  message=self.description,
101
- severity=Severity.WARNING,
101
+ severity=Severity.ERROR,
102
102
  column_encoding=ColumnEncoding.CODEPOINTS,
103
103
  )
104
104
  for comment in comments
@@ -137,7 +137,7 @@ class PreferImmutableModuleConstant(Rule):
137
137
  statement.col_offset + 1,
138
138
  self.code,
139
139
  _message(name, kind),
140
- Severity.WARNING,
140
+ Severity.ERROR,
141
141
  )
142
142
  )
143
143
  return findings
@@ -290,9 +290,7 @@ def _isinstance_ladder(
290
290
 
291
291
  subject: str | None = None
292
292
  for branch in branches:
293
- # Guarded ladders are part of the newly calibrated two-arm advisory.
294
- # Preserve SARJ080's established 3+ ladder boundary to avoid promoting
295
- # an uncalibrated population directly to blocking diagnostics.
293
+ # Guarded ladders preserve the narrow evidence used by two-arm dispatch.
296
294
  branch_subject = (
297
295
  _matchable_isinstance_dispatch(branch.test, runtime_tuple_aliases, frozenset())
298
296
  if branch_count < _ERROR_TYPE_DISPATCH_BRANCHES
@@ -354,8 +352,8 @@ def _leading_isinstance(test: ast.expr) -> ast.Call | None:
354
352
  return None
355
353
 
356
354
 
357
- def _type_dispatch_severity(branch_count: int) -> Severity:
358
- return Severity.WARNING if branch_count < _ERROR_TYPE_DISPATCH_BRANCHES else Severity.ERROR
355
+ def _type_dispatch_severity() -> Severity:
356
+ return Severity.ERROR
359
357
 
360
358
 
361
359
  def _sequential_isinstance_dispatches(
@@ -411,7 +409,7 @@ def _sequential_isinstance_dispatches(
411
409
  line=first.lineno,
412
410
  col=first.col_offset + 1,
413
411
  code=code,
414
- severity=_type_dispatch_severity(branch_count),
412
+ severity=_type_dispatch_severity(),
415
413
  message=(
416
414
  f"{branch_count}-branch terminating isinstance dispatch on '{subject}' — use "
417
415
  "match/case class patterns so the exclusive type dispatch is explicit."
@@ -777,7 +775,7 @@ class _TypeDispatchVisitor(ast.NodeVisitor):
777
775
  line=node.lineno,
778
776
  col=node.col_offset + 1,
779
777
  code=self.code,
780
- severity=_type_dispatch_severity(ladder.branch_count),
778
+ severity=_type_dispatch_severity(),
781
779
  message=(
782
780
  f"{ladder.branch_count}-branch isinstance dispatch on '{ladder.subject}' — use match/case "
783
781
  "class patterns (combining tuple members with `|`) so the type dispatch is explicit."
@@ -890,8 +888,8 @@ class PreferMatchTypeDispatch(Rule):
890
888
  category=RuleCategory.MAINTAINABILITY,
891
889
  autofix=AutofixPolicy.NONE,
892
890
  limitations=(
893
- "Safe two-branch `isinstance` dispatch is advisory; dispatch with three or more branches and the rule's established parser shapes remain blocking.",
894
- "Generated files, runtime tuple aliases, general two-arm stdlib AST visitors, subject-rebinding guards, idiomatic None/Unset prologues, and code that shadows `isinstance` are excluded; exact AST attribute projections remain advisory, and test files omit the control-flow-raise check.",
891
+ "Safe two-branch `isinstance` dispatch uses narrower evidence than dispatch with three or more branches and the rule's established parser shapes.",
892
+ "Generated files, runtime tuple aliases, general two-arm stdlib AST visitors, subject-rebinding guards, idiomatic None/Unset prologues, and code that shadows `isinstance` are excluded; exact AST attribute projections use the narrow two-arm evidence, and test files omit the control-flow-raise check.",
895
893
  ),
896
894
  examples=(
897
895
  RuleExample(
@@ -110,7 +110,7 @@ class PreferNonNullableCollection(Rule):
110
110
  "make the list required, or accept an immutable empty-default input such as "
111
111
  "`Sequence[T] = ()` and materialize a list internally."
112
112
  ),
113
- severity=Severity.WARNING,
113
+ severity=Severity.ERROR,
114
114
  )
115
115
  for argument in _equivalent_empty_parameters(function)
116
116
  if owner is None or not _forwards_inherited_constructor_parameter(owner, function, argument.arg)
@@ -107,7 +107,7 @@ class PreferOneForRequiredRow(Rule):
107
107
  f"required-row helper (for example `{binding.name} = one({binding.expression})`) so "
108
108
  "optimized Python cannot remove the contract."
109
109
  ),
110
- severity=Severity.WARNING,
110
+ severity=Severity.ERROR,
111
111
  )
112
112
  )
113
113
  return diagnostics
@@ -110,7 +110,7 @@ def _diagnose(
110
110
  line=annotation.lineno,
111
111
  col=annotation.col_offset + 1,
112
112
  code=code,
113
- severity=Severity.WARNING,
113
+ severity=Severity.ERROR,
114
114
  message=f"`{name}` erases declared nominal `{nominal.name}`; propagate the nominal ID type through this boundary",
115
115
  )
116
116
  ]
@@ -112,7 +112,7 @@ class PreserveEnumTypes(ProjectRule):
112
112
  line=call.lineno,
113
113
  col=call.col_offset + 1,
114
114
  code=self.code,
115
- severity=Severity.WARNING,
115
+ severity=Severity.ERROR,
116
116
  message="matched enum is converted to unconstrained `str`; preserve its enum type through the receiving boundary",
117
117
  )
118
118
  )
@@ -287,7 +287,7 @@ class ProductionDerivedTestCases(Rule):
287
287
  line=decorator.lineno,
288
288
  col=decorator.col_offset + 1,
289
289
  code=self.code,
290
- severity=Severity.WARNING,
290
+ severity=Severity.ERROR,
291
291
  message=(
292
292
  f"`{collection.id}` supplies the cases from production, so removing a member can remove "
293
293
  "the test that should catch the regression; define an independent expected case table and "
@@ -112,7 +112,7 @@ class RepeatedStaticCallCases(Rule):
112
112
  line=run[0].lineno,
113
113
  col=run[0].col_offset + 1,
114
114
  code=self.code,
115
- severity=Severity.WARNING,
115
+ severity=Severity.ERROR,
116
116
  message=(
117
117
  f"these {len(run)} static call assertions run as one coarse test; move the inputs and "
118
118
  "expectations into named pytest parameters."
@@ -155,7 +155,7 @@ class RequirePydanticForExternalJson(Rule):
155
155
  "External JSON field is read without Pydantic validation — validate the payload with "
156
156
  "`Model.model_validate_json(...)` or `TypeAdapter(Model).validate_json(...)` first."
157
157
  ),
158
- severity=Severity.WARNING,
158
+ severity=Severity.ERROR,
159
159
  )
160
160
  for sink, _origin in first_by_origin.values()
161
161
  ]
@@ -107,7 +107,7 @@ class RequirePydanticOrdinalLowerBound(Rule):
107
107
  line=field.lineno,
108
108
  col=field.col_offset + 1,
109
109
  code=self.code,
110
- severity=Severity.WARNING,
110
+ severity=Severity.ERROR,
111
111
  message=f"`{field.target.id}` documents minimum {default} but does not enforce it; add `ge={default}` or an equivalent constrained integer type",
112
112
  )
113
113
  )
@@ -97,7 +97,7 @@ class RequireValidatedRowFactory(Rule):
97
97
  line=cursor_call.lineno,
98
98
  col=cursor_call.col_offset + 1,
99
99
  code=self.code,
100
- severity=Severity.WARNING,
100
+ severity=Severity.ERROR,
101
101
  message=(
102
102
  "fetched Psycopg cursor has no `class_row(Model)` factory; validate the result at the DB "
103
103
  "boundary (use a one-field model for scalar projections)"
@@ -121,7 +121,7 @@ class SqlRequiresInjectedPoolOwner(Rule):
121
121
  line=call.lineno,
122
122
  col=call.col_offset + 1,
123
123
  code=self.code,
124
- severity=Severity.WARNING,
124
+ severity=Severity.ERROR,
125
125
  message="database execution is outside a constructor-injected pool owner; route it through the owning store/service API",
126
126
  )
127
127
  )