Skip to content

fix(platform-objects): re-translate the metadata-form leaves that contradict their current en source - #20707

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-20666-stale-metadata-forms-leaves
Sep 29, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-20666-stale-metadata-forms-leaves

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #20666

Clause-②: no

What changed

A translated leaf in packages/platform-objects/src/apps/translations/{ja-JP,es-ES,zh-CN}.metadata-forms.generated.ts that a translator wrote by hand keeps its value when its en source changes later. This PR measures that set first, with PR #20652's instrument and the two widening checks the services seat named, then re-translates the leaves whose meaning now contradicts the current en: 12 leaves (ja-JP 4, es-ES 4, zh-CN 4).

  • Values only. No key is added or dropped, no en file is edited, and no provenance companion or echo-decision ledger changes (measured below; the tooling requires none).
  • .changeset/20666-stale-authored-metadata-form-leaves.md: @objectstack/platform-objects patch.
  • No new gate, per triage. No test pins any of the old or new strings.
  • File surface held: the three translated metadata-forms bundles and the changeset. Not the object bundles, not the plugin bundles, and not {en,ja-JP,es-ES,zh-CN}.ts or *.source-hashes.ts (PR fix(platform-objects,plugin-audit): nav entries for the console's Audit Log and Integrations & APIs pages #20699).

The measurement (base a8acee28d, before any edit)

Instruments

The clone is not shallow (git rev-parse --is-shallow-repository answers false, 15122 commits), so every walk below reads full history.

  1. Since last edit (PR fix(platform-objects): re-translate the object leaves that contradict their current en source #20652's condensed instrument, ported with two edits: the bundle kind objects.generated.ts becomes metadata-forms.generated.ts, and the recorded-copy test reads "metadataForms.PATH" in both LOCALE.source-hashes.generated.ts and LOCALE.source-hashes.ts). Population: every string leaf of the locale's metadata-forms bundle, minus echoes of the current en and recorded byte copies. Last edit: the first-parent commit where the parsed value last changed. A leaf is a candidate when en at that commit differs from en today. Meaning is then judged by hand.
  2. Renamed key. A leaf whose path was born at its last edit, while the same value sat under a path (any path in the bundle, not only a sibling) that disappeared in that commit. For each, en at the old path's last edit is compared with en at the new path today.
  3. Penultimate edit. For each non-candidate, en at its previous edit, and whether en moved in the last-edit commit itself. Every flag was read by hand, including the ones where en moved in the same commit.
  4. Merge side. For a leaf last edited by a merge commit, en is also read at the second parent.
  5. Retired term. The locale's word for project, department, role (ADR-0090) or profile (ADR-0090 D2), where en at the same path does not carry the English term. The raw ja-JP role pattern hit four leaves, all false positives (ロールアップ roll-up three times, コントロール control once); the refined pattern excludes both and hits 0.
  6. Retired concept (added here; the concept-level twin of PR fix(platform-objects): re-translate the object leaves that contradict their current en source #20652's retired-term scan). For each re-model the contradicting candidates surfaced, the locale's words for the retired concept, flagged where en at the same path does not carry it: agent tools (ADR-0109 removed agent.tools[], scope agent.*); PermissionSet.contextVariables (removed by the ADR-0105 commit 879ea1304); sharing rules named for RLS; an email template sent by id with a content type (47a92f427 re-modelled the type); a report block that joins objects (ADR-0021 dataset-bound reports, 18178454c, scope report.*).
  7. Carve-in continuity. The zh-CN leaves last edited at e0077ea36 were carried in from a hand overlay, packages/platform-objects/src/metadata-translations/zh-CN.ts, born at dc721729a and consolidated at e0077ea36 the same day. So the instrument dates them at the carve-in. Between those two commits packages/spec/src changed no form label, description or help string (git diff dc721729a e0077ea36 -- packages/spec/src: two changed label / description lines, both schema declarations, no string). The carve-in dating therefore hides no source move inside the overlay's lifetime.

Known-positive controls

  • permission.sections.tab_and_row_level_security.description (line 2251 of all four bundles) and agent.sections.capabilities.description (line 2347). The since-last-edit instrument flags both in all three locales. In ja-JP and es-ES both promise what en dropped (custom context variables; tools). zh-CN's agent leaf promises tools too, but zh-CN's permission leaf never promised context variables: it names the section after sharing rules, read below.
  • Renamed-key check: dashboard.fields.refreshIntervalSeconds.label, renamed from refreshInterval at e9fcd6bbd with the value carried. It fires in all three locales.
  • Retired-concept scan: it fires on every one of the four contradicting paths in ja-JP and es-ES, and on the three in zh-CN.
  • Provenance (for H2): with ja-JP agent.sections.capabilities.description set to the en string, check-i18n-bundles --filter=platform-objects goes red, platform-objects DRIFTED (1) (through scripts/ablation-replace.mjs: anchor 1 to 0, blob 1b912e07c279 to 70cb7a0e1ba3, restored: blob equals HEAD and git diff HEAD is empty). In hold mode, --write adds exactly one row, "metadataForms.agent.sections.capabilities.description", to ja-JP.source-hashes.generated.ts. Both files were restored with git checkout HEAD --, and their blobs equal HEAD's (1b912e07c279, 2429910c839c).

Population and radius

  • Covered: the metadata-forms bundles of @objectstack/platform-objects for ja-JP, es-ES and zh-CN. There are 1116 leaves per locale. Echoes of the current en number 0 / 0 / 0, recorded byte copies 0, and authored leaves 1116 / 1116 / 1116. History: every first-parent commit since the bundles were created at ae2da1e7d (69 / 70 / 70 commits touching each locale file, 56 for en), plus the zh-CN overlay's lifetime (instrument 7).
  • Not covered: a leaf that was wrong when it was authored and whose en never moved, outside the vocabularies of instruments 5 and 6; where the zh-CN overlay's strings came from before dc721729a (the file was born with them); the object bundles, the plugin bundles and the other packages' bundle sets.

Counts per locale

ja-JP es-ES zh-CN
authored leaves 1116 1116 1116
candidates (en moved since last edit), before 9 9 6
of which contradicting, before 4 4 3
penultimate-edit flags, before (contradicting) 18 (0) 18 (0) 22 (0)
renamed-key flags, before (contradicting) 1 (0) 1 (0) 1 (0)
merge-side flags, before 0 0 0
retired-term flags, before 0 0 0
retired-concept flags outside the candidates, before (contradicting) 0 0 1 (1)
re-translated here 4 4 4
candidates after (at aaee46778) 5 5 3
contradicting after 0 0 0
retired-concept flags after 0 0 0

The raw candidate counts equal the #20653 dev's 9 / 9 / 6 exactly. After, both walks re-run at aaee46778: each after-candidate set is exactly the before set minus the re-translated paths (0 added), the echo count is still 0 (no new value equals its en leaf), and the renamed-key flag is the same one. The penultimate-edit flags after are the before set plus the 11 re-translated candidates (their last edit is now this PR, after en moved), which is the expected shape.

The judgment rule

As in PR #20652 and PR #20684. A leaf contradicts the current en when a reader who acts on it would believe something about the current form that en now says is false: en now denies what the leaf asserts; the type was re-modelled, so the leaf describes a different thing; or the leaf names a mechanism the section does not hold. Added detail, narrowing, rewording, punctuation and title-casing are not contradictions.

Re-translated (12): before and after

agent.sections.capabilities.description, all three. ADR-0109 removed agent.tools[] (e2616e0cf); an agent's tools come from its skills. en: Skills and knowledge sources the agent can use.

  • ja-JP: 「エージェントが使用できるスキル、ツール、ナレッジソース。」 → 「エージェントが使用できるスキルとナレッジソース。」
  • es-ES: 「Skills, herramientas y fuentes de conocimiento que puede usar el agente.」 → 「Skills y fuentes de conocimiento que puede usar el agente.」
  • zh-CN: 「代理可使用的技能、工具与知识来源」 → 「代理可使用的技能与知识来源」

permission.sections.tab_and_row_level_security.description, all three. 879ea1304 removed PermissionSet.contextVariables (enforce-or-remove, zero consumers) and its form row. en: Tab visibility and RLS policies.

  • ja-JP: 「タブ表示、RLS ポリシー、述語評価用カスタムコンテキスト変数。」 → 「タブ表示と RLS ポリシー。」
  • es-ES: 「Visibilidad de pestañas, políticas RLS y variables de contexto personalizadas para evaluar predicados.」 → 「Visibilidad de pestañas y políticas RLS.」
  • zh-CN: 「导航可见性与共享规则」 → 「标签页可见性与行级安全策略」. The old value named the section after sharing rules. In this repository 共享规则 is the zh-CN name of a different mechanism, the sys_sharing_rule object (plugin-sharing) and its own Setup entry nav_sharing_rules. The section holds tabPermissions and rowLevelSecurity. The new value uses the bundle's own words: 标签页 (this section's label), 行级安全策略 (the bundle's rendering of an RLS policy, as in object.fields["access.default"].helpText).

report.fields.blocks.helpText, all three. The 9.0 single-form cutover (ADR-0021, 18178454c) made every report block a dataset-bound sub-report; no block joins objects. en: Dataset-bound sub-reports (joined report only)

  • ja-JP: 「複数オブジェクトを結合(joined レポートのみ)」 → 「データセットにバインドされたサブレポート(joined レポートのみ)」
  • es-ES: 「Une varios objetos (solo informe joined)」 → 「Subinformes vinculados a un conjunto de datos (solo informe joined)」
  • zh-CN: 「joined 报表的联合查询块」 → 「绑定数据集的子报表(仅 joined 报表)」

email_template.sections.identity.description, ja-JP and es-ES. 47a92f427 promoted email_template to a first-class metadata type and rewrote its form. The Identity section now holds name / label / category / locale / description, with no id and no content type, and IEmailService.sendTemplate resolves the template by name. A reader who copied the old sample would pass template: id. zh-CN already said what en says. en: Template identifier resolved by IEmailService.sendTemplate({ template: name, locale, ... }).

  • ja-JP: 「識別子とコンテンツ型。id は sendTemplate({ template: id, ... }) で参照される。」 → 「IEmailService.sendTemplate({ template: name, locale, ... }) が解決するテンプレート識別子。」
  • es-ES: 「Identificador y tipo de contenido. El id se referencia con sendTemplate({ template: id, ... }).」 → 「Identificador de plantilla que resuelve IEmailService.sendTemplate({ template: name, locale, ... }).」

report.sections.joined_blocks.label, zh-CN only. It was found by the retired-concept scan, not by the since-last-edit instrument, because en (Joined blocks) never moved. The zh-CN label said 关联对象 ("related objects"), the pre-9.0 model in which blocks joined objects. The section holds the blocks repeater of dataset-bound sub-reports. ja-JP 結合ブロック and es-ES Bloques unidos render en literally and are unchanged.

  • zh-CN: 「关联对象」 → 「joined 报表分块」 (分块 is report.fields.blocks.label, and joined stays the report-type token, as in 仅 joined 报表).

Stale but not contradicting (listed, left as written)

path locales what en did
object.sections.fields.description all three "each row becomes a column" became "each entry becomes a column" (9f8ec35c8). Rewording.
hook.fields.condition.helpText all three "Optional formula — skip the hook when this evaluates to false" became "CEL predicate — the hook runs only when TRUE" (48efe915b, which changed the row's editor language from javascript to expression). The runtime gate is if (!conditionFn(ctx)) in packages/objectql/src/hook-wrappers.ts, whose own docblock says "skip when the formula evaluates FALSE", and the leaf makes no JavaScript claim. This is the closest call among the leaves left alone.
app.fields.defaultAgent.helpText ja-JP, es-ES "AI agent" became "Platform agent", with the ask / build defaults added. Narrowing and detail. zh-CN already says what en says.
report.sections.joined_blocks.description ja-JP, es-ES "blocks joined into a single report" became "dataset-bound blocks stacked into a single report". Combined into one report is still true, and the en label still says Joined blocks. The second closest call. zh-CN already says what en says.
report.sections.filter_and_chart.description all three "Report-level filters" became "Render-time scope filter". The filter is still the report's.
dashboard.fields.refreshIntervalSeconds.label (renamed key) all three The rename added the unit to the label. Each locale's helpText on the same field already says seconds (自動更新(秒), Actualización automática (segundos), 自动刷新间隔(秒)).

Penultimate-edit flags, all read by hand. Every one already says what en says:

  • en moved EARLIER than the last edit: object.fields.isSystem.helpText, object.fields.fields.reference.helpText, object.fields.fields.trackHistory.helpText, view.fields.filter.helpText, page.fields.type.helpText, dashboard.fields.gap.helpText, action.sections.advanced.description, action.fields.body.helpText, report.sections.basics.description, report.fields.columns.helpText and dataset.fields.measures.helpText (all three locales); plus, zh-CN only, app.fields.defaultAgent.helpText, report.sections.joined_blocks.description, email_template.sections.identity.description and email_template.fields.variables.helpText.
  • en moved IN the last-edit commit: object.fields.indexes.fields.helpText, field.fields.precision.helpText, field.fields.maskingRule.helpText, page.fields.variables.source.label, app.sections.content.description, app.sections.access_and_sharing.description and permission.sections.identity.description (all three locales).

Dispatch hypotheses, measured

  • H1 held, and widened. The targets are exactly the three translated bundles against en.metadata-forms.generated.ts. Both known leaves are candidates in all three locales, and the raw counts reproduce as 9 / 9 / 6 at a8acee28d. Judged, 4 / 4 / 3 contradict. Two more paths besides the known two contradict: report.fields.blocks.helpText (all three) and email_template.sections.identity.description (ja-JP, es-ES). zh-CN's permission leaf contradicts for a different reason than the known one (sharing rules). The retired-concept scan added one zh-CN label.
  • H2 held: values only. The companions work as they do for the object bundles. collectFilledFromHashes records a metadataForms leaf only while it is a byte copy of the current en or of a recorded digest. Today that is 0 rows in all three locales, and a hand translation records nothing. At aaee46778, node scripts/check-i18n-bundles.mjs --write --filter=platform-objects printed regenerated and rewrote all eleven bundle and companion files on disk (every mtime moved), and git status --porcelain stayed empty. The control that shows the tool really reads these leaves is in "Known-positive controls" above. No echo-decision ledger row and no test names any of the 12 paths (git grep over the ledgers). metadata-forms-vocabulary.test.ts asserts group keys only.
  • H3 held: both checks ran, and their hits are reported separately. Renamed key: 1 / 1 / 1 hit (dashboard.fields.refreshIntervalSeconds.label), stale but not contradicting. Penultimate edit: 18 / 18 / 22 flags, 0 contradicting.

Verification (all at aaee46778)

  • Build: turbo run build --filter=@objectstack/cli... --filter=@objectstack/platform-objects... --concurrency=2: 59/59 tasks, VERDICT command-exit 0.
  • pnpm --filter @objectstack/platform-objects test: 58 files, 942 tests passed. pnpm --filter @objectstack/platform-objects typecheck: exit 0, check:test-typecheck: OK.
  • The new values ship: the built dist/metadata-translations/index.mjs and index.js were imported, and MetadataFormsTranslations was read by path after withSourceFallback. In both, 12 of 12 re-translated rows equal the HEAD source and differ from the base, and 9 of 9 unchanged-leaf controls equal both.
  • Gates: node scripts/pm/dispatch-gates.mjs --commands (no paths) derived 55 commands from the real diff, and all 55 exit 0. --ran printed "55 derived famil(ies) accounted for — 55 run, 0 NOT-MEASURED (a DERIVED zero — all 55 recorded an exit code and none of them is 3)". pnpm check:dual-build-cjs-loads first refused with exit 3 (PREREQUISITE NOT MET: nine packages outside this diff had no dist/). Those were built and the gate re-ran: exit 0.
  • Named in the verdicts: check:i18n "OK (9 package(s) — all bundles in sync, no undeclared authoring keys)"; check:i18n-stale-fill "OK (10 bundle set(s) — no new stale fills, 0 baselined)"; check:nul-bytes exit 0.
  • Lint, narrowed to the three edited bundles: eslint --no-inline-config --format json read 3 files, 0 errors, 0 warnings. ESLint#isPathIgnored answers false for all three, read from the repo's own config. The config enables no type-aware linting (no parserOptions.project or projectService, eslint.config.mjs states it at line 328), so a change to string values in these files cannot move the verdict on any other file. The full pnpm lint is left to CI.
  • origin/main moved two commits past the base (defc7f7b5). Neither touches a form declaration or a translation bundle, so the measurement stands on it.

Acceptance notes

  • dataset-panel-echo-decisions.test.ts, the reason on the include label row, cites "report.sections.joined_blocks.label is 关联对象" as a precedent for the word 关联. After this PR that label reads joined 报表分块, so the citation is out of date. It is prose: no assertion reads it, and the include decision stands on its own, since 关联 keeps authored precedents such as sys_email.fields.related_object.label 关联对象 in the objects bundle. The ledger is outside this card's file surface and is not edited here.
  • zh-CN page.sections.data_context.description reads 关联对象与变量 ("related objects and variables") against en "Record binding and page-local state." It is loose but does not contradict: the bound record's object is the related object, and the variables are the page-local state. No instrument flags it, and it is left as written.
  • The since-last-edit instrument cannot see a leaf that was wrong when it was authored while en never moved. Instruments 5 and 6 cover only the vocabularies they name. Instruments were run from the session scratchpad.

Generated by Claude Code

…tradict their current en source

Twelve authored ja-JP / es-ES / zh-CN leaves in the metadata-forms bundles
said what a superseded English source said: the agent Capabilities section
still offered tools, the permission-set Tab & Row-Level Security section
still offered custom context variables (zh-CN named it after sharing
rules), the report blocks help still described a join of several objects,
the email-template Identity section still sent the template by id with a
content type, and the zh-CN Joined blocks label named related objects.
Values only; no key added or dropped, no en file or provenance table
edited.

Claude-Session: https://claude.ai/code/session_01DEvba2nBuD4tWzfq8r8NFY
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/platform-objects, touching 1 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))
What this run could not see
  • 3 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 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.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 3 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 defc7f7b504e22b9e2c12a5374ebb255efe9b7da → packageMentionDocs.

Which tree this was computed on

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

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

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

@github-actions github-actions Bot added documentation Improvements or additions to documentation tooling labels Sep 29, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: aaee46778305b33d8afaf19c8243ccc930d04840
Local-runs: none

Read: card #20666 (body and all four comments: triage 5893971675, the widening-check note 5894872722, the claim 5896670403, the os-dev-report 5897252407), PR #20707 (body, file list, git diff origin/main...refs/review/20707 at merge-base a8acee28d), the head's check-runs, and en.metadata-forms.generated.ts on origin/main (735594bea9; untouched since the merge-base, so it equals the en the PR was judged against). The head is still aaee46778.

Check-runs on the head, read once, not polled: 31 runs, 0 failed. Completed 19: 16 success (Auto Label, Dogfood Verify CLI, Build Core, Type Check debt ledger, Governed Surface Queue Guard, single-writer path, Type Check consumer gates, Flag docs, Type Check source gates, filter, card-claims-branch, single-issue, Check PR Size, Part-of, Check Changeset, Check Documentation Links), 3 skipped (Console Pin Gate, Build Docs, Packed-tarball smoke, all opt-in or path-gated). Still in_progress 12: Test Core 1-6, Dogfood Regression Gate 1-3, Temporal Conformance, Type Check workspace, Lint and Repo Gates (the run that carries check:i18n and check:i18n-stale-fill). Every completed gate is green; the landing still waits for the twelve, as the queue rule already requires.

① Derived judgments

Accept-set: no change — right. The diff touches no schema, no form declaration and no en file: four files, .changeset/20666-stale-authored-metadata-form-leaves.md (new) and the ja-JP / es-ES / zh-CN metadata-forms.generated.ts bundles, 4 value lines each. Key skeleton of each translated bundle is identical to en at the head (2516 lines, 1116 string leaves per locale, same key sequence), so it is values only: no key added or dropped. No source-hash companion changes, and none is owed: all six *.source-hashes*.ts files carry 0 metadataForms. rows at the head, and packages/cli/src/utils/i18n-extract.ts states that collectFilledFromHashes records generated (echoed) leaves, not authored ones. The PR body's ablation (setting one leaf to its en string reddens check-i18n-bundles, --write on the unmutated tree leaves git status empty) is the dev's measurement and was not re-run here; the structural facts above are consistent with it.

Public surface: 12 customer-visible runtime strings in a released package — right to publish under a changeset. @objectstack/platform-objects exports the three bundles; the console renders them. Each re-translated value, judged against the current en value of the same key:

  • agent.sections.capabilities.description, en "Skills and knowledge sources the agent can use." — ja-JP, es-ES, zh-CN now say exactly that; the dropped word is tools. agent.form.ts section capabilities holds skills only (ADR-0109 removed agent.tools[] at e2616e0cf, resolved above). Right ×3.
  • permission.sections.tab_and_row_level_security.description, en "Tab visibility and RLS policies." — ja-JP and es-ES drop the custom context variables (PermissionSet.contextVariables gone at 879ea1304; permission.form.ts section holds tabPermissions and rowLevelSecurity only). zh-CN's old value named the section after sharing rules, which in this repo is sys_sharing_rule / nav_sharing_rules (共享规则 in zh-CN.ts and the plugin-sharing bundle), a different mechanism; the new 标签页可见性与行级安全策略 uses the section's own label word 标签页 and the bundle's rendering of RLS (object.fields["access.default"].helpText). Right ×3.
  • report.fields.blocks.helpText, en "Dataset-bound sub-reports (joined report only)" — all three now render that; the old values described a join of several objects (pre-ADR-0021, 18178454c; report.form.ts declares the repeater with this helpText). Right ×3.
  • email_template.sections.identity.description, en "Template identifier resolved by IEmailService.sendTemplate({ template: name, locale, ... })." — ja-JP and es-ES now render that; the old values sent the template by id with a content type, neither of which the Identity section holds (email-template.form.ts: name, label, category, locale, description). zh-CN already said it and is unchanged. Right ×2.
  • zh-CN report.sections.joined_blocks.label, en "Joined blocks" — old 关联对象 (related objects) named the retired object-join model over a section whose own zh-CN description already says dataset blocks, so a reader believed the section configured related objects: a contradiction of the current en, even though en never moved. New joined 报表分块 says what en says; its one extra token 报表 restates the section's own scope (visibleWhen data.type == 'joined', and the sibling values' 仅 joined 报表) and claims nothing en does not. Right, noted as the least literal of the twelve.

No new value byte-equals its en leaf (no echo introduced). No test or ledger asserts any of the old or new strings: git grep at the head finds the old strings nowhere outside the three bundles except 关联对象 in driver CHANGELOG prose and in the reason prose of one dataset-panel-echo-decisions.test.ts row (line 309), which no assertion reads (the ledger asserts row count, keys and verdicts). metadata-forms-vocabulary.test.ts asserts group keys only.

Stale-but-not-contradicting leaves left as written — right on each. Read against en in all locales: object.sections.fields.description (row vs entry: rewording), hook.fields.condition.helpText (translations say optional formula, skip when false; en says CEL predicate, runs only when TRUE — the same gate, hook-wrappers.ts line 440 if (!conditionFn(ctx)), whose own docblock at line 239 calls it a formula; the schema is .optional(); no JavaScript claim survives), app.fields.defaultAgent.helpText ja/es (AI agent vs Platform agent: narrowing), report.sections.joined_blocks.description ja/es (joined into vs stacked into), report.sections.filter_and_chart.description (report-level vs render-time scope), dashboard.fields.refreshIntervalSeconds.label (unit missing from the label, present in each locale's helpText). Five of the penultimate-edit flags (object.fields.isSystem.helpText, view.fields.filter.helpText, app.sections.access_and_sharing.description, permission.sections.identity.description, zh-CN email_template.fields.variables.helpText) spot-read: each already says what en says. No re-translation in the diff rests on a title-case-only change, per the ruling.

Ruling compliance: values only ✓; only contradicting leaves ✓; title-case ruling honoured ✓; no new gate (no script, test or workflow in the file list) ✓; file surface as claimed (5896670403) ✓: not the object bundles, not the plugin bundles, not {en,ja-JP,es-ES,zh-CN}.ts or *.source-hashes.ts.

PR body, factual sentences: line numbers (2251, 2347, 1856, 2151, 1801) true; 12 = 4 + 4 + 4 true; 1116 leaves per locale true; 69 / 70 / 70 first-parent commits per translated bundle before this PR and 56 for en true (70 / 71 / 71 at the head, the PR commit included); every cited sha resolves to the commit described (e2616e0cf, 879ea1304, 47a92f427, 18178454c, e9fcd6bbd, 9f8ec35c8, 48efe915b, ae2da1e7d, dc721729a, e0077ea36, f4ce10c89); the retired-term 0 reproduces by reading (every ja-JP ロール hit sits under an en path that carries role: ARIA roles, the persona Role label, role names; zh-CN and es-ES have no hit whose en lacks the term). "origin/main moved two commits past the base" was true when written; a third commit, 735594bea9, docs-only under content/docs/**, has landed since, and none of the three touches a *.form.ts, a translation bundle or a companion (the two spec edits are zod .describe() strings on flow and http-node maps that the metadata-forms bundle does not carry), so the conclusion still holds. The build, test, gate and ablation counts are the dev's measurements, consistent with everything read and not reproduced here (read-only).

② Semver level

@objectstack/platform-objects: patch, Clause-②: no, no arm — right. The diff publishes changed runtime strings from a released package and nothing else: no accept-set change (so not yes), no removal or rename of anything authorable (so not (narrowing)), a bug fix (so patch, not skip-changeset). Clause-②: no appears in the changeset body and in the PR body, and they agree. The package sits in the changeset fixed group; a patch on it is the ordinary shape. No model identifier in the changeset, the PR title, the PR body or the commit; the commit ends with the model-free trailer pair AGENTS.md requires. Every factual sentence of the changeset is true against the diff and the forms: the hand-kept-leaf mechanism (stated in the en bundle's own header), the two known leaves and the keys that were removed, the twelve-leaf list and its per-locale split, the name-resolved template with no content type, the zh-CN section label, "values only: no key is added or removed, and no provenance table changes". Check Changeset is green on the head.

③ Boundary flags

open_questions is empty. Dev flags from the os-dev-report, each answered:

  1. Instruments beyond the three named (merge side, retired term, retired concept, carve-in continuity) — accepted. The ruling bounds what may be re-translated (only leaves whose meaning contradicts en, values only), not what may be read while measuring; the added scans are read-only, add no gate and touched no file outside the surface. Their zero results are consistent with what was re-read here (retired-term 0 reproduced).
  2. zh-CN report.sections.joined_blocks.label, found only by the retired-concept scan — accepted as within the ruling: the leaf's meaning contradicts the current en (judged in ① above), which is the ruling's criterion; the ruling does not require en to have moved. The dev listed it separately and the changeset names it. Not escalated.
  3. Stale-but-not-contradicting leaves left as written — accepted, each judged in ①. The two closest calls (hook.fields.condition.helpText, report.sections.joined_blocks.description) stay on the right side of the contradiction line; re-translating them would exceed "only the leaves whose meaning now contradicts en".
  4. origin/main not merged — accepted. Merge-base a8acee28d; main is now three commits ahead, none touching a form declaration, a bundle or a companion; the PR is mergeable per the API and the measurement stands on the current en.
  5. Three out-of-scope notes, carrier none — accepted as notes, none blocks: (a) the dataset-panel-echo-decisions.test.ts reason prose now cites a superseded zh-CN label; verified prose-only, no assertion reads it, and the file is outside the card's surface — a later hygiene edit to that ledger, not this PR; (b) zh-CN page.sections.data_context.description 关联对象与变量 over a section holding object (bound object) and variables: loose, not contradicting; (c) the instrument's blind spot (wrong when authored, en never moved) is inherent and already recorded on the family's sibling PR.
  6. Attribution deviation (model-free commit trailer pair; session-URL PR footer) — correct per AGENTS.md, which outranks the harness reminder; not a deviation to repair.
  7. Worktree removed after the PR opened — housekeeping; the pushed head equals the reviewed head.

Nothing escalated. No FAIL item.

Implemented-by: claude/issue-20666-stale-metadata-forms-leaves
Reviewed-by: session_01DEvba2nBuD4tWzfq8r8NFY

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 29, 2026 19:48
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 29, 2026
Merged via the queue into main with commit 5757463 Sep 29, 2026
36 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20666-stale-metadata-forms-leaves branch September 29, 2026 20:08
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

Development

Successfully merging this pull request may close these issues.

i18n(platform-objects): authored ja-JP / es-ES / zh-CN metadata-forms leaves contradict their moved en source (the metadata-forms half of #20653)

2 participants