Skip to content

Commit 41a9a6e

Browse files
committed
benchmarks: add governance approval workflow
1 parent f6c4399 commit 41a9a6e

7 files changed

Lines changed: 347 additions & 0 deletions
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using BenchmarkDotNet.Attributes;
2+
using ModularityKit.Mutator.Governance.Abstractions.Requests.Model;
3+
using ModularityKit.Mutator.Benchmarks.Governance.Approval.Support;
4+
5+
namespace ModularityKit.Mutator.Benchmarks.Governance.Approval;
6+
7+
/// <summary>
8+
/// Benchmarks approval request creation and assignment overhead.
9+
/// </summary>
10+
[BenchmarkCategory("Governance")]
11+
[MemoryDiagnoser]
12+
[InProcess]
13+
public class ApprovalRequestCreationBenchmarks : ApprovalWorkflowBenchmarkBase
14+
{
15+
/// <summary>
16+
/// Measures approval request creation and single approval assignment.
17+
/// </summary>
18+
[Benchmark(Baseline = true)]
19+
public MutationRequest PendingApproval_SingleRequest()
20+
=> ApprovalWorkflowBenchmarkSupport.CreatePendingApprovalRequest(RequestId);
21+
22+
/// <summary>
23+
/// Measures approval request creation with two approval steps.
24+
/// </summary>
25+
[Benchmark]
26+
public MutationRequest PendingApproval_TwoStepRequest()
27+
=> ApprovalWorkflowBenchmarkSupport.CreateTwoStepApprovalRequest(RequestId);
28+
29+
/// <summary>
30+
/// Measures approval request creation driven by role metadata.
31+
/// </summary>
32+
[Benchmark]
33+
public MutationRequest PendingApproval_RoleBasedRequest()
34+
=> ApprovalWorkflowBenchmarkSupport.CreateRoleBasedApprovalRequest(RequestId);
35+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
using BenchmarkDotNet.Attributes;
2+
using ModularityKit.Mutator.Abstractions.Context;
3+
using ModularityKit.Mutator.Governance.Abstractions.Approval.Model;
4+
using ModularityKit.Mutator.Governance.Abstractions.Requests.Model;
5+
using ModularityKit.Mutator.Benchmarks.Governance.Approval.Support;
6+
7+
namespace ModularityKit.Mutator.Benchmarks.Governance.Approval;
8+
9+
/// <summary>
10+
/// Benchmarks approval decision paths in the governance runtime.
11+
/// </summary>
12+
[BenchmarkCategory("Governance")]
13+
[MemoryDiagnoser]
14+
[InProcess]
15+
public class ApprovalWorkflowDecisionBenchmarks : ApprovalWorkflowBenchmarkBase
16+
{
17+
/// <summary>
18+
/// Measures single approval requirement being approved and the request being finalized.
19+
/// </summary>
20+
[Benchmark(Baseline = true)]
21+
public async Task<MutationRequest> ApproveRequirement_Granted()
22+
{
23+
var (manager, request) = CreateSingleApprovalWorkflow();
24+
var approvalId = request.ApprovalRequirements[0].ApprovalId;
25+
26+
return await manager.ApproveRequirement(
27+
request.RequestId,
28+
approvalId,
29+
ApprovalWorkflowBenchmarkSupport.CreateDecisionContext("alice", "Approve governance benchmark request"))
30+
.ConfigureAwait(false);
31+
}
32+
33+
/// <summary>
34+
/// Measures the first approval in two step approval workflow before the request can finalize.
35+
/// </summary>
36+
[Benchmark]
37+
public async Task<MutationRequest> ApproveRequirement_FirstStepPending()
38+
{
39+
var (manager, request) = CreateTwoStepApprovalWorkflow();
40+
var approvalId = request.ApprovalRequirements[0].ApprovalId;
41+
42+
return await manager.ApproveRequirement(
43+
request.RequestId,
44+
approvalId,
45+
ApprovalWorkflowBenchmarkSupport.CreateDecisionContext("alice", "Approve first step in benchmark workflow"))
46+
.ConfigureAwait(false);
47+
}
48+
49+
/// <summary>
50+
/// Measures the second approval in two step approval workflow that finalizes the request.
51+
/// </summary>
52+
[Benchmark]
53+
public async Task<MutationRequest> ApproveRequirement_FinalizeAfterSecondStep()
54+
{
55+
var (manager, request) = CreateTwoStepApprovalWorkflow();
56+
var firstApprovalId = request.ApprovalRequirements[0].ApprovalId;
57+
var secondApprovalId = request.ApprovalRequirements[1].ApprovalId;
58+
59+
var firstApproved = await manager.ApproveRequirement(
60+
request.RequestId,
61+
firstApprovalId,
62+
ApprovalWorkflowBenchmarkSupport.CreateDecisionContext("alice", "Approve first benchmark step"))
63+
.ConfigureAwait(false);
64+
65+
return await manager.ApproveRequirement(
66+
firstApproved.RequestId,
67+
secondApprovalId,
68+
ApprovalWorkflowBenchmarkSupport.CreateDecisionContext("bob", "Approve second benchmark step"))
69+
.ConfigureAwait(false);
70+
}
71+
72+
/// <summary>
73+
/// Measures single approval requirement being rejected and the request being terminated.
74+
/// </summary>
75+
[Benchmark]
76+
public async Task<MutationRequest> RejectRequirement_Rejected()
77+
{
78+
var (manager, request) = CreateSingleApprovalWorkflow();
79+
var approvalId = request.ApprovalRequirements[0].ApprovalId;
80+
81+
return await manager.RejectRequirement(
82+
request.RequestId,
83+
approvalId,
84+
ApprovalWorkflowBenchmarkSupport.CreateDecisionContext("alice", "Reject governance benchmark request"),
85+
rejection: ApprovalWorkflowBenchmarkSupport.CreateRejectionReason())
86+
.ConfigureAwait(false);
87+
}
88+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using BenchmarkDotNet.Attributes;
2+
using ModularityKit.Mutator.Abstractions.Context;
3+
using ModularityKit.Mutator.Governance.Abstractions.Requests.Model;
4+
using ModularityKit.Mutator.Benchmarks.Governance.Approval.Support;
5+
6+
namespace ModularityKit.Mutator.Benchmarks.Governance.Approval;
7+
8+
/// <summary>
9+
/// Benchmarks expiration handling for pending governance approvals.
10+
/// </summary>
11+
[BenchmarkCategory("Governance")]
12+
[MemoryDiagnoser]
13+
[InProcess]
14+
public class ApprovalWorkflowExpirationBenchmarks : ApprovalWorkflowBenchmarkBase
15+
{
16+
/// <summary>
17+
/// Measures expiration of pending approval request and the resulting rejection bookkeeping.
18+
/// </summary>
19+
[Benchmark(Baseline = true)]
20+
public async Task<IReadOnlyList<MutationRequest>> ExpirePendingApprovals_Sweep()
21+
{
22+
var (manager, _) = CreateExpiredWorkflow();
23+
24+
return await manager.ExpirePendingApprovals(
25+
DateTimeOffset.UtcNow,
26+
MutationContext.Service("approval-sweeper", "Expire pending governance approvals"))
27+
.ConfigureAwait(false);
28+
}
29+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using ModularityKit.Mutator.Governance.Abstractions.Approval.Contracts;
2+
using ModularityKit.Mutator.Governance.Abstractions.Requests.Model;
3+
using ModularityKit.Mutator.Governance.Runtime.Approval.Execution;
4+
using ModularityKit.Mutator.Governance.Runtime.Storage;
5+
6+
namespace ModularityKit.Mutator.Benchmarks.Governance.Approval.Support;
7+
8+
/// <summary>
9+
/// Shared setup helpers for governance approval workflow benchmarks.
10+
/// </summary>
11+
public abstract class ApprovalWorkflowBenchmarkBase
12+
{
13+
protected const string RequestId = "governance-approval-request";
14+
15+
protected static (IMutationRequestApprovalWorkflowManager Manager, MutationRequest Request) CreateSingleApprovalWorkflow()
16+
{
17+
var store = new InMemoryMutationRequestStore();
18+
var manager = new MutationRequestApprovalWorkflowManager(store);
19+
var request = store.Create(ApprovalWorkflowBenchmarkSupport.CreatePendingApprovalRequest(RequestId))
20+
.GetAwaiter()
21+
.GetResult();
22+
23+
return (manager, request);
24+
}
25+
26+
protected static (IMutationRequestApprovalWorkflowManager Manager, MutationRequest Request) CreateTwoStepApprovalWorkflow()
27+
{
28+
var store = new InMemoryMutationRequestStore();
29+
var manager = new MutationRequestApprovalWorkflowManager(store);
30+
var request = store.Create(ApprovalWorkflowBenchmarkSupport.CreateTwoStepApprovalRequest(RequestId))
31+
.GetAwaiter()
32+
.GetResult();
33+
34+
return (manager, request);
35+
}
36+
37+
protected static (IMutationRequestApprovalWorkflowManager Manager, MutationRequest Request) CreateExpiredWorkflow()
38+
{
39+
var store = new InMemoryMutationRequestStore();
40+
var manager = new MutationRequestApprovalWorkflowManager(store);
41+
var request = store.Create(ApprovalWorkflowBenchmarkSupport.CreateExpiredApprovalRequest(RequestId))
42+
.GetAwaiter()
43+
.GetResult();
44+
45+
return (manager, request);
46+
}
47+
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
using ModularityKit.Mutator.Abstractions.Context;
2+
using ModularityKit.Mutator.Abstractions.Intent;
3+
using ModularityKit.Mutator.Abstractions.Policies;
4+
using ModularityKit.Mutator.Governance.Abstractions.Approval.Model;
5+
using ModularityKit.Mutator.Governance.Abstractions.Requests.Factory;
6+
using ModularityKit.Mutator.Governance.Abstractions.Requests.Model;
7+
8+
namespace ModularityKit.Mutator.Benchmarks.Governance.Approval.Support;
9+
10+
/// <summary>
11+
/// Builds repeatable approval workflow benchmark fixtures.
12+
/// </summary>
13+
internal static class ApprovalWorkflowBenchmarkSupport
14+
{
15+
/// <summary>
16+
/// Creates a pending approval request with one approval requirement.
17+
/// </summary>
18+
public static MutationRequest CreatePendingApprovalRequest(
19+
string requestId,
20+
DateTimeOffset? expiresAt = null)
21+
=> MutationRequestFactory.PendingApproval(
22+
stateId: "governance-benchmark:approval",
23+
stateType: "GovernanceState",
24+
mutationType: "ApproveGovernanceMutation",
25+
intent: CreateIntent(),
26+
context: MutationContext.User("requester", "Requester", "Need approval"),
27+
requirements:
28+
[
29+
PolicyRequirement.Approval("alice", "Manager approval")
30+
],
31+
expectedStateVersion: "v10",
32+
expiresAt: expiresAt)
33+
with
34+
{
35+
RequestId = requestId
36+
};
37+
38+
/// <summary>
39+
/// Creates a pending approval request with two sequential approval requirements.
40+
/// </summary>
41+
public static MutationRequest CreateTwoStepApprovalRequest(string requestId)
42+
=> MutationRequestFactory.PendingApproval(
43+
stateId: "governance-benchmark:approval",
44+
stateType: "GovernanceState",
45+
mutationType: "ApproveGovernanceMutation",
46+
intent: CreateIntent(),
47+
context: MutationContext.User("requester", "Requester", "Need approval"),
48+
requirements:
49+
[
50+
PolicyRequirement.Approval("alice", "Manager approval"),
51+
PolicyRequirement.Approval("bob", "Security approval")
52+
],
53+
expectedStateVersion: "v10")
54+
with
55+
{
56+
RequestId = requestId
57+
};
58+
59+
/// <summary>
60+
/// Creates a request that already has an expired approval requirement.
61+
/// </summary>
62+
public static MutationRequest CreateExpiredApprovalRequest(string requestId)
63+
=> CreatePendingApprovalRequest(
64+
requestId,
65+
DateTimeOffset.UtcNow.AddMinutes(-5));
66+
67+
/// <summary>
68+
/// Creates a pending approval request driven by role metadata.
69+
/// </summary>
70+
public static MutationRequest CreateRoleBasedApprovalRequest(string requestId)
71+
=> MutationRequestFactory.PendingApproval(
72+
stateId: "governance-benchmark:approval-role",
73+
stateType: "GovernanceState",
74+
mutationType: "ApproveGovernanceMutation",
75+
intent: CreateIntent(),
76+
context: MutationContext.User("requester", "Requester", "Need role-based approval"),
77+
requirements:
78+
[
79+
new PolicyRequirement
80+
{
81+
Type = "Approval",
82+
Description = "Role approval",
83+
Data = new
84+
{
85+
ApproverRole = "security-approver",
86+
StepOrder = 1,
87+
Reason = "Role-based sign-off"
88+
}
89+
}
90+
],
91+
expectedStateVersion: "v10")
92+
with
93+
{
94+
RequestId = requestId
95+
};
96+
97+
/// <summary>
98+
/// Creates the intent used by approval workflow benchmark scenarios.
99+
/// </summary>
100+
public static MutationIntent CreateIntent()
101+
=> new()
102+
{
103+
OperationName = "ApproveGovernanceChange",
104+
Category = "Governance",
105+
Description = "Approve governance request in benchmark workflow"
106+
};
107+
108+
/// <summary>
109+
/// Creates a user context used for benchmark approval decisions.
110+
/// </summary>
111+
public static MutationContext CreateDecisionContext(string actorId, string reason)
112+
=> MutationContext.User(actorId, actorId, reason);
113+
114+
/// <summary>
115+
/// Creates structured rejection payload for benchmark scenarios.
116+
/// </summary>
117+
public static MutationApprovalRejectionReason CreateRejectionReason()
118+
=> new()
119+
{
120+
Code = "benchmark-rejection",
121+
Category = "policy",
122+
Message = "Benchmark rejection path"
123+
};
124+
125+
/// <summary>
126+
/// Creates a mutation context that carries approval roles for role-based resolution.
127+
/// </summary>
128+
public static MutationContext CreateRoleDecisionContext(
129+
string actorId,
130+
string actorName,
131+
string reason,
132+
params string[] roles)
133+
=> MutationContext.User(actorId, actorName, reason) with
134+
{
135+
Metadata = new Dictionary<string, object>
136+
{
137+
["ActorRoles"] = roles
138+
}
139+
};
140+
}

Benchmarks/ModularityKit.Mutator.Benchmarks.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
<ItemGroup>
1212
<ProjectReference Include="..\src\ModularityKit.Mutator.csproj" />
13+
<ProjectReference Include="..\src\ModularityKit.Mutator.Governance.csproj" />
1314
</ItemGroup>
1415

1516
<ItemGroup>

Benchmarks/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ This folder contains BenchmarkDotNet measurements for `ModularityKit.Mutator`.
1414
- interception, audit/history, and logging diagnostics overhead in the core runtime
1515
- parallel execution, state gate contention, and concurrent batch scheduling pressure in the core runtime
1616
- mutation result creation and history/audit output materialization in the core runtime
17+
- governance approval workflow overhead in the governance runtime
1718

1819
The throughput benchmarks use cloned array backed state so state size effects remain visible in the
1920
actual mutation path rather than being hidden behind an artificial inner loop.
@@ -63,6 +64,12 @@ Run the results suite:
6364
dotnet Benchmarks/bin/Release/net10.0/ModularityKit.Mutator.Benchmarks.dll --anyCategories Results
6465
```
6566

67+
Run the governance suite:
68+
69+
```bash
70+
dotnet Benchmarks/bin/Release/net10.0/ModularityKit.Mutator.Benchmarks.dll --anyCategories Governance
71+
```
72+
6673
Key parameters reported by BenchmarkDotNet:
6774

6875
- `StateSize` controls the size of the cloned mutation state

0 commit comments

Comments
 (0)