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
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_fieldoff an object-schema field def — theFieldSchemabag served byDataSource.getObjectSchema— not off the widget bag. On that contract those spellings are refused, so these legs can never fire.FieldSchemais strict (strictObject(options, shape)isz.object(shape).strict(),packages/spec/src/shared/strict-object.ts:327). Declared key set read from objectstack's generatedpackages/spec/authorable-surface.base.json:An absent key on a strict object parses to
unrecognized_keys, soPUT /api/v1/meta/object/:namerefuses the document and no spec-compliant producer can emit one. Nothing manufactures one inbound either:getObjectSchema's only key rewrites arenormalizeSchemaReferenceKeys(thereference/reference_topair) andapplyFieldWidgetOverrides(widget) — zero occurrences of all four acrosspackages/data-objectstack/src, against a lit control (reference_to: 1 inindex.ts, 7 ingetObjectSchema.test.ts).The sites
packages/plugin-charts/src/ObjectChart.tsxfieldDef.id_field || 'id'·fieldDef.reference_field || fieldDef.display_field || 'name'packages/plugin-form/src/deriveMasterDetail.tscol.displayField = d?.display_field || d?.reference_fieldpackages/plugin-list/src/ListView.tsxdisplayField: f.display_field || f.reference_field·idField: f.id_fieldpackages/plugin-list/src/UserFilters.tsxfieldDef.display_field ?? fieldDef.reference_field·fieldDef.id_fieldpackages/app-shell/src/utils/resolveActionParams.tsActionParamDef's camelCasepackages/app-shell/src/views/metadata-admin/inspectors/ObjectFieldInspector.tsxdef.lookupFilters ?? def.lookup_filtersresolveActionParams.ts:536states its own provenance in-file, which is what makes this population identifiable rather than inferred:Note
reference_fieldrides along in four of these chains and is also absent fromFieldSchema(it already carries verdictno-producerinpackages/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.
@object-ui/typesand 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.objectstack+objectuiwhoseDataSource.getObjectSchemareturns 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