Skip to content

TRI-NET: three boards, ten families, and four numbers that were wrong - #355

Open
gHashTag wants to merge 186 commits into
mainfrom
trinet-fleet-truth
Open

TRI-NET: three boards, ten families, and four numbers that were wrong#355
gHashTag wants to merge 186 commits into
mainfrom
trinet-fleet-truth

Conversation

@gHashTag

@gHashTag gHashTag commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Three commits. The first answers a question that was asked; the other two are
things found while trying to answer it, and they matter more.

The cell is portable, and that was the surprise

Option C's falsifier was "port the cell to a non-Xilinx target and measure how
much of it is 7-series-specific; if most of it is, there is no IP to sell."
It
was expected to kill the option.

Splitting trinet_node_v2_ax7203.v found exactly two Xilinx primitives —
STARTUPE2 for the clock, DNA_PORT for the device identity — both board
concerns, not node concerns. With those lifted into a wrapper, the core
synthesises with zero errors on ten families from eight vendors and no
inferred multiplier anywhere. Nine of the ten recover 819 flip-flops; Intel
ALM finds 831 because its register cell absorbs reset logic the others express
separately.

xilinx ice40 ecp5 nexus gowin gatemate anlogic efinix nanoxplore intel_alm
LUTs 1737 1459 1286 939 1663 1180 1059 1187 1178 1267
FFs 819 819 819 819 819 819 819 819 819 831
mult 0 0 0 0 0 0 0 0 0 0

The wrapper instantiates the core rather than keeping a copy, so the claim
cannot rot while both files still build. CI asserts the invariant — sequential
state agrees, nobody infers a multiplier — not the numbers, which would fail on
a yosys upgrade for no reason.

This does not establish portability of product: synthesis is not P&R, no
non-Xilinx mapping has met timing, and only xc7 has run on silicon. It also does
not make anyone want the IP. The recommendation to defer option C does not
change
— its real obstacles were never engineering.

Four numbers were wrong

Found by pointing the host at the boards and disbelieving the first thing it
printed.

The third board was never broken. It had been recorded for a day as a wiring
fault — configured, DONE=1, UART silent. It answers at 1124474 baud against a
hardcoded 1186267: a 5.2% error where a UART tolerates about 3. Swept, it scores
6400/6400 over 100 independent runs, equal to the best board in the fleet.
The cause generalises: CFGMCLK is an untrimmed RC oscillator, and these three
dies run at 71.18 / 70.46 / 67.47 MHz — a 5.5% spread, not the 1.25% that
two samples had suggested. Line rate is now negotiated per board.

Every board is running a receipt key published in this repository. W01 nulled
the keys in the source and never reached the silicon. node0 verifies 64/64 under
0x00..0x0f, node2 63/64 under 0x20..0x2f. Every "keyed receipt verified on
silicon" result this project has reported is a tag any reader of the git log can
compute. A compromised key and a good key are indistinguishable to any test that
only asks whether the tag matches, so the suite stayed green. "Fixed" meant the
source changed and nobody checked the artifact.

Throughput counted jobs that failed. jobs_per_s divided by attempts. A
board answering nothing returns instantly, so total failure read as the fastest
run ever recorded — 5409 jobs/s against a transport ceiling of 4942, with 0/64
verified. Every published jobs/s figure is withdrawn rather than restated.

The host picked the wire format from its own config file. Response width came
from key != null; the width belongs to the bitstream. A keyless host read 15
bytes of a 19-byte response and offset every later read by four — a healthy board
reporting MalformedResponse forever, and throughput defect's accomplice.

Statistical base

100 independent runs × 64 jobs per board, port reopened every run (the FPGA frame
parser survives the host process, so that matters):

correct perfect runs min
node0 6400/6400 100/100 64
node1 6400/6400 100/100 64
node2 6308/6400 42/100 60

node2's loss is not the baud — swept to its own centre it scored slightly worse.
That hypothesis is cleanly falsified and the marginal link stands.

New laws and guards

  • Verdict.unverifiable — the fleet slashed an honest board 400 mTRI over a
    missing key-file entry. Holding the wrong key is a statement about the receipt;
    holding no key is a statement about the verifier, and only the first costs stake.
  • protocol.publishedKeyUsed() — a board on a published key gets no credit and
    no slash. It is honest and useless at the same time.
  • conformance/key_default_check.py — null defaults in RTL, explicit keys in
    testbenches. Both rules were broken at once and neither failure was visible:
    nulling the default silently disabled the testbench guarding the receipt, which
    had failed 0/6 ever since. CI breaks each rule on purpose to prove the guard
    still fails when it should.

180 tests pass across protocol, node, ledger, mesh, model and agent.

What this does not do

The boards are not re-flashed. Until they are, no receipt from this hardware is
evidence of anything, and the fleet correctly refuses to settle — it now explains
that instead of dying on NoEligibleNode. That is one day of work and it gates
everything downstream.

🤖 Generated with Claude Code

gHashTag and others added 30 commits August 9, 2026 00:28
A 100% failure rate is a broken harness until proven otherwise. A sweep
reported "PARSE OK: 0  FAIL: 496" — read literally, the parser was dead.
It was exit 127: cwd had reset and t27c builds to the workspace target
dir, not bootstrap/target/release/. The tell was the shape, not the
content — real breakage is ragged, a clean 0% or 100% means the
measurement never reached the thing measured. True count was 496/496.

A gate that is always bypassed is not a gate. scripts/tri was broken for
every subcommand (--repo-root passed before the subcommand, but it is a
per-subcommand clap option). It is pre-commit Gate 1/4, so it was not
fixed, it was routed around with --no-verify — invisible in history.
When a hook blocks you, read the hook before satisfying it; the block is
a symptom. And run the fences in your own README: doc-sync passes edit
prose and never execute the commands they ship.

Ask who occupies the corner you claim to own. COMPETITORS.md was sourced
and honest in tone, named five commercial NPUs it declined to race, then
claimed the formal/assurance corner while naming nobody in it — a corner
that holds Vericert, Kami, and Amaranth, all ahead on that axis. A
competitive document is not honest because each sentence is true; it is
honest when the omissions do not do the arguing.

Refs gHashTag/t27#1951

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
validate-conformance printed "43 valid, 58 empty/skipped". That went into a
report, a NOW.md entry, a GitHub issue, and a memory file as "the corpus is
half-hollow", and became the next wave's headline recommendation: populate
the empty files.

