Skip to content

fix(direct-upgrade): a declaration that admits the fix gets the in-range refresh, not a bump (E8 follow-up) - #993

Merged
runyourempire merged 4 commits into
mainfrom
fix/c2-direct-refresh
Oct 18, 2026
Merged

runyourempire merged 4 commits into
mainfrom
fix/c2-direct-refresh

Conversation

@runyourempire

Copy link
Copy Markdown
Collaborator

What

This answers recovery-lead's live-check finding (2) on the rebuilt operator app. In navcal, the direct rows for sharp, next and vite told the user to npm install "pkg@^x", although each declared ^ range already admitted the fix target.

When the declared range admits the target, the minimal correct advice is the tool's in-range lockfile refresh, which leaves the manifest as it is. An install command is kept only when the range excludes the target.

Every form was run against the real tool (2026-10-18)

The project declared ms ^2.1.1 with the lock at 2.1.1. Each command moved the lock to 2.1.3, and package.json was unchanged:

Tool Command Note
npm 10.9.2 npm update ms Also inside a workspace member, run in the member's directory, both hoisted and nested
pnpm 10.34.6 pnpm update ms --no-save Plain pnpm update ms rewrote ^2.1.1 to ^2.1.3. Also inside a workspace member, where only that member's importer moved
yarn 1.22.22 yarn upgrade ms No version given
yarn 4.9.1 yarn up -R ms
cargo 1.97 cargo update -p itoa@1.0.5 --precise 1.0.11 On itoa = "1.0.5"; Cargo.toml unchanged

How

  • DirectFix gains a Refresh(String) variant. direct_fix checks that the declaration admits the installed copy, as before. If it also admits the target, it returns the refresh. Any readable range qualifies: a >= range carries no operator, but it admits the target.
  • The declines are unchanged:
    • a yarn workspace member, because yarn's up/upgrade act on the whole workspace;
    • Cargo workspace = true, git, path and registry entries.
  • Plan: "Fixable now with a lockfile refresh (the declared range already admits the fix): npm update sharp" when every fix is a refresh. In a mixed row, the refresh is labelled "(a lockfile refresh: the declared range already admits it)".
  • Brief: "bump sharp to >= x: within its declared range, run npm update sharp" (DirectFix::brief_clause).
  • Parent levers (fix(upgrade-plan): name the parent release that fixes a nested advisory (audit H1) #981): lever_command and the decide-mode vector helper both cover Refresh, and both matches are now exhaustive (the helper had a _ => None that silently dropped it). A lever declared ^4.13.4 moving to 4.22.3 names npm update express.

Vectors

  • New, all refresh: true:
    • fix_command fc-054 to fc-058: npm, pnpm, yarn 1, yarn berry and cargo, one line each;
    • fc-059: a >= range;
    • fc-060: an npm workspace member.
  • fc-029..035 and fc-037..041: these used targets their own declared range admits, which now correctly gives a refresh. They move to targets outside the range, so they keep pinning the operator-keeping install, and their sources say so.
  • fc-036: keeps its decline purpose, using 1.x → 2.0.0.
  • parent_release pr-001: now expects npm update express. The rule text says why. pr-002..004 declare exact versions and keep their installs.
  • The MCP mirror: it has implemented the same rule and passes these lines on its side. It is pending the repin.

Checks

  • cargo clippy --locked -- -D warnings: clean.
  • osv::, brief, evidence::, morning_ and preemption: 914 passed, including the vector gate, rebased on main d63fd7c.
  • New tests:
    • every refresh form, by lockfile kind;
    • a >= range;
    • the note wording, for refresh-only and mixed rows;
    • an end-to-end plan row for ~1.2.5, which names the refresh;
    • the end-to-end and Brief/plan parity tests, now on an exact pin so the install path stays covered.

🤖 Generated with Claude Code

https://claude.ai/code/session_015KSXWNneouUjTiVV3Rc3FH

runyourempire and others added 4 commits October 19, 2026 00:15
…nge refresh, not a bump (E8 follow-up)

Live 2026-10-18 (navcal): sharp, next and vite were told to
`npm install "x@^v"` though their `^` range already admitted the fix
target. When the declared range admits the target, the minimal correct
advice is the tool's in-range lockfile refresh, which leaves the manifest
as it is. Each form was run on 2026-10-18 against a project declaring
`ms ^2.1.1` locked at 2.1.1; each moved the lock to 2.1.3 with
package.json unchanged:
- npm 10.9.2: `npm update ms`
- pnpm 10.34.6: `pnpm update ms --no-save` (plain `pnpm update ms`
  rewrote the range to `^2.1.3`); also inside a workspace member, where
  only that member's importer moved
- yarn 1.22.22: `yarn upgrade ms`; yarn 4.9.1: `yarn up -R ms`
- cargo 1.97: `cargo update -p itoa --precise 1.0.11` on `itoa = "1.0.5"`,
  Cargo.toml unchanged

DirectFix gains Refresh. The plan reads "Fixable now with a lockfile
refresh (the declared range already admits the fix)" when every fix is
one; in a mixed row a refresh says what it is. The Brief reads "bump x
to >= v: within its declared range, run `npm update x`". Any readable
range qualifies (a `>=` range carries no operator but admits the
target). Yarn workspace members, Cargo workspace/git/path/registry
entries keep their existing declines.

Vectors: fc-054..fc-059 (refresh: true). fc-029..035 and fc-037..041 had
in-range targets; they move outside the declared range (sources say
so) to keep pinning the operator-keeping install. fc-036 keeps its
decline purpose with `1.x` -> 2.0.0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015KSXWNneouUjTiVV3Rc3FH
…efresh (fc-060)

pr-001: express is declared ^4.13.4, which already admits 4.22.3, so the
lever's command is `npm update express` (fc-054's rule), not an install
that rewrites the declaration. fc-060: an npm workspace member's in-range
refresh runs in the member's directory; npm 10.9.2 moved the member's copy
both hoisted and nested, declaration unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015KSXWNneouUjTiVV3Rc3FH
… in-range refresh

lever_command and the decide-mode vector helper matched only
DirectFix::Command (the helper with a `_ => None` wildcard), so the new
DirectFix::Refresh dropped out and pr-001's lever named nothing. Both are
now exhaustive: a lever declared ^4.13.4 moving to 4.22.3 names
`npm update express`.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015KSXWNneouUjTiVV3Rc3FH
…ases; refresh only a bounded range; refresh wording only when every direct site refreshes

- B1: fc-029..035 and fc-037..041 had been moved to releases that do not
  exist (minimist 2.0.1, ms 2.2.0, serde 2.0.0, ...). They now use REAL
  targets, with the declaration chosen so the target is out of range, and
  each command was re-run against the real tool on 2026-10-18; each
  vector's source is that run (npm 10.9.2 --package-lock-only, pnpm
  10.34.6, yarn 1.22.22, yarn 4.9.1, cargo 1.97). npm wrote ^8.0.7 for
  `npm install "@babel/traverse@^8.0.0"` (the resolved release), so fc-031
  says so. fc-036 is `0.2.x` -> 1.2.8 (a rule, no run). fc-058 cites the
  exact command text it ran.
- S3: only a range bounded below the next major (`^`, `~`, or a Cargo
  requirement) is refreshed: a refresh installs the NEWEST release in
  range, so `>=` / `||` / `*` / x-ranges could jump a major or land on a
  withdrawn or held release. fc-059 (`>=1.2.0`) names nothing.
- S2: the refresh wording applies only when every direct site of the row
  got a refresh; a site whose fix could not be named keeps "bump".
- S4: the Brief/plan parity test covers the exact pin (install) and the
  tilde (refresh) again.
- S5: the npm and Cargo arms are their own functions. S6: the module doc
  says which ranges refresh.
- fc-061: the pnpm workspace-member refresh (run 2026-10-18). The twin
  plan test's skip message names itself.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015KSXWNneouUjTiVV3Rc3FH
@runyourempire
runyourempire added this pull request to the merge queue Oct 18, 2026
Merged via the queue into main with commit a69afe9 Oct 18, 2026
24 checks passed
@runyourempire
runyourempire deleted the fix/c2-direct-refresh branch October 18, 2026 14:33
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