Follow-up to objectstack#13632, which landed in @objectstack/spec 17.3.0 and closed the declared-but-unenforced gap on FieldSchema.reference. The refinement it added refuses a missing and an empty-string target, but accepts a whitespace-only one, which names no object either.
Measured, on the installed 17.3.0 artifact
node, require('@objectstack/spec/data'):
| document |
field level (FieldSchema) |
document level (ObjectSchema) |
{ type: 'lookup', label: 'L' } (absent) |
refused — custom at reference |
refused — custom at fields.rel.reference |
reference: '' |
refused — custom at reference |
refused — custom at fields.rel.reference |
reference: ' ' |
⚠️ accepted |
⚠️ accepted |
reference: 42 / null |
refused — invalid_type at reference |
refused — invalid_type at fields.rel.reference |
The refusal message the other two cases produce is explicit that the property being enforced is a name:
A lookup field requires a non-empty reference naming the target object its records link to (snake_case, e.g. reference: 'account'). Without a target the relationship is not actionable: the record picker has no object to query, $expand has nothing to resolve, and no relationship index can be built.
Every consequence that sentence lists applies verbatim to ' ': there is no object to query, to $expand, or to index. The value passes the check only because the check is spelled .length > 0 rather than .trim().length > 0.
Two further reasons this looks like a gap rather than deliberate latitude:
- The declared grammar excludes it anyway. The prescription says snake_case, and
ObjectSchema.fields' own key schema is /^[a-z_][a-z0-9_]*$/ — no whitespace-bearing string can be a legal object name, so a reference that is only whitespace can never resolve to one. The refinement is the only place that could catch it before storage.
- It is the state a cleared picker emits. A designer whose target picker was opened and cleared emits
'' (caught); one that trims to a space, or that round-trips a value through an input with a stray space, emits ' ' (not caught). Those two authoring gestures are indistinguishable to the author and get opposite verdicts.
Impact — a downstream repo is already compensating
objectui (objectstack-ai/objectui) refuses whitespace-only reference in both of its metadata writers, @object-ui/app-shell's MetadataService.saveFields/saveObject and @object-ui/plugin-designer's MetadataFieldsPage, with the predicate
typeof reference === 'string' && reference.trim() !== ''
That is a client that is stricter than the contract, which is a divergence rather than a neutral choice. It has now been declared as deliberate in both docblocks and in the changeset on objectui#7685 (part of objectui#7122) rather than left implicit — but the honest resolution is upstream: if the spec trims, the client's extra strictness becomes contract-following instead of a local opinion, and the declaration can be retired.
Suggested fix
Trim before the emptiness test in the lookup/master_detail reference refinement, so ' ' joins '' and absent under the same custom issue and the same message. Non-string values should keep answering invalid_type — that distinction is correct and worth preserving.
⛔ Filed as a finding, not as a blocker for objectui#7685. Nothing in that PR depends on this changing; if it does change, objectui's guard keeps behaving identically and only its "declared divergence" note goes away.
Reported from objectui#7122 / objectui#7685 contract review.
Follow-up to objectstack#13632, which landed in
@objectstack/spec17.3.0 and closed the declared-but-unenforced gap onFieldSchema.reference. The refinement it added refuses a missing and an empty-string target, but accepts a whitespace-only one, which names no object either.Measured, on the installed 17.3.0 artifact
node,require('@objectstack/spec/data'):FieldSchema)ObjectSchema){ type: 'lookup', label: 'L' }(absent)customatreferencecustomatfields.rel.referencereference: ''customatreferencecustomatfields.rel.referencereference: ' 'reference: 42/nullinvalid_typeatreferenceinvalid_typeatfields.rel.referenceThe refusal message the other two cases produce is explicit that the property being enforced is a name:
Every consequence that sentence lists applies verbatim to
' ': there is noobject to query, to$expand, or to index. The value passes the check only because the check is spelled.length > 0rather than.trim().length > 0.Two further reasons this looks like a gap rather than deliberate latitude:
ObjectSchema.fields' own key schema is/^[a-z_][a-z0-9_]*$/— no whitespace-bearing string can be a legal object name, so areferencethat is only whitespace can never resolve to one. The refinement is the only place that could catch it before storage.''(caught); one that trims to a space, or that round-trips a value through an input with a stray space, emits' '(not caught). Those two authoring gestures are indistinguishable to the author and get opposite verdicts.Impact — a downstream repo is already compensating
objectui (
objectstack-ai/objectui) refuses whitespace-onlyreferencein both of its metadata writers,@object-ui/app-shell'sMetadataService.saveFields/saveObjectand@object-ui/plugin-designer'sMetadataFieldsPage, with the predicateThat is a client that is stricter than the contract, which is a divergence rather than a neutral choice. It has now been declared as deliberate in both docblocks and in the changeset on objectui#7685 (part of objectui#7122) rather than left implicit — but the honest resolution is upstream: if the spec trims, the client's extra strictness becomes contract-following instead of a local opinion, and the declaration can be retired.
Suggested fix
Trim before the emptiness test in the
lookup/master_detailreferencerefinement, so' 'joins''and absent under the samecustomissue and the same message. Non-string values should keep answeringinvalid_type— that distinction is correct and worth preserving.⛔ Filed as a finding, not as a blocker for objectui#7685. Nothing in that PR depends on this changing; if it does change, objectui's guard keeps behaving identically and only its "declared divergence" note goes away.
Reported from objectui#7122 / objectui#7685 contract review.