Skip to main content

max / synckit

Record blob.rs's two equivalent index bounds where a run can read them The ruling on unkillable mutants (infra 374e1971) makes `.cargo/mutants.toml` the record and impossibility the bar, and this crate had no such file, so the argument for the two part/chunk index bounds lived only in a source comment that no run consults. Both are provably equivalent: the part loop never sends the final part, and one chunk past the count breaks on an empty buffer. Both entries carry a position. Each function holds other `<` comparisons that are caught, so the description alone would suppress those too. Checked by diffing `cargo mutants --list` against an empty config: 145 mutants become 143, and the two that vanish are these. The size guards are the counter-example and are deliberately absent: they were cost rather than impossibility, and 7bc19e6 made the cap injectable so two tests could stand on either side of it. The source comments keep the code-level claim and point at the file for the mutation argument, rather than stating it twice.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session
https://claude.ai/code/session_013vDpLixQiknHhfHiGxFWo7
Author: Max Johnson <me@maxj.phd> · 2026-08-31 23:42 UTC
Signed with PGP, not checked
Commit: 84451fdf8c9c6083cf5b793e799539cfa3a7d564
Parent: 7bc19e6
2 files changed, +48 insertions, -11 deletions
@@ -626,13 +626,9 @@
626 626
627 627 // Every part but the last is exactly `part_size`; the remainder is
628 628 // the final part, so it is never sent from inside this loop.
629 - //
630 - // `<` and `<=` on the part index are equivalent, provably, so no
631 - // test distinguishes them: `staged.len() >= part_size` can hold at
632 - // `next_part == part_count` only when the ciphertext is an exact
633 - // multiple of the part size, and the extra iteration would then
634 - // send byte-identical bytes to the same URL before the function
635 - // returned down the resume branch regardless.
629 + // The index bound is belt and braces rather than the thing that
630 + // stops the loop, so `<=` would be equivalent here. Recorded in
631 + // `.cargo/mutants.toml` with the argument.
636 632 while staged.len() >= part_size && next_part < start.part_count {
637 633 let body = staged.split_to(part_size).freeze();
638 634 let etag = self
@@ -938,10 +934,8 @@
938 934 header = Some(h);
939 935 }
940 936 // Peel and decrypt every complete chunk currently buffered.
941 - //
942 - // `<` and `<=` are equivalent here too: one chunk past the count
943 - // breaks on the next line against an empty buffer. The bound is
944 - // written as the count because that is what the count means.
937 + // The bound is written as the count because that is what the
938 + // count means; `<=` would be equivalent. See `.cargo/mutants.toml`.
945 939 let h = header.unwrap();
946 940 while next_chunk < h.chunk_count {
947 941 let len = h.sealed_chunk_len(next_chunk);
@@ -1,0 +1,43 @@
1 + # Mutants no test can kill, with the reason each is equivalent to the code it
2 + # replaces.
3 + #
4 + # THE BAR IS IMPOSSIBILITY, NOT COST (infra `374e1971`, settled 2026-08-31). A
5 + # mutant a test could kill, where nobody has written that test, stays a visible
6 + # survivor with a GoingsOn task against it however unappealing the test looks.
7 + # An exclusion list that also absorbs "not worth it" reports zero while real
8 + # coverage gaps sit underneath it, which is the one thing the number is for.
9 + #
10 + # The two in-memory size guards are the worked example of the other direction.
11 + # `data.len() > cap` and `total_in > cap` survived every run because the cap was
12 + # a 4 GiB constant and `>`, `>=` and `==` agree at every size a fixture can
13 + # reach. That is cost, not impossibility: the cap became a per-client field
14 + # (`synckit@7bc19e6`) and two tests now stand on both sides of it. Neither is in
15 + # this file.
16 + #
17 + # The patterns are regexes matched against the WHOLE listing line, `file:line:col`
18 + # included, so `+`, `*` and `|` need escaping. A bare `||` is an empty
19 + # alternation and silently excludes every mutant in the crate.
20 + #
21 + # Both entries carry a position because the description alone is ambiguous:
22 + # cargo-mutants names a mutant by its operator and enclosing function and
23 + # nothing else, and each of these functions holds other `<` comparisons that are
24 + # caught. A stale anchor stops matching and the mutant returns as a visible
25 + # survivor, which is the safe direction. Re-derive by diffing
26 + # `cargo mutants --list` with and without this file.
27 +
28 + exclude_re = [
29 + # `next_part < start.part_count` in the part-filling loop. Every part but
30 + # the last is exactly `part_size`, so the loop never sends the final part;
31 + # `staged.len() >= part_size` can hold at `next_part == start.part_count`
32 + # only when the ciphertext is an exact multiple of the part size, and the
33 + # extra iteration then sends byte-identical bytes to the same URL before the
34 + # function returns down the resume branch regardless. No observation
35 + # distinguishes it.
36 + "src/client/blob.rs:632:58: replace < with <= in SyncKitClient::stream_blob_parts",
37 +
38 + # `next_chunk < h.chunk_count` in the chunk-peeling loop. One chunk past the
39 + # count breaks on the next line against an empty buffer, before anything is
40 + # decrypted or appended. The bound is written as the count because that is
41 + # what the count means, not because it is what stops the loop.
42 + "src/client/blob.rs:940:30: replace < with <= in SyncKitClient::blob_download",
43 + ]