@@ -456,25 +456,29 @@ def cooldown_hint(specs)
456456 def cooldown_excluded? ( spec )
457457 return false unless spec . respond_to? ( :created_at ) && spec . created_at
458458 return false unless spec . respond_to? ( :remote ) && spec . remote
459- return false if pinned_by_lockfile_floor ?( spec )
459+ return false if locked_by_lockfile ?( spec )
460460 days = spec . remote . effective_cooldown
461461 return false if days . nil? || days <= 0
462462 ( cooldown_now - spec . created_at ) < ( days * 86_400 )
463463 end
464464
465- # A spec sitting exactly at a `>= locked_version` prevent-downgrade floor is
466- # the version the lockfile currently pins. `bundle update` and `bundle
467- # outdated` install that floor so resolution never moves a gem backwards.
468- # Filtering it out for cooldown would then make resolution impossible
469- # whenever the locked version is itself inside the cooldown window, which is
470- # exactly what happens to a lockfile written before cooldown was enabled.
471- # Keep it eligible; gems being explicitly updated carry an exact `=`
472- # requirement instead and stay subject to the cooldown filter.
473- def pinned_by_lockfile_floor? ( spec )
465+ # A version already written to the lockfile has been adopted, and cooldown
466+ # only governs the adoption of *new* versions, so it must never retract one
467+ # the lockfile already pins. Keying this off the locked specs rather than the
468+ # prevent-downgrade floor matters because that floor is absent on resolutions
469+ # that re-pick a gem from scratch: the auxiliary full update run to compute
470+ # `--update` targets, and the from-scratch retries after a conflict unlocks a
471+ # gem. In those passes the locked version is the only candidate, so filtering
472+ # it out makes an unrelated operation impossible whenever every published
473+ # version matching the requirement sits inside the cooldown window.
474+ #
475+ # Gems named on a `bundle update GEM` command are the exception: the user
476+ # asked to move them, so they stay subject to cooldown and a locked-but-fresh
477+ # release is pushed back to an older one (or fails loudly when none exists).
478+ def locked_by_lockfile? ( spec )
474479 return false unless defined? ( @base ) && @base
475- requirement = base_requirements [ spec . name ]
476- return false unless requirement && !requirement . exact?
477- requirement . requirements . any? { |op , version | op == ">=" && version == spec . version }
480+ return false if @base . explicitly_unlocked? ( spec . name )
481+ @base . locked_specs [ spec . name ] . any? { |locked | locked . version == spec . version }
478482 end
479483
480484 def cooldown_now
0 commit comments