Skip to content

Commit e33ec7f

Browse files
committed
Refine governance execution runtime
1 parent 32e6468 commit e33ec7f

5 files changed

Lines changed: 343 additions & 22 deletions

File tree

‎src/Governance/Runtime/Execution/Mutation/GovernedMutation.cs‎

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using ModularityKit.Mutator.Abstractions.Engine;
33
using ModularityKit.Mutator.Abstractions.Intent;
44
using ModularityKit.Mutator.Abstractions.Results;
5+
using ModularityKit.Mutator.Governance.Abstractions.Execution.Model.Compensation;
56
using ModularityKit.Mutator.Governance.Abstractions.Requests.Model;
67

78
namespace ModularityKit.Mutator.Governance.Runtime.Execution.Mutation;
@@ -15,6 +16,8 @@ internal sealed class GovernedMutation<TState> : IMutation<TState>
1516
private const string GovernanceRequestMetadataKey = "GovernanceRequestMetadata";
1617
private const string GovernanceIntentMetadataKey = "GovernanceIntentMetadata";
1718
private const string GovernanceEstimatedBlastRadiusMetadataKey = "GovernanceEstimatedBlastRadius";
19+
private const string GovernanceExecutionKindMetadataKey = "GovernanceExecutionKind";
20+
private const string GovernanceCompensationMetadataKey = "GovernanceCompensation";
1821

1922
private readonly IMutation<TState> _inner;
2023

@@ -58,7 +61,8 @@ private IReadOnlyDictionary<string, object> MergeContextMetadata(MutationRequest
5861
{
5962
var metadata = new Dictionary<string, object>(_inner.Context.Metadata)
6063
{
61-
[GovernanceRequestIdMetadataKey] = request.RequestId
64+
[GovernanceRequestIdMetadataKey] = request.RequestId,
65+
[GovernanceExecutionKindMetadataKey] = request.Execution.Kind.ToString()
6266
};
6367

6468
if (request.Metadata.Count > 0)
@@ -70,19 +74,48 @@ private IReadOnlyDictionary<string, object> MergeContextMetadata(MutationRequest
7074
if (request.Intent.EstimatedBlastRadius is not null)
7175
metadata[GovernanceEstimatedBlastRadiusMetadataKey] = request.Intent.EstimatedBlastRadius;
7276

77+
if (request.Execution.Compensation is not null)
78+
metadata[GovernanceCompensationMetadataKey] = CreateCompensationMetadata(request.Execution.Compensation);
79+
7380
return metadata;
7481
}
7582

7683
private IReadOnlyDictionary<string, object> MergeIntentMetadata(MutationRequest request)
7784
{
7885
var metadata = new Dictionary<string, object>(request.Intent.Metadata)
7986
{
80-
[GovernanceRequestIdMetadataKey] = request.RequestId
87+
[GovernanceRequestIdMetadataKey] = request.RequestId,
88+
[GovernanceExecutionKindMetadataKey] = request.Execution.Kind.ToString()
8189
};
8290

8391
if (_inner.Intent.Metadata.Count > 0)
8492
metadata["ExecutionIntentMetadata"] = _inner.Intent.Metadata;
8593

94+
if (request.Execution.Compensation is not null)
95+
metadata[GovernanceCompensationMetadataKey] = CreateCompensationMetadata(request.Execution.Compensation);
96+
97+
return metadata;
98+
}
99+
100+
private static IReadOnlyDictionary<string, object> CreateCompensationMetadata(
101+
GovernedCompensationPlan compensation)
102+
{
103+
var metadata = new Dictionary<string, object>
104+
{
105+
["OriginalRequestId"] = compensation.OriginalRequestId,
106+
["Kind"] = compensation.Kind.ToString(),
107+
["Trigger"] = compensation.Trigger.ToString()
108+
};
109+
110+
if (!string.IsNullOrWhiteSpace(compensation.BatchId))
111+
metadata["BatchId"] = compensation.BatchId;
112+
113+
if (compensation.RelatedRequestIds.Count > 0)
114+
metadata["RelatedRequestIds"] = compensation.RelatedRequestIds;
115+
116+
if (!string.IsNullOrWhiteSpace(compensation.Reason))
117+
metadata["Reason"] = compensation.Reason;
118+
86119
return metadata;
87120
}
88121

‎src/Governance/Runtime/Execution/Orchestration/GovernanceExecutionManager.cs‎

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
using ModularityKit.Mutator.Abstractions.Results;
44
using ModularityKit.Mutator.Governance.Abstractions.Execution.Contracts;
55
using ModularityKit.Mutator.Governance.Abstractions.Execution.Model;
6+
using ModularityKit.Mutator.Governance.Abstractions.Execution.Model.Links;
7+
using ModularityKit.Mutator.Governance.Abstractions.Exceptions.Storage;
8+
using ModularityKit.Mutator.Governance.Abstractions.Requests.Model;
69
using ModularityKit.Mutator.Governance.Abstractions.Resolution.Contracts;
710
using ModularityKit.Mutator.Governance.Abstractions.Resolution.Model;
811
using ModularityKit.Mutator.Governance.Abstractions.Resolution.Strategies;
@@ -21,11 +24,25 @@ public sealed class GovernanceExecutionManager(
2124
IMutationRequestVersionResolutionManager resolutionManager,
2225
IMutationEngine mutationEngine) : IGovernanceExecutionManager
2326
{
27+
private readonly IMutationRequestStore _requestStore = requestStore ?? throw new ArgumentNullException(nameof(requestStore));
2428
private readonly IMutationRequestVersionResolutionManager _resolutionManager = resolutionManager ?? throw new ArgumentNullException(nameof(resolutionManager));
2529
private readonly IMutationEngine _mutationEngine = mutationEngine ?? throw new ArgumentNullException(nameof(mutationEngine));
2630
private readonly GovernedExecutionOutcomeHandler _outcomeHandler =
2731
new(new GovernedExecutionRequestPersistence(requestStore ?? throw new ArgumentNullException(nameof(requestStore))));
2832

33+
/// <summary>
34+
/// Executes approved governed mutation request against provided state snapshot.
35+
/// </summary>
36+
/// <typeparam name="TState">The state type handled by the governed mutation.</typeparam>
37+
/// <param name="requestId">Stable identifier of the governed mutation request.</param>
38+
/// <param name="mutation">Mutation instance to execute after governance resolution succeeds.</param>
39+
/// <param name="currentState">Current state snapshot used for execution.</param>
40+
/// <param name="currentStateVersion">Current version or concurrency token of the state snapshot.</param>
41+
/// <param name="resultingStateVersionProvider">Delegate that resolves the resulting state version from the post-mutation state.</param>
42+
/// <param name="governanceContext">Context describing the governance actor or service performing execution.</param>
43+
/// <param name="strategy">Version-resolution strategy applied before execution.</param>
44+
/// <param name="cancellationToken">Cancellation token.</param>
45+
/// <returns>The governed execution result, including persisted request state and optional mutation outcome.</returns>
2946
public async Task<GovernedExecutionResult<TState>> ExecuteApproved<TState>(
3047
string requestId,
3148
IMutation<TState> mutation,
@@ -74,11 +91,27 @@ public async Task<GovernedExecutionResult<TState>> ExecuteApproved<TState>(
7491
throw;
7592
}
7693

77-
return await _outcomeHandler
94+
var result = await _outcomeHandler
7895
.HandleMutationResult(execution, mutationResult, cancellationToken)
7996
.ConfigureAwait(false);
97+
98+
if (result.WasExecuted)
99+
await LinkCompensationExecution(result.Request, governanceContext, cancellationToken).ConfigureAwait(false);
100+
101+
return result;
80102
}
81103

104+
/// <summary>
105+
/// Executes approved governed mutation request against versioned state snapshot.
106+
/// </summary>
107+
/// <typeparam name="TState">The versioned state type handled by the governed mutation.</typeparam>
108+
/// <param name="requestId">Stable identifier of the governed mutation request.</param>
109+
/// <param name="mutation">Mutation instance to execute after governance resolution succeeds.</param>
110+
/// <param name="currentState">Current versioned state snapshot used for execution.</param>
111+
/// <param name="governanceContext">Context describing the governance actor or service performing execution.</param>
112+
/// <param name="strategy">Version-resolution strategy applied before execution.</param>
113+
/// <param name="cancellationToken">Cancellation token.</param>
114+
/// <returns>The governed execution result, including persisted request state and optional mutation outcome.</returns>
82115
public Task<GovernedExecutionResult<TState>> ExecuteApproved<TState>(
83116
string requestId,
84117
IMutation<TState> mutation,
@@ -97,6 +130,19 @@ public Task<GovernedExecutionResult<TState>> ExecuteApproved<TState>(
97130
strategy,
98131
cancellationToken);
99132

133+
/// <summary>
134+
/// Resolves the governed request and builds execution context for the core mutation engine.
135+
/// </summary>
136+
/// <typeparam name="TState">The state type handled by the governed mutation.</typeparam>
137+
/// <param name="requestId">Stable identifier of the governed mutation request.</param>
138+
/// <param name="mutation">Mutation instance to wrap for governed execution.</param>
139+
/// <param name="currentState">Current state snapshot used for execution.</param>
140+
/// <param name="currentStateVersion">Current version or concurrency token of the state snapshot.</param>
141+
/// <param name="resultingStateVersionProvider">Delegate that resolves the resulting state version from the post-mutation state.</param>
142+
/// <param name="governanceContext">Context describing the governance actor or service performing execution.</param>
143+
/// <param name="strategy">Version-resolution strategy applied before execution.</param>
144+
/// <param name="cancellationToken">Cancellation token.</param>
145+
/// <returns>Resolved execution context passed to the core mutation engine.</returns>
100146
private async Task<GovernedExecutionContext<TState>> ResolveExecutionContext<TState>(
101147
string requestId,
102148
IMutation<TState> mutation,
@@ -123,11 +169,80 @@ private async Task<GovernedExecutionContext<TState>> ResolveExecutionContext<TSt
123169
governanceContext);
124170
}
125171

172+
/// <summary>
173+
/// Executes the wrapped governed mutation through the core mutation engine.
174+
/// </summary>
175+
/// <typeparam name="TState">The state type handled by the governed mutation.</typeparam>
176+
/// <param name="execution">Resolved governed execution context.</param>
177+
/// <param name="cancellationToken">Cancellation token.</param>
178+
/// <returns>The core mutation result.</returns>
126179
private Task<MutationResult<TState>> ExecuteMutation<TState>(
127180
GovernedExecutionContext<TState> execution,
128181
CancellationToken cancellationToken)
129182
=> _mutationEngine.ExecuteAsync(
130183
execution.Mutation,
131184
execution.CurrentState,
132185
cancellationToken);
186+
187+
/// <summary>
188+
/// Links successful compensating execution back to the original governed request.
189+
/// </summary>
190+
/// <param name="executedRequest">Persisted compensating request after successful execution.</param>
191+
/// <param name="governanceContext">Context describing the governance actor or service recording the compensation link.</param>
192+
/// <param name="cancellationToken">Cancellation token.</param>
193+
private async Task LinkCompensationExecution(
194+
MutationRequest executedRequest,
195+
MutationContext governanceContext,
196+
CancellationToken cancellationToken)
197+
{
198+
if (executedRequest.Execution.Kind != GovernedExecutionKind.Compensation || executedRequest.Execution.Compensation is null)
199+
return;
200+
201+
executedRequest.Execution.Compensation.EnsureValid();
202+
203+
var originalRequest = await _requestStore
204+
.Get(executedRequest.Execution.Compensation.OriginalRequestId, cancellationToken)
205+
.ConfigureAwait(false)
206+
?? throw new MutationRequestNotFoundException(executedRequest.Execution.Compensation.OriginalRequestId);
207+
208+
if (originalRequest.Execution.RelatedExecutions.Any(link => link.RequestId == executedRequest.RequestId &&
209+
link.Type == GovernedExecutionLinkType.CompensatedBy))
210+
return;
211+
212+
var linkedAt = executedRequest.Versioning.ExecutedAt ?? DateTimeOffset.UtcNow;
213+
var decision = GovernedExecutionDecisionFactory.CreateCompensatedDecision(
214+
governanceContext,
215+
executedRequest.RequestId,
216+
executedRequest.Execution.Compensation,
217+
executedRequest.Versioning.ResultingStateVersion ?? string.Empty,
218+
linkedAt);
219+
220+
var updatedOriginalRequest = originalRequest with
221+
{
222+
UpdatedAt = linkedAt,
223+
Execution = originalRequest.Execution with
224+
{
225+
RelatedExecutions =
226+
[
227+
.. originalRequest.Execution.RelatedExecutions,
228+
new GovernedExecutionLink
229+
{
230+
RequestId = executedRequest.RequestId,
231+
Type = GovernedExecutionLinkType.CompensatedBy,
232+
ExecutionKind = GovernedExecutionKind.Compensation,
233+
CompensationKind = executedRequest.Execution.Compensation.Kind,
234+
Trigger = executedRequest.Execution.Compensation.Trigger,
235+
BatchId = executedRequest.Execution.Compensation.BatchId,
236+
LinkedAt = linkedAt
237+
}
238+
]
239+
},
240+
Decisions = [.. originalRequest.Decisions, decision]
241+
};
242+
243+
var persistedRequest = await _requestStore
244+
.TryStore(updatedOriginalRequest, originalRequest.Revision, cancellationToken)
245+
.ConfigureAwait(false)
246+
?? throw new MutationRequestConcurrencyException(originalRequest.RequestId, originalRequest.Revision);
247+
}
133248
}

0 commit comments

Comments
 (0)