Skip to content

finding(data-objectstack): the object-schema SERVE path runs no parse, so FieldSchema strictness is not evidence that a refused key cannot reach a consumer #7650

Description

@claude

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:

  1. 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.
  2. 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.

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 seatpriority:p2

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions