C# examples

Ordo in C#

Ordo is language-independent. We will start with things you already understand in real life, then show the same ideas in ordinary C#. The first measured engineering examples come from the C# time-entry trial. The later decision, coverage, Capability, and reconciliation examples are explicit C# translations of the current Ordo 1.3 semantics.

Start with real life

You already understand state. You just may not call it that.

State is simply what is true right now. A transition is a change that is allowed from that state.

The 30-second example

A light switch

A normal light is either Off or On. If it is Off, the useful action is Turn On. If it is On, the useful action is Turn Off.

C# · the same idea
public enum LightState
{
    Off,
    On
}

public static LightState TurnOn(LightState state) =>
    state == LightState.Off
        ? LightState.On
        : throw new InvalidOperationException(
            "The light is already on.");

That is the core idea. Ordo takes the same thing you already understand about the light and applies it to business software, where the legal states and transitions are harder to see.

A more useful example

Ordering a cup of coffee

Suppose you order coffee. You can ask for it black, with cream, with sugar, or with both. Those choices describe what you ordered. They are not necessarily the order's state.

Coffee With cream + sugar
Current state Preparing

The lifecycle is different: Taking Order → Preparing → Ready → Served. That is state because each step changes what can legally happen next.

Now make the rule visible in code

Cream and sugar are data. Preparing and Served are state.

This distinction matters. If every combination becomes a state, the model explodes into things like PreparingBlack, PreparingWithCream, ReadyWithCreamAndSugar, and so on. Ordo models only the distinctions that change meaning or legal behavior.

C# · coffee configuration and lifecycle
public sealed record CoffeeSpec(
    bool Cream,
    bool Sugar)
{
    public static CoffeeSpec Black =>
        new(Cream: false, Sugar: false);

    public static CoffeeSpec CreamAndSugar =>
        new(Cream: true, Sugar: true);
}

public enum OrderStatus
{
    TakingOrder,
    Preparing,
    Ready,
    Served,
    Cancelled
}

public sealed record CoffeeOrder(
    Guid Id,
    CoffeeSpec Coffee,
    OrderStatus Status);
C# · legal next moves
public static CoffeeOrder AddCream(CoffeeOrder order)
{
    if (order.Status != OrderStatus.TakingOrder)
        throw new InvalidOperationException(
            "The drink can only be changed while taking the order.");

    return order with
    {
        Coffee = order.Coffee with { Cream = true }
    };
}

public static CoffeeOrder Submit(CoffeeOrder order)
{
    if (order.Status != OrderStatus.TakingOrder)
        throw new InvalidOperationException(
            "Only an open order can be submitted.");

    return order with { Status = OrderStatus.Preparing };
}

public static CoffeeOrder MarkReady(CoffeeOrder order)
{
    if (order.Status != OrderStatus.Preparing)
        throw new InvalidOperationException(
            "Only a drink being prepared can become ready.");

    return order with { Status = OrderStatus.Ready };
}

public static CoffeeOrder Serve(CoffeeOrder order)
{
    if (order.Status != OrderStatus.Ready)
        throw new InvalidOperationException(
            "Only a ready drink can be served.");

    return order with { Status = OrderStatus.Served };
}

What became explicit?

You cannot serve a drink that is still being prepared. You cannot change cream after preparation starts. You cannot accidentally treat sugar as a lifecycle state.

That is the Ordo move

Put meaning in one authoritative model, expose the legal next actions, and stop every UI, API, engineer, and agent from having to rediscover the rules independently.

The important question is not "is this a state machine?"

Ask: what is true now, and does that change what is legally allowed next? If the answer changes the legal next move, it probably belongs in the semantic state. If it merely describes the thing, like cream or sugar, it is usually data carried by that state.

1 · State

Name the state instead of reconstructing it.

The measured C# trial used a closed enum for the lifecycle and immutable records for the entity. That is a pragmatic C# representation when the cases do not need different payload shapes.

Measured C# trial · simplified
public enum TimeEntryStatus
{
    Recorded,
    Voided,
    Superseded
}

public sealed record TimeEntry(
    EntryId Id,
    PersonId PersonId,
    DateOnly Date,
    TimeOfDay Start,
    DurationMinutes Duration,
    ProjectId ProjectId,
    TimeEntryStatus Status,
    long Revision);
Time entry lifecycle: three states and four legal transitions A time entry is created into the Recorded state. Recorded can move to Voided, and Voided back to Recorded. Recorded can move to Superseded when the entry is split or merged. Superseded is terminal: no transition leaves it. create void restore split or merge Recorded Voided Superseded terminal
The lifecycle implemented independently by three separate builds of the same requirements. Nothing leaves Superseded: every guard refuses it, and two acceptance criteria assert the refusal rather than relying on its absence from the code.

C# enums are useful here because a switch expression over an enum can produce CS8509 when a case is missing. With TreatWarningsAsErrors, adding a lifecycle state can turn unhandled switches into build failures.

2 · Domain types

Do not let two different meanings share the same primitive.

A time of day and a duration can both be represented by integers. They are still not the same thing.

Measured C# trial · nominal types
public sealed record TimeOfDay
{
    public int Hour { get; }
    public int Minute { get; }

    private TimeOfDay(int hour, int minute)
    {
        Hour = hour;
        Minute = minute;
    }

    public static TimeOfDay Of(int hour, int minute)
    {
        if (hour is < 0 or > 23)
            throw new ArgumentOutOfRangeException(nameof(hour));

        if (minute is < 0 or > 59)
            throw new ArgumentOutOfRangeException(nameof(minute));

        return new TimeOfDay(hour, minute);
    }

    public int MinutesSinceMidnight => Hour * 60 + Minute;
}

public sealed record DurationMinutes
{
    public int Value { get; }

    private DurationMinutes(int value) => Value = value;

    public static DurationMinutes Of(int minutes) =>
        minutes <= 0
            ? throw new ArgumentOutOfRangeException(nameof(minutes))
            : new DurationMinutes(minutes);
}

What this prevents

A caller cannot accidentally pass a DurationMinutes where a TimeOfDay is required and still compile.

What this does not prevent

A mathematically wrong calculation can still produce a perfectly valid DurationMinutes. Types narrow the failure space; they do not prove business arithmetic.

3 · Legal transitions

Change state through named operations.

The write is not "set Status." The write is "Void this Recorded entry at this revision."

Measured C# trial · simplified
public static CommandResult Void(
    WeekDocument week,
    EntryId entryId,
    long expectedRevision,
    string? reason)
{
    var error = RequireStatus(
        week,
        entryId,
        expectedRevision,
        TimeEntryStatus.Recorded,
        out var entry);

    if (error is not null)
        return new CommandResult.Rejected([error]);

    var voided = entry! with
    {
        Status = TimeEntryStatus.Voided,
        Revision = entry.Revision + 1,
        Provenance = entry.Provenance with
        {
            CorrectionReason = reason
        }
    };

    var updatedWeek = week with
    {
        Entries = ReplaceEntry(week.Entries, voided),
        DocumentRevision = week.DocumentRevision + 1
    };

    return new CommandResult.Success(updatedWeek, voided);
}
  1. 1Current stateThe named state the entity is actually in — not a combination of flags a reader has to infer.
  2. 2Requested actionA named action, not an arbitrary field write.
  3. 3Capability checkIs this action one the current state permits at all? If not, the request is refused here.
  4. 4Evidence checkIs the basis for the transition present and valid?
  5. 5Policy checkDo the business rules covering this action hold for these particular values?
  6. 6Legal transitionOnly now does state change, and only to a state the model declares reachable from the current one.
  7. 7New state, obligations, effectsThe resulting state, what must still happen for the transition to be complete, and the external effects requested as data rather than performed in place.
A requested action is checked against the current state before it can become a new state. Each gate refuses with a named reason rather than throwing or returning null.

