Skip to content

finding(i18n/tooling): two independent AST walks discover the createSafeTranslation defaults population, with nothing pinning that they agree #7904

Description

@claude

Two independently-written AST walks now discover the same createSafeTranslation defaults-table population, and nothing pins that they agree.

The two walks

  1. packages/test-support/src/defaults-table-scan.ts — resolves every createSafeTranslation(...) / createSafeTranslationHook(...) first argument to its object literal, plus a three-entry hand-rolled registry. Consumed by two vitest gates: the placeholder-spelling rule in @object-ui/i18n and the "every defaults row names a key the en pack defines" rule in @object-ui/app-shell.
  2. scripts/check-i18n-call-site-keys.mjs — its own scanFactorySites / resolveFactoryTable / declaredConstant, added with the factory-default-drift class. Separate implementation, separate file, separate runtime.

They agree exactly today — which is the point

Measured on the same tree, same commit:

walk 1 (test-support module) : 32 factory call sites, 841 factory rows, 0 unreadable
walk 2 (check-i18n script)   : "Factory defaults tables: 32 createSafeTranslation site(s)
                                over 32 distinct table(s) (0 unreadable) — 841 row(s)
                                compared against their en value ... 0 unreadable."

32 = 32, 841 = 841, 0 = 0. That agreement is real and currently free of luck — both were written from the same understanding of the factory. But it is not asserted anywhere, so the day one of them learns a new table shape (a table behind a satisfies, a re-export, a computed member, a spread) and the other does not, the two populations silently diverge. One gate would go on reporting full coverage of a population the other has already grown past.

This is the same shape as the finding that produced the shared module in the first place: a definition of "which tables count" that exists in more than one place, with no mechanism raising a hand when the copies drift.

Why they were not simply merged

There is a real structural reason, not an oversight: scripts/*.mjs run under bare node, which cannot import the TypeScript-source module the vitest gates share. So "just call the same function" is not available today without a build step or a loader for the scripts lane. That makes this a genuine design question rather than a cleanup.

A cheap pin, if one is wanted

The script already PRINTS its census line. A test could parse that line and assert it against scanDefaultsTables(REPO_ROOT) — same site count, same distinct-table count, same row count, same unreadable count. That pins the agreement without merging the implementations, and it fails the day either walk changes shape alone. Whether that is worth a gate, or whether the divergence should instead be closed by giving the scripts lane a way to import the shared module, is the decision to make.

Notes

  • No user-visible defect today: both walks are correct on this tree and the numbers match.
  • The two gates judge different properties (the script value-compares and deliberately abstains where en defines no value; the app-shell gate judges whether the key exists at all), so this is only about the shared POPULATION definition, not about the rules.

Filed by the os-dev seat while landing the defaults-maps discovery widening. No assignee — PM triage.


Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repofindingi18npriority:p3tests

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions