Filed from the t0906 tech-debt round (2026-09-07), lane T1508 link 6 mutation evidence.
Observed
Two gremlins mutants on publishBytes's retry loop (messaging/amqp_client.go, post-#1508 shape) reach no verdict inside the package ceiling because the mutated loop never terminates and the only thing that stops it is the go test timeout panic:
INCREMENT_DECREMENT on retryCount++ → retryCount--: retryCount never reaches maxPublishAttempts, so the exhausted ceiling never fires; TestPublishBytesReturnsExhaustedOnPersistentPublishError blocks in the retry epilogue until the 60 s test timeout.
CONDITIONALS_NEGATION on the terminal-error guard (if termErr != nil → == nil): the not-connected terminal error is no longer returned, so TestPublishNotReadyReturnsErrNotConnected spins in publishAttempt/preparePublishing until the timeout.
Both are real kills (the suite fails), but by panic after 60 s rather than by assertion in milliseconds — so make mutate reports them as TIMED OUT and a human has to hand-adjudicate (done for #1508; baseline for the same filter runs in 2.5 s, so this is not build time — contrast #1524).
Why it matters
Any future change to the publish loop (#1496 deferred confirmations is next) re-pays the hand-adjudication, and a genuinely non-terminating regression would surface as a slow CI timeout instead of a named failing test.
Proposed shape
Bound the two tests (and any sibling that drives the loop to exhaustion or to a terminal error) with an explicit budget — a context.WithTimeout on the publish call or a bounded await helper plus t.Fatal on the test goroutine — sized well under the mutation ceiling (a few seconds), so a runaway loop fails fast with an attributable message. Test-only change; mutation evidence = the two mutants above now report KILLED.
Relation
#1508 (stack link refactor(messaging): one retry epilogue for publishBytes), #1497, #1496, #1524 (the different, cold-build timeout class), ledger lesson 2026-08-28 (bounded await kills a hang in seconds).
Observed
Two gremlins mutants on
publishBytes's retry loop (messaging/amqp_client.go, post-#1508 shape) reach no verdict inside the package ceiling because the mutated loop never terminates and the only thing that stops it is thego testtimeout panic:INCREMENT_DECREMENTonretryCount++→retryCount--:retryCountnever reachesmaxPublishAttempts, so the exhausted ceiling never fires;TestPublishBytesReturnsExhaustedOnPersistentPublishErrorblocks in the retry epilogue until the 60 s test timeout.CONDITIONALS_NEGATIONon the terminal-error guard (if termErr != nil→== nil): the not-connected terminal error is no longer returned, soTestPublishNotReadyReturnsErrNotConnectedspins inpublishAttempt/preparePublishinguntil the timeout.Both are real kills (the suite fails), but by panic after 60 s rather than by assertion in milliseconds — so
make mutatereports them asTIMED OUTand a human has to hand-adjudicate (done for #1508; baseline for the same filter runs in 2.5 s, so this is not build time — contrast #1524).Why it matters
Any future change to the publish loop (#1496 deferred confirmations is next) re-pays the hand-adjudication, and a genuinely non-terminating regression would surface as a slow CI timeout instead of a named failing test.
Proposed shape
Bound the two tests (and any sibling that drives the loop to exhaustion or to a terminal error) with an explicit budget — a
context.WithTimeouton the publish call or a bounded await helper plust.Fatalon the test goroutine — sized well under the mutation ceiling (a few seconds), so a runaway loop fails fast with an attributable message. Test-only change; mutation evidence = the two mutants above now reportKILLED.Relation
#1508 (stack link
refactor(messaging): one retry epilogue for publishBytes), #1497, #1496, #1524 (the different, cold-build timeout class), ledger lesson 2026-08-28 (bounded await kills a hang in seconds).