The important part is not the amount of code. The operation names the precondition, the legal transition, the revision check, and the result. Callers do not get a generic mutation surface.

4 · Available actions

Derive what can be attempted from the same semantic rules.

The measured C# trial called this projection EntryCapabilities. Current Ordo terminology reserves Capability for host-established semantic authority, so think of this older type as an available-action projection.

Measured C# trial · available-action projection
[Flags]
public enum EntryActions
{
    None           = 0,
    Correct        = 1 << 0,
    Split          = 1 << 1,
    Void           = 1 << 2,
    Restore        = 1 << 3,
    LinkEvidence   = 1 << 4,
    UnlinkEvidence = 1 << 5,
    Merge          = 1 << 6
}

public static class AvailableActions
{
    public static EntryActions For(TimeEntry entry) =>
        entry.Status switch
        {
            TimeEntryStatus.Voided =>
                EntryActions.Restore,

            TimeEntryStatus.Superseded =>
                EntryActions.None,

            TimeEntryStatus.Recorded =>
                ForRecorded(entry)
        };

    private static EntryActions ForRecorded(TimeEntry entry)
    {
        var actions =
            EntryActions.Correct |
            EntryActions.Void |
            EntryActions.LinkEvidence |
            EntryActions.Merge;

        if (entry.Duration.Value >= 2)
            actions |= EntryActions.Split;

        if (entry.Evidence.Count > 0)
            actions |= EntryActions.UnlinkEvidence;

        return actions;
    }
}

The UI does not own this rule.

A UI, API, or agent may ask the domain which actions are available. It should not recreate the rules independently and hope they stay synchronized with the transition guards.

5 · Evidence and scoped coverage

Separate what you observed from how completely you looked.

This is a C# translation of current Ordo 1.3 semantics. It was not part of the earlier C# effort trial.

Illustrative C# · current Ordo semantics
public readonly record struct EvidenceId(Guid Value);
public readonly record struct CoverageScope(string Value);

public sealed record Evidence(
    EvidenceId Id,
    string Source,
    DateTimeOffset ObservedAt,
    string Basis);

public enum CoverageStatus
{
    Complete,
    Partial,
    Unknown
}

public sealed record ContextCoverageClaim(
    CoverageScope Scope,
    CoverageStatus Status,
    IReadOnlySet<EvidenceId> Provenance);

public sealed record DecisionContract(
    IReadOnlySet<CoverageScope> RequiredCompleteScopes);

public static class CoverageGate
{
    public static IReadOnlyList<CoverageScope> MissingRequiredCoverage(
        DecisionContract contract,
        IReadOnlyList<ContextCoverageClaim> claims) =>

        contract.RequiredCompleteScopes
            .Where(required =>
                !claims.Any(c =>
                    c.Scope == required &&
                    c.Status == CoverageStatus.Complete))
            .ToList();
}
  1. 1Observation produces EvidenceEvidence carries identity, source, observation time, basis, and typed content. Derived evidence must have a closed, acyclic dependency set.
  2. 2Evidence supports a Coverage claimA ContextCoverageClaim names a contract/domain-defined scope and says Complete, Partial, or Unknown, citing Evidence as provenance.
  3. 3Contract declares required Complete scopesCompleteness is required only where the contract says it matters. Other scopes may legitimately remain Partial or Unknown.
  4. 4Coverage gate runs before provider executionMissing, Partial, or Unknown required coverage becomes InsufficientCoverage. Ordo does not ask the provider to reason through an unestablished context.
  5. 5Negative knowledge stays scopedA negative observation can support absence only when the method could detect the target and matching coverage is Complete.
  6. 6Decision proceeds with explicit knowledge limitsThe provider sees coverage as data. Confidence, quantity of context, successful execution, or absence of errors never upgrades coverage implicitly.
Evidence answers what was observed. Coverage answers how completely the observation method covered a named scope. They remain separate so 'not found' cannot silently become 'absent'.

A large evidence collection is not automatically Complete coverage. A confident model answer is not Complete coverage. A successful API call is not Complete coverage. The contract names the scopes for which completeness is required.

6 · Capability

Authority is supplied by the host, not invented by the decision.

In current Ordo, Capability means a semantic authority prerequisite that the surrounding system has already established.

Illustrative C# · current Ordo semantics
public readonly record struct CapabilityName(string Value);

public sealed record Capability(
    CapabilityName Name,
    string EstablishedBy,
    DateTimeOffset EstablishedAt);

public static class CapabilityGate
{
    private static readonly CapabilityName CorrectTimeEntry =
        new("time-entry.correct");

    public static bool CanCorrect(IReadOnlySet<Capability> supplied) =>
        supplied.Any(c => c.Name == CorrectTimeEntry);
}

Capability is not authentication

It is not a JWT, API key, user identity, signature, or provider confidence score. Those may participate in how the host establishes authority, but the semantic core receives the already-established fact it needs.

The model cannot grant itself authority

A provider response may choose among legal alternatives. It cannot manufacture a missing Capability and thereby make an otherwise-illegal transition legal.

7 · A bounded decision

Refuse before model execution when prerequisites are missing.

The decision boundary is where state identity, evidence, coverage, authority, obligations, and the legal choice space come together.

Illustrative C# · current Ordo semantics
public abstract record DecisionGateResult
{
    private DecisionGateResult() { }

    public sealed record Allowed(
        IReadOnlyList<string> LegalChoices) : DecisionGateResult;

    public sealed record InsufficientCoverage(
        IReadOnlyList<CoverageScope> Missing) : DecisionGateResult;

    public sealed record MissingCapability(
        CapabilityName Capability) : DecisionGateResult;

    public sealed record BlockedByObligation(
        string Obligation) : DecisionGateResult;
}

public static DecisionGateResult EvaluateCorrection(
    DecisionContract contract,
    IReadOnlyList<ContextCoverageClaim> coverage,
    IReadOnlySet<Capability> capabilities,
    IReadOnlyList<string> openObligations)
{
    var missingCoverage =
        CoverageGate.MissingRequiredCoverage(contract, coverage);

    if (missingCoverage.Count > 0)
        return new DecisionGateResult.InsufficientCoverage(missingCoverage);

    var requiredCapability =
        new CapabilityName("time-entry.correct");

    if (!capabilities.Any(c => c.Name == requiredCapability))
        return new DecisionGateResult.MissingCapability(requiredCapability);

    if (openObligations.Count > 0)
        return new DecisionGateResult.BlockedByObligation(
            openObligations[0]);

    return new DecisionGateResult.Allowed(
        ["accept-correction", "reject-correction", "escalate"]);
}
  1. 1Semantically complete state viewThe domain projects the smallest authoritative view in which no omitted fact could change legality, evidence requirements, coverage, capability applicability, obligations, policy, or transition outcome.
  2. 2EvidenceTyped evidence identifies what was observed, where it came from, when it was observed, and what it depends on.
  3. 3Scoped coverageEach contract-defined scope is Complete, Partial, or Unknown. Required non-Complete coverage refuses the decision before provider execution.
  4. 4Capabilities and obligationsHost-established semantic authority is supplied as capabilities; unresolved required work remains explicit as obligations.
  5. 5Legal choice spacePolicy, guards, state, evidence, coverage, capabilities, and obligations determine which outcomes are actually legal.
  6. 6Compute, bounded decide, refuse, or escalateDeterministic work stays deterministic. Judgment happens only inside the legal choice space. Missing prerequisites do not force an answer.
  7. 7Result, requested effects, and auditThe result carries the state identity and decision facts forward; external work is requested as data and observations are recorded for later verification.
A bounded Ordo decision is legal only after the domain has established the state being judged, the evidence and coverage supporting it, the authority already granted, and the choices that remain legal. The model or engineer selects inside that envelope; it does not create the envelope.

Only the Allowed case reaches a model or human judgment step. Missing coverage, authority, or unresolved work is a semantic refusal, not another prompt.

8 · Unknown external effects

Do not turn "we do not know" into failure.

A timeout after a write does not prove the write failed. If repeating the operation may duplicate a real-world effect, the uncertainty must survive.

Illustrative C# · current Ordo semantics
public enum EffectStatus
{
    Succeeded,
    Failed,
    Unknown
}

public sealed record EffectObservation(
    string EffectId,
    EffectStatus Status,
    string? Detail);

public sealed record ReconciliationObligation(
    string EffectId,
    string Reason);

public static ReconciliationObligation? Interpret(
    EffectObservation observation) =>

    observation.Status switch
    {
        EffectStatus.Succeeded => null,

        EffectStatus.Failed => null,

        EffectStatus.Unknown =>
            new ReconciliationObligation(
                observation.EffectId,
                "Observe the external system before any dependent transition.")
    };
  1. 1Tier Two requests an effectThe semantic core describes the work as data. It does not perform the I/O.
  2. 2Tier Three coordinates the boundaryApplication code translates the request and hands it to the host without re-deciding the domain rule.
  3. 3Tier Four performs or observes the effectThe host returns what it can establish: Succeeded, Failed, or Unknown.
  4. 4Succeeded or Failed is interpreted normallyThe semantic core applies the domain meaning of the known result.
  5. 5Unknown creates a reconciliation obligationThe system preserves that the effect may or may not have happened. It does not pretend failure and blindly replay state-changing work.
  6. 6Tier Three / Four reconcile against realityThe external system is observed using the real contract. Retry is allowed early only when that contract proves retry safety.
  7. 7Tier Two interprets the reconciled factOnly after reality is re-established may later transitions that depend on the outcome proceed.
External effects do not collapse uncertainty. Succeeded, Failed, and Unknown remain different semantic facts; Unknown creates reconciliation work before later actions may rely on the outcome.

Why the extra state matters

If an HTTP request times out after a payment, message, deployment, or database operation, retrying blindly can be more dangerous than refusing to continue. Ordo keeps the unresolved effect visible until reality is re-established.

9 · Verification

Make C# help, then verify what C# cannot know.

The compiler should be one member of the verification system, not the definition of correctness.

Project-level mechanical guardrail
<PropertyGroup>
  <TargetFramework>net8.0</TargetFramework>
  <Nullable>enable</Nullable>
  <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
Behavioral assertion
[Fact]
public void Superseded_entries_have_no_available_actions()
{
    var entry = ExampleEntry.Recorded()
        with { Status = TimeEntryStatus.Superseded };

    var actions = AvailableActions.For(entry);

    Assert.Equal(EntryActions.None, actions);
}
Verification mechanisms and their blind spots
MechanismRefusesCannot refuse
Type and exhaustiveness checkingUnrepresentable states, unhandled cases, signature driftCode that type-checks and does the wrong thing
Architecture and dependency checksLayering violations, inverted dependenciesCorrect layering around incorrect behaviour
Contract and boundary checksRepresentation collapse at a declared boundaryBoundaries nobody declared
Behavioural testsBehaviour the author thought to assertA check that passes for the wrong reason
Integration against a real boundaryPresent-but-inert implementations, wire-format mismatchesConditions the integration environment never produces
Independent adversarial reviewDeclared-but-unreachable outcomes, vacuous assertionsAnything the reviewer also fails to imagine
Each row's right-hand column is what some later mechanism has to cover. This is why Ordo asks for several mechanisms with different sensitivities rather than a higher number from one.

Types can reject unrepresentable substitutions and missing enum cases. Architecture checks can reject dependency violations. Tests can verify behavior. Real-boundary integration can catch semantically inert implementations. Independent review can catch assumptions shared by the author and the tests.

The point

C# does not need to become F# for Ordo to work.

Use the strongest mechanisms C# gives you, make their limits explicit, and compensate where the language cannot encode a rule as strongly as the domain requires. Ordo is about semantic authority and bounded reasoning, not loyalty to a syntax.