feat(package): resolve and lock two majors of one package #328

Merged
argus merged 1 commit from loop/323-two-majors-resolve into main 2026-10-12 13:56:50 +00:00
Collaborator

Closes #323

Slice 1 of 3 of #28: the resolver and the lock take two majors of one package. Workspace loading and checker identity (#324) and lowering and the back ends (#325) are the next slices; until then a graph with two majors resolves and locks but does not build.

Loop decisions

  • Two runs, not one. resolve first runs the solver exactly as before, one version per package. Only when that has no solution does it run again with every package whose candidates span several majors split into one solver package per major (acme/json 1.x, acme/json 2.x). A graph that resolved before therefore resolves and locks byte-identically, and an update never adds a second major where one does. The second run's answer, success or explanation, is the answer.
  • Spanning requirements are alternatives. A requirement that reaches into several majors (*, >=1.0.0) becomes one incompatibility {depender, not acme/json 1.x R, not acme/json 2.x R}. It holds as soon as one major in the graph satisfies it. Where none does yet and nothing else is left to decide, the solver decides the last (highest) major and learns its way down if that fails (chooseMajor, only active in the split run). A requirement that reaches no major with a release becomes a major of its own with no versions, so the explanation says no version of acme/json 3.x matches ^3.0.0.
  • Labels. In the split run a split package's key is name M.x, so explanations read acme/a depends on acme/json 1.x ^1.2.0. Unsplit packages keep their plain name, so explanations of single-major graphs are unchanged.
  • API. Resolution.versions and preferred are Map<String, List<Version>>, with one version per major, lowest first. The lock keeps the highest allowed locked version per key.
  • Lock. renderLock sorts graph by name, then version; single-major locks are unchanged. Lock.lockedWithin(name, versions) picks the entry of a requirement's major. lockProblems (so torb lock --check and torb install) and publishProblems use it.
  • CLI. torb add/remove/update print changes and capability gains per (name, major).

Tests (compiler/tests/packages.test.trb)

  • New group "Two majors of one package":
    • a -> json ^1 with b -> json ^2 resolves to one version of each.
    • A graph that one version does stays at one version, even with a newer major in the registry.
    • The lock's version is kept per major.
    • A spanning requirement takes the highest major, and the lower one where the highest fails.
    • A spanning requirement adds nothing when a major already in the graph satisfies it.
    • A conflict inside one major (^1.2.0 vs <1.1.0) still fails, naming acme/json 1.x.
  • Lock: a round trip with two majors gives the same bytes, sorted by name then version. A two-major lock gives no lockProblems for requirements of either major.
  • Two existing explanation tests (the impossible set, and the chain of three) relied on a cross-major conflict, which now resolves. They are rewritten as within-major conflicts with the same explanation shape.

docs/design/PROJECT.md section 6a and the RELEASE.md "not built" list are updated. Design docs do not feed skills/, so no regeneration is needed. Seed impact: none.

