Skip to content

A tenant object overlay drops every objectExtensions field from the resolved schema, and effective contradicts code in the same response #8027

Description

@huangyiirene

Filed by the domain:metadata PM seat on behalf of #7556's dev, which measured this while fixing that card but could not file it — the objectstack-ai org has no Claude GitHub App connection, so cloud dev containers get 403 on the GitHub API. Text is the dev's.

Measured, not inferred; distinct from #7556 and not caused by its fix (PR #8015).

The defect

When a sys_metadata overlay row exists for an object — e.g. an admin customises the object's label in Studio — the protocol's by-name read replaces the resolved body with the raw stored row, and the list read does the same via mergePackageAwareOverlay, which replaces wholesale rather than merging fields.

⚠️ Both routes therefore agree — which is precisely why #7556's agreement pin does not catch it — but they agree on a body that has lost every objectExtensions field.

Measured

One extend contributor (loyalty_tier, linkedin_url, csat_score), one env-wide overlay row, no metadata service:

byName fields:     name, industry + system columns   — no extension fields
listed fields:     identical                          — no extension fields
layers.code:       has all 3                          (correct, ADR-0029 D9.6)
layers.overlay:    the raw row                        (correct)
layers.effective:  no extension fields

A single ?layers=true response reports a code layer that has the fields and an effective layer that does not, with no customisation explaining the difference.

Why this looks wrong

ADR-0029 D9.2 states the base layer is overlay ?? own and that extenders are then folded on — which is exactly what SchemaRegistry.resolveObject does for overlays it knows about. The protocol's sys_metadata path never folds.

The practical consequence mirrors #7556: an admin who customises an object's label silently removes three extension-contributed fields from every writable form, while the data API keeps accepting and persisting them.

Suggested shape — ⚠️ and a trap in it

Deliberately not implemented in #7556: it widens blast radius beyond that card and needs its own reverse-verification.

Treat the overlay row as the base layer and fold extenders onto it, matching D9.2, rather than serving it as the resolved schema.

⚠️ mergeObjectDefinitions concatenates validations and indexes, so the fold is NOT idempotent. Any fix must establish that the stored row has not already been through it — a row saved from a previously-merged read may already contain extension fields, and folding again would duplicate validations and indexes. This is the part most likely to be missed.

Reproduction

Register an object own plus an extend contributor, seed one sys_metadata row (type:'object', state:'active', organization_id:null) whose body is the un-extended object, then compare getMetaItem / getMetaItems / getMetaItemLayered.

Note for whoever picks this up

⚠️ This card is a good candidate for the same agreement-pin discipline #7556 used — but note that an agreement pin alone is insufficient here, because the two routes already agree. The assertion needs to be against the registry's resolved schema (what D9.2 defines), not against the other route.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions