sync-lockfiles: support the rebuilt zenoh-java/zenoh-kotlin (add zenoh-flat, zenoh-flat-jni; drop the crate-path special case) - #465
Merged
Conversation
Both are zenoh bindings and both now carry a committed Cargo.lock and a rust-toolchain.toml, so they can join the ABI-alignment sync like the other dependants. No other change is needed: both keep their manifest at the repository root, so the zenoh-java/kotlin crate-path special case does not apply and does not false-match zenoh-flat-jni.
5 tasks
milyin
force-pushed
the
sync-lockfiles-zenoh-flat
branch
from
August 10, 2026 17:37
3d9aba8 to
ca9abe5
Compare
milyin
added a commit
to eclipse-zenoh/zenoh-kotlin
that referenced
this pull request
Aug 10, 2026
A pinned SHA has to be hand-edited for every upstream fix, and that hop - zenoh-flat-jni to this SDK - is the one no bot covers. The timestamp node-id flake is what that costs: the fix sat in zenoh-flat for a day while CI kept testing the commit the pin named. Below zenoh-flat-jni the chain is automatic (eclipse-zenoh/ci#465 puts it and zenoh-flat on the lockfile sync), so following its default branch makes the whole chain automatic. The trade is deliberate: a run is no longer reproducible from this repository's commit alone, and a broken zenoh-flat-jni main breaks CI here - which, for a branch whose entire purpose is to track those bindings, is the signal we want.
milyin
added a commit
to eclipse-zenoh/zenoh-java
that referenced
this pull request
Aug 10, 2026
A pinned SHA has to be hand-edited for every upstream fix, and that hop - zenoh-flat-jni to this SDK - is the one no bot covers. The timestamp node-id flake is what that costs: the fix sat in zenoh-flat for a day while CI kept testing the commit the pin named. Below zenoh-flat-jni the chain is automatic (eclipse-zenoh/ci#465 puts it and zenoh-flat on the lockfile sync), so following its default branch makes the whole chain automatic. The trade is deliberate: a run is no longer reproducible from this repository's commit alone, and a broken zenoh-flat-jni main breaks CI here - which, for a branch whose entire purpose is to track those bindings, is the signal we want.
milyin
added a commit
to eclipse-zenoh/zenoh-kotlin
that referenced
this pull request
Aug 10, 2026
…699) * CI: let Gradle drive the zenoh-flat-jni native build The workflow added rustfmt and clippy to a hardcoded 1.93.0 toolchain and ran `cargo build` in the zenoh-flat-jni checkout. Neither is needed here: zenoh-flat-jni pins its own toolchain in rust-toolchain.toml, and the composite build's test task already depends on its native build, so Gradle drives cargo. Verified by deleting the built dylib and running `jvmTest`, which rebuilt it and passed. Naming a toolchain version this repo does not own is also how the same step broke in zenoh-java: the components landed on 1.93.0 while the checks ran on the pinned 1.97.1. `rustup show`, run from the zenoh-flat-jni directory, installs whatever that repo pins. * CI: pin zenoh-flat-jni to the merged commit #698 pinned the PR branch commit, which is reachable but not on main. eclipse-zenoh/zenoh-flat-jni#35 has since merged as e75529c with an identical tree, so this only makes the pin name a commit that main actually carries. * CI: track zenoh-flat-jni main instead of a pinned commit A pinned SHA has to be hand-edited for every upstream fix, and that hop - zenoh-flat-jni to this SDK - is the one no bot covers. The timestamp node-id flake is what that costs: the fix sat in zenoh-flat for a day while CI kept testing the commit the pin named. Below zenoh-flat-jni the chain is automatic (eclipse-zenoh/ci#465 puts it and zenoh-flat on the lockfile sync), so following its default branch makes the whole chain automatic. The trade is deliberate: a run is no longer reproducible from this repository's commit alone, and a broken zenoh-flat-jni main breaks CI here - which, for a branch whose entire purpose is to track those bindings, is the signal we want.
milyin
added a commit
to eclipse-zenoh/zenoh-java
that referenced
this pull request
Aug 10, 2026
* CI: pin zenoh-flat-jni to the timestamp node-id fix zenoh-flat carried a timestamp's node id trimmed to its significant bytes while `ZenohId` carries the full zero-padded width, so a received sample's timestamp compared unequal to the one that stamped the reply whenever the session identifier had a high-order zero byte - about once in 256 sessions, both sides rendering identically. `QueryableTest` asserts exactly that equality, and the same flake was observed in zenoh-kotlin. Fixed in eclipse-zenoh/zenoh-flat#86 and picked up by eclipse-zenoh/zenoh-flat-jni#35, which this pins. Drop the zenoh-flat checkout: nothing reads it, since zenoh-flat-jni resolves zenoh-flat from git and its Cargo.lock is the rev that decides - a pin that pinned nothing while reading as if it did. * CI: stop rebuilding and re-linting zenoh-flat-jni here This repo pinned the Rust toolchain at 1.93.0 to add rustfmt and clippy to it, then ran `cargo fmt --check`, clippy, `cargo test --no-default-features` and `cargo build` inside the zenoh-flat-jni checkout. zenoh-flat-jni pins its own toolchain in rust-toolchain.toml, so the components landed on one toolchain while the checks ran on another - `cargo fmt` failed with "'cargo-fmt' is not installed" as soon as that pin moved to 1.97.1. Rather than chase the version, drop the checks: they are zenoh-flat-jni's own CI, run on three platforms for the very commit pinned here, and its format check uses an import-granularity config this bare `cargo fmt` never applied - so the two were not even the same check. `cargo build` goes too. The composite build's test task already depends on zenoh-flat-jni's native build, so Gradle drives cargo; verified by deleting the built dylib and running `jvmTest`, which rebuilt it and passed all 112 tests. What remains is `rustup show` in the zenoh-flat-jni directory, which installs whatever toolchain that repo pins, so this repo never names a version its dependency is free to change. * CI: pin zenoh-flat-jni to the merged commit The pin named the PR branch commit, which is reachable but not on main. eclipse-zenoh/zenoh-flat-jni#35 has since merged as e75529c with an identical tree, so this only makes the pin name a commit that main actually carries. * CI: track zenoh-flat-jni main instead of a pinned commit A pinned SHA has to be hand-edited for every upstream fix, and that hop - zenoh-flat-jni to this SDK - is the one no bot covers. The timestamp node-id flake is what that costs: the fix sat in zenoh-flat for a day while CI kept testing the commit the pin named. Below zenoh-flat-jni the chain is automatic (eclipse-zenoh/ci#465 puts it and zenoh-flat on the lockfile sync), so following its default branch makes the whole chain automatic. The trade is deliberate: a run is no longer reproducible from this repository's commit alone, and a broken zenoh-flat-jni main breaks CI here - which, for a branch whose entire purpose is to track those bindings, is the signal we want.
milyin
added a commit
to eclipse-zenoh/zenoh-java
that referenced
this pull request
Aug 10, 2026
* Open the zenoh-flat transition integration branch
Tracking document for the rebuild of zenoh-java on the generated
zenoh-flat-jni bindings. Constituent PRs target this branch; it merges to
main as a whole when the transition completes (removing this file).
* Use receiver-style zenoh-flat-jni bindings (#481)
* initial code generation
* templates in separate files in prebindgen-ext
* mangle harness
* dependency on zenoh-flat-jni added
* switch to keyexprValidate, package support
* zenoh-jni reexported by zenoh-flat-jni
* No load generator
* output only declared in prebindgen-ext
* better error reporting
* reporting fix
* report unresolved descendants from required roots
BFS from each required-but-unresolved type through struct fields, enum
variants, generic args, and impl Fn args, surfacing transitively
unresolved entries that propagate_required cannot reach (because subs
edges don't exist past an unresolved parent). Without this, a missing
declaration in build.rs for a type only referenced as a field of another
unresolved struct went unreported.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* refactored, test added
* ptr class added
* qualify non-fqn types
* filter undeclared fns out of JNINative/JNIOrphaned emit
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* drop JNIOrphaned bucket; reuse declared_functions trait method
.method() is a namespace declaration — every #[prebindgen] fn must
be assigned to a typed handle, data class, enum, or kotlin_package.
There is no third "orphan" bucket, so JNIOrphaned was always emitted
empty and the duplicate declared_function_names helper just repeated
the PrebindgenExt::declared_functions trait method.
- delete write_jni_orphaned, jni_orphaned_class_name, the stale
generated JNIOrphaned.kt file
- rename render_jni_orphaned_source -> render_jni_package_source,
drop the now-redundant include_only_promoted / wrap_in_object /
declared / class_name params
- drop declared_function_names; render_jni_native_source now takes
&HashSet<syn::Ident> from declared_functions() directly
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* missing functions added to autogeneration
* into in build.rs
* universal post-emit qualification via VisitMut visitor
Replace the five scattered self.qualify_emitted_type() calls (build_input_fn,
build_output_fn, opaque_handle_input, build_handle_destructor_items,
option_input) with a single AST visitor pass applied once at write time.
The previous design qualified only at specific emit sites — each new emit
site had to remember the call. emit_into_dispatcher missed it, so the
impl-Into dispatcher body emitted bare KeyExpr/ZKeyExpr references that
failed to resolve in the binding crate's scope.
Architecture:
- New PrebindgenExt::post_process_item(item: &mut syn::Item) trait hook
(default no-op), invoked by write_rust on every emitted item.
- JniExt implements it via qualify_item, which runs QualifyEmittedTypes —
a syn::visit_mut::VisitMut that walks the full AST (signatures, generic
args, type ascriptions in let bindings, *mut/*const in cast expressions)
and prefixes every bare single-segment Type::Path matching a registered
source-module name with source_module.
- option_input no longer takes &JniExt; the body uses the bare t1 token
and the post-emit visitor qualifies it later.
Also fixes the pre-existing TypeEntry field-name drift in
final_invariant_reports_unresolved_field_of_unresolved_struct.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* transition to zenoh-flat keyexpr for zenoh-java
* ZKeyExpr improved
* closeable data classes
* universa close for data classes
* throwable data class
* config in zenoh-flat
* scouting api
* JNIScout removed
* whatami from jni
* value type
* zenoh id is a wrapper
* &str as input parameter
* logging api
* structured methods
* buildrs api renamr
* scouting package
* packaging fixes
* set intersection level
* zenoh-flat as mods
* qos enums ported
* enum validation in prebindgen
* wrap enums
* bytearray in zenoh-flat
* zenoh-java zbytes
* jni synchronized
* enum return fix
* take and ptr wraper in callback
* manually implemented serialization
* publisher ported
* attachment into
* encoding in zenoh-flat
* encoding constants
* renaming, fromstring added
* zencoding
* subscriber ported
* querier/queriable ported
* query and reply methods ported
* query structure
* renaming
* get and corresponding structures
* full timestamp representation in zenoh-flat
* old log init removed
* liveliness migrated, codegenerator fixed
* value type return fix
* sesstion migrated
* valur type handling fix
* niche fix
* wrappers directly in Session and. Liveliness
* bytes form zenoh_id to string added
* zenoh-jni removed
* jni generator and zenoh-flat moved out
* wrapper lock function
* blob type
* vlaue blob zzenohid
* missing api surface added
* switch to wrapped functions
* single into
* gtom struct from callback
* recursive call
* flatten callbacks
* create java object directly
* flatten input
* no array for locks
* removed expanded code
* expand constructor support
* types expanded
* use accessors
* sample attachment accessor
* nested output
* vec unfold
* comment added
* naming shortened
* converter, renamings
* process error as normal type
* zerror declared
* keyexpr default constructor
* accessor_fun
* full expand
* recursive expamd demo
* new api migration
* zbytes support callback error interface
* expand callbacks
* output kotlin one file per package
* flattened structure
* named callback parameters
* regenrate kotlin code
* generated files reformatted
* cargo fmt
* callback formatting
* no throws in generated code
* hello and query unwrap
* tests fixed
* query close in reply operations
* reply unwrap
* multiline long calls
* sample attributes added
* typed interface callback
* generated code insignificant change
* derfaults in error
* proxy interface
* accelerated onerror handler
* unfold encoding for acceleration
* renamings in builder
* refactor: Remove local zenoh-flat-jni subproject, consume as Maven dependency
- Remove :zenoh-flat-jni from gradle subprojects (moved to separate repo)
- Replace local source dependency with Maven: org.eclipse.zenoh:zenoh-flat-jni:1.9.0
- Remove Cargo build orchestration (buildZenohFlatJni, buildZenohFlatJNI, BuildMode)
- Remove build paths for zenoh-flat-jni libraries (now bundled in Maven JAR)
- Remove Cargo-related Gradle plugins and tasks
- Native libraries are now obtained from the zenoh-flat-jni Maven artifact
- Kotlin sources are now imported from the Maven JAR as a dependency
- Supports optional local development via: includeBuild("../zenoh-flat-jni")
- Simplifies zenoh-java build: Gradle now only compiles Kotlin, not Rust
Migration notes:
- zenoh-flat-jni is now at https://github.com/ZettaScaleLabs/zenoh-flat-jni
- Versions will be synchronized with Zenoh releases (1.9.0, etc.)
- For local development, clone both repos and use composite builds if needed
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* build: Enable Gradle composite build for local zenoh-flat-jni development
- Uncomment includeBuild for ../zenoh-flat-jni since it's now in the PREBINDGEN workspace
- zenoh-flat-jni is at /Users/milyin/PREBINDGEN/zenoh-flat-jni
- Gradle will prefer local build over Maven artifact when composite build is enabled
- Allows coordinated development of zenoh-java and zenoh-flat-jni
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore: Remove zenoh-flat-jni directory from zenoh-java
The zenoh-flat-jni project has been moved to:
- GitHub: https://github.com/ZettaScaleLabs/zenoh-flat-jni
- Local workspace: /Users/milyin/PREBINDGEN/zenoh-flat-jni
zenoh-java now consumes zenoh-flat-jni via:
1. Maven artifact (org.eclipse.zenoh:zenoh-flat-jni:1.9.0) in production
2. Gradle composite build (../zenoh-flat-jni) for local development
Removing the local copy avoids duplication and confusion.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* api change ported
* serialize fixes
* loading in zenoh-flat-jni
* import predefined encodings as consts
* Use typed zenoh-flat-jni key expression overloads
* Install CI lint components for the pinned Rust toolchain
* Run flat JNI clippy without a tracked lockfile
* Preserve consuming Rust API semantics
* Limit rustfmt CI to the JNI crate
* Remove stale local task lock
* Use generated JNI receiver methods
* Pin receiver-style JNI bindings in CI
* Track de-prefixed zenoh-flat type names in JNI bindings
zenoh-flat's consolidated API dropped the Z prefix from its Rust types
(ZSample -> Sample, ...), and the generated callback interface names follow
the type name mechanically: ZSampleCallback -> SampleCallback etc. Rename the
four callback references in FlatCallbacks.kt (signatures are unchanged) and
follow the encoding id accessor rename (id -> getId).
Pin CI to the updated zenoh-flat-jni (receiver-style bindings regenerated
against prebindgen main and zenoh-flat main) and to zenoh-flat main.
Verified locally: jvmTest 101 tests, 0 failures against the composite build.
* Use derived JNI factory and getter names
zenoh-flat-jni now derives all regular method names from the flat crate's
identifiers (explicit .name() only where a clash forces it), so the internal
calls follow: JniKeyExpr.newTryFrom/newAutocanonize/newJoin/newConcat,
JniConfig.newFrom*, JniEncoding.newFromString/newWithSchema,
JniZBytes.newFromVec, zSession.getPeersZid/getRoutersZid. SDK public API
unchanged. CI pin bumped to the matching zenoh-flat-jni commit.
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Encoding as a pure JVM value + native publisher default + pinned per-put override (#484)
* Make Encoding and ZenohId.toString pure JVM values
An encoding IS its (id, schema) pair — Zenoh's own representation; the
textual form is derived from a fixed table. With the id<->name table already
JVM-side (the generated ENCODING_* constants) and the companion
zenoh-flat-jni change crossing Encoding by value in both directions, the SDK
Encoding becomes a plain immutable value: no native handle, no native calls,
no lazy caches, no locking, nothing to close. This also removes a native
memory leak: received Sample/Query/ReplyError previously retained a cloned
Encoding handle with no close path.
- Encoding.kt: immutable (id, schema); from()/toString()/withSchema()
implement Zenoh's exact conversion rules (incl. the custom-encoding
name-preserving withSchema and the render-only CUSTOM<->"" table row);
equality on (id, schema), matching Zenoh core.
- ZenohId.toString: the little-endian lowercase-hex rule in pure Kotlin.
- Callback/fromParts leaves follow the new (encId, encSchema) decomposition;
outbound sites pass .id/.schema directly.
- Correspondence tests (EncodingCorrespondenceTest, ZenohIdCorrespondenceTest)
verify the pure implementations against the native ones across the whole
predefined id range, parse/render edge shapes, and random ids — the
contract for any JVM-side reimplementation of zenoh-flat API. One test
caught and fixed a real divergence (custom-encoding withSchema).
Pairs with ZettaScaleLabs/zenoh-flat-jni#4 (CI pin bumped). jvmTest: 105
tests, 0 failures.
* Publisher default encoding at declare + PinnedEncoding per-put override
Two encoding fast paths over the dual-arm expansion (prebindgen#80,
zenoh-flat#3, zenoh-flat-jni#4):
- The publisher's default encoding is set NATIVELY once at declare time
(PublisherOptions.encoding -> declarePublisher's encoding arm); plain
put(payload) passes absent (sel -1), so no encoding data crosses per call.
Previously the JVM tier re-sent (id, schema) on every put.
- PinnedEncoding (Encoding.pinned()) preallocates the native form; passing it
in PutOptions/ReplyOptions/GetOptions crosses only a borrowed handle (native
clone = Arc bump) — no schema-string traffic in hot publish loops. close()
releases the handle (falls back to the plain (id, schema) arm); a finalizer
backstops leaks. A PinnedEncoding equals its plain counterpart.
- All outbound sites route through one Encoding?.forWire() helper emitting the
selector tuple (-1 absent / 0 value / 1 pinned handle).
Tests: PinnedEncodingTest (publisher-default applies natively end-to-end;
pinned override reused across puts; post-close fallback), correspondence
tests updated to the dual-arm newWithSchema. jvmTest: 107 tests, 0 failures.
CI pins: zenoh-flat-jni cbbca40, zenoh-flat d792547.
* Move encoding/zid logic to the shared bindings tier; drop pinning
zenoh-flat-jni is the shared tier reused by both zenoh-java and zenoh-kotlin,
so the pure-JVM conversion logic moves there (EncodingCodec, ZenohId.zidString
— next to the generated constants they are built from). io.zenoh.bytes.Encoding
shrinks to a thin facade: the established constants and API delegating to the
shared codec, zero logic.
Drop PinnedEncoding and the selector-tuple wire helper: with the publisher's
default encoding set natively at declare time, the hot publish loop crosses no
encoding data at all, and per-call overrides crossing (id, schema) cost only a
short string decode — not worth a resource class in the public API. Encoding
params revert to the plain (present, id, schema) crossing.
jvmTest: 106 tests, 0 failures (correspondence suites now exercise the shared
codec; PublisherEncodingTest verifies the native declare-time default +
per-put override end-to-end). CI pins: zenoh-flat-jni 9bc155b, zenoh-flat
main (6d22091, #3 merged).
* Encoding: native handle where it is born, value where it is cheapest (#485)
* Encoding: native handle where it is born, value where it is cheapest
Minimize JNI crossings for every encoding flow (consumes zenoh-flat-jni
PR #5):
- Predefined constants stay VALUE-ONLY: a send carries just their id
inside the send call itself — no native handle ever exists for them,
no extra crossing.
- Custom (schema-carrying) encodings create their native handle at
construction — the one natural crossing — and every send after passes
a bare jlong (borrowed and cloned natively, reusable forever).
- Received encodings (sample/query/reply) arrive send-ready: the
delivery decomposition now includes the owned handle, so the
save-and-republish scenario never rebuilds the native value from its
schema string. Handle release is GC-managed (EncodingCleaner in the
shared tier); Encoding stays a plain non-closeable value with
unchanged (id, schema) equality/rendering.
Send call sites drive the generated encoding selector block through the
internal Encoding?.jniSel/jniId/jniSchema/jniHandle helpers — one flat
call, zero extra crossings in every case.
New EncodingHandleTest covers the ownership model end-to-end; 110 jvm
tests green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Lifecycle backstops moved into the gc_managed JNI tier
zenoh-flat-jni's Encoding, Session, Publisher, Subscriber, Queryable,
Querier, Scout, LivelinessToken and Query handles are now gc_managed
(milyin/prebindgen#82): a shared Cleaner frees an unreachable handle
whose owner never released it, with explicit close/undeclare settling
the release ticket first.
Delete the 8 deprecated-for-removal finalize() nets (JEP 421) — the JNI
handle is its own backstop now — and drop Encoding's EncodingCleaner
registration (superseded by the gc_managed class; the hand-written
helper is deleted in zenoh-flat-jni).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* CI: bump zenoh-flat-jni pin to the merged encoding-handle commit
The encoding selector wire shape and gc_managed lifecycles this branch
consumes landed in ZettaScaleLabs/zenoh-flat-jni#5 (e802317); the old
pin predates it, so CI compiled against the previous
(present, id, schema) encoding signatures.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Received preset encodings are value-only end to end (#488)
* Received preset encodings are value-only end to end
zenoh-flat-jni#6 makes the delivered encoding handle CONDITIONAL on
schema presence (binding-local encoding_if_schema behind prebindgen#84's
field! leaf): a received preset re-sends through the id arm for free, so
no per-message native handle — clone + Box + JVM wrapper + gc-Cleaner
registration + Cleaner free — is ever materialized for it. Schema-
carrying encodings keep the send-ready handle.
Adapt the delivery plumbing: Sample.fromParts encH becomes nullable
(Query's already was), the reply callback drops its non-null assertion,
and EncodingHandleTest's preset round-trip flips to assert the received
copy has NO handle — the new contract. CI pin bumped to the flat-jni#6
tip.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* CI: bump zenoh-flat-jni pin to the #6 merge commit
The previous pin was a branch commit deleted on merge; 1e040c9 is
ZettaScaleLabs/zenoh-flat-jni#6 on main (conditional encoding handle,
final fun!+sig! syntax).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Receive path goes value-only: KeyExpr string-backed except declared, Encoding always (id, schema) (#489)
* Received preset encodings are value-only end to end
zenoh-flat-jni#6 makes the delivered encoding handle CONDITIONAL on
schema presence (binding-local encoding_if_schema behind prebindgen#84's
field! leaf): a received preset re-sends through the id arm for free, so
no per-message native handle — clone + Box + JVM wrapper + gc-Cleaner
registration + Cleaner free — is ever materialized for it. Schema-
carrying encodings keep the send-ready handle.
Adapt the delivery plumbing: Sample.fromParts encH becomes nullable
(Query's already was), the reply callback drops its non-null assertion,
and EncodingHandleTest's preset round-trip flips to assert the received
copy has NO handle — the new contract. CI pin bumped to the flat-jni#6
tip.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* CI: bump zenoh-flat-jni pin to the #6 merge commit
The previous pin was a branch commit deleted on merge; 1e040c9 is
ZettaScaleLabs/zenoh-flat-jni#6 on main (conditional encoding handle,
final fun!+sig! syntax).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* KeyExpr: string-backed except declared
Consumes zenoh-flat-jni's string-delivering KeyExpr output. Ownership
model (the Encoding model transplanted): a native handle exists ONLY
behind Session.declareKeyExpr — the single case zenoh attaches a wire
declaration (the only state beyond the string, paying off only on send
through the declaring session). Everything else is a plain string value:
- tryFrom validates natively once and releases the probe handle;
autocanonize reads the canonized string and releases;
- RECEIVED keyexprs (sample/query/reply) arrive as ONE eager string —
no native allocation, nothing to free; the old per-message handle
never carried a declaration (zenoh's RX path builds declaration-less)
so it bought nothing on re-send while costing an unfreeable alloc and
a second getStr crossing for every string-reading consumer;
- sends pick the string or handle selector arm via
KeyExpr.jniSel/jniStr/jniHandle (cloneHandle for consuming params);
- algebra ops use the declared handle or a transient probe;
- undeclare demotes to string-backed; close() is meaningful only for
declared instances (doc matches upstream again).
113 jvmTest green incl. the new KeyExprHandleTest (constructed/declared/
received ownership + saved-keyexpr re-publish). CI pin bumped.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* CI: bump flat-jni pin to the gc_managed-KeyExpr commit
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* examples: ping-pong exercises the receive-then-resend encoding path
ZPing puts carry a custom schema-carrying encoding and verify (after
warmup) that the echoed sample brings it back; ZPong retransmits the
RECEIVED payload AND encoding — a pong that drops metadata is not a
faithful echo, and the round trip now measures the real
receive-then-resend path the send-ready Encoding handle was built for.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Encoding: received encodings are value-only (id, schema)
Adapts to zenoh-flat-jni aecffac, which dropped the receive-side
conditional handle leaf: the ping-pong A/B (ZPing/ZPong with a custom
schema-carrying encoding, real payload+encoding retransmission) showed
the per-receive handle lifecycle costs more than the schema re-decode it
saves on resend (flat medians 68/68us handle vs 58/67us value-only at
8B/1KB, value-only never slower).
Custom encodings still own a construction-born handle and send as a bare
jlong; a saved received encoding re-sends through the (id, schema) value
arm. Callback lambdas and Sample/Query.fromParts lose the encH leaf;
EncodingHandleTest asserts value-only delivery; CI pin bumped.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* ci: bump zenoh-flat-jni pin to e4a8ed7
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* examples: restore upstream ZPing/ZPong
The custom-encoding echo instrumentation was measurement scaffolding for
the Encoding handle A/B (verdict shipped: received encodings are
value-only); the examples keep their upstream form.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* docs: align lifecycle KDoc with actual GC management (#490)
Every handle-owning class except ZBytes is now GC-backstopped (Config
joined in zenoh-flat-jni PR#8), and the docs now say exactly what
happens to forgotten instances:
- Session: close() KDoc contradicted the code — close undeclares every
live declaration, it does not leave them alive; class KDoc notes the
GC backstop.
- ZBytes: new lifecycle section — created ZBytes are plain values;
received ones free their native buffer on first read; a received
payload/attachment that is never read keeps it (deliberately NOT
GC-managed: per-message Cleaner cost measured -23% thr @8B).
- Publisher/Querier: session reference is weak — forgotten instances
are undeclared by the GC backstop, not at session close only.
- Subscriber/Queryable: session reference is strong — dropping the
reference does NOT stop them; active until undeclare or session
close.
- Scout/LivelinessToken: 'dropped' now spelled out as GC-collected,
non-deterministic; close for prompt effect.
- KeyExpr (declared) / Config: backstop notes.
CI pin bumped to flat-jni dbb1f8c (Config gc_managed, PR#8).
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Session.undeclare: detach the key-expr handle even when undeclare fails (#491)
* Session.undeclare: detach the key-expr handle even when undeclare fails
The generated wrapper consumes the handle even when the native undeclare
errors (the Rust side takes it by value). Clearing keyExpr.handle only after
a successful call left a dead handle attached on failure: every later
operation selected the closed handle and failed instead of degrading to the
string form. Detach in a finally; regression-tested by undeclaring through
the wrong session.
Found while porting zenoh-kotlin (eclipse-zenoh/zenoh-kotlin#668), which had
inherited the same pattern.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Record PR #491 in the transition table
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* Parameters becomes a thin facade over the shared string-backed implementation (#492)
* Parameters becomes a thin facade over the shared string-backed implementation
The per-SDK map-backed parser duplicated zenoh-kotlin's and diverged from
Rust: it percent-decoded values, rejected duplicated keys (throwing on
attacker-controlled selector parameters in the queryable upcall), and
normalized eagerly. io.zenoh.query.Parameters now delegates every operation
to the shared io.zenoh.jni.query.Parameters (zenoh-flat-jni), a pure-Kotlin
string-backed mirror of Rust's zenoh-protocol parameters.rs — construction is
infallible on any input with zero JNI crossings.
Behavior changes, all aligning with Rust: no percent-decoding; from(String)
never throws; duplicated keys accepted with first-match-wins get (last wins
in toMap, as Rust's HashMap conversion); trailing ';'/'='/'|' trimmed at
construction; toString round-trips the stored string; equality is string
equality.
New ParametersCorrespondenceTest validates the shared implementation against
the native parameters_get/insert/is_well_formed oracle (zenoh-flat#4) over
edge shapes and 500 randomized inputs — it caught the trailing-separator trim
rule. remove is correspondence-exempt: upstream zenoh's parameters::remove
has an iterator-consumption bug (find advances past entries preceding the
first match before filter runs, dropping them; removing an absent key erases
everything); the shared implementation follows the documented 'preserving the
insertion order' contract instead, and nativeRemoveBugCanary pins the buggy
native behavior so the exemption is removed when upstream fixes it.
Also folds in the RX hardening that motivated the unification:
queryCallbackOf frees the owned native leaves and finalizes the query if
decomposition ever throws, and QueryParametersTest sends a=1;a=2;bad=%zz
through the raw bindings end to end. CI pins bump to zenoh-flat#4 and
zenoh-flat-jni#9.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Extend parameters correspondence to the full API; reframe the adaptation
zenoh-flat's parameters functions are regular API (values, contains_key and
extend now declared too); the JVM runs the same semantics in pure Kotlin
because crossing JNI per string operation is expensive — a JNI peculiarity,
not a zenoh-flat design choice. Correspondence now covers get/values/
containsKey/insert/extend/isWellFormed over the edge shapes and 500
randomized inputs; comments and test names updated to say 'native
implementation' (remove stays exempt behind nativeRemoveBugCanary until
eclipse-zenoh/zenoh#2687 lands). CI pins bump to the reworked zenoh-flat and
zenoh-flat-jni heads.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* ci: pin zenoh-flat to merged main (parameters API, zenoh-flat#4)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* Migrate to the split error-handler API (zenoh-flat-jni #45) (#495)
* Migrate to the split error-handler API (zenoh-flat-jni #45)
prebindgen split the generated JNI error callback into two channels: the
binding `JniErrorHandler.run(je)` (unchanged) and the typed domain
`ErrorHandler.run(message)` (`je` removed, called only on a domain `Err`).
A fallible flat wrapper now takes both — `onBindingError` then `onError`.
`JNIErrorHandlers.kt`:
- `throwZError` becomes the 1-arg domain handler
(`ErrorHandler { message -> throw ZError(message) }`).
- `throwZError0` (the binding `JniErrorHandler`) is unchanged — it now doubles
as the `onBindingError` of a fallible call.
Call sites: every fallible flat call gains `throwZError0` before its
`throwZError` (42 sites across Config/Session/KeyExpr/Liveliness/Publisher/
Querier/Query/Scout + one jvmTest), matching the wrapper's
`(…, onBindingError, onError)` order. Infallible calls (`throwZError0`) and
the binding-only `Encoding.newFromId { je -> … }` lambda are untouched.
Builds against the local composite zenoh-flat-jni (split-error-handler);
120 jvmTest tests pass, including the domain-error paths
(`configFailsWithIllFormatedYAMLTest`, `insertIllFormattedJson5ShouldFailTest`).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* ci: repoint zenoh-flat-jni pin to the split regen + ownership marker
The split error-handler regeneration (zenoh-flat-jni #10) plus the merged
Kotlin ownership-marker fix (zenoh-flat-jni #11, 249fe9e on shared-parameters).
CI pinned the pre-split 757cc6a, whose single-channel
`ErrorHandler.run(je, message)` wrappers are incompatible with this branch's
two-caller call sites; #11's marker is also required for the composite
`cargo build` to regenerate at all (write_kotlin refuses a non-empty output
root without it). Point CI at the merged commit so the composite build both
matches the source and regenerates.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* Serialize via the shared pure-Kotlin SerializationCodec (no JNI) (#496)
* Serialize via the shared pure-Kotlin SerializationCodec (no JNI)
Replace the per-element JNI serialization (serializeViaJNI) with the shared
pure-Kotlin SerializationCodec. ZSerializer/ZDeserializer build a
SerializationCodec.SerdeType from the Guava TypeToken's java.lang.reflect.Type
(serdeTypeOfJava) and call the shared codec through the SAME throwZError0 error
handler used for generated wrappers (the codec never throws — it invokes the
handler, which throws ZError), so the hand-written serializer is wired exactly
like a generated one. No duplication — the same codec zenoh-kotlin uses.
Full jvmTest: 120 pass (the 10 ZBytes tests now run via pure Kotlin). The Type
path's supported set is unchanged (signed/collection; Java can't express the
unsigned/Pair/Triple types the KType path adds).
Depends on the zenoh-flat-jni SerializationCodec commit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* ci: bump zenoh-flat-jni pin to the pure-Kotlin serializer commit
The pure-Kotlin SerializationCodec this branch delegates to lives in
zenoh-flat-jni#13 (db4fb2d). Point CI at it so the composite build has the
shared codec.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* ci: repoint zenoh-flat-jni pin to the rebased serializer commit
The zenoh-flat-jni serialization PR was rebased onto main (conflict resolution),
changing the SerializationCodec commit SHA. Repoint CI at the current commit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Drop correspondence tests moved down to zenoh-flat-jni
The Parameters/Encoding/ZenohId correspondence tests moved into zenoh-flat-jni's
own test suite (it now self-verifies its pure implementations against the native
oracle, which relocated to the internal io.zenoh.jni.test package). SDK
production is unaffected — it uses the pure io.zenoh.jni.query.Parameters and the
generated Encoding/ZenohId handles, all unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* ci: bump zenoh-flat-jni pin to the test-package/self-verify commit
zenoh-flat-jni moved its native oracle to the internal io.zenoh.jni.test
package and added self-verifying correspondence tests. Point CI at that commit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* ci: bump zenoh-flat-jni pin to merged main (71736f2)
zenoh-flat-jni #13 squash-merged to main as 71736f2. Re-pin from the
pre-merge branch commit 2755c06 (an orphan once the pure-kotlin-serde
branch is deleted) to the permanent main commit, which also carries the
final merged SerializationCodec (strict UTF-8).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* Realign with zenoh-flat HEAD; a timestamp carries its clock's id
zenoh-flat renamed part of its surface and moved several handle types to
value forms (ZettaScaleLabs/zenoh-flat-jni#16). Mostly mechanical, with one
change that reaches the public API.
## A reply timestamp needs a real originating-node id
`ReplyOptions.timeStamp` was an Apache commons-net `TimeStamp`: an NTP64
instant and nothing else. Zenoh's timestamp is the pair `(ntp64, id)`, and it
orders and de-duplicates on the pair — so the missing half was supplied by the
JNI layer, which built one with a **random** id:
Timestamp::new(NTP64(timestamp_ntp_64 as u64), ID::rand()) // zenoh-jni/src/query.rs:82
The time looked right while the id was untethered from any node. zenoh-flat
removed that (ZettaScaleLabs/zenoh-flat#47) and `query_reply_success` now takes
a whole `Timestamp`, so there is no longer anywhere for a fabricated id to
come from — which is the point.
`io.zenoh.time.Timestamp` is that pair, mirroring `zenoh::time::Timestamp`.
`ReplyOptions.timeStamp` and `Sample.timestamp` carry it instead of the
commons-net type. **This is source-breaking**: a caller now writes
options.setTimeStamp(Timestamp.ofNtp64(ntp64, session.info().zid()));
`ofNtp64` exists because the primary constructor takes `ULong`, which Java
cannot express; it takes the same 64 bits `TimeStamp(long)` does, so
`TimeStamp.getCurrentTime().ntpValue()` still feeds it. The id is the
replying session's — the reply does originate here — and `Sample.timestamp`
now surfaces the sender's id, which was previously discarded on receive.
The library no longer depends on commons-net; the tests and the ZQueryable
example still use it, as an NTP64 clock.
## Mechanical
* `keyexpr_get_str` -> `asStr`, `zbytes_as_bytes` -> `toBytes`.
* `session_get` takes a whole `Selector`, so the folded (key_expr, parameters)
pair is gone. A Selector holds an owned key-expr handle with no string arm,
so `KeyExpr.intoJniHandle()` materializes one for a string-backed key
expression — the slot trio cannot express this case.
* An encoding schema is raw bytes: zenoh transmits it verbatim and does not
require UTF-8. This SDK's `Encoding` carries a String, so it is encoded at
the boundary on send and decoded lossily on receive, rather than throwing on
a received message.
* A sample's source and a reply's replier arrive as whole `SourceInfo` /
`EntityGlobalId` values, so optionality lives on the value rather than on a
leading zid leaf.
* `config_new_from_json` is gone as an invented constructor (base zenoh has no
`from_json`). `Config.fromJson` parses via JSON5, of which JSON is a subset,
so every input accepted before still parses to the same config.
CI pins move to zenoh-flat 3f431b6b and zenoh-flat-jni 5e0ae509.
112 JVM tests pass; examples compile.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* CI: pin zenoh-flat-jni to the merged realignment
ZettaScaleLabs/zenoh-flat-jni#16 merged as 498ba26, so the pin moves off the
PR branch commit it was tracking.
The squash merge's tree is byte-identical to the branch tip this was verified
against, and 498ba26 pins zenoh-flat at the same 3f431b6b already pinned here,
so nothing but the SHA changes. 112 JVM tests pass against it; examples
compile.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* CI: build zenoh-flat-jni against published prebindgen (#511)
* CI: track zenoh-flat-jni onto the split prebindgen crates
zenoh-flat-jni#23 replaces its single `prebindgen` dependency with the
three crates the #371 split produced, so this workflow's two hardcoded
per-crate rewrites no longer match anything: the path deps would survive
into a checkout that has no sibling `../prebindgen`. One regex over every
`../prebindgen/*` dep covers the split and whatever it grows next.
The pinned checkouts also move from the stale ZettaScaleLabs forks (last
pushed in July) to the eclipse-zenoh repositories the work actually lives
in, at the two open PR heads.
No Kotlin change: zenoh-flat-jni regenerates byte-identical Kotlin over
the split, and all 112 jvmTest cases pass against it locally.
* CI: drop the prebindgen path rewrite, it is published
prebindgen 0.5.0 is on crates.io, so zenoh-flat-jni and zenoh-flat
declare version constraints and nothing has to be rewritten before the
build. Re-pin both checkouts to the PRs that make that switch.
* CI: re-pin zenoh-flat to main, zenoh-flat-jni to zenoh-flat-jni#24
eclipse-zenoh/zenoh-flat#80 merged as 81feb94. zenoh-flat-jni#25 landed
on the #24 branch rather than main, so that checkout tracks #24 now.
* CI: re-pin zenoh-flat-jni to main
eclipse-zenoh/zenoh-flat-jni#24 merged as 6f81eb8; both sibling
checkouts now sit on their repositories' main.
* Release preparation: repair the release path and document publishing (#514)
* Make the release resolve zenoh-flat-jni like any other consumer
The release path on this branch was dead, not merely dated. bump-and-tag
wrote to zenoh-jni/Cargo.toml, publish-jvm cross-built it and
publish-android installed Rust Android targets - and zenoh-jni/ was
removed when the SDK moved onto the generated bindings. The first job of
a release would have failed.
- settings.gradle.kts included ../zenoh-flat-jni unconditionally, so a
release would have been built against whatever sat on the builder's
disk. Now opt-in via -PuseLocalFlatJni=true; CI passes it explicitly
because it checks the sibling out, and a release does not.
- The dependency version moves to gradle.properties, so the release can
bump it and a rehearsal can point at a snapshot without editing
tracked source.
- bump-and-tag bumps version.txt and that property, and refuses a
-SNAPSHOT dependency outright: consumers do not configure the snapshot
repository, and snapshots mutate and expire, so a release that
depended on one would be broken for everyone but us.
- The zenoh-version input becomes zenoh-flat-jni-version, which is the
only upstream version this repository now chooses.
- Both publish workflows drop their native toolchains; the libraries
arrive inside the zenoh-flat-jni artifact. The unapplied
rust-android-gradle plugin goes with them.
Verified: the build fails to resolve without the flag (as a release
should), succeeds with it, and the bump script rejects a snapshot and
rewrites both files otherwise.
* Document how zenoh-java is published
Mirrors zenoh-flat-jni's guide in structure and intent, but the pipeline
here is much smaller and the document says why: this repository builds
no native code, so there is no matrix, no cross-compilation and no
artifact to inspect - the libraries arrive inside the zenoh-flat-jni
dependency.
The concepts that guide already explains once - coordinates, staging,
signing - are linked rather than restated.
What is specific here is the ordering constraint, which is the question
the transition keeps raising: zenoh-java cannot be released until the
zenoh-flat-jni version it depends on is really on Central, because
consumers do not configure the snapshot repository and snapshots mutate
and expire. Rehearsals have no such constraint, and the document gives
the ladder - local composite, snapshot dependency, snapshot publication
- with what each one actually proves.
Known gaps are stated rather than implied: the repaired release path has
never run, there is no consumer test, and the Android NDK configuration
is retained unverified.
* Let a rehearsal resolve an unreleased zenoh-flat-jni
The guide told the reader to add the snapshot repository 'for the
duration of a rehearsal' - editing tracked source, which is the thing
the rest of this change removes. That was not a procedure, it was a
gap.
The Central snapshot repository is now declared conditionally: present
only when zenohFlatJniVersion ends in -SNAPSHOT, and content-filtered to
that single module. A rehearsal names the version on the command line
and nothing is edited; a release version never ends in -SNAPSHOT, so a
release build cannot reach a mutable artifact even if someone tried. The
guarantee is structural rather than procedural.
Verified against the snapshot the zenoh-flat-jni rehearsal actually
published:
-PzenohFlatJniVersion=1.9.0-rc4-SNAPSHOT -> 112 tests pass, no
composite build
default (1.9.0) -> unresolvable, and the
snapshot repository is
absent from the searched
locations
* Let a rehearsal use a snapshot, and stop aborting on a no-op bump
Three defects the review found in the release script and workflows, all
of which would have stopped a release or a rehearsal outright.
The snapshot rejection was unconditional, so the rehearsal this PR
documents - pass the snapshot as zenoh-flat-jni-version - was refused
before Gradle ran. It now applies only to live runs, which is where the
reasoning actually holds: a release may not depend on a snapshot, a
rehearsal is how the SDK is exercised before the binding exists at all.
git commit on an unchanged gradle.properties exits non-zero, so
supplying the version already configured aborted the release under
set -e before tagging. It commits only when the file moved.
Neither publish workflow ran Gradle at all unless maven_publish was on,
so an unchecked rehearsal proved nothing while the guide claimed it
proved the artifact assembles. Both now assemble and generate the POM
unconditionally; only the upload is gated.
The guide also claimed one binding artifact carries both desktop and
Android natives. It does not - they are separate coordinates, and the
Android publication currently depends on the desktop one, so Android
consumers get no jni/<abi>/ libraries. Recorded as a defect with the
release blocked on it rather than described as working.
* Stop telling users this repository builds native code
The README described the build that was removed: publishing 'triggers
the compilation of Zenoh-JNI in release', the Android section required
an NDK and four Rust targets, and a warning said the result was tied to
the builder's rustup target. None of that is true now - the libraries
arrive inside the zenoh-flat-jni artifacts, cross-compiled elsewhere,
which is precisely why the result is *not* machine-specific.
Also fixes coordinates that were wrong independently of this work: the
Android dependency example named zenoh-kotlin-android in the zenoh-java
README, and the JVM examples used zenoh-java-jvm, which is not what this
repository publishes.
* Give each publication the binding artifact it needs
The Android POM named the desktop coordinate, so an Android consumer
received an artifact with six desktop libraries and no jni/<abi>/ - it
could not load anything. The build now selects zenoh-flat-jni-android
when -Pandroid=true and zenoh-flat-jni otherwise, and the conditional
snapshot repository serves both modules.
Verified by generating both POMs, which turns out not to need an Android
SDK even though building the AAR does:
jvm/pom-default.xml -> zenoh-flat-jni
androidRelease/pom-default.xml -> zenoh-flat-jni-android
The selection is build-wide rather than per source set because
commonMain references the generated classes, and Kotlin Multiplatform
cannot see a dependency declared only in platform source sets. That is
also the reason the two publications cannot come from one Gradle
invocation, so the atomicity gap stays - now with its cause recorded and
the upstream fix named: variant-aware module metadata in zenoh-flat-jni
would collapse the two coordinates into one.
Also fixes the javadoc JAR, which shipped empty: it packaged
build/dokka/html, which Dokka never writes. Pointed at dokka/javadoc,
with a check that fails the build rather than publishing 25 bytes again
- now 780 KB across 172 files.
README: Rust is no longer a prerequisite, the Android section no longer
claims to build an NDK library, jvmTest no longer 'compiles the native
library in debug mode', and the examples use 1.9.0, which resolves,
instead of 1.1.1, which 404s.
* One dependency, one invocation, one staging repository
The payoff of the zenoh-flat-jni KMP change.
commonMain declared the desktop or Android coordinate depending on a
build-wide -Pandroid flag, because the two were unrelated coordinates
and Kotlin Multiplatform cannot see a dependency declared only in the
platform source sets. That forced the JVM and Android publications into
separate Gradle invocations, hence separate staging repositories: a live
release could make one coordinate public and then fail on the other.
zenoh-flat-jni now publishes a root coordinate with module metadata, so
one declaration resolves per target. The flavour selection goes, and
with it the reason the publications were split - publish-jvm.yml and
publish-android.yml merge into one publish.yml whose single Gradle
invocation produces both and is followed by one closeAndRelease.
Verified against the KMP snapshot 1.9.0-rc8-SNAPSHOT: the zenoh-java POM
resolves zenoh-flat-jni-jvm and the zenoh-java-android POM
zenoh-flat-jni-android, from the same single declaration; one invocation
publishes both to mavenLocal; 112 tests pass.
* Repoint the callers at the merged publish workflow
Merging publish-jvm.yml and publish-android.yml left three references to
the deleted files: two snapshot-publish jobs in ci.yml and the needs
list of release.yml's publish-github. GitHub rejects the whole workflow
when a reference does not resolve, so CI failed at startup with no jobs
at all - which is why the PR showed a single check.
The release.yml one is instructive: my edit replaced
'needs: [tag, publish-jvm, publish-android]', but the file said
'[tag, publish-android, publish-jvm]'. Different order, no match, no
error - the replacement simply did nothing.
The two snapshot jobs become one, for the same reason the release
publishes once: a single Gradle invocation produces both publications.
Added a check that every './.github/workflows/...' reference and every
'needs' entry resolves, and ran it over all six files.
* Correct PUBLISHING.md for the merged publish job and KMP resolution
- the pipeline section still named publish-jvm/publish-android
- the snapshot-repository snippet still showed includeModule, which under the
KMP dependency would hide zenoh-flat-jni-jvm/-android and fail resolution
- state that zenoh-java itself publishes two plain coordinates and why the
workflow names its publication tasks rather than publishing all of them
* Restore the documentation deployment in the release sequence
Merging publish-jvm/publish-android into one publish job dropped the
publish-dokka call with them, orphaning publish-dokka.yml and leaving the
gh-pages API site README.md links to unmaintained. Restore it after publish,
with the contents:write the gh-pages push needs.
* Retire the transition scaffolding (#515)
Two files on this branch describe a repository that no longer exists.
ZENOH_FLAT_TRANSITION.md was the integration branch's own bookkeeping — an
architecture sketch and a table of the constituent PRs. The table stopped at
#484 and its links still pointed at the ZettaScaleLabs forks the work has
since left, so it now misinforms rather than informs; the same content, kept
current, lives in the #482 description. It was always meant to go when the
branch merges.
rust-toolchain.toml pinned 1.97.1 for a repository that builds no Rust. CI
installs its components explicitly (`--toolchain 1.93.0`) and runs every cargo
command inside the sibling zenoh-flat-jni checkout, where that repository's own
toolchain file applies, so this pin selects nothing and can only mislead
whoever reads it next.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* fix(ZBytes): publish the lazy byte cache safely (#517)
The lazy materialization in ZBytes is double-checked locking: the fast path
reads `eager` outside the monitor while the slow path writes it under it.
Without volatile, a reader observing the non-null reference has no
happens-before edge to the copy that filled the array.
Mark `eager` volatile. It moves out of the primary constructor because
@Volatile targets fields. The synchronized slow path stays: it is what
guarantees exactly one thread copies out of and closes the native handle.
`handle` stays non-volatile — it is only touched inside the monitor.
Closes #516
* CI: track zenoh-flat-jni main, and stop rebuilding it here (#520)
* CI: pin zenoh-flat-jni to the timestamp node-id fix
zenoh-flat carried a timestamp's node id trimmed to its significant
bytes while `ZenohId` carries the full zero-padded width, so a received
sample's timestamp compared unequal to the one that stamped the reply
whenever the session identifier had a high-order zero byte - about once
in 256 sessions, both sides rendering identically. `QueryableTest`
asserts exactly that equality, and the same flake was observed in
zenoh-kotlin.
Fixed in eclipse-zenoh/zenoh-flat#86 and picked up by
eclipse-zenoh/zenoh-flat-jni#35, which this pins.
Drop the zenoh-flat checkout: nothing reads it, since zenoh-flat-jni
resolves zenoh-flat from git and its Cargo.lock is the rev that decides
- a pin that pinned nothing while reading as if it did.
* CI: stop rebuilding and re-linting zenoh-flat-jni here
This repo pinned the Rust toolchain at 1.93.0 to add rustfmt and clippy
to it, then ran `cargo fmt --check`, clippy, `cargo test
--no-default-features` and `cargo build` inside the zenoh-flat-jni
checkout. zenoh-flat-jni pins its own toolchain in rust-toolchain.toml,
so the components landed on one toolchain while the checks ran on
another - `cargo fmt` failed with "'cargo-fmt' is not installed" as soon
as that pin moved to 1.97.1.
Rather than chase the version, drop the checks: they are zenoh-flat-jni's
own CI, run on three platforms for the very commit pinned here, and its
format check uses an import-granularity config this bare `cargo fmt`
never applied - so the two were not even the same check.
`cargo build` goes too. The composite build's test task already depends
on zenoh-flat-jni's native build, so Gradle drives cargo; verified by
deleting the built dylib and running `jvmTest`, which rebuilt it and
passed all 112 tests.
What remains is `rustup show` in the zenoh-flat-jni directory, which
installs whatever toolchain that repo pins, so this repo never names a
version its dependency is free to change.
* CI: pin zenoh-flat-jni to the merged commit
The pin named the PR branch commit, which is reachable but not on main.
eclipse-zenoh/zenoh-flat-jni#35 has since merged as e75529c with an
identical tree, so this only makes the pin name a commit that main
actually carries.
* CI: track zenoh-flat-jni main instead of a pinned commit
A pinned SHA has to be hand-edited for every upstream fix, and that hop -
zenoh-flat-jni to this SDK - is the one no bot covers. The timestamp
node-id flake is what that costs: the fix sat in zenoh-flat for a day
while CI kept testing the commit the pin named.
Below zenoh-flat-jni the chain is automatic (eclipse-zenoh/ci#465 puts it
and zenoh-flat on the lockfile sync), so following its default branch
makes the whole chain automatic. The trade is deliberate: a run is no
longer reproducible from this repository's commit alone, and a broken
zenoh-flat-jni main breaks CI here - which, for a branch whose entire
purpose is to track those bindings, is the signal we want.
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
milyin
added a commit
to eclipse-zenoh/zenoh-kotlin
that referenced
this pull request
Aug 10, 2026
* chore: add branch placeholder zbobr_fix-72-make-zenoh-kotlin-depend-on-zenoh-jni-runtime
* chore: add zenoh-java as git submodule on common-jni branch
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* build: update Gradle build to depend on zenoh-jni-runtime, remove Rust wiring
- Remove :zenoh-jni from settings.gradle.kts, add gated composite build
for zenoh-java submodule
- Remove rust-android-gradle plugin from root build.gradle.kts
- Rewrite zenoh-kotlin/build.gradle.kts: add zenoh-jni-runtime dependency,
add jvmAndAndroidMain source set, remove all Cargo/NDK task wiring
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor: move ZSerialize/ZDeserialize to jvmAndAndroidMain source set
Kotlin reflection (typeOf<T>()) is not available on Kotlin/Native or
Kotlin/JS. Moving these functions to jvmAndAndroidMain mirrors the
intentional design in zenoh-jni-runtime where JNIZBytesKotlin is also
in jvmAndAndroidMain.
Update call sites to use runtime's JNIZBytesKotlin instead of the
deleted local JNIZBytes adapter.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor: delete zenoh-kotlin JNI adapter layer and duplicate shared classes
Delete all JNI adapter files (except JNILiveliness.kt which will be
adapted to use runtime's public JNISession methods):
- All callbacks in jni/callbacks/
- JNISession, JNIPublisher, JNISubscriber, JNIQuerier, etc.
- JNIZBytes (replaced by runtime's JNIZBytesKotlin)
Delete duplicate classes that conflict with zenoh-jni-runtime:
- exceptions/ZError.kt (runtime provides io.zenoh.exceptions.ZError)
- jvmMain/Target.kt (runtime provides io.zenoh.Target)
- jvmMain/Zenoh.kt and androidMain/Zenoh.kt (actual ZenohLoad impls,
runtime provides io.zenoh.ZenohLoad)
- commonMain/Zenoh.kt expect ZenohLoad declaration will be removed next
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor: adapt Zenoh.kt, Config.kt, KeyExpr.kt, ZenohId.kt, Logger.kt to runtime API
- Zenoh.kt: remove expect ZenohLoad declaration; adapt scout methods to use
JNIScout.scout(Int, JNIScoutCallback, JNIOnCloseCallback, JNIConfig?)
- Config.kt: loadDefault/loadFromFile/loadFromJson/loadFromYaml replace old
methods; wrap runtime calls in runCatching where needed
- KeyExpr.kt: tryFrom/autocanonize now return String from runtime, wrap in
runCatching { KeyExpr(...) }; intersects/includes/relationTo/join/concat
updated to pass JNIKeyExpr? primitives explicitly
- ZenohId.kt: toStringViaJNI → toString (runtime method)
- Logger.kt: replace private external fun with JNILogger.startLogs(filter)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: remove zenoh-jni Rust crate and rust-toolchain.toml
All JNI functionality is now provided by zenoh-jni-runtime from the
zenoh-java submodule. The local Rust crate is no longer needed.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* build: remove CompileZenohJNI task and cargo wiring from examples
The examples no longer need to build a local Rust JNI library since
zenoh-jni-runtime is now the JNI provider via Maven dependency.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* ci: remove Rust build steps, simplify workflows to use zenoh-jni-runtime
- ci.yml: add submodules: recursive checkout, remove cargo fmt/clippy/build steps
- publish-jvm.yml: remove 6-platform cross-compilation matrix, simplify to
single publish job that depends on zenoh-jni-runtime via Maven
- publish-android.yml: remove NDK setup, Rust cross-compilation for Android ABIs
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: correct ntpValue call, ReplyKeyExpr ordinal, and KeyExpr undeclare
- Fix ntpValue property access to ntpValue() function call in Query.kt
- Reorder ReplyKeyExpr enum so MATCHING_QUERY=0, ANY=1 to match Rust mapping
- Fix Session.undeclare(KeyExpr) to null out jniKeyExpr after undeclaring
and return failure when already undeclared (prevents double-free SIGABRT)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: remove zenoh-jni references from bump-and-tag script and README
The zenoh-jni Rust crate was deleted; update the release automation script
to stop editing the now-nonexistent Cargo.toml and update README to reflect
that zenoh-kotlin now depends on zenoh-jni-runtime rather than building its
own native JNI library.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: decouple publish from local submodule, add separate runtime version property
- Gate includeBuild("zenoh-java") behind zenoh.useLocalJniRuntime property so
publication resolves against the published Maven artifact, not a local submodule build
- Add zenohJniRuntimeVersion in gradle.properties to independently track the
zenoh-jni-runtime release version, decoupling it from zenoh-kotlin's own version.txt
- Remove submodule checkout and Rust toolchain from publish-jvm and publish-android
workflows; publish path no longer needs them
- Pass -Pzenoh.useLocalJniRuntime=true in ci.yml test step so CI still builds against
the local submodule, and remove now-unnecessary Rust toolchain install step
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: update README to distinguish Maven vs local submodule test modes
Clarify that the default build/test path resolves zenoh-jni-runtime from
Maven and requires no Rust toolchain, while the opt-in local submodule
path (-Pzenoh.useLocalJniRuntime=true) builds from source and does require
one. Fixes the documentation to match the actual opt-in build wiring.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: use runtime acceptReplies value in Query and fail-fast on selector params
- Query now stores acceptReplies from the JNI callback instead of inferring from selector parameters
- resolveQueryable passes ReplyKeyExpr.entries[acceptReplies] to Query constructor
- Selector param parsing now uses getOrThrow() so malformed data surfaces as a failure
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: fail fast when useLocalJniRuntime=true but submodule is absent
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: remove stale publish-crates-github job from release workflow
zenoh-kotlin no longer contains any Rust crates (zenoh-jni/ directory
was removed as part of the migration to zenoh-jni-runtime). The
publish-github job that invoked publish-crates-github@main is now
obsolete and would fail at release time.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Update zenoh-java submodule reference
Update to latest common-jni commit with markdownlint fix.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Update zenoh-java submodule to eclipse-zenoh/zenoh-java:common-jni HEAD
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test fix attempt
* Open the zenoh-flat transition integration branch
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Record PR #668 in the transition table
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Port zenoh-kotlin to zenoh-flat-jni generated bindings (#668)
* Port zenoh-kotlin to zenoh-flat-jni generated bindings
Replace the external zenoh-jni-runtime dependency (zenoh-java common-jni,
git submodule + conditional composite build) with the generated zenoh-flat-jni
bindings, consumed via an unconditional Gradle composite build against a
sibling checkout — the architecture proven by zenoh-java's
zenoh-flat-transition branch.
The flat bindings never throw: every fallible generated wrapper takes a
trailing error sink and returns onError.run(...) on failure. Since
zenoh-kotlin's public API is Result-based, errors become Result.failure
directly inside the sink (exceptions/ResultHandlers.kt zCall* helpers,
born-closed sentinel handles on the error path) — zero try/catch and zero
runCatching on the JNI path.
Value models follow zenoh-java #484-#489: Encoding stays a pure JVM value
with the (id, schema) selector block; KeyExpr is string-backed except
declared handles; received ZBytes materialize lazily; ZenohId renders via the
shared-tier zidString codec; callbacks arrive value-decomposed in one JNI
crossing (FlatCallbacks.kt + fromParts factories). Query.reply* now closes
the native query (leak fix), Config.fromJson5 actually parses JSON5 (bugfix),
CongestionControl.BLOCK_FIRST added for receive-path totality.
Advanced pub/sub is not yet exposed by zenoh-flat/zenoh-flat-jni: public
signatures are kept but Session.declareAdvanced* return Result.failure and
AdvancedPubSubTest is disabled. zSerialize/zDeserialize bridge KType to
java.lang.reflect.Type (top-level primitives boxed); the Kotlin-specific
unsigned/Pair/Triple types are disabled pending a KType-aware serializer in
the shared tier.
CI mirrors zenoh-java's: sibling checkouts of zenoh-flat-jni@dbb1f8c and
zenoh-flat@6d22091 (same pins), prebindgen from git main, cargo build, then
gradle jvmTest. 113 tests, 0 failed, 12 skipped by design.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Querier: invalid-querier error is a Result.failure, not a throw
performGet returns Result<R>, but an invalid (undeclared) querier threw an
unchecked ZError — a pre-existing inconsistency carried over from the old
runtime-based code. Align it with the Result-based API: Publisher and Session
already report their closed-handle state as Result.failure.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Preserve the Result contract for JVM-side exceptions
The zCall* helpers captured only native errors (reported via the sink) and
let exceptions thrown by the block itself escape from Result-returning
functions — unlike main, whose runCatching also converted argument
preparation, user IntoZBytes.into() conversions, and native-library loading
failures into Result.failure. Concretely, scout(whatAmI = emptySet()) threw
from reduce despite returning Result.
Run each helper block inside runCatching (the sink capture takes precedence
and is still exception-free for native errors), move scout's whatAmI
reduction inside the captured block, make the zDeserialize cast mapCatching,
rebuild KeyExpr.fromProbe on zCall, and add a regression test for the
empty-whatAmI case. ZENOH_FLAT_TRANSITION.md now states the two-channel
contract precisely.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Session.undeclare: detach the key-expr handle even when undeclare fails
The generated wrapper consumes the handle even when the native undeclare
errors (the Rust side takes it by value), and on a pre-call guard failure the
handle instead stays live. Clearing jniKeyExpr only on success left a dead
handle attached: every later operation selected the closed handle and failed
instead of degrading to the string form. Close (no-op if consumed) and detach
unconditionally; regression-tested by undeclaring through the wrong session.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* RX path: never fault on attacker-controlled selector parameters
In the Rust layer a selector's parameters are an unvalidated string view —
any string is valid and forwarded untouched. The Kotlin queryable path
instead parsed them with the strict Parameters.from, whose failures
(duplicated names, invalid percent-encoding — both trivially producible by
any remote client) threw inside the JNI upcall: the exception is swallowed by
the callback bridge, but the query's owned payload/attachment buffers (no GC
backstop) leaked and the query was silently dropped — a remotely repeatable
leak, strictly weaker than the Rust layer.
Parse leniently on receive (Parameters.fromLenient: first duplicate wins,
split on the first '=', undecodable values kept verbatim), and add
defense-in-depth to queryCallbackOf: if decomposition ever throws, free the
owned native leaves and finalize the query before rethrowing. E2E-tested by
sending 'a=1;a=2;bad=%zz' through the raw bindings, as a remote non-Kotlin
client would.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* Parameters becomes a thin facade over the shared string-backed implementation (#670)
* Parameters becomes a thin facade over the shared string-backed implementation
The per-SDK map-backed parser (strict from + fromLenient) duplicated
zenoh-java's and diverged from Rust: it percent-decoded values, rejected
duplicated keys, and normalized eagerly. io.zenoh.query.Parameters now
delegates every operation to the shared io.zenoh.jni.query.Parameters
(zenoh-flat-jni), a pure-Kotlin string-backed mirror of Rust's
zenoh-protocol parameters.rs — construction is infallible on any (remote,
attacker-controlled) input with zero JNI crossings.
Behavior changes, all aligning with Rust:
- values are no longer percent-decoded (the URL-encoding claim was a
zenoh-jni legacy; docs updated),
- from(String) never fails (the Result signature is kept for source
compatibility and is always success); duplicated keys are accepted with
first-match-wins get,
- toString round-trips the stored string verbatim; insert/remove normalize,
- equality is string equality.
fromLenient is deleted (superseded — the shared parse is inherently total).
CI pins bump to zenoh-flat#4 (native parameters oracle) and zenoh-flat-jni#9
(shared implementation + oracle declarations).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Bump zenoh-flat-jni pin: trailing-separator trim in shared Parameters
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Bump pins: parameters API reworked as regular zenoh-flat API
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* ci: pin zenoh-flat to merged main (parameters API, zenoh-flat#4)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* Migrate to the split error-handler API (zenoh-flat-jni #45) (#673)
* Migrate to the split error-handler API (zenoh-flat-jni #45)
prebindgen split the generated JNI error callback into two channels: the
binding `JniErrorHandler.run(je)` (unchanged) and the typed domain
`ErrorHandler.run(message)` (`je` removed, called only on a domain `Err`).
A fallible flat wrapper now takes both — `onBindingError` then `onError`.
`ResultHandlers.kt`:
- `zCall` / `zCallUnit` blocks change from `(ErrorHandler<T>) -> T` to
`(JniErrorHandler<T>, ErrorHandler<T>) -> T`; the helper supplies both
handlers (each records into the shared `err` local; both produce the
sentinel where the return type demands one).
- `throwZError` becomes the 1-arg domain handler
(`ErrorHandler { message -> throw ZError(message) }`).
- `zCall0` / `zCallUnit0` / `throwZError0` (binding-only) are unchanged.
Call sites: every `zCall`/`zCallUnit` block threads both handlers into its
flat wrapper call across Config/Session/Zenoh/KeyExpr/Liveliness/Publisher/
Querier/Query; `KeyExpr.fromProbe` gains the binding param and its callers
(`join`/`concat`/`tryFrom`/`autocanonize`/`withHandle`) thread both. The two
direct `throwZError` passes (`KeyExpr.withHandle`, a `commonTest` reply) gain
`throwZError0`. Binding-only calls (`zCall0`/`throwZError0`/`newClone`/algebra
ops) are untouched.
Builds against the local composite zenoh-flat-jni (split-error-handler);
117 jvmTest tests pass (12 skipped), including the Result error paths.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* ci: repoint zenoh-flat-jni pin to the split regen + ownership marker
The split error-handler regeneration (zenoh-flat-jni #10) plus the merged
Kotlin ownership-marker fix (zenoh-flat-jni #11, 249fe9e on shared-parameters).
CI pinned the pre-split 757cc6a, whose single-channel
`ErrorHandler.run(je, message)` wrappers are incompatible with this branch's
two-caller `zCall`/`zCallUnit` sites; #11's marker is also required for the
composite `cargo build` to regenerate at all (write_kotlin refuses a non-empty
output root without it). Point CI at the merged commit so the composite build
both matches the source and regenerates.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* Serialize via the pure-Kotlin SerializationCodec (no JNI) (#675)
* Enable unsigned + Pair/Triple serialization via the KType path
zenoh-flat-jni now exposes a KType-aware serializer. Switch the
zSerialize/zDeserialize bridge from the erased `.javaType`
(`serializeViaJNI`/`deserializeViaJNI`) to passing the full `KType`
(`serializeViaJNIKType`/`deserializeViaJNIKType`), so `UByte`/`UShort`/
`UInt`/`ULong`/`Pair`/`Triple` work. Delete the `javaBoxedType()` bridge
(the KType is passed directly now) and the transition TODOs; update the
supported-types KDoc.
Un-`@Ignore` the 9 ZBytesTest cases (unsigned/Pair/Triple/nested). Full
jvmTest: 117 pass (advanced pub/sub still the only skipped suite).
Depends on the zenoh-flat-jni KType-serializer commit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* ci: bump zenoh-flat-jni pin to the KType-serializer commit
The unsigned/Pair/Triple serialization enabled here needs zenoh-flat-jni's
new KType externs (serializeViaJNIKType/deserializeViaJNIKType, ZettaScaleLabs/
zenoh-flat-jni#12, 9503f73). Point CI at that commit so the composite build
has the KType serializer.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Serialize via the pure-Kotlin SerializationCodec (no JNI)
Replace the per-element JNI serialization (serializeViaJNIKType) with the shared
pure-Kotlin SerializationCodec. `serdeTypeOf(KType)` (jvmAndAndroidMain,
kotlin-reflect) builds a SerializationCodec.SerdeType from the KType classifier —
recognizing the unsigned value classes and Pair/Triple — and zSerialize/
zDeserialize call the codec through the SAME zCall0 error-sink wiring used for
generated wrappers (the codec takes a JniErrorHandler and never throws), so the
hand-written serializer is indistinguishable from a generated one at the call site.
Adds SerializationCorrespondenceTest (Parameters-style): the pure output is
asserted byte-identical to the native serializeViaJNIKType oracle across scalars,
unsigned, strings/bytes, containers, tuples and nested types, plus RFC golden
vectors, plus a perf comparison. Measured pure-Kotlin speedup on small payloads:
~156x (Int), ~320x (List<Int>(4)), ~279x (Map<String,Int>(2)). Full jvmTest passes.
Depends on the zenoh-flat-jni SerializationCodec commit. The native KType
serializer (serializeViaJNIKType) is kept temporarily as the correspondence oracle.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* ci: bump zenoh-flat-jni pin to the pure-Kotlin serializer commit
The pure-Kotlin SerializationCodec this branch delegates to lives in
zenoh-flat-jni#13 (db4fb2d). Point CI at it so the composite build has the
shared codec.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* ci: repoint zenoh-flat-jni pin to the rebased serializer commit
The zenoh-flat-jni serialization PR was rebased onto main (conflict resolution),
changing the SerializationCodec commit SHA. Repoint CI at the current commit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Drop SerializationCorrespondenceTest moved down to zenoh-flat-jni
The serialization correspondence test (pure SerializationCodec vs the native
serializer oracle) moved into zenoh-flat-jni's own test suite. The native
serializer externs relocated to the internal io.zenoh.jni.test package; SDK
production uses the pure io.zenoh.jni.bytes.SerializationCodec, unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* ci: bump zenoh-flat-jni pin to the test-package/self-verify commit
zenoh-flat-jni moved its native oracle to the internal io.zenoh.jni.test
package and added self-verifying correspondence tests. Point CI at that commit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* ci: bump zenoh-flat-jni pin to merged main (71736f2)
zenoh-flat-jni #13 squash-merged to main as 71736f2. Re-pin from the
pre-merge branch commit 2755c06 (an orphan once the pure-kotlin-serde
branch is deleted) to the permanent main commit, which also carries the
final merged SerializationCodec (strict UTF-8).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* Add Gradle wrapper (8.12.1), like zenoh-java (#676)
zenoh-kotlin had no committed Gradle wrapper (gradlew/gradle-wrapper.jar
were gitignored), so CI installed gradle via setup-gradle's gradle-version
pin and the publish workflows generated a throwaway wrapper with
`gradle wrapper` before calling ./gradlew. Commit the wrapper (copied
verbatim from zenoh-java, Gradle 8.12.1) and drop those workarounds:
- .gitignore: stop ignoring gradle/, gradlew, gradlew.bat (keep .gradle cache).
- Add gradlew, gradlew.bat, gradle/wrapper/{gradle-wrapper.jar,.properties}.
- Workflows: drop `gradle-version: 8.12.1` (wrapper supplies it) and the
`gradle wrapper` generation steps; invoke ./gradlew everywhere (ci.yml and
publish-dokka switched from bare `gradle`). Matches zenoh-java's CI.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* Advanced pub/sub: AdvancedPublisher/Subscriber, matching + sample-miss listeners (#678)
* Enable advanced publisher + matching listener (Round 1)
Un-stub AdvancedPublisher and MatchingListener over the generated
io.zenoh.jni.pubsub bindings from zenoh-flat-jni's advanced-pubsub branch.
- Session.declareAdvancedPublisher now calls resolveAdvancedPublisher, lowering
the pure-Kotlin config holders (MissDetectionConfig heartbeat mode, CacheConfig
maxSamples, publisherDetection) to the generated scalar params.
- AdvancedPublisher: put/delete/getMatchingStatus and matching listeners
(callback/handler/channel + background variants) over the never-throw sink
(zCall/zCallUnit), mirroring the regular Publisher.
- MatchingListener wraps the generated handle (null for background listeners).
- AdvancedPublisherTest (Round 1): advanced publisher + matching listener vs a
regular subscriber on a single loopback session — put/delete round-trip,
matching status, encoding fallback. 4 tests pass.
The advanced subscriber / sample-miss / detect-publishers stubs and the full
AdvancedPubSubTest remain for Round 2. Requires zenoh-flat + zenoh-flat-jni
advanced-pubsub branches.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Enable advanced subscriber + sample-miss (Round 2)
Un-stub the subscriber side of advanced pub/sub over the generated
io.zenoh.jni.pubsub bindings from zenoh-flat-jni (advanced pub/sub, #14), and
re-align the Round 1 publisher lowering to the current typed data-class holders.
- Session: declareAdvancedSubscriber (callback/handler/channel) now wires a new
private resolveAdvancedSubscriber, mirroring resolveSubscriber (zCall +
sampleCallbackOf + strong-ref registration). HistoryConfig / RecoveryConfig /
subscriberDetection lower to the generated HistoryConfig / RecoveryConfig
holders (queryTimeout is not surfaced). resolveAdvancedPublisher is re-aligned
to build the generated MissDetectionConfig / CacheConfig(RepliesConfig) holders
— #14 replaced the old scalar params — which also restores CacheConfig.repliesQoS
(Round 1 dropped it). Removed the advancedUnsupported stub error.
- AdvancedSubscriber: carries the JNI handle; the 12 declared methods fold into
two private resolvers (detect-publishers → Subscriber, sample-miss →
SampleMissListener), foreground/background as in Round 1's matching listener.
- SampleMiss is modernized to `SampleMiss(source: EntityGlobalId, missedCount)`
(was four raw Longs), matching how Sample.sourceId already exposes the identity;
FlatCallbacks gains a sampleMissCallbackOf bridge (generated ZenohId → SDK
EntityGlobalId). SampleMissListener mirrors MatchingListener.
- AdvancedPubSubTest: removed @ignore; initialize receivedSamples before the
subscriber that appends to it; sleep for publisher/subscriber-detection
propagation.
- CI: pin zenoh-flat-jni -> d57837a (main, #14) and zenoh-flat -> b6b0ecf
(main, #5 advanced pub/sub).
Verified against zenoh-flat-jni main + zenoh-flat main (composite build,
Gradle 8.12.1): AdvancedPubSubTest 3/3 + AdvancedPublisherTest 4/4 pass; full
jvmTest 121/121 (no regressions).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Rename SampleMiss data type to Miss (follow zenoh naming)
zenoh-flat renamed its sample-miss report struct to `Miss` (zenoh's own name)
carrying an `EntityGlobalId` source. Track that in the SDK data type.
- pubsub/SampleMiss.kt -> pubsub/Miss.kt: `data class Miss(source:
EntityGlobalId, nb: Long)` (field missedCount -> nb, matching zenoh_ext::Miss).
- FlatCallbacks.sampleMissCallbackOf now returns the generated `MissCallback` and
reads the nested `miss.source.zid`/`miss.source.eid`.
- The SampleMiss* handler/callback surface keeps its names (SampleMissListener is
zenoh's own name); only the payload type changes to `Miss`.
The rename stays a data-type change: declareSampleMissListener and the listener
class are unchanged. Compiles; AdvancedPubSubTest 3/3 + AdvancedPublisherTest 4/4
+ full jvmTest 121/121 pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* Realign with zenoh-flat HEAD; a timestamp carries its clock's id
zenoh-flat renamed part of its surface and moved several handle types to
value forms (ZettaScaleLabs/zenoh-flat-jni#16). Mostly mechanical, with one
change that reaches the public API. Mirrors the same realignment in
zenoh-java.
## A reply timestamp needs a real originating-node id
`Query.reply(timestamp = ...)` took an Apache commons-net `TimeStamp`: an
NTP64 instant and nothing else. Zenoh's timestamp is the pair `(ntp64, id)`,
and it orders and de-duplicates on the pair — so the missing half was supplied
by the JNI layer, which built one with a **random** id:
Timestamp::new(NTP64(timestamp_ntp_64 as u64), ID::rand()) // zenoh-jni/src/query.rs:82
The time looked right while the id was untethered from any node. zenoh-flat
removed that (ZettaScaleLabs/zenoh-flat#47) and `query_reply_success` now takes
a whole `Timestamp`, so there is no longer anywhere for a fabricated id to
come from — which is the point.
`io.zenoh.time.Timestamp` is that pair, mirroring `zenoh::time::Timestamp`.
The reply/replyDel `timestamp` parameters and `Sample.timestamp` carry it
instead of the commons-net type. **This is source-breaking**: a caller now
writes
query.reply(keyExpr, payload, timestamp = Timestamp.ofNtp64(ntp64, zid))
`ofNtp64` exists because the primary constructor takes `ULong`, which Java
cannot express; it takes the same 64 bits `TimeStamp(long)` does, so
`TimeStamp.getCurrentTime().ntpValue()` still feeds it. The id is the
replying session's — the reply does originate here — and `Sample.timestamp`
now surfaces the sender's id, which was previously discarded on receive.
The library no longer depends on commons-net; the tests and the ZQueryable
example still use it, as an NTP64 clock.
## Mechanical
* `keyexpr_get_str` -> `asStr`, `zbytes_as_bytes` -> `toBytes`.
* `session_get` takes a whole `Selector`, so the folded (key_expr, parameters)
pair is gone. A Selector holds an owned key-expr handle with no string arm,
so `KeyExpr.intoJniHandle()` materializes one for a string-backed key
expression — the slot trio cannot express this case.
* An encoding schema is raw bytes: zenoh transmits it verbatim and does not
require UTF-8. This SDK's `Encoding` carries a String, so it is encoded at
the boundary on send and decoded lossily on receive, rather than throwing on
a received message.
* A sample's source and a reply's replier arrive as whole `SourceInfo` /
`EntityGlobalId` values, so optionality lives on the value rather than on a
leading zid leaf.
* `RecoveryMode` is a data-carrying enum, so the choice and its payload are one
value rather than a `(period, flag)` pair in which only one of the two was
ever meaningful. `RecoveryConfig.retentionPeriod` has no counterpart on the
SDK config and stays absent, as it effectively was before.
* `config_new_from_json` is gone as an invented constructor (base zenoh has no
`from_json`). `Config.fromJson` parses via JSON5, of which JSON is a subset,
so every input accepted before still parses to the same config.
CI pins move to zenoh-flat 3f431b6b and zenoh-flat-jni 5e0ae509.
121 JVM tests pass; examples compile.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* CI: pin zenoh-flat-jni to the merged realignment
ZettaScaleLabs/zenoh-flat-jni#16 merged as 498ba26, so the pin moves off the
PR branch commit it was tracking.
The squash merge's tree is byte-identical to the branch tip this was verified
against, and 498ba26 pins zenoh-flat at the same 3f431b6b already pinned here,
so nothing but the SHA changes. 121 JVM tests pass against it; examples
compile.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* CI: build zenoh-flat-jni against published prebindgen (#692)
* CI: track zenoh-flat-jni onto the split prebindgen crates
zenoh-flat-jni#23 replaces its single `prebindgen` dependency with the
three crates the #371 split produced, so this workflow's two hardcoded
per-crate rewrites no longer match anything: the path deps would survive
into a checkout that has no sibling `../prebindgen`. One regex over every
`../prebindgen/*` dep covers the split and whatever it grows next.
The pinned checkouts also move from the stale ZettaScaleLabs forks (last
pushed in July) to the eclipse-zenoh repositories the work actually lives
in, at the two open PR heads.
No Kotlin change: zenoh-flat-jni regenerates byte-identical Kotlin over
the split, and all 121 jvmTest cases pass against it locally.
* CI: drop the prebindgen path rewrite, it is published
prebindgen 0.5.0 is on crates.io, so zenoh-flat-jni and zenoh-flat
declare version constraints and nothing has to be rewritten before the
build. Re-pin both checkouts to the PRs that make that switch.
* CI: re-pin zenoh-flat to main, zenoh-flat-jni to zenoh-flat-jni#24
eclipse-zenoh/zenoh-flat#80 merged as 81feb94. zenoh-flat-jni#25 landed
on the #24 branch rather than main, so that checkout tracks #24 now.
* CI: re-pin zenoh-flat-jni to main
eclipse-zenoh/zenoh-flat-jni#24 merged as 6f81eb8; both sibling
checkouts now sit on their repositories' main.
* Release preparation: repair the release path and document publishing (#695)
* Release preparation: repair the release path and document publishing
zenoh-kotlin no longer contains Rust, but its release path still assumed it
did, and its dependency on zenoh-flat-jni was unreleasable as written.
- Resolve zenoh-flat-jni from Maven Central by default, at a version held in
gradle.properties, instead of hardcoding 1.9.0 and always substituting a
sibling checkout. The composite build becomes opt-in (-PuseLocalFlatJni=true);
CI passes it explicitly, a release must not.
- Declare the Central snapshot repository conditionally, so a rehearsal can
build against a zenoh-flat-jni snapshot while a release provably cannot.
- Merge publish-jvm.yml and publish-android.yml into one publish.yml: both
coordinates now come from a single Gradle invocation, so they share one
staging repository and are released together or not at all.
- Teach bump-and-tag.bash the zenoh-flat-jni version, refusing a snapshot on a
live run; drop the BUMP_DEPS_* inputs, dead since the Cargo crate was removed.
- Add PUBLISHING.md, and correct the README where it described the composite
build as unconditional.
* Correct the JVM coordinate in README.md
The publications set artifactId explicitly, so the JVM one is zenoh-kotlin,
not the zenoh-kotlin-jvm that plain Kotlin Multiplatform naming would give.
Every JVM copy/paste snippet named the latter, which does not exist.
* Review fixes for the zenoh-flat transition (#696)
* Review fixes: ZBytes publication race, ReplyKeyExpr order, build script
Three defects found reviewing #669, plus one stale comment.
- `ZBytes.bytes` is double-checked locking over a non-volatile field. A
received ZBytes is materialized lazily under a lock but read outside it,
and it routinely crosses threads (a sample piped through a Channel), so a
reader could see the array reference published before its copied contents.
`eager` becomes `@Volatile`; it moves out of the constructor parameter list
because Kotlin only allows the annotation on a body property.
- `ReplyKeyExpr` had its constants swapped so that MATCHING_QUERY is
declared first. The wire values live in `value`, so the declaration order
buys nothing — and reordering an enum silently changes `ordinal()` and the
order of `values()`/`entries` for every consumer. Restored, with a test
that pins it.
- Two lines in `build.gradle.kts` lost their indentation when the
rust-android-gradle entries next to them were deleted.
- AdvancedPublisherTest's KDoc claimed AdvancedPubSubTest "remains
@ignore'd"; it is not ignored and it passes. Replaced with what the file
actually adds: the same advanced publisher against a *plain* subscriber.
Verified with `./gradlew jvmTest -PuseLocalFlatJni=true` against the
zenoh-flat-jni / zenoh-flat commits CI pins: 122 tests, all green.
* Address the #669 review: drop dead guava, refresh the transition doc
- `com.google.guava:guava` is dead weight in `commonMain`. Nothing in this
repo references `TypeToken`, and since #675 serialization runs through the
pure-Kotlin `SerializationCodec`. The only `TypeToken` user is
zenoh-flat-jni's own `io.zenoh.jni.test.Serialization`, which declares
guava in its `jvmTest` set — so it was never ours to declare. Removed;
`jvmTest` stays at 122 green.
- The `jvmAndAndroidMain` rationale said the source set "carries
kotlin-reflect", but the same change removed that dependency. `typeOf<T>()`
and `KClass.qualifiedName` resolve through `kotlin.jvm.internal.Reflection`
in the JVM stdlib — which is a real JVM/Android-only constraint, just not
the stated one. Corrected in both the build script and KTypeSerde's KDoc.
- `ZENOH_FLAT_TRANSITION.md` still pointed at the `ZettaScaleLabs` forks
(README and settings.gradle.kts were already corrected in #695), listed
constituent PRs only up to #668, described the superseded
KType-over-JNI serializer as the approach taken, and listed advanced
pub/sub as planned. Table synced with the branch, serializer note
rewritten, and the remaining-work list is now what is actually left.
Also: `prebindgen` resolves from crates.io since #692, and the error
model has had two sink channels since #673.
* Remove ZENOH_FLAT_TRANSITION.md
The file was always meant to be deleted when this branch merges, and keeping
it current in the meantime costs a refresh on every constituent PR — the
previous commit is proof. Nothing in the repository links to it; the umbrella
PR #669 carries the same architecture, error-model and constituent-PR
descriptions in its body, which is where they stay accurate for free.
* CI: pin zenoh-flat-jni to the timestamp node-id fix (#698)
zenoh-flat carried a timestamp's node id trimmed to its significant
bytes while `ZenohId` carries the full zero-padded width, so a received
sample's `timestamp.id` compared unequal to the session zid that stamped
it whenever the identifier had a high-order zero byte - about once in
256 sessions, both sides rendering identically. That is the flaky
`QueryableTest.queryable_runsWithCallback` failure; the visible
difference in its message, the encoding, is cosmetic (`Encoding.equals`
ignores the description).
Fixed in eclipse-zenoh/zenoh-flat#86 and picked up by
eclipse-zenoh/zenoh-flat-jni#35, which this pins.
Drop the zenoh-flat checkout: nothing reads it, since zenoh-flat-jni
resolves zenoh-flat from git and its Cargo.lock is the rev that decides
- a pin that pinned nothing while reading as if it did.
* CI: track zenoh-flat-jni main, and let Gradle drive its native build (#699)
* CI: let Gradle drive the zenoh-flat-jni native build
The workflow added rustfmt and clippy to a hardcoded 1.93.0 toolchain
and ran `cargo build` in the zenoh-flat-jni checkout. Neither is needed
here: zenoh-flat-jni pins its own toolchain in rust-toolchain.toml, and
the composite build's test task already depends on its native build, so
Gradle drives cargo. Verified by deleting the built dylib and running
`jvmTest`, which rebuilt it and passed.
Naming a toolchain version this repo does not own is also how the same
step broke in zenoh-java: the components landed on 1.93.0 while the
checks ran on the pinned 1.97.1. `rustup show`, run from the
zenoh-flat-jni directory, installs whatever that repo pins.
* CI: pin zenoh-flat-jni to the merged commit
#698 pinned the PR branch commit, which is reachable but not on main.
eclipse-zenoh/zenoh-flat-jni#35 has since merged as e75529c with an
identical tree, so this only makes the pin name a commit that main
actually carries.
* CI: track zenoh-flat-jni main instead of a pinned commit
A pinned SHA has to be hand-edited for every upstream fix, and that hop -
zenoh-flat-jni to this SDK - is the one no bot covers. The timestamp
node-id flake is what that costs: the fix sat in zenoh-flat for a day
while CI kept testing the commit the pin named.
Below zenoh-flat-jni the chain is automatic (eclipse-zenoh/ci#465 puts it
and zenoh-flat on the lockfile sync), so following its default branch
makes the whole chain automatic. The trade is deliberate: a run is no
longer reproducible from this repository's commit alone, and a broken
zenoh-flat-jni main breaks CI here - which, for a branch whose entire
purpose is to track those bindings, is the signal we want.
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This was referenced Aug 10, 2026
The crate-path step existed for zenoh-java and zenoh-kotlin alone, whose Cargo manifest lived under zenoh-jni/. Both SDKs are being rebuilt on the generated zenoh-flat-jni bindings and no longer contain that crate; what they keep is a manifest at the repository root, whose lockfile records the zenoh-flat-jni commit their CI builds against - so this workflow moves that pin for them exactly as it moves everyone else's. With no dependant left below the toplevel, the step, its `if` and the three interpolations of its output all go. zenoh-c's build-resources steps are unaffected: they always named their manifest explicitly.
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.
Everything
sync-lockfilesneeds for the rebuilt zenoh-java / zenoh-kotlin, infive changed lines: two new dependants, and the removal of the special case that
existed for the two old ones.
Background: what changed in the SDKs
zenoh-java and zenoh-kotlin no longer contain a Rust JNI crate. Their bindings
are generated from a single annotated Rust surface and shipped as a separate
library:
zenoh-flat— the annotated Rust surface multi-language bindings aregenerated from. Depends on
zenoh/zenoh-extin the usualversion+git+branch = "main"shape.zenoh-flat-jni— the JNI/Kotlin binding generated from it, published asorg.eclipse.zenoh:zenoh-flat-jni. Depends onzenoh,zenoh-extandzenoh-flat.Both commit a
Cargo.lockand carry arust-toolchain.toml(channel = "1.97.1", matching zenoh), which is what this workflow needs from a dependant.They are two more zenoh bindings that have to stay ABI-aligned with zenoh, and
today nothing keeps them aligned.
Why the crate-path step goes
It exists for exactly two repositories — the
zenoh-(java|kotlin) → zenoh-jnibranch above — and
zenoh-jni/no longer exists in either of them (merged2026-08-10: eclipse-zenoh/zenoh-java#482, eclipse-zenoh/zenoh-kotlin#669). The
last sync run predates that, so the next run — the next push to zenoh’s
maintouching its
Cargo.lock— points both jobs at amanifest that is gone. This PR is what restores them, not a cleanup.
What each SDK keeps instead is a manifest at the repository root, whose
lockfile records the
zenoh-flat-jnicommit its CI builds against:That commit is the pin their Gradle build reads and checks out, and this workflow
already knows how to move it, with no new step: overwrite the lockfile with
zenoh's — which carries no
zenoh-flat-jnientry, so the pin is removed —rectify by resolving again, which writes the current commit back and compiles it.
Same sync, same auto-merging pull request, no special casing. The SDKs chose a
lockfile as their pin precisely so that this workflow, unmodified, is what keeps
them current.
With no dependant left below the toplevel, the step, its
if, and the threeinterpolations of its output all go. zenoh-c's
build-resources/opaque-typessteps are unaffected — they always named their manifest explicitly.
Two things I expected to need, and verified are not needed
A crate path for the new entries. Both keep their manifest at the root, so
the default
.is right. The old special case did not false-match them either —the pattern needs
zenoh-immediately followed byjava/kotlin, andzenoh-flat-jnihasflat-jnithere:A
cargo update zenoh-flatstep.zenoh-flat-jniis the first two-leveldependant here — it reaches zenoh both directly and through
zenoh-flat— so Iexpected the hardcoded
cargo update zenohto leave the intermediate cratestale. It does not, and the reason is structural: zenoh's lockfile has no
zenoh-flatentry at all, so overwriting the dependant's lock with it removesthat pin, and the rectify step then resolves the git dependency fresh from
branch = "main". Ran the full sequence locally against zenoh's realCargo.lockto confirm:So the intermediate crate refreshes on every sync for free, and zenoh's pins
survive — which is the property the sync exists for. An explicit update step
would have been dead weight. The same argument is what makes the SDK pin work:
zenoh-flat-jniis absent from zenoh's lockfile for exactly the same reason.Verifying
workflow_dispatchon this branch and check thezenoh-flat,zenoh-flat-jni,zenoh-javaandzenoh-kotlinjobs. Each should open a lockfile pull requestagainst its dependant.
fail-fast: falsealready keeps one dependant's failureoff the others, and nothing outside those four jobs is touched.
The crate-path removal needs the root manifest on each SDK's default branch —
eclipse-zenoh/zenoh-kotlin#700 and eclipse-zenoh/zenoh-java#521. Until those
land, the two SDK jobs fail on a missing root
Cargo.tomlrather than on themissing
zenoh-jni/Cargo.tomlthey hit today.