Skip to content

feat(security): make the npm cooldown real — Node 24 in CI plus gate-84 - #469

Merged
rubenvdlinde merged 3 commits into
mainfrom
feat/npm11-supply-chain-cooldown
Aug 15, 2026
Merged

feat(security): make the npm cooldown real — Node 24 in CI plus gate-84#469
rubenvdlinde merged 3 commits into
mainfrom
feat/npm11-supply-chain-cooldown

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

What

Two changes that only make sense together:

  1. node-version default 22 → 24 in the shared quality.yml.
  2. gate-84 npm-supply-chain-config — asserts the three settings that make the cooldown real.

Companion per-app PRs set min-release-age=2, min-release-age-exclude[]=@conduction/* and engines.npm: ^11.0.0 across all 19 repos.

Why the Node bump is really an npm bump

The fleet's .npmrc files have carried a comment describing a 24-hour supply-chain cooldown. It has never been in effect anywhere, for two independent reasons — either alone is enough.

min-release-age does not exist in npm 10. Not rejected, not warned about:

$ grep -c min-release-age .../npm/node_modules/@npmcli/config/lib/definitions/definitions.js
0
$ npm --version && npm config get min-release-age
10.9.8
undefined

And every Node 22 release bundles npm 10 — 22.23.2, the latest, ships 10.9.8. So no value of node-version in the 22 line could ever deliver the feature. Node 24.19 bundles npm 11.17.

This supersedes the previous "Node 22 is what Nextcloud's own apps target" rationale. That remains true about upstream, but a cooldown that is silently inert is worse than no cooldown: it reads as protection nobody has. The apps' own engines.node already declared ^22.14 || ^24 || >=26, so 24 was always an allowed runtime — this makes it the one CI uses.

A caller that must stay on 22 can still pass node-version: "22". It gets npm 10, and gate-84 reports that rather than letting it pass quietly.

Why the gate needs all three settings

setting what it does failure mode alone
min-release-age=2 the window inert if the toolchain is npm 10
min-release-age-exclude[]=@conduction/* first-party exemption without it, first-party installs silently resolve backwards
engines.npm: ^11.0.0 a toolchain that can read the other two without it, both are read by nothing

The exclusion is not optional. Without it the cooldown does not fail loudly — it silently resolves backwards. Measured on 2026-08-15, an install of @conduction/nextcloud-vue on release day:

config resolves to exit
min-release-age=1, no exclusion 2.0.7 0
min-release-age=1 + @conduction/* excluded 2.3.0 0

A green install of months-old code is a worse outcome than a red one, and nothing in the output distinguishes it from a correct install.

Full-tree, not diff-scoped

Conformance is a property of the repository, not of a change. Diff-scoped, this gate would report nothing on every PR that does not touch .npmrc — nearly all of them — and a gate silent on 99% of PRs cannot establish fleet-wide conformance, which is its whole purpose.

Verification

Against the real fleet, before and after the companion per-app change:

BEFORE (committed state):  pass=0  fail=19
AFTER  (with the change):  pass=19 fail=0

Suite (scripts/lib/test_check_npm_supply_chain_config.sh, auto-discovered) — 15 assertions. Each setting is asserted individually load-bearing: drop any one from an otherwise conformant repo and the gate still fails. That matters because 4 of the 19 apps already had a correct min-release-age=1 while sitting on a toolchain that cannot read it — a gate firing only when everything is missing would have passed them.

The engines.npm: ">=10" case is the one most likely to rot: a range a person would write meaning "modern npm", which silently admits the one npm where the feature does not exist.

Also covered: unparseable package.json fails rather than reading as clean, commented-out settings do not count, and the terminal summary the runner greps for is asserted so a crashed checker cannot be mistaken for a clean one.

The fleet's .npmrc files have carried a comment describing a 24h supply-chain
cooldown. It has never been in effect anywhere, for two independent reasons.

1. `min-release-age` DOES NOT EXIST IN NPM 10. Not rejected, not warned about:
   `npm config get min-release-age` answers `undefined`. Every Node 22 release
   bundles npm 10 — 22.23.2, the latest, ships 10.9.8 — so no value of the
   node-version input in the 22 line could ever deliver it. Node 24.19 bundles
   npm 11.17. That is why the default moves to 24; it is an npm change, not a
   Node one, and the apps' own engines.node already declared ^24.

2. 13 of 19 apps set `min-release-age=0`, which disables it outright.

gate-84 asserts the three settings that only work together: the window (>= 2
days), the `@conduction/*` exclusion, and an engines.npm that admits only
npm 11+. Any one alone looks like protection and is not.

The exclusion is not optional. Without it the cooldown does NOT fail loudly —
it silently resolves backwards: measured 2026-08-15, an install of
@conduction/nextcloud-vue on release day picked 2.0.7 instead of 2.3.0 and
exited 0. A green install of months-old code is worse than a red one.

Gate is full-tree, not diff-scoped: conformance is a property of the repo, not
of a change, and diff-scoped it would be silent on almost every PR.

Verified against the real fleet: 19 of 19 apps FAIL at their current committed
state, 19 of 19 PASS with the accompanying per-app change. The 15-assertion
suite is auto-discovered by run-helper-suites.sh and asserts each setting is
individually load-bearing — including engines.npm '>=10', a range a person
would write meaning 'modern npm' that silently admits the one npm where the
feature does not exist.
This was referenced Aug 15, 2026
…fix SC2015

Two failures from the package's own CI, both real.

1. On a repo with no package.json, gate-84's `if [ -f package.json ]` guard made
   it emit NOTHING — and a silence is indistinguishable from a pass, which is
   precisely what --require-full-coverage exists to catch. It reported
   'GATES THAT DID NOT RUN: 84' and exited 98 on a repository with nothing
   wrong with it. Adds the applicability declaration so the absence is stated
   by name instead of being a gap.

2. ShellCheck SC2015: the suite's last assertion used `A && ok || no`, which is
   not if-then-else — the failure branch also runs when the check passes and
   the reporter fails, so one assertion could print both PASS and FAIL.
   Rewritten as a real if/then/else.

Verified: tests/test-hydra-gates-bin.sh passes locally (70 assertions, rc 0)
and the gate-84 suite is still 15 of 15.
…-arming errexit

Two more findings from the package's own invariants, both correct.

1. The acceptance ratchet: 'gate-84 is DECLARED by the runner but has neither a
   planted/clean fixture nor a reasoned row in UNCOVERED.md. A gate can be
   added to the runner and never tested; this is that moment.' It was exactly
   that moment. Adds the bundle rather than an excuse — gate-84 is full-tree
   and needs no base ref, so nothing blocks fixturing it.

   The arms differ ONLY in the three cooldown settings. planted/ reproduces the
   pre-2026-08-16 fleet state (min-release-age=0 with engines.npm ^10.0.0);
   clean/ is conformant and deliberately keeps an unrelated legacy-peer-deps
   line, so a gate grading .npmrc as a whole file rather than per-setting turns
   the CLEAN arm red instead of leaving the planted one green.

2. errexit discipline: the gate-84 block restored with 'set -e'. Errexit off is
   the state this script actually runs in, and re-arming it would abort the run
   on the first non-zero anything downstream. Restore sites say 'set +e'.

Verified locally: acceptance matrix rc 0 (64 of 69 gates fixtured, gate-84
FAILs on planted and NAMES min-release-age, silent on clean), errexit
discipline rc 0, package invariants rc 0.
@rubenvdlinde
rubenvdlinde merged commit 84724d1 into main Aug 15, 2026
36 checks passed
rubenvdlinde added a commit to ConductionNL/nextcloud-app-template that referenced this pull request Aug 15, 2026
`gh run rerun` replays the workflow version resolved when the run was created,
so a reusable workflow referenced as @main is NOT re-resolved — every re-run
after ConductionNL/.github#469 merged still executed Node 22 with npm 10.9.8,
where `min-release-age` does not exist and `npm ci` cannot read an npm-11
lockfile. Only a new run picks up the merged workflow. This empty commit is
that trigger.
rubenvdlinde added a commit to ConductionNL/decidesk that referenced this pull request Aug 15, 2026
`gh run rerun` replays the workflow version resolved when the run was created,
so a reusable workflow referenced as @main is NOT re-resolved — every re-run
after ConductionNL/.github#469 merged still executed Node 22 with npm 10.9.8,
where `min-release-age` does not exist and `npm ci` cannot read an npm-11
lockfile. Only a new run picks up the merged workflow. This empty commit is
that trigger.
rubenvdlinde added a commit to ConductionNL/docudesk that referenced this pull request Aug 15, 2026
`gh run rerun` replays the workflow version resolved when the run was created,
so a reusable workflow referenced as @main is NOT re-resolved — every re-run
after ConductionNL/.github#469 merged still executed Node 22 with npm 10.9.8,
where `min-release-age` does not exist and `npm ci` cannot read an npm-11
lockfile. Only a new run picks up the merged workflow. This empty commit is
that trigger.
rubenvdlinde added a commit to ConductionNL/doriath that referenced this pull request Aug 15, 2026
`gh run rerun` replays the workflow version resolved when the run was created,
so a reusable workflow referenced as @main is NOT re-resolved — every re-run
after ConductionNL/.github#469 merged still executed Node 22 with npm 10.9.8,
where `min-release-age` does not exist and `npm ci` cannot read an npm-11
lockfile. Only a new run picks up the merged workflow. This empty commit is
that trigger.
rubenvdlinde added a commit to ConductionNL/hermiq that referenced this pull request Aug 15, 2026
`gh run rerun` replays the workflow version resolved when the run was created,
so a reusable workflow referenced as @main is NOT re-resolved — every re-run
after ConductionNL/.github#469 merged still executed Node 22 with npm 10.9.8,
where `min-release-age` does not exist and `npm ci` cannot read an npm-11
lockfile. Only a new run picks up the merged workflow. This empty commit is
that trigger.
rubenvdlinde added a commit to ConductionNL/zaakafhandelapp that referenced this pull request Aug 16, 2026
The shared quality.yml moved to Node 24 (ConductionNL/.github#469), but this
repo's OWN workflows did not, and they run `npm ci` too. Node 20 and 22 both
bundle npm 10, which cannot install from an npm 11 lockfile — it exits EUSAGE
with 'Missing: <pkg> from lock file'. Measured here: Lint Check and Spec
Validation went red on exactly that while every shared-workflow job passed.

pull-request-lint-check.yaml had no setup-node AT ALL, so it silently inherited
the runner default. That is the harder half to notice: nothing in the file
named a Node version, so nothing looked wrong.

Left alone deliberately: api-test-coverage.yml stays on Node 20 where it exists
— it runs `npm install -g newman`, a global tool install with no lockfile, so
the npm major is irrelevant there. l10n.yml likewise: its only 'npm ci' is
inside a comment saying it needs none.
rubenvdlinde added a commit to ConductionNL/doriath that referenced this pull request Aug 16, 2026
The shared quality.yml moved to Node 24 (ConductionNL/.github#469), but this
repo's OWN pull-request-lint-check.yaml did not, and it runs `npm ci` too.
Node 20 and 22 both bundle npm 10, which cannot install from an npm 11
lockfile — it exits EUSAGE with 'Missing: <pkg> from lock file'.

That workflow had no setup-node AT ALL, so it silently inherited the runner
default. That is the harder half to notice: nothing in the file named a Node
version, so nothing looked wrong.
rubenvdlinde added a commit to ConductionNL/nldesign that referenced this pull request Aug 16, 2026
* chore(security): enable the npm supply-chain cooldown on npm 11

Sets `min-release-age=2` and `min-release-age-exclude[]=@conduction/*`, raises
`engines.npm` to ^11.0.0, and regenerates the lockfile under npm 11.

The .npmrc comment here has described a cooldown for months and it has never
been in effect. `min-release-age` does not exist in npm 10 — `npm config get
min-release-age` answers `undefined` — and every Node 22 release bundles npm
10, so the setting was read by nothing. Most repos also had it at 0, which
disables it outright.

@conduction/* is exempt because without the exemption the cooldown does not
fail loudly, it silently resolves backwards: measured 2026-08-15, an install of
@conduction/nextcloud-vue on release day picked 2.0.7 instead of 2.3.0 and
exited 0.

The lock is regenerated under npm 11 and iterated to a fixed point. Where the
tree changed rather than its metadata, that is npm 10 -> 11 reconciling a lock
shaped by the older resolver, not the cooldown — verified by regenerating with
the cooldown enabled and disabled and getting identical trees.

Verified: npm ci exit 0 under npm 11.19.0, @conduction/nextcloud-vue resolves
to 2.3.0, gate-84 conformance passes.

* ci: re-run against the merged shared workflow

`gh run rerun` replays the workflow version resolved when the run was created,
so a reusable workflow referenced as @main is NOT re-resolved — every re-run
after ConductionNL/.github#469 merged still executed Node 22 with npm 10.9.8,
where `min-release-age` does not exist and `npm ci` cannot read an npm-11
lockfile. Only a new run picks up the merged workflow. This empty commit is
that trigger.
rubenvdlinde added a commit to ConductionNL/opencatalogi that referenced this pull request Aug 16, 2026
* chore(security): enable the npm supply-chain cooldown on npm 11

Sets `min-release-age=2` and `min-release-age-exclude[]=@conduction/*`, raises
`engines.npm` to ^11.0.0, and regenerates the lockfile under npm 11.

The .npmrc comment here has described a cooldown for months and it has never
been in effect. `min-release-age` does not exist in npm 10 — `npm config get
min-release-age` answers `undefined` — and every Node 22 release bundles npm
10, so the setting was read by nothing. Most repos also had it at 0, which
disables it outright.

@conduction/* is exempt because without the exemption the cooldown does not
fail loudly, it silently resolves backwards: measured 2026-08-15, an install of
@conduction/nextcloud-vue on release day picked 2.0.7 instead of 2.3.0 and
exited 0.

The lock is regenerated under npm 11 and iterated to a fixed point. Where the
tree changed rather than its metadata, that is npm 10 -> 11 reconciling a lock
shaped by the older resolver, not the cooldown — verified by regenerating with
the cooldown enabled and disabled and getting identical trees.

Verified: npm ci exit 0 under npm 11.19.0, @conduction/nextcloud-vue resolves
to 2.3.0, gate-84 conformance passes.

* ci: re-run against the merged shared workflow

`gh run rerun` replays the workflow version resolved when the run was created,
so a reusable workflow referenced as @main is NOT re-resolved — every re-run
after ConductionNL/.github#469 merged still executed Node 22 with npm 10.9.8,
where `min-release-age` does not exist and `npm ci` cannot read an npm-11
lockfile. Only a new run picks up the merged workflow. This empty commit is
that trigger.
rubenvdlinde added a commit to ConductionNL/softwarecatalog that referenced this pull request Aug 16, 2026
* chore(security): enable the npm supply-chain cooldown on npm 11

Sets `min-release-age=2` and `min-release-age-exclude[]=@conduction/*`, raises
`engines.npm` to ^11.0.0, and regenerates the lockfile under npm 11.

The .npmrc comment here has described a cooldown for months and it has never
been in effect. `min-release-age` does not exist in npm 10 — `npm config get
min-release-age` answers `undefined` — and every Node 22 release bundles npm
10, so the setting was read by nothing. Most repos also had it at 0, which
disables it outright.

@conduction/* is exempt because without the exemption the cooldown does not
fail loudly, it silently resolves backwards: measured 2026-08-15, an install of
@conduction/nextcloud-vue on release day picked 2.0.7 instead of 2.3.0 and
exited 0.

The lock is regenerated under npm 11 and iterated to a fixed point. Where the
tree changed rather than its metadata, that is npm 10 -> 11 reconciling a lock
shaped by the older resolver, not the cooldown — verified by regenerating with
the cooldown enabled and disabled and getting identical trees.

Verified: npm ci exit 0 under npm 11.19.0, @conduction/nextcloud-vue resolves
to 2.3.0, gate-84 conformance passes.

* ci: re-run against the merged shared workflow

`gh run rerun` replays the workflow version resolved when the run was created,
so a reusable workflow referenced as @main is NOT re-resolved — every re-run
after ConductionNL/.github#469 merged still executed Node 22 with npm 10.9.8,
where `min-release-age` does not exist and `npm ci` cannot read an npm-11
lockfile. Only a new run picks up the merged workflow. This empty commit is
that trigger.
rubenvdlinde added a commit to ConductionNL/docudesk that referenced this pull request Aug 16, 2026
* chore(security): enable the npm supply-chain cooldown on npm 11

Sets `min-release-age=2` and `min-release-age-exclude[]=@conduction/*`, raises
`engines.npm` to ^11.0.0, and regenerates the lockfile under npm 11.

The .npmrc comment here has described a cooldown for months and it has never
been in effect. `min-release-age` does not exist in npm 10 — `npm config get
min-release-age` answers `undefined` — and every Node 22 release bundles npm
10, so the setting was read by nothing. Most repos also had it at 0, which
disables it outright.

@conduction/* is exempt because without the exemption the cooldown does not
fail loudly, it silently resolves backwards: measured 2026-08-15, an install of
@conduction/nextcloud-vue on release day picked 2.0.7 instead of 2.3.0 and
exited 0.

The lock is regenerated under npm 11 and iterated to a fixed point. Where the
tree changed rather than its metadata, that is npm 10 -> 11 reconciling a lock
shaped by the older resolver, not the cooldown — verified by regenerating with
the cooldown enabled and disabled and getting identical trees.

Verified: npm ci exit 0 under npm 11.19.0, @conduction/nextcloud-vue resolves
to 2.3.0, gate-84 conformance passes.

* ci: re-run against the merged shared workflow

`gh run rerun` replays the workflow version resolved when the run was created,
so a reusable workflow referenced as @main is NOT re-resolved — every re-run
after ConductionNL/.github#469 merged still executed Node 22 with npm 10.9.8,
where `min-release-age` does not exist and `npm ci` cannot read an npm-11
lockfile. Only a new run picks up the merged workflow. This empty commit is
that trigger.
rubenvdlinde added a commit to ConductionNL/launchpad that referenced this pull request Aug 16, 2026
* chore(security): enable the npm supply-chain cooldown on npm 11

Sets `min-release-age=2` and `min-release-age-exclude[]=@conduction/*`, raises
`engines.npm` to ^11.0.0, and regenerates the lockfile under npm 11.

The .npmrc comment here has described a cooldown for months and it has never
been in effect. `min-release-age` does not exist in npm 10 — `npm config get
min-release-age` answers `undefined` — and every Node 22 release bundles npm
10, so the setting was read by nothing. Most repos also had it at 0, which
disables it outright.

@conduction/* is exempt because without the exemption the cooldown does not
fail loudly, it silently resolves backwards: measured 2026-08-15, an install of
@conduction/nextcloud-vue on release day picked 2.0.7 instead of 2.3.0 and
exited 0.

The lock is regenerated under npm 11 and iterated to a fixed point. Where the
tree changed rather than its metadata, that is npm 10 -> 11 reconciling a lock
shaped by the older resolver, not the cooldown — verified by regenerating with
the cooldown enabled and disabled and getting identical trees.

Verified: npm ci exit 0 under npm 11.19.0, @conduction/nextcloud-vue resolves
to 2.3.0, gate-84 conformance passes.

* ci: re-run against the merged shared workflow

`gh run rerun` replays the workflow version resolved when the run was created,
so a reusable workflow referenced as @main is NOT re-resolved — every re-run
after ConductionNL/.github#469 merged still executed Node 22 with npm 10.9.8,
where `min-release-age` does not exist and `npm ci` cannot read an npm-11
lockfile. Only a new run picks up the merged workflow. This empty commit is
that trigger.
rubenvdlinde added a commit to ConductionNL/scholiq that referenced this pull request Aug 16, 2026
* chore(security): enable the npm supply-chain cooldown on npm 11

Sets `min-release-age=2` and `min-release-age-exclude[]=@conduction/*`, raises
`engines.npm` to ^11.0.0, and regenerates the lockfile under npm 11.

The .npmrc comment here has described a cooldown for months and it has never
been in effect. `min-release-age` does not exist in npm 10 — `npm config get
min-release-age` answers `undefined` — and every Node 22 release bundles npm
10, so the setting was read by nothing. Most repos also had it at 0, which
disables it outright.

@conduction/* is exempt because without the exemption the cooldown does not
fail loudly, it silently resolves backwards: measured 2026-08-15, an install of
@conduction/nextcloud-vue on release day picked 2.0.7 instead of 2.3.0 and
exited 0.

The lock is regenerated under npm 11 and iterated to a fixed point. Where the
tree changed rather than its metadata, that is npm 10 -> 11 reconciling a lock
shaped by the older resolver, not the cooldown — verified by regenerating with
the cooldown enabled and disabled and getting identical trees.

Verified: npm ci exit 0 under npm 11.19.0, @conduction/nextcloud-vue resolves
to 2.3.0, gate-84 conformance passes.

* ci: re-run against the merged shared workflow

`gh run rerun` replays the workflow version resolved when the run was created,
so a reusable workflow referenced as @main is NOT re-resolved — every re-run
after ConductionNL/.github#469 merged still executed Node 22 with npm 10.9.8,
where `min-release-age` does not exist and `npm ci` cannot read an npm-11
lockfile. Only a new run picks up the merged workflow. This empty commit is
that trigger.

* ci: run every npm job on Node 24

The shared quality.yml moved to Node 24 (ConductionNL/.github#469), but this
repo's OWN workflows did not, and they run `npm ci` too. Node 20 and 22 both
bundle npm 10, which cannot install from an npm 11 lockfile — it exits EUSAGE
with 'Missing: <pkg> from lock file'. Measured here: Lint Check and Spec
Validation went red on exactly that while every shared-workflow job passed.

pull-request-lint-check.yaml had no setup-node AT ALL, so it silently inherited
the runner default. That is the harder half to notice: nothing in the file
named a Node version, so nothing looked wrong.

Left alone deliberately: api-test-coverage.yml stays on Node 20 where it exists
— it runs `npm install -g newman`, a global tool install with no lockfile, so
the npm major is irrelevant there. l10n.yml likewise: its only 'npm ci' is
inside a comment saying it needs none.
rubenvdlinde added a commit to ConductionNL/nextcloud-app-template that referenced this pull request Aug 16, 2026
* chore(security): enable the npm supply-chain cooldown on npm 11

Sets `min-release-age=2` and `min-release-age-exclude[]=@conduction/*`, raises
`engines.npm` to ^11.0.0, and regenerates the lockfile under npm 11.

The .npmrc comment here has described a cooldown for months and it has never
been in effect. `min-release-age` does not exist in npm 10 — `npm config get
min-release-age` answers `undefined` — and every Node 22 release bundles npm
10, so the setting was read by nothing. Most repos also had it at 0, which
disables it outright.

@conduction/* is exempt because without the exemption the cooldown does not
fail loudly, it silently resolves backwards: measured 2026-08-15, an install of
@conduction/nextcloud-vue on release day picked 2.0.7 instead of 2.3.0 and
exited 0.

The lock is regenerated under npm 11 and iterated to a fixed point. Where the
tree changed rather than its metadata, that is npm 10 -> 11 reconciling a lock
shaped by the older resolver, not the cooldown — verified by regenerating with
the cooldown enabled and disabled and getting identical trees.

Verified: npm ci exit 0 under npm 11.19.0, @conduction/nextcloud-vue resolves
to 2.3.0, gate-84 conformance passes.

* ci: re-run against the merged shared workflow

`gh run rerun` replays the workflow version resolved when the run was created,
so a reusable workflow referenced as @main is NOT re-resolved — every re-run
after ConductionNL/.github#469 merged still executed Node 22 with npm 10.9.8,
where `min-release-age` does not exist and `npm ci` cannot read an npm-11
lockfile. Only a new run picks up the merged workflow. This empty commit is
that trigger.

* ci: run every npm job on Node 24

The shared quality.yml moved to Node 24 (ConductionNL/.github#469), but this
repo's OWN workflows did not, and they run `npm ci` too. Node 20 and 22 both
bundle npm 10, which cannot install from an npm 11 lockfile — it exits EUSAGE
with 'Missing: <pkg> from lock file'. Measured here: Lint Check and Spec
Validation went red on exactly that while every shared-workflow job passed.

pull-request-lint-check.yaml had no setup-node AT ALL, so it silently inherited
the runner default. That is the harder half to notice: nothing in the file
named a Node version, so nothing looked wrong.

Left alone deliberately: api-test-coverage.yml stays on Node 20 where it exists
— it runs `npm install -g newman`, a global tool install with no lockfile, so
the npm major is irrelevant there. l10n.yml likewise: its only 'npm ci' is
inside a comment saying it needs none.
rubenvdlinde added a commit to ConductionNL/decidesk that referenced this pull request Aug 16, 2026
* chore(security): enable the npm supply-chain cooldown on npm 11

Sets `min-release-age=2` and `min-release-age-exclude[]=@conduction/*`, raises
`engines.npm` to ^11.0.0, and regenerates the lockfile under npm 11.

The .npmrc comment here has described a cooldown for months and it has never
been in effect. `min-release-age` does not exist in npm 10 — `npm config get
min-release-age` answers `undefined` — and every Node 22 release bundles npm
10, so the setting was read by nothing. Most repos also had it at 0, which
disables it outright.

@conduction/* is exempt because without the exemption the cooldown does not
fail loudly, it silently resolves backwards: measured 2026-08-15, an install of
@conduction/nextcloud-vue on release day picked 2.0.7 instead of 2.3.0 and
exited 0.

The lock is regenerated under npm 11 and iterated to a fixed point. Where the
tree changed rather than its metadata, that is npm 10 -> 11 reconciling a lock
shaped by the older resolver, not the cooldown — verified by regenerating with
the cooldown enabled and disabled and getting identical trees.

Verified: npm ci exit 0 under npm 11.19.0, @conduction/nextcloud-vue resolves
to 2.3.0, gate-84 conformance passes.

* ci: re-run against the merged shared workflow

`gh run rerun` replays the workflow version resolved when the run was created,
so a reusable workflow referenced as @main is NOT re-resolved — every re-run
after ConductionNL/.github#469 merged still executed Node 22 with npm 10.9.8,
where `min-release-age` does not exist and `npm ci` cannot read an npm-11
lockfile. Only a new run picks up the merged workflow. This empty commit is
that trigger.

* ci: run every npm job on Node 24

The shared quality.yml moved to Node 24 (ConductionNL/.github#469), but this
repo's OWN workflows did not, and they run `npm ci` too. Node 20 and 22 both
bundle npm 10, which cannot install from an npm 11 lockfile — it exits EUSAGE
with 'Missing: <pkg> from lock file'. Measured here: Lint Check and Spec
Validation went red on exactly that while every shared-workflow job passed.

pull-request-lint-check.yaml had no setup-node AT ALL, so it silently inherited
the runner default. That is the harder half to notice: nothing in the file
named a Node version, so nothing looked wrong.

Left alone deliberately: api-test-coverage.yml stays on Node 20 where it exists
— it runs `npm install -g newman`, a global tool install with no lockfile, so
the npm major is irrelevant there. l10n.yml likewise: its only 'npm ci' is
inside a comment saying it needs none.
rubenvdlinde added a commit to ConductionNL/portaliq that referenced this pull request Aug 16, 2026
The shared quality.yml moved to Node 24 (ConductionNL/.github#469) because npm
10 cannot install from an npm 11 lockfile — EUSAGE, 'Missing: <pkg> from lock
file'. This repo's OWN workflows did not move with it and they run npm ci too:
measured on the cooldown PR, every shared-workflow job passed while Lint Check
and Spec Validation went red on exactly that error.

pull-request-lint-check.yaml had no setup-node AT ALL, so it silently inherited
the runner default. That is the harder half to notice — nothing in the file
named a Node version, so nothing looked wrong.

Left alone deliberately: l10n.yml (its only 'npm ci' is in a comment saying it
needs none) and api-test-coverage.yml (npm install -g newman, no lockfile).
rubenvdlinde added a commit to ConductionNL/hermiq that referenced this pull request Aug 16, 2026
The shared quality.yml moved to Node 24 (ConductionNL/.github#469) because npm
10 cannot install from an npm 11 lockfile — EUSAGE, 'Missing: <pkg> from lock
file'. This repo's OWN workflows did not move with it and they run npm ci too:
measured on the cooldown PR, every shared-workflow job passed while Lint Check
and Spec Validation went red on exactly that error.

pull-request-lint-check.yaml had no setup-node AT ALL, so it silently inherited
the runner default. That is the harder half to notice — nothing in the file
named a Node version, so nothing looked wrong.

Left alone deliberately: l10n.yml (its only 'npm ci' is in a comment saying it
needs none) and api-test-coverage.yml (npm install -g newman, no lockfile).
rubenvdlinde added a commit to ConductionNL/larpingapp that referenced this pull request Aug 16, 2026
The shared quality.yml moved to Node 24 (ConductionNL/.github#469) because npm
10 cannot install from an npm 11 lockfile — EUSAGE, 'Missing: <pkg> from lock
file'. This repo's OWN workflows did not move with it and they run npm ci too:
measured on the cooldown PR, every shared-workflow job passed while Lint Check
and Spec Validation went red on exactly that error.

pull-request-lint-check.yaml had no setup-node AT ALL, so it silently inherited
the runner default. That is the harder half to notice — nothing in the file
named a Node version, so nothing looked wrong.

Left alone deliberately: l10n.yml (its only 'npm ci' is in a comment saying it
needs none) and api-test-coverage.yml (npm install -g newman, no lockfile).
rubenvdlinde added a commit to ConductionNL/openregister that referenced this pull request Aug 16, 2026
The shared quality.yml moved to Node 24 (ConductionNL/.github#469) because npm
10 cannot install from an npm 11 lockfile — EUSAGE, 'Missing: <pkg> from lock
file'. This repo's OWN workflows did not move with it and they run npm ci too:
measured on the cooldown PR, every shared-workflow job passed while Lint Check
and Spec Validation went red on exactly that error.

pull-request-lint-check.yaml had no setup-node AT ALL, so it silently inherited
the runner default. That is the harder half to notice — nothing in the file
named a Node version, so nothing looked wrong.

Left alone deliberately: l10n.yml (its only 'npm ci' is in a comment saying it
needs none) and api-test-coverage.yml (npm install -g newman, no lockfile).
rubenvdlinde added a commit to ConductionNL/zaakafhandelapp that referenced this pull request Aug 16, 2026
The shared quality.yml moved to Node 24 (ConductionNL/.github#469) because npm
10 cannot install from an npm 11 lockfile — EUSAGE, 'Missing: <pkg> from lock
file'. This repo's OWN workflows did not move with it and they run npm ci too:
measured on the cooldown PR, every shared-workflow job passed while Lint Check
and Spec Validation went red on exactly that error.

pull-request-lint-check.yaml had no setup-node AT ALL, so it silently inherited
the runner default. That is the harder half to notice — nothing in the file
named a Node version, so nothing looked wrong.

Left alone deliberately: l10n.yml (its only 'npm ci' is in a comment saying it
needs none) and api-test-coverage.yml (npm install -g newman, no lockfile).
rubenvdlinde added a commit to ConductionNL/doriath that referenced this pull request Aug 16, 2026
The shared quality.yml moved to Node 24 (ConductionNL/.github#469) because npm
10 cannot install from an npm 11 lockfile — EUSAGE, 'Missing: <pkg> from lock
file'. This repo's OWN workflows did not move with it and they run npm ci too:
measured on the cooldown PR, every shared-workflow job passed while Lint Check
and Spec Validation went red on exactly that error.

pull-request-lint-check.yaml had no setup-node AT ALL, so it silently inherited
the runner default. That is the harder half to notice — nothing in the file
named a Node version, so nothing looked wrong.

Left alone deliberately: l10n.yml (its only 'npm ci' is in a comment saying it
needs none) and api-test-coverage.yml (npm install -g newman, no lockfile).
rubenvdlinde added a commit to ConductionNL/openconnector that referenced this pull request Aug 16, 2026
The shared quality.yml moved to Node 24 (ConductionNL/.github#469) because npm
10 cannot install from an npm 11 lockfile — EUSAGE, 'Missing: <pkg> from lock
file'. This repo's OWN workflows did not move with it and they run npm ci too:
measured on the cooldown PR, every shared-workflow job passed while Lint Check
and Spec Validation went red on exactly that error.

pull-request-lint-check.yaml had no setup-node AT ALL, so it silently inherited
the runner default. That is the harder half to notice — nothing in the file
named a Node version, so nothing looked wrong.

Left alone deliberately: l10n.yml (its only 'npm ci' is in a comment saying it
needs none) and api-test-coverage.yml (npm install -g newman, no lockfile).
rubenvdlinde added a commit to ConductionNL/openbuild that referenced this pull request Aug 16, 2026
The shared quality.yml moved to Node 24 (ConductionNL/.github#469) because npm
10 cannot install from an npm 11 lockfile — EUSAGE, 'Missing: <pkg> from lock
file'. This repo's OWN workflows did not move with it and they run npm ci too:
measured on the cooldown PR, every shared-workflow job passed while Lint Check
and Spec Validation went red on exactly that error.

pull-request-lint-check.yaml had no setup-node AT ALL, so it silently inherited
the runner default. That is the harder half to notice — nothing in the file
named a Node version, so nothing looked wrong.

Left alone deliberately: l10n.yml (its only 'npm ci' is in a comment saying it
needs none) and api-test-coverage.yml (npm install -g newman, no lockfile).
rubenvdlinde added a commit to ConductionNL/shillinq that referenced this pull request Aug 16, 2026
The shared quality.yml moved to Node 24 (ConductionNL/.github#469) because npm
10 cannot install from an npm 11 lockfile — EUSAGE, 'Missing: <pkg> from lock
file'. This repo's OWN workflows did not move with it and they run npm ci too:
measured on the cooldown PR, every shared-workflow job passed while Lint Check
and Spec Validation went red on exactly that error.

pull-request-lint-check.yaml had no setup-node AT ALL, so it silently inherited
the runner default. That is the harder half to notice — nothing in the file
named a Node version, so nothing looked wrong.

Left alone deliberately: l10n.yml (its only 'npm ci' is in a comment saying it
needs none) and api-test-coverage.yml (npm install -g newman, no lockfile).
rubenvdlinde added a commit to ConductionNL/launchpad that referenced this pull request Aug 16, 2026
…er a file-wide no-undef (#125)

* chore(security): enable the npm supply-chain cooldown on npm 11

Sets `min-release-age=2` and `min-release-age-exclude[]=@conduction/*`, raises
`engines.npm` to ^11.0.0, and regenerates the lockfile under npm 11.

The .npmrc comment here has described a cooldown for months and it has never
been in effect. `min-release-age` does not exist in npm 10 — `npm config get
min-release-age` answers `undefined` — and every Node 22 release bundles npm
10, so the setting was read by nothing. Most repos also had it at 0, which
disables it outright.

@conduction/* is exempt because without the exemption the cooldown does not
fail loudly, it silently resolves backwards: measured 2026-08-15, an install of
@conduction/nextcloud-vue on release day picked 2.0.7 instead of 2.3.0 and
exited 0.

The lock is regenerated under npm 11 and iterated to a fixed point. Where the
tree changed rather than its metadata, that is npm 10 -> 11 reconciling a lock
shaped by the older resolver, not the cooldown — verified by regenerating with
the cooldown enabled and disabled and getting identical trees.

Verified: npm ci exit 0 under npm 11.19.0, @conduction/nextcloud-vue resolves
to 2.3.0, gate-84 conformance passes.

* ci: re-run against the merged shared workflow

`gh run rerun` replays the workflow version resolved when the run was created,
so a reusable workflow referenced as @main is NOT re-resolved — every re-run
after ConductionNL/.github#469 merged still executed Node 22 with npm 10.9.8,
where `min-release-age` does not exist and `npm ci` cannot read an npm-11
lockfile. Only a new run picks up the merged workflow. This empty commit is
that trigger.

* fix(lint): clear the last tranche-A suppression — require.context under a file-wide no-undef

`require.context()` is a WEBPACK build-time API the bundler rewrites at compile
time, so eslint is right that no runtime `require` exists and the code is right
too. The file-wide `no-undef` suppression that recorded this also switched the
rule off for every OTHER identifier in the file, so a genuine typo there would
have been silent. Scoped to `/* global require */`.

Same shape found in 9 apps across the fleet; all cleared the same way.

Verified: eslint 0 errors, build exit 0, tests pass.
rubenvdlinde added a commit to ConductionNL/openbuild that referenced this pull request Aug 16, 2026
…er a file-wide no-undef (#217)

* ci: run every npm job on Node 24

The shared quality.yml moved to Node 24 (ConductionNL/.github#469) because npm
10 cannot install from an npm 11 lockfile — EUSAGE, 'Missing: <pkg> from lock
file'. This repo's OWN workflows did not move with it and they run npm ci too:
measured on the cooldown PR, every shared-workflow job passed while Lint Check
and Spec Validation went red on exactly that error.

pull-request-lint-check.yaml had no setup-node AT ALL, so it silently inherited
the runner default. That is the harder half to notice — nothing in the file
named a Node version, so nothing looked wrong.

Left alone deliberately: l10n.yml (its only 'npm ci' is in a comment saying it
needs none) and api-test-coverage.yml (npm install -g newman, no lockfile).

* fix(lint): clear the last tranche-A suppression — require.context under a file-wide no-undef

`require.context()` is a WEBPACK build-time API the bundler rewrites at compile
time, so eslint is right that no runtime `require` exists and the code is right
too. The file-wide `no-undef` suppression that recorded this also switched the
rule off for every OTHER identifier in the file, so a genuine typo there would
have been silent. Scoped to `/* global require */`.

Same shape found in 9 apps across the fleet; all cleared the same way.

Verified: eslint 0 errors, build exit 0, tests pass.
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