Skip to content

Push the release wave to dependent repos (event-driven) instead of waiting to be polled - #2691

Merged
rbuergi merged 1 commit into
mainfrom
feat/event-driven-release-fanout
Aug 29, 2026
Merged

rbuergi merged 1 commit into
mainfrom
feat/event-driven-release-fanout

Conversation

@rbuergi

@rbuergi rbuergi commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Makes the release wave event-driven — pushed to every dependent repo the moment the platform releases, instead of each repo noticing at its next schedule poll.

Why this is not "another poll optimisation"

A plugin repo compiles against a pinned platform. When the platform releases — images from main-cd, NuGet packages from a v* tag — every plugin repo must rebuild and republish, or its portals read FrameworkDeclined (built against <old>, live <new>) and adopt nothing. Until now that wave only travelled by pull.

The two objections in main-cd.yml, answered

That file states a dispatch job here "never will be" added. Both reasons were true when written:

Objection Status
"no credential in the release path" DEPENDENT_DISPATCH_APP_ID / _PRIVATE_KEY were provisioned 2026-08-19 and, until this PR, referenced by nothing in this repo. grep -rn DEPENDENT_DISPATCH .github/ returned zero hits.
"no hand-maintained list HERE" There is no list. Subscribers are the repos the App is installed on, read at run time from GET /installation/repositories. Adding a repo to the wave is installing the App on it.

This does not replace memex's broadcast; the two are independent paths to the same event. The reason for a second is that a wave depending on one hop through a mesh stops whenever that hop is unreachable — and that is not observable from here. The schedule poll stays as the fallback, so a lost dispatch costs one delayed wave rather than a missed one.

Failure modes this refuses to repeat

It fails RED when it notifies nobody. Its predecessor printed NOT CONFIGURED and went green for its entire life — which is exactly why nothing was ever notified and nobody noticed. A fan-out reaching zero repositories is indistinguishable from one that never ran.

Per-repo failures are collected, not fatal on the first. A wave that stops at the first unreachable repo leaves the rest of the fleet stale, with the partial state invisible.

An empty version is refused. needs.<skipped-job>.outputs.* is the empty string, not an error, and a CD run already died on precisely that (#2496). Relatedly, release-packages.yml wrote its version only to $GITHUB_ENV, which a dependent job cannot read — it now writes $GITHUB_OUTPUT too, since that silent "" is the same defect one layer down.

Verified

Receivers already exist and agree on the event name — types: [meshweaver-framework-released] in MeshWeaver.Plugins, SocialMedia, Reinsurance, Education and Crm. MeshWeaver.Manufacturing has no repository_dispatch receiver at all and will need one added separately to be part of "every repo".

Both workflows parse (yaml.safe_load).

⚠️ This changes the delivery pipeline's behaviour, so it is a maintainer call to merge, not something I should land on my own judgement. Two things also need checking before it can work end to end: that the App is installed on every plugin repo, and that its installation token may write repository_dispatch there.

Companion work: the ordered rebuild that each notified repo should then perform is Systemorph/MeshWeaver.Plugins#925 — a package may not rebuild until every package it requires has republished at the new version.

A plugin repo is built against a PINNED platform, so when the platform releases
— images here, NuGet packages on a tag — every plugin repo must rebuild and
republish or its portals read `FrameworkDeclined (built against <old>, live
<new>)` and adopt nothing.

That wave was only ever PULLED. Each repo's `schedule` noticed the release at its
next poll, up to a full interval late, and this file stated that a GitHub→GitHub
dispatch "never will be" added, for two reasons:

  * no credential in the release path
  * no subscriber list that would not drift

Both are answered now.

`DEPENDENT_DISPATCH_APP_ID` / `_PRIVATE_KEY` were provisioned on 2026-08-19 and,
until this commit, were referenced by NOTHING in this repository — a credential
minted for exactly this and never wired.

And there is no list. The subscribers are the repositories the App is INSTALLED
on, read at run time from `GET /installation/repositories`. Adding a repo to the
wave is installing the App on it; nothing in this file can go stale, which was
the specific objection.

This does not replace memex's broadcast — the two are independent paths to the
same event. The reason for a second one is that a wave depending on a single hop
through a mesh stops whenever that hop is unreachable, and that is not observable
from here.

🚨 It FAILS RED when it notifies nobody. Its predecessor printed "NOT CONFIGURED"
and went green for its entire life, which is why nothing was ever notified and
nobody noticed. A fan-out reaching zero repositories is indistinguishable from
one that never ran, so it is an error. Per-repo failures are COLLECTED rather
than fatal on the first, because a wave that stops at the first unreachable repo
leaves the rest of the fleet stale with the partial state invisible.

An empty `inputs.version` is refused for the same reason: `needs.<skipped>.outputs.*`
is the empty string rather than an error, and a CD run already died that way
(#2496). release-packages.yml's version now goes to $GITHUB_OUTPUT as well as
$GITHUB_ENV — the latter is invisible to a dependent job, which is how a
downstream job silently receives "".

The `schedule` poll in each repo stays as the fallback, so a lost dispatch costs
one delayed wave rather than a missed one. What changes is that the normal case
is seconds instead of an interval.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 29, 2026 18:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR makes the “release wave” to dependent repositories event-driven by adding a reusable repository_dispatch fan-out workflow and invoking it from both the platform CD pipeline and the NuGet release workflow, so dependent repos rebuild immediately rather than waiting for scheduled polls.

Changes:

  • Expose the release version from release-packages.yml as a job output (in addition to $GITHUB_ENV) so downstream jobs can consume it safely.
  • Add a reusable workflow (notify-dependents.yml) that mints a GitHub App installation token, discovers subscriber repos via /installation/repositories, and dispatches meshweaver-framework-released to each, failing if no subscribers or any dispatch fails.
  • Invoke the reusable fan-out workflow from main-cd.yml (platform/images) and release-packages.yml (NuGet/packages).

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
.github/workflows/release-packages.yml Publishes a version job output and triggers dependent-repo notifications after a successful package release.
.github/workflows/notify-dependents.yml New reusable workflow that discovers subscriber repos from the App installation and dispatches release events with version + reason.
.github/workflows/main-cd.yml Adds a post-promote job to push the platform release event to dependent repos via the reusable workflow.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 3)

689 tests  ±0   497 ✅ ±0   4m 43s ⏱️ -1s
  5 suites ±0   192 💤 ±0 
  5 files   ±0     0 ❌ ±0 

Results for commit 9bba6db. ± Comparison against base commit ba8c883.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 2)

