D2 (new path): SDK-direct WCF revision orchestrator + probe

Implemented HistorianWcfRevisionOrchestrator that talks WCF directly
to /Trx, bypassing the native wrapper entirely. Probes
AddNonStreamValuesBegin2 against the live local Historian and surfaces
what the server returns. Internal-only API; no public surface added —
the path isn't viable yet.

Findings (live test against localhost):

-  The wire path is reachable. After moving from V1 (uint handle, no
  errorBuffer) to V2 (string handle GUID, out errorBuffer), the server
  recognizes the call (no ContractFilter mismatch, no exception).
-  Server processes the call and returns a structured 5-byte error
  buffer: 04 33 00 00 00 = type 4 (CustomError) + code 51
  (UnknownClient).
-  Tried four handle formats (contextKey upper/lower, storageSessionId
  upper, ClientHandle as decimal string) — all return the same
  UnknownClient.
-  Adding the full priming chain (Stat.GetV ×2, Stat.GETHI ×2, UpdC3,
  6× Stat.GetSystemParameter, AllowRenameTags, Trx.GetV, Stat.GetV,
  Retr.GetV) — same result.

ITransactionServiceContract2 has no Validate/Register/Open op of its
own. The client-with-Trx registration must happen via some cross-
service side effect we haven't isolated.

Important takeaway: the wire-format mismatch is solved (contract method
names + parameter shapes match what the server expects). The remaining
gap is a single missing initialization step. Documented in
docs/plans/revision-write-path.md as concrete next-session steps.

178/178 tests pass (one new probe test added). Probe is gated on
HISTORIAN_HOST=localhost.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-05-05 02:51:26 -04:00
parent b5e5f5485b
commit b40e6948e2
4 changed files with 375 additions and 0 deletions
@@ -0,0 +1,60 @@
using System.Runtime.Versioning;
using AVEVA.Historian.Client;
using AVEVA.Historian.Client.Models;
using AVEVA.Historian.Client.Wcf;
using Xunit.Abstractions;
namespace AVEVA.Historian.Client.Tests;
/// <remarks>
/// Probes the SDK-direct WCF revision-write path (D2 new path). Calls
/// <c>AddNonStreamValuesBegin</c> through <see cref="HistorianWcfRevisionOrchestrator"/>
/// against the live local Historian and surfaces what the server returns. The
/// underlying native wrapper is gated client-side by err 129 TagNotFoundInCache;
/// this test bypasses the wrapper entirely and asks the SERVER directly. Gated on
/// HISTORIAN_HOST=localhost; skips otherwise.
/// </remarks>
[SupportedOSPlatform("windows")]
public sealed class HistorianWcfRevisionProbeTests
{
private readonly ITestOutputHelper _output;
public HistorianWcfRevisionProbeTests(ITestOutputHelper output)
{
_output = output;
}
[Fact]
public async Task AddNonStreamValuesBegin_ProbeReturnsServerResult()
{
string? host = Environment.GetEnvironmentVariable("HISTORIAN_HOST");
if (string.IsNullOrWhiteSpace(host) || !string.Equals(host, "localhost", StringComparison.OrdinalIgnoreCase) || !OperatingSystem.IsWindows())
{
return;
}
HistorianClientOptions options = new()
{
Host = host,
IntegratedSecurity = true,
Transport = HistorianTransport.LocalPipe,
};
HistorianWcfRevisionOrchestrator orchestrator = new(options);
HistorianRevisionProbeResult result = await orchestrator.ProbeBeginAsync(CancellationToken.None);
_output.WriteLine($"OpenSucceeded: {result.OpenSucceeded}");
_output.WriteLine($"ClientHandle: {result.ClientHandle}");
_output.WriteLine($"StorageSessionId: {result.StorageSessionId}");
_output.WriteLine($"TrxInterfaceVersion: {result.TrxInterfaceVersion} (rc={result.TrxInterfaceVersionReturnCode}) ex={result.TrxInterfaceVersionException}");
_output.WriteLine($"BeginSucceeded: {result.BeginSucceeded}");
_output.WriteLine($"BeginTransactionId: {result.BeginTransactionId}");
foreach (HistorianRevisionBeginAttempt attempt in result.BeginAttempts)
{
_output.WriteLine($" attempt[{attempt.HandleLabel}] handle={attempt.HandleSent} ok={attempt.Succeeded} tx={attempt.TransactionId} err={attempt.ErrorHex} ex={attempt.Exception}");
}
Assert.True(result.OpenSucceeded, "Auth chain failed; revision probe never reached the Trx endpoint.");
// Don't assert BeginSucceeded — we're surfacing whatever the server says, not requiring success.
}
}