From 54d26fb20c968daac385bdc059419e5ad27c02bf Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Fri, 3 Jul 2026 07:36:14 -0700 Subject: [PATCH] Auto-merge every Dependabot tier, drop semver-major skip (#799) The merge-bot held semver-major NuGet bumps for human review via a version-update:semver-major guard fed by dependabot/fetch-metadata. Per the every-tier policy already in Utilities and LanguageTags, the required CI checks are the gate, not the version magnitude. - Remove the metadata step and the semver-major guard from the merge step so every in-repo Dependabot PR auto-merges on open once checks pass. - Update the workflow contract to match: WORKFLOW.md self-sufficiency prose, merge-bot diagram, automation prose, D8.2, D8/D9 summary, S12 trace row, and the AGENTS.md Dependabot invariant. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/merge-bot-pull-request.yml | 13 ++------- AGENTS.md | 2 +- WORKFLOW.md | 29 +++++++++----------- 3 files changed, 16 insertions(+), 28 deletions(-) diff --git a/.github/workflows/merge-bot-pull-request.yml b/.github/workflows/merge-bot-pull-request.yml index 163e78d3..f13dca38 100644 --- a/.github/workflows/merge-bot-pull-request.yml +++ b/.github/workflows/merge-bot-pull-request.yml @@ -23,7 +23,8 @@ jobs: merge-dependabot: name: Merge dependabot pull request job runs-on: ubuntu-latest - # Dependabot PRs from this repo (not forks). Only on opened/reopened so the disable job stays sticky. + # In-repo Dependabot PRs only, on opened/reopened so the disable job stays sticky. Every tier + # auto-merges, semver-major included: the required checks are the gate, not the version bump. if: >- (github.event.action == 'opened' || github.event.action == 'reopened') && github.event.pull_request.user.login == 'dependabot[bot]' && @@ -41,17 +42,7 @@ jobs: client-id: ${{ secrets.CODEGEN_APP_CLIENT_ID }} private-key: ${{ secrets.CODEGEN_APP_PRIVATE_KEY }} - - name: Get dependabot metadata step - id: metadata - uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0 - with: - github-token: "${{ secrets.GITHUB_TOKEN }}" - - # Skip semver-major NuGet bumps so they land via human review; other ecosystems' majors auto-merge. - name: Merge pull request step - if: >- - (steps.metadata.outputs.package-ecosystem != 'nuget') || - (steps.metadata.outputs.update-type != 'version-update:semver-major') run: | set -euo pipefail case "${{ github.event.pull_request.base.ref }}" in diff --git a/AGENTS.md b/AGENTS.md index f708f7d0..c0fbd3a6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,7 +26,7 @@ This file is the canonical reference for cross-cutting AI-agent rules. The CI/CD - **Both rulesets intentionally omit "Require branches to be up to date before merging" (`strict_required_status_checks_policy: false`), for two distinct reasons:** - *Main* - the check is graph-based; it asks whether main's tip commit is reachable from develop, not whether the two branches have the same content. After any develop -> main release, main's tip is a brand-new merge commit that develop's history doesn't contain. Forward-only develop never adds it (no back-merge of main into develop), so the check would fail on every subsequent release. - *Develop* - bot auto-merge incompatibility. When two bot PRs against develop land in the same minute (e.g. two grouped Dependabot PRs from the same daily run), the first to merge pushes the second into `mergeStateStatus: BEHIND`. GitHub's auto-merge will not fire while the strict flag is on, and nothing in the workflow set auto-updates a bot branch in that window - the merge-bot enables auto-merge via `gh pr merge --auto` but never rebases a stalled branch onto base (see [`merge-bot-pull-request.yml`](./.github/workflows/merge-bot-pull-request.yml)). Real file-level conflicts are still caught textually (`mergeable: CONFLICTING` blocks merge regardless); semantic-but-not-textual conflicts that combine cleanly are caught by the post-merge develop CI run rather than pre-merge. Do not reintroduce the strict flag on develop thinking it's hygiene - it breaks bot auto-merge. -- **Dependabot targets both `main` and `develop` in parallel.** [`.github/dependabot.yml`](./.github/dependabot.yml) duplicates every ecosystem entry (one per branch). Each branch absorbs its own bot PRs independently, so neither falls behind, and the forward-only rule still holds (nothing is back-merged from main to develop - both branches receive their updates directly). Parallel auto-merge across same-batch bot PRs is race-proof only because both rulesets have the strict "up to date" flag off (see bullet above). The merge-bot ([`.github/workflows/merge-bot-pull-request.yml`](./.github/workflows/merge-bot-pull-request.yml)) dispatches `--squash` or `--merge` from each PR's base ref via a `case` statement so the form matches the ruleset on either base. Dependabot **security** PRs (CVE-driven) always open against the repo default branch (`main`) regardless of `target-branch` - the same `case` statement covers them. Semver-major NuGet bumps gate on human review; everything else auto-merges. +- **Dependabot targets both `main` and `develop` in parallel.** [`.github/dependabot.yml`](./.github/dependabot.yml) duplicates every ecosystem entry (one per branch). Each branch absorbs its own bot PRs independently, so neither falls behind, and the forward-only rule still holds (nothing is back-merged from main to develop - both branches receive their updates directly). Parallel auto-merge across same-batch bot PRs is race-proof only because both rulesets have the strict "up to date" flag off (see bullet above). The merge-bot ([`.github/workflows/merge-bot-pull-request.yml`](./.github/workflows/merge-bot-pull-request.yml)) dispatches `--squash` or `--merge` from each PR's base ref via a `case` statement so the form matches the ruleset on either base. Dependabot **security** PRs (CVE-driven) always open against the repo default branch (`main`) regardless of `target-branch` - the same `case` statement covers them. Every tier auto-merges, semver-major included - the required checks are the gate, not the version bump. - **Maintainer-pushed commits on a bot PR auto-disable auto-merge.** The merge-bot's `merge-dependabot` job only fires on `opened` / `reopened` events (auto-merge is enabled exactly once per PR, for Dependabot-authored PRs that originate from this repository, not forks). When a maintainer pushes commits to the bot's branch (a `synchronize` event with a non-bot actor), the `disable-auto-merge-on-maintainer-push` job fires and calls `gh pr merge --disable-auto`; the maintainer's commits stay in the PR but won't auto-merge with the bot's content. Re-enable manually (`gh pr merge --auto `) when ready. The merge-bot is on `pull_request_target` with per-PR concurrency; it carries only `merge-dependabot` + `disable-auto-merge-on-maintainer-push`. - **App-token workflows use Client ID, not App ID.** `actions/create-github-app-token` deprecated the numeric `app-id` input in v3.0.0; the merge-bot uses `client-id: ${{ secrets.CODEGEN_APP_CLIENT_ID }}` (with `private-key: ${{ secrets.CODEGEN_APP_PRIVATE_KEY }}`). The App token - not `GITHUB_TOKEN` - is required so the merge push is committed by the App and fires downstream workflows (`GITHUB_TOKEN` pushes are blocked from triggering further runs by GitHub's recursion guard). When adding new App-token call sites, use the same form - do not reintroduce `app-id`. - **Why parallel dual-target rather than develop-only with eventual flow-through:** consumers pull the Docker image and the release executables from `main` directly. A develop-only model would leave `main` running stale code during long-running develop features, so both branches receive their own bot updates on their own cadence and each stays current. diff --git a/WORKFLOW.md b/WORKFLOW.md index e5155e6b..bf699fcb 100644 --- a/WORKFLOW.md +++ b/WORKFLOW.md @@ -166,10 +166,10 @@ run's artifact set is never blanket-deleted. ### Self-sufficiency: automatic updates -Every Dependabot pull request, any ecosystem and any tier, auto-merges once the required checks pass (the -checks are the safety net), except a **semver-major NuGet** bump, which waits for human review. A merged bump -does not itself publish - it ships in the next weekly publish. There is no codegen and no upstream-version -tracker. A person steps in only for a breaking change (a red check) or to dispatch a release. +Every Dependabot pull request, any ecosystem and any tier including **semver-major**, auto-merges once the +required checks pass - the checks are the gate, not the version bump. A merged bump does not itself publish - +it ships in the next weekly publish. There is no codegen and no upstream-version tracker. A person steps in +only for a breaking change (a red check) or to dispatch a release. ### Flow diagrams @@ -248,18 +248,15 @@ flowchart TD ``` **Automation - Dependabot + merge-bot.** Dependabot opens in-repo bot PRs; the merge-bot enables auto-merge -on open (squash on `develop`, merge on `main`), excepting semver-major NuGet, and disables it on a -maintainer push. There is no codegen here; a merged bump does not publish - it ships in the next scheduled -run (D8). +on open (squash on `develop`, merge on `main`) for every tier, and disables it on a maintainer push. There +is no codegen here; a merged bump does not publish - it ships in the next scheduled run (D8). ```mermaid flowchart TD DEP(["Dependabot opens PR
(in-repo branch, daily)"]):::trig --> MB subgraph MBT ["merge-bot-pull-request.yml (pull_request_target, key = PR number)"] MB{"event / author / in-repo?"}:::gate - MB -- "opened/reopened
dependabot[bot]" --> MAJ{"semver-major NuGet?"}:::gate - MAJ -- "yes" --> HUM(["no auto-merge
human review"]):::stop - MAJ -- "no" --> EN["enable auto-merge
--squash develop / --merge main
(App token, --delete-branch)"] + MB -- "opened/reopened
dependabot[bot]
(every tier)" --> EN["enable auto-merge
--squash develop / --merge main
(App token, --delete-branch)"] MB -- "synchronize by maintainer
on dependabot branch" --> DIS["disable auto-merge
(App token)"] end EN --> CK{"required check passes?"}:::gate @@ -397,10 +394,10 @@ Each is a **MUST**, stated as input -> output plus the failure it prevents. checking out its code. Enables auto-merge on `opened`/`reopened`; squash on `develop`, merge-commit on `main` by the PR's base ref; disables auto-merge when a maintainer pushes to a bot branch. Concurrency keyed on PR number. -- **D8.2 Dependabot auto-merges on green, semver-major NuGet excepted.** Output: every Dependabot PR - auto-merges once the required checks pass, except a semver-major NuGet bump (human review). A failing check - blocks the merge. A merged bump does **not** itself publish (dependencies are not shipped inputs); it ships - in the next weekly publish. +- **D8.2 Dependabot auto-merges on green, every tier.** Output: every Dependabot PR, any ecosystem and any + tier including semver-major, auto-merges once the required checks pass - the checks are the gate, not the + version bump. A failing check blocks the merge. A merged bump does **not** itself publish (dependencies are + not shipped inputs); it ships in the next weekly publish. - **D8.3 No codegen / upstream-version automation.** This repo has neither; the merge-bot carries only `merge-dependabot` + `disable-auto-merge-on-maintainer-push`. @@ -453,7 +450,7 @@ Read the workflow files plus `version.json` and assert the fact behind each appl - **D7:** the publisher group is ref-independent with `cancel-in-progress: false`; the merge-bot keys on PR number; CI uses the standard group; reusable jobs declare permissions. - **D8/D9:** the merge-bot runs on `pull_request_target` with the App token, keyed on PR number; Dependabot - auto-merge excepts semver-major NuGet only; no codegen/upstream-version, date-badge, tool-versions, + auto-merge covers every tier; no codegen/upstream-version, date-badge, tool-versions, docker-readme task, `PUBLISH_ON_MERGE`, or `dorny/paths-filter`; actions SHA-pinned; names/shells/ conditionals per section 2. @@ -472,7 +469,7 @@ Read the workflow files plus `version.json` and assert the fact behind each appl | S9 | merged GitHub-Actions bump | not a shipped input, merges don't publish -> **no release** | D4.1 | | S10 | PR with a CSharpier / format / markdown / spelling / workflow-YAML violation | the `lint` job fails -> aggregator blocks the merge | D1.3, D1.5 | | S11 | `version.json` floor bump merged | merges don't publish -> no immediate release; the new floor ships in the next weekly publish | D3.3, D4.1 | -| S12 | Dependabot semver-major NuGet bump | gated on human review -> does not auto-merge; other majors auto-merge on green | D8.2 | +| S12 | Dependabot semver-major bump (any ecosystem) | auto-merges on green like every other tier; the required checks are the gate | D8.2 | | S13 | `develop` -> `main` promotion (merge commit) | the merge itself does not publish; `main`'s accumulated changes ship in the next weekly run | D4.1, D8.1 | ### 5C. Live probe (where warranted, never publishing)