All notable changes to ankusa_rabbitmq are documented here. Format follows
Keep a Changelog; this project
follows Semantic Versioning.
Unreleased
0.7.0 - 2026-10-28
Added
:max_message_bytes(default 128 MiB, RabbitMQ'smax_message_size; DEL-5): the size preflight ofAnkusa.Sink.Messaging. Requires anankusarelease with themax_message_bytessink callback; publish core first.Sink.RabbitMQ: a hook of anordered: truesink carries the lane's key as theankusa_ordering_keyAMQP header (consumer-side ordering, ADR 0004).
Changed
- Requires
ankusa ~> 0.7(Ankusa.Sink.Messaging,Ankusa.Sink.Connections); publish core first. Ankusa.Sink.RabbitMQis built on core'sAnkusa.Sink.MessagingandAnkusa.Sink.Connections. What it publishes is unchanged.
0.6.0 - 2026-10-14
Added
:max_inflight(default 256): publishes awaiting a confirm per connection; past it a publish answers{:error, :busy}without being sent.:idle_timeout_ms(default600_000,0never, at least 16 s): a connection with no delivery for that long is closed (Ankusa.Sink.Reaper, requires core's) and the next delivery opens a new one, so a deleted or re-pointed source no longer leaves its connection open for the life of the node.- Every publish carries the
ankusa_idempotency_keyAMQP header: the same tenant-scoped key as the message'sidempotency_key(Ankusa.Envelope.idempotency_key/1), always present. Requiresankusa0.5 or newer: the key comes fromAnkusa.Envelope.idempotency_key/1, which 0.4.x does not have, so the Hex requirement is now~> 0.5. - AMQP
message_idis the hook id; theankusa_dedupe_keyandankusa_replay_idAMQP headers are set when present.
Changed
- Requires
ankusa ~> 0.6(Ankusa.Sink.Reaper); publish core first. - Stopping a connection process closes its AMQP connection:
amqp_clientowns that connection, so it used to outlive the process. Publishes waiting on it answer{:error, :not_connected}. - Confirms are asynchronous: many publishes are outstanding on the channel at once and each caller is answered on its own confirm, so one slow confirm no longer serializes every delivery on the connection.
- A publish still queued in the connection when its caller's deadline passed
answers
{:error, :expired}and is never sent. - The connection dials after it starts (
handle_continue), so a dead broker never blocks the supervisor; it is registered under a digest of the URL, and itsformat_status/1redacts the URL, so a password never appears in a process name,:sys.get_state/1or a crash report. - The connection's child spec carries only the options the connection reads,
with the URL inside a closure, so
:sys.get_status/1ofAnkusa.Sink.RabbitMQ.Supervisor, or a supervisor report, never prints the password. - Breaking:
Ankusa.Sink.RabbitMQ.Connection.publish/4's fourth argument is a keyword list of properties (message_id:,headers:), not a header list. - Breaking: every publish is mandatory. A publish no queue is bound to
receive is no longer reported as delivered:
deliver/3answers{:error, {:unroutable, routing_key}}instead of:ok, so the hook is retried and then dead-lettered (a503underwal.type: none) rather than settled while the broker kept nothing. There is no option to turn this off.
Fixed
- amqp_client's processes hold the connection password encrypted: the
application sets a random
credentials_obfuscationsecret at start unless the host already set one, so:sys.get_status/1or a crash report of an amqp_client process no longer prints{:plaintext, password}. - A channel the broker closes (a
404publishing to a deleted exchange, a406) is reopened at once on the same connection, re-declaring the exchange. The publish it interrupted answers{:error, {:channel_closed, reason}}. Previously every later publish failed with{:publish_failed, _}until the node restarted. :confirm_timeout_msis milliseconds. The confirm wait read it as seconds, so an unconfirmed publish blocked the connection for up to 70 s while its caller gave up at 15 s. Each publish now waits for its own confirm only, so one stale publish no longer delays later ones.- A channel that fails setup (for example a
406onexchange.declare) no longer leaks an AMQP connection on every retry; the connection is kept and only the channel is retried.
0.4.0 - 2026-10-02
Added
Ankusa.Sink.RabbitMQimplementsAnkusa.Sink.describe/2, advertising its broker, vhost, exchange, and routing key to the AsyncAPI document the admin API serves. Credentials in:urlare never part of it.Ankusa.Sink.RabbitMQ.Connection.default_url/0.
Changed
- The Hex requirement on
ankusais~> 0.4(was~> 0.2):describe/2needsAnkusa.Sink.Description, which core has from 0.4.0.
Removed
- The sink no longer implements the removed Ankusa.Sink.ordering_key/2 callback:
deliveries are unordered. The routing key still decides the queue, so
RabbitMQ's per-queue ordering is unchanged. Requires the matching
ankusacore.
0.3.0 - 2026-10-01
No code changes. Released as 0.3.0 in step with ankusa 0.3.0 and the other packages in this release.
0.2.1 - 2026-09-28
Added
- A
sha256field on a claim-checked message: the lowercase hex digest of the payload, for the reader to verify the bytes it redeems against. It used to live inside the claim ref.
Changed
- The message
claimfield isurn:ankusa:claim:v1:<tenant>:<claim_id>: a tenant and a canonical (uppercase) ULID, where it used to carry the pack id, byte offset/length, and digest. The messagevstays1. Both come fromAnkusa.Sink.Messageinankusa0.2.1.
0.2.0 - 2026-09-27
Added
- Ankusa.Sink.ordering_key/2, implemented by this sink as the routing key: RabbitMQ orders per queue, and the routing key decides the queue. Dispatch now serializes deliveries sharing a key and runs different keys concurrently.
Changed
- The message
claimfield is now a claim-check ref URN string instead of a nested ticket object, breaking for consumers; the messagevstays1. :inline_max_bytesdefaults to 64 KiB (was 8 KiB), read fromAnkusa.Sink.Message.inline_max_bytes/1.- Depends on
ankusa~> 0.2: core 0.1 has neitherAnkusa.Sink.Message.inline_max_bytes/1nor Ankusa.Sink.ordering_key/2.
0.1.0 - 2026-09-23
Added
Ankusa.Sink.RabbitMQ: publishes delivered hooks to a RabbitMQ exchange (never a queue: consumers own their own queue/binding). Bodies under:inline_max_bytesride along base64-encoded; larger bodies are checked in throughAnkusa.ClaimCheck, with the message carrying a ticket. Publisher-confirmed delivery; supervised, auto-reconnecting connection per(instance, exchange).- Messages carry a format version,
"v": 1. Additive: consumers that ignore unknown keys need no change.
Changed
- The message is built by
Ankusa.Sink.Message(inankusa), shared withankusa_kafka, so both sinks publish byte-identical messages.