Skip to content

zenoh-flat transition (integration branch) - #669

Merged
milyin merged 37 commits into
mainfrom
zenoh-flat-transition
Aug 10, 2026
Merged

zenoh-flat transition (integration branch)#669
milyin merged 37 commits into
mainfrom
zenoh-flat-transition

Conversation

@milyin

@milyin milyin commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Umbrella PR for rebuilding zenoh-kotlin on top of the generated JNI/Kotlin bindings
(zenoh-flat-jni, produced by prebindgen from
zenoh-flat). It removes the embedded
zenoh-jni Rust crate and the hand-written io.zenoh.jni adapter layer that sat on
top of it. This is the zenoh-kotlin counterpart of eclipse-zenoh/zenoh-java#482 — both
SDKs converge on the same shared bindings tier:

zenoh (Rust)
  └─ zenoh-flat            flat #[prebindgen]-annotated Rust API
       └─ zenoh-flat-jni   generated JNI externs + Kotlin classes (prebindgen JniGen)
            └─ zenoh-kotlin  Kotlin SDK wrapper (this repo)

The branch also carries the earlier external-jni series (#651), which had already
replaced the in-repo Rust crate with the zenoh-jni-runtime artifact; that interim
dependency is gone too.

The zenoh-jni crate (25 files, ~9.1k lines) and every JNI*.kt adapter are deleted;
what remains in zenoh-kotlin/src is the public Result-based SDK facade plus three
small adapter files (FlatCallbacks.kt, FlatEnums.kt, ResultHandlers.kt).

Error model

The generated bindings never throw across JNI: a fallible wrapper takes trailing
error-sink callbacks and returns the sink's value on failure. zenoh-kotlin's public
API is Result-based, so the failure is recorded directly inside the sink — the SDK
has zero try/catch and zero runCatching around native errors on the JNI path.

ResultHandlers.kt folds two channels into one Result: onBindingError
(JniErrorHandler — UTF-8 decode, closed handle, …) and onError (the typed domain
ErrorHandler, carrying the decomposed zenoh message). Call sites use
zCall/zCallUnit (both channels) or zCall0/zCallUnit0 (binding-only). The
enclosing runCatching exists solely for JVM-side throws — argument preparation,
user IntoZBytes.into() conversions, native-library loading during class init — so
the public Result contract of the pre-flat API is preserved exactly.

Callbacks are value-decomposed: a Sample, Query, Hello or Reply arrives as
its leaves in one JNI crossing, with no per-field accessor calls.

Serialization

zSerialize/zDeserialize no longer cross JNI at all. They build a
SerializationCodec.SerdeType from the full KType (KTypeSerde.kt) and run the
shared pure-Kotlin codec in zenoh-flat-jni, which is byte-identical to the native
serializer (correspondence tested upstream against the native oracle). Measured
speedup on small payloads: ~156× (Int), ~320× (List<Int>), ~279×
(Map<String,Int>). UByte/UShort/UInt/ULong/Pair/Triple are supported.

Public API changes

Source-breaking, all tracking zenoh/zenoh-flat semantics:

  • TimestampSample.timestamp and Query.reply(timestamp = …) carry
    io.zenoh.time.Timestamp (the (ntp64, id) pair, mirroring zenoh::time::Timestamp)
    instead of commons-net's TimeStamp. The old JNI layer fabricated the missing id with
    ID::rand(); a reply now carries the replying session's real id, and a received
    sample surfaces the sender's, which was previously discarded. Use
    Timestamp.ofNtp64(ntp64, zid). commons-net drops out of the library's dependencies
    (tests and the ZQueryable example still use it as an NTP64 clock).
  • Parameters — a thin facade over the shared string-backed implementation
    (Rust parameters.rs semantics): values are no longer percent-decoded, from never
    fails (the Result signature is kept for source compatibility), duplicate keys are
    accepted with first-match-wins get, and toString round-trips verbatim.
  • Config.fromJson parses via JSON5 (of which JSON is a subset — every input
    accepted before parses to the same config); the invented config_new_from_json
    entry point is gone.
  • SampleMissMiss as the payload type (Miss(source: EntityGlobalId, nb: Long)),
    following zenoh's own naming. The SampleMissListener surface is unchanged.
  • 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.
  • A declared KeyExpr is the only one holding a native handle; every other key
    expression is a plain validated string with nothing to close.

Build & CI

  • No Rust toolchain, NDK cross-compilation or cargoBuild wiring in this repo; the
    native library ships with org.eclipse.zenoh:zenoh-flat-jni.
  • Gradle wrapper (8.12.1) is committed (Add Gradle wrapper (8.12.1), like zenoh-java #676), replacing the CI gradle-version pin and
    the throwaway wrapper the publish workflows used to generate.
  • CI checks out sibling zenoh-flat-jni / zenoh-flat at pinned commits and builds
    through the Gradle composite build; prebindgen resolves from crates.io. Pins are
    bumped as the upstream PRs land.

Constituent PRs

PR Scope
#651 external-jni: drop the in-repo zenoh-jni crate for an external runtime artifact
#668 Port zenoh-kotlin to the zenoh-flat-jni generated bindings
#670 Parameters becomes a facade over the shared string-backed implementation
#673 Migrate to the split error-handler API
#675 Serialize via the pure-Kotlin SerializationCodec (no JNI)
#676 Add the Gradle wrapper (8.12.1)
#678 Advanced pub/sub: AdvancedPublisher/AdvancedSubscriber, matching + sample-miss listeners
5977bb1 Realign with zenoh-flat HEAD; a timestamp carries its clock's id (direct commit)
#692 CI: build zenoh-flat-jni against published prebindgen
#695 Release preparation: repair the release path, document publishing

Process

This PR stays draft while the transition is in progress. Individual changes land as
reviewable PRs targeting the zenoh-flat-transition branch; this PR accumulates
them and merges to main as a whole. Upstream companion PRs (prebindgen → zenoh-flat →
zenoh-flat-jni) merge first; the zenoh-kotlin PR then pins the exact upstream commits in
CI and merges once green. This description is the single record of the architecture
and the constituent-PR list.

🤖 Generated with Claude Code

milyin and others added 30 commits April 18, 2026 22:59
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…t 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>
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>
…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>
…t 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>
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>
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.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 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>
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>
…ion 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>
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>
…or 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>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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 to latest common-jni commit with markdownlint fix.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…oh-kotlin-depend-on-zenoh-jni-runtime

Zbobr fix 72 make zenoh kotlin depend on zenoh jni runtime
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* 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>
…entation (#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)

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>
* 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>
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>
…s 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>
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>
@milyin

milyin commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

Review of the branch as of 71969d0

Read the whole diff vs main (119 files, +2,365 / −12,345). The substance holds up:
the error-sink wiring in ResultHandlers.kt is consistent at every call site, the
consuming-vs-borrowing key-expr param split (cloneHandle()/intoJniHandle() vs the
jniSel/jniStr/jniHandle trio) is used correctly, the query-drop-on-reply model
matches what main already did, and queryCallbackOf frees the owned leaves if
decomposition ever throws. No correctness objections.

Five loose ends, none blocking:

  1. Stale repository links. README.md (lines 128/138/163), settings.gradle.kts:27
    and ZENOH_FLAT_TRANSITION.md (lines 20/21/51) still point at the old
    ZettaScaleLabs/zenoh-flat{,-jni} forks; CI already checks out eclipse-zenoh.

  2. ZENOH_FLAT_TRANSITION.md is out of date. Its constituent-PR table stops at Port zenoh-kotlin to zenoh-flat-jni generated bindings #668
    plus "shared-parameters — open", and its follow-up list still describes the
    KType-over-JNI serializer as the approach taken — Serialize via the pure-Kotlin SerializationCodec (no JNI) #675 replaced that with the
    pure-Kotlin SerializationCodec. Advanced pub/sub is listed as planned but landed in
    Advanced pub/sub: AdvancedPublisher/Subscriber, matching + sample-miss listeners #678. Either refresh it or drop it at merge time (it is meant to be removed anyway).

  3. guava looks dead. zenoh-kotlin/build.gradle.kts:57 declares
    com.google.guava:guava:33.3.1-jre "required by zenoh-flat-jni's deserializer (guava
    TypeToken)", but nothing in this repo touches TypeToken since Serialize via the pure-Kotlin SerializationCodec (no JNI) #675 moved
    serialization to the pure-Kotlin codec and the Java-Type path relocated to
    zenoh-flat-jni's internal test package. If it is still needed it is
    zenoh-flat-jni's own transitive dependency to declare.

  4. kotlin-reflect comment vs dependency. The same commit removed
    implementation("org.jetbrains.kotlin:kotlin-reflect") from commonMain, but
    KTypeSerde.kt's KDoc still says it lives in jvmAndAndroidMain because that is "the
    source set carrying kotlin-reflect". KClass.qualifiedName resolves through the
    stdlib's ClassReference on JVM, so this most likely works by accident rather than by
    the stated reason — worth either restoring the explicit dependency or correcting the
    comment.

  5. Stale test comment. AdvancedPublisherTest.kt:33 still says the advanced
    subscriber / sample-miss / detect-publishers side "remains @Ignore'd until Round 2";
    Round 2 landed in Advanced pub/sub: AdvancedPublisher/Subscriber, matching + sample-miss listeners #678 and no @Ignore remains in the suite.

Merge mechanics: the only conflict with main is zenoh-jni/Cargo.lock — deleted on
this branch, bumped daily by the zenoh sync bot. Resolve by keeping the deletion.

🤖 Generated with Claude Code

…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.
@milyin

milyin commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

@copilot resolve the merge conflicts in this pull request

@milyin milyin closed this Aug 10, 2026
@milyin milyin reopened this Aug 10, 2026
@milyin
milyin marked this pull request as ready for review August 10, 2026 09:02
@milyin

milyin commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

@copilot resolve the merge conflicts in this pull request

Co-authored-by: milyin <1909657+milyin@users.noreply.github.com>

Copilot AI commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

@copilot resolve the merge conflicts in this pull request

Resolved by merging origin/main into this branch and keeping the deletion of zenoh-jni/Cargo.lock as requested. Commit: c38a78a.

@copilot resolve the merge conflicts in this pull request

Merge conflicts are resolved. I merged origin/main and resolved the only conflict by keeping zenoh-jni/Cargo.lock deleted. Commit: c38a78a.

milyin added a commit that referenced this pull request Aug 10, 2026
- `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.
@milyin

milyin commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

The five loose ends from the review above are addressed in #696 (which also
carries three defects found in a second pass: a ZBytes publication race, the
ReplyKeyExpr reordering, and the build.gradle.kts indentation).

  1. Stale repository links — only ZENOH_FLAT_TRANSITION.md was still
    pointing at the ZettaScaleLabs forks; README.md and
    settings.gradle.kts:27 were already corrected by Release preparation: repair the release path and document publishing #695. Fixed.

  2. ZENOH_FLAT_TRANSITION.md out of date — refreshed rather than dropped,
    since the umbrella PR links to it. Constituent-PR table synced with the
    branch (Zbobr fix 72 make zenoh kotlin depend on zenoh jni runtime #651Release preparation: repair the release path and document publishing #695), the superseded KType-over-JNI serializer note replaced
    with what Serialize via the pure-Kotlin SerializationCodec (no JNI) #675 actually did, advanced pub/sub no longer listed as planned,
    and the follow-up list is now what is genuinely left before the merge.
    Also corrected: prebindgen resolves from crates.io since CI: build zenoh-flat-jni against published prebindgen #692, and the
    error model has had two sink channels since Migrate to the split error-handler API (zenoh-flat-jni #45) #673.

  3. guava dead — confirmed and removed. Nothing here references
    TypeToken; the only user is zenoh-flat-jni's own
    io.zenoh.jni.test.Serialization, which declares guava in its jvmTest
    source set, so it was never exported to us in the first place. jvmTest
    is 122 green without it.

  4. kotlin-reflect comment vs dependency — corrected the comment rather
    than restoring the dependency: it does work by design, not by accident.
    typeOf<T>() and KClass.qualifiedName are served by
    kotlin.jvm.internal.Reflection in the JVM stdlib, no kotlin-reflect
    artifact involved. That is still a genuine JVM/Android-only constraint, so
    jvmAndAndroidMain stays — the stated reason was just the wrong one.
    Updated in both zenoh-kotlin/build.gradle.kts and KTypeSerde.kt.

  5. Stale test comment — done in Review fixes for the zenoh-flat transition #696's first commit.

Verified with ./gradlew jvmTest -PuseLocalFlatJni=true against the pinned
zenoh-flat-jni@6f81eb8 / zenoh-flat@81feb94.

milyin added a commit that referenced this pull request Aug 10, 2026
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.
@milyin

milyin commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

Correction to item 2 above: ZENOH_FLAT_TRANSITION.md is removed in #696 rather than refreshed. It was always going to be deleted at merge time, and keeping it current costs a refresh on every constituent PR. Nothing in the repository linked to it, and this PR's description already carries the same architecture, error model and constituent-PR list — so that is now the single record. I dropped the pointer to the file from the description accordingly.

* 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.
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.
milyin added a commit to eclipse-zenoh/ci that referenced this pull request Aug 10, 2026
Both SDKs are being rebuilt on the generated zenoh-flat-jni bindings
(eclipse-zenoh/zenoh-java#482, eclipse-zenoh/zenoh-kotlin#669). On those
branches the in-repo `zenoh-jni` crate is gone, and with it
rust-toolchain.toml and every Cargo manifest - the SDKs carry no Rust at
all. Once that lands on main, their legs of this matrix fail at the
toolchain step, on a rust-toolchain.toml that no longer exists.

They are not losing the ABI alignment the sync exists for, they are
inheriting it one level down: zenoh-flat and zenoh-flat-jni, added
above, reach zenoh for them.

Their departure also takes the crate-path workaround with it. It existed
solely because those two kept their manifest under zenoh-jni/; every
remaining dependant has it at the toplevel, so the path is just `.`.
…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
milyin merged commit 8bdd60f into main Aug 10, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants