Skip to content

v2.1.0

Choose a tag to compare

@github-actions github-actions released this 02 Aug 01:54
43aae59

Tier 7, complete. Four claims that were previously compiled, argued or asserted are now things a build
proves: the REST engine runs on every target kdrant-core does and the shared contract runs from a
native binary, a scoped token is a credential the client knows about, a native image is built and made
to search, and every published POM names the platforms that module actually has.

Added

  • kdrant-transport-rest is multiplatform (M38). kdrant-core had compiled for eight
    Kotlin/Native targets since 2.0.0, and not one of them could send a request: the engine lived in
    src/main and was Ktor CIO on the JVM, so an iOS build got the models, the query DSL and the filter
    builders with nothing to put them on the wire. RestQdrantTransport is in commonMain now and the
    engine is chosen per target — CIO on the JVM, unchanged; Darwin on iOS and macOS; Curl on Linux;
    WinHttp on Windows. An iOS or Linux consumer depends on the same kdrant-transport-rest coordinate a
    JVM one does.
    Two engine choices have consequences worth knowing before a stack trace tells you: Darwin is
    NSURLSession and inherits App Transport Security, so a plaintext http:// Qdrant is refused by the
    platform before Kdrant sees the request, and Curl links against the system libcurl, which a slim
    container image may not have. Kotlin/JS stays out for the reason already written in kdrant-core.
  • kdrant-testkit is multiplatform, which is what makes the above more than a compilation
    exercise. The behavioural contract both engines are held to moved to commonMain as
    QdrantClientContractSuite, which knows no test framework; the JUnit and Testcontainers wrapper
    stays on the JVM and still declares one test per behaviour. CI runs the same suite from a linuxX64
    and a macosArm64 binary against a real Qdrant.
  • Scoped access (M39). KdrantConfig takes a bearerToken beside apiKey, mutually exclusive
    with it: a Qdrant JWT narrowed to read-only, to named collections, or to a payload filter deciding
    which points a caller may see at all. Both engines send it — Authorization: Bearer over REST, the
    same header as gRPC metadata — because the credential belongs to the config rather than to the wire.
    kdrant-testkit signs one for tests through QdrantJwt; minting tokens for a running system stays
    Qdrant's job.
  • KdrantException.Forbidden, a subclass of Unauthorized, for HTTP 403 and gRPC
    PERMISSION_DENIED. A read-only token refused on a write is a different fact from a missing key, and
    only one of them is worth retrying. Being a subclass keeps an existing
    catch (e: KdrantException.Unauthorized) catching it and keeps a when over the sealed hierarchy
    exhaustive.
  • kdrant-otel (M41), a new module: one OpenTelemetry client span per operation, on the transport
    seam, so one implementation covers both engines and a third would inherit it. Attributes follow
    OpenTelemetry's database conventions rather than an invented vocabulary. No payload value, vector or
    filter reaches an attribute, and a failed span carries the exception type rather than the server's
    message, because Qdrant quotes the request back in its errors. It depends on the OpenTelemetry API,
    never the SDK, so the exporter stays the consumer's.
  • kdrant-migrate (M42), a new module: migrateCollection(from, to, alias) copies a collection
    into one with a different vector size, verifies the result, and moves an alias so readers cross in one
    step. It resumes from a cursor after an interruption rather than starting over, and the alias moves
    only after the counts match and a sample of queries returns the same neighbours from both collections
    above a stated recall. A failed check throws MigrationVerificationFailed with the numbers in it and
    leaves the alias where it was: a tool that swaps because the copy finished without throwing is a tool
    that will one day point production at an empty collection.
  • decorateTransport on Kdrant(...) and KdrantGrpc(...), the hook kdrant-otel needs and the
    place a caching or rate-limiting decorator of your own goes.
  • ScrollBuilder.startAt, the id cursor a resumable job over a collection needs. It came out of M42
    and is worth more than the migration.
  • A GraalVM native image (M40). example-native-image is compiled with --no-fallback in CI and made
    to answer a real search against a real Qdrant, so the README's claim is a job that fails the day a
    dependency starts reflecting rather than a sentence in a table. Measured: 37 ms from process start
    to first search, in a 42 MB static binary. The comparison table quotes that instead of the word
    friendly.
    Building it settled the claim in the second of the two ways it could go. One thing does reflect: Ktor
    resolves a serializer from the response type at run time, and kotlinx-serialization answers by looking
    for the compiler-generated $$serializer, which a native image cannot find unless the class is
    registered. kdrant-transport-rest now ships that registration in its own jar, generated from the
    classes on the classpath rather than written by hand, so a model added tomorrow is in the file the
    same day and a consumer building a native image writes nothing.