Closes #323 Slice 1 of 3 of #28: the resolver and the lock take two majors of one package. Workspace loading and checker identity (#324) and lowering and the back ends (#325) are the next slices; until then a graph with two majors resolves and locks but does not build. ## Loop decisions - **Two runs, not one.** `resolve` first runs the solver exactly as before, one version per package. Only when that has no solution does it run again with every package whose candidates span several majors split into one solver package per major (`acme/json 1.x`, `acme/json 2.x`). A graph that resolved before therefore resolves and locks byte-identically, and an update never adds a second major where one does. The second run's answer, success or explanation, is the answer. - **Spanning requirements are alternatives.** A requirement that reaches into several majors (`*`, `>=1.0.0`) becomes one incompatibility `{depender, not acme/json 1.x R, not acme/json 2.x R}`. It holds as soon as one major in the graph satisfies it. Where none does yet and nothing else is left to decide, the solver decides the last (highest) major and learns its way down if that fails (`chooseMajor`, only active in the split run). A requirement that reaches no major with a release becomes a major of its own with no versions, so the explanation says `no version of acme/json 3.x matches ^3.0.0`. - **Labels.** In the split run a split package's key is `name M.x`, so explanations read `acme/a depends on acme/json 1.x ^1.2.0`. Unsplit packages keep their plain name, so explanations of single-major graphs are unchanged. - **API.** `Resolution.versions` and `preferred` are `Map<String, List<Version>>`, with one version per major, lowest first. The lock keeps the highest allowed locked version per key. - **Lock.** `renderLock` sorts `graph` by name, then version; single-major locks are unchanged. `Lock.lockedWithin(name, versions)` picks the entry of a requirement's major. `lockProblems` (so `torb lock --check` and `torb install`) and `publishProblems` use it. - **CLI.** `torb add/remove/update` print changes and capability gains per (name, major). ## Tests (compiler/tests/packages.test.trb) - New group "Two majors of one package": - `a -> json ^1` with `b -> json ^2` resolves to one version of each. - A graph that one version does stays at one version, even with a newer major in the registry. - The lock's version is kept per major. - A spanning requirement takes the highest major, and the lower one where the highest fails. - A spanning requirement adds nothing when a major already in the graph satisfies it. - A conflict inside one major (`^1.2.0` vs `<1.1.0`) still fails, naming `acme/json 1.x`. - Lock: a round trip with two majors gives the same bytes, sorted by name then version. A two-major lock gives no `lockProblems` for requirements of either major. - Two existing explanation tests (the impossible set, and the chain of three) relied on a cross-major conflict, which now resolves. They are rewritten as within-major conflicts with the same explanation shape. `docs/design/PROJECT.md` section 6a and the `RELEASE.md` "not built" list are updated. Design docs do not feed `skills/`, so no regeneration is needed. Seed impact: none.
feat(package): resolve and lock two majors of one package
Some checks failed
ci / commit messages (pull_request) Successful in 10s
ci / what the change touches (pull_request) Successful in 6s
pdisc / pdisc (pull_request) Successful in 10s
ci / which runners exist (pull_request) Successful in 1s
ci / bootstrap and conformance (windows-x64) (pull_request) Has been skipped
ci / bootstrap and conformance (linux-arm64) (pull_request) Has been skipped
ci / bootstrap and conformance (macos-arm64) (pull_request) Has been skipped
ci / bootstrap (linux-x64) (pull_request) Failing after 15s
ci / tier A (linux-x64) (pull_request) Has been skipped
ci / tier B (linux-x64) (pull_request) Has been skipped
ci / tools/release-sync and tools/registry (linux-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (windows-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (linux-arm64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (macos-arm64) (pull_request) Has been skipped
ci / every host emits the same C (pull_request) Has been skipped
ci / the playground (torb as WebAssembly) (pull_request) Has been skipped
ci / the VS Code extension (editors/vscode) (pull_request) Successful in 12s
ci / gates (pull_request) Has been cancelled
ci / pack the seed (pull_request) Has been cancelled
ci / archives (pull_request) Has been cancelled
cd99b615a5
Resolution runs once with one version per package and, only where that
has no solution, again with every package whose candidates span several
majors split into one solver package per major (`acme/json 1.x`). A
requirement reaching into several majors is one incompatibility with a
negative term per major; the highest major is decided first. The lock
writes one `package` line per major, sorted by name and then version,
and `torb lock --check` matches each requirement against the entry of
its major.

Closes #323
orion force-pushed loop/323-two-majors-resolve from cd99b615a5
Some checks failed
ci / commit messages (pull_request) Successful in 10s
ci / what the change touches (pull_request) Successful in 6s
pdisc / pdisc (pull_request) Successful in 10s
ci / which runners exist (pull_request) Successful in 1s
ci / bootstrap and conformance (windows-x64) (pull_request) Has been skipped
ci / bootstrap and conformance (linux-arm64) (pull_request) Has been skipped
ci / bootstrap and conformance (macos-arm64) (pull_request) Has been skipped
ci / bootstrap (linux-x64) (pull_request) Failing after 15s
ci / tier A (linux-x64) (pull_request) Has been skipped
ci / tier B (linux-x64) (pull_request) Has been skipped
ci / tools/release-sync and tools/registry (linux-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (windows-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (linux-arm64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (macos-arm64) (pull_request) Has been skipped
ci / every host emits the same C (pull_request) Has been skipped
ci / the playground (torb as WebAssembly) (pull_request) Has been skipped
ci / the VS Code extension (editors/vscode) (pull_request) Successful in 12s
ci / gates (pull_request) Has been cancelled
ci / pack the seed (pull_request) Has been cancelled
ci / archives (pull_request) Has been cancelled
to df9de48e77
Some checks failed
ci / commit messages (pull_request) Successful in 16s
ci / what the change touches (pull_request) Successful in 9s
pdisc / pdisc (pull_request) Successful in 13s
ci / which runners exist (pull_request) Successful in 2s
ci / bootstrap and conformance (windows-x64) (pull_request) Has been skipped
ci / bootstrap and conformance (linux-arm64) (pull_request) Has been skipped
ci / bootstrap and conformance (macos-arm64) (pull_request) Has been skipped
ci / the VS Code extension (editors/vscode) (pull_request) Successful in 18s
ci / bootstrap (linux-x64) (pull_request) Successful in 5m28s
ci / tier B (linux-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (windows-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (linux-arm64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (macos-arm64) (pull_request) Has been skipped
ci / tier A (linux-x64) (pull_request) Failing after 4m21s
ci / tools/release-sync and tools/registry (linux-x64) (pull_request) Successful in 1m52s
ci / gates (pull_request) Has been cancelled
ci / pack the seed (pull_request) Has been cancelled
ci / archives (pull_request) Has been cancelled
ci / the playground (torb as WebAssembly) (pull_request) Has been cancelled
ci / every host emits the same C (pull_request) Has been cancelled
2026-10-12 11:23:15 +00:00
Compare
orion force-pushed loop/323-two-majors-resolve from df9de48e77
Some checks failed
ci / commit messages (pull_request) Successful in 16s
ci / what the change touches (pull_request) Successful in 9s
pdisc / pdisc (pull_request) Successful in 13s
ci / which runners exist (pull_request) Successful in 2s
ci / bootstrap and conformance (windows-x64) (pull_request) Has been skipped
ci / bootstrap and conformance (linux-arm64) (pull_request) Has been skipped
ci / bootstrap and conformance (macos-arm64) (pull_request) Has been skipped
ci / the VS Code extension (editors/vscode) (pull_request) Successful in 18s
ci / bootstrap (linux-x64) (pull_request) Successful in 5m28s
ci / tier B (linux-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (windows-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (linux-arm64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (macos-arm64) (pull_request) Has been skipped
ci / tier A (linux-x64) (pull_request) Failing after 4m21s
ci / tools/release-sync and tools/registry (linux-x64) (pull_request) Successful in 1m52s
ci / gates (pull_request) Has been cancelled
ci / pack the seed (pull_request) Has been cancelled
ci / archives (pull_request) Has been cancelled
ci / the playground (torb as WebAssembly) (pull_request) Has been cancelled
ci / every host emits the same C (pull_request) Has been cancelled
to 6f592335db
Some checks failed
ci / commit messages (pull_request) Successful in 12s
ci / what the change touches (pull_request) Successful in 10s
pdisc / pdisc (pull_request) Successful in 13s
ci / which runners exist (pull_request) Successful in 1s
ci / bootstrap and conformance (windows-x64) (pull_request) Has been skipped
ci / bootstrap and conformance (linux-arm64) (pull_request) Has been skipped
ci / bootstrap and conformance (macos-arm64) (pull_request) Has been skipped
ci / the VS Code extension (editors/vscode) (pull_request) Successful in 14s
ci / bootstrap (linux-x64) (pull_request) Successful in 5m15s
ci / cross-compiled on linux-x64 (windows-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (linux-arm64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (macos-arm64) (pull_request) Has been skipped
ci / tier A (linux-x64) (pull_request) Failing after 4m15s
ci / tools/release-sync and tools/registry (linux-x64) (pull_request) Successful in 1m51s
ci / gates (pull_request) Has been cancelled
ci / the playground (torb as WebAssembly) (pull_request) Has been cancelled
ci / pack the seed (pull_request) Has been cancelled
ci / archives (pull_request) Has been cancelled
ci / every host emits the same C (pull_request) Has been cancelled
ci / tier B (linux-x64) (pull_request) Has been cancelled
2026-10-12 12:12:29 +00:00
Compare
orion force-pushed loop/323-two-majors-resolve from 6f592335db
Some checks failed
ci / commit messages (pull_request) Successful in 12s
ci / what the change touches (pull_request) Successful in 10s
pdisc / pdisc (pull_request) Successful in 13s
ci / which runners exist (pull_request) Successful in 1s
ci / bootstrap and conformance (windows-x64) (pull_request) Has been skipped
ci / bootstrap and conformance (linux-arm64) (pull_request) Has been skipped
ci / bootstrap and conformance (macos-arm64) (pull_request) Has been skipped
ci / the VS Code extension (editors/vscode) (pull_request) Successful in 14s
ci / bootstrap (linux-x64) (pull_request) Successful in 5m15s
ci / cross-compiled on linux-x64 (windows-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (linux-arm64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (macos-arm64) (pull_request) Has been skipped
ci / tier A (linux-x64) (pull_request) Failing after 4m15s
ci / tools/release-sync and tools/registry (linux-x64) (pull_request) Successful in 1m51s
ci / gates (pull_request) Has been cancelled
ci / the playground (torb as WebAssembly) (pull_request) Has been cancelled
ci / pack the seed (pull_request) Has been cancelled
ci / archives (pull_request) Has been cancelled
ci / every host emits the same C (pull_request) Has been cancelled
ci / tier B (linux-x64) (pull_request) Has been cancelled
to 580eb6611f
All checks were successful
ci / commit messages (pull_request) Successful in 5s
ci / what the change touches (pull_request) Successful in 4s
pdisc / pdisc (pull_request) Successful in 10s
ci / which runners exist (pull_request) Successful in 1s
ci / bootstrap and conformance (windows-x64) (pull_request) Has been skipped
ci / bootstrap and conformance (linux-arm64) (pull_request) Has been skipped
ci / bootstrap and conformance (macos-arm64) (pull_request) Has been skipped
ci / bootstrap (linux-x64) (pull_request) Successful in 2m3s
ci / tier B (linux-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (windows-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (linux-arm64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (macos-arm64) (pull_request) Has been skipped
ci / the VS Code extension (editors/vscode) (pull_request) Successful in 14s
ci / tools/release-sync and tools/registry (linux-x64) (pull_request) Successful in 1m33s
ci / tier A (linux-x64) (pull_request) Successful in 4m46s
ci / every host emits the same C (pull_request) Successful in 4m22s
ci / the playground (torb as WebAssembly) (pull_request) Successful in 1m57s
ci / pack the seed (pull_request) Successful in 8s
ci / archives (pull_request) Has been skipped
ci / gates (pull_request) Successful in 0s
2026-10-12 13:16:04 +00:00
Compare
argus approved these changes 2026-10-12 13:56:49 +00:00
argus left a comment

Approved.

  • Tests: new group "Two majors of one package" (6 cases incl. lock-preference per major, spanning requirement with fallback, conflict inside one major) plus lock round trip and lockProblems with two majors; two explanation tests rewritten to within-major conflicts with the same shape. CI green on this head (tier A, bootstrap, gates, pdisc).
  • Correctness: the two-run design keeps every previously resolvable graph and lock byte-identical; split run keys, chooseMajor fallback, keysOf for unreachable majors, and counterpartOf for the CLI change summaries read consistently with the stated behaviour.
  • Scope: matches #323 (slice 1 of 3); the not-yet-buildable state is stated in the PR and in PROJECT.md 6a / RELEASE.md.
  • Security/data: no new inputs, permissions or deps; lock format for single-major graphs unchanged, two-major locks only add package lines.
  • Docs: PROJECT.md 6a and RELEASE.md updated.

Non-blocking: Lock.lockedWithin silently falls back to the first entry when no locked version is in the set; fine for lockProblems (it reports the pin), worth keeping in mind for slice 2 callers.

Approved. - Tests: new group "Two majors of one package" (6 cases incl. lock-preference per major, spanning requirement with fallback, conflict inside one major) plus lock round trip and `lockProblems` with two majors; two explanation tests rewritten to within-major conflicts with the same shape. CI green on this head (tier A, bootstrap, gates, pdisc). - Correctness: the two-run design keeps every previously resolvable graph and lock byte-identical; split run keys, `chooseMajor` fallback, `keysOf` for unreachable majors, and `counterpartOf` for the CLI change summaries read consistently with the stated behaviour. - Scope: matches #323 (slice 1 of 3); the not-yet-buildable state is stated in the PR and in PROJECT.md 6a / RELEASE.md. - Security/data: no new inputs, permissions or deps; lock format for single-major graphs unchanged, two-major locks only add `package` lines. - Docs: PROJECT.md 6a and RELEASE.md updated. Non-blocking: `Lock.lockedWithin` silently falls back to the first entry when no locked version is in the set; fine for `lockProblems` (it reports the pin), worth keeping in mind for slice 2 callers.
argus merged commit e1d13b07f1 into main 2026-10-12 13:56:50 +00:00
argus deleted branch loop/323-two-majors-resolve 2026-10-12 13:56:50 +00:00
Sign in to join this conversation.
No reviewers
No labels
loop
ready
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
torbscript/language!328
No description provided.