Skip to content

feat: hold the update floor above what the package managers accept - #29

Merged
stxkxs merged 1 commit into
mainfrom
feat/renovate-age-floor
Aug 8, 2026
Merged

feat: hold the update floor above what the package managers accept#29
stxkxs merged 1 commit into
mainfrom
feat/renovate-age-floor

Conversation

@stxkxs

@stxkxs stxkxs commented Aug 8, 2026

Copy link
Copy Markdown
Member

Two settings that were never compared

The shared preset set no minimumReleaseAge, so Renovate proposed a dependency the moment it was published. pnpm 11 refuses one that new. Nothing had ever put those two facts side by side.

Why this fails in a nasty place

pnpm 11 defaults minimumReleaseAge to 1440 minutes, and — the part that makes it sharp — re-verifies every entry in the lockfile on every install, not only the entries being added:

✗ Lockfile failed supply-chain policy check (574 entries in 1.7s)
[ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION] 1 lockfile entries failed verification:
  nanoid@3.3.18 was published at 2026-08-07T16:35:33.000Z, within the
  minimumReleaseAge cutoff

So one too-young package anywhere in a tree fails pnpm install --frozen-lockfile for the whole repository. The red lands on whichever pull request runs next, having changed nothing related to it. The disagreement does not fail where it is caused — it fails somewhere else, later, on someone else's change.

The floor

minimumReleaseAge: "3 days" — above pnpm's one day with headroom, and free against the weekly schedule this preset already runs on. Worth having on its own terms too: a compromised publish is usually caught within hours, and nothing here needs to be the first consumer of a release.

vulnerabilityAlerts states minimumReleaseAge: null rather than inheriting it. Renovate already defaults that way; writing it down means a later edit to the top-level floor cannot quietly start delaying CVE fixes.

A security PR whose fix is itself hours old may still be refused by pnpm until it ages out. Opening it immediately is the point — the PR is the signal — and it goes green on a re-run with no config change.

A check, because the validator cannot do this one

renovate-config-validator checks shape. It accepts this without complaint:

"minimumReleaseAge": "3 bananas"

The schema says "string" and stops there. It also has no opinion on whether the value agrees with the tools that consume the lockfiles Renovate writes — which is the invariant that actually matters.

scripts/check-renovate-floor.py asserts what the schema cannot: the floor parses as a duration, it is at or above pnpm's, and the security path is explicitly exempt.

--self-test breaks the config nine ways and fails unless every break is rejected, with the shipped config as a control so the suite cannot pass by rejecting everything:

  rejected  no floor at all
  rejected  floor below pnpm's
  rejected  floor exactly one minute short
  rejected  unparseable duration
  rejected  numeric instead of duration string
  rejected  null floor
  rejected  security path delayed
  rejected  security path left implicit
  rejected  vulnerabilityAlerts removed
  passed    (control) the shipped config

OK    all 9 breaks rejected, and the shipped config passes.

CI

Both run in a new job, alongside the validator in --strict mode (verified to exit 1 on a bad key, 0 on this file). default.json is consumed by every repository in the org and until now nothing read it before it shipped.

The new job is added to the merge gate's needs, so it cannot be added and then quietly ignored — the gate refuses any workflow containing a job it does not watch.

The shared preset set no minimumReleaseAge, so Renovate proposed a dependency
the moment it was published. pnpm 11 refuses one that new. The two settings had
never been compared, and they disagree in a way that surfaces far from either.

─── Why it matters more than it looks ───

pnpm 11 defaults minimumReleaseAge to 1440 minutes, and — the part that makes
this sharp — it re-verifies EVERY entry in the lockfile on every install, not
just the entries being added. A single package younger than the floor anywhere
in the tree fails `pnpm install --frozen-lockfile`, so the red lands on whatever
pull request happens to run next, having changed nothing related to it.

That means the disagreement does not fail where it is caused. It fails
somewhere else, later, on someone else's change.

─── The floor ───

minimumReleaseAge is three days: above pnpm's one, with headroom, and free
against the weekly schedule the preset already runs on. It is also worth having
on its own terms — a compromised publish is usually caught within hours, and
nothing here needs to be the first consumer of a release.

vulnerabilityAlerts states minimumReleaseAge: null rather than inheriting it.
Renovate already defaults that way; writing it down means a later edit to the
top-level floor cannot quietly start delaying CVE fixes. A security PR whose fix
is itself hours old may still be refused by pnpm until it ages out — opening it
immediately is the point, and it goes green on a re-run.

─── A check, because the validator cannot do this one ───

renovate-config-validator checks shape. It accepts `minimumReleaseAge:
"3 bananas"` without complaint, because the schema says "string" and stops
there, and it has no opinion on whether the value agrees with the tools that
consume the lockfiles Renovate writes.

scripts/check-renovate-floor.py asserts what the schema cannot: that the floor
parses as a duration, that it is at or above pnpm's, and that the security path
is explicitly exempt. `--self-test` breaks the config nine ways and fails unless
every break is rejected, with the shipped config as the control — a gate nobody
has watched fail is not known to be a gate.

Both run in CI, alongside the validator in --strict mode. default.json is
consumed by every repository in the org and until now nothing read it before it
shipped. The new job joins the merge gate's needs, so it cannot be added and
then quietly ignored.
@stxkxs
stxkxs merged commit 2488257 into main Aug 8, 2026
8 checks passed
@stxkxs
stxkxs deleted the feat/renovate-age-floor branch August 8, 2026 07:29
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