Skip to content

fix(platform-objects): nine es-ES and ja-JP metadata-form leaves say what their source says - #16819

Merged
os-musk merged 2 commits into
mainfrom
claude/issue-16227-es-ja-source-parity-nine-leaves
Sep 8, 2026
Merged

fix(platform-objects): nine es-ES and ja-JP metadata-form leaves say what their source says#16819
os-musk merged 2 commits into
mainfrom
claude/issue-16227-es-ja-source-parity-nine-leaves

Conversation

@os-musk

@os-musk os-musk commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Fixes #16227

Nine metadataForms leaves served a superseded English source revision in both es-ES and ja-JP. Each was a faithful translation of the sentence the source carried when it was extracted; the English moved afterwards, and bundle merge fills gaps only — a present-but-stale leaf is not a gap, so no amount of re-extraction corrects it.

Clause-②: no

Values only: no exported symbol, no key added to a published payload, and the key set does not move. Measured, not assumed — all four bundles flatten to 773 leaves with identical key sets before and after this change.

Deliverable 1 — the nine-row table, re-measured

The card measured at origin/main 9f3989771; this branch bases on 56ee7aef1d. The comparison was re-run rather than inherited, by the card's own method: the bundles under packages/platform-objects/src/apps/translations/ flattened to dotted leaf paths and each locale's value read against its en counterpart.

The re-measured table is identical to the card's at all nine leaves — same paths, same strings, both locales. The es-ES and ja-JP metadata-forms bundles moved exactly once between the two commits, at dashboard.fields.gap.helpText (PR #16190), which is none of the nine.

leaf source (en) was (es-ES / ja-JP) now
object.fields.fields.trackHistory.helpText Summarize this field on the record activity timeline "keep change history" in both Resume este campo en la cronología de actividad del registro / 「レコードのアクティビティタイムラインでこのフィールドを要約する」
object.fields.isSystem.helpText System object (protected from deletion; defaults sharing to public) dropped defaults sharing to public …; el uso compartido predeterminado es público) / 「…。共有の既定は公開)」
view.fields.filter.helpText Filter conditions — same visual builder as the list toolbar, with field-type-aware operators and value inputs everything after the dash dropped full clause carried in both
action.fields.body.helpText Either an L1 expression or an L2 sandboxed JS body "JavaScript code to run" Una expresión L1 o un body JS L2 en sandbox / 「L1 式または L2 サンドボックス JS body」
action.sections.advanced.description AI exposure and API request shape. asserted bulk operations Exposición a IA y forma de la solicitud API. / 「AI 公開と API リクエスト形状。」
page.fields.type.helpText Page kind. "List / Interface" binds a source view… asserted the page-kind enum the source stopped listing the "List / Interface" sentence carried in both
report.sections.basics.description Identity and report type. "data source" Identidad y tipo de informe. / 「ID とレポートタイプ。」
report.fields.columns.helpText Dimension names across (matrix only) "columns to show in the report" Nombres de dimensión en horizontal (solo matrix) / 「横方向のディメンション名(matrix のみ)」
email_template.fields.variables.helpText a JSON shape example described a list of variable names the JSON example verbatim (language-neutral)

An independent instrument agrees with the card

Rather than only re-reading the nine, every leaf's value history was reconstructed across all 38 commits that ever touched the three bundles, and each leaf asked: did en move after this locale's own value last changed? That yields 18 candidate paths mechanically, with no judgement involved. All nine of the card's leaves are in it, and so are both leaves the card names as borderline. The card's floor holds.

The two borderline leaves — deliberately left alone

app.fields.defaultAgent.helpText and email_template.sections.identity.description were both re-read and both still show what the card describes. They are not touched here: the card names them without counting them, and this branch repairs the counted nine. Repairing them is a separate wording judgement.

Written from the source, not copied from the zh-CN repair

Per the dispatch: 「zh-CN 那一半的成因是坏导入,这九个的成因是源漂移;两者的正确目标文本可能不同。」 Two places where the zh-CN target text was deliberately not copied:

  • object.fields.fields.trackHistory.helpText — the zh-CN repair reads 「…概述该字段的变更」, adding changes, which the source does not say. es-ES and ja-JP summarize the field.
  • email_template.fields.variables.helpText — the zh-CN repair prefixes 「示例:」. The source is a bare JSON array, so both locales carry it verbatim.

For action.fields.body.helpText the rendering is not invented: the sibling leaf hook.fields.body.helpText already translates the identical source sentence correctly in both locales, and this leaf now matches it.

The digest entry this adds, and why it is the generator's and not a hand-edit

Carrying the JSON example verbatim makes those two leaves byte copies of their source, which is exactly the condition under which os i18n extract records a source digest — so es-ES.source-hashes.generated.ts and ja-JP.source-hashes.generated.ts each gain one entry, written by node scripts/check-i18n-bundles.mjs --write.

That file's header warns against editing it to silence a staleness report. This is the opposite: the entry records a true fact — the leaf is a copy of the current source — and its effect is to move this one leaf out of LEGACY-TRUSTED and into the instrument's coverage. If the JSON example is ever revised, the leaf now reports stale instead of silently serving the superseded revision.

Why no gate, and why these nine were invisible

Verified mechanically rather than assumed: all nine leaves carry zero entries in either locale's *.source-hashes.generated.ts (which holds 190 of the 773 metadataForms paths). The table records a digest only while a leaf is still a byte copy of its source, and drops the entry as soon as a translator writes real text — so a hand-written translation is LEGACY-TRUSTED and never reported stale, by construction. That is the coverage hole, and it is documented in source-hash.ts's own header.

No gate is proposed or extended here. #14931 already ruled that a second stale-detection instrument is debt rather than fix, and that whether a gate should exist is a maintainer call.

Changeset — route and level, on a measurement

.github/workflows/pr-automation.yml's WHICH LEVEL block gives two routes, chosen by what the PR actually releases.

Route 2 (skip-changeset) is rejected. Its stated scope is a diff that publishes nothing — ".github/, .claude/, skills/, docs/, content/, examples/, tests-only, and the like". Measured false here: @objectstack/platform-objects carries no private flag, is version 17.3.0, publishes files: ["dist"], and the repaired strings are present in the built output — npm pack --dry-run reports 69 files including six metadata-translations entries, and the repaired text appears in dist/metadata-translations/index.mjs, dist/index.js and dist/plugin.* (as Nombres de dimensi\xF3n en horizontal (solo matrix) — the bundler hex-escapes non-ASCII, which is why a raw grep for the accented form finds nothing).

Route 1, level patch. The block says a purely additive widening of a published surface takes at least minor, and that "a fix( that changes no public surface stays patch". This adds no exported symbol and no accepted key or value; the key set is unchanged at 773. AGENTS.md's floor against none is satisfied rather than exceeded.

Verification

Dependency closure built before the first gate. Gate coverage reconciliation, verbatim:

Run reconciliation — 50 derived, 50 run, 0 NOT-MEASURED, 0 UNRUN.
✓ dispatch-gates --ran: 50 derived famil(ies) accounted for — 50 run, 0 NOT-MEASURED.

That line accounts for coverage, not verdicts. The verdicts, stated separately: 50 of 50 exit 0. Two needed a prerequisite build first and were re-run — check:i18n and check:dual-build-cjs-loads both returned exit 3 (PREREQUISITE NOT MET, which is NOT MEASURED, never a pass) against a partial dist, and both exit 0 once their named closure was built.

pnpm check:i18n              exit 0   OK (9 package(s) — all bundles in sync, no undeclared authoring keys)
pnpm check:i18n-stale-fill   exit 0   OK (10 bundle set(s) — no new stale fills, 0 baselined)
pnpm check:i18n-coverage     exit 0   OK (13 config(s), 621 baselined untranslated string(s), none new)
pnpm --filter @objectstack/platform-objects test       37 files, 545 tests passed
pnpm --filter @objectstack/platform-objects typecheck  exit 0
packages/cli test/platform-page-i18n-parity.test.ts    26 tests passed

check:i18n-coverage is run beyond the derived union on purpose: making a leaf a byte copy of its source is the extractor's definition of an untranslated fill, so the ratchet is the thing that could have moved. It reports none new.

Every exit code was captured before any pipe, and each gate's own verdict line is quoted rather than a bare $?.

验收备注

Out-of-scope observations from the re-measurement, recorded here and not repaired in this PR:

  • Filed separatelyobject.fields.fields.reference.helpText is stale in all three translated locales, and is not part of this card's frame. The census read it against en = Target object name and reasonably placed it below its threshold; on 2026-09-05 the source grew a normative sentence (a tree field's reference is optional and must name the declaring object), and all three locales still say only "target object, for lookup/master_detail". Fresh drift on a leaf the census only ever judged against the shorter source, so it is a threshold-independent finding rather than a reversal of one.
  • Noted, not filed — three leaves flagged by the mechanical sweep sit in the census's own explicitly-enumerated "18 looser paraphrases … NOT in the count" list: report.fields.blocks.helpText, permission.sections.tab_and_row_level_security.description and agent.sections.capabilities.description. That threshold call is deliberate and reasoned, so it is reported rather than reversed. Worth a maintainer's eye that the last two now add a concept the source omits (custom context variables; tools) rather than merely paraphrasing, and that agent.tools[] is a retired key.
  • Noted, not filedreport.sections.joined_blocks.description is in the card's 26-leaf frame and the card judged it faithful in both locales. On re-reading it drops only the dataset-bound qualifier; the card's judgement is upheld.
  • Noted, not filedobject.sections.fields.description renders each entry as "each row" in both locales. It is in neither the 26-leaf count nor the 18-leaf paraphrase list; the concept survives.

Card numbers appearing above — #14931, #14587, #16165, #11671, #15979 and PRs #16226 / #16190 — are context only and are not addressed here.


Generated by Claude Code

…what their source says

The English source for these nine leaves moved after es-ES and ja-JP were
translated from it. Bundle merge fills gaps only, so a present-but-stale
translation is never refreshed, and the recorded-source-hash table drops a
leaf's entry as soon as a translator writes real text -- which makes every
one of these nine LEGACY-TRUSTED and invisible to the staleness instrument.

Each leaf is rewritten from what the source says now, not from the zh-CN
repair: the two have different causes and their correct target text differs.
`action.fields.body.helpText` adopts the rendering the sibling leaf
`hook.fields.body.helpText` already carries for the same source sentence.

Key sets are unchanged (773 leaves, identical across all four bundles).

Claude-Session: https://claude.ai/code/session_01ADLdAs2pVcH17h9tZKWMBg
Co-authored-by: Claude <noreply@anthropic.com>
…and the changeset

`email_template.fields.variables.helpText` is a JSON shape example — language-neutral,
so the faithful es-ES and ja-JP rendering is the source verbatim. That makes each leaf a
byte copy of its source, which is exactly the condition under which `os i18n extract`
records a source digest, so both tables gain one entry.

Generator output, not a hand-edit: the entry records a TRUE fact (the leaf is a copy of
the CURRENT source), which is the opposite of the false claim the file header warns
against. Its effect is to move this one leaf OUT of LEGACY-TRUSTED and INTO the
staleness instrument's coverage — if the JSON example is ever revised, this leaf now
reports stale instead of silently serving the superseded revision.

Claude-Session: https://claude.ai/code/session_01ADLdAs2pVcH17h9tZKWMBg
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation tooling labels Sep 8, 2026
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/automation/email-templates.mdx (via email_template (symbol, a field of const object esESMetadataForms; a field of const object jaJPMetadataForms))
  • content/docs/concepts/metadata-lifecycle.mdx (via email_template (symbol, a field of const object esESMetadataForms; a field of const object jaJPMetadataForms))

1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17.mdx (via email_template (symbol, a field of const object esESMetadataForms; a field of const object jaJPMetadataForms))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 5 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 216 client-bound route-ledger rows — the other 156 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 156: 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; 100 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 — 2 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 d4401f75bbe2e9e1b6f52f9bad83e377c5c33277packageMentionDocs.

Which tree this was computed on

This run read content/docs from c17e6753bdf050d55d9ef605d8735b51c9e9c554 — the merge of head 2af79e7a7b9bffa32330cb360d860d833d2416fb into base d4401f75bbe2e9e1b6f52f9bad83e377c5c33277, 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 c17e6753bdf050d55d9ef605d8735b51c9e9c554 && git checkout c17e6753bdf050d55d9ef605d8735b51c9e9c554
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d4401f75bbe2e9e1b6f52f9bad83e377c5c33277 2af79e7a7b9bffa32330cb360d860d833d2416fb && git checkout -B drift-repro d4401f75bbe2e9e1b6f52f9bad83e377c5c33277 && git merge --no-ff 2af79e7a7b9bffa32330cb360d860d833d2416fb

node scripts/docs-audit/affected-docs.mjs --json d4401f75bbe2e9e1b6f52f9bad83e377c5c33277

⚠️ 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 d4401f75bbe2e9e1b6f52f9bad83e377c5c33277 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-musk
os-musk marked this pull request as ready for review September 8, 2026 09:30
@os-musk
os-musk enabled auto-merge September 8, 2026 09:30
@os-musk
os-musk added this pull request to the merge queue Sep 8, 2026
Merged via the queue into main with commit acf4d38 Sep 8, 2026
35 checks passed
@os-musk
os-musk deleted the claude/issue-16227-es-ja-source-parity-nine-leaves branch September 8, 2026 09:56
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 size/s tooling

Projects

None yet

2 participants