Skip to content

gateway: close mutation-test gaps; deep mutants in 32 two-stage shards - #118

Merged
jaredLunde merged 11 commits into
mainfrom
mutants-fanout
Oct 4, 2026
Merged

jaredLunde merged 11 commits into
mainfrom
mutants-fanout

Conversation

@jaredLunde

Copy link
Copy Markdown
Contributor

Summary

Results from the full mutation run over the gateway's core code.

  • New tests for real gaps (each verified by applying the mutant and watching the test fail):
    • reset_before_reading (D80): each condition false on its own.
    • plan_stream_usage_injection: a body that doesn't open with an object is never planned.
    • escaped_at: odd vs even backslash runs.
    • ModelScanner: a comma disarms a root message that wasn't an object.
    • Usage-tail wraparound at exactly its capacity.
  • Dead and equivalent code removed: the unused CircuitBreakerConfig::threshold, and a guard that could only skip an empty copy.
  • Deep CI: mutants now run in 32 two-stage shards: lib unit tests first, then only survivors against the full suite, at roughly 1.5–2 h wall time instead of a single run that couldn't finish. Each shard uploads its survivor lists as an artifact.

Remaining survivors from the 32-shard run will be added to this PR.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JimHGjsfk2Ktm5GxyZJKKk

jaredLunde and others added 10 commits October 3, 2026 07:56
…aker threshold()

Real gaps, each test verified to fail under its mutant by hand:
- reset_before_reading (D80): each conjunct alone false keeps the request unresent.
- plan_stream_usage_injection: a body not opening with an object is never planned.
- ModelScanner::feed: a comma disarms a root `message` that was not an object.
- escaped_at: an escaped `"type"` opening quote (odd run) is not a member.
- UsageTail: explicit exact-capacity and wrap-straddling usage event test (BIL-15).

Equivalent: UsageTail::push's `if rest > 0` only skipped an empty copy; removed.
Dead: CircuitBreakerConfig::threshold had no callers; deleted.

Five listed survivors (scan_buffered guard, root_open, escaped_at ==, push rest<0,
push head*len) were already killed by existing lib tests: the run shared one
CARGO_TARGET_DIR across --jobs 3, so builds raced and tested other jobs' mutants.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JimHGjsfk2Ktm5GxyZJKKk
…against the full suite)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JimHGjsfk2Ktm5GxyZJKKk
…ds filled disks and slowed every mutant)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JimHGjsfk2Ktm5GxyZJKKk
…or diff

--re over mutant names skipped most stage-1 survivors (26 of 41 in one shard),
so stage 2 never re-tested them against the full suite.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JimHGjsfk2Ktm5GxyZJKKk
…he full mutants run

53 listed survivors (translate.rs left to a parallel pass), every kill verified by
hand-applying the mutant and running the targeted test.

Real gaps (new tests):
- catalog_error_relay: a same-endpoint vendor's foreign error (DeepSeek) is put in the
  client's envelope; a sub-resource error (count_tokens) is relayed byte for byte. The
  D100 test had drifted off this path when grok moved to Responses.
- catalog_chat_relay: an OpenAI Chat stream is a byte relay (no identity bridge).
- request_filter's up-front body reservations: a declared body that fits the budget
  once but not twice is refused before a byte is read (headerless and header-won
  walks), and a buffered /{provider} body is refused before the upstream sees it.
- walk_reads_body / walk_reads_tools / candidate_path_is_responses unit tests.
- breaker: a generation-0 probe success closes it; Debug prints the config.
- value_end: a separator is no value; disable_openrouter_compression shapes;
  empty model span rewrite; a cancelled full-body attempt reaps as Finished.

Dead or simplified: redundant STATE_OPEN arms (breaker), splice_out's no-op guard,
InputTally::run's unreachable (None, Some) arm, estimate_stream_output's event
counter (deltas == 0 is the whole guard), clip_catalog_name -> floor_char_boundary,
catalog_chat_relay's client check (any other client is translated anyway),
request_summary's body moved to a tested summary_line.

Equivalent, excluded in .cargo/mutants.toml with reasons: pack's | vs ^, the TALLY
index | vs ^, close()'s flag clear, remove_items k<l vs <=, rename_max_tokens' comma
guard, force_include_usage s>at vs >=, request_summary delegation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JimHGjsfk2Ktm5GxyZJKKk
92 survivors in translate.rs: 88 killed by new tests, 3 removed as dead
code, 1 excluded as equivalent. No defects found.

Tests (request, response/stream, and pure-table unit tests) pin: the
reasoning-value table and both Claude relay rules, assistant refusals in
both directions, each legacy function form alone (and the modern field
winning), effort/budget tables, tool-result part mapping, strict-schema
nesting, json_object both ways, custom grammar flattening, allowed_tools
both ways, session-field edge inputs, the FNV-1a vectors, and Chat <->
Messages/Responses stream details (role chunk, summary separator,
item_id-only calls, nameless calls, escaped quotes, DONE-only streams).

Dead: message_text's Null arm (same as `_`), anthropic_system_text's
String arm (only ever called on an array), and the cache_control copy in
the Responses instructions helper (only its text was ever read).
Equivalent: ArgsEnd::whole's memo `||` (either operator, same answer).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JimHGjsfk2Ktm5GxyZJKKk
Without --file, --in-diff falls back to mutants.toml's examine_globs and skips route.rs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JimHGjsfk2Ktm5GxyZJKKk
84 survivors of the full mutants run in translate.rs: 67 killed by tests
(5 already by existing tests, 2 more by existing tests once the key
packing was factored out), 10 removed as dead or equivalent code, 7
excluded as equivalent. No defects found.

