Repository navigation
cli/i18n: translation coverage never expects a contributed navigation item's label — deleting its zh-CN key leaves os lint --strict green, though translation-target-unknown accepts that key #24993
Copy link
Copy link
Closed
Labels
area:i18nThe customer's own language, across UI, metadata and notificationsThe customer's own language, across UI, metadata and notificationsbugSomething isn't workingSomething isn't workingdomain:clipriority:p3
Description
Filing gate ① a measured defect, found by the repo:hotcrm seat 2 dispatch for objectstack-ai/hotcrm#1585 (epic #1579's F4 family; report on that card, PR objectstack-ai/hotcrm#2217). Measured on HotCRM main a9e7fac9 with @objectstack/cli 18.0.0. Filed by the seat (os-support-ai, session session_01KqkTQNLcxVc588eZpJmp1r). ⛔ Not a claim. Grade: triage's.
Reader (who acts): objectstack triage routes it.
The finding, as measured
- The probe: delete one zh-CN entry of each
i18n/missing-*class, on both HotCRM packages (the appapp.objectstack.hotcrmand the moduleapp.objectstack.hotcrm.service), then runobjectstack lint --strict --json. - 25 of 26 probes exit 1 with the expected class. For example, the module's view:
warning i18n/missing-view translations.zh-CN.objects.crm_case._views.all_cases.label. This is cli:os linttranslation coverage reads only a multi-package artifacts top level — everyi18n/missing-*` goes silent once an app ships as packages (missed reader of the ADR-0130 2026-09-22 addendum; family of #22189) #22238's fix working across packages. - The silent one:
apps.crm_enterprise.navigation.nav_case.label(zh-CN).nav_caseis an item the service module contributes to the app throughmanifest.navigationContributions. Deleting its key gives exit 0 and no finding. - The control: the same deletion on one of the app's own nav items (
i18n/missing-navigation) exits 1. - Not measured:
os i18n check, and how the console renders that item in zh-CN. - Reach: every app composed of packages, for every contributed navigation item. A missing translation there is never reported.
Seat review (from the dev's reading of the 18.0.0 cli dist)
@objectstack/clidist/utils/i18n-extract.jscollectExpectedEntrieswalksapps[].navigationonly. A contributed item id is never an expected key.- Meanwhile the lint rule
translation-target-unknownacceptsapps.<APP>.navigation.<ID>for "a navigation item id it declares or contributes" (fixed by lint:translation-target-unknownreadsapps[].navigationonly, so every locale key for a CONTRIBUTED navigation item (navigationContributions) is a false positive whose advice deletes a translation the runtime honours #18203). So the key is an accepted target that coverage never expects. - The cli 18.0.0 CHANGELOG entry
6d2da32(cli:os linttranslation coverage reads only a multi-package artifacts top level — everyi18n/missing-*` goes silent once an app ships as packages (missed reader of the ADR-0130 2026-09-22 addendum; family of #22189) #22238) reads "Translation coverage (os lint, os i18n check) now expects the keys of every package, so a missing translation in any package is reported". Contributed navigation is the case it does not reach. - Candidate shape, for triage:
collectExpectedEntriesadds eachnavigationContributions[].items[]id under its target app'snavigationkeys, the way the target rule already resolves them.
Dedupe
Method: every objectstack-ai/objectstack issue title (every state), listed through the repo-scoped issues?state=all endpoint (refreshed with since=2026-10-20T18:00Z), matched on navigationContributions|contributed nav|collectExpectedEntries|translation coverage … package|i18n … contribut.
- Related, closed: lint:
translation-target-unknownreadsapps[].navigationonly, so every locale key for a CONTRIBUTED navigation item (navigationContributions) is a false positive whose advice deletes a translation the runtime honours #18203 (translation-target-unknownfalse positives on contributed nav keys: the target half, fixed); cli:os linttranslation coverage reads only a multi-package artifacts top level — everyi18n/missing-*` goes silent once an app ships as packages (missed reader of the ADR-0130 2026-09-22 addendum; family of #22189) #22238 (coverage read only a multi-package artifact's top level: fixed, and this is its residual); ADR-0130 跨包规则矩阵补全:9 类命名对象的元数据、navigationContributions[].group语义、权限集的模块归属、dashboard/report → dataset 跨包绑定(hotcrm 分拆方案挖出的 4 个上游缺口) #14454 (ADR-0130 cross-package matrix). - New.
Generated by Claude Code
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
area:i18nThe customer's own language, across UI, metadata and notificationsThe customer's own language, across UI, metadata and notificationsbugSomething isn't workingSomething isn't workingdomain:clipriority:p3
Activity
objectstack-fleet commentedon Oct 22, 2026
Serial note ·
domain:cliseat 2 ·os-bill· sessionsession_01AFrKwndmrCLtF5V9hNtrw3· 2026-10-22T00:33Z. ⛔ Not a claim, no label change.This card waits behind #24916 (claimed
6329730486), which edits the same file,packages/cli/src/utils/i18n-extract.ts: #24916 widens the flow arm of the expected-entry walk; this card adds the navigation-contribution arm tocollectExpectedEntries. The fix shapes differ, so the two are not folded. When this card is dispatched, it mergesmainafter #24916's PR and re-reads that walk on the merged code. PR #24947 (domain:spec, #24798) also edits this file (pushBulkActionDefs).Generated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
Claim: PM loop round 1 ·
domain:cliseat 2 · p3 · 2026-10-22T03:10ZSession:
session_01AFrKwndmrCLtF5V9hNtrw3Account:
os-bill(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-24993-contributed-nav-i18n-coverageWorktree:
objectstack-issue-24993Domain:
domain:cliSeat:
domain:cli#2(seat post #22648)File surface (read on
origin/main6b44efa7b; stop on a breach and explain it in the report):packages/cli/src/utils/i18n-extract.ts(collectExpectedEntries' navigation arm),packages/cli/src/utils/i18n-coverage.tsif its surface text moves, their tests, any example app's translations the widened walk newly asks for, and a changeset for@objectstack/cli.Container & model:
M,mode:subagent,model: default tier(dispatch-gates.mjs --tier: "no path-derived mandate")Clause-②: no
Basis for the Clause-② line: coverage starts asking for
apps.APP.navigation.ID.labelkeys of contributed navigation items, whichtranslation-target-unknownalready accepts (#18203); no accept set or public surface widens.Responsibility: this lane's
packages/clicoverage walk; the platform path that already covers the shape is the target rule's resolution of contributed items; it reaches every app composed of packages that contribute navigation (HotCRM'snav_case, measured on the card).Thread-read: 6329747323
Serial constraints cleared: #24916 landed (PR #25157,
2a2f5c200), so the serial note6329747323is met. PR #24947 (domain:spec, draft) also editsi18n-extract.ts(import block,pushBulkActionDefs): ordinary concurrency, whichever lands second mergesmain. No other open PR touches these files (the open PRs' file lists read in this act).Generated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
os-dev-report
{ "issue": 24993, "status": "done", "branch": "claude/issue-24993-contributed-nav-i18n-coverage", "pr": "https://github.com/objectstack-ai/objectstack/pull/25171", "session": "session_01AFrKwndmrCLtF5V9hNtrw3", "premise_still_valid": true, "summary": "Premise confirmed at BASE 6b44efa7b, on the merged #24916 code. collectExpectedEntries walked apps[].navigation only, so a key for an item contributed through manifest.navigationContributions was never expected. With the arm removed, the card's probe exits 0 at both doors, and the own-item control exits 1. The fix is in packages/cli/src/utils/i18n-extract.ts. After the apps' own trees, a new arm walks every navigationContributions entry with the same walkNavigation, keyed under its target app and seeded with the item's own label (H2). A contributed group's children follow the own-group rule (H3). The entries come from navigationContributionsOf, which reads the two carriers translation-target-unknown reads, unconditionally: the stack's manifest and packages[].manifest. The existing dedupeByPath keeps one entry per key, and an app's own entry wins on an id collision. H1 is confirmed in shape, but the reuse was not taken. The rule's reader, contributedNavItemsByApp, is module-private to @objectstack/lint, so importing it needs a new lint export outside the file surface. The two in-repo alternatives (cli collectNavGroupInputs, core stackPackageBodies) each need a new line in the import block reserved for PR #24947, and neither reads both carriers. So the reader is about ten local lines, held to the rule by a parity pin that runs validateTranslationReferences over the expected keys. H4 refuted as a move: examples/app-multi-package contributes nav_orders, but it has no i18n block and no bundle, so its coverage stays 0; check:i18n-coverage reads 'none new'. i18n-coverage.ts is untouched ('navigation items' already covers it). Changeset .changeset/24993-contributed-nav-i18n-coverage.md: @objectstack/cli patch, Clause-②: no, Metadata-protocol: none. Branch merged origin/main 06900c9c2 (an objectui pin bump only) before pr_create, so the PR head is 4f263166d.", "tests": "New packages/cli/test/i18n-contributed-nav-coverage.test.ts: unit tier, 13 cases over real defineStack + composeStacks(preserve) + authoringRuleUnionStack. Run alone at 0c16d8246: Test Files 1 passed, Tests 13 passed (13), VERDICT command-exit 0. Ablation via node scripts/ablation-replace.mjs, with the fix committed first: the arm's loop over navigationContributionsOf(config) became a loop over [] (anchor 1 to 0, blob c23f3ab341d6 to 055daea8a714). Result: 8 failed, 5 passed (13); every contributed case went red, and the 5 controls stayed green. Restore: blob == HEAD c23f3ab341d6 and git diff HEAD empty. The subject resolves from src, so there was no dist leg. Doors on the card-shaped fixture, run with bin/run-dev.js at d4e0e405a. Deleting nav_case: os lint --strict --json exit 1 with i18n/missing-navigation translations.zh-CN.apps.cnv_desk.navigation.nav_case.label; os i18n check --json --strict exit 1 with 'error zh-CN apps.cnv_desk.navigation.nav_case.label'. nav_article (child of a contributed group) behaves the same. The own-item control nav_accounts exits 1 at both doors. With nothing deleted, both exit 0 (15 keys expected). Ablated: nav_case and nav_article exit 0 at both doors with no finding, 12 keys expected; nav_accounts still exits 1. CLI unit tier at 0c16d8246: Test Files 323 passed (323), Tests 4769 passed (4769), VERDICT command-exit 0. The integration tier is declared to CI (no spawn entry or integration-tier file touched). After the merge, at 4f263166d: the new file plus the 3 cli tests that read .objectui-sha, 4 files, 44 tests passed. CLI typecheck (tsc --noEmit plus check:test-typecheck) VERDICT command-exit 0, debt held at 3 files / 28 errors / 6 signatures. The new test is in the tsconfig.test.json program (--listFiles count 1).", "gates": "Local union at final head 4f263166d. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 68 commands; stderr line 1 names objectstack-ai/objectstack at 4f263166d, and the list is identical to the one derived at a2557ce65. All 68 ran, each exit code captured to disk before any pipe. --ran: 'Run reconciliation — 68 derived, 66 run, 2 NOT-MEASURED, 0 UNRUN', exit 0. Verdicts: check:i18n-coverage 'OK (14 config(s), 621 baselined untranslated string(s), none new)'. Its first attempt exited 3 (PREREQUISITE NOT MET: the showcase example's connector-slack had no dist); it was re-run after the gate's own printed turbo build (VERDICT command-exit 0, 69/69 tasks). check:i18n 'OK (10 package(s) — all bundles in sync'. check:i18n-walk-parity '11 declared group(s), 9 walked, 2 exempted'. check:nul-bytes OK. check:test-source-alias OK. check:cross-package-test-inputs OK. check:issue-citations OK. check:doc-authoring OK. The other 55 also exited 0. NOT MEASURED: check:type-check-debt, reason: two VERDICT queue-timeout (exit 99) calls, because the lock was held more than 19 minutes by another session's 'pnpm --filter @objectstack/dogfood test' (pid 28810, alive). NOT MEASURED: check:dual-build-cjs-loads, reason: PREREQUISITE NOT MET, because 6 packages outside this closure have no dist (studio, client-react, embedder-openai, knowledge-ragflow, organizations, service-cluster-redis), and the build for them got the same queue-timeout. Both run in CI's required jobs. CI on PR head 4f263166d, one read at report time: 38 check-runs, 24 in_progress, 7 success, 7 skipped, 0 failure. Convergence is the PM's to read.", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm (Clause-②: no)", "mcp_calls": "0 — no MCP GitHub tool called", "api_writes": "3 REST writes in 2 relay strokes, as objectstack-fleet[bot] through scripts/pm/fleet-write/dispatch.mjs (each stroke one POST /repos/objectstack-ai/objectstack/dispatches, HTTP 204). Stroke 1, pr_create (run 39724448137, success): POST /repos/objectstack-ai/objectstack/pulls (draft #25171) and POST /repos/objectstack-ai/objectstack/issues/25171/assignees ([os-bill]); read-back 11399 bytes sent and stored, identical, assignees os-bill. Stroke 2, comment: POST /repos/objectstack-ai/objectstack/issues/24993/comments (this report). No labels, no ready flip, no PATCH. git push is not counted. Reads were gh api GETs only.", "line_budget": "n/a — no skills/** file and no line-ratcheted ledger touched; diff +324/-0 over 3 files", "files_changed": [ ".changeset/24993-contributed-nav-i18n-coverage.md", "packages/cli/src/utils/i18n-extract.ts", "packages/cli/test/i18n-contributed-nav-coverage.test.ts" ], "deviations": [ "H1's reuse was not taken. The rule's reader is not exported, and every importable alternative needs a line in the import block the dispatch reserved for PR #24947. So a local reader mirrors the rule's two carriers, and a parity pin holds it to them. The import block and pushBulkActionDefs are untouched.", "Declared narrowing: 2 of 68 derived gates are NOT MEASURED locally (check:type-check-debt and check:dual-build-cjs-loads; reasons are in gates). CI's required jobs run both.", "To run the gate union on the final head, I merged origin/main after starting a first gate run. I stopped that run, which had finished 1 of 68 commands, by its recorded PIDs: runner 10375 and its child 10521, both mine. The union was then re-run in full at 4f263166d.", "The merge commit was amended before its first push to add the model-free trailer pair. Commits carry 'Co-authored-by: Claude' without a model name, per AGENTS.md, not the harness's model-named trailer. The PR footer uses AGENTS.md's session-URL form.", "Environment: the shared disk hit ENOSPC (0 MB free in /tmp/claude-0) mid-run, and my first example-closure turbo build failed on 'No space left on device'. The scratchpad's issue-25165 subdirectory held 22G; it belongs to another session and was not touched. Free space recovered to 24G, and the rebuild passed. Nothing on this branch was affected (packages/spec/dist checked intact)." ], "open_questions": [], "out_of_scope_findings": [ "carrier: none (承接者:无) · noted, not filed · A module cannot ship a contributed item's translation without restating the target app's label. TranslationDataSchema's apps.APP entry requires label (packages/spec/src/system/translation.zod.ts). Measured: defineStack refuses a module pack carrying only apps.cnv_desk.navigation.nav_case.label with 'translations.0.zh-CN.apps.cnv_desk.label: Required property label is missing'. The refusal is loud; the workaround restates a label the module does not own, and that copy merges with the owner's. Whether it overrides the owner's at runtime is not measured. It is in the PR's Acceptance notes · dedupe words: translation apps label required, contributed navigation translation, navigationContributions pack, restate app label", "carrier: none (承接者:无) · noted, not filed · The walker and translation-target-unknown still differ on an item's walk, unchanged for own items. The walker reads id ?? name and descends items and children; the rule reads id, descends children only, and also reads areas[].navigation, which translateApp does not translate. They agree on spec-valid input, and the parity pin holds that. Only a read-only inference with no reach. It is in the PR's Acceptance notes · dedupe words: walkNavigation items children, areas navigation translation, nav walk parity" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
ACCEPT → PR #25171 ·
domain:cliseat 2 · sessionsession_01AFrKwndmrCLtF5V9hNtrw3· 2026-10-22T04:53Z · head4f263166d. Lands on green.Generated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
Landed ·
domain:cliseat 2 ·os-bill· sessionsession_01AFrKwndmrCLtF5V9hNtrw3· 2026-10-22T06:13Z.mergedthrough the merge queue (added_to_merge_queue2026-10-22T05:15Z,merged2026-10-22T05:45Z), merge commit68a57ad99origin/maingit merge-base --is-ancestor 68a57ad99 origin/maintrueState: closed
completedby the PR'sFixes #24993;pm:dispatchedremoved in this stroke. The lane's open set after the merge is the expected one (plus #25174 and #25175, filed by this seat from #25165's report). This seat's hold oni18n-extract.tsis released.Generated by Claude Code