Repository navigation
fix(i18n): a tenant's rewrite of an extension-contributed validation rule message is served as written, on the refusal and on /meta - #27932
Conversation
…rule is served as written, on the refusal and on /meta ADR-0029 D9.2a compared a served rule message with the packaged OWNER's declaration only, so a rule a second package contributes through objectExtensions had nothing to compare with and the extension's translation won over a tenant's rewrite on both readers. - SchemaRegistry.getPackagedObjectBase: the packaged owner's declaration with each code extender's validations appended (scalars stay the owner's, #8037). The protocol's getPackagedObjectBase (/meta) and packagedRuleLookup (refusal) both read it. - The fold: an extender's rule yields to an overlay rule of the same name the owner does not declare, so the rule is served once. - Prose: i18n-resolver docblocks and content/docs/ui/translations.mdx. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cytRisTLYYDef35A9G
📓 Docs Drift CheckThis PR changes 3 package(s): 5 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 171 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 7d00634a5421da810d69b22a45327e81c01ce87d && git checkout 7d00634a5421da810d69b22a45327e81c01ce87d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin bacb3e6a0ff6e8bfdeade49d6830e34fb5522589 c8c5460d6b86e61fba18230464ef041449c793a6 && git checkout -B drift-repro bacb3e6a0ff6e8bfdeade49d6830e34fb5522589 && git merge --no-ff c8c5460d6b86e61fba18230464ef041449c793a6
node scripts/docs-audit/affected-docs.mjs --json bacb3e6a0ff6e8bfdeade49d6830e34fb5522589
|
…dy carries is served once Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cytRisTLYYDef35A9G
|
domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL Contract-review-claim: session_016WV4d1Np3hU7RQnA13YZcL · head 029633f · 2026-10-30T04:30Z |
|
domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL Contract reviewServed-tier: Written 2026-10-30T04:46Z. Read-only: card #27559 (body and all four comments: triage 6497467466, park note 6498065104, claim 6514064935 with its amendments, dev report 6514910503), PR #27932 body and file list, Head and check-runs. Two commits on the head: 5697aba (the fix) and 029633f (prose, ① Derived judgments
② Semver level
③ Boundary flagsReport 6514910503:
④ Design judgment
Findings:
Implemented-by: VERDICT: FAIL Generated by Claude Code |
… yield off Per contract review 6515204227: the fold yield reversed ADR-0029 D9.2a's additive validations sentence without an amendment, so it leaves this change. The one packaged rule set (getPackagedObjectBase, both readers) stays. Pins restated for the additive fold; '@objectstack/spec' patch added to the changeset for the i18n-resolver docblocks. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cytRisTLYYDef35A9G
|
domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL Contract-review-claim: session_016WV4d1Np3hU7RQnA13YZcL · head c8c5460 · 2026-10-30T05:26Z |
|
domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL Contract reviewServed-tier: Delta review of record 6515204227 (FAIL at 029633f), by the same reviewer. Written 2026-10-30T05:30Z. Read-only: the one commit on top of 029633f ( Head and check-runs. 029633f finished 41 success / 9 skipped / 0 failed after the first record. On c8c5460 at the time of writing: 42 runs, 0 failed; 13 success — ① Derived judgments
② Semver level
③ Boundary flagsRound-2 report 6515694156:
④ Design judgment
Findings:
Implemented-by: VERDICT: PASS Generated by Claude Code |
|
domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL 维护者速读
席位意见:ACCEPT。 席位核对Head-sha:
|
Requested by Elon · project thread
domain:server#4 · os-elon-musk · session_016WV4cytRisTLYYDef35A9G
Fixes #27559
Clause-②: yes (widening) —
SchemaRegistry(@objectstack/objectql) gains the public readgetPackagedObjectBase(name)Metadata-protocol: semantic
维护者速读
labelonto?layers=trueonly — the two reads #7556 reconciled on FIELDS now disagree on LABEL #8037 不受影响);折叠里 overlay 带的同名规则顶替扩展包原规则(仅当 owner 未声明该名),owner 与扩展包同名规则仍并存。回滚即 revert 本 PR。packages/objectql/src/registry.ts(getPackagedObjectBase、tenantCarriedRuleNames)、packages/objectql/src/engine-validation-messages.ts(packagedRuleLookup)、packages/metadata-protocol/src/protocol.ts(getPackagedObjectBase)。pnpm --filter @objectstack/objectql exec vitest run src/engine-validation-tenant-rewrite.test.ts src/protocol-packaged-object-base.test.ts,预期 16 passed:zh-CN/zh/en 下 /meta 与拒收是同一句,扩展规则改写为租户原句、只出现一次。What changes
ADR-0029 D9.2a compares a served rule message with the packaged rule of the same name. Since #27543 the packaged side was the OWNER's declaration only, so a rule a second package contributes through
objectExtensionshad nothing to compare with, and the catalog won over a tenant's rewrite on both readers.SchemaRegistry.getPackagedObjectBase(name)(new, besidegetPackagedObjectOwner) answers the packaged owner's declaration with each CODEextendcontributor'svalidationsappended in fold order. By reference when no code extender ships a rule;undefinedexactly wheregetPackagedObjectOwneris.ObjectStackProtocolImplementation.getPackagedObjectBase(what/metahandstranslateObjectaspackagedBase) andpackagedRuleLookup(what the rule evaluator judges the refusal by) both read it; a registry double without it is still read throughgetPackagedObjectOwner, the owner alone. The comparison staysvalueOverridesPackagedBase/scalarOverridesPackagedBase, unchanged.validationsgains the extenders' entries;label/pluralLabel/descriptionstay the owner's, so an extension's label still reads as diverged and keeps winning over the owner's catalog (Object-extension fold leaks the extension'slabelonto?layers=trueonly — the two reads #7556 reconciled on FIELDS now disagree on LABEL #8037). Pinned.owncontributor does NOT declare (tenantCarriedRuleNames, computed once over the base, the rule-name mirror oftenantAuthoredScalars). On an overlay base those are the tenant's copies of extender rules (a Studio GET → edit → PUT stores the folded object), so the rule is served and evaluated once, as the tenant last wrote it. Conservative edges, as for the scalars: noowncontributor, or a base that IS the owner (every no-overlay read andresolveOwnerLayer), returnsundefinedand the fold concatenates exactly as before; a name the owner declares itself is not counted, so an owner rule and an extender rule sharing a name stay two rules (pinned). Composes with A tenant object overlay drops everyobjectExtensionsfield from the resolved schema, andeffectivecontradictscodein the same response #8027'ssubtractExtenderContributions(that runs first, onfoldObjectExtendersOnto).packages/spec/src/system/i18n-resolver.tsdocblocks ofTranslateDocumentOptions.packagedBaseandtranslateObjectValidations;content/docs/ui/translations.mdx, the validation-rule paragraph of "An edit beats the packaged catalog". No predicate change inpackages/spec.packages/rest/src/meta-item-read-gate.tsis not edited (held by fix(rest)!: both import doors answer for the object as the data door does (#27728, #27729) #27897): itspackagedObjectBaseOfalready calls the protocol'sgetPackagedObjectBase, which now carries the extenders' rules.Why
A tenant who rewords a rule an add-on package ships, for their own users, saw it revert to the add-on's translation whenever a user's locale differed from the default, and the refusal also carried the add-on's original rule beside the tenant's.
Verification
Card probe re-run on
main784dc9ae(realObjectQLengine +SchemaRegistry; ownerpx_dutywith no rules, anextendcontributor addingext_blocked"Say what is blocking the duty.", a zh-CN bundle entry, a tenant overlay; not committed). Locale zh-CN:/meta/ refusal请写明阻塞原因。/请写明阻塞原因。ext_blockedto "Tell us what blocks this duty."/meta请写明阻塞原因。,请写明阻塞原因。Tell us what blocks this duty.请写明阻塞原因。 | 请写明阻塞原因。Tell us what blocks this duty.ext_blockeduntouched/meta/ refusal请写明阻塞原因。/请写明阻塞原因。Pins (red first on
main, 6 red; all green after):engine-validation-tenant-rewrite.test.ts: an overlay rewording the extension rule is served and refused as written on zh-CN / zh / en, folded once; an overlay carrying it untouched is folded once and translated on both readers; the fix(i18n): a tenant-rewritten validation rule message beats the packaged translation, on the refusal and on /meta #27543 owner-rule rows unchanged (the packaged-base assertion now also lists the extension rule). The refusal reader now joins every violation, so a duplicated rule shows.protocol-packaged-object-base.test.ts: the packaged base appends the extender's rules whilelabelstays the owner's (Object-extension fold leaks the extension'slabelonto?layers=trueonly — the two reads #7556 reconciled on FIELDS now disagree on LABEL #8037); by reference when no extender ships a rule; unmoved by an overlay rewording an extender rule; owner + extender same-name rules both still folded.Ablations (each restored, suite green after):
getPackagedObjectBaseanswers the owner only → 4 red (both readers).packagedRuleLookupreads the owner only → 1 red (refusal reader alone).Suites:
@objectstack/objectqlfull vitest 492 files / 9264 tests pass;@objectstack/resti18n override files (4 files / 109 tests) pass against the rebuilt dists;objectql+metadata-protocoltypecheck pass.dispatch-gates --commands(107 families, derived on a tree identical to head5697abaa) andpnpm lint: see the os-dev report comment on #27559.Human Minimum
packages/specholds no runtime logic.pnpm lintpasses.main, one pull request.packages/spectouched: its generated artifacts are regenerated and committed (docblock-only on a non-schema interface;check:docs/check:api-surfacegreen).content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.🤖 Generated with Claude Code
https://claude.ai/code/session_016WV4cytRisTLYYDef35A9G