Skip to content

feat(fleet): re-run every app's gates weekly, so drift can turn a badge red - #507

Open
rubenvdlinde wants to merge 2 commits into
mainfrom
feat/fleet-drift-sweep
Open

feat(fleet): re-run every app's gates weekly, so drift can turn a badge red#507
rubenvdlinde wants to merge 2 commits into
mainfrom
feat/fleet-drift-sweep

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

Answering a direct question: no — the Friday merge routine would not have caught this, and nothing else would either.

A green badge is a verdict about a moment, not a state. The gates are consumed at @main, so a stricter gate reaches every app the instant it merges here — but a gate only produces a verdict when a workflow runs, and app workflows trigger on push and pull_request only. An app nobody has touched keeps the green badge it earned under the old gates. Tighten a gate and the fleet is non-compliant and green at the same time, until somebody happens to push.

Measured 2026-08-19: of 21 swept apps, exactly one (pipelinq) had any scheduled run at all. For a fleet that intends to be managed through ADRs and gates, that is the load-bearing gap — the gates can be changed centrally and the change reaches nothing.

Why it is central, which is the part that is easy to get wrong

schedule: cannot choose a branch. GitHub runs scheduled workflows from the repo's default branch with no ref input. The fleet's defaults are split almost evenly — 11 default to development, 10 to main:

docudesk, launchpad, nldesign, opencatalogi, openconnector, openregister, procest, softwarecatalog, zaakafhandelapp, nextcloud-app-template

So copying pipelinq's cron everywhere would measure development on eleven and main on ten — and main is stale-red across much of the fleet, so those ten would go red for reasons unrelated to drift. The comment in pipelinq's own workflow ("a scheduled run always uses the default branch's ref (development)") is true for pipelinq and becomes false the moment it is copied.

workflow_dispatch does take a ref, so the sweep dispatches centrally with --ref development stated explicitly.

What it refuses to do silently

  • Asserts FLEET_DISPATCH_TOKEN before dispatching. Without a cross-repo token every call would be refused and the job would report a green sweep that measured nothing — the exact failure this exists to stop. GITHUB_TOKEN is repository-scoped and cannot.
  • Counts dispatches and fails when short, naming the apps it could not reach. A partial sweep exiting 0 reads as "the fleet was re-measured".
  • Refuses an empty app list, because a sweep over nothing also exits 0.

It grades nothing — each app's Quality Report decides. Its only job is to ensure a verdict exists that was computed against today's gates.

Not yet live

The token has to be provisioned: an org-level FLEET_DISPATCH_TOKEN with actions: write on every repo in fleet-apps.json. The workflow fails loudly until it is, which is the honest state to ship in.

Conduction Release Bot added 2 commits August 19, 2026 07:25
…ge red

ANSWERING A DIRECT QUESTION: no, the Friday merge routine would not have
caught this, and nothing else would either.

A GREEN BADGE IS A VERDICT ABOUT A MOMENT, NOT A STATE. The gates are
consumed at `@main`, so a stricter gate reaches every app the instant it
merges here — but a gate only produces a verdict when a workflow RUNS, and
app workflows trigger on `push` and `pull_request` only. An app nobody has
touched keeps the green badge it earned under the OLD gates. Tighten a gate
and the fleet is non-compliant and green simultaneously, until somebody
happens to push. The badge is not lying about the past; it is being read as
a claim about the present.

Measured 2026-08-19: of 21 swept apps, exactly ONE (pipelinq) had any
scheduled run at all. Twenty could only be re-measured by hand. For a fleet
that intends to be managed through ADRs and gates, that is the load-bearing
gap — the gates can be changed centrally and the change reaches nothing.

WHY IT IS CENTRAL, WHICH IS THE PART THAT IS EASY TO GET WRONG

`schedule:` cannot choose a branch. GitHub runs scheduled workflows from the
repository's DEFAULT branch and offers no ref input. The fleet's defaults are
split almost evenly — measured today, 11 apps default to `development` and 10
to `main`:

  docudesk, launchpad, nldesign, opencatalogi, openconnector, openregister,
  procest, softwarecatalog, zaakafhandelapp, nextcloud-app-template

Copying pipelinq's cron into every app would therefore measure `development`
on eleven and `main` on ten. `main` is stale-red across much of the fleet, so
those ten would go red for reasons that have nothing to do with drift. The
comment in pipelinq's own workflow — "a scheduled run always uses the default
branch's ref (development)" — is true FOR PIPELINQ and becomes false the
moment it is copied.

`workflow_dispatch` does take a ref, so the sweep dispatches centrally with
`--ref development` stated explicitly for every app.

WHAT IT REFUSES TO DO SILENTLY

  - It asserts FLEET_DISPATCH_TOKEN before dispatching anything. Without a
    cross-repo token every call would be refused and the job would report a
    green sweep that measured nothing — the exact failure this exists to stop.
    GITHUB_TOKEN cannot do this; it is repository-scoped.
  - It COUNTS dispatches and fails when the count is short, naming the apps it
    could not reach. A partial sweep that exits 0 tells the reader the fleet
    was re-measured, and the apps that were not are indistinguishable from the
    ones that passed.
  - It refuses an empty app list, because a sweep over nothing also exits 0.

It grades nothing. Each app's own Quality Report decides; the sweep only
ensures a verdict exists that was computed against TODAY's gates.

fleet-apps.json is the machine-readable scope of record beside the human
app-health page. `planix` is listed as deprecated rather than omitted, so the
decision is visible — a name quietly absent is indistinguishable from a name
never added, which is how hrmq sat red for six days unseen.

NOT YET LIVE: the token has to be provisioned. The workflow fails loudly
until it is, which is the honest state to ship in.
The workflow dispatched every app's Code Quality and stopped there, on the
stated reasoning that "each app's own Quality Report decides, and the badges on
the app-health page show the outcome".

Measured 2026-08-19, that is false. Every badge on the app-health page carries
`&event=push`, and a sweep produces `workflow_dispatch` runs — shields.io does
not look at them at all, so the badge keeps showing the last PUSH verdict. An
app could fail every gate in the Friday sweep and the fleet page would stay
green: the exact silent failure this workflow was written to prevent, rebuilt
one layer down.

Positive control on pipelinq/development, so the filter is shown to be live
rather than assumed:

    ?branch=development&event=push  -> newest: push, success
    ?branch=development             -> newest: pull_request, FAILURE

Dropping the filter is not the fix — unfiltered, the newest run on a branch is
often a pull_request run of a merge ref, a verdict about a proposal rather than
about the branch.

So a `collect` job waits for the runs this sweep started and fails when any app
is not `success`, with a per-app table in the run summary. It identifies "this
sweep's run" by a timestamp stamped before the first dispatch, because
`gh workflow run` returns no run id and "the newest dispatch run" would happily
adopt one from last week. `cancelled`, `no run appeared` and `still running at
deadline` are each reported red — an app that produced no verdict is never
subtracted as though it passed.

A second badge column on `event=workflow_dispatch` is viable (an unswept app
renders grey `no status`, not a false red — verified against
nextcloud-app-template) but is deliberately deferred: every app already carries
unrelated MANUAL dispatch runs, openregister's newest being 2026-08-11 and
failed, so the column would open showing week-old verdicts as this Friday's.
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