Skip to content

fix(spec): localise the tenant-scope and owning-business-unit injected columns on the /meta read exits - #15786

Merged
os-project-manager merged 3 commits into
mainfrom
claude/issue-14972-injected-system-column-labels-localised
Sep 5, 2026
Merged

fix(spec): localise the tenant-scope and owning-business-unit injected columns on the /meta read exits#15786
os-project-manager merged 3 commits into
mainfrom
claude/issue-14972-injected-system-column-labels-localised

Conversation

@claude

@claude claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Fixes #14972

Every platform-injected system column now renders a localised display name on the /meta read 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-private SYSTEM_FIELD_LABELS table in packages/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/main at 1c1421401; identical at the merged head 7ca00ef17)

Definitions read from packages/spec/src/data/injected-system-column-provenance.ts; rows read from SYSTEM_FIELD_LABELS; the last column is translateObject on a document spread from injectedSystemColumnDefs(...) with { locale: 'zh-CN', fallbackChain: ['zh-CN'] } and no bundle (the /meta read path for a custom object).

column definition label row present row en equals label rendered on a zh-CN read, before
organization_id (TENANT_SCOPE_FIELD_DEF, hidden: true) Organization no n/a Organization (leaks)
created_at Created At yes yes 创建时间
created_by Created By yes yes 创建人
updated_at Last Modified At yes yes 更新时间
updated_by Last Modified By yes yes 更新人
owner_id (OWNER_FIELD_DEF) Owner yes yes 所有者
owning_business_unit_id (OWNING_BUSINESS_UNIT_FIELD_DEF, hidden: true) Owning Business Unit no n/a Owning 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 /meta document already localised five of the seven columns, Created By included, through builtinSystemFieldLabel. The two columns that did leak English on every locale are organization_id and owning_business_unit_id — and both definitions are hidden: true, so per triage neither counts toward the user-visible harm; they are fixed for consistency. Whether a consumer renders a hidden column is a consumer decision this PR does not touch.

The change

  • packages/spec/src/system/i18n-resolver.tsSYSTEM_FIELD_LABELS gains organization_id and owning_business_unit_id for en / zh-CN / ja-JP / es-ES; the table's docblock now names the full injected set and states the rule the rows must obey (en byte-equal to the definition's label, or the row silently stops matching). builtinSystemFieldLabel is unchanged, so the tenant-rename guard (currentLabel !== entry.en ⇒ leave it alone) still holds.
  • Wording comes from the generated platform bundles so a system column reads the same on custom and platform objects: organization_id = 组织 / 組織 / Organización (9 of 10 organization_id leaves per bundle, plus sys_organization.label). owning_business_unit_id has 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 their sys_user.primary_business_unit_id leaves 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/spec patch.

Rendering after, per column and locale (same document, no bundle):

locale organization_id owning_business_unit_id the other five
en Organization Owning Business Unit unchanged English defaults
zh-CN 组织 所属业务单元 创建时间 / 创建人 / 更新时间 / 更新人 / 所有者 (as before)
ja-JP 組織 所属ビジネスユニット 作成日時 / 作成者 / 更新日時 / 更新者 / 所有者 (as before)
es-ES Organización Unidad de negocio propietaria Creado 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 (wrapping applyInjectedSystemColumns(applyAuditFieldGovernance(item))) is applied inside getMetaItems, inside getMetaItem (all three arms), inside getMetaItemLayered on effective, and getMetaItemCached delegates to getMetaItem. rest/src/rest-server.ts: the single read (cached, cached-undetermined-mask and uncached arms) hands the protocol's document to translateMetaEnvelopetranslateMetaItemtranslateMetadataDocument(..., { locale, packagedBase }); the list read hands p.getMetaItems(...) to translateMetaItems; the public-form path builds its schema from p.getMetaItems(...) and translates it afterwards. Injection precedes translation on all three; no path adds the columns after translation. Nothing in metadata-protocol / metadata-core / objectql is edited.

