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
<PackageReference Include="Optima.Net.Decisioning" Version="1.0.1" />
<PackageVersion Include="Optima.Net.Decisioning" Version="1.0.1" />
<PackageReference Include="Optima.Net.Decisioning" />
paket add Optima.Net.Decisioning --version 1.0.1
#r "nuget: Optima.Net.Decisioning, 1.0.1"
#:package Optima.Net.Decisioning@1.0.1
#addin nuget:?package=Optima.Net.Decisioning&version=1.0.1
#tool nuget:?package=Optima.Net.Decisioning&version=1.0.1
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
- Construct an intent object from your application logic.
- Invoke your handler to produce a Decision.
- 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 | Versions 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. |
-
net8.0
- Optima.Net (>= 1.0.9)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
RELEASENOTES.md