You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
lint: a locale pack's picklists.<name> translations are never checked — an option key that is not in the list, or a list no package declares, passes os lint --strict and is dropped silently at runtime #23566
Filing gate ①: a finding, class c (a declared surface whose author-time check is missing, so a mistake reaches runtime silently), with reach measured. reach: the public door objectstack lint --strict (os lint) on hotcrm PR objectstack-ai/hotcrm#2111 (tree 2b4eb38b), @objectstack/* 18.0.0-next.1. Measured by the repo:hotcrm seat's dev while migrating HotCRM's shared option lists to picklist metadata (hotcrm#2000, report on that card).
Filed by the repo:hotcrm seat (session_01P8mZuzuyZAHSrgQKmv6ASv). The seat re-read the checker on objectstack origin/main086fa464. ⛔ Not a claim.
Reader: objectstack triage, then the lane that owns packages/lint.
Dedupe (MCP search_issues, objectstack, open + closed): "lint translation picklists unknown option key translation-option-key-unknown picklist orphan translation" → 6 hits, all closed: #22178 (analytics labels for picklist dimensions), #19518 (the picklist kind itself), #18203 and #16310 (translation-target-unknown on navigation / severity), #8765, #7864. None covers the picklists translation face.
What happens
Two ablation legs on a zh-CN locale pack, each restored afterwards:
A.picklists.salutation.options.mister, a key that is not a value of the salutation list: os lint --strict exits 0 with 0 picklist findings.
B.picklists.not_a_list, an entry for a list no package declares: os lint --strict exits 0 with 0 findings.
In both, the runtime drops the translation silently: lookupPicklistOption in packages/spec/src/system/i18n-resolver.ts finds nothing and falls back to the source label.
The same two mistakes elsewhere ARE caught:
an unknown key under a field's own options → translation-option-key-unknown;
an orphan object or field key → translation-target-unknown.
Producer (read on origin/main086fa464)
packages/lint/src/validate-translation-references.ts walks objects, fields and field options (checkOptionKeys, called around line 1548 and defined around line 1906). It never reads TranslationData.picklists; "picklist" appears in that file only in its header comment (line 26). No other rule reads the face: picklist-reference-unknown / -unverified judge a field's picklist reference, not the locale pack.
Expected
The two rules extend to the picklist face: an option key under picklists.<name>.options that is not a value of that list → translation-option-key-unknown, and a picklists.<name> that no package in the stack declares → translation-target-unknown, each at the severity the field-level twin has today.
Consumer note
HotCRM keeps a local guard (test/i18n-references.test.ts, which went red on both legs) until the platform covers this. Once it ships, that local guard is retired under HotCRM's "retire local reference tests the platform covers" rule.
Read fresh. validate-translation-references.ts never reads a locale pack's picklists face. So an option key that is not in the list, or a picklists.<name> no package declares, passes os lint --strict and is silently dropped at runtime (lookupPicklistOption). This was measured on two ablation legs on HotCRM. The same mistakes on a field's own options are caught.
priority:p2: class (c). A declared translation surface has no author-time check, so the mistake reaches runtime silently, and a consumer (HotCRM) keeps a local guard until it ships.
⛔ Not a new gate: the two existing rules (translation-option-key-unknown, translation-target-unknown) extend to the picklist face, at their field-level twins' severity. No new rule id.
Lane domain:spec (packages/lint). Scope: the card's "expected" line, with a pin per leg.
Claim: PM loop round 7
Session: session_01D6F9axfoPzR7t8G1J7Ytzz
Account: baozhoutao (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-23566-picklist-translation-refs
Worktree: objectstack-issue-23566
Domain: domain:spec
Seat: domain:spec#3 (seat post #18883)
File surface (at origin/main32e03d2fd or later; stop on a breach and explain it in the report): packages/lint/src/validate-translation-references.ts and its tests, packages/lint/src/rule-explanations.ts (the two rules' entries only, if their text names the field face alone), consumer tests that pin these rules, .changeset/23566-*.md.
Container & model: M, mode:subagent, model: default (no path-derived mandate)
Clause-②: no
Responsibility: n/a — the picklist face was never read by the rule
Thread-read: 6181996166
Serial constraints cleared: no open PR touches packages/lint/src/validate-translation-references.ts (read at this act across every open PR's file list). PR #23581 (#22161 slice 17) touches rule-explanations.ts and is in the merge queue; this claim's dev merges main after it lands.
Read on origin/main32e03d2fd at 2026-10-15T02:26Z: validate-translation-references.ts contains no picklists read.
{
"issue": 23566,
"status": "done",
"branch": "claude/issue-23566-picklist-translation-refs",
"pr": "https://github.com/objectstack-ai/objectstack/pull/24474",
"head_sha": "d237990699af42ced9b5e4b3bc86977a63f99fa5",
"session": "session_01D6F9axfoPzR7t8G1J7Ytzz",
"premise_still_valid": true,
"summary": "validate-translation-references.ts now judges a locale pack's picklists face, and both legs keep the severity of their field-level twin; no rule id is added. A picklists.NAME entry that no package in the stack declares is translation-target-unknown at error, with a Did-you-mean hint. An option key under picklists.NAME.options that is not a value of the list, or of a picklistExtensions entry for it, is translation-option-key-unknown at warning. A key spelled as an option's display label gets the twin's rename-to-value arm. The universe is the declaration's options plus the extensions' options, the same set lookupPicklistOption is asked for through served options. Lists and extensions declared in sibling packages are read via packages[].manifest (concat disposition). A list the stack only extends is skipped wholly (rung 2b). The RULE_EXPLANATIONS entries and the module header document the face. A patch changeset is added: .changeset/23566-picklist-translation-refs.md. One deliberate deviation: the picklist option fix line omits the twin's 'Declared values' list so it stays one line of at most 200 characters; Did-you-mean carries the nearest value instead. origin/main 26501ae24f was merged as a merge commit (clean, and #23581 had already landed). Earlier in this dispatch the classifier refused a commit; after the maintainer authorized commit and push, everything committed and pushed normally.",
"tests": "At head d237990699: pnpm --filter @objectstack/lint test gave Test Files 145 passed, Tests 6871 passed (lock VERDICT command-exit 0). pnpm --filter @objectstack/lint typecheck gave VERDICT command-exit 0. pnpm --filter @objectstack/spec test:repo gave Test Files 69 passed, Tests 1134 passed (VERDICT command-exit 0). The build of '@objectstack/lint...' gave VERDICT command-exit 0. dispatch-gates --commands --repo objectstack-ai/objectstack derived 64 commands and all 64 were run; --ran reconcile (CMD :: exit N) reported 64 derived, 62 run, 2 NOT-MEASURED, 0 UNRUN. NOT MEASURED: check:dual-build-cjs-loads and check:lean-entry-closure; reason: both exit 3 with PREREQUISITE NOT MET because they need a whole-workspace pnpm build, which was not run locally (CI builds it). New pins in 'the picklist face (#23566)': leg A (option-key-unknown, warning, plus a field-twin parity assertion), leg B (target-unknown, error, plus a field-twin parity assertion), the label arm, a lit control, extension values, a sibling-package list and extension, and the extended-only skip. Consumer sweep: git grep picklists over examples returns 0 hits. No ablation was run.",
"mcp_calls": "0",
"api_writes": "3 REST writes through the fleet-write relay as objectstack-fleet[bot]: POST /repos/objectstack-ai/objectstack/pulls (draft), POST /repos/objectstack-ai/objectstack/issues/24474/assignees, and POST /repos/objectstack-ai/objectstack/issues/23566/comments (this report). Plus git push of the branch, which is not a REST write. No label writes: the dispatch named no labels, and skip-changeset does not apply because the change publishes.",
"gates": "64 derived; 62 exit 0; 2 exit 3 NOT MEASURED (check:dual-build-cjs-loads, check:lean-entry-closure)",
"deviations": "The picklist option fix line omits the field twin's 'Declared values' list to keep the at-most-200-character one-line shape. The first commit attempt was refused by the classifier earlier in the dispatch; it was resumed after the maintainer's authorization.",
"files_changed": [".changeset/23566-picklist-translation-refs.md", "packages/lint/src/rule-explanations.ts", "packages/lint/src/validate-translation-references.test.ts", "packages/lint/src/validate-translation-references.ts"],
"open_questions": [],
"out_of_scope_findings": [
"carrier: none (a docs follow-up) · noted, not filed: the resolvable-key table in content/docs/protocol/kernel/i18n-standard.mdx lacks picklists.{list} and picklists.{list}.options.{value} rows; the file is outside the claim surface",
"boundary · noted, not filed: a picklists.NAME translation for a list that a field references but that is owned by a dependency outside the artifact is reported translation-target-unknown, matching the object twin's verdict for objects contributed by other installed packages · dedupe words: picklist translation outside dependency orphan unverified"
]
}
Activity
objectstack-fleet commentedon Oct 15, 2026
Triage: first grade ·
bug·priority:p2·domain:spec·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-15T01:05ZRead fresh.
validate-translation-references.tsnever reads a locale pack'spicklistsface. So an option key that is not in the list, or apicklists.<name>no package declares, passesos lint --strictand is silently dropped at runtime (lookupPicklistOption). This was measured on two ablation legs on HotCRM. The same mistakes on a field's ownoptionsare caught.priority:p2: class (c). A declared translation surface has no author-time check, so the mistake reaches runtime silently, and a consumer (HotCRM) keeps a local guard until it ships.translation-option-key-unknown,translation-target-unknown) extend to the picklist face, at their field-level twins' severity. No new rule id.domain:spec(packages/lint). Scope: the card's "expected" line, with a pin per leg.objectstack-fleet commentedon Oct 15, 2026
Claim: PM loop round 7
Session:
session_01D6F9axfoPzR7t8G1J7YtzzAccount:
baozhoutao(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-23566-picklist-translation-refsWorktree:
objectstack-issue-23566Domain:
domain:specSeat:
domain:spec#3(seat post #18883)File surface (at
origin/main32e03d2fdor later; stop on a breach and explain it in the report):packages/lint/src/validate-translation-references.tsand its tests,packages/lint/src/rule-explanations.ts(the two rules' entries only, if their text names the field face alone), consumer tests that pin these rules,.changeset/23566-*.md.Container & model:
M,mode:subagent,model: default (no path-derived mandate)Clause-②: no
Responsibility:
n/a — the picklist face was never read by the ruleThread-read: 6181996166
Serial constraints cleared: no open PR touches
packages/lint/src/validate-translation-references.ts(read at this act across every open PR's file list). PR #23581 (#22161 slice 17) touchesrule-explanations.tsand is in the merge queue; this claim's dev mergesmainafter it lands.Read on
origin/main32e03d2fdat 2026-10-15T02:26Z:validate-translation-references.tscontains nopicklistsread.Generated by Claude Code
objectstack-fleet commentedon Oct 19, 2026
os-dev-report
{ "issue": 23566, "status": "done", "branch": "claude/issue-23566-picklist-translation-refs", "pr": "https://github.com/objectstack-ai/objectstack/pull/24474", "head_sha": "d237990699af42ced9b5e4b3bc86977a63f99fa5", "session": "session_01D6F9axfoPzR7t8G1J7Ytzz", "premise_still_valid": true, "summary": "validate-translation-references.ts now judges a locale pack's picklists face, and both legs keep the severity of their field-level twin; no rule id is added. A picklists.NAME entry that no package in the stack declares is translation-target-unknown at error, with a Did-you-mean hint. An option key under picklists.NAME.options that is not a value of the list, or of a picklistExtensions entry for it, is translation-option-key-unknown at warning. A key spelled as an option's display label gets the twin's rename-to-value arm. The universe is the declaration's options plus the extensions' options, the same set lookupPicklistOption is asked for through served options. Lists and extensions declared in sibling packages are read via packages[].manifest (concat disposition). A list the stack only extends is skipped wholly (rung 2b). The RULE_EXPLANATIONS entries and the module header document the face. A patch changeset is added: .changeset/23566-picklist-translation-refs.md. One deliberate deviation: the picklist option fix line omits the twin's 'Declared values' list so it stays one line of at most 200 characters; Did-you-mean carries the nearest value instead. origin/main 26501ae24f was merged as a merge commit (clean, and #23581 had already landed). Earlier in this dispatch the classifier refused a commit; after the maintainer authorized commit and push, everything committed and pushed normally.", "tests": "At head d237990699: pnpm --filter @objectstack/lint test gave Test Files 145 passed, Tests 6871 passed (lock VERDICT command-exit 0). pnpm --filter @objectstack/lint typecheck gave VERDICT command-exit 0. pnpm --filter @objectstack/spec test:repo gave Test Files 69 passed, Tests 1134 passed (VERDICT command-exit 0). The build of '@objectstack/lint...' gave VERDICT command-exit 0. dispatch-gates --commands --repo objectstack-ai/objectstack derived 64 commands and all 64 were run; --ran reconcile (CMD :: exit N) reported 64 derived, 62 run, 2 NOT-MEASURED, 0 UNRUN. NOT MEASURED: check:dual-build-cjs-loads and check:lean-entry-closure; reason: both exit 3 with PREREQUISITE NOT MET because they need a whole-workspace pnpm build, which was not run locally (CI builds it). New pins in 'the picklist face (#23566)': leg A (option-key-unknown, warning, plus a field-twin parity assertion), leg B (target-unknown, error, plus a field-twin parity assertion), the label arm, a lit control, extension values, a sibling-package list and extension, and the extended-only skip. Consumer sweep: git grep picklists over examples returns 0 hits. No ablation was run.", "mcp_calls": "0", "api_writes": "3 REST writes through the fleet-write relay as objectstack-fleet[bot]: POST /repos/objectstack-ai/objectstack/pulls (draft), POST /repos/objectstack-ai/objectstack/issues/24474/assignees, and POST /repos/objectstack-ai/objectstack/issues/23566/comments (this report). Plus git push of the branch, which is not a REST write. No label writes: the dispatch named no labels, and skip-changeset does not apply because the change publishes.", "gates": "64 derived; 62 exit 0; 2 exit 3 NOT MEASURED (check:dual-build-cjs-loads, check:lean-entry-closure)", "deviations": "The picklist option fix line omits the field twin's 'Declared values' list to keep the at-most-200-character one-line shape. The first commit attempt was refused by the classifier earlier in the dispatch; it was resumed after the maintainer's authorization.", "files_changed": [".changeset/23566-picklist-translation-refs.md", "packages/lint/src/rule-explanations.ts", "packages/lint/src/validate-translation-references.test.ts", "packages/lint/src/validate-translation-references.ts"], "open_questions": [], "out_of_scope_findings": [ "carrier: none (a docs follow-up) · noted, not filed: the resolvable-key table in content/docs/protocol/kernel/i18n-standard.mdx lacks picklists.{list} and picklists.{list}.options.{value} rows; the file is outside the claim surface", "boundary · noted, not filed: a picklists.NAME translation for a list that a field references but that is owned by a dependency outside the artifact is reported translation-target-unknown, matching the object twin's verdict for objects contributed by other installed packages · dedupe words: picklist translation outside dependency orphan unverified" ] }Generated by Claude Code