Skip to content

[spec] FieldSchema accepts a lookup/master_detail with no reference target, though its own TSDoc calls the key required #13632

Description

@claude

Measured while implementing #13222 part (2). Filed unassigned by a domain:engine dev seat; not a blocker for that card.

The gap

FieldSchema's own TSDoc for reference says, at packages/spec/src/data/field.zod.ts:1001-1003:

Used by lookup and master_detail field types to define cross-object references.
The reference property is required for these types

The schema does not enforce it. Measured against @objectstack/spec built from origin/main at e2debee631, via FieldSchema.safeParse:

fixture verdict
{ name:'company_id', type:'lookup', reference:'company' } success: true
{ name:'company_id', type:'lookup' } — no reference success: true
{ name:'company_id', type:'lookup', reference:'' } success: true
{ name:'p', type:'master_detail', reference:'company' } success: true
positive control — { type:'lookup', reference_to:'company' } success: false, unrecognized_keys

The control proves the harness really refuses things, so the three trues are real acceptances and not a dead parse.

So a relationship field that points nowhere is a shape an author can publish today, and declared = enforced does not hold for this key.

Why it is worth closing

  • A lookup with no target is not actionable downstream. The record picker has no object to query, $expand has nothing to expand, and deleteBehavior has no parent to apply to. Consumers each have to decide what to do with the hole, which is the shape that produces divergent tolerant fallbacks.
  • It reaches the physical schema. Since driver-mongodb indexes lookup joins off reference_to — a key the spec REFUSES — so no authored lookup field has ever been indexed on MongoDB #13222 part (2), driver-mongodb builds idx_FIELD_lookup for a lookup carrying reference and declines when it is absent or empty — a correct call, but it means the unenforced key now silently decides whether an index exists.
  • AI-authored metadata is the population most exposed. A generator that omits reference gets a clean parse and a field that looks declared, with the failure surfacing much later at the UI or query layer rather than at publish time.

Options, not a recommendation to adopt as-is

  1. A superRefine on FieldSchema requiring a non-empty reference when type is lookup or master_detail — matches the prose, and is the declared = enforced answer. It is a contract tightening: any existing authored object with a targetless lookup starts being refused, so it needs the usual measurement of the affected population first.
  2. Correct the TSDoc to say reference is optional, and state what a targetless lookup means. Cheaper, but keeps a key whose absence nothing checks.

Option 1 is the direction ADR-0049 (enforce-or-remove) points at, but the population measurement should come before the ruling — this is filed as evidence, not as a decided fix.

Reproduce

pnpm --filter @objectstack/spec build
node -e "const {FieldSchema}=require('./packages/spec/dist/data/index.js'); console.log(FieldSchema.safeParse({name:'company_id',type:'lookup'}).success)"

Related

#13222 (where this was measured) · ADR-0049 enforce-or-remove


Generated by Claude Code

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions