Item::is_pinned (crates/dependable-core/src/item.rs) decides whether fix may move a constraint. It is a spelling test:
pub fn is_pinned(&self) -> bool {
let c = self.version_constraint.trim_start();
c.starts_with('=') && !c.starts_with("==")
}
So a Cargo =1.2.3 is protected from a plain dependable fix, and an npm "lodash": "1.2.3" is not — even though both name exactly one release. The difference is which characters the ecosystem uses to write a pin, not whether the author pinned anything.
Why this surfaces now
#106 (refactor/92-ecosystem-aware-wildcard, shipped: the ecosystem-aware wildcard decline) formally records what a bare version means in each ecosystem, via Ecosystem::bare_version and BareVersion. It states that npm, Composer, pub, Hex and Poetry read a bare version as BareVersion::Exact — one release and no other. Ecosystem::bare_version_is_exact() is precisely the predicate that would close this gap, and is_pinned does not consult it.
That makes the inconsistency a recorded one rather than an implicit one: the repository now asserts in dependable-core that an npm 1.2.3 is an exact pin, while is_pinned continues to answer false for it.
Confirmed behaviour
Against check_version_for and is_pinned on the current tree, with versions ["1.2.3", "1.9.0"]:
| constraint |
ecosystem |
lockfile |
status |
latest_compatible |
is_pinned |
plain fix |
=1.2.3 |
Rust |
1.2.3 |
UpdateAvailable |
1.2.3 |
true |
skipped |
1.2.3 |
Npm |
1.2.3 |
UpdateAvailable |
1.9.0 |
false |
rewritten to 1.9.0 |
The npm row moves because to_version_req normalizes a bare 1.2.3 to ^1.2.3 — the Rust semver crate's reading, not node-semver's — so the checker reports a 1.9.0 update against a constraint node-semver admits only 1.2.3 for. fix then takes it, because no guard in rewrite_constraint objects: a full three-component version is not a wildcard and not a partial version, and fix.rs asserts that shape stays rewritable on purpose.
So the same bare npm version is read two ways in one codebase: Exact by Ecosystem::bare_version, and ^1.2.3 by the checker that decides whether to offer an update at all.
What needs deciding
- Whether
is_pinned should become semantic — ecosystem.bare_version_is_exact() && the constraint is a full concrete version — or whether the pin question belongs somewhere that already knows the ecosystem. Item does not currently carry one.
- Whether the checker's caret reading of a bare npm version is itself the bug, in which case fixing that removes the
UpdateAvailable that gets fix involved, and is_pinned matters less. These are two fixes for one symptom and only one may be wanted.
- What
--all should then do. Today it deliberately moves a Cargo =1.2.3; whatever is_pinned comes to mean, --all should keep overriding it.
Out of scope
Ranges, dist-tags, wildcards, and partial versions — #106 settled how fix treats those. This is only about a constraint that names exactly one release.
Related but distinct: #107 is about reporting an exact pin's version on a tree node rather than unknown. This issue is about protecting one from being rewritten. They share the "does this constraint admit exactly one version" question and may want a common helper.
Came out of the review of #106.
Item::is_pinned(crates/dependable-core/src/item.rs) decides whetherfixmay move a constraint. It is a spelling test:So a Cargo
=1.2.3is protected from a plaindependable fix, and an npm"lodash": "1.2.3"is not — even though both name exactly one release. The difference is which characters the ecosystem uses to write a pin, not whether the author pinned anything.Why this surfaces now
#106 (
refactor/92-ecosystem-aware-wildcard, shipped: the ecosystem-aware wildcard decline) formally records what a bare version means in each ecosystem, viaEcosystem::bare_versionandBareVersion. It states that npm, Composer, pub, Hex and Poetry read a bare version asBareVersion::Exact— one release and no other.Ecosystem::bare_version_is_exact()is precisely the predicate that would close this gap, andis_pinneddoes not consult it.That makes the inconsistency a recorded one rather than an implicit one: the repository now asserts in
dependable-corethat an npm1.2.3is an exact pin, whileis_pinnedcontinues to answerfalsefor it.Confirmed behaviour
Against
check_version_forandis_pinnedon the current tree, with versions["1.2.3", "1.9.0"]:latest_compatibleis_pinnedfix=1.2.31.2.3UpdateAvailable1.2.3true1.2.31.2.3UpdateAvailable1.9.0false1.9.0The npm row moves because
to_version_reqnormalizes a bare1.2.3to^1.2.3— the Rustsemvercrate's reading, not node-semver's — so the checker reports a 1.9.0 update against a constraint node-semver admits only1.2.3for.fixthen takes it, because no guard inrewrite_constraintobjects: a full three-component version is not a wildcard and not a partial version, andfix.rsasserts that shape stays rewritable on purpose.So the same bare npm version is read two ways in one codebase:
ExactbyEcosystem::bare_version, and^1.2.3by the checker that decides whether to offer an update at all.What needs deciding
is_pinnedshould become semantic —ecosystem.bare_version_is_exact() && the constraint is a full concrete version— or whether the pin question belongs somewhere that already knows the ecosystem.Itemdoes not currently carry one.UpdateAvailablethat getsfixinvolved, andis_pinnedmatters less. These are two fixes for one symptom and only one may be wanted.--allshould then do. Today it deliberately moves a Cargo=1.2.3; whateveris_pinnedcomes to mean,--allshould keep overriding it.Out of scope
Ranges, dist-tags, wildcards, and partial versions — #106 settled how
fixtreats those. This is only about a constraint that names exactly one release.Related but distinct: #107 is about reporting an exact pin's version on a tree node rather than
unknown. This issue is about protecting one from being rewritten. They share the "does this constraint admit exactly one version" question and may want a common helper.Came out of the review of #106.