Skip to content

fix(cli): fail loudly on a truncated introspection type-ref - #279

Open
thedavidmeister wants to merge 3 commits into
mainfrom
2026-08-25-issue-200
Open

fix(cli): fail loudly on a truncated introspection type-ref#279
thedavidmeister wants to merge 3 commits into
mainfrom
2026-08-25-issue-200

Conversation

@thedavidmeister

Copy link
Copy Markdown
Contributor

Closes #200
Closes #222

The defect

INTROSPECTION_QUERY asked the server for four type-ref levels:

type { kind name ofType { kind name ofType { kind name ofType { kind name } } } }

A wrapper chain longer than that comes back with the innermost ofType
cut off, and render_type turned that cut-off level into the literal
Unknown via name.unwrap_or("Unknown"). check then compared the
invented type against the consumer snapshot and reported a mismatch that
did not exist, naming a type the deployed schema never had.

Confirmed on origin/main (45ca96c) with a probe test feeding
fetch_live_entities_as_sdl a mocked response with the innermost
ofType cut off:

BASE_SDL="type MetaBoard @entity {\n  metas: [Unknown]!\n}\n\n"
BASE_CHECK_ERRORS=["field `MetaBoard.metas` type mismatch: source `[Unknown]!` vs consumer `[MetaV1!]!`"]

Base returns Ok and produces the spurious mismatch, exactly as #200
describes.

#222 is the same cap, and adds the part that decides the fix: nothing in
the code asserted the backend's type depth, so the 4-level assumption was
invisible either way.

The fix

The query is built from TYPE_REF_DEPTH (8, the depth the reference
introspection query settled on), and render_type returns a Result. A
wrapper with no ofType, or a named ref with no name, is now an error
naming the field:

field `MetaBoard.metas`: `LIST` wrapper has no `ofType`: the type nests
deeper than the 8 levels the introspection query resolves

The Unknown placeholder is gone. GraphQL puts no bound on wrapper
chains, so no depth is provably enough; the point is that past the cap
the command now fails with the reason instead of silently comparing an
invented type against the snapshot.

QA

  • Discriminating tests: fetch_live_entities_errors_on_a_truncated_type_ref,
    render_type_errors_on_a_truncated_wrapper_chain,
    render_type_errors_on_missing_name,
    introspection_query_is_valid_graphql_nested_to_the_declared_depth -
    each fails on base (verified by checking origin/main out into a
    worktree and running the probe above: base returns Ok where these
    tests unwrap_err(), and base's render_type returns String, so the
    Unknown placeholder these assert against is what base actually
    produces).
  • Mutations applied (8 run against the new suite, 6 killed):
    • of_type: drop .filter(|v| !v.is_null()) -> killed by
      render_type_errors_on_a_truncated_wrapper_chain and
      fetch_live_entities_errors_on_a_truncated_type_ref
    • render_type LIST arm: "[{}]" -> "{}" -> killed by
      render_type_unwraps_introspection_typeref_recursively and
      render_type_round_trips_a_chain_at_the_query_depth
    • render_type NON_NULL arm: "{}!" -> "{}" -> killed by those two
      plus fetch_live_entities_filters_to_entity_object_types
    • named arm: ok_or_else(...) -> unwrap_or("Unknown") -> killed by
      render_type_errors_on_missing_name
    • fetch_live_entities_as_sdl: drop the field-naming map_err ->
      killed by fetch_live_entities_errors_on_a_truncated_type_ref
    • query builder: 1..TYPE_REF_DEPTH -> 2..TYPE_REF_DEPTH -> killed by
      introspection_query_is_valid_graphql_nested_to_the_declared_depth
    • SURVIVING: TYPE_REF_DEPTH 8 -> 4, and 8 -> 3. Not killable from
      in-repo evidence: graph-node's entity type model tops out at [X!]!
      = 4 levels, and subgraph/schema.graphql's deepest field is
      metas: [MetaV1!] = 3, so no oracle here distinguishes 4 from 8. The
      depth above 4 is margin for the non-graph-node backends schema_check introspection query caps type nesting at 4 ofType levels; deeper wrapper types silently render as Unknown and report false mismatches #222 raises,
      which nothing in this repo can assert. What the tests do pin is that
      truncation at whatever depth fails loudly instead of rendering
      Unknown - the behaviour that caused the false positive. A literal
      assert!(TYPE_REF_DEPTH >= 4) was tried and dropped: clippy rejects
      it as this assertion has a constant value, which is the correct
      verdict - it pins nothing.
  • Oracle: GraphQL's own type syntax, applied by hand to each wrapper
    chain (NON_NULL -> X!, LIST -> [X]), not read off the
    implementation; and graphql_parser::query::parse_query for the
    generated query's validity. The query is assembled from a format string
    now and the mocked tests never parse it, so nothing else would have
    caught a malformed one - the brace balance is checked by a real parser
    rather than by eye.
  • Category check: schema-check INTROSPECTION_QUERY truncates wrapper chains deeper than 4 ofType levels, rendering them as Unknown #200 asks for the truncation to stop rendering as
    Unknown and stop producing false mismatches (covered: the error path
    plus the end-to-end repro test); schema_check introspection query caps type nesting at 4 ofType levels; deeper wrapper types silently render as Unknown and report false mismatches #222 asks additionally that the depth
    assumption be asserted rather than assumed (covered: the cap is now a
    named constant the query is generated from, and exceeding it is a hard
    error naming the field, rather than a comment pinning an assumption).

cargo test -p rain-metadata --lib cli::schema_check: 38 passed.
cargo fmt --check and cargo clippy -p rain-metadata --all-targets -D warnings
clean. The full suite was not run locally; CI covers it.

🤖 Generated with Claude Code

Claude and others added 3 commits August 25, 2026 17:11
The introspection query resolved four type-ref levels and render_type
rendered anything past that as `Unknown`, so a wrapper chain the server
truncated came back as a type the deployed schema does not have and the
check reported a mismatch that was not there.

Build the query from TYPE_REF_DEPTH (8, the reference introspection
depth) and make an unresolvable ref an error naming the field, so the
depth assumption is asserted in code rather than assumed.

Closes #200
Closes #222

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 25, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 56 minutes.

View limit details

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.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 62899103-4acb-4461-b387-833bc6899a8e

📥 Commits

Reviewing files that changed from the base of the PR and between 45ca96c and 09db5ae.

📒 Files selected for processing (1)
  • crates/cli/src/cli/schema_check.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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant