release-verify-rc: validate rc jars
- Dominant language
- Python
- Stars
- 92
- Forks
- 92
- Avg merge
- 1d 12h
- Merged PRs (30d)
- 104
Description
I had Claude AI draft this. It is pretty verbose but I have read the text to validate that there is nothing obviously incorrect.
I'm aware that it would be useful to validate other convenience binaries or packages (cargo files, pypi releases, etc.) but I think they would need their own separate discussions.
### What should happen
`release-verify-rc` should validate **published JVM artefacts** — the jar, its
companion `.pom`, and the `-sources.jar` / `-javadoc.jar` published alongside
them — not just treat `.jar` as contraband inside the source tree. Four
blocking checks, three informational ones.
**Blocking (FAIL):**
1. **`.pom` license entry.** The companion POM must declare Apache-2.0 in its
`` block, plus `` and ``.
2. **Incubator disclaimer in ``** — for podlings only. The
POM's `` element must contain the incubation disclaimer text.
3. **Companion `-sources.jar` and `-javadoc.jar` present and signed.** For
every published jar, both companions must exist and each must carry its own
`.asc` signature and checksums — the same coverage the main jar gets today.
4. **Nexus staging repository at `repository.apache.org`** — ASF projects
only. Verify the staged repo is `closed` (not `open`), that its coordinates
and version match the RC under vote, and that every artefact in it carries
its `.asc` and checksums with a complete companion set.
**Informational (INFO / WARN, never FAIL):**
5. **Reproducibility signal from jar entry timestamps.** If every entry in the
jar shares one identical timestamp, the project has almost certainly set
`project.build.outputTimestamp`. If timestamps vary across entries, it
almost certainly has not. Report the observation; do not assert
reproducibility either way.
6. **`org.apache` namespace and package/`groupId` correspondence.** Two
related observations, **both informational even for ASF projects**:
(a) whether the declared `groupId` sits under `org.apache.*`, and
(b) whether the class-file path roots in the jar (`org/apache/foo/...`)
correspond to that `groupId` (`org.apache.foo`). Report what is observed;
never fail on either.
7. **Companion jar content sanity.** Whether the `-sources.jar` actually
contains `.java` (or `.scala` / `.kt`) sources and no `.class` files, and
whether the `-javadoc.jar` carries generated documentation or is a
placeholder. Both observations only — see boundary conditions, placeholder
jars are explicitly permitted by Central.
### Why
**The two blocking checks are stated ASF Incubator policy**, verbatim from
[the Incubator distribution guidelines § Maven distribution](https://incubator.apache.org/guides/distribution.html):
> To comply with ASF release and distributions, please ensure the following:
>
> - The project description should include the incubator disclaimer.
> - Artifacts should be under the groupId of **org.apache.\**.
> - The official release artifacts should not contain unapproved code.
> - Release candidates, nightlys or snapshots need to be clearly marked by
> adding suffix in their version, like
> **org.apache.\.\:0.1-SNAPSHOT**
> - The POM file should set the ALv2 license as its license, Apache
> \ as its developer name, and proper source control information.
Nothing in the framework checks any of these. Today the `release-*` family
handles jars in exactly three places, none of which look inside one:
| Where | What it does |
|---|---|
| `skills/release-verify-rc/SKILL.md:440`, `:457` | Puts `.class` / `.jar` on the **prohibited-binary** baseline and greps the unpacked source tree for them — the *opposite* of validating a jar |
| `skills/release-verify-rc/SKILL.md:239` | Step 2/3 extend GPG + checksum verification to "any convenience binary", so a jar gets byte-integrity coverage — but Steps 4–8 (RAT, NOTICE/LICENSE, binary exclusion, symlinks, version strings) all operate on the unpacked **source tree** |
| `skills/pr-management-code-review/criteria.md:306` | Flags `.jar` / `.war` / `.ear` as binaries that shouldn't land in a PR — again contraband |
`dependency-license-audit` does cover Maven, but via `license-maven-plugin`
against `pom.xml` **dependency trees** (`SKILL.md:210`, `:287`) — it never
opens a built artefact.
The informational checks (3) and (4) are cheap signals a reviewer currently
has to derive by hand, and both catch real mistakes: a jar built without
`outputTimestamp` when the project claims reproducible builds, and a groupId
that was never renamed to `org.apache.` after entering incubation.
Note that although the `org.apache.` groupId bullet above *is* quoted
policy, the corresponding check is deliberately proposed as **informational,
not blocking, for ASF projects as well as podlings** — see boundary conditions.
It is a signal for a human reviewer, not a gate.
**On the companion jars (checks 3 and 7).** These are a hard Maven Central
requirement, not a nicety —
[Central's publishing requirements](https://central.sonatype.org/publish/requirements/)
state that "Projects with packaging other than `pom` have to supply JAR files
that contain Javadoc and sources." A staged repo missing them will fail Nexus's
close-time validation, so an RM who discovers this during the vote has to
re-cut. `release-verify-rc` should catch it before the `[VOTE]` opens. Today
`projects/_template/release-build.md` models an "Expected artefact list" that
distinguishes canonical source from convenience binaries, but has no notion of
the `-sources.jar` / `-javadoc.jar` / `.pom` set that must accompany each jar.
**On `repository.apache.org` (check 4).** For a JVM ASF project the Nexus
staging repository *is* the release — the `dist/` tree carries the voted source
artefact, but the jars downstream consumers actually resolve come from the
staging repo promoted to Maven Central. The framework models the `dist/` half
in detail and the Nexus half not at all: grepping `nexus|repository.apache.org`
across `skills/`, `docs/release-management/`, and `projects/_template/` returns
nothing. That leaves three concrete holes:
- `release-verify-rc` cannot confirm the staged artefacts correspond to the RC
being voted on.
- `release-vote-draft` does not emit the staging-repo URL, which a JVM `[VOTE]`
thread must carry for voters to check the jars at all.
- `release-promote` handles only the `svn mv dist/dev → dist/release` sequence
(`SKILL.md:51`); it has no counterpart for the Nexus
**close / drop / promote** lifecycle that runs in parallel with it.
Per [the infra guide](https://infra.apache.org/publishing-maven-artifacts.html),
`mvn release:perform` "automatically inserts the release into a temporary
staging repository"; the RM then *closes* it to "trigger the quality checks and
prepare it for a vote", *drops* it if the vote fails, and on success promotes
"the artifacts to the release repository where they will get picked up by
Central".
### Which layer
Skill (`.claude/skills//`)
### Boundary conditions
**Check 1 — POM license**
- Inherited POMs. `` is commonly inherited from the ASF parent POM
(`org.apache:apache`) rather than declared locally. The check must resolve
against the **effective** POM, or explicitly report "inherited from parent,
not verified" rather than failing a correct POM.
- Multi-module reactors publish one POM per module; all must be checked, and
the aggregator POM (`pom`) has no jar.
**Check 2 — disclaimer in description**
- Only applies when the project stage is `incubating` (see #1172, which
proposes the `project_stage` plumbing this check depends on — these two
issues share that dependency).
- The Incubator permits **two** disclaimer texts (standard and
`DISCLAIMER-WIP`); matching must accept either, and should tolerate
whitespace/line-wrapping differences since the text is embedded in XML.
- The `` may also be inherited or absent entirely — absent is a
FAIL for a podling, inherited needs effective-POM resolution as above.
**Check 3 — companion jars**
- **`packaging=pom` modules are exempt.** Central's rule is explicit that it
applies to "packaging other than `pom`", so aggregator and BOM modules have
no jar and must not be flagged for missing companions.
- **Placeholder jars are legitimate.** Central states: "If you cannot provide
sources or documentation, we allow uploading placeholder `.jar` files in
order to pass validation." So an empty or near-empty `-javadoc.jar` is a
sanctioned pattern, not a defect. Check 7 must report it as an observation
and must never fail on it.
- **Non-Java JVM languages.** Scala and Kotlin projects publish scaladoc/dokka
output under the `-javadoc` classifier for Central compliance. Don't assert
a Javadoc-specific internal structure (`index.html`, `element-list`); check
for non-emptiness, not for a particular doc tool's layout.
- **Other classifiers are not companions.** `-tests`, `-shaded`, `-linux-x86_64`
and similar classified artefacts are not part of the required set and should
not be mistaken for it, in either direction.
- **`META-INF/LICENSE` and `META-INF/NOTICE` inside each jar — needs a policy
citation before it can be blocking.** In practice the ASF parent POM injects
these into every jar via `maven-remote-resources-plugin`, and it is widely
treated as required. But I could not confirm it verbatim:
[licensing-howto](https://infra.apache.org/licensing-howto.html) says only
that "LICENSE and NOTICE files belong at the top level of the source tree"
and that binary distributions may need different content — it does not state
a META-INF requirement for jars. **Someone should pin the authority before
this is implemented.** Proposed interim behaviour: WARN, not FAIL. The same
caveat applies to a podling DISCLAIMER inside each jar.
**Check 5 — timestamp reproducibility signal**
- This is a **heuristic, not a proof**. Only a rebuild-and-compare
demonstrates reproducibility. The report wording must not claim the jar *is*
reproducible — only that its timestamps are or aren't consistent with a
reproducible configuration.
- ZIP stores MS-DOS local times at 2-second granularity with no timezone, so
exact-equality comparison needs a tolerance and must not assume UTC.
- `META-INF/MANIFEST.MF` legitimately carries varying `Created-By` /
`Build-Jdk` values; those are a separate signal and shouldn't be conflated
with entry timestamps.
- An empty jar, or one with a single entry, gives no signal — report
`INSUFFICIENT-DATA`, not PASS.
**Check 6 — `org.apache` namespace and package vs groupId**
- **Both halves are INFO-only, including for ASF top-level projects.** The
`org.apache.` guidance is real policy, but a released artefact can
sit outside that namespace for legitimate historical reasons — established
projects that entered the ASF with existing Maven coordinates and kept them
for downstream compatibility, and modules published under coordinates
predating a rename. A FAIL would misfire on correct, long-standing releases,
and the groupId of a *published* artefact cannot be changed retroactively
anyway, so failing an RC over it gives the RM no available remedy.
- Divergence between package roots and groupId is likewise frequently
**legitimate**: shaded or
relocated dependencies, `META-INF/` entries, `module-info.class`,
multi-release jars (`META-INF/versions//`), and the very common case of a
groupId that intentionally differs from the package root. A FAIL here would
be wrong more often than right.
- Report correspondence as a proportion of class entries plus the actual
roots found, so the reviewer can judge — not a boolean.
- Useful specifically for podlings, where it surfaces whether the
`org.apache.` rename has actually happened.
**Check 4 — Nexus staging repository**
- **ASF-only.** Gate on the ASF organization adapter; skip entirely for
non-ASF adopters, consistent with the existing `--non-asf` flag on
`release-promote` (`SKILL.md:179`).
- **JVM-only.** Skip when the project publishes no Maven artefacts. A Python
or Rust ASF project has no staging repo and must not be warned about one.
- **Promotion is irreversible and must be gated accordingly.** Once a staging
repo is promoted it syncs to Maven Central, where artefacts are immutable and
can never be withdrawn — strictly less recoverable than the `dist/release/`
case that already justifies a hard denylist and a PMC gate
(`skills/release-promote/SKILL.md:92`, `:107`). Any promote/drop step this
work adds to `release-promote` needs an equivalent golden rule; a promote
must never be emitted as an autopilot action.
- **Verification stays read-only.** In `release-verify-rc` this check must only
*read* the staging repo. It must never close, drop, or promote — any voter
may run `release-verify-rc`, including someone with no karma on the repo.
- **Repo state matters.** An `open` (not yet closed) staging repo is still
mutable and is not a valid vote target; report that distinctly from a missing
repo. The snapshots repository
(`repository.apache.org/content/repositories/snapshots/`) is never a valid
vote target.
- **Staging repo id is not derivable.** Nexus assigns
`orgapache-NNNN` at deploy time; the number cannot be predicted, so
it must be read from the planning issue or passed in — the same way
`staging_url` is sourced today (`skills/release-promote/SKILL.md:241`).
- **Multiple staging repos.** A reactor deployed in more than one pass, or a
retried deploy, can leave several staging repos for one version. Surface all
of them rather than silently picking one; stale repos from an earlier RC are
a common real-world footgun.
- Checks 1–3 and 5–7 should run against the jars and POMs **in the staging
repo** when one is supplied, so every check shares a single artefact source.
**Family-wide**
- Non-JVM projects must be unaffected: all four checks skip cleanly when the
artefact set contains no jars.
- Requires a new config surface. `projects/_template/release-build.md` already
has an "Expected artefact list" that distinguishes canonical source from
convenience binaries; the companion `.pom` / `-sources.jar` / `-javadoc.jar`
set is not modelled there yet.
### Out of scope
- Rebuilding the jar to *prove* reproducibility — check 3 is a signal only.
- Bytecode target-level verification against the declared minimum JDK.
- **Automating** the Nexus close / drop / promote actions. This issue covers
read-only verification in `release-verify-rc`, the staging-repo URL in
`release-vote-draft`, and the **config surface plus emitted instructions**
for `release-promote`. Actually performing an irreversible promote on the
RM's behalf is out of scope.
- Relaxing the existing prohibited-binary rule at
`skills/release-verify-rc/SKILL.md:440`. Jars must still be absent from the
**source** artefact; this proposal validates separately-published jars.
### References
- § Maven distribution
— source of both blocking checks
- — the staging /
close / drop / promote workflow behind check 5
- `skills/release-promote/SKILL.md:51`, `:92`, `:107`, `:179`, `:241` — the
`dist/` promote sequence, denylist and PMC gate that the Nexus promote needs
a counterpart to
- — mandatory sources /
javadoc jars behind check 3, and the placeholder-jar exception behind check 7
- —
`project.build.outputTimestamp` semantics behind check 3
- #1172 — podling handling in the `release-*` family; check 2 depends on the
`project_stage` plumbing proposed there
- `skills/release-verify-rc/SKILL.md:239`, `:440`, `:457` — current jar handling
- `projects/_template/release-build.md` — expected-artefact-list config surface
Contributor guide
Research direction
Start with skills/release-verify-rc/SKILL.md, especially the cited checks around lines 239, 440, and 457, then review projects/_template/release-build.md and the release-vote-draft and release-promote skills. Confirm how project stage and published artefacts are represented, including the dependency on #1172. Done means the skill validates the specified JVM artefact, POM, companion-jar, and ASF Nexus conditions while reporting the informational checks without incorrectly failing permitted cases.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java
- Domain
- devops, release, tooling
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100