Authoritative tooling per ADR-003:
axion-release-plugin(git-tag-driven SemVer), Kotlin's built-in klib ABI validation (Kotlin 2.2+, formerly the standalonebinary-compatibility-validator), Vanniktechmaven-publish-pluginpublishing direct to Sonatype Central Portal (no OSSRH staging dance).
MAJOR.MINOR.PATCH, derived from the most recent annotated git tag (vX.Y.Z):
| Bump | When |
|---|---|
| MAJOR | Source- or binary-incompatible change to :core or any storage interface. Adding a new sealed-class subtype (forces consumer when exhaustiveness break). Bumping the proto submodule MAJOR (rare). |
| MINOR | New public API; new transport module; bumping the proto submodule MINOR (most common — new PortNum, new Config field). New optional Builder method. |
| PATCH | Bug fix; internal refactor; doc-only change; bumping the proto submodule PATCH (additive proto field with default value). |
Snapshot builds (X.Y.Z-SNAPSHOT) publish on every push to main to the Central Portal snapshot repository; tagged releases publish to Maven Central via manual workflow dispatch.
While version is 0.x.y:
- Breaking changes are allowed between any two MINOR versions (
0.1.x → 0.2.0). - Every breaking change MUST:
- Bump MINOR.
- Regenerate
api/files via./gradlew updateKotlinAbiin the same commit. - Add a
## [0.MINOR.0]section toCHANGELOG.mdwith### Breakingsubsection. - Be called out in the GitHub release notes as
**BREAKING**.
- Deprecation is optional but encouraged for changes a consumer can adapt to: mark with
@Deprecated(level = WARNING)for one MINOR, then remove in the next. - Kotlin's built-in klib ABI validation runs in PR CI as hard-gate from Phase 0 (matches
mqtt-client's day-1 enforcement). A PR that changesapi/*.apiMUST commit the regenerated dump.
From 1.0.0:
- No breaking change to public API in any non-MAJOR release. Period.
- ABI validation continues as hard-gate; an
api/*.apichange without a MAJOR-bump label fails CI. - Removal cycle: deprecate at version
N.M, remove at version(N+1).0(next MAJOR). - Sealed-class additions count as breaking for consumers using exhaustive
when— they ship in MAJOR releases only. Code review flags any new sealed-class case in a:corePR (no automated rule covers this; reviewers consult ADR-005). - Adding a new throwable subclass to
MeshtasticExceptionis breaking for the same reason. New error categories ship as MAJOR.
Kotlin 2.2 brings klib ABI validation into KGP itself, deprecating the standalone kotlinx-binary-compatibility-validator plugin. We use the built-in mechanism:
./gradlew checkKotlinAbi— fails ifapi/*.apidiffers from the current public surface../gradlew updateKotlinAbi— regenerate; commit the result.- Per-target
klibApiCheckruns against each Kotlin/Native and Kotlin/Wasm target's effective ABI, in addition to the JVMcheckKotlinAbi. - Configuration lives in
build-logic/convention/MeshtasticKmpPublishPlugin.kt(one place, applied to every published module).
:proto is excluded from ABI validation. Reason:
:protois mechanically generated by Wire from the vendoredmeshtastic/protobufssubmodule.- Every proto bump regenerates symbols; the diff is, by design, the change.
- We track proto bumps via the SemVer rules in the next section, not via line-by-line ABI gates.
- A consumer's reaction to a proto change is governed by Wire's own source/ABI rules, not by ours.
checkKotlinAbi is therefore disabled on :proto (configured in MeshtasticKmpPublishPlugin with apiValidation { ignoredProjects += "proto" } or equivalent in the new KGP config). The Gradle dep graph plus :core:verifyModuleBoundary enforces that :core does not re-export proto types beyond what api(project(":proto")) already exposes — the proto surface remains stable in one place (Wire), not two.
proto/src/protobufs is a git submodule pointing at meshtastic/protobufs. Bumping it is the integration point for every new firmware feature.
| Upstream proto change | This SDK's bump |
|---|---|
| New optional field on existing message | PATCH |
New PortNum value |
MINOR (new app payload becomes routable) |
New Config.* / ModuleConfig.* substructure |
MINOR |
Sealed oneof extension |
MINOR |
| Field removal / type change | MAJOR |
| Renumbered enum value | MAJOR |
Top-level message added (e.g., new FromRadio arm) |
MINOR (Wire generates a sealed-class case in FromRadio.body; consumers' exhaustive whens break — but FromRadio is internal/transient enough that we accept this at MINOR pre-1.0; after 1.0 this also escalates to MAJOR) |
The proto bump commit MUST:
- Update the submodule pointer.
- Run
./gradlew updateKotlinAbito capture the new generated symbols on:core(the BCV-relevant surface).:proto's own dump is not part of the gate. - Note the upstream commit range in the bump commit body.
- If a wire message has a new
oneofarm, auditWireCodecfor places that exhaustivelywhenover it.
Renovate's git-submodules manager opens a PR weekly with the latest submodule pointer; a maintainer reviews and merges. See ../renovate.json.
git tag -a v0.5.0 -m "Release 0.5.0"
git push --tagsaxion-release reads the tag; the resulting ./gradlew currentVersion returns 0.5.0.
Vanniktech publishes direct to the Sonatype Central Portal in one shot — there is no separate staging-repository "close-and-release" step.
# Snapshots (any non-tag build):
./gradlew publishToMavenCentral --no-configuration-cache
# Tagged release:
./gradlew publishAndReleaseToMavenCentral --no-configuration-cacheDriven from a manual release.yml GitHub Actions workflow (workflow_dispatch) on the tagged commit. Required secrets (vanniktech-standard names):
MAVEN_CENTRAL_USERNAME— Central Portal token username.MAVEN_CENTRAL_PASSWORD— Central Portal token password.SIGNING_IN_MEMORY_KEY— ASCII-armored GPG private key (gpg --armor --export-secret-keys $KEY), newlines preserved.SIGNING_IN_MEMORY_KEY_PASSWORD— passphrase for the above.
| Coordinates | Source module |
|---|---|
org.meshtastic:sdk-bom |
:bom (Maven BOM aligning the versions below) |
org.meshtastic:sdk-core |
:core |
org.meshtastic:sdk-proto |
:proto |
org.meshtastic:sdk-transport-ble |
:transport-ble |
org.meshtastic:sdk-transport-tcp |
:transport-tcp |
org.meshtastic:sdk-transport-serial |
:transport-serial (single KMP module covering JVM via jSerialComm and Android via usb-serial-for-android) |
org.meshtastic:sdk-storage-sqldelight |
:storage-sqldelight |
org.meshtastic:sdk-testing |
:testing |
All modules ship at the same version. KMP variants (Android AAR, JVM JAR, iOS Klib + XCFramework via ADR-007) publish under the same coordinates with platform classifiers per Gradle convention.
Roadmap artifacts (sdk-rpc, sdk-transport-rpc, sdk-host-rpc-server, sdk-transport-mqtt-proxy) ship additively when the wasm/RPC roadmap lands; see ./future/wasm-rpc-roadmap.md.
:bom is a tiny Maven BOM module declaring <dependencyManagement> entries for every published artifact above at the current release version. Consumers can:
implementation(platform("org.meshtastic:sdk-bom:0.5.0"))
implementation("org.meshtastic:sdk-core") // version comes from BOM
implementation("org.meshtastic:sdk-transport-ble") // version comes from BOMThe BOM is itself versioned and bumped on every release (axion handles this uniformly).
Generated from CHANGELOG.md ## [X.Y.Z] section + a manually-curated highlight blurb. Format:
## [0.5.0] — 2026-MM-DD
### Added
- ...
### Changed
- ...
### Breaking
- ... (only pre-1.0; post-1.0 these only appear in MAJOR releases)
### Fixed
- ...
### Proto
- Bumped `meshtastic/protobufs` submodule to <sha> (range <prev>..<sha>).
main— always green; tags are cut from here.release/X.Y— long-lived once we ship1.0, for back-port patch releases. Pre-1.0 onlymainexists.- Feature branches are short-lived; rebase + squash-merge into
main(no merge commits inmain's linear history).
Documented in docs/compatibility.md (TBD) and the README:
| SDK version | Proto submodule | Min firmware (capability-gated) | Kotlin | Gradle | Min Android SDK | iOS deployment |
|---|---|---|---|---|---|---|
0.x.y |
(varies; commit pin in CHANGELOG) | No hard minimum; capabilities gate on proto fields (e.g. DeviceMetadata.hasPKC for PKI, ClientNotification variants for security warnings). Practical floor tracks the pinned submodule — currently ≈ firmware 2.6.x. |
latest stable at release | 9.x | 26 | iOS 14 |
The SDK is forward-compatible for additive proto fields and does not hard-fail on
firmware-version mismatch today. MeshtasticException.FirmwareTooOld is reserved for a
future opt-in check (see docs/error-taxonomy.md). docs/compatibility.md will be added
when a real hard minimum is enforced.
If a release ships a critical defect:
- Open a hotfix PR; bump PATCH.
- Cut a new tag immediately.
- The bad version is not unpublished from Maven Central (Central does not support unpublish). Instead, the CHANGELOG marks it
## [0.5.0] — YANKEDwith a pointer to0.5.1and a brief reason. - README installation snippets always show the latest non-yanked version.
- ADR-003 — release tooling rationale
- ADR-007 — iOS distribution (KMMBridge)
api-reference.md— what the ABI gate actually protectserror-taxonomy.md— sealed hierarchies whose extension is MAJORci-cd.md— the workflows that enforce thisfuture/wasm-rpc-roadmap.md— additive artifacts deferred to post-1.0