Skip to content

fix(project): read a usage's versionConstraint as a semver range - #1568

Merged
HuiJun merged 6 commits into
developfrom
fix/usage-version-ranges
Oct 28, 2026
Merged

HuiJun merged 6 commits into
developfrom
fix/usage-version-ranges

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

What and why

Interchange.satisfies understood only an exact version, so a usage whose versionConstraint is a range (^1.0.0, >=1.0.0, ~1.1.0, 1.1.x, >=1.0.0 <2.0.0) never linked by elimination — every candidate was dropped, no warning — and on the publisher/name match pickInstalled fell back to the latest installed version when nothing satisfied, so the range was ignored (an installed 2.0.0 picked for ^1.0.0). Reproducer: one usage {"resource": "pkg:hg/hg.example.org/models/hg-widgets", "versionConstraint": "^1.0.0"}, lib/acme-widgets_1.1.0/ installed, private import Widgets::*; → unresolved reference: Widgets, while "1.1.0", "=1.1.0" or no constraint loaded it.

  • One grammar, one place. internal/workspace/project/constraint.go: parseVersionRange reads the npm-style semver range — exact (1.2.3, =1.2.3, v1.2.3), comparators < <= > >=, caret ^, tilde ~, x/X/* wildcards (1.x, *), hyphen ranges (1.0.0 - 2.0.0), space or comma as AND, || as OR, partial versions as their wildcard (1.2 = 1.2.x), build metadata validated and ignored (1.2.3+feature-one is 1.2.3), pre-releases ordered as semver 2.0.0 orders them (newerVersion) and admitted only where an alternative names their release (>=1.0.0-rc.1 <1.0.0 admits 1.0.0-rc.9; ^1.2.3, <2.0.0 and * admit no pre-release), as npm ranges do; ^*, ^x, ~* are unbounded. Usage.versionRange() parses the usage's constraint (versionConstraint, else the IRI's @version); Interchange.satisfies(versionRange) is the only test, used by pickInstalled, pickVersion and pickUnclaimed.
  • Highest satisfying, else a warning. pickInstalled is now "highest installed version in the range; else nothing", returning the versions it found named so used can say why. A constraint no installed version satisfies (usage … asks for version "^3.0.0" of acme/widgets, which none of the installed versions satisfies: 1.1.0 (…)) and a constraint outside the grammar (usage …: "latest": malformed version constraint: …) are project.load warnings at the consumer's .project.json — the same routing as the ambiguity warning, so -warnings-as-errors, -baseline, -quiet, JSON/SARIF/JUnit treat them alike — and the usage links nothing; never silently "any", never silently the latest.

Specification basis. KerML 1.1 §10.3 Table 12 makes versionConstraint an optional string and defines no syntax for it. The npm-style range is the de-facto syntax package managers share; it is named as such in docs/project/spec-compliance.md (Table 12 row, status unchanged), docs/internals/design/package-management.md (§1 row and §3 "Versions") and the guide's "Working inside a package-manager project".

Behaviour changes (adjudicated against the tests that relied on "exact, else latest"):

  • A versionConstraint or @version no installed version satisfies no longer takes the latest installed version: it is a warning and the usage selects nothing. TestPickInstalledPrefersTheConstrainedVersionThenTheLatest asserted the fallback (^3.0.0 over 1.0.0/2.0.0/10.0.0 → 10.0.0; @3.0.0 → latest) and is replaced by TestPickInstalledTakesTheHighestVersionInTheRange; kept because a constraint the project author wrote is a statement about what the model was validated against, and loading something outside it silently is the failure this PR removes. No fixture under tests/ or examples/ depended on the fallback; no update flag re-recorded anything.
  • @1.0.0%2Bbuild (1.0.0+build) now selects 1.0.0 — build metadata is outside semver precedence — where it fell back to the latest.
  • An unconstrained usage still takes the latest installed version (TestAroundReadsLibBesideTheProjectFile unchanged).

This branch merges fix/usage-elimination-named-forms (#1561), whose used/pickUnclaimed lines it changes, then origin/develop at 12e4fc392, then #1561's head 6320ce1c0 again for its review fix (a deferred usage is revisited after every claim, even one that enqueues nothing) — one merge commit each, no rebase; it lands after #1561 (it applies clean on develop both alone and after #1561). The develop merge keeps #1563's elimination-link warning (Projects.Linking, eliminationUsage, the installedKey exemption, pickUnclaimed never picking the consumer itself) beside the spellsLocation and versionRange rules: an elimination link is said once as a warning, the publisher/name match says nothing, and the unsatisfied/malformed warnings join the same Linking list. The new tests assert that merged semantics (TestUsedHonoursARangeConstraintOnBothPaths expects the link said once on the pkg:hg/ path and nothing on the pkg:sysand/ path; the exhaustion test expects its two links said).

How it was verified

  • internal/workspace/project: TestVersionRangeMatches (every form above, 38 constraints, yes/no versions each, pre-release admission, ^*/~x, hyphenated build metadata) and TestParseVersionRangeRefuses (=, ^, 1.2.3.4, abc, 1.x.3, >*, latest, 1.0.0 -, 1.0.0-, 1.2.3+, 1.2.3+a..b, …); TestUsedHonoursARangeConstraintOnBothPaths (12 constraints × the slug path pkg:sysand/acme/widgets and the elimination path pkg:hg/…/hg-widgets, over 1.0.0-rc.1/1.0.0/1.1.0/2.0.0 installed, each selecting the expected version alone); TestUsedReportsAnUnsatisfiedOrMalformedConstraint (^3.0.0, <1.0.0 over 1.1.0, latest, 1.x.3 on both paths: nothing used, the warning at .project.json with its text); the two pickInstalled tests reworked as above.
  • tests/kpar/TestARangeConstraintLinksTheInstalledVersionItAdmits: the reproducer under ^1.0.0, >=1.0.0, ~1.1.0, 1.1.x, >=1.0.0 <2.0.0, 1.1.0, =1.1.0 and none loads lib/acme-widgets_1.1.0/Widgets.sysml through repl.Session and sysml -validate; under latest, -validate -warnings-as-errors fails with malformed version constraint and unresolved reference: Widgets.
  • Gate: gofmt -l . empty; go vet ./..., go build ./...; go test -count=1 -timeout 40m ./... with OPENSYSML_REQUIRE_TRAINING_CORPUS, _PILOT_CORPORA, _SPEC_EXAMPLES, _PILOT_LIBRARY_XMI, _PSSM_SUITE set → all packages ok; make lint; make man-check; make docs-counts; python3 scripts/changelog.py check; tests/wasm budgets.