New tests pin: both bridge overflow bounds one byte past their size
(and MAX_STREAM_OUTPUT against the catalog), the 8 MiB thinking cap in
HeldThinking and Gather, Gather::close, the raw-JSON keep rules and
negative integers in KeepInputs, looks_like_error / in_client_envelope
/ status_error_type / anthropic_error_type tables, clip at a char
boundary, error_info's message sources and the overflow code, the
plain-text document mapping, Responses body created/text/content-null
rules, OpenRouter reasoning.summary onto Responses, interrupted
thinking never folded into a later signature, an unterminated last
event, byte-exact relay role cuts (LF, CRLF, unterminated), a repeated
call id without a role, nothing after message_stop onto Chat, every
Messages stream event shape onto Chat, and Responses refusal deltas.

Dead: map_response's and request's `a == b` arms (same as `_`),
KeepInputs' visit_string/visit_none/visit_some (serde_json's
deserialize_any never calls them), error_info's `is_object` guard on a
parsed raw error, chat_finish's tool_calls arm (responses_status reads
only length and content_filter), Usage::from_chat's `r > 0` (adds 0),
and flush's Responses `!errored` guard (error marks it completed).
ChatIdentity's key packing is one fn now. Excluded: KeepInputs
expecting/visit_unit, `|` vs `^` in ChatIdentity::key, and assembled's
clone-skipping guard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JimHGjsfk2Ktm5GxyZJKKk
…-run survivors

78 survivors of the full beyond-ai suite (proxy.rs, usage.rs, peek.rs, circuit_breaker.rs,
route.rs). Every kill verified by hand-applying the mutant and running the targeted test.

Real gaps (52, new tests):
- Large-body walks: every pool key is walked (30 keys) and the attempt bound's last attempt is
  the client's answer (150 keys, a 429 not a 502); each candidate of a three-candidate walk gets
  its own refused-stream resend, then fails over.
- Catalog walks: a refused stream is resent once; a reused-connection reset is resent on the same
  candidate; a cooling provider is skipped from any catalog slot; a rewrite ahead of `model`
  (developer->system, Claude-on-Chat reasoning cut, gateway reasoning cut) is rescanned so the
  model is re-spelled in place; same-wire relays keep reasoning_details (non-Claude host) and
  explicit nulls (Anthropic); a Responses stream's stream_options gets no include_usage.
- Body budget: bodies within the 64 KiB replay buffer are not charged; a chunked body that
  outgrows the budget is a 503, not a 413; a body declared at exactly MAX_REQUEST_BODY is read; a
  translation needing exactly the budget is a 503, not a 413; client_write_timeout_secs = 0
  disables the timeout.
- Headers and errors: a managed key beside mixed BYO credentials is not ambiguous; a BYO ?key=
  is forwarded; an allowed anthropic-beta line goes out untouched; a rewritten JSON error is
  chunked to an HTTP/1.1 client; a non-JSON error keeps its Content-Length; a streaming request's
  account remedy is neutralized.
- Billing: message_start behind a few KiB of pings is still read from the head; a final
  multi-line SSE event without a blank line bills; skipped strings (escaped quotes, non-letter
  keys) end at their own closing quote in the input tally.
- Units: h2_body_unsent, Redact with no key / a whole body, the unanswered-attempt 502
  (Reaped::unanswered), CircuitOpen's Display, str_is on an escaped value, has_typed_member's span
  end, an escaped stream_options in the injection planner, error_body returns the static table
  body (REJECT_BODIES is now a static, so the pointer check holds).

Dead or simplified (11): ModelScanner's and the planner's depth guards on ':'/',', the planner's
whitespace skip and key-skip condition, scan_buffered's key-skip condition, peek_body_model's
empty-chunk arm and `complete` guard, the walked-arms fold (now control::Walk::mask), and a
stream-only candidate's force_stream usage flag (the usage splice every Chat stream gets already
asks for it; ARCHITECTURE.md updated). BODY_PEEK_LIMIT's six comparisons are one
past_replay_buffer helper.

Equivalent, excluded in .cargo/mutants.toml with reasons (14): str_is's raw fast path,
unserved's and the TALLY tables' | vs ^, de_service_tier's Ok(None) visitors, UsageTail::push at
exactly the cap, release_body(0), settle_health's undecided arm, retain_managed_client_headers'
(None, _) arm, mask_all's at *= (same hits, quadratic), Ctx::deref (DerefMut's supertrait; a
panicking body passes the integration suites).

Already removed before this pass: 1 (the breaker's STATE_OPEN arm).

Found, not fixed: a managed 402 that fails over to the next candidate is never read, so its key
is never cooled (D180 cools only a relayed one) and every later request pays a round trip to the
unfunded provider first. Cooling needs the body (OpenRouter's "requires more credits" 402 is not
unfunded), which the failover discards.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JimHGjsfk2Ktm5GxyZJKKk
…r equivalent mutants

The replica breaker test opened A's breaker with a 2s reset and then asserted A routed away
for three turns: on a loaded host the turns outran the reset and A's half-open probe went to
the healed primary. That flake also made cargo-mutants count mutants as caught when nothing
else failed. Now two tests: open-phase routing with a reset far past the test, and recovery
with a 1s reset, polled.

ChatIdentity::key's & -> ^ keeps keys distinct (XOR with a mask is a bijection in range);
tally_eager (now its own fn) only picks where the same input estimate is made.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JimHGjsfk2Ktm5GxyZJKKk
@jaredLunde
jaredLunde merged commit 9041db0 into main Oct 4, 2026
16 of 20 checks passed
@jaredLunde
jaredLunde deleted the mutants-fanout branch October 4, 2026 15:38
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