diff --git a/MultiversalDiplomacy/Adjudicate/IPhaseAdjudicator.cs b/MultiversalDiplomacy/Adjudicate/IPhaseAdjudicator.cs
new file mode 100644
index 0000000..3bdfbf9
--- /dev/null
+++ b/MultiversalDiplomacy/Adjudicate/IPhaseAdjudicator.cs
@@ -0,0 +1,22 @@
+using MultiversalDiplomacy.Orders;
+
+namespace MultiversalDiplomacy.Adjudicate;
+
+///
+/// An input handler for game phases.
+///
+public interface IPhaseAdjudicator
+{
+ ///
+ /// Given a list of orders, determine which orders are valid for this adjudicator and
+ /// which should be rejected before adjudication. Adjudication should be performed on
+ /// all orders in the output for which is true.
+ ///
+ /// Orders to validate for adjudication.
+ ///
+ /// A list of order validation results. Note that this list may be longer than the input
+ /// list if illegal orders were replaced with hold orders, as there will be an invalid
+ /// result for the illegal order and a valid result for the replacement order.
+ ///
+ public IEnumerable ValidateOrders(IEnumerable orders);
+}
diff --git a/MultiversalDiplomacy/Adjudicate/OrderValidation.cs b/MultiversalDiplomacy/Adjudicate/OrderValidation.cs
new file mode 100644
index 0000000..553e47c
--- /dev/null
+++ b/MultiversalDiplomacy/Adjudicate/OrderValidation.cs
@@ -0,0 +1,46 @@
+using MultiversalDiplomacy.Orders;
+
+namespace MultiversalDiplomacy.Adjudicate;
+
+///
+/// Represents the result of validating an order.
+///
+public class OrderValidation
+{
+ ///
+ /// The order that was validated.
+ ///
+ public Order Order { get; }
+
+ ///
+ /// Whether the order is valid.
+ ///
+ public bool Valid { get; }
+
+ ///
+ /// The reason for the order validation result.
+ ///
+ public ValidationReason Reason { get; }
+
+ internal OrderValidation(Order order, bool valid, ValidationReason reason)
+ {
+ this.Order = order;
+ this.Valid = valid;
+ this.Reason = reason;
+ }
+}
+
+public static class OrderValidationExtensions
+{
+ ///
+ /// Create an accepting this order.
+ ///
+ public static OrderValidation Validate(this Order order, ValidationReason reason)
+ => new OrderValidation(order, true, reason);
+
+ ///
+ /// Create an rejecting this order.
+ ///
+ public static OrderValidation Invalidate(this Order order, ValidationReason reason)
+ => new OrderValidation(order, false, reason);
+}
diff --git a/MultiversalDiplomacy/Adjudicate/ValidationReason.cs b/MultiversalDiplomacy/Adjudicate/ValidationReason.cs
new file mode 100644
index 0000000..50b65d3
--- /dev/null
+++ b/MultiversalDiplomacy/Adjudicate/ValidationReason.cs
@@ -0,0 +1,24 @@
+namespace MultiversalDiplomacy.Adjudicate;
+
+public enum ValidationReason
+{
+ ///
+ /// The order is valid.
+ ///
+ Valid = 0,
+
+ ///
+ /// The order type is not valid for the current phase of the game.
+ ///
+ InvalidOrderTypeForPhase = 1,
+
+ ///
+ /// A hold order was created to replace an illegal order.
+ ///
+ IllegalOrderReplacedWithHold = 2,
+
+ ///
+ /// Another order was submitted that replaced this order.
+ ///
+ SupersededByLaterOrder = 3,
+}
\ No newline at end of file
diff --git a/MultiversalDiplomacy/Orders/Order.cs b/MultiversalDiplomacy/Orders/Order.cs
new file mode 100644
index 0000000..a8f1900
--- /dev/null
+++ b/MultiversalDiplomacy/Orders/Order.cs
@@ -0,0 +1,19 @@
+using MultiversalDiplomacy.Model;
+
+namespace MultiversalDiplomacy.Orders;
+
+///
+/// A submitted action by a power.
+///
+public abstract class Order
+{
+ ///
+ /// The power that submitted this order.
+ ///
+ public Power Power { get; }
+
+ public Order(Power power)
+ {
+ this.Power = power;
+ }
+}
diff --git a/MultiversalDiplomacyTests/AdjudicatorTests.cs b/MultiversalDiplomacyTests/AdjudicatorTests.cs
new file mode 100644
index 0000000..576c2d8
--- /dev/null
+++ b/MultiversalDiplomacyTests/AdjudicatorTests.cs
@@ -0,0 +1,28 @@
+using MultiversalDiplomacy.Adjudicate;
+using MultiversalDiplomacy.Model;
+using MultiversalDiplomacy.Orders;
+
+using NUnit.Framework;
+
+namespace MultiversalDiplomacyTests;
+
+public class AdjudicatorTests
+{
+ [Test]
+ public void OrderValidationTest()
+ {
+ IPhaseAdjudicator rubberStamp = new TestAdjudicator(orders => {
+ return orders.Select(o => o.Validate(ValidationReason.Valid));
+ });
+ Power power = new Power(nameof(Power));
+
+ Order order = new NullOrder(power);
+ List orders = new List { order };
+ IEnumerable results = rubberStamp.ValidateOrders(orders);
+
+ Assert.That(results.Count(), Is.EqualTo(1));
+ Assert.That(results.First().Order, Is.EqualTo(order));
+ Assert.That(results.First().Reason, Is.EqualTo(ValidationReason.Valid));
+ Assert.That(results.First().Valid, Is.True);
+ }
+}
\ No newline at end of file
diff --git a/MultiversalDiplomacyTests/NullOrder.cs b/MultiversalDiplomacyTests/NullOrder.cs
new file mode 100644
index 0000000..cab6c7a
--- /dev/null
+++ b/MultiversalDiplomacyTests/NullOrder.cs
@@ -0,0 +1,9 @@
+using MultiversalDiplomacy.Model;
+using MultiversalDiplomacy.Orders;
+
+namespace MultiversalDiplomacyTests;
+
+public class NullOrder : Order
+{
+ public NullOrder(Power power) : base(power) {}
+}
\ No newline at end of file
diff --git a/MultiversalDiplomacyTests/TestAdjudicator.cs b/MultiversalDiplomacyTests/TestAdjudicator.cs
new file mode 100644
index 0000000..0404b94
--- /dev/null
+++ b/MultiversalDiplomacyTests/TestAdjudicator.cs
@@ -0,0 +1,18 @@
+using MultiversalDiplomacy.Adjudicate;
+using MultiversalDiplomacy.Orders;
+
+namespace MultiversalDiplomacyTests;
+
+public class TestAdjudicator : IPhaseAdjudicator
+{
+ private Func, IEnumerable> ValidateOrdersCallback;
+
+ public TestAdjudicator(
+ Func, IEnumerable> validateOrdersCallback)
+ {
+ this.ValidateOrdersCallback = validateOrdersCallback;
+ }
+
+ public IEnumerable ValidateOrders(IEnumerable orders)
+ => this.ValidateOrdersCallback.Invoke(orders);
+}
\ No newline at end of file