Releasing the libraries
How an agent publishes the HCERT libraries (STT-NFR-002) once the owner has
supplied credentials. Nothing is published yet. No GitHub workflow
publishes and no workflow holds a credential: CI runs only the dry runs
below, in the check, kotlin and rust jobs of
.github/workflows/requirements-kg.yml. An agent runs the commands here, from
a clean checkout of the release tag, and stops at the first failure.
What is published
One version for all of them, from VERSION (STT-NFR-001), with
an entry in CHANGELOG.md that cites each smart-trust
changes.md entry it implements.
| Registry | Coordinates | Built by |
|---|---|---|
| Maven Central | io.github.litlfred:hcert-api, io.github.litlfred:hcert-core (Kotlin Multiplatform: the root module, plus -jvm and -js) |
gradle -p tools/hcert-kotlin publishDryRun |
| crates.io | litlfred-hcert (library name hcert) |
cargo package in tools/hcert-rust |
| npm | @litlfred/hcert-kotlin-js (Kotlin/JS ES module) |
tools/release-packaging/assemble-npm.mjs |
| npm | @litlfred/hcert-component (the Rust WebAssembly component, transpiled with jco) |
tools/hcert-rust/build-component.sh, then assemble-npm.mjs |
The coordinates follow the repository’s GitHub org (the owner’s decision of
2026-10-23). Their single source is "repository": "<org>/<name>" in
smart-trust-tools.json: the Maven group is io.github.<org> (a namespace
the org can verify on the Sonatype Central Portal through GitHub), the npm
scope is @<org>, and the crate is <org>-hcert, because crates.io has no
namespaces (the Rust library itself stays hcert). The Gradle build and
tools/release-packaging/coordinates.mjs derive them; check-version.mjs
fails if Cargo.toml, a POM or a package.json disagrees. Moving the
repository to another org (say, WorldHealthOrganization) means changing that
one field, renaming the crate in Cargo.toml and Cargo.lock as the check
asks, and verifying the new namespaces below. The Kotlin package root stays
who.smart.trust. Android uses the -jvm jar as it is; there is no AAR until
an androidTarget() exists.
Once, before the first release
- Licence. Apache-2.0 (the owner’s decision of 2026-10-23; OI-14).
LICENSEis the official text from apache.org (sha256cfc7749b…3d30); the crate ships its copytools/hcert-rust/LICENSE(the check requires it to be identical),assemble-npm.mjscopies it into each npm package, and every jar carries it asMETA-INF/LICENSE. - Maven Central. Sign in to https://central.sonatype.com with the
litlfredGitHub account; theio.github.litlfrednamespace is verified by that. Generate a user token (View Account → Generate User Token): it is a name and a password,CENTRAL_TOKEN_USERandCENTRAL_TOKEN_PASSbelow. - Signing key. Central requires an OpenPGP signature on every file.
Generate a key for releases, then publish its public half:
gpg --quick-gen-key "Carl Leitner (smart-trust-tools releases)" ed25519 sign 2y gpg --list-secret-keys --keyid-format long # note the key id, KEYID below gpg --keyserver keys.openpgp.org --send-keys KEYID gpg --keyserver keyserver.ubuntu.com --send-keys KEYIDThe key stays on the owner’s machine (or a hardware token). No key, token or passphrase is ever committed or given to a workflow.
- crates.io. Sign in with GitHub at https://crates.io, verify an
e-mail address, and create an API token scoped to
publish-newandpublish-updatefor the cratelitlfred-hcert(CARGO_REGISTRY_TOKEN). - npm. The packages are scoped to the npm user
litlfred. Create a granular access token with read and write on@litlfred/*(NPM_TOKEN), with two-factor authentication on the account.
Pre-release checks
From the repository root, on main, with a clean tree:
git status --porcelain # must print nothing
git log -1 --format=%H origin/main # must equal HEAD; CI green on it
cat VERSION # the version to release, no pre-release suffix
- Set the version, if it changes, in the three places that hold it, then let
Cargo update its own entry in the lock (no other crate moves):
echo 0.1.0 > VERSION sed -i 's/^version = ".*"/version = "0.1.0"/' tools/hcert-rust/Cargo.toml # the [package] version, the only line it matches (cd tools/hcert-rust && cargo update --workspace --offline) - In
CHANGELOG.md, replace— unreleasedin the version’s heading with the date (## [0.1.0] — 2026-11-02), make sure every wire-format or subclaim-default change cites its smart-trustchanges.mdentry, and point the link at the tag ([0.1.0]: https://github.com/litlfred/smart-trust-tools/releases/tag/v0.1.0). A wire-format or subclaim-default change needs a major or minor bump. - Run the checks CI runs, and the dry runs:
node tools/release-packaging/check-version.mjs bun run check:req-nodes python3 tools/test-plan/make_test_plan.py --check python3 tools/wit2kotlin/wit2kotlin.py --check gradle -p tools/hcert-kotlin --no-daemon compileKotlinJvm compileKotlinJs :hcert-core:jvmTest :hcert-core:jvmTestCoverageCheck :hcert-core:compileProductionLibraryKotlinJs (cd tools/hcert-rust && cargo test --locked && cargo test --locked --features qr --test api) - Commit (
Release 0.1.0), push tomainoflitlfred/smart-trust-tools, wait for CI to pass on that commit, then tag it with a signed tag and push the tag:git tag -s v0.1.0 -m "smart-trust-tools 0.1.0" git push origin v0.1.0
Every artefact is then built from a fresh clone of the tag, never from a working copy:
git clone --branch v0.1.0 --depth 1 https://github.com/litlfred/smart-trust-tools.git /tmp/stt-release
cd /tmp/stt-release
bun run cat mount:lock && bun install # the mounted layers, for check:req-nodes
. toolchain/node_tools.sh # jco, from the lockfile, no install scripts
Maven Central (Central Portal bundle upload)
- Publish into the build-local repository and check the result. Every
archive is reproducible (no timestamps, fixed order), so building the tag
twice from a fresh clone gives the same jars, klibs, sources jars and POMs.
The Kotlin Gradle plugin (2.0.21) writes the
org.gradle.libraryelementsattribute intohcert-api’s.modulefiles or not depending on which tasks the build runs, so run exactly this command:gradle -p tools/hcert-kotlin --no-daemon publishDryRun node tools/release-packaging/check-version.mjs tools/hcert-kotlin/build/maven-dry-runpublishDryRunlists, for each ofhcert-api,hcert-api-jvm,hcert-api-js,hcert-core,hcert-core-jvmandhcert-core-js: the.pom, the.module(Gradle metadata), the main artefact (.jar, or.klibfor-js),-sources.jar,-javadoc.jar(the module README; the KDoc is in the sources) and, for the root modules,-kotlin-tooling-metadata.json. Gradle writes.md5,.sha1,.sha256and.sha512beside each. - Sign every artefact (not the checksums, not
maven-metadata.xml):repo=tools/hcert-kotlin/build/maven-dry-run find "$repo" -name 'maven-metadata.xml*' -delete find "$repo" -type f ! -name '*.md5' ! -name '*.sha1' ! -name '*.sha256' ! -name '*.sha512' \ -exec gpg --local-user KEYID --armor --detach-sign {} \; find "$repo" -name '*.asc' -exec gpg --verify {} \; - Bundle the repository layout and upload it, as a deployment the owner
releases by hand (
USER_MANAGED):(cd "$repo" && zip -qrX ../central-bundle-0.1.0.zip io) token=$(printf '%s:%s' "$CENTRAL_TOKEN_USER" "$CENTRAL_TOKEN_PASS" | base64 -w0) curl --fail --request POST \ --header "Authorization: Bearer $token" \ --form bundle=@tools/hcert-kotlin/build/central-bundle-0.1.0.zip \ "https://central.sonatype.com/api/v1/publisher/upload?name=smart-trust-hcert-0.1.0&publishingType=USER_MANAGED" # prints the deployment id curl --fail --request POST --header "Authorization: Bearer $token" \ "https://central.sonatype.com/api/v1/publisher/status?id=DEPLOYMENT_ID"Wait for
"deploymentState": "VALIDATED"; onFAILED, the response lists each problem. Then release it, which cannot be undone:curl --fail --request POST --header "Authorization: Bearer $token" \ "https://central.sonatype.com/api/v1/publisher/deployment/DEPLOYMENT_ID"(or Publish on the deployment at https://central.sonatype.com/publishing/deployments).
crates.io
cd tools/hcert-rust
cargo package --locked --list # exactly: Cargo.toml, Cargo.lock, README.md, DEPENDENCIES.md, LICENSE, src/*.rs
cargo package --locked # builds the .crate on its own, from target/package/litlfred-hcert-0.1.0
cargo publish --locked --dry-run
CARGO_REGISTRY_TOKEN=... cargo publish --locked
The tests are not in the crate: they read the shared vectors at
../../vectors, so they run from the repository. The component feature
reads the WIT contract at ../../api/wit and so builds only from the
repository too; the component is published to npm instead. A crates.io
version cannot be replaced, only yanked (cargo yank --version 0.1.0).
npm
tools/hcert-rust/build-component.sh /tmp/stt-component
gradle -p tools/hcert-kotlin --no-daemon :hcert-core:compileProductionLibraryKotlinJs
node tools/release-packaging/assemble-npm.mjs --out /tmp/stt-npm \
--component /tmp/stt-component \
--kotlin-js tools/hcert-kotlin/hcert-core/build/compileSync/js/main/productionLibrary/kotlin
node tools/release-packaging/check-version.mjs /tmp/stt-npm
node tools/conformance-ts/run.mjs rust /tmp/stt-npm/hcert-component # the shipped files pass the suite
node tools/conformance-ts/run.mjs kotlin /tmp/stt-npm/hcert-kotlin-js
for p in hcert-component hcert-kotlin-js; do
(cd /tmp/stt-npm/$p && npm pack --ignore-scripts --pack-destination /tmp/stt-npm)
done
sha256sum /tmp/stt-npm/*.tgz # record in the release notes
Then publish the tarballs that were checked, not the directories (scoped
packages are private unless --access public):
printf '//registry.npmjs.org/:_authToken=%s\n' "$NPM_TOKEN" > /tmp/stt-npmrc
npm publish /tmp/stt-npm/litlfred-hcert-component-0.1.0.tgz --access public --ignore-scripts --userconfig /tmp/stt-npmrc --otp=CODE
npm publish /tmp/stt-npm/litlfred-hcert-kotlin-js-0.1.0.tgz --access public --ignore-scripts --userconfig /tmp/stt-npmrc --otp=CODE
rm /tmp/stt-npmrc
An npm version cannot be republished once used, even after npm unpublish.
After publishing
- Check that each coordinate resolves to the release (the STT-NFR-002
success criterion):
curl -sI https://repo1.maven.org/maven2/io/github/litlfred/hcert-core-jvm/0.1.0/hcert-core-jvm-0.1.0.pom | head -1 curl -s https://crates.io/api/v1/crates/litlfred-hcert/0.1.0 | head -c 200 npm view @litlfred/hcert-component@0.1.0 version npm view @litlfred/hcert-kotlin-js@0.1.0 versionMaven Central can take up to an hour to show a released deployment.
- Create the GitHub release for the tag with the changelog entry and the sha256 of each artefact.
- Open the next entry at the top of
CHANGELOG.md(## [0.2.0] — unreleased) and setVERSIONandCargo.tomlto it in the same commit, socheck-version.mjskeeps passing.