test(mutants,assets): a mutant is judged by the suite that owns it, 45 of 45

The runner could only run one test file, which is why PM's three
survivors from `43331fc` could not be added: two are held by the gate's
row 3 and one by the soft-hyphen door's suite. A mutant now names its
suite; the catalogue goes 39 to 45.

X3 and X4 rewritten against the code as it now stands -- a mutant table
is a copy of the code it mutates, and this round moved the lines both of
them quoted. X6 is the defeated state exactly, X7 cuts the ledger off at
its source, X8 removes the cursor rule, P6/P11/P12 are PM's three.

Two survivors on the first run, both findings, both closed:
- X4 survived because every forgery arm now fails on the ledger check
  before the binding is reached. An arm was added where the run DID book
  the pair and the block stating it points at another picture.
- X5 survived the WHOLE suite -- 2134 passed with the disarming removed
  -- because a document-supplied field can no longer reach the gate. The
  property is about the BUNDLE and not about one judge, so it is kept and
  measured in `tests/test_assets.py`, with a known-positive counting the
  run's own two fields on the same expression.

killed 45 of 45, exit 0. Report, CHANGELOG and CLAUDE.md written.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-19 19:33:23 +02:00
commit 44ad845e29
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
6 changed files with 431 additions and 17 deletions

View file

@ -282,6 +282,77 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- **The conversion claim the content-accounting gate believes now comes from
the RUN, not from the bundle's prose (0.10.1).** The previous round bound
the claim to a pointer block, which closed the two forgeries PM had
measured and did not close the class: a pointer block is two lines of
markdown, and one ordinary HTML file with two `<p>` elements writes them.
Reproduced through the real `okf build` — a BMP refused `asset_too_large`
and absent from `assets/` read as CARRIED, from a document naming one
digest that is public in the bundle and one that is computable in advance.
- `okf build --accounting` now books every conversion the run performed:
`assets.conversion` names the `(source digest, asset digest)` pair,
`DocumentAssets.conversions` carries it out of the run and the accounting
JSON states it per document as `conversions: [{from, to}]`.
- The gate reads the pair from there and uses the bundle text only to
CONFIRM it. The confirmation can be forged and the ledger cannot, which is
why the ledger decides.
- **Chosen over neutralising pointer-shaped text at extraction**, because
that fix changes what every document SAYS in order to defend a tool
outside the build: a source quoting a bundle listing would come out
altered and existing bundles would move bytes.
- A build run with no accounting door has no ledger, so a converted image
is reported claimed-and-not-found rather than believed. That is the same
reading the gate had before the conversion route existed.
- Measured: the three arms PM reproduced go forged → refused, 3 of 3, with
the known-positive (a BMP the run really does convert) True in all three.
The text-level regression guard goes 3 arms to 13. R761 rebuilt is
`diff -r`-identical, 50 assets (29 JPEG + 21 PNG), 19 of 19 conversions
confirmed against 19 declared, SHY 71, u = 0, d = 0.
- **An RLE8 stream that stops before the frame is refused (0.10.1).** The
terminator rule added earlier in this version asks only that a stream SAY it
is finished, and a stream can say so anywhere: measured 2026-09-19, one
whose FIRST two bytes are the end-of-bitmap escape was carried with 32 of 32
pixels never decoded, while an independent decoder refuses the same file.
`_bmp_rle8_rows` now also requires the cursor to stand at or past the end of
the last row (`asset_samples_invalid`).
- **The line is the cursor and not the pixels.** A delta escape and an
end-of-line escape state their skip, so the pixels they pass over keep
index 0 and every decoder produces the same picture; a pixel-coverage
count would refuse both constructions the format defines. The corpus
cannot choose between the two rules — over the 25 RLE8 BMPs the R761
delivery ships, 25 of 25 paint every pixel, 25 of 25 reach the end of the
frame and 0 of 25 use a delta — and an independent decoder can: Pillow
reads 5 of the 8 streams in the table and refuses the same 3 the new rule
does, one of them short by a single pixel.
- Two docstrings this round was sent to correct are rewritten: the test no
longer claims every pixel is decoded, and `_bmp_rle8_rows` no longer
frames the delta argument as read off the corpus, which it never was.
- **The published `tbx:` count is one number, guarded without the delivery
(0.10.1).** `assert sum(tbx.values()) == 568` sat behind a `skipif` on a file
only one machine has, so on a fresh clone the sentence five files publish was
unguarded — the state in which 574 survived in four docstrings. `N101_TBX_TAGS`
is now the one place it lives and a second test holds all five published
sentences to it, with no corpus and no clock. What it does not prove is
stated: five files agreeing is agreement, not a count.
- **The mutant runner judges a mutant by the suite that owns it, and the
catalogue goes 39 to 45 (0.10.1).** It could only run one test file, which is
why PM's three survivors from `43331fc` could not be added. `X3`/`X4` were
rewritten against the code as it now stands; `X6` is the defeated state
exactly (a pointer block believed without the run having booked it); `X7`
cuts the ledger off at its source; `X8` removes the cursor rule; `P6`, `P11`
and `P12` are PM's three. Two survivors appeared on the first run and both
were findings — the asset binding had stopped being exercised, and the
`_inline` disarming survived the WHOLE suite (2134 passed) because the gate
no longer reads its claim from the bundle. The disarming is KEPT and now
measured in `tests/test_assets.py`: the property is about the bundle, not
about one judge. `killed 45 of 45`, exit 0.
- **`test_the_four_existing_goldens_are_untouched` skips, with its reason, in
a `git archive` extract (0.10.1).** It called `git status` with `check=True`
outside a repository and raised. It was the single failure of the whole
suite run from a clean extract, twice reported as a round's one failure by a
round that had not touched the file.
- **A truncated RLE8 BMP is refused instead of carried as a partly blank PNG
(0.10.1).** `_bmp_rle8_rows` painted what the stream held and left the rest
of the frame at palette index 0 — which is what the format says about a

