Closes #260. Unblocks stacksjs/stacks#3062 and the pantry install --frozen-lockfile that Stacks cannot currently scaffold (stacksjs/stacks#2905).
The problem
Every declared range was resolved against the registry and the newest match won, so a committed pantry.lock never fixed what got installed. A plain install rewrote the pin; --frozen-lockfile installed the newer release and only then failed. That makes any upstream release inside a declared range turn a frozen CI run red with no change in the project — curl.se moving 8.22.0 → 8.23.0 between two pushes is what did it to Stacks.
Two things worth noting about the cause:
- The machinery to consult the lock already existed and had no callers:
resolveVersionWithLockfilewithprefer_lockfilewas never wired into this path. - It would not have sufficed on its own either — it returns the pin without asking whether the declared spec still admits it, which would pin straight through a dependency edit.
The rule
lockedVersionSatisfying honours a pin only while it satisfies the declared spec — bun install / npm ci semantics, with pantry update as the thing that moves a pin.
| declared | lock | installs |
|---|---|---|
^3.53.4 | 3.53.4 | 3.53.4 (even though 3.54.1 exists) |
latest / * / empty | 3.53.4 | 3.53.4 — any pin covers a rolling spec; committing a lock is the point |
^3.54.0 | 3.53.4 | resolves again — the range no longer admits the pin |
3.54.1 (exact) | 3.53.4 | resolves itself, so a pin/spec disagreement stays visible |
| unparseable | anything | resolves — never install a pin nothing vouched for |
The frozen check now runs before the pipeline, which is the report's other half: a frozen run fails without having written to pantry/. It skips the .env-inferred services from #253, which are deliberately absent from the lock and would otherwise fail every frozen install on a project with a .env.
Verified against the live registry, not just in unit tests
Reproduced the issue's own table with the built binary:
| scenario | before | after |
|---|---|---|
| plain install, lock pins 3.53.4 | installs 3.54.1, rewrites the lock | installs v3.53.4, lock byte-identical |
| frozen, lock satisfies | exit 1 after installing 3.54.1 | exit 0, pinned version, lock untouched |
| frozen, lock short a dep | — | exit 1, nothing written to pantry/, lock untouched |
zig build test: 573 pass / 6 skip. The 2 failures (shell.install_route bash + zsh) are pre-existing on main on this machine and unrelated.
One thing for a reviewer
helpers.zig's 21 tests had never run — nothing imported it for their own sake, the same gap package.zig had in #261 — so lib.zig now names it and they all pass. That this has now bitten twice suggests the explicit include list is a standing trap rather than a one-off; worth its own issue, and I did not fold a fix for it in here.
🤖 Generated with Claude Code