You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 0e658fb
Browse filesBrowse the repository at this point in the historyBrowse files
fix(platform-objects): decide the object capability toggles and validations en-echoes per leaf, and derive the population from the form (#19597)
Part of #19403
Clause-②: no
Round 8 of the en-echo decision series. The eleven `en` string leaves of
`object.fields.enable.*` and `object.fields.validations.*` — nine labels
and two helpTexts — were byte copies of their English source in `zh-CN`,
`ja-JP` and `es-ES`. They are decided here, one leaf at a time, with the
reason recorded for each, and rendered. **33 locale-leaves of
judgement.**
Regenerated with **`pnpm i18n:extract`**, never by hand.
## The eleven, and what each was decided against
| leaf | `en` | zh-CN | ja-JP | es-ES | the evidence |
|:--|:--|:--|:--|:--|:--|
| `enable.label` | Enable | 启用 | 有効化 | Habilitar | four authored `Enable
X` action labels agree |
| `enable.helpText` | Enable/disable system features | 启用或禁用系统功能 |
システム機能の有効/無効 | Activa/desactiva funciones del sistema | both halves have
twins; "system features" is the section description one line above |
| `enable.trackHistory.label` | Track History | 历史跟踪 | 履歴追跡 |
Seguimiento de historial | ⭐ **exact twin**, identical English,
`object.fields['fields.trackHistory'].label` |
| `enable.searchable.label` | Searchable | 可搜索 | 検索可能 | Buscable | ⭐
**two exact twins**, already agreeing with each other |
| `enable.apiEnabled.label` | Api Enabled | 已启用 API | API 有効 | API
habilitada | ⚠️ the humanize trap, below; `X Enabled` copied from two
authored twins |
| `enable.files.label` | Files | 文件 | ファイル | Archivos |
`sys_attachment.fields.file_id.label` |
| `enable.feeds.label` | Feeds | 动态 | フィード | Publicaciones | ⛔ **no
authored twin — composed, and the row says so** |
| `enable.activities.label` | Activities | 活动 | アクティビティ | Actividades |
the twin is the same feature: "record activity timeline" |
| `enable.clone.label` | Clone | 克隆 | クローン | Clonación | ⛔ **no authored
twin — composed, and the row says so** |
| `validations.label` | Validations | 校验规则 | 検証ルール | Validaciones | one
twin for the stem; the three locales depart on purpose |
| `validations.helpText` | (the long prose) | … | … | … | ADR-0020, read
at the schema; the JSON sample kept byte-identical |
## ⚠️⚠️ The phantom-translation trap, refused in writing
`enable.apiEnabled.label` is `"Api Enabled"`. That is not a considered
English rendering: `objectForm` declares **no label** on that field, so
the string is manufactured by the extractor's `humanizeFieldPath`. Its
correct English is `"API Enabled"`.
⇒ **Fixing the English would differ in bytes, satisfy the echo predicate
in all three locales, drop this card's census by a key, and tell a
`zh-CN` author nothing.** An echo that stops matching is ⛔ not a leaf
that got translated.
It is **not** done here — it would be an edit to
`packages/spec/src/data/object.form.ts`, outside this round's file
surface, and it is a separate act with its own reason that does ⛔ not
discharge the translation. The ledger asserts three things so the
shortcut cannot be taken quietly: the form declares no label, the two
English spellings differ in bytes, and **none of the three renderings is
either of them**. Ablation E performs exactly that "fix" and reds two
assertions.
## ADR-0020 and the validations schema, read before a word was rendered
The worked JSON example is kept **byte-identical in all three locales**
— this catalog's own convention (`({field, operator, value})`,
`{name,type,required,description}` are both kept verbatim elsewhere) and
load-bearing here, because `type: "script"` is a literal member of the
`ValidationRuleSchema` discriminator and `name` must match a snake_case
regex. Every claim the prose makes is asserted at the **live** schema:
- `validations` takes an **array** — the same rule passed as a bare
object is refused.
- the example parses **verbatim** as a union member.
- **ADR-0020 D1**: `state_machine` is a member whose payload is a
required `transitions` **record**; a rendered phrase cannot land in it.
- **ADR-0020 D2**: `type: "workflow"` — the top-level shape that ADR
retired — is **refused**.
- ⚠️ **the near-miss is real and cleared**: the prose spells
`State-machine` while the value is `state_machine`, and the
discriminator refuses the hyphenated, capitalised form ⇒ the prose names
a concept, not a value an author writes, so it is rendered.
And the class-(c) question is answered at the schema rather than
assumed: every capability key takes a **boolean**, so no rendered word
can land in a value, and `ObjectCapabilities` is strict, so a translated
**key** is refused with `unrecognized_keys` rather than silently
stripped.
## ⭐ One schema key, one rendering
`trackHistory` and `searchable` were **already rendered in this very
catalog**, under the identical English string, by an earlier hand —
`object.fields['fields.trackHistory'].label` and
`object.fields['fields.searchable'].label`, with
`field.fields.searchable.label` as a third position agreeing on another
panel. Those words are **copied, not composed**, and the copy is
asserted, so the positions can only move together.
That pair is also this round's sharpest evidence the echoes were fills:
the same English string, the same schema key, one position authored and
the other a byte copy. One leaf cannot be a decision and a fill at once.
Deliberately **untouched**, and asserted so:
`object.sections.capabilities.label` (功能开关 / 機能 / Capacidades — the
feature-toggle heading round 6 left alone) and `hook`/`action`
`.fields.body.capabilities.label` (能力 / ケイパビリティ — round 6's token list,
round 7's third position). `enable.label` is asserted
**distinguishable** from its own section heading in every locale, and
`feeds` from `activities` and from the authored Timeline rendering.
## The population is DERIVED from the FORM, and it sees a two-locale
echo
Round 7 raised the bar to "a shape over a spec-side source that
manufactures the leaves". This round keeps it there with the source that
manufactures these eleven: **`objectForm`, crossed with one predicate
read off its own shape — a section is in when the form ships it
`collapsed: true`** — walked by the same recursion the CLI extractor
uses to emit these keys. Two of four sections qualify. **45 leaves.**
⛔ What the predicate is **not**: an explanation. The open `fields`
section was a wall of en-echoes too, until round 1 of this card decided
it. The open sections are authored because rounds of this card authored
them. The predicate earns its place by deriving and by excluding, not by
a causal story.
Three controls, all in the same walk:
| control | what it excludes / produces | measured |
|:--|:--|--:|
| **OUTWARD** | the 2 open sections, with `fields.placeholder` — a key
#19403's body samples — asserted **out by name**, and already authored,
so a walk that wrongly swept it in would not even go red | 94 leaves,
**0** echoing |
| **INWARD, authored** | `datasource` is **inside** the population and
comes back **non-echoing** in all three locales | 2 leaves |
| ⭐⭐ **INWARD, two-locale** | `lifecycle.*` comes back echoing in
`ja-JP` and `es-ES` while `zh-CN` has all 32 authored — a whole panel
the card's all-three predicate reads as **zero** | **32 leaves**,
carried as a declared deferral |
The deferral is written as a **shape**, never as 64 hand-listed strings,
and the assertion over it is **shrink-only**: nothing outside it may
echo. So round 9 emptying it leaves this file green, while a new row
arriving in a collapsed section reds it on the day it lands.
## The round 9 re-measure this round owed
The claim's `16 keys × 2 locales = 32 locale-leaves` reading **still
holds** on base `1f69917c5`, and the second witness agrees exactly: all
32 are carried in the `ja-JP` and `es-ES` source-hash companions and in
**neither** the `zh-CN` bundle nor its companion.
⚠️ **But it is a `.label` reading, and the full-leaf reading is
double.** `lifecycle.*` has **32 `en` leaves** — 16 labels and 16
helpTexts, every one of them echoing in both locales ⇒ **64
locale-leaves**, not 32. That is this card's own recorded hazard ("the
headline number measures the wrong thing") applying to its own handoff
table.
**The complete remaining decidable population of these catalogs, per
locale, on this head:**
| echoing in | `en` leaves | which |
|:--|--:|:--|
| all three | **6** | `report.sections.dataset_binding` ·
`report.fields.drilldown` · `report.fields.runtimeFilter` (label +
description/helpText each) |
| exactly two (`ja-JP`, `es-ES`) | **33** | `object.fields.lifecycle.*`
(32) · `email_template.fields.variables.helpText` (1) |
| exactly one | **0** | — |
| none | 854 | |
## Measured — every reading re-taken on this base, ⛔ none inherited
Census over `*.metadata-forms.generated.ts`, `.label` leaves against
`en`:
| reading | base `1f69917c5` | head `c49a182d5` |
|:--|--:|--:|
| `en` string leaves / `.label` leaves | 893 / 538 | 893 / 538 |
| label keys echoing in **all three** | **12** ⇒ 36 leaves | **3** ⇒
**9** |
| ⭐ POSITIVE CONTROL — genuinely translated | 526 · 510 · 510 | **535 ·
519 · 519** |
| decidable remainder (every leaf, `helpText` included) | 17 | **6** |
⭐ The headline drops by **9** and the remainder by **11**, because this
round decides nine labels **and** two helpTexts. State which count you
mean.
**Second witness** — the `*.source-hashes.generated.ts` companions,
under the `metadataForms.` prefix the bundles do not carry (a naive
comparison reads 0 for everything):
| locale | echoing → in companion | ⭐ translated → in companion | ⛔
dark: no prefix |
|:--|--:|--:|--:|
| `zh-CN` | 3 → **3** | 535 → **0** | **0** |
| `ja-JP` | 19 → **19** | 519 → **0** | **0** |
| `es-ES` | 19 → **19** | 519 → **0** | **0** |
`pnpm i18n:extract` dropped exactly the **11 rows per locale / 33 in
total** for these leaves and **added none**.
**Add and remove no keys** — the full flattened key sets, base vs head,
on all four bundles:
```
bundle base head added removed values-changed
en 893 893 0 0 0
zh-CN 893 893 0 0 11
ja-JP 893 893 0 0 11
es-ES 893 893 0 0 11
```
⭐ NEGATIVE CONTROL, both directions: fed a set with one key added, the
comparator reports `added 1 / removed 0`; fed one with a key removed,
`added 0 / removed 1`. It can see a one-key delta either way.
⭐ And a second proof the edits are values-only: re-running `pnpm
i18n:extract` after them rewrote all eleven generated files and **only
the three companions changed** — the bundles came back byte-identical to
the hand edits, so no structure was touched.
## The changeset is a `patch`, and `skip-changeset` is measurably wrong
`@objectstack/platform-objects` declares no `private` and ships `files:
["dist", "README.md", "CHANGELOG.md"]`. The renderings measurably reach
those files — probed with every escape spelling **built from the
character itself**, so no shell layer can interpret it:
| subject | raw | `\uXXXX` UPPER | `\uxxxx` lower | `\xNN` UPPER |
`\xnn` lower |
|:--|--:|--:|--:|--:|--:|
| zh `已启用 API` | 0 | **6** | 0 | — | — |
| ja `フィード` | 0 | **6** | 0 | — | — |
| es `Clonación` | 0 | 0 | 0 | **6** | 0 |
| ⭐ es `Publicaciones` (ASCII, **raw negative control**) | **6** | — | —
| — | — |
| ⛔ a string never written (dark control) | **0** | — | — | — | — |
⇒ the ASCII control **fires raw**, so a zero on a raw non-ASCII probe
reads 「escaped」 and ⛔ not 「absent」; and the dark control reads 0, so the
probe can say NO. CJK and kana land as **UPPER-CASE `\uXXXX`**;
U+0080–U+00FF lands as **UPPER-CASE `\xNN`**. ⚠️ And the same character
has two spellings inside one `files[]` set: `克隆` is raw in
`dist/system/index.d.ts` while escaped in `dist/index.js`.
## Ablations — 5 mutations, 10 distinct assertions, two files
Every leg proved the mutation on disk (anchor count + blob hash), and
every restore proved by blob hash **and** a whole-tree `git status
--porcelain`. `ablation-replace.mjs` throughout; never `sed -i`.
| # | file | mutation | assertions reddened |
|:--|:--|:--|--:|
| **A** | `zh-CN.metadata-forms.generated.ts` | re-fill a **decided**
leaf (`enable.clone`) | **3** |
| **B** | `ja-JP.metadata-forms.generated.ts` | re-fill the **inward
control** (`datasource`) — a leaf **no row decides** | **2** |
| **C** | `zh-CN.metadata-forms.generated.ts` | reword the **authored
twin** `fields.trackHistory` | **1** |
| **D** | `packages/spec/src/data/object.form.ts` | flip `collapsed` on
the `advanced` section | **5** |
| **E** | `packages/spec/src/data/object.form.ts` | **take the phantom
shortcut** — add `label: 'API Enabled'` | **2** |
⭐ **C and D/E prove the two legs independent.** Rewording a catalog twin
reds **only** the cross-panel invariant, leaving all eleven decided rows
green. Mutating the spec-side form reds the derivation, the three dark
controls and the third leg, leaving the catalog rows green.
⭐ **D and E are real dist ablations**, because this ledger imports
`@objectstack/spec/data` unaliased and so resolves through `spec/dist` —
unlike the bundle legs, whose subject is a sibling source. Each carried
a **rebuild** and a `ablation-dist-preflight.mjs` run on both legs
(marker present before the verdict, marker `--absent` after the
restore). Stated as a measurement, not an omission.
⛔ **Two disclosures, volunteered:**
1. **D's first marker was `collapsed: false`, which PRE-EXISTS 6 times
in `packages/spec/dist`.** Caught by checking the marker's baseline
occurrence count **before** running, so no reading was taken from it.
Re-authored to a unique `ABLATION_D_ADVANCED_OPEN` token.
2. **E's restore leg was REFUSED by the preflight** — the marker was
spelled `label: "API Enabled"` (the dist spelling) while the source
carries single quotes, so the tool could not see the marker in the dirty
source and reported the tree unrestored. The mutation was
**re-authored** to carry a quote-agnostic token that is identical in
source and dist, ⛔ not re-run until something landed. The label's own
dist spelling was then preflighted separately, present and absent.
## Gates
**60 derived / 60 run / 0 NOT MEASURED / 0 UNRUN**, every one exit
**0**, reconciled with `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --ran` over a record that carries each
command's exit code, captured **before** any pipe.
**One exit 3, discharged rather than waved past**:
`check:dual-build-cjs-loads` refused its own prerequisite (12 packages
with no `dist`). The closure it names was built — `pnpm exec turbo run
build --concurrency=2`, 74/74 tasks — and the gate re-run to exit **0**
(104 require entry points across 67 packages load; 620 emitted CJS files
parse).
Also green, outside the derived set: `pnpm --filter
@objectstack/platform-objects test` (**52 files / 813 tests**), its
`typecheck`, and repo-wide `eslint . --no-inline-config` over **6980
files / 0 messages** — the full population, so ⛔ no narrowing is
claimed.
## Merge duty
`git fetch origin main` then `git merge origin/main`, performed
immediately before the final push: **`Already up to date`**.
`origin/main` is `1f69917c5` both at worktree creation and now, so it
did not move during this round. ⇒ the two-legged proof (base ≠ main
**and** head == main) is **not applicable here and is not claimed** —
leg 1 reads **0 changed paths** over this region, which is the honest
reading rather than a proof that was never needed. `HEAD` vs `main` over
the region is exactly this round's 8 files.
⚠️ **The card's merge hazard re-measured on this base and it still
holds**: `git check-attr merge` reads `unspecified` for
`*.metadata-forms.generated.ts`, while the control
`packages/spec/spec-changes.json` reads `os-regen`. Two branches each
adding keys here would merge exit 0 with one side silently lost. Routing
those paths is an `AGENTS.md` decision and ⛔ not this card's — recorded,
not acted on. No `os-regen-pending` deferral is owed by this branch.
## ⚠️ One in-place repair, declared
`bare-type-display-echo-decisions.test.ts` (round 7) went **red on this
round's progress**: its provenance control carried
`expect(formKeys.length).toBeGreaterThan(10)`, and this round took
`zh-CN`'s metadata-form provenance slice from 12 rows to **6**. That is
a landed assertion this diff makes false, so it is fixed here rather
than filed.
The floor is **removed, not lowered** — it is a reading that expires by
design, because every landing of this card shrinks that slice — and the
leg-3 discrimination is re-seeded off the whole table instead of its
metadata-form slice. Same repair applied in this round's own ledger
before it was written: its discrimination runs over the **objects**
catalog, a population this card does not touch, with a real positive
**and** a real negative in every locale (every echoing objects leaf
carries a row, every authored one carries none).
## Acceptance notes
- **The `*.objects.generated.ts` catalogs carry a far larger echo
population that no census on this card counts.** Measured here as a
by-product of building the provenance control: **362** (`zh-CN`) ·
**383** (`ja-JP`) · **392** (`es-ES`) `en`-echoing leaves, each carrying
a source-hash row, against 1203 · 1182 · 1173 genuinely translated.
`objects.sys_session.fields.last_activity_at.label` is one example met
while hunting twins. This card's population is the metadata-form
catalogs only, and this one has no declared carrier — recorded here
rather than assumed to be someone's.
- **`humanizeFieldPath` is the producer for every unlabelled form
field**, so "Api Enabled" is one instance of a general shape, not a
one-off typo. Nothing here surveys the other unlabelled fields for the
same defect.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01NcPSwnmJHczmTu6FG7NMjE)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
The object editor's Capabilities panel and its `Validations` row no longer read English to a Chinese, Japanese or Spanish author: the eleven string leaves of `object.fields.enable.*` and `object.fields.validations.*` — nine labels and two helpTexts — were decided leaf by leaf and rendered in `zh-CN`, `ja-JP` and `es-ES` (#19403). These sit in the two sections `objectForm` ships collapsed, and until now every one of them was its English source in all three locales.
6
+
7
+
An en-echo is not automatically a defect, so every leaf carries a recorded reason in `object-collapsed-sections-echo-decisions.test.ts`, naming the authored leaf its wording came from — and saying so out loud where no authored twin exists (`feeds` and `clone` have none, and the rows state that rather than leaning on one).
8
+
9
+
-**One schema key, one rendering — and two of them were already rendered.**`object.fields['fields.trackHistory'].label` and `object.fields['fields.searchable'].label` carry the identical English strings one section away and were already 历史跟踪 / 履歴追跡 / Seguimiento de historial and 可搜索 / 検索可能 / Buscable. Those words are copied, not composed, and the copy is asserted, so the positions can only ever move together — `field.fields.searchable.label`, a third position on another panel, included. That pair is also the round's sharpest evidence the echoes were unauthored fills: the same English, the same schema key, one position authored and the other a byte copy.
10
+
-**The phantom-translation shortcut was refused, in writing.**`enable.apiEnabled.label` is `"Api Enabled"` — an extractor humanize, because `objectForm` declares no label there — and its correct English is `"API Enabled"`. Fixing the English would have differed in bytes, satisfied the echo predicate in all three locales and dropped the card's census while telling a `zh-CN` author nothing. It is not done here; the row is decided against the concept, and the ledger asserts that none of the three renderings is either English spelling.
11
+
-**ADR-0020 and the validations schema were read at the schema before a word was rendered.** The helpText's worked JSON example is kept byte-identical in every locale (this catalog's own convention, and load-bearing here because `type: "script"` is a literal member of the `ValidationRuleSchema` discriminator). Asserted against the live schema: `validations` takes an array and refuses a bare object, the example parses verbatim, `state_machine` is a member whose payload is a `transitions` table, and the `workflow` shape ADR-0020 retired is refused — as is `State-machine`, the hyphenated spelling the prose itself uses, which is why the prose is rendered rather than kept. `ADR-0020` and `API` stay verbatim, guarded on word boundaries rather than substrings.
12
+
-**The population is derived from the FORM, not from key names.** The ledger walks `objectForm` crossed with one predicate read off its shape — a section is in when the form ships it `collapsed: true` — by the same recursion the extractor uses to emit these keys. Two of four sections qualify, 45 leaves. A field added to either lands in the population automatically. Three controls run in the same walk: the 94 open-section leaves are excluded (with `fields.placeholder`, a key #19403's body samples, asserted out by name); `datasource` is *inside* the population and comes back non-echoing in all three locales; and the 32 `lifecycle.*` leaves come back echoing in `ja-JP` and `es-ES` only — a panel the card's all-three predicate reads as zero — carried as a declared, shrink-only deferral instead of being silently excluded.
13
+
-**Values only.** No key was added or removed: the full flattened key sets are identical on all four bundles (893 keys, 0 added, 0 removed) with a negative control proving the comparator sees a one-key delta in both directions, and `en` is untouched. Regenerated with `pnpm i18n:extract`, which dropped exactly the 11 provenance rows per locale that recorded these leaves as unauthored extractor fills, and added none.
14
+
15
+
Measured on the metadata-form catalogs: label keys echoing in all three locales fall **12 → 3** while the genuinely-translated control rises **526 → 535** (`zh-CN`) and **510 → 519** (`ja-JP`, `es-ES`), same population (893 string leaves, 538 of them labels), same run. The decidable remainder — every string leaf, `helpText` included — falls **17 → 6**, because this round decides nine labels *and* two helpTexts and only the labels move the headline.
helpText: "Predicado CEL: el campo es obligatorio cuando es TRUE (aplicado en el servidor)"
226
226
},
227
227
enable: {
228
-
label: "Enable",
229
-
helpText: "Enable/disable system features"
228
+
label: "Habilitar",
229
+
helpText: "Activa/desactiva funciones del sistema"
230
230
},
231
231
"enable.trackHistory": {
232
-
label: "Track History"
232
+
label: "Seguimiento de historial"
233
233
},
234
234
"enable.searchable": {
235
-
label: "Searchable"
235
+
label: "Buscable"
236
236
},
237
237
"enable.apiEnabled": {
238
-
label: "Api Enabled"
238
+
label: "API habilitada"
239
239
},
240
240
"enable.files": {
241
-
label: "Files"
241
+
label: "Archivos"
242
242
},
243
243
"enable.feeds": {
244
-
label: "Feeds"
244
+
label: "Publicaciones"
245
245
},
246
246
"enable.activities": {
247
-
label: "Activities"
247
+
label: "Actividades"
248
248
},
249
249
"enable.clone": {
250
-
label: "Clone"
250
+
label: "Clonación"
251
251
},
252
252
validations: {
253
-
label: "Validations",
254
-
helpText: "Object-level validation rules — an array of rule objects, e.g. [{ \"type\": \"script\", \"name\": \"amount_positive\", \"condition\": \"amount > 0\", \"message\": \"Amount must be positive\" }]. State-machine transition tables are declared here too (ADR-0020)"
253
+
label: "Validaciones",
254
+
helpText: "Reglas de validación a nivel de objeto — un array de objetos de regla, p. ej. [{ \"type\": \"script\", \"name\": \"amount_positive\", \"condition\": \"amount > 0\", \"message\": \"Amount must be positive\" }]. Las tablas de transición de máquinas de estado también se declaran aquí (ADR-0020)"
helpText: "Object-level validation rules — an array of rule objects, e.g. [{ \"type\": \"script\", \"name\": \"amount_positive\", \"condition\": \"amount > 0\", \"message\": \"Amount must be positive\" }]. State-machine transition tables are declared here too (ADR-0020)"
253
+
label: "検証ルール",
254
+
helpText: "オブジェクトレベルの検証ルール — ルールオブジェクトの配列。例: [{ \"type\": \"script\", \"name\": \"amount_positive\", \"condition\": \"amount > 0\", \"message\": \"Amount must be positive\" }]。ステートマシンの遷移テーブルもここで宣言します(ADR-0020)"
0 commit comments