@@ -54,6 +54,66 @@ All notable changes to AgentFlow are documented in this file.
5454 failing at import. The stale-shim hazard is now written into the test's own
5555 design rules, since the next rename will ripple the same way.
5656
57+ * ** The gate is measurable on this machine now, not only on Sundays.**
58+ ` python scripts/mutation_local.py --module <target> ` runs one module of the
59+ same gate locally in about two minutes. It exists because ` mutmut run ` calls
60+ ` sys.exit(1) ` at import time on native Windows, so between weekly runs nobody
61+ here could see a score at all — which is part of why nine consecutive red
62+ Sundays went unnoticed. The driver never invokes the ` mutmut ` CLI: it reads
63+ its targets from ` scripts/mutation_report.MODULE_TARGETS ` , builds the
64+ workspace with ` prepare_workspace ` , generates mutants with mutmut's own
65+ ` mutate_file_contents ` , and runs each one as a plain ` pytest ` subprocess
66+ selected through ` MUTANT_UNDER_TEST ` . The gate's definition of a target is
67+ still declared in exactly one place.
68+ * ** It reproduces CI rather than approximating it.** Measured against run
69+ 34266462154 on ` serving/semantic_layer/query/sql_builder.py ` : 141 mutants,
70+ the same population, down to the surviving mutant names. At ` 10e20a5 ` the
71+ module scores 90.8% (128 killed, 13 survived) — the 88.7% CI last reported
72+ plus the three mutants ` 2cda8da ` and ` 10e20a5 ` killed since. Only pytest exit
73+ 0 (survived) and 1 (killed) count as verdicts; anything else is reported as a
74+ harness failure and fails the run, where ` mutation_report.py ` counts exit 3
75+ as a kill. That is the one deliberate divergence, and ` CONTRIBUTING.md ` says
76+ so rather than claiming exact parity.
77+ * ** A score you can trust to be about your own tree.** Three failure modes are
78+ closed by construction: the workspace is stamped with the root, the module,
79+ its source, the materialized package tree, the target's tests and
80+ ` pyproject.toml ` , and is rebuilt whenever any of those move, so a second run
81+ never reports the first one's sources; a mutant that comes back without a
82+ verdict is retried once serially before it is called a harness failure, so
83+ the number does not drift with machine load; and a ` --workspace ` that is a
84+ checkout — this repository, anything inside it, or any directory holding a
85+ ` .git ` — is refused instead of emptied. The mutated module never leaves the
86+ temp workspace: the working tree is clean after a run.
87+
88+ * ** ` sql_builder.py ` is off the threshold line, and its residue is honest.**
89+ It cleared 90% by a single mutant (90.8%, 128 killed of 141), which is not a
90+ margin worth keeping: the next covered line added to the module would have
91+ put the gate back in the red for reasons unrelated to the change. Nine of the
92+ thirteen survivors could never have been killed. Eight mutated a
93+ ` typing.cast ` type argument — a cast returns its second argument untouched
94+ and never evaluates the first — so both casts are plain annotations now and
95+ the mutants stop existing; the ninth turned ` rows = [] ` into ` rows = None ` in
96+ a branch whose next statement is ` bool(rows) ` , and carries a
97+ ` # pragma: no mutate ` with the reason above it. The remaining four were the
98+ ` dialect="duckdb" ` argument, and two of them are now dead: DuckDB list
99+ indexing is 1-based where sqlglot's default dialect is not, so
100+ ` list_value(1, 2)[1] ` read without the dialect comes back out of the scoper
101+ as ` [2] ` — the tenant scoper would have changed which element the query asked
102+ for while it added a WHERE clause. The module measures 98.4% (124 killed of
103+ 126 ) with ` scripts/mutation_local.py ` on py3.13.
104+ * ** The two mutants still alive are named in the test file, not suppressed.**
105+ ` _scope_sql__mutmut_41 ` and ` _43 ` drop the dialect from the parse of the
106+ relation ` _qualify_table ` generated itself, and that string has one fixed
107+ shape which — parsed with the dialect or without it — renders identically
108+ under the ` sql(dialect="duckdb") ` ` _scope_sql ` applies on the way out, so no
109+ input reaches them with a difference to observe. Not the same as neutral: the
110+ * default-dialect render* of that shape rewrites ` EXCLUDE ` to ` EXCEPT ` , which
111+ is why the mutants on the render itself stay killable and dead.
112+ They are not equivalent — a ` _qualify_table ` that ever emitted
113+ DuckDB-specific syntax would make them killable — so they get a written
114+ record of what was tried and came out identical rather than a pragma that
115+ would outlive its reason.
116+
57117### Terraform — an exact core pin took the provider update channel down with it
58118
59119* ** ` required_version = "= 1.15.4" ` broke Dependabot's terraform ecosystem the
0 commit comments