skills(i18n): validation messages are a translation group since #14253 — correct the claim and its neighbours (net +7 tokens) - #14664

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-14377-i18n-validations-group
Sep 2, 2026
Merged

skills(i18n): validation messages are a translation group since #14253 — correct the claim and its neighbours (net +7 tokens)#14664
os-zhuang merged 2 commits into
mainfrom
claude/issue-14377-i18n-validations-group

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#14377

skills/objectstack-i18n/SKILL.md taught that validation messages are not a translation group. That was true when written and is false on main today: #14253 (landed as b2eab95, "i18n: give bulk-action defs, custom validation messages and dataset labels a bundle key") added three key families at once, and the published catalog ships to codebases this repo cannot see. This corrects the claim and the two neighbouring inventories it sits in.

A correction, not an expansion — maintainer ruling 2026-08-21, quoted verbatim and untranslated: 「对外发布的 skills 是整个平台的最大价值,尤其要整体考虑和评估。」「……不能为了一个小功能扩写很多。」 Three new key families did not license three new sections: the diff is one added table row, one clause inside an existing row, one word in each of two existing prose lists, and one deleted blockquote. No new section. No ceiling change. The ratchet script is untouched, and the headroom #14574 left un-re-locked is not spent.

Per-claim: where, before, after

Line numbers are on origin/main at merge base 20b88391.

1. The claim itself — SKILL.md:172 (blockquote, 3 lines)

Before

validationMessages is not a translation group — it was removed in spec 17.0.0.
Author the message on the rule itself (object.validations[].message), which the
engine returns on every rejected write.

After — deleted; its substance moves into the new _validations row below, which is where a reader looking up an object sub-key will actually find it.

