Skip to content

Commit 40e8653

Browse files
huangyiireneclaude
andauthored
fix(metadata-protocol): key duplicatePackage's source-row dedup by the i18n bundle discriminator (#7932) (#7993)
`duplicatePackage`'s source-row scan deduped the scanned `sys_metadata` rows with a NUL-separated key built from the row's `type` and `name` only, 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 collapsed two rows that are two different things. `EmailTemplateDefinitionSchema` declares such a type: rows sharing a `name` but differing in `locale` form an i18n bundle resolved by `(name, locale)`. Within one org the collapse cannot happen — overlay uniqueness is `(type, name, organization_id, package_id)` and the table has no locale column — but across the env-wide and org tiers it can, and this scan is where the two tiers meet. An env-wide `auth.welcome` in `en-US` plus an org `auth.welcome` in `zh-CN` are two members of one bundle; the duplicate shipped one of them and reported success. Append the canonical-normalized discriminator when the type declares one and nothing otherwise — the shape #7774 gave `metaItemKey` — so every undiscriminated type keeps a byte-identical two-component key. Precedence is unchanged where it was ever meaningful: an org row still overrides the env-wide row of the same member. Claude-Session: https://claude.ai/code/session_01553EtKW75AGxDcCMyqkSEs Co-authored-by: Claude <noreply@anthropic.com>
1 parent c7e7900 commit 40e8653

3 files changed

Lines changed: 490 additions & 1 deletion

File tree

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
---
2+
"@objectstack/metadata-protocol": patch
3+
---
4+
5+
fix(metadata-protocol): `duplicatePackage` no longer drops a locale from a two-tier i18n bundle (#7932)
6+
7+
Duplicating a package as an org-scoped caller copied **one locale of a
8+
two-locale email-template customization** and reported `success: true`.
9+
10+
`duplicatePackage`'s source-row scan deduplicates the scanned `sys_metadata`
11+
rows with a `NUL`-separated key built from the row's `type` and `name` only,
12+
keeping the org-scoped row over the env-wide one. That key is `(type, name)`
13+
with **no discriminator**, so for a type whose identity the spec declares as a
14+
**pair** it collapses two rows that are two different things.
15+
`EmailTemplateDefinitionSchema` declares exactly such a type: multiple rows with
16+
the same `name` but different `locale` form an i18n bundle, resolved by
17+
`(name, locale)`.
18+
19+
**Why the exposure is narrow, and why it is nevertheless real.**
20+
`sys_metadata`'s overlay uniqueness is
21+
`idx_sys_metadata_overlay_active = (type, name, organization_id, package_id)`,
22+
and the table has **no locale column** — an `email_template`'s locale lives in
23+
the `metadata` JSON body. So within **one** org, two rows differing only by body
24+
locale cannot exist and no collapse is possible. Across the **env-wide**
25+
(`organization_id IS NULL`) and **org** tiers they can, and this scan — widened
26+
to span both tiers in #7819 — is the one place the two tiers meet. An env-wide
27+
`auth.welcome` customized in `en-US` plus an org-scoped `auth.welcome`
28+
customized in `zh-CN` are two distinct bundle members; the scan kept only the
29+
org one. Registry-shipped (code-authored) members are unaffected, because this
30+
scan reads `sys_metadata` overlays only — the exposure is limited to templates
31+
customized at **both** scopes.
32+
33+
The dedup key now appends the canonical-normalized discriminator **when the type
34+
declares one, and nothing otherwise** — the same shape #7774 gave `metaItemKey`
35+
and `mergePackageAwareOverlay` for the `GET /meta/<type>` list. `email_template`
36+
is the only type in `ITEM_KEY_DISCRIMINATORS` today, so **every other type's key
37+
is byte-identical** to what it was before and this change's blast radius is
38+
provable rather than argued.
39+
40+
**Precedence is unchanged wherever it was ever meaningful.** An org row still
41+
overrides the env-wide row of the *same* bundle member; a member that declares
42+
no locale is still keyed as the canonical (`en-US`) member, so the bundle-blind
43+
and bundle-aware answers continue to agree for a single-member "bundle". Only
44+
rows that were never the same thing stay separate. The no-`organizationId` door
45+
never ran this dedup and is untouched.

0 commit comments

Comments
 (0)