Skip to content

Commit f9a4dc0

Browse files
authored
Feat: add governance lifecycle benchmark suite (#80)
2 parents c78a464 + 99a8798 commit f9a4dc0

12 files changed

Lines changed: 602 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+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using BenchmarkDotNet.Attributes;
2+
using ModularityKit.Mutator.Governance.Abstractions.Requests.Model;
3+
using ModularityKit.Mutator.Benchmarks.Governance.Lifecycle.Support;
4+
5+
namespace ModularityKit.Mutator.Benchmarks.Governance.Lifecycle;
6+
7+
/// <summary>
8+
/// Benchmarks expiration sweeps for governed requests.
9+
/// </summary>
10+
[BenchmarkCategory("Governance")]
11+
[MemoryDiagnoser]
12+
[InProcess]
13+
public class RequestLifecycleExpirationBenchmarks : RequestLifecycleBenchmarkBase
14+
{
15+
/// <summary>
16+
/// Measures expiration of due requests in the pending lifecycle.
17+
/// </summary>
18+
[Benchmark(Baseline = true)]
19+
public async Task<IReadOnlyList<MutationRequest>> ExpireDueRequests_Sweep()
20+
{
21+
var manager = CreateExpirationWorkflow();
22+
23+
return await manager.ExpireDueRequests(
24+
DateTimeOffset.UtcNow,
25+
RequestLifecycleBenchmarkSupport.CreateSweepContext())
26+
.ConfigureAwait(false);
27+
}
28+
}

0 commit comments

Comments
 (0)