Compare commits

..
Author SHA1 Message Date
jakman85 b4ff2879be Merge pull request 'fix(renovate): edit catalog sources, track CNPG imageName, honour update.range, guard real-run auto-merges' (#86) from fix/renovate-run-hygiene into main
gate / gate (push) Successful in 2m53s
gate / conformance (push) Has been skipped
Reviewed-on: #86
2026-10-30 07:43:28 +00:00
jakman85andClaude Opus 5.5 d9571fc6d8 fix(renovate): edit catalog sources, track CNPG imageName, honour update.range, guard real-run auto-merges
gate / gate (pull_request) Successful in 2m55s
gate / conformance (pull_request) Has been skipped
Audit of the first completed dry runs (353/354) plus a debug-level local reproduction:

- App-image updates edited only the generated apps/wave-*. A ServiceDefinition has no apiVersion,
  so the kubernetes manager extracts nothing from catalog/, and every such PR would fail
  render-golden (reproduced: GOLDEN DRIFT). New regex managers for catalog source.image and
  render.py OAUTH2_PROXY_IMAGE put the source and generated edits in one branch (render-golden
  rc 0, reproduced).
- CNPG Cluster imageName was untracked (3 Clusters), while the grant Job was tracked. New manager.
- allowedVersions mirror catalog update.range: this drops gitea 28.x, qbittorrent 20.x, radarr 6,
  nextcloud 35 and postgresql 18.
- Never auto-merge: gitea, act_runner, postgresql (blast radius), flux range widening, python
  minors. Majors wait for a Dependency Dashboard tick.
- velero plugin RC: semver versioning (v1.14.3-rc.2 -> v1.14.4 now proposed).
- flux docker duplicates disabled (the "Cannot find replaceString" line). Two false comments
  corrected.

Local dry run: regex deps 7 -> 20, branches 33 -> 29. The validator passes on 44.167.0.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 02:35:33 -05:00
jakman85 593d0fad15 Merge pull request 'fix(renovate): document and probe the five token scopes Renovate needs; lint daemon-bound actions' (#85) from fix/renovate-auth into main
gate / gate (push) Successful in 2m54s
gate / conformance (push) Has been skipped
Reviewed-on: #85
Reviewed-by: Keith Jackson <[email protected]>
2026-10-30 07:10:02 +00:00
jakman85andClaude Opus 5.5 0c6ee4921d fix(renovate): document and probe the five token scopes Renovate needs; lint daemon-bound actions
gate / gate (pull_request) Successful in 2m54s
gate / conformance (pull_request) Has been skipped
Run 350 cleared platform init (/user 200, /version 200, /repos 200), then stopped at
GET /api/v1/orgs/Home-Network with Gitea's own 403:
  token does not have at least one of required scope(s), required=[read:organization],
  token scope=read:package,write:repository,read:user
The workflow's header listed only those three scopes, and the operator minted exactly that.
Renovate's initRepo -> isOrg needs read:organization, and Gitea files /issues and /labels under
the issue scope, which the dashboard and PR labels need: write:issue.

- renovate.yaml: corrects the scope list (read:user, read:organization, write:repository,
  write:issue, read:package), with the call each serves. A new preflight step probes them with
  GETs, the token passed to curl on stdin, and names a missing scope instead of Renovate's bare
  "Authentication failure".
- ci/lint.sh: rejects workflow `uses:` lines that need a Docker daemon (docker://,
  renovatebot/github-action, docker/*-action), the class fixed in #84.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 01:55:39 -05:00
jakman85 d550a501e2 Merge pull request 'fix(renovate): run Renovate as the job container; the action needs a Docker daemon jobs do not get' (#84) from fix/renovate-first-run into main
gate / gate (push) Successful in 2m52s
gate / conformance (push) Has been skipped
Reviewed-on: #84
Reviewed-by: Keith Jackson <[email protected]>
2026-10-30 06:36:11 +00:00
jakman85andClaude Opus 5.5 365e43cdea fix(renovate): run Renovate as the job container; the action needs a Docker daemon jobs do not get
gate / gate (pull_request) Successful in 2m52s
gate / conformance (pull_request) Has been skipped
renovatebot/github-action is a wrapper around `docker run`. On this runner a job has no Docker
daemon by decision (decisions/2026-10-29-image-pipeline.md), so every run, including dispatch
run 343, died before Renovate started:

  failed to connect to the docker API at unix:///var/run/docker.sock ... no such file or directory

The pinned image ghcr.io/renovatebot/renovate:44.167.0 is now the job container, and a run step
execs renovate-entrypoint.sh. actions/checkout is dropped, since Renovate clones the repo itself.
An optional RENOVATE_GITHUB_COM_TOKEN secret feeds GITHUB_COM_TOKEN.

Local dry run (--platform=local, uid 12021, the step body verbatim): 179 deps extracted, 42
updates, and all 4 own-image digest lookups against the Gitea registry succeed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 01:23:36 -05:00
jakman85 dfc432c6ea Merge pull request 'fix(keycloak-passkey): passkey reconciler with review fixes — bind only on git's say-so (default false), protect real sub-flows, body-id screening, failure alert' (#82) from feat/keycloak-passkey-reconcile into main
gate / gate (push) Successful in 2m50s
gate / conformance (push) Has been skipped
Reviewed-on: #82
Reviewed-by: Keith Jackson <[email protected]>
2026-10-30 05:59:11 +00:00
6 changed files with 611 additions and 34 deletions

No files matched your search

+138 -26
View File
@@ -60,9 +60,21 @@
# entry's "What I did NOT verify" — that gap is stated, not papered over.
#
# OPERATOR PREREQUISITE — ONE SECRET, NOT COMMITTED HERE:
# `RENOVATE_TOKEN` — a Gitea access token for the bot account, scopes `write:repository`,
# `read:user` and (since 2026-10-29) `read:package`. Set it as an Actions secret on this
# repository. Nothing in this repo mints,
# `RENOVATE_TOKEN` — a Gitea access token for the bot account (`renovate-bot`) with FIVE scopes.
# Each is required by a call Renovate 44.x makes against Gitea 1.24.3, and Gitea refuses a
# token missing one with `403 token does not have at least one of required scope(s)`:
# read:user GET /user (platform init: initPlatform)
# read:organization GET /orgs/{owner}, /orgs/{o}/labels (initRepo -> isOrg, then org labels)
# write:repository clone/push branches, /pulls, /statuses
# write:issue /issues, /labels, /issues/{n}/labels (dependency dashboard, PR labels;
# Gitea files all of these under the ISSUE scope, not repository)
# read:package the Gitea container registry (RENOVATE_HOST_RULES below)
# CORRECTED 2026-10-30. This list used to name only write:repository + read:user (+ read:package),
# and the operator minted exactly that. Run 350 then failed at
# `GET /api/v1/orgs/Home-Network 403 ... required=[read:organization], token scope=read:package,
# write:repository,read:user`; write:issue would have been the next wall. The preflight below now
# PROBES these scopes, so a short token fails in seconds with the missing scope named.
# Set it as an Actions secret on this repository. Nothing in this repo mints,
# stores or references its value; it is consumed by name only.
# Until it exists, this workflow FAILS LOUDLY on every scheduled run. That is intended: a
# visible red is the correct state for "automation configured but not credentialed".
@@ -116,15 +128,37 @@ concurrency:
jobs:
renovate:
runs-on: ubuntu-latest
# RENOVATE RUNS AS THE JOB CONTAINER, NOT THROUGH renovatebot/github-action. FIXED 2026-10-30.
# That action does exactly one thing: `docker run ghcr.io/renovatebot/renovate:<version>`. On
# this runner a job has NO Docker daemon, by decision (decisions/2026-10-29-image-pipeline.md:
# act_runner sets container.docker_host to `-` when DOCKER_HOST is tcp://, and the DinD daemon
# is privileged, so a job that could reach it would be root on the node). So every run of the
# action died before Renovate started. Dispatch run 343 (2026-10-30), verbatim:
# [command]/usr/bin/docker run --env LOG_LEVEL ... --env RENOVATE_TOKEN=*** ...
# failed to connect to the docker API at unix:///var/run/docker.sock; ... no such file or directory
# ::error::Error: The process '/usr/bin/docker' failed with exit code 1
# No auth, scope, config or network question was ever reached. Making the Renovate image the
# job container needs no daemon: act starts it through the runner's own DinD exactly as it
# starts the default ubuntu image, then `exec`s each step inside it.
#
# PINNED here (was the action's `renovate-version: 44.167.0`). renovate.json5 is validated
# against this exact tag — keep them in step. Renovate's github-actions manager reads
# `.gitea/workflows` and extracts `container.image`, so this pin is still self-maintained.
#
# The image runs as uid 12021 (`ubuntu`), and that is fine under act: the step command files
# ($GITHUB_STEP_SUMMARY etc.) are created mode 0666 (gitea/act v0.261.2 pkg/runner/step.go),
# HOME comes from the image env, and no step writes the workspace. There is deliberately NO
# actions/checkout: Renovate clones the repo itself from RENOVATE_ENDPOINT, and as uid 12021
# checkout could not `git init` the root-owned workspace volume anyway.
container:
image: ghcr.io/renovatebot/renovate:44.167.0
# Renovate scans every manager across the whole repo; 40m is generous for this repo's size and
# well under a runner's patience. Without a timeout a wedged run holds the `renovate`
# concurrency group and silently blocks every subsequent scheduled run.
timeout-minutes: 40
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Preflight - the token must exist, and its absence must be LOUD
shell: bash
env:
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
run: |
@@ -139,33 +173,94 @@ jobs:
That is the failure mode that let renovate.json sit inert in this repo for months while
seven files claimed "Renovate maintains this pin".
TO FIX: create a Gitea access token for the bot account with scopes
`write:repository`, `read:user` and `read:package` (the last one lets Renovate read
this repo's own images in the Gitea registry), then add it to this repository as the
Actions secret RENOVATE_TOKEN. Do not commit it anywhere.
TO FIX: create a Gitea access token for the bot account (renovate-bot) with scopes
read:user, read:organization, write:repository, write:issue and read:package (the
header of .gitea/workflows/renovate.yaml says which call needs which), then add it to
this repository as the Actions secret RENOVATE_TOKEN. Do not commit it anywhere.
MSG
exit 1
fi
echo "RENOVATE_TOKEN is present (value not printed)."
# Proves the step is running INSIDE the pinned Renovate image, not the default job image.
echo "renovate $(renovate --version)"
- name: Preflight - the token has every scope Renovate needs
shell: bash
env:
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
# ADDED 2026-10-30. Renovate reports EVERY platform-init failure as the bare
# "Authentication failure" (lib/modules/platform/gitea/index.ts initPlatform swallows the
# HTTP error), and a later scope gap surfaces as "Repository has unknown error". Two
# dispatches were spent learning one missing scope at a time. These probes are GETs only,
# and they name the missing scope. The token never reaches argv: curl reads it from stdin
# (`--config -`), so it is not in `ps` output or in the log.
run: |
set -uo pipefail
GITEA=https://gitea.home.keithjackson.dev
REPO=Home-Network/home-network-k3s-impl
fail=0
body=$(mktemp)
probe() { # <scope it proves> <path> <auth: bearer|basic>
local code
if [ "$3" = basic ]; then
code=$(printf 'user = "renovate:%s"\n' "$RENOVATE_TOKEN" \
| curl -sS --config - -o "$body" -w '%{http_code}' "$GITEA$2") || code=000
else
code=$(printf 'header = "Authorization: Bearer %s"\n' "$RENOVATE_TOKEN" \
| curl -sS --config - -o "$body" -w '%{http_code}' "$GITEA$2") || code=000
fi
if [ "$code" = 200 ]; then
echo "ok $1 (GET ${2%%\?*} -> 200)"
return
fi
fail=1
echo "::error::scope $1: GET ${2%%\?*} -> $code"
case "$code" in
401) echo " 401 = Gitea rejected the token VALUE (wrong, expired or revoked)." ;;
403) if grep -q 'Access denied' "$body"; then
echo " 403 'Access denied' = a Cilium/Envoy policy denial, not Gitea."
else
echo " Gitea: $(jq -r '.message // empty' "$body" 2>/dev/null | head -c 300)"
fi ;;
*) echo " body: $(head -c 200 "$body")" ;;
esac
}
probe read:user "/api/v1/user" bearer
probe read:organization "/api/v1/orgs/${REPO%%/*}" bearer
probe read:repository "/api/v1/repos/$REPO" bearer
probe read:issue "/api/v1/repos/$REPO/labels?limit=1" bearer
probe read:package "/v2/token?service=container_registry&scope=repository:home-network/toolbox:pull" basic
# WRITE scopes cannot be proven by a GET. Gitea's scope-refusal message, however, lists
# the token's scopes (names only, never the value): ask for an admin route this bot
# cannot have, and read `token scope=` from the 403. Advisory if the format changes.
printf 'header = "Authorization: Bearer %s"\n' "$RENOVATE_TOKEN" \
| curl -sS --config - -o "$body" "$GITEA/api/v1/admin/orgs?limit=1" || true
scopes=$(jq -r '.message // empty' "$body" 2>/dev/null | sed -n 's/.*token scope=//p')
if [ -n "$scopes" ]; then
echo "token scopes (from Gitea): $scopes"
for want in write:repository write:issue; do
case ",$scopes," in
*",$want,"*|*",all,"*) echo "ok $want" ;;
*) echo "::error::scope $want is missing (token scope=$scopes)"; fail=1 ;;
esac
done
else
echo "::warning::could not read the token's scope list; write:repository and write:issue are UNVERIFIED"
fi
rm -f "$body"
if [ "$fail" -ne 0 ]; then
echo "::error::RENOVATE_TOKEN failed the checks above (a 401 is the value, a 403 is a scope). Re-mint it for renovate-bot with read:user, read:organization, write:repository, write:issue, read:package and overwrite the secret."
exit 1
fi
- name: Run Renovate
uses: renovatebot/[email protected]
with:
# Pinned, and Renovate maintains this pin itself: renovate.json's enabledManagers
# includes `github-actions`, and `helpers:pinGitHubActionDigests` is in its `extends`.
# This is the one dependency in the repo whose updater is its own updater.
# MUST match the file's real name. #79 renamed it to renovate.json5 and left this line
# saying `renovate.json`; every scheduled run from 2026-10-18 then died in ~1s with
# ENOENT before Renovate started, and nothing in the gate noticed. ci/lint.sh now
# asserts that every `configurationFile:` here names a file that exists.
configurationFile: renovate.json5
token: ${{ secrets.RENOVATE_TOKEN }}
# PINNED 2026-10-29. Unset, this action version runs Renovate `39` (action.yml
# `renovate-version` default at v41.0.9), i.e. 39.264.0, and renovate.json5 uses
# `managerFilePatterns` (>= 40.2.0): under 39.x validation fails with five errors and
# the run stops at CONFIG_VALIDATION. #79 validated against `latest`, not this.
renovate-version: 44.167.0
shell: bash
# The repo config is discovered by Renovate from the cloned repo (renovate.json5,
# RENOVATE_REQUIRE_CONFIG=required). The action used to ALSO load it as the global config
# via RENOVATE_CONFIG_FILE; that double-loading is gone. ci/lint.sh's `configurationFile:`
# check therefore has nothing to resolve in this file, and passes.
env:
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
# Self-hosted Gitea. `platform` + `endpoint` were the two values renovate.json's own
# header said were "set at runtime by the operator's renovate runner" — this is that
# runner, so they are set here rather than in the repo config, which keeps renovate.json
@@ -189,6 +284,23 @@ jobs:
# token; it therefore needs scope `read:package` in addition to the ones listed in the
# preflight above. Gitea ignores the username for token auth (services/auth/basic.go).
RENOVATE_HOST_RULES: '[{"hostType":"docker","matchHost":"gitea.home.keithjackson.dev","username":"renovate","password":"${{ secrets.RENOVATE_TOKEN }}"}]'
# OPTIONAL, ADDED 2026-10-30. A github.com token (a fine-grained PAT with NO permissions,
# public-repo read only) for lookups of GitHub-hosted dependencies. Without it Renovate
# SKIPS them with `github-token-required` — measured in a local dry run: 17 deps, i.e.
# every `uses: actions/*` here plus kubevirt/kubevirt, kubevirt/containerized-data-importer
# and intel/intel-device-plugins-for-kubernetes. Unset is safe: the step below drops the
# empty variable, and those deps simply stay unmaintained, with a WARN in the log.
GITHUB_COM_TOKEN: ${{ secrets.RENOVATE_GITHUB_COM_TOKEN }}
run: |
set -euo pipefail
# An expression that evaluates to '' still EXPORTS the variable, as an empty string.
# Drop those so an unset value means "Renovate's default", never "set to empty".
for v in RENOVATE_DRY_RUN GITHUB_COM_TOKEN; do
[ -n "${!v:-}" ] || unset "$v"
done
# Through the image's entrypoint, as `docker run` would have gone: it sources the
# containerbase env and initialises the bundled tools before exec'ing Renovate.
exec renovate-entrypoint.sh renovate
- name: Summary
if: always()
+16
View File
@@ -42,3 +42,19 @@ while IFS=: read -r wf _ value; do
done < <(grep -nE '^[[:space:]]*configurationFile:' .gitea/workflows/*.y*ml 2>/dev/null || true)
[ "$missing" -eq 0 ] || exit 1
ok "workflow configurationFile references resolve"
# A workflow must not use an action that needs a Docker daemon: jobs on this runner get NONE, by
# decision (decisions/2026-10-29-image-pipeline.md; act_runner maps docker_host to `-`). Such an
# action is valid YAML and passes every other stage, then dies at runtime with
# "failed to connect to the docker API at unix:///var/run/docker.sock". renovatebot/github-action
# did exactly that on every Renovate run until 2026-10-30 (run 343). Run the tool's image as the
# job `container:` instead (see .gitea/workflows/renovate.yaml). Container (`docker://`) actions
# and Docker's own daemon-bound actions are rejected for the same reason. Comments are ignored.
daemon_uses='uses:[[:space:]]*["'\'']?(docker://|renovatebot/github-action|docker/(build-push|setup-buildx|setup-qemu|login)-action)'
bad=0
while IFS= read -r hit; do
err "${hit%%:*}: needs a Docker daemon, which jobs here do not get: $(echo "${hit#*:*:}" | sed -E 's/^[[:space:]]+//')"
bad=1
done < <(grep -HnE "^[^#]*${daemon_uses}" .gitea/workflows/*.y*ml 2>/dev/null || true)
[ "$bad" -eq 0 ] || exit 1
ok "no workflow uses a Docker-daemon-bound action"
+108
View File
@@ -0,0 +1,108 @@
---
date: 2026-10-30
branch: fix/renovate-auth
title: Renovate's token needs five scopes, not three; the workflow documented the wrong list, and now probes it
---
## What changed
**The fix is operator-side.** `RENOVATE_TOKEN` lacks `read:organization`, and it will next lack
`write:issue`. The operator must re-mint it with five scopes:
- `read:user`
- `read:organization`
- `write:repository`
- `write:issue`
- `read:package`
The git half fixes the two things that sent the operator round this loop twice.
**`.gitea/workflows/renovate.yaml`**:
- **The header's scope list is corrected**, with the call each scope serves. The old list named
only `write:repository`, `read:user` and `read:package`, and the operator minted exactly that.
The preflight's "TO FIX" text is corrected the same way.
- **A new step, "Preflight - the token has every scope Renovate needs".**
- It probes read-only GETs: `/user` for read:user, `/orgs/Home-Network` for read:organization,
`/repos/{repo}` for read:repository, `/repos/{repo}/labels` for read:issue, and registry
`/v2/token` for read:package.
- It reads the token's own scope list out of Gitea's 403 from `/admin/orgs` (names only), and
requires `write:repository` and `write:issue` in it.
- It says what a failure means: 401 is the value; a Gitea 403 is a scope, and the probe prints
Gitea's message; an "Access denied" 403 is a Cilium/Envoy denial.
- The token is fed to curl on stdin (`--config -`), never in argv.
**`ci/lint.sh`**: a new guard rejects any non-comment `uses:` in `.gitea/workflows` that needs a
Docker daemon: `docker://`, `renovatebot/github-action` and Docker's daemon-bound
`docker/*-action`s. That is the class of bug fixed in #84.
### Root cause, with evidence
Run 350 (task 567, 06:49 UTC) **got further than 348 and 349.** Gitea's router log, filtered to
the act-runner pod `10.0.0.38`:
06:49:31 GET /api/v1/user 200 OK
06:49:31 GET /api/v1/version 200 OK
06:49:31 GET /api/v1/repos/Home-Network/home-network-k3s-impl 200 OK
06:49:31 GET /api/v1/orgs/Home-Network 403 Forbidden
There were no `/v2/` requests: Renovate stopped before extraction. From the job log, verbatim:
ERROR: Repository has unknown error (repository=Home-Network/home-network-k3s-impl)
"url": "https://gitea.home.keithjackson.dev/api/v1/orgs/Home-Network",
"message": "token does not have at least one of required scope(s), required=[read:organization], token scope=read:package,write:repository,read:user"
"x-envoy-upstream-service-time": "27", "server": "envoy"
INFO: Repository finished ... "result": "unknown-error", "exitCode": 8
**This is not Cilium.** The body is Gitea's JSON scope message, Gitea logged the request, and
`x-envoy-upstream-service-time` shows that Envoy forwarded it upstream.
**The secret WAS updated.** Runs 348 and 349 got `GET /user 403` from `tokenRequiresScopes`. Run 350
got `/user 200`, and its token scope list now includes `read:user`.
**Why `read:organization` is needed.** Renovate 44.167.0, `lib/modules/platform/gitea/index.ts`
`initRepo`, calls `helper.isOrg` → `GET /orgs/{owner}` and rethrows on error. Gitea 1.24.3,
`routers/api/v1/api.go`, puts `/orgs/{org}` behind
`tokenRequiresScopes(AccessTokenScopeCategoryOrganization)`.
**Why `write:issue` is needed next.** In the same `api.go`, `/repos/{o}/{r}/issues…`, `/labels`
and `/milestones` sit in a group with `tokenRequiresScopes(AccessTokenScopeCategoryIssue)`
(lines 1466–1582). Renovate's gitea-helper uses `/issues`, `/issues/{n}/labels`, `/labels` and
`/issues/{n}/comments`, for the dependency dashboard (`:dependencyDashboard` is extended) and for
PR labels. A dry run only reads them; a scheduled run writes them.
The other causes the coordinator listed are ruled out:
- **Schedule window and dry-run.** The run never reached branch processing.
- **Repository config.** The run never reached the clone (`"cloned": undefined`).
- **The Summary step.** It is not involved; the failure is Renovate's own exit 8.
## What I verified
- I read the whole run 350 job log (204 lines), and the router lines above. Runs 348 and 349 are
as recorded in `decisions/2026-10-30-renovate-first-run.md`'s follow-up: `/user` got 403 from
`tokenRequiresScopes`.
- **The probe step, in the pinned image.** I extracted the step body from the YAML and ran it in
`ghcr.io/renovatebot/renovate:44.167.0` (`curl` 8.5.0 and `jq` are present) with a deliberately
**bogus** token. Every probe reported 401 with the "token VALUE" diagnosis, the scope-list read
fell back to a warning, and the step exited 1.
- **The scope-list parser.** Against run 350's exact message format it reports `write:issue`
MISSING. Against a five-scope list it reports both OK. Against `all` it reports both OK.
- **The lint guard.** It flags `renovatebot/github-action` in the pre-#84 `renovate.yaml`
(dfc432c, line 152). It reports nothing on the current workflows, and it ignores commented
lines. `bash -n ci/lint.sh` passes.
- **Account facts, read-only.** `renovate-bot` is a collaborator with `write`, and the org is
public.
## What I did NOT verify
- **The probe with a real token. UNVERIFIED.** I never had the token. In particular, I have not
observed that `GET /api/v1/admin/orgs` gives renovate-bot's token a 403 with a `token scope=`
list. The source says the scope check runs before `reqSiteAdmin`. If the message format
differs, the step warns and does not fail.
- **That five scopes are enough for a full non-dry run.** They cover every helper endpoint Renovate
44.167.0 calls, mapped to Gitea 1.24.3's route categories by reading source. Branch pushes go
over git-over-HTTPS with the token; I believe that needs write:repository, but I have not
observed it.
- **Registry reads as renovate-bot.** `read:package` on a public org is expected to work. The new
probe is the first real test of it.
- **Sensitive paths.** `.gitea/**` and `ci/**` are both sensitive. CI's `approval-policy` will block
until the operator posts `/approve-override` as a PR review.
+113
View File
@@ -0,0 +1,113 @@
---
date: 2026-10-30
branch: fix/renovate-first-run
title: Renovate never started because its action needs a Docker daemon jobs here do not get; it now runs as the job container
---
## What changed
**`.gitea/workflows/renovate.yaml`**: the job no longer uses `renovatebot/github-action`. The
pinned Renovate image (`ghcr.io/renovatebot/renovate:44.167.0`, the same version the action was
pinned to) is now the job's `container:`. A `run:` step calls `renovate-entrypoint.sh renovate`
inside it. `actions/checkout` is gone, because Renovate clones the repo itself.
`GITHUB_COM_TOKEN` is wired from a new **optional** secret, `RENOVATE_GITHUB_COM_TOKEN`. The step
unsets it, and `RENOVATE_DRY_RUN`, when they evaluate to the empty string. The preflight also
prints `renovate --version`, which proves the step ran inside the pinned image.
**`renovate.json5`**: one comment now names `container.image` as the version pin, in place of
`renovate-version`. The config itself is unchanged.
### Why: the root cause
Every run of the action died before Renovate started. That includes the operator's dispatch,
run 343 (task 555, 2026-10-30 06:14 UTC), and the 11 scheduled runs before it (312–322). From
run 343's log:
RENOVATE_TOKEN is present (value not printed).
[command]/usr/bin/docker run --env LOG_LEVEL ... --env RENOVATE_TOKEN=*** --env RENOVATE_CONFIG_FILE=/github-action/renovate.json5 ...
failed to connect to the docker API at unix:///var/run/docker.sock; check if the path is correct and if the daemon is running: dial unix /var/run/docker.sock: connect: no such file or directory
Error: The process '/usr/bin/docker' failed with exit code 1
The action is only a wrapper around `docker run`. On this runner a job deliberately gets no
Docker daemon (`decisions/2026-10-29-image-pipeline.md`): act_runner maps `docker_host` to `-` when
`DOCKER_HOST` is `tcp://`, and the DinD daemon is privileged. So this was a
platform-compatibility failure. The run never reached authentication, token scope, config or the
network. **The re-minted token is not implicated, and nothing in this log can say whether its
scopes are right.**
A job `container:` needs no daemon. act creates it through the runner's DinD the same way it
creates the default `ubuntu-latest` image, then `exec`s each step inside it. I checked this
against gitea/act v0.261.2:
- The job container's entrypoint is `/bin/sleep` (`run_context.go`). The Renovate image has
`/usr/bin/sleep`.
- The step command files are written `0o666` (`step.go`), so the image's uid 12021 can write
`$GITHUB_STEP_SUMMARY`.
- `HOME` comes from the image env.
## What I verified
- **The root cause.** I read all of run 343's job log (162 lines, job 0; job 1 is 404). It has one
error, quoted above, and no Renovate output at all. Runs 312–322 are all `failure` from the
`schedule` event.
- **The config validates against the pinned tag.** `podman run ... ghcr.io/renovatebot/renovate:44.167.0
renovate-config-validator /tmp/renovate.json5` printed `Config validated successfully`. It also
printed the known migration diff for the top-level `docker` block.
- **A dry run with the fix extracts and looks up dependencies.** This was a local reproduction. I
made a clean snapshot of this branch's tracked files. I ran it in a podman container of the pinned
image with entrypoint `/bin/sleep`, as the image's own uid 12021 with `HOME=/home/ubuntu`.
Inside it I `exec`ed the workflow step's `run:` body, extracted verbatim from the YAML, with:
- `RENOVATE_PLATFORM=local`, `RENOVATE_DRY_RUN=full` and `GITHUB_COM_TOKEN=` (empty, to
exercise the unset);
- a docker host rule for `gitea.home.keithjackson.dev` using the `gitea-admin` credential from
`~/.git-credentials`, passed through env and never printed.
The run exited 0 and logged `Dependency extraction complete`:
- flux 15 deps / 13 files
- github-actions 24 / 4
- kubernetes 131 / 67
- kustomize 2 / 2
- regex (both custom managers) 7 / 6
- **total 179 deps / 92 files**
It found **42 updates**: 29 kubernetes, 6 flux, 2 kustomize, 1 github-actions and 4 regex
`digest`. 7 of them are `major`.
- **The `# renovate-digest:` lookups against the Gitea registry succeed.** All three
`/v2/token?scope=repository:home-network/<img>:pull` requests succeeded, and so did
`HEAD /v2/home-network/<img>/manifests/main`. They proposed `digest` updates for
`conformance/versions.env`, `infra/agent/versions.env`, `platform/pm-agent/configmap.yaml` and
`platform/pm-agent/deployment.yaml`, all of which carry the zero placeholder digest today. The
`kubernetes` manager's copies of those images report `disabled`, which is the intended effect of
the packageRule.
- **The new pin is still maintained by Renovate.** The github-actions manager extracts
`ghcr.io/renovatebot/renovate:44.167.0` from `renovate.yaml` as depType `container`, and proposes
44.198.0.
- **The `regex` vs `custom.regex` question from #73/#77 is closed.** Both custom managers produced
dependencies.
- **Missing GitHub token.** The local run logged `GitHub token is required for some dependencies`.
That accounts for 17 deps skipped with `github-token-required`, which is why
`RENOVATE_GITHUB_COM_TOKEN` is offered as an optional secret.
## What I did NOT verify
- **The workflow on the real runner. UNVERIFIED.** My reproduction ran with podman, not
act_runner + DinD, and used `--platform=local`. The act behaviour (sleep entrypoint, `exec` as the
image user, 0666 command files) comes from reading the gitea/act v0.261.2 source, not from a run.
The first real evidence will be a `workflow_dispatch` after merge.
- **The Gitea platform path and `RENOVATE_TOKEN`'s scopes. UNVERIFIED.** I have no access token, only
the `gitea-admin` password, which Renovate's Gitea platform cannot use. So these were not
exercised: platform init (`GET /user`, which needs `read:user`), repo clone, and the registry
lookups using the **token** with `read:package` rather than a password.
- **Behaviour inside the schedule window.** A dispatch outside 03:00–06:00 America/New_York
(07:00–10:00 UTC during EDT) will extract and look up, but will log "not within schedule"
instead of `DRY-RUN: Would create ...` lines. I did not change this.
- **Renovate as uid 12021 doing artifact updates** (for example helm lockfiles) on a real,
non-dry run. `--dry-run=full` does not run the tools.
- **The self-bump.** Renovate will propose `ghcr.io/renovatebot/renovate` 44.167.0 → 44.198.0 as a
minor, which the default rule auto-merges. `.gitea/**` is a sensitive path, so it will in fact
wait for an administrator override. Nothing re-runs `renovate-config-validator` on that bump,
and no gate check runs the validator.
- **No alert for "Renovate red every day".** That gap is still open. This failure ran for 12 days
without anyone noticing.
- **Sensitive path.** `.gitea/**` is in `infra/gitea/approval-policy.json`, so CI's
`approval-policy` stage will block until the operator posts `/approve-override` as a PR review.
@@ -0,0 +1,128 @@
---
date: 2026-10-30
branch: fix/renovate-run-hygiene
title: Renovate's first completed dry runs, audited; it would have opened red PRs for every app and missed the databases. Config fixed
---
## What changed
**`renovate.json5` only.** Each change carries a comment quoting the measurement behind it.
1. **Three new regex managers, so updates edit the SOURCE.**
- `catalog/*.yaml` `source.image`.
- `catalog/render.py` `OAUTH2_PROXY_IMAGE`.
- CNPG `imageName:` under `platform/` and `apps/`.
The first two match the rendered line's depName, so the catalog edit and the generated
`apps/wave-*` edit land in **one** branch.
2. **`allowedVersions` mirroring each ServiceDefinition's `update.range`:**
- gitea/gitea `<2.0.0`
- nextcloud `<32.0.0`
- qbittorrent `<6.0.0`
- radarr `<6.0.0`
- sonarr `<5.0.0`
- semaphore `<3.0.0`
Plus CNPG postgresql `<17.0.0`, because a Postgres major is a migration, not a bump.
3. **The blast-radius never-automerge list gains** `gitea/gitea`, `gitea/act_runner` and
`ghcr.io/cloudnative-pg/postgresql`.
4. **HelmRelease range widening** (`matchManagers: flux`, ranged currentValue) is never
auto-merged and is labelled `range-widen`.
5. **Majors need a Dependency Dashboard tick** (`dependencyDashboardApproval`) before a PR opens.
6. **`flux` manager docker deps are disabled.** These are duplicates of `kubernetes` manager
extractions.
7. **`velero/velero-plugin-for-aws` uses `semver` versioning**, so its RC pin can reach a stable
release.
8. **`python` minors are not auto-merged.**
9. **Two false comments are corrected.**
- Ranges "cap Renovate at patches": false, Renovate widens them.
- `nextcloud:31-apache` and `docker:27-dind` "cannot be maintained": false, they were.
PR #85 is merged, so nothing here overlaps an open branch.
### The audit: dispatch runs 353 (07:12 UTC) and 354 (07:23 UTC), on 593d0fa
Both runs ended `success`, Renovate `"result": "done"`, `exitCode 0`. Both were at `info` level, so
I reproduced the per-dependency detail with a debug-level local dry run of the same commit (see
below).
The runs were inside the 03:00–06:00 America/New_York window, so branches were processed:
- run 353: 37 "Would commit" branches;
- run 354: 40, because the operator had added `RENOVATE_GITHUB_COM_TOKEN` 9 s before it.
Gitea's router log for the act-runner pod during both runs shows 317 2xx responses. The only
non-2xx were all expected:
- `/v2/` 401: the registry challenge;
- `/admin/orgs` 403: the #85 scope probe, by design;
- 6× `HEAD …/manifests/sha256:000…` 404: the placeholder digests.
| # | Class | Count | Cause | Severity | Fix |
|---|---|---|---|---|---|
| 1 | App-image PRs edit only generated `apps/wave-*` | 9 branches | A ServiceDefinition has no `apiVersion`, so the `kubernetes` manager extracts 0 deps from `catalog/`. The oauth2-proxy sidecar is a Python constant (`catalog/render.py:132`) | **Real problem.** Every such PR fails `ci/render-golden.sh` (reproduced: rc 1 "GOLDEN DRIFT"), stays red and holds a PR slot | git (1) |
| 2 | CNPG `imageName` untracked | 3 Clusters | The `kubernetes` manager reads `image:` only. The one tracked postgres line was the grant Job (`nextcloud-db.yaml:303`, "keep in step with spec.imageName") | **Real problem.** A real run bumps the Job and leaves the databases behind | git (1) |
| 3 | Bogus or out-of-policy majors | 5 branches: gitea 28.2.0, qbittorrent 20.04.1, radarr 6.x, nextcloud 35, postgresql 18.6 | Odd legacy tags, plus `update.range` in `catalog/` not being read by Renovate | Risk if run for real: unmergeable PRs, which hold slots | git (2) |
| 4 | Auto-merge of the merge gate's own host | gitea 1.24.3→1.27.3, act_runner 0.2.11→0.6.1, both "minor" | Not in the blast-radius list. A 0.x minor is breaking by semver | **Risk if run for real** | git (3) |
| 5 | Range widening auto-merged | external-dns, headlamp (minor); longhorn (blast radius); kube-prometheus-stack (major) | Renovate widens `>=a <b`. The config's comment claimed the opposite | **Risk if run for real:** silently lets Flux adopt new minors | git (4, 9) |
| 6 | Majors hold PR slots | 10 majors in run 354 against `prConcurrentLimit: 5` | Majors never auto-merge, so they stay open | Risk: starves auto-mergeable PRs | git (5) |
| 7 | `Cannot find replaceString … alpine:3.22` (INFO) | 1 per run | The same HelmRelease `image:` line is extracted by `flux` and `kubernetes` (also the velero plugin) | Cosmetic. The branch is still right | git (6) |
| 8 | velero-plugin-for-aws stuck on `v1.14.3-rc.2` | 1 dep, 0 updates | Docker versioning treats `-rc.2` as a suffix, so stable `v1.14.3` and `v1.14.4` never qualify | **Real problem** (silent) | git (7) |
| 9 | python 3.13→3.15 as auto-mergeable "minor" | 2 files | Language runtime minor | Risk if run for real | git (8) |
| 10 | `WARN GitHub token is required` | run 353: 5 packages, 17 deps | No github.com token | Real (they were unmaintained) | **operator, done:** run 354 has no WARN and adds `pin-dependencies`, `kubevirt-cdi`, `actions-checkout-7.x`, `major-github-artifact-actions` |
| 11 | Floating or untagged images (`invalid-value`) | 4: jackett, plex `latest`; pihole, keycloak no tag | No version to compare | Real, already documented | none in git. Pinning by hand is an operator/PM decision |
| 12 | `runs-on: ubuntu-latest` `invalid-version` | 11 | The github-actions manager extracts runner labels | Cosmetic, debug-only | none |
| 13 | `Docker Manifest is unknown` (debug) | 3 | HEAD on the all-zero placeholder digests | Expected. Clears when the own-image digest PRs merge | none |
| 14 | `Docker: error fetching data from DockerHub` 403 (debug) | 5 | Hub refuses `page=11`. Renovate already has the tags | Cosmetic. Updates were still found | none |
| 15 | `Config migration necessary` | 1 | Top-level `"docker": {"enabled": true}` | Cosmetic, pre-existing | none (as before) |
**Checked and fine:**
- The 3 own-image digest updates were found in both runs. All 4 `# renovate-digest:` markers parse.
- No marker failed to parse.
- No package file is under `conformance/runs` or `tests`, nor is `DECISIONS-LOG.md`, `decisions/` or
any `*.sops.yaml` (77 package files checked).
- `kubevirt-cdi` grouping works.
- The schedule window behaved as designed.
- Vulnerability alerts: none in either log.
## What I verified
- I read both job logs in full (132 and 129 lines), and the router log for 10.0.0.38 during both
runs.
- **`renovate-config-validator` on 44.167.0**: `Config validated successfully`. The migration diff
is only the pre-existing `docker` block.
- **Local dry runs.** Pinned image, `--platform=local`, debug, origin/main snapshot. "Before" is
593d0fa and "after" is this config; the GitHub-hosted deps were skipped in both.
- **Extraction:** `regex` went from 6 files / 7 deps to **19 files / 20 deps**. Every other
manager is unchanged: flux 15, github-actions 24, kubernetes 131, kustomize 2.
- **Branches:** 33 before, **29 after**. Removed: gitea-28.x, qbittorrent-20.x, radarr-6.x,
nextcloud-35.x and postgresql-18.x. Added: velero `v1.14.3-rc.2 → v1.14.4` (patch).
- **Gitea, qbittorrent, radarr, sonarr, semaphore and seerr branches** now each carry
`catalog/<svc>.yaml` + `apps/wave-*/<svc>.yaml`.
- **The oauth2-proxy branch** carries `catalog/render.py` plus the 5 rendered apps and
`platform/crafty`.
- **The postgresql-16.x branch** carries all three Clusters.
- The 2 flux docker duplicates report `disabled`.
- **render==golden holds for the new branch shape.**
- In the toolbox, I applied the gitea branch edit (catalog + apps) and the oauth2-proxy branch
edit (`render.py` constant + 5 apps) textually. `./ci/render-golden.sh` reported
`all 10 onboarded service golden(s) reproduce`, rc 0.
- Negative control, the old shape with only `apps/wave-0/gitea.yaml` bumped: rc 1,
`GOLDEN DRIFT`.
- Both edits were reverted afterwards.
## What I did NOT verify
- **The effect of the automerge, label and dashboard-approval rules (3, 4, 5, 8). UNVERIFIED in a
run.** `--platform=local` stops before branch processing, so I saw only which updates exist, not
their merged flags. The matchers are standard (`matchPackageNames`, `matchManagers`,
`matchCurrentValue`, `matchUpdateTypes`). The next dispatch's debug log shows them per branch.
- **`allowedVersions` under docker versioning** used Renovate's "Falling back to npm semver
syntax" path (16 debug lines). That it filters is shown by the five removed branches.
- **render==golden for the other app branches** (qbittorrent, radarr, sonarr, semaphore, seerr). I
proved it only for gitea and oauth2-proxy. They have the same shape: one `source.image` line ↔
one rendered `image:` line.
- **The `allowedVersions` rules duplicate `catalog/*/update.range`** and can drift from it. Nothing
checks them against each other. A lint comparing the two is the obvious follow-up.
- **Any real (non-dry) run.** None has happened. PR creation, labels, auto-merge through Gitea's
API and the Dependency Dashboard issue are all unobserved.
- **Sensitive path?** `renovate.json5` is not in `infra/gitea/approval-policy.json`, as far as I
know. CI's `approval-policy` result on the PR is the measurement.
+108 -8
View File
@@ -29,10 +29,11 @@
// `managerFilePatterns` key ("Custom Manager contains disallowed fields", "Invalid configuration
// option: flux.managerFilePatterns", ...), because 39.x only knows `fileMatch`; the rename landed
// in 40.2.0. So even after the workflow found this file, every run would have stopped at
// CONFIG_VALIDATION. renovate.yaml now pins `renovate-version` to the exact release this file is
// validated against. VALIDATE WITH THAT SAME TAG:
// CONFIG_VALIDATION. renovate.yaml now pins the exact release this file is validated against —
// since 2026-10-30 as the job's `container.image` tag (the action is gone: it needed a Docker
// daemon jobs here do not get). VALIDATE WITH THAT SAME TAG:
// podman run --rm -v "$PWD/renovate.json5:/tmp/renovate.json5:ro,Z" \
// ghcr.io/renovatebot/renovate:<renovate-version from renovate.yaml> \
// ghcr.io/renovatebot/renovate:<container.image tag in renovate.yaml> \
// renovate-config-validator /tmp/renovate.json5
// (Not `--strict`: that also fails on a pending migration of the pre-existing top-level
// `"docker": {"enabled": true}` block at the bottom, which is a warning, not an error.)
@@ -148,6 +149,50 @@
],
"datasourceTemplate": "docker",
"versioningTemplate": "docker"
},
// ADDED 2026-10-30 — THE CATALOG IS THE SOURCE; apps/wave-*/*.yaml IS GENERATED FROM IT.
// First real dry run (dispatch runs 353/354): EVERY app-image update edited only the rendered
// apps/wave-*/*.yaml and NONE edited catalog/. The `kubernetes` manager lists catalog/ above
// but extracts nothing there: a ServiceDefinition has no `apiVersion`, so it is not a
// Kubernetes manifest to that manager (measured: 0 deps from catalog/). Every such PR would
// fail ci/render-golden.sh (the canonical render reverts the bump), sit red and hold a
// prConcurrentLimit slot. These two managers read the SOURCES, under the SAME depName as the
// rendered line, so both land in ONE branch and render==golden holds: `source.image` in each
// ServiceDefinition, and render.py's OAUTH2_PROXY_IMAGE (the six auth sidecars).
{
"customType": "regex",
"description": "ServiceDefinition `source.image: <repo>:<tag>` (catalog/*.yaml) — the source of the rendered apps/wave-* image.",
"managerFilePatterns": ["/(^|/)catalog/[^/]+\\.ya?ml$/"],
"matchStrings": [
"\\n\\s+image:\\s*(?<depName>[^\\s:@#\"']+):(?<currentValue>[^\\s@#\"']+)"
],
"datasourceTemplate": "docker"
},
{
"customType": "regex",
"description": "catalog/render.py OAUTH2_PROXY_IMAGE — the per-app oauth2-proxy sidecar every forward-auth app is rendered with.",
"managerFilePatterns": ["/(^|/)catalog/render\\.py$/"],
"matchStrings": [
"\\nOAUTH2_PROXY_IMAGE\\s*=\\s*\"(?<depName>[^\":@]+):(?<currentValue>[^\"@]+)\""
],
"datasourceTemplate": "docker"
},
// ADDED 2026-10-30 — CNPG `Cluster.spec.imageName`. The `kubernetes` manager reads `image:` only,
// so the three database Clusters (platform/keycloak/postgres-cluster.yaml, apps/wave-0/
// nextcloud-db.yaml, apps/wave-0/semaphore-db.yaml) were untracked, while the grant Job in
// nextcloud-db.yaml — whose comment says "keep the tag in step with spec.imageName" — WAS. A real
// run would have bumped the Job and left the database behind. Same depName, so one branch.
{
"customType": "regex",
"description": "CloudNativePG Cluster `imageName: <repo>:<tag>`.",
"managerFilePatterns": [
"/(^|/)platform/.+\\.ya?ml$/",
"/(^|/)apps/.+\\.ya?ml$/"
],
"matchStrings": [
"\\n\\s+imageName:\\s*(?<depName>[^\\s:@#\"']+):(?<currentValue>[^\\s@#\"']+)"
],
"datasourceTemplate": "docker"
}
],
"packageRules": [
@@ -199,7 +244,15 @@
"longhorn",
"cilium",
"cloudnative-pg",
"kube-prometheus-stack"
"kube-prometheus-stack",
// ADDED 2026-10-30. gitea and act_runner ARE the merge gate: a bad auto-merged bump takes
// down the very check that would have to approve its revert (the dry run proposed gitea
// 1.24.3 -> 1.27.3 and act_runner 0.2.11 -> 0.6.1, both as auto-mergeable "minor"; a 0.x
// minor is a breaking line by semver). The postgresql image restarts every CNPG database,
// Keycloak's (the IdP) included, now that imageName is tracked.
"gitea/gitea",
"gitea/act_runner",
"ghcr.io/cloudnative-pg/postgresql"
],
"automerge": false,
"addLabels": [
@@ -226,6 +279,51 @@
"gitea.home.keithjackson.dev/home-network/conformance-runner"
],
"addLabels": ["own-image"]
},
{
"description": "ADDED 2026-10-30. The `flux` manager also extracts `image:` lines inside HelmRelease values, which the `kubernetes` manager already owns. Measured: exactly two (alpine in platform/oauth2-proxy/helmrelease.yaml, velero-plugin-for-aws in platform/velero/helmrelease.yaml), each extracted twice; the second rewrite of the same line logged `Cannot find replaceString in current file content` in runs 353/354. Flux keeps its charts (datasource helm).",
"matchManagers": ["flux"],
"matchDatasources": ["docker"],
"enabled": false
},
{
"description": "ADDED 2026-10-30. MIRRORS catalog/<svc>.yaml `update.range` — the operator's declared per-service policy (schemas/servicedefinition.schema.json), which Renovate does not read. Without it the dry run proposed gitea 28.2.0, qbittorrent 20.04.1 (both stale/odd tags, not real successors), radarr 6.x and nextcloud 35 (four majors past 31; Nextcloud cannot skip majors). KEEP IN STEP with the catalog ranges.",
"matchPackageNames": ["gitea/gitea"],
"allowedVersions": "<2.0.0"
},
{ "matchPackageNames": ["nextcloud"], "allowedVersions": "<32.0.0" },
{ "matchPackageNames": ["lscr.io/linuxserver/qbittorrent"], "allowedVersions": "<6.0.0" },
{ "matchPackageNames": ["lscr.io/linuxserver/radarr"], "allowedVersions": "<6.0.0" },
{ "matchPackageNames": ["lscr.io/linuxserver/sonarr"], "allowedVersions": "<5.0.0" },
{ "matchPackageNames": ["semaphoreui/semaphore"], "allowedVersions": "<3.0.0" },
{
"description": "ADDED 2026-10-30. A CNPG Postgres MAJOR is a migration (new cluster + import), not an image bump; the dry run proposed 16.4 -> 18.6. Minors stay (and are blast-radius, above).",
"matchPackageNames": ["ghcr.io/cloudnative-pg/postgresql"],
"allowedVersions": "<17.0.0"
},
{
"description": "ADDED 2026-10-30. A HelmRelease semver RANGE is a policy ceiling, and Renovate does not respect it — it WIDENS it (dry run: external-dns `<1.22.0` -> `<1.24.0`, headlamp `<0.42.0` -> `<0.47.0`), and the default rule would then auto-merge that as a 'minor', silently letting Flux adopt every new minor. Raising a ceiling is deliberate per-component work: propose it, never auto-merge it.",
"matchManagers": ["flux"],
"matchCurrentValue": "/^[<>=~^]|\\.x$/",
"automerge": false,
"addLabels": ["needs-review", "range-widen"]
},
{
"description": "ADDED 2026-10-30. velero-plugin-for-aws is pinned to a RELEASE CANDIDATE (v1.14.3-rc.2) and Renovate proposed NOTHING for it, although v1.14.3 and v1.14.4 are stable on Docker Hub. Docker versioning treats everything after the first `-` as a compatibility SUFFIX, so only other `*-rc.2` tags qualify and the RC is a fossil forever. Semver versioning reads it as a prerelease, and a prerelease upgrades to the stable release.",
"matchPackageNames": ["velero/velero-plugin-for-aws"],
"versioning": "semver"
},
{
"description": "ADDED 2026-10-30. A python MINOR is a language-runtime release (the dry run proposed 3.13 -> 3.15 for the Keycloak reconcilers), and the gate does not execute those scripts. Review it; patches still auto-merge.",
"matchPackageNames": ["python"],
"matchUpdateTypes": ["minor"],
"automerge": false,
"addLabels": ["needs-review"]
},
{
"description": "ADDED 2026-10-30. MAJORS WAIT FOR A TICK ON THE DEPENDENCY DASHBOARD instead of opening a PR. They are never auto-merged anyway, and with prConcurrentLimit 5 the dry run's 10 majors would hold every PR slot open indefinitely, starving the patch/minor/digest PRs that can merge on green.",
"matchUpdateTypes": ["major"],
"dependencyDashboardApproval": true
}
],
// WHAT RENOVATE STILL CANNOT FIX, so nobody mistakes a working bot for full coverage. A
@@ -234,10 +332,12 @@
// lscr.io/linuxserver/jackett:latest, pihole/pihole (NO TAG AT ALL), quay.io/keycloak/keycloak
// (no tag), nextcloud:31-apache and docker:27-dind. Those must be pinned BY HAND to a concrete
// version before any automation can maintain them, and until then they drift on every pull with
// nothing recording what is running. Separately, the HelmRelease minor-locking ranges (9 of 12,
// e.g. `>=88.0.0 <89.0.0` on kube-prometheus-stack) cap Renovate at patches inside a line it
// has been told not to leave; raising those ceilings is deliberate per-component work and is
// NOT part of enabling the bot. Both lists are in decisions/2026-10-17-automated-upgrades.md.
// nothing recording what is running. CORRECTED 2026-10-30: nextcloud:31-apache and docker:27-dind
// ARE maintained (the first dry run proposed 35-apache and 29-dind; the suffix is kept).
// Separately, the HelmRelease minor-locking ranges (9 of 12, e.g. `>=88.0.0 <89.0.0` on
// kube-prometheus-stack): THIS FILE USED TO SAY they cap Renovate at patches. They do not —
// Renovate WIDENS the range (runs 353/354). The `range-widen` rule above keeps that a reviewed
// PR rather than an auto-merge. Both lists are in decisions/2026-10-17-automated-upgrades.md.
// platform/kyverno/tests/** ADDED 2026-10-17: those Kyverno policy fixtures pin `alpine:latest`
// and a `conformance-runner:latest`, which are test inputs whose whole point is to be a fixed
// shape, not software to keep current. `conformance/**` was already ignored for the same