Optima.Net.Decisioning 1.0.1

Prefix Reserved
dotnet add package Optima.Net.Decisioning --version 1.0.1
                    
NuGet\Install-Package Optima.Net.Decisioning -Version 1.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Optima.Net.Decisioning" Version="1.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Optima.Net.Decisioning" Version="1.0.1" />
                    
Directory.Packages.props
<PackageReference Include="Optima.Net.Decisioning" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Optima.Net.Decisioning --version 1.0.1
                    
#r "nuget: Optima.Net.Decisioning, 1.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Optima.Net.Decisioning@1.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Optima.Net.Decisioning&version=1.0.1
                    
Install as a Cake Addin
#tool nuget:?package=Optima.Net.Decisioning&version=1.0.1
                    
Install as a Cake Tool

Optima.Net.Decisioning

Optima.Net.Decisioning is a minimal, immutable, framework-agnostic representation of what a system decided and why, independent of how the decision was reached. It serves as the authoritative record of a decision outcome, not as an execution engine, workflow system, or evaluation framework.

TL;DR

If you prefer to see DomainModel in action instead of reading about it, you can clone and run the test harness here:

https://github.com/snamretsuek/Optima.Net.TestHarnesses

It contains console applications that:

  • Wire everything together correctly
  • Show real execution flows
  • Let you step through the code in a debugger

If you still have questions after running the harness, then you will need to come back and read this README.

Note: The test harnesses are for demonstration purposes only. The test harness project contains examples for multiple Optima.Net packages. You need to run the one that applies to Optima.Net.Decisioning.


Principles

Decisioning is built on these core tenets:

  • Outcome-centric, not process-centric.
  • Immutable facts - once created, decisions cannot be changed.
  • Framework-agnostic - no dependency on domain, infrastructure, or orchestration systems.
  • Semantic clarity - decisions record meaning, not control flow.
  • Audit-friendly - explicit metadata and evidence for traceability.

Key Concepts

Decision<TIntent, TResult>

Represents a completed decision, capturing:

  • Intent - what was attempted.
  • Outcome - what was decided.
  • Result - produced value, if any.
  • Evidence - optional diagnostics or context.
  • Metadata - audit information (timestamp, actor, correlation).
  • Negotiation - optional negotiated outcome.

DecisionOutcome

Semantic classification of the decision result:

public enum DecisionOutcome
{
    Approved,
    Rejected,
    CounterProposed,
    Deferred,
    Escalated,
    Indeterminate
}

DecisionMetadata

Contextual information that accompanies every decision:

public sealed record DecisionMetadata
{
    public DateTimeOffset Timestamp { get; init; } = DateTimeOffset.UtcNow;
    public Optional<string> CorrelationId { get; init; }
    public Optional<string> Actor { get; init; }
    public Optional<string> Source { get; init; }
}

INegotiationOutcome

Used to record the result of a negotiation without performing it:

public interface INegotiationOutcome
{
    NegotiationDisposition Disposition { get; }
    Optional<object> Proposal { get; }
    IReadOnlyCollection<object> Evidence { get; }
}

Example Domain Types

These classes are domain examples used to demonstrate how Decisioning can model real problems. These belong to your application layer, not the Decisioning library itself.

Intent

public sealed record InsuranceClaimIntent(
    string ClaimId,
    string PolicyNumber,
    decimal ClaimedAmount,
    string Reason);

Result / Proposal

public sealed record ClaimSettlementProposal(
    string ClaimId,
    decimal ApprovedAmount,
    string SettlementNotes);

Negotiation Outcome Implementation

public sealed class NegotiationOutcome : INegotiationOutcome
{
    public NegotiationDisposition Disposition { get; }
    public Optional<object> Proposal { get; }
    public IReadOnlyCollection<object> Evidence { get; }

    public NegotiationOutcome(
        NegotiationDisposition disposition,
        Optional<object> proposal,
        IReadOnlyCollection<object> evidence)
    {
        Disposition = disposition;
        Proposal = proposal;
        Evidence = evidence;
    }

    public static NegotiationOutcome CounterProposal(object proposal, params object[] evidence) =>
        new(NegotiationDisposition.Modified, Optional<object>.Some(proposal), evidence);
}

Handling Decisions

This handler shows how you might produce a Decision<TIntent, TResult> in an application flow. It evaluates incoming intents, applies domain logic, and returns immutable decisions.

public sealed class InsuranceProposalDecisionHandler
{
    private readonly PolicyDiagnosticEvaluator _evaluator;
    private readonly INegotiatR _negotiator;

    public InsuranceProposalDecisionHandler(
        PolicyDiagnosticEvaluator evaluator,
        INegotiatR negotiator)
    {
        _evaluator = evaluator ?? throw new ArgumentNullException(nameof(evaluator));
        _negotiator = negotiator ?? throw new ArgumentNullException(nameof(negotiator));
    }

    public Decision<IProposal, IProposal> Handle(
        IReadOnlyCollection<(IPolicy<IProposal> Policy, IPolicyJustification<IProposal> Justification)> policies,
        IProposal proposal)
    {
        if (proposal == null)
            throw new ArgumentNullException(nameof(proposal));

        if (policies == null)
            throw new ArgumentNullException(nameof(policies));

        var metadata = new DecisionMetadata
        {
            Actor = Optional<string>.Some("Application.PolicyAdjudicator"),
            Source = Optional<string>.Some("Optima.Net.Decisioning.Integration"),
            CorrelationId = Optional<string>.Some(Guid.NewGuid().ToString())
        };

        var result = _evaluator.EvaluateAll(policies, proposal);

        if (result.Fulfilled)
        {
            return new Decision<IProposal, IProposal>(
                proposal,
                DecisionOutcome.Approved,
                Optional<IProposal>.Some(proposal),
                Optional<IReadOnlyCollection<object>>.None(),
                metadata,
                Optional<INegotiationOutcome>.None());
        }

        var failures = ExtractFailures(result).ToArray();

        var negotiation = _negotiator.Negotiate(proposal, failures);

        return MapOutcomeToDecision(proposal, failures, negotiation, metadata);
    }

    private static IEnumerable<PolicyFailure> ExtractFailures(
        PolicyDiagnosticResult<IProposal> result)
    {
        if (result.Fulfilled)
            yield break;

        yield return new PolicyFailure(
            result.PolicyType,
            result.Code ?? "Unknown",
            result.Semantics,
            result.Data);

        foreach (var child in result.Children)
        {
            foreach (var sub in ExtractFailures(child))
                yield return sub;
        }
    }

    private static Decision<IProposal, IProposal> MapOutcomeToDecision(
        IProposal original,
        IReadOnlyCollection<PolicyFailure> failures,
        NegotiatROutcome outcome,
        DecisionMetadata metadata)
    {
        switch (outcome)
        {
            case NegotiatRAccepted accepted:
                return new Decision<IProposal, IProposal>(
                    original,
                    DecisionOutcome.Approved,
                    Optional<IProposal>.Some(original),
                    Optional<IReadOnlyCollection<object>>.Some(failures.Cast<object>().ToList()),
                    metadata,
                    Optional<INegotiationOutcome>.None());

            case NegotiatRCounterProposed counter:
                var negotiation = new NegotiationOutcomeAdapter(
                    NegotiationDisposition.Modified,
                    Optional<object>.Some(counter.CounterProposal),
                    failures.Cast<object>().ToList());
                return new Decision<IProposal, IProposal>(
                    original,
                    DecisionOutcome.CounterProposed,
                    Optional<IProposal>.Some(counter.CounterProposal),
                    Optional<IReadOnlyCollection<object>>.Some(failures.Cast<object>().ToList()),
                    metadata,
                    Optional<INegotiationOutcome>.Some(negotiation));

            case NegotiatRRejected rejected:
                var negotiationRejected = new NegotiationOutcomeAdapter(
                    NegotiationDisposition.Unchanged,
                    Optional<object>.None(),
                    failures.Cast<object>().ToList());
                return new Decision<IProposal, IProposal>(
                    original,
                    DecisionOutcome.Rejected,
                    Optional<IProposal>.None(),
                    Optional<IReadOnlyCollection<object>>.Some(failures.Cast<object>().ToList()),
                    metadata,
                    Optional<INegotiationOutcome>.Some(negotiationRejected));

            default:
                throw new InvalidOperationException(
                    $"Unknown negotiation outcome type: {outcome.GetType().Name}");
        }
    }
}

Usage Summary

  1. Construct an intent object from your application logic.
  2. Invoke your handler to produce a Decision.
  3. The Decision object is immutable, semantically meaningful, and suitable for:
    • Auditing
    • Logging
    • Persistence
    • Event publication
    • Analytical inspection

Decisioning does not execute business logic. That belongs in your domain or application layer.

This project embodies a clear separation of concerns: your system decides, and Decisioning records the outcome. Optional fields explicitly represent presence or absence using Optional<T> from Optima.Net.

Product 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 was computed.  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 was computed.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.1 119 1/8/2026
1.0.0 121 1/4/2026

RELEASENOTES.md