Skip to content

ci: smoke-test the packaged jar and fail the release on an unsigned asset - #152

Merged
bernardladenthin merged 2 commits into
mainfrom
claude/release-asset-guards
Sep 1, 2026
Merged

ci: smoke-test the packaged jar and fail the release on an unsigned asset#152
bernardladenthin merged 2 commits into
mainfrom
claude/release-asset-guards

Conversation

@bernardladenthin

Copy link
Copy Markdown
Owner

Summary

Two release-asset guards, both cross-repo standards this repo was the last one missing.

  • Nothing in this pipeline ever loaded the assembled jar. Tests, jqwik, Lincheck, PIT, SpotBugs and javadoc all run off target/classes, while the jar in target/ is what gets attached to the release and deployed to Central — so a jar missing classes, carrying a broken module-info.class or assembled from a stale target/ passes an all-green pipeline. That gap is what shipped a corrupt native library through three java-llama.cpp releases, which is why the rule "no release asset is attached that CI has not run" exists.

    streambuffer was read as exempt because it ships no fat jar. That was a gap in the rule's wording, not an exemption: the rule is about the attached artifact, not the fat-jar shape. A library with no Main-Class cannot satisfy java -jar, so the new smoke-jar job does what a real consumer does — puts the packaged jar on a classpath and performs a write/read/EOF round-trip through the API via the JDK single-file source launcher. It additionally asserts the jar carries module-info.class, which is produced by a separate release 9 compile execution and can be dropped without failing anything else. ~10 s, no network.

  • An unsigned asset can no longer be attached quietly. The attach jobs collect target/*.jar.asc with || true, so a failed signing step yields an attach that looks complete and is not. The check cannot simply refuse to attach — both attach jobs deliberately run when the publish job failed, because if Central is unreachable the GitHub assets are the only way to get the build output at all. So it reports before the upload, uploads unconditionally, and fails the job afterwards. Byte-identical with the copies in java-llama.cpp, BitcoinAddressFinder and srcmorph.

Test plan

  • bash .github/smoke-jar.sh target 'streambuffer-*.jar' against the real packaged jar — packaged-jar smoke OK: 9 bytes round-tripped, smoke test PASSED
  • publish.yml parses as YAML; smoke-jar added to both publish jobs' needs:
  • The unsigned-asset guard block is byte-identical across all four sibling repos (checked by hash)
  • CI is green on this branch
  • Docs / CHANGELOG updated where applicable — rationale in workspace/policies/fat-jar-release-assets.md, updated in the companion workspace PR

The smoke jar glob excludes -sources/-javadoc unconditionally and requires exactly one match — an ambiguous glob is precisely how the wrong artifact gets smoke-tested while the shipped one is not.

Related issues / PRs

Cross-repo rollout; companion PRs in java-llama.cpp, BitcoinAddressFinder, srcmorph and workspace.

Checklist

  • I have read CONTRIBUTING.md and CODE_OF_CONDUCT.md
  • My commits follow Conventional Commits
  • No security-sensitive changes

Generated by Claude Code

claude added 2 commits August 31, 2026 22:25
The attach jobs collect `target/*.jar.asc` with `|| true`, so a signing step
that produced nothing yields an attach that looks complete and is not: the
jars land on the release without a signature and nothing says so.

The obvious fix -- verify, refuse to attach -- would defeat the reason both
attach jobs run on a failed publish job in the first place: when Central is
unreachable, the GitHub assets are the only way to get the build output at
all, so withholding them over a missing signature is the worst outcome
available.

So the check is split around the upload: report before it (never exits
non-zero, one annotation per unsigned jar, count written to the step
output), upload unconditionally, assert after it. Assets always land; an
unsigned release is loudly red instead of quietly wrong. -1 distinguishes
"nothing was collected at all" from a signing failure.

Byte-identical with the copies in srcmorph, BitcoinAddressFinder and
java-llama.cpp; see workspace/policies/fat-jar-release-assets.md.
Nothing in this pipeline ever loaded the assembled jar. Tests, jqwik,
Lincheck, PIT, SpotBugs and javadoc all run off target/classes, while the
jar in target/ is what gets attached to the GitHub release and deployed to
Central -- so a jar missing classes, carrying a broken module-info.class or
assembled from a stale target/ passes an all-green pipeline. That gap is
what shipped a corrupt native library through three java-llama.cpp
releases, which is why the cross-repo rule "no release asset is attached
that CI has not run" exists.

streambuffer was the one sibling without such a job, because the rule was
written around fat jars and this repo ships none: a library with no
Main-Class cannot satisfy `java -jar`. The equivalent real use is a consumer
putting the jar on its classpath and calling the API, so that is what the
job does -- same shape as the sibling smokes (exactly one jar matched, exit
0 plus a success marker), repo-appropriate assertion.

smoke-jar.sh also asserts the jar carries module-info.class: it is produced
by a separate `release 9` compile execution and can be dropped without
failing anything else. StreamBufferSmoke.java runs through the JDK
single-file source launcher, so no Maven and no test framework are needed.
Both publish jobs now need it. Runs in about ten seconds with no network.
@bernardladenthin
bernardladenthin merged commit 25c2cb7 into main Sep 1, 2026
10 of 13 checks passed
@bernardladenthin
bernardladenthin deleted the claude/release-asset-guards branch September 1, 2026 06:58
@sonarqubecloud

sonarqubecloud Bot commented Sep 1, 2026

Copy link
Copy Markdown

@claude

claude Bot commented Sep 1, 2026

Copy link
Copy Markdown

Review Summary

This PR implements two important release-asset guards that strengthen the CI/CD safety model. I've reviewed the code across all dimensions and found no bugs or security issues.

Strengths

Smoke test design - The StreamBufferSmoke.java test is minimal and focused, correctly validating the packaged JAR by:

  • Loading classes from the JAR (not target/classes)
  • Testing the actual API contract (write → read → EOF)
  • Verifying module-info.class presence to catch JPMS descriptor losses

Bash script quality - smoke-jar.sh has proper error handling:

  • Strict mode (set -euo pipefail)
  • Exact-match JAR validation (rejects ambiguous globs)
  • Clear error messages with diagnostic info

Unsigned asset guard - The 3-step pattern (report → upload → fail) correctly prioritizes availability:

  • Assets upload even if signing failed (Central uptime protection)
  • Failure is loud and explicit (CI fails red, not silent)
  • Both snapshot and release jobs use identical code (cross-repo consistency achieved)

Workflow structure - Dependencies are correct:

  • smoke-jar depends on build (artifacts ready)
  • Both publish jobs depend on smoke-jar (smoke passes before Central)

Documentation - Excellent comments explaining why this exists and why the pattern matters (workspace policy alignment).

Code Quality

  • Try-with-resources ensures cleanup in StreamBufferSmoke.java
  • Read loop correctly handles partial reads
  • GitHub Actions API usage is modern (GITHUB_OUTPUT)
  • Test payload is appropriately sized for integration testing

Minor Notes

The unsigned-asset detection uses for jar in $jars; which isn't fully shell-safe with spaces in filenames, but this is intentional consistency with sibling repos (java-llama.cpp, BitcoinAddressFinder, srcmorph), and Maven JAR names never contain special characters in practice.


Verdict: Ready to merge. This addresses a real gap (packaged JAR was never tested) with the same rigor applied to those repos.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants