Skip to content

Require release notes to be generated from the changelog (R07), standard 1.7.0 - #32

Merged
trsdn merged 3 commits into
mainfrom
trsdn-changelog-release-criterion
Aug 31, 2026
Merged

trsdn merged 3 commits into
mainfrom
trsdn-changelog-release-criterion

Conversation

@trsdn

@trsdn trsdn commented Aug 31, 2026

Copy link
Copy Markdown
Owner

The gap

R06 asks that release notes describe meaningful changes and upgrade concerns,
and accepts "GitHub release or changelog" as evidence. That disjunction is
the hole.

trsdn/PtionsPlus demonstrated it concretely:

  • it keeps a Keep-a-Changelog CHANGELOG.md with an ## Unreleased section and
    one ## X.Y.Z - YYYY-MM-DD section per release;
  • its release workflow published every GitHub release with hardcoded boilerplate
    --notes "Signed, notarized, stapled, and verified macOS release artifacts." — and never read the changelog;
  • nothing failed when a tag had no changelog section, and nothing failed when
    entries were still sitting under ## Unreleased at tag time. Those entries
    shipped and then appeared in no release notes at all.

Assessed against R06 as written, that repository passed: a maintained
changelog existed, and it did describe meaningful changes. The criterion was
satisfied by a document nobody consumed, while the release page — the surface a
consumer actually lands on — said nothing.

What changed

New criterion R07

ID Requirement Expected evidence
R07 Release notes are generated from the changelog entry for the version being released, and automation fails the release when that entry is missing, empty, or still held in an unreleased section Release workflow gate plus a published release whose notes match its changelog entry

Prose under the Package And Release table states what the gate must reject, and
notes that a repository keeping no unreleased section satisfies the last
condition by construction, having nowhere to strand an entry.

R06 is deliberately left unchanged

Sharpening R06 was the obvious alternative and was rejected:

  • It buys nothing. Once R07 holds, boilerplate notes over an unread
    changelog cannot pass, because the notes are no longer written separately from
    the entry. The failure is closed by provenance, not by restating the content
    requirement more forcefully.
  • It is expensive. Narrowing a criterion so a recorded Pass could become a
    Fail is a major change under Versioning And Compatibility. Every recorded
    result in the estate would be invalidated and every repository would be due for
    reassessment — to reach an outcome R07 already reaches as a minor change.

The two stay orthogonal and each says something the other cannot. A changelog
entry reading "bug fixes" still fails R06, and R07 cannot catch it, because
such an entry is faithfully published. Recorded in
decision 0010.

Reference implementation

R07 mandates automation, so it has to say where the automation comes from —
otherwise every repository invents the gate again and the estate ends up with as
many gates as it has release workflows.

templates/release-notes/release.yml is
published as the reference gate and linked from the criterion's prose and the
README, the same way templates/AGENTS.md backs G01-G08 and
templates/repo-stats/ backs P09. It stays a starting point, not a mandate:
R07 asks for the outcome, and a repository reaching it with a shell script, a
release tool, or a different CI system passes just as well.

Its extraction accepts both the plain and the Keep-a-Changelog bracketed heading
form, escapes dots so a version is not read as a wildcard, and does not match an
adjacent version. Verified against six changelog shapes, including 1.2.30 while
releasing 1.2.3.

Version is 1.7.0, not 1.5.2

The request asked for 1.5.2. Two things make that wrong:

  1. Adding a criterion is a minor change under the standard's own versioning
    table, not a patch. A patch bump would tell consumers no new criterion exists
    and no reassessment is due, which is exactly false.
  2. 1.6.0 was published while this change was in progress (Correct the badge hosting rationale and make the image rule assessable #31), so the next
    free minor is 1.7.0. This branch was rebased onto it.

Confirmed with the maintainer before proceeding.

Ripple effects, all resolved in this PR

  • standard.yml regenerated with scripts/standard.py — 94 criteria (was 93).
  • .github/conformance.ymlstandard_version 1.7.0, R07: pass.
  • .github/badges/conformance.svg regenerated from the record.
  • docs/self-assessment.md — version bumped, R07 recorded under notable passes
    with its evidence.
  • CHANGELOG.md1.7.0 entry, matching the document version as the check
    requires.
  • docs/decisions/ — ADR 0010 added and indexed.
  • README.md — the new template listed alongside the existing two.

Dogfooding: this repository's own release gate

.github/workflows/release.yml already extracted the changelog section for the
tag, failed when it was empty, and published that text as the notes body — so
this repository already satisfied the first half of R07.

The second half was satisfied only by absence: there is no ## Unreleased
section, so nothing can be stranded. A gate that holds only while a convention
holds is not a gate, so an explicit "Reject entries left in an unreleased
section"
step was added. It is a no-op today and was tested against three
changelog shapes.

Validation

Every command from Validation passes:

Command Result
python3 scripts/standard.py --check 94 criteria, catalog in sync
python3 scripts/conformance.py --check Healthy (na=39, pass=55), badge in sync
python3 scripts/links.py internal links resolve across 27 Markdown files
python3 -m unittest discover -s tests 71 tests, OK
markdownlint-cli2@0.18.1 "**/*.md" 0 errors
ruff==0.14.5 check . / format --check . All checks passed, 25 files formatted

Both workflow files parse, and the new template link was mutation-tested to
confirm scripts/links.py actually catches it.

Follow-up in already-assessed repositories

trsdn/PtionsPlus is already fixed. The work that motivated this criterion
ran in parallel and merged the same day
(PtionsPlus#34, merged
2026-08-31): scripts/changelog.sh release-notes <version> extracts and verifies
the section, the gate runs in the metadata job and reads CHANGELOG.md from
the tag itself so workflow_dispatch on older tags is covered too, the publish
job uses --notes-file, and bump-version.sh promotes the Unreleased entries.
It will record R07: pass at its next reassessment, not fail.

Everything else with the trsdn-standard topic still needs R07 added.
scripts/conformance.py --check reports R07 is missing from the record once a
record is repointed at 1.7.0. This is the designed behaviour — a record stays
valid for the version it names, so nothing breaks until each repository is
reassessed — but the entry has to be added at that point.

These are deliberately not attempted here; this PR only changes the standard
and this repository.

Deliberately not changed

  • R06's wording and evidence column — see above.
  • No tag is created. v1.7.0 is a maintainer action after merge.
  • No other repository is touched.

Co-authored-by: Copilot App 223556219+Copilot@users.noreply.github.com

trsdn and others added 3 commits August 31, 2026 14:34
R06 accepts "GitHub release or changelog" as evidence, and that disjunction
is a hole. A repository in the estate keeps a Keep-a-Changelog file with an
Unreleased section and one dated section per release, while its release
workflow publishes every release with a hardcoded notes string describing
the artifacts rather than the change. Nothing reads the changelog, nothing
fails when a tag has no section, and nothing fails when entries are still
under Unreleased at tag time. Assessed against R06 as written, it passes:
the changelog exists and does describe meaningful changes, while the surface
consumers land on says nothing.

R07 closes it by provenance rather than by restating the content
requirement: the notes published for a tag are generated from the changelog
entry for exactly that version, enforced by a gate that fails when the entry
is missing, empty, or still held in an unreleased section.

R06 is left unchanged. Narrowing it would be a major change under the
versioning policy, invalidating every recorded result in the estate, to
reach an outcome R07 already reaches as a minor one. The two stay
orthogonal: an entry reading "bug fixes" fails R06 and cannot fail R07,
because it is faithfully published. Decision 0010 records this.

Version is 1.7.0, not 1.5.2 as originally requested: adding a criterion is
minor under Versioning And Compatibility, and 1.6.0 was published while this
change was in progress.

This repository already satisfies R07. The unreleased guard was added to its
release workflow in the same change, so the gate is complete rather than
complete by absence of an unreleased section.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
R07 mandates automation but pointed at no implementation, so every
repository adopting it would invent the gate again and the estate would end
up with as many gates as it has release workflows. The only working example
was this repository's own release.yml, which nobody reading the standard
would think to open.

templates/release-notes/release.yml is the reference gate, linked from the
criterion's prose and from the README, the same way templates/AGENTS.md
backs G01-G08 and templates/repo-stats/ backs P09.

It stays a starting point rather than a mandate: R07 asks for the outcome,
and a repository reaching it with a shell script, a release tool, or another
CI system passes just as well. PtionsPlus, which motivated the criterion,
extracts the same logic into scripts/changelog.sh and passes on its own
terms.

The extraction accepts both the plain and the Keep-a-Changelog bracketed
heading form, escapes dots so a version is not read as a wildcard, and does
not match an adjacent version. Verified against six changelog shapes,
including 1.2.30 while releasing 1.2.3.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
main published 1.6.1, a patch that closed an ambiguity in the badge image
rule. All six conflicts were version bookkeeping between that patch and this
branch's minor, not disagreements about content.

Resolved by keeping 1.7.0 as the declared version in the standard, the
conformance record, and the self-assessment: adding R07 is a minor change
under Versioning And Compatibility, so it supersedes the 1.6.1 patch rather
than competing with it. The changelog keeps both entries, 1.7.0 above 1.6.1.

standard.yml and the conformance badge were regenerated from the resolved
sources rather than hand-resolved, since both are generated files.

Verified that main's Status Badges rewrite survived the merge intact: that
section is now byte-identical to origin/main.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@trsdn
trsdn merged commit 54e402e into main Aug 31, 2026
6 checks passed
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