From 9c959f54380edcdda07ea65b14105d82fd2c1a6f Mon Sep 17 00:00:00 2001
From: Christian Kaltenbach The fix
The fix has two parts, and they attack the same rewrite from opposite ends.
- The version qualifier is load-bearing. Measured 2026-08-14 with a one-package fixture, on a
- runner and locally: npm 10.2.4 — node 18.19.1, what every pipeline pins today — rewrites the
- host onto the configured registry correctly and installs through the proxy, while
- npm 11.3.0 — developer machines, and any runner on node 24 — drops the prefix and fails. The
- prefix drop is a regression in newer npm: nothing is failing in CI today, and everything
- un-normalized fails the moment runners move to node 24. That makes this work a prerequisite for the Node 24
- migration rather than a cleanup after it.
+ The version qualifier is load-bearing. Measured 2026-08-14 with a one-package fixture, on
+ a runner and locally: npm 10.2.4 rewrites the host onto the configured registry correctly and
+ installs through the proxy, while npm 11.3.0 drops the prefix and fails. The prefix
+ drop is a regression in newer npm — and which npm a pipeline gets is decided
+ per branch by the reusable workflows: only release/25.2 still pins node 18.19.1
+ (npm 10); release/25.3, 25.4, 26.1, 26.2,
+ 26.3 and master all pin node 22.15.0, which is npm 11. Six of the seven branches
+ were therefore already failing on un-normalized lockfiles — as cplace-vwg-ptm-fe reported against
+ release/26.2: E404 on update-browserslist-db, inside the composite's own
+ npm ci. This work is a prerequisite for the rest of the Node 24 migration, not a cleanup after it.
replace-registry-host=neverrelease/25.3 and every newer branch pin; on npm 10.2.4
+ (release/25.2 only) the same rewrite lands on the proxy correctly and installs. Lane 2 removes
the rewrite; lane 3 removes the need for it. The pairing is what makes the rollout
lazy rather than a coordinated switchover: on a normalized lockfile the flag changes nothing, so the two
compose in either order and the flag can come out per branch — once the advisory stops reporting anywhere.
diff --git a/specs/2026-08-10_normalize-package-lock-resolved-urls/overview.md b/specs/2026-08-10_normalize-package-lock-resolved-urls/overview.md
index 0d6764d..7ae4caf 100644
--- a/specs/2026-08-10_normalize-package-lock-resolved-urls/overview.md
+++ b/specs/2026-08-10_normalize-package-lock-resolved-urls/overview.md
@@ -25,12 +25,20 @@ request 404s, and because that prefix is the `JFROG_URL` secret, CI prints the w
| npm | un-normalized entry, no mitigation | tarball served by |
| --- | --- | --- |
-| 10.2.4 — node 18.19.1, what every pipeline pins today | installs | the **proxy**; the rewrite keeps the path prefix |
-| 11.3.0 — developer machines, and any runner on node 24 | **`E404`** | — prefix dropped |
+| 10.2.4 | installs | the **proxy**; the rewrite keeps the path prefix |
+| 11.3.0 | **`E404`** | — prefix dropped |
-So the prefix drop is a **regression in newer npm**: nothing is failing in CI today, and everything un-normalized
-fails the moment runners move to node 24. That makes this work a **prerequisite for the Node 24 migration** rather
-than a cleanup after it.
+The prefix drop is a **regression in newer npm** — and which npm a pipeline gets is decided **per branch** by the
+reusable workflows:
+
+| branch | node pinned | npm | un-normalized behaviour |
+| --- | --- | --- | --- |
+| `release/25.2` | 18.19.1 | 10 | works without the flag |
+| `release/25.3`, `25.4`, `26.1`, `26.2`, `26.3`, `master` | 22.15.0 | **11** | **fails** without the flag |
+
+Six of the seven branches were already on npm 11, so un-normalized lockfiles **were** failing there — as
+`cplace-vwg-ptm-fe` reported against `release/26.2`: `E404` on `update-browserslist-db`, inside the composite's own
+`npm ci`. This work is a **prerequisite for the rest of the Node 24 migration**, not a cleanup after it.
The fix has two parts, attacking the same rewrite from opposite ends:
@@ -77,7 +85,8 @@ flowchart TB
class C2,C3,C4 ok;
```
-Lane 1 is npm 11; on npm 10.2.4 the same rewrite lands on the proxy correctly and installs. Lane 2 removes the
+Lane 1 is npm 11 — which is what `release/25.3` and every newer branch pin; on npm 10.2.4 (`release/25.2` only)
+the same rewrite lands on the proxy correctly and installs. Lane 2 removes the
rewrite; lane 3 removes the *need* for it. Because the flag is a no-op on a normalized lockfile, the two compose in
either order and the flag can be dropped **per branch** rather than in a coordinated switchover — once the advisory
stops reporting anywhere.
diff --git a/tools/scripts/lockfile/README.md b/tools/scripts/lockfile/README.md
index 7556a1b..55c341d 100644
--- a/tools/scripts/lockfile/README.md
+++ b/tools/scripts/lockfile/README.md
@@ -55,21 +55,32 @@ This tells npm to fetch each `resolved` URL **verbatim** rather than rewriting i
It makes an un-normalized lockfile install on any npm version, so it protects **consumer** repositories too, not just
this one. JFrog URLs stay authenticated by the secret, so it introduces no dependency on anonymous JFrog access.
-**The rewrite it disables is only broken on newer npm.** Measured 2026-08-14 with a one-package fixture, on a runner
-and locally:
+**The rewrite it disables is broken on npm 11, not on npm 10.** Measured 2026-08-14 with a one-package fixture, on a
+runner and locally:
| npm | un-normalized entry, no mitigation | tarball served by |
| --- | --- | --- |
-| 10.2.4 (node 18.19.1 — what every pipeline pins today) | installs | the **proxy** — the rewrite keeps its path prefix |
-| 11.3.0 (developer machines; any runner on node 24) | **`E404`**, masked as `***` | — prefix dropped |
+| 10.2.4 | installs | the **proxy** — the rewrite keeps its path prefix |
+| 11.3.0 | **`E404`**, masked as `***` | — prefix dropped |
+
+**Which npm a pipeline gets is decided per branch, and most branches are on the broken one.** The reusable workflows
+pin the node version, and only `release/25.2` is still on the old one:
+
+| branch | node pinned | npm | un-normalized behaviour |
+| --- | --- | --- | --- |
+| `release/25.2` | 18.19.1 | 10 | worked without the flag |
+| `release/25.3`, `25.4`, `26.1`, `26.2`, `26.3`, `master` | 22.15.0 | 11 | **failed** without the flag |
+
+That is not hypothetical: `cplace-vwg-ptm-fe` reported exactly this against `release/26.2` — `E404` on
+`update-browserslist-db`, inside the composite's own `npm ci`, on node 22.15.0.
**It is a mitigation, not the fix**, and it is not free on either version. Under it, an entry still pointing at
`registry.npmjs.org` is fetched *directly from npmjs*, bypassing the proxy — no Xray, no curation. On npm 10 that is
its only effect, because the rewrite it disables was working. On npm 11 it buys compatibility and spends proxy
routing. Normalizing buys both: the entry resolves through the proxy on every version, flag or no flag.
-**This is why the Node 24 migration depends on the rollout, not the other way round.** Nothing fails in CI today;
-everything un-normalized fails the moment runners move to npm 11.
+**The Node 24 migration and this rollout are the same problem.** Six of the seven branches already run npm 11, which
+is why un-normalized lockfiles were failing there before this landed.
Because of that, `use-npmrc` also runs `warn-foreign-registry.sh` against the consumer's own `package-lock.json` and
emits a `::warning` annotation plus a job summary listing the offending package paths. **Those warnings are the
diff --git a/tools/scripts/lockfile/warn-foreign-registry.sh b/tools/scripts/lockfile/warn-foreign-registry.sh
index a26ca42..c20a130 100755
--- a/tools/scripts/lockfile/warn-foreign-registry.sh
+++ b/tools/scripts/lockfile/warn-foreign-registry.sh
@@ -87,8 +87,9 @@ main() {
printf 'What would happen without that flag depends on the npm version: **npm 10.2.4** rewrites the '
printf 'host onto the configured registry correctly, and the entry resolves *through* the proxy; '
printf '**npm 11.3.0** drops the path prefix and fails with an `E404` masked as `***`. Both measured '
- printf '2026-08-14. Runners pinned to node 18.19.1 are on npm 10; developer machines, and any runner '
- printf 'moving to node 24, are not.\n\n'
+ printf '2026-08-14. Only `release/25.2` still pins node 18.19.1 (npm 10) - `release/25.3` and every '
+ printf 'newer branch pin node 22.15.0, which is npm 11. So most pipelines are on the version that '
+ printf 'fails, and un-normalized entries there depend on this flag.\n\n'
printf 'Fix by normalizing the lockfile onto the proxy (PFM-ISSUE-34453) - it then resolves through '
printf 'the proxy on every npm version, with or without the flag, and the mitigation can be removed '
printf 'once no lockfile reports this.\n\n'