Provenance: measured while running the objectui#7642 census. Filed unassigned as a finding — the measurement is clean, the disposition is a judgement call.
What is measured
A recurring argument in this repo's alias-retirement cards runs: @objectstack/spec's FieldSchema is strict, the key is absent from it, therefore no producer can emit it, therefore the consumer-side read is dead. That argument is sound about authoring. It is not sound about serving, because the serve path never parses.
ObjectStackAdapter.getObjectSchema (packages/data-objectstack/src/index.ts) fetches the object document and returns it verbatim, with exactly two mutations applied on the way out:
normalizeSchemaReferenceKeys(schema) — the reference / reference_to pair only
applyFieldWidgetOverrides(objectName, schema) — stamps a render widget on specific platform fields
There is no schema validation anywhere on that path:
ObjectSchema.parse | ObjectSchema.safeParse in packages/data-objectstack/src/index.ts : 0
LIT CONTROL for that zero — the same file does import and use spec parsers, so the instrument is not dark: DroppedFieldsEventSchema and isFilterAST are both imported from @objectstack/spec/data and used. The zero is a real absence, not a failed grep.
The other two DataSource implementations in the workspace are consistent with this: ValueDataSource.getObjectSchema infers { type: typeof value } per key from a sample record, and ApiDataSource.getObjectSchema returns { name, fields: {} }. Neither parses either.
Why it matters
The strict schema gates the write door — PUT /api/v1/meta/object/:name refuses a document carrying an undeclared key. It does not gate the read door. So:
- A document stored before a key was tightened is still served, verbatim, to every consumer, forever. It cannot be re-saved through the strict door, but nothing ever asks it to be.
- A host whose own
getObjectSchema implementation returns a non-spec dialect is likewise served straight through — the adapter interface types the return as an unconstrained value.
⇒ "no spec-compliant producer can emit this key" and "this consumer read can never fire" are different claims, and only the first is established by reading the schema. Several retirement cards in this family treat them as the same claim.
Scope of the consequence
This is not an argument that no alias may ever be retired. It is an argument that the retirement question has a second half that the schema cannot answer: what happens to a stored document that already carries the key? The reference_to retirement (objectui#6837 half 2) answered it — the maintainer's ruling there was that a legacy def is canonicalised once, at the ingestion choke point (normalizeSchemaReferenceKeys), never at the consumer. That is why narrowing the consumer reads was safe: the choke point covers the gap.
No equivalent choke-point canonicalisation exists for display_field, description_field, id_field or lookup_filters. So for those keys the same manoeuvre is the first half of the pattern without the second.
What a fix would be
Not a code change on its own. Either:
- A — decide the serve path should canonicalise, and extend the ingestion choke point (
normalizeSchemaReferenceKeys or a sibling) to cover the rest of the retired dialect, which then makes consumer-side narrowing safe by the same argument that made it safe for reference_to; or
- B — decide stored legacy documents are the server's problem (a metadata migration), and record that consumer reads may be narrowed without a client-side compatibility leg; or
- C — leave both, and require every future retirement card in this family to state which of A or B it is relying on, rather than citing schema strictness alone.
Filed unassigned, for triage. Related: objectui#7642, objectui#6837, objectui#7435, objectui#7155.
Routing note (2026-09-05) — id_field lands on option A here, not on the additive camel-leg follow-up
Appended after the contract review of PR objectui#7649 (REFUSE, text-only; comment 5548607662): the follow-up blessed as C on objectui#7642 ("add the declared camel legs") rested on a measurement that came back false for one key. Re-measured 2026-09-05 on the pinned @objectstack/spec@17.2.0, FieldSchema.safeParse, controls lit (minimal lookup def ACCEPTED; displayField / descriptionField / lookupFilters ACCEPTED; zzz_not_a_real_key REJECTED unrecognized_keys): id_field and idField are both REJECTED unrecognized_keys — the spec's only idField sits on InlineGridColumnSchema, a different shape. So of the four keys above, three (display_field, description_field, lookup_filters) have a declared camel target the additive follow-up (objectui#7435) can re-point to; id_field has none and is excluded from that follow-up. Its route is option A here — which for this key first needs a declared target on FieldSchema, a spec-side decision — or B; never an idField consumer read, which would fossilise an undeclared spelling. The text above this note is unchanged.
Generated by Claude Code, session session_01KbJQ1y1J12nZxYzFWhP8Q3, from the objectui#7642 census.
Provenance: measured while running the objectui#7642 census. Filed unassigned as a
finding— the measurement is clean, the disposition is a judgement call.What is measured
A recurring argument in this repo's alias-retirement cards runs:
@objectstack/spec'sFieldSchemais strict, the key is absent from it, therefore no producer can emit it, therefore the consumer-side read is dead. That argument is sound about authoring. It is not sound about serving, because the serve path never parses.ObjectStackAdapter.getObjectSchema(packages/data-objectstack/src/index.ts) fetches the object document and returns it verbatim, with exactly two mutations applied on the way out:normalizeSchemaReferenceKeys(schema)— thereference/reference_topair onlyapplyFieldWidgetOverrides(objectName, schema)— stamps a renderwidgeton specific platform fieldsThere is no schema validation anywhere on that path:
LIT CONTROL for that zero — the same file does import and use spec parsers, so the instrument is not dark:
DroppedFieldsEventSchemaandisFilterASTare both imported from@objectstack/spec/dataand used. The zero is a real absence, not a failed grep.The other two
DataSourceimplementations in the workspace are consistent with this:ValueDataSource.getObjectSchemainfers{ type: typeof value }per key from a sample record, andApiDataSource.getObjectSchemareturns{ name, fields: {} }. Neither parses either.Why it matters
The strict schema gates the write door —
PUT /api/v1/meta/object/:namerefuses a document carrying an undeclared key. It does not gate the read door. So:getObjectSchemaimplementation returns a non-spec dialect is likewise served straight through — the adapter interface types the return as an unconstrained value.⇒ "no spec-compliant producer can emit this key" and "this consumer read can never fire" are different claims, and only the first is established by reading the schema. Several retirement cards in this family treat them as the same claim.
Scope of the consequence
This is not an argument that no alias may ever be retired. It is an argument that the retirement question has a second half that the schema cannot answer: what happens to a stored document that already carries the key? The
reference_toretirement (objectui#6837 half 2) answered it — the maintainer's ruling there was that a legacy def is canonicalised once, at the ingestion choke point (normalizeSchemaReferenceKeys), never at the consumer. That is why narrowing the consumer reads was safe: the choke point covers the gap.No equivalent choke-point canonicalisation exists for
display_field,description_field,id_fieldorlookup_filters. So for those keys the same manoeuvre is the first half of the pattern without the second.What a fix would be
Not a code change on its own. Either:
normalizeSchemaReferenceKeysor a sibling) to cover the rest of the retired dialect, which then makes consumer-side narrowing safe by the same argument that made it safe forreference_to; orFiled unassigned, for triage. Related: objectui#7642, objectui#6837, objectui#7435, objectui#7155.
Routing note (2026-09-05) —
id_fieldlands on option A here, not on the additive camel-leg follow-upAppended after the contract review of PR objectui#7649 (REFUSE, text-only; comment 5548607662): the follow-up blessed as C on objectui#7642 ("add the declared camel legs") rested on a measurement that came back false for one key. Re-measured 2026-09-05 on the pinned
@objectstack/spec@17.2.0,FieldSchema.safeParse, controls lit (minimal lookup def ACCEPTED;displayField/descriptionField/lookupFiltersACCEPTED;zzz_not_a_real_keyREJECTEDunrecognized_keys):id_fieldandidFieldare both REJECTEDunrecognized_keys— the spec's onlyidFieldsits onInlineGridColumnSchema, a different shape. So of the four keys above, three (display_field,description_field,lookup_filters) have a declared camel target the additive follow-up (objectui#7435) can re-point to;id_fieldhas none and is excluded from that follow-up. Its route is option A here — which for this key first needs a declared target onFieldSchema, a spec-side decision — or B; never anidFieldconsumer read, which would fossilise an undeclared spelling. The text above this note is unchanged.Generated by Claude Code, session
session_01KbJQ1y1J12nZxYzFWhP8Q3, from the objectui#7642 census.