A label source must declare its display field under the literal name label (labelFieldOf, schema.js:266):
export function labelFieldOf(searchType) {
const field = fieldNamed(searchType, 'label');
return field !== undefined && field.kind === 'text' &&
field.output === true && field.searchable !== undefined ? field : undefined;
}
The name is the only thing identifying the role, and that leaks into the API. In a SCHEMA-AP-NDE schema every label source reads schema:name, so six root types declare an identical field renamed to label purely to satisfy the contract:
{ name: 'label', kind: 'text', path: schemaProperty('name'), locales: LOCALES,
required: true, output: true, searchable: { weight: 5 }, sortable: true }
An API consumer then sees the same triple under two keys depending on which door they came in – persons { label } from the collection, creator { name } through the reference (PersonReference { id, name }) – with nothing in the response indicating they are the same property. Reported downstream by an implementor who expected name, because name is what the profile they were reading calls it.
Proposal
Identify the label field by role, not by name: an additive capability opt-in alongside output/sortable, e.g. isLabel: true. It restricts nothing – the field keeps whatever capabilities it declares – it only states that this type serves its display label from this field. Exactly one per label source, enforced by validation.
// SCHEMA-AP-NDE
{ name: 'name', kind: 'text', path: schemaProperty('name'), isLabel: true, … }
// a SKOS-based profile
{ name: 'prefLabel', kind: 'text', path: skosProperty('prefLabel'), isLabel: true, … }
labelFieldOf finds the flagged field instead of the named one.
- Validation becomes: a type named as a
labelSource declares exactly one flagged text field, output and searchable.
labelSource: 'Person' is unchanged at every reference site – the field name stays a property of the referenced type, declared once, so it cannot drift between references.
- The generated reference type's key follows the label source's field name (
PersonReference { id, prefLabel }), so a concept has one name across both surfaces.
Why not just rename the contract to name
That moves the presumption rather than removing it: label presumes SKOS/RDFS, name presumes schema.org. A profile-neutral layer should decline to pick, and let each schema surface its own vocabulary.
Also fixes
A type with a field legitimately named label that is not meant as a display label currently becomes a valid label source by accident.
Migration
Breaking for the generated GraphQL surface where a reference key changes. In this repo the six declarations rename label → name and add the flag.
A label source must declare its display field under the literal name
label(labelFieldOf,schema.js:266):The name is the only thing identifying the role, and that leaks into the API. In a SCHEMA-AP-NDE schema every label source reads
schema:name, so six root types declare an identical field renamed tolabelpurely to satisfy the contract:An API consumer then sees the same triple under two keys depending on which door they came in –
persons { label }from the collection,creator { name }through the reference (PersonReference { id, name }) – with nothing in the response indicating they are the same property. Reported downstream by an implementor who expectedname, becausenameis what the profile they were reading calls it.Proposal
Identify the label field by role, not by name: an additive capability opt-in alongside
output/sortable, e.g.isLabel: true. It restricts nothing – the field keeps whatever capabilities it declares – it only states that this type serves its display label from this field. Exactly one per label source, enforced by validation.labelFieldOffinds the flagged field instead of the named one.labelSourcedeclares exactly one flagged text field,outputandsearchable.labelSource: 'Person'is unchanged at every reference site – the field name stays a property of the referenced type, declared once, so it cannot drift between references.PersonReference { id, prefLabel }), so a concept has one name across both surfaces.Why not just rename the contract to
nameThat moves the presumption rather than removing it:
labelpresumes SKOS/RDFS,namepresumes schema.org. A profile-neutral layer should decline to pick, and let each schema surface its own vocabulary.Also fixes
A type with a field legitimately named
labelthat is not meant as a display label currently becomes a valid label source by accident.Migration
Breaking for the generated GraphQL surface where a reference key changes. In this repo the six declarations rename
label→nameand add the flag.