Skip to content

[spec] FieldSchema accepts a WHITESPACE-ONLY reference on lookup/master_detail — the 17.3.0 refinement checks non-empty, not non-blank #16126

Description

@os-sam

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions