---
title: "Release 2026-08-19 - Changelog"
url: "https://nsta1.github.io/Orleans.Lattice/changelog/2026-08-19.html"
source: "https://github.com/NSTA1/Orleans.Lattice/blob/release/9.9/CHANGELOG.md?plain=1#L1604-L1618"
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-19

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

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

## Changed

- **The `GSet`, `MvRegister`, and `OrMap` CRDT clone/merge hot paths shed avoidable rehashing and allocation.** Three independent primitive optimisations, each behaviour-, ordering-, and wire-format-neutral: (1) `GSet.Merge` now builds its result as a union pre-sized to the combined operand count and fills it with two `UnionWith` folds, instead of cloning the left operand and growing it element by element - roughly 42% less allocation on the merge (5,016 B to 2,908 B for two 64-element sets, `MemoryDiagnoser`, deterministic). (2) `MvRegister.Clone` and (3) `OrMap.Clone` now copy their per-replica dot `Context` dictionary with the source's own comparer rather than re-specifying `StringComparer.Ordinal`, which restores the `Dictionary` bulk-copy fast path (a comparer mismatch had forced a full element-by-element rehash) - allocation-neutral and about 8-10% less clone CPU at 4,096 entries, and because `Merge` folds through `Clone` the saving carries into merge. This mirrors the existing comparer-preserving copy in `Rga.Clone`. New/extended `MemoryDiagnoser` microbenchmarks (`Crdt gset merge`, and a new `OrMap clone (multi-replica context)`) cover the change. (`Orleans.Lattice` 9.0.6, [#1544](https://github.com/NSTA1/Orleans.Lattice/pull/1544))

## Fixed

