diff --git a/src/Modules/IdentityAccess/ManageIdentityAndRoles/Domain/IdentityState.cs b/src/Modules/IdentityAccess/ManageIdentityAndRoles/Domain/IdentityState.cs
new file mode 100644
index 00000000..f86fd5fc
--- /dev/null
+++ b/src/Modules/IdentityAccess/ManageIdentityAndRoles/Domain/IdentityState.cs
@@ -0,0 +1,115 @@
+namespace KArtSell.Modules.IdentityAccess.ManageIdentityAndRoles.Domain;
+
+///
+/// Identity lifecycle state machine (AEG-VS-01-03)
+/// Immutable value object for state transitions
+///
+public sealed record IdentityState
+{
+ public const string Undefined = "UNDEFINED";
+ public const string Active = "ACTIVE";
+ public const string RequiresMfaSetup = "REQUIRES_MFA_SETUP";
+ public const string MfaConfigured = "MFA_CONFIGURED";
+ public const string MfaSuspended = "MFA_SUSPENDED";
+ public const string Inactive = "INACTIVE";
+ public const string Revoked = "REVOKED";
+
+ private static readonly HashSet ValidStates =
+ [
+ Undefined, Active, RequiresMfaSetup, MfaConfigured, MfaSuspended, Inactive, Revoked
+ ];
+
+ public string Value { get; }
+
+ private IdentityState(string value)
+ {
+ if (!ValidStates.Contains(value))
+ throw new ArgumentException($"Invalid identity state: {value}", nameof(value));
+ Value = value;
+ }
+
+ // Factory methods
+ public static IdentityState CreateUndefined() => new(Undefined);
+ public static IdentityState CreateActive() => new(Active);
+ public static IdentityState RequireMfaSetup() => new(RequiresMfaSetup);
+ public static IdentityState MfaSetupComplete() => new(MfaConfigured);
+ public static IdentityState SuspendMfa() => new(MfaSuspended);
+ public static IdentityState Deactivate() => new(Inactive);
+ public static IdentityState Revoke() => new(Revoked);
+ public static IdentityState Parse(string value) => new(value);
+
+ // State transitions (immutable - return new state)
+ public IdentityState Register()
+ {
+ return Value switch
+ {
+ Undefined => new(Active),
+ _ => throw new InvalidOperationException($"Cannot register from {Value}")
+ };
+ }
+
+ public IdentityState RequestMfaSetup()
+ {
+ return Value switch
+ {
+ Active => new(RequiresMfaSetup),
+ _ => throw new InvalidOperationException($"Cannot request MFA setup from {Value}")
+ };
+ }
+
+ public IdentityState CompleteMfaSetup()
+ {
+ return Value switch
+ {
+ RequiresMfaSetup => new(MfaConfigured),
+ _ => throw new InvalidOperationException($"Cannot complete MFA setup from {Value}")
+ };
+ }
+
+ public IdentityState SuspendMfaTemporarily()
+ {
+ return Value switch
+ {
+ MfaConfigured => new(MfaSuspended),
+ _ => throw new InvalidOperationException($"Cannot suspend MFA from {Value}")
+ };
+ }
+
+ public IdentityState ResumeMfa()
+ {
+ return Value switch
+ {
+ MfaSuspended => new(MfaConfigured),
+ _ => throw new InvalidOperationException($"Cannot resume MFA from {Value}")
+ };
+ }
+
+ public IdentityState Deactivate()
+ {
+ return Value switch
+ {
+ Active or RequiresMfaSetup or MfaConfigured or MfaSuspended => new(Inactive),
+ _ => throw new InvalidOperationException($"Cannot deactivate from {Value}")
+ };
+ }
+
+ public IdentityState Revoke()
+ {
+ return Value switch
+ {
+ Inactive => new(Revoked),
+ _ => throw new InvalidOperationException($"Cannot revoke from {Value}")
+ };
+ }
+
+ // State queries
+ public bool IsActive() => Value == Active;
+ public bool IsMfaRequired() => Value is RequiresMfaSetup or MfaConfigured or MfaSuspended;
+ public bool IsMfaConfigured() => Value == MfaConfigured;
+ public bool IsInactive() => Value == Inactive;
+ public bool IsRevoked() => Value == Revoked;
+ public bool CanRegister() => Value == Undefined;
+ public bool CanReceiveRoles() => Value is Active or RequiresMfaSetup or MfaConfigured;
+
+ public override string ToString() => Value;
+}
diff --git a/src/Modules/IdentityAccess/ManageIdentityAndRoles/Domain/RoleAssignmentState.cs b/src/Modules/IdentityAccess/ManageIdentityAndRoles/Domain/RoleAssignmentState.cs
new file mode 100644
index 00000000..5466a496
--- /dev/null
+++ b/src/Modules/IdentityAccess/ManageIdentityAndRoles/Domain/RoleAssignmentState.cs
@@ -0,0 +1,106 @@
+namespace KArtSell.Modules.IdentityAccess.ManageIdentityAndRoles.Domain;
+
+///
+/// Role Assignment workflow state (Maker-Checker pattern)
+/// AEG-VS-01-03: Immutable value object for approval workflow
+///
+public sealed record RoleAssignmentState
+{
+ public const string PendingApproval = "PENDING_APPROVAL";
+ public const string ApprovedBy1 = "APPROVED_BY_1";
+ public const string ApprovedBy2 = "APPROVED_BY_2";
+ public const string Active = "ACTIVE";
+ public const string Expired = "EXPIRED";
+ public const string Revoked = "REVOKED";
+ public const string Rejected = "REJECTED";
+
+ private static readonly HashSet ValidStates =
+ [
+ PendingApproval, ApprovedBy1, ApprovedBy2, Active, Expired, Revoked, Rejected
+ ];
+
+ public string Value { get; }
+
+ private RoleAssignmentState(string value)
+ {
+ if (!ValidStates.Contains(value))
+ throw new ArgumentException($"Invalid role assignment state: {value}", nameof(value));
+ Value = value;
+ }
+
+ // Factory methods
+ public static RoleAssignmentState CreatePending() => new(PendingApproval);
+ public static RoleAssignmentState Activate() => new(Active);
+ public static RoleAssignmentState Expire() => new(Expired);
+ public static RoleAssignmentState Revoke() => new(Revoked);
+ public static RoleAssignmentState Reject() => new(Rejected);
+ public static RoleAssignmentState Parse(string value) => new(value);
+
+ // State transitions
+ public RoleAssignmentState ApproveByFirst()
+ {
+ return Value switch
+ {
+ PendingApproval => new(ApprovedBy1),
+ _ => throw new InvalidOperationException($"Cannot approve from {Value}")
+ };
+ }
+
+ public RoleAssignmentState ApproveBySecond()
+ {
+ return Value switch
+ {
+ ApprovedBy1 => new(ApprovedBy2),
+ _ => throw new InvalidOperationException($"Cannot approve second from {Value}")
+ };
+ }
+
+ public RoleAssignmentState ActivateAfterApproval()
+ {
+ return Value switch
+ {
+ ApprovedBy2 => new(Active),
+ _ => throw new InvalidOperationException($"Cannot activate from {Value}")
+ };
+ }
+
+ public RoleAssignmentState ExpireTimebound()
+ {
+ return Value switch
+ {
+ Active => new(Expired),
+ _ => throw new InvalidOperationException($"Cannot expire from {Value}")
+ };
+ }
+
+ public RoleAssignmentState RevokeActive()
+ {
+ return Value switch
+ {
+ Active or Expired => new(Revoked),
+ _ => throw new InvalidOperationException($"Cannot revoke from {Value}")
+ };
+ }
+
+ public RoleAssignmentState RejectRequest()
+ {
+ return Value switch
+ {
+ PendingApproval or ApprovedBy1 => new(Rejected),
+ _ => throw new InvalidOperationException($"Cannot reject from {Value}")
+ };
+ }
+
+ // State queries
+ public bool IsPending() => Value == PendingApproval;
+ public bool IsAwaitingSecondApproval() => Value == ApprovedBy1;
+ public bool IsApproved() => Value == ApprovedBy2;
+ public bool IsActive() => Value == Active;
+ public bool IsExpired() => Value == Expired;
+ public bool IsRevoked() => Value == Revoked;
+ public bool IsRejected() => Value == Rejected;
+ public bool CanApprove() => Value is PendingApproval or ApprovedBy1;
+ public bool RequiresSecondApproval() => Value == ApprovedBy1;
+
+ public override string ToString() => Value;
+}
diff --git a/tests/KArtSell.IdentityAccess.UnitTests/ManageIdentityAndRoles/IdentityStateTests.cs b/tests/KArtSell.IdentityAccess.UnitTests/ManageIdentityAndRoles/IdentityStateTests.cs
new file mode 100644
index 00000000..2549c475
--- /dev/null
+++ b/tests/KArtSell.IdentityAccess.UnitTests/ManageIdentityAndRoles/IdentityStateTests.cs
@@ -0,0 +1,142 @@
+using Xunit;
+using KArtSell.Modules.IdentityAccess.ManageIdentityAndRoles.Domain;
+
+namespace KArtSell.IdentityAccess.UnitTests.ManageIdentityAndRoles;
+
+///
+/// AEG-VS-01-03: Domain policy tests for Identity state machine
+/// Pure domain logic (no infrastructure dependencies)
+///
+public class IdentityStateTests
+{
+ [Fact]
+ public void CanTransitionFromUndefinedToActive()
+ {
+ var state = IdentityState.CreateUndefined();
+ var nextState = state.Register();
+
+ Assert.Equal(IdentityState.Active, nextState.Value);
+ }
+
+ [Fact]
+ public void CanTransitionFromActiveToRequiresMfaSetup()
+ {
+ var state = IdentityState.CreateActive();
+ var nextState = state.RequestMfaSetup();
+
+ Assert.Equal(IdentityState.RequiresMfaSetup, nextState.Value);
+ }
+
+ [Fact]
+ public void CanTransitionFromRequiresMfaSetupToMfaConfigured()
+ {
+ var state = IdentityState.Parse(IdentityState.RequiresMfaSetup);
+ var nextState = state.CompleteMfaSetup();
+
+ Assert.Equal(IdentityState.MfaConfigured, nextState.Value);
+ }
+
+ [Fact]
+ public void CanSuspendAndResumeMfa()
+ {
+ var state = IdentityState.Parse(IdentityState.MfaConfigured);
+ var suspended = state.SuspendMfaTemporarily();
+ var resumed = suspended.ResumeMfa();
+
+ Assert.Equal(IdentityState.MfaSuspended, suspended.Value);
+ Assert.Equal(IdentityState.MfaConfigured, resumed.Value);
+ }
+
+ [Fact]
+ public void CanDeactivateFromMultipleStates()
+ {
+ var states = new[]
+ {
+ IdentityState.CreateActive(),
+ IdentityState.Parse(IdentityState.RequiresMfaSetup),
+ IdentityState.Parse(IdentityState.MfaConfigured),
+ IdentityState.Parse(IdentityState.MfaSuspended)
+ };
+
+ foreach (var state in states)
+ {
+ var deactivated = state.Deactivate();
+ Assert.Equal(IdentityState.Inactive, deactivated.Value);
+ }
+ }
+
+ [Fact]
+ public void CanRevokeFromInactive()
+ {
+ var state = IdentityState.Parse(IdentityState.Inactive);
+ var revoked = state.Revoke();
+
+ Assert.Equal(IdentityState.Revoked, revoked.Value);
+ }
+
+ [Fact]
+ public void InvalidTransitionThrowsException()
+ {
+ var state = IdentityState.CreateUndefined();
+
+ Assert.Throws(() => state.RequestMfaSetup());
+ Assert.Throws(() => state.Deactivate());
+ }
+
+ [Fact]
+ public void CanQueryStateProperties()
+ {
+ var active = IdentityState.CreateActive();
+ Assert.True(active.IsActive());
+ Assert.False(active.IsInactive());
+
+ var mfaRequired = IdentityState.Parse(IdentityState.RequiresMfaSetup);
+ Assert.True(mfaRequired.IsMfaRequired());
+
+ var revoked = IdentityState.Parse(IdentityState.Revoked);
+ Assert.True(revoked.IsRevoked());
+ }
+
+ [Fact]
+ public void CanCheckCapabilities()
+ {
+ var undefined = IdentityState.CreateUndefined();
+ Assert.True(undefined.CanRegister());
+
+ var active = IdentityState.CreateActive();
+ Assert.True(active.CanReceiveRoles());
+ Assert.False(active.CanRegister());
+
+ var revoked = IdentityState.Parse(IdentityState.Revoked);
+ Assert.False(revoked.CanReceiveRoles());
+ }
+
+ [Theory]
+ [InlineData(IdentityState.Undefined)]
+ [InlineData(IdentityState.Active)]
+ [InlineData(IdentityState.RequiresMfaSetup)]
+ [InlineData(IdentityState.MfaConfigured)]
+ [InlineData(IdentityState.MfaSuspended)]
+ [InlineData(IdentityState.Inactive)]
+ [InlineData(IdentityState.Revoked)]
+ public void CanParseAllValidStates(string stateValue)
+ {
+ var state = IdentityState.Parse(stateValue);
+ Assert.Equal(stateValue, state.Value);
+ }
+
+ [Fact]
+ public void InvalidStateThrowsException()
+ {
+ Assert.Throws(() => IdentityState.Parse("INVALID_STATE"));
+ }
+
+ [Fact]
+ public void StateIsValueObject()
+ {
+ var state1 = IdentityState.CreateActive();
+ var state2 = IdentityState.Parse(IdentityState.Active);
+
+ Assert.Equal(state1, state2);
+ }
+}