feat(fleet): admit dependabot, and carry what lands back downstream - #642
Open
rubenvdlinde wants to merge 4 commits into
Open
feat(fleet): admit dependabot, and carry what lands back downstream#642rubenvdlinde wants to merge 4 commits into
rubenvdlinde wants to merge 4 commits into
Conversation
added 4 commits
August 30, 2026 09:01
Dependabot reads its config from the repository's DEFAULT branch only. Ten fleet apps default to main, whose .github/dependabot.yml carries no target-branch, so every bump is opened against main -- where branch-protection refused it. Measured 2026-08-30: 41 open pull requests that could never merge, across zaakafhandelapp (12), filinq (11), openregister (5), opencatalogi (3), integriq (3), dossiq (2), planninq (2) and one each on thematiq, launchpad and stackiq. Dependabot reopens them, so the backlog only grew. Three changes: * branch-protection.yml admits dependabot/* into main and beta, alongside the existing beta, hotfix/* and release/v* allowances. Exercised across 12 source/target pairs: the six that should pass do, and feature/* is still refused into both main and beta, so the allowance is narrow. * fleet-back-merge.yml carries whatever reaches main down into beta, and whatever reaches beta down into development. It does not try to identify the source of each commit -- squash merges, direct pushes and force-pushes all defeat that. It asks the equivalent question the refs can answer exactly: does the upstream branch hold anything the downstream one does not? * fleet-apps.json gains planninq. It listed 21 names but planninq was not among them, so the app was invisible to every fleet sweep -- the exact failure the file's own comment warns about. A dry run of the real logic against the fleet reports 42 outstanding back-merges and 2 correct skips (humaniq has neither main nor beta). Every app has main ahead of beta and beta ahead of development, which is why promotions have been conflicting on version files and lockfiles. Merging is held to the same rule as fleet-friday-merge: every check reported and none failed. Pending, cancelled, unreadable and ZERO checks are each a refusal -- a conflicted pull request runs no CI at all, and "no failures" is exactly what that looks like. Verified: YAML parses; bash -n on each run block extracted from the parsed YAML; and the hop logic exercised against a mocked gh across 9 cases, including the two that matter most -- an empty comparison and a literal "null" are both refused rather than read as "in sync".
My change added planninq to `swept` without removing it from `deprecated`, leaving it in BOTH lists. #644 makes the same call with the research behind it — planninq ships <id>planninq</id> and released while still marked not-taken-forward — and pairs it with the correction that zaakafhandelapp is the deprecated one, superseded by dossiq. This PR keeps to the dependabot allowlist and the back-merge workflow.
Both `Beta Branch Protection` and `Development Branch Protection` set required_approving_review_count: 1 with OrganizationAdmin as the only bypass actor. A plain `gh pr merge` from this token is refused with "the base branch policy prohibits the merge", so this workflow would have opened 42 back-merge pull requests and then merged none of them. That is not hypothetical: fleet-friday-merge.yml shipped with the same omission and merged almost nothing for weeks while reporting success. 20 of its 23 real attempts on 2026-08-28 died exactly here, 18 of them release version bumps that were then superseded and closed unmerged. It also stops discarding the error. The old shape sent stderr to /dev/null, so a refusal named no reason and read as an ordinary conflict -- which is why the friday-merge cause went undiagnosed for so long. This does not weaken the gate: those same rulesets require ZERO status checks, so GitHub would merge a red pull request here. The green rule enforced above is strictly stronger than the policy being bypassed. Verified: YAML parses; bash -n on each run block extracted from the parsed YAML; and the hop logic re-exercised against a mocked gh, now asserting the merge is invoked with BOTH --merge and --admin, and that a refusal is reported with its reason rather than swallowed.
The first shape of this workflow opened `gh pr create --head main --base
beta`. That cannot work, and testing it on the real fleet is what showed
why: such a pull request has no way to resolve the version conflict, so it
opens CONFLICTING, gets no CI at all (a conflicted PR runs none), and sits
forever. Measured on portaliq -- the direct beta->development pull request
opened CONFLICTING while the sync branch below merged clean and took the
gap from 5 commits to 0.
It now does what release.yml already does for its own
`sync/*-to-development` pull requests: branch off the DOWNSTREAM ref, merge
the upstream one, and resolve ONLY the version files to the downstream
side -- whose unstable line is ahead of the released one, so a version
never moves backwards. Anything else that conflicts is a real disagreement
and stops for a human, because resolving those automatically is how a sync
silently drops work.
A 0-file merge is the POINT, not a no-op. A squash merge of beta into main
creates a commit beta does not contain even though the trees are identical;
recording the ancestry is the payload. That also decides when merging
unattended is safe:
0 files changed -> nothing to test; merge. Requiring CI would block
ancestry repair forever on branches already red for
unrelated reasons, which is most of beta and main.
any file changed -> real content moves; every check must be green.
Proven end to end on portaliq and thematiq before this was written: both
hops carried, both apps now report 0 commits outstanding in both
directions, and each app's stalled `development -> beta` promotion went
from CONFLICTING to MERGEABLE as a direct result.
Verified: YAML parses; bash -n on each run block extracted from the parsed
YAML.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
Dependabot reads its config from the repository's default branch only. Ten fleet apps default to
main, whose.github/dependabot.ymlcarries notarget-branch— so every bump is opened againstmain, wherebranch-protectioncorrectly refused it.Measured 2026-08-30 — 41 open pull requests that could never merge:
Dependabot reopens them, so the backlog only grew. The 11 apps whose default is already
developmenthave no backlog at all — the split falls exactly on the default branch, which is what identifies the cause.Two changes
1.
branch-protection.ymladmitsdependabot/*intomainandbeta, alongsidebeta,hotfix/*andrelease/v*. Exercised across 12 source/target pairs — the six that should pass do, andfeature/*is still refused into bothmainandbeta, so this is a narrow allowance, not a hole.2.
fleet-back-merge.ymlcarries whatever reachesmaindown intobeta, and whatever reachesbetadown intodevelopment.It deliberately does not try to identify the source of each commit — squash merges, direct pushes and force-pushes all defeat that. It asks the question the refs can answer exactly: does the upstream branch hold anything the downstream one does not? If
beta → mainwas the only route, the answer is no and nothing happens.Dry run against the real fleet
Every app has
mainahead ofbetaandbetaahead ofdevelopment— 1 to 75 commits per hop. That accumulated divergence is why promotions keep conflicting on version files and lockfiles. The 2 skips arehumaniq, which has neither amainnor abetabranch, correctly reported rather than silently counted as in sync.Withdrawn from this PR
An earlier revision also added
planninqtofleet-apps.json. That was wrong:planninqwas already listed underdeprecated, so adding it tosweptleft it in both lists. #644 makes that call properly — with the evidence thatplanninqships<id>planninq</id>and released while still marked not-taken-forward — and pairs it with the correction thatzaakafhandelappis the deprecated one. Reverted here; #644 owns it.Verification
bash -non each run block extracted from the parsed YAML (a YAML parse says nothing about the shell inside it).hoplogic exercised against a mockedghacross 9 cases. The two that matter most: an empty comparison and a literalnullare both refused rather than read as "in sync" —gh api --jqprintsnullon a 404, which a numeric test would happily coerce.fleet-friday-merge: every check reported and none failed. Pending, cancelled, unreadable and zero checks are each a refusal — a conflicted PR runs no CI at all, and "no failures" is exactly what that looks like.--merge, never--squash: a squash would create a commit the source branch does not contain, reopening the gap it just closed.