View file

@ -1081,6 +1081,35 @@ and fixtures, never code.
and not FIDELITY, which its docstring now states: a converter writing a
blank PNG is accepted, the suite fells that mutant and the judge cannot.
Report: `docs/2026-09-19-bmp-resten-terminator-og-pekerblokk.md`.
**AND THAT ROUND NARROWED BOTH DEFECTS WITHOUT CLOSING EITHER.** A POINTER
BLOCK IS A FORM AND A FORM IS NOT A SIGNATURE: measured through the real
`okf build`, one ordinary HTML file with **two `<p>` elements** writes the
two lines `_POINTER` matches, and the same refused BMP read as held again --
also with `<p>`+`<br>`, and with a markdown note beside the HTML carrier,
**3 of 3**. So the pair is read from the RUN's own ledger (`--accounting`
now books `conversions: [{from, to}]` per document, off `assets.conversion`)
and the bundle text only CONFIRMS it: the confirmation can be forged and the
ledger cannot. **Chosen over neutralising pointer-shaped text at
extraction**, which would change what every document SAYS to defend a tool
outside the build. A build with no accounting door reports a converted image
claimed-and-not-found rather than believing it. Measured: 3 of 3 forged ->
refused with the known-positive True in all three, the text-level regression
guard 3 arms -> 13, R761 rebuilt `diff -r`-identical, 50 assets, **19 of 19**
conversions confirmed against 19 declared, SHY 71, u = 0, d = 0. And the
TERMINATOR asks only that a stream say it is finished, which it may do
anywhere: a stream whose FIRST two bytes are the EOB escape was carried with
**32 of 32** pixels never decoded while Pillow refuses the same file. The
cursor must now also stand at or past the end of the last row. **The line is
the CURSOR and not the pixels** -- a delta and an end-of-line escape state
their skip, so every decoder agrees on the index-0 pixels they pass over,
and a pixel-coverage count would refuse both constructions the format
defines. The corpus cannot choose (over the **25** RLE8 BMPs the delivery
ships, 25 of 25 paint every pixel, 25 of 25 reach the end, **0 of 25** use a
delta) and an independent decoder can: Pillow reads **5 of 8** streams and
refuses the same **3**, one short by a single pixel. The earlier `19 of 19`
figures above are the BUNDLE's carried assets; 25 is what the delivery
ships. Report:
`docs/2026-09-19-pekerblokken-bindes-til-kjoringen.md`.
- **Account for content: `okf build --accounting PATH` and its judge
`python3 tools/okf_accounting_gate.py` (2026-09-17).**
- The gate was written RED at `0b00de4` and turned GREEN on all six rows by

