Remove dead dialect methods; unblock explicit-creds tag-metadata path

Two cleanups from the post-EnsureTagAsync punch list — both isolated, no
protocol discovery required.

#89 dead code in Historian2020ProtocolDialect:
  - BrowseTagNamesAsync and GetTagMetadataAsync on the dialect both threw
    ProtocolEvidenceMissingException, but HistorianClient routes those calls
    directly to HistorianWcfTagClient — the dialect overrides were never
    reached. Removed both methods. ReadBlocksAsync stays (it's a deliberate
    guardrailed entry on the public surface).

#90 explicit-creds tag-metadata path:
  - HistorianWcfTagClient.WcfRetrievalSession.ValidateSupportedAuth threw
    ProtocolEvidenceMissingException whenever IntegratedSecurity=false AND
    UserName/Password were supplied. But the surrounding code already wires
    those creds through ApplyWindowsCredential ->
    factory.Credentials.Windows.ClientCredential — the validator was just
    being conservative about an untested combination.
  - Inverted the check: now only rejects the no-auth-at-all combination
    (IntegratedSecurity=false + no UserName + no Password). The other three
    valid auth shapes pass through to WCF.

Tests: 161 -> 163 (+2). New unit test verifies the no-auth case still
throws; new gated live integration test
GetTagMetadataAsync_ExplicitCredentials_AgainstLocalHistorian exercises the
explicit-creds path when HISTORIAN_USER+HISTORIAN_PASSWORD are set, skips
cleanly otherwise.

CLAUDE.md updated: removed the two now-resolved entries from "Remaining
gaps"; explicit-creds line refined to note the live-verification env-var
requirement.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-05-04 15:04:51 -04:00
parent 15a8446f63
commit f32fd57874
4 changed files with 60 additions and 15 deletions
@@ -87,17 +87,6 @@ internal sealed class Historian2020ProtocolDialect
#pragma warning restore CA1416
}
public IAsyncEnumerable<string> BrowseTagNamesAsync(string filter, CancellationToken cancellationToken)
{
return Missing<string>("StartLikeTagNameSearch/GetLikeTagnames", cancellationToken);
}
public Task<HistorianTagMetadata?> GetTagMetadataAsync(string tag, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
throw new ProtocolEvidenceMissingException("GetTagInfoByName/GetTagInfos");
}
public Task<HistorianConnectionStatus> GetConnectionStatusAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
@@ -351,10 +351,18 @@ internal static class HistorianWcfTagClient
private static void ValidateSupportedAuth(HistorianClientOptions options)
{
// Three valid auth shapes:
// 1. IntegratedSecurity=true (current Windows identity, no UserName/Password)
// 2. IntegratedSecurity=false + UserName + Password (NTLM/Kerberos with explicit creds)
// 3. IntegratedSecurity=true + UserName + Password (impersonation/explicit override)
// The fourth combination — IntegratedSecurity=false with no UserName/Password — has
// no way to authenticate against the /Hist-Integrated endpoint and is rejected.
if (!options.IntegratedSecurity
&& (!string.IsNullOrEmpty(options.UserName) || !string.IsNullOrEmpty(options.Password)))
&& string.IsNullOrEmpty(options.UserName)
&& string.IsNullOrEmpty(options.Password))
{
throw new ProtocolEvidenceMissingException("Open2 explicit username/password tag browse");
throw new ProtocolEvidenceMissingException(
"Tag browse / metadata requires either IntegratedSecurity=true OR an explicit UserName + Password.");
}
}