Skip to content

Fix catalog/base/skills/openrouter search returning zero results#5388

Draft
richardhjtan wants to merge 3 commits into
mainfrom
fix/catalog-search-item-id-mismatch
Draft

Fix catalog/base/skills/openrouter search returning zero results#5388
richardhjtan wants to merge 3 commits into
mainfrom
fix/catalog-search-item-id-mismatch

Conversation

@richardhjtan

@richardhjtan richardhjtan commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Origin

Found while debugging the Virtual Piano App in the public catalog: its in-card song search (a context.getCards query for MusicSheet cards in the card's own realm) showed "No Music Sheets found" when the app ran from the catalog realm, yet the identical search worked after remixing the app into a personal realm. The backend returned all five MusicSheet results correctly — the host silently dropped every one of them at hydration because of the id mismatch below. Personal realms were immune only by coincidence: they have no registered alias prefix, so the stored id and the resolved URL happen to be the same string. (Same investigation also surfaced a realm-indexing failure mode, fixed separately in #5389.)

Summary

  • A search-entry response's item resource inherited its id straight from the stored pristine_doc, which realms with a registered alias prefix (catalog, base, skills, openrouter) store in unresolved alias form (e.g. @cardstack/catalog/...) for storage portability across environments.
  • The item's own entry relationship always points at the row's resolved absolute URL instead, so the two ids never matched. A consumer doing a strict JSON:API (type, id) lookup on included (e.g. the host's search hydration) silently found nothing, so any search requesting fields[entry]=item against these realms returned zero hydrated results despite the backend having correct, fully-indexed data.
  • Confirmed this is not just a local edge case — spot-checked staging's boxel_index and found the same id mismatch present across a majority of instance rows in the catalog/base/skills/openrouter realms there.
  • Fix: set the item's id/links.self to the same resolved URL its relationship already uses, instead of trusting whatever form happened to be in storage.
  • Follow-ups from review: loadLinks's visited/omitSet now index every equivalent spelling of each root id (resolved first via toURLHref, then expanded via equivalentURLForms) so the resolved-form item id and alias-form linked-resource ids can't be treated as different cards.

Test plan

  • Added a regression test that stores a card under a simulated alias-form pristine_doc.id and asserts the served item's id still matches its relationship
  • Red/green verified: with the fix reverted the regression test fails (included item not found under the resolved id — the exact reported symptom); with the fix it passes
  • Full search-entries-engine-test.ts module run: 22/22 pass on the rebased branch
  • tsc --noEmit clean

🤖 Generated with Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ab5c18f742

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/runtime-common/realm-index-query-engine.ts
@richardhjtan richardhjtan marked this pull request as draft July 2, 2026 14:14
@richardhjtan richardhjtan requested a review from Copilot July 2, 2026 14:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a JSON:API identity mismatch in search-entry results for realms that store pristine_doc.id in registered-alias form (e.g. @cardstack/catalog/...). The projection engine now ensures the included item resource uses the same resolved URL (cardUrl) as the search-entry id and the item relationship, allowing consumers that hydrate via strict (type, id) matching on included to find the item.

Changes:

  • Set included item.id and item.links.self to the resolved cardUrl instead of the stored pristine_doc.id.
  • Add a regression test that simulates an alias-form stored pristine_doc.id and asserts the included item is still keyed by the resolved id.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
packages/runtime-common/realm-index-query-engine.ts Forces included item resources to use cardUrl for id/links.self so JSON:API relationship hydration matches.
packages/realm-server/tests/search-entries-engine-test.ts Adds coverage for the alias-stored-pristine_doc.id case to prevent regressions in search-entry item hydration.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Host Test Results

    1 files      1 suites   2h 16m 22s ⏱️
3 372 tests 3 357 ✅ 15 💤 0 ❌
3 391 runs  3 376 ✅ 15 💤 0 ❌

Results for commit 95f05d3.

Realm Server Test Results

    1 files  ±  0      1 suites  ±0   11m 37s ⏱️ + 2m 31s
1 816 tests +141  1 816 ✅ +141  0 💤 ±0  0 ❌ ±0 
1 895 runs  +141  1 895 ✅ +141  0 💤 ±0  0 ❌ ±0 

Results for commit 66d2718. ± Comparison against earlier commit 95f05d3.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

Comment thread packages/runtime-common/realm-index-query-engine.ts Outdated
Comment thread packages/runtime-common/realm-index-query-engine.ts Outdated
The `item` resource in a search-entry response inherited its id
directly from the stored pristine_doc, which realms with a registered
alias prefix (catalog, base, skills, openrouter) store in unresolved
alias form (e.g. @cardstack/catalog/...) for storage portability. The
item's search-entry relationship, however, always points at the row's
resolved absolute URL. The mismatched ids meant a strict JSON:API
(type, id) lookup on the consuming side never found the included item,
so searches against these realms silently returned zero results.

Set the item's id/links.self to the same resolved URL the relationship
already uses.
Overriding the search-entry item's id to the resolved cardUrl (previous
commit) left loadLinks comparing that against alias-form ids elsewhere
in the same pass: a fetched linked resource's own id, and
relationshipIdStr, both stay in registered-alias form for storage
portability. A root that links to itself, or to another card already
present as a search result, was no longer recognized as visited/omitted
under its alias spelling, risking a duplicate included entry under a
mismatched id.

Index both the resolved and alias spellings of each omitted/visited
root via VirtualNetwork.equivalentURLForms so either form matches.
equivalentURLForms only enumerates spellings of an already-resolved
URL, so a root id arriving in registered-alias form (e.g. a root whose
id retains pristine_doc's stored alias spelling) never got its
resolved-URL form added to omitSet/visited.

Resolve via toURLHref first — a no-op when the id is already a resolved
URL or belongs to a non-aliased realm — so the normalization holds
regardless of which form a caller's root ids arrive in.
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.

2 participants