Chd.Workflow 8.0.2

There is a newer version of this package available.
See the version list below for details.
dotnet add package Chd.Workflow --version 8.0.2
                    
NuGet\Install-Package Chd.Workflow -Version 8.0.2
                    
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="Chd.Workflow" Version="8.0.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Chd.Workflow" Version="8.0.2" />
                    
Directory.Packages.props
<PackageReference Include="Chd.Workflow" />
                    
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 Chd.Workflow --version 8.0.2
                    
#r "nuget: Chd.Workflow, 8.0.2"
                    
#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 Chd.Workflow@8.0.2
                    
#: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=Chd.Workflow&version=8.0.2
                    
Install as a Cake Addin
#tool nuget:?package=Chd.Workflow&version=8.0.2
                    
Install as a Cake Tool

Chd.Workflow

NuGet .NET License: MIT

Chd.Workflow is a lightweight workflow engine for ASP.NET Core applications. It helps you model business processes as a tree of nodes and transitions instead of spreading workflow logic across controllers, services, and large if/else blocks.

The package is designed for practical business applications. You define the process structure once, then let the engine manage state movement, validation, transition execution, history, runtime data, and API exposure.

Typical usage areas include:

  • approval flows,
  • budget and purchase requests,
  • leave and HR processes,
  • document review,
  • onboarding or operational checklists,
  • internal admin workflows.

Table of Contents


Why this package exists

Many applications start with a very small process:

  • Draft
  • Submitted
  • Approved
  • Rejected

Over time, that process grows:

  • some requests need manager approval,
  • others need finance approval,
  • large amounts need one more step,
  • different tenants need different routing,
  • each step needs a different form,
  • audit and history become mandatory.

When that happens, workflow logic often becomes hard to follow. The state machine is no longer in one place. It is spread across controllers, services, validation code, and database logic.

Chd.Workflow solves that by giving you one structured workflow model:

  • nodes describe steps,
  • transitions describe possible movements,
  • actions contain business side effects,
  • guards perform pre-checks,
  • rules choose routes,
  • the engine stores state and history.

This makes workflows easier to maintain, easier to visualize, and easier to evolve.


Main capabilities

Tree-based definitions

A workflow is built around a RootNode, and every step can contain child nodes.

Dynamic forms

Each node can define its own fields and form behavior.

Transition actions

Transitions can execute backend methods through [WorkflowAction] classes.

Guard support

Transitions can run one or more guards before moving to the next step.

Rule-based routing

A transition can use a workflow rule to decide the target node dynamically.

Runtime instance data

Workflow instances keep runtime data and history.

Built-in API surface

The package can expose minimal API endpoints automatically.

Multi-provider persistence

Use in-memory storage for simple scenarios, or relational storage for real environments.

React companion package

The engine is designed to work very well with qp-workflow-react, which provides a visual designer and runtime UI.


Installation

dotnet add package Chd.Workflow

Current package target in this repository: .NET 8.0.


Quick start

The smallest useful setup is only a few lines.

using Chd.Workflow.Extensions;

var builder = WebApplication.CreateBuilder(args);

builder.AddWorkflow();

var app = builder.Build();

await app.UseChdWorkflowAsync();

app.Run();

A common matching configuration looks like this:

{
  "DatabaseProvider": "PostgreSQL",
  "ConnectionStrings": {
    "PostgreSQL": "Host=localhost;Database=workflow;Username=postgres;Password=secret",
    "SqlServer": "Server=localhost;Database=Workflow;Trusted_Connection=True;TrustServerCertificate=True"
  }
}

What this setup does:

  1. registers workflow services,
  2. scans the calling assembly for actions, guards, and rules,
  3. resolves the storage provider,
  4. optionally runs packaged migrations,
  5. maps workflow endpoints under the configured prefix.

Hosting model

The current package uses a single main registration style:

builder.AddWorkflow();

You can also use typed overloads when the host application wants to provide custom participant resolution or custom notification behavior.

Basic host

builder.AddWorkflow(options =>
{
    options.RoutePrefix = "api/workflow";
});

Host with participant directory

builder.AddWorkflow<MyParticipantDirectory>(options =>
{
    options.AutoMigrate = true;
});

Host with participant directory and notifier

builder.AddWorkflow<MyParticipantDirectory, MyStateChangeNotifier>();

Then complete the startup with:

await app.UseChdWorkflowAsync();

This second call is responsible for startup-time workflow behavior such as migrations and endpoint mapping.


Configuration with ChdWorkflowOptions

ChdWorkflowOptions is the main configuration object for the package.

Property Description
DatabaseProvider Optional provider override. Common values: InMemory, SqlServer, PostgreSQL.
ConnectionString Explicit connection string override.
AutoMigrate If true, packaged workflow migrations run during startup.
MapEndpoints If true, workflow minimal APIs are mapped automatically.
RoutePrefix Route prefix for the workflow API.
WorkflowDatabaseSchema Schema used for workflow tables in relational providers.
WorkflowTableNaming Naming convention for workflow tables.

Example:

builder.AddWorkflow(options =>
{
    options.DatabaseProvider = "SqlServer";
    options.ConnectionString = builder.Configuration.GetConnectionString("SqlServer");
    options.RoutePrefix = "api/workflow";
    options.AutoMigrate = true;
});

You can also register host integrations through the options object:

builder.AddWorkflow(options =>
{
    options.UseParticipantDirectory<MyParticipantDirectory>();
    options.UseStateChangeNotifier<MyNotifier>();
});

Core domain model

The package is centered around a few core models.

WorkflowDefinition

This is the template of a workflow.

Typical data includes:

  • Id
  • Name
  • Description
  • Version
  • IsActive
  • RootNode
  • TenantId
  • audit fields such as CreatedAt, UpdatedAt, CreatedBy

Node

A node represents one step in the process.

Important members commonly include:

  • Id
  • Name
  • Title
  • Description
  • Type
  • Children
  • FormType
  • FormUrl
  • FormComponent
  • DataQuery
  • Fields
  • Transitions
  • Rules
  • AllowedRoles
  • Timeout
  • TimeoutAction

Transition

A transition describes how a node can move to another node.

Important members include:

  • Action
  • Label
  • TargetNodeId
  • Guard
  • GuardHandler
  • GuardHandlers
  • GuardFailureMessage
  • ActionHandler
  • ActionCondition
  • ActionConditionMode
  • RuleHandler
  • RuleMappings
  • ButtonStyle
  • RequiresConfirmation
  • RequiresComment

Field

A field defines one form input on a node.

Backend field types include:

  • Text
  • TextArea
  • Number
  • Decimal
  • Date
  • DateTime
  • Time
  • Checkbox
  • Radio
  • Dropdown
  • MultiSelect
  • File
  • Image
  • RichText
  • Hidden

WorkflowInstance

This is the runtime state of an active workflow.

It stores data such as:

  • DefinitionId
  • DefinitionVersion
  • CurrentNodeId
  • Status
  • Data
  • History
  • CreatedAt
  • UpdatedAt
  • CompletedAt
  • TenantId

Workflow actions

Workflow actions are how you connect transitions to business code.

Actions are discovered by assembly scanning and are identified with the [WorkflowAction] attribute.

Example action

using Chd.Workflow.Attributes;
using Chd.Workflow.Interfaces;

[WorkflowAction("approve-leave", DisplayName = "Approve Leave", Category = "HR")]
public sealed class ApproveLeaveAction : IWorkflowAction
{
    private readonly ILeaveService _leaveService;
    private readonly IEmailService _emailService;

    public ApproveLeaveAction(ILeaveService leaveService, IEmailService emailService)
    {
        _leaveService = leaveService;
        _emailService = emailService;
    }

