From 645bf7c059477ada8fc618e8994bef64f5d74a9a Mon Sep 17 00:00:00 2001 From: "Rian.be" Date: Sat, 4 Jul 2026 02:49:39 +0200 Subject: [PATCH 1/2] benchmarks: add governance query --- .../Queries/GovernanceQueryReadBenchmarks.cs | 63 +++++++++ .../GovernanceQueryReadBenchmarkSupport.cs | 26 ++++ .../PendingApprovalQueryBenchmarkSeed.cs | 117 +++++++++++++++ .../PendingRequestQueryBenchmarkSeed.cs | 104 ++++++++++++++ .../RecentDecisionQueryBenchmarkSeed.cs | 133 ++++++++++++++++++ Benchmarks/README.md | 1 + 6 files changed, 444 insertions(+) create mode 100644 Benchmarks/Governance/Queries/GovernanceQueryReadBenchmarks.cs create mode 100644 Benchmarks/Governance/Queries/Support/GovernanceQueryReadBenchmarkSupport.cs create mode 100644 Benchmarks/Governance/Queries/Support/PendingApprovalQueryBenchmarkSeed.cs create mode 100644 Benchmarks/Governance/Queries/Support/PendingRequestQueryBenchmarkSeed.cs create mode 100644 Benchmarks/Governance/Queries/Support/RecentDecisionQueryBenchmarkSeed.cs diff --git a/Benchmarks/Governance/Queries/GovernanceQueryReadBenchmarks.cs b/Benchmarks/Governance/Queries/GovernanceQueryReadBenchmarks.cs new file mode 100644 index 0000000..c0676f2 --- /dev/null +++ b/Benchmarks/Governance/Queries/GovernanceQueryReadBenchmarks.cs @@ -0,0 +1,63 @@ +using BenchmarkDotNet.Attributes; +using ModularityKit.Mutator.Governance.Abstractions.Queries.Model.Decisions; +using ModularityKit.Mutator.Governance.Abstractions.Queries.Model.Requests; +using ModularityKit.Mutator.Benchmarks.Governance.Queries.Support; + +namespace ModularityKit.Mutator.Benchmarks.Governance.Queries; + +/// +/// Benchmarks governance query and read paths in the governance runtime. +/// +[BenchmarkCategory("Governance")] +[MemoryDiagnoser] +[InProcess] +public class GovernanceQueryReadBenchmarks +{ + private GovernanceQueryReadBenchmarkSupport.GovernanceQueryReadBenchmarkFixture _fixture = null!; + + /// + /// Prepares a seeded query store for the read benchmarks. + /// + [GlobalSetup] + public void Setup() + { + _fixture = GovernanceQueryReadBenchmarkSupport.CreateFixture(); + } + + /// + /// Measures listing pending governance requests. + /// + [Benchmark(Baseline = true)] + public async Task GetPendingRequests() + { + var result = await _fixture.RequestStore.GetPendingRequestsAsync( + MutationRequestQueries.Pending()).ConfigureAwait(false); + + GC.KeepAlive(result); + } + + /// + /// Measures listing pending approval queue entries. + /// + [Benchmark] + public async Task GetPendingApprovalQueue() + { + var result = await _fixture.ApprovalStore.GetPendingApprovalQueueAsync( + MutationRequestQueries.PendingApprovalQueue()).ConfigureAwait(false); + + GC.KeepAlive(result); + } + + /// + /// Measures reading recent execution outcome decisions. + /// + [Benchmark] + public async Task GetRecentExecutionOutcomes() + { + var result = await _fixture.DecisionStore.GetRecentDecisionsAsync( + MutationRequestDecisionQuery.RecentExecutionOutcomes(), + take: 64).ConfigureAwait(false); + + GC.KeepAlive(result); + } +} diff --git a/Benchmarks/Governance/Queries/Support/GovernanceQueryReadBenchmarkSupport.cs b/Benchmarks/Governance/Queries/Support/GovernanceQueryReadBenchmarkSupport.cs new file mode 100644 index 0000000..a90234b --- /dev/null +++ b/Benchmarks/Governance/Queries/Support/GovernanceQueryReadBenchmarkSupport.cs @@ -0,0 +1,26 @@ +using ModularityKit.Mutator.Governance.Runtime.Storage; + +namespace ModularityKit.Mutator.Benchmarks.Governance.Queries.Support; + +/// +/// Builds repeatable governance query benchmark fixtures. +/// +internal static class GovernanceQueryReadBenchmarkSupport +{ + /// + /// Creates a seeded fixture for query and read benchmarks. + /// + public static GovernanceQueryReadBenchmarkFixture CreateFixture() + => new( + RequestStore: PendingRequestQueryBenchmarkSeed.CreateStore(), + ApprovalStore: PendingApprovalQueryBenchmarkSeed.CreateStore(), + DecisionStore: RecentDecisionQueryBenchmarkSeed.CreateStore()); + + /// + /// Shared fixture for governance query and read scenarios. + /// + internal sealed record GovernanceQueryReadBenchmarkFixture( + InMemoryMutationRequestStore RequestStore, + InMemoryMutationRequestStore ApprovalStore, + InMemoryMutationRequestStore DecisionStore); +} diff --git a/Benchmarks/Governance/Queries/Support/PendingApprovalQueryBenchmarkSeed.cs b/Benchmarks/Governance/Queries/Support/PendingApprovalQueryBenchmarkSeed.cs new file mode 100644 index 0000000..a8126cb --- /dev/null +++ b/Benchmarks/Governance/Queries/Support/PendingApprovalQueryBenchmarkSeed.cs @@ -0,0 +1,117 @@ +using ModularityKit.Mutator.Abstractions.Context; +using ModularityKit.Mutator.Abstractions.Intent; +using ModularityKit.Mutator.Abstractions.Policies; +using ModularityKit.Mutator.Governance.Abstractions.Approval.Model; +using ModularityKit.Mutator.Governance.Abstractions.Lifecycle.Model; +using ModularityKit.Mutator.Governance.Abstractions.Requests.Factory; +using ModularityKit.Mutator.Governance.Abstractions.Requests.Model; +using ModularityKit.Mutator.Governance.Runtime.Storage; + +namespace ModularityKit.Mutator.Benchmarks.Governance.Queries.Support; + +/// +/// Seeds pending approval queue scenarios. +/// +internal static class PendingApprovalQueryBenchmarkSeed +{ + private static readonly DateTimeOffset BaseTime = new(2026, 6, 1, 14, 0, 0, TimeSpan.Zero); + + /// + /// Creates a store preloaded with pending approval queue and noise data. + /// + public static InMemoryMutationRequestStore CreateStore() + { + var store = new InMemoryMutationRequestStore(); + + for (var index = 0; index < 64; index++) + { + var approverId = index % 2 == 0 ? "security-lead" : "platform-lead"; + var category = index % 2 == 0 ? "Security" : "Platform"; + + var request = MutationRequestFactory.PendingApproval( + stateId: "governance-benchmark:query-approval", + stateType: "GovernanceState", + mutationType: "GovernanceBenchmarkApprovalMutation", + intent: CreateIntent(), + context: MutationContext.User("requester", "Requester", "List pending approvals"), + requirements: + [ + PolicyRequirement.Approval(approverId, "Benchmark approval") + ], + expectedStateVersion: "v10") + with + { + RequestId = $"approval-request-{index:000}", + Lifecycle = new MutationRequestLifecycleDetails + { + Status = MutationRequestStatus.Pending, + PendingReason = PendingMutationReason.Approval, + CreatedAt = BaseTime.AddMinutes(index), + UpdatedAt = BaseTime.AddMinutes(index) + }, + ApprovalRequirements = + [ + new MutationApprovalRequirement + { + ApproverId = approverId, + ApproverRole = category == "Security" ? "SecurityLead" : "PlatformLead", + ApproverGroup = category.ToLowerInvariant(), + Status = MutationApprovalRequirementStatus.Pending, + StepOrder = 1 + } + ] + }; + + store.Create(request).GetAwaiter().GetResult(); + } + + for (var index = 0; index < 48; index++) + { + var request = new MutationRequest + { + RequestId = $"approval-noise-{index:000}", + Scope = new MutationRequestScopeDetails + { + StateId = "governance-benchmark:query-approval", + StateType = "GovernanceState", + MutationType = "GovernanceBenchmarkCompletedMutation" + }, + Payload = new MutationRequestPayloadDetails + { + Intent = CreateIntent("CompletedApprovalNoise", "Completed governance approval noise"), + Context = MutationContext.User("requester", "Requester", "Completed request") + }, + Lifecycle = new MutationRequestLifecycleDetails + { + Status = MutationRequestStatus.Approved, + CreatedAt = BaseTime.AddHours(1).AddMinutes(index), + UpdatedAt = BaseTime.AddHours(1).AddMinutes(index + 1) + } + }; + + store.Create(request).GetAwaiter().GetResult(); + } + + return store; + } + + private static MutationIntent CreateIntent() + => new() + { + OperationName = "ListPendingApprovals", + Category = "Governance", + Description = "List pending approval queue entries", + RiskLevel = MutationRiskLevel.Low, + IsReversible = true + }; + + private static MutationIntent CreateIntent(string operationName, string description) + => new() + { + OperationName = operationName, + Category = "Governance", + Description = description, + RiskLevel = MutationRiskLevel.Low, + IsReversible = true + }; +} diff --git a/Benchmarks/Governance/Queries/Support/PendingRequestQueryBenchmarkSeed.cs b/Benchmarks/Governance/Queries/Support/PendingRequestQueryBenchmarkSeed.cs new file mode 100644 index 0000000..ea3d60e --- /dev/null +++ b/Benchmarks/Governance/Queries/Support/PendingRequestQueryBenchmarkSeed.cs @@ -0,0 +1,104 @@ +using ModularityKit.Mutator.Abstractions.Context; +using ModularityKit.Mutator.Abstractions.Intent; +using ModularityKit.Mutator.Abstractions.Policies; +using ModularityKit.Mutator.Governance.Abstractions.Lifecycle.Model; +using ModularityKit.Mutator.Governance.Abstractions.Requests.Factory; +using ModularityKit.Mutator.Governance.Abstractions.Requests.Model; +using ModularityKit.Mutator.Governance.Runtime.Storage; + +namespace ModularityKit.Mutator.Benchmarks.Governance.Queries.Support; + +/// +/// Seeds pending request read scenarios. +/// +internal static class PendingRequestQueryBenchmarkSeed +{ + private static readonly DateTimeOffset BaseTime = new(2026, 6, 1, 12, 0, 0, TimeSpan.Zero); + + /// + /// Creates a store preloaded with pending request and noise data. + /// + public static InMemoryMutationRequestStore CreateStore() + { + var store = new InMemoryMutationRequestStore(); + + for (var index = 0; index < 96; index++) + { + var request = MutationRequestFactory.Pending( + stateId: "governance-benchmark:query-pending", + stateType: "GovernanceState", + mutationType: "GovernanceBenchmarkMutation", + intent: CreateIntent(), + context: MutationContext.User("requester", "Requester", "List pending governance requests"), + pendingReason: index % 2 == 0 + ? PendingMutationReason.Approval + : PendingMutationReason.ExternalCheck) + with + { + RequestId = $"pending-request-{index:000}", + Lifecycle = new MutationRequestLifecycleDetails + { + Status = MutationRequestStatus.Pending, + PendingReason = index % 2 == 0 + ? PendingMutationReason.Approval + : PendingMutationReason.ExternalCheck, + CreatedAt = BaseTime.AddMinutes(index), + UpdatedAt = BaseTime.AddMinutes(index) + } + }; + + store.Create(request).GetAwaiter().GetResult(); + } + + for (var index = 0; index < 48; index++) + { + var request = new MutationRequest + { + RequestId = $"completed-request-{index:000}", + Scope = new MutationRequestScopeDetails + { + StateId = "governance-benchmark:query-pending", + StateType = "GovernanceState", + MutationType = "GovernanceBenchmarkCompletedMutation" + }, + Payload = new MutationRequestPayloadDetails + { + Intent = CreateIntent("CompletedRequest", "Completed governance request"), + Context = MutationContext.User("requester", "Requester", "Completed request") + }, + Lifecycle = new MutationRequestLifecycleDetails + { + Status = index % 3 == 0 + ? MutationRequestStatus.Approved + : MutationRequestStatus.Executed, + CreatedAt = BaseTime.AddHours(1).AddMinutes(index), + UpdatedAt = BaseTime.AddHours(1).AddMinutes(index + 1) + } + }; + + store.Create(request).GetAwaiter().GetResult(); + } + + return store; + } + + private static MutationIntent CreateIntent() + => new() + { + OperationName = "ListPendingRequests", + Category = "Governance", + Description = "List pending governance requests", + RiskLevel = MutationRiskLevel.Low, + IsReversible = true + }; + + private static MutationIntent CreateIntent(string operationName, string description) + => new() + { + OperationName = operationName, + Category = "Governance", + Description = description, + RiskLevel = MutationRiskLevel.Low, + IsReversible = true + }; +} diff --git a/Benchmarks/Governance/Queries/Support/RecentDecisionQueryBenchmarkSeed.cs b/Benchmarks/Governance/Queries/Support/RecentDecisionQueryBenchmarkSeed.cs new file mode 100644 index 0000000..c2aedf2 --- /dev/null +++ b/Benchmarks/Governance/Queries/Support/RecentDecisionQueryBenchmarkSeed.cs @@ -0,0 +1,133 @@ +using ModularityKit.Mutator.Abstractions.Context; +using ModularityKit.Mutator.Abstractions.Intent; +using ModularityKit.Mutator.Abstractions.Policies; +using ModularityKit.Mutator.Governance.Abstractions.Lifecycle.Model; +using ModularityKit.Mutator.Governance.Abstractions.Requests.Decisions; +using ModularityKit.Mutator.Governance.Abstractions.Requests.Model; +using ModularityKit.Mutator.Governance.Runtime.Storage; + +namespace ModularityKit.Mutator.Benchmarks.Governance.Queries.Support; + +/// +/// Seeds recent decision read scenarios. +/// +internal static class RecentDecisionQueryBenchmarkSeed +{ + private static readonly DateTimeOffset BaseTime = new(2026, 6, 1, 16, 0, 0, TimeSpan.Zero); + + /// + /// Creates a store preloaded with recent decision and noise data. + /// + public static InMemoryMutationRequestStore CreateStore() + { + var store = new InMemoryMutationRequestStore(); + + for (var index = 0; index < 72; index++) + { + var request = CreateDecisionRequest( + requestId: $"decision-request-{index:000}", + stateId: "governance-benchmark:query-decision", + decisionType: (index % 4) switch + { + 0 => MutationRequestLifecycleDecisionType.Executed, + 1 => MutationRequestLifecycleDecisionType.Rejected, + 2 => MutationRequestLifecycleDecisionType.Canceled, + _ => MutationRequestLifecycleDecisionType.Expired + }, + createdAt: BaseTime.AddMinutes(index), + decisionOffsetMinutes: index); + + store.Create(request).GetAwaiter().GetResult(); + } + + for (var index = 0; index < 32; index++) + { + var request = CreateDecisionRequest( + requestId: $"decision-noise-{index:000}", + stateId: "governance-benchmark:query-decision", + decisionType: MutationRequestLifecycleDecisionType.Submitted, + createdAt: BaseTime.AddHours(1).AddMinutes(index), + decisionOffsetMinutes: index, + includeExecutionOutcome: false); + + store.Create(request).GetAwaiter().GetResult(); + } + + return store; + } + + private static MutationRequest CreateDecisionRequest( + string requestId, + string stateId, + MutationRequestLifecycleDecisionType decisionType, + DateTimeOffset createdAt, + int decisionOffsetMinutes, + bool includeExecutionOutcome = true) + { + var decisions = new List + { + MutationRequestDecision.Create( + MutationRequestDecisionType.Lifecycle(MutationRequestLifecycleDecisionType.Submitted), + MutationContext.User("requester", "Requester", "Submitted")) + with + { + Timestamp = createdAt + } + }; + + if (includeExecutionOutcome) + { + decisions.Add( + MutationRequestDecision.Create( + MutationRequestDecisionType.Lifecycle(MutationRequestLifecycleDecisionType.Pending), + MutationContext.User("requester", "Requester", "Pending")) + with + { + Timestamp = createdAt.AddMinutes(1) + }); + + decisions.Add( + MutationRequestDecision.Create( + MutationRequestDecisionType.Lifecycle(decisionType), + MutationContext.System("Execution outcome")) + with + { + Timestamp = createdAt.AddMinutes(2 + decisionOffsetMinutes) + }); + } + + return new MutationRequest + { + RequestId = requestId, + Scope = new MutationRequestScopeDetails + { + StateId = stateId, + StateType = "GovernanceState", + MutationType = "GovernanceBenchmarkDecisionMutation" + }, + Payload = new MutationRequestPayloadDetails + { + Intent = CreateIntent("RecentExecutionOutcomes", "Recent governance execution outcomes"), + Context = MutationContext.User("requester", "Requester", "Read recent outcomes") + }, + Lifecycle = new MutationRequestLifecycleDetails + { + Status = includeExecutionOutcome ? MutationRequestStatus.Executed : MutationRequestStatus.Pending, + PendingReason = includeExecutionOutcome ? null : PendingMutationReason.ExternalCheck, + CreatedAt = createdAt, + UpdatedAt = createdAt.AddMinutes(includeExecutionOutcome ? 3 + decisionOffsetMinutes : 1) + }, + Decisions = decisions + }; + } + + private static MutationIntent CreateIntent(string operationName, string description) + => new() + { + OperationName = operationName, + Category = "Governance", + Description = description, + RiskLevel = MutationRiskLevel.Low, + IsReversible = true + }; +} diff --git a/Benchmarks/README.md b/Benchmarks/README.md index c8740b9..4a38abf 100644 --- a/Benchmarks/README.md +++ b/Benchmarks/README.md @@ -18,6 +18,7 @@ This folder contains BenchmarkDotNet measurements for `ModularityKit.Mutator`. - governance request lifecycle overhead in the governance runtime - governance execution orchestration overhead in the governance runtime - governance version resolution overhead in the governance runtime +- governance query and read performance overhead in the governance runtime The throughput benchmarks use cloned array backed state so state size effects remain visible in the actual mutation path rather than being hidden behind an artificial inner loop. From 1fe5584e856a9c325c702f77afe4b98b7928941c Mon Sep 17 00:00:00 2001 From: "Rian.be" Date: Sat, 4 Jul 2026 03:13:18 +0200 Subject: [PATCH 2/2] benchmarks: add governance materialization support --- ...vernanceMaterializationOutputBenchmarks.cs | 91 +++++++++++++++++++ ...vernanceMaterializationBenchmarkSupport.cs | 68 ++++++++++++++ ...overnanceMaterializationScenarioFactory.cs | 22 +++++ ...GovernanceMaterializationSideEffectData.cs | 11 +++ .../Support/GovernanceMaterializationState.cs | 11 +++ .../MaterializeGovernanceOutputMutation.cs | 79 ++++++++++++++++ Benchmarks/README.md | 1 + 7 files changed, 283 insertions(+) create mode 100644 Benchmarks/Governance/Materialization/GovernanceMaterializationOutputBenchmarks.cs create mode 100644 Benchmarks/Governance/Materialization/Support/GovernanceMaterializationBenchmarkSupport.cs create mode 100644 Benchmarks/Governance/Materialization/Support/GovernanceMaterializationScenarioFactory.cs create mode 100644 Benchmarks/Governance/Materialization/Support/GovernanceMaterializationSideEffectData.cs create mode 100644 Benchmarks/Governance/Materialization/Support/GovernanceMaterializationState.cs create mode 100644 Benchmarks/Governance/Materialization/Support/MaterializeGovernanceOutputMutation.cs diff --git a/Benchmarks/Governance/Materialization/GovernanceMaterializationOutputBenchmarks.cs b/Benchmarks/Governance/Materialization/GovernanceMaterializationOutputBenchmarks.cs new file mode 100644 index 0000000..7ebe195 --- /dev/null +++ b/Benchmarks/Governance/Materialization/GovernanceMaterializationOutputBenchmarks.cs @@ -0,0 +1,91 @@ +using BenchmarkDotNet.Attributes; +using ModularityKit.Mutator.Abstractions.Audit; +using ModularityKit.Mutator.Abstractions.History; +using ModularityKit.Mutator.Governance.Abstractions.Queries.Model.Decisions; +using ModularityKit.Mutator.Benchmarks.Governance.Materialization.Support; + +namespace ModularityKit.Mutator.Benchmarks.Governance.Materialization; + +/// +/// Benchmarks governance output materialization paths in the governance runtime. +/// +[BenchmarkCategory("Governance")] +[MemoryDiagnoser] +[InProcess] +public class GovernanceMaterializationOutputBenchmarks +{ + private GovernanceMaterializationBenchmarkSupport.GovernanceMaterializationBenchmarkFixture _fixture = null!; + + /// + /// Prepares a representative governed execution result for output materialization benchmarks. + /// + [GlobalSetup] + public void Setup() + { + _fixture = GovernanceMaterializationBenchmarkSupport.CreateFixture(); + } + + /// + /// Measures materialization of the history entry, including copied side effects from governed execution. + /// + [Benchmark(Baseline = true)] + public MutationHistoryEntry HistoryEntry_FromGovernedExecution() + { + return new MutationHistoryEntry + { + ExecutionId = _fixture.Result.Request.RequestId, + StateId = _fixture.Result.Request.StateId, + Intent = _fixture.Mutation.Intent, + Context = _fixture.Mutation.Context, + Changes = _fixture.Result.MutationResult!.Changes, + SideEffects = _fixture.Result.Request.SideEffects.ToList(), + Timestamp = _fixture.Result.Request.Versioning.ExecutedAt ?? DateTimeOffset.UtcNow, + ExecutionTime = TimeSpan.FromMilliseconds(2) + }; + } + + /// + /// Measures materialization of the audit entry produced from the same governed execution output. + /// + [Benchmark] + public MutationAuditEntry AuditEntry_FromGovernedExecution() + { + return new MutationAuditEntry + { + ExecutionId = _fixture.Result.Request.RequestId, + StateId = _fixture.Result.Request.StateId, + StateType = _fixture.Result.Request.StateType, + MutationIntent = _fixture.Mutation.Intent, + Context = _fixture.Mutation.Context, + Changes = _fixture.Result.MutationResult!.Changes, + IsSuccess = _fixture.Result.MutationResult.IsSuccess, + ErrorMessage = null, + PolicyDecisions = _fixture.Result.MutationResult.PolicyDecisions, + SideEffects = _fixture.Result.Request.SideEffects.ToList(), + Timestamp = _fixture.Result.Request.Versioning.ExecutedAt ?? DateTimeOffset.UtcNow, + Duration = TimeSpan.FromMilliseconds(2), + Metadata = new Dictionary + { + ["ExecutionKind"] = _fixture.Result.ExecutionKind.ToString(), + ["RequestStatus"] = _fixture.Result.Request.Status.ToString() + } + }; + } + + /// + /// Measures request decision materialization for downstream consumers. + /// + [Benchmark] + public IReadOnlyList DecisionViews_FromGovernedRequest() + { + var request = _fixture.Result.Request; + var views = request.Decisions.Select(decision => new MutationRequestDecisionView + { + Request = request, + Decision = decision + }).ToList(); + + GC.KeepAlive(views); + return views; + } +} diff --git a/Benchmarks/Governance/Materialization/Support/GovernanceMaterializationBenchmarkSupport.cs b/Benchmarks/Governance/Materialization/Support/GovernanceMaterializationBenchmarkSupport.cs new file mode 100644 index 0000000..c457e73 --- /dev/null +++ b/Benchmarks/Governance/Materialization/Support/GovernanceMaterializationBenchmarkSupport.cs @@ -0,0 +1,68 @@ +using ModularityKit.Mutator.Abstractions; +using ModularityKit.Mutator.Abstractions.Context; +using ModularityKit.Mutator.Abstractions.Engine; +using ModularityKit.Mutator.Benchmarks.Engine; +using ModularityKit.Mutator.Governance.Abstractions.Execution.Model; +using ModularityKit.Mutator.Governance.Abstractions.Requests.Factory; +using ModularityKit.Mutator.Governance.Abstractions.Requests.Model; +using ModularityKit.Mutator.Governance.Abstractions.Resolution.Strategies; +using ModularityKit.Mutator.Governance.Runtime.Execution.Orchestration; +using ModularityKit.Mutator.Governance.Runtime.Resolution.Execution; +using ModularityKit.Mutator.Governance.Runtime.Storage; + +namespace ModularityKit.Mutator.Benchmarks.Governance.Materialization.Support; + +/// +/// Builds repeatable governance materialization benchmark fixtures. +/// +internal static class GovernanceMaterializationBenchmarkSupport +{ + public const string StateId = "governance-benchmark:materialization"; + + /// + /// Creates a fresh execution result fixture for a single scenario run. + /// + public static GovernanceMaterializationBenchmarkFixture CreateFixture() + { + var engine = MutationEngineBenchmarkSupport.BuildEngine(MutationEngineOptions.Strict); + var store = new InMemoryMutationRequestStore(); + var resolutionManager = new MutationRequestVersionResolutionManager(store, new MutationRequestVersionResolver()); + var executionManager = new GovernanceExecutionManager(store, resolutionManager, engine); + var request = store.Create(CreateApprovedRequest("governance-materialization-request")) + .GetAwaiter() + .GetResult(); + var mutation = new MaterializeGovernanceOutputMutation( + MutationContext.User("operator", "Operator", "Execute governance materialization benchmark"), + nextVersion: "v11"); + + var result = executionManager.ExecuteApproved( + request.RequestId, + mutation, + new GovernanceMaterializationState(StateId, 10, "v10"), + governanceContext: mutation.Context, + strategy: VersionedRequestResolutionStrategy.RejectStale) + .GetAwaiter() + .GetResult(); + + return new GovernanceMaterializationBenchmarkFixture(result, mutation); + } + + private static MutationRequest CreateApprovedRequest(string requestId) + where TMutation : IMutation + => MutationRequestFactory.Approved( + stateId: StateId, + intent: GovernanceMaterializationScenarioFactory.CreateIntent(), + context: MutationContext.User("requester", "Requester", "Need governance output materialization"), + expectedStateVersion: "v10") + with + { + RequestId = requestId + }; + + /// + /// Shared execution result fixture for a governance materialization scenario. + /// + internal sealed record GovernanceMaterializationBenchmarkFixture( + GovernedExecutionResult Result, + MaterializeGovernanceOutputMutation Mutation); +} diff --git a/Benchmarks/Governance/Materialization/Support/GovernanceMaterializationScenarioFactory.cs b/Benchmarks/Governance/Materialization/Support/GovernanceMaterializationScenarioFactory.cs new file mode 100644 index 0000000..6163566 --- /dev/null +++ b/Benchmarks/Governance/Materialization/Support/GovernanceMaterializationScenarioFactory.cs @@ -0,0 +1,22 @@ +using ModularityKit.Mutator.Abstractions.Intent; + +namespace ModularityKit.Mutator.Benchmarks.Governance.Materialization.Support; + +/// +/// Creates governance materialization benchmark intent metadata. +/// +internal static class GovernanceMaterializationScenarioFactory +{ + /// + /// Creates the intent used by governance materialization scenarios. + /// + public static MutationIntent CreateIntent() + => new() + { + OperationName = "MaterializeGovernanceOutput", + Category = "Governance", + Description = "Materialize governance output for audit, history, and downstream consumers", + RiskLevel = MutationRiskLevel.Low, + IsReversible = true + }; +} diff --git a/Benchmarks/Governance/Materialization/Support/GovernanceMaterializationSideEffectData.cs b/Benchmarks/Governance/Materialization/Support/GovernanceMaterializationSideEffectData.cs new file mode 100644 index 0000000..a430dcb --- /dev/null +++ b/Benchmarks/Governance/Materialization/Support/GovernanceMaterializationSideEffectData.cs @@ -0,0 +1,11 @@ +using ModularityKit.Mutator.Abstractions.Effects; + +namespace ModularityKit.Mutator.Benchmarks.Governance.Materialization.Support; + +/// +/// Typed payload used to give governance side effects realistic materialization shape. +/// +/// A stable payload token. +/// The ordinal of the side effect. +[SideEffectDataContract("governance.materialization.side-effect", 1)] +internal sealed record GovernanceMaterializationSideEffectData(string Token, int Index); diff --git a/Benchmarks/Governance/Materialization/Support/GovernanceMaterializationState.cs b/Benchmarks/Governance/Materialization/Support/GovernanceMaterializationState.cs new file mode 100644 index 0000000..a968825 --- /dev/null +++ b/Benchmarks/Governance/Materialization/Support/GovernanceMaterializationState.cs @@ -0,0 +1,11 @@ +using ModularityKit.Mutator.Governance.Abstractions.Execution.Contracts; + +namespace ModularityKit.Mutator.Benchmarks.Governance.Materialization.Support; + +/// +/// Minimal versioned state used by governance materialization benchmarks. +/// +/// Stable state identifier. +/// Benchmark counter value. +/// Current state version. +internal sealed record GovernanceMaterializationState(string StateId, int Value, string Version) : IVersionedState; diff --git a/Benchmarks/Governance/Materialization/Support/MaterializeGovernanceOutputMutation.cs b/Benchmarks/Governance/Materialization/Support/MaterializeGovernanceOutputMutation.cs new file mode 100644 index 0000000..02c0926 --- /dev/null +++ b/Benchmarks/Governance/Materialization/Support/MaterializeGovernanceOutputMutation.cs @@ -0,0 +1,79 @@ +using ModularityKit.Mutator.Abstractions.Changes; +using ModularityKit.Mutator.Abstractions.Context; +using ModularityKit.Mutator.Abstractions.Effects; +using ModularityKit.Mutator.Abstractions.Engine; +using ModularityKit.Mutator.Abstractions.Intent; +using ModularityKit.Mutator.Abstractions.Results; + +namespace ModularityKit.Mutator.Benchmarks.Governance.Materialization.Support; + +/// +/// Minimal mutation used to produce executed governance output with side effects. +/// +internal sealed class MaterializeGovernanceOutputMutation(MutationContext context, string nextVersion) + : IMutation +{ + /// + /// Gets the intent associated with the materialization mutation. + /// + public MutationIntent Intent { get; } = new() + { + OperationName = "MaterializeGovernanceOutputMutation", + Category = "Governance", + Description = "Produce governed execution output for materialization benchmarks", + RiskLevel = MutationRiskLevel.Low, + IsReversible = true + }; + + /// + /// Gets the invocation context for the mutation. + /// + public MutationContext Context { get; } = context; + + /// + /// Applies the mutation and emits governance side effects. + /// + public MutationResult Apply(GovernanceMaterializationState state) + { + var next = state with + { + Value = state.Value + 1, + Version = nextVersion + }; + + return MutationResult.Success( + next, + ChangeSet.FromChanges( + StateChange.Modified(nameof(GovernanceMaterializationState.Value), state.Value, next.Value), + StateChange.Modified(nameof(GovernanceMaterializationState.Version), state.Version, next.Version)), + CreateSideEffects()); + } + + /// + /// Validates the provided state before mutation execution. + /// + public ValidationResult Validate(GovernanceMaterializationState state) + { + return state.Value < 0 + ? ValidationResult.WithError(nameof(GovernanceMaterializationState.Value), "Value must be non-negative.") + : ValidationResult.Success(); + } + + /// + /// Simulates the mutation using the same state transition as execution. + /// + public MutationResult Simulate(GovernanceMaterializationState state) => Apply(state); + + private static IReadOnlyList CreateSideEffects() + => [ + SideEffect.Create( + "GovernanceOutputMaterialized", + "Governed execution output materialized for history and audit consumers", + new GovernanceMaterializationSideEffectData("history-audit", 1), + SideEffectSeverity.Info), + SideEffect.Critical( + "GovernanceOutputLinked", + "Governed execution output linked for downstream request consumers", + new GovernanceMaterializationSideEffectData("request-link", 2)) + ]; +} diff --git a/Benchmarks/README.md b/Benchmarks/README.md index 4a38abf..7d0f70b 100644 --- a/Benchmarks/README.md +++ b/Benchmarks/README.md @@ -19,6 +19,7 @@ This folder contains BenchmarkDotNet measurements for `ModularityKit.Mutator`. - governance execution orchestration overhead in the governance runtime - governance version resolution overhead in the governance runtime - governance query and read performance overhead in the governance runtime +- governance side effect and decision materialization overhead in the governance runtime The throughput benchmarks use cloned array backed state so state size effects remain visible in the actual mutation path rather than being hidden behind an artificial inner loop.