Skip to content

fix(spec,objectql,metadata-protocol): a user field carries its target in the TYPE — bare {type:'user'} is not targetless - #4438

Merged
os-zhuang merged 2 commits into
mainfrom
fix/user-field-implicit-sys-user-target
Aug 1, 2026
Merged

fix(spec,objectql,metadata-protocol): a user field carries its target in the TYPE — bare {type:'user'} is not targetless#4438
os-zhuang merged 2 commits into
mainfrom
fix/user-field-implicit-sys-user-target

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

The contract question, settled first

type: 'user' — does it imply the target object, or must it carry an explicit reference?

It implies it. The spec says so in its own words:

  • field.zod type list: "User reference — a lookup specialized to the sys_user system object … Stored IDENTICALLY to 'lookup' (FK string column → sys_user.id)".
  • Field.user(): "target fixed to the sys_user system object" — and unlike Field.lookup(reference, …) / Field.masterDetail(reference, …) it takes no target argument; it writes reference: 'sys_user' itself.
  • reference's own docstring names the types that require it: "Used by lookup and master_detail field types".
  • objectui agrees from the other side: buildExpandFields expands on TYPE alone ("the reference / reference_to target is irrelevant to the decision"), and lookupTargetObject({type:'user'})sys_user.

So the target is a constant of the type. reference on a user field materializes that constant; it does not supply it. Metadata authored without it — hand-written JSON, an AI author, a Studio form — is complete, not under-specified.

What was broken

Two callers read field.reference raw and so disagreed with that definition:

  • protocol.assertExpandTargetsExist refused ?expand=<bare user field>400 INVALID_FIELD … declares no target object
  • engine.expandRelatedRecords skipped it (if (!fieldDef.reference) continue)

Live capture (cloud#983): an AI-built equipment app modelled 负责人 as { type: 'user' }, objectui's default list expanded that column, and the very first screen of the brand-new app rendered 「该视图的查询被拒绝」:

GET /api/v1/data/fzqb_equipment?populate=responsible_person&top=100&select=… → 400
{"code":"INVALID_FIELD","field":"responsible_person","object":"fzqb_equipment",
 "error":"Field 'responsible_person' … declares no target object."}

The fix

referenceTargetOf in @objectstack/spec/data, next to REFERENCE_VALUE_TYPES — the set those same two callers already share for "is this a reference at all". It answers the companion question "what does it point at", folding the type-implied targets. Both halves of the expand path read it, which is what stops the gate from refusing a field the engine would have expanded, or blessing one it skips.

A gate-only fix would have been worse than none: the request would be admitted and the engine would still skip the field, answering 200 with a raw user id in the cell — the "client renders raw ids where names belong" failure the whole expand axis exists to close. The conformance test pins both halves; each was verified to fail on its own (engine reverted → expected 'usr_1' to match object {id:'usr_1', name:'Ada'}; gate reverted → the issue's exact 400).

Deliberately unchanged

seed-loader's reference resolution still requires an explicit reference. An unresolvable seed reference is a hard failure there, so folding implicit targets in would turn seeds that today write a raw string into failed loads. That is a different subsystem's contract question, not this one.

Tests

  • specreferenceTargetOf unit tests, plus a drift guard asserting user is the only reference type with an implied target
  • objectql/query-expression-conformance — a bare {type:'user'} field is admitted and actually expands ({id:'usr_1', name:'Ada'}, not the raw id)
  • @objectstack/spec 7230 · metadata-protocol 139 · objectql 1507 — all green; spec + objectql typecheck clean

🤖 Generated with Claude Code

…et in the TYPE — bare `{type:'user'}` is not targetless

`field.zod` defines `user` as "a lookup specialized to the `sys_user` system
object … target fixed to the `sys_user` system object", and `Field.user()` —
unlike `Field.lookup(reference, …)` / `Field.masterDetail(reference, …)` —
takes NO target argument and writes `reference: 'sys_user'` itself. The target
is a CONSTANT OF THE TYPE. `reference` on a `user` field materializes that
constant; it does not supply it.

Two callers read `field.reference` raw and so disagreed with that definition:
the protocol's expand gate refused `?expand=<a bare user field>` with
`400 INVALID_FIELD … declares no target object`, and objectql's expand loop
skipped it. Metadata authored without the redundant `reference` — hand-written
JSON, an AI author, a Studio form — was therefore read as under-specified when
it was complete.

Live capture (cloud#983): an AI-built equipment app modelled 负责人 as
`{ type: 'user' }`; objectui's default list expanded that column (its
`EXPANDABLE_FIELD_TYPES` keys on the TYPE, deliberately ignoring the target);
the very first screen of the brand-new app rendered "该视图的查询被拒绝" over
that 400.

`referenceTargetOf` in `@objectstack/spec/data` is now the single arbiter of
"what does this reference field point at", next to `REFERENCE_VALUE_TYPES` —
the set the same two callers already share for "is this a reference at all".
Both halves of the expand path read it, which is what stops the gate from
refusing a field the engine would have expanded, or blessing one it skips.

Fixing only the gate would be worse than not fixing it: the request would be
admitted and the engine would still skip the field, answering 200 with a raw
user id in the cell — the "client renders raw ids where names belong" failure
the expand axis exists to close. The conformance test pins BOTH halves (each
was verified to fail alone).

Deliberately unchanged: `seed-loader`'s reference resolution still requires an
explicit `reference`. An unresolvable seed reference is a HARD failure there,
so folding implicit targets in would turn seeds that today write a raw string
into failed loads — a different subsystem's contract question, not this one.
@vercel

vercel Bot commented Aug 1, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 1, 2026 7:21am

Request Review

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/metadata-protocol, @objectstack/objectql, @objectstack/spec.

110 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/metadata-protocol, @objectstack/objectql, packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via packages/objectql, @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/migration-from-objectql.mdx (via @objectstack/objectql)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/deployment/vercel.mdx (via @objectstack/objectql)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/metadata-protocol, @objectstack/objectql, @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/permissions/authentication.mdx (via @objectstack/objectql)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via packages/objectql, @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/metadata-protocol, @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actions github-actions Bot added the size/m label Aug 1, 2026
@github-actions github-actions Bot added documentation Improvements or additions to documentation tooling labels Aug 1, 2026
@os-zhuang
os-zhuang merged commit ad5fe25 into main Aug 1, 2026
18 checks passed
@os-zhuang
os-zhuang deleted the fix/user-field-implicit-sys-user-target branch August 1, 2026 07:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:data size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant