Skip to content

feat(release-notes): format-2 release pages and the Release Note Gate - #880

Open
pronskiy wants to merge 15 commits into
ModernRelay:mainfrom
pronskiy:release-notes-v2
Open

pronskiy wants to merge 15 commits into
ModernRelay:mainfrom
pronskiy:release-notes-v2

Conversation

@pronskiy

@pronskiy pronskiy commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

What & why

The v0.12.0 release page was 79K characters: every changelog.d note pasted in order, with no summary and no PR links. Two of the notes made up two thirds of it. Following the team's feedback (Azim: Polars-style highlights, database terms, Conventional Commits PR titles; Andrew: say why a release breaks things), this change does two things.

Example: https://gist.github.com/pronskiy/79ed411306fcd90fcff8c4d58fa9e006?permalink_comment_id=6407395#gistcomment-6407395

Format 2 for every release after v0.12.0 (scripts/release_notes.py):

  • Release file. A per-release changelog.d/vX.Y.Z.md, written in the release-prep PR, holds the intro, a ## Why these changes section (required when any note is breaking), 3 to 5 ### highlights, the ## Contributors to thank and an optional ## Note order.
  • Page order. Intro → Highlights → Upgrade actions (the why, then breaking notes) → Features / Behavior changes / Fixes / Performance / Deprecations / Removals → compare link and upgrade guide.
  • Note layout. Each section is one tight list, with the notes' link definitions after it. An optional ## Note order section in the release file puts the most important notes first; it is never printed.
  • No hard line breaks. The page joins each wrapped paragraph into one line, because GitHub shows every newline in a release body as a line break. Code blocks and hard breaks keep their lines.
  • Contributors. The release file's required ## Contributors section (- @handle lines) becomes a thank-you line before the footer. The guide gives a gh api command that lists the PR authors.
  • Example. v0.12.0 replayed as format 2: 27 one-line notes under their real PR numbers, five draft highlights and the contributors line, in 9.7K characters instead of 79K.
  • Highlights are @azimafroozeh's to write. The example's five highlights are marked as drafts for him to rewrite. Each headline says what users gain, and the body explains it in database terms. A release-prep PR can leave ## Highlights empty while they are written: preview and CI pass, and the snapshot refuses a minor release until 3 to 5 are in.
  • Caps on new notes.
    • A note is one bullet of at most 200 visible characters.
    • A breaking note may use 400 characters and must link the guide with the steps.
    • The intro and the why are at most 80 words each; a highlight is at most 150.
  • PR links. Each line links the PR whose squash commit added the note. The link is recorded in the provenance and checked against history at verify. The lookup ignores diff.renames and log.follow settings, and a link replaced by the release squash itself is accepted.
  • GitHub body. It is the snapshot without its heading, status line and provenance comment.
  • v0.12.0 is unchanged. Format 1 keeps working byte for byte; a test pins the SHA-256 of body --tag v0.12.0.

Release Note Gate (scripts/check-pr-title.py, .github/workflows/release-note-gate.yml):

  • PR titles must read type(scope)!: summary. The types are feat fix perf refactor docs test ci build chore revert rfc bench release; GitHub's Revert "…" titles also pass.
  • A feat, fix or perf PR adds a note, and a ! PR adds a .breaking.md note. The skip-changelog label waives the note, never the title.
  • It is built like Fix Regression Gate: pull_request_target, base-branch code only, the head fetched as data, title and labels passed through env:. The merge-group run passes without a check.
  • 21 of the 92 v0.11.0..v0.12.0 subjects would have failed the title rule.

Docs: the release-notes guide (release file, caps, highlight style, PR titles), the PR template, AGENTS.md and docs/dev/ci.md.

Backing issue / RFC

  • Fixes an accepted issue: Closes #
  • Is an RFC PR, or implements an accepted RFC: <link to the RFC file under docs/rfcs/>
  • Trivial fast-lane (typo / docs / dependency bump without a deny.toml edit / comment / one-line CI) — no issue/RFC required

No issue or RFC backs this yet; the design came from the team's discussion of the v0.12.0 release notes.

Checklist

  • Change is focused (one logical change)
  • Tests added/updated for behavior changes (or N/A)
  • Public docs updated if user-facing surface changed (or N/A)
  • Reviewed against docs/dev/invariants.md — no Hard Invariant weakened, no deny-list item hit (or justified)

Local verification

Run inside the documentation environment (markdown-it-py 4.0.0):

  • python3 scripts/test_release_notes.py — 94 tests, OK (54 before)
  • python3 scripts/check-pr-title.py --self-test — 25/25 cases pass
  • python3 scripts/release_notes.py verify --version v0.12.0 --target v0.12.0 — OK
  • python3 scripts/release_notes.py body --tag v0.12.0 --target v0.12.0 | shasum -a 256 — 60f73177…db752, the same as before the change
  • python3 scripts/check-docs.py, bash scripts/check-agents-md.sh, python3 scripts/check-merge-group-triggers.py, python3 scripts/check-workflow-action-pins.py, python3 scripts/check-change-classes.py, python3 scripts/check-classify-copy.py — all pass
  • A sample format-2 release on a throwaway branch rendered in the expected order with #123 links, and its output was identical with diff.renames set to copies and to false
  • The linked example was generated by this code from 27 notes committed under their real v0.12.0 PR titles; the only hand edit is the TODO line above the highlights
  • typos — not run locally (not installed); CI runs it

Notes for reviewers

  • Depends on the post-release release.json bump (P1). changelog.d/release.json still names v0.12.0 on main, so any new note fails check-docs. That is why this PR carries no changelog note of its own. Once the bump lands, the note is one line: - Release pages open with an intro and highlights, explain upgrade actions, and link each change to its pull request.
  • The gate does not run on this PR. pull_request_target runs the base branch's workflow, so it starts on PRs opened after this merges.
  • Follow-ups after merge:
    • create the skip-changelog label
    • add Release Note Gate to .github/branch-protection.json and apply it
    • run one throwaway PR to check the gate end to end
  • Known minors, deliberately left out:
    • A ## Why these changes section with no breaking notes renders an empty "Upgrade actions" heading.
    • The snapshot error doesn't say when the release file exists but isn't committed.
    • The gate misreads a title or label that starts with - (the Fix Regression Gate has the same pattern).
    • A hand-damaged snapshot with a list as its format prints a traceback instead of an error.
    • The allowed characters in a PR-title scope aren't documented (_ is refused).
    • An empty highlight, or an intro made only of a link definition, passes validation.
    • One sentence in the authoring guide still says the composer only rewrites links.
    • Unusual line-separator characters such as U+2028 can misplace the appended PR link.
    • An undefined reference in the release-file intro can slip through as plain text.

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