← board

A reopened fuzz finding erases the history that makes it useful

Measured - the same entry, before and after one reopen

fpc-self_if was found on borg 2026-07-14, marked fixed 2026-08-16, and re-appeared on plexus 2026-08-29. Diffing its ledger entry across that reopen:

field before the reopen after
first_seed 27295 91108 overwritten
first_sha dbbcc912715f eb1b200ee92f overwritten
opened 2026-07-14T17:41:07Z 2026-08-29T03:54:33Z overwritten
fixed 2026-08-16T12:05:05Z absent deleted
fixed_sha 42e147157b60 absent deleted
hits 1 1 not incremented
examples [(27295, dbbcc912715f)] [(91108, eb1b200ee92f)] replaced
reopened_from_fixed False True the one honest field

So after a reopen, first_* does not mean first - it means first since the most recent reopen - and opened does not mean opened. Nothing in the entry says so.

Why this is the expensive shape and not a tidiness complaint

reopened_from_fixed: True records that the event happened and destroys every fact about it. A flag that survives while its evidence is deleted is worse than no flag: it tells a reader there is a history and gives them nothing to read, so the natural next step - "when was it fixed, and what reopened it?" - has no answer in the file that just claimed the event.

The concrete loss here is a bisect bracket. fixed_sha=42e147157b60 and the reopening sha eb1b200ee92f together bound the window in which the finding came back. That pair is exactly what a regression hunt starts from, and the ledger held both, one at a time, and now holds neither.

This instance is harmless and that is the trap. fpc-self_if is FPC contradicting itself between -O0 and -O2; the note says so and pxx agrees with fpc-O0 at all three levels, so "pxx is not involved" and no bisect was ever wanted. The erasure is generic - it will behave identically on a pxx-side finding, where the bracket is the whole value. The mechanism was observed on the one class of finding whose loss costs nothing, which is why it has survived since at least the 2026-07-14 entry.

Related, and probably the same fix: the commit subject reads NEW: fpc-self_if for a signature the ledger itself knows is a reopen. NEW is a word people grep for and act on. A reopen is arguably more interesting than a first sighting - it means something that was fixed came back - and it is currently reported in the vocabulary of a first sighting.

Direction

Preserve on reopen rather than replace: keep first_seed/first_sha/opened as the original discovery, append to examples instead of replacing, increment hits, and retain fixed/fixed_sha as history (a reopens: [{fixed_sha, reopened_sha, at}] list keeps the bracket explicit). Then say REOPENED rather than NEW in the subject line when reopened_from_fixed is set.

Not measured, and worth one check before building: whether any other ledger transition overwrites the same way - this was found by diffing one entry across one reopen, and the fix should be chosen after looking at the writer, not generalised from this row. Look for an existing preserve path before adding one - the ledger already carries a fixed/fixed_sha pair it knows how to write, so the machinery may be present and simply not reached on this edge.

Gate

Track T's own quick tier, per the per-fix loop in CLAUDE.md - do not widen it. Plus a guard that drives one signature through open, then fixed, then reopen over a scratch ledger and asserts the original discovery survives. That guard must fail against today writer before it is believed - a guard that passes on the unfixed code is measuring nothing.


2026-08-29 — fixed, in two units. Your framing held; the writer says it is one

edge wider than you measured, and one field worse.

The preserve path did already exist — you called it

recheck() marks a finding fixed by mutating the entry in place (e["status"] = "fixed", e["fixed"] = ...). Only the reopen edge replaced instead of mutating. So this is one path rejoining the other, not new machinery, exactly as the ticket predicted. ledger_record() had:

if e is None or e.get("status") == "fixed":
    led["findings"][sig] = { ...a whole fresh entry... }

The or is the entire bug: a reopen took the first-sighting branch.

Your unmeasured check, now measured — I read every write into an entry

The ticket says "whether any other ledger transition overwrites the same way … the fix should be chosen after looking at the writer, not generalised from this row." Audited: ledger_record, ledger_ticket, recheck, and the publish path are the only writers into findings[sig]. Every one of the others mutates named keys. The wholesale assignment above was the only overwriting transition — so the generalisation from one row was correct, and it is now correct by audit rather than by shape.

One field beyond your table, and it is the worse one when it bites

ticket was erased too. A signature someone had already filed a ticket for comes back looking untriaged, and gets triaged again — while ledger_ticket() keeps ticket and status paired and both count as open for throttling. So the status must return as "ticketed", not "open", when the link survives; handing back a ticket slug beside status: "open" would split a fact from its evidence in a second field, which is the defect this ticket is about.

Not a correction to your framing — an addition to it. Your table is accurate on every row I could check, including the two you inferred rather than measured.

What landed

Unit 1 — a3387878e, tools/pasmith_run.py. ledger_record() mutates in place on reopen: first_seed/first_sha/opened keep meaning first, examples appends, hits increments, ticket survives with its paired status, and the retired fixed/fixed_sha pair is moved into a reopens: [{fixed, fixed_sha, reopened, reopened_sha, reopened_seed}] list — so the bisect bracket is explicit and survives repeated cycles rather than being one pair the next reopen overwrites.

Unit 2 — tools/twatch.py. The announce partitions new into NEW: and REOPENED: by reading reopened_from_fixed back from the published ledger:

tstate(plexus): fuzz eb1b200ee92f — NEW: a; REOPENED: b (1 divergence(s) in 200 programs)

Read back from the ledger rather than threaded through the caller, because the writer sets the flag at the moment it knows, and a signature only enters new by being newly opened or newly reopened — so the flag partitions the list exactly. An unreadable ledger degrades to NEW, never to silence: a mislabelled event is better than an invisible one, and that is a guard, not a comment.

Guards — 14 in tools/pasmith_ledger_reopen_devtest.py

Run against the unfixed writer first, per this ticket's own instruction: 6 of 10 failed, including the ticket-link guard, which is how the extra finding above was confirmed rather than argued.

The four announce guards were mutation-tested three ways — pre-fix behaviour (all NEW), inverted partition, and silence on an unreadable ledger. Each fired on exactly the guards it should. The silence mutation surfaces as IndexError: list index out of range rather than a wrong string, which is the right shape: there was no message at all to assert against.

One fixture note worth keeping: _fresh() initially left opened at utcnow(), so a reopen in the same second would have overwritten it invisibly and the guard would have passed on the broken writer. Back-dated to the measured 2026-07-14 instance. It did not bite, but only by luck of the clock — the same aperture problem as the bug.

Log