feat(wire): attribute updates carry source_execution_id/trace; PullTraceEvents RPC + site read-by-execution-ids
Flow Debugger (design 2026-09-05 §5.3/§5.4): AttributeValueUpdate fields 7/8 stamped by StreamRelayActor and parsed by SiteStreamGrpcClient (older frames => null/false). New PullTraceEvents unary RPC on SiteStreamService (same PSK-gated prefix as PullAuditEvents): a point read of the site audit SQLite by ExecutionId OR ParentExecutionId (ISiteAuditQueue.ReadByExecutionIdsAsync, json_extract on the codec's camelCase keys, new IX_audit_event_occurred index, 64-id / 2000-row caps) that moves no cursor and flips no ForwardState. Central IPullTraceEventsClient/GrpcPullTraceEventsClient mirror the audit pull client (endpoint resolution, NodeB failover, fault => empty) and register beside it. Claude-Session: https://claude.ai/code/session_01GSLkkHKz9JrAtmJjmQK3cP
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using Grpc.Core;
|
||||
using Grpc.Net.Client;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ZB.MOM.WW.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.Communication;
|
||||
using ZB.MOM.WW.ScadaBridge.Communication.Grpc;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Central;
|
||||
|
||||
/// <summary>
|
||||
/// Production <see cref="IPullTraceEventsClient"/> over the site-hosted <c>PullTraceEvents</c>
|
||||
/// unary RPC (Flow Debugger design 2026-09-05 §5.4). Mirrors
|
||||
/// <see cref="GrpcPullAuditEventsClient"/> exactly: endpoint resolution through
|
||||
/// <see cref="ISiteEnumerator"/> on every call, one NodeB failover dial on a transport fault,
|
||||
/// every fault collapsed to an empty list (the debugger renders "no site rows yet" and the
|
||||
/// value-change poll simply tries again), and an invoker seam for tests.
|
||||
/// </summary>
|
||||
public sealed class GrpcPullTraceEventsClient : IPullTraceEventsClient
|
||||
{
|
||||
/// <summary>Hard cap on ids per call — matches the site's <c>SqliteAuditWriter.MaxExecutionIdsPerRead</c>.</summary>
|
||||
public const int MaxExecutionIds = 64;
|
||||
|
||||
private readonly ISiteEnumerator _sites;
|
||||
private readonly IPullTraceEventsInvoker _invoker;
|
||||
private readonly ILogger<GrpcPullTraceEventsClient> _logger;
|
||||
|
||||
/// <summary>Creates the client over the given site enumerator and unary-call invoker.</summary>
|
||||
public GrpcPullTraceEventsClient(
|
||||
ISiteEnumerator sites,
|
||||
IPullTraceEventsInvoker invoker,
|
||||
ILogger<GrpcPullTraceEventsClient> logger)
|
||||
{
|
||||
_sites = sites ?? throw new ArgumentNullException(nameof(sites));
|
||||
_invoker = invoker ?? throw new ArgumentNullException(nameof(invoker));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<AuditEvent>> PullAsync(
|
||||
string siteId,
|
||||
IReadOnlyCollection<Guid> executionIds,
|
||||
DateTime sinceUtc,
|
||||
int maxEvents,
|
||||
CancellationToken ct)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(executionIds);
|
||||
if (executionIds.Count == 0 || maxEvents <= 0)
|
||||
{
|
||||
return Array.Empty<AuditEvent>();
|
||||
}
|
||||
if (executionIds.Count > MaxExecutionIds)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(executionIds), $"at most {MaxExecutionIds} execution ids per call.");
|
||||
}
|
||||
|
||||
var (endpoint, fallback) = await ResolveEndpointsAsync(siteId, ct).ConfigureAwait(false);
|
||||
if (endpoint is null)
|
||||
{
|
||||
_logger.LogDebug("PullTraceEvents skipped: no gRPC endpoint registered for site {SiteId}.", siteId);
|
||||
return Array.Empty<AuditEvent>();
|
||||
}
|
||||
|
||||
var request = new PullTraceEventsRequest
|
||||
{
|
||||
SinceUtc = Timestamp.FromDateTime(EnsureUtc(sinceUtc)),
|
||||
MaxEvents = maxEvents,
|
||||
};
|
||||
foreach (var id in executionIds)
|
||||
{
|
||||
request.ExecutionIds.Add(id.ToString("D"));
|
||||
}
|
||||
|
||||
var (reply, transportFault) = await TryInvokeAsync(endpoint, request, siteId, ct).ConfigureAwait(false);
|
||||
|
||||
if (reply is null && transportFault
|
||||
&& !string.IsNullOrWhiteSpace(fallback)
|
||||
&& !string.Equals(fallback, endpoint, StringComparison.Ordinal)
|
||||
&& !ct.IsCancellationRequested)
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"PullTraceEvents failing over from primary {Primary} to NodeB {Fallback} for site {SiteId}.",
|
||||
endpoint, fallback, siteId);
|
||||
(reply, _) = await TryInvokeAsync(fallback, request, siteId, ct).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (reply is null)
|
||||
{
|
||||
return Array.Empty<AuditEvent>();
|
||||
}
|
||||
|
||||
return reply.Events
|
||||
.Select(AuditEventDtoMapper.FromDto)
|
||||
.OrderBy(e => e.OccurredAtUtc)
|
||||
.ThenBy(e => e.EventId.ToString(), StringComparer.Ordinal)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private async Task<(PullTraceEventsResponse? Reply, bool TransportFault)> TryInvokeAsync(
|
||||
string endpoint, PullTraceEventsRequest request, string siteId, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var reply = await _invoker.InvokeAsync(siteId, endpoint, request, ct).ConfigureAwait(false);
|
||||
return (reply, false);
|
||||
}
|
||||
catch (RpcException ex) when (ex.StatusCode is StatusCode.Unavailable or StatusCode.DeadlineExceeded or StatusCode.Cancelled)
|
||||
{
|
||||
_logger.LogDebug(ex, "PullTraceEvents tolerable transport fault for site {SiteId} ({Endpoint}): {Status}.",
|
||||
siteId, endpoint, ex.StatusCode);
|
||||
return (null, true);
|
||||
}
|
||||
catch (Exception ex) when (ex is HttpRequestException or System.Net.Sockets.SocketException)
|
||||
{
|
||||
_logger.LogDebug(ex, "PullTraceEvents connection-layer fault for site {SiteId} ({Endpoint}).", siteId, endpoint);
|
||||
return (null, true);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return (null, true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "PullTraceEvents unexpected fault for site {SiteId} ({Endpoint}). Returning empty.", siteId, endpoint);
|
||||
return (null, false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<(string? Primary, string? Fallback)> ResolveEndpointsAsync(string siteId, CancellationToken ct)
|
||||
{
|
||||
var sites = await _sites.EnumerateAsync(ct).ConfigureAwait(false);
|
||||
foreach (var site in sites)
|
||||
{
|
||||
if (string.Equals(site.SiteId, siteId, StringComparison.Ordinal) && !string.IsNullOrWhiteSpace(site.GrpcEndpoint))
|
||||
{
|
||||
return (site.GrpcEndpoint, site.FallbackGrpcEndpoint);
|
||||
}
|
||||
}
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
private static DateTime EnsureUtc(DateTime value) =>
|
||||
value.Kind == DateTimeKind.Utc ? value : DateTime.SpecifyKind(value, DateTimeKind.Utc);
|
||||
|
||||
/// <summary>
|
||||
/// Seam over the <c>PullTraceEvents</c> unary gRPC call. Production binds
|
||||
/// <see cref="GrpcPullTraceEventsInvoker"/>; tests inject a fake.
|
||||
/// </summary>
|
||||
public interface IPullTraceEventsInvoker
|
||||
{
|
||||
/// <summary>Issues the RPC against <paramref name="endpoint"/> with the site's preshared key.</summary>
|
||||
Task<PullTraceEventsResponse> InvokeAsync(
|
||||
string siteId, string endpoint, PullTraceEventsRequest request, CancellationToken ct);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Production <see cref="GrpcPullTraceEventsClient.IPullTraceEventsInvoker"/>: one cached
|
||||
/// <see cref="GrpcChannel"/> per (site, endpoint), keepalive from <see cref="CommunicationOptions"/>,
|
||||
/// per-site PSK call credentials — the same shape as <see cref="GrpcPullAuditEventsInvoker"/>.
|
||||
/// </summary>
|
||||
public sealed class GrpcPullTraceEventsInvoker : GrpcPullTraceEventsClient.IPullTraceEventsInvoker, IDisposable
|
||||
{
|
||||
private readonly ConcurrentDictionary<(string Site, string Endpoint), GrpcChannel> _channels = new();
|
||||
private readonly CommunicationOptions _options;
|
||||
private readonly ISitePskProvider? _pskProvider;
|
||||
|
||||
/// <summary>Creates the invoker with the given options and optional per-site PSK provider.</summary>
|
||||
public GrpcPullTraceEventsInvoker(CommunicationOptions options, ISitePskProvider? pskProvider)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
_pskProvider = pskProvider;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<PullTraceEventsResponse> InvokeAsync(
|
||||
string siteId, string endpoint, PullTraceEventsRequest request, CancellationToken ct)
|
||||
{
|
||||
var channel = GetOrCreateChannel(siteId, endpoint);
|
||||
var client = new SiteStreamService.SiteStreamServiceClient(channel);
|
||||
using var call = client.PullTraceEventsAsync(request, cancellationToken: ct);
|
||||
return await call.ResponseAsync.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private GrpcChannel GetOrCreateChannel(string siteId, string endpoint)
|
||||
{
|
||||
var key = (siteId, endpoint);
|
||||
if (!_channels.TryGetValue(key, out var channel))
|
||||
{
|
||||
var created = CreateChannel(siteId, endpoint);
|
||||
channel = _channels.GetOrAdd(key, created);
|
||||
if (!ReferenceEquals(channel, created))
|
||||
{
|
||||
created.Dispose();
|
||||
}
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
// Keyed by (site, endpoint): call credentials are per-site and bound to the channel.
|
||||
private GrpcChannel CreateChannel(string siteId, string endpoint) =>
|
||||
GrpcChannel.ForAddress(endpoint, new GrpcChannelOptions
|
||||
{
|
||||
HttpHandler = new SocketsHttpHandler
|
||||
{
|
||||
KeepAlivePingDelay = _options.GrpcKeepAlivePingDelay,
|
||||
KeepAlivePingTimeout = _options.GrpcKeepAlivePingTimeout,
|
||||
KeepAlivePingPolicy = HttpKeepAlivePingPolicy.Always,
|
||||
},
|
||||
}.WithSiteCredentials(_pskProvider, siteId));
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var channel in _channels.Values)
|
||||
{
|
||||
channel.Dispose();
|
||||
}
|
||||
_channels.Clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using ZB.MOM.WW.Audit;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Central;
|
||||
|
||||
/// <summary>
|
||||
/// Flow Debugger (design 2026-09-05 §5.4): central-side client for the site-hosted
|
||||
/// <c>PullTraceEvents</c> unary RPC — a point read of a site's <c>ScriptTrace</c> rows for a
|
||||
/// set of execution ids (by <c>ExecutionId</c> or <c>ParentExecutionId</c>). NOT a
|
||||
/// reconciliation path: it moves no cursor and flips no forward state. Best-effort like the
|
||||
/// audit pull: a transport fault collapses to an empty list after one NodeB failover dial.
|
||||
/// </summary>
|
||||
public interface IPullTraceEventsClient
|
||||
{
|
||||
/// <summary>Pulls the site's trace rows for <paramref name="executionIds"/> (max 64) since <paramref name="sinceUtc"/>.</summary>
|
||||
/// <param name="siteId">The site to pull from (resolved to its gRPC endpoint + preshared key).</param>
|
||||
/// <param name="executionIds">Execution ids to match on <c>ExecutionId</c> or <c>ParentExecutionId</c>.</param>
|
||||
/// <param name="sinceUtc">Lower bound on <c>OccurredAtUtc</c> (the run's start is the natural choice).</param>
|
||||
/// <param name="maxEvents">Row cap (the site caps at 2000).</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>The matching rows ordered <c>(OccurredAtUtc, EventId)</c>; empty on fault or unknown site.</returns>
|
||||
Task<IReadOnlyList<AuditEvent>> PullAsync(
|
||||
string siteId,
|
||||
IReadOnlyCollection<Guid> executionIds,
|
||||
DateTime sinceUtc,
|
||||
int maxEvents,
|
||||
CancellationToken ct);
|
||||
}
|
||||
@@ -551,6 +551,26 @@ public static class ServiceCollectionExtensions
|
||||
sp.GetRequiredService<GrpcPullAuditEventsClient.IPullAuditEventsInvoker>(),
|
||||
sp.GetRequiredService<ILogger<GrpcPullAuditEventsClient>>()));
|
||||
|
||||
// Flow Debugger (design 2026-09-05 §5.4): PullTraceEvents point-read client — the
|
||||
// value-change entry's site-row fetch. Same invoker/channel/PSK shape as the audit
|
||||
// pull client above; singleton because the invoker owns the channel cache.
|
||||
services.TryAddSingleton<GrpcPullTraceEventsInvoker>(sp =>
|
||||
{
|
||||
var options = sp
|
||||
.GetService<Microsoft.Extensions.Options.IOptions<
|
||||
ZB.MOM.WW.ScadaBridge.Communication.CommunicationOptions>>();
|
||||
var psk = sp.GetService<ZB.MOM.WW.ScadaBridge.Communication.Grpc.ISitePskProvider>();
|
||||
return options is null
|
||||
? new GrpcPullTraceEventsInvoker(new ZB.MOM.WW.ScadaBridge.Communication.CommunicationOptions(), psk)
|
||||
: new GrpcPullTraceEventsInvoker(options.Value, psk);
|
||||
});
|
||||
services.TryAddSingleton<GrpcPullTraceEventsClient.IPullTraceEventsInvoker>(
|
||||
sp => sp.GetRequiredService<GrpcPullTraceEventsInvoker>());
|
||||
services.TryAddSingleton<IPullTraceEventsClient>(sp => new GrpcPullTraceEventsClient(
|
||||
sp.GetRequiredService<ISiteEnumerator>(),
|
||||
sp.GetRequiredService<GrpcPullTraceEventsClient.IPullTraceEventsInvoker>(),
|
||||
sp.GetRequiredService<ILogger<GrpcPullTraceEventsClient>>()));
|
||||
|
||||
// Site Call Audit reconciliation pull client — central-only, the
|
||||
// sibling of the audit pull client above. Lives here (not in the
|
||||
// SiteCallAudit project) so it can reuse the central-only
|
||||
|
||||
@@ -265,6 +265,12 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable
|
||||
-- four reads + the backlog COUNT/MIN.
|
||||
CREATE INDEX IF NOT EXISTS IX_fwd
|
||||
ON audit_forward_state (ForwardState, IsCachedKind, OccurredAtUtc);
|
||||
|
||||
-- Flow Debugger (design 2026-09-05 §5.4): PullTraceEvents reads audit_event by
|
||||
-- time window + json_extract on ExecutionId/ParentExecutionId; this bounds the scan
|
||||
-- to the window. Additive and idempotent.
|
||||
CREATE INDEX IF NOT EXISTS IX_audit_event_occurred
|
||||
ON audit_event (OccurredAtUtc);
|
||||
""";
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
@@ -848,6 +854,71 @@ public class SqliteAuditWriter : IAuditWriter, ISiteAuditQueue, IAsyncDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Flow Debugger PullTraceEvents: hard cap on ids per read (design §5.4).</summary>
|
||||
public const int MaxExecutionIdsPerRead = 64;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<AuditEvent>> ReadByExecutionIdsAsync(
|
||||
IReadOnlyCollection<Guid> executionIds, DateTime sinceUtc, int limit, CancellationToken ct = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(executionIds);
|
||||
if (limit <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(limit), "limit must be > 0.");
|
||||
}
|
||||
if (executionIds.Count == 0)
|
||||
{
|
||||
return Task.FromResult<IReadOnlyList<AuditEvent>>(Array.Empty<AuditEvent>());
|
||||
}
|
||||
if (executionIds.Count > MaxExecutionIdsPerRead)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(executionIds),
|
||||
$"at most {MaxExecutionIdsPerRead} execution ids per read.");
|
||||
}
|
||||
|
||||
// Flow Debugger (design 2026-09-05 §5.4): a POINT read independent of the forwarding
|
||||
// sidecar — a traced row is served whether Pending, Forwarded or Reconciled, and
|
||||
// serving it moves no cursor. DetailsJson is the codec's camelCase JSON, so the paths
|
||||
// are $.executionId / $.parentExecutionId; ids are stored in "D" form.
|
||||
lock (_readLock)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
|
||||
using var cmd = _readConnection.CreateCommand();
|
||||
var names = new List<string>(executionIds.Count);
|
||||
var i = 0;
|
||||
foreach (var id in executionIds)
|
||||
{
|
||||
var name = $"$e{i++}";
|
||||
names.Add(name);
|
||||
cmd.Parameters.AddWithValue(name, id.ToString("D"));
|
||||
}
|
||||
var inList = string.Join(", ", names);
|
||||
|
||||
cmd.CommandText = $"""
|
||||
SELECT EventId, OccurredAtUtc, Actor, Action, Outcome,
|
||||
Category, Target, SourceNode, CorrelationId, DetailsJson
|
||||
FROM audit_event
|
||||
WHERE OccurredAtUtc >= $since
|
||||
AND (json_extract(DetailsJson, '$.executionId') IN ({inList})
|
||||
OR json_extract(DetailsJson, '$.parentExecutionId') IN ({inList}))
|
||||
ORDER BY OccurredAtUtc ASC, EventId ASC
|
||||
LIMIT $limit;
|
||||
""";
|
||||
cmd.Parameters.AddWithValue("$since", EnsureUtc(sinceUtc).ToString(
|
||||
"o", System.Globalization.CultureInfo.InvariantCulture));
|
||||
cmd.Parameters.AddWithValue("$limit", limit);
|
||||
|
||||
var rows = new List<AuditEvent>(Math.Min(limit, 256));
|
||||
using var reader = cmd.ExecuteReader();
|
||||
while (reader.Read())
|
||||
{
|
||||
rows.Add(MapRow(reader));
|
||||
}
|
||||
return Task.FromResult<IReadOnlyList<AuditEvent>>(rows);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Highest <c>audit_forward_state.rowid</c> this instance has ever SERVED from
|
||||
/// <see cref="ReadPendingSinceAsync"/> — i.e. the insertion-order high-water mark of
|
||||
|
||||
@@ -223,4 +223,14 @@ public interface ISiteAuditQueue
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>A task that resolves to the number of canonical rows deleted.</returns>
|
||||
Task<int> PurgeExpiredAsync(DateTime olderThanUtc, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Flow Debugger (design 2026-09-05 §5.4): point read of the rows whose
|
||||
/// <c>ExecutionId</c> OR <c>ParentExecutionId</c> is one of <paramref name="executionIds"/>
|
||||
/// and whose <c>OccurredAtUtc</c> is at or after <paramref name="sinceUtc"/>, ordered
|
||||
/// <c>(OccurredAtUtc, EventId)</c>, at most <paramref name="limit"/> rows. Independent of
|
||||
/// forwarding state — moves no cursor and flips nothing. At most 64 ids per call.
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<AuditEvent>> ReadByExecutionIdsAsync(
|
||||
IReadOnlyCollection<Guid> executionIds, DateTime sinceUtc, int limit, CancellationToken ct = default);
|
||||
}
|
||||
|
||||
@@ -47,7 +47,11 @@ public class StreamRelayActor : ReceiveActor
|
||||
AttributeName = msg.AttributeName,
|
||||
Value = AttributeValueCodec.Encode(msg.Value) ?? string.Empty,
|
||||
Quality = MapQuality(msg.Quality),
|
||||
Timestamp = Timestamp.FromDateTimeOffset(msg.Timestamp)
|
||||
Timestamp = Timestamp.FromDateTimeOffset(msg.Timestamp),
|
||||
// Flow Debugger (design 2026-09-05 §5.3/§5.4): additive fields 7/8 — empty /
|
||||
// false for every change that did not come from a traced write.
|
||||
SourceExecutionId = msg.SourceExecutionId?.ToString("D") ?? string.Empty,
|
||||
Trace = msg.Trace
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -557,7 +557,11 @@ public class SiteStreamGrpcClient : IAsyncDisposable, IDisposable
|
||||
evt.AttributeChanged.AttributeName,
|
||||
evt.AttributeChanged.Value,
|
||||
MapQuality(evt.AttributeChanged.Quality),
|
||||
evt.AttributeChanged.Timestamp.ToDateTimeOffset()),
|
||||
evt.AttributeChanged.Timestamp.ToDateTimeOffset(),
|
||||
// Flow Debugger (design §5.4): fields 7/8 — an older site leaves them at the
|
||||
// proto3 defaults, which map to null / false.
|
||||
SourceExecutionId: Guid.TryParse(evt.AttributeChanged.SourceExecutionId, out var traceSource) ? traceSource : null,
|
||||
Trace: evt.AttributeChanged.Trace),
|
||||
SiteStreamEvent.EventOneofCase.AlarmChanged => new AlarmStateChanged(
|
||||
evt.AlarmChanged.InstanceUniqueName,
|
||||
evt.AlarmChanged.AlarmName,
|
||||
|
||||
@@ -641,6 +641,77 @@ public class SiteStreamGrpcServer : SiteStreamService.SiteStreamServiceBase
|
||||
return ack;
|
||||
}
|
||||
|
||||
/// <summary>Flow Debugger PullTraceEvents: server-side row cap (design 2026-09-05 §5.4).</summary>
|
||||
public const int MaxTraceEventsPerPull = 2000;
|
||||
|
||||
/// <summary>Flow Debugger PullTraceEvents: server-side id cap, matching the site queue's.</summary>
|
||||
public const int MaxTraceExecutionIdsPerPull = 64;
|
||||
|
||||
/// <summary>
|
||||
/// Flow Debugger (design 2026-09-05 §5.4): point read of this site's audit rows whose
|
||||
/// <c>ExecutionId</c> or <c>ParentExecutionId</c> is one of the requested ids. NOT a
|
||||
/// reconciliation path — moves no cursor, flips no ForwardState. Gated by the same
|
||||
/// <c>ControlPlaneAuthInterceptor</c> prefix as <see cref="PullAuditEvents"/>.
|
||||
/// </summary>
|
||||
public override async Task<PullTraceEventsResponse> PullTraceEvents(
|
||||
PullTraceEventsRequest request,
|
||||
ServerCallContext context)
|
||||
{
|
||||
var queue = _siteAuditQueue;
|
||||
if (queue is null)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"PullTraceEvents invoked before SetSiteAuditQueue was called; returning empty response.");
|
||||
return new PullTraceEventsResponse();
|
||||
}
|
||||
|
||||
if (request.MaxEvents <= 0 || request.MaxEvents > MaxTraceEventsPerPull)
|
||||
{
|
||||
throw new RpcException(new GrpcStatus(
|
||||
StatusCode.InvalidArgument, $"max_events must be 1..{MaxTraceEventsPerPull}"));
|
||||
}
|
||||
if (request.ExecutionIds.Count == 0 || request.ExecutionIds.Count > MaxTraceExecutionIdsPerPull)
|
||||
{
|
||||
throw new RpcException(new GrpcStatus(
|
||||
StatusCode.InvalidArgument, $"execution_ids must hold 1..{MaxTraceExecutionIdsPerPull} ids"));
|
||||
}
|
||||
|
||||
var ids = new List<Guid>(request.ExecutionIds.Count);
|
||||
foreach (var raw in request.ExecutionIds)
|
||||
{
|
||||
if (!Guid.TryParse(raw, out var id))
|
||||
{
|
||||
throw new RpcException(new GrpcStatus(
|
||||
StatusCode.InvalidArgument, $"execution_ids entry '{raw}' is not a GUID"));
|
||||
}
|
||||
ids.Add(id);
|
||||
}
|
||||
|
||||
var since = request.SinceUtc is not null
|
||||
? DateTime.SpecifyKind(request.SinceUtc.ToDateTime(), DateTimeKind.Utc)
|
||||
: DateTime.MinValue;
|
||||
|
||||
IReadOnlyList<AuditEvent> events;
|
||||
try
|
||||
{
|
||||
events = await queue.ReadByExecutionIdsAsync(ids, since, request.MaxEvents, context.CancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex,
|
||||
"ReadByExecutionIdsAsync failed for {Count} ids since={Since}; returning empty response.",
|
||||
ids.Count, since);
|
||||
return new PullTraceEventsResponse();
|
||||
}
|
||||
|
||||
var response = new PullTraceEventsResponse();
|
||||
foreach (var evt in events)
|
||||
{
|
||||
response.Events.Add(AuditEventDtoMapper.ToDto(evt));
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<PullAuditEventsResponse> PullAuditEvents(
|
||||
PullAuditEventsRequest request,
|
||||
|
||||
@@ -14,6 +14,10 @@ service SiteStreamService {
|
||||
rpc IngestAuditEvents(AuditEventBatch) returns (IngestAck);
|
||||
rpc IngestCachedTelemetry(CachedTelemetryBatch) returns (IngestAck);
|
||||
rpc PullAuditEvents(PullAuditEventsRequest) returns (PullAuditEventsResponse);
|
||||
// Flow Debugger (design 2026-09-05 §5.4): point read of a site's ScriptTrace rows for a
|
||||
// set of execution ids (by ExecutionId OR ParentExecutionId). NOT a reconciliation path —
|
||||
// moves no cursor, flips no ForwardState. Same PSK interceptor as PullAuditEvents.
|
||||
rpc PullTraceEvents(PullTraceEventsRequest) returns (PullTraceEventsResponse);
|
||||
rpc PullSiteCalls(PullSiteCallsRequest) returns (PullSiteCallsResponse);
|
||||
}
|
||||
|
||||
@@ -95,6 +99,11 @@ message AttributeValueUpdate {
|
||||
string value = 4;
|
||||
Quality quality = 5;
|
||||
google.protobuf.Timestamp timestamp = 6;
|
||||
// Flow Debugger (design 2026-09-05 §5.3/§5.4): the writing execution's id ("D" GUID
|
||||
// form; empty represents null) and the trace flag, so the debug stream can hand the
|
||||
// debugger the spawned run's parent. Additive; an older site never sets them.
|
||||
string source_execution_id = 7;
|
||||
bool trace = 8;
|
||||
}
|
||||
|
||||
message AlarmStateUpdate {
|
||||
@@ -218,6 +227,20 @@ message PullAuditEventsResponse {
|
||||
bool more_available = 2;
|
||||
}
|
||||
|
||||
// Flow Debugger (design 2026-09-05 §5.4): PullTraceEvents request/response. The site
|
||||
// returns audit rows whose ExecutionId or ParentExecutionId is in execution_ids and whose
|
||||
// OccurredAtUtc >= since_utc, ordered (OccurredAtUtc, EventId), at most max_events (server
|
||||
// caps at 2000). Ids capped at 64 per call.
|
||||
message PullTraceEventsRequest {
|
||||
repeated string execution_ids = 1; // "D" GUID form
|
||||
google.protobuf.Timestamp since_utc = 2;
|
||||
int32 max_events = 3;
|
||||
}
|
||||
|
||||
message PullTraceEventsResponse {
|
||||
repeated AuditEventDto events = 1;
|
||||
}
|
||||
|
||||
// Site Call Audit (#22) reconciliation pull: central→site request for any
|
||||
// site-local operation-tracking rows whose UpdatedAtUtc >= since_utc — the
|
||||
// self-heal feed that backfills the eventually-consistent central SiteCalls
|
||||
|
||||
@@ -36,85 +36,92 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
|
||||
"ZBgDIAEoCzIcLnNpdGVzdHJlYW0uQWxhcm1TdGF0ZVVwZGF0ZUgAEjEKBWJh",
|
||||
"dGNoGAQgASgLMiAuc2l0ZXN0cmVhbS5TaXRlU3RyZWFtRXZlbnRCYXRjaEgA",
|
||||
"QgcKBWV2ZW50IkMKFFNpdGVTdHJlYW1FdmVudEJhdGNoEisKBmV2ZW50cxgB",
|
||||
"IAMoCzIbLnNpdGVzdHJlYW0uU2l0ZVN0cmVhbUV2ZW50IsgBChRBdHRyaWJ1",
|
||||
"IAMoCzIbLnNpdGVzdHJlYW0uU2l0ZVN0cmVhbUV2ZW50IvQBChRBdHRyaWJ1",
|
||||
"dGVWYWx1ZVVwZGF0ZRIcChRpbnN0YW5jZV91bmlxdWVfbmFtZRgBIAEoCRIW",
|
||||
"Cg5hdHRyaWJ1dGVfcGF0aBgCIAEoCRIWCg5hdHRyaWJ1dGVfbmFtZRgDIAEo",
|
||||
"CRINCgV2YWx1ZRgEIAEoCRIkCgdxdWFsaXR5GAUgASgOMhMuc2l0ZXN0cmVh",
|
||||
"bS5RdWFsaXR5Ei0KCXRpbWVzdGFtcBgGIAEoCzIaLmdvb2dsZS5wcm90b2J1",
|
||||
"Zi5UaW1lc3RhbXAirwUKEEFsYXJtU3RhdGVVcGRhdGUSHAoUaW5zdGFuY2Vf",
|
||||
"dW5pcXVlX25hbWUYASABKAkSEgoKYWxhcm1fbmFtZRgCIAEoCRIpCgVzdGF0",
|
||||
"ZRgDIAEoDjIaLnNpdGVzdHJlYW0uQWxhcm1TdGF0ZUVudW0SEAoIcHJpb3Jp",
|
||||
"dHkYBCABKAUSLQoJdGltZXN0YW1wGAUgASgLMhouZ29vZ2xlLnByb3RvYnVm",
|
||||
"LlRpbWVzdGFtcBIpCgVsZXZlbBgGIAEoDjIaLnNpdGVzdHJlYW0uQWxhcm1M",
|
||||
"ZXZlbEVudW0SDwoHbWVzc2FnZRgHIAEoCRIMCgRraW5kGAggASgJEg4KBmFj",
|
||||
"dGl2ZRgJIAEoCBIUCgxhY2tub3dsZWRnZWQYCiABKAgSEQoJY29uZmlybWVk",
|
||||
"GAsgASgIEhQKDHNoZWx2ZV9zdGF0ZRgMIAEoCRISCgpzdXBwcmVzc2VkGA0g",
|
||||
"ASgIEhgKEHNvdXJjZV9yZWZlcmVuY2UYDiABKAkSFwoPYWxhcm1fdHlwZV9u",
|
||||
"YW1lGA8gASgJEhAKCGNhdGVnb3J5GBAgASgJEhUKDW9wZXJhdG9yX3VzZXIY",
|
||||
"ESABKAkSGAoQb3BlcmF0b3JfY29tbWVudBgSIAEoCRI3ChNvcmlnaW5hbF9y",
|
||||
"YWlzZV90aW1lGBMgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIV",
|
||||
"Cg1jdXJyZW50X3ZhbHVlGBQgASgJEhMKC2xpbWl0X3ZhbHVlGBUgASgJEiQK",
|
||||
"HG5hdGl2ZV9zb3VyY2VfY2Fub25pY2FsX25hbWUYFiABKAkSIQoZaXNfY29u",
|
||||
"ZmlndXJlZF9wbGFjZWhvbGRlchgXIAEoCBIsCghhY2tfdGltZRgYIAEoCzIa",
|
||||
"Lmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAivQQKDUF1ZGl0RXZlbnREdG8S",
|
||||
"EAoIZXZlbnRfaWQYASABKAkSMwoPb2NjdXJyZWRfYXRfdXRjGAIgASgLMhou",
|
||||
"Z29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIPCgdjaGFubmVsGAMgASgJEgwK",
|
||||
"BGtpbmQYBCABKAkSFgoOY29ycmVsYXRpb25faWQYBSABKAkSFgoOc291cmNl",
|
||||
"X3NpdGVfaWQYBiABKAkSGgoSc291cmNlX2luc3RhbmNlX2lkGAcgASgJEhUK",
|
||||
"DXNvdXJjZV9zY3JpcHQYCCABKAkSDQoFYWN0b3IYCSABKAkSDgoGdGFyZ2V0",
|
||||
"GAogASgJEg4KBnN0YXR1cxgLIAEoCRIwCgtodHRwX3N0YXR1cxgMIAEoCzIb",
|
||||
"Lmdvb2dsZS5wcm90b2J1Zi5JbnQzMlZhbHVlEjAKC2R1cmF0aW9uX21zGA0g",
|
||||
"ASgLMhsuZ29vZ2xlLnByb3RvYnVmLkludDMyVmFsdWUSFQoNZXJyb3JfbWVz",
|
||||
"c2FnZRgOIAEoCRIUCgxlcnJvcl9kZXRhaWwYDyABKAkSFwoPcmVxdWVzdF9z",
|
||||
"dW1tYXJ5GBAgASgJEhgKEHJlc3BvbnNlX3N1bW1hcnkYESABKAkSGQoRcGF5",
|
||||
"bG9hZF90cnVuY2F0ZWQYEiABKAgSDQoFZXh0cmEYEyABKAkSFAoMZXhlY3V0",
|
||||
"aW9uX2lkGBQgASgJEhsKE3BhcmVudF9leGVjdXRpb25faWQYFSABKAkSEwoL",
|
||||
"c291cmNlX25vZGUYFiABKAkiPAoPQXVkaXRFdmVudEJhdGNoEikKBmV2ZW50",
|
||||
"cxgBIAMoCzIZLnNpdGVzdHJlYW0uQXVkaXRFdmVudER0byInCglJbmdlc3RB",
|
||||
"Y2sSGgoSYWNjZXB0ZWRfZXZlbnRfaWRzGAEgAygJIokDChZTaXRlQ2FsbE9w",
|
||||
"ZXJhdGlvbmFsRHRvEhwKFHRyYWNrZWRfb3BlcmF0aW9uX2lkGAEgASgJEg8K",
|
||||
"B2NoYW5uZWwYAiABKAkSDgoGdGFyZ2V0GAMgASgJEhMKC3NvdXJjZV9zaXRl",
|
||||
"GAQgASgJEg4KBnN0YXR1cxgFIAEoCRITCgtyZXRyeV9jb3VudBgGIAEoBRIS",
|
||||
"CgpsYXN0X2Vycm9yGAcgASgJEjAKC2h0dHBfc3RhdHVzGAggASgLMhsuZ29v",
|
||||
"Z2xlLnByb3RvYnVmLkludDMyVmFsdWUSMgoOY3JlYXRlZF9hdF91dGMYCSAB",
|
||||
"KAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEjIKDnVwZGF0ZWRfYXRf",
|
||||
"dXRjGAogASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIzCg90ZXJt",
|
||||
"aW5hbF9hdF91dGMYCyABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1w",
|
||||
"EhMKC3NvdXJjZV9ub2RlGAwgASgJIoABChVDYWNoZWRUZWxlbWV0cnlQYWNr",
|
||||
"ZXQSLgoLYXVkaXRfZXZlbnQYASABKAsyGS5zaXRlc3RyZWFtLkF1ZGl0RXZl",
|
||||
"bnREdG8SNwoLb3BlcmF0aW9uYWwYAiABKAsyIi5zaXRlc3RyZWFtLlNpdGVD",
|
||||
"YWxsT3BlcmF0aW9uYWxEdG8iSgoUQ2FjaGVkVGVsZW1ldHJ5QmF0Y2gSMgoH",
|
||||
"cGFja2V0cxgBIAMoCzIhLnNpdGVzdHJlYW0uQ2FjaGVkVGVsZW1ldHJ5UGFj",
|
||||
"a2V0Im0KFlB1bGxBdWRpdEV2ZW50c1JlcXVlc3QSLQoJc2luY2VfdXRjGAEg",
|
||||
"ASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBISCgpiYXRjaF9zaXpl",
|
||||
"GAIgASgFEhAKCGFmdGVyX2lkGAMgASgJIlwKF1B1bGxBdWRpdEV2ZW50c1Jl",
|
||||
"c3BvbnNlEikKBmV2ZW50cxgBIAMoCzIZLnNpdGVzdHJlYW0uQXVkaXRFdmVu",
|
||||
"dER0bxIWCg5tb3JlX2F2YWlsYWJsZRgCIAEoCCJrChRQdWxsU2l0ZUNhbGxz",
|
||||
"UmVxdWVzdBItCglzaW5jZV91dGMYASABKAsyGi5nb29nbGUucHJvdG9idWYu",
|
||||
"VGltZXN0YW1wEhIKCmJhdGNoX3NpemUYAiABKAUSEAoIYWZ0ZXJfaWQYAyAB",
|
||||
"KAkiaQoVUHVsbFNpdGVDYWxsc1Jlc3BvbnNlEjgKDG9wZXJhdGlvbmFscxgB",
|
||||
"IAMoCzIiLnNpdGVzdHJlYW0uU2l0ZUNhbGxPcGVyYXRpb25hbER0bxIWCg5t",
|
||||
"b3JlX2F2YWlsYWJsZRgCIAEoCCpcCgdRdWFsaXR5EhcKE1FVQUxJVFlfVU5T",
|
||||
"UEVDSUZJRUQQABIQCgxRVUFMSVRZX0dPT0QQARIVChFRVUFMSVRZX1VOQ0VS",
|
||||
"VEFJThACEg8KC1FVQUxJVFlfQkFEEAMqXQoOQWxhcm1TdGF0ZUVudW0SGwoX",
|
||||
"QUxBUk1fU1RBVEVfVU5TUEVDSUZJRUQQABIWChJBTEFSTV9TVEFURV9OT1JN",
|
||||
"QUwQARIWChJBTEFSTV9TVEFURV9BQ1RJVkUQAiqFAQoOQWxhcm1MZXZlbEVu",
|
||||
"dW0SFAoQQUxBUk1fTEVWRUxfTk9ORRAAEhMKD0FMQVJNX0xFVkVMX0xPVxAB",
|
||||
"EhcKE0FMQVJNX0xFVkVMX0xPV19MT1cQAhIUChBBTEFSTV9MRVZFTF9ISUdI",
|
||||
"EAMSGQoVQUxBUk1fTEVWRUxfSElHSF9ISUdIEAQyhgQKEVNpdGVTdHJlYW1T",
|
||||
"ZXJ2aWNlElUKEVN1YnNjcmliZUluc3RhbmNlEiEuc2l0ZXN0cmVhbS5JbnN0",
|
||||
"YW5jZVN0cmVhbVJlcXVlc3QaGy5zaXRlc3RyZWFtLlNpdGVTdHJlYW1FdmVu",
|
||||
"dDABEk0KDVN1YnNjcmliZVNpdGUSHS5zaXRlc3RyZWFtLlNpdGVTdHJlYW1S",
|
||||
"ZXF1ZXN0Ghsuc2l0ZXN0cmVhbS5TaXRlU3RyZWFtRXZlbnQwARJHChFJbmdl",
|
||||
"c3RBdWRpdEV2ZW50cxIbLnNpdGVzdHJlYW0uQXVkaXRFdmVudEJhdGNoGhUu",
|
||||
"c2l0ZXN0cmVhbS5Jbmdlc3RBY2sSUAoVSW5nZXN0Q2FjaGVkVGVsZW1ldHJ5",
|
||||
"EiAuc2l0ZXN0cmVhbS5DYWNoZWRUZWxlbWV0cnlCYXRjaBoVLnNpdGVzdHJl",
|
||||
"YW0uSW5nZXN0QWNrEloKD1B1bGxBdWRpdEV2ZW50cxIiLnNpdGVzdHJlYW0u",
|
||||
"UHVsbEF1ZGl0RXZlbnRzUmVxdWVzdBojLnNpdGVzdHJlYW0uUHVsbEF1ZGl0",
|
||||
"RXZlbnRzUmVzcG9uc2USVAoNUHVsbFNpdGVDYWxscxIgLnNpdGVzdHJlYW0u",
|
||||
"UHVsbFNpdGVDYWxsc1JlcXVlc3QaIS5zaXRlc3RyZWFtLlB1bGxTaXRlQ2Fs",
|
||||
"bHNSZXNwb25zZUIrqgIoWkIuTU9NLldXLlNjYWRhQnJpZGdlLkNvbW11bmlj",
|
||||
"YXRpb24uR3JwY2IGcHJvdG8z"));
|
||||
"Zi5UaW1lc3RhbXASGwoTc291cmNlX2V4ZWN1dGlvbl9pZBgHIAEoCRINCgV0",
|
||||
"cmFjZRgIIAEoCCKvBQoQQWxhcm1TdGF0ZVVwZGF0ZRIcChRpbnN0YW5jZV91",
|
||||
"bmlxdWVfbmFtZRgBIAEoCRISCgphbGFybV9uYW1lGAIgASgJEikKBXN0YXRl",
|
||||
"GAMgASgOMhouc2l0ZXN0cmVhbS5BbGFybVN0YXRlRW51bRIQCghwcmlvcml0",
|
||||
"eRgEIAEoBRItCgl0aW1lc3RhbXAYBSABKAsyGi5nb29nbGUucHJvdG9idWYu",
|
||||
"VGltZXN0YW1wEikKBWxldmVsGAYgASgOMhouc2l0ZXN0cmVhbS5BbGFybUxl",
|
||||
"dmVsRW51bRIPCgdtZXNzYWdlGAcgASgJEgwKBGtpbmQYCCABKAkSDgoGYWN0",
|
||||
"aXZlGAkgASgIEhQKDGFja25vd2xlZGdlZBgKIAEoCBIRCgljb25maXJtZWQY",
|
||||
"CyABKAgSFAoMc2hlbHZlX3N0YXRlGAwgASgJEhIKCnN1cHByZXNzZWQYDSAB",
|
||||
"KAgSGAoQc291cmNlX3JlZmVyZW5jZRgOIAEoCRIXCg9hbGFybV90eXBlX25h",
|
||||
"bWUYDyABKAkSEAoIY2F0ZWdvcnkYECABKAkSFQoNb3BlcmF0b3JfdXNlchgR",
|
||||
"IAEoCRIYChBvcGVyYXRvcl9jb21tZW50GBIgASgJEjcKE29yaWdpbmFsX3Jh",
|
||||
"aXNlX3RpbWUYEyABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEhUK",
|
||||
"DWN1cnJlbnRfdmFsdWUYFCABKAkSEwoLbGltaXRfdmFsdWUYFSABKAkSJAoc",
|
||||
"bmF0aXZlX3NvdXJjZV9jYW5vbmljYWxfbmFtZRgWIAEoCRIhChlpc19jb25m",
|
||||
"aWd1cmVkX3BsYWNlaG9sZGVyGBcgASgIEiwKCGFja190aW1lGBggASgLMhou",
|
||||
"Z29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCK9BAoNQXVkaXRFdmVudER0bxIQ",
|
||||
"CghldmVudF9pZBgBIAEoCRIzCg9vY2N1cnJlZF9hdF91dGMYAiABKAsyGi5n",
|
||||
"b29nbGUucHJvdG9idWYuVGltZXN0YW1wEg8KB2NoYW5uZWwYAyABKAkSDAoE",
|
||||
"a2luZBgEIAEoCRIWCg5jb3JyZWxhdGlvbl9pZBgFIAEoCRIWCg5zb3VyY2Vf",
|
||||
"c2l0ZV9pZBgGIAEoCRIaChJzb3VyY2VfaW5zdGFuY2VfaWQYByABKAkSFQoN",
|
||||
"c291cmNlX3NjcmlwdBgIIAEoCRINCgVhY3RvchgJIAEoCRIOCgZ0YXJnZXQY",
|
||||
"CiABKAkSDgoGc3RhdHVzGAsgASgJEjAKC2h0dHBfc3RhdHVzGAwgASgLMhsu",
|
||||
"Z29vZ2xlLnByb3RvYnVmLkludDMyVmFsdWUSMAoLZHVyYXRpb25fbXMYDSAB",
|
||||
"KAsyGy5nb29nbGUucHJvdG9idWYuSW50MzJWYWx1ZRIVCg1lcnJvcl9tZXNz",
|
||||
"YWdlGA4gASgJEhQKDGVycm9yX2RldGFpbBgPIAEoCRIXCg9yZXF1ZXN0X3N1",
|
||||
"bW1hcnkYECABKAkSGAoQcmVzcG9uc2Vfc3VtbWFyeRgRIAEoCRIZChFwYXls",
|
||||
"b2FkX3RydW5jYXRlZBgSIAEoCBINCgVleHRyYRgTIAEoCRIUCgxleGVjdXRp",
|
||||
"b25faWQYFCABKAkSGwoTcGFyZW50X2V4ZWN1dGlvbl9pZBgVIAEoCRITCgtz",
|
||||
"b3VyY2Vfbm9kZRgWIAEoCSI8Cg9BdWRpdEV2ZW50QmF0Y2gSKQoGZXZlbnRz",
|
||||
"GAEgAygLMhkuc2l0ZXN0cmVhbS5BdWRpdEV2ZW50RHRvIicKCUluZ2VzdEFj",
|
||||
"axIaChJhY2NlcHRlZF9ldmVudF9pZHMYASADKAkiiQMKFlNpdGVDYWxsT3Bl",
|
||||
"cmF0aW9uYWxEdG8SHAoUdHJhY2tlZF9vcGVyYXRpb25faWQYASABKAkSDwoH",
|
||||
"Y2hhbm5lbBgCIAEoCRIOCgZ0YXJnZXQYAyABKAkSEwoLc291cmNlX3NpdGUY",
|
||||
"BCABKAkSDgoGc3RhdHVzGAUgASgJEhMKC3JldHJ5X2NvdW50GAYgASgFEhIK",
|
||||
"Cmxhc3RfZXJyb3IYByABKAkSMAoLaHR0cF9zdGF0dXMYCCABKAsyGy5nb29n",
|
||||
"bGUucHJvdG9idWYuSW50MzJWYWx1ZRIyCg5jcmVhdGVkX2F0X3V0YxgJIAEo",
|
||||
"CzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASMgoOdXBkYXRlZF9hdF91",
|
||||
"dGMYCiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEjMKD3Rlcm1p",
|
||||
"bmFsX2F0X3V0YxgLIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAS",
|
||||
"EwoLc291cmNlX25vZGUYDCABKAkigAEKFUNhY2hlZFRlbGVtZXRyeVBhY2tl",
|
||||
"dBIuCgthdWRpdF9ldmVudBgBIAEoCzIZLnNpdGVzdHJlYW0uQXVkaXRFdmVu",
|
||||
"dER0bxI3CgtvcGVyYXRpb25hbBgCIAEoCzIiLnNpdGVzdHJlYW0uU2l0ZUNh",
|
||||
"bGxPcGVyYXRpb25hbER0byJKChRDYWNoZWRUZWxlbWV0cnlCYXRjaBIyCgdw",
|
||||
"YWNrZXRzGAEgAygLMiEuc2l0ZXN0cmVhbS5DYWNoZWRUZWxlbWV0cnlQYWNr",
|
||||
"ZXQibQoWUHVsbEF1ZGl0RXZlbnRzUmVxdWVzdBItCglzaW5jZV91dGMYASAB",
|
||||
"KAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEhIKCmJhdGNoX3NpemUY",
|
||||
"AiABKAUSEAoIYWZ0ZXJfaWQYAyABKAkiXAoXUHVsbEF1ZGl0RXZlbnRzUmVz",
|
||||
"cG9uc2USKQoGZXZlbnRzGAEgAygLMhkuc2l0ZXN0cmVhbS5BdWRpdEV2ZW50",
|
||||
"RHRvEhYKDm1vcmVfYXZhaWxhYmxlGAIgASgIInIKFlB1bGxUcmFjZUV2ZW50",
|
||||
"c1JlcXVlc3QSFQoNZXhlY3V0aW9uX2lkcxgBIAMoCRItCglzaW5jZV91dGMY",
|
||||
"AiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEhIKCm1heF9ldmVu",
|
||||
"dHMYAyABKAUiRAoXUHVsbFRyYWNlRXZlbnRzUmVzcG9uc2USKQoGZXZlbnRz",
|
||||
"GAEgAygLMhkuc2l0ZXN0cmVhbS5BdWRpdEV2ZW50RHRvImsKFFB1bGxTaXRl",
|
||||
"Q2FsbHNSZXF1ZXN0Ei0KCXNpbmNlX3V0YxgBIAEoCzIaLmdvb2dsZS5wcm90",
|
||||
"b2J1Zi5UaW1lc3RhbXASEgoKYmF0Y2hfc2l6ZRgCIAEoBRIQCghhZnRlcl9p",
|
||||
"ZBgDIAEoCSJpChVQdWxsU2l0ZUNhbGxzUmVzcG9uc2USOAoMb3BlcmF0aW9u",
|
||||
"YWxzGAEgAygLMiIuc2l0ZXN0cmVhbS5TaXRlQ2FsbE9wZXJhdGlvbmFsRHRv",
|
||||
"EhYKDm1vcmVfYXZhaWxhYmxlGAIgASgIKlwKB1F1YWxpdHkSFwoTUVVBTElU",
|
||||
"WV9VTlNQRUNJRklFRBAAEhAKDFFVQUxJVFlfR09PRBABEhUKEVFVQUxJVFlf",
|
||||
"VU5DRVJUQUlOEAISDwoLUVVBTElUWV9CQUQQAypdCg5BbGFybVN0YXRlRW51",
|
||||
"bRIbChdBTEFSTV9TVEFURV9VTlNQRUNJRklFRBAAEhYKEkFMQVJNX1NUQVRF",
|
||||
"X05PUk1BTBABEhYKEkFMQVJNX1NUQVRFX0FDVElWRRACKoUBCg5BbGFybUxl",
|
||||
"dmVsRW51bRIUChBBTEFSTV9MRVZFTF9OT05FEAASEwoPQUxBUk1fTEVWRUxf",
|
||||
"TE9XEAESFwoTQUxBUk1fTEVWRUxfTE9XX0xPVxACEhQKEEFMQVJNX0xFVkVM",
|
||||
"X0hJR0gQAxIZChVBTEFSTV9MRVZFTF9ISUdIX0hJR0gQBDLiBAoRU2l0ZVN0",
|
||||
"cmVhbVNlcnZpY2USVQoRU3Vic2NyaWJlSW5zdGFuY2USIS5zaXRlc3RyZWFt",
|
||||
"Lkluc3RhbmNlU3RyZWFtUmVxdWVzdBobLnNpdGVzdHJlYW0uU2l0ZVN0cmVh",
|
||||
"bUV2ZW50MAESTQoNU3Vic2NyaWJlU2l0ZRIdLnNpdGVzdHJlYW0uU2l0ZVN0",
|
||||
"cmVhbVJlcXVlc3QaGy5zaXRlc3RyZWFtLlNpdGVTdHJlYW1FdmVudDABEkcK",
|
||||
"EUluZ2VzdEF1ZGl0RXZlbnRzEhsuc2l0ZXN0cmVhbS5BdWRpdEV2ZW50QmF0",
|
||||
"Y2gaFS5zaXRlc3RyZWFtLkluZ2VzdEFjaxJQChVJbmdlc3RDYWNoZWRUZWxl",
|
||||
"bWV0cnkSIC5zaXRlc3RyZWFtLkNhY2hlZFRlbGVtZXRyeUJhdGNoGhUuc2l0",
|
||||
"ZXN0cmVhbS5Jbmdlc3RBY2sSWgoPUHVsbEF1ZGl0RXZlbnRzEiIuc2l0ZXN0",
|
||||
"cmVhbS5QdWxsQXVkaXRFdmVudHNSZXF1ZXN0GiMuc2l0ZXN0cmVhbS5QdWxs",
|
||||
"QXVkaXRFdmVudHNSZXNwb25zZRJaCg9QdWxsVHJhY2VFdmVudHMSIi5zaXRl",
|
||||
"c3RyZWFtLlB1bGxUcmFjZUV2ZW50c1JlcXVlc3QaIy5zaXRlc3RyZWFtLlB1",
|
||||
"bGxUcmFjZUV2ZW50c1Jlc3BvbnNlElQKDVB1bGxTaXRlQ2FsbHMSIC5zaXRl",
|
||||
"c3RyZWFtLlB1bGxTaXRlQ2FsbHNSZXF1ZXN0GiEuc2l0ZXN0cmVhbS5QdWxs",
|
||||
"U2l0ZUNhbGxzUmVzcG9uc2VCK6oCKFpCLk1PTS5XVy5TY2FkYUJyaWRnZS5D",
|
||||
"b21tdW5pY2F0aW9uLkdycGNiBnByb3RvMw=="));
|
||||
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
|
||||
new pbr::FileDescriptor[] { global::Google.Protobuf.WellKnownTypes.TimestampReflection.Descriptor, global::Google.Protobuf.WellKnownTypes.WrappersReflection.Descriptor, },
|
||||
new pbr::GeneratedClrTypeInfo(new[] {typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.Quality), typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AlarmStateEnum), typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AlarmLevelEnum), }, null, new pbr::GeneratedClrTypeInfo[] {
|
||||
@@ -122,7 +129,7 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
|
||||
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.SiteStreamRequest), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.SiteStreamRequest.Parser, new[]{ "CorrelationId", "BatchingSupported" }, null, null, null, null),
|
||||
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.SiteStreamEvent), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.SiteStreamEvent.Parser, new[]{ "CorrelationId", "AttributeChanged", "AlarmChanged", "Batch" }, new[]{ "Event" }, null, null, null),
|
||||
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.SiteStreamEventBatch), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.SiteStreamEventBatch.Parser, new[]{ "Events" }, null, null, null, null),
|
||||
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AttributeValueUpdate), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AttributeValueUpdate.Parser, new[]{ "InstanceUniqueName", "AttributePath", "AttributeName", "Value", "Quality", "Timestamp" }, null, null, null, null),
|
||||
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AttributeValueUpdate), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AttributeValueUpdate.Parser, new[]{ "InstanceUniqueName", "AttributePath", "AttributeName", "Value", "Quality", "Timestamp", "SourceExecutionId", "Trace" }, null, null, null, null),
|
||||
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AlarmStateUpdate), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AlarmStateUpdate.Parser, new[]{ "InstanceUniqueName", "AlarmName", "State", "Priority", "Timestamp", "Level", "Message", "Kind", "Active", "Acknowledged", "Confirmed", "ShelveState", "Suppressed", "SourceReference", "AlarmTypeName", "Category", "OperatorUser", "OperatorComment", "OriginalRaiseTime", "CurrentValue", "LimitValue", "NativeSourceCanonicalName", "IsConfiguredPlaceholder", "AckTime" }, null, null, null, null),
|
||||
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AuditEventDto), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AuditEventDto.Parser, new[]{ "EventId", "OccurredAtUtc", "Channel", "Kind", "CorrelationId", "SourceSiteId", "SourceInstanceId", "SourceScript", "Actor", "Target", "Status", "HttpStatus", "DurationMs", "ErrorMessage", "ErrorDetail", "RequestSummary", "ResponseSummary", "PayloadTruncated", "Extra", "ExecutionId", "ParentExecutionId", "SourceNode" }, null, null, null, null),
|
||||
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AuditEventBatch), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AuditEventBatch.Parser, new[]{ "Events" }, null, null, null, null),
|
||||
@@ -132,6 +139,8 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
|
||||
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.CachedTelemetryBatch), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.CachedTelemetryBatch.Parser, new[]{ "Packets" }, null, null, null, null),
|
||||
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullAuditEventsRequest), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullAuditEventsRequest.Parser, new[]{ "SinceUtc", "BatchSize", "AfterId" }, null, null, null, null),
|
||||
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullAuditEventsResponse), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullAuditEventsResponse.Parser, new[]{ "Events", "MoreAvailable" }, null, null, null, null),
|
||||
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullTraceEventsRequest), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullTraceEventsRequest.Parser, new[]{ "ExecutionIds", "SinceUtc", "MaxEvents" }, null, null, null, null),
|
||||
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullTraceEventsResponse), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullTraceEventsResponse.Parser, new[]{ "Events" }, null, null, null, null),
|
||||
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullSiteCallsRequest), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullSiteCallsRequest.Parser, new[]{ "SinceUtc", "BatchSize", "AfterId" }, null, null, null, null),
|
||||
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullSiteCallsResponse), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullSiteCallsResponse.Parser, new[]{ "Operationals", "MoreAvailable" }, null, null, null, null)
|
||||
}));
|
||||
@@ -1321,6 +1330,8 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
|
||||
value_ = other.value_;
|
||||
quality_ = other.quality_;
|
||||
timestamp_ = other.timestamp_ != null ? other.timestamp_.Clone() : null;
|
||||
sourceExecutionId_ = other.sourceExecutionId_;
|
||||
trace_ = other.trace_;
|
||||
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
|
||||
}
|
||||
|
||||
@@ -1402,6 +1413,35 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "source_execution_id" field.</summary>
|
||||
public const int SourceExecutionIdFieldNumber = 7;
|
||||
private string sourceExecutionId_ = "";
|
||||
/// <summary>
|
||||
/// Flow Debugger (design 2026-09-05 §5.3/§5.4): the writing execution's id ("D" GUID
|
||||
/// form; empty represents null) and the trace flag, so the debug stream can hand the
|
||||
/// debugger the spawned run's parent. Additive; an older site never sets them.
|
||||
/// </summary>
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public string SourceExecutionId {
|
||||
get { return sourceExecutionId_; }
|
||||
set {
|
||||
sourceExecutionId_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "trace" field.</summary>
|
||||
public const int TraceFieldNumber = 8;
|
||||
private bool trace_;
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public bool Trace {
|
||||
get { return trace_; }
|
||||
set {
|
||||
trace_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public override bool Equals(object other) {
|
||||
@@ -1423,6 +1463,8 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
|
||||
if (Value != other.Value) return false;
|
||||
if (Quality != other.Quality) return false;
|
||||
if (!object.Equals(Timestamp, other.Timestamp)) return false;
|
||||
if (SourceExecutionId != other.SourceExecutionId) return false;
|
||||
if (Trace != other.Trace) return false;
|
||||
return Equals(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
@@ -1436,6 +1478,8 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
|
||||
if (Value.Length != 0) hash ^= Value.GetHashCode();
|
||||
if (Quality != global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.Quality.Unspecified) hash ^= Quality.GetHashCode();
|
||||
if (timestamp_ != null) hash ^= Timestamp.GetHashCode();
|
||||
if (SourceExecutionId.Length != 0) hash ^= SourceExecutionId.GetHashCode();
|
||||
if (Trace != false) hash ^= Trace.GetHashCode();
|
||||
if (_unknownFields != null) {
|
||||
hash ^= _unknownFields.GetHashCode();
|
||||
}
|
||||
@@ -1478,6 +1522,14 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
|
||||
output.WriteRawTag(50);
|
||||
output.WriteMessage(Timestamp);
|
||||
}
|
||||
if (SourceExecutionId.Length != 0) {
|
||||
output.WriteRawTag(58);
|
||||
output.WriteString(SourceExecutionId);
|
||||
}
|
||||
if (Trace != false) {
|
||||
output.WriteRawTag(64);
|
||||
output.WriteBool(Trace);
|
||||
}
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(output);
|
||||
}
|
||||
@@ -1512,6 +1564,14 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
|
||||
output.WriteRawTag(50);
|
||||
output.WriteMessage(Timestamp);
|
||||
}
|
||||
if (SourceExecutionId.Length != 0) {
|
||||
output.WriteRawTag(58);
|
||||
output.WriteString(SourceExecutionId);
|
||||
}
|
||||
if (Trace != false) {
|
||||
output.WriteRawTag(64);
|
||||
output.WriteBool(Trace);
|
||||
}
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(ref output);
|
||||
}
|
||||
@@ -1540,6 +1600,12 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
|
||||
if (timestamp_ != null) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeMessageSize(Timestamp);
|
||||
}
|
||||
if (SourceExecutionId.Length != 0) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeStringSize(SourceExecutionId);
|
||||
}
|
||||
if (Trace != false) {
|
||||
size += 1 + 1;
|
||||
}
|
||||
if (_unknownFields != null) {
|
||||
size += _unknownFields.CalculateSize();
|
||||
}
|
||||
@@ -1573,6 +1639,12 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
|
||||
}
|
||||
Timestamp.MergeFrom(other.Timestamp);
|
||||
}
|
||||
if (other.SourceExecutionId.Length != 0) {
|
||||
SourceExecutionId = other.SourceExecutionId;
|
||||
}
|
||||
if (other.Trace != false) {
|
||||
Trace = other.Trace;
|
||||
}
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
@@ -1619,6 +1691,14 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
|
||||
input.ReadMessage(Timestamp);
|
||||
break;
|
||||
}
|
||||
case 58: {
|
||||
SourceExecutionId = input.ReadString();
|
||||
break;
|
||||
}
|
||||
case 64: {
|
||||
Trace = input.ReadBool();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1665,6 +1745,14 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
|
||||
input.ReadMessage(Timestamp);
|
||||
break;
|
||||
}
|
||||
case 58: {
|
||||
SourceExecutionId = input.ReadString();
|
||||
break;
|
||||
}
|
||||
case 64: {
|
||||
Trace = input.ReadBool();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5813,6 +5901,472 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flow Debugger (design 2026-09-05 §5.4): PullTraceEvents request/response. The site
|
||||
/// returns audit rows whose ExecutionId or ParentExecutionId is in execution_ids and whose
|
||||
/// OccurredAtUtc >= since_utc, ordered (OccurredAtUtc, EventId), at most max_events (server
|
||||
/// caps at 2000). Ids capped at 64 per call.
|
||||
/// </summary>
|
||||
[global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
|
||||
public sealed partial class PullTraceEventsRequest : pb::IMessage<PullTraceEventsRequest>
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
, pb::IBufferMessage
|
||||
#endif
|
||||
{
|
||||
private static readonly pb::MessageParser<PullTraceEventsRequest> _parser = new pb::MessageParser<PullTraceEventsRequest>(() => new PullTraceEventsRequest());
|
||||
private pb::UnknownFieldSet _unknownFields;
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public static pb::MessageParser<PullTraceEventsRequest> Parser { get { return _parser; } }
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public static pbr::MessageDescriptor Descriptor {
|
||||
get { return global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.SitestreamReflection.Descriptor.MessageTypes[14]; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
pbr::MessageDescriptor pb::IMessage.Descriptor {
|
||||
get { return Descriptor; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public PullTraceEventsRequest() {
|
||||
OnConstruction();
|
||||
}
|
||||
|
||||
partial void OnConstruction();
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public PullTraceEventsRequest(PullTraceEventsRequest other) : this() {
|
||||
executionIds_ = other.executionIds_.Clone();
|
||||
sinceUtc_ = other.sinceUtc_ != null ? other.sinceUtc_.Clone() : null;
|
||||
maxEvents_ = other.maxEvents_;
|
||||
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public PullTraceEventsRequest Clone() {
|
||||
return new PullTraceEventsRequest(this);
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "execution_ids" field.</summary>
|
||||
public const int ExecutionIdsFieldNumber = 1;
|
||||
private static readonly pb::FieldCodec<string> _repeated_executionIds_codec
|
||||
= pb::FieldCodec.ForString(10);
|
||||
private readonly pbc::RepeatedField<string> executionIds_ = new pbc::RepeatedField<string>();
|
||||
/// <summary>
|
||||
/// "D" GUID form
|
||||
/// </summary>
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public pbc::RepeatedField<string> ExecutionIds {
|
||||
get { return executionIds_; }
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "since_utc" field.</summary>
|
||||
public const int SinceUtcFieldNumber = 2;
|
||||
private global::Google.Protobuf.WellKnownTypes.Timestamp sinceUtc_;
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public global::Google.Protobuf.WellKnownTypes.Timestamp SinceUtc {
|
||||
get { return sinceUtc_; }
|
||||
set {
|
||||
sinceUtc_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "max_events" field.</summary>
|
||||
public const int MaxEventsFieldNumber = 3;
|
||||
private int maxEvents_;
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public int MaxEvents {
|
||||
get { return maxEvents_; }
|
||||
set {
|
||||
maxEvents_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public override bool Equals(object other) {
|
||||
return Equals(other as PullTraceEventsRequest);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public bool Equals(PullTraceEventsRequest other) {
|
||||
if (ReferenceEquals(other, null)) {
|
||||
return false;
|
||||
}
|
||||
if (ReferenceEquals(other, this)) {
|
||||
return true;
|
||||
}
|
||||
if(!executionIds_.Equals(other.executionIds_)) return false;
|
||||
if (!object.Equals(SinceUtc, other.SinceUtc)) return false;
|
||||
if (MaxEvents != other.MaxEvents) return false;
|
||||
return Equals(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public override int GetHashCode() {
|
||||
int hash = 1;
|
||||
hash ^= executionIds_.GetHashCode();
|
||||
if (sinceUtc_ != null) hash ^= SinceUtc.GetHashCode();
|
||||
if (MaxEvents != 0) hash ^= MaxEvents.GetHashCode();
|
||||
if (_unknownFields != null) {
|
||||
hash ^= _unknownFields.GetHashCode();
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public override string ToString() {
|
||||
return pb::JsonFormatter.ToDiagnosticString(this);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public void WriteTo(pb::CodedOutputStream output) {
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
output.WriteRawMessage(this);
|
||||
#else
|
||||
executionIds_.WriteTo(output, _repeated_executionIds_codec);
|
||||
if (sinceUtc_ != null) {
|
||||
output.WriteRawTag(18);
|
||||
output.WriteMessage(SinceUtc);
|
||||
}
|
||||
if (MaxEvents != 0) {
|
||||
output.WriteRawTag(24);
|
||||
output.WriteInt32(MaxEvents);
|
||||
}
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(output);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
|
||||
executionIds_.WriteTo(ref output, _repeated_executionIds_codec);
|
||||
if (sinceUtc_ != null) {
|
||||
output.WriteRawTag(18);
|
||||
output.WriteMessage(SinceUtc);
|
||||
}
|
||||
if (MaxEvents != 0) {
|
||||
output.WriteRawTag(24);
|
||||
output.WriteInt32(MaxEvents);
|
||||
}
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(ref output);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public int CalculateSize() {
|
||||
int size = 0;
|
||||
size += executionIds_.CalculateSize(_repeated_executionIds_codec);
|
||||
if (sinceUtc_ != null) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeMessageSize(SinceUtc);
|
||||
}
|
||||
if (MaxEvents != 0) {
|
||||
size += 1 + pb::CodedOutputStream.ComputeInt32Size(MaxEvents);
|
||||
}
|
||||
if (_unknownFields != null) {
|
||||
size += _unknownFields.CalculateSize();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public void MergeFrom(PullTraceEventsRequest other) {
|
||||
if (other == null) {
|
||||
return;
|
||||
}
|
||||
executionIds_.Add(other.executionIds_);
|
||||
if (other.sinceUtc_ != null) {
|
||||
if (sinceUtc_ == null) {
|
||||
SinceUtc = new global::Google.Protobuf.WellKnownTypes.Timestamp();
|
||||
}
|
||||
SinceUtc.MergeFrom(other.SinceUtc);
|
||||
}
|
||||
if (other.MaxEvents != 0) {
|
||||
MaxEvents = other.MaxEvents;
|
||||
}
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public void MergeFrom(pb::CodedInputStream input) {
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
input.ReadRawMessage(this);
|
||||
#else
|
||||
uint tag;
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
if ((tag & 7) == 4) {
|
||||
// Abort on any end group tag.
|
||||
return;
|
||||
}
|
||||
switch(tag) {
|
||||
default:
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
|
||||
break;
|
||||
case 10: {
|
||||
executionIds_.AddEntriesFrom(input, _repeated_executionIds_codec);
|
||||
break;
|
||||
}
|
||||
case 18: {
|
||||
if (sinceUtc_ == null) {
|
||||
SinceUtc = new global::Google.Protobuf.WellKnownTypes.Timestamp();
|
||||
}
|
||||
input.ReadMessage(SinceUtc);
|
||||
break;
|
||||
}
|
||||
case 24: {
|
||||
MaxEvents = input.ReadInt32();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
|
||||
uint tag;
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
if ((tag & 7) == 4) {
|
||||
// Abort on any end group tag.
|
||||
return;
|
||||
}
|
||||
switch(tag) {
|
||||
default:
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
|
||||
break;
|
||||
case 10: {
|
||||
executionIds_.AddEntriesFrom(ref input, _repeated_executionIds_codec);
|
||||
break;
|
||||
}
|
||||
case 18: {
|
||||
if (sinceUtc_ == null) {
|
||||
SinceUtc = new global::Google.Protobuf.WellKnownTypes.Timestamp();
|
||||
}
|
||||
input.ReadMessage(SinceUtc);
|
||||
break;
|
||||
}
|
||||
case 24: {
|
||||
MaxEvents = input.ReadInt32();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
|
||||
public sealed partial class PullTraceEventsResponse : pb::IMessage<PullTraceEventsResponse>
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
, pb::IBufferMessage
|
||||
#endif
|
||||
{
|
||||
private static readonly pb::MessageParser<PullTraceEventsResponse> _parser = new pb::MessageParser<PullTraceEventsResponse>(() => new PullTraceEventsResponse());
|
||||
private pb::UnknownFieldSet _unknownFields;
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public static pb::MessageParser<PullTraceEventsResponse> Parser { get { return _parser; } }
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public static pbr::MessageDescriptor Descriptor {
|
||||
get { return global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.SitestreamReflection.Descriptor.MessageTypes[15]; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
pbr::MessageDescriptor pb::IMessage.Descriptor {
|
||||
get { return Descriptor; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public PullTraceEventsResponse() {
|
||||
OnConstruction();
|
||||
}
|
||||
|
||||
partial void OnConstruction();
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public PullTraceEventsResponse(PullTraceEventsResponse other) : this() {
|
||||
events_ = other.events_.Clone();
|
||||
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public PullTraceEventsResponse Clone() {
|
||||
return new PullTraceEventsResponse(this);
|
||||
}
|
||||
|
||||
/// <summary>Field number for the "events" field.</summary>
|
||||
public const int EventsFieldNumber = 1;
|
||||
private static readonly pb::FieldCodec<global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AuditEventDto> _repeated_events_codec
|
||||
= pb::FieldCodec.ForMessage(10, global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AuditEventDto.Parser);
|
||||
private readonly pbc::RepeatedField<global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AuditEventDto> events_ = new pbc::RepeatedField<global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AuditEventDto>();
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public pbc::RepeatedField<global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AuditEventDto> Events {
|
||||
get { return events_; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public override bool Equals(object other) {
|
||||
return Equals(other as PullTraceEventsResponse);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public bool Equals(PullTraceEventsResponse other) {
|
||||
if (ReferenceEquals(other, null)) {
|
||||
return false;
|
||||
}
|
||||
if (ReferenceEquals(other, this)) {
|
||||
return true;
|
||||
}
|
||||
if(!events_.Equals(other.events_)) return false;
|
||||
return Equals(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public override int GetHashCode() {
|
||||
int hash = 1;
|
||||
hash ^= events_.GetHashCode();
|
||||
if (_unknownFields != null) {
|
||||
hash ^= _unknownFields.GetHashCode();
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public override string ToString() {
|
||||
return pb::JsonFormatter.ToDiagnosticString(this);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public void WriteTo(pb::CodedOutputStream output) {
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
output.WriteRawMessage(this);
|
||||
#else
|
||||
events_.WriteTo(output, _repeated_events_codec);
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(output);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
|
||||
events_.WriteTo(ref output, _repeated_events_codec);
|
||||
if (_unknownFields != null) {
|
||||
_unknownFields.WriteTo(ref output);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public int CalculateSize() {
|
||||
int size = 0;
|
||||
size += events_.CalculateSize(_repeated_events_codec);
|
||||
if (_unknownFields != null) {
|
||||
size += _unknownFields.CalculateSize();
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public void MergeFrom(PullTraceEventsResponse other) {
|
||||
if (other == null) {
|
||||
return;
|
||||
}
|
||||
events_.Add(other.events_);
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public void MergeFrom(pb::CodedInputStream input) {
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
input.ReadRawMessage(this);
|
||||
#else
|
||||
uint tag;
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
if ((tag & 7) == 4) {
|
||||
// Abort on any end group tag.
|
||||
return;
|
||||
}
|
||||
switch(tag) {
|
||||
default:
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
|
||||
break;
|
||||
case 10: {
|
||||
events_.AddEntriesFrom(input, _repeated_events_codec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
|
||||
uint tag;
|
||||
while ((tag = input.ReadTag()) != 0) {
|
||||
if ((tag & 7) == 4) {
|
||||
// Abort on any end group tag.
|
||||
return;
|
||||
}
|
||||
switch(tag) {
|
||||
default:
|
||||
_unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
|
||||
break;
|
||||
case 10: {
|
||||
events_.AddEntriesFrom(ref input, _repeated_events_codec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Site Call Audit (#22) reconciliation pull: central→site request for any
|
||||
/// site-local operation-tracking rows whose UpdatedAtUtc >= since_utc — the
|
||||
@@ -5837,7 +6391,7 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public static pbr::MessageDescriptor Descriptor {
|
||||
get { return global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.SitestreamReflection.Descriptor.MessageTypes[14]; }
|
||||
get { return global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.SitestreamReflection.Descriptor.MessageTypes[16]; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
@@ -6127,7 +6681,7 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
|
||||
public static pbr::MessageDescriptor Descriptor {
|
||||
get { return global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.SitestreamReflection.Descriptor.MessageTypes[15]; }
|
||||
get { return global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.SitestreamReflection.Descriptor.MessageTypes[17]; }
|
||||
}
|
||||
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
|
||||
|
||||
@@ -62,6 +62,10 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
static readonly grpc::Marshaller<global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullAuditEventsResponse> __Marshaller_sitestream_PullAuditEventsResponse = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullAuditEventsResponse.Parser));
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
static readonly grpc::Marshaller<global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullTraceEventsRequest> __Marshaller_sitestream_PullTraceEventsRequest = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullTraceEventsRequest.Parser));
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
static readonly grpc::Marshaller<global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullTraceEventsResponse> __Marshaller_sitestream_PullTraceEventsResponse = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullTraceEventsResponse.Parser));
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
static readonly grpc::Marshaller<global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullSiteCallsRequest> __Marshaller_sitestream_PullSiteCallsRequest = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullSiteCallsRequest.Parser));
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
static readonly grpc::Marshaller<global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullSiteCallsResponse> __Marshaller_sitestream_PullSiteCallsResponse = grpc::Marshallers.Create(__Helper_SerializeMessage, context => __Helper_DeserializeMessage(context, global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullSiteCallsResponse.Parser));
|
||||
@@ -106,6 +110,14 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
|
||||
__Marshaller_sitestream_PullAuditEventsRequest,
|
||||
__Marshaller_sitestream_PullAuditEventsResponse);
|
||||
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
static readonly grpc::Method<global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullTraceEventsRequest, global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullTraceEventsResponse> __Method_PullTraceEvents = new grpc::Method<global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullTraceEventsRequest, global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullTraceEventsResponse>(
|
||||
grpc::MethodType.Unary,
|
||||
__ServiceName,
|
||||
"PullTraceEvents",
|
||||
__Marshaller_sitestream_PullTraceEventsRequest,
|
||||
__Marshaller_sitestream_PullTraceEventsResponse);
|
||||
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
static readonly grpc::Method<global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullSiteCallsRequest, global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullSiteCallsResponse> __Method_PullSiteCalls = new grpc::Method<global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullSiteCallsRequest, global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullSiteCallsResponse>(
|
||||
grpc::MethodType.Unary,
|
||||
@@ -163,6 +175,20 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
|
||||
throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, ""));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flow Debugger (design 2026-09-05 §5.4): point read of a site's ScriptTrace rows for a
|
||||
/// set of execution ids (by ExecutionId OR ParentExecutionId). NOT a reconciliation path —
|
||||
/// moves no cursor, flips no ForwardState. Same PSK interceptor as PullAuditEvents.
|
||||
/// </summary>
|
||||
/// <param name="request">The request received from the client.</param>
|
||||
/// <param name="context">The context of the server-side call handler being invoked.</param>
|
||||
/// <returns>The response to send back to the client (wrapped by a task).</returns>
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
public virtual global::System.Threading.Tasks.Task<global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullTraceEventsResponse> PullTraceEvents(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullTraceEventsRequest request, grpc::ServerCallContext context)
|
||||
{
|
||||
throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, ""));
|
||||
}
|
||||
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
public virtual global::System.Threading.Tasks.Task<global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullSiteCallsResponse> PullSiteCalls(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullSiteCallsRequest request, grpc::ServerCallContext context)
|
||||
{
|
||||
@@ -296,6 +322,62 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
|
||||
{
|
||||
return CallInvoker.AsyncUnaryCall(__Method_PullAuditEvents, null, options, request);
|
||||
}
|
||||
/// <summary>
|
||||
/// Flow Debugger (design 2026-09-05 §5.4): point read of a site's ScriptTrace rows for a
|
||||
/// set of execution ids (by ExecutionId OR ParentExecutionId). NOT a reconciliation path —
|
||||
/// moves no cursor, flips no ForwardState. Same PSK interceptor as PullAuditEvents.
|
||||
/// </summary>
|
||||
/// <param name="request">The request to send to the server.</param>
|
||||
/// <param name="headers">The initial metadata to send with the call. This parameter is optional.</param>
|
||||
/// <param name="deadline">An optional deadline for the call. The call will be cancelled if deadline is hit.</param>
|
||||
/// <param name="cancellationToken">An optional token for canceling the call.</param>
|
||||
/// <returns>The response received from the server.</returns>
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
public virtual global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullTraceEventsResponse PullTraceEvents(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullTraceEventsRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken))
|
||||
{
|
||||
return PullTraceEvents(request, new grpc::CallOptions(headers, deadline, cancellationToken));
|
||||
}
|
||||
/// <summary>
|
||||
/// Flow Debugger (design 2026-09-05 §5.4): point read of a site's ScriptTrace rows for a
|
||||
/// set of execution ids (by ExecutionId OR ParentExecutionId). NOT a reconciliation path —
|
||||
/// moves no cursor, flips no ForwardState. Same PSK interceptor as PullAuditEvents.
|
||||
/// </summary>
|
||||
/// <param name="request">The request to send to the server.</param>
|
||||
/// <param name="options">The options for the call.</param>
|
||||
/// <returns>The response received from the server.</returns>
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
public virtual global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullTraceEventsResponse PullTraceEvents(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullTraceEventsRequest request, grpc::CallOptions options)
|
||||
{
|
||||
return CallInvoker.BlockingUnaryCall(__Method_PullTraceEvents, null, options, request);
|
||||
}
|
||||
/// <summary>
|
||||
/// Flow Debugger (design 2026-09-05 §5.4): point read of a site's ScriptTrace rows for a
|
||||
/// set of execution ids (by ExecutionId OR ParentExecutionId). NOT a reconciliation path —
|
||||
/// moves no cursor, flips no ForwardState. Same PSK interceptor as PullAuditEvents.
|
||||
/// </summary>
|
||||
/// <param name="request">The request to send to the server.</param>
|
||||
/// <param name="headers">The initial metadata to send with the call. This parameter is optional.</param>
|
||||
/// <param name="deadline">An optional deadline for the call. The call will be cancelled if deadline is hit.</param>
|
||||
/// <param name="cancellationToken">An optional token for canceling the call.</param>
|
||||
/// <returns>The call object.</returns>
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
public virtual grpc::AsyncUnaryCall<global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullTraceEventsResponse> PullTraceEventsAsync(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullTraceEventsRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken))
|
||||
{
|
||||
return PullTraceEventsAsync(request, new grpc::CallOptions(headers, deadline, cancellationToken));
|
||||
}
|
||||
/// <summary>
|
||||
/// Flow Debugger (design 2026-09-05 §5.4): point read of a site's ScriptTrace rows for a
|
||||
/// set of execution ids (by ExecutionId OR ParentExecutionId). NOT a reconciliation path —
|
||||
/// moves no cursor, flips no ForwardState. Same PSK interceptor as PullAuditEvents.
|
||||
/// </summary>
|
||||
/// <param name="request">The request to send to the server.</param>
|
||||
/// <param name="options">The options for the call.</param>
|
||||
/// <returns>The call object.</returns>
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
public virtual grpc::AsyncUnaryCall<global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullTraceEventsResponse> PullTraceEventsAsync(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullTraceEventsRequest request, grpc::CallOptions options)
|
||||
{
|
||||
return CallInvoker.AsyncUnaryCall(__Method_PullTraceEvents, null, options, request);
|
||||
}
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]
|
||||
public virtual global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullSiteCallsResponse PullSiteCalls(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullSiteCallsRequest request, grpc::Metadata headers = null, global::System.DateTime? deadline = null, global::System.Threading.CancellationToken cancellationToken = default(global::System.Threading.CancellationToken))
|
||||
{
|
||||
@@ -335,6 +417,7 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
|
||||
.AddMethod(__Method_IngestAuditEvents, serviceImpl.IngestAuditEvents)
|
||||
.AddMethod(__Method_IngestCachedTelemetry, serviceImpl.IngestCachedTelemetry)
|
||||
.AddMethod(__Method_PullAuditEvents, serviceImpl.PullAuditEvents)
|
||||
.AddMethod(__Method_PullTraceEvents, serviceImpl.PullTraceEvents)
|
||||
.AddMethod(__Method_PullSiteCalls, serviceImpl.PullSiteCalls).Build();
|
||||
}
|
||||
|
||||
@@ -350,6 +433,7 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
|
||||
serviceBinder.AddMethod(__Method_IngestAuditEvents, serviceImpl == null ? null : new grpc::UnaryServerMethod<global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AuditEventBatch, global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.IngestAck>(serviceImpl.IngestAuditEvents));
|
||||
serviceBinder.AddMethod(__Method_IngestCachedTelemetry, serviceImpl == null ? null : new grpc::UnaryServerMethod<global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.CachedTelemetryBatch, global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.IngestAck>(serviceImpl.IngestCachedTelemetry));
|
||||
serviceBinder.AddMethod(__Method_PullAuditEvents, serviceImpl == null ? null : new grpc::UnaryServerMethod<global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullAuditEventsRequest, global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullAuditEventsResponse>(serviceImpl.PullAuditEvents));
|
||||
serviceBinder.AddMethod(__Method_PullTraceEvents, serviceImpl == null ? null : new grpc::UnaryServerMethod<global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullTraceEventsRequest, global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullTraceEventsResponse>(serviceImpl.PullTraceEvents));
|
||||
serviceBinder.AddMethod(__Method_PullSiteCalls, serviceImpl == null ? null : new grpc::UnaryServerMethod<global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullSiteCallsRequest, global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.PullSiteCallsResponse>(serviceImpl.PullSiteCalls));
|
||||
}
|
||||
|
||||
|
||||
@@ -127,6 +127,10 @@ public class SiteAuditRetentionServiceTests
|
||||
/// <summary>Stub queue that records purge cutoffs and can throw on the first tick.</summary>
|
||||
private sealed class RecordingSiteAuditQueue : ISiteAuditQueue
|
||||
{
|
||||
public Task<IReadOnlyList<AuditEvent>> ReadByExecutionIdsAsync(
|
||||
IReadOnlyCollection<Guid> executionIds, DateTime sinceUtc, int limit, CancellationToken ct = default)
|
||||
=> throw new NotSupportedException("not exercised by the retention service");
|
||||
|
||||
private readonly ConcurrentQueue<DateTime> _purgeCalls = new();
|
||||
private int _calls;
|
||||
|
||||
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using ZB.MOM.WW.ScadaBridge.AuditLog.Site;
|
||||
using ZB.MOM.WW.ScadaBridge.AuditLog.Tests.TestSupport;
|
||||
using ZB.MOM.WW.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Site;
|
||||
|
||||
/// <summary>
|
||||
/// Flow Debugger (design 2026-09-05 §5.4): <see cref="SqliteAuditWriter.ReadByExecutionIdsAsync"/>
|
||||
/// matches on ExecutionId OR ParentExecutionId, honours the since bound, orders by
|
||||
/// (OccurredAtUtc, EventId), caps the row count, and is independent of forwarding state.
|
||||
/// </summary>
|
||||
public class SqliteAuditWriterReadByExecutionTests : IDisposable
|
||||
{
|
||||
private readonly string _dbPath = Path.Combine(Path.GetTempPath(), $"audit-readbyexec-{Guid.NewGuid():N}.db");
|
||||
private static readonly DateTime T0 = new(2026, 9, 5, 12, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (File.Exists(_dbPath)) { try { File.Delete(_dbPath); } catch { /* best-effort */ } }
|
||||
}
|
||||
|
||||
private SqliteAuditWriter CreateWriter() => new(
|
||||
Options.Create(new SqliteAuditWriterOptions { DatabasePath = _dbPath }),
|
||||
NullLogger<SqliteAuditWriter>.Instance,
|
||||
new FakeNodeIdentityProvider());
|
||||
|
||||
private static AuditEvent Row(DateTime at, Guid? exec, Guid? parent, AuditKind kind = AuditKind.ScriptRun) =>
|
||||
ScadaBridgeAuditEventFactory.Create(AuditChannel.ScriptTrace, kind, AuditStatus.Delivered,
|
||||
occurredAtUtc: at, executionId: exec, parentExecutionId: parent, sourceSiteId: "site-a");
|
||||
|
||||
[Fact]
|
||||
public async Task MatchesExecutionId_OrParentExecutionId_OrderedAndBounded()
|
||||
{
|
||||
var root = Guid.NewGuid();
|
||||
var child = Guid.NewGuid();
|
||||
var unrelated = Guid.NewGuid();
|
||||
var rows = new[]
|
||||
{
|
||||
Row(T0.AddSeconds(3), child, root, AuditKind.AttributeWrite), // parent match
|
||||
Row(T0.AddSeconds(1), root, null), // exec match
|
||||
Row(T0.AddSeconds(2), child, root), // parent match
|
||||
Row(T0.AddSeconds(4), unrelated, null), // no match
|
||||
Row(T0.AddSeconds(-60), root, null), // before since
|
||||
};
|
||||
|
||||
await using (var writer = CreateWriter())
|
||||
{
|
||||
foreach (var r in rows) await writer.WriteAsync(r);
|
||||
|
||||
var hits = await writer.ReadByExecutionIdsAsync([root], T0, limit: 100);
|
||||
Assert.Equal(3, hits.Count);
|
||||
Assert.Equal(rows[1].EventId, hits[0].EventId);
|
||||
Assert.Equal(rows[2].EventId, hits[1].EventId);
|
||||
Assert.Equal(rows[0].EventId, hits[2].EventId);
|
||||
Assert.Equal(rows[1], hits[0]);
|
||||
|
||||
// Bounded.
|
||||
var two = await writer.ReadByExecutionIdsAsync([root], T0, limit: 2);
|
||||
Assert.Equal(2, two.Count);
|
||||
|
||||
// Forwarding state is irrelevant: mark everything Reconciled, still served.
|
||||
await writer.MarkReconciledAsync(rows.Select(r => r.EventId).ToList());
|
||||
Assert.Equal(3, (await writer.ReadByExecutionIdsAsync([root], T0, limit: 100)).Count);
|
||||
|
||||
// Empty id set → empty; multiple ids union.
|
||||
Assert.Empty(await writer.ReadByExecutionIdsAsync(Array.Empty<Guid>(), T0, 10));
|
||||
Assert.Equal(4, (await writer.ReadByExecutionIdsAsync([root, unrelated], T0, 100)).Count);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Guards_LimitAndIdCap()
|
||||
{
|
||||
await using var writer = CreateWriter();
|
||||
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(() => writer.ReadByExecutionIdsAsync([Guid.NewGuid()], T0, 0));
|
||||
var tooMany = Enumerable.Range(0, SqliteAuditWriter.MaxExecutionIdsPerRead + 1).Select(_ => Guid.NewGuid()).ToList();
|
||||
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(() => writer.ReadByExecutionIdsAsync(tooMany, T0, 10));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
using Akka.TestKit.Xunit2;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using Grpc.Core;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using NSubstitute;
|
||||
using ZB.MOM.WW.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
||||
using ZB.MOM.WW.ScadaBridge.Communication.Grpc;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.Communication.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Flow Debugger (design 2026-09-05 §5.4): <see cref="SiteStreamGrpcServer.PullTraceEvents"/>
|
||||
/// — request validation, the queue point-read, DTO projection, and the guarantee that it is
|
||||
/// NOT a reconciliation path (no cursor flip). Also pins that the RPC sits under the
|
||||
/// PSK-gated <c>/sitestream.SiteStreamService/</c> prefix.
|
||||
/// </summary>
|
||||
public class SiteStreamPullTraceEventsTests : TestKit
|
||||
{
|
||||
private readonly ISiteStreamSubscriber _subscriber = Substitute.For<ISiteStreamSubscriber>();
|
||||
|
||||
private SiteStreamGrpcServer CreateServer() => new(_subscriber, NullLogger<SiteStreamGrpcServer>.Instance);
|
||||
|
||||
private static ServerCallContext NewContext()
|
||||
{
|
||||
var context = Substitute.For<ServerCallContext>();
|
||||
context.CancellationToken.Returns(CancellationToken.None);
|
||||
return context;
|
||||
}
|
||||
|
||||
private static AuditEvent Row(Guid executionId) => ScadaBridgeAuditEventFactory.Create(
|
||||
AuditChannel.ScriptTrace, AuditKind.ScriptRun, AuditStatus.Delivered,
|
||||
occurredAtUtc: new DateTime(2026, 9, 5, 12, 0, 0, DateTimeKind.Utc), executionId: executionId, sourceSiteId: "site-a");
|
||||
|
||||
[Fact]
|
||||
public void PullTraceEvents_IsUnderThePskGatedServicePrefix()
|
||||
{
|
||||
var method = SiteStreamService.Descriptor.FindMethodByName("PullTraceEvents");
|
||||
Assert.NotNull(method);
|
||||
Assert.Equal("sitestream.SiteStreamService", SiteStreamService.Descriptor.FullName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NoQueueWired_ReturnsEmpty()
|
||||
{
|
||||
var response = await CreateServer().PullTraceEvents(new PullTraceEventsRequest
|
||||
{
|
||||
ExecutionIds = { Guid.NewGuid().ToString("D") }, MaxEvents = 10,
|
||||
}, NewContext());
|
||||
Assert.Empty(response.Events);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadsByIds_ProjectsDtos_AndNeverFlipsState()
|
||||
{
|
||||
var queue = Substitute.For<ISiteAuditQueue>();
|
||||
var id = Guid.NewGuid();
|
||||
var rows = new List<AuditEvent> { Row(id), Row(id) };
|
||||
IReadOnlyCollection<Guid>? seenIds = null;
|
||||
DateTime seenSince = default;
|
||||
int seenLimit = 0;
|
||||
queue.ReadByExecutionIdsAsync(
|
||||
Arg.Do<IReadOnlyCollection<Guid>>(x => seenIds = x), Arg.Do<DateTime>(x => seenSince = x),
|
||||
Arg.Do<int>(x => seenLimit = x), Arg.Any<CancellationToken>())
|
||||
.Returns((IReadOnlyList<AuditEvent>)rows);
|
||||
var server = CreateServer();
|
||||
server.SetSiteAuditQueue(queue);
|
||||
var since = new DateTime(2026, 9, 5, 11, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
var response = await server.PullTraceEvents(new PullTraceEventsRequest
|
||||
{
|
||||
ExecutionIds = { id.ToString("D") }, SinceUtc = Timestamp.FromDateTime(since), MaxEvents = 500,
|
||||
}, NewContext());
|
||||
|
||||
Assert.Equal(2, response.Events.Count);
|
||||
Assert.Equal(rows[0].EventId.ToString(), response.Events[0].EventId);
|
||||
Assert.Equal(new[] { id }, seenIds);
|
||||
Assert.Equal(since, seenSince);
|
||||
Assert.Equal(500, seenLimit);
|
||||
await queue.DidNotReceiveWithAnyArgs().MarkReconciledUpToAsync(default, default, default);
|
||||
await queue.DidNotReceiveWithAnyArgs().MarkReconciledAsync(default!, default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 1)]
|
||||
[InlineData(2001, 1)]
|
||||
[InlineData(10, 0)]
|
||||
[InlineData(10, 65)]
|
||||
public async Task InvalidArguments_AreRejected(int maxEvents, int idCount)
|
||||
{
|
||||
var server = CreateServer();
|
||||
server.SetSiteAuditQueue(Substitute.For<ISiteAuditQueue>());
|
||||
var request = new PullTraceEventsRequest { MaxEvents = maxEvents };
|
||||
for (var i = 0; i < idCount; i++) request.ExecutionIds.Add(Guid.NewGuid().ToString("D"));
|
||||
|
||||
var ex = await Assert.ThrowsAsync<RpcException>(() => server.PullTraceEvents(request, NewContext()));
|
||||
Assert.Equal(StatusCode.InvalidArgument, ex.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NonGuidId_IsRejected()
|
||||
{
|
||||
var server = CreateServer();
|
||||
server.SetSiteAuditQueue(Substitute.For<ISiteAuditQueue>());
|
||||
var ex = await Assert.ThrowsAsync<RpcException>(() => server.PullTraceEvents(
|
||||
new PullTraceEventsRequest { ExecutionIds = { "nope" }, MaxEvents = 10 }, NewContext()));
|
||||
Assert.Equal(StatusCode.InvalidArgument, ex.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task QueueFault_CollapsesToEmpty()
|
||||
{
|
||||
var queue = Substitute.For<ISiteAuditQueue>();
|
||||
queue.ReadByExecutionIdsAsync(default!, default, default, default)
|
||||
.ReturnsForAnyArgs<IReadOnlyList<AuditEvent>>(_ => throw new InvalidOperationException("boom"));
|
||||
var server = CreateServer();
|
||||
server.SetSiteAuditQueue(queue);
|
||||
|
||||
var response = await server.PullTraceEvents(
|
||||
new PullTraceEventsRequest { ExecutionIds = { Guid.NewGuid().ToString("D") }, MaxEvents = 10 }, NewContext());
|
||||
Assert.Empty(response.Events);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using System.Threading.Channels;
|
||||
using Akka.Actor;
|
||||
using Akka.TestKit.Xunit2;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Streaming;
|
||||
using ZB.MOM.WW.ScadaBridge.Communication.Actors;
|
||||
using ZB.MOM.WW.ScadaBridge.Communication.Grpc;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.Communication.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Flow Debugger (design 2026-09-05 §5.3/§5.4): <c>AttributeValueUpdate</c> fields 7/8 —
|
||||
/// <see cref="StreamRelayActor"/> stamps <c>source_execution_id</c>/<c>trace</c> on the way
|
||||
/// out and <see cref="SiteStreamGrpcClient"/> parses them back; an unstamped change (and an
|
||||
/// older site's frame) round-trips to null/false.
|
||||
/// </summary>
|
||||
public class StreamRelayTraceStampTests : TestKit
|
||||
{
|
||||
private static SiteStreamEvent Relay(AttributeValueChanged change, ActorSystem sys)
|
||||
{
|
||||
var channel = Channel.CreateUnbounded<SiteStreamEvent>();
|
||||
var actor = sys.ActorOf(Props.Create(() => new StreamRelayActor("corr", channel.Writer)));
|
||||
actor.Tell(change);
|
||||
SiteStreamEvent? evt = null;
|
||||
for (var i = 0; i < 40 && !channel.Reader.TryRead(out evt); i++) Thread.Sleep(50);
|
||||
Assert.NotNull(evt);
|
||||
return evt!;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TracedChange_IsStampedOnTheWire_AndParsedBack()
|
||||
{
|
||||
var source = Guid.NewGuid();
|
||||
var now = new DateTimeOffset(2026, 9, 5, 12, 0, 0, TimeSpan.Zero);
|
||||
var change = new AttributeValueChanged("P1", "Status", "Status", "Running", "Good", now, SourceExecutionId: source, Trace: true);
|
||||
|
||||
var evt = Relay(change, Sys);
|
||||
Assert.Equal(source.ToString("D"), evt.AttributeChanged.SourceExecutionId);
|
||||
Assert.True(evt.AttributeChanged.Trace);
|
||||
|
||||
var back = Assert.IsType<AttributeValueChanged>(SiteStreamGrpcClient.ConvertToDomainEvent(evt));
|
||||
Assert.Equal(source, back.SourceExecutionId);
|
||||
Assert.True(back.Trace);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnstampedChange_RoundTripsToNullAndFalse()
|
||||
{
|
||||
var change = new AttributeValueChanged("P1", "Status", "Status", "Idle", "Good", DateTimeOffset.UtcNow);
|
||||
|
||||
var evt = Relay(change, Sys);
|
||||
Assert.Equal(string.Empty, evt.AttributeChanged.SourceExecutionId);
|
||||
Assert.False(evt.AttributeChanged.Trace);
|
||||
|
||||
var back = Assert.IsType<AttributeValueChanged>(SiteStreamGrpcClient.ConvertToDomainEvent(evt));
|
||||
Assert.Null(back.SourceExecutionId);
|
||||
Assert.False(back.Trace);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OlderSiteFrame_WithoutTheFields_ParsesToNullAndFalse()
|
||||
{
|
||||
var evt = new SiteStreamEvent
|
||||
{
|
||||
CorrelationId = "c",
|
||||
AttributeChanged = new AttributeValueUpdate
|
||||
{
|
||||
InstanceUniqueName = "P1", AttributePath = "A", AttributeName = "A", Value = "1",
|
||||
Quality = Quality.Good, Timestamp = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow),
|
||||
},
|
||||
};
|
||||
var back = Assert.IsType<AttributeValueChanged>(SiteStreamGrpcClient.ConvertToDomainEvent(evt));
|
||||
Assert.Null(back.SourceExecutionId);
|
||||
Assert.False(back.Trace);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user