This is an automated email from the ASF dual-hosted git repository. lukaszlenart pushed a commit to branch docs/releasing-struts-skill in repository https://gitbox.apache.org/repos/asf/struts.git
commit edb14160020e2c9df5044e218b0c30654c456c0f Author: Lukasz Lenart <[email protected]> AuthorDate: Fri Aug 14 10:11:50 2026 +0200 docs(skills): add releasing-struts, the end-to-end release runbook The only end-to-end description of the release process was the cwiki page "Building Struts 2 - Normal release", last revised in 2017. It is still right about JIRA, the release plugin, Nexus and dist.apache.org, and wrong about everything downstream: branches develop/master, tag STRUTS_2_3_x, exporting the wiki to /docs, building the site with Docker Jekyll and committing content/ into an svn production repo, and publishing via people.apache.org. Following it today publishes to a repository that no longer serves the site. The four existing skills cover the paperwork -- Version Notes, the [VOTE] mail, bulletins, report triage. Nothing covered the mechanics, the ordering, or the last mile: the GA [ANN] mail, CVE registration, the site update, and post-release follow-through. releasing-struts is the spine: seven phases, each with a gate that someone else can verify, cross-referencing the existing skills rather than restating them. release-runbook.md holds the commands, with every step marked as verified against the 7.3.0/6.11.0 run or as carried over from the cwiki page and still unconfirmed, so the next release manager knows which is which. Three things the cwiki got wrong that were verified here: - Releases are cut on a release/X.Y.Z-RC1 branch off the line, not on main. The [maven-release-plugin] commits never reach main, which is why the root pom still read 7.2.2-SNAPSHOT after 7.3.0 shipped -- and why the pom is worthless as a source for the release number. - The BOM needs no version sync; bom/pom.xml inherits through <parent> and the struts-version.version property no longer exists. - The GA [ANN] mail goes To user@ with Cc announce@ and announcements@struts, not to dev@. Co-authored-by: Claude Opus 5 <[email protected]> --- .claude/skills/creating-release-vote-mail/SKILL.md | 3 +- .claude/skills/creating-version-notes/SKILL.md | 2 + .claude/skills/releasing-struts/SKILL.md | 140 +++++++++++++ .claude/skills/releasing-struts/release-runbook.md | 221 +++++++++++++++++++++ 4 files changed, 365 insertions(+), 1 deletion(-) diff --git a/.claude/skills/creating-release-vote-mail/SKILL.md b/.claude/skills/creating-release-vote-mail/SKILL.md index e34815b88..4c14a96fd 100644 --- a/.claude/skills/creating-release-vote-mail/SKILL.md +++ b/.claude/skills/creating-release-vote-mail/SKILL.md @@ -14,7 +14,8 @@ boilerplate, around a plain-text rendering of the release's Version Notes page. **This is the step after `creating-version-notes`.** That skill produces the page, the GitHub release and the `[TEST]` announcement; this one consumes all three. If they do not exist yet, -you are in the wrong skill. +you are in the wrong skill. `releasing-struts` holds the surrounding phases and what happens +once the vote passes. [`vote-mail-template.md`](vote-mail-template.md) is the source of truth for the artifact. diff --git a/.claude/skills/creating-version-notes/SKILL.md b/.claude/skills/creating-version-notes/SKILL.md index e52ca187d..fd8a0fd3c 100644 --- a/.claude/skills/creating-version-notes/SKILL.md +++ b/.claude/skills/creating-version-notes/SKILL.md @@ -13,6 +13,8 @@ A Version Notes page answers one question for a user deciding whether to upgrade **One skill covers every maintenance line.** 6.x and 7.x pages share an identical structure. The line changes the data (version, prior page, JIRA ids), never the process. +**This is phase 3 of a seven-phase release.** `releasing-struts` holds the sequence, the gates and the mechanics either side of it; this skill owns the paperwork. + ## The Iron Rule ``` diff --git a/.claude/skills/releasing-struts/SKILL.md b/.claude/skills/releasing-struts/SKILL.md new file mode 100644 index 000000000..627539a7c --- /dev/null +++ b/.claude/skills/releasing-struts/SKILL.md @@ -0,0 +1,140 @@ +--- +name: releasing-struts +description: Use when running or planning an Apache Struts release on any maintenance line (6.x, 7.x) - cutting the tag, staging artifacts, opening the vote, promoting, updating the site and announcing - or when asked what the next step in a release is. +--- + +# Releasing Struts + +## Overview + +A release is seven phases with a gate between each. Most of the *writing* is already covered by +other skills; this one owns the **order, the gates, and the mechanics** — and it is the only +place that covers the last mile after the vote passes. + +**Core principle:** a phase is finished when its gate is verifiable by someone other than you. +"I ran the command" is not a gate; "the URL resolves" is. + +[`release-runbook.md`](release-runbook.md) holds the commands. This page holds the sequence and +the judgement. + +## The phases + +| # | Phase | Gate before moving on | +|---|---|---| +| 1 | Prepare | Branch green, versions decided, BOM in sync | +| 2 | Cut | Tag pushed, artifacts in a **closed** Nexus staging repo | +| 3 | Stage | Assemblies in `dist/dev`, Version Notes page live, `[TEST]` mail sent | +| 4 | Vote | 72 h elapsed, three binding `+1`, result mail sent | +| 5 | Promote | Nexus repo released, `dist/dev` → `dist/release`, 24 h rsync waited | +| 6 | Publish | Site PR merged, GitHub release un-flagged, `[ANN]` mail delivered | +| 7 | Advisories | Bulletins public, CVE records filled, advisory mails delivered | + +Phase 7 only exists when the release carries a security fix, and it is **strictly after** phase +6 — see *Security work is a separate clock* below. + +## Which skill owns which artifact + +Cross-references, not copies. Do not restate what these settle: + +- **`creating-version-notes`** — the Version Notes page, its Staging Repository block, the + Migration Guide entry, the GitHub release notes, and the `[TEST]` mail. All of phase 3's + paperwork. +- **`creating-release-vote-mail`** — the `[VOTE]` mail. All of phase 4's paperwork. +- **`creating-security-bulletins`** — the S2-XXX page, what may be disclosed and when, + publication, and the advisory mails. All of phase 7. + +This skill covers what none of them do: phases 1, 2, 5 and 6, and the ordering that binds them. + +## Two lines, two releases + +`main` is the 7.x line; `support/struts-6-x-x` is 6.x. Both are protected and both require their +build to pass. A change that lands on both is **two releases**, each with its own tag, vote, +site entry and announcement — not one release mentioned twice. + +They can be cut in parallel and voted in parallel, and usually are. Keep the version numbers +independent: 6.11.0 and 7.3.0 shipped together and share nothing but a date. + +**Neither line branch is where the release is cut.** Both August 2026 releases were built on a +`release/X.Y.Z-RC1` branch off the line, so the `[maven-release-plugin]` commits never reach +`main`. A failed vote is then a deleted branch, not a revert. + +## The version number is chosen at release time + +The `-SNAPSHOT` in the pom is a placeholder, not a decision. Pick the number from the semver +impact of what actually landed since the last tag, and say so out loud before cutting — the tag +is the first irreversible act of the release. + +The pom cannot tell you: because releases are cut on a side branch, `main` still read +`7.2.2-SNAPSHOT` after 7.3.0 had shipped. + +## Security work is a separate clock + +**Nothing about an unpublished advisory goes into the release paperwork.** Not the Version +Notes, not the `[TEST]` mail, not the `[VOTE]`, not the commit messages, not the site entry. +The tickets are neutral; that is deliberate and it is what makes the embargo survive a public +release process. + +The advisory follows the release, and the ordering is not negotiable: + +``` +release GA → bulletin unrestricted → advisory mails → CVE pushed to MITRE +``` + +A bulletin published before the fixed artifact is downloadable tells attackers what to look for +and gives operators nothing to do about it. + +**A 6.x release containing only embargoed fixes is self-disclosing** — the diff between the two +tags is the vulnerability whatever the commit messages say. That is a reason to bundle it with +unrelated work, or to publish the bulletins with the release, not a reason to pretend otherwise. + +## What the old cwiki page gets wrong + +[Building Struts 2 — Normal release](https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=27832970) +was last revised in **2017** and is the page a release manager is most likely to find. It is +still right about JIRA, `release:prepare`/`release:perform`, Nexus and `dist.apache.org`, and +wrong about everything downstream: + +| It says | Reality | +|---|---| +| Branches `develop` / `master` | `main` and `support/struts-6-x-x` | +| Tag `STRUTS_2_3_x` | `STRUTS_X_Y_Z` for the version being cut | +| Export the wiki to `/docs` | The site no longer embeds exported Confluence pages | +| Build the site with Docker Jekyll, commit `content/` | The site builds from a PR to `apache/struts-site` | +| `svn co .../infra/websites/production/struts` | Gone; publishing is the merge | +| `people.apache.org`, `source/announce.md`, `downloads.html` | Dead host, and the files are `announce-YYYY.md` and `download.cgi` | + +Treat it as history. If you follow it, you will publish to a repository that no longer serves +the site. + +## Gates that are actually load-bearing + +- **A closed Nexus staging repo, not just a successful `release:perform`.** Until it is closed + the URL in the Version Notes resolves to nothing and every tester is blocked. +- **72 hours, and three binding `+1`.** PMC votes are the binding ones; `private@` is on the + vote mail so binding voters see it. +- **24 hours after the `dist` move, before announcing.** ASF mirroring guidance. Announcing into + an unmirrored release sends everyone to a 404. +- **The GitHub release stops being a prerelease at phase 6, not at phase 3.** During the vote it + must still be flagged, or the vote is on an artifact the world already treats as final. + +## Red Flags — STOP + +- Cutting a tag before the version number has been stated and agreed +- A `[VOTE]` opened on a staging repo that is not closed, or on a link that 404s +- Announcing before the 24-hour mirror wait +- Any severity, CVE, S2-XXX or bulletin link in release paperwork +- A bulletin unrestricted before the fixed release is downloadable +- Following the 2017 cwiki page for anything after the Nexus step +- One release "covering" both maintenance lines +- Inferring the release version from the `-SNAPSHOT` in the pom + +## Common Mistakes + +| Mistake | Reality | +|---|---| +| "`release:perform` succeeded, so the artifacts are staged" | They are staged and *open*. Close the repo or nobody can fetch them. | +| "The vote passed, so it's released" | Nexus release, dist move and the mirror wait all come after. | +| "I'll announce now and fix the site after" | The announcement links the site. Merge the site PR first. | +| "The 6.x fix is the same change, so one announcement covers both" | Two artifacts, two downloads, two sets of affected users. | +| "The pom says 7.3.1-SNAPSHOT, so this is 7.3.1" | The placeholder is not a decision. Semver impact decides. | +| "The cwiki page is the official process" | It is the 2017 process. Where they disagree, this skill is current. | diff --git a/.claude/skills/releasing-struts/release-runbook.md b/.claude/skills/releasing-struts/release-runbook.md new file mode 100644 index 000000000..7dac56367 --- /dev/null +++ b/.claude/skills/releasing-struts/release-runbook.md @@ -0,0 +1,221 @@ +# Release Runbook + +The commands, in order. [`SKILL.md`](SKILL.md) holds the sequence, the gates and the judgement; +this file is what you type. + +**Provenance.** Everything marked ✔ was verified against the repository or a completed release +(7.3.0 / 6.11.0, August 2026). Everything marked **⚠ unverified** is carried over from the 2017 +cwiki page and has *not* been confirmed against a current run — check it before relying on it, +and correct this file when you do. + +--- + +## Phase 1 — Prepare + +✔ Two lines, two releases: + +| Line | Branch | Build check that must pass | +|---|---|---| +| 7.x | `main` | `Build and Test (JDK 17)` | +| 6.x | `support/struts-6-x-x` | `Build and Test (8)` | + +Both branches are protected in `.asf.yaml` and must be green before you start. + +```bash +git checkout main && git pull --ff-only +mvn clean install -DskipAssembly +``` + +Then: + +- Decide the version number from semver impact. Do not read it off the `-SNAPSHOT`. +- ✔ Confirm `struts-master` (currently `15`) and `struts-annotations` are released versions, not + snapshots. The root pom's `<parent>` must not point at a snapshot. +- ✔ The BOM needs no version sync. `bom/pom.xml` inherits the root version through its + `<parent>` and declares members as `${project.version}`. The cwiki's + `struts-version.version` property no longer exists — ignore that step. +- Review JIRA: every issue fixed since the last tag has a fix version; nothing unresolved carries + this one. +- ⚠ unverified: the cwiki's "omnibus ticket" step. The 7.3.0 and 6.11.0 runs show no such ticket + — treat it as abandoned unless the PMC says otherwise. + +## Phase 2 — Cut + +✔ **Cut from a release branch, not from the line branch.** Both August 2026 releases were built +on `release/X.Y.Z-RC1` branched off the line: + +```bash +git checkout -b release/7.3.0-RC1 main # or off support/struts-6-x-x for 6.x +git push -u origin release/7.3.0-RC1 +``` + +The two `[maven-release-plugin]` commits land there and **`main` is never touched** — which is +why the root pom still said `7.2.2-SNAPSHOT` after 7.3.0 shipped, and why the pom is worthless +as a source for the release number. + +✔ `maven-release-plugin` 3.3.1, driven interactively, on that branch: + +```bash +mvn release:prepare -DautoVersionSubmodules=true +``` + +✔ **At the SCM tag prompt, type `STRUTS_X_Y_Z`.** The plugin's default would be +`struts2-parent-X.Y.Z`; every Struts tag in history is the underscore form, and the GitHub +release, the Version Notes and the site all assume it. + +Dry run first if you want one — add `-DdryRun=true`, then `mvn release:clean` before the real +run. On failure, re-run the same command: `-Dresume` defaults to true and it picks up where it +stopped. + +✔ The result is two commits on the release branch, +`[maven-release-plugin] prepare release STRUTS_X_Y_Z` and +`[maven-release-plugin] prepare for next development iteration`, plus the tag. + +```bash +mvn release:perform -DretryFailedDeploymentCount=10 +``` + +⚠ unverified: the fallback for re-running `perform` elsewhere — +`git checkout STRUTS_X_Y_Z && mvn javadoc:javadoc deploy -DperformRelease=true -Papache-release`. + +**Then close the staging repository** at <https://repository.apache.org/> — Staging Repositories +→ select → Close. ⚠ unverified in detail, but the gate is checkable: the artifacts must resolve +under + +``` +https://repository.apache.org/content/groups/staging/org/apache/struts/struts2-core/$VERSION/ +``` + +The staging repo is keyed by user *and* public IP. If your IP changed mid-release you will have +two; drop the stale one, checking the dates. + +## Phase 3 — Stage + +⚠ unverified: the assembly-copying script on the cwiki page targets `people.apache.org`, which no +longer exists. What must be true at the end is checkable: + +``` +https://dist.apache.org/repos/dist/dev/struts/$VERSION/ +``` + +holds the assemblies. ✔ Verified naming, from `dist/release/struts/7.3.0/`: `struts-$VERSION-all.zip`, +`-apps.zip`, `-docs.zip`, `-lib.zip`, `-src.zip`, each with `.asc`, `.sha256` and `.sha512` +alongside. The `2-assembly` infix Nexus uses is stripped. `KEYS` lives one level up, in +`dist/release/struts/`. + +Everything else in this phase belongs to **`creating-version-notes`**: the Version Notes page, +its Staging Repository block, the Migration Guide entry, the GitHub release (created as a +**prerelease**), and the `[TEST]` mail to `dev@` and `user@`. + +## Phase 4 — Vote + +**`creating-release-vote-mail`** owns the mail. The mechanics around it: + +- 72 hours minimum, three binding `+1` (PMC members). +- ✔ `To: [email protected]`, `Bcc: [email protected]`. Never `user@`. +- Close with a result mail on the same thread. + +## Phase 5 — Promote + +⚠ unverified command, from the cwiki: + +```bash +svn mv https://dist.apache.org/repos/dist/dev/struts/$VERSION/ \ + https://dist.apache.org/repos/dist/release/struts/ \ + -m "Release Struts $VERSION" +``` + +Then **release** the staging repository in Nexus, which replicates to Maven Central. + +✔ On pruning old releases: the cwiki says to keep only the latest. Current practice does not — +`dist/release/struts/` held 6.8.0, 6.9.0, 6.10.0, 6.11.0, 7.1.1, 7.2.1, 7.3.0 and `KEYS` in +August 2026. Everything removed stays available at +<https://archive.apache.org/dist/struts/>. Decide deliberately; do not prune on autopilot. + +**Then wait 24 hours** for mirrors before anything in phase 6. + +## Phase 6 — Publish + +### The site — a PR to `apache/struts-site` + +✔ Verified against PR #322 (the 7.3.0 / 6.11.0 GA announcement) and #323. + +`_config.yml` — all of these move together: + +```yaml +current_version: 7.3.0 +current_version_short: 730 +prev_version: 6.11.0 +prev_version_short: 6110 +release_date: 1 August 2026 +prev_release_date: 1 August 2026 +release_date_short: 20260801 +prev_release_date_short: 20260801-6110 +``` + +`release_date` is the **tag** date, not the announcement date — 7.2.1 was tagged 15 June and +announced 30 June, and the site says 15 June. The `*_date_short` values are the anchors in +`announce-YYYY.md`; when two releases share a tag date, disambiguate the second +(`20260801-6110`) so the two home-page boxes link to their own entries. + +Then: + +- `source/announce-YYYY.md` — a new `####` entry at the top, newest first, with its `{#aYYYYMMDD}` + anchor. +- `source/index.html` — the GA boxes read from `_config.yml`; the security boxes are hand-edited. +- `source/download.cgi` — the Prior Releases section. +- `source/dtds/` — only if a new DTD shipped. + +Publishing is the merge. There is no separate deploy step and no svn. + +### GitHub release + +✔ Un-flag the prerelease. Title `Struts X.Y.Z`, tag `STRUTS_X_Y_Z`. + +### The `[ANN]` mail + +✔ Recipients, from the 7.3.0 and 6.11.0 announcements: + +``` +To: [email protected] +Cc: [email protected], [email protected] +``` + +`dev@` is not on it — the list already saw the `[TEST]` mail and the vote. + +✔ **Plain text only, and sent from the `@apache.org` identity.** `[email protected]` rejects +any message carrying a `text/html` part — + +``` +ezmlm-reject: fatal: Sorry, a message part has an unacceptable MIME Content-Type: 'text/html' (#5.2.3) +``` + +— and `[email protected]` answers *"Must be sent from an @apache.org address."* +A draft made with the Gmail tool is an HTML draft whatever you pass it; see *The mail must be +text/plain* in `creating-release-vote-mail` for the full contract. One list accepting the mail +is not evidence the format was right. + +Body: the GA boilerplate ("pleased to announce … General Availability … highest quality grade"), +the Version Notes link, the Migration Guide link for a major line, the minimum JDK/spec +requirements for that line, and the download page. + +## Phase 7 — Advisories + +Only when the release carries a security fix, and only after phase 6. + +**`creating-security-bulletins`** owns all of it: unrestricting the bulletin, the CVE record on +<https://cveprocess.apache.org>, and the advisory mails from that record's *OSS/ASF Emails* tab. + +The order that matters here: the CVE record goes `RESERVED → DRAFT → READY`, and **READY is the +last state a PMC sets**. ASF Security submits it to the CVE Program and sets `PUBLIC`, so +`cve.org` links 404 until they do. That is expected, and it is not a reason to delay the +bulletin or the mails. + +## Post-release + +- Add the site announcement entry for any advisory (`announce-YYYY.md`), same form as the GA one. +- Check NVD once the CVE is public — affected ranges have been wrong before, and the fix is an + email to `[email protected]` citing the CVE record. +- Answer any coordinator (JPCERT/CC and similar) in their existing thread once the bulletin is + live; they hold their advisory until you confirm. +- Update the Version Notes page if the vote forced a re-cut.
