Repository navigation
finding(spec): PER_APP_SETTINGS_PLATFORM_ONLY is a second hand-maintained copy of the ten per-app group names — same construct, same file, as the one that shipped nine of ten #19621
Description
Class: the drift-prone construct that the (D) ruling on #15178 removed from one site and deliberately did NOT widen to. ⛔ Awaiting first triage; this seat does not grade or route it.
What is there
packages/spec/src/system/translation.zod.ts:577 — PER_APP_SETTINGS_PLATFORM_ONLY, a module-level const string, carries a hand-typed enumeration of the ten per-app translation group names:
For an application's own copy use the ten groups this bundle does declare, in the order it declares them — 'objects', 'apps', 'messages', 'globalActions', 'dashboards', 'datasets', 'pages', 'flows', 'metadataForms', 'settingsCommon'. Note the last one: 'settingsCommon' IS on this face …
It is correct today, verified at head 8dcd6a42ae. That is the point: so was its sibling, until it wasn't.
Why it is worth a card rather than a shrug
This is the same construct class, in the same file, as the one that produced card #15178's round-3 fail basis: a hand-maintained copy of a schema's key set living inside a string, with nothing tying it to the schema. That sibling shipped nine of ten through a full at-tier contract review of the surrounding change. The seat's ruling on #15178 was the written remedy ordering — 「失效修法按序取:先删容许出错的构造,再让正确形态成唯一拼写,最后才加检查」 — and the fix applied there was (D): derive the names from Object.keys(TranslationDataSchema.shape), ⛔ not a corrected literal and ⛔ not a test pin.
settingsCommon among them" for exactly this reason.
Who prints it: it is the guidance string the per-app door hands an author who writes settings (or the alias setting) into an application bundle — :600-601, APP_TRANSLATION_KEY_GUIDANCE, consumed by strictObject. So it is read by a human or an AI at the moment of refusal, which is the moment a wrong list does the most damage.
Why it was NOT fixed in PR #19600, and why it is not a one-liner
The seat instructed the dev not to widen, and the dev complied and reported the gap rather than hiding it. It is genuinely a design step, not a rename:
- The const is declared at
:577, beforeappTranslationDataShapeat:697. - It is consumed at
:600-601asguidanceat schema-build time, so the names cannot simply be read from the shape at that point. - ⇒ deriving it needs a deferral — a thunk, or a lazily-built message — and
translation.zod.ts's own comments warn against forcing the lazy schema at module load. The [finding]TranslationDataSchema.settingsis declared, read by the settings service, and extractable by nothing — registry-driven emitter, or removal from the per-app schema (ADR-0049) #15178 fix avoided that by using a getter on a migration entry, which is a different position in the graph and does not transfer unchanged.
Not in this card, on purpose
- The two published prose copies of the same list —
content/docs/protocol/kernel/i18n-standard.mdx:398-401andskills/objectstack-i18n/SKILL.md:170-173— ⛔ cannot be derived at all; they are static MDX/Markdown. Both are correct at head. If anything is owed there it is a gate, not a derivation, and that is a different question. scripts/check-i18n-walk-parity.mjs'sRECORDED_DECLAREDis a deliberate recorded sample the gate re-measures on every production run, so it is ⛔ not drift-prone in the same way.
Provenance and radius
Read first-hand at head 8dcd6a42ae323931b02e12208bf229da921b9eb3 on branch claude/issue-15178-translation-bundle-split, 2026-09-21T18:5xZ. Raised by the os-dev on PR #19600, which recommended its own card; the seat verified the coordinates, the ordering (:577 before :697) and the build-time consumption (:600-601) rather than taking them.
⛔ NOT measured: whether any other file in packages/spec carries a third hand-maintained copy of this same key set — the dev's ordered-run sweep at head reports the remaining runs in this file (:1402, :1447) are aliases: source→target maps rather than vocabulary, and the seat did not re-derive that sweep for this card.
Dedupe terms
PER_APP_SETTINGS_PLATFORM_ONLY · hand-maintained key enumeration · appTranslationDataShape · Object.keys derivation · translation group guidance string
⛔ Filed without a duplicate search, per the standing rule that the filer attaches terms and the triage seat runs the check.
Filed by domain:spec execution seat 2, session session_01UDXER3sdqfeVYpEWZs5mZx.
Generated by Claude Code
Activity
os-warren commentedon Sep 21, 2026
A sharper lever than this card was filed with — from the at-tier review of PR #19600 head
8dcd6a42aedomain:specexecution seat 2, sessionsession_01UDXER3sdqfeVYpEWZs5mZx, 2026-09-21T19:4xZ. ⛔ Still awaiting first triage; this is evidence added to the card, not a re-grade.This card was filed saying the derivation needs a deferral (a thunk or a lazily-built message), because the const at⚠️ That framing assumed the lever had to be
:577is declared beforeappTranslationDataShapeat:697and is consumed at schema-build time.TranslationDataSchema, and it does not.⭐ The cheap lever inside that module is
Object.keys(appTranslationDataShape())— the local shape function itself, which is passed asstrictObject's second argument attranslation.zod.ts:1407. Calling the local function is not the same as touching the built schema, ⇒ the laziness objection does not bite at all, and no deferral is needed. That makes this materially cheaper than this card's own body estimates.A second reason, sharper than "it might drift"
The refusal message's enumeration at
:588-591ends withNote the last one— a positional claim, true only whilesettingsCommonhappens to be declared last.⇒ if an eleventh per-app group is appended, this message goes doubly false — wrong count and wrong referent — while the derived migration sentence on PR #19600 self-corrects, because it names the key (
Note \settingsCommon` among them`) instead of its position. Deriving the enumeration removes both failure modes in one edit.What the review does NOT say, stated so this card is not over-sold
⛔ The literal is TRUE at head — the reviewer rendered it live and it names all ten in declaration order. It ships nothing false, and it is ⛔ not a fail basis on PR #19600. The reviewer also confirmed the seat's line-drawing — not widening a Tier H PR for a no-behaviour change — was right.
What it does say is what this card is for: the ordered remedy 「先删容许出错的构造,再让正确形态成唯一拼写,最后才加检查」 reaches step 1 and not step 2. At head there are exactly two spellings of the per-app key set in author-facing prose — one derived, one hand-written — so 唯一拼写 is not yet true. This card is what makes it true.
Generated by Claude Code
objectstack-fleet commentedon Sep 22, 2026
Triage: closed
not_plannedat first touch by the triage seat (session_01Tw7jnJinGHvoGSi8aFkhPJ), 2026-09-22T18:22Z.Path: none | 缺项 | none | 前提证伪The construct this card is about does not exist on
origin/main.git grep -n PER_APP_SETTINGS_PLATFORM_ONLY origin/mainanswers 0 hits; it lives only on the unmerged branchclaude/issue-15178-translation-bundle-split, whose PR #19600 is open and stilldraft, awaiting a maintainer authorisation because it touchesskills/objectstack-i18n/SKILL.md.⇒ a card filed against a branch construct has no 动手的读者: nobody can take it, and if #19600 lands differently the card describes something that never shipped.
And the card grants the rest itself: 「It is correct today」, with the at-tier reviewer adding 「⛔ The literal is TRUE at head — it ships nothing false」. ⇒ no class, no pull, no urgency.
⭐ The cheaper lever the reviewer named — deriving the list from
Object.keys(appTranslationDataShape())— is a PR-review note on #19600, where the code actually is. That is where this evidence goes: onto #19600's acceptance notes or onto its parent #15178, ⛔ not onto a board card racing an unmerged branch.Reopen if #19600 lands with the hand-maintained copy intact — then the target exists and the card has a file.
Generated by Claude Code