Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 9 additions & 8 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -206,14 +206,15 @@ asyncio_mode = "auto"
# security-critical surfaces). It is a superset of what the CI gate actually
# mutates: scripts/mutation_report.py drives the gate from its own
# MODULE_TARGETS, which now mutates sdk/agentflow/retry.py,
# src/serving/semantic_layer/sql_guard.py AND src/serving/masking.py live. The
# serving modules are mutated as a top-level `serving` package against duckdb-free
# narrow tests: mutmut's trampoline rejects a module name starting with `src.`,
# which (not duckdb) was the real blocker. The remaining serving modules below
# stay declared-only for now -- their unit tests pull the duckdb-backed query
# engine, so mutating them in isolation needs a duckdb-free test per module (the
# pattern sql_guard and masking now use); the blocker is the test import chain,
# not the module. See scripts/mutation_report.py.
# src/serving/semantic_layer/sql_guard.py, src/serving/masking.py AND
# src/serving/api/rate_limiter.py live. The serving modules are mutated as a
# top-level `serving` package against duckdb-free narrow tests: mutmut's
# trampoline rejects a module name starting with `src.`, which (not duckdb) was
# the real blocker. The remaining serving modules below stay declared-only for
# now -- their unit tests pull the duckdb-backed query engine, so mutating them
# in isolation needs a duckdb-free test per module (the pattern sql_guard,
# masking and rate_limiter now use); the blocker is the test import chain, not
# the module. See scripts/mutation_report.py.
paths_to_mutate = [
"src/serving/api/auth/manager.py",
"src/serving/api/auth/key_rotation.py",
Expand Down
22 changes: 15 additions & 7 deletions scripts/mutation_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,17 @@ class ModuleTarget:
# that, not duckdb, was the real blocker for the serving modules. The fix is to
# (a) copy the module so it imports as a top-level package and (b) pair it with a
# NARROW test that does not pull the duckdb-backed engine import chain. So
# retry.py mutates as agentflow.retry (from sdk/agentflow), and sql_guard and
# masking mutate as serving.* (from src/serving) against duckdb-free tests. Each
# duckdb-free test also avoids fixtures and calls the module's methods directly:
# under mutate_only_covered_lines a fixture-built object left every method line
# uncovered, so only __init__ got mutated. The remaining serving modules whose
# tests still need the duckdb engine (the query/auth surfaces) stay declared-only
# in the [tool.mutmut] policy until they get duckdb-free unit tests of their own.
# retry.py mutates as agentflow.retry (from sdk/agentflow), and sql_guard,
# masking and rate_limiter mutate as serving.* (from src/serving) against
# duckdb-free tests. Each duckdb-free test also avoids fixtures and calls the
# module's methods directly: under mutate_only_covered_lines a fixture-built
# object left every method line uncovered, so only __init__ got mutated.
# rate_limiter additionally imports `from src.constants import ...`; its test
# registers a tiny src.constants stub before importing the module, because the
# serving workspace copies src/serving -> top-level `serving` without `src`. The
# remaining serving modules whose tests still need the duckdb engine (the
# query/auth surfaces) stay declared-only in the [tool.mutmut] policy until they
# get duckdb-free unit tests of their own.
MODULE_TARGETS = {
Path("agentflow/retry.py"): ModuleTarget(
threshold=0.75,
Expand All @@ -49,6 +53,10 @@ class ModuleTarget:
threshold=0.90,
tests=("tests/unit/test_masking_mutation.py",),
),
Path("serving/api/rate_limiter.py"): ModuleTarget(
threshold=0.90,
tests=("tests/unit/test_rate_limiter_mutation.py",),
),
}

STATUS_BY_EXIT_CODE = {
Expand Down
10 changes: 5 additions & 5 deletions tests/unit/test_mutmut_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@
# assembled here.
# NOTE: these are the *declared* targets (intent). Actual mutation execution is
# gated by scripts/mutation_report.py (MODULE_TARGETS), which now runs retry.py,
# sql_guard.py AND masking.py live (the serving modules via duckdb-free narrow
# tests, mutated as a top-level `serving` package so mutmut's trampoline accepts
# them). The other serving modules below stay declared-only until they get
# duckdb-free unit tests of their own. These assertions guard the declared
# policy, not live coverage.
# sql_guard.py, masking.py AND rate_limiter.py live (the serving modules via
# duckdb-free narrow tests, mutated as a top-level `serving` package so mutmut's
# trampoline accepts them). The other serving modules below stay declared-only
# until they get duckdb-free unit tests of their own. These assertions guard the
# declared policy, not live coverage.
REQUIRED_MUTATION_TARGETS = {
"src/serving/semantic_layer/sql_guard.py",
"src/serving/api/auth/manager.py",
Expand Down
Loading