Observation and subscriptions

Observation is a projection of accepted ledger operations. Statesman has one event model: the ledger operation. Async streams and callback subscriptions are conveniences over that model rather than separate notification systems.

Async streams

A typed handle exposes ObserveAsync, and a root or container exposes an untyped stream.

await foreach (StateChange<CartState> change in cart.ObserveAsync())
{
    Console.WriteLine($"{change.Operation}: r{change.Current.Revision}");
}

IncludeCurrent emits each existing local record directly before buffered changes. An absent, never-recorded handle does not invent a synthetic ledger operation. BufferCapacity controls the bounded per-subscriber channel for subsequent notifications. Slow subscribers drop their oldest pending notification rather than applying backpressure to authoritative writes.

A container stream is filtered before changes enter its buffer. Traffic elsewhere in the root cannot crowd out notifications from the container being observed.

Callback subscriptions

For IOptionsMonitor-style integration, the core package provides operation subscriptions:

await using StateSubscription subscription = user.OnSet(
    async (change, cancellationToken) =>
    {
        await projection.RenderAsync(change.Current.RequiredValue, cancellationToken);
    });

Available helpers include OnChange, On, OnSet, OnTransition, OnInvalidated, OnCleared, and OnFaulted.

Callbacks execute outside the append critical section. A callback failure faults that subscription task; it does not reverse the accepted record. Keep callback work bounded and move durable processing to a queue or ledger cursor.

Observation is not durable delivery

The bounded stream is intended for UI binding, cache invalidation, process-local coordination, diagnostics, and similar reactions. It is not a message broker. A process can stop, a subscriber can lag, and an old item can be dropped.

Consumers that cannot miss a revision should store a cursor and read ledger history. A provider-neutral durable change-feed capability is planned separately so its guarantees are not confused with process-local channels.