Skip to content

AMT coverage: g3-graphql-query-processing - #193

Open
thedavidmeister wants to merge 4 commits into
mainfrom
2026-08-22-amt-g3-graphql-query-processing
Open

AMT coverage: g3-graphql-query-processing#193
thedavidmeister wants to merge 4 commits into
mainfrom
2026-08-22-amt-g3-graphql-query-processing

Conversation

@thedavidmeister

@thedavidmeister thedavidmeister commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

AMT (adversarial mutation testing) coverage for the g3-graphql-query-processing group: 30 behaviours across crates/cli/src/meta/query/mod.rs, crates/cli/src/meta/types/solidity_abi/v2.rs, crates/cli/src/meta/types/authoring/v1.rs and crates/cli/src/meta/types/authoring/v2.rs.

Process: baseline green on unmutated code; the full 81-mutant probe ran against the pre-existing suite FIRST (16 killed, 65 survived); survivors got new discriminating tests (41 tests added, no existing test edited or deleted); the full probe then re-ran against the finished suite. Two mutants remain unkilled because they are provably equivalentSolidityAbiMeta validation is vacuous (zero #[validate] rules), so removing the validate gate or its early return changes nothing observable; filed as #153 rather than papered over.

Adversarial findings filed (all labelled audit + adversarial): #153, #154, #156, #157, #160, #164, #166, #168.

Behaviour matrix

Verdict legend: pre = killed by a pre-existing test in the first probe pass; new = survived the first pass, killed by a test added here; equivalent = survived both passes, no discriminating test exists (see #153).

Behaviour Mutation Verdict Killing test
query/mod.rs — process_deployer_query
bytecode None guard -> NoRecordFound Q01: guard else-arm returns Vec::new() new test_process_deployer_query_null_bytecode_is_no_record_found
parser None guard -> NoRecordFound Q02: guard else-arm returns Vec::new() new test_process_deployer_query_null_parser_is_no_record_found
store None guard -> NoRecordFound Q03: guard else-arm returns Vec::new() new test_process_deployer_query_null_store_is_no_record_found
interpreter None guard -> NoRecordFound Q04: guard else-arm returns Vec::new() new test_process_deployer_query_null_interpreter_is_no_record_found
deploy_transaction None guard -> NoRecordFound Q05: guard else-arm returns Vec::new() new test_process_deployer_query_null_deploy_transaction_is_no_record_found
meta.len()==1 exact (rejects >1) Q06: == 1 -> !is_empty() new test_process_deployer_query_two_metas_is_no_record_found
meta.len()==1 exact (rejects 0 without panic) Q07: == 1 -> <= 1 new test_process_deployer_query_zero_metas_is_no_record_found
bytecode hex decode error -> NoRecordFound Q08: .or(Err(NoRecordFound))? -> .unwrap_or_default() new test_process_deployer_query_invalid_bytecode_hex_is_no_record_found
parser hex decode error -> NoRecordFound Q09: same swap new test_process_deployer_query_invalid_parser_hex_is_no_record_found
store hex decode error -> NoRecordFound Q10: same swap new test_process_deployer_query_invalid_store_hex_is_no_record_found
interpreter hex decode error -> NoRecordFound Q11: same swap new test_process_deployer_query_invalid_interpreter_hex_is_no_record_found
meta[0].id hex decode error -> NoRecordFound Q12: same swap new test_process_deployer_query_invalid_meta_id_hex_is_no_record_found
deploy tx id hex decode error -> NoRecordFound Q13: same swap new test_process_deployer_query_invalid_tx_id_hex_is_no_record_found
constructorMetaHash hex decode error -> NoRecordFound Q14: same swap new test_process_deployer_query_invalid_constructor_meta_hash_hex_is_no_record_found
constructorMeta hex decode error -> NoRecordFound Q15: same swap new test_process_deployer_query_invalid_constructor_meta_hex_is_no_record_found
solidity_abi/v2.rs — SolidityAbiMeta
from_artifact indexes ["abi"] then parses S01: ["abi"] -> ["ABI"] new test_from_artifact_extracts_abi_key
Validate: per-index annotation + early return S02: early return Err(e) dropped equivalent none possible — vacuous validation, #153
TryFrom bytes: validate gate S03a: Ok(t.validate().map(|_| t)?) -> Ok(t) equivalent none possible — vacuous validation, #153
TryFrom Vec: parse errors propagate S03b: parse error -> Ok(empty) new test_try_from_bytes_rejects_invalid_json
TryFrom &[u8]: parse errors propagate S04: parse error -> Ok(empty) new test_try_from_bytes_rejects_invalid_json
TryFrom item -> SolidityAbiMeta via unpack S05: unpack()? -> raw payload new test_try_from_item_unpacks_content_encoding
TryFrom item -> JsonAbi via unpack S06: unpack()? -> raw payload new test_try_from_item_unpacks_content_encoding
SolidityAbiMeta -> JsonAbi via re-serialization S07: conversion returns empty JsonAbi pre test_all
JsonAbi -> SolidityAbiMeta via re-serialization S08: conversion returns empty meta pre test_all
fn serialize type tag "function" S09: tag -> "Function" pre test_all
constructor serialize type tag "constructor" S10: tag -> "Constructor" new test_serialize_all_item_kinds_roundtrip
receive serialize type tag "receive" S11: tag -> "Receive" new test_serialize_all_item_kinds_roundtrip
fallback serialize type tag "fallback" S12: tag -> "Fallback" new test_serialize_all_item_kinds_roundtrip
event serialize type tag "event" S13: tag -> "Event" pre test_all
error serialize type tag "error" S14: tag -> "Error" new test_serialize_all_item_kinds_roundtrip
stateMutability serialized camelCase S15: field -> "state_mutability" pre test_all
fn serialize exact field order S16: inputs/name swapped new test_serialize_fn_exact_field_order_and_component_skipping
components skipped when None S17: skip attribute removed pre test_all
Deserialize type-tag enum lowercase S18: rename_all -> UPPERCASE pre test_all
mutability enum lowercase both directions S19: rename_all -> UPPERCASE pre test_all
map_item_fn_io rejects indexed S20: guard -> if false new test_deserialize_rejects_indexed_on_fn_io
map_item_fn_io recursive components S21: components dropped to None new test_serialize_all_item_kinds_roundtrip
event input indexed REQUIRED S22: ok_or(...) -> unwrap_or(false) new test_deserialize_requires_indexed_on_event_input
event component indexed forbidden S23: guard -> if false new test_deserialize_rejects_indexed_on_event_component
event input recursive components S24: components dropped to None new test_serialize_all_item_kinds_roundtrip
map_item_error_input rejects indexed S25: guard -> if false new test_deserialize_rejects_indexed_on_error_input
error input recursive components S26: components dropped to None new test_serialize_all_item_kinds_roundtrip
fn missing name errors S27: ok_or -> unwrap_or_default new test_deserialize_missing_required_fields_error_messages
fn missing mutability errors S28: ok_or -> unwrap_or(NonPayable) new test_deserialize_missing_required_fields_error_messages
constructor missing mutability errors S29: ok_or -> unwrap_or(NonPayable) new test_deserialize_missing_required_fields_error_messages
receive missing mutability errors S30: ok_or -> unwrap_or(NonPayable) new test_deserialize_missing_required_fields_error_messages
fallback missing mutability errors S31: ok_or -> unwrap_or(NonPayable) new test_deserialize_missing_required_fields_error_messages
event missing name errors S32: ok_or -> unwrap_or_default new test_deserialize_missing_required_fields_error_messages
event missing anonymous errors S33: ok_or -> unwrap_or(false) new test_deserialize_missing_required_fields_error_messages
error missing name errors S34: ok_or -> unwrap_or_default new test_deserialize_missing_required_fields_error_messages
fn missing inputs defaults to [] S35: None => vec![] -> error new test_deserialize_missing_inputs_outputs_default_to_empty
fn missing outputs defaults to [] S36: None => vec![] -> error new test_deserialize_missing_inputs_outputs_default_to_empty
authoring/v1.rs — AuthoringMeta
item abi_encode (bytes32,u8,string) layout A101: offset encoded +1 new test_item_encode_decode_roundtrip_offset_and_word_bytes
item abi_encode_validate validates A102: validate call dropped new test_item_abi_encode_validate_rejects_invalid_word
item abi_decode field mapping A103: decoded offset +1 new test_item_encode_decode_roundtrip_offset_and_word_bytes
item abi_decode_validate validates A104: validate call dropped new test_item_abi_decode_validate_rejects_invalid_word
word validated by REGEX_RAIN_SYMBOL A105: word validated by RAIN_STRING new test_item_abi_encode_validate_rejects_invalid_word
description validated by REGEX_RAIN_STRING A106: description validated by RAIN_SYMBOL pre test_encode_decode_validate, meta::tests::authoring_meta_roundtrip
array encode loop A107: offset +1 in loop pre test_encode_decode_validate, meta::tests::authoring_meta_roundtrip
array decode map+collect A108: offset +1 in map pre test_encode_decode_validate
Validate early return on first bad item A109: early return dropped new test_array_validate_rejects_and_annotates_offending_index
Validate per-index annotation A110: index forced to 0 new test_array_validate_rejects_and_annotates_offending_index
TryFrom Vec serde_json fallback A111: fallback arm removed new test_try_from_bytes_json_fallback
TryFrom &[u8] serde_json fallback A112: fallback arm removed new test_try_from_bytes_json_fallback
TryFrom meta item via unpack A113: unpack()? -> raw payload new test_try_from_meta_item_unpacks_content_encoding
authoring/v2.rs — AuthoringMetaV2
word trimmed at FIRST NUL A201: position -> rposition pre test_abi_decode_valid, test_try_from_valid
NUL-less word keeps full 32 bytes A202: fallback length -> 0 new test_abi_decode_full_32_byte_word_kept_whole
invalid utf8 word -> Utf8Error A203: from_utf8 -> lossy new test_abi_decode_invalid_utf8_word_is_utf8_error
per-item description cloned A204: description -> empty pre test_abi_decode_valid, test_try_from_valid
empty rpcs -> NoRpcs A205: NoRpcs -> HasNoWords new test_fetch_for_contract_empty_rpcs_is_no_rpcs_error
rpc url parse error wrapped A206: wrap -> HasNoWords new test_fetch_for_contract_invalid_rpc_url_is_url_parse_error
HasNoWords gate on implements check A207: gate inverted pre test_get_metabytes_by_hash_success
eth_call error wrapped as RpcError A208: wrap -> HasNoWords new test_fetch_for_contract_rpc_error_on_describe_call
abi_decode_returns error wrapped A209: wrap -> HasNoWords new test_fetch_for_contract_abi_decode_error_on_describe_call
metaboard url parse error wrapped A210: wrap -> HasNoWords new test_fetch_for_contract_invalid_metaboard_url_is_url_parse_error
get_metabytes_by_hash error wrapped A211: wrap -> HasNoWords new test_fetch_for_contract_empty_metaboard_response_is_subgraph_error
cbor_decode of metas[0] A212: decodes last meta instead new test_fetch_for_contract_success_decodes_first_meta
decoded[0] try_into A213: index 0 -> 1 new test_fetch_for_contract_success_decodes_first_meta
magic gate -> MetaMagicNumberMismatch A214: gate removed pre test_try_from_invalid_magic
TryFrom item unpack A215: unpack()? -> raw payload new test_try_from_deflate_encoded_item_unpacks
TryFrom delegates to abi_decode A216: returns empty words pre test_try_from_valid

QA

🤖 Generated with Claude Code

Cross-probe addendum (6abd15f, ef51fb8)

A second, independently derived 84-mutant set over the same 30 behaviours (different mutation choices, same probe harness and suite command) was run against this branch as corroboration:

  • Against the base commit bba50a7 (pre-existing suite): 15 KILLED / 68 SURVIVED / 0 NO-RUN — consistent with the pass-1 numbers above.
  • Against this branch: 77/84 KILLED. Six survivors were killable — places where the two mutant sets genuinely differ — and each got one discriminating test in ef51fb8:
    • description validation removed entirely -> test_description_rejects_unprintable_chars. No prior test fed an invalid description: the A106 regex-swap died only because a valid description fails the swapped-in symbol regex, which leaves outright removal of the validator attribute undetected.
    • constructor / event / error missing inputs default to [] -> test_deserialize_missing_inputs_default_for_constructor_event_error. The prior defaults test covered the function arm only.
    • nested event tuple component recursion -> test_event_component_nested_components_roundtrip. The prior roundtrip nested event components one level deep, so dropping the inner recursion survived.
    • cbor_decode failure wrapped as MetaError in fetch_for_contract -> test_fetch_for_contract_invalid_cbor_is_meta_error.
  • The seventh survivor is the documented equivalent mutant (vacuous SolidityAbiMeta validation, SolidityAbiMeta validation is vacuous: Validate derives carry no rules, so the TryFrom validate gate and per-index annotation can never reject #153).
  • --only re-probe of those six after ef51fb8: all KILLED.
  • 6abd15f additionally renames the shadowed metaboard_url binding in the pre-existing test_get_metabytes_by_hash_success so its assertion compares the wrapped error field against the input url rather than itself.

… and solidity abi meta

Adds discriminating tests for the mutation-probe survivors of the
g3-graphql-query-processing behaviour list: process_deployer_query
guards/len/hex-decode error mapping, AuthoringMeta v1 item-level
encode/decode/validate + json fallback + unpack, AuthoringMetaV2
word trimming/utf8 and the fetch_for_contract error-wrap pipeline
over httpmock, and SolidityAbiMeta per-kind serialization, exact
field order, indexed guards, required-field errors and defaults.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 22, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@thedavidmeister, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 5 minutes

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

Wait for the limit to reset, then comment @coderabbitai review or push new commits to the PR.

An organization admin can change what happens after included review limits in Billing.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 96a192f4-1e89-4f2d-b7d2-08191e90603e

📥 Commits

Reviewing files that changed from the base of the PR and between bba50a7 and 3b5a506.

📒 Files selected for processing (4)
  • crates/cli/src/meta/query/mod.rs
  • crates/cli/src/meta/types/authoring/v1.rs
  • crates/cli/src/meta/types/authoring/v2.rs
  • crates/cli/src/meta/types/solidity_abi/v2.rs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

The destructured metaboard_url binding in
test_get_metabytes_by_hash_success shadowed the server url it was
meant to be checked against, so the assertion compared a String with
its own to_string. Renaming the binding makes the assertion read the
error field against the url the fetch was actually given.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@thedavidmeister

Copy link
Copy Markdown
Contributor Author

Pushed 6abd15f on top: renames the destructured metaboard_url binding in the pre-existing test_get_metabytes_by_hash_success so its assertion compares the wrapped error field against the input url instead of comparing a String to its own to_string (the binding shadowed the server url — the assertion was vacuously true). Full suite green locally (172 lib + 2 doc tests).

Independent corroboration: a parallel run of the same g3 campaign re-derived this PR's matrix from scratch with its own 84-mutant probe against bba50a7 — pass 1 (pre-existing suite): 15 killed / 68 survived / 0 no-run; pass 2 with equivalent new tests: 83/84 killed with the single survivor being the same equivalent mutant documented here (vacuous SolidityAbiMeta validation, #153). The parallel run's duplicate issues (#207 #208 #209 #212) are closed in favour of #153 #164 #156 #160; its three non-duplicate findings stayed open: #210 (no hash verification of subgraph meta bytes), #211 (panic-freedom of metas[0]/decoded[0] rests on cross-crate invariants), #213 (corrupt subgraph payloads collapse into NoRecordFound).

A second independently derived mutant set probed against this branch
surfaced six killable survivors: description validation removal in
AuthoringMetaItem, missing-inputs defaults for the constructor, event
and error deserialize arms, nested event tuple component recursion,
and the cbor_decode error wrap in fetch_for_contract. One new
discriminating test per gap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@thedavidmeister

Copy link
Copy Markdown
Contributor Author

Pushed ef51fb8: the corroborating 84-mutant cross-probe described above found six killable survivors on this branch — description-validation removal, constructor/event/error missing-inputs defaults, nested event tuple component recursion, and the cbor_decode error wrap in fetch_for_contract — each now has one discriminating test, and the --only re-probe kills all six (6/6, exit 0; suite 178 green). Details in the Cross-probe addendum appended to the PR body.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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