fix(spec): localise the tenant-scope and owning-business-unit injected columns on the /meta read exits - #15786
Conversation
…d columns on the /meta read exits Add the two rows the built-in SYSTEM_FIELD_LABELS table was missing (organization_id, owning_business_unit_id) for every locale the table already carries, pin every injected column per shipped locale from the provenance module's own definitions, and keep the tenant-rename guard. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M59rPZZFzqhfMUPFqqZTkf
…t localised Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M59rPZZFzqhfMUPFqqZTkf
…jected-system-column-labels-localised
📓 Docs Drift CheckThis PR changes 1 package(s): 24 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 4 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 129 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 b3dfb88999986c697396f62977ee7f724b1623c2 && git checkout b3dfb88999986c697396f62977ee7f724b1623c2
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6a3cc134c489cf6b2474750cda20ccc2ec165c3c 7ca00ef17c6456e55f56493c3ed265fcbb1f9c76 && git checkout -B drift-repro 6a3cc134c489cf6b2474750cda20ccc2ec165c3c && git merge --no-ff 7ca00ef17c6456e55f56493c3ed265fcbb1f9c76
node scripts/docs-audit/affected-docs.mjs --json 6a3cc134c489cf6b2474750cda20ccc2ec165c3c
|
Fixes #14972
Every platform-injected system column now renders a localised display name on the
/metaread exits for the locales the platform already ships (zh-CN/ja-JP/es-ES), without touching the identity-stable definitions and without adding any authorable or published surface. The fix is two rows in the module-privateSYSTEM_FIELD_LABELStable inpackages/spec/src/system/i18n-resolver.ts(triage's option 1: presentation-layer resolution at the read exit, which already existed for five of the seven columns).H1 — measurement first (pristine tree,
origin/mainat1c1421401; identical at the merged head7ca00ef17)Definitions read from
packages/spec/src/data/injected-system-column-provenance.ts; rows read fromSYSTEM_FIELD_LABELS; the last column istranslateObjecton a document spread frominjectedSystemColumnDefs(...)with{ locale: 'zh-CN', fallbackChain: ['zh-CN'] }and no bundle (the/metaread path for a custom object).labelenequals labelorganization_id(TENANT_SCOPE_FIELD_DEF,hidden: true)OrganizationOrganization(leaks)created_atCreated At创建时间created_byCreated By创建人updated_atLast Modified At更新时间updated_byLast Modified By更新人owner_id(OWNER_FIELD_DEF)Owner所有者owning_business_unit_id(OWNING_BUSINESS_UNIT_FIELD_DEF,hidden: true)Owning Business UnitOwning Business Unit(leaks)Premise partly false at runtime. The card (and cloud#1904's linter) read the DEFINITIONS, which are English by design and must stay byte-identical; the rendered
/metadocument already localised five of the seven columns,Created Byincluded, throughbuiltinSystemFieldLabel. The two columns that did leak English on every locale areorganization_idandowning_business_unit_id— and both definitions arehidden: true, so per triage neither counts toward the user-visible harm; they are fixed for consistency. Whether a consumer renders ahiddencolumn is a consumer decision this PR does not touch.The change
packages/spec/src/system/i18n-resolver.ts—SYSTEM_FIELD_LABELSgainsorganization_idandowning_business_unit_idforen/zh-CN/ja-JP/es-ES; the table's docblock now names the full injected set and states the rule the rows must obey (enbyte-equal to the definition'slabel, or the row silently stops matching).builtinSystemFieldLabelis unchanged, so the tenant-rename guard (currentLabel !== entry.en⇒ leave it alone) still holds.organization_id=组织/組織/Organización(9 of 10organization_idleaves per bundle, plussys_organization.label).owning_business_unit_idhas no bundle leaf anywhere (injected, hidden, declared by no platform object), so its wording composes the bundles'sys_business_unit.label(业务单元/ビジネスユニット/Unidad de negocio) with the ownership qualifier theirsys_user.primary_business_unit_idleaves use:所属业务单元/所属ビジネスユニット/Unidad de negocio propietaria. That composition is the one wording judgement in this PR; it sits on a hidden column and is trivially re-wordable in place..changeset/injected-system-column-labels-localised.md—@objectstack/specpatch.Rendering after, per column and locale (same document, no bundle):
organization_idowning_business_unit_idenOrganizationOwning Business Unitzh-CN组织所属业务单元创建时间/创建人/更新时间/更新人/所有者(as before)ja-JP組織所属ビジネスユニット作成日時/作成者/更新日時/更新者/所有者(as before)es-ESOrganizaciónUnidad de negocio propietariaCreado el/Creado por/Actualizado el/Actualizado por/Propietario(as before)H3 — the read exits have the locale, and inject before they translate (verified on
origin/main)metadata-protocol/src/protocol.ts:governServedObject(wrappingapplyInjectedSystemColumns(applyAuditFieldGovernance(item))) is applied insidegetMetaItems, insidegetMetaItem(all three arms), insidegetMetaItemLayeredoneffective, andgetMetaItemCacheddelegates togetMetaItem.rest/src/rest-server.ts: the single read (cached, cached-undetermined-mask and uncached arms) hands the protocol's document totranslateMetaEnvelope→translateMetaItem→translateMetadataDocument(..., { locale, packagedBase }); the list read handsp.getMetaItems(...)totranslateMetaItems; the public-form path builds its schema fromp.getMetaItems(...)and translates it afterwards. Injection precedes translation on all three; no path adds the columns after translation. Nothing inmetadata-protocol/metadata-core/objectqlis edited.Pins
packages/spec/src/system/i18n-resolver.test.ts— new blocktranslateObject localises every platform-injected column (objectstack#14972): the fixture spreadsinjectedSystemColumnDefs(...)(never a retyped label), asserts all seven columns by name foren(unchanged),zh-CN,ja-JP,es-ES; a tenant that relabelledorganization_idkeeps its label on every locale while the other columns still localise (H4); the input document and the shipped definitions are not mutated.packages/rest/src/meta-object-injected-column-i18n.test.ts— the seam: a protocol double serving a document whose injected columns were spread from the provenance module, read throughGET /meta/:type/:nameandGET /meta/:typewithAccept-Language: zh-CN— every injected column named, Chinese on both reads;enkeeps the shipped defaults on both reads.Ablation (fix committed first; both legs rebuilt; run under the shared verification lock)
Mutation leg: the two rows removed by an exact-anchor script (each anchor asserted to match once) — on disk: row-key greps
1 → 0for both,git diff --stat7 deletions, blob hash4033d70vs HEAD0dc4e42;pnpm --filter @objectstack/spec buildexit 0; executable output:owning_business_unit_id: {count indist/system/index.js1 → 0; theni18n-resolver.test.ts3 failed | 236 passed (exactly thezh-CN/ja-JP/es-ESpins) and the rest pin 2 failed | 2 passed (exactly the two zh-CN reads). Direction as predicted: red, nothing else moved. Restore leg:git checkout HEAD -- ABSOLUTE_PATHfrom anEXIT INT TERMtrap, proved bygit hash-objectequal to the HEAD blob and an emptygit diff HEAD; then a rebuild, proved bynode scripts/ablation-dist-preflight.mjs @objectstack/spec 'Unidad de negocio propietaria'(marker present in 4 built files, tree clean) and the row-key count back to1. One correction worth recording: my first restore-leg preflight used the Chinese marker and came back void, because the built output escapes non-ASCII — the executable-output proof on the mutation leg is the row-key count above, and the restore leg was re-proved with an ASCII marker.Verification union — run at the merged head
7ca00ef17(origin/main8e500f23emerged; #15707 has NOT landed, itsi18n-resolver.tshunks — header andResolveOptions.fallbackChaindocblocks — are disjoint from this PR's table region)Consumer direction: prefix filter, downstream of
@objectstack/spec; dependency closures built first (turbo run build).@objectstack/specvitest run --maxWorkers=2@objectstack/spectypecheck(tsc + scripts + test layer)check:test-typecheck: OK — 54 file(s) / 261 error(s) / 145 pinned(shrink-only ledger, unchanged)@objectstack/speccheck:generated✓ All 15 generated artifacts are up to date—check:api-surface:public API surface + factory signatures unchanged ✓(no surface added)@objectstack/restvitest run --maxWorkers=2@objectstack/metadata-protocolvitest run --maxWorkers=2@objectstack/platform-objectsvitest run --maxWorkers=2 --passWithNoTests@objectstack/runtimevitest run --maxWorkers=2@objectstack/clivitest run --project unit --maxWorkers=2published-subpath-hook-body.pin.test.ts) refused loudly becausepackages/cli/distwas absent (PREREQUISITE NOT MET, not red) and passed (13 tests) oncepnpm --filter @objectstack/cli buildhad run; theintegrationlayer is declared to CI (this diff touches no integration file, spawn entry or driver/kernel start path)turbo ls --affectedagainst the base1c1421401lists 75+ packages because the merge brought 26 main-side commits; the PR-shaped diff (origin/main...HEAD) is exactly the four files above.Gates (derived from the actual diff with
node scripts/pm/dispatch-gates.mjs, no paths: 73 families at570a66334, re-run at7ca00ef17; exits captured before any pipe)@objectstack/spec/@objectstack/lintrun check:*families (api-surface,authorable-surface,browser-reachable-entries,docs,dual-source-exports,empty-state,entry-nameability,error-code-provenance,export-origins,exported-any,liveness,llms-txt,meta-url-spelling,react-blocks,skill-refs,strictness-ledger,variant-docs,yaml-examples,doc-formula-expressions): all exit 0.pnpm check:*families incl.widget-option-census(OK … 5 key(s) … all in CONSUMED_WIDGET_OPTION_KEYS),cross-package-test-inputs(OK: 27 package(s) … all declared),test-source-alias(OK — 72 packages),engine-double-contract(OK — 783 pinned),where-matcher(347 matcher(s) … correctly),nul-bytes(OK (scanned 7614 text file(s)),changeset-gate-self-tests,merge-driver,published-files,type-check-coverage,type-source-resolution: all exit 0.node scripts/*families (each with and without--self-testwhere derived), incl.check-adr-0087-registration --base origin/main(adds no declared-breaking changeset),check-empty-changeset,check-changeset-no-major,check-keyed-text-bounds,check-platform-object-tenancy-census,check-system-context-census,check-comment-mask-*,check-ci-filter-parity: all exit 0.check:dual-build-cjs-loads(PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/),check:type-check-debt(re-measures tsc per ledger entry over the whole built workspace), the seven$RUNNER_TEMP/ matrix-valued invocations the derivation itself marks not runnable, and the two PR-context guards (check-single-claim-pathsneeds a PR number;check-partof-closing-keywordwas run locally with this body asPR_BODY— see the report comment for its reading).Not touched, by instruction
packages/spec/src/data/injected-system-column-provenance.ts(byte-identity guarded),packages/spec/src/migrations/registry.ts, the generated bundles underpackages/platform-objects/src/apps/translations/,packages/spec/src/data/filter.zod.ts,packages/spec/src/data/field-value.zod.ts,packages/metadata-protocol/**,packages/metadata-core/**,packages/objectql/**.Out of scope, observed but not filed (already ratcheted by
check:i18n-coverage): each translated bundle still carries one untranslatedorganization_idleaf (Organization) and several untranslatedcreated_at/updated_atleaves on platform objects.Generated by Claude Code