Browse Source

Automerge patch bumps of components whose role has a Molecule scenario

A component bump touches that role's defaults/main.yml, which is on the Molecule
workflow's path filter, so the change-detection job runs that role's scenario.
The bump therefore only merges once the new version has actually been started on
the configuration the role renders. That is a real gate, and a stronger one than
a human reading a version number in a diff.

Patch only. A minor carries behaviour changes that no static rule can judge - the
MASH fleet tried a minor-automerge preset across 21 stateless roles and reverted
all of them, because reading a given release's notes is what decides it, and that
is a job for a human or an agent rather than a config file.

Branch push rather than a PR, as with the other automerge rules here.

The list has to stay in step with the roles that actually have a scenario, so
bin/check-molecule-automerge-list.py enforces it from prek. The direction that
matters is a role keeping automerge after losing its scenario: bumps would then
merge with nothing exercising them. It reports the harmless direction too, since
a role gaining a scenario without being listed is usually an oversight.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SEH3vxYSQ5SV4N5z61eyGT
create-pull-request/i18n
Slavi Pantaleev 12 hours ago
parent
commit
2d258cf0e0
4 changed files with 97 additions and 0 deletions
  1. +18
    -0
      .github/renovate.json
  2. +6
    -0
      .pre-commit-config.yaml
  3. +70
    -0
      bin/check-molecule-automerge-list.py
  4. +3
    -0
      bin/check-molecule-automerge-list.py.license

+ 18
- 0
.github/renovate.json View File

@@ -79,6 +79,24 @@
],
"automerge": true,
"automergeType": "branch"
},
{
"description": "Automerge patch bumps of components whose role has a Molecule scenario (via branch push - no PR). A bump touches that role's defaults/main.yml, which makes the Molecule workflow run that role's scenario, so the update only merges once the component has been started on the configuration the role renders. Patch only: a minor carries behaviour changes no static rule can judge, and reading the release notes to decide is a job for a human or an agent. Keep this list in step with the roles that have a scenario - bin/check-molecule-automerge-list.py enforces that.",
"matchFileNames": [
"roles/custom/matrix-alertmanager-receiver/defaults/main.yml",
"roles/custom/matrix-bot-baibot/defaults/main.yml",
"roles/custom/matrix-bot-matrix-reminder-bot/defaults/main.yml",
"roles/custom/matrix-bridge-heisenbridge/defaults/main.yml",
"roles/custom/matrix-bridge-hookshot/defaults/main.yml",
"roles/custom/matrix-bridge-mautrix-discord/defaults/main.yml",
"roles/custom/matrix-bridge-mautrix-meta-messenger/defaults/main.yml",
"roles/custom/matrix-bridge-mautrix-whatsapp/defaults/main.yml"
],
"matchUpdateTypes": [
"patch"
],
"automerge": true,
"automergeType": "branch"
}
],
"pre-commit": {


+ 6
- 0
.pre-commit-config.yaml View File

@@ -38,3 +38,9 @@ repos:
language: script
files: '(examples/vars\.yml|roles/custom/matrix_playbook_migration/defaults/main\.yml)'
pass_filenames: false
- id: check-molecule-automerge-list
name: Check the Molecule automerge list matches the roles that have a scenario
entry: bin/check-molecule-automerge-list.py
language: script
files: '(\.github/renovate\.json|roles/custom/[^/]+/molecule/default/molecule\.yml)'
pass_filenames: false

+ 70
- 0
bin/check-molecule-automerge-list.py View File

@@ -0,0 +1,70 @@
#!/usr/bin/env python3
"""Keeps the Molecule patch-automerge rule in step with the roles that have a scenario.

.github/renovate.json automerges patch bumps for roles listed by file name, on the grounds that
the bump runs that role's Molecule scenario before merging. That reasoning only holds while the
role actually has one.

The dangerous direction is a role keeping automerge after losing its scenario: bumps would then
merge with nothing exercising them. The harmless direction - a role gaining a scenario without
being added - only means a missed opportunity, but it is reported too, since it is usually an
oversight rather than a decision.
"""

import json
import pathlib
import sys

REPO = pathlib.Path(__file__).resolve().parent.parent
RENOVATE = REPO / ".github" / "renovate.json"
MARKER = "bin/check-molecule-automerge-list.py"


def main() -> int:
with_scenario = {
p.parts[-4] for p in (REPO / "roles" / "custom").glob("*/molecule/default/molecule.yml")
}

config = json.loads(RENOVATE.read_text())
rules = [r for r in config.get("packageRules", []) if MARKER in r.get("description", "")]

if len(rules) != 1:
print(f"Expected exactly one rule mentioning {MARKER}, found {len(rules)}.", file=sys.stderr)
return 1

listed = set()
for name in rules[0].get("matchFileNames", []):
parts = pathlib.PurePosixPath(name).parts
if parts[:2] == ("roles", "custom") and parts[3:] == ("defaults", "main.yml"):
listed.add(parts[2])
else:
print(f"Unexpected entry in the automerge rule: {name}", file=sys.stderr)
return 1

automerged_without_scenario = sorted(listed - with_scenario)
scenario_without_automerge = sorted(with_scenario - listed)

if automerged_without_scenario:
print(
"These roles automerge patch bumps but have no Molecule scenario, so nothing would\n"
"exercise the bump before it merges. Remove them from the rule in\n"
".github/renovate.json, or give them a scenario:",
file=sys.stderr,
)
for role in automerged_without_scenario:
print(f" {role}", file=sys.stderr)

if scenario_without_automerge:
print(
"These roles have a Molecule scenario but are not in the automerge rule in\n"
".github/renovate.json, so their patch bumps still need a button press:",
file=sys.stderr,
)
for role in scenario_without_automerge:
print(f" {role}", file=sys.stderr)

return 1 if (automerged_without_scenario or scenario_without_automerge) else 0


if __name__ == "__main__":
sys.exit(main())

+ 3
- 0
bin/check-molecule-automerge-list.py.license View File

@@ -0,0 +1,3 @@
SPDX-FileCopyrightText: 2026 Slavi Pantaleev

SPDX-License-Identifier: AGPL-3.0-or-later

Loading…
Cancel
Save