test(release): drive the inconclusive publish guard with registry census cells - #1604
Conversation
a246630 to
5a5c888
Compare
…sus cells The publish preflight refuses when the exact-version census cannot be read, but no fixture drove that state, so the guard was unverified and deleting it whole left the suite green. readExactVersion returns `unknown:<status>` for any response that is neither 200 nor 404, and `unknown:<message>` when the fetch throws. Both feed the inconclusive rung of the verdict ladder. Every existing fixture answered 200 or 404, so neither branch had ever executed under test. This adds the fixtures that drive it: a registry that answers 503 for one package, a registry that answers 302 for every package, and a transport that throws instead of answering. Each reaches the inconclusive refusal, and the cells assert the census names the unreadable package and carries its status or thrown message, and that the refusal happens before any OIDC exchange, trust read, or write-shaped call. Five mutations carry expectRed so the guard cannot be removed silently again: deleting the block whole, treating an unreadable status as absent, swallowing a thrown read as absent, reordering the rung below all-absent, and dropping the census print that names the offending package. The incomplete rung is a different case and is deliberately not deleted. With no unknown rows, zero present rows forces absent === rows, which the earlier all-absent rung already claims, so no composition of the current status domain reaches it. A cell now pins that domain to present, absent and unknown, so if a later change adds a fourth outcome the backstop becomes reachable and the cell reds rather than leaving a deletion to look safe. Refs #1584
…lable reorder The mutation proof reported one survivor: moving the inconclusive rung below all-absent left every cell green. That looked like a coverage gap and is not one. A row counted in absent is never counted in unknown, so absent === rows already implies unknown === 0 and the two orderings cannot disagree. Enumerating all 35 present, absent and unknown partitions gives 0 disagreements, and applying the reorder to the module itself gives byte-identical verdicts across all 27 status compositions. Both comparators were proved able to report a disagreement first, against a planted ordering that genuinely differs. So the mutation is equivalent, and no fixture can kill it. Writing a cell to chase it would have produced a test that passes for the wrong reason. The mutation is dropped and the premise it depended on is pinned instead: a cell now asserts each census row lands in exactly one bucket, so if a later change lets a row count twice the ordering stops being safe and a named cell reds. Refs #1584
While testing whether the new domain cell actually catches the drift it claims to guard, the drift run produced no verdict from that cell at all. The cell had not passed, it had never executed: the manual token census calls the preflight without a guard, so a refusal there aborts the file and every cell below it reports nothing. Green by not running and green by passing are indistinguishable to a reader counting failures, which is what made the first reading of that run wrong. The exposure is pre-existing, and it is the same class the suite exists to catch. The unguarded call maps 404 to absent and reaches ready, so it refuses only if the 404 mapping changes, which is precisely the drift the domain cell is there to detect. The cell meant to catch that drift was among the cells the drift silenced. The call now captures its own refusal and names it, so the cells below it still run. Measured: under the out-of-domain drift the suite previously aborted after 7 reds with no completion marker, and now completes with 23 reds including both domain cells by name. On the committed behaviour the suite is 69 passed, 0 failed. Refs #1584
The domain cell added earlier drives HTTP 200, 404 and 503 and records what the census carried. That is an observation over the statuses it sends, so a fourth outcome guarded by a status it never sends escapes it: a branch returning a new value on 418 leaves the suite fully green. A review of this branch demonstrated exactly that with a parse-checked mutant, which falsifies the claim that the cell reds when a fourth readExactVersion outcome appears. Add a second cell that reads the shipped source and enumerates every return in readExactVersion, requiring each to be present, absent or an unknown value. A new branch is a new return whatever status guards it, so this half cannot miss an unenumerated outcome. The extractor returns null and reds when it stops finding the function or its body, because an extractor that reports nothing looks identical to a source with nothing wrong. Rename the behavioural cell to say what it actually measures, and pin the escape with a mutation so it stays closed rather than being closed once. Refs #1584
…e line Attacking the new structural cell rather than reasoning about it found a real escape in it. Slicing the function body to the first occurrence of a newline, closing brace, newline sequence is defeated by a template literal that contains one: the slice ends early, and every return below that point, including an out-of-domain one, falls outside the extracted body and is never examined. The cell stayed green on a source that returns a fourth census value. Walk brace depth from the opening brace instead, skipping string, template and comment context so a brace inside any of them cannot close the body. Measured against eight sources, each parse-checked at exit zero and each asserted byte-different from the real file before scoring: the real source greens, and all seven attacks red, including the template-literal truncation the previous spelling missed and a renamed function, which reds by refusing to extract rather than by passing. Refs #1584
…pplies The verdict ladder filtered rows with three inline expressions, so a test that wanted to check bucket disjointness had no shipped definition to depend on and had to transcribe them. A transcribed predicate grades the transcript: widening the shipped absent bucket leaves the copy green, which is what made the disjointness cell unkillable. Name the three predicates, export them, and have the ladder call them, so the membership rule a test imports is the one the release path actually applies.
… a regex The domain cell claimed every return in readExactVersion lands in the census domain, and rested that universal on /\\breturn\\s+([^;]+);/g over a brace-walked body. A panel killed the instrument, not the claim: a bare `return;` carries no expression, so the regex matched nothing and the cell stayed green while the function gained an undefined return; and a semicolon-free return was swallowed into the next capture, laundering an out-of-domain value into a domain-looking blob. Both parse. Both change behaviour. Neither could red the cell. Enumerate ReturnStatement nodes with the TypeScript compiler instead, excluding nested functions, and accept a return only when the syntax pins its value: the string literals present and absent, or a template whose head is a static unknown: prefix. A bare return is out of domain by construction. The cell now reports the count it found in its own name, so a changed return count is visible in the transcript rather than implied. Grade the instrument too: eight escape shapes the retired regex passed clean are asserted to be refused, each parse-checked so it is a change a commit could really land, with a positive control and a nested-function false-positive case so a parser that refused everything could not pose as rigour. Derive bucket membership from the shipped predicates. The old cell retyped them inline and asked for exactly-one membership, which no string can violate, so the overlap it documented was unreachable and widening the shipped absent bucket left it green. It now applies CENSUS_BUCKETS, the predicates the ladder calls, to production rows and to the enumerated return domain, and is renamed to what it proves.
…shipped bucket predicates Five mutations, each proved to bite: the find matches exactly once, applying it changes the file, and applied alone the suite reds with the NAMED cell red. Two are the escapes that killed the retired regex, now landed on the real source: a bare return, and a semicolon-free return closed by ASI. Both parse (node --check exits 0) and both previously left the cell green. One widens the shipped absent bucket to overlap unknown. That is the overlap the previous disjointness cell documented as its purpose and could not detect, because it retyped the predicates instead of importing them. Two grade the instrument itself, so it cannot be relaxed back into the defect it replaced: dropping the bare-return refusal, and dropping the nested-function boundary. Entry 27 keeps its mutation and moves to the renamed domain cell. Anchors for the two injected-return mutations span two lines so they stay distinct from entries 0 and 23, which anchor on those same single lines.
…that calls them Found by attacking the new bucket cells rather than by reasoning about them: rewriting one CENSUS_BUCKETS entry to an equivalent inline arrow left all 105 cells green. The exported table agreed with the ladder today but was free to diverge, which would have reintroduced the transcription defect one layer up: the suite would grade a copy again, just a copy with an export keyword on it. Two cells close it. One parses preflightNpmPublish and requires each bucket filter to CALL an exported predicate by name, so exports the ladder stops using cannot stay as decoration. The other requires each CENSUS_BUCKETS entry to be the exported function by IDENTITY, not merely a predicate that agrees. Both carry a mutation, and both were confirmed to red before being committed.
…claration
Both enumerators located their target with a recursive forEachChild that
assigned on every match, so the LAST declaration of that name anywhere in the
file won. A nested same-name declaration therefore replaced the real target
silently, and the printed return count did not move, so the transcript offered
a reader no signal.
Measured, not assumed. With the retired lookup restored:
- a nested readExactVersion decoy with four in-domain returns hid a real
function returning "outside-domain"
- a nested preflightNpmPublish decoy calling all three predicates by name
returned the ladder cell to green while the shipped ladder had already
drifted off the exported absent predicate
Both now locate their target as exactly one top-level FunctionDeclaration taken
from parsed.statements. Nothing nested can be selected because nothing nested is
looked at. Zero and two or more are both refusals carrying the COUNT, because
"found 0" and "found 2" are different repairs.
The return-walk boundary was a hand-rolled six-way union that omitted
ConstructorDeclaration and ClassStaticBlockDeclaration, so a local class
constructor's return was attributed to the enclosing function and reddened a
clean source with five enumerated returns. It is now
ts.isFunctionLike(node) || ts.isClassStaticBlockDeclaration(node), pinned in one
place. ts.isFunctionLike covers constructors; a class static block is not
function-like to the compiler, so it is named separately.
Five cells, each verified to red against the exact retired implementation:
114 passed 0 failed at this head, 110/4 with the old lookup restored and
112/2 with the old boundary restored.
Disclosed: a return inside a class static block parses under TypeScript but node
rejects it as an illegal return statement, so that cell grades the boundary
predicate rather than a change a commit could land. It is kept for that reason
and says so where it stands.
…ary too Found by attacking the pin I had just added, which is the only way to learn that a fix is half a fix. Pinning the target to one top-level declaration stops a SIBLING decoy from being SELECTED. It does nothing about a decoy declared INSIDE the real function, whose `filter` calls the walk was still collecting into the same list. Measured at that intermediate state, with the top-level pin already in place: the shipped ladder with the absent comparison inlined, plus a nested decoy `preflightNpmPublish` calling isAbsentRegistry, produced callees [isUnknownRegistry, isPresentRegistry, isAbsentRegistry] and the cell stayed GREEN over a ladder that had already drifted. Location and attribution are two claims and the pin makes only one of them. The ladder walk now stops at the same boundary the return walk uses, and the matched `filter` call does not recurse into its own arrow, because the callee name has already been read and nothing below it is the ladder's code. 115 passed, 0 failed. The real ladder cell stays green, which is the control that says the boundary did not simply stop finding everything.
… shadowing appends Four mutations, preserving all 45 existing entries (49 total, 49 unique names). One per rule: - the census top-level pin reverted to the retired last-match-wins lookup - the return-walk boundary reverted to the hand-rolled six-way union One per enumerator that APPENDS a shadowing nested declaration, applied to the shipped source rather than a synthetic one: - a nested readExactVersion decoy beside a poisoned real function - a nested preflightNpmPublish decoy beside a drifted unknown comparison Bite evidence, measured per mutation, applied ALONE, with no report-all switch in the environment (that variable does not exist in this repo at all; grep is empty with a passing positive control, so "unset" is the only state available): [45] anchor 1x, changes file, suite rc=1, 110/5, names its cell [46] anchor 1x, changes file, suite rc=1, 113/2, names its cell [47] anchor 1x, changes file, suite rc=1, 114/1, names its cell [48] anchor 1x, changes file, suite rc=1, 114/1, names its cell Every `find` is code-only; the fixture suite's prose gate reports 0 anchors spanning prose over 2529 anchors. Also re-anchored the existing nested-function mutation at index 42. Renaming the boundary predicate rotted its anchor to 0 matches, and the obvious repair was ambiguous: the single line `if (isReturnScopeBoundary(node)) return;` now occurs TWICE, because the ladder walk was given the same guard. It is anchored on a two-line code-only window that pins the census walk specifically. The nearest comment would have been unique for free, which is the trap the fixture suite exists to catch.
… real red as WRONG-RED Found by running the full proof rather than trusting the previous run's summary. Mutation 37 reddened exactly the cell it aims at and mutation-proof still graded it WRONG-RED, because its expectRed quoted a cell name that no longer exists. Not caused by this branch's changes. Measured at the base commit 23fc9d2a: the expectRed string occurs 0 times in the suite file there, with a positive control confirming the grep works. An earlier commit rewrote the enumerator to use the compiler, reworded the cell, and left the tag on the retired wording. The cell name is a template literal carrying the live return count, so the tag now pins the invariant substring rather than the whole rendered line. A count change cannot rot it again, which is what happened to the anchor at index 42 in the previous commit for the analogous reason.
…ch one it is
Self-review caught a real error in the previous commit. The cell that pairs the
ladder decoy with a fixture drift described itself as applying "fixture entry
44", and entry 44 by name is the CENSUS_BUCKETS drift, not the ladder-inline
drift. It applies the ladder one. The comment named the wrong entry.
The distinction is not cosmetic, and the two are easy to confuse because they
inline the same absent comparison and sit adjacent in the fixture. They touch
different code. Measured: the bucket entry rewrites the module-level
CENSUS_BUCKETS table, which is OUTSIDE preflightNpmPublish, so the ladder
enumerator cannot see it at all. Applying it leaves that enumerator's callee list
byte-identical, so it could never have defeated the cell in question.
A unique anchor is not necessarily the right anchor, and a named entry is not
necessarily the right entry.
Four changes, each with a control run:
- both drift entries are now read out of the fixture BY NAME and asserted to
match the strings this file uses, so the cell grades the mutation the fixture
actually ships. Pointing it at the bucket entry reds 1 cell.
- the bucket entry is separately asserted to change the source while leaving
the ladder enumerator's reading identical, which is the measurement that
proves the two are not interchangeable.
- the bucket drift IS graded, by a runtime identity comparison over imported
function objects, which no textual decoy can launder. Stated where it stands
rather than left implicit.
- the by-name lookups must RESOLVE. Found by control: renaming one fixture
entry silently DELETED three cells from the run and reported zero failures,
because a missed lookup returns empty and the cells below pass on nothing. A
rename is a legitimate later commit, so it now reds 2 cells instead of
quietly shrinking the suite.
Also repaired a positive control that asserted its anchor occurs exactly once in
the live file. A decoy legitimately makes it occur twice, so the control failed
for the wrong reason under precisely the input it exists to grade.
119 passed, 0 failed. Fixtures 437 files, 2529 anchors, all zero, rc=0.
f160743 to
7d9f125
Compare
… by text presence ladderFilterCallees asks whether the ladder's TEXT contains a call to each exported predicate. A call inside `if (false)` satisfies that and launders a live drift. Measured at 7d9f125, each bucket drifted alone with a dead call retained: unknown drifted + dead call -> 119 passed, 0 failed NOT CAUGHT present drifted + dead call -> 119 passed, 0 failed NOT CAUGHT absent drifted + dead call -> 116 passed, 3 failed NOT CAUGHT EITHER The absent row is a trap: its three reds are the existing positive control and the two decoy cells, all of which anchor on the exact absent line the drift rewrote. They are anchor rot, not detection. ladderConsumedBuckets reads which value registryVerdict actually consumes: the sole top-level ladder, the sole registryVerdict declaration in its own statement list, the identifiers that initialiser reads, resolved to declarations in that same list, each required to be initialised by a call of the exported predicate. A call that is not the initialiser of a consumed binding is never looked at, so there is no guard to relax. Four mutants, one per bucket plus the rename variant where the dead call is made live and the verdict reads an inline copy beside a still-correct binding. The controls are founded on a skeleton assembled from the fixture's own strings and an AST-lifted verdict statement, never on a shipped line a mutant rewrites. The first version anchored on shipped text and the present mutant produced 2 reds, only 1 of them detection.
…not AST-lifted The rename mutant graded WRONG-RED: 5 reds in the transcript, only 1 of them detection. The skeleton lifted the shipped registryVerdict statement out of the module by AST, and that mutant rewrites exactly that statement, so the lifted text changed under the mutant and the clean control stopped being clean. AST-lifting is not rot-proof, it is rot with more steps: it still reads the file the mutants edit. The verdict is now written out synthetically, which nothing edits. The risk that a synthetic stand-in drifts from the shipped ladder is paid for by a new cell that runs the same three shipped bucket lines through the skeleton AND through the shipped module and requires both readings to agree.
…g, not the synthetic skeleton mutation-proof graded this entry WRONG-RED: the suite went red, the detection was real, and the expectRed named the wrong cell. It was tagged on the skeleton cell that describes this same laundering shape, and that cell is synthetic by design, so it does not move when the shipped module is mutated. A mutation of the shipped file must be graded on a cell that reads the shipped file. Re-tagged on the shipped absent rung cell, which is where it actually reds.
The rung pin asked which declaration each consumed bucket callee binds to, and
answered it with a scope walk written in the suite: enclosing source file, block,
function parameters, catch clause, first visible declaration wins. Every revision
of that walk was a better enumeration of the ways a name can be rebound, and each
one was defeated by a shape it did not enumerate. Two more were found at the
previous head, on a tree where all 143 cells were green:
a same-name predicate in the ladder's destructured parameter list 143 passed, 0 failed
`{ var isUnknownRegistry = ... }` in a sibling block, hoisted 143 passed, 0 failed
The first escaped because the walk filtered parameters on `ts.isIdentifier(name)`
and the ladder's only parameter is an object binding pattern, so every binding
inside it was invisible. The second escaped because `var` hoists to the function
scope while the walk read only the statements of the block it was handed. Both are
live: driving the real preflight over a 404/404/410 census under the second moves
the refusal from inconclusive to incomplete with every cell green.
So stop enumerating scope kinds and ask the binder. `ladderConsumedBuckets` now
builds a Program over the module, takes `getSymbolAtLocation` on the call's
expression, follows aliases with `getAliasedSymbol`, and accepts the callee only
when that symbol is the module's exported symbol of the same name, which must have
exactly one declaration and that declaration must be the top-level one. Refusal is
by symbol identity. Binding patterns, `var` hoisting, parameters and catch clauses
are already modelled, because it is the same binder the language uses.
Five controls are added on the skeleton: the two hoisted-var placements, the
destructured-parameter placement with its positive control, and an export-clause
module that must still resolve, which is what makes following aliases the right
comparison rather than a stricter one. Three mutants are added, one per new
placement, each aimed at its bucket's structural cell. The three rung cells are
renamed to state the symbol-identity claim they now make. No behavioural cell is
added for any of the widened values: a cell per input kills one mutant and leaves
every unenumerated sibling alive.
|
Panel complete: three independent reviews of Relayed by the orchestrator. The measurements below belong to the reviewers. All three reviews approve, so this is recorded as green. Each review reproduced the suite baseline at the head, red-ran all six sealed acceptance patches on the structural rung cell for their own bucket, and tried to defeat the symbol-identity resolution with placements of its own; none found an escape that rebinds the callee. Each also confirmed the three body corrections landed on the live body at this head. Review 1:
|
What this changes
The publish preflight refuses a release when the registry census cannot be read, but no fixture ever drove that state. The guard was unverified rather than dead, and deleting it whole left the suite green. This adds the cells that drive it.
Refs #1584.
Direction was established before any code was written
The issue asks whether the guards should be deleted. The A/B differential was built first: A is the base commit verbatim, B is A with exactly one guard block removed and nothing else. Per cell rows, not a pass count.
The differential agreed everywhere, which reproduces the report exactly. That is a result, not a failure to find one, and it does not by itself say the guards are dead. The comparator was checked against a copy of the A rows with a single verdict flipped, and reported that difference, so the zeros above are a finding rather than a broken reader.
Reachability was then argued from the code path and measured against the shipped function, and the two rungs turned out to be different cases.
The inconclusive rung is live, and the fix is fixtures
readExactVersionreturnsunknown:<status>for any response that is neither 200 nor 404, andunknown:<message>when the fetch throws. Driving the shippedpreflightNpmPublishover every composition of three statuses across three packages, 27 in total, the inconclusive rung is reached 19 times.A first version of that enumeration was wrong and is worth naming: it matched packages by substring against the request URL, and the bare name
cotal-aiis a substring of the encoded scoped names, so every request matched the same package and the tally depended only on the last status. It was caught by printing the real URLs. The table above dispatches by call order and asserts, for all 27 rows, that the statuses delivered are the ones intended, with 0 mismatches.So the guard is load bearing and the remedy is the one the issue suggests. Added cells:
With these in place the same deletion that was invisible now reds by name:
The incomplete rung is not deleted, and not covered either
The issue proposes the same remedy for the
incompleteblock. That one is a different case, and deleting it would be wrong for a different reason than the report assumes.With no unknown rows, present and absent partition the census, so zero present rows forces absent equal to rows, which the earlier all-absent rung already claims. Enumerating every present, absent and unknown partition gives 0 that reach
incomplete. The enumerator can emit that label: removing the all-absent rung makes it reachable in 3 partitions, which is the control for that zero.So it is an unreachable backstop, not a tested guard and not a confirmed dead one. Deleting it would remove a refusal that becomes live the moment the status domain grows a fourth outcome, and no test would notice. Instead this pins the premise, so the reasoning cannot rot silently:
The second and third bullets are quoted from the live suite rather than paraphrased, which they were until this revision: the paraphrases matched no cell in the file, so a reader checking them found nothing and could not tell a renamed cell from a deleted one. The second cell's name is a template literal carrying the live return count, so the leading
4moves with the source.A review falsified the first version of this claim, and the correction is the second cell
An earlier revision of this PR carried one cell here and claimed it reds when a fourth
readExactVersionoutcome appears. That claim was FALSE, and a reviewer disproved it with a parse-checked, byte-different mutant adding a branch that returns a new value on HTTP 418: the suite completed with 69 of 69 passed, 0 failed, and the named cell red 0 of 1.The mechanism is worth stating because it is not specific to this file. That cell drives three registry answers and records what the census carried, so it is an OBSERVATION over the statuses it happens to send. A branch guarded by a status it never sends is invisible to it by construction. A sampling test cannot support a universal claim, and the claim was the thing that was wrong, not the cell.
The second cell closes it structurally: it reads the shipped source, extracts
readExactVersionand enumerates EVERY return, requiring each to be"present","absent"or anunknown:template. A new branch is a new return whatever status guards it.Attacking the new cell rather than reasoning about it then found a real escape inside it. Finding the body by slicing to the first closing-brace line is defeated by a template literal that contains one, which ends the slice early and drops every return below it. Walking brace depth while skipping string, template and comment context fixes it. Measured against eight sources, each parse-checked at exit 0 and each asserted byte-different from the real file before scoring:
The last row is deliberate. An extractor that stops finding its target reds rather than passing, because an extractor reporting nothing is indistinguishable from a source with nothing wrong.
A second reviewer then supplied three more shapes and all three red as well: a
finallyblock overriding the returned value, a helper arrow declared INSIDE the function so its literal is not outside the extracted body, and a near-miss string such as"absent "with a trailing space. Eight attacks, eight reds, one green on the real source.What the escape was actually worth, which is less than the first correction implied
The same reviewer also showed the hazard is narrower than "a fourth outcome appears and nothing notices", and independent enumeration here reproduces it exactly.
readExactVersionends in a catch-all returning anunknown:template, so a genuinely new HTTP status is absorbed and stays in domain: 418 reads asunknown:418. Producing an out-of-domain value requires ADDING A RETURN, not meeting a new status.And if one were added, the ladder still refuses. Enumerating every three-row partition over present, absent, unknown and an escaped value, with the ladder transcribed from the shipped lines:
incompleterung deletedThe control is what makes the zero mean anything, and it also settles the issue's original question. The
incompleterung earns its place because it REFUSES: delete it and seven escaped-value states reach a live recursive publish. Its justification is its own measured refusal behaviour, not the tripwire's promise. That makes the false sentence a real defect in a secondary cell, now fixed, rather than the load-bearing argument for keeping the guard.Both escapes were then driven through the real suite rather than through the probe alone. The reviewer's 418 mutant and the template-literal truncation each produce exit 1 with the completion marker present and exactly one red, named, printing the offending return. The restore was verified by blob sha against the commit in both cases, not by a clean
git status. The reviewer's mutant is also pinned in the mutation config, so the escape stays closed rather than having been closed once.The backstop claim was a prediction, so it was measured
The paragraph above says the
incompleterung becomes reachable if a later change lets a row carry a value outside the domain. That was an argument, published beside measurements, so it was tested rather than left as reasoning.Making
readExactVersionmap 404 to an out-of-domain value, using a constant that already exists in the same file rather than an invented one, drives the shipped function straight into the backstop:So the rung is a live backstop rather than dead code, and the reason for keeping it is now measured.
Testing that also exposed a real weakness in the suite, pre-existing and worth fixing here because it defeats the cell this PR adds. The manual token census called the preflight without a guard, so a refusal there aborted the file and every cell below it reported nothing at all. Under the drift the suite stopped after 7 reds with no completion marker, and the domain cell, which sits below that call, never ran. Read as a pass count that looks like the cell failing to fire, and green by not running is indistinguishable from green by passing.
29 of the 68 cells at the base commit already sat below that call. The call maps 404 to absent and reaches ready, so it refuses only if the 404 mapping changes, which is exactly the drift the domain cell exists to detect: the cell meant to catch that class was among the cells that class silenced.
The call now captures its own refusal and names it, so the cells below it still run:
The cell predicate was also checked directly, after asserting the predicate text is a true substring of the suite so the probe grades the shipped code and not a transcription of it: it reds on a value set containing an out of domain value and stays green on an in domain one, so it discriminates rather than being always red.
One mutation was dropped rather than chased
An added mutation that reorders the inconclusive rung below all-absent survived the proof. It looked like a coverage gap and is not one: because a row in
absentis never inunknown,absent === rowsalready impliesunknown === 0, so the two orderings cannot disagree. Two independent checks agree, one enumerating all 35 partitions of the bucket counts and one applying the reorder to the module itself and comparing all 27 measured compositions, byte identical.Writing a cell to kill it would have produced a test that passes for the wrong reason, so the mutation is dropped and the disjointness it relies on is asserted instead. Both comparators were first shown able to report a disagreement against a planted ordering that genuinely differs; the first planted ordering was itself equivalent and the control correctly refused to license the result until it was replaced.
Evidence a reviewer can re-run
This branch is now MERGED onto current main
794e3d2ec, at merge commite2e244c3b, with merge-base81dc5b7144272d68234b54401d9ae716e1cabe6e. Main moved again after the previous revision of this body was measured, so every figure below is re-pinned rather than carried over. Two commits touched the same suite and the same fixture:dfa3d0273(#1618, ten mutation entries pinning each census state refusal branch) and47fb01548(#1611, a comment correction). Exactly one textual hunk conflicted, in the fixture, and it is resolved by keeping BOTH sides. No entry from either side was dropped.Old figure with the sha that produced it, beside the new figure with the sha that produces it now. Nothing is restated under a sha that did not measure it:
5a5c888ebe2e244c3bThe suite total does not move, and that is a measurement rather than an omission.
dfa3d0273is fixture-only: 80 added lines, ten mutation entries, no new cell. Each of its entries pins the cellthe enumerated branch set has one entry per refusal in the grader, so the list cannot rot, which already existed at the merge base.47fb01548changes comments only. The merged suite file differs from this branch's by 11 lines, all of them comment lines and 0 non-comment lines.Both sides survive, measured as a set rather than asserted:
e2e244c3b5a5c888eband794e3d2ec, so main is an ancestor rather than a fast forwardfindanchors matching exactly once in the merged treeexpectRedcells still present in the merged suiteThe proof instrument was positive-controlled rather than trusted, because a reader never shown to return anything but KILLED is an instrument and not a measurement. Three entries in one run produced three different verdicts:
findandreplaceidenticalThe mutation proof prints its own scope limit: it proves the suite depends on the mutated code, not that a real entry point reaches it. The cells call the exported function directly, so that caveat applies to them. Proved separately by running the actual release command,
node scripts/preflight-npm-publish.mjs, the same oneci:publishruns, against a fake registry:The 404 control is what makes the 503 row mean something: the same harness and the same command produce no refusal when the registry answers normally.
Scope
Four files touched, and this is no longer test-only.
scripts/preflight-npm-publish.mjsgains threenamed, exported bucket predicates and a
CENSUS_BUCKETStable, and its verdict ladder calls theminstead of inlining the comparisons;
scripts/preflight-npm-publish.d.mtsis regenerated from themodule to match. The other two are the suite and its mutation fixture, both under
bin/smoke.The production change is behaviour-preserving, but it is a production change and the earlier
"test only, two files" wording was carried over from a revision where it was true.
Head note, superseded. This paragraph described the merge commit
e2e244c3b1c73a5e7e1d60a50173f6f3a952ecb4as the push, with a fixture of 38 entries, a suite at 93 passed and a proof of all 38 killed. That commit is real and is still an ancestor of the current head, but every figure in the sentence is two revisions out of date: the fixture now carries 49 entries and the suite 119 cells. The current state is recorded in the head note at the end of this description, which is the one to read.v2: Pin the census return domain with the compiler, and the buckets with the shipped predicates
Two cells in this PR claimed more than they proved. A three-family panel at
e2e244c3breturnedone APPROVE and two REQUEST_CHANGES, and the two REQUEST_CHANGES were reached independently and
landed on the same two cells. Both were right. The feature work is unchanged; the instruments
behind those two cells are replaced.
Head:
f16074385f9297b287186eb2c786a3bb2a5854b1(branched frome2e244c3b).1. The return extractor could not see the returns it claimed to enumerate
The cell asserted a universal: every return in
readExactVersionyields present, absent or anunknown value, so no fourth census outcome can reach the verdict ladder unbucketed. The instrument
behind that universal was:
over a brace-walked body. That regex requires a terminating semicolon and a non-empty expression,
so it structurally cannot represent a bare
return;, and a semicolon-free return is swallowed intothe next capture. The shipped function has four in-domain returns, so the cell passed for the
right reason and would have kept passing after a change that broke it.
What it is now. Returns are enumerated by the TypeScript compiler:
readExactVersionis locatedas exactly one top-level
FunctionDeclarationtaken from the parsed file's own statement list,and every
ReturnStatementinside it is collected, with the walk stopping at any functionboundary, constructors and class static blocks included.
That sentence is this specific in the third revision because the second revision's version of it
was wrong in a way the prose hid. Location was previously a recursive descent that kept the LAST
declaration of the name, so a nested same-name declaration replaced the real target and the cell
read a clean decoy while the shipped function returned an out-of-domain value. Zero matches and two
or more are now both refusals, and the refusal carries the count. A return is in domain only when the syntax pins its
value: the string literals
presentorabsent, or a template whose head beginsunknown:.Anything else, including a bare return, an identifier, or a call, is out of domain and reds.
What the cell claims now. Not "every return" as an unqualified universal, but the N return
statements the compiler finds, with N printed in the cell's own name. At this head N is 4 and the
transcript says so.
The printed count is a real signal only because location is now pinned, and the second revision
claimed it unconditionally when it was not yet true. Under the shadow that defeated the old lookup,
a fifth return in the shipped function did NOT change the printed count: the enumerator was reading
the four-return decoy, so the rendered name stayed "the 4 return statements ...", byte-identical to
a clean run, and the transcript offered a reader no signal at all. With the top-level pin the count
moves as the sentence always said it would, which is measured here rather than asserted: the
shadowing mutation renders the cell at 5 returns.
The tell
Introducing a bare
return;into the shipped source must make this cell red. It now does:node --checkexits 0 on that source, so it is a change a commit could really land. Under theretired regex the same edit left the cell green.
The eight escapes, as evidence rather than as proof
Eight shapes the retired regex passed clean are now asserted to be refused, each parse-checked so
it is a real change rather than a syntax error refused for the wrong reason. This table is
evidence about the instrument, not a proof of closure: it says these eight shapes are caught,
not that no ninth exists.
return;`unknown:and ended`;;/}}/above an escapeTwo controls keep this from being a parser that refuses everything: a positive control asserting the
unmodified skeleton yields exactly four in-domain returns, and a false-positive control asserting a
nested function's return is not attributed to
readExactVersion.2. The disjointness cell restated the predicates it should have checked
The cell re-implemented bucket membership inline and asked whether each row satisfied exactly one.
A reviewer checked all three predicate pairs and found no string can satisfy two, so the
cell's documented purpose, catching a row in two buckets, was unreachable by construction. It
was an unkillable cell, which is the exact defect the commit it replaced claimed to remove.
What it is now. The three predicates are exported from the script as
isUnknownRegistry,isPresentRegistryandisAbsentRegistry, and the verdict ladder itself calls them, so theyare the shipped membership rule rather than a second copy. The cell applies them to production rows
and is renamed to what it proves: under the shipped bucket predicates every production census row
lands in exactly one bucket. Both failure directions are live, since a row matching zero buckets
and a row matching two are both counted.
It can now fail. Widening the shipped
absentbucket to also claimunknown:rows reds it(
104 passed, 3 failed). Under the previous cell that same production change left the copy green.A second cell extends the check from the three rows the probe happens to produce to the whole value
domain the enumerator proved
readExactVersioncan return.A gap found by attacking the fix
Rewriting one
CENSUS_BUCKETSentry to an equivalent inline arrow left all 105 cells green. Theexported table agreed with the ladder today but was free to drift, which would have restored the
transcription defect one layer up: the suite would grade a copy again, just a copy with
exportonit. Two cells close it, one requiring the ladder to call the predicates by name (read with the
same parser), one requiring each table entry to be the exported function by identity. Both
carry mutations and both were confirmed red before being committed.
3. Production change
Minimal and behaviour-preserving: three predicates named and exported, the ladder's three filters
calling them instead of inlining the comparisons, and a
CENSUS_BUCKETStable. The declaration isregenerated from the module, as the drift cell requires.
Grading
49 mutations, all killed, each red and naming its own cell (
All 49 mutation(s) killed. The suite discriminates.). Every mutation was verified to bite under real conditions: itsfindmatches exactly once at this head, applying it genuinely changes the file, and applied alone
the suite goes red with the cell it names red, not merely some cell. No report-all switch was
used; a bite proved only under one is not a bite proof, because report-all continues past the first
casualty. Every
findis code-only, so no anchor can be disarmed by a commit that reflows acomment.
The earlier revision of this section said "all 45 killed" and "independently verified to bite", and
review found that only the mechanical half of that was witnessed, correctly. It is now witnessed in
full, and the full run also found something the previous summary had not: one inherited mutation
graded WRONG-RED. Its
expectRedquoted a cell name retired when the enumerator was rewritten touse the compiler, so a genuine, correctly-aimed red was being graded as a miss. It is re-tagged on
the invariant substring of the cell name, since that name is a template literal carrying the live
count. That defect predates this revision: at the previous head the tag matches the suite file zero
times.
Twelve mutations are new or retargeted, which is also the row count of the table below: eleven new
and one retargeted, re-derived from the fixture against the 38-entry state this revision started
from. Earlier revisions of this sentence said seven and then eight, both of which disagreed with the
twelve-row table printed directly beneath them.
One further correction, recorded because a reader would otherwise re-derive it. The two fixture
entries that inline the
absentcomparison are adjacent and nearly identically worded, and they arenot interchangeable: one rewrites the module-level
CENSUS_BUCKETStable, the other rewrites theladder's own filter. Only the second is visible to the ladder enumerator, because the table sits
outside
preflightNpmPublish. Measured: applying the bucket entry leaves that enumerator's calleelist byte-identical, so it could not defeat the ladder cell even in principle. The suite now reads
both entries out of the fixture by name and asserts which is which, and a cell records that the
bucket entry is graded instead by a runtime identity comparison over imported function objects,
which no textual decoy can launder. The by-name lookups must also resolve: a missed lookup is
otherwise silent, and while building this a renamed entry deleted three cells from the run while
reporting zero failures.
absentwidened to overlap unknownCENSUS_BUCKETSentry drifts off the exportreadExactVersionappendedpreflightNpmPublishappendedThe last eight grade the instrument itself, so it cannot be quietly relaxed back into the defect it
replaced. The final two are the shadowing appends: one per enumerator, applied to the shipped source
rather than a synthetic one.
Suite: 93 → 119 cells, 0 failed (twelve added at this revision, none removed). Fixture: 38 → 49 entries, every existing entry preserved BY NAME, 0 dropped, and 43 of the 45 inherited objects byte-identical. The two that are not: one
expectRed/cellre-tag (entry 27, which keeps its mutation and moves to the renamed cell) and onefind/replacere-anchor. An earlier revision of this sentence claimed every inherited entry was byte-identical, which was false in exactly those two places; preservation is by name, not by bytes. One existinganchor was re-anchored rather than edited in meaning: renaming the boundary predicate rotted it to
zero matches, and the one-line repair would have been ambiguous, because that line now occurs twice
once the ladder walk gained the same guard. It is anchored on a two-line code-only window. The shared 401
findremains legitimate double-grading (entry 21 deletes the branch, entry 26 corrupts it tocreatePackage), and both still red for distinct reasons.Corrections to figures quoted in the review packet
e2e244c3bcombined 28 + 33 → 38 unique, 0 dropped, 0 invented (23 shared). Thebrief said 27 + 32 → 37. The property is exactly as described and still holds; the counts were
each low by one. Measured with a reader proven against the
mutationskey and against a bad ref.merge-tree --write-treeagainst live main6cc504bebreturns 0, but a real rebase conflicts,because
e2e244c3bis itself a merge commit and a linear rebase replays a pre-merge commit. Areal merge of live main is clean, and the suite is 107/0 and all 45 anchors intact on the
merged tree. Reported because a zero from an unproven reader is a claim about the instrument.
Head note: this revision is 7d9f125, fourteen commits rebased onto live main 08cff35 (which carries #1626, #1628 and #1605). The rebase of the previous head conflicted twice in the preflight mutation fixture and was resolved as a union, not by taking this branch's side: an earlier analysis of this PR against an older main concluded main's entries were a strict subset of this branch's, which is false against current main because ten census-state refusal entries landed in between. Final fixture: 49 entries, 49 unique names, 0 dropped from main, 4 new here.
Measured at this head on a second machine, with the real instruments: the preflight suite 119 passed, 0 failed; bin/smoke/mutation-fixtures.smoke.ts 437 files, 2,548 anchors, 0 dead, 0 ambiguous, 0 spanning prose; suite-ambient-env PASS (this suite strips COTAL_ keys before spreading the environment into its child); ci-fragments and docs voice clean; shard-stability STABLE against main. The full mutation proof over the 49-entry fixture is recorded on the checks tab through CI's reproof workflow and was run on the second machine before the push.
No catalogue, spec or docs file is touched here, so the docs-bundle gate is not triggered by this branch; run anyway on the second machine, the chain's regeneration is a no-op.
Head note, v4 revision. This revision is e7ec3e6, three commits on top of 7d9f125. Everything above this note that is not explicitly marked superseded describes 7d9f125 and its figures are that sha's; the figures below are this sha's and are not interchangeable with them.
A review found that
ladderFilterCalleesasks whether the ladder's TEXT contains a call to each exported predicate, which a call insideif (false)satisfies while the live ladder has drifted. Measured at 7d9f125, each bucket drifted alone with a dead call retained: unknown 119/0 NOT CAUGHT, present 119/0 NOT CAUGHT, absent 116/3 where those three reds are the existing positive control and the two decoy cells failing because theirfindanchors on the exactabsentline the drift rewrote. That is anchor rot, not detection, so all three buckets were exploitable.ladderConsumedBucketspins the ladder by data flow instead: the sole top-levelpreflightNpmPublish, the soleregistryVerdictdeclaration in its own statement list, the identifiers that initialiser reads, each resolved to a declaration in that same list. For each of those consumed bindings the reader requires a direct identifier call whose callee resolves, by the TypeScript checker's symbol identity, to the module's exported predicate of the same name with a single top-level declaration (v6, below). A call that is not the initialiser of a consumed binding is never read, so a dead call, a nested call and a call in an unread branch all fail to satisfy it. Buckets are partitioned by initialiser SHAPE, not by binding NAME, so a ladder that keeps a correctabsentbinding and reads an inlineabsentRowsin the verdict is still caught.Four mutations, one per bucket plus that rename variant. Measured at this sha, each applied alone: every one reds the rung cell that grades its own bucket's data flow. The
absentpair additionally reds the three older text-anchored controls, which is the pre-existing anchor rot described above and not detection; it is called out here because a reader counting reds would otherwise credit it as such.Two defects in the first version of this work, both caught by the instruments rather than by review, both disclosed because the shape generalises. First, the controls built their inputs by substituting a literal shipped line, so the mutants rotted the controls and a genuine detection arrived as 2 reds with only 1 real. Second, the replacement lifted the shipped
registryVerdictstatement out of the module BY AST, which still reads the file the mutants edit: the rename mutant rewrites that statement, the lifted text moved under it, andmutation-proofgraded a real detection WRONG-RED. AST-lifting is not rot-proof, it is rot with more steps. The verdict is now synthetic, and the drift risk that creates is paid for by a cell that reads the same three shipped bucket lines through the skeleton and through the shipped module as resolved top-level export bindings and requires their sorted resolved-callee sets to agree, so a missing, inline or locally shadowed shipped callee diverges from the clean skeleton. The cell compares which exported declarations feed the verdict, not rung order, arguments or predicate semantics. That cell reds under a shipped-module drift, which is correct and is why two cells red on the acceptance patches rather than one.Measured at e7ec3e6: the preflight suite 140 passed, 0 failed; the full mutation proof over the 53-entry fixture ALL 53 KILLED;
mutation-fixtures.smoke.ts437 files, 2,552 anchors, 0 dead, 0 ambiguous, 0 spanning prose;suite-ambient-envPASS. The acceptance patches against this tree, in a clean clone: the dead-call laundering that was green at 7d9f125 is 138 passed, 2 failed, and the drift without the dead call is 137 passed, 3 failed. Only the two smoke files change, so the docs-bundle gate is not triggered by this revision either.Verified on a second machine at e7ec3e6 before the push: the preflight suite 140 passed, 0 failed; the 53-entry mutation proof ALL 53 KILLED; mutation-fixtures 437 files, 2,552 anchors, 0 dead, 0 ambiguous; suite-ambient-env PASS; ci-fragments and docs voice clean; shard-stability STABLE on the merged tree against live main 08cff35, which is an ancestor of this head. Not verified here: CI at this sha, which has not run yet; the docs-bundle gate is not triggered by this diff (two smoke files only).
Head note, v5 revision. This revision is 3bd71b6, one commit on top of e7ec3e6. The v4 figures above are that sha's.
A review of v4 found that the data-flow pin still classified each rung's callee by identifier text: a local predicate declared inside the ladder with the same name as the export satisfies "initialised by a call of
isPresentRegistry" while the shipped rung calls the local copy. Measured at e7ec3e6, each of the three buckets shadowed alone stays 140 passed, 0 failed. A widened localisAbsentRegistrythat also admits a value the export refuses stays green too, and is not an equivalent mutant: the behavioural cells never feed that value, so they cannot see it. That is why the repair is structural rather than another behavioural cell.ladderConsumedBucketsresolved each consumed rung callee through the call site's lexical scope chain at this revision (superseded by v6, below: the scope walk is gone and resolution is by the checker's symbol identity); a same-name local recorded a null callee and the bucket's structural cell reds. The synthetic skeleton declares the same three exported predicates before its function so the agreement cell has a positive control on real exports. Three mutants are added in the same-name-local shape, one per bucket, the absent one widened; their bucket lines are byte-identical so no control anchor moves. The two sentences above that overclaimed the v4 reader are rewritten in place.Measured at 3bd71b6: the suite 143 passed, 0 failed; the 56-entry proof ALL 56 KILLED, each new mutant on its own bucket's structural cell (141 marks against 143); the three acceptance patches (present shadow, dead-call drift, widened absent shadow) each 141 passed, 2 failed, the bucket's structural cell plus the agreement cell, with no behavioural cell added for the widened value; suite-ambient-env PASS. Verified on a second machine at 3bd71b6 before the push: suite 143/0; proof 56 KILLED, 0 survived, 0 wrong-red; the three acceptance patches 141/2 each on the same two cells; mutation-fixtures, suite-ambient-env, ci-fragments and docs voice clean; shard-stability STABLE on the merged tree against live main 08cff35. Not verified here: CI at this sha. The reader does not pin the callback argument (a rung reading
row.directinstead ofrow.registryis caught by behavioural cells, not by this pin), and that is stated rather than claimed.Head note, v6 revision. This revision is 1ae54cf, one commit on 3bd71b6. The v5 panel found two shapes the scope walker did not enumerate (a destructured parameter, and a
varhoisted out of a sibling block), each leaving the suite green at 143/0 while the shipped export and the shadow diverged on a real input. v6 stops enumerating scope kinds.ladderConsumedBucketsbuilds a TypeScript program over the module, takesgetSymbolAtLocationon each consumed rung call's expression, follows aliases withgetAliasedSymbol, and accepts the callee only when that symbol is the module's exported symbol of the same name, whose sole declaration is the top-level one. Refusal is by symbol identity, so binding patterns,varhoisting, parameters and catch clauses are covered by the binder rather than by a list maintained here. What this proves is that the call binds to the export. What it does not prove: nothing about the argument the callback passes, nothing about the order of the rungs, and nothing about what the predicate means. Five reader controls are added and no behavioural cell: two hoisted-varplacements (unknown and absent), the destructured-parameter placement, a positive control that the destructured-parameter skeleton really rebinds, and an export-clause module that must still resolve all three rungs. Measured at 1ae54cf: the suite 148 passed, 0 failed; the 59-entry proof ALL 59 KILLED, one new mutant per new placement, each red on its bucket's structural cell; all six acceptance patches (present shadow, dead-call drift, widened absent shadow, sibling-block var, destructured parameter, hoisted var on absent) each 146 passed, 2 failed, the bucket's structural cell plus the agreement cell, with the dts-freshness cell green in every run; the three new patches applied to the v5 reader are invisible to it (143/0 each);mutation-fixturesevery anchor present and unique;suite-ambient-envPASS. Verified on a second machine at 1ae54cf: suite 148/0, the same six patches red on the same cells, proof ALL 59 KILLED;mutation-fixturesOK;suite-ambient-envPASS;ci-fragments8 passed.