M3 R3.1 decode: AddNonStreamValues reaches server StoreNonStreamValues (storage-engine console pipe)

Empirically decoded the AddNonStreamValues btInput framing against the live 2023
R2 server (grpc-nonstream-decode command + ProbeNonStreamedBuffersAsync driver).
Every transaction rolled back (bCommit=false) — no data written.

Finding: the btInput is assembled native-C++-side (not in any decompile), so 6
evidence-based framings (44-54B, packed HISTORIAN_VALUE2 variants) were probed.
All 6 returned the IDENTICAL server error while an empty buffer returned a
different InvalidParameter — so non-empty buffers pass parameter validation into
CHistStorageConnection::StoreNonStreamValues, which routes to the
\.\pipe\aahStorageEngine\console pipe server-side. Identical-across-framings =>
the blocker is NOT the btInput layout but a missing storage-engine console
session / tag-registration precondition for the connection.

Next step (untested): StorageService.OpenStorageConnection + tag registration
(RegisterTags/AddTagidPairs/AddShardTagids) before AddNonStreamValues, then
commit + read-back on a sandbox tag. Documented in revision-write-path.md
(R3.1 decode section); raw artifact gitignored.

272 unit tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B6mcaT2PjRFKcogzp9UkfC
This commit is contained in:
Joseph Doherty
2026-06-21 18:08:27 -04:00
parent 23798db1ef
commit 8fbb868813
3 changed files with 272 additions and 0 deletions
@@ -33,6 +33,106 @@ internal sealed class HistorianGrpcRevisionProbe
public Task<HistorianGrpcRevisionProbeResult> ProbeBeginAsync(CancellationToken cancellationToken)
=> Task.Run(() => ProbeBegin(cancellationToken), cancellationToken);
/// <summary>
/// Empirical-decode driver for the <c>AddNonStreamValues</c> <c>btInput</c> buffer (R3.1). For
/// each candidate buffer it opens a fresh transaction, sends the buffer, records the server's
/// accept/reject, and ALWAYS ends with <c>bCommit=false</c> (rollback) so nothing persists.
/// The candidate buffers are supplied by the caller (the RE tool) — this method does not invent
/// wire bytes, it just reports what the live server says about each. Safe against a real tag key
/// because every transaction is discarded.
/// </summary>
public Task<IReadOnlyList<HistorianGrpcNonStreamedCandidateResult>> ProbeNonStreamedBuffersAsync(
IReadOnlyList<(string Label, byte[] Buffer)> candidates,
CancellationToken cancellationToken)
=> Task.Run<IReadOnlyList<HistorianGrpcNonStreamedCandidateResult>>(
() => ProbeNonStreamedBuffers(candidates, cancellationToken), cancellationToken);
private List<HistorianGrpcNonStreamedCandidateResult> ProbeNonStreamedBuffers(
IReadOnlyList<(string Label, byte[] Buffer)> candidates,
CancellationToken cancellationToken)
{
var results = new List<HistorianGrpcNonStreamedCandidateResult>();
using HistorianGrpcConnection connection = HistorianGrpcChannelFactory.Create(_options);
HistorianGrpcHandshake.Session session = HistorianGrpcHandshake.OpenSession(
connection, _options, cancellationToken,
connectionMode: HistorianWcfAuthChainHelper.NativeIntegratedWriteEnabledConnectionMode);
var transactionClient = new GrpcTransaction.TransactionService.TransactionServiceClient(connection.Channel);
string handle = session.StringHandle;
DateTime Deadline() => DateTime.UtcNow.Add(_options.RequestTimeout);
// Prime the Transaction service session table.
try
{
transactionClient.GetTransactionInterfaceVersion(
new GrpcTransaction.GetTransactionInterfaceVersionRequest(), connection.Metadata, Deadline(), cancellationToken);
}
catch { /* version prime is best-effort */ }
foreach ((string label, byte[] buffer) in candidates)
{
var candidate = new HistorianGrpcNonStreamedCandidateResult { Label = label, BufferLength = buffer.Length };
string? transactionId = null;
try
{
GrpcTransaction.AddNonStreamValuesBeginResponse begin = transactionClient.AddNonStreamValuesBegin(
new GrpcTransaction.AddNonStreamValuesBeginRequest { StrHandle = handle },
connection.Metadata, Deadline(), cancellationToken);
if (!(begin.Status?.BSuccess ?? false) || string.IsNullOrEmpty(begin.StrTransactionId))
{
candidate.BeginFailed = true;
byte[] be = begin.Status?.BtError?.ToByteArray() ?? [];
candidate.AddErrorHex = be.Length == 0 ? null : Convert.ToHexString(be);
results.Add(candidate);
continue;
}
transactionId = begin.StrTransactionId;
GrpcTransaction.AddNonStreamValuesResponse add = transactionClient.AddNonStreamValues(
new GrpcTransaction.AddNonStreamValuesRequest
{
StrHandle = handle,
StrTransactionId = transactionId,
BtInput = ByteString.CopyFrom(buffer),
},
connection.Metadata, Deadline(), cancellationToken);
candidate.AddSucceeded = add.Status?.BSuccess ?? false;
byte[] ae = add.Status?.BtError?.ToByteArray() ?? [];
candidate.AddErrorHex = ae.Length == 0 ? null : Convert.ToHexString(ae);
}
catch (Exception ex)
{
candidate.Exception = $"{ex.GetType().Name}: {ex.Message}";
}
finally
{
// Always roll back — bCommit=false writes nothing.
if (!string.IsNullOrEmpty(transactionId))
{
try
{
transactionClient.AddNonStreamValuesEnd(
new GrpcTransaction.AddNonStreamValuesEndRequest
{
StrHandle = handle,
StrTransactionId = transactionId,
BCommit = false,
},
connection.Metadata, Deadline(), cancellationToken);
}
catch { /* rollback best-effort */ }
}
}
results.Add(candidate);
}
return results;
}
private HistorianGrpcRevisionProbeResult ProbeBegin(CancellationToken cancellationToken)
{
var result = new HistorianGrpcRevisionProbeResult();
@@ -155,3 +255,13 @@ internal sealed class HistorianGrpcRevisionBeginAttempt
public string? ErrorHex { get; set; }
public string? Exception { get; set; }
}
internal sealed class HistorianGrpcNonStreamedCandidateResult
{
public string Label { get; set; } = "";
public int BufferLength { get; set; }
public bool BeginFailed { get; set; }
public bool AddSucceeded { get; set; }
public string? AddErrorHex { get; set; }
public string? Exception { get; set; }
}