Changed

  • Every published module's POM description now names the platforms that module actually has, and
    verifyPublishedDescription fails the build when one stops being true. The description Maven Central
    serves for kdrant-core:2.0.0 ends with "Core module for RAG and embedding search on the JVM",
    which klibs.io was about to put next to badges reading iOS, macOS, Linux and Windows generated from
    the same artifact's own tooling metadata. It did not go stale by accident: everything else moved at
    2.0.0 and the POM did not, because nothing reads it. The em dashes went with the correction.
  • A credential no longer requires TLS when the host is a loopback address. A key sent in the clear
    across a network is a key someone else has; a request to 127.0.0.1 never reaches a network. This
    only accepts configurations that were previously rejected, and it is what makes a local Qdrant with
    an API key work without a certificate.
  • Three signatures changed shape, and all three need a recompile rather than a jar swap.
    Kdrant(...) and KdrantGrpc(...) gained decorateTransport, and KdrantConfig gained
    bearerToken. Every parameter is optional and every 2.0.0 call site compiles unchanged, but a
    default parameter changes the signature Kotlin emits, so an application compiled against 2.0.0
    that swaps in the 2.1.0 jar without rebuilding will not find them. This is the case
    STABILITY.md already describes for data classes,
    now stated for functions and constructors too. git diff v2.0.0 v2.1.0 -- '*/api/*.api' shows the
    seven removed lines, and nothing else was removed.
  • KdrantException.Unauthorized is open, so Forbidden can extend it. Opening a class removes
    nothing a caller could use.
  • kdrant-transport-rest's JVM classes are published as kdrant-transport-rest-jvm, the same move
    kdrant-core made at 2.0.0. A Gradle build resolves the variant from the plain coordinate and
    changes nothing; a Maven build naming kdrant-transport-rest has to move to the -jvm one.

Fixed

  • ScrollRequest.offset was documented as the id to start after. It is inclusive, which is what
    the paging code has always relied on and what Qdrant returns as next_page_offset.
  • The count of operations Qdrant serves over HTTP only was eleven in five places and is fourteen.
    It was written into a KDoc once and never counted, and from there it reached the README, this
    changelog, the stability policy and the migration guide. grep -o 'restOnly("[a-zA-Z]*")' | sort -u | wc -l settles it, and the number now comes from that rather than from memory.
  • STABILITY.md said 2.0.0 broke nothing but the artifact layout. It broke two things: that, and
    ScrollRequest/SearchRequest gaining a shardKey parameter, which changed their generated copy
    and componentN. The upgrade section names both.

Internal

  • The release workflow's linked-artifacts step can no longer fail a release. On the v2.0.0 tag it
    returned 404 for a digest that does have an attestation and took down a job whose jars were already
    published and attested. A metadata step that can undo a successful publish is worth less than the
    metadata, so it is continue-on-error with a per-artifact fallback; the run's log still says which
    records were not written.
  • The same step's artifact list now names kdrant-transport-rest-jvm, and gains kdrant-otel,
    kdrant-migrate-jvm and kdrant-koog, which had been missing since the step shipped.
  • CI gained three jobs: the client contract from a linuxX64 and a macosArm64 binary, and the
    GraalVM native image. The two native jobs set KDRANT_QDRANT_REQUIRED, which turns the contract's
    skip into a failure, because a job that was meant to run it and silently skipped would report green
    for having proven nothing.