Add StellaOps.Workflow engine: 14 libraries, WebService, 8 test projects

Extract product-agnostic workflow engine from Ablera.Serdica.Workflow into
standalone StellaOps.Workflow.* libraries targeting net10.0.

Libraries (14):
- Contracts, Abstractions (compiler, decompiler, expression runtime)
- Engine (execution, signaling, scheduling, projections, hosted services)
- ElkSharp (generic graph layout algorithm)
- Renderer.ElkSharp, Renderer.ElkJs, Renderer.Msagl, Renderer.Svg
- Signaling.Redis, Signaling.OracleAq
- DataStore.MongoDB, DataStore.PostgreSQL, DataStore.Oracle

WebService: ASP.NET Core Minimal API with 22 endpoints

Tests (8 projects, 109 tests pass):
- Engine.Tests (105 pass), WebService.Tests (4 E2E pass)
- Renderer.Tests, DataStore.MongoDB/Oracle/PostgreSQL.Tests
- Signaling.Redis.Tests, IntegrationTests.Shared

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
master
2026-03-20 19:14:44 +02:00
parent e56f9a114a
commit f5b5f24d95
422 changed files with 85428 additions and 0 deletions

View File

@@ -0,0 +1,101 @@
using System.Collections.Generic;
using System.Text.Json;
using WorkflowEngine.Abstractions;
using WorkflowEngine.Contracts;
namespace WorkflowEngine.Tutorials;
public sealed class ApprovalRequest
{
public long PolicyId { get; set; }
public long AnnexId { get; set; }
}
public sealed class ApprovalWorkflow : IDeclarativeWorkflow<ApprovalRequest>
{
public string WorkflowName => "ApprovalExample";
public string WorkflowVersion => "1.0.0";
public string DisplayName => "Approval Example";
public IReadOnlyCollection<string> WorkflowRoles => ["DBA", "UR_UNDERWRITER"];
public WorkflowSpec<ApprovalRequest> Spec { get; } = WorkflowSpec
.For<ApprovalRequest>()
.InitializeState(WorkflowExpr.Object(
WorkflowExpr.Prop("policyId", WorkflowExpr.Path("start.policyId")),
WorkflowExpr.Prop("annexId", WorkflowExpr.Path("start.annexId"))))
// Register the task definition (separate from activation).
.AddTask(approveTask)
// Start flow: validate, then activate the approval task.
.StartWith(flow => flow
.Call("Validate",
Address.LegacyRabbit("pas_policy_validate"),
WorkflowExpr.Object(
WorkflowExpr.Prop("policyId", WorkflowExpr.Path("state.policyId"))),
WorkflowHandledBranchAction.Complete,
WorkflowHandledBranchAction.Complete)
.ActivateTask("Approve Policy")) // pauses here
.Build();
public IReadOnlyCollection<WorkflowTaskDescriptor> Tasks => Spec.TaskDescriptors;
// Define the human task with roles, payload, optional deadline, and OnComplete flow.
private static readonly WorkflowHumanTaskDefinition<ApprovalRequest> approveTask =
WorkflowHumanTask.For<ApprovalRequest>(
"Approve Policy", // task name
"PolicyApproval", // task type (UI component)
"business/policies", // route
taskRoles: ["UR_UNDERWRITER"]) // only underwriters
.WithPayload(WorkflowExpr.Object(
WorkflowExpr.Prop("policyId", WorkflowExpr.Path("state.policyId")),
WorkflowExpr.Prop("annexId", WorkflowExpr.Path("state.annexId"))))
.WithTimeout(86400) // 24-hour deadline (optional)
.OnComplete(BuildApprovalFlow);
private static void BuildApprovalFlow(WorkflowFlowBuilder<ApprovalRequest> flow)
{
flow
// Store the user's answer in state for auditability.
.Set("answer", WorkflowExpr.Path("payload.answer"))
// Branch on the answer.
.WhenPayloadEquals("answer", "reject", "Rejected?",
rejected => rejected
.Call("Cancel Application",
Address.LegacyRabbit("pas_annexprocessing_cancelaplorqt"),
WorkflowExpr.Object(
WorkflowExpr.Prop("policyId", WorkflowExpr.Path("state.policyId"))),
WorkflowHandledBranchAction.Complete,
WorkflowHandledBranchAction.Complete)
.Complete(),
approved => approved
.Call<object>("Perform Operations",
Address.LegacyRabbit("pas_operations_perform",
SerdicaLegacyRabbitMode.MicroserviceConsumer),
WorkflowExpr.Object(
WorkflowExpr.Prop("policyId", WorkflowExpr.Path("state.policyId")),
WorkflowExpr.Prop("stages", WorkflowExpr.Array(
WorkflowExpr.String("UNDERWRITING"),
WorkflowExpr.String("CONFIRMATION")))),
WorkflowHandledBranchAction.Complete,
WorkflowHandledBranchAction.Complete,
resultKey: "operations")
.Set("passed", WorkflowExpr.Path("result.operations.passed"))
.WhenStateFlag("passed", true, "Operations Passed?",
passed => passed
.Call("Convert To Policy",
Address.LegacyRabbit("pas_polreg_convertapltopol"),
WorkflowExpr.Object(
WorkflowExpr.Prop("policyId", WorkflowExpr.Path("state.policyId"))),
WorkflowHandledBranchAction.Complete,
WorkflowHandledBranchAction.Complete)
.Complete(),
// Operations failed: re-open the same task for the user to fix and retry.
failed => failed.ActivateTask("Approve Policy")));
}
}