Cohesive.Transitions
0.1.0-alpha.158
dotnet add package Cohesive.Transitions --version 0.1.0-alpha.158
NuGet\Install-Package Cohesive.Transitions -Version 0.1.0-alpha.158
<PackageReference Include="Cohesive.Transitions" Version="0.1.0-alpha.158" />
<PackageVersion Include="Cohesive.Transitions" Version="0.1.0-alpha.158" />
<PackageReference Include="Cohesive.Transitions" />
paket add Cohesive.Transitions --version 0.1.0-alpha.158
#r "nuget: Cohesive.Transitions, 0.1.0-alpha.158"
#:package Cohesive.Transitions@0.1.0-alpha.158
#addin nuget:?package=Cohesive.Transitions&version=0.1.0-alpha.158&prerelease
#tool nuget:?package=Cohesive.Transitions&version=0.1.0-alpha.158&prerelease
Cohesive.Transitions
Instance composition
DomainModel offers optional instance authoring over DomainModelBuilder: declare entities in the derived constructor and use inherited Definition. First access freezes the builder and caches the exact immutable entity catalog; late additions fail. Explicit authoring uses no reflection discovery or virtual construction hook. Optional scoped discovery uses the same POCO shape projection.
Cohesive.Transitions models one entity change as portable, inspectable semantics: admission rules, branching,
sparse updates, invariants, interactions, machine movements, and typed outcomes.
Canonical execution IR declares IImmutableExecutionDefinition, so repeated typed reads share
document-owned immutable decoding. Integrity, canonical-wire, contextual semantics, and deployment
admission remain independently validated. See immutable execution preparation.
Install
dotnet add package Cohesive.Transitions
Start with an immutable record
Transition expressions can use ordinary POCO properties without inheriting from Entity<T>:
public sealed record RunControl(bool Eligible, string Status);
public sealed record ApproveRun(bool Approved);
At application startup, resolve the entity shape and author the transition:
var entity = ObjectEntityDefinition.For<RunControl>(new("run-control"));
var metadata = new TransitionAuthoringMetadata(
new("run-control/approve"), new("revision/1"), new("body"),
new(new(TransitionAuthoring.Producer), new("example/run-control"), DocumentOrigin.Generated));
var approve = TransitionAuthoring.Create<RunControl, ApproveRun, string>(
entity.Shape,
metadata,
transition => transition
.Invariant(new("valid-status"), state => state.Status != "invalid")
.Requires(new("eligible"), (state, input) => state.Eligible && input.Approved,
(state, input) => "rejected")
.Set(new("approve"), state => state.Status, "approved")
.Return(new("result"), TransitionOutcomeDisposition.Applied, "approved"));
The shape and the restricted expressions produce the same canonical Transition IR as explicit entities and direct IR authoring. Compile the document once and reuse its plan. Invocation expressions read the original observation; ordered patches produce a candidate observation, and invariants check that candidate. Neither authoring nor execution mutates the record. POCO authoring and execution covers materialization, names, value-object contracts, and the supported subset.
Entity field updates
For a declarative Entity<T> field, both Set(entity => entity.Status, "ready")
and Set(entity => entity.Status, (_, input) => input.Status) use the entity-field
selector overload. Their step identity is set/Status, independent of authoring
order. SetNull(entity => entity.OptionalValue) produces a Set patch containing
JSON null; it does not remove the field. Nullable value types are checked by the
method's generic constraints; reference fields must also permit null in their
semantic contract. Use Remove when absence is intended.
Updating the same field more than once, including in different branches, requires explicit step IDs. Duplicate field-named IDs produce ordinary duplicate-node validation diagnostics. Explicit-ID overloads remain available for every update. POCO short constant updates retain their documented authoring-order identities.
Candidate state and composition
TransitionStateProjector.ApplyToEntity prepares a validated entity candidate from a canonical
TransitionDecision. API, service, and process transition consumers share its version and
patch-before-value rules. Commit fences and emission ownership remain at their execution boundaries.
See entity/transition/process composition
for the implemented boundary and the remaining CRUD, history, and reconstitution work.
Explicit entity declarations
The C# entity surface discovers fields and produces the canonical observation shape. Ordinary field declarations do not require node IDs:
public enum LoadStatus { Draft, Assigned }
public sealed class Load : Entity<Load>
{
public Load()
{
Status = Field(nameof(Status), LoadStatus.Draft);
CarrierId = Field<string?>(
nameof(CarrierId),
initialValue: null,
configure: field => field.Optional());
Invariant(
"AssignedLoadsHaveACarrier",
load => load.Status != LoadStatus.Assigned ||
load.CarrierId != null);
}
public Field<LoadStatus> Status { get; }
public Field<string?> CarrierId { get; }
}
Transition authoring then uses restricted typed expressions over the current entity observation and a typed input. Those expressions lower immediately into canonical IR; arbitrary method calls, hidden I/O, captured runtime state, loops, and mutation are rejected.
What a Transition produces
- A canonical, persistable
ExecutionDefinitionDocument. - Exact input, entity-observation, and outcome contracts.
- Admission rules and deterministic branch structure.
- An algebraic sparse patch rather than in-place entity mutation.
- Interaction intents and machine movements when declared.
- Candidate-state invariant checks and typed outcome evidence.
- Structured source maps, validation diagnostics, and a semantic fingerprint.
The document is the semantic authority. The typed handle, static plan, reference interpreter decision, repository commit, and emitted interaction envelopes are projections or interpretations of that exact document.
Current authoring boundary
Entity shape discovery and common field defaults are convention-driven. The current Transition builder keeps stable identities explicit for durable rules, branches, updates, and outcomes; advanced examples and the canonical IR are therefore documented separately. Additional identity conventions should be added to the authoring API before they are presented as ordinary syntax.
Use it when
- Entity invariants and legal changes should be explicit and reusable across hosts.
- A decision must be testable without a database, message broker, or HTTP endpoint.
- Storage needs a sparse patch and exact concurrency evidence rather than an opaque callback.
- Processes, APIs, presentation actions, and tooling should reference the same domain behavior.
Continue
- Internals contains the complete explicit authoring example, canonical IR, persistence, compilation, reference interpretation, and expression-site details.
- Execution kernel guide explains Transition linking and execution boundaries.
Cohesive.Storageattaches repository and commit interpretations.Cohesive.Processescoordinates Transition invocations with other semantic work.
Start a multi-entity domain
DomainModelBuilder.Entity<T>(semanticName) returns a typed DomainEntity<T> retaining the
exact definition in the built entity catalog. Declare it once, then attach downstream concerns:
var domain = new DomainModelBuilder().Version("1");
var orders = domain.Entity<Order>("orders");
var reservations = domain.Entity<Reservation>("reservations");
var reservationOrder = reservations.References(reservation => reservation.OrderId, orders);
var definition = domain.Build();
entity.Definition supplies transition/storage authority. entity.QueryShape(author) imports that
exact graph into a Relations expression session, avoiding an independently inferred CLR query shape.
author.Source(entity) combines that import with source creation when a separate shape handle is unnecessary.
References uses an existing Relations relationship definition and names the target observation identity;
it does not create a foreign key, establish tenant scope or enforce authorization. The typed handle carries
both exact endpoint documents, imported automatically by forward and inverse traversal. Explicit imports
remain valid; conflicting session registrations fail closed. A traversal retains
the separately declared relationship in its authoring catalog. Entity handles contain no runtime state,
persistence or workflow. See the runnable fulfillment example.
Domain entity handles expose entity.Transition<TInput, TOutcome>(id, revision, configure) and
entity.Transition<TOutcome>(...) for Unit-input operations. These delegate to TransitionAuthoring.Create
with the entity's exact declared shape and caller attribution; no parallel model or invocation-time
compilation is introduced. Instance methods retain the entity type without repeating it as a method
argument; generic extension receivers cannot partially infer explicit method type arguments here.
Scoped entity discovery and explicit-model verification
Discovery is an opt-in authoring producer, not assembly-wide convention-based semantics:
sealed class SalesModule;
[DomainEntity(typeof(SalesModule), "sales/order")]
public record Order(string Id, int Quantity);
var declarations = DomainDiscovery.For<SalesModule>();
var builder = new DomainModelBuilder().Version("1");
var order = builder.Discover(declarations, entities => entities.Entity<Order>());
var model = builder.Build();
Only attributed types belonging to the selected marker in its own assembly participate.
No unrelated loaded assemblies, constructors or property getters are evaluated. Stable entity
names are explicit; properties use the existing ObjectEntityDefinition projection, including
serialized member names. Reflection metadata and definitions are prepared once per module.
For trimmed/AOT applications, preserve the selected types and their public properties or use
explicit registration; reflection discovery is annotated as requiring unreferenced code.
An explicit model can verify discovery coverage at completion, regardless of registration order:
var builder = new DomainModelBuilder().Version("1")
.VerifyDiscovery(declarations, DomainDiscoveryCoverage.ExactMembership);
var order = builder.Entity<Order>("sales/order");
var model = builder.Build();
The default DiscoveredEntities mode requires all discovered entities but allows additional
explicit entities. ExactMembership also rejects extras. declarations.Exclude<Order>("Owned by another service") returns a new catalog retaining the omission reason; unknown exclusions fail.
DomainModel subclasses expose the same protected Discover and VerifyDiscovery operations.
declarations.Verify(model) returns structured diagnostics without throwing; builder completion
raises DomainDiscoveryException with all failed coverage and state-shape checks.
Verification compares canonical state-shape identity, not CLR reference identity. It does not infer or verify behavioral invariants, transitions, relationship semantics, identity policy, storage or infrastructure. Those remain explicit declarations. Exclusion reasons and CLR module membership are inspectable authoring metadata; only canonical entities enter the portable IR. This check detects registration omissions and shape drift, not independent proof of domain correctness.
The running Aspire fulfillment domain uses discovery and typed handles for its order, inventory,
and reservation entities; existing transitions, relationships, queries and storage all consume
those same definitions. Discovery does not require re-registering an entity to attach behavior.
Model activation calls IAuthoringModel.Complete, which performs scheduled verification before
returning models to their consumers. For manual construction, request Definition during setup:
its first access can throw DomainDiscoveryException and caches that failure.
Discovered shapes are cached, but each domain owns distinct entity definitions. Prefer the
projection overload below for discovery. Keep Entity<T>(name) for explicit declarations;
matching named adoption remains supported for code shared between the two authoring modes.
Storage bindings still reject handles from a different domain instance.
For instance models, discovery and property assignment can be one expression:
(Orders, Inventory, Reservations) = Discover(DomainDiscovery.For<FulfillmentDomain>(),
entities => (entities.Entity<Order>(), entities.Entity<InventoryItem>(), entities.Entity<Reservation>()));
The builder exposes the same projection overload. Selection receives a read-only, domain-owned scope containing only this catalog's included declarations. It cannot escape the model's freeze rules by retaining a mutable builder. The callback selects from staged definitions before registration. If it throws (including selection of an excluded type), discovery adds nothing, and the builder can be corrected and reused. External side effects performed by callbacks are not rolled back. Builder mutation and materialization through captured references are rejected while selection runs; the guard is cleared even when selection throws. Escaped handles from a failed selection are not registered and do not match a subsequent registration's storage authority. Plain discovery delegates to the same staged registration path as projected discovery. There is no separate allocation-optimized path or performance claim.
A retained handle from a failed selector is not revoked: it still describes a valid immutable
entity and can author transitions or relationships. It is not registered in the failed domain,
and does not become the authority of a later successful registration. Storage bindings for that
successful domain reject it, even if its canonical shape is identical. Discovery provides atomic
registration, not a capability sandbox for caller-owned authoring objects. A discovered CLR type cannot be re-registered under a different semantic
name: Entity<Order>("different-name") fails without changing the domain.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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
- Cohesive (>= 0.1.0-alpha.158)
- Cohesive.Relations (>= 0.1.0-alpha.158)
NuGet packages (7)
Showing the top 5 NuGet packages that depend on Cohesive.Transitions:
| Package | Downloads |
|---|---|
|
Cohesive.Processes
Cohesive semantic system definition and orchestration building blocks. |
|
|
Cohesive.Storage
Cohesive semantic system definition and orchestration building blocks. |
|
|
Cohesive.Api
Cohesive semantic system definition and orchestration building blocks. |
|
|
Cohesive.Identity
Cohesive semantic system definition and orchestration building blocks. |
|
|
Cohesive.Host
Cohesive semantic system definition and orchestration building blocks. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.1.0-alpha.158 | 182 | 10/24/2026 |
| 0.1.0-alpha.157 | 188 | 10/24/2026 |
| 0.1.0-alpha.156 | 175 | 10/22/2026 |
| 0.1.0-alpha.155 | 179 | 10/20/2026 |
| 0.1.0-alpha.154 | 174 | 10/20/2026 |
| 0.1.0-alpha.153 | 171 | 10/20/2026 |
| 0.1.0-alpha.152 | 174 | 10/19/2026 |
| 0.1.0-alpha.151 | 176 | 10/19/2026 |
| 0.1.0-alpha.150 | 177 | 10/19/2026 |
| 0.1.0-alpha.149 | 176 | 10/18/2026 |
| 0.1.0-alpha.148 | 172 | 10/18/2026 |
| 0.1.0-alpha.147 | 183 | 10/18/2026 |
| 0.1.0-alpha.146 | 176 | 10/18/2026 |
| 0.1.0-alpha.145 | 173 | 10/17/2026 |
| 0.1.0-alpha.144 | 173 | 10/17/2026 |
| 0.1.0-alpha.143 | 179 | 10/16/2026 |
| 0.1.0-alpha.142 | 165 | 10/16/2026 |
| 0.1.0-alpha.141 | 179 | 10/16/2026 |
| 0.1.0-alpha.140 | 177 | 10/15/2026 |
| 0.1.0-alpha.139 | 186 | 10/13/2026 |