ryer.io

Item Version Spec & GraphQL Effective Version Investigation

TL;DR

  • Agent 1472 requested a model-focused spec for the item version scope, since it’s currently only tested at an integration/request layer.
  • Worked through itemversion.rb, the container_item_pairs scope, and how GraphQL resolvers map to types and fields like effectiveVersion.
  • Investigated the effectiveVersion field’s logic around project ID vs group ID, including an error thrown if both or neither are present; Agent 2474 recommended an alternate method for this.
  • Dug into how the batch loader (GraphQL gem) queues promises per project/item pair and resolves them together via a single SQL query.
  • Traced the flow through load_item_consumer_configuration and the item consumer scope, and started examining Active Record’s where method and Postgres column mapping.

Model Spec Request

Agent 1472 suggested we need a focused model spec since the item version logic is currently only exercised through an indirect request specification (integration layer), not covered at the model layer. I’m reading itemversion.rb - there’s a scope (a reusable named query) that takes an array of item ID/version tuples. I was initially confused about the scope’s naming convention but worked through it: it grabs items for a given set of item version pairs.

Effective Version Field

On the item interface (types AI CatalogItemInterface), I’m creating an effectiveVersion field of type versionInterface, which abstracts away specific versions since agents have prompts while flows have definitions. The field takes project ID and group ID arguments. Logic: if neither or both project ID and group ID exist, it throws an error. Agent 2474 recommended using another method for this, which I’ll implement. If neither ID is present, it returns the latest version via version by ID.

Batch Loader Mechanics

When project ID or group ID exists, it calls a batch loader for the configuration, which batches project ID and item ID pairs rather than looping directly. The ‘for’ method doesn’t run a query itself - it establishes a promise for that condition. The batch loader queues these calls (e.g., same project ID with different item IDs) and later resolves them together, seemingly in-memory storing keys against the request, so all effective version calls for a page of items return unresolved promises until the batch method accumulates all keys and resolves them together.

Tracing the Full Request Path

Summarizing the flow: the front end sends a GraphQL request with an operation name, which the back end maps to a resolver; the resolver finds the relevant type/fields. For effectiveVersion, if project ID or group ID exists, it assembles a batch loader key per item, and once all are collected, load_item_consumer_configuration is called with the container item pairs, loader, and container type (e.g., project). This goes into the item consumer class, which uses the container_item_pairs scope - a named query likely backed by an index - to fetch consumers for each pair via a single SQL query. This scope is exactly what Danielle wants a model-focused spec for.

Scope and Active Record Details

The container_item_pairs scope defines columns (container_type_id and ai_catalog_item_id) and raises an error if the container type isn’t project or group. The where method comes from Active Record and can take a hash mapping columns to arrays of tuple data matching column order - the container item pairs array corresponds to this. Once consumers are returned from Postgres, the code loops through consumers.find_each, retrieving the container ID (project ID or group ID) from each consumer to call the loader - a part I still need to clarify further.