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 pathFor 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.
Summary
lintLivenessPropertiesregisters{ type: 'translation', key: 'translations' }and then walksstack.translationsitems as if each were aTranslationItem— readingitem['flows'],item['objects'], … off the top level. Butstack.translationsisz.array(TranslationBundleSchema)(packages/spec/src/stack.zod.ts:275): each item is a locale-keyed bundle, so its top-level keys areen/zh-CN/ja-JP/ …, and every warned property lookup misses.Net effect: the whole
translationliveness ledger is silent for file-authored bundles — the only way apps author translations today. It is the exact failure the surrounding code comment names: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.
flowsis the ledger's only currently-warnedtranslationproperty (status: planned,authorWarn: true, with anauthorHintthat says no shipped runner reads the group). Injected aflows:section intosrc/translations/zh-CN.ts, ranobjectstack lint --json, restored viatrap: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
flows(a top-level, dotless path)checkItemtakes the[item[path]]branch, i.e.bundle['flows']— and aTranslationBundlehas noflowskey 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
translationmetadata item haslocale+ groups at the top level (which is the shape this walk assumes), while the file-authored stack collection isRecord<locale, TranslationData>.Consequence
An app author who reaches for a
plannedtranslation group gets silence — no warning, no error, a greenos lint— and ships translations that render nowhere. That is strictly worse than the ledger being absent, because the ledger's stated contract is thatauthorWarnis what tells the author. Measured today this affectsflows(the only warned row); it will affect every futureplannedtranslation group the same way.Suggested fix
Give the
translationtype a bespoke walk, the wayobjectalready has one for its field nesting: for each bundle instack.translations, iterate its locale entries andcheckItemtheTranslationDataunder 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 aTranslationItempasses on the broken walk.Worth deciding at the same time whether runtime-authored
translationitems 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
flowsrow'sauthorWarnnever reached the app author who would have benefited from it.No labels applied — routing is triage's call.