ci: hoist Renovate's release-age gate to global; disable pnpm's own #59

Merged
ullrich merged 1 commit from ci/release-age-policy into main 2026-07-31 15:18:19 +00:00
Owner

Two changes to one policy area, prompted by #38 failing pnpm i both with and without --frozen-lockfile.

What was actually wrong

pnpm 11 enforces a 24-hour minimum release age on lockfile entries by default. It is invisible to pnpm config get minimumReleaseAge (returns undefined) and appears in no npmrc. Measured empirically as exactly 1440 minutes, with the cutoff tracking now − 24h across two runs a day apart.

#38's lockfile contained playwright/playwright-core@1.62.1, published hours before the branch was built:

[ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION] 2 lockfile entries failed verification:
  playwright-core@1.62.1 published 2026-07-30T16:36:44Z, within the
  minimumReleaseAge cutoff (2026-07-30T15:07:12Z)

So pnpm refused to install it for a full 24 hours — CI red the whole time, with nothing actually wrong.

Renovate was not at fault

Worth stating plainly, because it was my first assumption and it was wrong. #38's own PR body says playwright 1.61.1 → 1.62.0, and the lockfile contains 1.62.0 as proposed. Renovate honoured its three-day gate.

The 1.62.1 entries arrived afterwards, from our own pnpm dedupe postUpgradeTask re-resolving ^1.61.1 to the newest in-range version — a resolution Renovate neither proposed nor age-checked. The lock ends up containing both:

+  playwright-core@1.62.0:      ← Renovate's choice
+  playwright-core@1.62.1:      ← pnpm dedupe's

So hoisting the Renovate setting does not fix this case. It is worth doing on its own merits, and the config now records which gap it leaves rather than implying coverage it does not have.

Changes

renovate.json5minimumReleaseAge: "3 days" moves from the auto-merge packageRule to the top level, so it gates every proposed update including majors, not only auto-mergeable minor/patch.

pnpm-workspace.yamlminimumReleaseAge: 0, disabling pnpm's default. It fires at install time rather than resolve time, so it never prevents a too-fresh entry being written — only from being used, a day late.

The trade

We lose a guard against installing a just-published compromised release. The path that matters stays covered, and more strictly: three days for everything Renovate proposes versus pnpm's 24 hours.

Transitive versions pulled in by pnpm dedupe are covered by neither. If that gap ever bites, the right fix is constraining dedupe — not re-enabling a check that fires after the fact.

Verified

On #38's branch, 82 minutes before its window would have expired naturally:

state result
pristine exit=1ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION
with minimumReleaseAge: 0 exit=0 — "Done in 1s using pnpm v11.18.0"

Config checks: renovate.json5 parses with the setting at top level and zero rule-scoped copies, auto-merge rule otherwise intact (squash / conflicted / minor,patch,pin,digest); pnpm-workspace.yaml has exactly one occurrence and no duplicate mapping key. pnpm-lock.yaml deliberately untouched.

Note on #38 itself

It needs no change — its window expires at 16:36 UTC today and it would then install on its own. This PR stops the next one costing a day.

