Repository navigation
fix(metadata-protocol)!: an email template locale added in a .custom pair is listed and sent on the base's address; a dependent's added locale stays under its own package (#28802) - #28828
Conversation
…s address, keep a dependent's under its own package The env-wide list narrowed a name one code package ships at name grain, so the pair's own added email template locale was read by name but never listed or executed, and the by-name reads on the base's address fell to the registry's cross-package tier and served a dependent's member under the base's name. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5m2Ryfx2Wubuz716mw
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5m2Ryfx2Wubuz716mw
…se's address Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5m2Ryfx2Wubuz716mw
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 10 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin b3289e424a6b817c4f05a9dca104d99dedbb5c2d && git checkout b3289e424a6b817c4f05a9dca104d99dedbb5c2d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 3cb8553c946905bf216dfaa6b84870380d112e25 47b1c37574444ec65bc0a544281952f2d5ff26af && git checkout -B drift-repro 3cb8553c946905bf216dfaa6b84870380d112e25 && git merge --no-ff 47b1c37574444ec65bc0a544281952f2d5ff26af
node scripts/docs-audit/affected-docs.mjs --json 3cb8553c946905bf216dfaa6b84870380d112e25
|
|
domain:server#2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo Contract reviewServed-tier: Reviewed at 2026-11-01T22:25Z against the merge-base ① Derived judgments1. The order's semantics, per read face (bundle types only,
2. Bundle-type derivation. Every new branch is gated on 3. Write gate. The diff touches neither 4. Evidence.
5. Skills-docs. The PR body carries ② Semver level
③ Boundary flagsAuthor
Implemented-by: VERDICT: PASS Generated by Claude Code |
…hangeset names any other package's member Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5m2Ryfx2Wubuz716mw
|
domain:server#2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo Contract reviewServed-tier: Re-pin at 2026-11-01T22:40Z of the record on ① Derived judgments
② Semver levelUnchanged from the prior record: ③ Boundary flagsAuthor
Implemented-by: VERDICT: PASS Generated by Claude Code |
|
domain:server#2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo 席位核对ACCEPT · The maintainer's standing authorisation for landing reviewed PRs, verbatim: 「按照 pm-dispatch 规则复核过的都授权」.
Generated by Claude Code |
Main's #28828 lists and sends a locale the pair adds on the base's address, so the second locale no longer needs a row stored before the close: `en-US` declares the override in the pair, `zh-CN` is the pair's own member, and the member reset names the pair. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5ni5yjiTswP6crgAkX
domain:server#2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo
Requested by Leehom · project thread
Fixes #28802
Fixes #28779
Clause-②: yes (narrowing)
Metadata-protocol: semantic
Skills-docs: follow-up #28829
维护者速读
BASE.custom里新增的基础包没有的语言(比如 showcase 只发en-US,客户加fr-FR),现在出现在全局列表、基础包列表和执行列表里,发fr-FR邮件真的用客户的模板;依赖包在基础包模板名下加的语言,只在依赖包自己的地址下可读,按基础包名或不带包名读取不再返回它(404)。email_template外的所有类型)字节不变。收窄:按基础包名读依赖包新增语言从 200 变 404。回滚即撤销本 PR。packages/metadata-protocol/src/protocol.ts的三处[#28802](全局列表成员粒度收窄、基础包列表保留配对包新增成员、按名读取的配对包行与注册表兜底守卫);protocol-environment-rows.ts的baseAddressServesMember;dogfood 测试文件。pnpm --filter @objectstack/dogfood exec vitest run test/email-template-dependent-unshipped-locale-read.dogfood.test.ts,预期 10/10 通过(main 上 4 红)。What changes
An email template is one item per
(name, locale)(ITEM_KEY_DISCRIMINATORS), and since #28746 a locale the base does not ship is admitted as an addition, from the base's.custompair (nooverrides) and from a dependent package. The reads of a base's address decided at name grain. They now decide member by member (option A of the #28779 report, the seat's pick; ADR-0146 D2, ruling A on #28390):_packageId: PAIR(unchanged)getMetaItemsForExecutionrenderTemplate/sendTemplatein that localeen-US)The dependent's member is still served by the read naming the dependent and by the list scoped to it; the list scoped to the pair is unchanged.
packages/metadata-protocol/src/protocol-environment-rows.ts.baseAddressServesMember(base, bound, baseShipsMember): the one member-grain answer. A member bound to the base or to no package is served; a member bound to the base's pair is served when the base does not ship that member; anything else (a dependent's member, a pair's undeclared row at a member the base ships, which the write gate refuses) is not.packages/metadata-protocol/src/protocol.ts, bundle types only (read fromITEM_KEY_DISCRIMINATORS, never a literal;email_templateis its only entry):readFlattenedMetaItems, the narrowing to the sole shipper): an item another package binds under a name one code package ships is kept whenbaseServesBundleMembersays so. For every other type the expression is byte-identical (isBundle && …is false).unseated.delete). Only for a name the base ships.readMetaItem): when no served-row candidate holds a row and the base ships the name but not the member, the pair's own row at that member is read (findServedOverlayRownaming the pair) and served under its binding. The registry's last tier (the first composite match across packages, ADR-0048 §3.3) is then guarded: an answer the base's address does not serve is dropped (registryMemberServedAt); for a read naming no member, the base's own canonical-first member is asked instead.shipsBundleName,baseServesBundleMember,pairOfAddedMember) count shipped members through the existing member-awareshippedArtifactsOf.Why the pair's row and not the registry. Measured: with the pair's and a dependent's
fr-FRboth stored, the registry holds one entry for that member (the last hydration wins), so the base's address served the dependent's body even with the pair present (red case in both test files below). The stored row is the pair's own, wherever the registry slot stands.PR #28800's shape pin flipped.
email-template-dependent-unshipped-locale-read.dogfood.test.tsnow pins the behaviour: its SHAPE cases are renamed and assert the 404, and a second describe (its own boot) pins the pair-added locale and the pair-plus-dependent case.Tests
packages/qa/dogfood/test/email-template-dependent-unshipped-locale-read.dogfood.test.ts: 10 cases over the real showcase and its HTTP doors. RED on mainc064d3a2(4 failed / 6 passed): the base-named and package-less reads served the dependent'sfr-FR(2 cases), the base's lists and executing list left the pair'sfr-FRout andrenderTemplate fr-FRrendereden-US, and with both stored the base's address served the dependent's. GREEN atf085057c: 10/10 (os-verify-lock VERDICT command-exit 0).packages/objectql/src/protocol-bundle-member-base-address.test.ts(new): the real protocol over the real engine and an in-memory driver; 5 cases: pair addition on every read; dependent addition 404 on the base's address; pair and dependent at one locale; a bundle with no canonical member read with no member (the base's member, not a dependent'sen-US); the pair's declareden-USoverride beside its addedfr-FR. RED on the base'smetadata-protocolsrc (5/5 failed); GREEN 5/5 atf085057c.f085057c:@objectstack/metadata-protocoltypecheck + test (296 files passed, 4 skipped; 22068 tests passed),@objectstack/objectqltypecheck + test (520 files, 9988 tests passed).scripts/ablation-replace.mjs(anchor 1 → 0, restore to the HEAD blob verified),metadata-protocolrebuilt and the mutation proved absent fromdist/byablation-dist-preflight.mjs --absent, both files run:isBundle && false, scoped-listunseated.delete(it)→void it): unit 3 red (the three pair-addition cases), dogfood 2 red (the pair's lists and send; pair beside dependent). Dependent-only cases stay green.if (false)replacements failed the DTS build (unused locals), so nothing was measured; re-run with type-neutral replacements as above. A restore build followed the last leg.Acceptance notes
Metadata-protocol: semantic, not the claim's expectednarrowed. What an author can write is unchanged (no key, value or write refused; the metadata-protocol: the override write gate judges an email template by (type, name), so a <base>.custom pair can declare and publish an override of a locale the base does not ship #28746 gate is untouched); what a written dependent locale and a written pair locale MEAN on the base's address changed. AGENTS.md: "a changed meaning or default issemantic". Both takeminor.previewDrafts) of a pair-added member on the base's address is not covered: the preview arm asks the served-row candidates only, so it falls through to the active read, which now serves the pair's published row. Dormant; noted, not filed./layers?package=BASE&member=fr-FR) still answers 404, as noted on PR test(dogfood): pin what the reads serve for a dependent's email template locale the base does not ship (#28779) #28800.pr_create: open PRs touchingpackages/metadata-protocol/src/orpackages/objectql/src/registry.tsare feat(metadata-protocol,objectql): an object extension is an authorable item of a writable package, saved, published and exported through the existing doors (#28574) #28811, fix(metadata-protocol)!: the adopt-orphans door asks the save door's owner judges (another package owns the object; a permission set naming another owner) before the__cjudge #28808, fix(metadata-protocol): the by-id OCC comparison answers only for a row the caller reads #28731, fix(objectql,cloud-connection)!: a field patch reaches the owner's own fields only, at the fold and the install door; the install door refuses it with 422 (#28733) #28791.git merge-treeagainst this head: no conflict with any; none of their diffs touches the narrowing, the scoped list's stand-ins,findServedOverlayRow,registry.getItemorITEM_KEY_DISCRIMINATORS.Verification
Gate sweep at
f085057c:dispatch-gates --commandsderived 66 families; 66 run, all exit 0 (dispatch-gates --ranreads "66 run, 0 NOT-MEASURED", a derived zero). One first ran exit 3 on a prerequisite and was re-run green:check:dual-build-cjs-loads(seven packages outside the dogfood closure were unbuilt). The record ispm-orders/server/28802-gates-ran.txtin the project files.Full dogfood battery (the
(narrowing)arm) atf085057c, under the lock as CI runs it (pnpm --filter @objectstack/dogfood test, no shard, no path): VERDICT command-exit 0, Test Files 380 passed, 3 skipped (383); Tests 3118 passed, 28 skipped (3146).Changeset
.changeset/28802-bundle-member-base-address.md:@objectstack/metadata-protocolminor, both declaration lines, the ADR-0087not-required (no-migration-prescription)disposition. CI families are read from this PR's check-runs, not re-run here.Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_01FEkQ5m2Ryfx2Wubuz716mw