mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-11-07 06:19:43 -10:00
* fix(autofix): charge regressions to the brake and gate test weakening
Two holes let an autofix round ship a new problem for free.
A round that pushed a fix and turned CI red counted as a SUCCESS: the
consecutive-failure brake counts "rounds that pushed nothing", so the red
it created reset the counter, came back as the next round's input, and was
paid for out of the round budget. A PR could alternate regress/repair
indefinitely while every brake read it as converging.
Every acted round now stamps what it pushed and whether the head it pushed
onto was fully green; the next round charges a regression only when the
live head is exactly that sha, the marker says green, the window key
matches, and the head is red now. The observing round records it in
whichever report it posts, and the brake stops resetting on a regressing
round. Attribution is conservative on every axis: a moved head, a re-arm,
a pending check at push time, a cancelled run, or the loop's own lanes all
drop the charge.
The second hole: relaxing an existing assertion was invisible to the whole
gate. Build/typecheck/lint never read assertions, the package tests run the
weakened file, and the bite check reads only the tests a round ADDS. The
SKILL already required content evidence for deleting or weakening a test;
nothing enforced it. The gate now measures deleted test files, net
assertion removal, and added skip/todo markers, and rejects the round
unless each file is recorded in test-weakening.json with a reason. It
judges that the claim exists, not that it is right — the reasons ride into
the round report for a maintainer to read against the diff.
Deletions get their own merge-freight discriminator: content equality reads
"absent on both sides" as identical, so a round deleting a test the PR
itself added looked like freight and escaped. The merge base separates the
two cases.
* fix(autofix): attribute test weakening to round commits, close marker escapes
* fix(autofix): close weakening-gate merge/typechange escapes, own-lane charge leaks (#10188)
* fix(autofix): close weakening-gate comment, attribute, and name-framing escapes (#10188)
* fix(autofix): close weakening-gate masking escapes and delete/restore false charge (#10188)
* fix(autofix): close weakening-gate lexer, marker-shape, and verdict escapes (#10188)
- WEAKEN_ASSERT_RE: left-boundary class (console.assert( never fails a
test) and a matcher requirement on added expect( lines (matcher-less
expect passes for anything); the del side stays bare so a
multi-line-formatted assertion removal still charges
- skip markers: concurrent/sequential/shuffle chains, tagged-template
each tails, computed accessors, and newline-split chains measured on
a joined view; sd counts comment-stripped del lines
- weaken_strip_comments gains string/template state (an in-string /*
can no longer poison the strip); weaken_strip_code gains
regex-literal and template-nesting state
- modify/delete merge resolutions that KEEP the file apply no freight
- the net-range deletion arm enumerates the explicit pre-round->tip
pair and is judged even when per-commit measurement is UNAVAILABLE
* fix(autofix): close weakening-gate member-assertion, marker-spelling, and lexer escapes (#10188)
- WEAKEN_ASSERT_RE/ADD_RE: the member arm requires the CALL (an
exported assert alias executes no assertion), and supertest's
throwing member form .expect( counts on every arm
- WEAKEN_SKIP_RE: backtick-quoted computed accessors, the ?.
optional-chaining spellings (it?.skip( and it.skip?.(), and the
reversed chain with an execution modifier behind the marker
- weaken_strip_code: regex-vs-division is keyed on the last
significant TOKEN (an operator including /, a regex-head keyword
preserved across whitespace, or a control-head close paren tracked
on a paren-kind stack), not the last output character
- weaken_strip_code/weaken_strip_comments: a ${} hole closes on the
brace matching its own ${ (per-hole brace depth), not the first }
inside the hole (an object literal or arrow body)
- the skip-marker net gains the whole-blob backstop twin of the
assertion arm, so a genuine addition cannot net against an
in-comment marker deletion or hide behind a block-comment span
opened above the hunk
- twelve witnessed fixtures (each guard flips its own test red under
mutation); fixtureWrite emits single-quoted shell words so
backtick/${ fixture content survives
* fix(autofix): close weakening-gate merge coverage, freight phantom, and lexer escapes (#10188)
* fix(autofix): close weakening-gate cross-commit, merge-discard, and skip-surface escapes (#10188)
* fix(autofix): close weakening-gate matcher-credit, each-skip, and merge-recount escapes (#10188)
* fix(autofix): close weakening-gate tail-drop, skip-netting, fast-forward, and JSX escapes (#10188)
* fix(autofix): stop the weakening gate charging honest test edits (#10188)
Three of the four measured defects were false rejects the gate published
as machine-measured fact, and the fourth was test surface it never saw.
The matcher-tail census counted every member call in a stripped blob, so
deleting a spy and its restore read as three removed assertion lines;
scope it to continuation-shaped lines, the only shape that arm exists to
see. A fast-forwarded main recorded a recount base only for files it
added, so main's own delta was charged to a round that synced and then
renamed a symbol; record bases for modified files too and read the ledger
instead of set membership. The body-call skip arm charged vitest's
condition-valued dynamic skip, which is the runtime spelling of the
.skipIf environment guard the doctrine already exempts. And `*_tests.rs`
matched no pathspec arm, leaving two tracked Rust test files invisible.
Each fix carries a witness that a mutation probe confirms goes red
without it; the pinned guards for all four arms stay green.
* fix(autofix): measure test weakening with the parser, not a text census (#10188)
Replace the awk lexer, the four extended regexes, the matcher-tail census,
the per-commit accumulators, the verdict-time recount and the netting skip
with one instrument: count-test-surface.mjs parses the whole file with the
TypeScript compiler and reports its declared test surface (statement-level
assertion chains, test/describe registrations with their enabled state,
bare early returns ahead of assertions). Each file's round delta is
tip - pre-round - main's contribution, where main's contribution across a
merge is measured on git's own auto-merge of main's side (git merge-file
--ours) and across a fast-forwarded main commit on the commit itself, so
the same weakening measures the same before, during or after a merge, and
main's delta neither charges nor shields.
Narrow the pathspec to test-NAMED files (a README under integration-tests/
is not a test deletion), read the enumeration producers' status instead of
swallowing it, re-apply the reason bar when rendering, and state the
declared non-goals in the gate header: reachability, condition-valued
guards and by-reference options are runtime facts the runner-backed
instruments judge.
Bind the af-155 regression classifier to the commit its rollup describes
(headRefOid read with the rollup; a moved head classifies none), exclude
the loop's own dispatch-pending StatusContext by its exact context value,
classify verdict-less checks (EXPECTED, STALE, STARTUP_FAILURE) as pending,
stamp pre=none after a salvage-merged push, drop the unread CHECK_STATE
from the failure report's env, and render the regression disclosure on
every report shape.
Drop the hand-rolled CommonMark fence model from workflow-size.test.js: the
existing anchor/pointer/contents pins already prove navigation, and a text
model of a renderer is the class of instrument this change removes.
* chore: record the merged qwen-autofix.yml size
* fix(autofix): stage the surface counter with the pre-merge absence guard (#10188)
The stage step copies count-test-surface.mjs from the trusted base, but
the script is new in this PR: pre-merge the base lacks it, and a bare cp
under the step's -e kills every PR-event round whose workflow resolves
from the PR's own ref — the deferral the reviewer kept re-reporting.
Give the cp the heartbeat guard: rm -rf first so a planted leftover
cannot survive a failed copy and be executed as trusted (af-149), then
|| true so absence skips; the gate already fails open on a missing
counter (WEAKEN_MEASURED=false). Pin the pair and their order beside
the heartbeat pins.
Co-authored-by: Qwen-Coder <[email protected]>
* fix(autofix): close the weakening gate's fail-open paths and make green an allowlist
Answers rounds 21-26 by retiring mechanisms rather than adding entrances.
Head-state classifier (af-155): GREEN is now an allowlist, never the
fall-through. A charge-enabling verdict requires every counted check to
hold a state the classifier recognises as passing, so CANCELLED, NEUTRAL,
SKIPPED and any state GitHub adds later read as pending instead of minting
the premise. (R22-3)
Test-surface counter: a run that exits 0 without printing a readable
verdict is a measurement FAILURE, not a verdict. weaken_measure validates
the shape before returning it, so the file takes the existing fail-closed
arm ("test surface could not be measured") instead of being silently
uncharged while the round still reports itself measured. (R26-2)
Merge attribution: git merge-file's failure test is its exit status, not
its output size. --ours resolves conflicts and exits 0, so a legitimately
empty result (main emptying a test file) was never conflated with failure
-- the line is replaced anyway so the intent no longer has to be
re-derived from a size test, and pinned by a fixture that makes main's
side zero bytes across a real merge commit. (R21-4)
Report-post fallback: gated on the af-155 state the round AUTHORED, not on
its outcome. A no-op round's observation about the PRIOR round was
unrecoverable once its report failed to post, because the marker it read
is superseded by the next push and the workflow's handoff comment excludes
exactly the fixed/noop outcomes. (R22-1, R26-3)
Instrument staging keeps the pre-merge absence guard it was given here and
moves to its own step after npm ci, so the parser is a .cjs copy of the
typescript the TRUSTED lockfile installed; both digests travel in
expression context and are verified before the counter executes. A
verbatim absence probe runs the step against a script-less tree with
leftovers planted in both shapes.
Boundaries stated where they can be read: --ours is the attribution model
and its error deliberately lands on the charging side; the base axis is a
documented residual with the same bound as the flake carve-out; the SKILL
no longer promises assertion counting for the Python and Rust shapes,
which are watched for deletion alone.
508/508 in scripts/tests; five mutants (green default, verdict validation,
emptiness-as-failure, outcome-gated fallback, no-op event overwriting the
baseline) all killed.
* test(autofix): execute the paths the weakening gate's own guards protect
A reverse audit of the previous commit found two guards that no test
reached, both load-bearing in the fail-open direction, plus one hazard in
the counter's CLI dispatch.
git merge-file refuses a file carrying a NUL byte (exit 255, no output),
and the branch's own side has to stand in for the auto-merge then. Nothing
executed that arm: making the substitution unreachable left the whole suite
green. Without it the refusal's empty output reads as main having emptied
the file, which credits the round with the entire baseline surface and
masks a removal of up to that size. New fixture `binary-merge` -- main
appends a comment, the round drops an assertion, and the NUL makes every
side of the merge binary -- charges `net 1 assertion(s) removed` and fails
when the substitution is removed. The round rewrites the file rather than
filtering it, because a line filter reads a NUL-bearing file as binary and
would empty it instead.
The staging step's present-counter branch -- the one that runs in
production once this lands -- was pinned only by text. Swapping `mkdir -p`
and `cp` inside it left the suite green while killing every round, because
the step is unconditional and runs under `-e`. The probe now runs the step
body the way GitHub runs a `run:` block (`bash --noprofile --norc -e -o
pipefail`) in both shapes: the pre-merge tree that lacks the script, and a
tree that has it with a parser planted where npm ci puts one, asserting the
staged bytes and both digests.
The counter's CLI dispatch ran at module scope, so importing it executed it
against the importer's argv -- a positional the importing program owns
lands in the unknown-mode arm and takes that process down with exit 2. It
now runs only when the file is the program, compared as REAL paths: the
gate runs it from RUNNER_TEMP, which on macOS is reached through
/var -> /private/var, and a literal comparison silently disables the CLI
and reports every file unmeasurable. Both halves are pinned, including a
run through a symlink.
Also: the head-state classifier keeps the status axis it had, so a check
still in flight is pending even when it carries a conclusion from an
earlier attempt; and the dialect table is exercised for every extension the
pathspec selects, not only `.ts` -- deleting the JS entries was invisible
to 52 tests while every tracked `*.test.js` file is measured by the gate.
509/509 in scripts/tests. Mutants killed: the merge-file substitution made
unreachable, the staging order swapped, the main-module guard compared
literally, the JS dialects deleted, plus the five from the previous commit.
* fix(autofix): follow git's trivial-merge rule when merge-file refuses
A reverse audit of the previous commit found that its new binary-merge
fixture pinned THAT a substitution happens, not WHICH side it takes -- and
the side it took was wrong half the time.
When `git merge-file` refuses (a NUL byte makes the file binary, exit 255,
no output), the previous commit substituted the branch's side
unconditionally. That is right only when main's side is unchanged from the
base. When MAIN is the side that moved, it reads main's own weakening as
the round's and rejects a round whose only act was `git merge origin/main`
-- the exact property the neighbouring main-empties fixture already guards,
re-broken by a second trigger into the same substitution.
The fallback now follows git's own trivial-merge rule: a side that did not
move from the base contributes nothing, so the other side IS the result,
and only a genuine two-sided change resolves for the branch the way
`--ours` would have. Three fixtures pin the three shapes -- the round
weakens (charged), main weakens (accepted), both move (charged for the
round's own removal only) -- and both fixed-side choices now fail.
The head-state classifier's status axis becomes an allowlist too. The
denylist it had enumerated four in-flight states and missed REQUESTED, a
real CheckStatusState member that four other sites in this same workflow
already list; the axis is now `status == "COMPLETED"`, with an absent field
reading as finished because a commit status carries no status field at all.
Both axes are allowlists for the same reason a denylist forgets the member
it was not told about -- which is how CANCELLED entered the green arm in
the first place.
The classifier's rationale moves to qwen-autofix.md#af-155, leaving the
workflow 1024 bytes below the hard gate.
509/509 in scripts/tests. Mutants killed: always take the branch's side,
always take main's side, the four-member status denylist restored, the
status axis dropped entirely.
* fix(autofix): bound main's contribution by what the merge actually took
A third reverse audit found a false ACCEPT in the attribution model, in
both lanes: the auto-merge is computed from the two sides and never
consults the merge commit's own tree, so a merge that DISCARDED main's side
still books main's delta as a contribution.
A round can therefore merge main with `-s ours` and delete an assertion in
the same breath: the credit for main's discarded weakening absorbs the
round's own removal exactly, and the round ships uncharged. The previous
commit widened the reach of this by extending the trivial-merge rule to the
binary lane, where the same shape had been rejected before.
Main's contribution is now bounded by what the tip took. When the merge
result is the branch's side byte for byte, main contributed nothing to that
file, whatever an auto-merge would have produced, and nothing is
subtracted. A resolution that took main's version, or produced something
new, still goes through the model unchanged -- including `merge-conflict-
drop`, whose resolution commit differs from both sides.
Two fixtures pin the shape in both lanes: `strategy-ours-hole` and
`strategy-ours-hole-binary`, each a round that merges `-s ours` and removes
an assertion. Deleting the bound leaves both accepted.
509/509 in scripts/tests.
* fix(autofix): clamp main's credit by what the merge actually landed
The previous commit's bound was byte-exact: it fired only when the merge
result equalled the branch's side to the byte. A fourth reverse audit
showed one added comment line inside the merge reinstates the whole hole,
and the blast radius is main's ENTIRE modelled delta -- a round can strip
every assertion from a pre-existing test file and ship it uncharged.
Main's contribution is now the part the MODEL and the LANDED blob agree
on, and only where they agree in sign. Equal, and the clamp is the
identity: the ordinary merge measures as it always did. They diverge
exactly when a resolution took neither side whole, and then the clamp cuts
both ways -- a resolution that discarded main's side credits nothing, and
a resolution that weakened the file itself is not main's contribution
either.
`weaken_auto_blob` emits three lines now, always: the modelled auto-merge,
an optional override for the side to compare against, and the blob the
merge commit actually holds. Presence is deliberately NOT clamped: the
baseline is what the round had available to weaken, and letting the merge
result define it would let a round discard a test main added and answer
for nothing -- the one thing the deletion signal exists to catch.
What the clamp cannot see is identity. Main removing one assertion while
the resolution puts it back and drops a different one nets to zero, the
same way an assertion moved within a file always has; that boundary is now
stated in the header beside the model.
Fixtures: `ours-hole-plus-comment` is the evasion the byte-exact bound
missed, and `binary-resolution-takes-main` pins the binary fallback's
branch-side arm -- which the clamp had quietly un-covered -- at its
declared over-charging direction.
509/509 in scripts/tests. Mutants killed: the clamp returning the model,
the clamp returning the landed delta, the landed blob never emitted, the
binary fallback's else arm taking main's side. One survivor recorded: that
arm taking the merge base, which the clamp makes equivalent in every shape
a fixture can reach.
* fix(autofix): clamp only main's removals, never its additions
The clamp the previous commit added was sign-symmetric, and its two signs
have opposite consequences. Capping a REMOVAL cuts the shield, which is
what it was written for. Capping an ADDITION cuts the charge -- so a round
could discard whatever main added to a test file mid-round and pay
nothing. A fifth reverse audit demonstrated it through the real gate in
three spellings, none of them needing a merge strategy flag: an ordinary
merge resolution that drops the assertion main just landed, a
delete/modify resolution that drops the test main appended, and the same
resolution keeping that test registered `.skip`.
The commit that added the clamp already left PRESENCE unclamped for
exactly this reason, and said so; the reasoning was never carried into the
deltas. It is now: main's additions raise the baseline whatever the merge
kept, and only its removals are credited as far as they landed. It also
contradicted a promise the gate header makes -- the same weakening
arriving by fast-forward was charged while the one arriving through a
merge was not.
The event skip goes back to testing the model alone. Its second half was
added to let a landed-only divergence through, but the clamp returns zero
for every such event, so the only thing processing it could still do was
latch `baselinePresent` off a side main never held: a file the round
authored itself, touched while resolving a merge and dropped afterwards,
was charged as a deleted pre-existing test.
Six fixtures, all through the real gate: the three discard spellings
(charged), the round's own file edited in a merge and dropped (accepted),
main removing two where the resolution keeps one and the round then
removes another (charged for its own), and main disabling a test the
resolution keeps enabled before the round disables it (charged).
509/509 in scripts/tests. The four mutants that survived the previous
commit are now killed: both clamp branches, and `clampBag` returning
either bag unclamped. Presence clamped by the landed blob, and the skip
processing every event, are killed too.
* fix(autofix): measure main's contribution where main made it
A sixth reverse audit found two more defects, and both trace to the same
mechanism: main's contribution was read off a three-way SPLICE of the two
sides (`git merge-file --ours`), which mixes the round's own edits into
what is supposed to be main's delta.
The splice can invent a surface neither side ever had. Witness: the round
un-skips a suite, main adds a test inside that suite, and the resolution
takes main's file verbatim -- so the tip is byte-identical to main's and
nothing was weakened. The splice combined the round's un-skip with main's
addition into an ENABLED registration that existed in no tree, and the
round was charged for disabling it.
The splice also decided PRESENCE, through an endpoint that is the branch's
side in two arms. A path both sides added during the round, resolved to
main's file and then deleted by the round, read as "the baseline never
held it" and shipped uncharged -- while the gate's own
`weaken_baseline_holds`, the definition its fail-closed arm uses, said the
opposite.
So the splice is gone. Main's contribution is main's OWN delta, measured
on main's own side against the merge base, and presence follows main's own
side directly. That makes the two arms one rule: a modify/delete resolved
for main was already measured from the merge base. `git merge-file` leaves
the gate entirely, and with it the binary-refusal fallback, its
trivial-merge rule and the over-charge they carried -- a resolution that
takes main's version now charges the round for nothing, where the model
used to bill it for main's removal too.
What the merge actually DID with main's contribution still clamps it, as
before: removals credited only as far as they landed, additions never
clamped.
509/509 in scripts/tests, with the gate section 60 lines shorter. Four new
fixtures: the splice artifact (accepted), the add/add path dropped after
the merge (charged), a resolution that removes more than main did (charged
for the difference), and one that moves opposite to main (charged for the
round's own removal only). Mutants killed: main's delta measured against
the branch side, presence forced either way, removals credited from the
landed blob, the landed guard dropped, additions clamped down.
* fix(autofix): decide the baseline by what main LANDED, not by what it holds
A seventh reverse audit found three defects in the new model. Two are
false rejections with high reachability, one is a false accept that a git
tie-break decides.
Presence flipped true whenever main held the file at a merge, even when
main's side was byte-identical to the merge base -- main landed nothing
there. So a test file an EARLIER round removed, or renamed, is charged
`test file deleted` again in every later round that merges main, until
main also loses the file. The gate's own header promises a rename costs
one ack entry naming the old path; it was costing one per round.
Presence now moves only when main's side and the merge base DISAGREE about
the file existing. Main adding it during the round puts it in the
baseline, and the round answers for dropping it afterwards. Main deleting
it takes it out only when the merge adopted that deletion. Main merely
still holding a file it has always held says nothing.
The "not an event at all" early return is gone with it. It dropped main's
deletion from the running total while the NEXT merge's base already
reflected that deletion, so main's re-add was credited from zero a second
time: add, delete-with-the-round-keeping-its-copy, re-add was charged
three assertions where one was removed. Every merge event is recorded now,
and the clamp handles the resolution that kept the round's copy -- its
landed delta is zero, so main's modelled deletion credits nothing.
A criss-cross history has more than one equally valid merge base and git
picks one without promising which. Since main's delta is measured against
that base, the pick decided the verdict -- and one of them credits main
with the ROUND's own removal, waiving a real weakening. The file is now
refused rather than measured: `test surface could not be measured`, which
one ack entry answers.
509/509 in scripts/tests. Three fixtures for the presence rule and the
event chain; both presence mutants (latching main's hold unconditionally,
and clearing on main's deletion whatever landed) are killed. The stale
auto-merge prose in the counter's header, its clamp comment, an orphaned
doc comment in the gate and two leftovers in the suite are corrected.
* fix(autofix): chain main's events, and let the merge decide what it adopted
An eighth reverse audit found that the previous commit's headline fix does
not work and its fixture is vacuous, plus two false accepts it introduced.
The removed early return was load-bearing. Its case -- main deleted the
file, the merge kept the round's own copy -- is now recorded as an event
whose `landed` blob IS the round's copy, so a copy weaker than the merge
base credits MAIN with the round's own removal. The round guts a test file,
main happens to delete it, the resolution keeps the gutted copy, and the
round ships uncharged. The early return is back.
What that removal was meant to fix is fixed properly: main's events CHAIN.
Each event now measures main's side against main's side at the PREVIOUS
event, not against a fresh merge base that already reflects it, so the
events telescope to main's own net for the round. Add, delete-with-the-
round-keeping-its-copy, re-add was charged three assertions where one was
removed -- and it still was after the previous commit, because recording
the deletion with a landed delta of zero is behaviourally identical to
dropping it. The fixture missed it by re-adding byte-identical content, so
the third merge was not an event at all; it re-adds a comment line now.
Presence follows what main CONTRIBUTED, not what it holds. `mainHolds`
alone exempted a whole path for the round whenever the pre-round ref
lacked it -- including when main GREW it and the round restored main's
file and gutted it. The rule is now: main holding the file and having
moved it puts it in the baseline, whatever the resolution then did; main
deleting it takes it out only when the merge adopted that deletion.
The criss-cross refusal was charging honest rounds. More than one merge
base only matters when the candidates DISAGREE about the file, which is
now the test; when they hold the same blob the pick cannot change the
verdict and the file is measured. Two fixtures pin both sides of that.
516/516 in scripts/tests. Six mutants killed, each the exact regression it
names: the early return removed, the chain removed, presence from
`mainHolds` alone, presence cleared without checking the base, the refusal
made unconditional, and the refusal dropped. The stale presence comment
the previous commit missed, and `weaken_baseline_holds`'s claim to be the
same definition the counter reports, are corrected.
* fix(autofix): drop the event whenever main holds no side and the file stays
A ninth reverse audit found that the reinstated early return was too
narrow, that presence read byte movement where it meant surface movement,
and that the criss-cross guard compared the wrong identity.
The early return required the merge base to hold the file. One merge
later it does not: after main deletes a file the round kept, the NEXT
merge sees no main side AND no base, the event is recorded, and the
round's own copy becomes the landed blob that sets the clamp -- crediting
main with exactly the round's removal, one merge after the guard that
exists to prevent it. The base is not part of the test any more: main
holding no side while the file stays in the round's hands is the same case
however the history reached it.
Presence asked whether main's side moved BYTES. Main appending a comment
to a file an earlier round deleted contributes no coverage, and reading it
as a contribution re-charged that deletion in every round main happened to
touch the file -- and made the verdict depend on how many times the round
merged main. It asks about the measured SURFACE now: the totals and both
registration multisets.
The criss-cross guard compared `git rev-parse <base>:<path>`, which
returns a TREE oid for a directory while the measurement treats a tree as
no blob at all. Two bases that agree for the measurement read as a
disagreement, and an honest round was charged unmeasurable on a tie-break
that could not have changed the verdict. It compares blob-or-absent
identity now, the same question `weaken_blob` asks.
One finding is answered with a stated precondition rather than code. The
model reads main's side as MAIN's, which holds while origin/main does not
contain the round's own commits -- true here, where pull requests land
squashed and main's first-parent history carries no merge of a PR branch.
A guard for it was written, found unreachable from every history these
fixtures can build, and removed in favour of saying so in the header.
516/516 in scripts/tests. Three fixtures, and with them the two mutants
the audit found surviving are killed: the caller's skip of a dropped event
(previously indistinguishable, because no fixture had a prior main event
for the file) and the early return's base condition. The clamp's
`landedHolds` condition is now redundant for manifests the gate produces,
and says so.
* fix(autofix): read movement from the bytes where the surface is unmeasurable
A tenth reverse audit found that the previous commit's surface test
disabled the deletion arm for every non-JavaScript test file -- 45 Rust
and 18 Python paths in this repository.
`count()` reads no surface from those shapes, so two present blobs always
compare equal and "main moved it" could never become true. Once the
pre-round ref lacked such a path, nothing main did during the round put it
back in the baseline, and the deletion arm -- the only arm those shapes
have, and one the gate header promises never fails open -- was waived.
Movement now falls back to the bytes when neither side is a dialect the
instrument reads, because there "the surface did not move" is unmeasurable
rather than false.
The surface test also compared main's DISABLED registrations, which reach
no signal: only the tip's do. Comparing them raised the baseline for a
change the gate can never charge -- main adding an `it.skip` re-charged a
deletion an earlier round had already answered for, which is the exact
failure the surface test was written to remove. It compares the chargeable
quantities now: the assertion totals and the enabled-registration
multiset, of which `enabled` is a derived count.
The criss-cross comparison goes through `weaken_blob`, the reader the
measurement itself uses, instead of a second identity built from
`rev-parse`. A tree, a gitlink and a missing path are "no blob" to all of
them alike, and a mode-only difference is the same blob, so the guard
refuses exactly the disagreements that could change a verdict.
Two fixtures for the deletion arm: a Python file main grows while the
round keeps its earlier deletion, and a file main adds whose measured
surface is indistinguishable from an absent one. Both are charged; both
fail if the fallback or the presence rule's existence test is removed.
The `main-absorbs-round-commit` fixture is deleted. The audit showed it
measured nothing -- the file is byte-identical on both sides of its merge,
so the event filter drops it before attribution runs -- and its comment
claimed a mechanism that was not the one making it pass. The precondition
it stood for is stated in the header instead, with its justification
corrected: a squash-merge of the round's own work into main mid-round is
the spelling that breaks the model, not a merge, which is self-correcting
because the merge base carries the same commit.
516/516 in scripts/tests.
* fix(autofix): decide the byte fallback by what was measured, not by the extension
An eleventh reverse audit found that the previous commit's fallback keyed
on the file's EXTENSION rather than on whether the instrument read
anything. A `.test.ts` that registers nothing measures exactly like a
`.py` -- and this repository has one, a type-level fixture under the CUA
driver -- so its deletion could still be waived after an earlier round
removed it. The test is the measured surface now: no registration of
either kind and no assertion, on both sides.
The audit also showed the previous commit's narrowing of the surface
comparison changed real verdicts with no coverage, in either direction.
Main moving only DISABLED registrations is no coverage and reaches no
signal, so it must not put a file an earlier round deleted back into the
baseline; re-adding that comparison now fails a fixture, as does dropping
any of the three fields that remain.
The criss-cross guard's presence arm was equally uncovered: both shipped
criss-cross fixtures disagree about the file's CONTENT, and the variable
that tells "no blob" from "the first candidate" was free to be removed. A
fixture whose candidate bases disagree about whether the file exists at
all pins it.
Three fixtures, three mutants killed: the fallback keyed on the extension,
the surface comparison widened back to disabled registrations, and the
criss-cross presence guard removed.
The header's three claims are corrected. It said movement falls back to
the bytes for "the Python and Rust shapes" (it is any file that measures
to nothing, in any dialect), that presence follows main having moved the
surface (main ADDING the path raises it with no movement at all), and that
comparing merge bases through `weaken_blob` buys agreement on trees,
gitlinks and mode-only changes (the identity it replaced already had all
three; what it buys is one reader instead of two that can drift apart).
The byte fallback's cost -- main touching such a file re-charges a
deletion an earlier round answered for -- is stated rather than left to be
discovered.
516/516 in scripts/tests.
* fix(autofix): let a skips-only file reach the bytes, and pin every field
A twelfth reverse audit proved the previous commit introduces no new
false accept -- structurally, and over forty thousand generated histories
-- and found one inconsistency and a coverage claim that was false.
`isEmptySurface` counted a disabled registration as content while
`sameSurface` cannot see one. A file that is NOTHING BUT disabled
registrations therefore took the comparison branch, where the comparator
is blind to the only thing in the file, so no rewrite of it could ever
read as main contributing. This repository has one such file. It reaches
the bytes now, like every other file the comparison cannot read, while
main moving disabled registrations on a file that has other content is
still not coverage arriving.
Four fixtures, four mutants killed, and each of the comparison's three
fields is load-bearing on its own for the first time: main planting a
guard (the live total falls, the declared total stands), main deleting an
assertion already behind one (the declared total falls, the live total
cannot), and main renaming a test (both totals stand, the multiset moves).
Those three survived every test in the file before.
The previous commit's message claimed dropping any of the three fields
fails a fixture. It did not; only the re-added disabled comparison was
pinned. The claim is not repeated here, and the comparison's own header
now says what it leaves out and where those files go instead.
The criss-cross presence assertion also gains the path check its sibling
already had: exiting 1 does not discriminate, because every guard mutant
still exits 1 -- charging main's own deletion to the round, which is the
hazard the guard exists for.
516/516 in scripts/tests.
* fix(autofix): close the escape hatches the review found in the weakening gate
- count-test-surface: see through type-only wrappers (`as`, `<T>x`,
`satisfies`, `!`) in constant folding, options objects and call
chains; fold comparison/equality/logical operators of two constants;
treat template-literal returns as non-thenable
- count-test-surface: an `if (true)` wrapper or a catch whose try holds
an assertion no longer shelters a body skip; a registration callback
handed by name resolves to its module-scope binding; a collector
factory binding (it.skipIf / it.each / test.extend) registers nothing
- af-155: charge a regression only when the run's window IS the live
re-arm key, and never stamp pre=green when the push carries a merge
past the head prepare classified
- tests: exercise all five own-lane names in the charge classifier, and
gate the verbatim staging witness on the host having sha256sum
Co-authored-by: Qwen-Coder <[email protected]>
* fix(autofix): arm-aware guard conditions, lane-list dedup, branch-side clamp (#10188)
Holes the round-27/28 review kept re-asserting on top of a211bc0:
- count-test-surface: the early-return silencer now fires only when the
runner provably reaches the return on EVERY entry — underCondition is
arm-aware for constant if/ternary/short-circuit operands (the executed
arm is no condition; the skipped arm is dead code), so the
environment-guard idiom `if (!cond) return;` keeps its assertions
measured (R27-21, R27-27's sync/async asymmetry, R27-1's else-arm).
HEAD's own contract.test.ts measured {assertions: 0} off that shape.
- constant() folds arithmetic and bitwise operators with JavaScript's
own semantics; opaque placeholders and bigints never enter the fold
(R27-19's remaining arms). `fixme` joins the disabling set (R21-2's
Playwright arm).
- measure: a merge event's landed delta is measured against the branch's
own side (new manifest `branch` field, emitted by the gate as
weaken_emit's fifth line), so a resolution that discarded main's
removal credits main with nothing (R27-20).
- af-155: the loop-owned lane exclusion lives in ONE workflow env entry
(LOOP_OWNED_LANES) referenced by all three head-state classifiers;
the hand-copied lists had drifted six lanes short (R27-23). Tripling
the list inline would have crossed the 470000-byte workflow start-runs
gate (the file stood 115 bytes under it), so the af-149 kill-block
prose moved to qwen-autofix.md per the file's own doctrine.
Every guard change is pinned discriminatively and mutation-checked both
ways; the gate-level fixtures re-spell their weakening shapes
unconditionally, with the conditional spellings pinned as no-charge.
* fix(autofix): Playwright-aware kinds, placeholder-safe folds, fallback numbering (#10188)
Round-30/31/32 leftovers:
- count-test-surface: the prefix arm no longer folds an operator over a
placeholder value — `-[]` is -0 (falsy) at runtime, never the
placeholder's -1 (R32-1); the object/bigint opacity guard is hoisted
and shared by the unary and binary arms.
- count-test-surface: Playwright's namespaced API is booked by kind —
`test.describe` is a suite whose disabled state propagates,
`test.beforeEach` is a hook, and the utility members (`test.step`,
`test.use`, …) register no phantom test (R30-2).
- push-and-report: the marker-only fallback now also carries the eval
marker, so a fallback-posted round consumes its round number and the
next round does not reuse it (R31-2's numbering arm).
- review-verification: the weakening rejection's ack list rides its own
workdir file and the remedy names it — the tail-bounded rejection
document used to cut a long list from the front while telling the
agent to ack "every file listed above" (R32-2).
Each new guard is pinned discriminatively and mutation-checked; the
Playwright row fails red without either the member gate or the kind
override, and the placeholder row fails red without the prefix guard.
* fix(autofix): measure a merge's landed delta by which side the resolution took (#10188)
The R27-20 branch-side baseline was right for a resolution that KEPT the
branch's side and wrong for one that took main's whole: the
binary-resolution-takes-main gate fixture (caught on CI) charged the
round for main's landed removal because `landed - branch` folds the
round's own discarded edit into the delta. The clamp now reads the
resolution per event: took main's side whole → main's delta landed
exactly; kept the branch's side whole → none of it did (the R27-20
case); a mixed resolution is still measured against the branch's side.
Mutation-checked in both directions, per the fixture family: forcing the
old merge-base baseline leaves binary-resolution-takes-main green but
reopens the R27-20 fixture red; dropping the kept-branch arm does the
reverse. The shipped rule keeps both green.
---------
Co-authored-by: qwen-code-ci-bot <[email protected]>
Co-authored-by: qwen-code-dev-bot <[email protected]>
Co-authored-by: qwen-code-dev-bot <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: probe <probe@local>