Table of Contents

Release 2026-08-16

This page is part of the documentation for Orleans.Lattice 9.9.0 (release line 9.9), built 2026-10-04. It is also published as markdown, with every table and list, at 2026-08-16.md, and llms.txt lists every page.

Part of the changelog.

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

Action required - Orleans.Lattice.Replication 9.0.1 is fail-closed. A silo that sets WalRetention (directly on LatticeOptions, or via the replication-side mirror) on a replicated tree will now refuse to start unless the anti-entropy digest probe (DigestProbeEnabled) is enabled or the new LatticeReplicationOptions.AllowWalRetentionWithoutAntiEntropy override (default false) is set. This closes a silent, permanent cross-cluster divergence path (see Fixed), but it means an operator running that exact combination must, before upgrading, either enable DigestProbeEnabled (recommended - the divergence then becomes observable and, with the repair stages on, self-healing) or set the override to acknowledge the risk. For a cluster group that has already silently diverged under this exact combination before upgrading, enabling the digest probe (with the repair stages on) is the valid remediation, not merely a preventative: the probe compares content digests out-of-band of the WAL, so it detects a divergence whose triggering entries have already been garbage-collected, and the bootstrap-fallback repair re-materialises the missing range - re-converging the affected cluster rather than only stopping new drift. Hosts that do not set WalRetention on a replicated tree - the default posture - are unaffected and upgrade as a drop-in patch.

Fixed

  • A silo now refuses to start when WalRetention is set on a replicated tree without the anti-entropy detection backstop, closing a silent cross-cluster divergence path. A WAL retention ceiling lets the sender's WAL GC trim entries a lagging cross-cluster shipper has not shipped yet (the TTL ceiling is a union with the consumer-cursor floor, so it trims past the shipper's pinned cursor). Unlike a local materialiser - whose next read surfaces the trimmed prefix to the auto-bootstrap trigger - the shipper advances past the trimmed prefix without emitting a fall-off-the-log event, and the receiver-side detector only compares against its own local WAL, so the receiver silently and permanently diverged for the trimmed range with no metric and no repair. A new fail-closed startup validator (LatticeWalRetentionReplicationStartupValidator) rejects the combination unless DigestProbeEnabled is enabled (the digest probe detects a garbage-collected divergence out-of-band) or the new opt-in LatticeReplicationOptions.AllowWalRetentionWithoutAntiEntropy acknowledgement (default false) is set. Effective retention is read from the per-tree core LatticeOptions.WalRetention, which already reflects any value mirrored from the replication-side WalRetention, so the rule catches retention configured on either surface; non-replicated trees are unaffected and the safe default posture (WalRetention unset) is unchanged. (Orleans.Lattice.Replication 9.0.1, #1496, #1499)
  • The WAL GC no longer trims a leaf's durably-checkpointed prefix that no durable snapshot covers, closing a cold-restart data-loss path that survived 9.0.2. A partition can durably advance its checkpoint to offset N while the leaf has persisted no snapshot materialising the rows in [0, N]; the durable-materialiser pin then reported (clock, N), authorising the shared-shard WAL GC to trim [0, N]. Because the leaf's per-activation projection cache is not persisted (it is rebuilt from the WAL on every activation), the next cold rebuild replayed from offset 0 over the trimmed WAL and silently lost the checkpointed prefix - a bounded but permanent loss per cold cycle, distinct from the unbounded-growth class fixed in 9.0.2. The durable pin is now coverage-gated: it authorises trimming only up to min(checkpoint, snapshotCoveredOffset) and reports the (Zero, -1) block pin for a partition whose checkpointed prefix is not yet snapshot-covered, so the WAL prefix is retained until a covering snapshot exists. Cadence capture is made unconditional (it proceeds whenever any partition has a checkpoint >= 0) and records per-partition snapshot offsets (LeafSnapshotBlob.SnapshotOffsetsByPartition, wire-compatible - legacy blobs decode with a null array and fall back to the scalar-only path byte-for-byte) so a busy non-zero partition is covered even when partition 0 is idle. A companion LeafSnapshotStorageGrain.HasCapturedPrefix guard recognises such a per-partition-only blob (any slot >= 0 is loadable) across the load, size, and clear seams, so the partition-0-idle scalar -1 sentinel no longer discards the sole durable copy on cold restart. Verified end-to-end against the local RepoContext container under repeated abrupt SIGKILL/restart cycles. (Orleans.Lattice 9.0.3, #1492)