Entity definition
- Owns
- Logical entity name, canonical shape, fields, annotations, computed fields, and entity invariants.
- Does not own
- A registry of Transitions or an execution service.
Building Blocks
Define entity shape and invariants, then author exact, portable Transitions that decide sparse state changes, interaction emissions, and typed outcomes.
An entity declares the shape and invariants of authoritative business state. A Transition is a separate, exact decision about how one entity may change.
Cohesive.Transitions gives both concepts a typed C# authoring surface. Entity classes describe fields and candidate-state invariants. Transition expressions lower into independently persisted canonical documents with stable identity, portable control flow, sparse patches, interaction emissions, and typed outcomes.
The application-facing syntax stays close to ordinary C#:
public enum LoadStatus { Draft, Assigned }
public enum AssignCarrierOutcome { Assigned, NotDraft, InvalidCarrier }
public sealed record AssignCarrierInput(string CarrierId);
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());
}
public Field<LoadStatus> Status { get; }
public Field<string?> CarrierId { get; }
}
var assignCarrier = TransitionAuthoring.Create<
Load, AssignCarrierInput, AssignCarrierOutcome>(
Load.Define().Shape,
metadata,
transition =>
{
transition.Requires(
new("assign-carrier/require-draft"),
(load, _) => load.Status == LoadStatus.Draft,
(_, _) => AssignCarrierOutcome.NotDraft);
transition.Choose(new("assign-carrier/validate"), choice => choice
.Case(
new("assign-carrier/valid"),
(_, input) => input.CarrierId != "",
valid => valid
.Set(
new("assign-carrier/set-carrier"),
load => load.CarrierId,
(_, input) => input.CarrierId)
.Set(
new("assign-carrier/set-status"),
load => load.Status,
LoadStatus.Assigned)
.Return(
new("assign-carrier/assigned"),
TransitionOutcomeDisposition.Applied,
AssignCarrierOutcome.Assigned))
.Fallback(
new("assign-carrier/invalid"),
invalid => invalid.Return(
new("assign-carrier/rejected"),
TransitionOutcomeDisposition.DomainRejected,
AssignCarrierOutcome.InvalidCarrier)));
transition.Invariant(
new("assign-carrier/carrier-required"),
load => load.Status != LoadStatus.Assigned ||
load.CarrierId != null);
});metadata supplies the definition identity, semantic revision, root-body identity, and provenance. The expressions use only the portable C# subset that can become canonical IR. The returned handle contains the exact document, its validation result, and its fingerprint-bound reference; it retains no executable callback.
The guided walkthrough builds the AssignCarrier example from entity fields through canonical authoring, compilation, sparse interpretation, and the external commit boundary.
Open the Getting Started guide →
The current model draws a firm boundary between stable state structure and legal change:
Transitions link to an entity's canonical shape, but they remain independently versioned definitions. A Process, API operation, or storage binding retains an exact Transition reference: definition identity, semantic revision, and content fingerprint.
A Transition is a finite, portable program over typed input and entity observations. Its closed node model includes lexical values, ordered predicate choices, exact matches, sparse updates, interaction emissions, Machine movements, and terminal outcomes.
Sparse updates are algebraic operations rather than an unstructured dictionary. The current model supports setting and removing values, increments, set addition, append, and owned-child upsert or removal. Each operation carries stable identity and a source-map location.
The expression frontend accepts only computation representable by the shared portable expression language. Captured runtime state, arbitrary method calls, loops, reflection, mutation, and hidden I/O fail during authoring instead of becoming opaque runtime behavior.
Canonical Transition lifecycle
Typed C# authoring or imported IR, then Canonical execution-definition document, then Validated compiled plan, then Non-committing Transition decision, then Capability-checked commit and publication
The canonical document can be serialized, restored, validated, compiled, and interpreted without loading the original authoring assembly. Display text and source locations remain useful attribution metadata, but they do not change the semantic fingerprint.
The compiler identifies possible requirements. Interpretation records which fields the selected execution path read. That distinction matters when a branch depends on two fields while another branch needs only one.
A sparse observation also preserves semantic absence. No entry means the path was not observed. An entry may explicitly contain Absent, Null, Unknown, Failed, or a concrete value. Adapters cannot collapse those cases without changing meaning.
Path-level actual reads support selective acquisition, targeted updates, and precise concurrency checks without requiring every runtime to hydrate an object graph.
TransitionReferenceInterpreter is deterministic and non-committing. It invokes no service or delegate, performs no I/O, and mutates no caller-owned state.
The result distinguishes accepted change, accepted no-change, admission rejection, authored domain rejection, concurrency conflict, invalid definition, and infrastructure failure. When a commit is required, the decision also states which actual observations must remain coherent and whether patches and emissions require atomic persistence.
Fresh commit observations can be supplied for conflict validation. A changed actual read produces exact expected-versus-observed evidence. Missing fresh evidence fails closed when the requested interpretation depends on it.
An effect begins in Transition semantics as a pure emission intent that references one exact interaction contract. The referenced contract determines whether the interaction is a domain event or a request.
After the state decision is accepted, infrastructure lowers those intents into canonical envelopes with stable logical emission identity, origin, correlation, typed payload, and target evidence. The Storage-owned Process integration can commit a Transition operation receipt and the resulting envelopes into the Process outbox as one durable successor.
This keeps the transactional outbox boundary explicit. A Transition describes what must be emitted; storage and delivery adapters prove how the emission becomes durable and publishable.
Lifecycle structure belongs to an authoritative state machine. A Transition that moves a Machine persists an exact Machine reference and edge identity rather than copying the lifecycle graph.
Compilation links the referenced edge and pins its source predicate, target predicate, and assignments into the plan. Interpretation verifies the source configuration, applies edge-owned assignments to candidate state, and checks the target configuration.
Every durable definition and semantic construct has stable identity. A Transition reference includes:
Activation admits only compatible schema versions, definition kinds, and exact references. A mismatch is a definition or activation failure, distinct from a business rejection.
Source maps connect canonical paths and diagnostics back to C# members and lines. Moving a source file does not change semantic identity; changing fingerprint-bearing semantics does.
Cohesive.Processes invokes exact Transitions and carries their durable operation and emission evidence forward.Cohesive.Relations supplies canonical query and entity-read semantics used before or around a change.Cohesive.Storage acquires observations, performs capability-checked commits, and owns durable Process persistence.Cohesive.Api and Cohesive.Presentation project operations, decisions, diagnostics, and controls without becoming execution authority.Entity behavior is often trapped inside methods, handlers, persistence hooks, and service callbacks. The current Transition model moves the legal change into a portable definition while leaving storage and delivery boundaries explicit.
That gives a compiler enough structure to validate the program before activation, gives runtimes exact evidence about what was read and decided, and gives other semantic blocks one fingerprinted Transition to reference. The same decision can be tested in memory, committed through storage, coordinated by a Process, and projected into operational tooling without turning any of those realizations into a second source of business meaning.