fix(r2-06): warning parity — malformed-endpoint warning in ServerHistorianOptions.Validate() (06/S-11)

This commit is contained in:
Joseph Doherty
2026-07-13 09:57:29 -04:00
parent e748f929c8
commit b77bbd13b7
3 changed files with 28 additions and 2 deletions
@@ -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"
]
@@ -69,7 +69,16 @@ public sealed class ServerHistorianOptions
public int MaxTieClusterOverfetch { get; init; } = 65536;
/// <summary>Returns operator-facing misconfiguration warnings for an <c>Enabled</c> historian
/// (empty when disabled or correctly configured). Pure — the registration logs each entry.</summary>
/// (empty when disabled or correctly configured). Pure — the registration logs each entry.
/// <para>
/// These are <b>warnings only</b>. The Host layers a fail-fast startup validator
/// (<c>ServerHistorianOptionsValidator</c>, wired via <c>AddValidatedOptions</c>) on top of this
/// for the <b>provably-crashing</b> subset — an empty/malformed <c>Endpoint</c> that is consumed
/// by an enabled historian or an enabled <c>AlarmHistorian</c> — so that config fails host start
/// with a named error instead of a raw <c>UriFormatException</c> crash-loop (archreview 06/S-11).
/// The empty-<c>ApiKey</c> / non-positive-<c>MaxTieClusterOverfetch</c> entries stay warnings
/// because they degrade rather than crash.
/// </para></summary>
/// <returns>Zero or more human-readable warning messages (never carrying secret values).</returns>
public IReadOnlyList<string> 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:
@@ -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()
{