fix(platform-objects): decide the report/dataset panel en-echoes per leaf, and pin the decisions as a ledger - #19401
Conversation
… card decided Twelve label leaves and nine helpText leaves at the four keys #19355 names shipped byte-identical to their `en` source in `zh-CN`, `ja-JP` and `es-ES`. Each was judged per leaf; every one is an unauthored extractor fill, not a rendering decision, and the evidence is per key in the PR body. Claude-Session: https://claude.ai/code/session_01NcPSwnmJHczmTu6FG7NMjE Co-authored-by: Claude <noreply@anthropic.com>
…ables `pnpm i18n:extract` — the command this package's extract config documents. It rewrote all eleven bundles; the three translated `metadata-forms` bundles round-tripped byte-identical, and the only change is the 21 provenance rows for the leaves the previous commit translated, which the extractor drops because those leaves are no longer byte copies of their `en` source. Claude-Session: https://claude.ai/code/session_01NcPSwnmJHczmTu6FG7NMjE Co-authored-by: Claude <noreply@anthropic.com>
…s a ledger One row per string leaf of the four keys #19355 names, each carrying the verdict and the reason it was reached. The population is a list because the neighbouring derived pin cannot take these keys: widening its 124 enumerated repeater row properties to all 417 declared form-field paths lands red on 230 echo leaves across 82 keys nobody has decided yet. Claude-Session: https://claude.ai/code/session_01NcPSwnmJHczmTu6FG7NMjE Co-authored-by: Claude <noreply@anthropic.com>
…ted panel leaves Measured rather than assumed: the package is not `private`, `files[]` ships `dist`, and the new strings reach five published entry points there (`index.js`/`index.mjs`, `plugin.js`/`plugin.mjs`, `metadata-translations/index.*`) — the same five a pre-existing translated leaf on the same panel reaches. So the diff publishes and `skip-changeset` would be wrong. Claude-Session: https://claude.ai/code/session_01NcPSwnmJHczmTu6FG7NMjE Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check3 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 3 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 a90fa2f4277b9cd3427152f2c1f257b3d4b138b8 && git checkout a90fa2f4277b9cd3427152f2c1f257b3d4b138b8
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin fade3da23eb4c5ce580bac64e4495832e30bac8d 68049082b9db68def033820be0b7887b373c8205 && git checkout -B drift-repro fade3da23eb4c5ce580bac64e4495832e30bac8d && git merge --no-ff 68049082b9db68def033820be0b7887b373c8205
node scripts/docs-audit/affected-docs.mjs --json fade3da23eb4c5ce580bac64e4495832e30bac8d |
Fixes #19355
Clause-②: no
What this is
The card's deliverable is a per-leaf judgement with its reason recorded, not twelve translations. Every leaf below was decided on its own evidence; the verdicts and their reasons live in a ledger the suite reads (
report-dataset-panel-echo-decisions.test.ts), not only in this body.The reading, re-taken on this base
Re-derived mechanically on the worktree base
3ff6ddf1e, over the four keys the card names, inzh-CN/ja-JP/es-ES:ensource — the card's count is CONFIRMED, key for key and locale for locale.helpTextleaves at the same four keys were also byte copies (report.fields.dataset/values/rows, three locales each). They are the same authored unit as the label they sit on, so a fix that left them English would leave a panel whose field name is Chinese and whose tooltip is not.dataset.fields.measures.helpTextwas already translated in all three locales. That leaf is this change's negative control and is recorded as such.⇒ decided population: 4 keys × 8 string leaves × 3 locales = 24 decisions, of which 21 were echoes and 3 were already authored.
The instrument, with both controls
This package ships its own provenance table,
PACKAGE/src/apps/translations/LOCALE.source-hashes.generated.ts: an entry exists exactly while that leaf is still a byte copy of the source revision.dataset.fields.measures.helpText, the one leaf here a translator had already written, carried none in any of them.After the translations,
pnpm i18n:extractdropped exactly 21 rows (7 keys × 3 locales), 0 added. The table now records these leaves as authored rather than copied.Per-leaf decisions
Every verdict below is
translate— the echo was an unauthored extractor fill. That is a conclusion per leaf, not a policy: the ledger'sechoverdict exists and costs one recorded sentence per locale, and no leaf here earned it.report.fields.dataset(label)blocks.datasetcarries authored text in all three locales — 数据集 / データセット / Conjunto de datos — landed deliberately by #19345. Note this settles the one case triage flagged as possibly legitimate (Datasetines-ES): this catalog's own authored answer for that word, in that locale, is "Conjunto de datos".report.fields.dataset(helpText)type,columns,order,chart) were authored.report.fields.values(label)blocks.valueson the same panel is authored 度量 / メジャー / Medidas.report.fields.values(helpText)order.helpTextnames the same two vocabularies (rows/columns dimensions, values measures) in authored text in every locale — the vocabulary existed; this leaf never got it.report.fields.rows(label)blocks.rowsis authored 行 / 行 / Filas, and the adjacent top-levelcolumnsis authored 列 / 列 / Columnas on the same panel.report.fields.rows(helpText)columns.helpText— the same sentence for the other axis — is authored in all three locales.dataset.fields.measures(label)helpTextin all three locales while itslabelwas a byte copy. One leaf cannot be a decision and a fill at once.dataset.fields.measures(helpText)The shared shape of the first seven: a translator who judged the English word right at the top level would not have written the translated one a few entries below it, in the same catalog, on the same panel, for the same concept.
The pin question, answered
The derived pin is not widened. A listed ledger is added instead.
repeater-row-properties.test.tsderives its population from the forms, and that derivation is the reason it is good: a repeater enumerated tomorrow is red the day it lands. Measured on this base:A pin that lands red is not a pin, and widening it would also retro-scope #17508's carrier ruling. So the derived population stays as #17508 drew it, and the DECIDED leaves are enumerated in a new file whose header states the split. This is the ledger row the card asks for rather than prose: a future deliberate echo is a
verdict: 'echo'row that must carry its own per-locale reason, and the suite refuses one without it.Each row is also pinned to the
ensource it was decided against, so a reworded source reds the file instead of leaving a decision standing over text nobody judged.Reverse verification
Both mutations ran through
scripts/ablation-replace.mjs(anchor must hit; on-disk blob before/after; restore proven equal to theHEADblob withgit diff HEADempty). Baseline first: 7 passed.ensource (zh-CNreport.fields.values.label): blob34c532953acfto3f7269b0789b, run 1 failed / 6 passed,AssertionError: zh-CN report.fields.values.label reads its en source again. Restored, blob back to34c532953acf.ensource a ledger row is pinned to (ValuestoValue): blobb8a246889186to689bf01a679d, run 2 failed / 5 passed — the source pin AND the dark control (expected 7 to be 8), so the control is live too. Restored, blob back tob8a246889186.An earlier attempt at A is reported as a no-op rather than quietly re-run: the mutation and its restore trap sat in one shell that exited between them, so the file was already restored when the suite ran and the green it printed measured nothing.
The generated-bundle hazard the claim flagged
These bundles are generated and
git check-attr mergereadsunspecifiedfor all of them (control:packages/spec/spec-changes.jsonreadsos-regen), so two branches adding i18n keys would merge clean and drop one side. Nothing collides here — this change adds and removes no keys at all, only leaf values — and the bundles were regenerated with the repo's own tooling, never by hand:It rewrote all eleven bundles; the three translated
metadata-formsbundles round-tripped byte-identical to the hand-edited values, which is itself the evidence that the edits are exactly what the extractor's merge baseline produces.Changeset, measured
@objectstack/platform-objectsis notprivate,publishConfig.accessispublic, andfiles[]shipsdist. Built, then greppeddistfor a new string with a pre-existing translated leaf on the same panel as the positive control: both reach the same five published entry points (index.js,index.mjs,plugin.js,plugin.mjs,metadata-translations/index.*). A test-only string reaches none of them. ⇒ the diff publishes,skip-changesetwould be wrong, and apatchchangeset is included.Verification
Head
68049082b.pnpm --filter @objectstack/platform-objects test— 43 files, 599 tests passed (was 43/592 with the new file's 7 tests added; the translations directory alone goes 9 files/108 tests to 10/115).pnpm --filter @objectstack/platform-objects typecheck— green, includingcheck:test-typecheck. The new test file is intsconfig.test.json's program (counted with--listFiles) and, by design, not in the main one.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack— 59 derived, 59 run, 0 NOT-MEASURED, every one recorded with its exit code. Includescheck:i18n,check:i18n-stale-fill,check:nul-bytes,check:published-files,check:type-check-coverage,check:type-check-debt,check:cross-package-test-inputs,check:test-source-alias.check:i18n-coverage(13 configs, 621 baselined, none new) andcheck:i18n-walk-parity— both green.pnpm lint— the whole repo, exit 0, no output.check:dual-build-cjs-loadsansweredPREREQUISITE NOT MET(exit 3) on its first run because twelve packages had nodistyet. That is not a red and was not recorded as one; once the workspace was fully built it ran for real and passed (104 entry points across 67 packages).Acceptance notes
Noted, not filed — observations, no defect named:
datasetpanel's ownlabel,descriptionand all foursectionsare stillenechoes in all three locales, as are seven further top-leveldataset.fields.*keys. Same class as this card, outside its four keys.report.sections.dataset_binding(label and description) andreport.fields.drilldown/runtimeFilterare echoes on the panel this change touches.To file (reproducible defect, class (a)) — handed to the seat, not filed by this PR:
3ff6ddf1eover all 417 declared form-field paths; 0 of them are insiderepeater-row-properties.test.ts's 124-property population, so no pin sees them. A user in a translated locale reads English on those keys. It is the same class as this card and explicitly not mechanical — each key needs the same per-leaf judgement — so it is not taken in-place here. Dedupe words:en echo translated locale·metadata form field label untranslated·dataset.fields.dimensions object.fields.enable page.fields.interfaceConfig·declared form field echo census·translated leaf copy of source.Generated by Claude Code