fix: verify says whose the other files in the directory are - #80
Merged
Conversation
A directory is allowed to hold more than one run. output.manifest exists so
that a second run records itself beside the first rather than being refused,
and verify called every file the neighbour had written "extra" - the word for a
file nobody asked for - and then called the directory a mismatch.
Measured on 2026-09-07, two runs one after another into one directory with name
templates that do not collide, both ending 0:
verify manifest-alpha.json 3 differences, exit 7
verify manifest-beta.json 4 differences, exit 7
Every one of those was the other run's work, and which run had written it was
recorded in the same directory the whole time. The report described a directory
somebody had polluted.
They get their own kind now, another-run, carrying the name of the record that
lists them, and they do not count towards the verdict. A shared directory ends
0 and matched is true. A real disagreement is untouched: a missing or changed
file is still a mismatch, still exit 7, and the heading counts only the
disagreements so the list under it does not mix the two.
This is the third time a category has been cut out of Extra for this reason,
after Leftover and Respelled, so the shape is the one already in the file.
The rule it follows is attribution rather than suppression, and that is the
half worth stating. Every file stays in the report: one entry each in --json,
and in the prose one line per neighbouring record rather than one per file.
Untouchable rule 6 is kept literally rather than on trust, and a manifest
somebody leaves in a directory can claim a file out loud and cannot hide one.
Measured: a neighbour's ten thousand files used to print 10 001 lines and exit
7, and now print one line and exit 0. The grouping is the lesson
manifest.Notes() already carries.
cleanup needed nothing. It walks the manifest's entries rather than the
directory, so it has never known the word.
Two limits, both deliberate and both pinned by a guard rather than left to be
discovered:
- a neighbour's record is only opened when its name ends in .json. Reading
the first bytes of every unlisted file was the first design and was
measured out the same day - opening a file on Windows is not free. The
factor is REFUSED rather than reported, because the canary runs of the
unchanged binary disagreed with each other sevenfold while it was taken.
What is not in doubt is that it was large enough to change the design. A
record under another name is reported exactly as it was before.
- a file no manifest in the directory lists is still extra, so leaving a
manifest somewhere does not account for everything beside it.
A second sieve was written and taken out again the same day. It read the first
half kilobyte of a candidate looking for the first key of a manifest, to avoid
reading a large JSON document in full. Nothing could redden it: a document that
got past it was refused by the schema check behind it anyway, so removing it
changed no answer and no test could tell.
Cost, interleaved with canaries on a directory of ten thousand unlisted files:
228 ms before against 258 ms after, canaries 188 and 194 ms. The canaries agree
to 3% and the two variants OVERLAP - 133-362 against 190-266 - so no factor is
claimed.
Seven guards, seven mutations. Two of the mutations did not compile at first
and had to be rewritten to consume what they replaced, and staleness.py then
found five EXISTING mutations that lifting the classification out of the loop
had invalidated.
Also in here, because the crowding ratchet caught it:
- three functions split out so that the count of functions nesting three deep
stays at 51 rather than being moved up to 53. The ratchet only tightens.
- the comment on worstComplexity named rawTarget.resolveSize, and the guard's
own log has named engine.Run since the run lock went in. The number has not
moved. Who holds it has, and a comment naming the wrong function sends the
next reader to flatten something that is not at the ceiling. O185 records
that Run now sits at exactly 22 of 22.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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.
Stacked on #79, which is merged. This branch is rebased onto it.
A directory is allowed to hold more than one run -
output.manifestexists so asecond run can record itself beside the first instead of being refused - and
verifycalled every file the neighbour had writtenextra, the word for afile nobody asked for, and then called the directory a mismatch.
What was measured
Two runs one after another into one directory, name templates that do not
collide, both ending
0:The report described a directory somebody had polluted. Every one of those files
was the neighbour's, and which neighbour was written down in the same directory
the whole time. The original report described this as being about the second
manifest - it is about every file of that run plus its manifest, and the
count depends on the neighbour's
count.cleanupneeded nothing. It walks the manifest's entries rather than thedirectory, so it has never known the word.
The repair
A new kind,
another-run, carrying the name of the record that lists the file.It does not count towards the verdict: a shared directory ends
0andmatchedis
true. A real disagreement is untouched - missing or changed is still amismatch and still
7- and the heading counts only the disagreements, so thelist under it does not mix the two.
This is the third category cut out of
Extrafor this reason, afterLeftoverandRespelled, so the shape is the one already in the file.Attribution, not suppression
Every file stays in the report. One entry each in
--json, and in the prose oneline per neighbouring record rather than one per file:
Untouchable rule 6 is kept literally rather than on trust, and a manifest
somebody leaves in a directory can claim a file out loud and cannot hide one.
That neighbour's ten thousand files used to print 10 001 lines and exit
7.Two limits, both pinned by a guard
.json. Reading the first bytesof every unlisted file was the first design and was measured out the same day.
The factor is refused rather than reported: the canary runs of the
unchanged binary disagreed with each other sevenfold while it was taken. What
is not in doubt is that it was large enough to change the design.
extra.A second sieve was written and removed the same day. It read half a kilobyte
of a candidate looking for the first key of a manifest. Nothing could redden it -
a document that got past it was refused by the schema check behind it anyway -
so it changed no answer and no test could tell.
Cost
Interleaved with canaries, directory of ten thousand unlisted files:
Canaries agree to 3%, and the two variants overlap, so no factor is claimed.
Guards
Seven guards, seven mutations, all caught. Two mutations did not compile at
first and were rewritten to consume what they replaced.
staleness.pythenfound five existing mutations that lifting the classification out of the
loop had invalidated.
Also in here, because the crowding ratchet caught it
at 51 rather than being moved to 53. The ratchet only tightens.
worstComplexitynamedrawTarget.resolveSize, and theguard's own log has named
engine.Runsince the run lock went in. The numberhas not moved, the holder has, and a comment naming the wrong function sends
the next reader to flatten something that is not at the ceiling.
O185records that
Runnow sits at exactly 22 of 22.Full suite green,
preflight --quickgreen.🤖 Generated with Claude Code