Two independently-written AST walks now discover the same createSafeTranslation defaults-table population, and nothing pins that they agree.
The two walks
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.
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
Two independently-written AST walks now discover the same
createSafeTranslationdefaults-table population, and nothing pins that they agree.The two walks
packages/test-support/src/defaults-table-scan.ts— resolves everycreateSafeTranslation(...)/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/i18nand the "every defaults row names a key the en pack defines" rule in@object-ui/app-shell.scripts/check-i18n-call-site-keys.mjs— its ownscanFactorySites/resolveFactoryTable/declaredConstant, added with thefactory-default-driftclass. Separate implementation, separate file, separate runtime.They agree exactly today — which is the point
Measured on the same tree, same commit:
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/*.mjsrun 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
endefines 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-devseat while landing the defaults-maps discovery widening. No assignee — PM triage.Generated by Claude Code