View file

@ -0,0 +1,174 @@
# The pointer block, bound to the run
2026-09-19. PM's checkpoint on `ae441ab` judged the previous round PARTIAL
with one load-bearing rest and two small ones. The rest is the same class the
round before it: the judge's fasit came from the reader it judges. It had been
narrowed, not closed. This closes it, closes the two small ones, and clears
three further residuals PM had listed. No new format, no new dependency, no
version bump and no tag.
## 1. A form is not a signature
`tools/okf_accounting_gate.py` read the conversion claim out of the bundle
text. The round before bound it to a POINTER BLOCK, which is the two lines
`assets.render_block` writes:
```
![<label>](/assets/<sha12>-<name>)
Image: <name> (<w>x<h> px) -- converted from <type> sha256:<A> to <type> sha256:<B>
```
That closed the two routes PM had measured (an `alt` attribute and plain body
text) and left the class open, because a document can write the whole block.
Reproduced here through the real `okf build`, three ways, each a whole build:
| way | before | known-positive in the same build |
|---|---|---|
| two `<p>` elements in one HTML file | **forged** | held |
| one `<p>` with a `<br>` | **forged** | held |
| a markdown note beside the HTML carrier | **forged** | held |
"Forged" means `asset_holds` returned True for a BMP declaring 50 000 x
50 000, refused `asset_too_large`, absent from `assets/`. The forger needs two
digests: the one it wants vouched for, and one of a picture the bundle really
carries — public in the bundle, and computable in advance for a PNG carried
verbatim.
### What was chosen, and why
Two fixes were on the table. The claim is now read from the RUN's own ledger:
- `assets.conversion(image)` names the `(source digest, asset digest)` pair.
- `DocumentAssets.conversions` carries it out of the run.
- `DocumentAccount.conversions` books it, and `okf build --accounting` writes
`conversions: [{from, to}]` per document.
- `_declared_conversions` reads that file; `_conversions` believes a pair only
when the ledger holds it AND a pointer block confirms it for the asset it
points at.
The confirmation can be forged and the ledger cannot, which is why the ledger
decides. The alternative — neutralising pointer-shaped text at extraction, the
way the soft-hyphen door neutralises U+00AD — was felled: it changes what every
document SAYS in order to defend a tool outside the build, a source quoting a
bundle listing would come out altered, and the bytes of existing bundles would
move. This reads a file the run already writes.
A build with no accounting door has no ledger. A converted image is then
reported claimed-and-not-found rather than believed, which is the reading the
gate had before the conversion route existed, and it is visible in the row.
### After
3 of 3 forged → refused, with the known-positive True in all three. The
text-level regression guard goes from 3 arms to 13: PM's eleven ways that the
shape anchoring already refused, kept so a later narrowing cannot widen one of
them, plus the two forms that defeated it — a perfectly written pointer block
the run never booked.
R761 rebuilt from the frozen delivery: bundle `diff -r`-identical to the build
before the change, 50 assets (29 JPEG + 21 PNG, 0 BMP), 19 of 19 conversions
confirmed against 19 declared, soft hyphens 71 → 0, u = 0, d = 0, exit 0.
## 2. A terminator is not a coverage proof
The previous round's terminator rule asks only that a stream SAY it is
finished, and a stream may say so anywhere. Measured: a stream whose FIRST two
bytes are the end-of-bitmap escape was carried, with 32 of 32 pixels never
decoded and all of them palette index 0. Pillow refuses the same file.
`_bmp_rle8_rows` now also requires the cursor to stand at or past the end of
the last row, refusing with the same `asset_samples_invalid` otherwise.
**The line is the cursor, not the pixels.** A delta escape and an end-of-line
escape STATE their skip, so the pixels they pass over keep index 0 and every
decoder produces the same picture; a pixel-coverage count would refuse both
constructions the format defines. The corpus cannot choose between the two
rules — over the 25 RLE8 BMPs the R761 delivery ships (24 distinct; the bundle
carries 19), 25 of 25 paint every pixel, 25 of 25 reach the end of the frame
and 0 of 25 use a delta. An independent decoder can:
| stream over one 8x4 frame | cursor reaches the frame | Pillow |
|---|---|---|
| end-of-bitmap before one pixel is decoded | no | refuses |
| one 4-pixel run, then end-of-bitmap | no | refuses |
| last row one pixel short, no end-of-line | no | refuses |
| every row painted and closed | yes | reads |
| last row one pixel short, then end-of-line | yes | reads |
| a delta skipping a whole row | yes | reads |
| last row painted to its end, no end-of-line | yes | reads |
| the shipped fixture, which uses a delta | yes | reads |
Eight of eight agree with the new rule, and a second test holds Pillow to the
table so it is not our own rule restated.
Both docstrings PM named are rewritten. The test no longer says "NO PIXEL MAY
BE GUESSED: either every one of them is decoded from the stream, or the picture
is refused" — that was false as measured. `_bmp_rle8_rows` no longer frames the
delta argument as read off the corpus, which it never was: 0 of the 25 files
use a delta, so the corpus had nothing to say about it.
## 3. One number, read from one place
`assert sum(tbx.values()) == 568` sat behind a `skipif` on a delivery only this
machine has, so on a fresh clone the sentence five files publish was unguarded
again — the state in which 574 survived in four docstrings until PM counted it.
`N101_TBX_TAGS` is now the one place the number lives, and a second test reads
the published sentence out of `CHANGELOG.md`, `CLAUDE.md`, `tools/okf_witness.py`,
`tests/test_accounting_gate.py` and this round's predecessor report, holding all
five to it. It needs no corpus and no clock.
It was red at birth, and for a reason worth keeping: the scan read the test
file's own known-positive string (`574`) as a sixth publisher. That string is
now assembled from pieces, and the failure is the demonstration that the scan
reads what it is pointed at. What the guard does NOT prove is stated in its
docstring: five files agreeing is agreement, not a count. The measurement stays
where it was.
## 4. A clean extract is not a checkout
`test_the_four_existing_goldens_are_untouched` ran `git status` with
`check=True` in a `git archive` extract and raised `CalledProcessError`. It was
the single failure of the whole suite run from a clean extract, twice reported
as a round's one failure by a round that had not touched the file. Both arms
measure the checkout with `git`, so an extract has nothing to ask: it now skips
with the reason stated, and a checkout still has to answer.
## 5. The mutant runner
`tools/okf_gate_mutants.py` could only run one test file, which is why PM's
three survivors from `43331fc` could not be added: two are held by the gate's
row 3 and one by the soft-hyphen door's own suite. A mutant now names the suite
it is judged by, and the catalogue goes 39 to 45:
- **X3** and **X4** were rewritten against the code as it now stands. A mutant
table is a copy of the code it mutates, and this round moved the lines both
of them quoted.
- **X6** is the defeated state exactly: a pointer block believed without the
run having booked it.
- **X7** cuts the ledger off at its source.
- **X8** removes the cursor rule.
- **P6**, **P11**, **P12** are PM's three, now held by what runs AS the gate.
Two survivors appeared on the first run and both were findings.
**X4** — "the claim need not be about the asset its block points at" — survived
because every forgery arm now fails on the ledger check before the binding is
reached. An arm was added where the run DID book the pair and the block stating
it points at another picture; under the mutant that reads as a carry.
**X5** — "a document-supplied label may emit a checksum field" — survived the
WHOLE suite, measured: 2134 passed with the disarming removed. The gate no
longer reads its claim out of the bundle, so a document-supplied field cannot
reach it. The property did not stop mattering — the line stands in every
concept body, and a bundle must not state a conversion in a sentence the run
did not write — so it is measured where it lives, in `tests/test_assets.py`,
and the mutant is judged there. Deleting an unfellable guard was the other
option and was felled: this guard is about the bundle, not about one judge.
## 6. What this round does not do
- It does not teach the gate FIDELITY. A converter writing a blank PNG is still
accepted, for the reason `asset_holds` states: both routes ask whether a file
holds the bytes the run names, and neither decodes a pixel.
- It does not touch the guard, the goldens, the proposer, any profile, any
version or any dependency.

View file

@ -755,13 +755,30 @@ def test_a_document_cannot_forge_a_conversion_claim(tmp_path: Path) -> None:
# KNOWN-POSITIVE on the same bytes: the last arm's text, believed once the
# RUN books the pair. Without it every arm above would pass on a route
# that had simply stopped working.
booked = _ledger((before, after))
honest = _build(
assets=assets,
bundle_text=arms["that block alone in the bundle"],
accounting=_ledger((before, after)),
accounting=booked,
)
assert gate.asset_holds(honest, never_carried) is True
# THE ASSET BINDING, ON ITS OWN. The run booked this pair and the block
# stating it points at a different picture -- a sentence nothing here
# writes. Measured 2026-09-19: with the ledger in front of it, every arm
# above passes whether or not the binding is checked, so without this the
# mutant that removes it survives the whole suite.
misplaced = _build(
assets=assets,
bundle_text=(
f"![Figur](/assets/{other[:12]}-annen.png)\nImage: figur.bmp (8x4 px) -- {clause}\n"
),
accounting=booked,
)
assert gate.asset_holds(misplaced, never_carried) is False, (
"a clause in one asset's block spoke for another asset's digest"
)
def test_the_build_never_writes_a_claim_the_document_supplied(tmp_path: Path) -> None:
"""PM's measured path, end to end through the real `okf build`.

View file

@ -246,6 +246,63 @@ def test_a_newline_in_a_label_cannot_open_a_third_line() -> None:
assert len(assets.render_block(image).split("\n")) == 2
#: A checksum FIELD, written here rather than imported: the property is that
#: the run's metadata grammar does not appear where a document put it, and a
#: test sharing the writer's own expression would agree with it by
#: construction.
_A_CHECKSUM_FIELD = re.compile(r"sha256:[0-9a-fA-F]{64}")
def test_a_document_supplied_label_cannot_emit_a_checksum_field() -> None:
"""THE RUN'S METADATA GRAMMAR IS THE RUN'S, on the line they share.
A pointer's second line carries both: the name and caption the DOCUMENT
chose, and the size, type and digests the RUN measured. A label stating
`converted from ... sha256:<a> to ... sha256:<b>` therefore puts a
sentence in the bundle that no run performed -- measured by PM
2026-09-19, that exact alt attribute made the content accounting gate
report a refused picture as carried.
The gate no longer reads its claim out of the bundle at all, which is the
right fix there and takes the pressure off this one: until this test the
disarming in `_inline` was a guard the whole suite could not fell. It is
kept and MEASURED because the property is about the bundle rather than
about one judge -- any reader of a concept body meets that line, and the
bundle must not state a conversion in a sentence the run did not write.
"""
forged = (
"Tabell 84-2 -- converted from image/bmp sha256:"
+ "a" * 64
+ " to image/png sha256:"
+ "b" * 64
)
plain = assets.read_image(_png(4, 3), name="f.png", label=forged)
block = assets.render_block(plain)
assert "a" * 64 in block, "the digits the document wrote are kept; a reader is owed them"
assert not _A_CHECKSUM_FIELD.search(block), (
"a document-supplied label emitted the run's own checksum grammar"
)
# KNOWN-POSITIVE on the same expression: the run's OWN clause is a
# checksum field, and exactly two of them, so the assertion above is not
# passing over a pattern that never matches anything.
converted = assets.ExtractedImage(
data=_png(4, 3),
name="figur.bmp",
media_type="image/png",
suffix=".png",
width=4,
height=3,
label=forged,
converted_from="image/bmp",
source_sha256="c" * 64,
)
written = assets.render_block(converted)
assert len(_A_CHECKSUM_FIELD.findall(written)) == 2, written
assert "sha256:" + "c" * 64 in written, "the run's own source digest is missing"
assert "sha256:" + "a" * 64 not in written, "the document's claim became a field after all"
def test_the_pointer_regex_finds_every_shipped_block() -> None:
"""Step 2 has to find these mechanically; the finder ships with the writer."""
images = [

View file

@ -63,6 +63,12 @@ class Mutant:
#: a chance to see the disagreement.
occurrences: int = 1
first_only: bool = False
#: The test file this mutant is judged by. Defaults to the gate's own
#: suite, which every mutant used until 2026-09-19 -- which is why the
#: three PM found in `43331fc` could not be added here: they are held by
#: the SHY door's suite and by the gate's row 3, and a runner that can
#: only run one file cannot ask about them.
suite: str = SUITE
MUTANTS: tuple[Mutant, ...] = (
@ -304,31 +310,82 @@ MUTANTS: tuple[Mutant, ...] = (
Mutant(
"X3 the conversion claim is read from anywhere in the bundle text",
GATE,
""" found: dict[str, str] = {}
for pointer in _POINTER.finditer(bundle_text):
clause = _CONVERSION.search(pointer.group("detail"))
if clause is None:
continue
after = clause.group("after")
# The claim has to be about the picture the block points at. A clause
# standing in one asset's block while naming another's digest is a
# sentence nothing in this build writes.
if pointer.group("asset").startswith(after[:12]):
found[clause.group("before")] = after
return found""",
""" return {m.group("before"): m.group("after") for m in _CONVERSION.finditer(bundle_text)}""",
""" declared = _declared_conversions(build)
if not declared:
return {}
found: dict[str, str] = {}""",
""" declared = _declared_conversions(build)
del declared
return {
m.group("before"): m.group("after") for m in _CONVERSION.finditer(build.bundle_text)
}
found: dict[str, str] = {}""",
),
Mutant(
"X4 the claim need not be about the asset its block points at",
GATE,
'if pointer.group("asset").startswith(after[:12]):',
"if True:",
'if (before, after) in declared and pointer.group("asset").startswith(after[:12]):',
"if (before, after) in declared:",
),
# X5 moved suites 2026-09-19. It was felled by the gate's own suite while
# the gate read its claim out of the bundle; once the claim came from the
# run's ledger, a document-supplied field could not reach the gate at all
# and the mutant survived the WHOLE suite -- measured, 2134 passed. The
# property did not stop mattering: the line is in every concept body, and
# a bundle must not state a conversion the run never performed. It is
# measured where it lives now.
Mutant(
"X5 a document-supplied label may emit a checksum field",
"src/llm_ingestion_okf/assets.py",
'return _CHECKSUM_FIELD.sub("sha256 ", collapsed.replace("[", "(").replace("]", ")"))',
'return collapsed.replace("[", "(").replace("]", ")")',
suite="tests/test_assets.py",
),
# PM's sjekkpunkt 2026-09-19 on `ae441ab`: the round above bound the claim
# to a pointer BLOCK, and one HTML file with two `<p>` elements writes one.
# X6 is that state exactly -- the shape believed without the run's ledger.
Mutant(
"X6 a pointer block is believed without the run having booked it",
GATE,
'if (before, after) in declared and pointer.group("asset").startswith(after[:12]):',
'if pointer.group("asset").startswith(after[:12]):',
),
Mutant(
"X7 the run's ledger is read from the bundle instead of the accounting",
GATE,
" accounting = build.accounting\n if not isinstance(accounting, dict):",
" accounting = None\n if not isinstance(accounting, dict):",
),
# The RLE8 cursor rule, judged by the file that measures it against an
# independent decoder. Without a per-mutant suite this could not be asked
# from here at all.
Mutant(
"X8 a stream may end before the cursor reaches the frame",
"src/llm_ingestion_okf/assets.py",
" if y < height - 1 or (y == height - 1 and x < width):",
" if False:",
suite="tests/test_asset_viewable.py",
),
# PM's three survivors from `43331fc`, held until now by ordinary tests
# and not by what runs AS the gate.
Mutant(
"P6 the normalisation door removes U+00A0 as well",
"src/llm_ingestion_okf/extract.py",
' return text.replace(SOFT_HYPHEN, ""), removed',
' return text.replace(SOFT_HYPHEN, "").replace("\\u00a0", ""), removed',
suite="tests/test_soft_hyphen_door.py",
),
Mutant(
"P11 row 3's detail line drops the refused count",
GATE,
'f"unverified={u.unverified} invalid={u.invalid} refused={u.refused}"',
'f"unverified={u.unverified} invalid={u.invalid}"',
),
Mutant(
"P12 row 3's reason drops the refused-whole clause",
GATE,
'f"{refused} element(s) lost with {refused_docs} of {documents} document(s) refused whole"',
'f"{refused} element(s) lost"',
),
)
@ -378,7 +435,16 @@ def main(argv: list[str] | None = None) -> int:
environment["PYTHONPATH"] = str(root / "src")
try:
run = subprocess.run(
[sys.executable, "-m", "pytest", SUITE, "-q", "-x", "-p", "no:cacheprovider"],
[
sys.executable,
"-m",
"pytest",
mutant.suite,
"-q",
"-x",
"-p",
"no:cacheprovider",
],
cwd=root,
capture_output=True,
text=True,