From 97f7c0375fd71fc63bcbfadb2547cb18e4673dbe Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Mon, 13 Jul 2026 13:43:10 +0200 Subject: [PATCH 1/2] chore: add renovate.json for reliable dependency automerge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ShapePipe had no repo Renovate config — everything came from Mend defaults. Two knobs, layered on top of those defaults, to make dependency automerge hands-off: - rangeStrategy = update-lockfile: pyproject.toml carries floor constraints (major-version signals); uv.lock is the real pin. Bump the lock for minor/patch updates, never rewrite the floors. (Non-lock ecosystems — Dockerfile, github-actions — fall back to "replace" automatically.) - minimumReleaseAge = 0 days: removes the release-age gate that was stalling automerge. With floor ranges, Renovate targets an aged version but `uv lock` resolves the floor to the newest in-range release — which may still be inside the age window — and Renovate can't pin uv to an exact version (renovatebot/renovate#41624), so the artifact update fails (this blocked #816). CI (full test suite + example pipeline, every PR) is the real safety gate for a dependency bump, so gate on CI, not a waiting period. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01CBC6HKdgXaZ7ei5XfdvZGo --- renovate.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 renovate.json diff --git a/renovate.json b/renovate.json new file mode 100644 index 00000000..ffd03e33 --- /dev/null +++ b/renovate.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + + "//": "Repo-level overrides layered on top of the Mend-hosted defaults (grouping, automerge, schedule stay as configured there). Two knobs, both aimed at making dependency automerge reliable without hand-nudging.", + + "//rangeStrategy": "pyproject.toml carries FLOOR constraints (major-version signals); uv.lock is the real pin. For minor/patch updates, bump the lock and leave the pyproject floors alone. (Non-lock ecosystems — Dockerfile, github-actions — fall back to 'replace' automatically.)", + "rangeStrategy": "update-lockfile", + + "//minimumReleaseAge": "Disable the minimum-release-age gate. With floor ranges, Renovate targets an aged version but `uv lock` resolves the floor to the NEWEST in-range release — which may still be inside the age window — and Renovate cannot pin uv to an exact version (renovatebot/renovate#41624). The result is a failed artifact update that stalls automerge (this blocked #816). ShapePipe CI (full test suite + the example pipeline, on every PR) is the real safety gate for a dependency bump, so gate on CI, not on a waiting period.", + "minimumReleaseAge": "0 days" +} From 91b1ffe93f6bf80d8334037314e276b2eecb6366 Mon Sep 17 00:00:00 2001 From: Cail Daley Date: Mon, 13 Jul 2026 14:06:32 +0200 Subject: [PATCH 2/2] chore: pin deps so Renovate honors minimumReleaseAge (supply-chain delay) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Keep the release-age gate — the point of it is supply-chain defense: a hijacked-but-legitimate package (compromised maintainer / malicious release) gets a detection window before it auto-merges — and make automerge reliable at the same time. Root problem: with floor ranges (>=) + rangeStrategy=update-lockfile, `uv lock` resolves the floor to the newest in-range release, which may be too fresh, and Renovate can't pin uv to an exact aged version (renovatebot/renovate#41624). So it fails the artifact update instead of waiting — this is what stalled #816. Fix: rangeStrategy=pin writes the exact aged version into pyproject.toml, so uv can't overshoot and minimumReleaseAge is honored. internalChecksFilter=strict ensures Renovate only ever acts on versions past the age gate. minimumReleaseAge set explicitly (3 days) so the control is owned in git, tunable. shapepipe ships as a container and isn't on PyPI (an app, not a library with downstream installers), so pinning is the appropriate, standard choice; the abstract-floors design targets libraries. Gates DIRECT deps only — transitive deps in uv.lock still float via lockFileMaintenance. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01CBC6HKdgXaZ7ei5XfdvZGo --- renovate.json | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/renovate.json b/renovate.json index ffd03e33..10d4c9dc 100644 --- a/renovate.json +++ b/renovate.json @@ -1,11 +1,14 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "//": "Repo-level overrides layered on top of the Mend-hosted defaults (grouping, automerge, schedule stay as configured there). Two knobs, both aimed at making dependency automerge reliable without hand-nudging.", + "//": "Repo-level overrides layered on the Mend-hosted defaults (grouping, automerge, schedule stay as configured there). Goal: KEEP the supply-chain release-age delay AND make dependency automerge reliable.", - "//rangeStrategy": "pyproject.toml carries FLOOR constraints (major-version signals); uv.lock is the real pin. For minor/patch updates, bump the lock and leave the pyproject floors alone. (Non-lock ecosystems — Dockerfile, github-actions — fall back to 'replace' automatically.)", - "rangeStrategy": "update-lockfile", + "//rangeStrategy": "Pin registry deps to exact versions. This is the only way Renovate can honor minimumReleaseAge with uv: given floor ranges (>=), `uv lock` resolves the floor to the NEWEST in-range release — which may still be too fresh — and Renovate cannot pin uv to an exact aged version (renovatebot/renovate#41624), so it fails the artifact update instead of waiting (this stalled #816). Pinning writes the exact aged version into pyproject.toml, so uv can't overshoot and the age gate is respected. shapepipe is deployed as a container and is not published to PyPI (an application, not a library with downstream installers), so pinning is the appropriate, standard choice; the abstract-floors design in CLAUDE.md targets libraries, which this isn't. Renovate opens a one-time 'Pin dependencies' PR. NOTE: this gates DIRECT deps only — transitive deps in uv.lock still float to latest via lockFileMaintenance.", + "rangeStrategy": "pin", - "//minimumReleaseAge": "Disable the minimum-release-age gate. With floor ranges, Renovate targets an aged version but `uv lock` resolves the floor to the NEWEST in-range release — which may still be inside the age window — and Renovate cannot pin uv to an exact version (renovatebot/renovate#41624). The result is a failed artifact update that stalls automerge (this blocked #816). ShapePipe CI (full test suite + the example pipeline, on every PR) is the real safety gate for a dependency bump, so gate on CI, not on a waiting period.", - "minimumReleaseAge": "0 days" + "//internalChecksFilter": "Only ever act on versions that have passed the internal checks (release age) — never pin to a too-fresh release.", + "internalChecksFilter": "strict", + + "//minimumReleaseAge": "Supply-chain delay: don't adopt a release until it is this old, so a hijacked-but-legitimate package (compromised maintainer / malicious release) has time to be detected and yanked before it auto-merges. Set explicitly here (owned in git, not an invisible Mend toggle). Tune up for a longer detection window.", + "minimumReleaseAge": "3 days" }