mirror of
https://github.com/apache/struts.git
synced 2026-08-09 00:27:04 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2e417d3b6d | |||
| f2f3a9d3ba |
@@ -0,0 +1,160 @@
|
||||
---
|
||||
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. |
|
||||
@@ -0,0 +1,149 @@
|
||||
# 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
|
||||
@@ -272,6 +272,8 @@ Do not take the recipients from a previous announcement: 6.11.0 went to `dev@` a
|
||||
|
||||
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.
|
||||
|
||||
## Re-read the page immediately before you write to it
|
||||
|
||||
Confluence has no conflict warning. Fetch the current version immediately before every write and compare the version number against the one you read; if it advanced, re-read, merge onto the newer content, and write that.
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
# `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.
|
||||
@@ -0,0 +1,187 @@
|
||||
# `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.
|
||||
Reference in New Issue
Block a user