Statesman 0.3.0
dotnet add package Statesman --version 0.3.0
NuGet\Install-Package Statesman -Version 0.3.0
<PackageReference Include="Statesman" Version="0.3.0" />
<PackageVersion Include="Statesman" Version="0.3.0" />
<PackageReference Include="Statesman" />
paket add Statesman --version 0.3.0
#r "nuget: Statesman, 0.3.0"
#:package Statesman@0.3.0
#addin nuget:?package=Statesman&version=0.3.0
#tool nuget:?package=Statesman&version=0.3.0
Statesman
Statesman is a declaration-driven state runtime and append-only state ledger for .NET.
It gives large, loosely coupled systems a practical route to authoritative state without requiring a Flux or Redux rewrite. A Statesman composition root declares what state exists, where it comes from, how it may change, how fresh it must be, how long its history is retained, where that history is stored, and how the state is observed.
The declaration is executable architecture. At build time it becomes an immutable, deterministic manifest. At runtime it coordinates typed reads, reactive writes, proactive loading, reducers, freshness, optimistic concurrency, history, observation, and storage.
StateKey<UserServiceState> user =
StateKey.Define<UserServiceState>("users/user");
StatesmanDeclaration declaration = Statesman.Declare("back-office", "1.0")
.Defaults(defaults => defaults
.Freshness(freshness => freshness
.FreshFor(TimeSpan.FromMinutes(5))
.ServeStaleFor(TimeSpan.FromMinutes(30))
.StaleWhileRevalidate())
.Retain(history => history
.Last(250)
.For(TimeSpan.FromDays(30))))
.Container("users", users => users
.Isolated()
.State(user, state => state
.Partitioned()
.Initial(new UserServiceState("", [], false))
.Load(load => load
.From<IUserApi, UserProfile>("profile",
(api, context, ct) =>
api.GetProfileAsync(context.Address.Partition.Value, ct))
.Into((current, profile, _) => current with
{
DisplayName = profile.DisplayName,
})
.From<IUserApi, IReadOnlyList<string>>("permissions",
(api, context, ct) =>
api.GetPermissionsAsync(context.Address.Partition.Value, ct))
.Into((current, permissions, _) => current with
{
Permissions = permissions,
})
.InParallel()
.RequireAll())
.Refresh(refresh => refresh
.OnFirstRead()
.WhenStale()
.Every(TimeSpan.FromMinutes(10))
.OnSignal("user.changed"))
.Interact<SetEnabled>("set-enabled", interaction => interaction
.Require((_, command) => command.ActorCanAdmin,
"The actor cannot administer users.")
.Reduce((current, command) => current with
{
Enabled = command.Enabled,
}))))
.Build();
builder.Services.AddStatesman(declaration, statesman => statesman
.UseFileSystemStore("local", "./state")
.UseRedisStore("shared", connection));
builder.Services.AddStatesmanHosting();
IState<UserServiceState> ada = statesman.State(user, "user-42");
IStateSnapshot<UserServiceState> snapshot =
await ada.GetAsync(StateReadOptions.Fresh);
await ada.DispatchAsync(new SetEnabled(
Enabled: true,
ActorCanAdmin: true));
await statesman.SignalAsync(new StateSignal(
"user.changed",
Partition: "user-42"));
What Statesman is
Statesman is a higher-order state abstraction. It sits above storage, transport, polling, event delivery, caches, and domain services. It does not ask those systems to become Statesman-aware. They remain sources and sinks. Statesman owns the authoritative runtime view and records how that view changed.
A state has more than a value. It has a typed identity, partition, revision, global ledger position, status, freshness window, schema version, source, causation, metadata, error information, retention policy, and declared interaction surface.
What Statesman is not
Statesman is not a database, distributed transaction coordinator, message broker, event-sourcing framework, entity tracker, or UI framework. A ledger provider may use a database or Redis, but Statesman does not expose their query models as its programming model. It deliberately keeps domain-facing code independent from the persistence mechanism.
Core model
| Concept | Purpose |
|---|---|
| Root | A complete, independently registered state authority with its own manifest and lifecycle. Multiple roots may coexist in one process. |
| Container | A nested declaration and runtime scope. Attached containers organize a root. Isolated containers add scoped access, observation, capture, and signals. |
| State key | A static, typed identity such as users/user. |
| Partition | An independent instance of a state definition, such as one user, tenant, device, or document. |
| Snapshot | An immutable observation of a state revision at a particular read time. |
| Operation | A ledger fact: seeded, set, transitioned, refreshed, invalidated, cleared, faulted, or imported. |
| Source | A proactive loader that obtains a complete value or one facet of a composed value. |
| Interaction | A named, typed command plus requirements and reducer. |
| Ledger store | An append and history contract implemented by memory, files, Redis, EF Core, or a tiered hot/cold store. |
| Manifest | The deterministic output of the composition root, including a SHA-256 fingerprint. |
Package map
| Package | Responsibility |
|---|---|
Statesman.Abstractions |
Identity types, snapshots, policies, manifests, runtime and ledger contracts. |
Statesman |
Fluent declaration DSL, runtime coordination, JSON serialization, in-memory ledger, manifest export. |
Statesman.Extensions.DependencyInjection |
DI registration, named stores, and multiple root registry. |
Statesman.Extensions.Hosting |
Warm-up and interval maintenance through IHostedService. |
Statesman.Persistence.FileSystem |
Atomic local JSON ledger. |
Statesman.Persistence.Redis |
Distributed optimistic ledger backed by Redis. |
Statesman.Persistence.EntityFrameworkCore |
Provider-neutral EF Core ledger model and store. |
Statesman.Persistence.Tiered |
Cold authoritative store with an exact-revision hot replica. |
Statesman.Transport.Http |
HTTP JSON sources and a remote Statesman client. |
Statesman.AspNetCore |
Optional manifest, snapshot, history, and signal endpoints. |
Statesman.Analyzers |
Mutation-boundary and deterministic-key analyzers. |
Statesman.Testing |
Deterministic time, fluent state seeding, portable fixtures, test services, assertions, and a runtime harness. |
Statesman.Tooling |
Portable ledger export and fingerprint-validated exact restore. |
Statesman.Outbox |
At-least-once delivery of ledger changes from the durable change feed to a message broker, with a persisted monotonic cursor and lease-gated single dispatch. |
Statesman.Outbox.Redis |
Redis Streams delivery and Redis cursor storage for the outbox. |
The core packages target .NET 8, 9, and 10. The EF Core provider and samples target .NET 10.
First run
dotnet restore Statesman.slnx
dotnet build Statesman.slnx --configuration Release --no-restore
dotnet test Statesman.slnx --configuration Release --no-build
dotnet run --project samples/Statesman.Sample.Console
The repository pins the .NET 10.0.303 SDK. See Getting started for the smallest useful declaration, Architecture for the complete model, and the published DocFX site for browsable guides and API reference.
Current implementation scope
This repository is an implementation-ready first release, not a mock scaffold. It includes the public contracts, fluent declaration system, runtime, in-memory and external providers, analyzers, host integrations, examples, tests, CI, packaging, CodeQL, Codecov coverage reporting, a DocFX site, and design documentation.
Run eng/build.ps1 or eng/build.sh for the local release validation loop, or let the included GitHub Actions workflows perform restore, build, tests, coverage, package creation, docs publishing, and optional NuGet publishing.
Design principles
- One declared authority, many migration paths. Existing code can begin by pushing updates into Statesman. Loaders, reducers, immutable state values, and analyzers can follow incrementally.
- The declaration is inspectable. Runtime behavior must be visible in a stable manifest instead of hidden across registration callbacks and conventions.
- History is a first-class consequence of state. Every authoritative change has a revision and lineage, even when retention later prunes it.
- Storage is replaceable. Domain code talks to state, never Redis keys, files, EF entities, or cache entries.
- Reads state their consistency intent. Cached, current, refresh-if-stale, and fresh reads are explicit.
- Concurrency is never last-write-wins by accident. Writes use expected revisions and retry pure updates or reducers against the newest snapshot.
- Failure is state. Loader failures are observable ledger records and can retain a last-known value according to policy.
- Boundaries say what they guarantee. A separate root is the hard identity and lifecycle boundary. Separate provider infrastructure makes it a storage failure boundary. Containers remain scopes within a root.
Documentation
- Getting started
- The declaration DSL
- State lifecycle and reads
- Containers, roots, and partitions
- Loaders and fan-in composition
- Interactions and reactive writes
- Migration guide
- Ledger and retention
- Storage providers
- ASP.NET Core and hosting
- Analyzers
- Testing — inline seeds, portable snapshot fixtures, ASP.NET Core, Playwright, and distributed E2E orchestration
- Observation and subscriptions
- Multiple roots
- Consistency and concurrency
- Public API map
- Diagnostics
- Production readiness
- Repository release readiness
- Architecture and invariants
- Implementation plan
- Roadmap
License
MIT. See LICENSE.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Statesman.Abstractions (>= 0.3.0)
-
net8.0
- Statesman.Abstractions (>= 0.3.0)
-
net9.0
- Statesman.Abstractions (>= 0.3.0)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on Statesman:
| Package | Downloads |
|---|---|
|
Statesman.Extensions.DependencyInjection
Microsoft dependency-injection integration and multi-root registry for Statesman. |
|
|
Statesman.Testing
Deterministic time, service fakes, harnesses, and assertions for Statesman tests. |
GitHub repositories
This package is not used by any popular GitHub repositories.