diff --git a/archreview/plans/R2-06-serverhistorian-failfast-plan.md.tasks.json b/archreview/plans/R2-06-serverhistorian-failfast-plan.md.tasks.json
index 9145edce..aebd3c96 100644
--- a/archreview/plans/R2-06-serverhistorian-failfast-plan.md.tasks.json
+++ b/archreview/plans/R2-06-serverhistorian-failfast-plan.md.tasks.json
@@ -43,7 +43,7 @@
{
"id": "T6",
"subject": "Warning parity: malformed-endpoint warning in ServerHistorianOptions.Validate() + Runtime.Tests case; commit 1 (S-11) after T1-T6 green",
- "status": "pending",
+ "status": "completed",
"blockedBy": [
"T4"
]
diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Historian/ServerHistorianOptions.cs b/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Historian/ServerHistorianOptions.cs
index 2ffedff4..98f4eb56 100644
--- a/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Historian/ServerHistorianOptions.cs
+++ b/src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Historian/ServerHistorianOptions.cs
@@ -69,7 +69,16 @@ public sealed class ServerHistorianOptions
public int MaxTieClusterOverfetch { get; init; } = 65536;
/// Returns operator-facing misconfiguration warnings for an Enabled historian
- /// (empty when disabled or correctly configured). Pure — the registration logs each entry.
+ /// (empty when disabled or correctly configured). Pure — the registration logs each entry.
+ ///
+ /// These are warnings only. The Host layers a fail-fast startup validator
+ /// (ServerHistorianOptionsValidator, wired via AddValidatedOptions) on top of this
+ /// for the provably-crashing subset — an empty/malformed Endpoint that is consumed
+ /// by an enabled historian or an enabled AlarmHistorian — so that config fails host start
+ /// with a named error instead of a raw UriFormatException crash-loop (archreview 06/S-11).
+ /// The empty-ApiKey / non-positive-MaxTieClusterOverfetch entries stay warnings
+ /// because they degrade rather than crash.
+ ///
/// Zero or more human-readable warning messages (never carrying secret values).
public IReadOnlyList Validate()
{
@@ -77,6 +86,13 @@ public sealed class ServerHistorianOptions
if (!Enabled) return warnings;
if (string.IsNullOrWhiteSpace(Endpoint))
warnings.Add("ServerHistorian:Endpoint is empty while the historian is enabled — the read client has no gateway address to dial.");
+ // archreview 06/S-11 warning parity: a non-empty but unparseable / non-absolute-http(s) Endpoint
+ // is just as fatal (it throws in the gateway client factory). Warn on it here so the Runtime-side
+ // registration logs describe malformed endpoints too, consistent with the Host's fail-fast
+ // ServerHistorianOptionsValidator (which turns this same condition into a startup failure).
+ else if (!Uri.TryCreate(Endpoint, UriKind.Absolute, out var uri)
+ || (uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps))
+ warnings.Add($"ServerHistorian:Endpoint ('{Endpoint}') is not an absolute http(s) URI while the historian is enabled — the read client cannot dial it (e.g. https://host:5222).");
if (string.IsNullOrWhiteSpace(ApiKey))
warnings.Add("ServerHistorian:ApiKey is empty while the historian is enabled — the gateway gRPC surface will reject unauthenticated calls.");
// MaxTieClusterOverfetch is intentionally checked AFTER the Enabled early-return above:
diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/ServerHistorianOptionsTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/ServerHistorianOptionsTests.cs
index d4e2eb04..88acedae 100644
--- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/ServerHistorianOptionsTests.cs
+++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/ServerHistorianOptionsTests.cs
@@ -21,6 +21,16 @@ public sealed class ServerHistorianOptionsTests
Assert.Contains(w, m => m.Contains("Endpoint"));
}
+ [Fact]
+ public void Enabled_with_malformed_endpoint_warns()
+ {
+ // archreview 06/S-11 warning parity: a non-empty but unparseable / non-absolute Endpoint warns
+ // too, so the Runtime-side registration logs describe malformed endpoints (not just empty ones),
+ // keeping this surface consistent with the Host's fail-fast ServerHistorianOptionsValidator.
+ var w = new ServerHistorianOptions { Enabled = true, Endpoint = "not a uri", ApiKey = "histgw_x_y" }.Validate();
+ Assert.Contains(w, m => m.Contains("Endpoint"));
+ }
+
[Fact]
public void Enabled_without_apikey_warns()
{