September 12, 2026
2 pull requests merged across 1 repo
Offline-Protocol/offline-protocol-sdk
What broke
The v0.26.0 Release & Publish run (34696833375) failed in all four Build Android jobs, in ring's build script:
error occurred in cc-rs: failed to find tool "aarch64-linux-android-clang": No such file or directory
Nothing published: both release and publish-crates depend on build-android.
Why
0.26.0 added ring to the mobile cdylib via the telemetry uploader (ureq -> rustls). It is the first dependency in the Android build that compiles C, and it does so through cc-rs, which never reads cargo's linker setting. The workflow set only the linker and AR. cc-rs probes PATH for a versioned clang and then <triple>-clang; setup-ndk puts the NDK root on PATH, not the toolchain bin, so every ABI came up empty.
build-uniffi-android.sh has the same gap. It does put the toolchain on PATH, so cc-rs's fallback list finds aarch64-linux-android21-clang and x86_64-linux-android21-clang, but the list names armv7a-linux-androideabi16-clang and i686-linux-android16-clang, API levels NDK r24+ no longer ship, so the 32-bit ABIs fail locally too.
Fix
Both paths export CC_<triple> and CXX_<triple> pointing at the same versioned clang the linker uses. The workflow derives the variable name from the matrix target, so no new matrix fields.
Verification
Local, NDK r27, the locked cc 1.2.43:
ringalone under the exact CI env (linker + AR, toolchain off PATH): fails with the CI error. WithCC_aarch64_linux_androidset: builds.ringalone under the script's env (toolchain on PATH) for armv7: fails witharm-linux-androideabi-clangnot found. WithCC_armv7_linux_androideabiset: builds.- Full
cargo build --release --locked --target armv7-linux-androideabi -p offline-protocol-uniffi: builds, andcheck-elf-alignment.pyreports 16 KB PT_LOAD alignment.
A workflow_dispatch dry run of the release workflow on this branch (version 0.26.0) is the CI-side proof; link in the comments.
After merge
Per the version-gate note and CONTRIBUTING, the tag can be deleted and re-pushed because nothing shipped:
git tag -d v0.26.0
git push origin :refs/tags/v0.26.0
git tag v0.26.0 <merge commit>
git push origin v0.26.0Cuts v0.26.0. Nothing here changes behaviour: it is the version bump, the
release notes, the 0.25.0 archive, and the corrections found while auditing the
range.
Minor rather than patch, decided on public surface: the telemetry sink API
is deleted on every binding (installTelemetrySink, onTelemetry,
pollTelemetry, uninstallTelemetrySink, fourteen TypeScript types, the UDL
TelemetrySink callback interface and the dictionaries and enums that typed
it, three Python methods), TelemetryConfig is reshaped, enableTelemetry and
six companion calls are added, ProtocolError gains TelemetryConfigInvalid
at position 24, offline-protocol-telemetry-wire is a new crate, and
Event::message_failed / Event::relay_demoted change parameter type.
The release itself is two things: the SDK ships its own telemetry (the whole
pipeline from classification to upload lives in the core, and the crates open
their first socket, over TLS with a bundled root set), and delivery survives a
restart (the inbound pending-decryption queue and the deduplicator's seen set
are persisted, two defaults grow to match, a relay-pushed DM is parked, and the
React Native bridges hold inbound message events while nothing can take them).
Before cutting: nothing compromising on main
Scanned before touching anything, because the repository is public.
gitleaksover all 2145 commits of history and over the working tree: no
credential. Every hit is a public NIP-44 test vector, a lockfile checksum,
the zero-key address fixture, or the self-signed interception.invalid test
key the uploader's certificate-refusal test uses.
- By name: the two
debug.keystorefiles are the stock React Native debug
keystore (fingerprint FA:C6:17:45…), the .xcode.env files carry only
NODE_BINARY, .env.example holds placeholders, and the demo app's
telemetry key is the PASTE_API_KEY_HERE placeholder.
- By content: no vendor token shape, no private IP, no internal hostname, no
home path, no real e-mail beyond the export notice and the third-party
licence authors.
Related issues
Refs #435, #436, #437, #438.
Type of change
-
chore— build, tooling, or dependency changes
Checklist
-
cargo fmt --all -- --checkpasses -
cargo clippy --workspace --locked -- -D warningspasses -
cargo test --workspace --libpasses -
cargo-denyis satisfied (advisories ok, bans ok, licenses ok, sources ok) - Commits follow Conventional Commits
- Docs /
CHANGELOG.mdupdated where relevant - No new
unsafein core crates - UDL unchanged in this PR, so no binding regeneration; the generated
bindings embed no version string, which is why a bump does not touch them
Also run: RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps, the
three no_std targets (core, sealed, leaf on thumbv8m.main-none-eabihf),
scripts/check-license-consistency.sh, npx tsc --noEmit in
bindings/react-native, both tool-workspace lockfiles with --locked, and a
repo-wide markdown link and anchor check (138 files, zero broken).
Version files
[workspace.package].version and the eleven internal dependencies in
[workspace.dependencies]; the four more declared locally in
offline-protocol-sealed and offline-protocol-leaf; Cargo.lock plus the
two tool-workspace lockfiles; bindings/python/pyproject.toml; the npm package
and its lockfile; and the three THIRD-PARTY-NOTICES.md copies. The eleven
previously published crates sit at 0.25.0 on the sparse index and 0.26.0 is
free.
One thing the tag needs that this PR cannot do:
offline-protocol-telemetry-wire is a new publishable workspace member and its
name is not reserved on crates.io (404 on the sparse index). `cargo workspaces
publish` will try to create it, which needs the registry token to carry the
publish-new scope. Reserve the name, or confirm the scope, before pushing
v0.26.0.
Archive
docs/changelog/0.25.md is new (first release of the series), holding the
0.25.0 section with its six ](docs/…) links rewritten to ](../…). The move
was proven lossless by re-extracting the section from HEAD:CHANGELOG.md,
applying the same rewrite, and diffing. Both archive tables gain the row.
Corrections that are not version bumps
Found by auditing the range: every commit since v0.25.0 against the
[Unreleased] section, every number quoted in the release notes and §20
against the constant that sets it (all match), and the version-adjacent prose.
docs/UPGRADING.md§4 stated the pre-0.26 dedup defaults as current
("Defaults are 1000 and 3600"), and §8.2 stated the 30-minute pending
TTL fallback in the present tense. Both now name the 0.26.0 values and point
at the new §21. bindings/python/README.md was the last surface still
passing pending_ttl_ms=1_800_000 "as the SDK default", and
docs/configuration.md still explained the default as 30 minutes one
paragraph under a table that said 24 hours.
- A
### Documentationsection and a third header paragraph for the
release notes. The section covered ADR 0024, the rewritten telemetry guide,
the threat model's egress section, C12 and the licensing FAQ nowhere; the
header named nothing from #438, whose default changes and parked pushed
messages are the runtime surprises of this release for an app that never
touches telemetry. §21 of the upgrade guide is new for the same reason.
- Counts that went stale with the eleventh crate.
CONTRIBUTING.md's
recipe said ten internal-dependency versions and release.yml's gate
message said eight; both say eleven, which is what the table holds.
- ADR 0018 and 0019 said "Shipped in: unreleased" for a data layer that
shipped in 0.23.0.
docs/README.mdcounted twenty-three ADRs against twenty-four files.
Two things checked and deliberately left alone: the archived 0.25.0 text
keeps its original punctuation (it is a move, not a rewrite), and the
[0.26.0] section keeps the one prose paragraph that sits between two
### Added bullets, because it was reviewed in that shape on #435.