Two changes to one policy area, prompted by #38 failing `pnpm i` both with **and** without `--frozen-lockfile`. ## What was actually wrong **pnpm 11 enforces a 24-hour minimum release age on lockfile entries by default.** It is invisible to `pnpm config get minimumReleaseAge` (returns `undefined`) and appears in no npmrc. Measured empirically as exactly **1440 minutes**, with the cutoff tracking `now − 24h` across two runs a day apart. #38's lockfile contained `playwright`/`playwright-core@1.62.1`, published hours before the branch was built: ``` [ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION] 2 lockfile entries failed verification: playwright-core@1.62.1 published 2026-07-30T16:36:44Z, within the minimumReleaseAge cutoff (2026-07-30T15:07:12Z) ``` So pnpm refused to install it for a full 24 hours — CI red the whole time, with nothing actually wrong. ## Renovate was not at fault Worth stating plainly, because it was my first assumption and it was wrong. #38's own PR body says `playwright 1.61.1 → 1.62.0`, and the lockfile contains **1.62.0 as proposed**. Renovate honoured its three-day gate. The `1.62.1` entries arrived *afterwards*, from our own `pnpm dedupe` postUpgradeTask re-resolving `^1.61.1` to the newest in-range version — a resolution Renovate neither proposed nor age-checked. The lock ends up containing both: ``` + playwright-core@1.62.0: ← Renovate's choice + playwright-core@1.62.1: ← pnpm dedupe's ``` **So hoisting the Renovate setting does not fix this case.** It is worth doing on its own merits, and the config now records which gap it leaves rather than implying coverage it does not have. ## Changes **`renovate.json5`** — `minimumReleaseAge: "3 days"` moves from the auto-merge `packageRule` to the **top level**, so it gates every proposed update including majors, not only auto-mergeable minor/patch. **`pnpm-workspace.yaml`** — `minimumReleaseAge: 0`, disabling pnpm's default. It fires at *install* time rather than *resolve* time, so it never prevents a too-fresh entry being written — only from being used, a day late. ## The trade We lose a guard against installing a just-published compromised release. The path that matters stays covered, and more strictly: **three days** for everything Renovate proposes versus pnpm's 24 hours. Transitive versions pulled in by `pnpm dedupe` are covered by **neither**. If that gap ever bites, the right fix is constraining dedupe — not re-enabling a check that fires after the fact. ## Verified On #38's branch, 82 minutes before its window would have expired naturally: | state | result | |---|---| | pristine | `exit=1` — `ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION` | | with `minimumReleaseAge: 0` | **`exit=0`** — "Done in 1s using pnpm v11.18.0" | Config checks: `renovate.json5` parses with the setting at top level and **zero** rule-scoped copies, auto-merge rule otherwise intact (`squash` / `conflicted` / `minor,patch,pin,digest`); `pnpm-workspace.yaml` has exactly one occurrence and no duplicate mapping key. `pnpm-lock.yaml` deliberately untouched. ## Note on #38 itself It needs no change — its window expires at 16:36 UTC today and it would then install on its own. This PR stops the next one costing a day.
ci: hoist Renovate's release-age gate to global; disable pnpm's own
Some checks failed
CI / Dockerfile Package Check (pull_request) Successful in 23s
CI / Security Scan (pull_request) Successful in 45s
CI / Checks (pull_request) Successful in 3m41s
CI / Visual Tests (pull_request) Successful in 3m38s
CI / E2E Tests (pull_request) Failing after 48s
CI / Journal Image Smoke Test (pull_request) Successful in 27m6s
fd95fbcf03
Two changes to one policy area, prompted by #38 failing `pnpm i` both with and
without --frozen-lockfile.

## What was actually wrong

pnpm 11 enforces a 24-hour minimum release age on lockfile entries BY DEFAULT.
It is invisible to `pnpm config get minimumReleaseAge` (returns undefined) and is
in no npmrc. Measured empirically as exactly 1440 minutes, the cutoff tracking
`now - 24h` across two runs a day apart.

#38's lockfile contained playwright/playwright-core 1.62.1, published hours
before the branch was built, so pnpm refused to install it — for 24 hours, with
CI red and nothing actually wrong.

## Renovate was NOT at fault

Worth stating plainly, because it was my first assumption. #38's PR body says
`playwright 1.61.1 -> 1.62.0`, and the lockfile contains 1.62.0 as proposed.
Renovate honoured its three-day gate. The 1.62.1 entries arrived afterwards, from
our own `pnpm dedupe` postUpgradeTask re-resolving `^1.61.1` to the newest
in-range version — a resolution Renovate neither proposed nor age-checked.

So hoisting the Renovate setting does not fix this case. It is still worth doing
on its own merits, and the config now says which gap it leaves.

## Changes

- renovate.json5: `minimumReleaseAge: "3 days"` moves from the auto-merge
  packageRule to the top level, so it gates every proposed update including
  majors rather than only auto-mergeable minor/patch.
- pnpm-workspace.yaml: `minimumReleaseAge: 0`, disabling pnpm's default. It fires
  at INSTALL time rather than resolve time, so it never prevents a too-fresh
  entry being written — only from being used, a day late.

## Trade

We lose a guard against installing a just-published compromised release. The path
that matters stays covered and is stricter: three days for everything Renovate
proposes, versus pnpm's 24 hours. Transitive versions pulled in by `pnpm dedupe`
are NOT covered by either; if that gap bites, constrain dedupe rather than
re-enable a check that fires after the fact.

## Verified

On #38's branch, 82 minutes before its window would have expired:

  pristine                      exit=1  ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION
  with minimumReleaseAge: 0     exit=0  "Done in 1s using pnpm v11.18.0"

Config checks: renovate.json5 parses with the setting at top level and zero
rule-scoped copies, the auto-merge rule otherwise intact (squash / conflicted /
minor,patch,pin,digest); pnpm-workspace.yaml has exactly one occurrence and no
duplicate mapping key. pnpm-lock.yaml deliberately untouched.

Refs #38

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X8hNxgYp777FRqYtVmQNaU
ullrich deleted branch ci/release-age-policy 2026-07-31 15:18:20 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
trails-cool/trails!59
No description provided.