Skip to content

ADR-1128: Make changelog fragments own release cuts

  • Status: Accepted
  • Date: 2026-08-31
  • Deciders: Lusoris
  • Tags: release, changelog, automation, ci, docs

Context

VMAFx treats changelog.d/<section>/*.md and the legacy archive as the source of truth for the rendered CHANGELOG.md Unreleased block. At the first ordinary SemVer cut, 1,498 active sources render more than two thousand changelog lines. Release-please's independent changelog updater can create a version heading, but it neither consumes those sources nor understands the renderer contract. The next renderer run would therefore place already-released entries back under Unreleased.

A release cut must also reject a stale generated PR: all coordinated version markers must equal the requested version, the rendered block must be current, and no late fragment may remain unaccounted for.

The cutover audit also found 24 fragments under changelog.d/chore/ and three under changelog.d/refactor/. Those directories are outside the six Keep-a-Changelog sections advertised by the PR template, so the renderer warned and silently omitted their contents. Leaving them in place would make the first release receipt look complete while dropping 27 historical entries.

Decision

The fragment renderer will be the sole owner of CHANGELOG.md, and release-please will run with skip-changelog: true. Before a generated release PR merges, scripts/release/rollover-changelog-fragments.sh will atomically prepare its changelog cut: validate the exact ordinary SemVer, UTC date, root manifest and coordinated version markers; require a clean rendered Unreleased block with at least one active source; add one version heading; remove the consumed sources; and write a content-hash receipt under changelog.d/releases/. A second identical invocation is a no-op. CI runs both release-script regression harnesses. Before that first cut, the 27 legacy chore and refactor fragments move verbatim into changed; their filenames retain the original section prefix so provenance remains visible in Git and in the rendered notes.

Alternatives considered

Option Pros Cons Why not chosen
Let release-please update CHANGELOG.md Built into the release action Leaves 1,498 canonical sources active, so the renderer republishes old entries Violates the existing fragment ownership contract
Keep released fragments and add an exclusion manifest Preserves every source file at the tip Adds permanent dual state and makes every render depend on a growing exclusion list Git history and a compact receipt already preserve provenance
Keep warning and omit noncanonical sections No migration diff Drops 27 existing entries from the release while still claiming all active sources were consumed A release receipt must account for every discovered fragment
Delete fragments manually after publication No new script The published release can already contain stale or duplicated notes; the operation is not reproducible Validation must happen before merge and tag creation
Deterministic pre-merge rollover One source of truth, fail-closed checks, reproducible receipt The release PR gains one operator finalization step and a large deletion Chosen; the deletion is reviewable and recoverable from Git

Consequences

  • Positive: every release contains the exact body reviewers saw under Unreleased, the first cut includes all 27 previously skipped entries, and a late merge makes the generated release PR fail closed.
  • Negative: the first rollover deletes a large active fragment set from the branch tip, although every source remains recoverable from Git history.
  • Neutral / follow-ups: the release operator runs the rollover only after release-please has updated every coordinated version marker; receipts are not active fragments and are ignored by the renderer.

References