    public async Task<WorkflowActionResult> ExecuteAsync(WorkflowActionContext ctx)
    {
        var leaveId = ctx.FormData["leaveId"]?.ToString();
        if (string.IsNullOrWhiteSpace(leaveId))
            return WorkflowActionResult.Fail("leaveId is required");

        await _leaveService.ApproveAsync(leaveId, ctx.UserId, ctx.CancellationToken);
        await _emailService.SendApprovalNotificationAsync(leaveId);

        return WorkflowActionResult.Ok(new
        {
            approvedAt = DateTime.UtcNow,
            approvedBy = ctx.UserId
        });
    }
}

Registration behavior

In many applications, you do not need to register actions manually. Calling builder.AddWorkflow(...) is enough, as long as your action classes are in the scanned assembly or in a referenced assembly that is part of the registration flow.

When to use actions

Actions are a good place for:

  • updating domain data,
  • calling services,
  • sending notifications,
  • saving external audit entries,
  • publishing output values for later workflow conditions.

WorkflowActionContext

The action context gives access to runtime information such as:

  • instance,
  • definition,
  • current node,
  • target node,
  • selected transition,
  • form data,
  • user id,
  • user roles,
  • comment,
  • service provider,
  • cancellation token.

Bind an action to a transition

{
  "action": "approve",
  "label": "Approve",
  "targetNodeId": "approved",
  "actionHandler": "approve-leave"
}

Legacy controller-style actions

If you are migrating an older project, you may want to keep an existing controller or service method close to its current shape instead of creating a separate wrapper class immediately.

For this case, the package also supports [WorkflowLegacyAction].

This option is useful when:

  • you already have stable request and response DTOs,
  • you want minimum source changes,
  • you want to annotate an existing method instead of writing a new IWorkflowAction class,
  • you are migrating a legacy codebase gradually.

Supported method patterns include:

  • Task<TDto> Method(RequestDto request)
  • Task<ActionResult<TDto>> Method(RequestDto request)
  • Task<IActionResult> Method(RequestDto request)
  • WorkflowActionResult Method(...)
  • optional special parameters such as CancellationToken, IServiceProvider, and WorkflowActionContext

Example:

using Chd.Workflow.Attributes;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/[controller]")]
public class LegacyBudgetController : ControllerBase
{
    private readonly IBudgetService _budgetService;

    public LegacyBudgetController(IBudgetService budgetService)
    {
        _budgetService = budgetService;
    }

    [HttpPost("create")]
    [WorkflowLegacyAction(
        "legacy-budget-create",
        DisplayName = "Legacy Budget Create",
        Category = "Legacy Budget",
        InputType = typeof(BudgetCreateRequest),
        OutputType = typeof(BudgetCreateResponse))]
    public async Task<ActionResult<BudgetCreateResponse>> Create(
        [FromBody] BudgetCreateRequest request,
        CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();

        var response = await _budgetService.CreateAsync(request);
        if (!response.Success)
            return BadRequest(response);

        return Ok(response);
    }
}

At runtime, the workflow engine will:

  • bind workflow form data into the request DTO,
  • create the controller or service through dependency injection,
  • call the annotated method,
  • convert DTO or MVC-style return values into WorkflowActionResult automatically.

This gives you a lower-friction migration path while keeping the modern IWorkflowAction model available for greenfield code.


Workflow guards

Guards run before the transition completes. They are intended for checks, validation, decision support, and safe routing support.

Typical guard use cases:

  • limit checks,
  • eligibility checks,
  • warning scenarios,
  • transition denial with a business reason,
  • dynamic rerouting.

Example guard

[WorkflowGuard("amount-check", DisplayName = "Amount Check", Category = "Budget")]
public sealed class AmountCheckGuard : IWorkflowGuard
{
    public Task<WorkflowGuardResult> EvaluateAsync(WorkflowGuardContext ctx)
    {
        var amount = Convert.ToDecimal(ctx.AllData["amount"] ?? 0m);

        if (amount > 100_000m)
            return Task.FromResult(WorkflowGuardResult.Deny("Amount is above the allowed limit."));

        if (amount > 10_000m)
            return Task.FromResult(WorkflowGuardResult.Warn("High amount. Please confirm before continuing."));

        return Task.FromResult(WorkflowGuardResult.Allowed(new { limitExceeded = false }));
    }
}

Supported transition guard patterns

