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:
@@ -169,15 +169,41 @@ finding what populates Trx's session table — likely:
|
|||||||
show up in the IL we've inspected (e.g., the
|
show up in the IL we've inspected (e.g., the
|
||||||
`aahClientCommon.CClientCommon` calls during InitializeProxy)
|
`aahClientCommon.CClientCommon` calls during InitializeProxy)
|
||||||
|
|
||||||
A future session that wants to push further should:
|
A future session that wants to push further should try (in order):
|
||||||
1. Add `RTag2` for the sandbox tag and retry Begin2 — quick experiment
|
|
||||||
2. If that fails, try sending the IStorageServiceContract.AddT or
|
1. ✅ **DONE 2026-05-05.** Add `RTag2(CM_EVENT tag id)` to the priming
|
||||||
similar to "introduce" the tag to Trx
|
chain — confirmed `RTag2` itself succeeds (returns 25-byte response),
|
||||||
3. If that fails, do an IL walk of `aahClientCommon.CClientCommon`
|
but `AddNonStreamValuesBegin2` still fails with `UnknownClient`.
|
||||||
methods called between Open2 and AddNonStreamValuesBegin in a
|
So RTag2 doesn't cascade client identity to Trx.
|
||||||
working native scenario (using a system tag the wrapper would
|
2. Try `IStorageServiceContract` ops (`AddT`, `AddTP`) on `/Storage`
|
||||||
accept — or capturing actual on-wire bytes via the IL-rewrite
|
— that endpoint isn't currently bound by our SDK but the contract
|
||||||
instrumentation if possible)
|
is declared in `Wcf/Contracts/IStorageServiceContract.cs`. Maybe
|
||||||
|
one of its ops registers the client with Trx as a side effect.
|
||||||
|
3. Decompile / IL-walk `aahClientCommon.CClientCommon` methods that
|
||||||
|
the native code calls between Open2 and AddNonStreamValuesBegin
|
||||||
|
to find any "client-with-Trx" registration we're missing.
|
||||||
|
4. As a last resort, decompile `aahClientAccessPoint.exe` (the server
|
||||||
|
binary) to find what populates Trx's session table — the answer
|
||||||
|
is in there, just not in the client surface.
|
||||||
|
|
||||||
|
### Current state of the SDK-direct probe
|
||||||
|
|
||||||
|
`HistorianWcfRevisionOrchestrator.ProbeBeginAsync` does:
|
||||||
|
|
||||||
|
```
|
||||||
|
Open2 (write-enabled, 0x401)
|
||||||
|
→ priming (Stat.GetV ×2, Stat.GETHI ×2, UpdC3, 6× GetSystemParameter,
|
||||||
|
AllowRenameTags, Trx.GetV, Stat.GetV, Retr.GetV)
|
||||||
|
→ RTag2(CM_EVENT tag id) // succeeds
|
||||||
|
→ Trx.GetInterfaceVersion // succeeds, returns version 2
|
||||||
|
→ Trx.AddNonStreamValuesBegin2 ×4 // all four handle formats fail with
|
||||||
|
// 04 33 00 00 00 (UnknownClient 51)
|
||||||
|
```
|
||||||
|
|
||||||
|
The probe is committed as a gated test
|
||||||
|
(`HistorianWcfRevisionProbeTests.AddNonStreamValuesBegin_ProbeReturnsServerResult`)
|
||||||
|
that can be re-run any time to verify the gate is still where we think
|
||||||
|
it is, or to test future priming additions.
|
||||||
|
|
||||||
## Decision
|
## Decision
|
||||||
|
|
||||||
|
|||||||
@@ -55,6 +55,29 @@ internal sealed class HistorianWcfRevisionOrchestrator
|
|||||||
EndpointAddress retrievalEndpoint = HistorianWcfBindingFactory.CreateAuxiliaryEndpointAddress(_options, HistorianWcfServiceNames.Retrieval);
|
EndpointAddress retrievalEndpoint = HistorianWcfBindingFactory.CreateAuxiliaryEndpointAddress(_options, HistorianWcfServiceNames.Retrieval);
|
||||||
RunPrimingChain(historyChannel, context, auxBinding, statusEndpoint, transactionEndpoint, retrievalEndpoint);
|
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);
|
ChannelFactory<ITransactionServiceContract2> trxFactory = new(auxBinding, transactionEndpoint);
|
||||||
HistorianWcfClientCredentialsHelper.Configure(trxFactory, _options);
|
HistorianWcfClientCredentialsHelper.Configure(trxFactory, _options);
|
||||||
ITransactionServiceContract2 trxChannel = trxFactory.CreateChannel();
|
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()
|
private static byte[] BuildUpdC3ClientStatusBlob()
|
||||||
{
|
{
|
||||||
byte[] blob = new byte[81];
|
byte[] blob = new byte[81];
|
||||||
@@ -219,6 +258,10 @@ internal sealed class HistorianRevisionProbeResult
|
|||||||
public string? BeginErrorHex { get; set; }
|
public string? BeginErrorHex { get; set; }
|
||||||
public string? BeginException { get; set; }
|
public string? BeginException { get; set; }
|
||||||
public List<HistorianRevisionBeginAttempt> BeginAttempts { get; } = new();
|
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
|
internal sealed class HistorianRevisionBeginAttempt
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ public sealed class HistorianWcfRevisionProbeTests
|
|||||||
_output.WriteLine($"ClientHandle: {result.ClientHandle}");
|
_output.WriteLine($"ClientHandle: {result.ClientHandle}");
|
||||||
_output.WriteLine($"StorageSessionId: {result.StorageSessionId}");
|
_output.WriteLine($"StorageSessionId: {result.StorageSessionId}");
|
||||||
_output.WriteLine($"TrxInterfaceVersion: {result.TrxInterfaceVersion} (rc={result.TrxInterfaceVersionReturnCode}) ex={result.TrxInterfaceVersionException}");
|
_output.WriteLine($"TrxInterfaceVersion: {result.TrxInterfaceVersion} (rc={result.TrxInterfaceVersionReturnCode}) ex={result.TrxInterfaceVersionException}");
|
||||||
|
_output.WriteLine($"RTag2Succeeded: {result.RTag2Succeeded} OutHex={result.RTag2OutHex} ErrHex={result.RTag2ErrorHex} Ex={result.RTag2Exception}");
|
||||||
_output.WriteLine($"BeginSucceeded: {result.BeginSucceeded}");
|
_output.WriteLine($"BeginSucceeded: {result.BeginSucceeded}");
|
||||||
_output.WriteLine($"BeginTransactionId: {result.BeginTransactionId}");
|
_output.WriteLine($"BeginTransactionId: {result.BeginTransactionId}");
|
||||||
foreach (HistorianRevisionBeginAttempt attempt in result.BeginAttempts)
|
foreach (HistorianRevisionBeginAttempt attempt in result.BeginAttempts)
|
||||||
|
|||||||
Reference in New Issue
Block a user