diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index f75289f0..00000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,75 +0,0 @@ -# Dependabot config — supply-chain conservative posture. -# -# Why this exists: -# - We now ship a `uv.lock`, so Dependabot's security-updates channel fires -# automatically on GHSAs touching transitive deps. This file adds -# *version-updates* policy on top: routine bumps that don't fall out -# of a security advisory. -# - 2025-2026 has seen a wave of maintainer-compromise attacks across npm -# and PyPI (Shai-Hulud / Mini Shai-Hulud, axios, PyTorch Lightning, -# tj-actions/changed-files, …). The published-to-Dependabot-PR-lag is -# often minutes. Cooldown buys a detection window for free. -# -# Policy: -# - 14-day cooldown on minor/patch (30 days on majors). Anything malicious -# is usually caught and yanked within a few days; the cooldown lets that -# happen before it reaches us. -# - Monthly schedule + grouped patch/minor PRs — one batched PR per month, -# atomic to revert, large enough to actually be reviewed. -# - Major bumps stay as individual PRs (potentially breaking; each -# deserves its own review). -# - Security updates are grouped too (one batched PR per ecosystem) so a -# same-day burst of advisories arrives as a single PR, not one per package. -# - No auto-merge anywhere. Lockfile diffs are uninspectable; we eyeball -# the version metadata and source links before merging. -# -# Note: cooldown and the monthly schedule do NOT apply to security-updates -# (the GHSA-driven PRs) — only to version-updates. Security PRs still fire on -# the CVE-publication timeline, which is the right tradeoff for shapepipe (fix -# CVEs fast). The `applies-to: security-updates` groups below don't change that -# timing; they only collapse a burst into one PR. Each `groups` block needs an -# explicit `applies-to`, hence the paired version/security groups per ecosystem. - -version: 2 - -updates: - # Python dependencies tracked via uv.lock - - package-ecosystem: "uv" - directory: "/" - schedule: - interval: "monthly" - day: "monday" - cooldown: - default-days: 14 - semver-major-days: 30 - groups: - lockfile-minor-patch: - applies-to: version-updates - patterns: ["*"] - update-types: ["minor", "patch"] - lockfile-security: - applies-to: security-updates - patterns: ["*"] - open-pull-requests-limit: 3 - labels: - - "dependencies" - - # GitHub Actions versions (SHA-pinned in workflows; Dependabot proposes - # SHA bumps with the new tag annotated in the PR body). - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "monthly" - cooldown: - default-days: 14 - groups: - actions: - applies-to: version-updates - patterns: ["*"] - actions-security: - applies-to: security-updates - patterns: ["*"] - open-pull-requests-limit: 2 - labels: - - "dependencies" - - "github-actions" diff --git a/.github/renovate.json5 b/.github/renovate.json5 new file mode 100644 index 00000000..6d4147d6 --- /dev/null +++ b/.github/renovate.json5 @@ -0,0 +1,86 @@ +{ + $schema: "https://docs.renovatebot.com/renovate-schema.json", + + // ── Renovate replaces Dependabot for shapepipe ────────────────────────────── + // + // Why the switch: Dependabot's only noise control is grouping, and its + // security channel bypasses even that — so a same-day burst of advisories + // arrives as N separate PRs that a human must merge by hand. Renovate gives a + // single Dependency Dashboard issue plus *branch automerge*: routine updates + // land silently on green CI with no PR at all. The supply-chain posture from + // the old dependabot.yml is carried over below and, via OSV, strengthened. + // + // Requires the Renovate GitHub App (or a self-hosted runner) — see the PR + // body for the one-time org-admin step. + + extends: [ + "config:recommended", + "helpers:pinGitHubActionDigests", // keep GitHub Actions SHA-pinned, as before + ], + + // One issue is the control surface: everything pending lives there, and + // nothing that needs a human decision becomes a PR until ticked. + dependencyDashboard: true, + + // pyproject declares ABSTRACT minimums; uv.lock holds the EXACT pins. Routine + // updates therefore touch only the lockfile — abstract floors change solely by + // deliberate human edit, never by the bot. (This makes the old "floor bumps + // need maintainer sign-off" rule structural rather than a manual hold.) + rangeStrategy: "update-lockfile", + + // Supply-chain cooldown (was 14d / 30d-major in dependabot.yml). 2025-26 saw a + // wave of maintainer-compromise attacks (Shai-Hulud, …); a release-age gate + // lets a malicious version be caught and yanked before it reaches us. + minimumReleaseAge: "14 days", + internalChecksFilter: "strict", // genuinely withhold updates until aged, not "surface as pending" + + // Keep the queue calm. + prConcurrentLimit: 5, + prHourlyLimit: 2, + + // Weekly batched lockfile refresh, merged on green CI — the routine + // "keep uv.lock current" job, fully hands-off. + lockFileMaintenance: { + enabled: true, + schedule: ["before 6am on monday"], + automerge: true, + }, + + // CVE fixes (GitHub alerts + OSV, which adds malicious-package detection) skip + // the long cooldown but keep a short 3-day window, then automerge on green CI — + // fast, not instant. Flip `automerge` to false here if you want eyes on every + // security fix. + osvVulnerabilityAlerts: true, + vulnerabilityAlerts: { + minimumReleaseAge: "3 days", + automerge: true, + labels: ["security"], + }, + + packageRules: [ + { + // Routine minor / patch / digest: batch into ONE weekly branch and land + // silently on green CI — no PR, no noise. (One branch = one container-build + // CI run, which matters since that build is slow.) + matchUpdateTypes: ["minor", "patch", "pin", "digest"], + groupName: "all non-major", + schedule: ["before 6am on monday"], + automerge: true, + automergeType: "branch", + }, + { + // Majors: never silent, never auto. 30-day age, then surface on the + // dashboard for explicit approval before a PR is even opened. + matchUpdateTypes: ["major"], + minimumReleaseAge: "30 days", + dependencyDashboardApproval: true, + }, + { + // ngmix is intentionally pinned to a tag in [tool.uv.sources] + // (esheldon v2.4.0). Tag changes are a deliberate shape-measurement + // decision, not a dependency bump — Renovate must not touch it. + matchDepNames: ["ngmix"], + enabled: false, + }, + ], +}