Skip to content

lintLivenessProperties walks stack.translations as if each item were a TranslationItem — it is a locale-keyed bundle, so the whole translation ledger warns nobody #11288

Description

@os-zhuang

Summary

lintLivenessProperties registers { type: 'translation', key: 'translations' } and then walks stack.translations items as if each were a TranslationItem — reading item['flows'], item['objects'], … off the top level. But stack.translations is z.array(TranslationBundleSchema) (packages/spec/src/stack.zod.ts:275): each item is a locale-keyed bundle, so its top-level keys are en / zh-CN / ja-JP / …, and every warned property lookup misses.

Net effect: the whole translation liveness ledger is silent for file-authored bundles — the only way apps author translations today. It is the exact failure the surrounding code comment names:

A newly governed type needs its collection registered or its ledger warns nobody, which would leave the ledger correct and silent: the exact shape this lint exists to prevent.

Here the collection is registered; the shape underneath it is the mismatch.

Measurement (HotCRM @ a0bc5e8d, @objectstack/* 17.1.0)

Guarded ablation on a real app. flows is the ledger's only currently-warned translation property (status: planned, authorWarn: true, with an authorHint that says no shipped runner reads the group). Injected a flows: section into src/translations/zh-CN.ts, ran objectstack lint --json, restored via trap:

injected-marker count: 1 # mutation confirmed on disk, anchored on the injected text
flows-key count: 1
objectstack lint exit: 0
total issues: 91 | errors: 0 | warnings: 81
liveness findings naming flows: 0
i18n/missing-* count: 0

Baseline on the unmutated tree, same command: 91 total issues as well. The mutation is completely invisible — zero delta, and specifically zero liveness advisories.

Expected direction was "one new advisory". Measured direction was "no change at all", which is what turned this from a corroboration into a finding.

Why it misses

packages/lint/src/lint-liveness-properties.ts:

for(const{ type, key }ofTYPE_COLLECTIONS){constwarnMap=loadWarnMap(dir,type);if(warnMap.size===0)continue;for(constitemofasArray(stack[key])){checkItem(type,item,subject,warnMap,findings);// reads item[path] for a dotless path

For flows (a top-level, dotless path) checkItem takes the [item[path]] branch, i.e. bundle['flows'] — and a TranslationBundle has no flows key at any depth reachable that way; the groups live one level down, under each locale code.

The two doors #3778 bridged share the group vocabulary but not the container: the runtime translation metadata item has locale + groups at the top level (which is the shape this walk assumes), while the file-authored stack collection is Record<locale, TranslationData>.

Consequence

An app author who reaches for a planned translation group gets silence — no warning, no error, a green os lint — and ships translations that render nowhere. That is strictly worse than the ledger being absent, because the ledger's stated contract is that authorWarn is what tells the author. Measured today this affects flows (the only warned row); it will affect every future planned translation group the same way.

Suggested fix

Give the translation type a bespoke walk, the way object already has one for its field nesting: for each bundle in stack.translations, iterate its locale entries and checkItem the TranslationData under each, with a subject naming the locale (e.g. translation bundle · locale 'zh-CN'). A test should pin it against a locale-keyed fixture, since a fixture shaped like a TranslationItem passes on the broken walk.

Worth deciding at the same time whether runtime-authored translation items are reached at all by this lint, or only ever the stack collection — the ledger's rows cite both doors, and only one of them is walked here.

Back-link

Found while measuring objectstack-ai/hotcrm#1210 (HotCRM's screen-flow localization card), whose platform half is filed as #11287. Not a blocker for either — this is the reason the flows row's authorWarn never reached the app author who would have benefited from it.

No labels applied — routing is triage's call.

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions