aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/mozart-registry
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-05-03 22:18:32 +0900
committernsfisis <nsfisis@gmail.com>2026-05-03 22:18:32 +0900
commit55e6f5367bf86d1dc6e99b7492d86c5208dd1f1c (patch)
treef923e44b120790aa1244f5183aba4f908754029b /crates/mozart-registry
parent9a467625ff8d135c650c7fefacfa0358002ce4d2 (diff)
downloadphp-mozart-55e6f5367bf86d1dc6e99b7492d86c5208dd1f1c.tar.gz
php-mozart-55e6f5367bf86d1dc6e99b7492d86c5208dd1f1c.tar.zst
php-mozart-55e6f5367bf86d1dc6e99b7492d86c5208dd1f1c.zip
fix(resolver): reject partial update when locked version fails stability
A partial update reuses every non-allow-listed locked package as a fixed pool entry, ignoring stability filters. So when a user tightens `minimum-stability` (or drops a `stability-flags` entry the lock used to ride on), Mozart silently kept the rejected version and produced a plan Composer would have failed on. Mirror Composer's `Pool::isUnacceptableFixedOrLockedPackage` path: walk the locked packages before pool construction, surface every entry whose version no longer passes `passes_stability_filter`, and bail with the same "fixed to <v> (lock file version) ... rejected by your minimum-stability" pointer Composer prints from `Problem::getPrettyString`.
Diffstat (limited to 'crates/mozart-registry')
-rw-r--r--crates/mozart-registry/src/resolver.rs38
1 files changed, 38 insertions, 0 deletions
diff --git a/crates/mozart-registry/src/resolver.rs b/crates/mozart-registry/src/resolver.rs
index 33c3659..67650e6 100644
--- a/crates/mozart-registry/src/resolver.rs
+++ b/crates/mozart-registry/src/resolver.rs
@@ -1010,6 +1010,44 @@ pub async fn resolve(request: &ResolveRequest) -> Result<Vec<ResolvedPackage>, R
// different version (whether directly, or via `replace`, which would
// otherwise let an upgraded replacer silently drop the dependency).
//
+ // Pre-check: a locked package whose version is rejected by the
+ // current minimum-stability (composer.json may have tightened
+ // stability or dropped a `stability-flags` entry the lock relied on)
+ // cannot be reused as a fixed pool entry. Mirrors what Composer
+ // surfaces via `Pool::isUnacceptableFixedOrLockedPackage` +
+ // `Problem::getPrettyString`: bail with the "fixed to <v> (lock file
+ // version) but that version is rejected by your minimum-stability"
+ // pointer so the user knows to add the package to the update
+ // arguments (or use `--with-all-dependencies`).
+ {
+ let mut rejected: Vec<String> = Vec::new();
+ for locked in &request.locked_packages {
+ let Ok(v) = Version::parse(&locked.version_normalized) else {
+ continue;
+ };
+ if !passes_stability_filter(
+ &locked.name,
+ &v,
+ request.minimum_stability,
+ &stability_flags,
+ ) {
+ rejected.push(format!(
+ " - {} is fixed to {} (lock file version) by a partial update but that version is rejected by your minimum-stability. Make sure you list it as an argument for the update command.",
+ locked.name, locked.pretty_version
+ ));
+ }
+ }
+ if !rejected.is_empty() {
+ let report = rejected
+ .into_iter()
+ .enumerate()
+ .map(|(i, msg)| format!(" Problem {}\n{}", i + 1, msg))
+ .collect::<Vec<_>>()
+ .join("\n");
+ return Err(ResolveError::NoSolution(report));
+ }
+ }
+
// Build a map first so the filter below knows which (name, version)
// pairs are the only allowed entries for locked names.
let locked_name_to_version: IndexMap<String, String> = request