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
19 changes: 10 additions & 9 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -206,15 +206,16 @@ 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, 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.
# src/serving/semantic_layer/sql_guard.py, src/serving/masking.py,
# src/serving/api/rate_limiter.py, src/serving/semantic_layer/query/sql_builder.py
# AND src/serving/semantic_layer/query/nl_queries.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 declared serving surface -- the auth
# manager / key_rotation -- stays declared-only for now: its unit tests pull the
# duckdb-backed query engine, so mutating it in isolation needs a duckdb-free test
# (the pattern the six live modules 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
32 changes: 22 additions & 10 deletions scripts/mutation_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,20 @@ class ModuleTarget:
# (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,
# 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.
# masking, rate_limiter, sql_builder and nl_queries 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`.
# sql_builder and nl_queries live under the query package whose __init__ imports
# the duckdb-backed QueryEngine, so their tests also stub
# serving.semantic_layer.query.{engine,contracts} (and the src.* helpers) before
# import. The only remaining declared-but-not-live serving surface is auth
# (manager / key_rotation), whose tests still need the duckdb engine; it stays
# declared-only in the [tool.mutmut] policy until it gets duckdb-free unit tests
# of its own.
MODULE_TARGETS = {
Path("agentflow/retry.py"): ModuleTarget(
threshold=0.75,
Expand All @@ -57,6 +61,14 @@ class ModuleTarget:
threshold=0.90,
tests=("tests/unit/test_rate_limiter_mutation.py",),
),
Path("serving/semantic_layer/query/sql_builder.py"): ModuleTarget(
threshold=0.90,
tests=("tests/unit/test_sql_builder_mutation.py",),
),
Path("serving/semantic_layer/query/nl_queries.py"): ModuleTarget(
threshold=0.90,
tests=("tests/unit/test_nl_queries_mutation.py",),
),
}

STATUS_BY_EXIT_CODE = {
Expand Down
11 changes: 6 additions & 5 deletions tests/unit/test_mutmut_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@
# 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, 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.
# sql_guard.py, masking.py, rate_limiter.py, sql_builder.py AND nl_queries.py live
# (the serving modules via duckdb-free narrow tests, mutated as a top-level
# `serving` package so mutmut's trampoline accepts them). The remaining declared
# serving surface -- auth manager / key_rotation -- stays declared-only until it
# gets duckdb-free unit tests of its 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