A transition can use:

  • GuardHandler for one guard,
  • GuardHandlers for multiple guards,
  • Guard for an expression evaluated on workflow data.

When a guard denies the transition, the API returns a 400 response with code = "GUARD_FAILED".


Workflow rules

Rules are useful when the next step depends on a computed string result.

For example, a rule might return:

  • low
  • medium
  • high
  • finance
  • manager

Example rule

[WorkflowRule("amount-band", DisplayName = "Amount Band", Category = "Routing", PossibleResults = new[] { "low", "medium", "high" })]
public sealed class AmountBandRule : IWorkflowRule
{
    public Task<WorkflowRuleResult> EvaluateAsync(WorkflowRuleContext ctx)
    {
        var amount = Convert.ToDecimal(ctx.AllData["amount"] ?? 0m);

        var result = amount > 100_000m
            ? "high"
            : amount > 10_000m
                ? "medium"
                : "low";

        return Task.FromResult(WorkflowRuleResult.Ok(result));
    }
}

Transition example with rule mappings

{
  "action": "submit",
  "label": "Submit",
  "targetNodeId": "manager-review",
  "ruleHandler": "amount-band",
  "ruleMappings": [
    { "ruleResult": "high", "targetNodeId": "gm-review" },
    { "ruleResult": "medium", "targetNodeId": "manager-review" },
    { "ruleResult": "low", "targetNodeId": "auto-approved" }
  ]
}

If no mapping matches, TargetNodeId stays as the fallback route.


Participant directory and notifications

The engine can work together with the host for user/group resolution and notification handling.

IWorkflowParticipantDirectory

This interface is the bridge between the workflow engine and your own identity or role data source.

It is useful for:

  • resolving node roles to users,
  • feeding designer pickers,
  • preparing recipients for notifications.

Main method:

Task<IReadOnlyList<WorkflowUser>> ResolveUsersInGroupsAsync(
    IReadOnlyList<string> groupOrRoleNames,
    CancellationToken cancellationToken = default);

There is also a convenience base class:

public abstract class WorkflowParticipantDirectoryBase : IWorkflowParticipantDirectory

Example participant directory

public sealed class HostParticipantDirectory : WorkflowParticipantDirectoryBase
{
    private readonly AppDbContext _db;

    public HostParticipantDirectory(AppDbContext db)
    {
        _db = db;
    }

    public override async Task<IReadOnlyList<WorkflowUser>> ResolveUsersInGroupsAsync(
        IReadOnlyList<string> groupOrRoleNames,
        CancellationToken cancellationToken = default)
    {
        return await _db.UserRoles
            .Where(x => groupOrRoleNames.Contains(x.RoleName))
            .Select(x => new WorkflowUser
            {
                Id = x.UserId,
                Email = x.Email,
                DisplayName = x.DisplayName
            })
            .ToListAsync(cancellationToken);
    }
}

IWorkflowStateChangeNotifier

This interface lets the host react to successful transitions.

Typical uses include:

  • sending email,
  • sending SMS,
  • writing integration logs,
  • pushing notifications to other systems.

Notification admin endpoints

The package exposes notification admin endpoints under /admin/notification-settings when the host has the required notification settings service registered.


REST API overview

By default, endpoints are exposed under:

/api/workflow

Definitions

Method Endpoint Description
GET /definitions List definitions
GET /definitions/{id} Get one definition
POST /definitions Create a definition
PUT /definitions/{id} Update a definition
DELETE /definitions/{id} Delete a definition

Instances

Method Endpoint Description
GET /instances/{id} Get raw instance data
GET /instances/{id}/state Get render-ready workflow state
GET /instances/{id}/field-options/{fieldName} Resolve dynamic select options
POST /instances Create instance
POST /instances/{id}/transition Execute transition
POST /instances/{id}/validate Validate values without transition
POST /instances/{id}/cancel Cancel instance

Metadata

Method Endpoint Description
GET /actions List registered actions
GET /actions/grouped List actions grouped by category
GET /guards List registered guards
GET /rules List registered rules
GET /rules/grouped List rules grouped by category
GET /rules/{name} Get one rule by name

Admin

Method Endpoint Description
GET /admin/notification-settings Read notification settings
PUT /admin/notification-settings Save notification settings
GET /admin/participant-groups List roles/groups for designer pickers

Error shape notes

Typical error patterns include:

  • { error: "..." }
  • { error: "...", errors: { fieldName: "message" } }
  • { code: "GUARD_FAILED", error: "...", guard: "...", action: "..." }

Dynamic forms and field options

Each node can expose fields for its active step.

Supported features include:

  • required fields,
  • read-only fields,
  • placeholders and help text,
  • min/max validation,
  • regex validation,
  • static options,
  • SQL-backed options,
  • field visibility rules.

Form types

Form type Meaning
Dynamic Render directly from workflow field definitions
External Redirect to an external page
Component Render through a named frontend component
None No form is rendered

SQL-backed options

Selectable fields such as Dropdown, Radio, and MultiSelect can use OptionsQuery.

Important design detail:

  • SQL stays on the server,
  • the browser does not receive raw SQL in the runtime payload,
  • the frontend resolves options through /instances/{id}/field-options/{fieldName}.

Field visibility

The field model also supports:

  • VisibilityGuard
  • VisibilityGuardHandler
  • VisibilityGuardHandlers

This allows more advanced runtime UX where fields appear only under specific conditions.


Database providers and persistence

The package supports several persistence strategies.

In-memory

Useful for:

  • development,
  • demos,
  • tests,
  • quick prototypes.

SQL Server

Useful when the host runs on Microsoft SQL Server.

PostgreSQL

Useful when the host runs on PostgreSQL.

Migrations

If AutoMigrate is enabled, packaged FluentMigrator migrations run during UseChdWorkflowAsync().

Custom repository

If you need a custom persistence model, replace the default repository registration with your own IWorkflowRepository implementation.

builder.AddWorkflow(o =>
{
    o.DatabaseProvider = "InMemory";
});

builder.Services.AddScoped<IWorkflowRepository, MyCustomRepository>();

The last registration wins, so the engine will use your repository.


Frontend integration

This package is designed to work with the React package in this repository:

  • folder: chd-workflow-react
  • npm package: qp-workflow-react

A common full-stack usage pattern is:

  1. host the backend with Chd.Workflow,
  2. expose the workflow API,
  3. use the React designer to manage definitions,
  4. use the React runner to execute instances.

Example React designer

import { TreeDesigner } from 'qp-workflow-react';

<TreeDesigner
  apiUrl="http://localhost:5035/api/workflow"
  initialDefinitionId="leave-request"
  locale="en"
  enableFormEditor
  onSaved={(definition) => console.log(definition.id)}
/>

Example React runner

import { WorkflowRunner } from 'qp-workflow-react';

<WorkflowRunner
  apiUrl="http://localhost:5035/api/workflow"
  instanceId="instance-123"
  locale="en"
  theme="dark"
/>

Migration advice

If you already have manual workflow logic in a legacy project, move gradually.

Suggested migration steps

  1. identify the hidden state machine in existing code,
  2. extract meaningful side effects into workflow actions,
  3. move pre-check logic into guards,
  4. move branching decisions into rules when needed,
  5. start with one business flow first,
  6. use feature flags if you want a low-risk rollout,
  7. remove old routing logic only after the new flow is stable.

Two migration styles are supported

You do not need to choose only one style for the whole application.

1. Modern workflow-first style

Use this for new code.

  • write dedicated IWorkflowAction classes,
  • keep workflow side effects explicit,
  • keep routing and rules fully workflow-oriented.
2. Legacy controller-friendly style

Use this when an old project already has stable controller or service methods.

  • keep the existing request DTO,
  • keep the existing response DTO,
  • add [WorkflowLegacyAction] to the method,
  • map the transition ActionHandler to that action name,
  • migrate deeper refactoring later.

This means you can start with low-change integration first, then move selected flows to dedicated workflow action classes over time.

Good first migration targets

These are usually good first candidates:

  • leave requests,
  • expense approval,
  • procurement requests,
  • budget approval,
  • internal review flows,
  • onboarding forms.

Example definition

Below is a simplified example workflow definition.

{
  "id": "leave-request",
  "name": "Leave Request Workflow",
  "description": "Approval flow for employee leave requests",
  "version": 1,
  "isActive": true,
  "rootNode": {
    "id": "start",
    "name": "Start",
    "type": "Start",
    "children": [
      {
        "id": "submit-request",
        "name": "Submit Request",
        "type": "Task",
        "formType": "Dynamic",
        "fields": [
          {
            "name": "days",
            "label": "Days",
            "type": "Number",
            "required": true,
            "readOnly": false,
            "order": 1,
            "min": 1,
            "max": 30
          },
          {
            "name": "reason",
            "label": "Reason",
            "type": "TextArea",
            "required": true,
            "readOnly": false,
            "order": 2
          }
        ],
        "transitions": [
          {
            "id": "tr-submit",
            "action": "submit",
            "label": "Submit",
            "targetNodeId": "manager-review",
            "actionHandler": "submit-leave-request",
            "order": 1,
            "requiresConfirmation": false,
            "requiresComment": false
          }
        ],
        "children": [
          {
            "id": "manager-review",
            "name": "Manager Review",
            "type": "Task",
            "allowedRoles": ["Manager"],
            "fields": [],
            "transitions": [
              {
                "id": "tr-approve",
                "action": "approve",
                "label": "Approve",
                "targetNodeId": "approved",
                "buttonStyle": "success",
                "order": 1,
                "requiresConfirmation": true,
                "requiresComment": false
              },
              {
                "id": "tr-reject",
                "action": "reject",
                "label": "Reject",
                "targetNodeId": "rejected",
                "buttonStyle": "danger",
                "order": 2,
                "requiresConfirmation": false,
                "requiresComment": true
              }
            ],
            "children": [
              {
                "id": "approved",
                "name": "Approved",
                "type": "End",
                "children": [],
                "fields": [],
                "transitions": [],
                "rules": [],
                "allowedRoles": [],
                "order": 1
              },
              {
                "id": "rejected",
                "name": "Rejected",
                "type": "End",
                "children": [],
                "fields": [],
                "transitions": [],
                "rules": [],
                "allowedRoles": [],
                "order": 2
              }
            ],
            "rules": [],
            "order": 1
          }
        ],
        "rules": [],
        "allowedRoles": [],
        "order": 1
      }
    ],
    "fields": [],
    "transitions": [],
    "rules": [],
    "allowedRoles": [],
    "order": 0
  },
  "createdAt": "2026-01-01T00:00:00Z"
}

Troubleshooting

Definitions save but enum values fail to bind

Use the package registration path so workflow JSON options are configured correctly.

Actions, guards, or rules do not appear

Check that:

  • the host calls builder.AddWorkflow(...),
  • attributes are applied correctly,
  • the related assembly is reachable by the host,
  • the application is using the expected startup path.

Inspect:

GET /api/workflow/instances/{id}/field-options/{fieldName}

If this is empty, review the field definition, the query, and the runtime data used by the query.

Notification settings endpoint returns 503

This usually means the host has not registered the required notification admin settings service.

Participant groups are empty in the designer

Implement IWorkflowParticipantDirectory.ListGroupsAsync(...) and register the participant directory in the host.

External forms do not return correctly

Check that the external page reads workflow parameters correctly and transitions the instance with the correct action and instance id.


License

MIT License. See LICENSE.


If you also use the frontend package from this repository, read chd-workflow-react/README.md for the visual designer, runtime runner, hooks, client API, localization, and external form helpers.

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
10.0.2 96 9/1/2026
10.0.1 121 8/30/2026
10.0.0 115 8/16/2026
8.0.2 95 9/5/2026
8.0.0 136 6/13/2026
1.0.5 135 4/26/2026
1.0.4 118 4/26/2026
1.0.3 120 4/19/2026
1.0.0 131 4/16/2026