A tree/TUI node built from a manifest carries no version, because a manifest declares a constraint and nothing resolved it. That is the right answer for a range — ^1.2 names a set of versions, not one — and #96 / #104 make the tool say unknown rather than guess.
But a narrow subset of declared constraints is not a range at all:
- a
*.csproj <PackageReference Include="Foo" Version="1.2.3" /> — NuGet's bare Version is a minimum, but in practice with a lockfile-less restore it is the version that lands
- a Gradle version catalog pin,
foo = { module = "g:a", version = "1.2.3" }
- any ecosystem where the declared string parses cleanly as an exact
semver::Version rather than a VersionReq with more than one member
For those, Node::version = None understates what the manifest actually said: there is exactly one version the constraint admits, and reporting it would give a correct freshness verdict where today the row reads unknown.
Why this was deliberately left out of #104
#96 is about not fabricating a verdict from an unread version, and #104 fixes that by making an unread version unrepresentable. The exact-pin case is the opposite problem — a version that was read and is being discarded — and the "a constraint is not a resolution" argument that justifies None for ^1.2 does not cover 1.2.3. Folding it into #104 would have widened that PR past its issue, so it is filed here instead.
What needs deciding
- Which ecosystems' exact pins count. NuGet's
Version="1.2.3" is formally [1.2.3,), so treating it as resolved is a judgement call about what users mean, not a parse fact.
- Where the narrowing lives.
dependable-core already parses constraints; the natural shape is a helper that answers "does this constraint admit exactly one version, and which?" and is used by direct_graph / shallow_graph in crates/dependable-fetch/src/tree.rs.
- Whether the resulting node is distinguishable from a lockfile-resolved one. A pin is a declaration, and the TUI currently draws no distinction between "resolved" and "declared and unambiguous".
Out of scope
Ranges of any kind, dist-tags, and git/path references. Those stay unknown.
Split out of #104's review; see decision (e) there.
A
tree/TUI node built from a manifest carries no version, because a manifest declares a constraint and nothing resolved it. That is the right answer for a range —^1.2names a set of versions, not one — and #96 / #104 make the tool sayunknownrather than guess.But a narrow subset of declared constraints is not a range at all:
*.csproj<PackageReference Include="Foo" Version="1.2.3" />— NuGet's bareVersionis a minimum, but in practice with a lockfile-less restore it is the version that landsfoo = { module = "g:a", version = "1.2.3" }semver::Versionrather than aVersionReqwith more than one memberFor those,
Node::version = Noneunderstates what the manifest actually said: there is exactly one version the constraint admits, and reporting it would give a correct freshness verdict where today the row readsunknown.Why this was deliberately left out of #104
#96 is about not fabricating a verdict from an unread version, and #104 fixes that by making an unread version unrepresentable. The exact-pin case is the opposite problem — a version that was read and is being discarded — and the "a constraint is not a resolution" argument that justifies
Nonefor^1.2does not cover1.2.3. Folding it into #104 would have widened that PR past its issue, so it is filed here instead.What needs deciding
Version="1.2.3"is formally[1.2.3,), so treating it as resolved is a judgement call about what users mean, not a parse fact.dependable-corealready parses constraints; the natural shape is a helper that answers "does this constraint admit exactly one version, and which?" and is used bydirect_graph/shallow_graphincrates/dependable-fetch/src/tree.rs.Out of scope
Ranges of any kind, dist-tags, and git/path references. Those stay
unknown.Split out of #104's review; see decision (e) there.