Skip to content

Release v3.5.1

Choose a tag to compare

@github-actions github-actions released this 25 Sep 16:48
682152f

Applications on cratis_chronicle 3.5.0 can lose data silently: projections leave fields empty, reducers never run, constraints fail to register, uniqueness on multi-word fields isn't enforced, and seeding appends duplicate events on every start. This release fixes all of those. Every fix below was checked against a Chronicle 19.4.8 development kernel, as well as by specs.

Action required when upgrading

  • Elixir 1.18 or later is now required. The package declared ~> 1.14, but every grpc 0.11 release depends on googleapis 0.1, which already required 1.18, so older versions could not compile the client. Nothing changes if your project already builds.
  • Check seeded event sources for duplicates. Under 3.5.0 seeders appended their events again on every start and reconnect. From 3.5.1 on, an event source that already has events is skipped, but duplicates already stored stay in the event log. Correct them with your own events if they matter.
  • Check reducer read models and multi-word projection fields. Active reducers now register and process their events from the start of the log, passive ones compute when read, and projections now fill fields they left empty before. Both change what reads return, so check anything that relied on the empty values.
  • Read model query pages count from 0. Chronicle.ReadModels.query/2 defaulted to page 1 and rejected 0, so it could never return the first page. It now defaults to 0. If you passed page: 1 for the first page, you were getting the second one; pass page: 0.
  • If you worked around the mapping defect with camelCase strings such as set: [owner_name: "ownerName"], they keep working. You can go back to set: [owner_name: :owner_name].

Fixed

  • Projections map multi-word event fields. Events are stored as camelCase JSON, but projection expressions sent atoms such as :owner_name as-is, so those fields were never set, with no error. Atom field references and key expressions are now sent as camelCase; a join's on: still names the read model property. Multi-word fields that the event and read model share are mapped automatically, following the kernel's own AutoMap rules: not for a from that only aggregates, never over an explicit mapping or a no_auto_map field, and never from an event field a join's explicit mapping already reads.
  • Boolean and float constants in projections work. set: [on_loan: true] was sent as a field name and never applied. It is now sent as a constant, like integers already were.
  • Reducers register. Every registration attempt failed with an UndefinedFunctionError for Cratis.Chronicle.Contracts.Observation.Reducers.SinkDefinition, so no reducer ever ran and its read model read as {:ok, nil}.
  • Constraints with remove_constraint register. The releasing event type was sent in a form the contract can't encode. Because constraints register before read models and projections, one such constraint stopped the whole client from reaching :registered. Several event types can now release the same constraint.
  • Uniqueness on multi-word fields is enforced. unique(:owner_email) checked a property the stored events don't have, so duplicate values were accepted.
  • has_events_for?/2 answers correctly. It read the result from the wrong part of the kernel's response, so it always returned {:ok, false}, and seeding re-appended seed data every time the client registered.
  • Tail and next sequence number lookups return the real value. Without explicit filters they narrowed to the "Default" source and stream type and returned {:ok, 0}. An omitted filter now includes every source and stream type.
  • Failed transport calls return errors. A call made while the channel was dying exited the calling process. A call the kernel rejected before replying, such as an unauthenticated one, raised a KeyError inside grpc. Both now return {:error, %GRPC.RPCError{}}, and the response process the call had already started is stopped. Exceptions raised outside grpc, such as protobuf encoding errors, still raise.
  • The compatibility check sends the pinned contracts version. The package update to contracts 19.4.7 left the version it sends to the kernel at 19.4.0.

Changed

  • The :retry_attempts option is documented as accepted and ignored. Reconnection has never stopped after a number of attempts.
  • The documentation and console sample describe the fixed behavior, 22 shared Chronicle examples gain an Elixir tab, and several existing snippets that passed plain structs to read model functions now define real read models.
  • Package updates now arrive as no-release pull requests, so the build and specs run on them before they reach main.

Compatibility

Verified against a Chronicle 19.4.8 development kernel with cratis_chronicle_contracts 19.4.7, the version this release pins. The client resolves any contracts version, so pair it with a kernel that understands the contracts in your mix.lock. The published 3.5.1 package was also verified against a Chronicle 19.7.0 development kernel with contracts 19.7.0, which a fresh mix deps.get resolves as of 2026-09-25. A 19.6.1 kernel rejects contracts 19.7.0 with {:error, {:incompatible_server, ...}}; update the kernel and mix.lock together (Cratis/Chronicle#4213). Kernels 19.5 and 19.6 were not verified with their matching contracts.