# Changelog

## v0.5.0 (2026-11-01)

### Bug fixes

- When a follower forwards a registration and the leader changes at the same
  time, the registration is now re-driven to the new leader rather than answered
  with `no`.
- A buffered unregister is no longer lost when the leader changes mid-commit.
- A partition heal no longer stalls the leader on conflict-detection liveness
  probes; they run off its loop.
- `whereis_name/1` and `get_metadata/1` no longer briefly read a bound name as
  `undefined` while a member re-baselines its replica (a resync or handoff).
- A leader that dies after a commit but before broadcasting it no longer stalls
  the next leader's handoff for 2 seconds. The new leader proceeds as soon as
  every member has seen the old leader go down.
- A follower could answer `yes` to a forwarded registration that a degraded handoff
  had lost. It held the answer until its replica reached the commit version, but the
  new leader's versions could get it there without that commit. A held `yes` now
  counts only in the epoch that committed it, and is otherwise sent again.

### Enhancements

- `dgen_registery_member`'s internal implementation is refactored into
  `dgen_registry_replica`, `dgen_registry_intents`, `dgen_registry_pipeline`,
  and `dgen_registry_conflicts`.

### Breaking changes

- Some internal legacy behavior has been removed. It may affect systems during
  a rolling upgrade from 0.4.x to 0.5.x.
- The handoff gather's peer reply changed shape. A 0.5.x leader skips a 0.4.x
  member's replica, and the reverse, so don't run the two in one cluster.

## v0.4.2 (2026-10-04)

### Enahancements

- Support retrieving tenant via anonymous function

## v0.4.1 (2026-09-04)

### Bug fixes

- Fixed a race where an unregister could delete a binding it didn't own,
  freeing a name twice under ordinary concurrency.
- Fixed the DST harness's readiness poll hanging on a virtual clock deadline.
- Fixed a lock holder that was busted mid-run releasing its successor's lock.
- Fixed a lock holder that was busted mid-run overwriting its successor's
  writes.

### Enhancements

- **`dgen_registry`** is now stable, and no longer experimental.
- The `dgen_server` lock is now fenced. A busted holder is refused at commit and
  its message is retried, so `lock_timeout` can be tuned for recovery speed.
  `handle_locked/4` must be safe to run twice. See
  `docs/design/dgen_server_design.md` §4.4.
- Added tests for `lock_timeout` and the `consume_batch` exit points.
- `dgen_registry_member` rewritten as a `gen_statem` with explicit
  searching/assuming/leader/follower states. `sys:get_state/1` now returns
  `{State, Data}`.
- Leadership handoff sped up (~2x faster election window at 200k names) by
  cutting map allocations, shrinking the snapshot encoding, and deferring
  monitor setup off the critical path.
- Expanded TLA+ model and DST invariants covering replication and leader
  handoff.
- Added a per-registry `connectivity` option (`provided_externally`) to let a
  registry share another registry's distribution mesh instead of running its
  own. See `docs/dgen_registry_design.md` §4.6 and §8.

## v0.4.0 (2026-07-12)

### Enhancements

- **`dgen_registry` — a defined consistency model (still experimental).** Full
  contract in `docs/dgen_registry_design.md`. Highlights:

  - **CP and fenced** — the leader is fenced on a backend key, so a deposed leader
    cannot commit; no split-brain, minority partitions refuse writes.
  - **Singleton uniqueness** across any single member failure; pids never persisted
    (~2 durable keys per registry).
  - **Two-holder addressability** — an acked registration is held by ≥2 members;
    tunable via `register_replicas` / `replicate_timeout` / `strict_replication`.
  - **Dynamic membership with an automatic mesh** — nodes join/leave by
    starting/stopping the registry; no external discovery.
  - **Lock-free snapshot reads** — `whereis_name/1` is an `ets:lookup/2` in the
    calling process (eventually-consistent snapshot; empty briefly after member
    restart).
  - **Per-registration metadata** — an `index` map + opaque `data`, set via
    `register_name/3` / `set_metadata/2`, read via `get_metadata/1` or
    `get_metadata_consistent/1`; lifetime is exactly the registration's (§4.7).
  - **Indexed queries** — `query/2` / `query_consistent/2` match AND-equal
    constraints over indexed attributes against a batch-consistent snapshot.
  - **Durable presence** — subscriptions (watch + notify queries under a `SubId`)
    push `{dgen_presence, SubId, Events}` on membership changes; stored durably, so
    they survive full cluster restarts.
  - **Formally verified replication** — core safety properties checked against a
    TLA+ model (`formal/`).

- **`dgen_transaction` (new behaviour)** — owns a single backend transaction in its
  own process with caller-controlled retry; substrate for the registry's group commit.

- **`dgen_server`** — `consume_k` documented; when `consume_k > 1`, inline `call`
  handling is disabled so batching stays in effect. `priority_call`/`priority_cast`
  still bypass the queue.

### Behavioral notes

- **The registry may forcibly kill a registered process to enforce uniqueness.**
  Register only restart-safe processes. Configurable via `terminate_on_conflict`
  (default `true`); see §5.6.

### Breaking changes

- **`dgen_registry:start_link/3` (`SupName` variant) removed** — the registry
  supervisor is nameless by design; hold the returned pid instead.
- **`elector_name/1` removed** in favor of `elector_pid/1`; `member_name/1` and
  `names_table/1` are now identity functions.
- **`register_name/2,3` exits on call timeout instead of returning `no`** — `no` is
  now strictly an adjudicated verdict. Timeout configurable via `register_timeout`
  (default 5000 ms).

## v0.3.0 (2026-06-17)

### Enhancements

- **Transactional callbacks** — optional `handle_cast_tx/3`, `handle_call_tx/4`,
  `handle_info_tx/3` receive a `tx_ctx()` (open transaction + directory) so callbacks
  can read/write keys atomically with the state commit.
- **`lock_timeout` option for `dgen_server`** — backstop timer clears a stale
  distributed lock left by a killed consumer. Default `infinity` (old behavior);
  `dgen_registry` uses 6000 ms.
- **`dgen_registry`** (experimental) — OTP-compatible process registry over the
  storage backend, addressed via `{via, dgen_registry, {Name, LogicalName}}`. Writes
  and consistent reads go through an elected leader; `whereis_name/1` is a local
  snapshot read. Partition recovery is token-fenced so stale `member_down` messages
  can't undo a rejoin.

### Breaking changes

- **`DGenServer` → `DGen.Server`** (`lib/dgen/server.ex`).
- **`handle_locked/3` → `handle_locked/4`** — a `db_ctx()` map is prepended as the
  first argument.

## v0.2.0 (2026-04-05)

### Enhancements

- **Dead-letter queue** — opt-in via `dead_letter_threshold`; messages crashing the
  consumer that many times move to a DLQ, callers raise `{dead_letter, N}`, and the
  optional `handle_dead_letter/2` callback fires.
- **`dgen_server:outbox_cast/1,2`** — returns a closure for enqueuing a cast
  atomically inside the caller's own backend transaction.

## v0.1.0 (2026-02-22)

Initial release.