Zero were empty. The validator used .as_array() and the corpus stores
vectors both as arrays and as objects. 45 of the 58 were fully populated,
8 were schema files, 5 were benchmark reports. Among the false positives
was FORMAT-SPEC-001.json -- the numeric SSOT the positioning rests on,
reported as empty by its own repo's validator.

Two rules recorded. When a count is about to become a plan, open the
objects it counted -- sampling across categories, not depth, since the 58
held four distinct shapes and three files of one shape would have
confirmed the wrong conclusion. And a gate with a high false-positive rate
is worse than no gate: nobody reads warning 43 of 58, which is how a CLARA
coverage file covering 7% of the corpus stayed hidden for months.

This is the third instance of one failure mode in this campaign, now
stated in general form: the defect is never a wrong number, it is an
unexamined label. "FAIL: 496" meant binary-not-found. "58 empty" meant
object-shaped. Both were accurate counts of something other than their
word.

Refs gHashTag/t27#1951

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three consecutive waves each found one gate enforcing something weaker
than its label. check-now claimed NOW freshness and tested nothing (broken
wrapper, bypassed with --no-verify). validate-conformance claimed a
populated corpus and tested array-shaped payloads only. Gate 2/4 claimed
"seal coverage" and tested that a file exists -- while 730 seals verified
zero, 480 of them written the same day as the commit that rewrote the
specs they sealed.

Presence is not integrity, and only presence was enforced, so the drift
was structurally invisible. The audit that finds this class in one pass:
for each gate, write down the property its name claims, then read it and
write down the property it tests. The gap is the hole.

Two riders. A gate that cannot fail teaches people to route around it --
the always-red Gate 1/4 produced --no-verify commits, which disabled the
other three. And check file resolution, not just the predicate: Gate 2/4
resolved basename -> gf16.json while seal --verify reads a path-derived
numeric_triformat-gf16.json, coinciding only because macOS is
case-insensitive. Two naming schemes for one artefact is a defect even
when every test passes.

Separately: evidence citing a command nobody can run is not evidence.
clara_spec_coverage.json carried "bash scripts/clara/demo.sh -> 20/20
passed" for four months; that path does not exist in the repo. There was
no claim to narrow. Run an evidence file's own stated reproduction command
before reading its numbers.

Corollary on scope: regenerating a measurement is repair, rewriting a
baseline is a decision. Re-running coverage was mechanical. Re-sealing 496
specs would canonicalise whatever current codegen emits with no oracle
that it is right, so it was reported and left for a human. An audit that
quietly re-baselines what it audits has destroyed the evidence it was sent
to check.

Refs gHashTag/t27#1954

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Applying the gate audit to CI found three of nineteen workflows whose
entire job body was an echo statement -- seal-coverage, schema-validation,
check-now-freshness -- each reporting green on every PR. The README cited
one of them as evidence that conformance vectors were validated. That row
was backed by an echo.

This is the terminal form of the claimed-vs-tested gap: not a weak proxy
but no test at all, wearing the name of one, and invisible from every
angle except opening the file. grep -L for workflows whose steps run
nothing but echo finds the class in seconds.

Two repair rules recorded. Do not make a hollow gate blocking on the same
commit that makes it real: seal-coverage was wired to a checker returning
0 of 496, so it went in non-blocking with the enforcing flip named as the
next step -- a gate that blocks on a number nobody has agreed to is a new
outage. And never hollow a gate out to make it pass; delete it instead,
since a missing gate is visible in the workflow list and an empty one
reports green.

Separately: two derivations of one path is a bug even when every test
passes. Seal files are <parent-dir>_<module-name>.json from the spec's
module declaration; the gate guessed basename. It flagged a correctly
sealed spec as missing, and "passed" another only because an unrelated
GF16.json collided case-insensitively on macOS -- on Linux it would not
have. Case-insensitive filesystems conceal naming disagreements until CI
or a differently-configured contributor hits them. The fix is to delete
the guess, not correct it: when a shell script and a compiled tool must
agree on a derived path, the script calls the tool.

Refs gHashTag/t27#1956

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A campaign of audits produced a findings document, and write-up time is
where observations get rounded up into theorems. Two could not survive it.

A seal-path function was committed as "injective by construction". It is
not: the encoding flattens / to _, and _ is legal inside a path component,
so specs/a_b/c.t27 and specs/a/b_c.t27 collide. What was true and
sufficient is that it is injective on this corpus -- 496 specs, 496
distinct images, measured. The general claim was never needed; it was
reached for because it sounded finished.

Propositions now carry an evidence class: PROVED (machine-checked),
MEASURED (reproducible over a stated domain), CONJECTURE. That forces the
domain into the sentence, and the domain is where these claims break.

Three habits recorded. Write the counterexample into a test that asserts
the limitation holds, not a TODO comment -- a limitation pinned by a
passing test is maintained, one pinned by prose is not. A partial invariant
plus a guard at the mutation site beats a total invariant nobody
re-checks: the predecessor scheme had a stronger-sounding rule, no guard,
and silently destroyed a seal that stayed broken for months. And validate a
checking pipeline in both directions before trusting it -- a yosys recipe
was confirmed on a true property (exit 0) and a false one (exit 1), because
a pipeline that only reports success is indistinguishable from one that
checks nothing.

Separately: six arXiv ids were assembled from memory for a related-work
table. Fetching each citation_title showed one, recalled confidently as a
numerics paper, was actually about pandemic concern extraction on Twitter.
Identifiers feel retrievable and are not. One request per entry, and not
optional in a document whose whole value is that its claims are checkable.

Refs gHashTag/t27#1963

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… check

The plan was to preprocess SystemVerilog with sv2v so Yosys could read the
project's assertions. sv2v's README: "Assertions are also supported, but
are simply dropped during conversion." Supported means parsed without
error, not preserved. Measured on 0.0.13 -- property block and assert
property in, zero assertions out, exit 0, no warning.

Wired up, sv2v -> yosys -> sby would have run green while proving nothing.
Worse than the broken state it replaced, which at least failed at parse. A
pipeline can be built entirely from real, well-regarded tools and still be
theater.

Two rules. Read a tool's stated limitations before adopting it and unpack
the word "supported" -- one README line saved a green meaningless CI job,
and the install came after the README, empirical confirmation after that.
And when a transform can silently reduce your inputs, measure the output
population, not the exit code: the CI job now counts $check cells and fails
at zero, validated both ways (real emitter 2, sv2v 0). Exit 0 over an empty
set is the most expensive kind of pass, because nothing downstream can tell
it from success.

Separately: when a tool refuses your input, consider changing the input.
Two waves went into Yosys not accepting named property blocks. It does
accept immediate assertions, and nearly the whole property set maps onto
them -- a |-> b is assert(!(a) || (b)), a |-> ##N b is
assert(!($past(a,N)) || (b)). Meeting the tool where it is removed a
dependency instead of adding one.

Riders: name what does not survive translation in the artefact itself
(s_eventually is liveness and cannot be an immediate assertion; it is
reported in-file), because a partial translation that hides its coverage is
the vacuity failure one level up. And let the prover correct you -- the
first delayed-implication guard was rst_n alone, and the counterexample
(one cycle after reset the antecedent's history predates the reset) was a
design error found for free.

Refs gHashTag/t27#1965

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…wn answer

Formal verification found a lost-interrupt race in generated RTL. Two unit
tests covered exactly that code and both passed -- they asserted the
literal emitted text, so they passed for precisely as long as the bug
existed and failed the moment it was fixed. They were not testing the
design, they were holding it still. A string-match against emitted code
asserts "the generator still produces what it produced yesterday", which is
a snapshot, not a property. If a test would fail when the code is
corrected, it is pinning the bug rather than guarding against it.

Second lesson: prove the mechanism, not just the counterexample. A
refutation says a bad state exists, which is easy to file as "can
occasionally misbehave". Stating the failure as a property and proving it --
$past(inference_done) && $past(status_read) |-> irq_status[0] == 0, PROVED --
shows the event is always lost, not sometimes, and that is what justified
changing RTL instead of adding a caveat. Supporting habits: make the
experiment discriminating (two properties differing by one guard, one
proving and one refuting, isolate the cause), and validate a regression
harness against the broken version, since a harness only ever run against a
fixed design might pass because its properties are vacuous.

Third: when a property that cannot fail fails, the harness is wrong. Three
properties came back refuted including a tautology; the cause was yosys sat
refusing to run with more than one module selected, whose error reads
exactly like a refutation. Keep one property per harness whose answer you
already know -- it converts a class of silent harness faults into an
obvious contradiction, the same instrument as "a clean 0%/100% is a harness
fault" applied to a prover.

Refs gHashTag/t27#1967

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Five properties checked against an AXI-Lite slave, three refuted, two real.
The third -- bresp == 2'b00 -- was an artifact: bresp is only ever assigned
2'b00, so it cannot be violated in any reachable state. sat -tempinduct may
begin its base case from an unreachable state. From a reachable start it
proves; both genuine defects refuted under both settings.

Rule recorded: cross-check every refutation against a reachable start
before believing it. A real bug survives a change of proof method, an
induction artifact does not. More generally, a model checker answers a
question about a model, and the model includes assumptions about which
states can occur -- when a result is surprising the first suspect is the
state space, not the design. This is the reachability twin of the existing
rules: a tautology that fails means a broken harness, a refutation from an
impossible state means a mis-scoped one. And a false bug report is more
expensive than a missed one, because it gets acted on -- someone would have
"fixed" a correct reset value.

Second lesson: count what must balance, not what should look right. The
AXI handshakes were shaped correctly and VALID-stability proved on the
buggy design; every shape-based check passed. The defect was arithmetic --
ready never dropped while one response register existed per channel, so two
accepted transactions shared one response beat. Counting exposed it:
outstanding <= outstanding + accepted - completed; assert (outstanding <= 1).
When a design promises a conservation law, assert the balance directly.
Shape properties check each event looks right; a balance checks none went
missing, and the second is what hangs a bus. A counter also makes the
violating state concrete, so the refutation resists dismissal as an
artifact.

Both RTL defects this campaign were missing events -- a lost interrupt and
a lost response -- in modules whose per-event logic was individually
correct.

Refs gHashTag/t27#1968

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A master-side property was refuted -- "beats consumed must not exceed beats
requested" -- and was not filed as a defect, because rvalid was a free
input and the prover could have the slave deliver beats nobody asked for. A
misbehaving environment is indistinguishable from a defect in the unit
under test, and only assumptions tell them apart. Adding a minimal slave
model made a different property meaningful and it caught a real bug; the
over-read property stayed refuted after the fixes and was recorded as
inconclusive rather than claimed.

Three rules. An unconstrained input is an adversary, not a wire: any
property about a unit's response to its environment is a property about
that environment's contract too, so state the contract or state that you
did not. Report inconclusive as inconclusive -- the pressure at write-up is
to sort every result into bug or non-bug, and a third category names the
next piece of work precisely. And the assume list belongs in the artefact,
since a harness with hidden assumptions overstates coverage exactly the way
a hollow gate does.

Separately: the test's name can be the bug report. One test holding a
defect in place was called dma_burst_length_is_max, asserting a fixed
256-beat burst on every transfer while the RTL abandoned the burst early.
Its name asserted the defect as the contract, so an auditor reading the
test list would take it for a design decision. Another asserted
`if (m_axi_rlast || bytes_remaining <= 32'd8)` -- that || was the bug.

Eight such tests were rewritten this campaign and all four RTL defects had
one. Not a coincidence: a defect that survives review usually does so
because something in the repo asserts it is correct. Read test names as
claims about intent -- *_is_max, *_always_*, *_never_* are assertions about
the specification, worth checking against it.

Refs gHashTag/t27#1970

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…anomalies

A formal environment model is code and has bugs. Wrong permissively it lets
defects through; wrong restrictively it manufactures them, which is worse
because it looks like a finding.

An AXI4 slave model asserted rather than assumed its single-burst
precondition, and that precondition refuted. Read naively: the master
overlaps bursts. Checked instead with properties using only the unit's own
ports -- !(arvalid && rready), no back-to-back AR handshakes -- both proved,
locating the fault in the model, which cleared burst_active from its own
counter rather than from the master-visible rlast.

Port-only properties are the arbiter because they involve no model at all.
When a modelled property and a port-only property disagree, the model is the
newer, less-tested artefact. Two habits: assert a model's preconditions and
never assume them, since an assumption that the unit behaves is exactly the
assumption that hides misbehaviour; and key model state off signals the unit
drives rather than a parallel count, because two counters that should agree
are two things that can disagree.

Second lesson: record the anomaly instead of picking a story. The same wave
ended with arlen == 0 refuting at the address handshake while the hand-trace
said it must hold. Two tidy stories were available -- a fifth defect, or a
harness artifact -- both writable confidently, one of them wrong. It was
recorded as an anomaly: named, reproduced, explicitly unclassified, with the
dependent property marked open on the ground that a harness with one
unexplained result cannot settle a second.

The asymmetry that decides it: a false finding costs more than a missing one
because it gets acted on -- someone edits correct RTL, or writes off a real
bug as noise. There is a pull at write-up time, especially after a run with
several real fixes, to make every thread end in a verdict. "Reproduced,
unexplained, here is the exact command" is a complete deliverable.

Refs gHashTag/t27#1972

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e guards

Yosys's sat ignores $assume cells unless -set-assumes is passed -- opt-in,
no warning. A harness missing it still runs and still prints PROVED or
REFUTED with every constraint doing nothing, so a property meant to hold
given a compliant environment is quietly checked against an arbitrary one.
That cost a wave: an anomaly recorded as unexplained and a dependent
property left open, when the harness had never applied its own constraints.

The catch is two lines -- assume(1'b0) plus assert(a == !a), which proves
vacuously when assumptions are live and refutes when they are inert. It is
now the first step of CI. Generalised: whenever a tool takes constraints,
construct an input whose answer differs between constrained and
unconstrained, and check which world you are in. Same shape for a linter
config, a mock's expectations, a fixture's setup. Third instance of one
instrument this campaign -- a tautology that fails means a broken harness, a
gate that cannot fail is not a gate, constraints that constrain nothing are
the same defect.

Second: put assertions inside the module when you need a readable
counterexample. A wrapper forces -flatten, which mangles every name and
leaves VCDs full of $auto$async2sync internals. Appending the assertions to
a copy of the module gives one module, no flatten, and a legible
cycle-by-cycle table via -show. Hours went into reasoning about what the
trace might contain; obtaining it took ten minutes. If a tool's diagnostic
output is unusable, that is the bug to fix first -- everything downstream is
speculation.

Third: proving code unreachable is a reason to delete it, not to add it. A
clamp for a counter wrap was written and reverted, because the same session
had proved that state unreachable under contract, and the out-of-contract
case underflows to a large value where the result is arithmetically correct.
A defensive check whose trigger you just proved impossible implies a hazard
that does not exist. And sometimes robustness against a lawless peer
conflicts with correctness against the spec -- the spec wins, but say so, so
the absent hardening reads as a decision rather than an oversight.

Refs gHashTag/t27#1974

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…elete

A passing property is compatible with two situations: the design is correct,
or the property was never really evaluated. The second has two causes and
neither reports anything unusual.

Guard vacuity: G |-> P is free when G is unreachable. Exact oracle, no cover
support needed -- replace the assertion body with assert(1'b0), keep the
guard, and the run proves iff G is unreachable. Neutralise the file's other
assertions first so each result speaks about one guard.

Interesting-case vacuity: guard reachability is necessary but not
sufficient, since assert(!A || B) is trivially satisfied whenever A is
false. Probe the case the property exists to cover by asserting its
negation; a refutation is the witness that the case occurs. The one that
justified the exercise: a regression witness for a burst-abandonment bug
needed a multi-beat burst to mean anything, and on single-beat bursts alone
would have proved, stayed green, and silently stopped guarding its defect.

Make witnesses permanent as runs that must fail. A CI step expecting
refutation looks strange and is exactly right. Distinguish refutation from
error by grepping the prover's failure text, since a syntax error also exits
non-zero.

Recurring shape, now found in five places: a check that cannot fail is
indistinguishable from no check -- a shell gate that always errored and got
bypassed, a CI job that was one echo, a validator matching the wrong shape,
constraints the prover ignored, and properties whose antecedents might never
occur. Same instrument each time: include a case whose answer you know.

Separately, two mechanical traps that both presented as "the tool is
broken". Deleting assertions to isolate one left dangling if-statements and
nineteen tool errors; substituting assert(1'b1) keeps syntax intact and
semantics inert. And an insertion offset computed before a regex
substitution was applied after it, landing the probe past endmodule --
recompute positions after any length-changing edit.

Refs gHashTag/t27#1976

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two modules failed to terminate when a count parameter was zero: a
terminator written `index == count - 1` compares against all-ones and never
matches. Non-termination is liveness, and immediate assertions -- the only
form the open-source prover accepts -- cannot express one. The instinct is
to escalate tooling; unnecessary, because a loop that never ends almost
always has a counter or index leaving its legitimate range, and that is pure
safety. `valid |-> neuron_id < num_neurons` and `writes <= num_words` both
refuted before the fix and proved after.

Look for the safety shadow before escalating. "It runs forever" is hard;
"this index exceeds its bound" is a one-line assertion a bounded model
checker settles in seconds. Same substitution for deadlock (queue depth
exceeds capacity), livelock (retry counter exceeds limit), leaks (allocation
balance goes negative). An unbounded property often has a bounded
consequence, and the consequence is the one worth asserting.

Second: when siblings disagree, the odd one out is a bug rather than a
contract. A zero-count defect could always be intentional -- maybe callers
must pass non-zero. Nothing in the module says. But layer_sequencer already
guarded `num_chunks == 0` and did not do the same for neurons, and the
adjacent multilayer_sequencer guards `num_layers > 0`. A module that guards
one of its own two counts and not the other is not expressing a contract, it
is inconsistent with itself.

Before deciding an omission is deliberate, look at how the same question is
answered next door. A convention followed in three places and broken in one
is an oversight; a rule broken everywhere may be the real convention. This
settles bug-or-design without finding the author, and makes the report hard
to wave away since the counter-example is the author's own code. The sweep
is cheap: grep a family for how each member handles the degenerate input and
line the answers up -- divergence in the column is the finding.

Refs gHashTag/t27#1977

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…grated

A design question rode nine waves as the deferred recommendation: "BitNet v2
moves the binding constraint from weight width to activation width -- is a
ternary-weight datapath still right?" Fetching the abstract took one
request. BitNet v2 keeps 1-bit weights; its contribution is a Hadamard
transform making 4-bit activations viable. The premise was validated by the
paper, not threatened by it, and the question as phrased had no answer
because it presupposed something false.

A question repeated across sessions accumulates authority it never earned --
each carry forward makes it look like settled context rather than a claim.
Before doing the work a long-standing question implies, spend one request
checking the sentence it rests on. Especially when the question came from
your own earlier summary: restating your own inference back to yourself is
how an inference becomes a premise.

The corollary made the wave worth it. Answering properly meant reading what
the system actually does, and that is where the finding was: the paper said
weights were fine, the RTL said activations were ternary too -- more
aggressive than any published variant on the axis the field finds hardest.
Nobody had asked that, because everybody was asking the other question.

Second lesson: "N of N emitted" is not "N of N integrated". A README row read
"RTL pipeline · GREEN · 9/9 modules". Nine are emitted, so it is true. The
top level instantiates three; the MAC, weight memory, DMA, bus slave and
interrupt block are never instantiated, the memory port is tied to zero, one
input is declared and never used. A count of artefacts presented where a
reader infers a count of working parts -- a proxy metric arriving in a status
table instead of a CI job.

Two mechanical checks: for any "N components" claim, grep the top level for
instantiations and compare; and ask what the verification ranges over, since
module-scoped properties cannot imply system behaviour. When a project
reports progress as a count, find out what the denominator counts -- modules
that exist, compile, are reachable from the top, or are exercised are four
different denominators, and the gap between first and last is where the real
state hides.

Refs gHashTag/t27#1979

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… the property

Wiring a MAC to a memory with one cycle of read latency needs the control
delayed by one cycle. The property written for it -- mac_valid_q ==
$past(layer_valid) -- is true and provable and useless: it constrains the
register, not the consumer. Rewiring the MAC's valid_in straight to
layer_valid, reintroducing exactly the hazard, left it PROVING, because the
register still existed and nothing tied it to the MAC.

The repair phrases the property over the consumer's own output, which can
only be what it is if the consumer saw the intended input. Correct build
proves, unskewed build refutes. When asserting that A drives B, phrase it
over something only B can produce -- a property naming only signals upstream
of the connection cannot see the connection. This is the integration-level
form of vacuity: not an unreachable guard, but a true statement about the
wrong side of a wire.

Two carry-overs. Composition bugs are invisible to module-scoped
verification by construction: sequencer correct, memory correct, MAC
correct, assembly wrong. And the rule that caught it was already written
down -- validate a regression harness against the broken version. Two
minutes, and the only reason eight green properties were not shipped with
one certifying nothing. A harness only run against a correct design has not
been tested, it has been demonstrated.

Separately: reduce the model, not the property, when the prover cannot
handle a construct. sat cannot model $mem_v2, so a 4096-entry BRAM errors.
Shrinking it is correct because the properties never read memory contents,
so chparam -set DEPTH 4 plus memory_map changes nothing they observe and the
claim proved is the same claim. Weakening the property would silently narrow
what was verified; reducing the model narrows only what is simulated, and is
auditable. State which you did -- "proved with reduced memory depth, because
no property reads memory" is a complete disclosure, "proved" is not.

Refs gHashTag/t27#1980

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…onditions

A design question sat open for ten waves -- ternary activations or 4-bit --
and could not be answered because the choice had no address. It lived in the
absence of a module: nothing said "activations are ternary", the datapath
simply had no layer boundary and the assumption leaked in through whatever
the neighbouring ports happened to be. Building the missing module changed
the question's status without answering it; the width is now one output
port, and a 4-bit variant is a diff.

An unmade decision with no interface is untrackable. Before agonising over
an open architectural question, check whether the artefact that would embody
it exists -- if not, building it is usually worth more than deciding. And an
assumption implied by an absence is the hardest kind to audit: grep finds a
wrong constant, nothing finds a missing stage.

Second: prefer a total function to a documented precondition. A negative
threshold makes two comparisons overlap; as parallel comparisons that is a
don't-care, and in a design whose output alphabet has a reserved invalid
code a don't-care is silent corruption with no error path downstream. A
priority chain costs one ternary operator and is legal for every input. A
precondition is a promise made by code you do not control. Any enum with an
unused encoding deserves a proof it is unreachable, not a comment.

Third: a substring ban catches the documentation that justifies it. Three
times now -- 8'hFF, 2'b11, FORMAT-SPEC -- a test forbidding a literal failed
on the comment explaining the ban, and once on the assertion enforcing it.
Narrow to the syntactic context: strip comments, skip assertion lines, match
an assignment rather than an occurrence. The tell is a test failing on the
very commit that adds the safeguard.

Fourth: a test whose name contains a number gets renamed every time the
system grows. bundle_order_has_twelve_entries and two entries[9]/entries[10]
lookups broke on adding one file. The invariants they should have carried --
len() == declared count, last entry is the manifest, lookup by filename --
survive growth untouched. If a test's name must change when the system grows
correctly, it is asserting the wrong thing.

Refs gHashTag/t27#1981

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…s its own class

A double-buffer controller computed a ping-pong decision, the top declared a
wire and connected it, and nothing read it -- the activation memory had
wr_en tied to 1'b0, so there was no path from a layer's output to the next
layer's input. The whole thing was visible in one number: grep -c gave 2,
meaning a declaration and a connection with no consumer. Three or more means
something reads it.

Count uses of each signal crossing a seam and look at every one with a count
of two. Same for a module whose outputs go nowhere and an output assigned
from a constant. A tie-off is a deferred decision, invisible in any view
that does not span the boundary it sits on.

Second: check the rate, not just the name, when reusing an address. The
double-buffer controller already produced write_addr, so wiring it looked
obvious -- and was wrong by 27x, because write_addr counts neurons and the
requantizer emits one packed word per 27 neurons. When connecting two
stages, compare their rates before their names: packing, batching and
serialisation all introduce a divisor a plausible name hides.

Third: integration defects are a distinct class and cannot be front-loaded.
Three consecutive waves found a latency skew, an absent stage and a dead
control signal -- every module had properties, every property proved, and
the composition was wrong in three different ways. Module-level verification
bounds its own claim, and saying so is part of reporting honestly: "28
properties proved" implies far more than it delivers if the modules are not
wired together. The seam has to exist before you can assert across it, so
expect the first assertions across a new seam to find something.

Refs gHashTag/t27#1983

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ed decisions

Wiring a prefetch controller to a shared memory produced a property that
refuted -- the prefetcher could write an address the compute stage was
reading -- and it still refuted with the environment constrained, so it was
not an artefact. Three options existed: ship the failing assertion, weaken
it until it passes, or record the gap.

Option three, every time. Shipping a failing assertion turns a finding into
an outage. Weakening it is deliberate vacuity, the exact failure catalogued
elsewhere in this file, committed knowingly -- and it is worse than a
missing assertion because it actively certifies the thing it stopped
checking. The pressure at the end of a session is to leave everything green,
and a weakened assertion looks exactly like a solved problem.

When recording, state three things: the property as written, that it was
reproduced under a constrained environment, and that it is not asserted.
That turns "we did not finish" into a precise starting point.

Second: count the tie-offs, they are decisions someone deferred. Every
structural gap in three waves of integration announced itself as a constant
-- prefetch_done = 1'b1, mem_addr = 32'd0, mem_rd_en = 1'b0, and wr_en(1'b0)
on both memories, meaning the weight memory in an ML accelerator had never
been loaded. Only the first carried a comment saying why.

A tie-off is a deferred decision wearing the costume of a design choice.
They are trivially greppable and each one bounds what any test above it can
be checking -- an engine whose memories are tied to zero still passes every
property about its sequencers. So grep for constant port connections before
believing an integration is complete, and when writing one, put the removal
condition in the comment: "tied off until X is wired" is a to-do with an
owner, while a bare 1'b0 is indistinguishable from intent.

Refs gHashTag/t27#1984

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…less output

A property refuted, the trace identified a stale completion flag, the flag
was fixed, and the property still refuted. The pull at that moment is to
conclude the diagnosis was wrong and revert. It was correct and incomplete:
a second independent defect sat in a different module, a level-triggered
handshake sampled one cycle too early, and each defect would have been
masked by the other's correctness.

Re-read the trace instead of reverting. If a fix changes the
counterexample, it addressed something real; a fix that leaves the
counterexample identical is the one that missed. Composition defects cluster
-- two modules each individually correct can disagree at their seam, and
when two seams are broken at once, fixing either alone changes nothing at
the top. Treat "still failing" as a request for the next trace, not a
verdict on the last one.

Second: the recorded gap is what made the fix possible. A previous session
left the property reproduced, documented and not asserted, rather than
weakening it. A softened assertion would have shipped two real defects under
a green check and nobody would have taken the trace. An honestly recorded
failure is a work item; a weakened assertion is a lie with a maintenance
cost. Leaving something visibly unfinished costs discomfort and preserves
the information; papering over it costs nothing now and destroys the
information permanently.

Third: ask the tool for the signals you named, not for a dump. Two waves
were lost to -dump_vcd after -flatten producing files full of mangled
auto-generated names. Top-level names survive flattening, so naming them
with -show gives a clean table, and two independent defects were visible in
one reading of it. When a diagnostic dump is unusable, ask for less rather
than more -- a named projection of the signals you suspect is legible by
construction, and it is usually one flag away.

Refs gHashTag/t27#1985

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…obsoleted

An AXI-Lite slave was instantiated to replace top-level config ports with
CSRs. Instantiation is easy to verify by eye and easy to get wrong in a way
nothing notices -- a previous wave left a double-buffer controller connected
to a wire no consumer read, dead for four waves.

So the properties name the connection, not the module: start == reg_ctrl[0],
and reg_status reflecting busy and done. Both would be vacuously true if the
slave were instantiated and ignored, since start would simply be something
else. When integrating a component, assert an equality that spans the
boundary -- "the module is instantiated" is greppable and means little,
while "this internal signal equals that register bit" cannot hold unless the
wire exists. Cheap version: the grep-count rule. Strong version: a property
that fails if the connection is removed.

Second: tests named for an interface break when the interface improves, and
the fix is often to invert them. Replacing the config bundle broke three
tests, none wrong about the old design, all asserting an incident of it. The
rewrite renamed them and inverted them -- they now assert the absence of the
old ports as well as the presence of the new ones. When a change makes a
test obsolete, ask whether the negation is now the interesting claim: often
what you just removed is exactly what must not come back, and inverting
converts a broken test into a regression guard for free.

Related smell seen repeatedly: a test asserting a string containing
formatting breaks when a declaration is realigned. Match the semantic part
or normalise whitespace -- a test that fails on changes invisible to
git diff -w is measuring the wrong thing.

Refs gHashTag/t27#1987

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A double-buffer invariant said never write the buffer being read. Correct,
proved, and it had guarded a real defect. Then a second writer arrived -- an
input DMA whose whole purpose is to fill the buffer about to be read,
because that is where the first layer's input belongs -- and the invariant
flagged the correct new code as a violation.

The reflex when a proved property starts failing on a change you believe is
right is to weaken or delete it. Neither was right: the property was always
about the requantizer writing into a buffer under active read, and that
scope was implicit only while there was one writer to be confused about.
Adding a producer to a shared resource is the moment to re-read every
invariant about that resource -- not to relax them, but to make explicit the
domain they were always about. Same for a second caller of a function, a
second writer to a table, a second scheduler on a queue.

Corollary: if scoping an invariant makes it vacuous, it was the wrong
invariant. Here the scoped version still bites, which is the check that the
scoping was honest rather than a way of switching it off.

Second lesson: busy was a decode, not a state. An interlock needed "is the
engine running?" and the available signal was
busy = (current_layer != 0) || layer_start -- a decode of a counter, false
during the entire first layer, so the interlock has a hole exactly where the
first inference happens. This is the proxy failure the campaign kept finding
in gates, arriving in RTL instead of CI.

When something needs a state, give it a register. Deriving it from whatever
is nearby costs nothing and produces a signal right in the common case and
wrong at the edges -- the worst distribution for a safety interlock. And
before keying safety logic off an existing signal, read its definition
rather than its name: busy, ready, done and active all invite the assumption
that someone maintained them as state.

Refs gHashTag/t27#1989

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… partial closes

A DMA and a compute engine shared a buffer and must never run together. The
guard blocked the DMA while compute was active, and nothing blocked compute
while the DMA was active -- a host starting the DMA then requesting
inference walked straight through. Mutual exclusion is symmetric by
definition, and a guard for it is naturally written from whichever side you
were thinking about when the hazard occurred to you. Write the second
direction at the same time, or the guard encodes the order in which you
imagined the failure. Mechanical test: for "A may not start while B runs",
ask whether "B may not start while A runs" exists.

Second: when a change invalidates a property, ask whether it becomes two.
An interlock broke a property asserting start == ctrl[0], correct before and
deliberately false once the interlock fires. Deleting or relaxing it are the
obvious moves; splitting is better. The general form allows the interlock,
and the original is kept under if (!dma_busy) -- pinning down that the
change did exactly what was intended and nothing more. Relaxing the original
would silently permit any future condition to suppress a start too. A
property invalidated by an intentional change usually splits into "the new
behaviour" and "nothing else changed", and the second half is the one that
catches the next unintended broadening.

Third: two fixes, neither sufficient -- report that, not the last one. A
state signal that was actually a decode, and a one-directional guard on a
symmetric constraint, both genuinely fixed, and the property still refutes.
The temptation is to describe the wave by its last action or keep going
until something is green. What is true: the property is the sole remaining
failure, confirmed by neutralising it alone and watching everything else
pass; the residual cause is bounded to a timing relationship; both fixes are
independently worth having. A partially-closed gap, precisely bounded, is a
better deliverable than an unbounded-but-green one.

Refs gHashTag/t27#1990

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
One property refused to close across three waves. Each attempt was real and
each narrowed the window: a status signal that was a decode became a state
register, a one-directional guard on a symmetric constraint became
symmetric, and the guard was extended across three more pipeline valids. All
correct, none sufficient -- and the pattern, each fix narrows and none
closes, is itself the diagnostic.

The fourth attempt was spent on a trace instead, and it showed why: the
supervisor being gated runs its own state machine and does not stop when the
host clears the request bit. The signal the gate keyed off tracked a
request, not a state. Quiescence lived inside a submodule and was not
observable from where the gate was written.

When successive guards each narrow a window without closing it, stop adding
conditions and ask what the guard cannot see. Accumulating terms at the
observation point is the signature of a missing observable, and the fix is
usually one output port on the module that actually knows rather than a
fifth conjunct on the module that does not.

The corollary that made stopping right: the diagnosis is a better
deliverable than a fourth narrowing. Naming the change converts an open bug
into a scoped task and leaves the accumulated conditions replaceable by one
that answers the right question, rather than entrenched as four that nearly
do. Same distinction as request-versus-acknowledge in a handshake, one level
up -- a supervisor that can be asked to stop is not one that has stopped.

Refs gHashTag/t27#1992

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… lost time

A four-condition interlock was replaced by one better condition -- the
module's own idle state, exported so the guard could ask the right question.
The new guard was more principled, shorter, and still wrong: seq_idle
subsumed three of the four old terms but not !reg_ctrl[0], which existed for
a different hazard, a host setting two control bits in the same write. At
that instant the sequencer genuinely is idle, so the new condition permits
exactly the case the dropped term blocked.

Before replacing a compound guard, write down what each term was for. A
replacement covering three of four leaves a hole precisely where the fourth
was, and it is harder to see than the original mess because the new guard
looks derived from first principles. The tell: the failure survived a fix
that was independently correct -- same signal as elsewhere, a refutation
surviving a real fix means another cause, and here the cause was something
the fix removed.

Second: time spent understanding why a fix fails is not lost from fixing it.
One property stayed open four waves -- recorded rather than weakened, then
two narrowings that shrank the window without closing it, then a wave that
produced a diagnosis instead of a fix, then a five-line change. Waves two
and three look like failure and were not: they bounded the problem until it
could be said why no top-level fix could work.

The temptation throughout is to weaken the property so the run goes green.
Each wave offered that exit, and each refusal is why the final fix was five
lines in the right place rather than a fifth condition in the wrong one.
When successive fixes each narrow without closing, spend the next attempt on
a trace rather than a fix -- the pattern is diagnostic. And a diagnosis
naming a concrete change is a complete deliverable, since the person acting
on it may be you next session with no memory of the reasoning.

Refs gHashTag/t27#1993

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four consecutive sessions were spent adding interlocks, each narrowing what
a system may do, each correct, and every safety property passed at the end.
That is exactly when a green result means least: an over-tight guard makes
every safety property hold by making the system do nothing, and no safety
property can distinguish "the bad thing cannot happen" from "nothing
happens".

So the next session audited instead of extending. Six probes, each asserting
an activity is impossible, where a refutation is the evidence it still
occurs -- the DMA can still start, compute can still run -- paired with the
inverse, that the two are never concurrent, which must prove. That pair is
what carries the claim: a safety property and a liveness witness together
say something neither says alone, since "this combination is impossible" is
only interesting once "each of these is possible" is established.

Two habits. Order matters -- audit before extending, because a new property
built on a stalled system proves trivially and the error compounds into
everything after it; one session of checking bought certainty for all the
work that follows. And write the liveness witnesses as CI rather than as a
one-off, since the hazard is not that today's guard is too tight but that
tomorrow's will be, while every safety property keeps passing.

Applies well beyond hardware: after tightening validation, rate limits,
permissions or retry conditions, the tests that still pass are not evidence
the system works -- they are evidence it does not do the forbidden thing,
which an inert system also achieves. Add a check that the permitted thing
still happens.

Refs gHashTag/t27#1995

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Five lessons from the cross-layer wave in t27.

A probe harness that reads a nonzero exit as "the probe refuted" cannot tell
your property failing from something else failing. An obligation yosys generates
itself began failing and every row of a liveness table silently flipped,
reporting on a failure no probe caused -- three confident wrong attributions
before the baseline run found it in one command.

A reference placed above its declaration makes Verilog conjure an implicit net
with the same name, so the code reads an undriven twin. In a generator the
insertion point is a correctness property, not formatting.

Completion is not evidence that work was done -- third instance of the
zero-sized-job shape. An open finding needs a CI gate asserting it still
refutes, or it rots. And a fix that does not fix the target while regressing the
baseline should be withdrawn, however much work it cost.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three lessons from the zero-sized-request sweep in t27.

A generated file's comments are not evidence about the generated file. An
emitter's comment said a zero-length request completes immediately; the code
dropped it entirely. Reading the comment produced a published claim that was
specifically wrong and propagated into a proposition, a README row, a commit and
an issue before a sweep contradicted it. Grep for behaviour, not prose.

When a defect shape appears twice, enumerate the class instead of fixing
instances. One sweep found both remaining cases at once and surfaced a policy
question no single-module investigation had: four modules disagreeing about the
same degenerate input, where neither policy is wrong alone.

The unobservable outcome is the dangerous one. A silently dropped request beats
no ranking -- it is worse than a vacuous completion, because the caller waits
forever for a result that was never coming. Prefer the outcome a caller can
observe, and assert both halves: that it completes, and that it did no work.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four lessons from auditing the t27 formal-foundations document.

Two reproduce blocks were added that violated a rule recorded in the same file,
by the same author, in the two waves right after that rule was written. Neither
was ever executed. Writing a rule down does not apply it; only a check that
fails when it is broken does.

Documentation is evidence and should be audited like evidence. 14 of 19
shell-fenced blocks were transcripts wearing a command's clothes -- a reader
cannot tell them apart. The fence is a type annotation. Then execute the
runnable ones and check specifically for exit 127.

Trace claims to gates mechanically, then check the misses by hand: of six claims
whose identifiers matched nothing, four were false negatives of the extractor.
A heuristic that produces a shortlist is working; treating its output as a
verdict is how an audit becomes the thing it audits.

And distinguish having a check from the check being sufficient -- recording that
limit is what keeps an audit honest.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three lessons from the gate-adequacy wave in t27.

Redirect the vacuity oracle at the gates themselves: for each gate, apply a
mutation that should violate the claim it guards and require it to go red. Three
phases, and the first two make the third mean anything -- a baseline where the
unmutated build passes everything, and a control with a semantically neutral
edit where everything still passes. Skipping the control is the subtle failure,
because a gate that fails unconditionally scores 100% on mutation testing.

Include at least one mutation that makes the system inert rather than wrong.
Stalling the system leaves every safety property true; only liveness notices. If
no gate goes red when the feature is disabled entirely, the suite is measuring
silence.

And state the lower bound the method gives you, at the same prominence as the
number. Mutation testing bounds from below, never from above. A result reported
without its ceiling gets cited later as if it had none -- usually by whoever
produced it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…lation is

Four lessons from the maximum-size sweep in t27.

A bounded check that cannot reach the counterexample reports success and
establishes nothing -- two modules passed a wrap property whose violation needed
4096 writes against a 24-cycle bound. Scale the model until the counterexample
fits, and state what scaling changed. Generalises to fuzzing, load tests and
soak tests: a green result from a search that could not have found the bug is
not evidence.

When a correct fix does not make a property pass, there is a second cause. The
most valuable defect of the wave was found that way; the temptation to revert
the fix or weaken the property destroys the signal.

An unconstrained input is an adversary -- two refutations were faults in my own
harness and looked exactly like design defects. A refutation is a claim about
the environment until the environment is pinned down.

And stop patching after the second attempt: the next action is a counterexample
read carefully, not a third patch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
gHashTag and others added 13 commits August 12, 2026 18:09
…angerous half

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…us before publishing a ratio

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ber can be the symptom

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ss boundary

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ons coarser than the defect hide partial fixes

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e warnings above the error

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…; ask Print Assumptions

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…structive checks need death-proof restores

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… cause the failure

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…n, do not read it

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…m or it recurs in one wave

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ge notes are a queue

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@gHashTag
gHashTag force-pushed the trinet-fleet-truth branch from 94f042c to fa6b480 Compare August 19, 2026 20:21
gHashTag and others added 16 commits August 20, 2026 11:29
A stale worktree 2399 commits behind master fabricated three findings in one
session, including a red CI gate that was reverted 34 minutes after landing,
and a rediscovery of a bug fixed two months earlier and presented as new.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…er reaches the code

Three lessons measured this wave:
- reachability, not depth: 406/485 functions have zero call sites, so
  deepening yosys flags 1 of 28 real defects and certifies 27
- a producer that annotates what it cannot lower and emits it anyway is
  a fail-open; the detector already exists, the edge to refusal does not
- proximity in a file is not attribution: 489/618 belonged to a different
  cause and that repair had already landed

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rately

A both-lines mutant proved half of what it appeared to: the first
assertion panics and aborts, leaving every later assertion unproven.
Plant one mutant per guard and read which assertion fired.

Two companions from the same pass: a pre-push gate that blocks you is a
question (the file it wanted was tracked in git, merely missing from a
sparse checkout) and a diff should carry only the defect it proved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Restoring a hardcoded verdict flag failed to break any test, which reads
as an uncaught fail-open. It was an equivalent mutant: every failure path
bails before the verdict is computed, so the flag is unobservable.
Composing it with a real 23-of-24 defect caught it.

Companion: a vm_compute proof pins what it computes over, not every
literal in its statement -- one side was uniquely determined, the other
admitted three values, one of which passed coqchk.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ards nothing

Mutation left the equivalent-mutant defence open. Copying the test body
plus only the types it names into a file with no production logic settled
it: before, 1 passed; after, 38 compile errors and no binary.

Two companions: prove consumption with the built artefact (two binaries,
stubbed gh, exit 1 vs exit 0), and never let an unreadable history share
a representation with zero -- one 502 downgraded every finding.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ng one

A deliberate fail-open hunt: 272 hits, 1 finding. Neither real result came
from the patterns -- one from reading a file whole, one from a set
difference (declarations vs assignments) that exposed a name never
assigned.

The fail-open family is disproportionately made of absences, and text
search enumerates presences. Build two sets and diff them.

Companion: the harness itself is in scope -- unquoted $PATHS under zsh
returned zero for all seven patterns and nearly shipped as a clean sweep.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… pass

Three tests printed SKIP and returned; libtest reported 173 passed,
0 ignored, and cargo swallowed the reason because passing tests' stdout
is captured. Invisible twice over: in the count and in the log.

Companions: a ratchet must baseline a set of name+reason pairs emitted by
its own parser, and must say 'not evaluated' rather than zero when it
cannot see; and an instrument never run against a non-defective 'before'
has demonstrated nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rtions executed

Adds the copy_propagate finding (return result -> return undefined, valid Zig
returning garbage), the bare expect() fix that unblocked 27 specs, and the
packed-format defects in specs/base/types.t27 filed as #2677.
… without moving 39

Records the flat-harness defect (it resolved imports by basename, which is not
how Zig resolves them) and the reverted relative-path attempt: Zig forbids a
module from reaching above its root directory, so 92 absent siblings became 92
illegal paths while specs-that-run stayed at 39.
…ror, not the fix

Records that yesterday's revert was wrong (Zig forbids leaving the module root,
not '..'), the harness change that proved it, and the three distinct defects
hiding behind one 'missing module' count.
Records the use-path split (149->150 valid, 1193->1159 errors, 41->15 absent
imports) and the measurement that 35 of 55 import-blocked specs depend on a
Rust-dialect module the emitter is not expected to parse.
…46 to 74

Records the math/constants conversion, the two constructs that failed silently
(dropped 'as' casts, a Rust range loop that swallowed the rest of the function),
and the numerical defect it exposed in pow(4.0, 0.5).
…alse-positive check

Records the emitter deleting every 'x as T' (compiling code with different
semantics, invisible to every instrument) and the correction from 645 to 639
after finding prose in the count-1 tail of the frequency table.
…ression VALID could not see

Records that 6 of 12 cast sites are untested (so yesterday's zero difference
proved less than it looked), the hyphen-as-path-separator rule verified against
the tree, and the module-binding regression only the per-spec error delta caught.
…r read

Records nearly rewriting the corpus for a compiler deficiency (the spec declares
'module tritype-base;' itself), the 281/237 declared-name count, and the third
instrument added because both existing ones were structurally blind to it.
… that compared strings

Records the 'using' keyword removal (39->35 unresolved imports, 110->106 gate
events) and that the per-spec error delta I relied on for two iterations was
comparing '2' > '14' lexicographically.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant