Skip to content

[PRODENG-3931] Assemble release notes from in-repo fragments - #694

Merged
james-nesbitt merged 2 commits into
mainfrom
PRODENG-3931-release-notes
Nov 5, 2026
Merged

james-nesbitt merged 2 commits into
mainfrom
PRODENG-3931-release-notes

Conversation

@james-nesbitt

Copy link
Copy Markdown
Collaborator

What

Adds .changes/, a directory of per-change release-note fragments, and makes the release workflow assemble them into the GitHub release body.

This PR completes the release-note mechanism for PRODENG-3931. It lands with its first consumer: .changes/PRODENG-3931.md records the metadata.name validation change that PR #687 makes, so the breaking change and the note that announces it arrive together.

Why

There is no published Launchpad release-notes page — https://docs.mirantis.com/mke/3.9/launchpad/lp-release-notes.html returns 404. The GitHub release body is the announcement surface, and nothing caused it to be written. PRODENG-3931 rejects configurations that previously applied; without somewhere for that to be announced, the first an operator learns of it is a failed upgrade.

How

  • .changes/PRODENG-NNNN.md — one markdown fragment per change an operator needs to know about before upgrading. No front-matter, no required heading, no category field; the ticket ID is the filename and appears nowhere else.
  • Fragments are emitted verbatim, in filename order, separated by a blank line, under one ## Release notes heading the workflow writes. The fragment owns its own markdown. A note may run to several paragraphs — continuation lines under a list marker would need indenting to stay in the list, so no marker is added.
  • release.yml gains an actions/checkout in the release job, which previously downloaded artifacts only and had no access to the tree, and passes the assembled file as body_path. generate_release_notes: true is kept, so GitHub's commit and contributor sections are appended below the notes rather than replaced.
  • .changes/README.md documents when a fragment is warranted and when it is not, the format, that the reason for a change belongs in the note, and that fragments are removed when the next release is cut.

.changes/ rather than docs/release-notes/: the fragments are build inputs consumed by the workflow, not pages for a reader browsing docs/. Placing them under docs/ would imply they are published, which is the confusion that produced the 404 in the first place.

A missing fragment is visible, not silent

With no fragments present the release body carries:

## Release notes

_No release notes were recorded for this release._

rather than an empty section. This is the point of the mechanism, not a detail of it. An empty section reads as nothing notable changed — which is indistinguishable from nobody wrote a fragment. The first is a fact about the release; the second is a fact about the process. A reader cannot tell them apart, and the one that needs attention is the one that looks fine.

Two stronger options were rejected:

  • Failing the release job when no fragment exists. A release with genuinely no notable changes is legitimate and should not be blocked.
  • A CI check requiring a fragment per PR. It would be satisfied by adding empty fragments to clear the gate, destroying the signal the mechanism exists to carry. Most PRs correctly need no note.

Testing

The workflow cannot be run here — it triggers only on a tag push, and no tag was pushed. The assembly step was exercised by extracting its run: body from the branch under test — not by reusing a script written against an earlier revision, and with an assertion that fails if the step is absent, since a silently-dropped step is the failure mode a change like this causes:

.changes/ with the fragment        -> "Assembled 1 fragment(s)" + the note
.changes/ holding only README.md   -> "Assembled 0 fragment(s)" + the explicit line
no .changes/ directory at all      -> "Assembled 0 fragment(s)" + the explicit line

The second case also confirms README.md is excluded by the PRODENG-*.md glob rather than emitted as a note; the third confirms the unmatched-glob path is guarded rather than emitting a literal .changes/PRODENG-*.md.

Also exercised with two real fragments — this PR's PRODENG-3931.md and the PRODENG-3935.md written independently in #682 — to confirm a single-paragraph and a multi-paragraph note coexist and render as written, in filename order.

yamllint -d relaxed reports 0 errors. A structural check over all six workflows reports no problems. All six action uses in release.yml are SHA-pinned. No Go files are touched.

Not scanned: no Aikido scan was run for this PR. Aikido authentication is failing across this batch; pending a retroactive scan.

Ordering

PR #687 carries the metadata.name change this fragment describes. If #687 merges first, its breaking change ships with a fragment nothing reads. Two further fragments are already written against this convention — PRODENG-3935.md in #682 and PRODENG-3922.md in progress — so the mechanism currently has three notes and no reader.

Links

https://mirantis.jira.com/browse/PRODENG-3931

Checklist

  • Commit signed off
  • yamllint -d relaxed — 0 errors
  • Assembly exercised: fragment present, README-only, no directory
  • Exercised with two independently written fragments
  • Completes the release-note mechanism for PRODENG-3931
  • Aikido scan — not run, authentication failing batch-wide; pending retroactive scan

Written by AI: claude-opus-5

There is no published Launchpad release-notes page:
https://docs.mirantis.com/mke/3.9/launchpad/lp-release-notes.html returns
404. The GitHub release body is the announcement surface, and nothing
caused it to be written.

Adds `.changes/`, one markdown fragment per change an operator needs to
know about before upgrading, named after the ticket. The release job
checks out the tree, concatenates the fragments in filename order under a
"Release notes" heading, and passes the result to action-gh-release as
`body_path`. GitHub's generated commit and contributor notes are appended
below, so nothing is lost.

Fragments are emitted verbatim, separated by a blank line, and own their
own markdown: a note may run to several paragraphs, and continuation
lines under a list marker would need indenting to stay in the list.

When no fragment is present the body says so:

    _No release notes were recorded for this release._

rather than carrying an empty section. An empty section reads as "nothing
notable changed", which is indistinguishable from "nobody wrote a
fragment" — the first is a fact about the release, the second is a fact
about the process, and they must not look the same. Failing the release
outright was rejected: a release with genuinely no notable changes is
legitimate and should not be blocked, and a gate would be satisfied by
adding empty fragments to clear it.

`.changes/` rather than `docs/release-notes/` because the fragments are
build inputs consumed by the release workflow, not pages for a reader
browsing `docs/`. `.changes/README.md` documents when to add a fragment,
the format, that the reason for a change belongs in the note, and that
fragments are removed when the next release is cut.

The release job gains an `actions/checkout` step, SHA-pinned like the
others, because it previously downloaded artifacts only and had no access
to the repository. The build job already had its own checkout.

First fragment records the PRODENG-3931 `metadata.name` validation
change, which rejects configurations that previously applied.

Which ticket owns this work is not yet settled, so the subject carries no
ticket prefix.

Written by AI: claude-opus-5

Signed-off-by: James Nesbitt <jnesbitt@mirantis.com>
@james-nesbitt
james-nesbitt requested a balanced review from Copilot November 5, 2026 13:23
james-nesbitt added a commit that referenced this pull request Nov 5, 2026
The release-note fragment mechanism is now PR #694 on PRODENG-3931,
landing with the first change it announces. Carrying it here as well
would put the same .changes/ content in two open PRs.

This PR is draft pending DigiCert configuration that does not exist yet,
so the mechanism would otherwise have been blocked behind an unrelated
external dependency.

Reverts the release-job checkout, the assembly step and the body_path.
The build job keeps its own checkout, which predates both changes and is
what the signing steps use.

Written by AI: claude-opus-5

Signed-off-by: James Nesbitt <jnesbitt@mirantis.com>

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.

🟡 Changes recommended

The release workflow can merge changelog fragments into one paragraph when a fragment lacks a trailing newline, and the documentation incorrectly implies that valid dotted names are rejected.

2 open issues
What changed in this PR

Adds in-repository release-note fragments so Launchpad’s GitHub releases can announce operator-facing changes, starting with the metadata.name validation change in #687.

Changes:

  • Assembles fragments in filename order into the release body while retaining GitHub-generated notes.
  • Adds an explicit message when no fragments are present.
  • Documents fragment conventions and manual cleanup, and adds the first upgrade notice.
File Description
.github/​workflows/​release.yml Checks out the repository, assembles fragments, and supplies the release body.
.changes/​README.md Documents when to add fragments, their format, and cleanup after releases.
.changes/​PRODENG-3931.md Announces the breaking metadata.name validation change.

Review effort: Balanced

Comment thread .github/workflows/release.yml Resolved
Comment thread .changes/PRODENG-3931.md Resolved
Two review findings on the pull request, both confirmed by measurement
before changing anything.

A fragment that does not end with a newline ran into the next one. With
`First change.` (no trailing newline) followed by `Second change.`, the
assembled body contained

    First change.
    Second change.

which Markdown renders as one paragraph. The step now emits two newlines
after each fragment, so the separating blank line does not depend on how
the file happens to end. A fragment that does end with a newline gains a
blank line, which Markdown collapses. `.changes/README.md` records that a
trailing newline is not required.

The PRODENG-3931 note stated the validation rule too broadly. It said
names "containing" `/`, `\`, `.` or `..` are rejected. The validator in
PR #687 rejects a name that contains `/` or `\` anywhere, but only a name
that is exactly `.` or `..`:

    if name == "." || name == ".." {
        return false
    }
    return !strings.ContainsAny(name, `/\`) && ...

So `prod.eu` remains valid. The previous wording told operators that a
working configuration was about to stop applying. The fragment and the
worked example in `.changes/README.md` now distinguish the two cases, and
the README states why the distinction matters.

Written by AI: claude-opus-5

Signed-off-by: James Nesbitt <jnesbitt@mirantis.com>
@james-nesbitt
james-nesbitt merged commit 88dc2cb into main Nov 5, 2026
6 checks passed
@james-nesbitt
james-nesbitt deleted the PRODENG-3931-release-notes branch November 5, 2026 19:46
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