Found in review of PR #94 (verdict: PLAUSIBLE — no live defect, forward-looking).
| Field |
Contents |
| Goal |
A jest config value that resolves a package by name cannot silently bind to the other major, in either app. |
| Context |
Review of PR #94 (feat/mobile-scaffold), which introduced the mix: apps/mobile needs jest 29 because jest-expo (Expo SDK 57) requires it, while apps/api is on jest 30. Both pins are legitimate. The consequence is that pnpm's hoisted fallback store (node_modules/.pnpm/node_modules) now holds jest@30.4.2 beside jest-circus, jest-config, jest-runtime, jest-snapshot, expect and pretty-format at 29.7.0. PR #94 already hit this once: 28 apps/api suites failed until jest-environment-node@^30.4.1 was declared explicitly. That pin closes the one observed name, not the class. See plans/DEBUG_mobile_scaffold_jest_metro_resolution.md, which records the rule ("every consumer declares the pieces it resolves by name") but no mechanism that enforces it. |
| Scope |
Decide how to enforce the rule. Open decision: (a) declare the remaining by-name resolvable packages explicitly in apps/api devDependencies, (b) set a pnpm hoistPattern excluding jest* from the hoisted store — the DEBUG doc considered and deferred this ("reach for it if a second package hits the same class of collision"), or (c) leave as-is and rely on CI running both apps' suites, which is only true once #11 lands. Verify with pnpm why jest / ls node_modules/.pnpm/node_modules before choosing. |
| Test boundary |
Both apps' existing suites are the regression signal; no new tests. |
| Done when |
Adding a name-resolved config value (a reporter, jest-environment-jsdom, an explicit testRunner) to apps/api resolves to a 30.x package, demonstrably, and a future dependency change cannot re-flip the hoist winner unnoticed. |
| Branch |
infra/jest-major-isolation |
| Dependency |
Best done after #11 (CI runs both apps' suites), which is what would catch a re-flip. |
| Risk |
MEDIUM — currently latent (testEnvironment resolves 30.4.1 and testRunner 30.4.2 via jest-config's own deps, verified). The failure mode is a dependency change silently breaking the untouched app's entire suite, as already happened once. |
The natural exit is jest-expo supporting Jest 30, at which point both apps go to 30 and the mix disappears. Close this if that lands first.
Found in review of PR #94 (verdict: PLAUSIBLE — no live defect, forward-looking).
feat/mobile-scaffold), which introduced the mix:apps/mobileneeds jest 29 becausejest-expo(Expo SDK 57) requires it, whileapps/apiis on jest 30. Both pins are legitimate. The consequence is that pnpm's hoisted fallback store (node_modules/.pnpm/node_modules) now holdsjest@30.4.2besidejest-circus,jest-config,jest-runtime,jest-snapshot,expectandpretty-formatat29.7.0. PR #94 already hit this once: 28apps/apisuites failed untiljest-environment-node@^30.4.1was declared explicitly. That pin closes the one observed name, not the class. Seeplans/DEBUG_mobile_scaffold_jest_metro_resolution.md, which records the rule ("every consumer declares the pieces it resolves by name") but no mechanism that enforces it.apps/apidevDependencies, (b) set a pnpmhoistPatternexcludingjest*from the hoisted store — the DEBUG doc considered and deferred this ("reach for it if a second package hits the same class of collision"), or (c) leave as-is and rely on CI running both apps' suites, which is only true once #11 lands. Verify withpnpm why jest/ls node_modules/.pnpm/node_modulesbefore choosing.jest-environment-jsdom, an explicittestRunner) toapps/apiresolves to a 30.x package, demonstrably, and a future dependency change cannot re-flip the hoist winner unnoticed.infra/jest-major-isolationtestEnvironmentresolves 30.4.1 andtestRunner30.4.2 viajest-config's own deps, verified). The failure mode is a dependency change silently breaking the untouched app's entire suite, as already happened once.The natural exit is
jest-exposupporting Jest 30, at which point both apps go to 30 and the mix disappears. Close this if that lands first.