Summary
When a package is declared as a top-level requirement with an == pin (to bypass the release-age cooldown), the bypass is ignored if the same package was already resolved as a transitive dependency earlier in the bootstrap process. This is due to the session-level cache in BootstrapRequirementResolver not including req_type in its cache key.
Root Cause
In src/fromager/bootstrap_requirement_resolver.py, the resolution cache key is (str(req), pre_built):
self._resolved_requirements: dict[
tuple[str, bool], tuple[tuple[str, Version], ...]
] = {}
The req_type (TOP_LEVEL vs INSTALL) is not part of the cache key. When a package is first encountered as a transitive dependency (RequirementType.INSTALL), the cooldown is enforced and the result is cached. When the same package is later encountered as a top-level requirement (RequirementType.TOP_LEVEL) with an == pin, the cached (cooldown-blocked) result is returned without re-evaluating resolve_package_cooldown().
Reproduction
- Package
B depends on A>=1.0
- Package
A==2.0.0 was published within the last X days (within cooldown window)
- Declare both
A==2.0.0 and B as top-level requirements
- If
B is processed first, A>=1.0 resolves as transitive → cooldown blocks version 2.0.0 → result cached
- When
A==2.0.0 is later processed as top-level, cache returns the blocked result → bypass never applies
Expected: Top-level A==2.0.0 should bypass the cooldown regardless of processing order.
Actual: Bypass only works if A==2.0.0 is resolved before any transitive dependency pulls in A.
Suggested Fix
Include req_type in the cache key:
cache_key = (str(req), pre_built, req_type)
This ensures transitive and top-level resolutions are cached separately, preserving the req_type context needed for cooldown bypass decisions.
Workaround
Move the cooldown-bypassing top-level requirement to the very first position in the requirements file so it is resolved before any transitive dependency can pull it in.
Summary
When a package is declared as a top-level requirement with an
==pin (to bypass the release-age cooldown), the bypass is ignored if the same package was already resolved as a transitive dependency earlier in the bootstrap process. This is due to the session-level cache inBootstrapRequirementResolvernot includingreq_typein its cache key.Root Cause
In
src/fromager/bootstrap_requirement_resolver.py, the resolution cache key is(str(req), pre_built):The
req_type(TOP_LEVEL vs INSTALL) is not part of the cache key. When a package is first encountered as a transitive dependency (RequirementType.INSTALL), the cooldown is enforced and the result is cached. When the same package is later encountered as a top-level requirement (RequirementType.TOP_LEVEL) with an==pin, the cached (cooldown-blocked) result is returned without re-evaluatingresolve_package_cooldown().Reproduction
Bdepends onA>=1.0A==2.0.0was published within the last X days (within cooldown window)A==2.0.0andBas top-level requirementsBis processed first,A>=1.0resolves as transitive → cooldown blocks version2.0.0→ result cachedA==2.0.0is later processed as top-level, cache returns the blocked result → bypass never appliesExpected: Top-level
A==2.0.0should bypass the cooldown regardless of processing order.Actual: Bypass only works if
A==2.0.0is resolved before any transitive dependency pulls inA.Suggested Fix
Include
req_typein the cache key:This ensures transitive and top-level resolutions are cached separately, preserving the
req_typecontext needed for cooldown bypass decisions.Workaround
Move the cooldown-bypassing top-level requirement to the very first position in the requirements file so it is resolved before any transitive dependency can pull it in.