Skip to content

finding(plugin-charts, plugin-form, plugin-list, app-shell): six sites read the retired snake lookup keys off an OBJECT-schema def, where FieldSchema refuses them #7642

Description

@claude

Provenance: measured while implementing objectui#7155 (maintainer ruling A′). Filed unassigned as a finding — the measurement is clean; the disposition is a judgement call.

objectui#7155 converged the widget-metadata dialect (@object-ui/types' LookupFieldMetadata / UserFieldMetadata) on the spec's camelCase. While measuring its blast radius I found a second, separate population that the ruling does not name and that PR deliberately left untouched.

What's measured

Six sites read display_field / description_field / lookup_filters / id_field off an object-schema field def — the FieldSchema bag served by DataSource.getObjectSchema — not off the widget bag. On that contract those spellings are refused, so these legs can never fire.

FieldSchema is strict (strictObject(options, shape) is z.object(shape).strict(), packages/spec/src/shared/strict-object.ts:327). Declared key set read from objectstack's generated packages/spec/authorable-surface.base.json:

data/Field declared property count: 66
LIT CONTROL name/type/label : all DECLARED
camelCase controls          : displayField, descriptionField, lookupColumns, lookupFilters, reference — all DECLARED
the four snake spellings    : display_field, description_field, lookup_filters, id_field — all ABSENT

An absent key on a strict object parses to unrecognized_keys, so PUT /api/v1/meta/object/:name refuses the document and no spec-compliant producer can emit one. Nothing manufactures one inbound either: getObjectSchema's only key rewrites are normalizeSchemaReferenceKeys (the reference / reference_to pair) and applyFieldWidgetOverrides (widget) — zero occurrences of all four across packages/data-objectstack/src, against a lit control (reference_to: 1 in index.ts, 7 in getObjectSchema.test.ts).

The sites

file line(s) shape
packages/plugin-charts/src/ObjectChart.tsx 197, 208 fieldDef.id_field || 'id' · fieldDef.reference_field || fieldDef.display_field || 'name'
packages/plugin-form/src/deriveMasterDetail.ts 249, 303 col.displayField = d?.display_field || d?.reference_field
packages/plugin-list/src/ListView.tsx 2714, 2736 displayField: f.display_field || f.reference_field · idField: f.id_field
packages/plugin-list/src/UserFilters.tsx 300, 301 fieldDef.display_field ?? fieldDef.reference_field · fieldDef.id_field
packages/app-shell/src/utils/resolveActionParams.ts 304-310, 539-544 a local interface declaring all four, then mapped to ActionParamDef's camelCase
packages/app-shell/src/views/metadata-admin/inspectors/ObjectFieldInspector.tsx 1145 def.lookupFilters ?? def.lookup_filters

resolveActionParams.ts:536 states its own provenance in-file, which is what makes this population identifiable rather than inferred:

Source here is owner.fields[param.field] — an object schema field def, i.e. the protocol.

Note reference_field rides along in four of these chains and is also absent from FieldSchema (it already carries verdict no-producer in packages/plugin-grid/src/relationalMetaKeys.ts).

Why this is a finding and not a fix

Same reasoning as objectui#6531 / #6711 / #6597 / #6625, all of which retired consumer-side aliases on exactly this evidence, and AGENTS.md #0.1 on tolerant renderers fossilising a second de-facto contract.

⚠️ But the caution objectui#7155 earned the hard way applies here too, and it is why this is not a drive-by sweep: on that card the "no producer can emit this" argument was true of the object contract and false of a second published one, and the host feeding the refused dialect turned out to be this repo's own @object-ui/types and docs. Before retiring any of these legs, someone should check each site's bag the same way — these six are typed loosely (any / Record<string, unknown>), so the compiler will not answer it.

⚠️ Also unmeasurable from here, as on objectui#7155: a host application outside objectstack + objectui whose DataSource.getObjectSchema returns snake_case defs, and stored metadata predating the strict schema. Both would still be served. These chains read snake first in four of the six sites, so retiring a leg changes which value wins, not merely which keys exist.

Suggested disposition

Enforce-or-remove triage per ADR-0049's spirit, one site at a time, starting with the question objectui#7155 shows this measurement cannot answer on its own: for each site, which contract types the bag it reads?


Generated by Claude Code

Activity

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

Metadata

Metadata

Assignees

Labels

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatfindingpm:dispatched

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions