Repository navigation
Nightly 0.6.1-nightly.20261025+01a34a8
Pre-release
Pre-release
Nightly build 0.6.1-nightly.20261025+01a34a8 of 01a34a8, built 2026-10-25.
Built from the newest commit on main whose CI run passed, then smoke-tested. That is all the testing it has had: do not run it in production.
docker pull ghcr.io/getfelix/felix-broker:nightly-20261025
docker pull ghcr.io/getfelix/felix-controlplane:nightly-20261025
docker pull ghcr.io/getfelix/felixctl:nightly-20261025The binaries report the version of the last release, not this build's.
Unreleased changes
Added
- The C ABI (
crates/sdk/felix-capi) runs caches, counters and cache watches (#1493):felix_client_cache_put,
_get,_get_versioned,_delete,_put_ifand_delete_if,felix_client_counter_addand_get, and
watches on a key or prefix of one shard (felix_client_watch_cache) or across every shard
(felix_client_watch_cache_sharded), both optionally retained, read withfelix_cache_watch_nextand
resumed fromfelix_cache_watch_resume_offsets. - The C ABI (
crates/sdk/felix-capi) runs atomic commits, conditional publishes and state reads (#1493):
felix_client_commit,_commit_on_shard,_commit_ifand_commit_if_on_shard,felix_client_publish_ifand
_publish_if_on_shard, andfelix_client_state_getand_state_get_on_shard. A commit that does not hold exactly
one event on one stream is refused at the call. A new status,FELIX_STATUS_UNSUPPORTED, reports a broker that
does not serve commits. - The C ABI test (
crates/sdk/felix-capi/tests/c/pubsub.c) is now a conformance harness (#1493): it tags the
felix-conformancecatalogue scenarios it demonstrates, records a reason for each one it skips, andrun.sh fixture
merges the shared and static builds into a results document at$FELIX_CONFORMANCE_RESULTSthat
felix-conformance verifyreads. CI reports the verdict without gating on it until the fault scenarios land. - The C ABI gains
felix_client_endpoints(#1493), which lists the brokers a client would try, seeds included, so a
caller can see that one seed was enough to discover the cluster. The C conformance harness now covers the
connection-fault scenarios and the broker-loss ones, and CI's C verdict gates. - The C ABI (
crates/sdk/felix-capi) runs consumer groups on a shard (#1493):felix_client_group_poll
with a named consumer, visibility timeout and reclaim, the settles_ack,_nack(with a delay),
_extend,_dead_letter,_discardand_redrive, and_create,_seek,_describe,_delete
and_dead_letters.felix_client_stream_shardsreturns a stream's shard count so a caller can run
one group per shard. - The C ABI (
crates/sdk/felix-capi) covers the rest of the Rust client surface (#1493):felix_client_group_sharded
withfelix_sharded_group_pollfor a consumer group across every shard, the stream-wide
felix_client_group_create_stream,_seek_stream,_describe_streamand_delete_stream, and
felix_client_connect_withwithFelixConnectOptionsso a client can ask for each record's publisher and append time,
read withfelix_event_publisher,felix_event_timestampand the newFelixGroupRecordfields.FelixGroupRecord
grew four fields, which changes its layout; regenerate bindings fromfelix.h. - The C ABI (
crates/sdk/felix-capi) runs idempotent producers (#1493):felix_client_idempotent_producermakes a
FelixProducer, andfelix_producer_publishand_publish_batchessend batches that land once however many times
they are sent,_abandon_in_doubtgives up on a stream's batches in doubt,_idand_id_forread the producer
id, and_closedrains and stops it. A producer used after_closereportsFELIX_STATUS_CLOSED. - The C ABI (
crates/sdk/felix-capi) publishes everything the Rust cluster client does apart from
idempotent and conditional writes (#1493).felix_client_publishtakes an optional routing key
and aFelixDelivery(FELIX_DELIVERY_AT_LEAST_ONCEresends after a reconnect, keyed or not).
felix_client_publish_batchsends a batch to one shard, unkeyed or by key,
felix_client_publish_on_shardto a shard the caller names, andfelix_client_publish_records
takes a key per record and sends each shard its share. Batch payloads are passed end to end in
one buffer with an array of lengths, and the callback gets aFelixRecordResultper record with
its status and log offset. - The C ABI (
crates/sdk/felix-capi) subscribes to a named shard and to every shard of a stream
(#1493).felix_client_subscribe_shardedandfelix_client_resubscribe_shardedreturn a
FelixShardedSubscriptionread withfelix_sharded_next, which delivers records and
lost, recovered, moved and stream-deleted notices per shard;felix_sharded_positionsgives the
offsets to resume from.felix_client_offset_for_timefinds the offset for a time,
felix_subscription_close_reasontells an ended stream from a deleted one, and
felix_subscription_start_offsetreturns where a subscription began. - The C ABI can refresh a token:
FelixConnectOptionsgainedtoken_fetch,token_user_dataandtoken_release, and a fetch completes itsFelixTokenReplywithfelix_token_reply_okorfelix_token_reply_errfrom any thread. The connecttokenmay beNULLwhen a fetch is set.FelixConnectOptionsgrew, so regenerate bindings fromfelix.h. - C# SDK,
Felix.Client(sdks/dotnet): connect, publish (single and batch) and subscribe to a
shard, bound to the C ABI over the Rust client. Targets net8.0 and net10.0 on Linux, macOS and
Windows, each on x64 and arm64. The native libraries will ship in the NuGet package in a later
release; until then, buildfelix-capiand setFELIX_NATIVE_LIB. Failures are typedFelixExceptions with the C ABI's retry classes, and
cancellation tokens cancel the native call. AdotnetCI job builds and tests it natively on all
six platform and architecture pairs and runs the fixture scenarios on Linux x64. It is not packaged or released yet. - C# SDK (
sdks/dotnet): consumer groups.FelixClient.JoinGroupgives aFelixGroupfor one shard
(poll with a named consumer, visibility and reclaim; ack, nack, extend, dead-letter, discard, redrive;
create, seek, describe, delete),OpenShardedGroupAsyncaFelixShardedGroupover every shard, and
GetShardCountAsyncand the*StreamGroupAsynccalls run the group admin across a stream.
FelixClientOptions.Publishersand.Timestampsask brokers for each record's publisher and append
time, set onFelixEventandGroupRecord. Tests tag conformance scenarios with[Scenario]and
write theFELIX_CONFORMANCE_RESULTSdocument; CI runs the verdict for C# without blocking on it. - C# SDK (
sdks/dotnet): caches, counters and cache watches.FelixClient.Cachegives aFelixCachewith
put (with a TTL), get, versioned get, delete,PutIfAbsentAsync,PutIfVersionAsync,DeleteIfVersionAsync
and counter add and get.WatchKeyAsync,WatchPrefixAsyncandWatchShardedAsyncopen a
FelixCacheWatch, anIAsyncEnumerable<WatchItem>that surfaces lag, shard moves and
state-complete as items, withRetainedCount,fromOffsetandResumeOffsets()for resuming.
Fixture tests tag the cache, counter and watch conformance scenarios. - C# SDK (
sdks/dotnet): atomic commits, conditional publishes and idempotent producers.
FelixClient.CommitAsyncwrites an event and state changes (CommitOp.Publish,Enqueue,Put,
Delete) as one record,CommitIfAsyncandPublishIfAsyncwrite only at an expected offset and
answer a refusal asConditionalWritewith the tail, andGetStateAsyncreads a key's value, version
and as-of offset; each takesshard:to name a shard.CreateProducerAsyncreturns aFelixProducer
whosePublishAsync,PublishBatchAsyncandPublishBatchesAsynccannot land twice, with
AbandonInDoubtAsync,GetId,CloseAsync(timeout)andDisposeAsync(a graceful close) or
Dispose(frees without waiting). Fixture tests tag the commit, conditional-publish and
named-shard commit conformance scenarios. - C# SDK (
sdks/dotnet): parity with the C ABI, except the token provider.SubscribeShardedAsync
andResubscribeShardedAsyncopen every shard as aFelixShardedSubscriptionthat reports
records and shard changes (ShardItem) and givesPositions()to resume from;PublishOnShardAsync,
PublishRecordsAsync(aPublishResultper record),OffsetForTimeAsyncandGetEndpointsAsync
bind the remaining exports.scripts/check_csharp_bindings.pyfails CI when an export of
felix.his unbound and not allowlisted with a reason, and the C# conformance verdict now blocks. - C# SDK (
sdks/dotnet):FelixClientOptions.TokenProvider, aFunc<CancellationToken, ValueTask<string>>
over the C ABI's token fetch callback, so a client outlives its first token.Tokenbecomes optional
when a provider is set. The library keeps the timing (lazy, refresh at two thirds of a token's life, one
retry after a refusal, one call at a time, a 30 s wait); the provider'sCancellationTokenfires after
30 s and on dispose, and its exception message reaches the caller'sFelixException(Auth). The SDK
now binds every export offelix.h. - A Python
AsyncClienttoken_provideris cancelled as an asyncio task when its call passes the 30 second fetch timeout and when the client goes away; the cancellation counts as a failed fetch. A blockingClientprovider cannot be cancelled, so it is joined: the next operation waits on the outstanding call instead of starting another. - The Python client takes
token_provider=onClient,AsyncClient.connectandwith_identity: a function (or, forAsyncClient, anasync def) that returns a fresh Felix token. It wraps the Rust client'sRefreshingToken, so the client refreshes at two thirds of a token's life, keeps a still-valid token when a fetch fails, retries a refused token once, and never runs two fetches at once.tokenis optional when a provider is set. - The TypeScript client takes a
tokenProviderinClient.connectandwithIdentity: a function
(signal: AbortSignal) => string | Promise<string>. The signal aborts at the 30 s fetch timeout and
once the last handle using the token is released (the client, itswithIdentityclients, open
subscriptions); a provider that ignores it is joined, not called again. The client calls it lazily, one call at a time, when
a stream authenticates without a valid token, and keeps a still-valid token if a fetch fails. It
is the RustRefreshingTokenunderneath, andtokenis now optional when a provider is set.
Breaking:Client.connect(addrs, tenantId, { token, tokenProvider, serverName, caFile, offerAlpn })
andclient.withIdentity(tenantId, { token, tokenProvider })now take an options object in place
of the positionaltoken,serverName,caFileandofferAlpn. An unknown option or an extra
positional argument throws aTypeError.
Changed
- The C ABI (
crates/sdk/felix-capi) is asynchronous (#1493).felix_client_connect,
felix_client_publish,felix_client_subscribeandfelix_subscription_next(which replaces
felix_subscription_next_event) return at once and report their result to a callback, exactly
once, with the failure's message passed to the callback instead of the per-thread last error.
Each takes an optionalFelixCancelToken; a cancelled call, or one pending when its handle is
freed, completes with the newFELIX_STATUS_CANCELLED, and a publish that may already have
been sent withFELIX_STATUS_OUTCOME_UNKNOWN. The blocking forms are gone. A subscription
allows one pending read at a time. felix_client_publishin the C ABI takeskey,key_lenanddeliveryafter the payload
(#1493). PassNULL, 0, FELIX_DELIVERY_AT_MOST_ONCEfor the previous behaviour.felix_client_subscribein the C ABI takes ashardbeforestart(#1493). Pass0for the
previous behaviour.- A refusal of
publish_if,commit,state_getand the other requests that share the group round trip now names the operation (publish_if refused: ...) instead of readinggroup request refused(#1531). RefreshingTokentreats a fetch that takes longer thanFETCH_TIMEOUT(30 s) as failed, so it keeps a still-valid token instead of waiting on the provider forever.- The release workflow now sends a
felix-releasedispatch to the companion repos listed in.github/companions.txt, so each can open a bump PR.
Fixed
A token every broker refuses at connect is now an auth error rather than "no broker answered", and the connect timeout no longer includes the token provider's fetch, which is cut off at 30 s whatever the provider. A token refused mid-handshake is refetched within the connect timeout.
ClusterClient::publish_if,commitandstate_get(and their_on_shardandcommit_ifforms) now ask again after a codednot_leader, with the client's reconnect backoff and attempt bound, as a plain publish does (#1530). Anot_leaderis refused before anything is applied, so the retry cannot write twice; other refusals, includingleadership_lostandquorum_timeout, are still returned as they came. The broker answers these requests with the typedNotLeaderredirect when leadership moves between its routing check and the write.
A replication pass waiting on a majority is no longer cut short by the tick, a route change or another shard's append, which made the next pass ask the same followers again and could push a quorum publish past its timeout on a slow link. Only this shard's new records or a busy follower's answer end the wait.- A token provider failure is final for the operation that asked (#1533). A subscribe open used to retry it up to five times, calling the provider each time (a rotation of the refresh token per call, or five 30 s waits for a hung one), depending on the message text. It now fails once with the new
felix_client::AuthError, carrying the provider's message, and a hung provider fails the open at the 30 s fetch limit instead of the 10 s open timer. A broker's credential refusal is anAuthErrortoo. The Python, TypeScript and C bindings classify auth failures by this type; the substring checks for "token" and "forbidden" are gone, so an untyped error that merely mentions a token is no longer reported as auth. - The macOS release build of
libfelix.dylibloads again: rustc's post-linkstripleft the library's LINKEDIT string pool misaligned and dyld rejected it.felix-capinow skips that pass, and CI loads the release library on every platform.
Documentation
- New C client page in the docs site: building and linking
libfelix, the callback model, each feature area, the status codes, and what the C ABI does not cover yet. The C ABI lines in the client overview, FAQ and status table now match the code. - The client pages say where a token comes from and how each client handles expiry. Choosing a Client now has a "Getting a token" section covering token exchange, development tokens, refresh, and what an expired token does to an open connection.
- The v0.6.0 performance page now says that the two largest Felix-vs-NATS ratios (14.8x and 9.5x) are partly publisher window differences, not yet measured apart, and points at the matched-window rerun (#1507).
- The old "Real-Network Performance (Azure)" page is retired. Its address redirects to the v0.6.0 performance page, and the two use-case claims that rested on it now describe the mechanism without numbers.