Skip to content

Commit e504133

Browse files
committed
refactor(runtime): reorganize Internal into Execution and Evaluation subfolders
Split the flat Internal directory into two purpose-scoped subdirectories: - Execution: pipeline orchestration, concurrency, mode running, batch, failure handling - Evaluation: policy evaluation and policy modification application Namespaces updated accordingly to match folder structure.
1 parent 1b1aa12 commit e504133

11 files changed

Lines changed: 629 additions & 176 deletions
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
using ModularityKit.Mutator.Abstractions;
2+
using ModularityKit.Mutator.Abstractions.Engine;
3+
using ModularityKit.Mutator.Abstractions.Exceptions;
4+
using ModularityKit.Mutator.Abstractions.Policies;
5+
6+
namespace ModularityKit.Mutator.Runtime.Internal.Evaluation;
7+
8+
/// <summary>
9+
/// Evaluates registered mutation policies in runtime priority order.
10+
/// </summary>
11+
internal sealed class MutationPolicyEvaluator(
12+
IPolicyRegistry policyRegistry,
13+
MutationEngineOptions options)
14+
{
15+
private readonly IPolicyRegistry _policyRegistry = policyRegistry ?? throw new ArgumentNullException(nameof(policyRegistry));
16+
private readonly MutationEngineOptions _options = options ?? throw new ArgumentNullException(nameof(options));
17+
18+
/// <summary>
19+
/// Evaluates all registered policies for the supplied mutation and state.
20+
/// </summary>
21+
/// <typeparam name="TState">The state type handled by the mutation.</typeparam>
22+
/// <param name="mutation">The mutation being evaluated.</param>
23+
/// <param name="state">The current state snapshot.</param>
24+
/// <param name="cancellationToken">Token used to cancel policy evaluation.</param>
25+
/// <returns>
26+
/// The first blocking or modifying <see cref="PolicyDecision"/>, or an allow decision when all policies pass.
27+
/// </returns>
28+
public async Task<PolicyDecision> EvaluateAsync<TState>(
29+
IMutation<TState> mutation,
30+
TState state,
31+
CancellationToken cancellationToken)
32+
{
33+
var policies = _policyRegistry.GetPolicies<TState>();
34+
35+
foreach (var policy in policies.OrderByDescending(p => p.Priority))
36+
{
37+
var decision = await EvaluatePolicyAsync(
38+
policy,
39+
mutation,
40+
state,
41+
cancellationToken).ConfigureAwait(false);
42+
43+
if (!decision.IsAllowed || decision.Modifications != null)
44+
return decision;
45+
}
46+
47+
return PolicyDecision.Allow();
48+
}
49+
50+
private async Task<PolicyDecision> EvaluatePolicyAsync<TState>(
51+
IMutationPolicy<TState> policy,
52+
IMutation<TState> mutation,
53+
TState state,
54+
CancellationToken cancellationToken)
55+
{
56+
if (!_options.PolicyEvaluationTimeout.HasValue)
57+
return await InvokePolicyAsync(policy, mutation, state, cancellationToken).ConfigureAwait(false);
58+
59+
using var timeoutSource = new CancellationTokenSource(_options.PolicyEvaluationTimeout.Value);
60+
using var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(
61+
cancellationToken,
62+
timeoutSource.Token);
63+
64+
try
65+
{
66+
return await policy.EvaluateAsync(mutation, state, linkedSource.Token).ConfigureAwait(false);
67+
}
68+
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
69+
{
70+
throw;
71+
}
72+
catch (OperationCanceledException) when (timeoutSource.IsCancellationRequested)
73+
{
74+
throw new PolicyEvaluationTimeoutException(policy.Name, _options.PolicyEvaluationTimeout.Value);
75+
}
76+
catch (Exception ex)
77+
{
78+
throw new PolicyEvaluationException(
79+
policy.Name,
80+
$"Policy '{policy.Name}' evaluation failed: {ex.Message}",
81+
ex);
82+
}
83+
}
84+
85+
private static async Task<PolicyDecision> InvokePolicyAsync<TState>(
86+
IMutationPolicy<TState> policy,
87+
IMutation<TState> mutation,
88+
TState state,
89+
CancellationToken cancellationToken)
90+
{
91+
try
92+
{
93+
return await policy.EvaluateAsync(mutation, state, cancellationToken).ConfigureAwait(false);
94+
}
95+
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
96+
{
97+
throw;
98+
}
99+
catch (Exception ex)
100+
{
101+
throw new PolicyEvaluationException(
102+
policy.Name,
103+
$"Policy '{policy.Name}' evaluation failed: {ex.Message}",
104+
ex);
105+
}
106+
}
107+
}

src/Runtime/Internal/PolicyModificationApplier.cs renamed to src/Runtime/Internal/Evaluation/PolicyModificationApplier.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,27 @@
11
using ModularityKit.Mutator.Abstractions.Effects;
22
using ModularityKit.Mutator.Abstractions.Results;
33

4-
namespace ModularityKit.Mutator.Runtime.Internal;
4+
namespace ModularityKit.Mutator.Runtime.Internal.Evaluation;
55

6+
/// <summary>
7+
/// Applies policy-level state and side-effect modifications to a mutation result.
8+
/// </summary>
69
internal static class PolicyModificationApplier
710
{
11+
/// <summary>
12+
/// Applies the given policy modifications to <paramref name="result" />, returning an updated result.
13+
/// </summary>
14+
/// <typeparam name="TState">The state type handled by the mutation.</typeparam>
15+
/// <param name="result">The original mutation result to apply modifications to.</param>
16+
/// <param name="modifications">
17+
/// A dictionary of modifications. Recognised keys are <c>"State"</c> (overrides the new state),
18+
/// <c>"SideEffect"</c> (appends a single <see cref="SideEffect" />), and
19+
/// <c>"SideEffects"</c> (appends a collection of <see cref="SideEffect" />).
20+
/// </param>
21+
/// <returns>
22+
/// The original <paramref name="result" /> unchanged when no applicable modifications exist or the result is not successful;
23+
/// otherwise a new result record with the modifications applied.
24+
/// </returns>
825
public static MutationResult<TState> Apply<TState>(
926
MutationResult<TState> result,
1027
IReadOnlyDictionary<string, object>? modifications)

src/Runtime/Internal/MutationBatchExecutor.cs renamed to src/Runtime/Internal/Execution/MutationBatchExecutor.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,27 @@
33
using ModularityKit.Mutator.Abstractions.Results;
44
using System.Diagnostics;
55

6-
namespace ModularityKit.Mutator.Runtime.Internal;
6+
namespace ModularityKit.Mutator.Runtime.Internal.Execution;
77

8+
/// <summary>
9+
/// Executes a sequence of mutations against an evolving state, accumulating results and changes.
10+
/// </summary>
811
internal static class MutationBatchExecutor
912
{
13+
/// <summary>
14+
/// Iterates over <paramref name="mutations" /> in order, executing each against the current state.
15+
/// Successful mutations advance the state; failed mutations are recorded and may halt the batch.
16+
/// </summary>
17+
/// <typeparam name="TState">The state type handled by the mutations.</typeparam>
18+
/// <param name="mutations">The ordered sequence of mutations to execute.</param>
19+
/// <param name="initialState">The starting state before any mutation is applied.</param>
20+
/// <param name="stopOnFirstFailure">When <see langword="true" />, halts execution after the first unsuccessful mutation.</param>
21+
/// <param name="executeAsync">The delegate used to execute a single mutation against the current state.</param>
22+
/// <param name="cancellationToken">Token used to cancel batch execution.</param>
23+
/// <returns>
24+
/// A <see cref="BatchMutationResult{TState}" /> containing all individual results, the aggregated
25+
/// change set, the final state (when all mutations succeeded), and the total execution time.
26+
/// </returns>
1027
public static async Task<BatchMutationResult<TState>> ExecuteAsync<TState>(
1128
IEnumerable<IMutation<TState>> mutations,
1229
TState initialState,

src/Runtime/Internal/MutationExecutionConcurrencyGate.cs renamed to src/Runtime/Internal/Execution/MutationExecutionConcurrencyGate.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using System.Collections.Concurrent;
22

3-
namespace ModularityKit.Mutator.Runtime.Internal;
3+
namespace ModularityKit.Mutator.Runtime.Internal.Execution;
44

55
/// <summary>
66
/// Coordinates core mutation execution concurrency across the engine.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using System.Diagnostics;
2+
using ModularityKit.Mutator.Abstractions.Engine;
3+
using ModularityKit.Mutator.Abstractions.Metrics;
4+
5+
namespace ModularityKit.Mutator.Runtime.Internal.Execution;
6+
7+
/// <summary>
8+
/// Carries shared execution state across the runtime mutation pipeline.
9+
/// </summary>
10+
/// <typeparam name="TState">The state type handled by the mutation.</typeparam>
11+
internal sealed record MutationExecutionContext<TState>
12+
{
13+
/// <summary>
14+
/// The mutation being executed.
15+
/// </summary>
16+
public IMutation<TState> Mutation { get; init; } = null!;
17+
18+
/// <summary>
19+
/// The current state snapshot being mutated.
20+
/// </summary>
21+
public TState State { get; init; } = default!;
22+
23+
/// <summary>
24+
/// The unique identifier for this execution run.
25+
/// </summary>
26+
public string ExecutionId { get; init; } = string.Empty;
27+
28+
/// <summary>
29+
/// The shared stopwatch tracking total execution time.
30+
/// </summary>
31+
public Stopwatch Stopwatch { get; init; } = null!;
32+
33+
/// <summary>
34+
/// The optional metrics scope for detailed runtime metrics.
35+
/// </summary>
36+
public IMetricsScope? MetricsScope { get; init; }
37+
38+
/// <summary>
39+
/// The cancellation token for the current execution.
40+
/// </summary>
41+
public CancellationToken CancellationToken { get; init; }
42+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
using ModularityKit.Mutator.Abstractions.Audit;
2+
using ModularityKit.Mutator.Abstractions.Context;
3+
using ModularityKit.Mutator.Abstractions.Engine;
4+
using ModularityKit.Mutator.Abstractions.Exceptions;
5+
using ModularityKit.Mutator.Abstractions.Interception;
6+
using ModularityKit.Mutator.Runtime.Diagnostics;
7+
8+
namespace ModularityKit.Mutator.Runtime.Internal.Execution;
9+
10+
/// <summary>
11+
/// Centralizes interceptor notification and audit persistence for mutation execution failures.
12+
/// </summary>
13+
internal sealed class MutationExecutionFailureHandler(
14+
IInterceptorPipeline interceptorPipeline,
15+
IMutationAuditor auditor)
16+
{
17+
private readonly IInterceptorPipeline _interceptorPipeline =
18+
interceptorPipeline ?? throw new ArgumentNullException(nameof(interceptorPipeline));
19+
20+
private readonly IMutationAuditor _auditor = auditor ?? throw new ArgumentNullException(nameof(auditor));
21+
22+
/// <summary>
23+
/// Processes known mutation exception without wrapping it again.
24+
/// </summary>
25+
/// <typeparam name="TState">The state type handled by the mutation.</typeparam>
26+
/// <param name="executionContext">The shared execution context for the failed mutation.</param>
27+
/// <param name="exception">The known mutation exception.</param>
28+
/// <param name="duration">The elapsed execution time before failure.</param>
29+
public async Task HandleKnownExceptionAsync<TState>(
30+
MutationExecutionContext<TState> executionContext,
31+
MutationException exception,
32+
TimeSpan duration)
33+
{
34+
await NotifyFailureAsync(
35+
executionContext,
36+
exception,
37+
executionContext.CancellationToken).ConfigureAwait(false);
38+
39+
await AuditExceptionAsync(
40+
executionContext.Mutation,
41+
exception,
42+
executionContext.ExecutionId,
43+
duration).ConfigureAwait(false);
44+
}
45+
46+
/// <summary>
47+
/// Processes an unexpected exception and converts it into runtime level mutation exception.
48+
/// </summary>
49+
/// <typeparam name="TState">The state type handled by the mutation.</typeparam>
50+
/// <param name="executionContext">The shared execution context for the failed mutation.</param>
51+
/// <param name="exception">The unexpected exception.</param>
52+
/// <param name="duration">The elapsed execution time before failure.</param>
53+
/// <returns>The wrapped runtime exception to rethrow.</returns>
54+
public async Task<MutationException> HandleUnexpectedExceptionAsync<TState>(
55+
MutationExecutionContext<TState> executionContext,
56+
Exception exception,
57+
TimeSpan duration)
58+
{
59+
await NotifyFailureAsync(
60+
executionContext,
61+
exception,
62+
executionContext.CancellationToken).ConfigureAwait(false);
63+
64+
await AuditExceptionAsync(
65+
executionContext.Mutation,
66+
exception,
67+
executionContext.ExecutionId,
68+
duration).ConfigureAwait(false);
69+
70+
return new MutationException(
71+
$"Mutation execution failed: {exception.Message}",
72+
exception)
73+
{
74+
ExecutionId = executionContext.ExecutionId
75+
};
76+
}
77+
78+
private async Task NotifyFailureAsync<TState>(
79+
MutationExecutionContext<TState> executionContext,
80+
Exception exception,
81+
CancellationToken cancellationToken)
82+
{
83+
await _interceptorPipeline.OnMutationFailedAsync(
84+
executionContext.Mutation.Intent,
85+
executionContext.Mutation.Context,
86+
executionContext.State!,
87+
exception,
88+
executionContext.ExecutionId,
89+
cancellationToken).ConfigureAwait(false);
90+
}
91+
92+
private async Task AuditExceptionAsync<TState>(
93+
IMutation<TState> mutation,
94+
Exception exception,
95+
string executionId,
96+
TimeSpan duration)
97+
{
98+
var entry = MutationAuditEntryFactory.CreateException(
99+
mutation,
100+
exception,
101+
executionId,
102+
duration);
103+
104+
await _auditor.AuditAsync(entry).ConfigureAwait(false);
105+
}
106+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using ModularityKit.Mutator.Abstractions;
2+
using ModularityKit.Mutator.Abstractions.Changes;
3+
using ModularityKit.Mutator.Abstractions.Context;
4+
using ModularityKit.Mutator.Abstractions.Engine;
5+
using ModularityKit.Mutator.Abstractions.Results;
6+
using ModularityExecutionContext = ModularityKit.Mutator.Abstractions.Context.ExecutionContext;
7+
8+
namespace ModularityKit.Mutator.Runtime.Internal.Execution;
9+
10+
/// <summary>
11+
/// Executes mutation behavior according to the current mutation mode.
12+
/// </summary>
13+
internal sealed class MutationExecutionModeRunner(
14+
IMutationExecutor executor,
15+
MutationEngineOptions options)
16+
{
17+
private readonly IMutationExecutor _executor = executor ?? throw new ArgumentNullException(nameof(executor));
18+
private readonly MutationEngineOptions _options = options ?? throw new ArgumentNullException(nameof(options));
19+
20+
/// <summary>
21+
/// Runs the mutation using simulate, validate, or commit execution semantics.
22+
/// </summary>
23+
public Task<MutationResult<TState>> ExecuteAsync<TState>(MutationExecutionContext<TState> executionContext)
24+
{
25+
var executorContext = new ModularityExecutionContext
26+
{
27+
ExecutionId = executionContext.ExecutionId,
28+
Timeout = _options.ExecutionTimeout,
29+
CancellationToken = executionContext.CancellationToken
30+
};
31+
32+
return executionContext.Mutation.Context.Mode switch
33+
{
34+
MutationMode.Simulate => Task.FromResult(executionContext.Mutation.Simulate(executionContext.State)),
35+
MutationMode.Validate => Task.FromResult(BuildValidationOnlyResult(
36+
executionContext.Mutation,
37+
executionContext.State)),
38+
_ => _executor.ExecuteAsync(
39+
executionContext.Mutation,
40+
executionContext.State,
41+
executorContext,
42+
executionContext.CancellationToken)
43+
};
44+
}
45+
46+
private static MutationResult<TState> BuildValidationOnlyResult<TState>(
47+
IMutation<TState> mutation,
48+
TState state)
49+
{
50+
var validation = mutation.Validate(state);
51+
return validation.IsValid
52+
? MutationResult<TState>.Success(state, ChangeSet.Empty)
53+
: MutationResult<TState>.Failure(validation);
54+
}
55+
}

0 commit comments

Comments
 (0)