ADR 013: Release tasks may move to event-driven dispatch
Status
Proposed (2026-09-02). Not adopted — this records a direction for moving release follow-on tasks from in-band coupling (ADR 012) to event-driven dispatch. If adopted, it supersedes ADR 012, whose Status then reads "Superseded by ADR-013."
Context
ADR 012 couples follow-on work to the
releasing workflows because GitHub doesn't start new runs from events created
by GITHUB_TOKEN. In-band coupling is correct today, but it has real costs:
- The releasing workflows name every consumer (
attach-sbom,attach-sign,verify-release), so a new consumer is a change to the production release path, and the fan-out list grows with each concern. - A skipped dependency or a subtly wrong
ifgate drops coverage silently. - Logically separate concerns share one run's history, retry, and concurrency semantics.
Event-driven dispatch is attractive because each concern becomes its own
workflow with an explicit contract: consumers subscribe and receive a durable
event rather than being invoked by the producer. GitHub offers one event type
that's exempt from the GITHUB_TOKEN suppression and can carry arbitrary
data: repository_dispatch. workflow_dispatch is the other exemption but
can't carry a custom payload.
Options for true event delivery:
repository_dispatchevent bus — the releasing workflow POSTs arelease-createddispatch withclient_payload(tag,sha) after the release-please step; every consumer subscribes independently. Fire-and- forget: a lost dispatch is silent, so the bus needs a reconciliation sweep (see Decision) to become at-least-once. Payload must be versioned.- GitHub App installation token — events created with an app token start
new runs, restoring native triggers such as
release: publishedandpush tags v*. Adds an org-installed GitHub App, key storage and rotation, permitting scope, and a write-credential security review. This is the "real principal" end state, but it's a heavy operational lift. workflow_runtrigger — keys off a dedicated "emit release events" workflow run rather than a repository event. The consumer receives no inputs and must resolve state from run metadata or artifacts, and the written security guidance prefersworkflow_callchains overworkflow_run. Kept out of scope.- State-file polling — a scheduled sweep diffs the release manifest against created tags and attached assets. Fully decoupled, slow to react, and failure-agnostic on its own; valuable as the reconciliation layer for the dispatch bus rather than as the primary mechanism.
Ordering constraint
SDK releases must ship SBOM before checksums: SHA256SUMS must cover the SBOM.
Event delivery is unordered across consumers, so any event-driven design must
either keep order-sensitive steps in-band (call sbom.yml before
sign-release.yml as today), encode ordering in the payload (a phase that
consumers honor), or have signing wait until the SBOM asset exists and retry.
Decision
Don't adopt yet. If adopted, pursue this phased path:
- Phase 1 — add a dispatch emitter alongside the in-band calls. The
releasing workflows POST
repository_dispatchevents of typerelease-createdwith a versioned payload (v1,tag,sha), with a small retry-and-jitter loop. In-band attachment continues unchanged. - Phase 2 — move stateless consumers to the bus.
release-verify.yml(and any future consumer that doesn't affect artifact ordering) subscribes torelease-createdinstead of being called in-band. SBOM and signing stay in-band because their ordering matters. - Phase 3 — reconciliation sweep. A scheduled workflow diffs tags against attached assets and re-dispatches anything missing, converting the bus from fire-and-forget to at-least-once with observable drift. Only when this sweep has proven itself in production would the in-band calls become redundant.
- Phase 4 (optional) — GitHub App for native triggers. If a maintainer
stands up a GitHub App, the emitter switches from
repository_dispatchto the app token, native triggers return, and the event bus disappears. This substitutes an operational credential for pipeline complexity; don't do it without an explicit cost decision.
Consequences
- Pros: consumers decouple from the release path — adding one doesn't touch the production release workflow; each concern reverts to its own workflow, history, and retry semantics; the design matches the mental model of subscriptions to durable events; reconciliation makes the pipeline fail loud instead of silent.
- Cons: the event bus and the reconciliation sweep are new pipeline to build and maintain; ordering between SBOM and signing must survive nondeterministic delivery, so the order-sensitive steps stay in-band for now; the phased approach intentionally keeps two mechanisms (in-band calls and dispatches) alive in parallel during the transition; the GitHub App option trades an operational credential for the least pipeline surface, and only a maintainer decision can settle that.
- Rule for future work: until both ordering and at-least-once delivery are
guaranteed, keep SBOM and signing in-band (
attach-sbombeforeattach-sign) and treat any event-driven consumer as best-effort overlays with their own reconciliation. Never make the event bus the only mechanism for a step that must not be missed.