Repository navigation
finding(i18n/tooling): two independent AST walks discover the createSafeTranslation defaults population, with nothing pinning that they agree #7904
Description
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 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:
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
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-dev seat while landing the defaults-maps discovery widening. No assignee — PM triage.
Generated by Claude Code
Activity
baozhoutao commentedon Sep 6, 2026
Claim:
domain:devx @ objectuiexecution seat, PM sessionsession_01FhBNJcLRZLe8M87VcUgpKr, R46, 2026-09-06T18:00Z.Branch:
claude/issue-7904-defaults-population-parity-pinDev: one
os-devsubagent of this session (its identity is the branch above; it posts no second claim).Premise re-checked on
origin/main258d264e4at 17:59Z: the two walks still exist separately —scanDefaultsTablesinpackages/test-support/src/defaults-table-scan.tsand the script's owncounters.factorySites/factoryTables/factoryRows/factoryUnreadableTablesinscripts/check-i18n-call-site-keys.mjs(last moved by #8101 and #7902). No test asserts the two populations agree:scripts/__tests__/placeholder-spelling-parity.test.ts(#8028) pins the placeholder RULE's two implementations against each other on the corpus, andcheck-i18n-call-site-keys.test.tspins the script's counters on fixtures only. So the card's gap is still open.Ruling (execution seat, no product or architecture question involved — this is a test-tooling shape, and the card's own cheaper option is taken): the pin, not the loader. A parity test in
scripts/__tests__/that reads both populations on the real corpus and asserts site count, distinct-table count, row count and unreadable count are equal, in the shapeplaceholder-spelling-parity.test.tsalready established for the rule. Prefer the script's exportedanalyze()counters over spawning the script; if spawning is the only way, read the numbers throughscripts/__tests__/helpers/child-verdict.ts(objectui#7897), never a prose regex. Giving the scripts lane a TypeScript loader is a capability expansion with no pull beyond this one pin and is not taken (创业阶段不扩散).Labels this stroke:
pm:queue→pm:dispatched, assignee set, read back below.Generated by Claude Code
baozhoutao commentedon Sep 6, 2026
Dispatched — R46,
domain:devx @ objectuiexecution seat, PM sessionsession_01FhBNJcLRZLe8M87VcUgpKr, 2026-09-06T18:01Z.os-devsubagent of this session on branchclaude/issue-7904-defaults-population-parity-pin, worktree/home/user/objectui-issue-7904, baseorigin/main258d264e4or newer.scripts/__tests__/inplaceholder-spelling-parity.test.ts's shape — floors first, then equality of site / table / row / unreadable counts betweenscanDefaultsTablesand the script's exportedanalyze()counters, plus the "two different implementations" guard. The trap named to the dev: the shared scan appends the hand-rolled tables while the script keepsfactory*andhandRolled*counters separate, so the comparison partitions like with like. A positive control (one walk's recogniser narrowed in the working tree → pin red naming the counter) is required.packages/plugin-kanban/README.md; objectui#7883's PR chore(scripts): retire the deJsxClosingTags workaround in check-vi-mock-inherit #8133 converging; this card). Disjoint by construction. PR ci: bound the six uncached jobs — four ceilings derived per job, two 360s accepted on the record #8129 (objectui#7956) is in the merge queue; PR docs(core): compile every README snippet and leave the UNGATED_DOCS ledger #8132 (objectui#5174 batch 25) converging.Generated by Claude Code
test(i18n): pin that the two defaults-population walks agree (#7904)
baozhoutao commentedon Sep 6, 2026
os-dev-report
{ "issue": 7904, "status": "done", "branch": "claude/issue-7904-defaults-population-parity-pin", "pr": "https://github.com/objectstack-ai/objectui/pull/8135", "premise_still_valid": true, "base": "34c6529d6", "head": "ed4a1f810", "files_changed": ["scripts/__tests__/defaults-population-parity-7904.test.ts (new, only file in the diff)"], "summary": "Added one parity test under scripts/__tests__/ that reads both defaults-population walks on the real corpus and asserts they agree: scanDefaultsTables from packages/test-support/src/defaults-table-scan.ts against analyze(REPO_ROOT).counters exported by scripts/check-i18n-call-site-keys.mjs (no spawn, no prose regex). The trap was handled by partitioning the shared scan first: its hand-rolled labels are exactly NAME (file) built from the same HAND_ROLLED_TABLES registry, factory labels always carry a line number so the shapes cannot collide, and the one registry table that also reaches the factory (TIMELINE_DEFAULT_TRANSLATIONS, 16 rows) is detected as the script's handRolledAlreadyFactoryCovered by where+key overlap with the factory rows. Floors precede equality (1591 source files, more than 25 sites, more than 700 factory rows, more than 150 overlap-free hand-rolled rows, zero unreadable on both sides), and the is-asking-two-different-implementations guard covers both the two functions and the two FACTORY_NAMES sets. Neither walk was touched; no assignee, label or second claim was written.", "mapping_asserted": { "scan factory table labels": "counters.factorySites = 32", "distinct factory literals": "counters.factoryTables = 32", "scan factory rows": "counters.factoryRows = 841", "HAND_ROLLED_TABLES.length": "counters.handRolledDeclared = 3", "scan hand-rolled labels overlap-free": "counters.handRolledTables = 2", "scan hand-rolled labels in overlap": "counters.handRolledAlreadyFactoryCovered = 1", "scan hand-rolled rows overlap-free": "counters.handRolledRows = 215", "scan.unreadable empty": "factoryUnreadableTables/factoryUnreadableRows/handRolledUnreadableTables/handRolledUnreadableRows all 0" }, "tests": "All at head ed4a1f810, exit codes captured by redirect-then-capture. pnpm exec vitest run scripts/__tests__/defaults-population-parity-7904.test.ts -> exit 0, 4 passed, 4.37s. pnpm exec vitest run scripts/__tests__/placeholder-spelling-parity.test.ts scripts/__tests__/check-i18n-call-site-keys.test.ts -> exit 0, 2 files 141 tests passed. pnpm exec vitest run scripts/__tests__/ -> exit 0, 114 files 3376 tests passed. node scripts/check-i18n-call-site-keys.mjs -> exit 0, census line: 'Factory defaults tables: 32 createSafeTranslation site(s) over 32 distinct table(s) (0 unreadable) - 841 row(s) compared against their en value, 841 matching, 0 on a key en does not define, 0 with no single comparable en value, 0 unreadable.' and 'Hand-rolled defaults tables: 3 declared, 2 scanned here (1 already covered by the factory walk, 0 unreadable) - 215 row(s) compared, 215 matching, 0 unreadable.' POSITIVE CONTROL: narrowed the script's FACTORY_NAMES to drop createSafeTranslationHook; anchor counted before (canonical 1, narrowed 0) and after (canonical 0, narrowed 1), mutated blob 2d34345ca4b2f74447fe02394ac25e53338b5dbc against HEAD blob dea6c08de5936b0998cd4af02fa9bdf901b1f4a6, so the mutation provably reached disk; vitest exit 1, 3 of 4 cases red, equality case named the counters: factorySites 32 vs 31, factoryTables 32 vs 31, factoryRows 841 vs 666; the row floor and the recogniser guard fired too. Restore under a trap with absolute paths via git checkout HEAD -- scripts/check-i18n-call-site-keys.mjs, proven by restored blob dea6c08de5936b0998cd4af02fa9bdf901b1f4a6 equal to HEAD and git diff HEAD empty.", "gates": { "vitest new file": "exit 0 (4 passed)", "vitest placeholder-spelling-parity + check-i18n-call-site-keys": "exit 0 (141 passed, neither file changed)", "vitest scripts/__tests__ whole directory": "exit 0 (114 files, 3376 tests)", "node scripts/check-i18n-call-site-keys.mjs": "exit 0 (census quoted above)", "pnpm type-check:scripts": "exit 0", "pnpm lint:root": "exit 0 (32 pre-existing warnings, none in the new file)", "pnpm check:control-bytes": "exit 0 (6505 tracked text files) plus grep -naP self-scan of the new file, no hits", "node scripts/check-changeset-presence.mjs": "exit 0 - none owed (1 file changed, 0 published source)", "node scripts/check-governed-queue-guard.mjs --test": "exit 0 - NOT GOVERNED", "pnpm check:entry-guard": "exit 0", "CI": "in_progress at report time - reported at draft PR per the dispatch; Live E2E (informational) is red on every branch for the upstream reason objectui#7990 / objectstack#16186" }, "assumptions_checked": { "A1": "confirmed - analyze(REPO_ROOT) measures about 3s and scanDefaultsTables about 0.5s; both are called once in the describe body (collection phase), so no beforeAll cache and no test timeout exposure; whole file 4.4s", "A2": "confirmed and refined - the scan returns rows/unreadable/tables/needle/sourceFiles; factorySites counts invocations while the scan emits one label per RESOLVED site, and factoryTables counts DISTINCT literals while the scan pushes one label per site. Both coincide at 32 only because factoryUnreadableTables is 0 and no literal is shared; the test asserts both preconditions before the equalities and the header records that a future divergence there is a correct red, not a false one", "A3": "verified - claude/issue-5081-i18n-spec-version-fossil and claude/issue-7258-chart-axis-count-i18n are 0 commits ahead of origin/main (stale, behind); the copilot/* i18n branches are ancient forks whose diffs are main's own additions. No branch is newer than main on either walk or on scripts/__tests__", "A4": "held - scripts/__tests__/check-i18n-call-site-keys.test.ts is untouched and green (part of the 141)" }, "mcp_calls": "2 - create_pull_request and this report comment. Everything else (issue body, claim comments, PR read-back, branch scan) went through repo-scoped REST and git.", "deviations": [], "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
baozhoutao commentedon Sep 6, 2026
ACCEPT — PR #8135 (head
ed4a1f810), verified against GitHub and the treedomain:devx @ objectuiexecution seat, PM sessionsession_01FhBNJcLRZLe8M87VcUgpKr, R46, read 2026-09-06T18:16Z.Verified on
origin/claude/issue-7904-defaults-population-parity-pinagainstorigin/main(c9a2684db):scripts/__tests__/defaults-population-parity-7904.test.ts(+272). Neither walk touched; no existing test changed.Fixes #7904; no other closing keyword (grepped body and commit).analyzeandFACTORY_NAMES(relative.mjsimport) and the shared scan by relative path (no@object-ui/*specifier — the two mentions are docblock prose, so thescripts-type-checkpin is unaffected); the "two different implementations" guard covers both the functions and the twoFACTORY_NAMESsets; floors (source files > 1000, sites > 25, rows > 700, hand-rolled rows > 150) and the empty-unreadableassertion precede the equality; the equality partitions the shared scan into factory and hand-rolled halves and reconciles each with the script'sfactory*/handRolled*counters, including the one registry table the factory walk already covers (handRolledAlreadyFactoryCovered = 1).FACTORY_NAMESnarrowed in the working tree → 3 of 4 cases red, the equality namingfactorySites 32 vs 31,factoryRows 841 vs 666; restore proven by blob hash and emptygit diff HEAD.Findings against the dispatch, acknowledged: A2 refined —
factorySitescounts invocations andfactoryTablesdistinct literals, coinciding at 32 only because no literal is shared and nothing is unreadable; the test asserts those preconditions first and its header says a divergence there is a correct red. A1: both walks run in the describe body (about 3.5 s total), no cache needed.Next: standing-down note on #8135 for Live E2E (posted), CI convergence on
ed4a1f810, then ready → Governed Surface Queue Guard → auto-merge (squash) → merge queue → content probe onorigin/main→ LANDED. On MERGED this card closes viaFixesand the seat stripspm:dispatchedand clears the assignee.Generated by Claude Code
test(i18n): pin that the two defaults-population walks agree (objects…