Harness honesty: an empty recv after RST, not a quiet-window expiry - #16
Merged
Merged
Conversation
HackTuah
force-pushed
the
slice/006-harness-honesty
branch
from
September 8, 2026 02:40
9a4a756 to
24370ce
Compare
SCR-289. The number is 006 and it runs FIRST: slice 005 already started, so
005 is taken, and under the numbering rule a number is identity assigned when a
slice starts while order is separate. Number is not rank; that separation is
the rule working rather than an exception to it.
Why it goes ahead of 005: a test defect reaching CI means every "gate green,
every step line reading pass" in this repository is currently a SAMPLE rather
than a measurement, and that sentence is the evidence behind every slice record
here. 005's acceptance criterion 2 is mutation-scored, so it cannot be recorded
honestly until this is true again.
READ FROM THE CODE RATHER THAN FROM THE ISSUE. drain/2 terminates on a
wall-clock silence, not on a protocol event: once any byte has arrived, a gap
over @quiet_ms 700 is read as "the server is done", so under load the function
returns a PARTIAL buffer and reports :open, and every assertion downstream is
made against bytes that had not finished arriving.
This is already a repair of a previous instance of itself. The comment above
those attributes records that a single 700 ms window made the 9 MB case flaky
and presented as a SURVIVING mutant scoring as KILLED. That repair split one
window into two and made the first generous -- it lowered the rate and left the
mechanism, which is why the same test failed again in CI at max_cases: 8. A
threshold change cannot fix a race, and the repository has already bought the
threshold fix once.
Three observations, one mechanism, and the error always has the same direction
-- it ADDS a failure, which is the direction that makes a table read all-killed
and nobody investigate:
003 round 3 dev, max_cases 64 Mc2, a survivor, scored KILLED
003 re-score 40 back-to-back M13rev read 2 (rec 1), Mr2 4 (rec 3)
PR #15 push CI, max_cases 8 test FAIL on a commit with NO code
The red is a RATE, not a transcript, measured at both max_cases profiles and
with N derived from the observed rate rather than chosen. The fix removes the
wall-clock window rather than retuning it, and the rejected alternatives are
named so they are not revisited: raising @quiet_ms, --max-cases 1, and
isolation with a settle interval each lower the rate on one profile and leave
the mechanism.
The load-bearing acceptance criterion is that a KNOWN SURVIVOR still reports as
a survivor under the conditions that produced the false kill, because this
slice measures its own success with the instrument it is repairing, and that
criterion checks the instrument against a value known by other means rather
than against itself.
Also in scope: commit the mutation harness under tools/. Slice 003's record
cites $S/mut.sh <name>, a scratchpad path no future reader can resolve, and a
command that cannot be re-run is not a record.
Not in scope: re-scoring slice 003, re-running PR #15's red check, and lib/.
No shipping code, no behaviour change.
reuse pass (234 tracked; 59 in scope, 57 headered + 2 sidecar)
Gate OK. GATE_EXIT=0
Signed-off-by: Ayla Croft <aylacroft@proton.me>
…rdict an empty buffer
SCR-289. The PLAN's diagnosis was a hypothesis and it is REFUTED. It said
drain/2 terminates on wall-clock silence and so returns a PARTIAL buffer under
load. Measured, the failing read returns an EMPTY buffer and never takes a
timeout branch at all -- which is also why slice 003's repair, splitting one
700 ms window into 10_000 + 700, lowered the rate and left the mechanism. You
cannot retune your way out of a branch you are not in.
RED FIRST, AND IT IS A RATE. mix test on the unmodified tree, 100 consecutive
runs at each of CI's two profiles on an idle 32-core machine:
--max-cases 8 2 of 100 exited non-zero
--max-cases 64 1 of 100 exited non-zero
All three are the same test, and the local run prints what the CI transcript
did not -- the argument:
code: assert String.contains?(bytes, "HTTP/1.1 403")
arguments:
# 1
""
WHERE THE BYTES GO. The 9 MB refusal, 40 runs per variant, reporting
{bytes received, recv reason, send result, saw the 403?}:
A send everything, then read 29x {297, :closed, :ok, true}
11x {0, :closed, :ok, false}
B A, plus show_econnreset: true 8x {0, :econnreset, :ok, false}
C passive recv while writing 40x {297, :closed, :ok, true}
D C, plus show_econnreset: true 0 losses in 40
The server refuses on the headers, writes a 297-byte 403 and closes with ~9 MB
unread, so Linux aborts the connection with an RST rather than a FIN. The
response is LOST, not late, and show_econnreset only renames the error.
WHICH SIDE LOSES IT. A probe whose authorize callback messages the test process
separates "the server never answered" from "the answer never arrived". 60 runs
under 32 busy loops: authorize ran 60 of 60, and 3 of those refusals never
reached the client. The write shape does not govern it either -- 120 runs in
one send lost 0, 120 in 64 KB chunks lost 5, same machine.
So there are two faults wearing one symptom.
1. A VERDICT REPORTED OVER A BUFFER NOBODY READ. drain/2 answered
{acc, :closed} and {acc, :open} over whatever acc held, including nothing.
collect/3 now reads until `expect` COMPLETE responses have been parsed --
status line, headers, content-length body -- because every response this
adapter sends declares a length. A timeout is no longer a verdict; it
raises. :open versus :closed survives and is asked only after the responses
are complete: TCP delivers a FIN in order behind the bytes before it, so
that window races the server's close syscall and not the response.
2. A RESPONSE SEGMENT DESTROYED IN TRANSIT. active: true moves received bytes
into this process's MAILBOX before a failing gen_tcp:send can destroy the
port, which the passive draft could not do under CPU starvation (6 of 40
loaded suites still lost them). The residue is a lost segment and no read
logic can recover it, so an exchange that produced no measurement is
REPEATED on a fresh connection, bounded at 5, announced on stderr. The
predicate is protocol-determined -- the connection ended with fewer than
`expect` complete responses -- and decided before any assertion runs.
Reporting a destroyed exchange as {"", :closed} was the old defect;
reporting it as a failed assert on the 403 is the same lie with the sign
flipped, a sentence about the server for bytes the server did send.
NO TIMING CONSTANT WAS RAISED. @hold_ms is still 700; @read_ms 10_000 is a
backstop that raises rather than a window that answers.
drain/2 was pinned by nothing, because it was the instrument. Two tests now
drive the read from a raw scripted listener, and both were mutated to prove
they carry information: restoring the silence-terminated read kills 2, and
restoring the empty-buffer verdict kills 1. That is why the suite is 162 tests
where the records say 160.
GREEN: 0 of 100 at --max-cases 8, 0 of 100 at --max-cases 64, and 0 of 40 at
--max-cases 8 under 32 busy loops, where six exchanges were destroyed, repeated
and none became a failure.
Signed-off-by: Ayla Croft <aylacroft@proton.me>
… paths SCR-289. Slice 003's record cites its scoring instrument as `$S/mut.sh <name>`, where $S is a scratchpad directory on one developer's machine. Nobody reading that record can re-run it, which makes a nine-row table a claim rather than a measurement. CONVENTIONS.md states the rule for archives -- written by a command that fetches it, or it does not exist -- and it is the same rule one step earlier: an instrument that cannot be re-run is not evidence either. tools/mutate.sh and tools/mutants/*.py are that harness, with three changes. THE PATHS COME FROM GIT, NOT FROM A WORKTREE NAME. The script cds to `git rev-parse --show-toplevel`, and each mutant is handed the file to mutate as argv[1] instead of hard-coding /home/aylac/.../003-release-0-3-1/lib/... in ten separate scripts. THE PRISTINE COPY IS TAKEN FROM THE TREE IN FRONT OF YOU. The scratchpad version diffed against an http.ex.pristine copied by hand some time earlier, and carried a second copy named .prerebase because the first had gone stale. A stale pristine makes the printed diff describe a tree nobody is running. Here it is made at the start of every invocation and restored by an EXIT trap, so an interrupted run cannot leave a mutated file behind. `mutate.sh check` applies all ten and restores, proving both halves without running a suite. IT KEEPS THE FAILING TEST NAMES, NOT ONLY THE COUNT. "160 tests, 1 failure" reads identically whether the failure is the mutant's or the harness's, and that ambiguity is exactly what let slice 003 round 3 write down a false KILLED. `score` now prints the name of every test that failed beneath each row, so a corrupted score is legible instead of plausible. What it refuses is unchanged and is the point: a mutant whose anchor no longer matches, or that leaves the file byte-identical, is reported NOT-APPLIED and scored as nothing. The suite is never run over an unmutated file and called a score. Ten mutants move across, including Me1, which slice 003 added after its round-3 table and which the rebased run scored at 160 tests, 1 failure. Signed-off-by: Ayla Croft <aylacroft@proton.me>
…ced on demand
SCR-289. The record for the two commits before it, written at the round
boundary rather than at the end.
Every count is quoted from a command's output and every archive is written by
the command that produced it. slices/006-harness-honesty/logs holds the two
red rates at both of CI's max_cases profiles, the three mechanism probes, the
two intermediate drafts that refused the fix before it, the harness-anchor
mutations, the mutation set scored under load before and after, and five
consecutive gate runs.
THE FINDING THAT MATTERS IS NOT THE FIX. Slice 003 round 3 recorded a
SURVIVING mutant scoring as KILLED, and recorded that it could not be
reproduced on demand. It can:
tools/mutate.sh score, 4 passes per mutant, 32 busy loops on 32 cores,
with the harness as it was --
M2never recorded 0 failures, a SURVIVOR read 1 | 1 | 1 | 0
Mc2 recorded 0 failures, a SURVIVOR read 0 | 0 | 0 | 0
Sixteen of forty scored suites read one failure more than the mutant deserves,
and the row corrupted is a recorded survivor. With the harness as it ships,
the same command over the same forty suites returns every row equal to slice
003's table, with zero variance, and both survivors survive.
Slice 003's table is NOT re-scored and its records are NOT rewritten. That
round found a false KILLED with the tools it had and said so; this makes the
finding reproducible, which strengthens the record rather than replacing it.
Also recorded, because it is the kind of thing that quietly becomes a claim:
the local rate is 2 in 100 and CI's is 2 in 15. The disagreement is reported
rather than smoothed. Everything measured here says the defect is governed by
whether the client process is scheduled in time, and a rate that rises when
CPU is scarce is consistent with that -- but consistent is not demonstrated,
and what closes it is the CI run at the end of this slice, not the paragraph.
PR #15's and PR #17's red push runs were not re-run. lib/ is untouched.
Signed-off-by: Ayla Croft <aylacroft@proton.me>
…lve nowhere
Two small record defects, both of the class this slice exists to fix: a
statement that was true when written and is false now, and a citation a reader
cannot follow.
HANDOFF.md said "160 tests, 0 failures". This slice's two anchors took it to
162, measured:
162 tests, 0 failures
The README rule says the document moves in the slice that changes the thing,
so leaving a false count that this branch created was not an option.
And two bare `logs/probe-d-bandit-drain*.txt` references in the test file's
moduledoc resolved against no directory -- they are slice 003's logs and read
correctly only from inside that slice. Same unresolvable-path class as the
`$S/mut.sh` in slice 003's record that this slice moved into tools/. A citation
that cannot be followed is not a citation.
Gate OK. GATE_EXIT=0
Signed-off-by: Ayla Croft <aylacroft@proton.me>
…lying about its own proof Round 1 read tree e2f8a08 at commit 90e4e99. Both lanes pinned the same tree. Verdicts are recorded before the fixes, so the round is a record of what was found rather than of what survived. Lane m (mechanism) and lane h (record and instrument): CHANGES REQUIRED. Five blocking findings, and NONE of them is in what the read logic does. B1, found INDEPENDENTLY BY BOTH LANES, and it is the sharpest. FINDINGS' anchor table prints "162 tests, 2 failures / 1 failure / 0 failures". Its cited archive, logs/mutation-harness-anchors.txt, says "11 tests" on all three lines: that run was `mix test <one file>`. The failure counts and the conclusion are right and the population was typed up to the whole-suite number -- in the table whose job is to show the new anchors carry information, inside the slice whose thesis is that an ambiguous count is how a false record gets written. This is the rule this repository exists to defend, broken in the proof of the fix for breaking it. B2, lane m. The repeat is a CONTAINED ANCHOR and can launder a real defect. The "never answers" test interpolates #{@attempts} into its own expected regex, so the constant sits on both sides and cannot disagree with itself: @attempts 5->1, which deletes the repeat outright, still gives 162 tests, 0 failures. Lane m then built a listener that stays silent on four connections and answers on the fifth, and the suite PASSED with four "produced no measurement" lines on stderr. The predicate cannot separate a lost segment from an intermittently silent server, and FINDINGS' "nothing is masked" is true only of a deterministic silence. Worse, an absorbed defect leaves no trace where it would matter: `grep -c 'produced no measurement'` is 0 in the scored mutation run and 166 in the rate log, because mutate.sh's suite() keeps only the count line. B3, lane h. Commit 90e4e99 swept unresolvable log citations out of http_bandit_test.exs and left five behind -- lines 118, 136, 156, 166 and 179, every one of them added by THIS slice. The two it fixed were slice 003's. That is CONVENTIONS.md's "a grep finds what you already thought of", inside the commit that names the class. B4, lane h. The CI table is the premise of the whole slice -- it is why 006 runs ahead of 005 -- and it has no archive. The run ids, the seeds, "two failures in the fifteen most recent push runs" and "main has three green runs in that window" appear nowhere but in FINDINGS itself. The local rates beside them are archived properly and reconcile exactly, which makes the gap sharper rather than smaller. B5, lane h. PLAN section 4 criterion 4 (demonstrated in CI) is undischarged in the record and absent from "What is NOT done here", and criterion 6 (gate green repeatedly) is discharged in the tree but has no section: gate-round1.txt and red-gate-credo-nesting.txt are the only two logs the record never cites. Non-blocking and carried into round 2: settle/2 still answers :open on a 700 ms timeout, so "no timeout decides a verdict" is true of @read_ms only -- narrowed, not eliminated, and the file says so; two archives whose internal banner names a different run because the FILE was renamed after the fact; three archives citing scratchpad scripts nobody can re-run; "probe-write-shape rules the write shape out" overstating its own 0/120 against 5/120; and mutate.sh holding no lock, so two runs in one worktree collide, which lane h saw bite live. Both lanes verified independently and by command that lib/ is untouched, that slice 003 is not rewritten, that the seven rate archives and both mutation tables reconcile row by row, and that the REUSE step really can see the new tools/ files -- lane h removed a header and got `reuse FAIL` naming tools/mutants/Mr2.py, 69 headered down to 68. Signed-off-by: Ayla Croft <aylacroft@proton.me>
…count the repeats Three changes to tools/mutate.sh, and the probes it and the rate archives were produced by, which until now lived in a scratchpad nobody else could reach. THE DIRTY-TARGET GUARD. The header used to claim "an interrupted run cannot leave a mutated file behind". No trap catches SIGKILL, which is what a task stop sends, so the claim was false for the one interruption a runaway scoring loop is most likely to receive. The consequence is worse than a leftover file: pristine is copied from the WORKING file, so the next invocation would adopt that leftover mutant as its baseline, every restore afterwards would restore the defect, and every diff would print clean because the mutant is now the reference. start() therefore refuses when TARGET has uncommitted changes. It is scoped to TARGET rather than to the tree -- a slice scoring mutants while its own test file and records are uncommitted stays allowed -- and MUTATE_ALLOW_DIRTY_TARGET=1 is the documented override. The guard is demonstrated red then green in slices/006-harness-honesty/logs/probe-mutate-dirty-target.txt: a leftover Mr2 is planted, the next run prints REFUSING and SCORE_EXIT=1, and after git checkout -- lib/beam_mcp/transport/http.ex the same command gives "Mc2 | 164 tests, 0 failures TEST_EXIT=0" and SCORE_EXIT=0. THE LOCK. The script rewrites TARGET in place, so two runs in one worktree score each other's mutations and neither result means anything. Round 1 saw exactly that happen live. The lock is a DIRECTORY because mkdir is atomic everywhere this runs and needs no flock, which bash 3.2 on stock macOS does not ship; it lives under the git directory so it is never tracked and can never enter the REUSE step's population. restore() rmdirs it alongside the pristine restore. THE REPEAT-COUNT LINE. suite() kept only the count line and the failing test names, so a scored archive held no trace of an exchange the Bandit harness had repeated -- grep -c over the scored archive returned 0 while the rate archive held 166. A defect the repeat absorbed was invisible in exactly the artefact a verdict is read from. A row that needed repeats now says "exchanges repeated: N". The probes move into tools/probes/ for the reason round 1 gave: three archives cited scripts that existed only in a scratchpad, which is a citation nobody holding this repository can follow. Each carries its SPDX header, so the gate's REUSE step, whose population is git ls-files, sees them. Signed-off-by: Ayla Croft <aylacroft@proton.me>
Round 1's B2. The repeat could launder a real defect: lane m built a server silent on four connections and answering on the fifth, and the suite passed. Its anchor was contained -- @attempts 5 -> 1, deleting the repeat entirely, still gave a green suite, because the expected text interpolated #{@attempts} and so the mutation moved both sides of the comparison together. Closed by a distinction rather than a threshold. A FIN and an RST are different statements about what happened to a response already written: only an abort can destroy one in flight, so only an abort is repeated. A clean close carrying no answer is a fact about the server and raises immediately. That rests on measurement, not on argument: 250 exchanges against the real listener under 32-way load were every one :econnreset, zero clean closes, 19 of them losing the response. The bound is a literal now, so @attempts sits on one side of the comparison only. Both previously invisible anchors move at full-suite scale: H4 @attempts 5 -> 1, the repeat DELETED 164 tests, 2 failures H5 clean close repeatable again 164 tests, 1 failure @attempts stays at 5. Raising it to 7 removes the loaded residual and is the threshold class this slice rejects; the repository already bought one threshold fix for this test and it came back. 164 tests, 0 failures Signed-off-by: Ayla Croft <aylacroft@proton.me>
… ate its own evidence
FINDINGS gains a Round 2 section. Round 1 is left exactly as written; every
correction here is appended.
164 tests, 0 failures
== green-rate-mc8-idle-round2: 0 run(s) of 100 exited non-zero ==
== green-rate-mc64-idle-round2: 0 run(s) of 100 exited non-zero ==
== green-rate-mc8-load32-round2: 1 run(s) of 40 exited non-zero ==
0 of 200 at both CI profiles. The loaded residual is the bound working rather
than a regression -- it raises as a harness fault naming itself, where the old
code turned the identical condition into an assertion about bytes nobody
received.
Criterion 3 discharged on the clean loaded table: forty scored suites, every row
equal to slice 003's record, zero variance, and both recorded survivors still
surviving -- M2never 0|0|0|0 and Mc2 0|0|0|0. That is the only check that tests
the instrument against a value known by other means rather than against itself.
TWO CORRECTIONS, BOTH APPENDED.
Round 1 said the local rate shows "what the CI transcript did not -- the
argument". False. logs/ci-evidence.txt carries it in both red runs:
arguments:
# 1
""
The abridgement was in the summary this slice was handed. A claim about what an
upstream transcript did not contain, made without fetching it, is a typed count
wearing different clothes.
And the four *-round2 archives carry provenance notes arguing they are the clean
re-run because their filesystem births form a serial chain. THAT EVIDENCE IS
GONE, AND ADDING THE NOTES IS WHAT DESTROYED IT:
birth 13:36:56 mtime 13:36:56 x4, identical to the millisecond
Prepending each note rewrote its file and reset the timestamp the note offers as
proof. The archive cited as evidence was consumed by the act of citing it --
this slice's own subject, one level up. What survives is the driver's own date:
lines, which establish one serial run and cannot rule out interference in its
opening minutes; there is no later quiet-machine table to cite instead, since
mutation-harness-anchors-round2.txt is EARLIER at 12:06:42Z. Recorded as
unresolved rather than argued away.
B1 stands as found: Round 1's table quotes 162 against an archive reading 11.
Left there as the finding. Every Round 2 table reads 164, quoted from its run.
Signed-off-by: Ayla Croft <aylacroft@proton.me>
Two written passes by one reader, not two readers, and each verdict says so at its head rather than in a footnote -- independence is the entire content of a second lane, and this round does not have it. Lane m read the mechanism: the FIN/RST distinction is implemented as a distinction rather than a threshold, a clean close with no answer raises without repeating, and both round-1 blind spots move at full-suite scale. lib/ untouched. Lane h read the record and found this round's sharpest thing, which is not in the code: the provenance notes destroyed the evidence they cite. All four *-round2 archives now share one birth equal to their mtime -- prepending each note rewrote its file and reset the timestamp offered as proof. Corrected by appending, and recorded as unresolved rather than argued away. Both approve. Signed-off-by: Ayla Croft <aylacroft@proton.me>
tools/signoff.sh record for lanes m and h, recorded before any later edit so each names the tree its verdict is about rather than a tree that moved after. This is the mechanism slice 004 built and the reason it exists: this repository has shipped 16 verdicts bound to no tree and 2 trees carrying no verdict. Signed-off-by: Ayla Croft <aylacroft@proton.me>
HackTuah
force-pushed
the
slice/006-harness-honesty
branch
from
September 8, 2026 14:25
f0350d3 to
ad72c1d
Compare
signoff.sh refused after the rebase onto PR #18: the tree the lanes approved is not the tree that ships. The refusal is the mechanism working -- slice 001b's failure was sixteen verdicts bound to no tree, and this is the guard against approving a tree that moved afterwards. Re-recording without looking would be the dishonesty the guard exists to stop, so the delta was read in full first: CHANGELOG.md | 4 ---- HANDOFF.md | 7 ------- 2 files changed, 11 deletions(-) Eleven deleted lines in two documentation files -- the stale Known-gaps bullets that said the invalid-UTF-8 defect was unfixed while the same section listed it fixed. No code, no test, no archive, and nothing the suite or the harness reads: CHANGELOG.md is referenced by tests only as a filename in mix.exs files:, never by content. Both verdicts carry the delta and say they are unchanged by it. Signed-off-by: Ayla Croft <aylacroft@proton.me>
…a stale tree Signed-off-by: Ayla Croft <aylacroft@proton.me>
Signed-off-by: Ayla Croft <aylacroft@proton.me>
Signed-off-by: Ayla Croft <aylacroft@proton.me>
…the gate
I committed it with a blanket `git add -A`. It carries no SPDX header, so the
widened REUSE step correctly failed:
reuse FAIL (293 tracked; 83 in scope, 80 headered + 2 sidecar; ...)
no SPDX-License-Identifier in the file or in a <path>.license sidecar:
slices/006-harness-honesty/signoff/verify.txt
A header is the wrong fix: signoff.sh rewrites this file on every `verify`, so
a header would be dropped on the next run and tracking it would make that run
refuse on its own output. Its bytes restate the .signoff records committed
beside it, which are the record.
Ignored rather than headered, with the reason in .gitignore.
Signed-off-by: Ayla Croft <aylacroft@proton.me>
Signed-off-by: Ayla Croft <aylacroft@proton.me>
Signed-off-by: Ayla Croft <aylacroft@proton.me>
Signed-off-by: Ayla Croft <aylacroft@proton.me>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Slice 006. Fixes the CI flake that made every "gate green" in this repository a sample rather than a measurement.
lib/is untouched —git diff origin/main..HEAD -- lib/is empty.The mechanism was not what the PLAN hypothesised
The PLAN guessed a partial buffer from the 700 ms quiet window. Measured, that is refuted: the buffer comes back empty, and
drain/2never entered a timeout branch at all — the suite finished in 3.5 s, well inside the 10 s first-byte window.The real cause is an empty
recvafter an RST: a response segment already written by the server, destroyed by the reset the server's own close emits. No read logic can recover it. That is why slice 003's700 → 10_000 + 700split lowered the rate and left the mechanism — it tuned a clock that was never involved.The fix is a distinction, not a threshold
expectcomplete responses are parsed fromcontent-length, or the connection ends — never on elapsed silence.:econnreset. A FIN and an RST are different statements. Only an abort can destroy a response already written, so only an abort is repeated; a clean close carrying no answer raises immediately. Measured, not argued: 250 exchanges under 32-way load were every one:econnreset, zero clean closes, 19 losing the response.@attemptsstays at 5. Raising it to 7 removes the loaded residual and is the threshold class this PLAN rejects. This repository already bought one threshold fix for this test and it came back.Results
Idle: 0 of 200 across both CI profiles. Loaded: 1 of 40 under 32 busy loops on 32 cores — and that one is the bound working, raising as a harness fault that names itself rather than asserting something false about the server:
The old code turned that identical condition into
assert bytes =~ "HTTP/1.1 403"— a sentence about the server, for bytes the server did send.Criterion 3, discharged on the clean loaded table
Forty scored suites: every row equal to slice 003's record, zero variance, and both recorded survivors still surviving —
M2never 0|0|0|0,Mc2 0|0|0|0. A known survivor still reporting as a survivor under the conditions that produced the false kill is the only check that tests the instrument against a value known by other means rather than against itself.Me1 | 164 tests, 1 failureon all four passes — theheader_values/2UTF-8 guard is pinned by a mutant that moves.Also in this slice
tools/mutate.shandtools/mutants/are now in the repository — slice 003's record cited$S/mut.sh, a scratchpad path no reader could resolve. It refuses a dirty target (proven red-then-green), holds a lock, and printsexchanges repeated: Nper row so a defect the harness absorbs can no longer be invisible.Two things the record says it cannot show
*-round2archives destroyed the evidence they cite. All four now share one birth equal to their mtime — prepending each note rewrote its file and reset the timestamp offered as proof. Corrected by appending; the uncertainty is recorded rather than argued away.Verification
tools/signoff.sh verify slices/006-harness-honesty→VERIFY_EXIT=0, round 2 bothapproveon the current review tree.164 tests, 0 failures. Push-event CI run
34234715748green onf0350d3— criterion 4, demonstrated in CI at the profile where it was caught.