diff --git a/Benchmarks/Governance/Materialization/GovernanceMaterializationOutputBenchmarks.cs b/Benchmarks/Governance/Materialization/GovernanceMaterializationOutputBenchmarks.cs
index 7ebe195..9d58b72 100644
--- a/Benchmarks/Governance/Materialization/GovernanceMaterializationOutputBenchmarks.cs
+++ b/Benchmarks/Governance/Materialization/GovernanceMaterializationOutputBenchmarks.cs
@@ -37,7 +37,7 @@ public MutationHistoryEntry HistoryEntry_FromGovernedExecution()
StateId = _fixture.Result.Request.StateId,
Intent = _fixture.Mutation.Intent,
Context = _fixture.Mutation.Context,
- Changes = _fixture.Result.MutationResult!.Changes,
+ Changes = _fixture.Result.MutationResult!.Value.Changes,
SideEffects = _fixture.Result.Request.SideEffects.ToList(),
Timestamp = _fixture.Result.Request.Versioning.ExecutedAt ?? DateTimeOffset.UtcNow,
ExecutionTime = TimeSpan.FromMilliseconds(2)
@@ -50,6 +50,7 @@ public MutationHistoryEntry HistoryEntry_FromGovernedExecution()
[Benchmark]
public MutationAuditEntry AuditEntry_FromGovernedExecution()
{
+ var mr = _fixture.Result.MutationResult!.Value;
return new MutationAuditEntry
{
ExecutionId = _fixture.Result.Request.RequestId,
@@ -57,10 +58,10 @@ public MutationAuditEntry AuditEntry_FromGovernedExecution()
StateType = _fixture.Result.Request.StateType,
MutationIntent = _fixture.Mutation.Intent,
Context = _fixture.Mutation.Context,
- Changes = _fixture.Result.MutationResult!.Changes,
- IsSuccess = _fixture.Result.MutationResult.IsSuccess,
+ Changes = mr.Changes,
+ IsSuccess = mr.IsSuccess,
ErrorMessage = null,
- PolicyDecisions = _fixture.Result.MutationResult.PolicyDecisions,
+ PolicyDecisions = mr.PolicyDecisions,
SideEffects = _fixture.Result.Request.SideEffects.ToList(),
Timestamp = _fixture.Result.Request.Versioning.ExecutedAt ?? DateTimeOffset.UtcNow,
Duration = TimeSpan.FromMilliseconds(2),
diff --git a/Benchmarks/Results/MutationOutputMaterializationBenchmarks.cs b/Benchmarks/Results/MutationOutputMaterializationBenchmarks.cs
index 68a6694..d39dc17 100644
--- a/Benchmarks/Results/MutationOutputMaterializationBenchmarks.cs
+++ b/Benchmarks/Results/MutationOutputMaterializationBenchmarks.cs
@@ -1,37 +1,45 @@
using BenchmarkDotNet.Attributes;
using ModularityKit.Mutator.Abstractions.Audit;
+using ModularityKit.Mutator.Abstractions.Context;
+using ModularityKit.Mutator.Abstractions.Effects;
using ModularityKit.Mutator.Abstractions.History;
+using ModularityKit.Mutator.Abstractions.Intent;
using ModularityKit.Mutator.Abstractions.Results;
using ModularityKit.Mutator.Benchmarks.Results.Support;
namespace ModularityKit.Mutator.Benchmarks.Results;
-///
-/// Benchmarks materialization of history and audit output from an executed mutation result.
-///
[BenchmarkCategory("Results")]
[MemoryDiagnoser]
[InProcess]
public class MutationOutputMaterializationBenchmarks
{
- private MutationResult _result = null!;
+ private MutationResult _result = default!;
private string _executionId = string.Empty;
private TimeSpan _duration;
+ private IReadOnlyList _sideEffectList = null!;
+ private MutationIntent _historyIntent = null!;
+ private MutationIntent _auditIntent = null!;
+ private MutationContext _historyContext = null!;
+ private MutationContext _auditContext = null!;
- ///
- /// Prepares a representative executed mutation result for output materialization benchmarks.
- ///
[GlobalSetup]
public void Setup()
{
_result = ResultsBenchmarkSupport.CreateExecutedResult(sideEffectCount: 3, changeCount: 4);
_executionId = "results-benchmark-execution";
_duration = TimeSpan.FromMilliseconds(2);
+ _sideEffectList = _result.SideEffects.ToList();
+ _historyIntent = ResultsBenchmarkSupport.CreateIntent(
+ "ResultHistoryMaterialization",
+ "Materialize history output for benchmark results.");
+ _auditIntent = ResultsBenchmarkSupport.CreateIntent(
+ "ResultAuditMaterialization",
+ "Materialize audit output for benchmark results.");
+ _historyContext = ResultsBenchmarkSupport.CreateContext("history");
+ _auditContext = ResultsBenchmarkSupport.CreateContext("audit");
}
- ///
- /// Measures materialization of the mutation history entry, including change and side effect copying.
- ///
[Benchmark(Baseline = true)]
public MutationHistoryEntry HistoryEntry_Materialization()
{
@@ -39,20 +47,15 @@ public MutationHistoryEntry HistoryEntry_Materialization()
{
ExecutionId = _executionId,
StateId = ResultsBenchmarkSupport.StateId,
- Intent = ResultsBenchmarkSupport.CreateIntent(
- "ResultHistoryMaterialization",
- "Materialize history output for benchmark results."),
- Context = ResultsBenchmarkSupport.CreateContext("history"),
+ Intent = _historyIntent,
+ Context = _historyContext,
Changes = _result.Changes,
- SideEffects = _result.SideEffects.ToList(),
+ SideEffects = _sideEffectList,
Timestamp = DateTimeOffset.UtcNow,
ExecutionTime = _duration
};
}
- ///
- /// Measures materialization of the audit entry produced from the same executed mutation result.
- ///
[Benchmark]
public MutationAuditEntry AuditEntry_Materialization()
{
@@ -60,11 +63,9 @@ public MutationAuditEntry AuditEntry_Materialization()
{
ExecutionId = _executionId,
StateId = ResultsBenchmarkSupport.StateId,
- StateType = nameof(ResultsBenchmarkSupport.ResultBenchmarkState),
- MutationIntent = ResultsBenchmarkSupport.CreateIntent(
- "ResultAuditMaterialization",
- "Materialize audit output for benchmark results."),
- Context = ResultsBenchmarkSupport.CreateContext("audit"),
+ StateType = "ResultBenchmarkState",
+ MutationIntent = _auditIntent,
+ Context = _auditContext,
Changes = _result.Changes,
IsSuccess = _result.IsSuccess,
ErrorMessage = null,
diff --git a/Benchmarks/Results/MutationResultCreationBenchmarks.cs b/Benchmarks/Results/MutationResultCreationBenchmarks.cs
index b79b009..789c2ce 100644
--- a/Benchmarks/Results/MutationResultCreationBenchmarks.cs
+++ b/Benchmarks/Results/MutationResultCreationBenchmarks.cs
@@ -14,17 +14,21 @@ namespace ModularityKit.Mutator.Benchmarks.Results;
[InProcess]
public class MutationResultCreationBenchmarks
{
- private ResultsBenchmarkSupport.ResultBenchmarkState _state = null!;
+ private ResultsBenchmarkSupport.ResultBenchmarkState _state = default!;
private ChangeSet _changes = null!;
+ private IReadOnlyList _singleSideEffect = null!;
+ private IReadOnlyList _multipleSideEffects = null!;
///
- /// Prepares the shared state and change set used by the result creation cases.
+ /// Prepares the shared state, change set, and side effect lists used by the result creation cases.
///
[GlobalSetup]
public void Setup()
{
_state = new ResultsBenchmarkSupport.ResultBenchmarkState(0, 42);
_changes = ResultsBenchmarkSupport.CreateChangeSet(_state.Revision, 2);
+ _singleSideEffect = ResultsBenchmarkSupport.CreateSideEffects(1);
+ _multipleSideEffects = ResultsBenchmarkSupport.CreateSideEffects(4);
}
///
@@ -45,22 +49,14 @@ _state with
///
[Benchmark]
public MutationResult Success_SingleSideEffect()
- {
- var sideEffect = SideEffect.Create(
- "ResultMaterialization",
- "Single side effect",
- new ResultsBenchmarkSupport.SideEffectPayload(1, "single"),
- SideEffectSeverity.Info);
-
- return MutationResult.Success(
+ => MutationResult.Success(
_state with
{
Revision = _state.Revision + 1,
Value = _state.Value + 1
},
_changes,
- [sideEffect]);
- }
+ _singleSideEffect);
///
/// Measures creation of a successful mutation result with several side effects.
@@ -74,5 +70,5 @@ _state with
Value = _state.Value + 1
},
_changes,
- ResultsBenchmarkSupport.CreateSideEffects(4));
+ _multipleSideEffects);
}
diff --git a/Benchmarks/Results/Support/ResultsBenchmarkSupport.cs b/Benchmarks/Results/Support/ResultsBenchmarkSupport.cs
index 98056e4..848eb99 100644
--- a/Benchmarks/Results/Support/ResultsBenchmarkSupport.cs
+++ b/Benchmarks/Results/Support/ResultsBenchmarkSupport.cs
@@ -119,12 +119,12 @@ public static MutationResult CreateExecutedResult(
///
/// The revision counter advanced on each benchmark mutation.
/// The mutable numeric value exercised by the benchmark mutation.
- public sealed record ResultBenchmarkState(int Revision, int Value);
+ public readonly record struct ResultBenchmarkState(int Revision, int Value);
///
/// Typed payload used to give side effects realistic materialization shape.
///
/// The ordinal of the side effect.
/// A stable payload token.
- public sealed record SideEffectPayload(int Index, string Token);
+ public readonly record struct SideEffectPayload(int Index, string Token);
}
diff --git a/Tests/ModularityKit.Mutator.Governance.Tests/Execution/GovernanceExecutionManagerCompensationTests.cs b/Tests/ModularityKit.Mutator.Governance.Tests/Execution/GovernanceExecutionManagerCompensationTests.cs
index 02dfdb7..38bddf6 100644
--- a/Tests/ModularityKit.Mutator.Governance.Tests/Execution/GovernanceExecutionManagerCompensationTests.cs
+++ b/Tests/ModularityKit.Mutator.Governance.Tests/Execution/GovernanceExecutionManagerCompensationTests.cs
@@ -73,7 +73,7 @@ public async Task ExecuteApproved_executes_operator_rollback_compensation_and_li
var compensationResult = await executionManager.ExecuteApproved(
compensationRequest.RequestId,
compensationMutation,
- originalResult.MutationResult!.NewState!,
+ originalResult.MutationResult!.Value.NewState!,
governanceContext: MutationContext.Service("governance-runtime", "Execute operator rollback"),
strategy: VersionedRequestResolutionStrategy.RejectStale);
diff --git a/src/Abstractions/Audit/MutationAuditEntry.cs b/src/Abstractions/Audit/MutationAuditEntry.cs
index f61762d..81efb55 100644
--- a/src/Abstractions/Audit/MutationAuditEntry.cs
+++ b/src/Abstractions/Audit/MutationAuditEntry.cs
@@ -7,7 +7,7 @@
namespace ModularityKit.Mutator.Abstractions.Audit;
///
-/// Represents a single audit record for a mutation operation.
+/// Represents single audit record for a mutation operation.
/// Captures the intent, context, changes, policy decisions, and metadata of the mutation.
///
///
@@ -15,12 +15,12 @@ namespace ModularityKit.Mutator.Abstractions.Audit;
/// Each entry is immutable once created. Typical consumers include auditors, logging systems,
/// or analytics pipelines.
///
-public sealed class MutationAuditEntry
+public readonly record struct MutationAuditEntry
{
///
/// Unique execution identifier for this mutation.
///
- public string ExecutionId { get; init; } = string.Empty;
+ public string ExecutionId { get; init; }
///
/// Identifier of the state object that was mutated.
@@ -35,17 +35,17 @@ public sealed class MutationAuditEntry
///
/// The intent describing what the mutation is trying to achieve.
///
- public MutationIntent MutationIntent { get; init; } = null!;
+ public MutationIntent MutationIntent { get; init; }
///
/// Context of the mutation (e.g., correlation data, user context).
///
- public MutationContext Context { get; init; } = null!;
+ public MutationContext Context { get; init; }
///
/// Changes applied by the mutation.
///
- public ChangeSet Changes { get; init; } = ChangeSet.Empty;
+ public ChangeSet Changes { get; init; }
///
/// Indicates whether the mutation was successful.
@@ -60,12 +60,12 @@ public sealed class MutationAuditEntry
///
/// Decisions made by policies during the mutation evaluation.
///
- public IReadOnlyList PolicyDecisions { get; init; } = [];
+ public IReadOnlyList PolicyDecisions { get; init; }
///
/// Side effects produced during the mutation.
///
- public IReadOnlyList SideEffects { get; init; } = [];
+ public IReadOnlyList SideEffects { get; init; }
///
/// Timestamp when the mutation started.
diff --git a/src/Abstractions/Changes/ChangeSet.cs b/src/Abstractions/Changes/ChangeSet.cs
index bd38869..345cd7d 100644
--- a/src/Abstractions/Changes/ChangeSet.cs
+++ b/src/Abstractions/Changes/ChangeSet.cs
@@ -1,11 +1,12 @@
namespace ModularityKit.Mutator.Abstractions.Changes;
///
-/// Represents a collection of state changes introduced by a mutation.
-/// This is a **primary feature** of a mutation and not an optional addition.
+/// Represents collection of state changes introduced by a mutation.
+/// This is primary feature of mutation and not an optional addition.
///
public sealed class ChangeSet
{
+ private static readonly ChangeSet _empty = new();
private readonly List _changes = [];
///
@@ -34,7 +35,7 @@ public sealed class ChangeSet
public string? Checksum { get; init; }
///
- /// Adds a new state change to the changeset.
+ /// Adds new state change to the changeset.
///
/// The state change to add.
/// Thrown if is null.
@@ -45,7 +46,7 @@ public void Add(StateChange change)
}
///
- /// Retrieves all changes corresponding to a specific path.
+ /// Retrieves all changes corresponding to specific path.
///
/// The path to filter changes.
/// Enumerable of matching the path.
@@ -53,7 +54,7 @@ public IEnumerable GetChanges(string path)
=> _changes.Where(c => c.Path == path);
///
- /// Determines whether a specific path has been changed.
+ /// Determines whether specific path has been changed.
///
/// The path to check.
/// True if the path has been changed; otherwise, false.
@@ -70,7 +71,7 @@ public IEnumerable GetChangedPaths()
///
/// Returns an empty changeset.
///
- public static ChangeSet Empty => new();
+ public static ChangeSet Empty => _empty;
///
/// Creates changeset containing single state change.
diff --git a/src/Abstractions/Effects/SideEffect.cs b/src/Abstractions/Effects/SideEffect.cs
index 5b511a9..fada0cf 100644
--- a/src/Abstractions/Effects/SideEffect.cs
+++ b/src/Abstractions/Effects/SideEffect.cs
@@ -1,3 +1,4 @@
+using System.Collections.Concurrent;
using System.Text.Json.Serialization;
namespace ModularityKit.Mutator.Abstractions.Effects;
@@ -189,20 +190,33 @@ private static SideEffect CreateCore(
};
}
+ private static readonly ConcurrentDictionary _contractCache = new();
+
private static (string? ContractType, int? ContractVersion) ResolveContract(object? data)
{
if (data is null)
return (null, null);
var dataType = data.GetType();
+ if (_contractCache.TryGetValue(dataType, out var cached))
+ return cached;
+
var contract = dataType.GetCustomAttributes(typeof(SideEffectDataContractAttribute), inherit: false)
.OfType()
.SingleOrDefault();
+ (string?, int?) result;
if (contract is null)
- return (null, null);
+ {
+ result = (null, null);
+ }
+ else
+ {
+ SideEffectDataContractRegistry.Register(dataType);
+ result = (contract.ContractType, contract.ContractVersion);
+ }
- SideEffectDataContractRegistry.Register(dataType);
- return (contract.ContractType, contract.ContractVersion);
+ _contractCache.TryAdd(dataType, result);
+ return result;
}
}
diff --git a/src/Abstractions/History/MutationHistory.cs b/src/Abstractions/History/MutationHistory.cs
index 79058b1..cb53869 100644
--- a/src/Abstractions/History/MutationHistory.cs
+++ b/src/Abstractions/History/MutationHistory.cs
@@ -3,10 +3,10 @@
namespace ModularityKit.Mutator.Abstractions.History;
///
-/// Represents the full mutation history of a specific state object.
+/// Represents the full mutation history of specific state object.
///
///
-/// MutationHistory stores a chronological sequence of entries.
+/// MutationHistory stores chronological sequence of entries.
/// It allows replaying state changes, querying timelines for specific paths, and computing statistics.
/// This is typically used in combination with to persist and retrieve histories.
///
@@ -32,13 +32,13 @@ public sealed class MutationHistory
/// Timestamp of the first mutation in the history.
///
public DateTimeOffset? FirstMutationAt
- => Entries.FirstOrDefault()?.Timestamp;
+ => Entries.Count > 0 ? Entries[0].Timestamp : null;
///
/// Timestamp of the last mutation in the history.
///
public DateTimeOffset? LastMutationAt
- => Entries.LastOrDefault()?.Timestamp;
+ => Entries.Count > 0 ? Entries[^1].Timestamp : null;
///
/// Total number of mutations recorded.
diff --git a/src/Abstractions/History/MutationHistoryEntry.cs b/src/Abstractions/History/MutationHistoryEntry.cs
index 94b38f5..c7690bd 100644
--- a/src/Abstractions/History/MutationHistoryEntry.cs
+++ b/src/Abstractions/History/MutationHistoryEntry.cs
@@ -11,39 +11,39 @@ namespace ModularityKit.Mutator.Abstractions.History;
///
/// Each captures the details of a single mutation,
/// including its intent, context, state changes, side effects, execution timing, and integrity hashes.
-/// This class is used by to store a chronological sequence of mutations.
+/// This type is used by to store a chronological sequence of mutations.
///
-public sealed class MutationHistoryEntry
+public readonly record struct MutationHistoryEntry
{
///
/// Unique identifier for the execution of this mutation.
///
- public string ExecutionId { get; init; } = string.Empty;
+ public string ExecutionId { get; init; }
///
/// Identifier of the state this mutation was applied to.
///
- public string StateId { get; init; } = string.Empty;
+ public string StateId { get; init; }
///
/// The intent behind the mutation.
///
- public MutationIntent Intent { get; init; } = null!;
+ public MutationIntent Intent { get; init; }
///
/// Contextual information about the mutation execution.
///
- public MutationContext Context { get; init; } = null!;
+ public MutationContext Context { get; init; }
///
/// Set of changes applied by this mutation.
///
- public ChangeSet Changes { get; init; } = ChangeSet.Empty;
+ public ChangeSet Changes { get; init; }
///
/// Side effects produced by this mutation.
///
- public IReadOnlyList SideEffects { get; init; } = [];
+ public IReadOnlyList SideEffects { get; init; }
///
/// Timestamp indicating when the mutation occurred.
diff --git a/src/Abstractions/Metrics/MutationMetrics.cs b/src/Abstractions/Metrics/MutationMetrics.cs
index 1e966db..63660c9 100644
--- a/src/Abstractions/Metrics/MutationMetrics.cs
+++ b/src/Abstractions/Metrics/MutationMetrics.cs
@@ -12,6 +12,7 @@ namespace ModularityKit.Mutator.Abstractions.Metrics;
///
/// Key considerations:
///
+/// - timestamp of the mutation recording.
/// - measures the total duration of the mutation.
/// - measures time spent validating the mutation.
/// - measures time spent evaluating policies.
@@ -25,6 +26,11 @@ namespace ModularityKit.Mutator.Abstractions.Metrics;
///
public sealed record MutationMetrics
{
+ private static readonly IReadOnlyDictionary _emptyAdditionalMetrics
+ = new Dictionary();
+
+ internal static readonly MutationMetrics Empty = new();
+
///
/// Timestamp when the mutation was recorded.
///
@@ -46,12 +52,12 @@ public sealed record MutationMetrics
public TimeSpan PolicyEvaluationTime { get; init; }
///
- /// Number of rules validated during the mutation.
+ /// Number of validated rules.
///
public int ValidatedRules { get; init; }
///
- /// Number of policies evaluated during the mutation.
+ /// Number of evaluated policies.
///
public int EvaluatedPolicies { get; init; }
@@ -61,23 +67,23 @@ public sealed record MutationMetrics
public int ChangesCount { get; init; }
///
- /// Size of the state before the mutation (in bytes, if applicable).
+ /// Size of the state object in bytes, if available.
///
public long? StateSize { get; init; }
///
- /// Memory used during mutation execution (in bytes).
+ /// Memory used during mutation execution in bytes, if available.
///
public long? MemoryUsed { get; init; }
///
- /// Indicates whether a cache was used.
+ /// Indicates whether caching was used during the mutation.
///
public bool UsedCache { get; init; }
///
- /// Additional custom metrics.
+ /// Additional metrics for extensibility.
///
public IReadOnlyDictionary AdditionalMetrics { get; init; }
- = new Dictionary();
+ = _emptyAdditionalMetrics;
}
diff --git a/src/Abstractions/Results/MutationResult.cs b/src/Abstractions/Results/MutationResult.cs
index a86229a..330a0d5 100644
--- a/src/Abstractions/Results/MutationResult.cs
+++ b/src/Abstractions/Results/MutationResult.cs
@@ -6,35 +6,35 @@
namespace ModularityKit.Mutator.Abstractions.Results;
///
-/// Represents the outcome of applying a mutation to a state.
-/// Always contains a trace of changes, even if the mutation fails.
+/// Represents the outcome of mutation operation for a given state type.
///
-/// The type of the state being mutated.
-public sealed record MutationResult
+/// The type of the state object being mutated.
+public readonly record struct MutationResult
{
+ public MutationResult() { }
+
///
- /// Indicates whether the mutation was successfully applied.
+ /// Indicates whether the mutation completed successfully.
///
public bool IsSuccess { get; init; }
///
- /// The new state after mutation, if successful; otherwise null.
+ /// The new state after a successful mutation, or default if unsuccessful.
///
public TState? NewState { get; init; }
///
- /// The set of changes describing what exactly was modified during the mutation.
- /// Always populated, even on failure.
+ /// The set of changes applied by the mutation.
///
- public ChangeSet Changes { get; init; } = ChangeSet.Empty;
+ public ChangeSet Changes { get; init; }
///
- /// Result of validation checks performed during mutation.
+ /// Validation result indicating any validation errors or warnings.
///
- public ValidationResult ValidationResult { get; init; } = ValidationResult.Success();
+ public ValidationResult ValidationResult { get; init; }
///
- /// Decisions from policy evaluation that influenced the mutation outcome.
+ /// Policy decisions that were made during mutation evaluation.
///
public IReadOnlyList PolicyDecisions { get; init; } = [];
@@ -44,27 +44,27 @@ public sealed record MutationResult
public IReadOnlyList SideEffects { get; init; } = [];
///
- /// Metrics related to the execution of the mutation (e.g., duration, performance counters).
+ /// Metrics collected during the mutation execution.
///
- public MutationMetrics Metrics { get; init; } = new();
+ public MutationMetrics Metrics { get; init; }
///
- /// Exception thrown during mutation, if any, for diagnostic purposes.
+ /// Exception thrown during the mutation, if any.
///
public Exception? Exception { get; init; }
///
- /// Timestamp indicating when the mutation completed.
+ /// Timestamp when the mutation completed.
///
- public DateTimeOffset CompletedAt { get; init; } = DateTimeOffset.UtcNow;
+ public DateTimeOffset CompletedAt { get; init; }
///
- /// Creates a successful mutation result.
+ /// Creates successful mutation result with the given state and changes.
///
- /// The new state after the mutation.
+ /// The new state after mutation.
/// The set of changes applied.
/// Optional list of side effects.
- /// A representing success.
+ /// A successful .
public static MutationResult Success(
TState newState,
ChangeSet changes,
@@ -74,16 +74,19 @@ public static MutationResult Success(
IsSuccess = true,
NewState = newState,
Changes = changes,
- SideEffects = sideEffects ?? []
+ SideEffects = sideEffects ?? [],
+ ValidationResult = ValidationResult.Success(),
+ Metrics = MutationMetrics.Empty,
+ CompletedAt = DateTimeOffset.UtcNow
};
///
- /// Creates a successful mutation result from a single state change.
+ /// Creates successful mutation result with single state change.
///
- /// The new state after the mutation.
- /// The single change applied.
+ /// The new state after mutation.
+ /// The single state change applied.
/// Optional list of side effects.
- /// A representing success.
+ /// A successful .
public static MutationResult Success(
TState newState,
StateChange change,
@@ -91,22 +94,24 @@ public static MutationResult Success(
=> Success(newState, ChangeSet.Single(change), sideEffects);
///
- /// Creates a failed mutation result due to validation errors.
+ /// Creates failed mutation result with validation errors.
///
- /// The validation result explaining the failure.
- /// A representing failure.
+ /// The validation result describing the failure.
+ /// A failed .
public static MutationResult Failure(ValidationResult validation)
=> new()
{
IsSuccess = false,
- ValidationResult = validation
+ ValidationResult = validation,
+ Metrics = MutationMetrics.Empty,
+ CompletedAt = DateTimeOffset.UtcNow
};
///
- /// Creates a failed mutation result due to policy enforcement.
+ /// Creates mutation result blocked by policy decision.
///
/// The policy decision that blocked the mutation.
- /// A representing a policy-blocked mutation.
+ /// A blocked .
public static MutationResult PolicyBlocked(PolicyDecision decision)
=> new()
{
@@ -114,6 +119,8 @@ public static MutationResult PolicyBlocked(PolicyDecision decision)
PolicyDecisions = [decision],
ValidationResult = ValidationResult.WithError(
"Policy",
- decision.Reason ?? "Blocked by policy")
+ decision.Reason ?? "Blocked by policy"),
+ Metrics = MutationMetrics.Empty,
+ CompletedAt = DateTimeOffset.UtcNow
};
}
diff --git a/src/Abstractions/Results/ValidationResult.cs b/src/Abstractions/Results/ValidationResult.cs
index 57b24c0..636b119 100644
--- a/src/Abstractions/Results/ValidationResult.cs
+++ b/src/Abstractions/Results/ValidationResult.cs
@@ -1,95 +1,108 @@
namespace ModularityKit.Mutator.Abstractions.Results;
///
-/// Represents the result of validating a mutation or state change.
-/// Contains errors, warnings, and informational messages produced during validation.
+/// Encapsulates the result of a mutation validation, including errors, warnings, and informational messages.
///
+///
+///
+/// A collects detailed validation feedback during mutation processing.
+/// It distinguishes between three severity levels:
+///
+/// - - critical issues that prevent mutation execution.
+/// - - non-blocking issues that may indicate potential problems.
+/// - - informational messages about validation context or suggestions.
+///
+///
+///
+/// The property returns true only when no errors are present.
+/// Static factory methods (, , )
+/// provide convenient creation patterns.
+///
+///
public sealed class ValidationResult
{
+ private static readonly ValidationResult _success = new();
private readonly List _errors = [];
private readonly List _warnings = [];
private readonly List _info = [];
///
- /// Indicates whether the validation passed successfully.
- /// True if there are no errors; false otherwise.
+ /// Indicates whether the validation passed (no errors).
///
public bool IsValid => _errors.Count == 0;
///
- /// Read-only list of validation errors.
- /// Each error indicates a violation of rules that prevents the mutation from being applied.
+ /// List of validation errors that prevent mutation execution.
///
public IReadOnlyList Errors => _errors;
///
- /// Read-only list of validation warnings.
- /// Warnings indicate potential issues that do not block mutation execution.
+ /// List of validation warnings (non-blocking issues).
///
public IReadOnlyList Warnings => _warnings;
///
- /// Read-only list of informational messages produced during validation.
+ /// List of informational validation messages.
///
public IReadOnlyList Info => _info;
///
- /// Adds a new validation error.
+ /// Adds validation error with the specified path, message, and optional code.
///
- /// The path or property associated with the error.
- /// Human-readable description of the error.
- /// Optional error code for categorization.
+ /// Path to the invalid property or field.
+ /// Description of the validation error.
+ /// Optional error code for categorization or localization.
public void AddError(string path, string message, string? code = null)
=> _errors.Add(new ValidationError(path, message, code));
///
- /// Adds a new validation warning.
+ /// Adds validation warning with the specified path, message, and optional code.
///
- /// The path or property associated with the warning.
- /// Human-readable description of the warning.
- /// Optional warning code for categorization.
+ /// Path to the property or field causing the warning.
+ /// Description of the warning.
+ /// Optional code for categorization or localization.
public void AddWarning(string path, string message, string? code = null)
=> _warnings.Add(new ValidationWarning(path, message, code));
///
/// Adds an informational validation message.
///
- /// The path or property associated with the info.
- /// Human-readable informational message.
+ /// Path to the property or concept for this informational message.
+ /// The informational message.
public void AddInfo(string path, string message)
=> _info.Add(new ValidationInfo(path, message));
///
- /// Adds an existing instance.
+ /// Adds a instance directly.
///
- /// The error to add.
+ /// The validation error to add.
public void AddError(ValidationError error) => _errors.Add(error);
///
- /// Adds an existing instance.
+ /// Adds a instance directly.
///
- /// The warning to add.
+ /// The validation warning to add.
public void AddWarning(ValidationWarning warning) => _warnings.Add(warning);
///
- /// Adds an existing instance.
+ /// Adds a instance directly.
///
/// The informational message to add.
public void AddInfo(ValidationInfo info) => _info.Add(info);
///
- /// Creates a successful validation result with no errors.
+ /// Returns a successful (empty) validation result.
///
- /// A indicating success.
- public static ValidationResult Success() => new();
+ /// A with no errors, warnings, or info.
+ public static ValidationResult Success() => _success;
///
/// Creates a validation result with a single error.
///
- /// The path or property associated with the error.
- /// Human-readable description of the error.
- /// Optional error code.
- /// A containing one error.
+ /// Path to the invalid property or field.
+ /// Description of the error.
+ /// Optional error code for categorization or localization.
+ /// A with the specified error.
public static ValidationResult WithError(string path, string message, string? code = null)
{
var result = new ValidationResult();
@@ -98,10 +111,10 @@ public static ValidationResult WithError(string path, string message, string? co
}
///
- /// Creates a validation result containing multiple errors.
+ /// Creates a validation result with multiple errors.
///
- /// Array of to include.
- /// A containing the specified errors.
+ /// The validation errors to include.
+ /// A with the specified errors.
public static ValidationResult WithErrors(params ValidationError[] errors)
{
var result = new ValidationResult();
diff --git a/src/Governance/Runtime/Execution/Outcome/GovernedExecutionOutcomeHandler.cs b/src/Governance/Runtime/Execution/Outcome/GovernedExecutionOutcomeHandler.cs
index 1174463..a6fb168 100644
--- a/src/Governance/Runtime/Execution/Outcome/GovernedExecutionOutcomeHandler.cs
+++ b/src/Governance/Runtime/Execution/Outcome/GovernedExecutionOutcomeHandler.cs
@@ -182,7 +182,7 @@ public async Task> HandleMutationResult(
mutationResult.SideEffects,
cancellationToken).ConfigureAwait(false);
- return BuildNonExecutedResult(
+ return BuildNonExecutedResult(
execution.Resolution with { Request = rejectedRequest },
mutationResult);
}
diff --git a/src/Runtime/Internal/Execution/MutationExecutionPipeline.cs b/src/Runtime/Internal/Execution/MutationExecutionPipeline.cs
index dfb3194..3f7ef40 100644
--- a/src/Runtime/Internal/Execution/MutationExecutionPipeline.cs
+++ b/src/Runtime/Internal/Execution/MutationExecutionPipeline.cs
@@ -13,9 +13,9 @@ namespace ModularityKit.Mutator.Runtime.Internal.Execution;
///
///
///
-/// The pipeline runs policy evaluation, validation, mode-specific execution, and
+/// The pipeline runs policy evaluation, validation, mode specific execution, and
/// outcome processing in fixed order. Each stage can short circuit:
-/// a blocking policy decision skips execution; a validation failure skips execution;
+/// blocking policy decision skips execution validation failure skips execution;
/// otherwise the mutation runs through the configured mode runner and the result
/// is finalized by the outcome processor.
///
@@ -81,9 +81,9 @@ await _interceptorPipeline.OnBeforeMutationAsync(
.ConfigureAwait(false);
var validationFailureResult = ValidateIfRequired(executionContext);
- if (validationFailureResult is not null)
+ if (validationFailureResult is MutationResult failure)
return await _outcomeProcessor
- .HandleValidationFailureAsync(executionContext, validationFailureResult)
+ .HandleValidationFailureAsync(executionContext, failure)
.ConfigureAwait(false);
var mutationResult = await _modeRunner.ExecuteAsync(executionContext).ConfigureAwait(false);