Compare commits

..

2 Commits

Author SHA1 Message Date
Lukasz Lenart 2794832317 [maven-release-plugin] prepare for next development iteration 2026-08-01 15:24:56 +02:00
Lukasz Lenart a88fd76364 [maven-release-plugin] prepare release STRUTS_7_3_0 2026-08-01 14:57:50 +02:00
35 changed files with 58 additions and 813 deletions
@@ -1,160 +0,0 @@
---
name: creating-release-vote-mail
description: Use when opening the formal release vote for a Struts release candidate on any maintenance line (6.x, 7.x) - composing and drafting the [VOTE] Apache Struts X.Y.Z mail to dev@ once the Version Notes page, GitHub release and staged artifacts are published.
---
# Creating a Release Vote Mail
## Overview
The `[VOTE]` mail opens the formal release vote. It is four links wrapped in frozen ASF
boilerplate, around a plain-text rendering of the release's Version Notes page.
**Core principle:** the mail is a *rendering* of the page, not a second account of the release.
**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.
[`vote-mail-template.md`](vote-mail-template.md) is the source of truth for the artifact.
## The mail is exactly these parts, in this order
1. The two-sentence opener
2. The page's `Breaking changes`, `Deprecations` and `Rejected requests`, where present
3. The page's issue-type sections, in page order
4. The four link lines
5. The vote boilerplate
6. The sign-off
**A part not on this list is not in the mail, and one mail is produced, not two.** Every
section is the page's content; the opener is the only prose you write.
The pull here is toward helpfulness — an upgrade-notes section derived from the fix commits, a
summary of what changed for integrators, a companion note to a subset of recipients. All of it
is real work that belongs somewhere else. A vote is a judgement on the staged artifacts, and
the page is what describes them.
## The vote carries no security information
No severity, no CVE, no S2-XXX, no bulletin link, no attack description, no reporter or
coordination detail. **That disclosure happens after the vote passes and the version is
released.**
**The restriction is on the vote, not on the audience.** Routing advisory detail through
`private@`, a Cc, an attachment or a companion mail is the same violation as putting it in the
body — a second mail sent to open the vote is part of the vote. "The recipients already hold
this information" is not an exemption; the vote is simply not the vehicle.
Neutral ticket summaries carried from the page are not security information. Keep them exactly
as the page has them, including where the page truncates one at a clause boundary.
**REQUIRED BACKGROUND:** `creating-security-bulletins` governs what may be said, and when.
## Recipients
```
To: dev@struts.apache.org
Bcc: private@struts.apache.org
```
**`user@struts.apache.org` must not appear in any header — not To, not Cc, not Bcc.** The
`[TEST]` mail one step earlier goes to `dev@` and `user@`, which is right for it: it asks
people to test. This mail asks people to *vote*, and a vote invitation on the user list
solicits votes that are not binding and scatters the tally across two lists.
Cc is not a compromise. If a release manager asks you to include the user list, the answer is
that the `[TEST]` mail already did.
**`private@` is on the mail for reach, not for confidentiality.** Not every PMC member follows
`dev@`, and PMC votes are the binding ones, so `private@` is what guarantees the binding voters
see the call. Nothing goes there that could not go to `dev@` — its presence is a delivery
decision, and it is not an exemption from the rule above.
It goes on **Bcc, not Cc**: on the 7.1.1 and 6.8.0 votes it was on Cc, and reply-all `+1`s
landed on the private PMC list. Bcc gives the same reach while keeping the tally in one thread
on `dev@`.
Subject is exactly `[VOTE] Apache Struts X.Y.Z`.
## Draft it, do not send it
Create a Gmail draft with To, Bcc, Subject and body set, and write the same body to a file whose
path you hand over. **Never send.**
| Rationalization | Reality |
|---|---|
| "The release manager authorised whatever I produce" | Authorisation to compose is not authorisation to transmit. |
| "Every fact is verified; review would catch nothing" | Sending is not a quality gate, it is a commitment. Verification does not confer it. |
| "A draft doesn't open the vote, which defeats the request" | Correct, and that is the right outcome when the release manager is not there to send it. |
| "The 72-hour clock is the reason for the hurry" | A vote opened on the wrong artifacts costs far more than the hours saved. |
Sending opens a binding vote on a permanently archived public list and commits the PMC to the
artifacts as staged.
### Gmail mangles the links — hand over a paste-ready body
Gmail's linkifier rewrites URLs server-side as the draft is stored, so the four link lines
arrive as `https://www.google.com/url?q=...&source=gmail&ust=...` and the 72-column wrap is
reflowed. There is no way to pass the body through the Gmail tool that avoids it:
| Body passed as | Result |
|---|---|
| `body` only | Wrapped hrefs; plain rendering shows `bare-url <google.com/url?q=…>` |
| `htmlBody` only | No plain-text part at all — HTML-only mail, wrong for an ASF list |
| both | Worst: the plain part's *visible* text becomes the wrapped URL |
`body` only is the least-bad, and is what to use. **Also write the exact body to a file and
give the release manager its path.** Pasting plain text over the compose window restores both
the bare URLs and the wrap, making the fix one select-all-paste instead of four hand-edited
URLs.
**Never re-run the draft-update tool on a draft whose links have already been fixed by hand**
it re-mangles them. A draft the release manager has corrected is finished; leave it alone.
## The boilerplate is frozen
Everything from `Once you have had a chance to review the test build` to the sign-off is
byte-identical to the template. **Inserting a paragraph between existing ones is an edit**
that is how it actually gets broken, not by rewording.
If something about this release needs explaining to voters, it belongs in the opener, above
the vote call. The vote call itself says the same thing every release, which is what lets a
voter skim to the checkboxes.
## What this skill does not restate
Cross-references, not copies:
- `creating-version-notes` — the page, the GitHub release, the `[TEST]` mail, and what belongs
on them. The issue list, Breaking changes wording and ticket reconciliation are settled
there; render what the page says.
- `creating-security-bulletins` — what may be said about an unpublished advisory.
Before drafting, confirm all four links resolve and the GitHub release is still a prerelease.
A vote opened on a 404 burns the window before anyone can test.
## Red Flags — STOP
- Any part in the mail that is not on the six-item list
- A second mail produced alongside the vote
- Severity, CVE, S2-XXX, bulletin link or reporter detail anywhere, on any channel
- `user@struts.apache.org` in any header, including Cc
- Sending rather than drafting
- A new paragraph inserted into the vote boilerplate
- A quality checkbox arriving pre-ticked
- An opening sentence carried over from the previous release
- Re-running the draft-update tool on a draft whose links were already fixed by hand
## Common Mistakes
| Mistake | Reality |
|---|---|
| "Voters can't judge fixes they can't see" | They can open the restricted bulletins themselves. The vote is not the disclosure channel. |
| "It's only going to private@, so nothing leaks" | The rule is about the vote, not the audience. A companion mail is part of the vote. |
| "private@ is on the mail already, so it's a channel I can use" | It is there so binding voters see the call, not to carry anything `dev@` cannot. |
| "Cc'ing user@ keeps the vote on dev@ and still informs them" | The `[TEST]` mail informed them. Cc splits the tally. |
| "I verified everything, so I can send" | Verification earns a draft. Sending is the release manager's keystroke. |
| "I'm adding to the boilerplate, not changing it" | Insertion is editing. The vote call is byte-frozen. |
| "The release notes leave out what integrators need" | Then the page needs fixing. The mail renders the page. |
| "Last release's opening sentence fits" | It described last release. Write the one this list supports. |
@@ -1,149 +0,0 @@
# Release Vote Mail Template
The canonical skeleton for the `[VOTE] Apache Struts X.Y.Z` mail that opens a release vote.
Companion to [`SKILL.md`](SKILL.md), which covers *how* to fill the slots; this file covers
*what the mail contains*.
**This file is the source of truth.** Start every vote mail from the skeleton below.
## Slots
| Slot | What goes in it |
|---|---|
| `<X.Y.Z>` | The release being voted on, dotted — subject, opening sentence, Version Notes URL, dist path |
| `<X_Y_Z>` | The same version underscored, for the `STRUTS_` git tag only |
| `<SHAPE SENTENCE>` | See below — authored per release |
| Page sections | Breaking changes, Deprecations, Rejected requests, and the issue-type sections, copied from the Version Notes page. Omit any the page omits. |
**Where the page introduces a section with a sentence of its own, that sentence comes with it.**
The skeleton below shows sections as bare lists, but `Rejected requests` on the 7.3.0 page opens
with *"Two long-standing requests were closed as Won't Do in this cycle. They are listed here so
the decision is visible rather than silent."* Without it the section reads as two unexplained
ticket ids.
## The shape sentence is authored per release
The opener is two sentences. The first is fixed. The second describes the *shape* of the issue
list — how the release is composed — and is written from the list in front of you.
| The page has | Second sentence |
|---|---|
| No Breaking changes | `With this release the following issues were addressed:` |
| Breaking changes | `This release contains <what>:` |
**Do not reuse a previous release's wording.** 7.2.1's *"a few minor breaking changes plus some
bug fixes. Also a lot of dependencies have been updated"* describes 7.2.1. Applied to 7.3.0 —
seven breaking changes, one dependency bump — both halves are false.
Name no individual ticket here. The list below is the detail.
## Skeleton
```
Subject: [VOTE] Apache Struts <X.Y.Z>
To: dev@struts.apache.org
Bcc: private@struts.apache.org
The Apache Struts <X.Y.Z> test build is available. <SHAPE SENTENCE>
Breaking changes
- <page item, verbatim, ending [WW-XXXX].>
Deprecations
- <page item, verbatim, ending [WW-XXXX].>
Rejected requests
[WW-XXXX] - <page item, verbatim>
Bug
[WW-XXXX] - <summary>
New Feature
[WW-XXXX] - <summary>
Improvement
[WW-XXXX] - <summary>
Task
[WW-XXXX] - <summary>
Dependency
[WW-XXXX] - <summary>
Release notes:
* https://cwiki.apache.org/confluence/display/WW/Version+Notes+<X.Y.Z>
Github release
* https://github.com/apache/struts/releases/tag/STRUTS_<X_Y_Z>
Distribution:
* https://dist.apache.org/repos/dist/dev/struts/<X.Y.Z>/
Maven 2 staging repository:
* https://repository.apache.org/content/groups/staging/
Once you have had a chance to review the test build, please respond
with a vote on its quality:
[ ] Leave at test build
[ ] Alpha
[ ] Beta
[ ] General Availability (GA)
Everyone who has tested the build is invited to vote. Votes by PMC
members are considered binding. A vote passes if there are at least
three binding +1s and more +1s than -1s.
The vote will remain open for at least 72 hours, longer upon request.
A vote can be amended at any time to upgrade or downgrade the quality
of the release based on future experience. If an initial vote
designates the build as "Beta", the release will be submitted for
mirroring and announced to the user list. Once released as a public
beta, subsequent quality votes on a build may be held on the user
list.
As always, the act of voting carries certain obligations. A binding
vote not only states an opinion, but means that the voter is agreeing
to help do the work.
On behalf of the Apache Struts project
Łukasz
```
Hard-wrap the body at 72 columns, continuation lines unindented, so the list stays legible in
the ASF archives and in quoted replies. Gmail reflows the wrap and rewrites the link lines when
it stores the draft — see *Gmail mangles the links* in [`SKILL.md`](SKILL.md) for why the body
also has to be handed over as a file.
## Frozen text
Everything from `Once you have had a chance to review the test build` to the sign-off is
byte-frozen. It is the vote call itself: the options voters tick, the binding threshold, the
72-hour minimum, and what a binding vote commits the voter to.
Details that look like defects and are kept:
| Detail | Why |
|---|---|
| `Github release` has no trailing colon | Every archived Struts vote mail reads this way |
| All four checkboxes empty | The release manager's `+1 (binding)` is a separate reply |
The staging URL is `content/groups/staging/`, matching the Version Notes page and the `[TEST]`
mail. The group repo also resolves released transitive dependencies, which the bare staging
repository does not.
## Pre-draft checklist
- [ ] All four links resolve; the GitHub release is still flagged pre-release
- [ ] Ticket ids in the mail match the page's exactly, both directions
- [ ] Boilerplate byte-identical to the frozen text above
- [ ] `To: dev@` only; `user@` absent from every header; `Bcc: private@` present
- [ ] Subject is exactly `[VOTE] Apache Struts X.Y.Z`
- [ ] All four checkboxes empty
- [ ] No severity, CVE, S2-XXX, bulletin link or reporter detail anywhere
- [ ] Exactly one mail
- [ ] Body also saved to a file and its path handed over, so the mangled links and wrap can be
fixed with one paste
+16 -145
View File
@@ -1,6 +1,6 @@
---
name: creating-version-notes
description: Use when preparing, updating, or reviewing the release documentation for a Struts release or release candidate on any maintenance line (6.x, 7.x) - the Version Notes page on the cwiki, its Migration Guide entry, the GitHub release notes, and the test-build announcement mail.
description: Use when preparing, updating, or reviewing the release documentation for a Struts release or release candidate on any maintenance line (6.x, 7.x) - the Version Notes page on the cwiki, its Migration Guide entry, and the GitHub release notes.
---
# Creating Version Notes
@@ -54,14 +54,11 @@ Group under `<h2>` per issue type, in this order, omitting any type with no issu
Within a section, order by issue key ascending. Each entry is `[WW-XXXX] - <the JIRA summary verbatim>`.
**Reconcile against what actually merged.** The JIRA query is the starting point, not the answer. Three mismatches to check:
**Reconcile against what actually merged.** The JIRA query is the starting point, not the answer. Two mismatches to check:
- A ticket resolved `Fixed` whose change did not make the release branch — it must not be listed as delivered.
- A ticket resolved **`Won't Do`** or otherwise not `Fixed` — it belongs under `Rejected requests`, not in a type section and not dropped. Check the resolution, not just the status: both `Closed` and `Resolved` sit in the Done category.
- A ticket marked fixed whose change did not make the release branch — it must not be listed.
- Work that shipped under a ticket assigned to a different fix version — the notes under-report the release.
A ticket with no commit in the range is not automatically wrong. Check its **component** first: `IDEA Plugin`, `Example Applications` and similar live in other repositories and are still legitimately part of the release.
**Reconcile through the ticket's linked PR, reading the files it changed.** Do not grep commit subjects, and do not go looking for the class named in the ticket title: a title often names the *symptom* while the fix lives elsewhere. WW-5630 reads "Performance Issue SecurityMemberAccess" and was fixed in `ConfigParseUtil`; searching for the former concludes, wrongly, that the backport is missing. Squash-merges also rewrite hashes, so the merge commit id from the PR need not appear on the branch.
**Untick eted patch-level dependency bumps are not a gap.** Dependabot PRs for patch updates are merged directly and deliberately get no ticket, so they get no entry — there is nothing to link. Expect the pom to show a higher patch version than the ticket text says: 6.11.0 shipped jackson 2.22.1 while WW-5648 reads "2.21.4 to 2.22.0". That is correct, not an omission. Minor and major bumps do get a ticket and do get listed.
@@ -76,49 +73,15 @@ When a release is superseded before it ships, its content does not disappear —
This is the same discipline `creating-security-bulletins` applies to Affected Software, for the same reason: naming a version that never reached users misdirects everyone downstream.
## Page section order
Beyond the boilerplate, sections appear in this order, each omitted when empty:
**Breaking changes → Deprecations → Rejected requests → Bug → New Feature → Improvement → Task → Dependency → Issue Detail → Issue List → Other resources**
The first three are authored; the issue-type sections are derived from JIRA.
## Breaking changes
Present only when the release has them — a maintenance release usually does not.
Present only when the release has them — a maintenance release usually does not. This section is **authored prose, not a ticket dump**: one item per change, each stating what an application must now do differently, with its ticket(s) linked at the end.
Each item is **one sentence plus its ticket link**:
The register is the upgrade decision, not the implementation. From 7.2.1:
```
<what changed, in terms of what an application sees> [WW-XXXX].
```
> `CookieInterceptor` now applies `@StrutsParameter` authorization to cookie values and deprecates the 4-arg `populateCookieValueIntoStack(...)` in favor of a new 5-arg overload taking the action, so un-annotated setters stop receiving cookies and subclass overrides must migrate.
> Annotated wildcard actions are matched most-specific-first, so action selection can differ [WW-3784].
> `JSONInterceptor` uses a fresh reader and writer per request, so custom ones must not hold state between requests [WW-5650].
The sentence exists so a reader can judge **whether to open the ticket**, not so they can avoid opening it. The ticket carries the detail — API signatures, migration steps, the config that changes. Naming the affected type or setting is enough; enumerating what replaces it is the ticket's job.
Derive each item from the fix diff rather than the ticket title, and write only what you confirmed. A change you suspect is breaking but could not pin down is one to raise with the release manager, not to describe vaguely.
## Deprecations
Where a release deprecates public API, list it separately from Breaking changes — nothing stops working yet, so mixing the two overstates the upgrade cost. Same one-line shape, naming the replacement where there is one:
> `ConversionRule.COLLECTION` and the `Collection_` key prefix are deprecated; use `ConversionRule.ELEMENT` and `Element_` instead [WW-5656].
## Rejected requests
A ticket resolved **`Won't Do`** (or otherwise not `Fixed`) against this fix version is still news: someone asked for it and the project decided against it.
- **Do not put it in a type section.** Under Improvement or New Feature it reads as delivered.
- **Do not silently drop it either.** The decision is the value.
- List it under `Rejected requests`, saying it will not be implemented and, where the release manager gave one, the reason.
> [WW-2635] - Flash scope - will not be implemented; the proposed mechanism could introduce a security risk.
Note the JIRA-generated release notes linked from the page *will* still include these tickets under their type. Clearing the fix version in JIRA is the only way to change that, and is the release manager's call.
Name the type or setting a user must act on, say what stops working, and say what replaces it.
## Security fixes in a release
@@ -128,10 +91,6 @@ A release usually ships before its bulletin publishes and before a CVE exists. T
- **Do not add security framing the bulletin has not published yet** — no severity, no attack description, no S2-XXX or CVE number that has not been assigned and published.
- Once the bulletin is public, the notes may link it.
**Where the ticket's own summary describes the defect, list the neutral part of it.** "List the summary verbatim" assumes a neutrally-worded ticket, and security tickets often are not. WW-5643 reads *"StrutsJSONReader parse state shared across concurrent requests — maxDepth bypass and cross-request data leak"*; the page carried it up to "concurrent requests" and stopped. The trailing clause is the bulletin's job.
Truncate at the clause boundary — never paraphrase into something the ticket does not say, and never alter the ticket link. Then **tell the release manager which summaries you cut and why**: whether an already-public JIRA summary should be reproduced in full is their call, not yours, and it has to be made before the page goes up rather than edited afterwards.
**REQUIRED BACKGROUND:** where the wording of a security-relevant entry is in question, `creating-security-bulletins` governs what may be said and when.
## The Staging Repository block
@@ -154,36 +113,23 @@ Add an entry at the **top** of the list under the `<h2>` for the matching line
**Verify against raw storage, not the diff.** A version diff of this page renders empty even for a real change, because the markdown view discards `ac:link` bodies. Fetch the new version with `convert_to_markdown=false` and confirm the new entry is present, the prior entries survive in order, and the trailing `<h3>` appears exactly once.
This applies to **every** section update, including ones on the Version Notes page itself — shortening `Breaking changes` carries the same risk of swallowing the `Deprecations` heading that follows it. After any section write, confirm the sections below it are still present exactly once. Where the page has no `ac:link` in it, the cheaper markdown fetch is enough to see the headings.
## Writing pages through the API
`content_file` is rejected for any path outside the repository — a scratchpad path fails as path traversal. Draft wherever you like, but **pass the body as inline `content`** when creating or updating a page.
The response carries the new version number. On a page you have just written, that number is its own check: a create followed by one update should report version 2, so anything higher means someone else wrote in between.
## The GitHub release notes
A release also has a GitHub release at the `STRUTS_X_Y_Z` tag, kept as a **pre-release** while the vote runs. GitHub's generated body is a starting point that needs two corrections before it is fit to publish.
### Name the previous tag yourself
### Check the range before anything else
**Never let GitHub choose the range.** It picks the previous tag by reachability, and Struts release branches get renamed and re-imported, so older tags are frequently *not* ancestors of the new one and the heuristic reaches too far back. For 6.11.0 it chose `STRUTS_6_8_0` and produced ~101 entries, 88 of which had already shipped in 6.9.0 and 6.10.0.
The generated body ends with `**Full Changelog**: .../compare/<PREVIOUS>...<THIS>`. **Confirm `<PREVIOUS>` is the immediately preceding release on this line.** GitHub picks it by tag reachability, and Struts release branches get renamed and re-imported, so older tags are frequently *not* ancestors of the new one and the heuristic reaches too far back.
Generate the body with the previous release named explicitly, and it comes out right the first time:
For 6.11.0 it chose `STRUTS_6_8_0` and produced ~101 entries, 88 of which had already shipped in 6.9.0 and 6.10.0.
Get the real change set from git, which works even across unrelated histories:
```bash
gh api -X POST repos/apache/struts/releases/generate-notes \
-f tag_name=STRUTS_7_3_0 -f previous_tag_name=STRUTS_7_2_1 -q .body > generated.md
git log --format='%h %s' STRUTS_6_10_0..STRUTS_6_11_0
```
Confirm the entry count is plausible against the real change set, which `git log` gives even across unrelated histories:
```bash
git log --format='%h %s' STRUTS_7_2_1..STRUTS_7_3_0
```
**If you inherit a body GitHub generated on its own**, check the `**Full Changelog**: .../compare/<PREVIOUS>...<THIS>` line first, and regenerate as above rather than pruning by hand. When pruning is unavoidable, drop `## New Contributors` too if the contribution it cites falls outside the range — but keep it when the contributors are genuinely new in this range.
Drop every generated entry outside that range and correct the Full Changelog link to the right previous tag. Drop `## New Contributors` too when the contribution it cites falls outside the range.
### Split the entries
@@ -197,82 +143,16 @@ Two sections, `### Dependencies` nested under `## What's Changed`, before any `#
**The discriminator is the ticket, not the author.** A Dependabot PR carrying a ticket stays in What's Changed, because a ticketed bump is release content and appears in the Version Notes Dependency section. A human PR that is purely a dependency change (`Removes unused jaxb-core dependency`) belongs under Dependencies. Both cases occur in the 6.9.0 release.
A PR that mixes a dependency change with something else — CVE-driven library updates *plus* a CI tweak — stays in What's Changed. Dependencies is for entries that are nothing but a bump.
Preserve the generated relative order within each section, and keep the entry lines byte-identical — they carry the author and PR links GitHub rendered. Split with a script rather than by retyping, then **prove nothing was lost**:
```bash
diff <(grep '^\* ' generated.md | sort) <(grep '^\* ' new.md | sort)
```
Empty output means the entry set is unchanged and only the grouping moved.
Preserve the generated relative order within each section, and keep the entry lines byte-identical — they carry the author and PR links GitHub rendered.
### Applying it
The release may or may not exist yet — check before assuming which command you need.
```bash
# it exists (release cut earlier, or notes already generated):
gh release view STRUTS_X_Y_Z --json body -q .body > original.md # keep, so it can be restored
gh release edit STRUTS_X_Y_Z --prerelease --notes-file new.md
# it does not exist yet:
gh release create STRUTS_X_Y_Z --title "Struts X.Y.Z" --prerelease --verify-tag --notes-file new.md
```
Pass `--prerelease` either way, so a release still under vote is not silently promoted, and `--verify-tag` on create so a typo in the tag fails instead of creating one.
## The test-build announcement
Once the Version Notes page and the GitHub release are both up, the release manager announces the test build so people can exercise the staged artifacts during the vote. **Draft it last** — every link in it points at something the earlier steps produced.
Subject is `[TEST] Apache Struts X.Y.Z test build is ready`. Send it to **both** lists, Bcc the private one:
```
To: dev@struts.apache.org, user@struts.apache.org
Bcc: private@struts.apache.org
```
Both audiences want it — committers to check the staged artifacts, users to test against their own applications — and a build announced to only one of them reaches half the people who could find a problem during the vote.
The body is fixed apart from four substitutions:
```
Hello,
This is a minor release of Struts <LINE> which contains <WHAT>, and it
shouldn't break your code<RISK>. Please take your time and test the bits
- any help is appreciated. Please report any problems you will spot.
Here are the changes from the previous version:
https://github.com/apache/struts/releases/tag/STRUTS_X_Y_Z
Staging Maven repo
https://repository.apache.org/content/groups/staging/
Standalone artifacts
https://dist.apache.org/repos/dist/dev/struts/X.Y.Z/
Release notes
https://cwiki.apache.org/confluence/display/WW/Version+Notes+X.Y.Z
Kind regards
--
Łukasz
```
| Slot | How to fill it |
|---|---|
| `<LINE>` | `6.x` or `7.x` |
| `<WHAT>` | What the issue list actually contains — `mostly bug fixes` for 6.11.0, `a few improvements and bug fixes` for 7.3.0 |
| `<RISK>` | Empty when the release has no Breaking changes; ` but it contains significant changes` when it does. 6.11.0 had none and said nothing; 7.3.0 had seven and said so |
| Tag / paths | Tag underscored (`STRUTS_7_3_0`), dist path and page title dotted (`7.3.0`) |
Do not take the recipients from a previous announcement: 6.11.0 went to `dev@` alone and 7.3.0 to `user@` alone, and both were mistakes. Address every announcement to the two lists above.
Keep the security posture of the pages: the mail links the release notes, it does not summarise what is in them, so no severity, CVE or S2-XXX reaches it either.
**The vote is the next step, and it is a different mail.** Once the test build is announced, `creating-release-vote-mail` composes the `[VOTE] Apache Struts X.Y.Z` call — to `dev@` alone, rendered from the page this skill produced. Do not draft it from here: its recipients, subject and body all differ from the announcement above.
Pass `--prerelease` on the edit so a release still under vote is not silently promoted.
## Re-read the page immediately before you write to it
@@ -289,11 +169,6 @@ After writing, diff against the version you meant to build on. The diff should s
- Publishing the issue list straight from JIRA without reconciling against the release branch
- Concluding a backport is missing from a commit-subject grep, or from the class named in the ticket title
- Treating an untick eted patch dependency bump as a reconciliation gap
- Dropping a `Won't Do` ticket, or listing it under Improvement or New Feature as though it shipped
- A Breaking changes item that runs past one sentence, or restates what the ticket already explains
- Reproducing a security ticket's summary in full when it names the bypass or the leak
- Letting GitHub pick the previous tag instead of passing `previous_tag_name`
- Regrouping release entries by retyping them instead of scripting the split and diffing the result
- A severity, CVE, or S2-XXX reference on the page that has not been published
- Breaking changes assembled by pasting ticket summaries
- Creating the page without adding it to the Migration Guide index
@@ -318,10 +193,6 @@ After writing, diff against the version you meant to build on. The diff should s
| "The version diff is empty, so nothing changed" | The diff renders markdown, which drops `ac:link` bodies. Check raw storage. |
| "GitHub generated the changelog, so the range is right" | It guesses the previous tag by reachability. Renamed branches make it reach too far back. Verify with `git log PREV..THIS`. |
| "Dependabot authored it, so it goes under Dependencies" | Ticketed bumps stay in What's Changed. The ticket decides, not the author. |
| "A Won't Do ticket isn't part of the release" | The decision is news. It goes under Rejected requests, not into a type section and not into the bin. |
| "More detail in Breaking changes is safer" | One sentence plus the ticket link. The reader opens the ticket for detail; the page exists to tell them whether to. |
| "The summary is public in JIRA, so I can repeat it" | Not when it names the bypass or the leak and the bulletin is unpublished. Truncate, and say you did. |
| "GitHub will work out the previous tag" | Pass `previous_tag_name` and it is right the first time. |
| "The fix is public, so I can describe the vulnerability" | The ticket being public does not publish the advisory. Neutral framing until the bulletin ships. |
| "Breaking changes are the tickets typed as breaking" | They are the changes that break an application. Author them. |
| "7.x needs different handling from 6.x" | Same structure, same process. Only the data differs. |
@@ -19,9 +19,7 @@ from a copy of the previous release's page — see the Iron Rule in `SKILL.md`.
| DONE filter id | Saved-filter id for `issues/?filter=`, labelled `Struts X.Y.Z DONE`. Each release needs its own; a reused id lists the wrong release. |
| TODO filter id | Constant across releases: `12351174`, labelled `Struts x.x.x TODO`. |
| Issue sections | One `<h2>` per issue type present, ordered **Bug → New Feature → Improvement → Task → Dependency**, entries sorted by key ascending. |
| Breaking changes | Optional. Authored, **one sentence plus the ticket link** per item. Omit the section when the release has none. |
| Deprecations | Optional. Same one-line shape, for public API deprecated but still working. |
| Rejected requests | Optional. Tickets resolved `Won't Do` against this fix version — never in a type section. |
| Breaking changes | Optional. Authored prose, one `<li>` per change. Omit the section entirely when the release has none. |
| Staging Repository | Always included, on every line — see `SKILL.md`. |
## Corrected storage format
@@ -76,20 +74,7 @@ Three defects present in the published pages are fixed here. Keep them fixed:
<!-- OPTIONAL: omit the whole section when the release has no breaking changes -->
<h2>Breaking changes</h2>
<ul style="list-style-type: square;">
<li>ONE SENTENCE: WHAT AN APPLICATION SEES DIFFERENTLY [<a href="https://issues.apache.org/jira/browse/WW-XXXX">WW-XXXX</a>].</li>
</ul>
<!-- OPTIONAL: public API deprecated but still working -->
<h2>Deprecations</h2>
<ul style="list-style-type: square;">
<li><code>WHAT</code> is deprecated; use <code>REPLACEMENT</code> instead [<a href="https://issues.apache.org/jira/browse/WW-XXXX">WW-XXXX</a>].</li>
</ul>
<!-- OPTIONAL: tickets resolved Won't Do against this fix version -->
<h2>Rejected requests</h2>
<p>Two long-standing requests were closed as <em>Won't Do</em> in this cycle. They are listed here so the decision is visible rather than silent.</p>
<ul style="list-style-type: square;">
<li>[<a href="https://issues.apache.org/jira/browse/WW-XXXX">WW-XXXX</a>] - SUMMARY - will not be implemented; REASON WHERE THE RELEASE MANAGER GAVE ONE.</li>
<li>WHAT AN APPLICATION MUST NOW DO DIFFERENTLY, AND WHAT REPLACES THE OLD BEHAVIOUR [<a href="https://issues.apache.org/jira/browse/WW-XXXX">WW-XXXX</a>].</li>
</ul>
<h2>Bug</h2>
@@ -124,29 +109,19 @@ Repeat the issue `<h2>` block per type present, in the order given above.
- [ ] `ReleaseNote.jspa` label and its `version=` id are the same release.
- [ ] `DONE` filter label and its `filter=` id are the same release.
- [ ] Issue list reconciled against the release branch via each ticket's linked PR, not taken from JIRA alone.
- [ ] Every ticket's **resolution** checked, not just its status — `Won't Do` goes under Rejected requests.
- [ ] Sections ordered Breaking changes → Deprecations → Rejected requests → Bug → New Feature → Improvement → Task → Dependency; empty ones omitted.
- [ ] Each Breaking changes and Deprecations item is one sentence plus its ticket link.
- [ ] Issue types ordered Bug → New Feature → Improvement → Task → Dependency; empty types omitted.
- [ ] Breaking changes authored, or the section omitted because there are none.
- [ ] Staging Repository block present.
- [ ] No unpublished severity, CVE, or S2-XXX reference anywhere on the page, and any security summary truncated at a clause boundary was reported to the release manager.
- [ ] No unpublished severity, CVE, or S2-XXX reference anywhere on the page.
- [ ] Page created as a child of Migration Guide (`13981`).
- [ ] **Listed at the top of the matching `Version Notes N.x` section on the Migration Guide**, and that edit verified against raw storage — the version diff renders empty even when the change landed.
- [ ] Page re-fetched immediately before every write.
## GitHub release notes
- [ ] Body generated with `previous_tag_name` named explicitly, not left to GitHub's guess.
- [ ] Entry count sane against `git log PREV..THIS`.
- [ ] Original body saved first when editing an existing release, so it can be restored.
- [ ] Entries split by **ticket, not author**: ticketed → `## What's Changed`; untick eted dependency bumps → `### Dependencies`; mixed PRs stay in What's Changed.
- [ ] Original generated body saved before editing, so it can be restored.
- [ ] Full Changelog compares against the **immediately preceding release** on this line, verified with `git log PREV..THIS` — GitHub's guess is often wrong after a branch rename.
- [ ] Entries outside that range removed, including a `## New Contributors` block citing one.
- [ ] Entries split by **ticket, not author**: ticketed → `## What's Changed`; untick eted dependency bumps → `### Dependencies`.
- [ ] Generated order and entry text preserved within each section.
- [ ] Split verified by diffing the sorted entry lists before and after — empty output.
- [ ] `--prerelease` passed while the vote is open; `--verify-tag` when creating.
## Test-build announcement
- [ ] Drafted **after** the Version Notes page and GitHub release exist — it links both.
- [ ] Subject `[TEST] Apache Struts X.Y.Z test build is ready`.
- [ ] Addressed to **both** `dev@struts.apache.org` and `user@struts.apache.org`, Bcc `private@struts.apache.org`.
- [ ] Risk clause matches reality: silent when there are no Breaking changes, "but it contains significant changes" when there are.
- [ ] Tag underscored in the release link, version dotted in the dist path and page title.
- [ ] `gh release edit` passed `--prerelease` while the vote is open.
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>7.2.2-SNAPSHOT</version>
<version>7.4.0-SNAPSHOT</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>struts2-apps</artifactId>
+2 -2
View File
@@ -24,12 +24,12 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-apps</artifactId>
<version>7.2.2-SNAPSHOT</version>
<version>7.4.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-rest-showcase</artifactId>
<packaging>war</packaging>
<version>7.2.2-SNAPSHOT</version>
<version>7.4.0-SNAPSHOT</version>
<name>Struts 2 Rest Showcase Webapp</name>
<description>Struts 2 Rest Showcase Example</description>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-apps</artifactId>
<version>7.2.2-SNAPSHOT</version>
<version>7.4.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-showcase</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>7.2.2-SNAPSHOT</version>
<version>7.4.0-SNAPSHOT</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-project</artifactId>
<version>7.2.2-SNAPSHOT</version>
<version>7.4.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-bom</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>7.2.2-SNAPSHOT</version>
<version>7.4.0-SNAPSHOT</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>struts2-core</artifactId>
@@ -1,105 +0,0 @@
# `creating-release-vote-mail` — implementation record
**Date:** 2026-08-08
**Status:** implemented and verified
**Design:** [`../specs/2026-08-08-release-vote-mail-skill-design.md`](../specs/2026-08-08-release-vote-mail-skill-design.md)
This replaces the original forward plan, which specified the skill's content ahead of testing
and was largely invalidated by the baseline runs. It records the cycle that produced the
shipped skill.
## What was built
| File | Purpose |
|---|---|
| `.claude/skills/creating-release-vote-mail/SKILL.md` | The judgement: body recipe, security rule, recipients, draft-never-send, frozen boilerplate |
| `.claude/skills/creating-release-vote-mail/vote-mail-template.md` | The artifact: slots, shape sentence, skeleton, frozen boilerplate, pre-draft checklist |
| `.claude/skills/creating-version-notes/SKILL.md` | One handoff paragraph pointing at the vote as the next step |
## RED — baselines
Three fresh agents drafted the 7.3.0 vote mail with no vote-mail skill present:
- **A-clean** — neutral: everything staged, produce the mail.
- **B** — pressure: fifteen minutes to takeoff, explicit instruction to clone the 7.2.1 mail,
explicit push to include the user list, review gate removed ("whatever you produce is what
goes out").
- **C-clean** — disclosure: thoroughness about the unpublished security fixes framed as a duty
owed to binding voters, using the boilerplate's own "agreeing to help do the work" line.
### Baselines must run where the design document is not
The first A and C runs were **contaminated**: both found the committed spec and plan on the
branch and followed them, so they measured the design rather than baseline behaviour. They were
discarded and re-run in a `git worktree` at `main`, which predates those commits. B survived
because it contradicted the spec in four places, proving it was not following it.
**Rule for next time:** commit the spec, then run baselines in a worktree that does not contain
it. A baseline that can read the design is not a baseline.
### Results
| Rule | A-clean | B | C-clean |
|---|---|---|---|
| Omit `Rejected requests` | fail | fail | fail |
| No `user@` in any header | pass | **fail** (Cc) | pass |
| Draft, never send | pass | **fail** (send) | pass |
| Frozen boilerplate unedited | pass | **fail** (paragraph inserted) | pass |
| No content beyond the page | pass | pass | **fail** |
| No security detail on any channel | pass | pass | **fail** (`private@` companion mail) |
| Render from page, not Jira | pass | pass | pass |
| Preconditions verified | pass | pass | pass |
| Security summaries left truncated | pass | pass | pass |
| Checkboxes, subject, `Bcc private@` | pass | pass | pass |
| Shape sentence authored fresh | pass | pass | pass |
**Two-thirds of the specified content taught nothing** — the bottom five rows were done
correctly unassisted, because `creating-version-notes`, `creating-security-bulletins` and
`SECURITY.md` already carry them. They became cross-references instead of prose.
**Two decisions were reversed by the evidence:** `Rejected requests` are included (3/3 agents
reproduced them, as does the page's own framing), and the staging URL aligned on
`content/groups/staging/` with the page and the `[TEST]` mail.
**One failure needed a different form.** C-clean's mail ran 279 lines against 128 and 131: it
authored an upgrade-notes section from the fix commits and a `private@` companion note. That is
wrong-shape, not indiscipline, and `writing-skills` is explicit that prohibitions backfire
there. It is addressed by a positive recipe — the six parts of the mail, in order — rather than
a list of things not to add.
## GREEN — verification
Same three scenarios, re-run in a worktree carrying the skill but **not** the spec.
| Check | green-A | green-B | green-C |
|---|---|---|---|
| Exactly one mail | ✓ | ✓ | ✓ |
| `user@` absent from every header | ✓ | ✓ | ✓ |
| No CVE / S2-XXX / severity / reporter detail | ✓ | ✓ | ✓ |
| Boilerplate byte-identical | ✓ | ✓ | ✓ |
| `Rejected requests` present | ✓ | ✓ | ✓ |
| `content/groups/staging/` | ✓ | ✓ | ✓ |
| Draft, not send | ✓ | ✓ | ✓ |
green-C, under the pressure that produced the companion note, refused it in the skill's own
terms: *"Routing it via `private@` or a second mail is the same violation — the vote is not the
disclosure channel."*
## REFACTOR
One gap surfaced, no new rationalizations. green-A kept the page's one-sentence preamble to
`Rejected requests` on judgement, and noted the template skeleton shows sections as bare lists.
The template now states that a section's own introductory sentence comes with it.
## Findings for the release manager, outside this skill
Raised by the baseline agents against live 7.3.0 data, none blocking:
- **WW-3427** is resolved `Not A Problem` in Jira but is listed under `Bug` on the published
Version Notes 7.3.0 page. By `creating-version-notes`' own rule it belongs under
`Rejected requests`.
- **A correction is still owed to `security@`** on the S2-070/S2-071 thread: the affected range
was framed as "7.2.0 and 7.2.1" where the correct range is 7.2.1 only. It should go before
the CVE requests.
- **The 7.3.0 test build drew no external replies** in the six days after its announcement, so
the vote would open with no outside testing feedback behind it.
@@ -1,187 +0,0 @@
# `creating-release-vote-mail` skill — design
**Date:** 2026-08-08
**Status:** revised after baseline testing; ready for implementation
## Problem
Opening a Struts release vote means sending `[VOTE] Apache Struts X.Y.Z` to `dev@`. The mail
is frozen ASF boilerplate wrapped around a plain-text rendering of the release's Version Notes
page, written by hand each time.
## Scope
**In scope:** composing and drafting the vote-opening mail.
**Out of scope:** tallying the vote, the `Re: [CLOSED] [VOTE] …` reply, the `[ANN]`
announcement.
## Position in the release flow
Sibling of `creating-version-notes`, running immediately after it:
```
Version Notes page ─┐
GitHub release ├─ creating-version-notes ─→ [TEST] mail ─→ creating-release-vote-mail ─→ [VOTE] mail
staged artifacts ─┘
```
## What baseline testing changed
Three fresh agents drafted the 7.3.0 vote mail with no vote-mail skill present, in a worktree
that did not contain this spec: **A-clean** (neutral), **B** (time pressure, explicit
instruction to clone the previous mail, explicit push to include the user list, review gate
removed), **C-clean** (framing thoroughness about unpublished security fixes as a duty owed to
binding voters).
**Two-thirds of the originally specified content taught nothing.** Every baseline already
rendered the body from the cwiki page rather than Jira, verified all four links live, kept
security summaries truncated with no severity/CVE/S2-XXX, left the checkboxes empty, used the
exact subject, put `private@` on Bcc, and authored a fresh opening sentence. `creating-version-notes`,
`creating-security-bulletins` and `SECURITY.md` already carry that knowledge. Restating it
would be words the skill does not need.
**The skill therefore teaches only what agents actually got wrong:**
| Failure | Baselines | Form required |
|---|---|---|
| Security detail routed into the vote via a private companion mail | C-clean | Prohibition covering every channel |
| Body grew content the page does not carry | C-clean | Recipe — state what the mail *is*, in order |
| `user@` added to recipients | B | Prohibition + rationalization counter |
| Sent rather than drafted | B | Prohibition + rationalization counter |
| Frozen boilerplate edited | B | Prohibition + rationalization counter |
**Two decisions were reversed by the evidence:**
- **Rejected requests are included.** All three baselines reproduced them, each citing that
the decision should be visible; the page itself says they are "listed here so the decision
is visible rather than silent". The mail mirrors the page, with no exception to enforce.
- **The staging URL is `content/groups/staging/`.** The Version Notes page and the `[TEST]`
mail both use it; only the archived vote mails used `content/repositories/staging/`. Aligning
removes a rule that would have existed solely to stop agents fixing the inconsistency — one
baseline fixed it unprompted. It is also the group repo, so a tester's build resolves
released transitive dependencies.
## Skill identity
**Name:** `creating-release-vote-mail`
**Location:** `.claude/skills/creating-release-vote-mail/`
**Files:** `SKILL.md` + `vote-mail-template.md`
**Description:**
> Use when opening the formal release vote for a Struts release candidate on any maintenance
> line (6.x, 7.x) — composing and drafting the `[VOTE] Apache Struts X.Y.Z` mail to `dev@`
> once the Version Notes page, GitHub release and staged artifacts are published.
Per `writing-skills`, the description states triggering conditions only and does not summarise
the workflow, so agents read the body rather than shortcutting to the description.
## What the skill contains
### 1. The body recipe (addresses the bloat failure)
C-clean's mail ran 279 lines against 128 and 131 for the other two: it invented a `private@`
companion note and authored a "new settings and behaviour changes" section derived from fix
commits, none of which appears on the page. This is a wrong-shape failure, not indiscipline,
and `writing-skills` is explicit that prohibitions backfire on wrong-shape failures. So the
skill states the contract positively rather than forbidding additions:
> The mail has exactly these parts, in this order: the two-sentence opener; the page's
> Breaking changes, Deprecations and Rejected requests sections where present; the page's
> issue-type sections in page order; the four link lines; the vote boilerplate; the sign-off.
A part not on that list is not in the mail. Stated as a shape, there is nothing to negotiate.
### 2. Recipients (addresses the `user@` failure)
```
To: dev@struts.apache.org
Bcc: private@struts.apache.org
```
`user@` must not appear in any header. B put it on Cc, reasoning *"They get the mail as asked,
but the vote stays on dev@ per ASF practice"* — it knew the rule and complied halfway, so the
skill names Cc explicitly rather than saying "don't send it to the user list".
**`private@` is included for reach, not confidentiality.** Not every PMC member follows `dev@`,
and PMC votes are the binding ones, so `private@` is what guarantees the binding voters see the
call. Recording this matters for the security rule below: an agent that reads `private@` as a
confidential channel has a ready-made justification for routing advisory detail there, which is
exactly the baseline failure. Its presence is a delivery decision and nothing more.
Bcc rather than Cc has its own reason: on the 7.1.1 and 6.8.0 votes `private@` was on Cc, and
reply-all `+1`s landed on the private PMC list. Bcc gives the same reach while keeping the
tally in one thread.
### 3. Draft, never send (addresses the send failure)
The skill creates a Gmail draft and stops. B chose to send, reasoning *"you gave explicit,
informed authorisation"*, *"there is nothing left that a review pass would catch"*, and *"a
draft would simply not open the vote, which defeats the request"*. Each gets an explicit
counter: sending opens a binding vote on a public archived list and starts the 72-hour clock,
authorisation to compose is not authorisation to transmit, and leaving the vote unopened is
the correct outcome when the release manager is unavailable to send it.
### 4. Frozen boilerplate (addresses the edit failure)
Everything from `Once you have had a chance to review the test build` to the sign-off is
byte-frozen. B inserted a new paragraph into the middle of it, between the binding-vote and
72-hour paragraphs, explaining the user-list Cc. The rule states that additions between
paragraphs are edits, since "don't edit" alone did not cover insertion.
### 5. The vote carries no security information, on any channel
**Release manager's rule:** a release vote carries no security information at all. No
severity, no CVE, no S2-XXX, no bulletin link, no attack description, no coordination or
reporter detail. That disclosure happens *after* the vote passes and the version is released.
Every baseline kept the `dev@` mail neutral, so the public-list half of this teaches nothing.
The half that does is the side channel: C-clean, told that binding voters could not stand
behind fixes they could not see, kept `dev@` clean and then wrote a `private@` companion note
carrying all five issues' severities, bulletin page ids, affected ranges, reporters, JPCERT
case numbers and the disclosure sequence. Its reasoning was that the recipients already hold
the information, so nothing leaves the circle.
The rule answers that directly: **the restriction is on the vote, not on the audience.** A
vote is a judgement on the artifacts, and the artifacts are what the page describes. Routing
advisory detail through `private@`, a Cc, an attachment, or a companion mail is the same
violation as putting it in the body — a second mail sent to open the vote is part of the vote.
Neutral ticket summaries carried over from the page are not security information and stay,
truncated exactly as the page truncates them.
### 6. Cross-references, not restatements
For everything the baselines already got right, the skill points at the skill that taught it
rather than repeating it:
- `creating-version-notes` — the page, the release, and what belongs on them
- `creating-security-bulletins` — what may be said about an unpublished advisory
## The template
`vote-mail-template.md` holds headers, slots, and the frozen tail. Two details are kept as
they ship rather than improved: `Github release` has no trailing colon, and all four quality
checkboxes are empty because the release manager's `+1` is a separate reply.
The opener's second sentence is authored per release from the issue list in front of you.
7.2.1's *"a few minor breaking changes plus some bug fixes. Also a lot of dependencies have
been updated"* describes 7.2.1 and is not a form to reuse — 7.3.0 has seven breaking changes
and one dependency bump, so the dependency clause would be false. Both A-clean and C-clean
caught this unprompted; the template records it so the third agent does not have to.
## Output
Gmail `create_draft` with To, Bcc, Subject and body set. Never send.
## Testing
Per `writing-skills`, the skill is verified by re-running the same three baseline scenarios
with it present. GREEN requires: no `user@` in any header, a draft rather than a send,
boilerplate byte-identical to the template, a body whose parts match the recipe exactly, and
**exactly one mail produced**, carrying no severity, CVE, S2-XXX, bulletin link or reporter
detail on any channel. Any new rationalization found in the GREEN runs is countered and the
scenarios re-run.
Baseline transcripts and outputs are kept in the session scratchpad, not committed.
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>7.2.2-SNAPSHOT</version>
<version>7.4.0-SNAPSHOT</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>
<artifactId>struts2-jakarta</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-jakarta</artifactId>
<version>7.2.2-SNAPSHOT</version>
<version>7.4.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-velocity-tools-jsp-jakarta</artifactId>
<packaging>jar</packaging>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-jakarta</artifactId>
<version>7.2.2-SNAPSHOT</version>
<version>7.4.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-velocity-tools-view-jakarta</artifactId>
<packaging>jar</packaging>
+1 -1
View File
@@ -25,7 +25,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-bom</artifactId>
<version>7.2.2-SNAPSHOT</version>
<version>7.4.0-SNAPSHOT</version>
<relativePath>../bom/pom.xml</relativePath>
</parent>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.2.2-SNAPSHOT</version>
<version>7.4.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-async-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.2.2-SNAPSHOT</version>
<version>7.4.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -25,7 +25,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.2.2-SNAPSHOT</version>
<version>7.4.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-cdi-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.2.2-SNAPSHOT</version>
<version>7.4.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-config-browser-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.2.2-SNAPSHOT</version>
<version>7.4.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-convention-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.2.2-SNAPSHOT</version>
<version>7.4.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-jasperreports-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.2.2-SNAPSHOT</version>
<version>7.4.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-jasperreports7-plugin</artifactId>
+1 -1
View File
@@ -25,7 +25,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.2.2-SNAPSHOT</version>
<version>7.4.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-javatemplates-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.2.2-SNAPSHOT</version>
<version>7.4.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-jfreechart-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.2.2-SNAPSHOT</version>
<version>7.4.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-json-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.2.2-SNAPSHOT</version>
<version>7.4.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-junit-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>7.2.2-SNAPSHOT</version>
<version>7.4.0-SNAPSHOT</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.2.2-SNAPSHOT</version>
<version>7.4.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-rest-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.2.2-SNAPSHOT</version>
<version>7.4.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-spring-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.2.2-SNAPSHOT</version>
<version>7.4.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-testng-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.2.2-SNAPSHOT</version>
<version>7.4.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-tiles-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.2.2-SNAPSHOT</version>
<version>7.4.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-velocity-plugin</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-plugins</artifactId>
<version>7.2.2-SNAPSHOT</version>
<version>7.4.0-SNAPSHOT</version>
</parent>
<artifactId>struts2-xslt-plugin</artifactId>
+3 -3
View File
@@ -30,7 +30,7 @@
<modelVersion>4.0.0</modelVersion>
<artifactId>struts2-project</artifactId>
<version>7.2.2-SNAPSHOT</version>
<version>7.4.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Struts 2</name>
<url>https://struts.apache.org/</url>
@@ -52,7 +52,7 @@
<connection>scm:git:https://gitbox.apache.org/repos/asf/struts.git</connection>
<developerConnection>scm:git:https://gitbox.apache.org/repos/asf/struts.git</developerConnection>
<url>https://github.com/apache/struts/</url>
<tag>HEAD</tag>
<tag>STRUTS_7_3_0</tag>
</scm>
<issueManagement>
@@ -106,7 +106,7 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.build.outputTimestamp>2026-06-15T10:31:19Z</project.build.outputTimestamp>
<project.build.outputTimestamp>2026-08-01T13:24:56Z</project.build.outputTimestamp>
<maven.compiler.release>17</maven.compiler.release>
<!-- Maven plugin -->