---
title: "Release 2026-08-18 - Changelog"
url: "https://nsta1.github.io/Orleans.Lattice/changelog/2026-08-18.html"
source: "https://github.com/NSTA1/Orleans.Lattice/blob/release/9.9/CHANGELOG.md?plain=1#L1619-L1631"
documents: "Orleans.Lattice 9.9.0 (release line 9.9)"
built: "2026-10-04"
all-pages: "https://nsta1.github.io/Orleans.Lattice/llms.txt"
---
# Release 2026-08-18

Part of the [changelog](../CHANGELOG.md).

A per-package patch advances `Orleans.Lattice.Replication` to `9.0.2` and `Orleans.Lattice` to `9.0.5` (see Changed and Fixed); all other packages remain at their `9.0.x`/`9.0.0` versions.

## Changed

- **`MvRegister` writes and single-valued reads no longer allocate on the steady-state hot path.** A write (`Set`) previously built a fresh `List` plus its backing array on every call, and a single-valued read (`Values()`, which the typed `MvRegister<T>` accessor read path funnels through) allocated a new one-element array on every read. `Set` now compacts its live entries in place, and the single-valued read snapshot is cached and reused between writes, so a steady-state (single-valued) register - the overwhelmingly common shape - writes and reads with zero per-operation allocation. No public API, behaviour, or wire-format change; ordering and conflict semantics are identical. (`Orleans.Lattice` 9.0.5, [#1509](https://github.com/NSTA1/Orleans.Lattice/issues/1509), [#1510](https://github.com/NSTA1/Orleans.Lattice/issues/1510))

## Fixed

- **Replicated cross-tree atomic writes now converge on peer clusters backed by Azure Table grain storage instead of stalling.** The receiver-side barrier grain for a replicated cross-tree atomic write (`LatticeCrossTreeReceiverGrain`) is keyed by a compound of `(originClusterId, operationId)`. That key was built by joining the two halves with the ASCII Unit Separator (`0x1F`) control char - chosen so neither half could be confused with the other - but the receiver grain persists through a storage provider, and Azure Table grain storage carries the primary key into the request URL and the Partition/Row key columns, both of which reject control chars `0x00-0x1F`. On a real Azure deployment the receiver therefore failed to activate with an HTTP 400 "Bad Request - Invalid URL" on `ReadStateAsync`/`WriteStateAsync`, so the receiver barrier for cross-tree atomic writes stalled and those writes converged across regions only slowly and erratically; single-tree atomic writes and plain writes were unaffected. The whole test and chaos suite stayed green because in-memory grain storage has no key-character restriction, hiding the defect from CI. The key is now a **length-prefixed** encoding (the decimal length of `originClusterId`, an underscore, then the two halves concatenated), which stays a single deterministic, control-char-free string while remaining unambiguous regardless of the characters in either half. Changing the key changes the receiver grain's identity, which is acceptable because the Azure Table path was non-functional and had no durable state to preserve. (`Orleans.Lattice` 9.0.5, [#1529](https://github.com/NSTA1/Orleans.Lattice/issues/1529))
- **Cross-tree atomic writes now become visible on peer clusters instead of hanging invisibly forever.** A cross-tree atomic write (`IGrainFactory.SetManyAtomicAsync(IReadOnlyList<LatticeTreeBatch>, operationId, ct)`) committed on its origin cluster but its keys never appeared on peer clusters, while single-tree atomic writes and plain writes on the same trees converged normally. The receiver's multi-entry batch apply path (`ReplicationApplier.ApplyBatchAsync` -> `ApplyOriginRunAsync`) had no case for saga terminal-mark records (`TxCommit` / `TxAbort`): unlike the single-entry `ApplyAsync` path, which intercepts terminals and routes them through the terminal seam, the batch path let a terminal fall through to `ApplyPointAsync`, whose op switch threw `Unsupported point-apply op`. Because the production shipper coalesces a saga's contiguous WAL entries - its prepared writes *and* its terminal - into a single inbound batch, the terminal arrived batched, the whole batch faulted on apply, and the cross-tree receiver barrier (which holds every participating tree's keys pending until a terminal has arrived for each) never released, so the peer's keys stayed invisible indefinitely. The batch path now intercepts `TxCommit` / `TxAbort` and routes them through the same terminal seam as the single-entry path (flushing any deferred LWW/CRDT batch first so the terminal linearizes in WAL order, and staying high-water-mark-neutral), forwarding the `CrossTreeOperationId` / `CrossTreeParticipants` barrier metadata intact. Single-tree atomic convergence and local idempotency semantics are unchanged. (`Orleans.Lattice.Replication` 9.0.2, [#1525](https://github.com/NSTA1/Orleans.Lattice/issues/1525))

Newer release: [Release 2026-08-19](../changelog/2026-08-19.md). Older release: [Release 2026-08-17](../changelog/2026-08-17.md). Contents: [Changelog](../CHANGELOG.md).
