Skip to content

fix(search): reject a surfaced reference that declares no ref typeName - #742

Closed
ddeboer wants to merge 2 commits into
mainfrom
fix-ref-typename-and-or-docs
Closed

fix(search): reject a surfaced reference that declares no ref typeName#742
ddeboer wants to merge 2 commits into
mainfrom
fix-ref-typename-and-or-docs

Conversation

@ddeboer

@ddeboer ddeboer commented Aug 14, 2026

Copy link
Copy Markdown
Member

Two code-review findings against #723 / #728 that were not addressed before that PR merged. Both are consequences of the per-target filter typing it introduced.

A surfaced reference with no ref.typeName publishes an invalid contract

typeName became optional when idOnly landed, because that strategy emits no type and so needs no name to emit one under. Every other surfaced strategy is served as a named type – but nothing checked it had a name: validateSearchType only checked that ref existed at all.

The failure is silent rather than loud. registerReferenceType skips the field, outputFieldType then resolves referenceTypes.get('') to undefined, and the SDL comes out as:

type Thing {
  id: IRI!
  creator: undefined
}

A published contract that is not a schema. Now:

Invalid search type “Thing”: “creator” (missing-ref-type-name).

TypeScript's union already forbids this, so it only bites a declaration built outside it – plain JS, or a generator – which is exactly the case validateSearchType exists to guard. A filter-only reference is deliberately left alone: nothing has to be emitted for it, so it falls back to the target-less IRIFilter.

Same issue name as the fix on feat/search-lookup-strategy (#731), with the predicate inverted: there lookup replaces labelOnly, whereas here idOnly is precisely the strategy that legitimately carries no typeName.

The documented cross-field or example no longer validates

docs/reference/search.md declared one PersonFilter variable and fed it to creator, about and contentLocation – three fields whose filters now carry different input types. GraphQL checks variable usage nominally, so the documented query is rejected before it runs.

The same section also filtered material on "oil" – a bare token on a field that keys on identity, which now fails coercion. That one was not in the review.

Fixed by drawing the alternatives from a single target (creator, contributor, publisher) – which is both ADR 18's own motivating example and what the refined discovery strategy actually returns for an agent IRI – filtering material on an AAT IRI, and stating why the alternatives can share a variable while fields pointing at different targets each need their own.

Notes

  • packages/search/vite.config.ts moves the branch threshold 99.49 → 99.5, an autoUpdate re-anchor from the new covered branch.
  • Ships as fix, not feat!: a declaration this now rejects was already emitting an invalid schema, so nothing that worked stops working.
  • Branched off main after feat(search)!: type a filter by what its field keys on #728 merged, so it does not overlap the projection-side IRI hardening still in flight.

`typeName` became optional when `idOnly` landed, since that strategy emits
no type and so needs no name to emit one under. Every other surfaced
strategy IS served as a named type, but nothing checked that it had a
name: `validateSearchType` only checked that `ref` existed at all.

TypeScript's union already forbids it, so this only bites a declaration
built outside it – plain JS, or a generator – which is exactly what this
validation exists to guard. The failure was silent rather than loud: the
GraphQL surface skipped registering the type, then printed `undefined` as
the field's type, publishing a contract that is not a schema.

- add a `missing-ref-type-name` issue, raised for an `output` reference
  whose strategy is not `idOnly` and whose `ref` declares no `typeName`
- leave a filter-only reference alone: nothing has to be emitted for it,
  so it falls back to the target-less IRI filter
The documented “everything related to Van Gogh” query no longer
validates. It declared one `PersonFilter` variable and fed it to
`creator`, `about` and `contentLocation` – three fields whose filters now
carry different input types – and GraphQL checks variable usage
nominally, so the query is rejected before it runs.

The same section also filtered `material` on `"oil"`, a bare token on a
field that keys on identity, which now fails coercion.

- draw the alternatives from one target (`creator`, `contributor`,
  `publisher`), which is what the refined discovery strategy actually
  returns for an agent IRI, and is ADR 18's own motivating example
- filter `material` on an AAT IRI
- state why the alternatives share a variable, and that fields pointing
  at different targets each need their own
@ddeboer

ddeboer commented Aug 14, 2026

Copy link
Copy Markdown
Member Author

Superseded by #740, which landed both fixes verbatim: missing-ref-type-name in validateSearchType, and the corrected cross-field or example ($agent: PersonFilter! over creator/contributor/publisher, plus the AAT IRI for material). This branch is now byte-identical to main on every file it touches, and its only remaining delta is a lower coverage threshold (99.5 vs 99.51). Nothing left to merge.

@ddeboer ddeboer closed this Aug 14, 2026
@ddeboer
ddeboer deleted the fix-ref-typename-and-or-docs branch August 14, 2026 13:10
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