R4.3: gRPC store-forward status probe + re-scope

Add HistorianGrpcStoreForwardStatusProbe and the `grpc-sf-status-probe` CLI
command. The idle-baseline run against the live 2023 R2 server resolves the
plan's §9.3 handle question: the direct StorageService SF pull RPCs
(GetSFParameter / GetRemainingSnapshotsSize) require the OpenStorageConnection
console handle and are D2-gated (err 132, identical under read-only and
write-enabled sessions), while StatusService.GetHistorianConsoleStatus IS
reachable on the session string handle (=3 at idle).

Records the gRPC re-scope and the idle-baseline findings in
docs/plans/store-forward-cache-reverse-engineering.md §9. The probe writes
nothing and releases any console session immediately.

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 23:14:05 -04:00
parent f840af5873
commit c2d8fb9bc8
3 changed files with 619 additions and 1 deletions
@@ -78,6 +78,7 @@ try
"grpc-revision-probe" => ProbeGrpcRevision(args),
"grpc-nonstream-decode" => ProbeGrpcNonStreamedDecode(args),
"grpc-open-storage-connection" => ProbeGrpcOpenStorageConnection(args),
"grpc-sf-status-probe" => ProbeGrpcStoreForwardStatus(args),
_ => UnknownCommand(args[0])
};
}
@@ -3386,6 +3387,48 @@ static int ProbeGrpcOpenStorageConnection(string[] args)
return result.OpenStorageSucceeded ? 0 : 2;
}
static int ProbeGrpcStoreForwardStatus(string[] args)
{
// Usage: grpc-sf-status-probe <host> [port] [--tls] [--dnsid <name>]
// R4.3 idle-baseline discovery (see docs/plans/store-forward-cache-reverse-engineering.md §9).
// Reads store-forward state via StorageService PULL RPCs (GetRemainingSnapshotsSize + sweep of
// GetSFParameter). Tries the cheap session ClientHandle first; falls back to an
// OpenStorageConnection console handle only if that fails, to disambiguate §9.3. Writes NOTHING.
// Reads HISTORIAN_USER / HISTORIAN_PASSWORD for explicit creds; else IntegratedSecurity.
string host = args.Length > 1 ? args[1] : "localhost";
int port = args.Length > 2 && int.TryParse(args[2], out int parsedPort)
? parsedPort
: HistorianClientOptions.DefaultGrpcPort;
bool tls = HasOption(args, "--tls");
string? dnsId = GetOption(args, "--dnsid");
string? user = Environment.GetEnvironmentVariable("HISTORIAN_USER");
string? password = Environment.GetEnvironmentVariable("HISTORIAN_PASSWORD");
bool explicitCreds = !string.IsNullOrEmpty(user);
var options = new HistorianClientOptions
{
Host = host,
Port = port,
Transport = HistorianTransport.RemoteGrpc,
GrpcUseTls = tls,
AllowUntrustedServerCertificate = tls,
ServerDnsIdentity = dnsId,
IntegratedSecurity = !explicitCreds,
UserName = user ?? string.Empty,
Password = password ?? string.Empty,
};
bool writeSession = HasOption(args, "--write-session");
var probe = new HistorianGrpcStoreForwardStatusProbe(options, writeSession);
HistorianGrpcStoreForwardStatusProbeResult result = probe.ProbeAsync(CancellationToken.None).GetAwaiter().GetResult();
Console.WriteLine(JsonSerializer.Serialize(result, CreateJsonOptions()));
bool anySucceeded = (result.SessionHandleAttempt?.AnySucceeded ?? false)
|| (result.ConsoleHandleAttempt?.AnySucceeded ?? false);
return anySucceeded ? 0 : 2;
}
static int ProbeGrpcRevision(string[] args)
{
// Usage: grpc-revision-probe <host> [port] [--tls] [--dnsid <name>] [--insecure-cert]