Changelog
View SourceAll notable changes to this project are documented here. The format follows Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased
0.2.0 - 2026-08-20
Added
Option validation in
start_link/1— raisesArgumentErroron unknown keys, mistyped values, a missing or empty:queue, and a:reconnect_min_msabove:reconnect_max_ms. Unknown keys matter most: a mistyped:reconnect_minpreviously passed throughKeyword.get/3and silently yielded the default. Hand-rolled rather than delegated to an options library, which would be a fourth runtime dependency for nine flat options.PhiAccrualAmqp.Consumer.status/2— reports:connected?,:queue,:consumer_tag,:backoff_ms,:disconnected_since,:last_delivery_atand:keys_tracked. The two timestamps are local monotonic milliseconds. Connection attempts run synchronously inside the consumer, so the call can block behind one; thetimeoutargument defaults to 5000 rather than:infinity.:keysmetadata on[:phi_accrual_amqp, :connection, :down]— the detector keys the consumer was feeding when delivery stopped. A policy layer can use it to distinguish a phi excursion caused by a transport outage from one that is evidence about the entities themselves. The consumer takes no action on the estimators: it does not own them (PhiAccrual.observe/2auto-tracks, and other sources may feed the same key), andPhiAccrual.untrack/1— core's only lever — destroys the estimator's calibration outright.:max_tracked_keysoption (default 1000) — bounds the set of detector keys a consumer remembers, with least-recently-seen eviction. The default resolver returns the routing key, so a wildcard binding could otherwise grow the set without limit.[:phi_accrual_amqp, :keys, :evicted]telemetry event — measurements%{tracked}, metadata%{queue, key, incoming_key, max_tracked_keys}. Fires when the cap displaces a key, signalling a broader binding or resolver than intended.PhiAccrualAmqp.Consumer.child_spec/1— reads the standard supervisor keys:id,:restartand:shutdownfrom the consumer option list without forwarding them tostart_link/1.:iddefaults to:namewhen one is given, and otherwise to{PhiAccrualAmqp.Consumer, queue}, so one consumer per queue can be supervised together without spelling out an:id.:connectand the:connection_opts-over-:urlprecedence rule are now documented. Both were observable behaviour with no description in any document; neither is new in this release.
Fixed
- The consumer now traps exits, so
terminate/2runs on supervisor shutdown. Without it the process was killed outright and the connection — started under theamqp_clientsupervision tree rather than linked to the consumer — outlived the consumer that opened it.
Changed
Every telemetry event now carries a non-empty measurement map.
[:sample, :received]gains%{monotonic_time, system_time}, wheremonotonic_timeis the exact value handed toPhiAccrual.observe/2— so a handler can derive inter-arrival intervals directly instead of reconstructing them, and the clock-discipline promise becomes inspectable rather than merely documented.[:connection, :down]gains%{tracked}, counting what its:keysmetadata lists, becauseTelemetry.Metricscannot aggregate a list. The remaining events carry%{system_time}. Handlers that pattern-matched on%{}continue to match; handlers that asserted an empty map do not.This is
phi_accrual_amqp's own committed telemetry schema. It is not a step toward unifying payloads withphi_accrual_udp: the transports are deliberate specializations of a transport-agnostic core, the only contract between them isPhiAccrual.observe/2, and per-transport handlers are the intended model rather than a gap left to close.Connections are opened with
heartbeat: 10andconnection_timeout: 5_000. The heartbeat matches the AMQP client's own default and is now pinned explicitly. The timeout replaces the client's 60s (URI) and 50s (keyword) defaults, bounding how long a connection attempt — and anystatus/2call queued behind it — can block against a broker that accepts packets without completing the handshake. A keyword list passed as:connection_optsis merged over them and wins; a binary:connection_optsis a URL, so there is nothing to merge and the defaults stand — and they also take precedence over the same values embedded in the URL's query string, since the client resolves explicit options ahead of parsed URI parameters.A server-initiated
basic.cancelnow emits[:phi_accrual_amqp, :connection, :down]withreason: :server_cancelled, and marks the consumer disconnected. Previously the cancel path went straight to teardown, so a cancelled consumer reported no outage at all while it sat in backoff with no subscription.[:consumer, :cancelled]is unchanged and still carries the consumer tag.Reconnect backoff is now jittered. The ceiling still doubles per attempt between
:reconnect_min_msand:reconnect_max_ms, but the delay actually waited is drawn uniformly between the floor and that ceiling. Undithered doubling had every consumer attached to a restarting broker retry in lockstep, stampeding a broker that was still recovering. No configuration change is required, and the documented bounds are unchanged.start_link/1no longer registers the process underPhiAccrualAmqp.Consumerby default. A consumer runs unnamed unless:nameis given. The previous default made a second consumer fail to start with{:error, {:already_started, pid}}, which blocked the one-queue-per-node topology described in the README. Callers that relied on the implicit name should passname: PhiAccrualAmqp.Consumerexplicitly.
0.1.0 - 2026-05-18
Initial public release.
Added
PhiAccrualAmqp.Consumer— AMQP 0-9-1 consumer with connection/channel lifecycle, server-cancel handling, and exponential-backoff reconnect between:reconnect_min_ms(default 1s) and:reconnect_max_ms(default 30s). Feeds broker deliveries toPhiAccrual.observe/2using local monotonic receipt time.PhiAccrualAmqp.Envelope— pure projection from AMQP delivery metadata to aPhiAccrual.detector_key/0. Configurable:key_resolver, defaulting torouting_key.- Consumer-only by design — no synthetic heartbeat publisher is shipped, to avoid conflating broker liveness with node liveness.
- Telemetry events under
[:phi_accrual_amqp, ...]:[:connection, :up | :down],[:consumer, :registered | :cancelled],[:sample, :received], and[:extract, :error]. - Broker-backed integration suite, gated behind the
:integrationExUnit tag. - Requires
phi_accrual ~> 1.1(forPhiAccrual.detector_key/0).
Notes
- AMQP 0-9-1 only (RabbitMQ-class brokers). Not compatible with AMQP 1.0 brokers such as ActiveMQ Artemis, Apache Qpid, Azure Service Bus, or Solace — AMQP 1.0 is a different, incompatible protocol.
- Alpha. Public API and telemetry schema may change before
v1.0based on real-deployment feedback. - Known. The
[:sample, :received]telemetry payload is not drop-in compatible withphi_accrual_udp— see the README cross-transport note for the shape differences. The channel-death:unexpected_delivery_and_no_default_consumerlog surfaced byamqp_client'sSelectiveConsumeris an expected, tolerated reconnect race; the Consumer recovers via the normal reconnect path.