DecisionKit.Core 0.1.0

dotnet add package DecisionKit.Core --version 0.1.0
                    
NuGet\Install-Package DecisionKit.Core -Version 0.1.0
                    
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="DecisionKit.Core" Version="0.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="DecisionKit.Core" Version="0.1.0" />
                    
Directory.Packages.props
<PackageReference Include="DecisionKit.Core" />
                    
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 DecisionKit.Core --version 0.1.0
                    
#r "nuget: DecisionKit.Core, 0.1.0"
                    
#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 DecisionKit.Core@0.1.0
                    
#: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=DecisionKit.Core&version=0.1.0
                    
Install as a Cake Addin
#tool nuget:?package=DecisionKit.Core&version=0.1.0
                    
Install as a Cake Tool

DecisionKit

A strongly typed, provider-independent decision engine for .NET, with TypeSafe JEV support.

CI License: MIT

DecisionKit lets a .NET application model a decision the way the domain sees it — typed questions, typed answers, explicit probabilities and scores — and execute that decision against a pluggable provider. TypeSafe JEV is the first production provider; it is not the architecture.

This is not another TypeSafe SDK. The decision domain lives in DecisionKit.Core and knows nothing about JEV, HTTP or JSON. Swapping the provider does not rewrite your application model.


Status

Pre-release — under active development. The public API is unstable until 1.0.0. See the roadmap for what is implemented and what comes next.


Why

Provider SDKs typically fuse the protocol, the transport, the retry policy and the domain model into one surface. The result works, but the application model ends up shaped by the vendor's wire format, and replacing the vendor means rewriting the domain.

DecisionKit inverts that:

Application
    |
    v
DecisionKit.Core            <- domain: questions, answers, results, provider abstraction
    |
    +---- DecisionKit.Jev          <- TypeSafe JEV protocol, HTTP, auth, retry
    +---- DecisionKit.Extensions   <- DI, configuration, HttpClientFactory, logging
    +---- DecisionKit.Testing      <- deterministic fakes, request capture, error injection

The dependency arrow only ever points at Core.


Packages

Package Purpose Dependencies
DecisionKit.Core Decision domain model and IDecisionProvider abstraction none
DecisionKit.Jev TypeSafe JEV provider: protocol, transport, auth, resilience Core
DecisionKit.Extensions IServiceCollection, IOptions, IHttpClientFactory, logging Core, Jev
DecisionKit.Testing Fake and deterministic providers for tests Core

Target frameworks: net8.0 and net10.0.


Design principles

  1. Provider independence. Core contains no provider URL, header, credential or DTO.
  2. Strong typing. A question determines the type of its answer at compile time.
  3. Forward compatibility. Known shapes map to typed models; unknown shapes are preserved as raw data. Unknown is never discarded.
  4. Explicit failure. Validation, authentication, authorization, transport, timeout, cancellation, rate limiting, serialization and provider errors are distinguishable.
  5. Resilience is semantics, not plumbing. Retry, timeout and idempotency are modelled as part of the operation's meaning.
  6. Testability by construction. Deterministic providers and virtual time; no HTTP in tests.
  7. Minimal public surface. A type is public only when a consumer must use it.

The full rationale lives in docs/architecture/.


Quick start

DecisionKit.Core is real and the snippet below compiles against it. DecisionKit.Jev now calls the TypeSafe JEV service, so provider can be a JevDecisionProvider — see Using the JEV provider below.

using DecisionKit.Errors;
using DecisionKit.Identifiers;
using DecisionKit.Providers;
using DecisionKit.Questions;
using DecisionKit.Results;

var routing = new ChoiceQuestion<Department>(
    new QuestionId("routing"),
    "Which team should handle this ticket?",
    [Department.Billing, Department.Support]);

var frustration = new ScoreQuestion(
    new QuestionId("frustration"),
    "How frustrated is the customer?",
    minimum: 0,
    maximum: 10);

var request = new DecisionRequest(QuestionSet.Create(routing, frustration))
{
    Input = DecisionInput.FromText(ticketText),
    ClientRequestId = RequestId.New(),
};

DecisionResult result = await provider.DecideAsync(request, cancellationToken);

// The answer type comes from the question, not from a cast.
Department team = result.Get(routing).Value.Selection;
double score = result.Get(frustration).Value.Value;

Failures are distinguishable without parsing a message:

try
{
    return await provider.DecideAsync(request, cancellationToken);
}
catch (DecisionTransientException ex) when (ex.Retry.Retryability == DecisionRetryability.Retryable)
{
    logger.LogWarning("Provider {Provider} asked to retry after {Delay}.", ex.Error.ProviderName, ex.Retry.RetryAfter);
    throw;
}

Using the JEV provider

JevDecisionProvider takes an HttpClient it never owns and a credential provider it asks on every call, so a rotated key takes effect without rebuilding anything.

using DecisionKit.Jev.Authentication;
using DecisionKit.Jev.Client;

var provider = new JevDecisionProvider(
    httpClient,
    new StaticJevCredentialProvider(apiKey));

Timeouts, the base address, retrying and the mapping rules are configured together:

var provider = new JevDecisionProvider(
    httpClient,
    new StaticJevCredentialProvider(apiKey),
    new JevProviderOptions
    {
        Endpoint = new JevEndpoint(new Uri("https://gateway.internal/jev/")),
        AttemptTimeout = TimeSpan.FromSeconds(10),
        OperationTimeout = TimeSpan.FromSeconds(30),
        Retry = JevRetryPolicy.Standard,
    },
    logger);

Retrying is off by default. JEV has no idempotency mechanism, so a repeated call is a second evaluation that is billed again and may answer differently: turning it on is the application's decision, not the library's.

The JEV protocol reference records the wire contract, the header precedence, the retry rules and every failure the transport can report.

With dependency injection

DecisionKit.Extensions registers the provider as a singleton, gives it an IHttpClientFactory client, the application's logger and the application's TimeProvider, and validates the configuration while the host starts.

builder.Services
    .AddDecisionKit()
    .AddJevProvider(builder.Configuration.GetSection("Jev"));
{
  "Jev": {
    "ApiKey": "<your-api-key>",
    "OperationTimeout": "00:01:00",
    "Retry": { "MaxAttempts": 3 }
  }
}

Registrations can be named, decorated and pointed at a credential that never touches configuration:

builder.Services
    .AddDecisionKit()
    .AddJevProvider("batch", builder.Configuration.GetSection("Jev:Batch"))
    .WithCredentials(container => container.GetRequiredService<VaultCredentials>())
    .Decorate((container, inner) => new MeteredDecisionProvider(inner, container.GetRequiredService<IMeterFactory>()));

See the dependency injection guide for every setting and what it does.

In tests

DecisionKit.Testing replaces the provider, so application code is tested with no HTTP:

using DecisionKit.Testing.Assertions;
using DecisionKit.Testing.Providers;

var provider = new FakeDecisionProvider()
    .Returns(routing, Department.Billing)
    .Returns(frustration, 7.0);

var result = await sut.HandleAsync(ticket, CancellationToken.None);

DecisionAssert.AskedExactly(DecisionAssert.CalledOnce(provider.Calls).Request, "routing", "frustration");
Assert.Equal(Department.Billing, result.Team);

Failures are injected with Fails and FailsTimes, latency is simulated on a TimeProvider, and DeterministicDecisionProvider derives stable answers for snapshots and samples. See the testing guide.


Documentation

Document Contents
Getting started From installing a package to reading a typed answer
Roadmap What exists today, and what is being considered next
Architecture overview Layers, boundaries, data flow
Type safety How a question decides the type of its answer
Error handling Categories, exception types, retry hints, cancellation
Testing Testing application code with fake and deterministic providers
AOT and trimming What is guaranteed, and how it is enforced
JEV provider Credentials, rubrics, timeouts, retrying, failure codes
JEV wire protocol The JEV payload and how it maps to the domain
Writing a provider Implementing IDecisionProvider yourself
FAQ The questions that come up first
Samples Six runnable projects, four of which need no credential
Decision records Why the architecture is what it is
Contributing Development rules, commit conventions, review gates
Security policy Vulnerability reporting and secret handling
Changelog Released changes, Semantic Versioning

Security and privacy

DecisionKit sends your application data to a third-party provider when you configure one.

  • API keys are never logged, never serialized into diagnostics and never included in exceptions.
  • Request and response payloads are not logged by default.
  • Raw protocol capture is opt-in and can be disabled entirely.
  • HTTPS is required for production endpoints.

Report vulnerabilities privately — see SECURITY.md.


Relationship to TypeSafe

TypeSafe and JEV are products of their respective owners. This project is an independent, community-maintained client. It is not affiliated with, endorsed by, or supported by TypeSafe.


License

MIT © Jonatha Panni

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 is compatible.  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.
  • net10.0

    • No dependencies.
  • net8.0

    • No dependencies.

NuGet packages (3)

Showing the top 3 NuGet packages that depend on DecisionKit.Core:

Package Downloads
DecisionKit.Jev

TypeSafe JEV provider for DecisionKit: protocol mapping, HTTP transport, authentication, error mapping, retry and idempotency.

DecisionKit.Testing

Deterministic test doubles for DecisionKit: fake providers, canned results, request capture and error injection. No HTTP, no real provider calls.

DecisionKit.Extensions

Dependency injection, configuration binding, HttpClientFactory and logging integration for DecisionKit and the JEV provider.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.0 152 9/29/2026