Why it is false.objects.OBJECT._validations.RULE.message is a declared group, object-scoped, with a reader:

  • packages/spec/src/system/translation.zod.ts:427_validations: z.record(...) on ObjectTranslationDataSchema, sibling to _views / _actions / _tabs. message is the only declared key; label, description, condition and when each carry guidance rejecting them, so the surface is one key by measurement, not by omission.
  • packages/spec/src/system/i18n-resolver.ts:1862objectValidationMessageKey(objectName, ruleName) returns objects.${objectName}._validations.${ruleName}.message.
  • packages/objectql/src/validation/rule-validator.ts:2107 — the ObjectQL rule evaluator calls it on the write path and resolves through the existing ValidationMessageContext.translate hook (the i18nService channel that has localized built-in messages since Field validation messages are hardcoded English + API field name — penalty_amount must be ≥ 0 reaches end users verbatim #3957). Authored message on a miss, translation on a hit, no interpolation.

The schema's own note (translation.zod.ts:404-410) states the distinction the old blockquote lost: this is notvalidationMessages returning. That group was keyed by rule name at the top level, so it could not tell two objects' rules apart, and nothing read it — which is why 17.0.0 (#4667, ADR-0049) retired it. The new one is object-scoped and has a reader.

2. Object sub-key table — SKILL.md:163 (_views row)

Beforelabel, description, emptyState.title / emptyState.message
After…unchanged…, bulkActions.{def_name} (label, confirmText, confirmLabel, params)

packages/spec/src/system/translation.zod.ts:281bulkActions is a record under _views.VIEW, keyed by BulkActionDefSchema.name. The four keys named are the ones the def actually declares; successMessage and description carry guidance rejecting them, and per-param help is spelled help, not helpText. The row names the declared keys so an author cannot reach for the two that parse-fail.

3. Top-level group inventory — SKILL.md:168-170

Beforeapps, messages, globalActions, dashboards, pages, flows, settings, metadataForms, settingsCommon
Aftersame list with datasets inserted after dashboards

packages/spec/src/system/translation.zod.ts:770datasets is a top-level group on TranslationDataSchema, resolved by translateDataset. The inventory was incomplete by exactly one entry; it is corrected in place as a list item, not promoted to a section of its own.

4. Unknown-target inventory — SKILL.md:201

Before…object, field, view, tab, action, param, section, app, nav item, dashboard, widget or flow screen…
After…object, field, view, tab, action, param, section, validation rule, app, nav item, dashboard, widget or flow screen…

packages/lint/src/validate-translation-references.ts:629-634 registers _validations.RULE targets from objects[].validations[].name (the code names #14253 at the site). The lint side already reports an unknown validation-rule target; the enumeration simply had not been extended. One word.

premise_false — claims checked and left alone because they are true on main

  • SKILL.md:387-391, the --objects-only divergence — true as written. --objects-only is on by default and --metadata-forms is orthogonal to it; packages/cli/test/i18n-extract-emitted-files.test.ts pins all three directions, including the regression where metadata-forms emission was briefly derived from --objects-only. Untouched — this is what skills(i18n): optimization flight — drop the restated overview, the zero-usage layout and plugin bootstrap and the duplicate template; teach the generated-bundle shape and the --objects-only divergence (net −2,069 tokens) #14574 taught and it stays consistent.
  • SKILL.md:380-386, the generated-bundle file shape — true as written. packages/platform-objects/src/apps/translations/ holds LOCALE.objects.generated.ts, LOCALE.metadata-forms.generated.ts and LOCALE.source-hashes.generated.ts exactly as described. Untouched.
  • SKILL.md:407-436, the withSourceFallback wrapping and the kernel:ready load — true as written, and re-verified against packages/platform-objects/src/apps/translations/index.ts. Untouched.
  • SKILL.md:462-465, the retired o.* sibling-key redirect list — correct to omitvalidationMessages. translation.zod.ts:470-480 defines LEGACY_OBJECT_FIRST_KEYS, and the schema comment at :532 states explicitly that validationMessages is "not a legacy object-first key". Adding it there would have been a factually wrong placement. Untouched.
  • SKILL.md:281-285, the os i18n check surface list — the groups it enumerates are the groups the walker actually visits (packages/cli/src/utils/i18n-coverage.ts:185-218, COVERAGE_SOURCE). Untouched. See the note below.

Known gap, already filed — not folded into this PR

os i18n extract and os i18n check walk none of the three families #14253 added: packages/cli/src/utils/i18n-extract.ts emits no _validations, bulkActions or datasets path (its emitted roots are enumerated at :579, :649, :786-862, :928-1001, :1106-1173), and COVERAGE_SOURCE has no bucket for any of them. That is #14376 (open, domain:cli, pm:dispatched), already tracking exactly this. A caveat sentence about it was drafted into this file and then removed: it is the CLI's gap, #14376 is the place it is being fixed, and paying for it out of this file's budget would have made the net delta positive for a fact that belongs to another card.

Token budget — every added token paid by a deletion in the same file

node scripts/check-skills-token-ratchet.mjs, its own verdict line, before and after:

FileBeforeAfterDeltaCeilingHeadroom after
skills/objectstack-i18n/SKILL.md46784685+7 (+0.15%)6338 (unchanged)1653
whole shipped bundle157662157669+7

Lines: 496 to 493 (-3). Diff: 7 insertions, 10 deletions. The blockquote deletion is the payment — its substance is carried by the _validations row, so nothing true was dropped to make the budget.

Gates

Head d2fa7a18fdb378722342f1cdba9e266f5bb797cd. Family re-derived after the last edit with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (change set: 1 path, skills/objectstack-i18n/SKILL.md; merge base 20b883918), run under scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-14377. Every exit code captured before any pipe.

GateExitIts own verdict line
check-skills-token-ratchet0✓ 31 authored bundle file(s) within their ceilings; 11 generator-owned file(s) measured, not ratcheted.
check:skill-identifier-liveness0OK — Leg 1: 465 citation(s) over 41 published file(s) checked against 93441 implementation word tokens (3 ledgered exemption(s)); Leg 2: 8 registered exhaustive section(s), 0 ledgered gap(s).
check:skill-docs0✅ Skill docs in sync
check:skill-compatibility0✓ 11 SKILL.md file(s) reconciled against 79 workspace packages
check:skill-frame-sync0✓ 4 copies of the decision frame are structurally isomorphic across 3 files
check:role-word0OK (self-test + scan; baseline unchanged, no --update)
check:corpus-claim-drift0OK
check:doc-authoring0✓ 14500 customer-facing string(s) across 710 spec sources clean
check:nul-bytes0OK (scanned 7995 text file(s); no raw ASCII control bytes)
check:agent-test-spelling0✓ 0 violations — 425 file(s)
check:pm-governed-merges0✓ self-test: 243 assertions
check-ci-filter-parity0OK: all 130 declared cross-package glob(s) (92 unique) are covered
check-cross-package-test-inputs / check:cross-package-test-inputs0 / 0OK: 25 package(s) read outside themselves, all declared
check-shard-attestation0✓ 2 aggregate gate(s) count 3 declared leg(s) across 3 attesting job(s).

check:skill-docs was green without regenerating anything — the frontmatter did not change, so skills/README.md and content/docs/ai/skills-reference.mdx are not in this diff.

NOT MEASURED (prerequisite refusals, never content findings)

GateExitPrerequisite it named
check-test-completeness3PREREQUISITE NOT MET — it grades a saved turbo run test log and no log was named. Its own text says the family derives it with no argument and "the local reading for this gate is NOT MEASURED". CI tees the log and passes the path.
check:doc-formula-expressions3PREREQUISITE NOT MET — the workspace package @objectstack/lint is not built. (After building @objectstack/formula the refusal advanced to the next unbuilt package in the chain.)
check:skill-examples1PREREQUISITE NOT MET in substance — packages/client-react/dist holds no .d.ts declarations — the package is not built. Exit 1 here is the refusal branch, not a finding.

Declared narrowing, with the measurement behind it. The shared verify lock was continuously held by four sibling agents (two 9-minute queue timeouts at exit 99 and one foreground cap kill at 10 minutes, with three waiters ahead each time), so the remaining dependency builds those three gates need were not run locally. Rather than assert the narrowing, it is measured: for the two gates whose input is this file,

  • check:skill-examples type-checks os:check-marked TypeScript blocks. os:check marker count in this file is 2 before and 2 after; fenced-block delimiter count is 24 before and 24 after; and git diff -U0 contains no added or removed line that is a fence or an os:check marker. The gate's own census reports 224 blocks on the skills + docs surface — this file's contribution to it is byte-identical to origin/main.
  • check:doc-formula-expressions flags CEL field-rule expressions with rejected root identifiers across .claude, docs, skills, content. Every added line is reproduced in section 2-4 above; none contains expression syntax (no record., no current_user, no operator, no formula).

So neither gate's verdict can differ from main's on account of this diff. Both run on this PR in CI regardless, which is where the authoritative reading is.

Landing

skills/** is a governed surface (Prime Directive #14). This PR stays draft: no merge, no queue, no auto-merge, no ready flip. It is a PR visibly awaiting the maintainer's own merge. Labels: skip-changeset (verified against scripts/check-empty-changeset.mjs:357, which enumerates skills/ among the paths that release nothing) and needs:contract-review (the corrected rows state which keys the i18n resolver honours — a platform contract claim).

🤖 Generated with Claude Code

https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1


Generated by Claude Code

`objects.OBJECT._validations.RULE.message` is object-scoped and resolved on the
write path by the ObjectQL rule evaluator through the existing i18n service, so
the blockquote teaching that validation messages are not a translation group is
false on main. Correct it and the two neighbouring inventories it sits in:
`_views.VIEW.bulkActions` on the object sub-key table and `datasets` in the
top-level group list, plus the validation-rule target `os validate` / `os lint`
already report but the list omitted.
A correction, not an expansion: the retired-key blockquote's substance moves into
the `_validations` row it belongs in, which pays the three additions in the same
file. Net +7 tokens (4678 to 4685), 3 lines shorter; ceiling unchanged and the
headroom left un-re-locked by #14574 is not spent.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
@github-actionsgithub-actionsBot added size/s documentation Improvements or additions to documentation labels Sep 2, 2026
@os-litantos-litant added skip-changeset PR has no user-facing published change; bypasses the changeset gate needs:contract-review and removed documentation Improvements or additions to documentation size/s labels Sep 2, 2026 — with Claude
@github-actionsgithub-actionsBot added size/s documentation Improvements or additions to documentation labels Sep 2, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review September 2, 2026 22:49
@os-zhuang
os-zhuang added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 511e2f6Sep 2, 2026
35 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-14377-i18n-validations-group branch September 2, 2026 23:10
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Published skill objectstack-i18n teaches that validation messages are not a translation group — false once #14253 lands

3 participants

@os-litant@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

skills(i18n): validation messages are a translation group since #14253 — correct the claim and its neighbours (net +7 tokens) - #14664

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-14377-i18n-validations-group
Sep 2, 2026
Merged

skills(i18n): validation messages are a translation group since #14253 — correct the claim and its neighbours (net +7 tokens)#14664
os-zhuang merged 2 commits into
mainfrom
claude/issue-14377-i18n-validations-group

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#14377

skills/objectstack-i18n/SKILL.md taught that validation messages are not a translation group. That was true when written and is false on main today: #14253 (landed as b2eab95, "i18n: give bulk-action defs, custom validation messages and dataset labels a bundle key") added three key families at once, and the published catalog ships to codebases this repo cannot see. This corrects the claim and the two neighbouring inventories it sits in.

A correction, not an expansion — maintainer ruling 2026-08-21, quoted verbatim and untranslated: 「对外发布的 skills 是整个平台的最大价值,尤其要整体考虑和评估。」「……不能为了一个小功能扩写很多。」 Three new key families did not license three new sections: the diff is one added table row, one clause inside an existing row, one word in each of two existing prose lists, and one deleted blockquote. No new section. No ceiling change. The ratchet script is untouched, and the headroom #14574 left un-re-locked is not spent.

Per-claim: where, before, after

Line numbers are on origin/main at merge base 20b88391.

1. The claim itself — SKILL.md:172 (blockquote, 3 lines)

Before

validationMessages is not a translation group — it was removed in spec 17.0.0.
Author the message on the rule itself (object.validations[].message), which the
engine returns on every rejected write.

After — deleted; its substance moves into the new _validations row below, which is where a reader looking up an object sub-key will actually find it.

Why it is false.objects.OBJECT._validations.RULE.message is a declared group, object-scoped, with a reader:

  • packages/spec/src/system/translation.zod.ts:427_validations: z.record(...) on ObjectTranslationDataSchema, sibling to _views / _actions / _tabs. message is the only declared key; label, description, condition and when each carry guidance rejecting them, so the surface is one key by measurement, not by omission.
  • packages/spec/src/system/i18n-resolver.ts:1862objectValidationMessageKey(objectName, ruleName) returns objects.${objectName}._validations.${ruleName}.message.
  • packages/objectql/src/validation/rule-validator.ts:2107 — the ObjectQL rule evaluator calls it on the write path and resolves through the existing ValidationMessageContext.translate hook (the i18nService channel that has localized built-in messages since Field validation messages are hardcoded English + API field name — penalty_amount must be ≥ 0 reaches end users verbatim #3957). Authored message on a miss, translation on a hit, no interpolation.

The schema's own note (translation.zod.ts:404-410) states the distinction the old blockquote lost: this is notvalidationMessages returning. That group was keyed by rule name at the top level, so it could not tell two objects' rules apart, and nothing read it — which is why 17.0.0 (#4667, ADR-0049) retired it. The new one is object-scoped and has a reader.

2. Object sub-key table — SKILL.md:163 (_views row)

Beforelabel, description, emptyState.title / emptyState.message
After…unchanged…, bulkActions.{def_name} (label, confirmText, confirmLabel, params)

packages/spec/src/system/translation.zod.ts:281bulkActions is a record under _views.VIEW, keyed by BulkActionDefSchema.name. The four keys named are the ones the def actually declares; successMessage and description carry guidance rejecting them, and per-param help is spelled help, not helpText. The row names the declared keys so an author cannot reach for the two that parse-fail.

3. Top-level group inventory — SKILL.md:168-170

Beforeapps, messages, globalActions, dashboards, pages, flows, settings, metadataForms, settingsCommon
Aftersame list with datasets inserted after dashboards

packages/spec/src/system/translation.zod.ts:770datasets is a top-level group on TranslationDataSchema, resolved by translateDataset. The inventory was incomplete by exactly one entry; it is corrected in place as a list item, not promoted to a section of its own.

4. Unknown-target inventory — SKILL.md:201

Before…object, field, view, tab, action, param, section, app, nav item, dashboard, widget or flow screen…
After…object, field, view, tab, action, param, section, validation rule, app, nav item, dashboard, widget or flow screen…

packages/lint/src/validate-translation-references.ts:629-634 registers _validations.RULE targets from objects[].validations[].name (the code names #14253 at the site). The lint side already reports an unknown validation-rule target; the enumeration simply had not been extended. One word.

premise_false — claims checked and left alone because they are true on main

  • SKILL.md:387-391, the --objects-only divergence — true as written. --objects-only is on by default and --metadata-forms is orthogonal to it; packages/cli/test/i18n-extract-emitted-files.test.ts pins all three directions, including the regression where metadata-forms emission was briefly derived from --objects-only. Untouched — this is what skills(i18n): optimization flight — drop the restated overview, the zero-usage layout and plugin bootstrap and the duplicate template; teach the generated-bundle shape and the --objects-only divergence (net −2,069 tokens) #14574 taught and it stays consistent.
  • SKILL.md:380-386, the generated-bundle file shape — true as written. packages/platform-objects/src/apps/translations/ holds LOCALE.objects.generated.ts, LOCALE.metadata-forms.generated.ts and LOCALE.source-hashes.generated.ts exactly as described. Untouched.
  • SKILL.md:407-436, the withSourceFallback wrapping and the kernel:ready load — true as written, and re-verified against packages/platform-objects/src/apps/translations/index.ts. Untouched.
  • SKILL.md:462-465, the retired o.* sibling-key redirect list — correct to omitvalidationMessages. translation.zod.ts:470-480 defines LEGACY_OBJECT_FIRST_KEYS, and the schema comment at :532 states explicitly that validationMessages is "not a legacy object-first key". Adding it there would have been a factually wrong placement. Untouched.
  • SKILL.md:281-285, the os i18n check surface list — the groups it enumerates are the groups the walker actually visits (packages/cli/src/utils/i18n-coverage.ts:185-218, COVERAGE_SOURCE). Untouched. See the note below.

Known gap, already filed — not folded into this PR

os i18n extract and os i18n check walk none of the three families #14253 added: packages/cli/src/utils/i18n-extract.ts emits no _validations, bulkActions or datasets path (its emitted roots are enumerated at :579, :649, :786-862, :928-1001, :1106-1173), and COVERAGE_SOURCE has no bucket for any of them. That is #14376 (open, domain:cli, pm:dispatched), already tracking exactly this. A caveat sentence about it was drafted into this file and then removed: it is the CLI's gap, #14376 is the place it is being fixed, and paying for it out of this file's budget would have made the net delta positive for a fact that belongs to another card.

Token budget — every added token paid by a deletion in the same file

node scripts/check-skills-token-ratchet.mjs, its own verdict line, before and after:

FileBeforeAfterDeltaCeilingHeadroom after
skills/objectstack-i18n/SKILL.md46784685+7 (+0.15%)6338 (unchanged)1653
whole shipped bundle157662157669+7

Lines: 496 to 493 (-3). Diff: 7 insertions, 10 deletions. The blockquote deletion is the payment — its substance is carried by the _validations row, so nothing true was dropped to make the budget.

Gates

Head d2fa7a18fdb378722342f1cdba9e266f5bb797cd. Family re-derived after the last edit with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (change set: 1 path, skills/objectstack-i18n/SKILL.md; merge base 20b883918), run under scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-14377. Every exit code captured before any pipe.

GateExitIts own verdict line
check-skills-token-ratchet0✓ 31 authored bundle file(s) within their ceilings; 11 generator-owned file(s) measured, not ratcheted.
check:skill-identifier-liveness0OK — Leg 1: 465 citation(s) over 41 published file(s) checked against 93441 implementation word tokens (3 ledgered exemption(s)); Leg 2: 8 registered exhaustive section(s), 0 ledgered gap(s).
check:skill-docs0✅ Skill docs in sync
check:skill-compatibility0✓ 11 SKILL.md file(s) reconciled against 79 workspace packages
check:skill-frame-sync0✓ 4 copies of the decision frame are structurally isomorphic across 3 files
check:role-word0OK (self-test + scan; baseline unchanged, no --update)
check:corpus-claim-drift0OK
check:doc-authoring0✓ 14500 customer-facing string(s) across 710 spec sources clean
check:nul-bytes0OK (scanned 7995 text file(s); no raw ASCII control bytes)
check:agent-test-spelling0✓ 0 violations — 425 file(s)
check:pm-governed-merges0✓ self-test: 243 assertions
check-ci-filter-parity0OK: all 130 declared cross-package glob(s) (92 unique) are covered
check-cross-package-test-inputs / check:cross-package-test-inputs0 / 0OK: 25 package(s) read outside themselves, all declared
check-shard-attestation0✓ 2 aggregate gate(s) count 3 declared leg(s) across 3 attesting job(s).

check:skill-docs was green without regenerating anything — the frontmatter did not change, so skills/README.md and content/docs/ai/skills-reference.mdx are not in this diff.

NOT MEASURED (prerequisite refusals, never content findings)

GateExitPrerequisite it named
check-test-completeness3PREREQUISITE NOT MET — it grades a saved turbo run test log and no log was named. Its own text says the family derives it with no argument and "the local reading for this gate is NOT MEASURED". CI tees the log and passes the path.
check:doc-formula-expressions3PREREQUISITE NOT MET — the workspace package @objectstack/lint is not built. (After building @objectstack/formula the refusal advanced to the next unbuilt package in the chain.)
check:skill-examples1PREREQUISITE NOT MET in substance — packages/client-react/dist holds no .d.ts declarations — the package is not built. Exit 1 here is the refusal branch, not a finding.

Declared narrowing, with the measurement behind it. The shared verify lock was continuously held by four sibling agents (two 9-minute queue timeouts at exit 99 and one foreground cap kill at 10 minutes, with three waiters ahead each time), so the remaining dependency builds those three gates need were not run locally. Rather than assert the narrowing, it is measured: for the two gates whose input is this file,

  • check:skill-examples type-checks os:check-marked TypeScript blocks. os:check marker count in this file is 2 before and 2 after; fenced-block delimiter count is 24 before and 24 after; and git diff -U0 contains no added or removed line that is a fence or an os:check marker. The gate's own census reports 224 blocks on the skills + docs surface — this file's contribution to it is byte-identical to origin/main.
  • check:doc-formula-expressions flags CEL field-rule expressions with rejected root identifiers across .claude, docs, skills, content. Every added line is reproduced in section 2-4 above; none contains expression syntax (no record., no current_user, no operator, no formula).

So neither gate's verdict can differ from main's on account of this diff. Both run on this PR in CI regardless, which is where the authoritative reading is.

Landing

skills/** is a governed surface (Prime Directive #14). This PR stays draft: no merge, no queue, no auto-merge, no ready flip. It is a PR visibly awaiting the maintainer's own merge. Labels: skip-changeset (verified against scripts/check-empty-changeset.mjs:357, which enumerates skills/ among the paths that release nothing) and needs:contract-review (the corrected rows state which keys the i18n resolver honours — a platform contract claim).

🤖 Generated with Claude Code

https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1


Generated by Claude Code

`objects.OBJECT._validations.RULE.message` is object-scoped and resolved on the
write path by the ObjectQL rule evaluator through the existing i18n service, so
the blockquote teaching that validation messages are not a translation group is
false on main. Correct it and the two neighbouring inventories it sits in:
`_views.VIEW.bulkActions` on the object sub-key table and `datasets` in the
top-level group list, plus the validation-rule target `os validate` / `os lint`
already report but the list omitted.
A correction, not an expansion: the retired-key blockquote's substance moves into
the `_validations` row it belongs in, which pays the three additions in the same
file. Net +7 tokens (4678 to 4685), 3 lines shorter; ceiling unchanged and the
headroom left un-re-locked by #14574 is not spent.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
@github-actionsgithub-actionsBot added size/s documentation Improvements or additions to documentation labels Sep 2, 2026
@os-litantos-litant added skip-changeset PR has no user-facing published change; bypasses the changeset gate needs:contract-review and removed documentation Improvements or additions to documentation size/s labels Sep 2, 2026 — with Claude
@github-actionsgithub-actionsBot added size/s documentation Improvements or additions to documentation labels Sep 2, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review September 2, 2026 22:49
@os-zhuang
os-zhuang added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 511e2f6Sep 2, 2026
35 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-14377-i18n-validations-group branch September 2, 2026 23:10
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Published skill objectstack-i18n teaches that validation messages are not a translation group — false once #14253 lands

3 participants

@os-litant@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

skills(i18n): validation messages are a translation group since #14253 — correct the claim and its neighbours (net +7 tokens) - #14664

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-14377-i18n-validations-group
Sep 2, 2026
Merged

skills(i18n): validation messages are a translation group since #14253 — correct the claim and its neighbours (net +7 tokens)#14664
os-zhuang merged 2 commits into
mainfrom
claude/issue-14377-i18n-validations-group

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#14377

skills/objectstack-i18n/SKILL.md taught that validation messages are not a translation group. That was true when written and is false on main today: #14253 (landed as b2eab95, "i18n: give bulk-action defs, custom validation messages and dataset labels a bundle key") added three key families at once, and the published catalog ships to codebases this repo cannot see. This corrects the claim and the two neighbouring inventories it sits in.

A correction, not an expansion — maintainer ruling 2026-08-21, quoted verbatim and untranslated: 「对外发布的 skills 是整个平台的最大价值,尤其要整体考虑和评估。」「……不能为了一个小功能扩写很多。」 Three new key families did not license three new sections: the diff is one added table row, one clause inside an existing row, one word in each of two existing prose lists, and one deleted blockquote. No new section. No ceiling change. The ratchet script is untouched, and the headroom #14574 left un-re-locked is not spent.

Per-claim: where, before, after

Line numbers are on origin/main at merge base 20b88391.

1. The claim itself — SKILL.md:172 (blockquote, 3 lines)

Before

validationMessages is not a translation group — it was removed in spec 17.0.0.
Author the message on the rule itself (object.validations[].message), which the
engine returns on every rejected write.

After — deleted; its substance moves into the new _validations row below, which is where a reader looking up an object sub-key will actually find it.

Why it is false.objects.OBJECT._validations.RULE.message is a declared group, object-scoped, with a reader:

  • packages/spec/src/system/translation.zod.ts:427_validations: z.record(...) on ObjectTranslationDataSchema, sibling to _views / _actions / _tabs. message is the only declared key; label, description, condition and when each carry guidance rejecting them, so the surface is one key by measurement, not by omission.
  • packages/spec/src/system/i18n-resolver.ts:1862objectValidationMessageKey(objectName, ruleName) returns objects.${objectName}._validations.${ruleName}.message.
  • packages/objectql/src/validation/rule-validator.ts:2107 — the ObjectQL rule evaluator calls it on the write path and resolves through the existing ValidationMessageContext.translate hook (the i18nService channel that has localized built-in messages since Field validation messages are hardcoded English + API field name — penalty_amount must be ≥ 0 reaches end users verbatim #3957). Authored message on a miss, translation on a hit, no interpolation.

The schema's own note (translation.zod.ts:404-410) states the distinction the old blockquote lost: this is notvalidationMessages returning. That group was keyed by rule name at the top level, so it could not tell two objects' rules apart, and nothing read it — which is why 17.0.0 (#4667, ADR-0049) retired it. The new one is object-scoped and has a reader.

2. Object sub-key table — SKILL.md:163 (_views row)

Beforelabel, description, emptyState.title / emptyState.message
After…unchanged…, bulkActions.{def_name} (label, confirmText, confirmLabel, params)

packages/spec/src/system/translation.zod.ts:281bulkActions is a record under _views.VIEW, keyed by BulkActionDefSchema.name. The four keys named are the ones the def actually declares; successMessage and description carry guidance rejecting them, and per-param help is spelled help, not helpText. The row names the declared keys so an author cannot reach for the two that parse-fail.

3. Top-level group inventory — SKILL.md:168-170

Beforeapps, messages, globalActions, dashboards, pages, flows, settings, metadataForms, settingsCommon
Aftersame list with datasets inserted after dashboards

packages/spec/src/system/translation.zod.ts:770datasets is a top-level group on TranslationDataSchema, resolved by translateDataset. The inventory was incomplete by exactly one entry; it is corrected in place as a list item, not promoted to a section of its own.

4. Unknown-target inventory — SKILL.md:201

Before…object, field, view, tab, action, param, section, app, nav item, dashboard, widget or flow screen…
After…object, field, view, tab, action, param, section, validation rule, app, nav item, dashboard, widget or flow screen…

packages/lint/src/validate-translation-references.ts:629-634 registers _validations.RULE targets from objects[].validations[].name (the code names #14253 at the site). The lint side already reports an unknown validation-rule target; the enumeration simply had not been extended. One word.

premise_false — claims checked and left alone because they are true on main

  • SKILL.md:387-391, the --objects-only divergence — true as written. --objects-only is on by default and --metadata-forms is orthogonal to it; packages/cli/test/i18n-extract-emitted-files.test.ts pins all three directions, including the regression where metadata-forms emission was briefly derived from --objects-only. Untouched — this is what skills(i18n): optimization flight — drop the restated overview, the zero-usage layout and plugin bootstrap and the duplicate template; teach the generated-bundle shape and the --objects-only divergence (net −2,069 tokens) #14574 taught and it stays consistent.
  • SKILL.md:380-386, the generated-bundle file shape — true as written. packages/platform-objects/src/apps/translations/ holds LOCALE.objects.generated.ts, LOCALE.metadata-forms.generated.ts and LOCALE.source-hashes.generated.ts exactly as described. Untouched.
  • SKILL.md:407-436, the withSourceFallback wrapping and the kernel:ready load — true as written, and re-verified against packages/platform-objects/src/apps/translations/index.ts. Untouched.
  • SKILL.md:462-465, the retired o.* sibling-key redirect list — correct to omitvalidationMessages. translation.zod.ts:470-480 defines LEGACY_OBJECT_FIRST_KEYS, and the schema comment at :532 states explicitly that validationMessages is "not a legacy object-first key". Adding it there would have been a factually wrong placement. Untouched.
  • SKILL.md:281-285, the os i18n check surface list — the groups it enumerates are the groups the walker actually visits (packages/cli/src/utils/i18n-coverage.ts:185-218, COVERAGE_SOURCE). Untouched. See the note below.

Known gap, already filed — not folded into this PR

os i18n extract and os i18n check walk none of the three families #14253 added: packages/cli/src/utils/i18n-extract.ts emits no _validations, bulkActions or datasets path (its emitted roots are enumerated at :579, :649, :786-862, :928-1001, :1106-1173), and COVERAGE_SOURCE has no bucket for any of them. That is #14376 (open, domain:cli, pm:dispatched), already tracking exactly this. A caveat sentence about it was drafted into this file and then removed: it is the CLI's gap, #14376 is the place it is being fixed, and paying for it out of this file's budget would have made the net delta positive for a fact that belongs to another card.

Token budget — every added token paid by a deletion in the same file

node scripts/check-skills-token-ratchet.mjs, its own verdict line, before and after:

FileBeforeAfterDeltaCeilingHeadroom after
skills/objectstack-i18n/SKILL.md46784685+7 (+0.15%)6338 (unchanged)1653
whole shipped bundle157662157669+7

Lines: 496 to 493 (-3). Diff: 7 insertions, 10 deletions. The blockquote deletion is the payment — its substance is carried by the _validations row, so nothing true was dropped to make the budget.

Gates

Head d2fa7a18fdb378722342f1cdba9e266f5bb797cd. Family re-derived after the last edit with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (change set: 1 path, skills/objectstack-i18n/SKILL.md; merge base 20b883918), run under scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-14377. Every exit code captured before any pipe.

GateExitIts own verdict line
check-skills-token-ratchet0✓ 31 authored bundle file(s) within their ceilings; 11 generator-owned file(s) measured, not ratcheted.
check:skill-identifier-liveness0OK — Leg 1: 465 citation(s) over 41 published file(s) checked against 93441 implementation word tokens (3 ledgered exemption(s)); Leg 2: 8 registered exhaustive section(s), 0 ledgered gap(s).
check:skill-docs0✅ Skill docs in sync
check:skill-compatibility0✓ 11 SKILL.md file(s) reconciled against 79 workspace packages
check:skill-frame-sync0✓ 4 copies of the decision frame are structurally isomorphic across 3 files
check:role-word0OK (self-test + scan; baseline unchanged, no --update)
check:corpus-claim-drift0OK
check:doc-authoring0✓ 14500 customer-facing string(s) across 710 spec sources clean
check:nul-bytes0OK (scanned 7995 text file(s); no raw ASCII control bytes)
check:agent-test-spelling0✓ 0 violations — 425 file(s)
check:pm-governed-merges0✓ self-test: 243 assertions
check-ci-filter-parity0OK: all 130 declared cross-package glob(s) (92 unique) are covered
check-cross-package-test-inputs / check:cross-package-test-inputs0 / 0OK: 25 package(s) read outside themselves, all declared
check-shard-attestation0✓ 2 aggregate gate(s) count 3 declared leg(s) across 3 attesting job(s).

check:skill-docs was green without regenerating anything — the frontmatter did not change, so skills/README.md and content/docs/ai/skills-reference.mdx are not in this diff.

NOT MEASURED (prerequisite refusals, never content findings)

GateExitPrerequisite it named
check-test-completeness3PREREQUISITE NOT MET — it grades a saved turbo run test log and no log was named. Its own text says the family derives it with no argument and "the local reading for this gate is NOT MEASURED". CI tees the log and passes the path.
check:doc-formula-expressions3PREREQUISITE NOT MET — the workspace package @objectstack/lint is not built. (After building @objectstack/formula the refusal advanced to the next unbuilt package in the chain.)
check:skill-examples1PREREQUISITE NOT MET in substance — packages/client-react/dist holds no .d.ts declarations — the package is not built. Exit 1 here is the refusal branch, not a finding.

Declared narrowing, with the measurement behind it. The shared verify lock was continuously held by four sibling agents (two 9-minute queue timeouts at exit 99 and one foreground cap kill at 10 minutes, with three waiters ahead each time), so the remaining dependency builds those three gates need were not run locally. Rather than assert the narrowing, it is measured: for the two gates whose input is this file,

  • check:skill-examples type-checks os:check-marked TypeScript blocks. os:check marker count in this file is 2 before and 2 after; fenced-block delimiter count is 24 before and 24 after; and git diff -U0 contains no added or removed line that is a fence or an os:check marker. The gate's own census reports 224 blocks on the skills + docs surface — this file's contribution to it is byte-identical to origin/main.
  • check:doc-formula-expressions flags CEL field-rule expressions with rejected root identifiers across .claude, docs, skills, content. Every added line is reproduced in section 2-4 above; none contains expression syntax (no record., no current_user, no operator, no formula).

So neither gate's verdict can differ from main's on account of this diff. Both run on this PR in CI regardless, which is where the authoritative reading is.

Landing

skills/** is a governed surface (Prime Directive #14). This PR stays draft: no merge, no queue, no auto-merge, no ready flip. It is a PR visibly awaiting the maintainer's own merge. Labels: skip-changeset (verified against scripts/check-empty-changeset.mjs:357, which enumerates skills/ among the paths that release nothing) and needs:contract-review (the corrected rows state which keys the i18n resolver honours — a platform contract claim).

🤖 Generated with Claude Code

https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1


Generated by Claude Code

`objects.OBJECT._validations.RULE.message` is object-scoped and resolved on the
write path by the ObjectQL rule evaluator through the existing i18n service, so
the blockquote teaching that validation messages are not a translation group is
false on main. Correct it and the two neighbouring inventories it sits in:
`_views.VIEW.bulkActions` on the object sub-key table and `datasets` in the
top-level group list, plus the validation-rule target `os validate` / `os lint`
already report but the list omitted.
A correction, not an expansion: the retired-key blockquote's substance moves into
the `_validations` row it belongs in, which pays the three additions in the same
file. Net +7 tokens (4678 to 4685), 3 lines shorter; ceiling unchanged and the
headroom left un-re-locked by #14574 is not spent.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
@github-actionsgithub-actionsBot added size/s documentation Improvements or additions to documentation labels Sep 2, 2026
@os-litantos-litant added skip-changeset PR has no user-facing published change; bypasses the changeset gate needs:contract-review and removed documentation Improvements or additions to documentation size/s labels Sep 2, 2026 — with Claude
@github-actionsgithub-actionsBot added size/s documentation Improvements or additions to documentation labels Sep 2, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review September 2, 2026 22:49
@os-zhuang
os-zhuang added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 511e2f6Sep 2, 2026
35 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-14377-i18n-validations-group branch September 2, 2026 23:10
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Published skill objectstack-i18n teaches that validation messages are not a translation group — false once #14253 lands

3 participants

@os-litant@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

skills(i18n): validation messages are a translation group since #14253 — correct the claim and its neighbours (net +7 tokens) - #14664

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-14377-i18n-validations-group
Sep 2, 2026
Merged

skills(i18n): validation messages are a translation group since #14253 — correct the claim and its neighbours (net +7 tokens)#14664
os-zhuang merged 2 commits into
mainfrom
claude/issue-14377-i18n-validations-group

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#14377

skills/objectstack-i18n/SKILL.md taught that validation messages are not a translation group. That was true when written and is false on main today: #14253 (landed as b2eab95, "i18n: give bulk-action defs, custom validation messages and dataset labels a bundle key") added three key families at once, and the published catalog ships to codebases this repo cannot see. This corrects the claim and the two neighbouring inventories it sits in.

A correction, not an expansion — maintainer ruling 2026-08-21, quoted verbatim and untranslated: 「对外发布的 skills 是整个平台的最大价值,尤其要整体考虑和评估。」「……不能为了一个小功能扩写很多。」 Three new key families did not license three new sections: the diff is one added table row, one clause inside an existing row, one word in each of two existing prose lists, and one deleted blockquote. No new section. No ceiling change. The ratchet script is untouched, and the headroom #14574 left un-re-locked is not spent.

Per-claim: where, before, after

Line numbers are on origin/main at merge base 20b88391.

1. The claim itself — SKILL.md:172 (blockquote, 3 lines)

Before

validationMessages is not a translation group — it was removed in spec 17.0.0.
Author the message on the rule itself (object.validations[].message), which the
engine returns on every rejected write.

After — deleted; its substance moves into the new _validations row below, which is where a reader looking up an object sub-key will actually find it.

Why it is false.objects.OBJECT._validations.RULE.message is a declared group, object-scoped, with a reader:

  • packages/spec/src/system/translation.zod.ts:427_validations: z.record(...) on ObjectTranslationDataSchema, sibling to _views / _actions / _tabs. message is the only declared key; label, description, condition and when each carry guidance rejecting them, so the surface is one key by measurement, not by omission.
  • packages/spec/src/system/i18n-resolver.ts:1862objectValidationMessageKey(objectName, ruleName) returns objects.${objectName}._validations.${ruleName}.message.
  • packages/objectql/src/validation/rule-validator.ts:2107 — the ObjectQL rule evaluator calls it on the write path and resolves through the existing ValidationMessageContext.translate hook (the i18nService channel that has localized built-in messages since Field validation messages are hardcoded English + API field name — penalty_amount must be ≥ 0 reaches end users verbatim #3957). Authored message on a miss, translation on a hit, no interpolation.

The schema's own note (translation.zod.ts:404-410) states the distinction the old blockquote lost: this is notvalidationMessages returning. That group was keyed by rule name at the top level, so it could not tell two objects' rules apart, and nothing read it — which is why 17.0.0 (#4667, ADR-0049) retired it. The new one is object-scoped and has a reader.

2. Object sub-key table — SKILL.md:163 (_views row)

Beforelabel, description, emptyState.title / emptyState.message
After…unchanged…, bulkActions.{def_name} (label, confirmText, confirmLabel, params)

packages/spec/src/system/translation.zod.ts:281bulkActions is a record under _views.VIEW, keyed by BulkActionDefSchema.name. The four keys named are the ones the def actually declares; successMessage and description carry guidance rejecting them, and per-param help is spelled help, not helpText. The row names the declared keys so an author cannot reach for the two that parse-fail.

3. Top-level group inventory — SKILL.md:168-170

Beforeapps, messages, globalActions, dashboards, pages, flows, settings, metadataForms, settingsCommon
Aftersame list with datasets inserted after dashboards

packages/spec/src/system/translation.zod.ts:770datasets is a top-level group on TranslationDataSchema, resolved by translateDataset. The inventory was incomplete by exactly one entry; it is corrected in place as a list item, not promoted to a section of its own.

4. Unknown-target inventory — SKILL.md:201

Before…object, field, view, tab, action, param, section, app, nav item, dashboard, widget or flow screen…
After…object, field, view, tab, action, param, section, validation rule, app, nav item, dashboard, widget or flow screen…

packages/lint/src/validate-translation-references.ts:629-634 registers _validations.RULE targets from objects[].validations[].name (the code names #14253 at the site). The lint side already reports an unknown validation-rule target; the enumeration simply had not been extended. One word.

premise_false — claims checked and left alone because they are true on main

  • SKILL.md:387-391, the --objects-only divergence — true as written. --objects-only is on by default and --metadata-forms is orthogonal to it; packages/cli/test/i18n-extract-emitted-files.test.ts pins all three directions, including the regression where metadata-forms emission was briefly derived from --objects-only. Untouched — this is what skills(i18n): optimization flight — drop the restated overview, the zero-usage layout and plugin bootstrap and the duplicate template; teach the generated-bundle shape and the --objects-only divergence (net −2,069 tokens) #14574 taught and it stays consistent.
  • SKILL.md:380-386, the generated-bundle file shape — true as written. packages/platform-objects/src/apps/translations/ holds LOCALE.objects.generated.ts, LOCALE.metadata-forms.generated.ts and LOCALE.source-hashes.generated.ts exactly as described. Untouched.
  • SKILL.md:407-436, the withSourceFallback wrapping and the kernel:ready load — true as written, and re-verified against packages/platform-objects/src/apps/translations/index.ts. Untouched.
  • SKILL.md:462-465, the retired o.* sibling-key redirect list — correct to omitvalidationMessages. translation.zod.ts:470-480 defines LEGACY_OBJECT_FIRST_KEYS, and the schema comment at :532 states explicitly that validationMessages is "not a legacy object-first key". Adding it there would have been a factually wrong placement. Untouched.
  • SKILL.md:281-285, the os i18n check surface list — the groups it enumerates are the groups the walker actually visits (packages/cli/src/utils/i18n-coverage.ts:185-218, COVERAGE_SOURCE). Untouched. See the note below.

Known gap, already filed — not folded into this PR

os i18n extract and os i18n check walk none of the three families #14253 added: packages/cli/src/utils/i18n-extract.ts emits no _validations, bulkActions or datasets path (its emitted roots are enumerated at :579, :649, :786-862, :928-1001, :1106-1173), and COVERAGE_SOURCE has no bucket for any of them. That is #14376 (open, domain:cli, pm:dispatched), already tracking exactly this. A caveat sentence about it was drafted into this file and then removed: it is the CLI's gap, #14376 is the place it is being fixed, and paying for it out of this file's budget would have made the net delta positive for a fact that belongs to another card.

Token budget — every added token paid by a deletion in the same file

node scripts/check-skills-token-ratchet.mjs, its own verdict line, before and after:

FileBeforeAfterDeltaCeilingHeadroom after
skills/objectstack-i18n/SKILL.md46784685+7 (+0.15%)6338 (unchanged)1653
whole shipped bundle157662157669+7

Lines: 496 to 493 (-3). Diff: 7 insertions, 10 deletions. The blockquote deletion is the payment — its substance is carried by the _validations row, so nothing true was dropped to make the budget.

Gates

Head d2fa7a18fdb378722342f1cdba9e266f5bb797cd. Family re-derived after the last edit with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (change set: 1 path, skills/objectstack-i18n/SKILL.md; merge base 20b883918), run under scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-14377. Every exit code captured before any pipe.

GateExitIts own verdict line
check-skills-token-ratchet0✓ 31 authored bundle file(s) within their ceilings; 11 generator-owned file(s) measured, not ratcheted.
check:skill-identifier-liveness0OK — Leg 1: 465 citation(s) over 41 published file(s) checked against 93441 implementation word tokens (3 ledgered exemption(s)); Leg 2: 8 registered exhaustive section(s), 0 ledgered gap(s).
check:skill-docs0✅ Skill docs in sync
check:skill-compatibility0✓ 11 SKILL.md file(s) reconciled against 79 workspace packages
check:skill-frame-sync0✓ 4 copies of the decision frame are structurally isomorphic across 3 files
check:role-word0OK (self-test + scan; baseline unchanged, no --update)
check:corpus-claim-drift0OK
check:doc-authoring0✓ 14500 customer-facing string(s) across 710 spec sources clean
check:nul-bytes0OK (scanned 7995 text file(s); no raw ASCII control bytes)
check:agent-test-spelling0✓ 0 violations — 425 file(s)
check:pm-governed-merges0✓ self-test: 243 assertions
check-ci-filter-parity0OK: all 130 declared cross-package glob(s) (92 unique) are covered
check-cross-package-test-inputs / check:cross-package-test-inputs0 / 0OK: 25 package(s) read outside themselves, all declared
check-shard-attestation0✓ 2 aggregate gate(s) count 3 declared leg(s) across 3 attesting job(s).

check:skill-docs was green without regenerating anything — the frontmatter did not change, so skills/README.md and content/docs/ai/skills-reference.mdx are not in this diff.

NOT MEASURED (prerequisite refusals, never content findings)

GateExitPrerequisite it named
check-test-completeness3PREREQUISITE NOT MET — it grades a saved turbo run test log and no log was named. Its own text says the family derives it with no argument and "the local reading for this gate is NOT MEASURED". CI tees the log and passes the path.
check:doc-formula-expressions3PREREQUISITE NOT MET — the workspace package @objectstack/lint is not built. (After building @objectstack/formula the refusal advanced to the next unbuilt package in the chain.)
check:skill-examples1PREREQUISITE NOT MET in substance — packages/client-react/dist holds no .d.ts declarations — the package is not built. Exit 1 here is the refusal branch, not a finding.

Declared narrowing, with the measurement behind it. The shared verify lock was continuously held by four sibling agents (two 9-minute queue timeouts at exit 99 and one foreground cap kill at 10 minutes, with three waiters ahead each time), so the remaining dependency builds those three gates need were not run locally. Rather than assert the narrowing, it is measured: for the two gates whose input is this file,

  • check:skill-examples type-checks os:check-marked TypeScript blocks. os:check marker count in this file is 2 before and 2 after; fenced-block delimiter count is 24 before and 24 after; and git diff -U0 contains no added or removed line that is a fence or an os:check marker. The gate's own census reports 224 blocks on the skills + docs surface — this file's contribution to it is byte-identical to origin/main.
  • check:doc-formula-expressions flags CEL field-rule expressions with rejected root identifiers across .claude, docs, skills, content. Every added line is reproduced in section 2-4 above; none contains expression syntax (no record., no current_user, no operator, no formula).

So neither gate's verdict can differ from main's on account of this diff. Both run on this PR in CI regardless, which is where the authoritative reading is.

Landing

skills/** is a governed surface (Prime Directive #14). This PR stays draft: no merge, no queue, no auto-merge, no ready flip. It is a PR visibly awaiting the maintainer's own merge. Labels: skip-changeset (verified against scripts/check-empty-changeset.mjs:357, which enumerates skills/ among the paths that release nothing) and needs:contract-review (the corrected rows state which keys the i18n resolver honours — a platform contract claim).

🤖 Generated with Claude Code

https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1


Generated by Claude Code

`objects.OBJECT._validations.RULE.message` is object-scoped and resolved on the
write path by the ObjectQL rule evaluator through the existing i18n service, so
the blockquote teaching that validation messages are not a translation group is
false on main. Correct it and the two neighbouring inventories it sits in:
`_views.VIEW.bulkActions` on the object sub-key table and `datasets` in the
top-level group list, plus the validation-rule target `os validate` / `os lint`
already report but the list omitted.
A correction, not an expansion: the retired-key blockquote's substance moves into
the `_validations` row it belongs in, which pays the three additions in the same
file. Net +7 tokens (4678 to 4685), 3 lines shorter; ceiling unchanged and the
headroom left un-re-locked by #14574 is not spent.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
@github-actionsgithub-actionsBot added size/s documentation Improvements or additions to documentation labels Sep 2, 2026
@os-litantos-litant added skip-changeset PR has no user-facing published change; bypasses the changeset gate needs:contract-review and removed documentation Improvements or additions to documentation size/s labels Sep 2, 2026 — with Claude
@github-actionsgithub-actionsBot added size/s documentation Improvements or additions to documentation labels Sep 2, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review September 2, 2026 22:49
@os-zhuang
os-zhuang added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 511e2f6Sep 2, 2026
35 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-14377-i18n-validations-group branch September 2, 2026 23:10
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Published skill objectstack-i18n teaches that validation messages are not a translation group — false once #14253 lands

3 participants

@os-litant@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

skills(i18n): validation messages are a translation group since #14253 — correct the claim and its neighbours (net +7 tokens) - #14664

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-14377-i18n-validations-group
Sep 2, 2026
Merged

skills(i18n): validation messages are a translation group since #14253 — correct the claim and its neighbours (net +7 tokens)#14664
os-zhuang merged 2 commits into
mainfrom
claude/issue-14377-i18n-validations-group

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#14377

skills/objectstack-i18n/SKILL.md taught that validation messages are not a translation group. That was true when written and is false on main today: #14253 (landed as b2eab95, "i18n: give bulk-action defs, custom validation messages and dataset labels a bundle key") added three key families at once, and the published catalog ships to codebases this repo cannot see. This corrects the claim and the two neighbouring inventories it sits in.

A correction, not an expansion — maintainer ruling 2026-08-21, quoted verbatim and untranslated: 「对外发布的 skills 是整个平台的最大价值,尤其要整体考虑和评估。」「……不能为了一个小功能扩写很多。」 Three new key families did not license three new sections: the diff is one added table row, one clause inside an existing row, one word in each of two existing prose lists, and one deleted blockquote. No new section. No ceiling change. The ratchet script is untouched, and the headroom #14574 left un-re-locked is not spent.

Per-claim: where, before, after

Line numbers are on origin/main at merge base 20b88391.

1. The claim itself — SKILL.md:172 (blockquote, 3 lines)

Before

validationMessages is not a translation group — it was removed in spec 17.0.0.
Author the message on the rule itself (object.validations[].message), which the
engine returns on every rejected write.

After — deleted; its substance moves into the new _validations row below, which is where a reader looking up an object sub-key will actually find it.

Why it is false.objects.OBJECT._validations.RULE.message is a declared group, object-scoped, with a reader:

  • packages/spec/src/system/translation.zod.ts:427_validations: z.record(...) on ObjectTranslationDataSchema, sibling to _views / _actions / _tabs. message is the only declared key; label, description, condition and when each carry guidance rejecting them, so the surface is one key by measurement, not by omission.
  • packages/spec/src/system/i18n-resolver.ts:1862objectValidationMessageKey(objectName, ruleName) returns objects.${objectName}._validations.${ruleName}.message.
  • packages/objectql/src/validation/rule-validator.ts:2107 — the ObjectQL rule evaluator calls it on the write path and resolves through the existing ValidationMessageContext.translate hook (the i18nService channel that has localized built-in messages since Field validation messages are hardcoded English + API field name — penalty_amount must be ≥ 0 reaches end users verbatim #3957). Authored message on a miss, translation on a hit, no interpolation.

The schema's own note (translation.zod.ts:404-410) states the distinction the old blockquote lost: this is notvalidationMessages returning. That group was keyed by rule name at the top level, so it could not tell two objects' rules apart, and nothing read it — which is why 17.0.0 (#4667, ADR-0049) retired it. The new one is object-scoped and has a reader.

2. Object sub-key table — SKILL.md:163 (_views row)

Beforelabel, description, emptyState.title / emptyState.message
After…unchanged…, bulkActions.{def_name} (label, confirmText, confirmLabel, params)

packages/spec/src/system/translation.zod.ts:281bulkActions is a record under _views.VIEW, keyed by BulkActionDefSchema.name. The four keys named are the ones the def actually declares; successMessage and description carry guidance rejecting them, and per-param help is spelled help, not helpText. The row names the declared keys so an author cannot reach for the two that parse-fail.

3. Top-level group inventory — SKILL.md:168-170

Beforeapps, messages, globalActions, dashboards, pages, flows, settings, metadataForms, settingsCommon
Aftersame list with datasets inserted after dashboards

packages/spec/src/system/translation.zod.ts:770datasets is a top-level group on TranslationDataSchema, resolved by translateDataset. The inventory was incomplete by exactly one entry; it is corrected in place as a list item, not promoted to a section of its own.

4. Unknown-target inventory — SKILL.md:201

Before…object, field, view, tab, action, param, section, app, nav item, dashboard, widget or flow screen…
After…object, field, view, tab, action, param, section, validation rule, app, nav item, dashboard, widget or flow screen…

packages/lint/src/validate-translation-references.ts:629-634 registers _validations.RULE targets from objects[].validations[].name (the code names #14253 at the site). The lint side already reports an unknown validation-rule target; the enumeration simply had not been extended. One word.

premise_false — claims checked and left alone because they are true on main

  • SKILL.md:387-391, the --objects-only divergence — true as written. --objects-only is on by default and --metadata-forms is orthogonal to it; packages/cli/test/i18n-extract-emitted-files.test.ts pins all three directions, including the regression where metadata-forms emission was briefly derived from --objects-only. Untouched — this is what skills(i18n): optimization flight — drop the restated overview, the zero-usage layout and plugin bootstrap and the duplicate template; teach the generated-bundle shape and the --objects-only divergence (net −2,069 tokens) #14574 taught and it stays consistent.
  • SKILL.md:380-386, the generated-bundle file shape — true as written. packages/platform-objects/src/apps/translations/ holds LOCALE.objects.generated.ts, LOCALE.metadata-forms.generated.ts and LOCALE.source-hashes.generated.ts exactly as described. Untouched.
  • SKILL.md:407-436, the withSourceFallback wrapping and the kernel:ready load — true as written, and re-verified against packages/platform-objects/src/apps/translations/index.ts. Untouched.
  • SKILL.md:462-465, the retired o.* sibling-key redirect list — correct to omitvalidationMessages. translation.zod.ts:470-480 defines LEGACY_OBJECT_FIRST_KEYS, and the schema comment at :532 states explicitly that validationMessages is "not a legacy object-first key". Adding it there would have been a factually wrong placement. Untouched.
  • SKILL.md:281-285, the os i18n check surface list — the groups it enumerates are the groups the walker actually visits (packages/cli/src/utils/i18n-coverage.ts:185-218, COVERAGE_SOURCE). Untouched. See the note below.

Known gap, already filed — not folded into this PR

os i18n extract and os i18n check walk none of the three families #14253 added: packages/cli/src/utils/i18n-extract.ts emits no _validations, bulkActions or datasets path (its emitted roots are enumerated at :579, :649, :786-862, :928-1001, :1106-1173), and COVERAGE_SOURCE has no bucket for any of them. That is #14376 (open, domain:cli, pm:dispatched), already tracking exactly this. A caveat sentence about it was drafted into this file and then removed: it is the CLI's gap, #14376 is the place it is being fixed, and paying for it out of this file's budget would have made the net delta positive for a fact that belongs to another card.

Token budget — every added token paid by a deletion in the same file

node scripts/check-skills-token-ratchet.mjs, its own verdict line, before and after:

FileBeforeAfterDeltaCeilingHeadroom after
skills/objectstack-i18n/SKILL.md46784685+7 (+0.15%)6338 (unchanged)1653
whole shipped bundle157662157669+7

Lines: 496 to 493 (-3). Diff: 7 insertions, 10 deletions. The blockquote deletion is the payment — its substance is carried by the _validations row, so nothing true was dropped to make the budget.

Gates

Head d2fa7a18fdb378722342f1cdba9e266f5bb797cd. Family re-derived after the last edit with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (change set: 1 path, skills/objectstack-i18n/SKILL.md; merge base 20b883918), run under scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-14377. Every exit code captured before any pipe.

GateExitIts own verdict line
check-skills-token-ratchet0✓ 31 authored bundle file(s) within their ceilings; 11 generator-owned file(s) measured, not ratcheted.
check:skill-identifier-liveness0OK — Leg 1: 465 citation(s) over 41 published file(s) checked against 93441 implementation word tokens (3 ledgered exemption(s)); Leg 2: 8 registered exhaustive section(s), 0 ledgered gap(s).
check:skill-docs0✅ Skill docs in sync
check:skill-compatibility0✓ 11 SKILL.md file(s) reconciled against 79 workspace packages
check:skill-frame-sync0✓ 4 copies of the decision frame are structurally isomorphic across 3 files
check:role-word0OK (self-test + scan; baseline unchanged, no --update)
check:corpus-claim-drift0OK
check:doc-authoring0✓ 14500 customer-facing string(s) across 710 spec sources clean
check:nul-bytes0OK (scanned 7995 text file(s); no raw ASCII control bytes)
check:agent-test-spelling0✓ 0 violations — 425 file(s)
check:pm-governed-merges0✓ self-test: 243 assertions
check-ci-filter-parity0OK: all 130 declared cross-package glob(s) (92 unique) are covered
check-cross-package-test-inputs / check:cross-package-test-inputs0 / 0OK: 25 package(s) read outside themselves, all declared
check-shard-attestation0✓ 2 aggregate gate(s) count 3 declared leg(s) across 3 attesting job(s).

check:skill-docs was green without regenerating anything — the frontmatter did not change, so skills/README.md and content/docs/ai/skills-reference.mdx are not in this diff.

NOT MEASURED (prerequisite refusals, never content findings)

GateExitPrerequisite it named
check-test-completeness3PREREQUISITE NOT MET — it grades a saved turbo run test log and no log was named. Its own text says the family derives it with no argument and "the local reading for this gate is NOT MEASURED". CI tees the log and passes the path.
check:doc-formula-expressions3PREREQUISITE NOT MET — the workspace package @objectstack/lint is not built. (After building @objectstack/formula the refusal advanced to the next unbuilt package in the chain.)
check:skill-examples1PREREQUISITE NOT MET in substance — packages/client-react/dist holds no .d.ts declarations — the package is not built. Exit 1 here is the refusal branch, not a finding.

Declared narrowing, with the measurement behind it. The shared verify lock was continuously held by four sibling agents (two 9-minute queue timeouts at exit 99 and one foreground cap kill at 10 minutes, with three waiters ahead each time), so the remaining dependency builds those three gates need were not run locally. Rather than assert the narrowing, it is measured: for the two gates whose input is this file,

  • check:skill-examples type-checks os:check-marked TypeScript blocks. os:check marker count in this file is 2 before and 2 after; fenced-block delimiter count is 24 before and 24 after; and git diff -U0 contains no added or removed line that is a fence or an os:check marker. The gate's own census reports 224 blocks on the skills + docs surface — this file's contribution to it is byte-identical to origin/main.
  • check:doc-formula-expressions flags CEL field-rule expressions with rejected root identifiers across .claude, docs, skills, content. Every added line is reproduced in section 2-4 above; none contains expression syntax (no record., no current_user, no operator, no formula).

So neither gate's verdict can differ from main's on account of this diff. Both run on this PR in CI regardless, which is where the authoritative reading is.

Landing

skills/** is a governed surface (Prime Directive #14). This PR stays draft: no merge, no queue, no auto-merge, no ready flip. It is a PR visibly awaiting the maintainer's own merge. Labels: skip-changeset (verified against scripts/check-empty-changeset.mjs:357, which enumerates skills/ among the paths that release nothing) and needs:contract-review (the corrected rows state which keys the i18n resolver honours — a platform contract claim).

🤖 Generated with Claude Code

https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1


Generated by Claude Code

`objects.OBJECT._validations.RULE.message` is object-scoped and resolved on the
write path by the ObjectQL rule evaluator through the existing i18n service, so
the blockquote teaching that validation messages are not a translation group is
false on main. Correct it and the two neighbouring inventories it sits in:
`_views.VIEW.bulkActions` on the object sub-key table and `datasets` in the
top-level group list, plus the validation-rule target `os validate` / `os lint`
already report but the list omitted.
A correction, not an expansion: the retired-key blockquote's substance moves into
the `_validations` row it belongs in, which pays the three additions in the same
file. Net +7 tokens (4678 to 4685), 3 lines shorter; ceiling unchanged and the
headroom left un-re-locked by #14574 is not spent.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
@github-actionsgithub-actionsBot added size/s documentation Improvements or additions to documentation labels Sep 2, 2026
@os-litantos-litant added skip-changeset PR has no user-facing published change; bypasses the changeset gate needs:contract-review and removed documentation Improvements or additions to documentation size/s labels Sep 2, 2026 — with Claude
@github-actionsgithub-actionsBot added size/s documentation Improvements or additions to documentation labels Sep 2, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review September 2, 2026 22:49
@os-zhuang
os-zhuang added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 511e2f6Sep 2, 2026
35 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-14377-i18n-validations-group branch September 2, 2026 23:10
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Published skill objectstack-i18n teaches that validation messages are not a translation group — false once #14253 lands

3 participants

@os-litant@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

skills(i18n): validation messages are a translation group since #14253 — correct the claim and its neighbours (net +7 tokens) - #14664

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-14377-i18n-validations-group
Sep 2, 2026
Merged

skills(i18n): validation messages are a translation group since #14253 — correct the claim and its neighbours (net +7 tokens)#14664
os-zhuang merged 2 commits into
mainfrom
claude/issue-14377-i18n-validations-group

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#14377

skills/objectstack-i18n/SKILL.md taught that validation messages are not a translation group. That was true when written and is false on main today: #14253 (landed as b2eab95, "i18n: give bulk-action defs, custom validation messages and dataset labels a bundle key") added three key families at once, and the published catalog ships to codebases this repo cannot see. This corrects the claim and the two neighbouring inventories it sits in.

A correction, not an expansion — maintainer ruling 2026-08-21, quoted verbatim and untranslated: 「对外发布的 skills 是整个平台的最大价值,尤其要整体考虑和评估。」「……不能为了一个小功能扩写很多。」 Three new key families did not license three new sections: the diff is one added table row, one clause inside an existing row, one word in each of two existing prose lists, and one deleted blockquote. No new section. No ceiling change. The ratchet script is untouched, and the headroom #14574 left un-re-locked is not spent.

Per-claim: where, before, after

Line numbers are on origin/main at merge base 20b88391.

1. The claim itself — SKILL.md:172 (blockquote, 3 lines)

Before

validationMessages is not a translation group — it was removed in spec 17.0.0.
Author the message on the rule itself (object.validations[].message), which the
engine returns on every rejected write.

After — deleted; its substance moves into the new _validations row below, which is where a reader looking up an object sub-key will actually find it.

Why it is false.objects.OBJECT._validations.RULE.message is a declared group, object-scoped, with a reader:

  • packages/spec/src/system/translation.zod.ts:427_validations: z.record(...) on ObjectTranslationDataSchema, sibling to _views / _actions / _tabs. message is the only declared key; label, description, condition and when each carry guidance rejecting them, so the surface is one key by measurement, not by omission.
  • packages/spec/src/system/i18n-resolver.ts:1862objectValidationMessageKey(objectName, ruleName) returns objects.${objectName}._validations.${ruleName}.message.
  • packages/objectql/src/validation/rule-validator.ts:2107 — the ObjectQL rule evaluator calls it on the write path and resolves through the existing ValidationMessageContext.translate hook (the i18nService channel that has localized built-in messages since Field validation messages are hardcoded English + API field name — penalty_amount must be ≥ 0 reaches end users verbatim #3957). Authored message on a miss, translation on a hit, no interpolation.

The schema's own note (translation.zod.ts:404-410) states the distinction the old blockquote lost: this is notvalidationMessages returning. That group was keyed by rule name at the top level, so it could not tell two objects' rules apart, and nothing read it — which is why 17.0.0 (#4667, ADR-0049) retired it. The new one is object-scoped and has a reader.

2. Object sub-key table — SKILL.md:163 (_views row)

Beforelabel, description, emptyState.title / emptyState.message
After…unchanged…, bulkActions.{def_name} (label, confirmText, confirmLabel, params)

packages/spec/src/system/translation.zod.ts:281bulkActions is a record under _views.VIEW, keyed by BulkActionDefSchema.name. The four keys named are the ones the def actually declares; successMessage and description carry guidance rejecting them, and per-param help is spelled help, not helpText. The row names the declared keys so an author cannot reach for the two that parse-fail.

3. Top-level group inventory — SKILL.md:168-170

Beforeapps, messages, globalActions, dashboards, pages, flows, settings, metadataForms, settingsCommon
Aftersame list with datasets inserted after dashboards

packages/spec/src/system/translation.zod.ts:770datasets is a top-level group on TranslationDataSchema, resolved by translateDataset. The inventory was incomplete by exactly one entry; it is corrected in place as a list item, not promoted to a section of its own.

4. Unknown-target inventory — SKILL.md:201

Before…object, field, view, tab, action, param, section, app, nav item, dashboard, widget or flow screen…
After…object, field, view, tab, action, param, section, validation rule, app, nav item, dashboard, widget or flow screen…

packages/lint/src/validate-translation-references.ts:629-634 registers _validations.RULE targets from objects[].validations[].name (the code names #14253 at the site). The lint side already reports an unknown validation-rule target; the enumeration simply had not been extended. One word.

premise_false — claims checked and left alone because they are true on main

  • SKILL.md:387-391, the --objects-only divergence — true as written. --objects-only is on by default and --metadata-forms is orthogonal to it; packages/cli/test/i18n-extract-emitted-files.test.ts pins all three directions, including the regression where metadata-forms emission was briefly derived from --objects-only. Untouched — this is what skills(i18n): optimization flight — drop the restated overview, the zero-usage layout and plugin bootstrap and the duplicate template; teach the generated-bundle shape and the --objects-only divergence (net −2,069 tokens) #14574 taught and it stays consistent.
  • SKILL.md:380-386, the generated-bundle file shape — true as written. packages/platform-objects/src/apps/translations/ holds LOCALE.objects.generated.ts, LOCALE.metadata-forms.generated.ts and LOCALE.source-hashes.generated.ts exactly as described. Untouched.
  • SKILL.md:407-436, the withSourceFallback wrapping and the kernel:ready load — true as written, and re-verified against packages/platform-objects/src/apps/translations/index.ts. Untouched.
  • SKILL.md:462-465, the retired o.* sibling-key redirect list — correct to omitvalidationMessages. translation.zod.ts:470-480 defines LEGACY_OBJECT_FIRST_KEYS, and the schema comment at :532 states explicitly that validationMessages is "not a legacy object-first key". Adding it there would have been a factually wrong placement. Untouched.
  • SKILL.md:281-285, the os i18n check surface list — the groups it enumerates are the groups the walker actually visits (packages/cli/src/utils/i18n-coverage.ts:185-218, COVERAGE_SOURCE). Untouched. See the note below.

Known gap, already filed — not folded into this PR

os i18n extract and os i18n check walk none of the three families #14253 added: packages/cli/src/utils/i18n-extract.ts emits no _validations, bulkActions or datasets path (its emitted roots are enumerated at :579, :649, :786-862, :928-1001, :1106-1173), and COVERAGE_SOURCE has no bucket for any of them. That is #14376 (open, domain:cli, pm:dispatched), already tracking exactly this. A caveat sentence about it was drafted into this file and then removed: it is the CLI's gap, #14376 is the place it is being fixed, and paying for it out of this file's budget would have made the net delta positive for a fact that belongs to another card.

Token budget — every added token paid by a deletion in the same file

node scripts/check-skills-token-ratchet.mjs, its own verdict line, before and after:

FileBeforeAfterDeltaCeilingHeadroom after
skills/objectstack-i18n/SKILL.md46784685+7 (+0.15%)6338 (unchanged)1653
whole shipped bundle157662157669+7

Lines: 496 to 493 (-3). Diff: 7 insertions, 10 deletions. The blockquote deletion is the payment — its substance is carried by the _validations row, so nothing true was dropped to make the budget.

Gates

Head d2fa7a18fdb378722342f1cdba9e266f5bb797cd. Family re-derived after the last edit with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (change set: 1 path, skills/objectstack-i18n/SKILL.md; merge base 20b883918), run under scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-14377. Every exit code captured before any pipe.

GateExitIts own verdict line
check-skills-token-ratchet0✓ 31 authored bundle file(s) within their ceilings; 11 generator-owned file(s) measured, not ratcheted.
check:skill-identifier-liveness0OK — Leg 1: 465 citation(s) over 41 published file(s) checked against 93441 implementation word tokens (3 ledgered exemption(s)); Leg 2: 8 registered exhaustive section(s), 0 ledgered gap(s).
check:skill-docs0✅ Skill docs in sync
check:skill-compatibility0✓ 11 SKILL.md file(s) reconciled against 79 workspace packages
check:skill-frame-sync0✓ 4 copies of the decision frame are structurally isomorphic across 3 files
check:role-word0OK (self-test + scan; baseline unchanged, no --update)
check:corpus-claim-drift0OK
check:doc-authoring0✓ 14500 customer-facing string(s) across 710 spec sources clean
check:nul-bytes0OK (scanned 7995 text file(s); no raw ASCII control bytes)
check:agent-test-spelling0✓ 0 violations — 425 file(s)
check:pm-governed-merges0✓ self-test: 243 assertions
check-ci-filter-parity0OK: all 130 declared cross-package glob(s) (92 unique) are covered
check-cross-package-test-inputs / check:cross-package-test-inputs0 / 0OK: 25 package(s) read outside themselves, all declared
check-shard-attestation0✓ 2 aggregate gate(s) count 3 declared leg(s) across 3 attesting job(s).

check:skill-docs was green without regenerating anything — the frontmatter did not change, so skills/README.md and content/docs/ai/skills-reference.mdx are not in this diff.

NOT MEASURED (prerequisite refusals, never content findings)

GateExitPrerequisite it named
check-test-completeness3PREREQUISITE NOT MET — it grades a saved turbo run test log and no log was named. Its own text says the family derives it with no argument and "the local reading for this gate is NOT MEASURED". CI tees the log and passes the path.
check:doc-formula-expressions3PREREQUISITE NOT MET — the workspace package @objectstack/lint is not built. (After building @objectstack/formula the refusal advanced to the next unbuilt package in the chain.)
check:skill-examples1PREREQUISITE NOT MET in substance — packages/client-react/dist holds no .d.ts declarations — the package is not built. Exit 1 here is the refusal branch, not a finding.

Declared narrowing, with the measurement behind it. The shared verify lock was continuously held by four sibling agents (two 9-minute queue timeouts at exit 99 and one foreground cap kill at 10 minutes, with three waiters ahead each time), so the remaining dependency builds those three gates need were not run locally. Rather than assert the narrowing, it is measured: for the two gates whose input is this file,

  • check:skill-examples type-checks os:check-marked TypeScript blocks. os:check marker count in this file is 2 before and 2 after; fenced-block delimiter count is 24 before and 24 after; and git diff -U0 contains no added or removed line that is a fence or an os:check marker. The gate's own census reports 224 blocks on the skills + docs surface — this file's contribution to it is byte-identical to origin/main.
  • check:doc-formula-expressions flags CEL field-rule expressions with rejected root identifiers across .claude, docs, skills, content. Every added line is reproduced in section 2-4 above; none contains expression syntax (no record., no current_user, no operator, no formula).

So neither gate's verdict can differ from main's on account of this diff. Both run on this PR in CI regardless, which is where the authoritative reading is.

Landing

skills/** is a governed surface (Prime Directive #14). This PR stays draft: no merge, no queue, no auto-merge, no ready flip. It is a PR visibly awaiting the maintainer's own merge. Labels: skip-changeset (verified against scripts/check-empty-changeset.mjs:357, which enumerates skills/ among the paths that release nothing) and needs:contract-review (the corrected rows state which keys the i18n resolver honours — a platform contract claim).

🤖 Generated with Claude Code

https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1


Generated by Claude Code

`objects.OBJECT._validations.RULE.message` is object-scoped and resolved on the
write path by the ObjectQL rule evaluator through the existing i18n service, so
the blockquote teaching that validation messages are not a translation group is
false on main. Correct it and the two neighbouring inventories it sits in:
`_views.VIEW.bulkActions` on the object sub-key table and `datasets` in the
top-level group list, plus the validation-rule target `os validate` / `os lint`
already report but the list omitted.
A correction, not an expansion: the retired-key blockquote's substance moves into
the `_validations` row it belongs in, which pays the three additions in the same
file. Net +7 tokens (4678 to 4685), 3 lines shorter; ceiling unchanged and the
headroom left un-re-locked by #14574 is not spent.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
@github-actionsgithub-actionsBot added size/s documentation Improvements or additions to documentation labels Sep 2, 2026
@os-litantos-litant added skip-changeset PR has no user-facing published change; bypasses the changeset gate needs:contract-review and removed documentation Improvements or additions to documentation size/s labels Sep 2, 2026 — with Claude
@github-actionsgithub-actionsBot added size/s documentation Improvements or additions to documentation labels Sep 2, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review September 2, 2026 22:49
@os-zhuang
os-zhuang added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 511e2f6Sep 2, 2026
35 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-14377-i18n-validations-group branch September 2, 2026 23:10
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Published skill objectstack-i18n teaches that validation messages are not a translation group — false once #14253 lands

3 participants

@os-litant@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

skills(i18n): validation messages are a translation group since #14253 — correct the claim and its neighbours (net +7 tokens) - #14664

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-14377-i18n-validations-group
Sep 2, 2026
Merged

skills(i18n): validation messages are a translation group since #14253 — correct the claim and its neighbours (net +7 tokens)#14664
os-zhuang merged 2 commits into
mainfrom
claude/issue-14377-i18n-validations-group

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#14377

skills/objectstack-i18n/SKILL.md taught that validation messages are not a translation group. That was true when written and is false on main today: #14253 (landed as b2eab95, "i18n: give bulk-action defs, custom validation messages and dataset labels a bundle key") added three key families at once, and the published catalog ships to codebases this repo cannot see. This corrects the claim and the two neighbouring inventories it sits in.

A correction, not an expansion — maintainer ruling 2026-08-21, quoted verbatim and untranslated: 「对外发布的 skills 是整个平台的最大价值,尤其要整体考虑和评估。」「……不能为了一个小功能扩写很多。」 Three new key families did not license three new sections: the diff is one added table row, one clause inside an existing row, one word in each of two existing prose lists, and one deleted blockquote. No new section. No ceiling change. The ratchet script is untouched, and the headroom #14574 left un-re-locked is not spent.

Per-claim: where, before, after

Line numbers are on origin/main at merge base 20b88391.

1. The claim itself — SKILL.md:172 (blockquote, 3 lines)

Before

validationMessages is not a translation group — it was removed in spec 17.0.0.
Author the message on the rule itself (object.validations[].message), which the
engine returns on every rejected write.

After — deleted; its substance moves into the new _validations row below, which is where a reader looking up an object sub-key will actually find it.

Why it is false.objects.OBJECT._validations.RULE.message is a declared group, object-scoped, with a reader:

  • packages/spec/src/system/translation.zod.ts:427_validations: z.record(...) on ObjectTranslationDataSchema, sibling to _views / _actions / _tabs. message is the only declared key; label, description, condition and when each carry guidance rejecting them, so the surface is one key by measurement, not by omission.
  • packages/spec/src/system/i18n-resolver.ts:1862objectValidationMessageKey(objectName, ruleName) returns objects.${objectName}._validations.${ruleName}.message.
  • packages/objectql/src/validation/rule-validator.ts:2107 — the ObjectQL rule evaluator calls it on the write path and resolves through the existing ValidationMessageContext.translate hook (the i18nService channel that has localized built-in messages since Field validation messages are hardcoded English + API field name — penalty_amount must be ≥ 0 reaches end users verbatim #3957). Authored message on a miss, translation on a hit, no interpolation.

The schema's own note (translation.zod.ts:404-410) states the distinction the old blockquote lost: this is notvalidationMessages returning. That group was keyed by rule name at the top level, so it could not tell two objects' rules apart, and nothing read it — which is why 17.0.0 (#4667, ADR-0049) retired it. The new one is object-scoped and has a reader.

2. Object sub-key table — SKILL.md:163 (_views row)

Beforelabel, description, emptyState.title / emptyState.message
After…unchanged…, bulkActions.{def_name} (label, confirmText, confirmLabel, params)

packages/spec/src/system/translation.zod.ts:281bulkActions is a record under _views.VIEW, keyed by BulkActionDefSchema.name. The four keys named are the ones the def actually declares; successMessage and description carry guidance rejecting them, and per-param help is spelled help, not helpText. The row names the declared keys so an author cannot reach for the two that parse-fail.

3. Top-level group inventory — SKILL.md:168-170

Beforeapps, messages, globalActions, dashboards, pages, flows, settings, metadataForms, settingsCommon
Aftersame list with datasets inserted after dashboards

packages/spec/src/system/translation.zod.ts:770datasets is a top-level group on TranslationDataSchema, resolved by translateDataset. The inventory was incomplete by exactly one entry; it is corrected in place as a list item, not promoted to a section of its own.

4. Unknown-target inventory — SKILL.md:201

Before…object, field, view, tab, action, param, section, app, nav item, dashboard, widget or flow screen…
After…object, field, view, tab, action, param, section, validation rule, app, nav item, dashboard, widget or flow screen…

packages/lint/src/validate-translation-references.ts:629-634 registers _validations.RULE targets from objects[].validations[].name (the code names #14253 at the site). The lint side already reports an unknown validation-rule target; the enumeration simply had not been extended. One word.

premise_false — claims checked and left alone because they are true on main

  • SKILL.md:387-391, the --objects-only divergence — true as written. --objects-only is on by default and --metadata-forms is orthogonal to it; packages/cli/test/i18n-extract-emitted-files.test.ts pins all three directions, including the regression where metadata-forms emission was briefly derived from --objects-only. Untouched — this is what skills(i18n): optimization flight — drop the restated overview, the zero-usage layout and plugin bootstrap and the duplicate template; teach the generated-bundle shape and the --objects-only divergence (net −2,069 tokens) #14574 taught and it stays consistent.
  • SKILL.md:380-386, the generated-bundle file shape — true as written. packages/platform-objects/src/apps/translations/ holds LOCALE.objects.generated.ts, LOCALE.metadata-forms.generated.ts and LOCALE.source-hashes.generated.ts exactly as described. Untouched.
  • SKILL.md:407-436, the withSourceFallback wrapping and the kernel:ready load — true as written, and re-verified against packages/platform-objects/src/apps/translations/index.ts. Untouched.
  • SKILL.md:462-465, the retired o.* sibling-key redirect list — correct to omitvalidationMessages. translation.zod.ts:470-480 defines LEGACY_OBJECT_FIRST_KEYS, and the schema comment at :532 states explicitly that validationMessages is "not a legacy object-first key". Adding it there would have been a factually wrong placement. Untouched.
  • SKILL.md:281-285, the os i18n check surface list — the groups it enumerates are the groups the walker actually visits (packages/cli/src/utils/i18n-coverage.ts:185-218, COVERAGE_SOURCE). Untouched. See the note below.

Known gap, already filed — not folded into this PR

os i18n extract and os i18n check walk none of the three families #14253 added: packages/cli/src/utils/i18n-extract.ts emits no _validations, bulkActions or datasets path (its emitted roots are enumerated at :579, :649, :786-862, :928-1001, :1106-1173), and COVERAGE_SOURCE has no bucket for any of them. That is #14376 (open, domain:cli, pm:dispatched), already tracking exactly this. A caveat sentence about it was drafted into this file and then removed: it is the CLI's gap, #14376 is the place it is being fixed, and paying for it out of this file's budget would have made the net delta positive for a fact that belongs to another card.

Token budget — every added token paid by a deletion in the same file

node scripts/check-skills-token-ratchet.mjs, its own verdict line, before and after:

FileBeforeAfterDeltaCeilingHeadroom after
skills/objectstack-i18n/SKILL.md46784685+7 (+0.15%)6338 (unchanged)1653
whole shipped bundle157662157669+7

Lines: 496 to 493 (-3). Diff: 7 insertions, 10 deletions. The blockquote deletion is the payment — its substance is carried by the _validations row, so nothing true was dropped to make the budget.

Gates

Head d2fa7a18fdb378722342f1cdba9e266f5bb797cd. Family re-derived after the last edit with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (change set: 1 path, skills/objectstack-i18n/SKILL.md; merge base 20b883918), run under scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-14377. Every exit code captured before any pipe.

GateExitIts own verdict line
check-skills-token-ratchet0✓ 31 authored bundle file(s) within their ceilings; 11 generator-owned file(s) measured, not ratcheted.
check:skill-identifier-liveness0OK — Leg 1: 465 citation(s) over 41 published file(s) checked against 93441 implementation word tokens (3 ledgered exemption(s)); Leg 2: 8 registered exhaustive section(s), 0 ledgered gap(s).
check:skill-docs0✅ Skill docs in sync
check:skill-compatibility0✓ 11 SKILL.md file(s) reconciled against 79 workspace packages
check:skill-frame-sync0✓ 4 copies of the decision frame are structurally isomorphic across 3 files
check:role-word0OK (self-test + scan; baseline unchanged, no --update)
check:corpus-claim-drift0OK
check:doc-authoring0✓ 14500 customer-facing string(s) across 710 spec sources clean
check:nul-bytes0OK (scanned 7995 text file(s); no raw ASCII control bytes)
check:agent-test-spelling0✓ 0 violations — 425 file(s)
check:pm-governed-merges0✓ self-test: 243 assertions
check-ci-filter-parity0OK: all 130 declared cross-package glob(s) (92 unique) are covered
check-cross-package-test-inputs / check:cross-package-test-inputs0 / 0OK: 25 package(s) read outside themselves, all declared
check-shard-attestation0✓ 2 aggregate gate(s) count 3 declared leg(s) across 3 attesting job(s).

check:skill-docs was green without regenerating anything — the frontmatter did not change, so skills/README.md and content/docs/ai/skills-reference.mdx are not in this diff.

NOT MEASURED (prerequisite refusals, never content findings)

GateExitPrerequisite it named
check-test-completeness3PREREQUISITE NOT MET — it grades a saved turbo run test log and no log was named. Its own text says the family derives it with no argument and "the local reading for this gate is NOT MEASURED". CI tees the log and passes the path.
check:doc-formula-expressions3PREREQUISITE NOT MET — the workspace package @objectstack/lint is not built. (After building @objectstack/formula the refusal advanced to the next unbuilt package in the chain.)
check:skill-examples1PREREQUISITE NOT MET in substance — packages/client-react/dist holds no .d.ts declarations — the package is not built. Exit 1 here is the refusal branch, not a finding.

Declared narrowing, with the measurement behind it. The shared verify lock was continuously held by four sibling agents (two 9-minute queue timeouts at exit 99 and one foreground cap kill at 10 minutes, with three waiters ahead each time), so the remaining dependency builds those three gates need were not run locally. Rather than assert the narrowing, it is measured: for the two gates whose input is this file,

  • check:skill-examples type-checks os:check-marked TypeScript blocks. os:check marker count in this file is 2 before and 2 after; fenced-block delimiter count is 24 before and 24 after; and git diff -U0 contains no added or removed line that is a fence or an os:check marker. The gate's own census reports 224 blocks on the skills + docs surface — this file's contribution to it is byte-identical to origin/main.
  • check:doc-formula-expressions flags CEL field-rule expressions with rejected root identifiers across .claude, docs, skills, content. Every added line is reproduced in section 2-4 above; none contains expression syntax (no record., no current_user, no operator, no formula).

So neither gate's verdict can differ from main's on account of this diff. Both run on this PR in CI regardless, which is where the authoritative reading is.

Landing

skills/** is a governed surface (Prime Directive #14). This PR stays draft: no merge, no queue, no auto-merge, no ready flip. It is a PR visibly awaiting the maintainer's own merge. Labels: skip-changeset (verified against scripts/check-empty-changeset.mjs:357, which enumerates skills/ among the paths that release nothing) and needs:contract-review (the corrected rows state which keys the i18n resolver honours — a platform contract claim).

🤖 Generated with Claude Code

https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1


Generated by Claude Code

`objects.OBJECT._validations.RULE.message` is object-scoped and resolved on the
write path by the ObjectQL rule evaluator through the existing i18n service, so
the blockquote teaching that validation messages are not a translation group is
false on main. Correct it and the two neighbouring inventories it sits in:
`_views.VIEW.bulkActions` on the object sub-key table and `datasets` in the
top-level group list, plus the validation-rule target `os validate` / `os lint`
already report but the list omitted.
A correction, not an expansion: the retired-key blockquote's substance moves into
the `_validations` row it belongs in, which pays the three additions in the same
file. Net +7 tokens (4678 to 4685), 3 lines shorter; ceiling unchanged and the
headroom left un-re-locked by #14574 is not spent.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
@github-actionsgithub-actionsBot added size/s documentation Improvements or additions to documentation labels Sep 2, 2026
@os-litantos-litant added skip-changeset PR has no user-facing published change; bypasses the changeset gate needs:contract-review and removed documentation Improvements or additions to documentation size/s labels Sep 2, 2026 — with Claude
@github-actionsgithub-actionsBot added size/s documentation Improvements or additions to documentation labels Sep 2, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review September 2, 2026 22:49
@os-zhuang
os-zhuang added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 511e2f6Sep 2, 2026
35 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-14377-i18n-validations-group branch September 2, 2026 23:10
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Published skill objectstack-i18n teaches that validation messages are not a translation group — false once #14253 lands

3 participants

@os-litant@os-zhuang@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

skills(i18n): validation messages are a translation group since #14253 — correct the claim and its neighbours (net +7 tokens) - #14664

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-14377-i18n-validations-group
Sep 2, 2026
Merged

skills(i18n): validation messages are a translation group since #14253 — correct the claim and its neighbours (net +7 tokens)#14664
os-zhuang merged 2 commits into
mainfrom
claude/issue-14377-i18n-validations-group

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#14377

skills/objectstack-i18n/SKILL.md taught that validation messages are not a translation group. That was true when written and is false on main today: #14253 (landed as b2eab95, "i18n: give bulk-action defs, custom validation messages and dataset labels a bundle key") added three key families at once, and the published catalog ships to codebases this repo cannot see. This corrects the claim and the two neighbouring inventories it sits in.

A correction, not an expansion — maintainer ruling 2026-08-21, quoted verbatim and untranslated: 「对外发布的 skills 是整个平台的最大价值,尤其要整体考虑和评估。」「……不能为了一个小功能扩写很多。」 Three new key families did not license three new sections: the diff is one added table row, one clause inside an existing row, one word in each of two existing prose lists, and one deleted blockquote. No new section. No ceiling change. The ratchet script is untouched, and the headroom #14574 left un-re-locked is not spent.

Per-claim: where, before, after

Line numbers are on origin/main at merge base 20b88391.

1. The claim itself — SKILL.md:172 (blockquote, 3 lines)

Before

validationMessages is not a translation group — it was removed in spec 17.0.0.
Author the message on the rule itself (object.validations[].message), which the
engine returns on every rejected write.

After — deleted; its substance moves into the new _validations row below, which is where a reader looking up an object sub-key will actually find it.

Why it is false.objects.OBJECT._validations.RULE.message is a declared group, object-scoped, with a reader:

  • packages/spec/src/system/translation.zod.ts:427_validations: z.record(...) on ObjectTranslationDataSchema, sibling to _views / _actions / _tabs. message is the only declared key; label, description, condition and when each carry guidance rejecting them, so the surface is one key by measurement, not by omission.
  • packages/spec/src/system/i18n-resolver.ts:1862objectValidationMessageKey(objectName, ruleName) returns objects.${objectName}._validations.${ruleName}.message.
  • packages/objectql/src/validation/rule-validator.ts:2107 — the ObjectQL rule evaluator calls it on the write path and resolves through the existing ValidationMessageContext.translate hook (the i18nService channel that has localized built-in messages since Field validation messages are hardcoded English + API field name — penalty_amount must be ≥ 0 reaches end users verbatim #3957). Authored message on a miss, translation on a hit, no interpolation.

The schema's own note (translation.zod.ts:404-410) states the distinction the old blockquote lost: this is notvalidationMessages returning. That group was keyed by rule name at the top level, so it could not tell two objects' rules apart, and nothing read it — which is why 17.0.0 (#4667, ADR-0049) retired it. The new one is object-scoped and has a reader.

2. Object sub-key table — SKILL.md:163 (_views row)

Beforelabel, description, emptyState.title / emptyState.message
After…unchanged…, bulkActions.{def_name} (label, confirmText, confirmLabel, params)

packages/spec/src/system/translation.zod.ts:281bulkActions is a record under _views.VIEW, keyed by BulkActionDefSchema.name. The four keys named are the ones the def actually declares; successMessage and description carry guidance rejecting them, and per-param help is spelled help, not helpText. The row names the declared keys so an author cannot reach for the two that parse-fail.

3. Top-level group inventory — SKILL.md:168-170

Beforeapps, messages, globalActions, dashboards, pages, flows, settings, metadataForms, settingsCommon
Aftersame list with datasets inserted after dashboards

packages/spec/src/system/translation.zod.ts:770datasets is a top-level group on TranslationDataSchema, resolved by translateDataset. The inventory was incomplete by exactly one entry; it is corrected in place as a list item, not promoted to a section of its own.

4. Unknown-target inventory — SKILL.md:201

Before…object, field, view, tab, action, param, section, app, nav item, dashboard, widget or flow screen…
After…object, field, view, tab, action, param, section, validation rule, app, nav item, dashboard, widget or flow screen…

packages/lint/src/validate-translation-references.ts:629-634 registers _validations.RULE targets from objects[].validations[].name (the code names #14253 at the site). The lint side already reports an unknown validation-rule target; the enumeration simply had not been extended. One word.

premise_false — claims checked and left alone because they are true on main

  • SKILL.md:387-391, the --objects-only divergence — true as written. --objects-only is on by default and --metadata-forms is orthogonal to it; packages/cli/test/i18n-extract-emitted-files.test.ts pins all three directions, including the regression where metadata-forms emission was briefly derived from --objects-only. Untouched — this is what skills(i18n): optimization flight — drop the restated overview, the zero-usage layout and plugin bootstrap and the duplicate template; teach the generated-bundle shape and the --objects-only divergence (net −2,069 tokens) #14574 taught and it stays consistent.
  • SKILL.md:380-386, the generated-bundle file shape — true as written. packages/platform-objects/src/apps/translations/ holds LOCALE.objects.generated.ts, LOCALE.metadata-forms.generated.ts and LOCALE.source-hashes.generated.ts exactly as described. Untouched.
  • SKILL.md:407-436, the withSourceFallback wrapping and the kernel:ready load — true as written, and re-verified against packages/platform-objects/src/apps/translations/index.ts. Untouched.
  • SKILL.md:462-465, the retired o.* sibling-key redirect list — correct to omitvalidationMessages. translation.zod.ts:470-480 defines LEGACY_OBJECT_FIRST_KEYS, and the schema comment at :532 states explicitly that validationMessages is "not a legacy object-first key". Adding it there would have been a factually wrong placement. Untouched.
  • SKILL.md:281-285, the os i18n check surface list — the groups it enumerates are the groups the walker actually visits (packages/cli/src/utils/i18n-coverage.ts:185-218, COVERAGE_SOURCE). Untouched. See the note below.

Known gap, already filed — not folded into this PR

os i18n extract and os i18n check walk none of the three families #14253 added: packages/cli/src/utils/i18n-extract.ts emits no _validations, bulkActions or datasets path (its emitted roots are enumerated at :579, :649, :786-862, :928-1001, :1106-1173), and COVERAGE_SOURCE has no bucket for any of them. That is #14376 (open, domain:cli, pm:dispatched), already tracking exactly this. A caveat sentence about it was drafted into this file and then removed: it is the CLI's gap, #14376 is the place it is being fixed, and paying for it out of this file's budget would have made the net delta positive for a fact that belongs to another card.

Token budget — every added token paid by a deletion in the same file

node scripts/check-skills-token-ratchet.mjs, its own verdict line, before and after:

FileBeforeAfterDeltaCeilingHeadroom after
skills/objectstack-i18n/SKILL.md46784685+7 (+0.15%)6338 (unchanged)1653
whole shipped bundle157662157669+7

Lines: 496 to 493 (-3). Diff: 7 insertions, 10 deletions. The blockquote deletion is the payment — its substance is carried by the _validations row, so nothing true was dropped to make the budget.

Gates

Head d2fa7a18fdb378722342f1cdba9e266f5bb797cd. Family re-derived after the last edit with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (change set: 1 path, skills/objectstack-i18n/SKILL.md; merge base 20b883918), run under scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-14377. Every exit code captured before any pipe.

GateExitIts own verdict line
check-skills-token-ratchet0✓ 31 authored bundle file(s) within their ceilings; 11 generator-owned file(s) measured, not ratcheted.
check:skill-identifier-liveness0OK — Leg 1: 465 citation(s) over 41 published file(s) checked against 93441 implementation word tokens (3 ledgered exemption(s)); Leg 2: 8 registered exhaustive section(s), 0 ledgered gap(s).
check:skill-docs0✅ Skill docs in sync
check:skill-compatibility0✓ 11 SKILL.md file(s) reconciled against 79 workspace packages
check:skill-frame-sync0✓ 4 copies of the decision frame are structurally isomorphic across 3 files
check:role-word0OK (self-test + scan; baseline unchanged, no --update)
check:corpus-claim-drift0OK
check:doc-authoring0✓ 14500 customer-facing string(s) across 710 spec sources clean
check:nul-bytes0OK (scanned 7995 text file(s); no raw ASCII control bytes)
check:agent-test-spelling0✓ 0 violations — 425 file(s)
check:pm-governed-merges0✓ self-test: 243 assertions
check-ci-filter-parity0OK: all 130 declared cross-package glob(s) (92 unique) are covered
check-cross-package-test-inputs / check:cross-package-test-inputs0 / 0OK: 25 package(s) read outside themselves, all declared
check-shard-attestation0✓ 2 aggregate gate(s) count 3 declared leg(s) across 3 attesting job(s).

check:skill-docs was green without regenerating anything — the frontmatter did not change, so skills/README.md and content/docs/ai/skills-reference.mdx are not in this diff.

NOT MEASURED (prerequisite refusals, never content findings)

GateExitPrerequisite it named
check-test-completeness3PREREQUISITE NOT MET — it grades a saved turbo run test log and no log was named. Its own text says the family derives it with no argument and "the local reading for this gate is NOT MEASURED". CI tees the log and passes the path.
check:doc-formula-expressions3PREREQUISITE NOT MET — the workspace package @objectstack/lint is not built. (After building @objectstack/formula the refusal advanced to the next unbuilt package in the chain.)
check:skill-examples1PREREQUISITE NOT MET in substance — packages/client-react/dist holds no .d.ts declarations — the package is not built. Exit 1 here is the refusal branch, not a finding.

Declared narrowing, with the measurement behind it. The shared verify lock was continuously held by four sibling agents (two 9-minute queue timeouts at exit 99 and one foreground cap kill at 10 minutes, with three waiters ahead each time), so the remaining dependency builds those three gates need were not run locally. Rather than assert the narrowing, it is measured: for the two gates whose input is this file,

  • check:skill-examples type-checks os:check-marked TypeScript blocks. os:check marker count in this file is 2 before and 2 after; fenced-block delimiter count is 24 before and 24 after; and git diff -U0 contains no added or removed line that is a fence or an os:check marker. The gate's own census reports 224 blocks on the skills + docs surface — this file's contribution to it is byte-identical to origin/main.
  • check:doc-formula-expressions flags CEL field-rule expressions with rejected root identifiers across .claude, docs, skills, content. Every added line is reproduced in section 2-4 above; none contains expression syntax (no record., no current_user, no operator, no formula).

So neither gate's verdict can differ from main's on account of this diff. Both run on this PR in CI regardless, which is where the authoritative reading is.

Landing

skills/** is a governed surface (Prime Directive #14). This PR stays draft: no merge, no queue, no auto-merge, no ready flip. It is a PR visibly awaiting the maintainer's own merge. Labels: skip-changeset (verified against scripts/check-empty-changeset.mjs:357, which enumerates skills/ among the paths that release nothing) and needs:contract-review (the corrected rows state which keys the i18n resolver honours — a platform contract claim).

🤖 Generated with Claude Code

https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1


Generated by Claude Code

`objects.OBJECT._validations.RULE.message` is object-scoped and resolved on the
write path by the ObjectQL rule evaluator through the existing i18n service, so
the blockquote teaching that validation messages are not a translation group is
false on main. Correct it and the two neighbouring inventories it sits in:
`_views.VIEW.bulkActions` on the object sub-key table and `datasets` in the
top-level group list, plus the validation-rule target `os validate` / `os lint`
already report but the list omitted.
A correction, not an expansion: the retired-key blockquote's substance moves into
the `_validations` row it belongs in, which pays the three additions in the same
file. Net +7 tokens (4678 to 4685), 3 lines shorter; ceiling unchanged and the
headroom left un-re-locked by #14574 is not spent.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
@github-actionsgithub-actionsBot added size/s documentation Improvements or additions to documentation labels Sep 2, 2026
@os-litantos-litant added skip-changeset PR has no user-facing published change; bypasses the changeset gate needs:contract-review and removed documentation Improvements or additions to documentation size/s labels Sep 2, 2026 — with Claude
@github-actionsgithub-actionsBot added size/s documentation Improvements or additions to documentation labels Sep 2, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review September 2, 2026 22:49
@os-zhuang
os-zhuang added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 511e2f6Sep 2, 2026
35 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-14377-i18n-validations-group branch September 2, 2026 23:10
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Published skill objectstack-i18n teaches that validation messages are not a translation group — false once #14253 lands

3 participants

@os-litant@os-zhuang@claude