Checklist

  • make test and make lint pass locally
  • Tests added or updated for the change
  • Documentation extended where it already covers the surface (see CONTRIBUTING.md)
  • Changelog entry added as changes/unreleased/<slug>.<section>.md, not as an edit to CHANGELOG.md
  • baselines regenerated and make docs-counts run if a gate count moved (compliance rows need nothing: the census is counted at docs build)
  • No internal work-item labels (waves, slices, F4, K5) in the body, docs, or changelog

Link to Devin session: https://nasa-jpl-demo.devinenterprise.com/sessions/020c7f4cdced4720a9afe70d9e4490ae
Open in Devin Desktop: https://nasa-jpl-demo.devinenterprise.com/desktop/session/020c7f4cdced4720a9afe70d9e4490ae?variant=devin
Requested by: @HuiJun

Exact, =/v prefix, comparators, caret, tilde, x/* wildcards, hyphen ranges,
space/comma as AND, || as OR, pre-release precedence per semver 2.0.0, in one
place (constraint.go) used by pickInstalled, pickVersion and pickUnclaimed.
The highest installed version in the range is taken; none, or a constraint the
grammar does not read, is a project.load warning and the usage links nothing.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

Co-Authored-By: jason.han <hanhuijun@gmail.com>

# Conflicts:
#	docs/guide/03-command-line.md
#	docs/internals/design/package-management.md
#	internal/workspace/project/discovery.go
…on-ranges

Co-Authored-By: jason.han <hanhuijun@gmail.com>

# Conflicts:
#	docs/internals/design/package-management.md
#	internal/workspace/project/interchange_test.go
@devin-ai-integration
devin-ai-integration Bot marked this pull request as ready for review October 28, 2026 07:06
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration Bot and others added 2 commits October 28, 2026 07:11
…ase; ^*/~* admit any version; build metadata is read

npm-style ranges let a pre-release in only through a comparator that is a
pre-release of the same release, so ^1.2.3 no longer takes 1.3.0-rc.1 and *
takes no pre-release; ^*, ^x and ~* lower to no bound; +build metadata is
validated and discarded instead of being read as an empty pre-release.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
Co-Authored-By: jason.han <hanhuijun@gmail.com>
…ranges

Co-Authored-By: jason.han <hanhuijun@gmail.com>

# Conflicts:
#	docs/project/spec-compliance.md
@HuiJun
HuiJun merged commit e19d9be into develop Oct 28, 2026
30 checks passed
@HuiJun
HuiJun deleted the fix/usage-version-ranges branch October 28, 2026 14:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant