Repository navigation
fix(metadata-protocol,rest): a .custom pair's declared override is labelled in the ruled order on every /meta read - #30353
Conversation
…verride (red on main) A page, dataset, app or book override serves the base's translation over its own label, and a view override never reads the pair's translation. Claude-Session: https://claude.ai/code/session_01Utp6CajcU31aZRQuQzwWj6 Co-authored-by: Claude <noreply@anthropic.com>
…s the ruled label order on every type The page, dataset, app and book packaged bases join their siblings, and the /meta reads hand each translator the pair's own translations, so a translation the pair ships beats the override's own label, which beats the base's translation (ruling record 6631250894). Claude-Session: https://claude.ai/code/session_01Utp6CajcU31aZRQuQzwWj6 Co-authored-by: Claude <noreply@anthropic.com>
…'s own translations on a real boot Claude-Session: https://claude.ai/code/session_01Utp6CajcU31aZRQuQzwWj6 Co-authored-by: Claude <noreply@anthropic.com>
…the pair's bundle would show Claude-Session: https://claude.ai/code/session_01Utp6CajcU31aZRQuQzwWj6 Co-authored-by: Claude <noreply@anthropic.com>
…anslation first; changeset Claude-Session: https://claude.ai/code/session_01Utp6CajcU31aZRQuQzwWj6 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Utp6CajcU31aZRQuQzwWj6 Co-authored-by: Claude <noreply@anthropic.com>
…s the bound and asserts findOne; ratchet the engine-double ledger Claude-Session: https://claude.ai/code/session_01Utp6CajcU31aZRQuQzwWj6 Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 8 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 4 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 26 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 4897081b20eb77633859b86e4fa384855fc67096 && git checkout 4897081b20eb77633859b86e4fa384855fc67096
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8c9f48f87c3d5aa99778d5809ceb92128a6f5ea4 644edc243cb470e69d05b182ed261aa3fc9f1bda && git checkout -B drift-repro 8c9f48f87c3d5aa99778d5809ceb92128a6f5ea4 && git merge --no-ff 644edc243cb470e69d05b182ed261aa3fc9f1bda
node scripts/docs-audit/affected-docs.mjs --json 8c9f48f87c3d5aa99778d5809ceb92128a6f5ea4
|
|
domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb 席位核对 · ACCEPT · 2026-11-05T07:12Z · head
|
|
domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb Re-queued · 2026-11-05T07:50Z: dequeued by the merge group's |
Requested by Bill · project thread
domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb
维护者速读
<基础包>.custom配对包里覆盖了基础包的页面、数据集、应用或书(仪表盘、视图、报表同样适用),改过的标签现在在所有/meta读上按裁定顺序显示:配对包自己的译文优先,其次是覆盖项自己写的文字,最后才是基础包的译文。packages/rest/src/meta-item-read-gate.ts的pairTranslationBundleOf和PACKAGED_BASE_ACCESSORS新加的四行,再看packages/metadata-protocol/src/protocol-discovery-and-bases.ts的getPairTranslationBundle。pnpm --filter @objectstack/rest exec vitest run src/meta-dashboard-view-i18n-explicit-override.test.ts,预期 108 条全绿;修复前的代码上有 19 条是红的。Fixes #30062
Clause-②: yes (widening) —
ObjectStackProtocolImplementationgains six public methods,getPackagedPageBase,getPackagedDatasetBase,getPackagedAppBase,getPackagedBookBase,servedOverrideBaseOfandgetPairTranslationBundle(measured on the builtpackages/metadata-protocol/dist/index.d.ts);@objectstack/rest's exportedMetaBookTreeSourcesaccepts one more optional key,resolveProtocol(measured on the builtpackages/rest/dist/index.d.ts). REST's newpairTranslationBundleOfis module-internal: it is not in the package's export list. Nothing accepted is taken away.Metadata-protocol: semantic
Skills-docs: none — the "Customizing an installed package (ADR-0146)" paragraph of
skills/objectstack-platform/SKILL.mdsays nothing about how an override's labels are translated, so no sentence there turns false; its stale served-type list is #30229's.What changes
The spec half (PR #30236, #30193) gave every document translator
packagedBaseandpairBundle. Its "Server-half contract for #30062", quoted:This PR is that server half, with the seat's rulings on where the contract does not fit the code (Deviations below).
packagedBasefor page, dataset, app and book. Four protocol accessors,getPackagedPageBase/getPackagedDatasetBase/getPackagedAppBase/getPackagedBookBase, answer the base package's own item through the same shadow-immune lookupgetPackagedDashboardBase/ViewBase/ReportBaseuse (packagedArtifactBase, now typed over the seven artifact types). They answer the base's item while the override is served, right after it is published and after a restart, andundefinedfor an unknown name.PACKAGED_BASE_ACCESSORSin the REST read gate gains the four rows, soGET /meta/:type/:nameandGET /meta/:typehand them to the translators.actionis now the one translatable type without a row.pairBundlefor a declared override.servedOverrideBaseOf(type, document)names the base a served document overrides: an overridable type, anoverrides.package, and a served_packageIdequal to it (the envelopeprotocol-meta-item-reads.tsstamps on a served override). Anything else, a base's own item included, answersundefined.getPairTranslationBundle(base)reads that base's pair's own activetranslationrows (package_id=customizationPackageId(base)) throughreadAuthoredTranslationLayer, the same reader the i18n service's authored layer uses (conversions, locale resolution and bookkeeping stripped), and answers{ locale: data }, orundefinedwhen the pair has none, the id is empty or invalid, or the read fails. The REST gate'spairTranslationBundleOfjoins the two, feature-detected on the protocol, and passes the result aspairBundleon the item read and on every listed item. A list reads each base's pair once per request.The book tree.
createMetaBookTreeAnswerforcedresolveProtocoltoundefined, soGET /meta/book/:name/treetranslated a book with no packaged base.MetaBookTreeSourcesnow takes the transport'sresolveProtocol, and the RestServer route passes it. The runtime dispatcher's tree sources already spread it. The docs sidebar and the book list now serve the same label (#26690).Deviations
packages/rest/src/meta-item-read-routes.ts(+2): the book tree route's sources, outside the dispatched surface; widened by the seat for the tree fix above.packages/objectql/src/protocol-custom-override-bare-slot-envelope.test.ts(+93, test only): the real-boot pins reuse metadata-protocol: page, app, dataset, doc and book serve a published override only from the registry bare slot; build the row-served path, then take them out of it #29613's sealed-boot fixture there. They cannot live inpackages/metadata-protocol: it does not depend on@objectstack/objectql(objectql depends on it), so the import would be a cycle. Widened by the seat.scripts/engine-double-contract.pinned.json(+5 / -1, generated):check:engine-double-contractratchets the two newfindOnedoubles the new pins assert (protocol.pair-translation-bundle.test.ts0 → 1,meta-dashboard-view-i18n-explicit-override.test.ts1 → 2), written bynode scripts/check-engine-double-contract.mjs --write. Coverage grew; nothing else in the ledger moved..custompackage is authored, never a code package, so it has none; and code bundles reach the i18n service's static layer with no package provenance.pairBundleis built from the pair's activetranslationrows only (seat ruling).servedOverrideBaseOfanswersundefinedfor it and no object read takes apairBundle. ItspackagedBaseis passed exactly as before.servedOverrideBaseOfanswersundefinedthere and the slots keep today'spackagedBaseorder.form-routes.ts(public form views) is outside the surface and is unchanged: it still passespackagedBase, with nopairBundle.Tests
packages/rest/src/meta-dashboard-view-i18n-explicit-override.test.ts(realSchemaRegistryand protocol over fake engine rows, throughRestServer): §1 now asks each of the eight accessor rows, the four new ones included, and asks no accessor foraction,flow, a plural and a prototype key. New §5 over page, dataset, app, book and view: an override's own label beats the base's translation (zh-CNanden); a translation the pair ships wins inzh-CNwhileenreads the override's label, on the item and the list read; no override reads the base's translation; a member reads what an admin reads; the book tree serves the label the book list serves for three seeds. 19 red on the pre-fix code, 108/108 after.packages/objectql/src/protocol-custom-override-bare-slot-envelope.test.ts: a real boot, page / app / dataset / book, with and without a metadata service: each accessor answers the base's item before the override, right after it and after a restart;servedOverrideBaseOfanswers the base on the served and listed items and nothing on the base's own;getPairTranslationBundleisundefinedbefore, then exactly the pair's saved translation beside a package-less row of the same key, andundefinedfor the pair's own id; fed totranslateMetadataDocumentthe three give pair → own → base. 16 new, file 87/87.packages/metadata-protocol/src/served-override.test.ts(servedOverrideBaseOftable) and the newprotocol.pair-translation-bundle.test.ts(pair rows only, every read namingpackage_id, bookkeeping stripped,undefinedwith no read for an empty, invalid or pair id,undefinedon a read failure): 15/15. Its fake engine refuses any operator or combinator, holds the caller'slimitafter the filter, and opensfindOnewithassertEngineFindOnePredicate.Ablations
Through
scripts/ablation-replace.mjs, each restored to blob == HEAD withgit diff HEADempty. A metadata-protocol mutation was rebuilt and proved indist/withablation-dist-preflight.mjs(exit 0) before the objectql and rest legs, which read itsdist/; after the last one a clean rebuild was proved free of all three markers (--absent, exit 0).PACKAGED_BASE_ACCESSORSrows deletedpairTranslationBundleOfnever fetchesresolveProtocoldeletedservedOverrideBaseOfalwaysundefinedpackage_idfilter droppedgetPackagedBookBaseanswersundefinedA5's first run left objectql green: the layer merges rows in name order, and the package-less row sorted before the pair's, so its text never showed. The row was renamed to sort after the pair's, and the pin now goes red.
Docs
content/docs/ui/translations.mdx, "An edit beats the packaged catalog": the bullet "An edited string is served in the language it was written in, to every reader" is false for a declared override whose pair translates the string. It now names that exception, and a new paragraph states the order for a declared override (page, dataset, app, book, dashboard, view, report; the pair's translations first; onlytranslationitems saved in the pair; the item, list and book tree reads agree).content/docs/concepts/metadata-lifecycle.mdxandcontent/docs/api/metadata-api.mdxwere read sentence by sentence: neither says how an override's labels are translated, so neither changes.Generated by Claude Code