Cohesive Systems logoCOHESIVE SYSTEMS

Search Cohesive Systems

Ready

Search Cohesive Systems

Find product pages, building blocks, technical articles, and graph definitions.

Building Blocks

Cohesive.Entities

Define entity shape and invariants, then author exact, portable Transitions that decide sparse state changes, interaction emissions, and typed outcomes.

Core Idea

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.

Getting Started

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 →

Entity Shape and Transition Authority

The current model draws a firm boundary between stable state structure and legal change:

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.

Transition document

Owns
Input and observation contracts, admission, finite control flow, patches, emissions, Machine movements, invariants, and outcome semantics.
Does not own
Storage access, commit behavior, message publication, HTTP routes, or workflow progress.

Execution decision

Owns
The evaluated outcome, patch, emissions, movements, actual reads, guarantee demands, conflicts, diagnostics, and trace.
Does not own
Authority to mutate storage or perform external I/O.

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.

The Canonical Transition Model

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.

typed input and observation
admission and finite control flow
sparse patches and emissions
candidate-state invariants
one typed Transition decision

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.

One Transition Lifecycle

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

Authoring is replaceable. The persisted document is semantic authority; compilation and interpretation derive attributable artifacts from it.

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.

Sparse Observation and Path-Level Reads

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.

A Decision Comes Before Commit

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.

Typed Interaction Emissions

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.

Identity, Provenance, and Compatibility

Every durable definition and semantic construct has stable identity. A Transition reference includes:

  • the definition identity shared by its revisions
  • the exact semantic revision
  • the normalized content fingerprint

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.

Integration Boundaries

  • 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.

Why It Matters

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.