941 tests  ±0   941 ✅ ±0   5m 2s ⏱️ -5s
  6 suites ±0     0 💤 ±0 
  6 files   ±0     0 ❌ ±0 

Results for commit 9bba6db. ± Comparison against base commit ba8c883.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 0)

913 tests  ±0   913 ✅ ±0   5m 28s ⏱️ +3s
  6 suites ±0     0 💤 ±0 
  6 files   ±0     0 ❌ ±0 

Results for commit 9bba6db. ± Comparison against base commit ba8c883.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 5)

1 528 tests  ±0   1 528 ✅ ±0   6m 29s ⏱️ +28s
    8 suites ±0       0 💤 ±0 
    8 files   ±0       0 ❌ ±0 

Results for commit 9bba6db. ± Comparison against base commit ba8c883.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 4)

   10 files  ±0     10 suites  ±0   5m 59s ⏱️ +30s
3 452 tests ±0  3 452 ✅ ±0  0 💤 ±0  0 ❌ ±0 
4 086 runs  ±0  4 086 ✅ ±0  0 💤 ±0  0 ❌ ±0 

Results for commit 9bba6db. ± Comparison against base commit ba8c883.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 1)

788 tests  ±0   784 ✅ ±0   7m 36s ⏱️ +9s
  6 suites ±0     4 💤 ±0 
  6 files   ±0     0 ❌ ±0 

Results for commit 9bba6db. ± Comparison against base commit ba8c883.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results

   41 files  ±0     41 suites  ±0   35m 19s ⏱️ + 1m 6s
8 311 tests ±0  8 115 ✅ ±0  196 💤 ±0  0 ❌ ±0 
8 945 runs  ±0  8 749 ✅ ±0  196 💤 ±0  0 ❌ ±0 

Results for commit 9bba6db. ± Comparison against base commit ba8c883.

@rbuergi
rbuergi merged commit ae4aaea into main Aug 29, 2026
25 checks passed
@rbuergi
rbuergi deleted the feat/event-driven-release-fanout branch October 10, 2026 13:10
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.

2 participants