Pins

  • packages/spec/src/system/i18n-resolver.test.ts — new block translateObject localises every platform-injected column (objectstack#14972): the fixture spreads injectedSystemColumnDefs(...) (never a retyped label), asserts all seven columns by name for en (unchanged), zh-CN, ja-JP, es-ES; a tenant that relabelled organization_id keeps 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 through GET /meta/:type/:name and GET /meta/:type with Accept-Language: zh-CN — every injected column named, Chinese on both reads; en keeps 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 → 0 for both, git diff --stat 7 deletions, blob hash 4033d70 vs HEAD 0dc4e42; pnpm --filter @objectstack/spec build exit 0; executable output: owning_business_unit_id: { count in dist/system/index.js 1 → 0; then i18n-resolver.test.ts 3 failed | 236 passed (exactly the zh-CN / ja-JP / es-ES pins) 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_PATH from an EXIT INT TERM trap, proved by git hash-object equal to the HEAD blob and an empty git diff HEAD; then a rebuild, proved by node 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 to 1. 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/main 8e500f23e merged; #15707 has NOT landed, its i18n-resolver.ts hunks — header and ResolveOptions.fallbackChain docblocks — are disjoint from this PR's table region)

Consumer direction: prefix filter, downstream of @objectstack/spec; dependency closures built first (turbo run build).

package command reading
@objectstack/spec vitest run --maxWorkers=2 Test Files 473 passed (473) · Tests 12711 passed
@objectstack/spec typecheck (tsc + scripts + test layer) exit 0; check:test-typecheck: OK — 54 file(s) / 261 error(s) / 145 pinned (shrink-only ledger, unchanged)
@objectstack/spec check:generated ✓ All 15 generated artifacts are up to datecheck:api-surface: public API surface + factory signatures unchanged ✓ (no surface added)
@objectstack/rest vitest run --maxWorkers=2 Test Files 181 passed (181) · Tests 3090 passed
@objectstack/metadata-protocol vitest run --maxWorkers=2 Test Files 163 passed, 2 skipped (165) · Tests 2387 passed, 10 skipped
@objectstack/platform-objects vitest run --maxWorkers=2 --passWithNoTests Test Files 33 passed (33) · Tests 518 passed
@objectstack/runtime vitest run --maxWorkers=2 Test Files 226 passed (226) · Tests 3243 passed
@objectstack/cli vitest run --project unit --maxWorkers=2 Test Files 174 passed of 175 on the first run, Tests 2328 passed, 6 expected fail, 13 skipped; the one remaining file (published-subpath-hook-body.pin.test.ts) refused loudly because packages/cli/dist was absent (PREREQUISITE NOT MET, not red) and passed (13 tests) once pnpm --filter @objectstack/cli build had run; the integration layer is declared to CI (this diff touches no integration file, spawn entry or driver/kernel start path)

turbo ls --affected against the base 1c1421401 lists 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 at 570a66334, re-run at 7ca00ef17; exits captured before any pipe)

  • 19 @objectstack/spec / @objectstack/lint run 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.
  • 26 repo-level 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.
  • 39 direct node scripts/* families (each with and without --self-test where 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.
  • NOT MEASURED locally, declared to CI: 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-paths needs a PR number; check-partof-closing-keyword was run locally with this body as PR_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 under packages/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 untranslated organization_id leaf (Organization) and several untranslated created_at / updated_at leaves on platform objects.


Generated by Claude Code

…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
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 3 documentable anchor(s).

24 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 6a3cc134c489cf6b2474750cda20ccc2ec165c3c.

4 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • the SDK route bridge reached 61 of 219 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 129 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 6a3cc134c489cf6b2474750cda20ccc2ec165c3cpackageMentionDocs.

Which tree this was computed on

This run read content/docs from b3dfb88999986c697396f62977ee7f724b1623c2 — the merge of head 7ca00ef17c6456e55f56493c3ed265fcbb1f9c76 into base 6a3cc134c489cf6b2474750cda20ccc2ec165c3c, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 6a3cc134c489cf6b2474750cda20ccc2ec165c3c → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-project-manager
os-project-manager marked this pull request as ready for review September 5, 2026 08:26
@os-project-manager
os-project-manager added this pull request to the merge queue Sep 5, 2026
Merged via the queue into main with commit d8d2776 Sep 5, 2026
43 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-14972-injected-system-column-labels-localised branch September 5, 2026 08:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:system size/m tests tooling

Projects

None yet

2 participants