diff --git a/ZB.MOM.WW.Configuration/src/ZB.MOM.WW.Configuration/ConfigPreflight.cs b/ZB.MOM.WW.Configuration/src/ZB.MOM.WW.Configuration/ConfigPreflight.cs
new file mode 100644
index 0000000..0ebfff6
--- /dev/null
+++ b/ZB.MOM.WW.Configuration/src/ZB.MOM.WW.Configuration/ConfigPreflight.cs
@@ -0,0 +1,75 @@
+using Microsoft.Extensions.Configuration;
+
+namespace ZB.MOM.WW.Configuration;
+
+///
+/// Fluent aggregator for validating raw BEFORE the host/DI container
+/// exists (e.g. pre-Akka startup). Collects all failures and surfaces them together via
+/// . For options that flow through DI, prefer
+/// .
+///
+public sealed class ConfigPreflight
+{
+ private readonly IConfiguration _configuration;
+ private readonly List _failures = [];
+
+ private ConfigPreflight(IConfiguration configuration) => _configuration = configuration;
+
+ /// Starts a preflight over .
+ public static ConfigPreflight For(IConfiguration configuration)
+ {
+ ArgumentNullException.ThrowIfNull(configuration);
+ return new ConfigPreflight(configuration);
+ }
+
+ /// The accumulated failure messages (empty when valid).
+ public IReadOnlyList Failures => _failures;
+
+ /// True when no failures have been accumulated.
+ public bool IsValid => _failures.Count == 0;
+
+ /// Requires the value at to satisfy .
+ public ConfigPreflight Require(string key, Func predicate, string reason)
+ {
+ ArgumentNullException.ThrowIfNull(predicate);
+ if (!predicate(_configuration[key])) _failures.Add($"{key} {reason}");
+ return this;
+ }
+
+ /// Requires a non-empty value at .
+ public ConfigPreflight RequireValue(string key) => AddIf(Checks.Required(_configuration[key], key));
+
+ /// Requires a valid integer TCP port (1-65535) at .
+ public ConfigPreflight RequirePort(string key)
+ {
+ var raw = _configuration[key];
+ if (!int.TryParse(raw, out var port))
+ {
+ _failures.Add($"{key} must be an integer port 1-65535 (was '{raw ?? "null"}')");
+ return this;
+ }
+ return AddIf(Checks.Port(port, key));
+ }
+
+ /// Runs only when holds (role-conditional rules).
+ public ConfigPreflight When(bool condition, Action block)
+ {
+ ArgumentNullException.ThrowIfNull(block);
+ if (condition) block(this);
+ return this;
+ }
+
+ /// Throws listing all failures when invalid; otherwise returns.
+ public void ThrowIfInvalid()
+ {
+ if (_failures.Count > 0)
+ throw new InvalidOperationException(
+ $"Configuration validation failed:\n{string.Join("\n", _failures.Select(e => $" - {e}"))}");
+ }
+
+ private ConfigPreflight AddIf(string? message)
+ {
+ if (message is not null) _failures.Add(message);
+ return this;
+ }
+}
diff --git a/ZB.MOM.WW.Configuration/tests/ZB.MOM.WW.Configuration.Tests/ConfigPreflightTests.cs b/ZB.MOM.WW.Configuration/tests/ZB.MOM.WW.Configuration.Tests/ConfigPreflightTests.cs
new file mode 100644
index 0000000..8b8428d
--- /dev/null
+++ b/ZB.MOM.WW.Configuration/tests/ZB.MOM.WW.Configuration.Tests/ConfigPreflightTests.cs
@@ -0,0 +1,49 @@
+using Microsoft.Extensions.Configuration;
+using ZB.MOM.WW.Configuration;
+
+namespace ZB.MOM.WW.Configuration.Tests;
+
+public sealed class ConfigPreflightTests
+{
+ private static IConfiguration Config(Dictionary values) =>
+ new ConfigurationBuilder().AddInMemoryCollection(values).Build();
+
+ [Fact]
+ public void Aggregates_all_failures()
+ {
+ var cfg = Config(new() { ["Node:Role"] = "Bogus", ["Node:RemotingPort"] = "0" });
+ var pf = ConfigPreflight.For(cfg)
+ .Require("Node:Role", v => v is "Central" or "Site", "must be 'Central' or 'Site'")
+ .RequirePort("Node:RemotingPort");
+ Assert.False(pf.IsValid);
+ Assert.Equal(2, pf.Failures.Count);
+ }
+
+ [Fact]
+ public void When_runs_block_only_if_condition_true()
+ {
+ var cfg = Config(new() { ["Node:Role"] = "Site" });
+ var pf = ConfigPreflight.For(cfg)
+ .When(cfg["Node:Role"] == "Site",
+ p => p.RequireValue("Node:SiteId"));
+ Assert.False(pf.IsValid); // SiteId missing
+ Assert.Contains(pf.Failures, f => f.Contains("Node:SiteId"));
+ }
+
+ [Fact]
+ public void ThrowIfInvalid_throws_aggregated_message()
+ {
+ var cfg = Config(new() { ["Node:Name"] = "" });
+ var ex = Assert.Throws(() =>
+ ConfigPreflight.For(cfg).RequireValue("Node:Name").ThrowIfInvalid());
+ Assert.StartsWith("Configuration validation failed:", ex.Message);
+ Assert.Contains(" - Node:Name", ex.Message);
+ }
+
+ [Fact]
+ public void ThrowIfInvalid_is_noop_when_valid()
+ {
+ var cfg = Config(new() { ["Node:Name"] = "ok" });
+ ConfigPreflight.For(cfg).RequireValue("Node:Name").ThrowIfInvalid(); // does not throw
+ }
+}