Skip to content

duplicatePackage's env-wide ⊕ org row scan dedups by (type, name) — an i18n bundle's two locales collapse to one #7932

Description

@huangyiirene

What

ObjectStackProtocolImplementation.duplicatePackage (packages/metadata-protocol/src/protocol.ts, in the request.organizationId branch of its source-row scan) dedups the scanned sys_metadata rows with a NUL-separated key built from the row's type and name only — literally `${row?.type}` + "" + `${row?.name}` — keeping the org-scoped row over the env-wide one.

That key is (type, name) with no discriminator, so for a type whose identity the spec declares as a pair it collapses two rows that are two different things.

email_template is such a type: EmailTemplateDefinitionSchema states that "multiple rows with the same name but different locale form an i18n bundle" and that a template "is resolved by (name, locale)". #7730 taught the SchemaRegistry that key; #7774 / PR #7931 taught the /meta list merges the same key, with the shared table now in @objectstack/metadata-core (ITEM_KEY_DISCRIMINATORS + itemDiscriminator). This scan was not in either card's declared surface and still keys on the name alone.

Reachability, measured — narrow but real

sys_metadata's overlay uniqueness is (type, name, organization_id, package_id) (idx_sys_metadata_overlay_active, packages/metadata-core/src/objects/sys-metadata.object.ts), and the table has no locale column — the locale lives in the metadata JSON body. So:

  • within one org, two rows differing only by body locale cannot exist ⇒ no collapse;
  • across the env-wide (organization_id IS NULL) and org tiers they can ⇒ an env-wide auth.welcome customized in en-US plus an org-scoped auth.welcome customized in zh-CN are two distinct bundle members, and this scan keeps only the org one. The duplicated package then ships one locale of a two-locale customization.

Registry-shipped (code-authored) members are unaffected — this scan reads sys_metadata overlays only, so the exposure is limited to customized templates that were customized at both scopes.

Suggested shape

The same fix PR #7931 applies to the sibling dedup inside getMetaItems: append the canonical-normalized discriminator when the type declares one, and nothing otherwise, so every undiscriminated type keeps a byte-identical key. Both the table and a body-reading helper are already available to this file — itemDiscriminator from @objectstack/metadata-core, and storedRowDiscriminator (local to protocol.ts), which skips the JSON parse entirely for an undiscriminated type.

There is no coverage of this path's dedup today.

Provenance

Found while implementing #7774 (PR #7931). Deliberately not fixed there: different feature, different endpoint, outside that PR's declared file surface. Filed unassigned — nobody is on it.


Generated by Claude Code

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions