build(sdk): make datahub-sdk publishable to Maven Central, with locally signed, CI-verified releases - #148
Merged
Conversation
Port the Central tooling from the 0.2.0 prep: the maven-central-conventions plugin and the root centralBundle task. The SDK now publishes as datahub-sdk (not datahub-java-sdk), the POMs no longer import the Spring Boot BOM, and the jars carry their LICENSE. zero-allocation-hashing moves off the 0.27ea0 early-access build to 2026.0, pinned once for every module. The id hashes are unchanged. Also fix README snippets that did not compile and stale version references. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: jgjesdal <jostein@intellistream.ai>
Mirrors the Rust/Python SDK release: pushing java-sdk-vX.Y.Z checks the tag against both versions, builds and tests, then signs, uploads and publishes from the release environment. Pull requests that touch the release machinery rehearse it with a throwaway signing key. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: jgjesdal <jostein@intellistream.ai>
Gradle's in-memory signer used the primary key, so a key whose primary only certifies produced signatures nothing could verify. -PsigningKeyId / SIGNING_KEY_ID selects the signing subkey. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: jgjesdal <jostein@intellistream.ai>
This was referenced Sep 30, 2026
…loads The release manager builds and signs the bundle on their own machine and attaches it to a java-sdk-vX.Y.Z GitHub Release. CI no longer holds a signing key. scripts/verify-central-bundle.sh checks the bundle holds exactly the expected files, that every file is signed by a key in RELEASE_SIGNERS, and that each is byte-identical to what the tagged commit builds. The workflow then uploads the verified bundle. Local gpg signing defaults to `gpg`, since not every install provides `gpg2`. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: jgjesdal <jostein@intellistream.ai>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: jgjesdal <jostein@intellistream.ai>
olavgg
approved these changes
Sep 30, 2026
This was referenced Oct 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
We want to publish the Java SDK (
ai.intellistream:datahub-sdk) and the wire-contract types it is built on (ai.intellistream:datahub-api-model) to Maven Central, the same way the Rust and Python SDKs go to crates.io and PyPI. Nothing has ever been published underai.intellistream, so 0.3.0 will be the first release.maincould not produce a correct release. The Central tooling from the earlier 0.2.0 prep only ever existed on the old-lineage branchrelease/java-sdk-0.2.0, which shares no history withmain, so it never landed here. Whatmainwould have published was wrong in ways Central makes permanent:datahub-java-sdk, while every doc tells users to depend ondatahub-sdk.spring-boot-dependenciesimport into the published<dependencyManagement>. These artifacts are meant to be framework-free, and that import would have forced Spring's version choices on every consumer.LICENSEin the jars. The modules are Apache-2.0 while the repo is AGPL, so the jar has to say so itself.zero-allocation-hashing:0.27ea0was anapidependency of api-model.A Central release can never be replaced or deleted, so these had to be fixed before the first upload rather than after.
What changes
Packaging (
62fe1a5e)maven-central-conventionsplugin intobuildSrc. It holds the POM metadata Central requires, sources and javadoc jars,META-INF/LICENSE, signing, and a local staging repository. Both module build files collapse onto it. The old branch's hardcoded Gitea registry is dropped; the optional-PmavenPublishUrlremote frommainis kept.centralBundletask. It stages both modules into one Maven layout, zips the deployment Central's Portal takes, and refuses to build if any file is unsigned.datahub-sdk, and stops the Spring Boot BOM from being written into the POMs.zero-allocation-hashingto the stable2026.0, pinned once aszeroAllocHashingVersionfor every module. The ids it produces are persisted, so every module has to hash identically.IdGeneratorHashStabilityTestandExternalIdHashingConventionTestpin golden values and pass unchanged.LICENSE.getByIdreturnsDataWrapper<NodeModel>,createtakes a relations list, andMap.ofrejects null values), stale0.1.0version references, and adatahub-api-modelREADME that still described Jackson 2 and a "future SDK".Release workflow (
3557013b, reworked in4f5bdf15, tags in the latest commit)Each release is signed on the release manager's own machine with their personal key, and CI never holds a signing key. The release manager:
release/vX.Y, sets both versions ingradle.propertiestoX.Y.Z;./gradlew centralBundle -PsigningUseGpgCommand=trueon that commit, which signs through their local gpg;gh release create vX.Y.Z --target release/vX.Y build/central/datahub-central-X.Y.Z.zip.Publishing that GitHub Release triggers
.github/workflows/java-sdk-release.yml, which is modelled on the Rust/Python SDK'srelease.yml:vX.Y.Z, match bothapiModelVersionandjavaSdkVersion, and point at a commit onrelease/vX.Y, the release-branch convention from ci: run checks on release/vX.Y branches #145.build.ymldoes not run on releases, so this is the only test run a release gets.scripts/verify-central-bundle.sh:X.Y.Z, with valid checksums;datahub-java-sdk/RELEASE_SIGNERS, fetched from the public keyservers as Central does. Revoked and expired keys are refused, even though gpg itself exits 0 for them;releaseenvironment after approval: uploads the bundleverifychecked, taken from the run's artifact rather than the release asset so a swapped asset cannot reach Central. It usespublishingType=AUTOMATICand waits forPUBLISHED, failing with Central's errors if validation fails.A pull request that touches the version or the release machinery rehearses steps 1–3, signing with a throwaway key made in the job, as the Rust repo does.
Tags are plain
vX.Y.Z, like the Rust and Python SDKs. Every published GitHub Release in this repo runs the workflow, sovX.Y.Ztags and releases belong to the Java SDK. If the platform ever cuts GitHub Releases of its own, it needs a different tag scheme, or this trigger needs narrowing.RELEASE_SIGNERScontrols who may sign, so adding a release manager is a reviewed change to it. It lists primary fingerprints, so a signer can rotate subkeys without a change. It is also the list consumers check a downloaded artifact against.Signing fixes (
b66abe25,4f5bdf15)-PsigningKeyId/SIGNING_KEY_ID(the subkey's last 8 hex digits) selects the subkey. It was tested with Ed25519 and RSA keys. The rehearsal uses this in-memory path; releases use local gpg.gpg, not Gradle's defaultgpg2, which only some installs provide.Before the first release
Not code, and not done by this PR:
releaseenvironment does not exist yet. It needs to be restricted tov*tags with a required reviewer, and hold the secretsCENTRAL_TOKEN_USERandCENTRAL_TOKEN_PASSWORD. There are no signing secrets. Central has no OIDC trusted publishing, so a token is unavoidable.ai.intellistreamnamespace has to be verified on central.sonatype.com, with a Portal token.906FF6D8…26F4BAB8) is onkeys.openpgp.organdkeyserver.ubuntu.comwith its signing subkey, and is listed inRELEASE_SIGNERS.Docs
ai.intellistream:datahub-sdk:0.3.0, but only once it is actually on Central. The stale branchdocs/java-sdk-0.2.0-maven-centralneeds retargeting to 0.3.0.Verification
./gradlew buildis green across all modules.centralBundlebuilt with a throwaway key contains, for both modules, signed jar, sources and javadoc jars, POM and module file; every signature verifies.<dependencyManagement>Spring import, namedatahub-sdk, and pinzero-allocation-hashingat2026.0. Both jars carryMETA-INF/LICENSE.release/v0.3passes; a tag on amain-only commit, a commit missing fromrelease/v0.4, and a missingrelease/v0.5are all refused.-PsigningUseGpgCommand=true, thenverify-central-bundle.shpasses..asc, straymaven-metadata.xml, a changed jar that was properly re-signed, a bad checksum, a revoked subkey, and a wrong file name.verifysteps were run locally as written.Not yet exercised: signing with the release manager's real key, and the upload itself.
🤖 Generated with Claude Code