Skip to content

feat(fleet): admit dependabot, and carry what lands back downstream - #642

Open
rubenvdlinde wants to merge 4 commits into
mainfrom
feat/dependabot-allowlist-and-back-merge
Open

feat(fleet): admit dependabot, and carry what lands back downstream#642
rubenvdlinde wants to merge 4 commits into
mainfrom
feat/dependabot-allowlist-and-back-merge

Conversation

@rubenvdlinde

@rubenvdlinde rubenvdlinde commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

The problem

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 correctly refused it.

Measured 2026-08-30 — 41 open pull requests that could never merge:

App PRs App PRs
zaakafhandelapp 12 dossiq 2
filinq 11 planninq 2
openregister 5 thematiq 1
opencatalogi 3 launchpad 1
integriq 3 stackiq 1

Dependabot reopens them, so the backlog only grew. The 11 apps whose default is already development have no backlog at all — the split falls exactly on the default branch, which is what identifies the cause.

Two changes

1. branch-protection.yml admits dependabot/* into main and beta, alongside beta, hotfix/* and release/v*. Exercised across 12 source/target pairs — the six that should pass do, and feature/* is still refused into both main and beta, so this is a narrow allowance, not a hole.

2. fleet-back-merge.yml carries whatever reaches main down into beta, and whatever reaches beta down into development.

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 → main was the only route, the answer is no and nothing happens.

Dry run against the real fleet

TOTALS: opened=42 merged=0 in-sync=0 skipped=2

Every app has main ahead of beta and beta ahead of development — 1 to 75 commits per hop. That accumulated divergence is why promotions keep conflicting on version files and lockfiles. The 2 skips are humaniq, which has neither a main nor a beta branch, correctly reported rather than silently counted as in sync.

Note before merging: the first scheduled run will open ~42 back-merge PRs. That is the true size of the existing divergence, not noise the workflow creates — but it is a visible one-off. workflow_dispatch offers a dry run if you want to see it first.

Withdrawn from this PR

An earlier revision also added planninq to fleet-apps.json. That was wrong: planninq was already listed under deprecated, so adding it to swept left it in both lists. #644 makes that call properly — with the evidence that 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. Reverted here; #644 owns it.

Verification

  • YAML parses; bash -n on each run block extracted from the parsed YAML (a YAML parse says nothing about the shell inside it).
  • The hop logic exercised against a mocked gh across 9 cases. The two that matter most: an empty comparison and a literal null are both refused rather than read as "in sync" — gh api --jq prints null on a 404, which a numeric test would happily coerce.
  • 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 PR runs no CI at all, and "no failures" is exactly what that looks like.
  • Merges use --merge, never --squash: a squash would create a commit the source branch does not contain, reopening the gap it just closed.

Conduction Release Bot 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant