diff --git a/Docs/Roadmap.md b/Docs/Roadmap.md
index b2724fe..a458b23 100644
--- a/Docs/Roadmap.md
+++ b/Docs/Roadmap.md
@@ -201,7 +201,7 @@ Why this matters:
### 2. Governance-Aware Policy Composition
-Add composition primitives for complex policy sets.
+Composition primitives for complex policy sets are now available in the core policy abstractions.
Scope:
diff --git a/Examples/Core/FeatureFlags/Policies/FeatureFlagGovernancePolicies.cs b/Examples/Core/FeatureFlags/Policies/FeatureFlagGovernancePolicies.cs
new file mode 100644
index 0000000..10ad98d
--- /dev/null
+++ b/Examples/Core/FeatureFlags/Policies/FeatureFlagGovernancePolicies.cs
@@ -0,0 +1,24 @@
+using FeatureFlags.State;
+using ModularityKit.Mutator.Abstractions.Policies;
+
+namespace FeatureFlags.Policies;
+
+///
+/// Reusable policy compositions for sensitive feature flag changes.
+///
+internal static class FeatureFlagGovernancePolicies
+{
+ ///
+ /// Composed governance policy set for critical feature flag changes.
+ ///
+ public static IMutationPolicy CriticalChanges() =>
+ PolicyComposition.AllOf(
+ name: "CriticalFeatureFlagGovernance",
+ policies:
+ [
+ new BusinessHoursPolicy(),
+ new RequireTwoManApprovalPolicy()
+ ],
+ priority: 200,
+ description: "Requires business-hours execution and two-man approval for critical feature flag changes.");
+}
diff --git a/Examples/Core/FeatureFlags/Program.cs b/Examples/Core/FeatureFlags/Program.cs
index d8fe1c8..808c8d4 100644
--- a/Examples/Core/FeatureFlags/Program.cs
+++ b/Examples/Core/FeatureFlags/Program.cs
@@ -17,8 +17,7 @@ private static async Task Main()
var provider = services.BuildServiceProvider();
var engine = provider.GetRequiredService();
- //engine.RegisterPolicy(new BusinessHoursPolicy());
- engine.RegisterPolicy(new RequireTwoManApprovalPolicy());
+ engine.RegisterPolicy(FeatureFlagGovernancePolicies.CriticalChanges());
Console.WriteLine("=== ModularityKit.Mutators - Complete Example ===\n");
@@ -41,4 +40,4 @@ private static async Task Main()
Console.WriteLine($" Median execution time: {stats.MedianExecutionTime.TotalMilliseconds:F2} ms");
Console.WriteLine($" P95 execution time: {stats.P95ExecutionTime.TotalMilliseconds:F2} ms");
}
-}
\ No newline at end of file
+}
diff --git a/Examples/Core/FeatureFlags/README.md b/Examples/Core/FeatureFlags/README.md
index dd682dc..7b4b06f 100644
--- a/Examples/Core/FeatureFlags/README.md
+++ b/Examples/Core/FeatureFlags/README.md
@@ -31,6 +31,7 @@ The example covers three workflows:
- [`Mutations/DisableFeatureMutation.cs`](Mutations/DisableFeatureMutation.cs)
- [`Policies/BusinessHoursPolicy.cs`](Policies/BusinessHoursPolicy.cs)
- [`Policies/RequireTwoManApprovalPolicy.cs`](Policies/RequireTwoManApprovalPolicy.cs)
+- [`Policies/FeatureFlagGovernancePolicies.cs`](Policies/FeatureFlagGovernancePolicies.cs)
- [`Scenarios/EnableNewCheckoutScenario.cs`](Scenarios/EnableNewCheckoutScenario.cs)
- [`Scenarios/DisableLegacyCheckoutScenario.cs`](Scenarios/DisableLegacyCheckoutScenario.cs)
- [`Scenarios/BatchFeatureToggleScenario.cs`](Scenarios/BatchFeatureToggleScenario.cs)
@@ -41,7 +42,7 @@ The example covers three workflows:
1. registers the engine with strict options
2. resolves `IMutationEngine`
-3. registers `RequireTwoManApprovalPolicy`
+3. registers the composed `CriticalFeatureFlagGovernance` policy set
4. runs the example scenarios
5. prints history for the main state
6. prints engine statistics
@@ -84,6 +85,16 @@ It demonstrates:
Use it as reference if you want to restrict rollout windows.
+### Composed governance set
+
+[`FeatureFlagGovernancePolicies.CriticalChanges`](Policies/FeatureFlagGovernancePolicies.cs) combines business-hours restrictions and two-man approval into one reusable policy set.
+
+It demonstrates:
+
+- `PolicyComposition.AllOf(...)`
+- explicit policy merge behavior
+- registering one composed policy instead of multiple hand-wired policy classes
+
## Scenarios
### Enable new checkout
diff --git a/Examples/Core/PolicyComposition/Mutations/SubmitReleaseMutation.cs b/Examples/Core/PolicyComposition/Mutations/SubmitReleaseMutation.cs
new file mode 100644
index 0000000..fd1baa0
--- /dev/null
+++ b/Examples/Core/PolicyComposition/Mutations/SubmitReleaseMutation.cs
@@ -0,0 +1,48 @@
+using ModularityKit.Mutator.Abstractions.Changes;
+using ModularityKit.Mutator.Abstractions.Context;
+using ModularityKit.Mutator.Abstractions.Engine;
+using ModularityKit.Mutator.Abstractions.Results;
+using PolicyComposition.State;
+
+namespace PolicyComposition.Mutations;
+
+///
+/// Submits a release and carries the governance metadata consumed by the policies.
+///
+///
+/// The mutation itself only moves the release into the submitted stage. The
+/// interesting governance behavior lives in the composed policies, which read the
+/// approval count, emergency flag, and target environment from the mutation
+/// context metadata.
+///
+internal sealed class SubmitReleaseMutation(
+ string releaseName,
+ int approvals,
+ bool emergency,
+ string environment) : MutationBase(
+ CreateIntent(
+ operationName: "SubmitRelease",
+ category: "ReleaseGovernance",
+ description: "Submit a release through composed governance policies"),
+ MutationContext.User("release-manager", "Release Manager", "Release composition example")
+ with
+ {
+ StateId = releaseName,
+ Metadata = new Dictionary
+ {
+ ["approvals"] = approvals,
+ ["emergency"] = emergency,
+ ["environment"] = environment
+ }
+ })
+{
+ ///
+ /// Marks the release as submitted before policy composition evaluates it.
+ ///
+ /// The current release state.
+ /// A mutation result that moves the release into the submitted stage.
+ public override MutationResult Apply(ReleaseGateState state)
+ => Success(
+ state with { Stage = "Submitted" },
+ StateChange.Modified("Stage", state.Stage, "Submitted"));
+}
diff --git a/Examples/Core/PolicyComposition/Policies/Approval/AddAuditTrailPolicy.cs b/Examples/Core/PolicyComposition/Policies/Approval/AddAuditTrailPolicy.cs
new file mode 100644
index 0000000..5558abb
--- /dev/null
+++ b/Examples/Core/PolicyComposition/Policies/Approval/AddAuditTrailPolicy.cs
@@ -0,0 +1,56 @@
+using ModularityKit.Mutator.Abstractions.Effects;
+using ModularityKit.Mutator.Abstractions.Engine;
+using ModularityKit.Mutator.Abstractions.Policies;
+using PolicyComposition.State;
+
+namespace PolicyComposition.Policies.Approval;
+
+///
+/// Appends audit trail information to an already allowed approval gate decision.
+///
+///
+/// This policy does not block or change the release state. It exists to show how
+/// composed policies can contribute side effects and metadata independently of
+/// the policy that made the main business decision.
+///
+internal sealed class AddAuditTrailPolicy : IMutationPolicy
+{
+ ///
+ /// Stable policy identifier used in composition metadata and diagnostics.
+ ///
+ public string Name => "AddAuditTrail";
+
+ ///
+ /// Medium priority so this policy can be grouped with the approval gate it decorates.
+ ///
+ public int Priority => 200;
+
+ ///
+ /// Describes the audit trail side effect this policy adds to the composed result.
+ ///
+ public string Description => "Adds audit metadata and a notification side effect.";
+
+ ///
+ /// Emits one audit side effect and a simple metadata flag.
+ ///
+ /// The mutation being evaluated.
+ /// The current release state.
+ /// An allowed decision with audit metadata and a single side effect.
+ public PolicyDecision Evaluate(IMutation mutation, ReleaseGateState state)
+ => new()
+ {
+ IsAllowed = true,
+ PolicyName = Name,
+ Modifications = new Dictionary
+ {
+ ["SideEffects"] = new[]
+ {
+ SideEffect.Create("audit", $"Release {state.ReleaseName} passed the composed approval gate.")
+ }
+ },
+ Metadata = new Dictionary
+ {
+ ["auditTrail"] = "enabled"
+ }
+ };
+}
diff --git a/Examples/Core/PolicyComposition/Policies/Approval/RequireApprovalsPolicy.cs b/Examples/Core/PolicyComposition/Policies/Approval/RequireApprovalsPolicy.cs
new file mode 100644
index 0000000..ba8b5e1
--- /dev/null
+++ b/Examples/Core/PolicyComposition/Policies/Approval/RequireApprovalsPolicy.cs
@@ -0,0 +1,82 @@
+using ModularityKit.Mutator.Abstractions.Effects;
+using ModularityKit.Mutator.Abstractions.Engine;
+using ModularityKit.Mutator.Abstractions.Policies;
+using PolicyComposition.State;
+
+namespace PolicyComposition.Policies.Approval;
+
+///
+/// Blocks release promotion until the expected approval threshold is present.
+///
+///
+/// The policy reads the approval count from mutation metadata, so the example can
+/// show how governance input travels alongside the mutation itself.
+/// When the threshold is met, the policy marks the release as approved and emits
+/// an audit side effect. When it is not met, the policy returns a requirement and
+/// error severity so the composed result can surface the missing approval.
+///
+internal sealed class RequireApprovalsPolicy : IMutationPolicy
+{
+ ///
+ /// Stable policy identifier used in diagnostics and composition metadata.
+ ///
+ public string Name => "RequireApprovals";
+
+ ///
+ /// Higher than the audit only policy, so approval gating is evaluated first.
+ ///
+ public int Priority => 300;
+
+ ///
+ /// Explains the minimum-approval requirement that this policy enforces.
+ ///
+ public string Description => "Requires at least two approvals before the release can proceed.";
+
+ ///
+ /// Reads the approval count from mutation metadata and either allows or blocks the release.
+ ///
+ /// The mutation carrying government metadata.
+ /// The current release state.
+ ///
+ /// An allowed decision when approvals are enough, otherwise a blocking
+ /// decision with an approval requirement and error severity.
+ ///
+ public PolicyDecision Evaluate(IMutation mutation, ReleaseGateState state)
+ {
+ var approvals = GetInt32(mutation.Context.Metadata, "approvals");
+
+ return approvals >= 2
+ ? new PolicyDecision
+ {
+ IsAllowed = true,
+ PolicyName = Name,
+ Modifications = new Dictionary
+ {
+ ["State"] = state with { Stage = "Approved" },
+ ["SideEffect"] = SideEffect.Create("audit", $"Release approved with {approvals} approvals.")
+ },
+ Metadata = new Dictionary
+ {
+ ["approvalCount"] = approvals
+ }
+ }
+ : new PolicyDecision
+ {
+ IsAllowed = false,
+ PolicyName = Name,
+ Severity = PolicyDecisionSeverity.Error,
+ Reason = $"Release requires at least two approvals; found {approvals}.",
+ Requirements =
+ [
+ PolicyRequirement.Approval("release-manager", "Two approvals are required before promotion.")
+ ],
+ Metadata = new Dictionary
+ {
+ ["approvalCount"] = approvals
+ }
+ };
+ }
+
+ private static int GetInt32(IReadOnlyDictionary metadata, string key)
+ => metadata.TryGetValue(key, out var value) && value is int number ? number : 0;
+}
diff --git a/Examples/Core/PolicyComposition/Policies/Deployment/DefaultDeploymentPolicy.cs b/Examples/Core/PolicyComposition/Policies/Deployment/DefaultDeploymentPolicy.cs
new file mode 100644
index 0000000..93ba654
--- /dev/null
+++ b/Examples/Core/PolicyComposition/Policies/Deployment/DefaultDeploymentPolicy.cs
@@ -0,0 +1,55 @@
+using ModularityKit.Mutator.Abstractions.Effects;
+using ModularityKit.Mutator.Abstractions.Engine;
+using ModularityKit.Mutator.Abstractions.Policies;
+using PolicyComposition.State;
+
+namespace PolicyComposition.Policies.Deployment;
+
+///
+/// Supplies the fallback deployment path for non production releases.
+///
+///
+/// The policy does not inspect environment-specific risk beyond the fact that it
+/// is the default branch in the composed deployment gate. It moves the release to
+/// a deploy-ready stage and contributes both a side effect and metadata so the
+/// composition result stays auditable.
+///
+internal sealed class DefaultDeploymentPolicy : IMutationPolicy
+{
+ ///
+ /// Policy identifier used in composition metadata.
+ ///
+ public string Name => "DefaultDeployment";
+
+ ///
+ /// Lowest priority in the deployment composition, so it acts as the fallback branch.
+ ///
+ public int Priority => 100;
+
+ ///
+ /// Describes the fallback deployment behavior.
+ ///
+ public string Description => "Default non-production deployment path.";
+
+ ///
+ /// Moves the release into the ready for deployment stage and emits an audit trace.
+ ///
+ /// The mutation being evaluated.
+ /// The current release state.
+ /// An allowed decision that marks the release ready for deployment.
+ public PolicyDecision Evaluate(IMutation mutation, ReleaseGateState state)
+ => new()
+ {
+ IsAllowed = true,
+ PolicyName = Name,
+ Modifications = new Dictionary
+ {
+ ["State"] = state with { Stage = "ReadyForDeploy" },
+ ["SideEffect"] = SideEffect.Create("audit", "Default deployment path selected.")
+ },
+ Metadata = new Dictionary
+ {
+ ["deploymentPath"] = "default"
+ }
+ };
+}
diff --git a/Examples/Core/PolicyComposition/Policies/Deployment/ProductionGuardPolicy.cs b/Examples/Core/PolicyComposition/Policies/Deployment/ProductionGuardPolicy.cs
new file mode 100644
index 0000000..d530610
--- /dev/null
+++ b/Examples/Core/PolicyComposition/Policies/Deployment/ProductionGuardPolicy.cs
@@ -0,0 +1,53 @@
+using ModularityKit.Mutator.Abstractions.Engine;
+using ModularityKit.Mutator.Abstractions.Policies;
+using PolicyComposition.State;
+
+namespace PolicyComposition.Policies.Deployment;
+
+///
+/// Stops production releases before fallback policies can be applied.
+///
+///
+/// This policy exists to demonstrate the priority composition mode. It evaluates
+/// first, looks at the environment metadata, and returns a decisive denial when
+/// the release targets production. For any other environment, it allows the
+/// composition to continue to the next policy.
+///
+internal sealed class ProductionGuardPolicy : IMutationPolicy
+{
+ ///
+ /// Policy identifier used in decision metadata.
+ ///
+ public string Name => "ProductionGuard";
+
+ ///
+ /// Highest priority in the deployment gate, so the production check runs first.
+ ///
+ public int Priority => 500;
+
+ ///
+ /// Summarizes the production protection rule enforced by this policy.
+ ///
+ public string Description => "Blocks production releases before lower priority policies can run.";
+
+ ///
+ /// Checks the environment metadata and either denies production or allows fallback.
+ ///
+ /// The mutation being evaluated.
+ /// The current release state.
+ ///
+ /// A critical denial for production deployments, or an allowed decision that
+ /// hands control to lower-priority policies.
+ ///
+ public PolicyDecision Evaluate(IMutation mutation, ReleaseGateState state)
+ {
+ var environment = GetString(mutation.Context.Metadata, "environment");
+
+ return string.Equals(environment, "production", StringComparison.OrdinalIgnoreCase)
+ ? PolicyDecision.DenyCritical("Production releases require a dedicated change window.", Name)
+ : PolicyDecision.Allow(Name, $"Environment '{environment}' falls through to the next policy.");
+ }
+
+ private static string GetString(IReadOnlyDictionary metadata, string key)
+ => metadata.TryGetValue(key, out var value) && value is string text ? text : string.Empty;
+}
\ No newline at end of file
diff --git a/Examples/Core/PolicyComposition/Policies/Emergency/ApprovalFallbackPolicy.cs b/Examples/Core/PolicyComposition/Policies/Emergency/ApprovalFallbackPolicy.cs
new file mode 100644
index 0000000..b70d7b5
--- /dev/null
+++ b/Examples/Core/PolicyComposition/Policies/Emergency/ApprovalFallbackPolicy.cs
@@ -0,0 +1,63 @@
+using ModularityKit.Mutator.Abstractions.Effects;
+using ModularityKit.Mutator.Abstractions.Engine;
+using ModularityKit.Mutator.Abstractions.Policies;
+using PolicyComposition.State;
+
+namespace PolicyComposition.Policies.Emergency;
+
+///
+/// Supplies the approval-based fallback branch for the emergency gate.
+///
+///
+/// This policy is only intended to make the emergency composition reusable when
+/// the explicit override is unavailable. It checks the same approval count used by
+/// the standard approval policy, but it maps the successful outcome to a different
+/// release stage so the example can show branch selection in the merged decision.
+///
+internal sealed class ApprovalFallbackPolicy : IMutationPolicy
+{
+ ///
+ /// Policy identifier used in composed diagnostics.
+ ///
+ public string Name => "ApprovalFallback";
+
+ ///
+ /// Lower than the override branch but high enough to participate in emergency gating.
+ ///
+ public int Priority => 200;
+
+ ///
+ /// Describes the approval-based fallback path.
+ ///
+ public string Description => "Fallback branch that allows the release once approvals exist.";
+
+ ///
+ /// Uses the approval count to decide whether the emergency fallback can proceed.
+ ///
+ /// The mutation being evaluated.
+ /// The current release state.
+ ///
+ /// An allowed decision that advances the release through the fallback stage
+ /// or a blocking decision when approvals are missing.
+ ///
+ public PolicyDecision Evaluate(IMutation mutation, ReleaseGateState state)
+ {
+ var approvals = GetInt32(mutation.Context.Metadata, "approvals");
+
+ return approvals >= 2
+ ? new PolicyDecision
+ {
+ IsAllowed = true,
+ PolicyName = Name,
+ Modifications = new Dictionary
+ {
+ ["State"] = state with { Stage = "ApprovedViaFallback" },
+ ["SideEffect"] = SideEffect.Create("audit", "Fallback approval branch selected.")
+ }
+ }
+ : PolicyDecision.Deny($"Fallback approval branch rejected the release; found {approvals} approvals.", Name);
+ }
+
+ private static int GetInt32(IReadOnlyDictionary metadata, string key)
+ => metadata.TryGetValue(key, out var value) && value is int number ? number : 0;
+}
diff --git a/Examples/Core/PolicyComposition/Policies/Emergency/EmergencyOverridePolicy.cs b/Examples/Core/PolicyComposition/Policies/Emergency/EmergencyOverridePolicy.cs
new file mode 100644
index 0000000..9656915
--- /dev/null
+++ b/Examples/Core/PolicyComposition/Policies/Emergency/EmergencyOverridePolicy.cs
@@ -0,0 +1,67 @@
+using ModularityKit.Mutator.Abstractions.Effects;
+using ModularityKit.Mutator.Abstractions.Engine;
+using ModularityKit.Mutator.Abstractions.Policies;
+using PolicyComposition.State;
+
+namespace PolicyComposition.Policies.Emergency;
+
+///
+/// Enables a hard emergency override when the request explicitly asks for it.
+///
+///
+/// This policy demonstrates a decisive allow-or-deny branch inside an AnyOf
+/// composition. It reads the emergency flag from mutation metadata, and when the
+/// flag is present, it promotes the release to an emergency-approved stage and
+/// adds a critical side effect for audit visibility.
+///
+internal sealed class EmergencyOverridePolicy : IMutationPolicy
+{
+ ///
+ /// Policy identifier used in metadata and logs.
+ ///
+ public string Name => "EmergencyOverride";
+
+ ///
+ /// Higher than the fallback branch, so the explicit override wins first.
+ ///
+ public int Priority => 400;
+
+ ///
+ /// Describes the emergency override behavior.
+ ///
+ public string Description => "Allows an emergency release override when requested explicitly.";
+
+ ///
+ /// Reads the emergency flag from metadata and either approves the override or denies it.
+ ///
+ /// The mutation being evaluated.
+ /// The current release state.
+ ///
+ /// An allowed override decision when the emergency flag is set, otherwise a
+ /// standard denial that allows the AnyOf composition to keep searching.
+ ///
+ public PolicyDecision Evaluate(IMutation mutation, ReleaseGateState state)
+ {
+ var emergency = GetBool(mutation.Context.Metadata, "emergency");
+
+ return emergency
+ ? new PolicyDecision
+ {
+ IsAllowed = true,
+ PolicyName = Name,
+ Modifications = new Dictionary
+ {
+ ["State"] = state with { Stage = "EmergencyApproved" },
+ ["SideEffect"] = SideEffect.Critical("release.override", "Emergency override applied.")
+ },
+ Metadata = new Dictionary
+ {
+ ["override"] = true
+ }
+ }
+ : PolicyDecision.Deny("Emergency override not requested.", Name);
+ }
+
+ private static bool GetBool(IReadOnlyDictionary metadata, string key)
+ => metadata.TryGetValue(key, out var value) && value is true;
+}
diff --git a/Examples/Core/PolicyComposition/Policies/ReleaseGovernancePolicies.cs b/Examples/Core/PolicyComposition/Policies/ReleaseGovernancePolicies.cs
new file mode 100644
index 0000000..2be809a
--- /dev/null
+++ b/Examples/Core/PolicyComposition/Policies/ReleaseGovernancePolicies.cs
@@ -0,0 +1,109 @@
+using ModularityKit.Mutator.Abstractions.Policies;
+using PolicyComposition.Policies.Approval;
+using PolicyComposition.Policies.Deployment;
+using PolicyComposition.Policies.Emergency;
+using PolicyComposition.Policies.Shared;
+using PolicyComposition.State;
+
+namespace PolicyComposition.Policies;
+
+///
+/// Named composed policy sets used by the release governance example.
+///
+///
+/// This class acts as the composition root for the example. It keeps the concrete
+/// child policies isolated from the scenarios and exposes reusable policy sets that
+/// demonstrate the three composition modes:
+///
+/// AllOf for mandatory approval gates.
+/// AnyOf for emergency fallback flows.
+/// Priority for deterministic ordered selection.
+///
+///
+internal static class ReleaseGovernancePolicies
+{
+ ///
+ /// Builds the standard approval gate used for release promotion.
+ ///
+ ///
+ /// The gate combines:
+ ///
+ /// to enforce the approval threshold.
+ /// to add traceability once the gate succeeds.
+ ///
+ /// The composed policy only succeeds when both child policies can contribute
+ /// without a conflict and the approval requirement is satisfied.
+ ///
+ public static IMutationPolicy ApprovalGate() =>
+ ModularityKit.Mutator.Abstractions.Policies.PolicyComposition.AllOf(
+ "ReleaseApprovalGate",
+ [
+ new RequireApprovalsPolicy(),
+ new AddAuditTrailPolicy()
+ ],
+ priority: 500,
+ description: "Requires two approvals and adds audit metadata.");
+
+ ///
+ /// Builds the emergency gate that prefers an explicit override and falls back to approvals.
+ ///
+ ///
+ /// The gate combines:
+ ///
+ /// for explicit emergency approval.
+ /// for the approval-based fallback path.
+ ///
+ /// The first allowed branch wins, which makes the result deterministic while
+ /// still allowing a reusable emergency path to be expressed in one place.
+ ///
+ public static IMutationPolicy EmergencyGate() =>
+ ModularityKit.Mutator.Abstractions.Policies.PolicyComposition.AnyOf(
+ "ReleaseEmergencyGate",
+ [
+ new EmergencyOverridePolicy(),
+ new ApprovalFallbackPolicy()
+ ],
+ priority: 400,
+ description: "Chooses the emergency override branch when available.");
+
+ ///
+ /// Builds the deployment gate that evaluates production checks before the default path.
+ ///
+ ///
+ /// The gate combines:
+ ///
+ /// to block production releases early.
+ /// as the fallback branch for non-production releases.
+ ///
+ /// Because this is a priority composition, the first decisive policy short-circuits
+ /// the rest of the chain.
+ ///
+ public static IMutationPolicy DeploymentGate() =>
+ ModularityKit.Mutator.Abstractions.Policies.PolicyComposition.Priority(
+ "ReleaseDeploymentGate",
+ [
+ new ProductionGuardPolicy(),
+ new DefaultDeploymentPolicy()
+ ],
+ priority: 300,
+ description: "Uses a production guard first, then falls back to the default deployment path.");
+
+ ///
+ /// Builds a composed gate that intentionally conflicts on the owner field.
+ ///
+ ///
+ /// The gate composes two instances that target the
+ /// same state field with different values. The example uses this to show that
+ /// the composition layer detects conflicting mutation results explicitly instead
+ /// of silently picking one branch.
+ ///
+ public static IMutationPolicy ConflictingOwnerGate() =>
+ ModularityKit.Mutator.Abstractions.Policies.PolicyComposition.AllOf(
+ "ReleaseOwnerConflictGate",
+ [
+ new SetOwnerPolicy("platform"),
+ new SetOwnerPolicy("security")
+ ],
+ priority: 200,
+ description: "Demonstrates explicit conflict handling when two policies set the same field differently.");
+}
diff --git a/Examples/Core/PolicyComposition/Policies/Shared/SetOwnerPolicy.cs b/Examples/Core/PolicyComposition/Policies/Shared/SetOwnerPolicy.cs
new file mode 100644
index 0000000..4471111
--- /dev/null
+++ b/Examples/Core/PolicyComposition/Policies/Shared/SetOwnerPolicy.cs
@@ -0,0 +1,48 @@
+using ModularityKit.Mutator.Abstractions.Engine;
+using ModularityKit.Mutator.Abstractions.Policies;
+using PolicyComposition.State;
+
+namespace PolicyComposition.Policies.Shared;
+
+///
+/// Forces the release owner to fixed value, so conflict handling is easy to observe.
+///
+///
+/// The policy only touches one field. That makes it useful for the conflict
+/// example because two instances of the same class can be composed and produce a
+/// deterministic clash when they disagree on the owner.
+///
+internal sealed class SetOwnerPolicy(string owner) : IMutationPolicy
+{
+ ///
+ /// Policy identifier that includes the target owner value.
+ ///
+ public string Name => $"SetOwner[{owner}]";
+
+ ///
+ /// Middle priority because the policy is only used as a composed leaf rule.
+ ///
+ public int Priority => 150;
+
+ ///
+ /// Describes the owner assignment performed by the policy.
+ ///
+ public string Description => "Sets the release owner.";
+
+ ///
+ /// Updates only the owner field and leaves the rest of the state unchanged.
+ ///
+ /// The mutation being evaluated.
+ /// The current release state.
+ /// An allowed decision that changes only the owner field.
+ public PolicyDecision Evaluate(IMutation mutation, ReleaseGateState state)
+ => new()
+ {
+ IsAllowed = true,
+ PolicyName = Name,
+ Modifications = new Dictionary
+ {
+ ["State"] = state with { Owner = owner }
+ }
+ };
+}
diff --git a/Examples/Core/PolicyComposition/PolicyComposition.csproj b/Examples/Core/PolicyComposition/PolicyComposition.csproj
new file mode 100644
index 0000000..e9b36cc
--- /dev/null
+++ b/Examples/Core/PolicyComposition/PolicyComposition.csproj
@@ -0,0 +1,17 @@
+
+
+
+ Exe
+ net10.0
+ enable
+ enable
+ true
+ $(NoWarn);1591
+ false
+
+
+
+
+
+
+
diff --git a/Examples/Core/PolicyComposition/Program.cs b/Examples/Core/PolicyComposition/Program.cs
new file mode 100644
index 0000000..3b22141
--- /dev/null
+++ b/Examples/Core/PolicyComposition/Program.cs
@@ -0,0 +1,22 @@
+using PolicyComposition.Scenarios;
+
+namespace PolicyCompositionExample;
+
+///
+/// Entry point for the policy composition example.
+///
+internal static class Program
+{
+ ///
+ /// Runs the four sample scenarios for composed governance policies.
+ ///
+ private static async Task Main()
+ {
+ Console.WriteLine("=== Policy Composition Example ===\n");
+
+ await AllOfScenario.Run();
+ await AnyOfScenario.Run();
+ await PriorityScenario.Run();
+ await ConflictScenario.Run();
+ }
+}
diff --git a/Examples/Core/PolicyComposition/README.md b/Examples/Core/PolicyComposition/README.md
new file mode 100644
index 0000000..f8f261f
--- /dev/null
+++ b/Examples/Core/PolicyComposition/README.md
@@ -0,0 +1,55 @@
+# PolicyComposition
+
+This example shows how to compose multiple governance policies into reusable sets.
+
+It is the sample to read when you want to see `AllOf`, `AnyOf`, and priority-based composition
+without pushing the rules into one large handwritten policy class.
+
+## Domain
+
+The sample uses a simple release gate model:
+
+- a release has a name, stage, and owner
+- policy metadata carries approvals, environment, and emergency flags
+- composed policies decide whether the release stays blocked, becomes approved, or gets an emergency path
+
+## What this example demonstrates
+
+- reusable composed policy sets
+- explicit merge rules for severity, requirements, side effects, and metadata
+- `AllOf`, `AnyOf`, and priority-based composition
+- deterministic conflict handling when two policies modify the same value
+
+## Project structure
+
+- [`Program.cs`](Program.cs)
+- [`PolicyComposition.csproj`](PolicyComposition.csproj)
+- [`State/ReleaseGateState.cs`](State/ReleaseGateState.cs)
+- [`Mutations/SubmitReleaseMutation.cs`](Mutations/SubmitReleaseMutation.cs)
+- [`Policies/ReleaseGovernancePolicies.cs`](Policies/ReleaseGovernancePolicies.cs)
+- [`Policies/Approval/RequireApprovalsPolicy.cs`](Policies/Approval/RequireApprovalsPolicy.cs)
+- [`Policies/Approval/AddAuditTrailPolicy.cs`](Policies/Approval/AddAuditTrailPolicy.cs)
+- [`Policies/Emergency/EmergencyOverridePolicy.cs`](Policies/Emergency/EmergencyOverridePolicy.cs)
+- [`Policies/Emergency/ApprovalFallbackPolicy.cs`](Policies/Emergency/ApprovalFallbackPolicy.cs)
+- [`Policies/Deployment/ProductionGuardPolicy.cs`](Policies/Deployment/ProductionGuardPolicy.cs)
+- [`Policies/Deployment/DefaultDeploymentPolicy.cs`](Policies/Deployment/DefaultDeploymentPolicy.cs)
+- [`Policies/Shared/SetOwnerPolicy.cs`](Policies/Shared/SetOwnerPolicy.cs)
+- [`Scenarios/AllOfScenario.cs`](Scenarios/AllOfScenario.cs)
+- [`Scenarios/AnyOfScenario.cs`](Scenarios/AnyOfScenario.cs)
+- [`Scenarios/PriorityScenario.cs`](Scenarios/PriorityScenario.cs)
+- [`Scenarios/ConflictScenario.cs`](Scenarios/ConflictScenario.cs)
+
+## Run
+
+```bash
+dotnet run --project Examples/Core/PolicyComposition/PolicyComposition.csproj
+```
+
+## Expected output
+
+You should see:
+
+- an `AllOf` release gate that merges state updates, metadata, and audit side effects
+- an `AnyOf` gate that selects the first allowed branch
+- a priority-based gate that prefers the first decisive policy
+- a conflict example that raises `PolicyCompositionConflictException`
diff --git a/Examples/Core/PolicyComposition/Scenarios/AllOfScenario.cs b/Examples/Core/PolicyComposition/Scenarios/AllOfScenario.cs
new file mode 100644
index 0000000..cc794a6
--- /dev/null
+++ b/Examples/Core/PolicyComposition/Scenarios/AllOfScenario.cs
@@ -0,0 +1,70 @@
+using ModularityKit.Mutator.Abstractions.Policies;
+using PolicyComposition.Mutations;
+using PolicyComposition.Policies;
+using PolicyComposition.State;
+
+namespace PolicyComposition.Scenarios;
+
+///
+/// Demonstrates the AllOf composition mode for reusable governance gates.
+///
+///
+/// This scenario is intentionally small: it creates a release state, builds a
+/// release submission mutation, evaluates the approval gate, and prints the
+/// composed result. The goal is to show how the merged decision contains the
+/// final state, audit side effects, and composition metadata.
+///
+internal static class AllOfScenario
+{
+ ///
+ /// Evaluates the approval gate against a sample release submission.
+ ///
+ ///
+ /// The mutation carries approval count, emergency flag, and environment data
+ /// through its execution context so the composed policies can make their own
+ /// decisions without needing extra helper state.
+ ///
+ public static async Task Run()
+ {
+ var state = new ReleaseGateState("release-42", "Draft", "platform");
+ var mutation = new SubmitReleaseMutation(
+ releaseName: state.ReleaseName,
+ approvals: 2,
+ emergency: false,
+ environment: "staging");
+
+ var decision = await ReleaseGovernancePolicies.ApprovalGate().EvaluateAsync(mutation, state);
+
+ WriteDecision(decision);
+ }
+
+ ///
+ /// Writes the important parts of the composed decision to the console.
+ ///
+ /// The composed policy decision returned by the gate.
+ private static void WriteDecision(PolicyDecision decision)
+ {
+ Console.WriteLine("[AnyOf] Reusable override gate");
+
+ Console.WriteLine($" allowed: {decision.IsAllowed}");
+ Console.WriteLine($" reason: {decision.Reason}");
+ Console.WriteLine($" mode: {decision.Metadata!["PolicyComposition.Mode"]}");
+
+ if (decision.Modifications is not null &&
+ decision.Modifications.TryGetValue("State", out var value) &&
+ value is ReleaseGateState state)
+ {
+ Console.WriteLine($" stage: {state.Stage}");
+ Console.WriteLine($" owner: {state.Owner}");
+ }
+
+ if (decision.Modifications is not null &&
+ decision.Modifications.TryGetValue("SideEffects", out var sideEffects) &&
+ sideEffects is IEnumerable