ci: smoke-test the packaged jar and fail the release on an unsigned asset - #152
Conversation
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.
|
Review SummaryThis 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:
✅ Bash script quality - smoke-jar.sh has proper error handling:
✅ Unsigned asset guard - The 3-step pattern (report → upload → fail) correctly prioritizes availability:
✅ Workflow structure - Dependencies are correct:
✅ Documentation - Excellent comments explaining why this exists and why the pattern matters (workspace policy alignment). Code Quality
Minor NotesThe unsigned-asset detection uses Verdict: Ready to merge. This addresses a real gap (packaged JAR was never tested) with the same rigor applied to those repos. |



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 intarget/is what gets attached to the release and deployed to Central — so a jar missing classes, carrying a brokenmodule-info.classor assembled from a staletarget/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-Classcannot satisfyjava -jar, so the newsmoke-jarjob 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 carriesmodule-info.class, which is produced by a separaterelease 9compile 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.ascwith|| 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 PASSEDpublish.ymlparses as YAML;smoke-jaradded to both publish jobs'needs:workspace/policies/fat-jar-release-assets.md, updated in the companion workspace PRThe smoke jar glob excludes
-sources/-javadocunconditionally 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,srcmorphandworkspace.Checklist
CONTRIBUTING.mdandCODE_OF_CONDUCT.mdGenerated by Claude Code