docs(examples): release-branch teaches what its adopter settled on #25

Merged
mikluko merged 1 commit from mikluko/octant/example-ground-truth into main 2026-09-07 07:17:15 +00:00
mikluko commented 2026-09-07 07:06:18 +00:00 (Migrated from github.com)

The release-branch example taught a strategy the repository it was derived from had already abandoned. This makes it teach what that repository actually settled on.

The evidence

up2-monitoring adopted this example, and its collapsed release workflow landed 2026-09-05T16:34Z — 18 hours before the map describing the example was charted. What it runs:

the example taught the adopter runs
what a heading names 1.3.0-rc.2 — the candidate 1.3.0 — the release it heads for
what cuts a candidate a push to release/* a pull request into release/*
where the ordinal comes from nowhere; the human writes it the tags, +1
prerelease-entry off on the branch, error on the trunk error everywhere
what gates a publish nothing a workflow_call suite in needs:

One rule instead of two

The changelog names the release it is heading for, and never an attempt at one. The document states the destination; the machine numbers the attempts.

That single change collapses several things the example was arguing:

  • prerelease-entry stops being per-invocation. A candidate is a tag, never a heading, so it is error on all four.
  • The two-reasons paragraph disappears rather than being argued better. undated-entry fires only on the newest versioned entry, which is exactly "a release is in flight" — one reason, not two, and ci.yaml can relax it for every pull request without weakening anything.
  • The four invocations now differ in exactly one input, and a test holds them to that.

Publishing is a pull request

A release branch has no push trigger. A push arrives with nothing in front of it; a pull request does not. That is what gives the suite somewhere to stand:

  tag:
    needs: [changelog, suite]

Both cutting workflows carry paths: [CHANGELOG.md] — the same thesis applied to the trigger, so a change proposing no release starts no run. On a pull request that filter is a three-dot diff over the whole request (verified in the docs), so once a revision names the release every later revision still matches and the ordinal advances.

Naming

Files are named for what each run yields, not for what triggers it:

ci.yaml                 a verdict — nothing is cut
release-candidate.yaml  v1.3.0-rc.4
release.yaml            v1.3.0
publish.yaml            artifacts, and a release for a final tag
suite.yaml              nothing — the gate the others call

That follows published practice rather than invention. Across 14 projects surveyed — release-please, changesets, semantic-release, goreleaser, helm, flux2, prometheus, argo-cd, otel-collector, vite, svelte, bubbletea, cli/cli, grafana — not one names a workflow for its trigger, ci + release is the near-universal spine, publish splits off for tag-triggered artifact builds, and multi-step releases use release-* prefixes (grafana's create-release-tag.yml, otel's prepare-release.yml).

The old names were pull-request.yaml, main.yaml and release-branch.yaml: two triggers and one that named its logic wrongly, since it cut candidates rather than a release.

Also in here

Three [Unreleased] CHANGELOG bullets described the intermediate state and are replaced rather than kept — they never shipped, and leaving them would ship 1.2.0 with release notes contradicting themselves.

The duplicated ### Fixed section under [Unreleased], left by two parallel branches each adding one, is collapsed into a single section in Keep a Changelog order. Nothing catches that today: unknown-section checks a heading's vocabulary, not repetition or order.

Verified

Suite green at -count=1, vet and gofmt clean, all five workflows parse, the document reads valid=true, and the example test now covers four invocations instead of three — TestReleaseBranchInvocationsDifferInOneInput is new and pins the claim above.

The `release-branch` example taught a strategy the repository it was derived from had already abandoned. This makes it teach what that repository actually settled on. ## The evidence `up2-monitoring` adopted this example, and its collapsed release workflow landed `2026-09-05T16:34Z` — 18 hours before the map describing the example was charted. What it runs: | | the example taught | the adopter runs | |---|---|---| | what a heading names | `1.3.0-rc.2` — the candidate | `1.3.0` — the release it heads for | | what cuts a candidate | a push to `release/*` | a pull request into `release/*` | | where the ordinal comes from | nowhere; the human writes it | the tags, `+1` | | `prerelease-entry` | off on the branch, error on the trunk | error everywhere | | what gates a publish | nothing | a `workflow_call` suite in `needs:` | ## One rule instead of two **The changelog names the release it is heading for, and never an attempt at one.** The document states the destination; the machine numbers the attempts. That single change collapses several things the example was arguing: - `prerelease-entry` stops being per-invocation. A candidate is a tag, never a heading, so it is `error` on all four. - **The two-reasons paragraph disappears rather than being argued better.** `undated-entry` fires only on the newest *versioned* entry, which is exactly "a release is in flight" — one reason, not two, and `ci.yaml` can relax it for every pull request without weakening anything. - The four invocations now differ in **exactly one input**, and a test holds them to that. ## Publishing is a pull request A release branch has **no push trigger**. A push arrives with nothing in front of it; a pull request does not. That is what gives the suite somewhere to stand: ```yaml tag: needs: [changelog, suite] ``` Both cutting workflows carry `paths: [CHANGELOG.md]` — the same thesis applied to the trigger, so a change proposing no release starts no run. On a pull request that filter is a **three-dot diff over the whole request** ([verified in the docs](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax)), so once a revision names the release every later revision still matches and the ordinal advances. ## Naming Files are named for what each run *yields*, not for what triggers it: ``` ci.yaml a verdict — nothing is cut release-candidate.yaml v1.3.0-rc.4 release.yaml v1.3.0 publish.yaml artifacts, and a release for a final tag suite.yaml nothing — the gate the others call ``` That follows published practice rather than invention. Across 14 projects surveyed — release-please, changesets, semantic-release, goreleaser, helm, flux2, prometheus, argo-cd, otel-collector, vite, svelte, bubbletea, cli/cli, grafana — **not one names a workflow for its trigger**, `ci` + `release` is the near-universal spine, `publish` splits off for tag-triggered artifact builds, and multi-step releases use `release-*` prefixes (grafana's `create-release-tag.yml`, otel's `prepare-release.yml`). The old names were `pull-request.yaml`, `main.yaml` and `release-branch.yaml`: two triggers and one that named its logic wrongly, since it cut candidates rather than a release. ## Also in here Three `[Unreleased]` CHANGELOG bullets described the intermediate state and are **replaced rather than kept** — they never shipped, and leaving them would ship 1.2.0 with release notes contradicting themselves. The **duplicated `### Fixed` section** under `[Unreleased]`, left by two parallel branches each adding one, is collapsed into a single section in Keep a Changelog order. Nothing catches that today: `unknown-section` checks a heading's vocabulary, not repetition or order. ## Verified Suite green at `-count=1`, `vet` and `gofmt` clean, all five workflows parse, the document reads `valid=true`, and the example test now covers four invocations instead of three — `TestReleaseBranchInvocationsDifferInOneInput` is new and pins the claim above.
Sign in to join this conversation.
No description provided.