33using ModularityKit . Mutator . Abstractions . Results ;
44using ModularityKit . Mutator . Governance . Abstractions . Execution . Contracts ;
55using 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 ;
69using ModularityKit . Mutator . Governance . Abstractions . Resolution . Contracts ;
710using ModularityKit . Mutator . Governance . Abstractions . Resolution . Model ;
811using 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