diff --git a/renovate/README.md b/renovate/README.md index e641573..deff8f8 100644 --- a/renovate/README.md +++ b/renovate/README.md @@ -45,9 +45,19 @@ The org default carries: - **JS lint stack grouping** — `eslint`, `prettier`, `jshint`, plus glob-matched `@eslint/*`, `eslint-config-*`, `eslint-plugin-*`. One coordinated PR instead of one-per-plugin. -- **Weekly Monday batch** for minor + major. Patches don't wait. -- **Patch automerge** on green CI. See **Patch automerge contract** - below. +- **Soak windows** (`minimumReleaseAge`) before automerge: patch/digest + 2 days, minor 5 days, major 7 days. The soak catches a yanked or + hotfixed release before it lands unattended. +- **PRs are created immediately** (`internalChecksFilter: "none"`), even + while soaking — so every update is visible and a human can manually + merge early (e.g. a hotfix). The soak only gates **automerge**: a + pending `renovate/stability-days` check holds the auto-merge until the + window passes, but it isn't a required branch-protection check, so + manual merge still works. +- **Automerge** on green CI after soak for **patch + digest** and + **minor**. **Majors never automerge** — always human-reviewed (see the + **Major-bump SOP** below). Minor automerge raises the CI bar; see the + **Automerge contract** below. Things that belong **per-repo**, not in the default: @@ -92,37 +102,69 @@ Three ways to unlock auto-merge on the private repos: minor friction. The patch automerge rule remains in the org default; it just no-ops on the private repos. -## Patch + digest automerge contract +## Automerge contract (patch + digest + minor) -The org default automerges patch-level **and digest-pin** updates -once CI passes. (Digest updates refresh the upstream image's content -hash without changing its tag — same code, freshly-rebuilt base -layer for CVEs — so they're inherently no-op-risk.) **A repo -extending this default must satisfy these conditions** or automerge -will silently land unreviewed code: +The org default automerges **patch**, **digest-pin**, and **minor** +updates once CI passes **and** the release has soaked +(`minimumReleaseAge`: patch/digest 2 days, minor 5 days) — long enough +for the ecosystem to surface a yanked or hotfixed release before it +lands unattended, short enough not to delay routine fixes. (Digest +updates refresh the upstream image's content hash without changing its +tag — same code, freshly-rebuilt base layer for CVEs — so they're +inherently no-op-risk.) **A repo extending this default must satisfy +these conditions** or automerge will silently land unreviewed code: 1. **PR-triggered CI must exist.** A workflow on `pull_request` (not just `workflow_dispatch` or `push`). Renovate's PRs run it; a missing workflow means no gate — Renovate would merge with no signal. Every existing jellyrock repo satisfies this today. -2. **CI must exercise the dependency surface.** "Build + lint" is - the baseline. "Build + lint + unit tests" is better. The - stronger the CI, the higher the confidence in patches. The - contract is intentionally fuzzy — repos with high test coverage - inherit higher safety than repos with just a build check. -3. **If a repo can't meet (1) or (2), override per-repo:** +2. **CI must exercise the dependency surface.** "Build + lint" is the + floor for patch/digest. **Minor automerge raises the bar:** a + breaking minor can pass a build but fail at runtime, so a repo that + automerges minors should have CI that exercises runtime behavior + (unit/integration/smoke tests), not just build+lint. The stronger + the CI, the higher the confidence. +3. **If a repo can't meet the bar, override per-repo.** Disable minor + automerge (keep patch) when CI is build+lint only: ```jsonc { "extends": ["github>jellyrock/.github//renovate/default"], "packageRules": [ { - "description": "This repo doesn't have CI strong enough for blind patch automerge", - "matchUpdateTypes": ["patch", "digest"], + "description": "CI is build+lint only — minors need human review", + "matchUpdateTypes": ["minor"], "automerge": false } ] } ``` + Or disable all automerge (patch + minor) for a repo with no real CI: + ```jsonc + { + "matchUpdateTypes": ["patch", "digest", "minor"], + "automerge": false + } + ``` + +## Major-bump SOP + +Majors never automerge. When a major PR appears, before merging: + +1. **Read the upstream changelog / migration guide** for the version + range (the PR body links the release notes). +2. **Pull the branch and run the repo's full gate locally** — build + + lint + the complete test suite (on JellyRock that includes the + on-device BS unit tests and the RTA functional pass, which CI's + PR-triggered run may not cover end-to-end). +3. **Grep for breaking-API usage** the changelog flags; migrate code in + the same PR. +4. **For runtime deps bundled into the app** (e.g. the Roku BS libs), + verify on a real device, not just a green build. +5. Merge only when the gate is green and any required migration is in + the PR. + +Repos may wrap this in a local script or skill so the steps run the same +way every time, rather than doing the checklist by hand. ## Adding a new repo diff --git a/renovate/default.json b/renovate/default.json index 5b134c4..1505b65 100644 --- a/renovate/default.json +++ b/renovate/default.json @@ -11,6 +11,7 @@ "ignorePaths": ["**/node_modules/**"], "timezone": "America/New_York", "separateMinorPatch": true, + "internalChecksFilter": "none", "packageRules": [ { "description": "Group GitHub Actions updates together; pin to SHA digests so version tags can't be silently re-pointed by upstream.", @@ -38,14 +39,22 @@ "addLabels": ["javascript"] }, { - "description": "Minor + major updates land on a weekly Monday review batch. Reduces PR noise; patches still fire immediately so security fixes aren't delayed.", - "matchUpdateTypes": ["minor", "major"], - "schedule": ["before 6am on monday"] + "description": "Minor updates automerge once CI is green AND the release has soaked 5 days. The PR is created immediately (internalChecksFilter:none) so it's visible and a human can manually merge early if needed (e.g. a hotfix); the renovate/stability-days pending check holds AUTOMERGE until the soak passes. Minor automerge raises the CI bar — the repo's PR-triggered CI must exercise runtime behavior, not just build+lint (a breaking minor can pass a build but fail at runtime). A repo whose CI can't meet that should override this rule to automerge:false. See renovate/README.md.", + "matchUpdateTypes": ["minor"], + "minimumReleaseAge": "5 days", + "automerge": true + }, + { + "description": "Major updates soak 7 days and NEVER automerge — a major is always human-reviewed regardless of CI. The PR is created immediately (internalChecksFilter:none) so it's visible during the soak; the soak gives the ecosystem time to surface a yanked or hotfixed release before a human picks it up. Follow the major-bump SOP (renovate/README.md) before merging.", + "matchUpdateTypes": ["major"], + "minimumReleaseAge": "7 days", + "automerge": false }, { - "description": "Patch + digest updates automerge once CI is green. Digest pins refresh the base layer for CVEs without changing the underlying version — same code, just a freshly-rebuilt upstream image — so they're inherently low-risk and don't need human review. Contract: every repo that extends this default must have PR-triggered CI that exercises the dependency surface (lint+build at minimum). See renovate/README.md.", + "description": "Patch + digest updates automerge once CI is green AND the release has soaked 2 days (minimumReleaseAge) — long enough to catch a yanked or hotfixed patch before it lands unattended, short enough not to delay routine fixes. Digest pins refresh the base layer for CVEs without changing the underlying version — same code, just a freshly-rebuilt upstream image — so they're inherently low-risk and don't need human review. Contract: every repo that extends this default must have PR-triggered CI that exercises the dependency surface (lint+build at minimum). See renovate/README.md.", "matchUpdateTypes": ["patch", "digest"], + "minimumReleaseAge": "2 days", "automerge": true } ] -} \ No newline at end of file +}