diff --git a/.github/renovate.json5 b/.github/renovate.json5 index c62d5a05..cbfbd83f 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -5,9 +5,19 @@ // re-proposing, every week, the exact bump this project has already decided // against - and to make the reason travel with the proposal when there is one. // -// This file does nothing on its own. Renovate only acts once the Renovate -// GitHub App is enabled for the repository (https://github.com/apps/renovate). -// Validated with `renovate-config-validator` before landing. +// The Renovate GitHub App was enabled on 2026-07-30 with "Automated PRs", +// "Require config file" and "Create onboarding PRs". Two consequences worth +// knowing: "Require config file" means Renovate does NOTHING, silently, if this +// file is absent or unparseable from the DEFAULT branch - so validate edits, and +// never let a broken config read as "no updates available". And onboarding PRs +// are moot while this file exists, since onboarding only fires with no config. +// Validate any edit with `renovate-config-validator` (run it from OUTSIDE the +// project root - a bare `npx` here fails with `EBADDEVENGINES`). +// +// `dependencyDashboardApproval` is deliberately NOT set. It would require ticking +// a box per update before a PR appears, which re-imposes scan-only behaviour at +// the config layer and would quietly defeat the app's "Automated PRs" setting. +// PRs are wanted; what is not wanted is anything MERGING unread - see `automerge`. // // THE pnpm INTERACTION, which is the subtle part. // This workspace relies on pnpm 11's built-in release-age cooldown - there is no @@ -44,9 +54,50 @@ dependencyDashboard: true, - // One weekly batch instead of a trickle. Times are UTC (no `timezone` set). + // Seattle. Without this the schedule below is UTC, i.e. "before 6am Monday" + // fires around midnight Sunday local - which reads as a bot that runs at + // random times. + timezone: "America/Los_Angeles", + + // One weekly batch instead of a trickle. To pull a run forward without editing + // this, tick the "Check this box to trigger a request for Renovate to run + // again" checkbox on the dependency dashboard issue. schedule: ["before 6am on monday"], + // Now that PRs actually arrive unattended, cap the burst. The first run after + // enabling the app sees every pending update at once; without a limit that is + // a dozen PRs in one morning, each triggering a ~4 minute CI run. + prConcurrentLimit: 3, + prHourlyLimit: 2, + + // Renovate's `lockFileMaintenance` is automated `pnpm up` for the lockfile, and + // that is the one dependency operation this repo has measured as harmful: + // re-resolving the ~22 surrounding transitive packages triggered + // `TS2321: Excessive stack depth comparing types ... 'UserConfig'` in + // vite.config.ts, while installing the SAME target versions directly did not. + // CLAUDE.md's "prefer targeted `pnpm add` over `pnpm up`" is exactly this. + // Off by default; pinned off explicitly so it cannot get switched on as tidying. + lockFileMaintenance: { enabled: false }, + + // Security fixes must not wait for the weekly window. Without this block a CVE + // fix could sit ~10 days: up to 3 for the age floor, then up to 7 for Monday. + // + // NOTE THE AGE VALUE, which is not zero and must not be. pnpm 11 refuses + // anything younger than 24h without writing a `minimumReleaseAgeExclude:` + // bypass into pnpm-workspace.yaml, so a same-day security PR would reintroduce + // precisely the hazard the top-of-file comment exists to prevent. 25 hours + // clears pnpm's floor with an hour to spare and drops 3 days to ~1. + vulnerabilityAlerts: { + schedule: [], + minimumReleaseAge: "25 hours", + prBodyNotes: [ + 'Security update - this bypassed the weekly schedule deliberately. Still read it: `brace-expansion`\'s advisory carries a flat `<=5.0.7` range, so `pnpm audit` flags a version that already has the fix. "The audit is red" is not by itself a reason to take a bump here.', + ], + }, + + // GitHub's own advisories miss ecosystem-only reports; OSV catches more. + osvVulnerabilityAlerts: true, + packageRules: [ { // THE ONE THAT MATTERS MOST. Without it a bot proposes 6.0.3 -> 7.x every diff --git a/CLAUDE.md b/CLAUDE.md index 51369e4c..1610a31b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -214,9 +214,27 @@ config, above pnpm's default, so Renovate can never propose a release pnpm would want a bypass for. If `minimumReleaseAgeExclude:` appears in a bot PR's diff, that PR is wrong; fix the age rule, don't commit the bypass. -Renovate is inert until the GitHub App is enabled on the repo. Validate any edit -with `renovate-config-validator` (run it from outside the project root - a bare -`npx` here fails with `EBADDEVENGINES`). +**The app is live as of 2026-07-30** - enabled with "Automated PRs", "Require +config file" and "Create onboarding PRs". So Renovate opens real PRs on its own +now; `automerge: false` is what keeps anything from _landing_ unread, and +`dependencyDashboardApproval` is deliberately unset because it would re-impose +scan-only behaviour at the config layer and defeat the app setting. + +"Require config file" is the one with teeth: **a config that fails to parse makes +Renovate do nothing at all, silently**, which is indistinguishable from "no +updates available". Validate any edit with `renovate-config-validator` (run it +from outside the project root - a bare `npx` here fails with `EBADDEVENGINES`). + +Two settings whose reasoning is not guessable from the outside: + +- **`lockFileMaintenance` is pinned off.** It is automated `pnpm up` for the + lockfile - the one dependency operation measured as harmful here, since + transitive re-resolution is what triggered the `TS2321` pathology below. +- **`vulnerabilityAlerts.minimumReleaseAge` is `"25 hours"`, not `0`.** Security + fixes skip the weekly window, but they cannot skip pnpm's 24-hour floor: a + same-day PR would make pnpm write the `minimumReleaseAgeExclude:` bypass this + whole section exists to prevent. 25 hours clears pnpm and still drops the wait + from 3 days to ~1. Branch protection on `main` is **not** configured. It is the natural follow-up now that a check exists, but it is a repository setting rather than a file.