D2 follow-up: RTag2 doesn't cascade client identity to Trx

Tested hypothesis (1) from the plan: add RTag2(CM_EVENT tag id) to the
priming chain before AddNonStreamValuesBegin2.

Result:
- RTag2 itself succeeds: returns 25-byte response
  (01000000000100000001EE39C30EDCDC010100000000000000), no error buffer.
- But AddNonStreamValuesBegin2 still fails with the same
  04 33 00 00 00 (UnknownClient = 51) for all four handle formats.

So RTag2 on /Hist isn't the cross-service registration trigger we need
for /Trx. Plan doc updated with the result + next-session ordered
probes (try IStorageServiceContract, then IL walk CClientCommon,
then server-side decompile as last resort).

Probe orchestrator now also performs the RTag2 step so the test gives
one-shot diagnostic visibility of both calls.

178/178 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-05-05 02:54:52 -04:00
parent b40e6948e2
commit 6b385441c1
3 changed files with 79 additions and 9 deletions
@@ -55,6 +55,29 @@ internal sealed class HistorianWcfRevisionOrchestrator
EndpointAddress retrievalEndpoint = HistorianWcfBindingFactory.CreateAuxiliaryEndpointAddress(_options, HistorianWcfServiceNames.Retrieval);
RunPrimingChain(historyChannel, context, auxBinding, statusEndpoint, transactionEndpoint, retrievalEndpoint);
// Hypothesis: calling RTag2 (RegisterTags2) cascades client identity into
// the Trx service's session table. The event flow uses RTag2 with the
// CM_EVENT tag id and subsequent ops succeed. Try RTag2 with that same
// tag id here as a registration probe.
try
{
string handle = context.StorageSessionId.ToString("D").ToUpperInvariant();
byte[] rtag2Buffer = BuildRTag2CmEventInputBuffer();
bool rtag2Ok = historyChannel.RegisterTags2(
handle: handle,
elementCount: 1,
inputBuffer: rtag2Buffer,
outputBuffer: out byte[] rtag2Out,
errorBuffer: out byte[] rtag2Err);
result.RTag2Succeeded = rtag2Ok;
result.RTag2OutHex = rtag2Out is null || rtag2Out.Length == 0 ? null : Convert.ToHexString(rtag2Out);
result.RTag2ErrorHex = rtag2Err is null || rtag2Err.Length == 0 ? null : Convert.ToHexString(rtag2Err);
}
catch (Exception ex)
{
result.RTag2Exception = $"{ex.GetType().Name}: {ex.Message}";
}
ChannelFactory<ITransactionServiceContract2> trxFactory = new(auxBinding, transactionEndpoint);
HistorianWcfClientCredentialsHelper.Configure(trxFactory, _options);
ITransactionServiceContract2 trxChannel = trxFactory.CreateChannel();
@@ -176,6 +199,22 @@ internal sealed class HistorianWcfRevisionOrchestrator
}
}
/// <summary>Same 24-byte RTag2 buffer the event flow uses (CM_EVENT tag id).</summary>
private static byte[] BuildRTag2CmEventInputBuffer()
{
byte[] buffer = new byte[24];
buffer[0] = 0x50;
buffer[1] = 0x67;
buffer[2] = 0x02;
buffer[3] = 0x00;
BinaryPrimitives.WriteUInt32LittleEndian(buffer.AsSpan(4, 4), 1u);
// CM_EVENT tag id — duplicated here to avoid a cross-class dependency on the
// event orchestrator. Verify against HistorianWcfEventOrchestrator.CmEventTagId
// if the value ever needs updating.
new Guid("353b8145-5df0-4d46-a253-871aef49b321").ToByteArray().CopyTo(buffer.AsSpan(8, 16));
return buffer;
}
private static byte[] BuildUpdC3ClientStatusBlob()
{
byte[] blob = new byte[81];
@@ -219,6 +258,10 @@ internal sealed class HistorianRevisionProbeResult
public string? BeginErrorHex { get; set; }
public string? BeginException { get; set; }
public List<HistorianRevisionBeginAttempt> BeginAttempts { get; } = new();
public bool RTag2Succeeded { get; set; }
public string? RTag2OutHex { get; set; }
public string? RTag2ErrorHex { get; set; }
public string? RTag2Exception { get; set; }
}
internal sealed class HistorianRevisionBeginAttempt