- **A checkpointed leaf partition with a momentarily empty cache no longer releases its block pin and licenses a WAL trim of its un-snapshotted prefix.** `BPlusLeafGrain.ResolveDurablePinForPartition` decided a partition was empty - and therefore safe to release its `HybridLogicalClock.Zero` block pin up to the checkpoint - purely from the per-activation in-memory cache, which does not reflect a leaf's durable data in the window between activation and cache hydration: a cold reactivation whose snapshot rehydrate finds nothing (yet the persisted projection checkpoint says the prefix `[0, checkpoint]` was durably applied), or tombstone reaping / compaction emptying a checkpointed partition's cache while its WAL prefix still must replay. Such a partition took the empty branch and reported `(clock, checkpoint)`, releasing the block before the coverage gate ([#1492](https://github.com/NSTA1/Orleans.Lattice/issues/1492)) could run and licensing the shared-shard WAL GC to trim a checkpointed, un-snapshotted prefix; the next cold rebuild then replayed from a WAL whose prefix was gone and came up with its checkpoint below the trim floor, failing to reactivate (`LeafProjectionStaleException`) and silently degrading the affected semantic index to keyword mode. The empty-partition release is now narrowed to a partition that has never durably checkpointed (`checkpoint < 0`, nothing committed to lose); a partition with a persisted checkpoint and a transiently empty cache falls through to the same coverage gate as a cache-populated partition, retaining the `(Zero, -1)` block pin until a covering snapshot exists. Liveness is preserved - snapshot capture stamps every partition's checkpoint into `SnapshotOffsetsByPartition`, dataless or not, so a checkpointed-but-empty partition's block lifts as soon as any snapshot is captured - and the genuinely-empty (`checkpoint < 0`) release path is byte-for-byte unchanged with no new allocations. (`Orleans.Lattice` 9.0.6, [#1535](https://github.com/NSTA1/Orleans.Lattice/issues/1535))
- **The core resilient range scan no longer silently truncates a credential-scoped full-tree read across a cursor reopen.** `ILattice.ScanEntriesAsync`/`ScanKeysAsync` and the `DeleteRangeAsync` drain re-open their underlying cursor when a segment is evicted mid-scan, and re-asserted only the caller's system-origin scope around each reopen - not the ambient credential scope (`LatticeCredentialContext`), which is backed by the same Orleans `RequestContext` and is likewise reset in the iterator's execution flow after the first segment. A caller scanning under a credential but no system-origin therefore lost its identity on the first reopen, and every later segment resolved anonymous, was denied a reject-all key-filter by the fail-closed access gate, and completed with zero rows. The ambient credential is now captured once and re-asserted around every reopened segment and cursor step, symmetric to the existing system-origin handling. This fixes the repository-context background reconcile reading back zero stored nodes and re-bootstrapping the whole index on every pass. (`Orleans.Lattice` 9.0.6, [#1539](https://github.com/NSTA1/Orleans.Lattice/issues/1539))
- **A short-lived leaf now captures a durable snapshot on graceful deactivation, so its WAL stops being retained forever.** A data-bearing leaf holds a `HybridLogicalClock.Zero` "block" pin - which correctly forbids the WAL GC from trimming its checkpointed-but-un-snapshotted prefix (the no-loss gate, [#1535](https://github.com/NSTA1/Orleans.Lattice/issues/1535)) - until a durable snapshot covers that prefix. Snapshot capture only ever had two triggers, both requiring the leaf to stay activated: the activation-time fall-off-log advisory and a periodic recheck every `LeafSnapshotReClassifyEveryNCheckpoints` checkpoints (default 64). A short-lived bursty activation (activate, take a few writes and checkpoints, then deactivate before the cadence threshold) fired neither, so the leaf checkpointed, went dormant, and left its block pin held with no snapshot ever taken - the shared per-partition WAL was then retained without bound even though every consumer had moved on. The leaf now captures a snapshot on graceful deactivation, gated on a genuine this-activation checkpoint advance so a cold reactivation never captures a partial cache and falsely claims coverage; the capture advances durable coverage, the materialiser pin resolves to the covered frontier, and the WAL GC trims the now-covered prefix. The capture is best-effort: if the snapshot store is unavailable the coverage does not advance, the block pin stays in place, and the WAL is retained rather than trimmed ahead of durable coverage, so the no-loss invariant is preserved. Crash deactivations bypass the hook by design; the persisted checkpoint still bounds the next activation's replay. (`Orleans.Lattice` 9.0.6, [#1537](https://github.com/NSTA1/Orleans.Lattice/issues/1537))
- **An already-converged, snapshot-less leaf now lifts its block pin on its next cold reactivation instead of retaining its WAL forever.** The graceful-deactivation capture ([#1537](https://github.com/NSTA1/Orleans.Lattice/issues/1537)) is gated on a genuine this-activation checkpoint advance, which correctly excludes a cold reactivation whose cache was restored without a forward apply. But a leaf that already checkpointed its whole prefix (checkpoint at the WAL head) and never captured a covering snapshot holds a `HybridLogicalClock.Zero` block pin, and on a cold reactivation it fully rebuilds its cache from the start of the readable WAL yet advances no checkpoint - so the deactivation capture stayed suppressed and none of the three capture triggers fired, leaving the block pin (and the shared-shard WAL it retains) held indefinitely. The graceful-deactivation capture now also proceeds when the cache was cold-rebuilt from the WAL start this activation over a pre-existing durable checkpoint: that rebuild leaves the cache holding the entire readable window, a faithful superset of the checkpointed prefix, so stamping coverage is truthful. A brand-new leaf (no pre-existing checkpoint) never qualifies, so its foreground writes are never auto-covered. The one shape that could make such a rebuild unfaithful - a trimmed WAL prefix with no covering snapshot - already throws `LeafProjectionStaleException` at activation (the [#945](https://github.com/NSTA1/Orleans.Lattice/issues/945) fall-off guard) before the capture is reached, so the no-loss invariant ([#1535](https://github.com/NSTA1/Orleans.Lattice/issues/1535)) is preserved. (`Orleans.Lattice` 9.0.6, [#1542](https://github.com/NSTA1/Orleans.Lattice/issues/1542))

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