feat(flow-debugger): carry site-side trace truncation on the route reply (ScadaBridge#54)

ScriptCallResult and RouteToCallResponse gain trailing Truncated/LogsTruncated,
filled from the site FlowTraceCollector after Complete(); RouteToCallResponseDto
fields 8/9 carry them (sitecommand regen). FlowDebugRunner ORs them into
FlowDebugRunResult on the script entry, and on the method entry through a new
optional truncation sink on RouteHelper.WithTraceSink (carried through every
With* clone). Previously a site run whose collector dropped rows came back as a
silently short list. Older sites send false.

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
Joseph Doherty
2026-11-09 12:37:57 -05:00
parent 5310fd0e26
commit 6d5bb6189f
15 changed files with 484 additions and 155 deletions
@@ -170,7 +170,11 @@ attaches the list to the `ScriptCallResult` → `RouteToCallResponse` when `Trac
collector applies `IAuditRedactor` inside `Add`, so the inline record is the same filtered event
the writer persists, and it caps at `MaxTraceEventsPerRun` (500) **or** 1 MB of summed
`DetailsJson` (`DefaultMaxBytes`), whichever comes first; beyond either bound it keeps the earlier
events, sets `Truncated`, and the audit rows remain complete (arch-review F1). A site on an older build ignores
events, sets `Truncated`, and the audit rows remain complete (arch-review F1). The site collector's
`Truncated`/`LogsTruncated` ride back on `ScriptCallResult` → `RouteToCallResponse`
(`RouteToCallResponseDto` fields 8/9, Gitea #54) and `FlowDebugRunner` ORs them into
`FlowDebugRunResult` on both the script entry and (through `RouteHelper.WithTraceSink`'s truncation
sink) the method entry, so a short site lane is labelled truncated. A site on an older build ignores
`Trace`, returns no `TraceEvents`, and the UI labels its lane `site not traced (older build)`.
`FlowStep` is a **central-side projection** (`Commons/Types/Debugging/FlowStepProjector`)
computed from `AuditEvent` + `AuditDetails`; nothing serializes it.
@@ -29,6 +29,8 @@ public record RouteToCallRequest(
/// </summary>
/// <param name="ChildExecutionId">Flow Debugger — the routed site run's <c>ExecutionId</c> (design 2026-09-05 §5.4); null from a site that predates the field.</param>
/// <param name="TraceEvents">Flow Debugger — the site's <c>ScriptTrace</c> audit events for a traced run, in emission order (design 2026-09-05 §5.4); null when untraced or from an older site.</param>
/// <param name="Truncated">Flow Debugger (Gitea #54) — the site's inline collector dropped step rows, so <see cref="TraceEvents"/> is short. False when untraced or from an older site.</param>
/// <param name="LogsTruncated">Flow Debugger (Gitea #54) — the site's inline collector dropped log-line rows. False when untraced or from an older site.</param>
public record RouteToCallResponse(
string CorrelationId,
bool Success,
@@ -36,7 +38,9 @@ public record RouteToCallResponse(
string? ErrorMessage,
DateTimeOffset Timestamp,
Guid? ChildExecutionId = null,
IReadOnlyList<AuditEvent>? TraceEvents = null);
IReadOnlyList<AuditEvent>? TraceEvents = null,
bool Truncated = false,
bool LogsTruncated = false);
/// <summary>
/// Request to read attribute(s) from a remote instance.
@@ -4,10 +4,14 @@ namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.ScriptExecution;
/// <param name="ExecutionId">Flow Debugger — the run's own <c>ExecutionId</c> (design 2026-09-05 §5.4). Additive; null from callers that predate the field.</param>
/// <param name="TraceEvents">Flow Debugger — the run's <c>ScriptTrace</c> audit events in emission order when traced (design 2026-09-05 §5.4); null otherwise.</param>
/// <param name="Truncated">Flow Debugger (Gitea #54) — the site's inline trace collector dropped a step row (event cap or byte budget), so <see cref="TraceEvents"/> is short. The audit rows are complete; Replay shows everything. Additive; false when untraced.</param>
/// <param name="LogsTruncated">Flow Debugger (Gitea #54) — the site's collector dropped low-priority log-line rows to keep room for the step rows. Additive; false when untraced.</param>
public record ScriptCallResult(
string CorrelationId,
bool Success,
object? ReturnValue,
string? ErrorMessage,
Guid? ExecutionId = null,
IReadOnlyList<AuditEvent>? TraceEvents = null);
IReadOnlyList<AuditEvent>? TraceEvents = null,
bool Truncated = false,
bool LogsTruncated = false);
@@ -2649,7 +2649,10 @@ public static class SiteCommandDtoMapper
ErrorMessage = OrEmpty(response.ErrorMessage),
Timestamp = Ts(response.Timestamp),
// Flow Debugger (design 2026-09-05 §5.4): additive fields 6/7.
ChildExecutionId = GuidToWire(response.ChildExecutionId)
ChildExecutionId = GuidToWire(response.ChildExecutionId),
// Gitea #54: additive fields 8/9 — the site collector's truncation flags.
Truncated = response.Truncated,
LogsTruncated = response.LogsTruncated
};
if (response.TraceEvents is { Count: > 0 } traceEvents)
{
@@ -2677,7 +2680,9 @@ public static class SiteCommandDtoMapper
GuidFromWire(dto.ChildExecutionId),
// An untraced run — or an older site that never sets the field — sends no
// events, which maps to null so the UI can label the lane "site not traced".
dto.TraceEvents.Count == 0 ? null : dto.TraceEvents.Select(AuditEventDtoMapper.FromDto).ToList());
dto.TraceEvents.Count == 0 ? null : dto.TraceEvents.Select(AuditEventDtoMapper.FromDto).ToList(),
dto.Truncated,
dto.LogsTruncated);
}
/// <summary>Projects a <see cref="RouteToGetAttributesResponse"/> onto the wire.</summary>
@@ -810,6 +810,10 @@ message RouteToCallResponseDto {
// Flow Debugger (design §5.4): the site's ScriptTrace audit events for a traced run,
// in emission order. Empty from an untraced run or an older site (mapper => null).
repeated sitestream.AuditEventDto trace_events = 7;
// Flow Debugger (Gitea #54): the site's inline trace collector dropped step rows (truncated)
// or log-line rows (logs_truncated). proto3 default false from an untraced run or older site.
bool truncated = 8;
bool logs_truncated = 9;
}
message RouteToGetAttributesRequestDto {
@@ -388,132 +388,133 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
"KAkSEwoLc2NyaXB0X25hbWUYAyABKAkSPQoKcGFyYW1ldGVycxgEIAEoCzIp",
"LnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLkxvb3NlVmFsdWVNYXASLQoJ",
"dGltZXN0YW1wGAUgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIb",
"ChNwYXJlbnRfZXhlY3V0aW9uX2lkGAYgASgJEg0KBXRyYWNlGAcgASgIIpIC",
"ChNwYXJlbnRfZXhlY3V0aW9uX2lkGAYgASgJEg0KBXRyYWNlGAcgASgIIr0C",
"ChZSb3V0ZVRvQ2FsbFJlc3BvbnNlRHRvEhYKDmNvcnJlbGF0aW9uX2lkGAEg",
"ASgJEg8KB3N1Y2Nlc3MYAiABKAgSPAoMcmV0dXJuX3ZhbHVlGAMgASgLMiYu",
"c2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuTG9vc2VWYWx1ZRIVCg1lcnJv",
"cl9tZXNzYWdlGAQgASgJEi0KCXRpbWVzdGFtcBgFIAEoCzIaLmdvb2dsZS5w",
"cm90b2J1Zi5UaW1lc3RhbXASGgoSY2hpbGRfZXhlY3V0aW9uX2lkGAYgASgJ",
"Ei8KDHRyYWNlX2V2ZW50cxgHIAMoCzIZLnNpdGVzdHJlYW0uQXVkaXRFdmVu",
"dER0byK7AQoeUm91dGVUb0dldEF0dHJpYnV0ZXNSZXF1ZXN0RHRvEhYKDmNv",
"cnJlbGF0aW9uX2lkGAEgASgJEhwKFGluc3RhbmNlX3VuaXF1ZV9uYW1lGAIg",
"ASgJEhcKD2F0dHJpYnV0ZV9uYW1lcxgDIAMoCRItCgl0aW1lc3RhbXAYBCAB",
"dER0bxIRCgl0cnVuY2F0ZWQYCCABKAgSFgoObG9nc190cnVuY2F0ZWQYCSAB",
"KAgiuwEKHlJvdXRlVG9HZXRBdHRyaWJ1dGVzUmVxdWVzdER0bxIWCg5jb3Jy",
"ZWxhdGlvbl9pZBgBIAEoCRIcChRpbnN0YW5jZV91bmlxdWVfbmFtZRgCIAEo",
"CRIXCg9hdHRyaWJ1dGVfbmFtZXMYAyADKAkSLQoJdGltZXN0YW1wGAQgASgL",
"MhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIbChNwYXJlbnRfZXhlY3V0",
"aW9uX2lkGAUgASgJIssBCh9Sb3V0ZVRvR2V0QXR0cmlidXRlc1Jlc3BvbnNl",
"RHRvEhYKDmNvcnJlbGF0aW9uX2lkGAEgASgJEjkKBnZhbHVlcxgCIAEoCzIp",
"LnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLkxvb3NlVmFsdWVNYXASDwoH",
"c3VjY2VzcxgDIAEoCBIVCg1lcnJvcl9tZXNzYWdlGAQgASgJEi0KCXRpbWVz",
"dGFtcBgFIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAi1AIKHlJv",
"dXRlVG9TZXRBdHRyaWJ1dGVzUmVxdWVzdER0bxIWCg5jb3JyZWxhdGlvbl9p",
"ZBgBIAEoCRIcChRpbnN0YW5jZV91bmlxdWVfbmFtZRgCIAEoCRJpChBhdHRy",
"aWJ1dGVfdmFsdWVzGAMgAygLMk8uc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQu",
"djEuUm91dGVUb1NldEF0dHJpYnV0ZXNSZXF1ZXN0RHRvLkF0dHJpYnV0ZVZh",
"bHVlc0VudHJ5Ei0KCXRpbWVzdGFtcBgEIAEoCzIaLmdvb2dsZS5wcm90b2J1",
"Zi5UaW1lc3RhbXASGwoTcGFyZW50X2V4ZWN1dGlvbl9pZBgFIAEoCRINCgV0",
"cmFjZRgGIAEoCBo2ChRBdHRyaWJ1dGVWYWx1ZXNFbnRyeRILCgNrZXkYASAB",
"KAkSDQoFdmFsdWUYAiABKAk6AjgBIpABCh9Sb3V0ZVRvU2V0QXR0cmlidXRl",
"c1Jlc3BvbnNlRHRvEhYKDmNvcnJlbGF0aW9uX2lkGAEgASgJEg8KB3N1Y2Nl",
"c3MYAiABKAgSFQoNZXJyb3JfbWVzc2FnZRgDIAEoCRItCgl0aW1lc3RhbXAY",
"BCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wIsMCCiFSb3V0ZVRv",
"V2FpdEZvckF0dHJpYnV0ZVJlcXVlc3REdG8SFgoOY29ycmVsYXRpb25faWQY",
"ASABKAkSHAoUaW5zdGFuY2VfdW5pcXVlX25hbWUYAiABKAkSFgoOYXR0cmli",
"dXRlX25hbWUYAyABKAkSOgoUdGFyZ2V0X3ZhbHVlX2VuY29kZWQYBCABKAsy",
"HC5nb29nbGUucHJvdG9idWYuU3RyaW5nVmFsdWUSKgoHdGltZW91dBgFIAEo",
"CzIZLmdvb2dsZS5wcm90b2J1Zi5EdXJhdGlvbhItCgl0aW1lc3RhbXAYBiAB",
"KAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEhsKE3BhcmVudF9leGVj",
"dXRpb25faWQYBSABKAkiywEKH1JvdXRlVG9HZXRBdHRyaWJ1dGVzUmVzcG9u",
"c2VEdG8SFgoOY29ycmVsYXRpb25faWQYASABKAkSOQoGdmFsdWVzGAIgASgL",
"Mikuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuTG9vc2VWYWx1ZU1hcBIP",
"CgdzdWNjZXNzGAMgASgIEhUKDWVycm9yX21lc3NhZ2UYBCABKAkSLQoJdGlt",
"ZXN0YW1wGAUgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCLUAgoe",
"Um91dGVUb1NldEF0dHJpYnV0ZXNSZXF1ZXN0RHRvEhYKDmNvcnJlbGF0aW9u",
"X2lkGAEgASgJEhwKFGluc3RhbmNlX3VuaXF1ZV9uYW1lGAIgASgJEmkKEGF0",
"dHJpYnV0ZV92YWx1ZXMYAyADKAsyTy5zY2FkYWJyaWRnZS5zaXRlY29tbWFu",
"ZC52MS5Sb3V0ZVRvU2V0QXR0cmlidXRlc1JlcXVlc3REdG8uQXR0cmlidXRl",
"VmFsdWVzRW50cnkSLQoJdGltZXN0YW1wGAQgASgLMhouZ29vZ2xlLnByb3Rv",
"YnVmLlRpbWVzdGFtcBIbChNwYXJlbnRfZXhlY3V0aW9uX2lkGAUgASgJEg0K",
"BXRyYWNlGAYgASgIGjYKFEF0dHJpYnV0ZVZhbHVlc0VudHJ5EgsKA2tleRgB",
"IAEoCRINCgV2YWx1ZRgCIAEoCToCOAEikAEKH1JvdXRlVG9TZXRBdHRyaWJ1",
"dGVzUmVzcG9uc2VEdG8SFgoOY29ycmVsYXRpb25faWQYASABKAkSDwoHc3Vj",
"Y2VzcxgCIAEoCBIVCg1lcnJvcl9tZXNzYWdlGAMgASgJEi0KCXRpbWVzdGFt",
"cBgEIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAiwwIKIVJvdXRl",
"VG9XYWl0Rm9yQXR0cmlidXRlUmVxdWVzdER0bxIWCg5jb3JyZWxhdGlvbl9p",
"ZBgBIAEoCRIcChRpbnN0YW5jZV91bmlxdWVfbmFtZRgCIAEoCRIWCg5hdHRy",
"aWJ1dGVfbmFtZRgDIAEoCRI6ChR0YXJnZXRfdmFsdWVfZW5jb2RlZBgEIAEo",
"CzIcLmdvb2dsZS5wcm90b2J1Zi5TdHJpbmdWYWx1ZRIqCgd0aW1lb3V0GAUg",
"ASgLMhkuZ29vZ2xlLnByb3RvYnVmLkR1cmF0aW9uEi0KCXRpbWVzdGFtcBgG",
"IAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASGwoTcGFyZW50X2V4",
"ZWN1dGlvbl9pZBgHIAEoCRIcChRyZXF1aXJlX2dvb2RfcXVhbGl0eRgIIAEo",
"CCL/AQoiUm91dGVUb1dhaXRGb3JBdHRyaWJ1dGVSZXNwb25zZUR0bxIWCg5j",
"b3JyZWxhdGlvbl9pZBgBIAEoCRIPCgdtYXRjaGVkGAIgASgIEjUKBXZhbHVl",
"GAMgASgLMiYuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuTG9vc2VWYWx1",
"ZRIPCgdxdWFsaXR5GAQgASgJEhEKCXRpbWVkX291dBgFIAEoCBIPCgdzdWNj",
"ZXNzGAYgASgIEhUKDWVycm9yX21lc3NhZ2UYByABKAkSLQoJdGltZXN0YW1w",
"GAggASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCKJAwoMUm91dGVS",
"ZXF1ZXN0EkoKDXJvdXRlX3RvX2NhbGwYASABKAsyMS5zY2FkYWJyaWRnZS5z",
"aXRlY29tbWFuZC52MS5Sb3V0ZVRvQ2FsbFJlcXVlc3REdG9IABJdChdyb3V0",
"ZV90b19nZXRfYXR0cmlidXRlcxgCIAEoCzI6LnNjYWRhYnJpZGdlLnNpdGVj",
"b21tYW5kLnYxLlJvdXRlVG9HZXRBdHRyaWJ1dGVzUmVxdWVzdER0b0gAEl0K",
"F3JvdXRlX3RvX3NldF9hdHRyaWJ1dGVzGAMgASgLMjouc2NhZGFicmlkZ2Uu",
"c2l0ZWNvbW1hbmQudjEuUm91dGVUb1NldEF0dHJpYnV0ZXNSZXF1ZXN0RHRv",
"SAASZAobcm91dGVfdG9fd2FpdF9mb3JfYXR0cmlidXRlGAQgASgLMj0uc2Nh",
"ZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuUm91dGVUb1dhaXRGb3JBdHRyaWJ1",
"dGVSZXF1ZXN0RHRvSABCCQoHY29tbWFuZCKJAwoKUm91dGVSZXBseRJLCg1y",
"b3V0ZV90b19jYWxsGAEgASgLMjIuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQu",
"djEuUm91dGVUb0NhbGxSZXNwb25zZUR0b0gAEl4KF3JvdXRlX3RvX2dldF9h",
"dHRyaWJ1dGVzGAIgASgLMjsuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEu",
"Um91dGVUb0dldEF0dHJpYnV0ZXNSZXNwb25zZUR0b0gAEl4KF3JvdXRlX3Rv",
"X3NldF9hdHRyaWJ1dGVzGAMgASgLMjsuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1h",
"bmQudjEuUm91dGVUb1NldEF0dHJpYnV0ZXNSZXNwb25zZUR0b0gAEmUKG3Jv",
"dXRlX3RvX3dhaXRfZm9yX2F0dHJpYnV0ZRgEIAEoCzI+LnNjYWRhYnJpZGdl",
"LnNpdGVjb21tYW5kLnYxLlJvdXRlVG9XYWl0Rm9yQXR0cmlidXRlUmVzcG9u",
"c2VEdG9IAEIHCgVyZXBseSJBChZUcmlnZ2VyU2l0ZUZhaWxvdmVyRHRvEhYK",
"DmNvcnJlbGF0aW9uX2lkGAEgASgJEg8KB3NpdGVfaWQYAiABKAkibQoSU2l0",
"ZUZhaWxvdmVyQWNrRHRvEhYKDmNvcnJlbGF0aW9uX2lkGAEgASgJEhAKCGFj",
"Y2VwdGVkGAIgASgIEhYKDnRhcmdldF9hZGRyZXNzGAMgASgJEhUKDWVycm9y",
"X21lc3NhZ2UYBCABKAkqywEKE0RlcGxveW1lbnRTdGF0dXNEdG8SJQohREVQ",
"TE9ZTUVOVF9TVEFUVVNfRFRPX1VOU1BFQ0lGSUVEEAASIQodREVQTE9ZTUVO",
"VF9TVEFUVVNfRFRPX1BFTkRJTkcQARIlCiFERVBMT1lNRU5UX1NUQVRVU19E",
"VE9fSU5fUFJPR1JFU1MQAhIhCh1ERVBMT1lNRU5UX1NUQVRVU19EVE9fU1VD",
"Q0VTUxADEiAKHERFUExPWU1FTlRfU1RBVFVTX0RUT19GQUlMRUQQBCrEAQoS",
"QnJvd3NlTm9kZUNsYXNzRHRvEiUKIUJST1dTRV9OT0RFX0NMQVNTX0RUT19V",
"TlNQRUNJRklFRBAAEiAKHEJST1dTRV9OT0RFX0NMQVNTX0RUT19PQkpFQ1QQ",
"ARIiCh5CUk9XU0VfTk9ERV9DTEFTU19EVE9fVkFSSUFCTEUQAhIgChxCUk9X",
"U0VfTk9ERV9DTEFTU19EVE9fTUVUSE9EEAMSHwobQlJPV1NFX05PREVfQ0xB",
"U1NfRFRPX09USEVSEAQqoQIKFEJyb3dzZUZhaWx1cmVLaW5kRHRvEicKI0JS",
"T1dTRV9GQUlMVVJFX0tJTkRfRFRPX1VOU1BFQ0lGSUVEEAASMAosQlJPV1NF",
"X0ZBSUxVUkVfS0lORF9EVE9fQ09OTkVDVElPTl9OT1RfRk9VTkQQARI0CjBC",
"Uk9XU0VfRkFJTFVSRV9LSU5EX0RUT19DT05ORUNUSU9OX05PVF9DT05ORUNU",
"RUQQAhIpCiVCUk9XU0VfRkFJTFVSRV9LSU5EX0RUT19OT1RfQlJPV1NBQkxF",
"EAMSIwofQlJPV1NFX0ZBSUxVUkVfS0lORF9EVE9fVElNRU9VVBAEEigKJEJS",
"T1dTRV9GQUlMVVJFX0tJTkRfRFRPX1NFUlZFUl9FUlJPUhAFKqoCChtSZWFk",
"VGFnVmFsdWVzRmFpbHVyZUtpbmREdG8SMAosUkVBRF9UQUdfVkFMVUVTX0ZB",
"SUxVUkVfS0lORF9EVE9fVU5TUEVDSUZJRUQQABI5CjVSRUFEX1RBR19WQUxV",
"RVNfRkFJTFVSRV9LSU5EX0RUT19DT05ORUNUSU9OX05PVF9GT1VORBABEj0K",
"OVJFQURfVEFHX1ZBTFVFU19GQUlMVVJFX0tJTkRfRFRPX0NPTk5FQ1RJT05f",
"Tk9UX0NPTk5FQ1RFRBACEiwKKFJFQURfVEFHX1ZBTFVFU19GQUlMVVJFX0tJ",
"TkRfRFRPX1RJTUVPVVQQAxIxCi1SRUFEX1RBR19WQUxVRVNfRkFJTFVSRV9L",
"SU5EX0RUT19TRVJWRVJfRVJST1IQBCqTAgoUVmVyaWZ5RmFpbHVyZUtpbmRE",
"dG8SJwojVkVSSUZZX0ZBSUxVUkVfS0lORF9EVE9fVU5TUEVDSUZJRUQQABIn",
"CiNWRVJJRllfRkFJTFVSRV9LSU5EX0RUT19VTlJFQUNIQUJMRRABEicKI1ZF",
"UklGWV9GQUlMVVJFX0tJTkRfRFRPX0FVVEhfRkFJTEVEEAISMQotVkVSSUZZ",
"X0ZBSUxVUkVfS0lORF9EVE9fVU5UUlVTVEVEX0NFUlRJRklDQVRFEAMSIwof",
"VkVSSUZZX0ZBSUxVUkVfS0lORF9EVE9fVElNRU9VVBAEEigKJFZFUklGWV9G",
"QUlMVVJFX0tJTkRfRFRPX1NFUlZFUl9FUlJPUhAFKuUBChpTdG9yZUFuZEZv",
"cndhcmRDYXRlZ29yeUR0bxIuCipTVE9SRV9BTkRfRk9SV0FSRF9DQVRFR09S",
"WV9EVE9fVU5TUEVDSUZJRUQQABIyCi5TVE9SRV9BTkRfRk9SV0FSRF9DQVRF",
"R09SWV9EVE9fRVhURVJOQUxfU1lTVEVNEAESLworU1RPUkVfQU5EX0ZPUldB",
"UkRfQ0FURUdPUllfRFRPX05PVElGSUNBVElPThACEjIKLlNUT1JFX0FORF9G",
"T1JXQVJEX0NBVEVHT1JZX0RUT19DQUNIRURfREJfV1JJVEUQAypoCg1BbGFy",
"bVN0YXRlRHRvEh8KG0FMQVJNX1NUQVRFX0RUT19VTlNQRUNJRklFRBAAEhoK",
"FkFMQVJNX1NUQVRFX0RUT19BQ1RJVkUQARIaChZBTEFSTV9TVEFURV9EVE9f",
"Tk9STUFMEAIquQEKDUFsYXJtTGV2ZWxEdG8SHwobQUxBUk1fTEVWRUxfRFRP",
"X1VOU1BFQ0lGSUVEEAASGAoUQUxBUk1fTEVWRUxfRFRPX05PTkUQARIXChNB",
"TEFSTV9MRVZFTF9EVE9fTE9XEAISGwoXQUxBUk1fTEVWRUxfRFRPX0xPV19M",
"T1cQAxIYChRBTEFSTV9MRVZFTF9EVE9fSElHSBAEEh0KGUFMQVJNX0xFVkVM",
"X0RUT19ISUdIX0hJR0gQBSqSAQoMQWxhcm1LaW5kRHRvEh4KGkFMQVJNX0tJ",
"TkRfRFRPX1VOU1BFQ0lGSUVEEAASGwoXQUxBUk1fS0lORF9EVE9fQ09NUFVU",
"RUQQARIgChxBTEFSTV9LSU5EX0RUT19OQVRJVkVfT1BDX1VBEAISIwofQUxB",
"Uk1fS0lORF9EVE9fTkFUSVZFX01YX0FDQ0VTUxADKugBChNBbGFybVNoZWx2",
"ZVN0YXRlRHRvEiYKIkFMQVJNX1NIRUxWRV9TVEFURV9EVE9fVU5TUEVDSUZJ",
"RUQQABIkCiBBTEFSTV9TSEVMVkVfU1RBVEVfRFRPX1VOU0hFTFZFRBABEisK",
"J0FMQVJNX1NIRUxWRV9TVEFURV9EVE9fT05FX1NIT1RfU0hFTFZFRBACEigK",
"JEFMQVJNX1NIRUxWRV9TVEFURV9EVE9fVElNRURfU0hFTFZFRBADEiwKKEFM",
"QVJNX1NIRUxWRV9TVEFURV9EVE9fUEVSTUFORU5UX1NIRUxWRUQQBDKEBQoS",
"U2l0ZUNvbW1hbmRTZXJ2aWNlEmwKEEV4ZWN1dGVMaWZlY3ljbGUSLC5zY2Fk",
"YWJyaWRnZS5zaXRlY29tbWFuZC52MS5MaWZlY3ljbGVSZXF1ZXN0Giouc2Nh",
"ZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuTGlmZWN5Y2xlUmVwbHkSYAoMRXhl",
"Y3V0ZU9wY1VhEiguc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuT3BjVWFS",
"ZXF1ZXN0GiYuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuT3BjVWFSZXBs",
"eRJgCgxFeGVjdXRlUXVlcnkSKC5zY2FkYWJyaWRnZS5zaXRlY29tbWFuZC52",
"MS5RdWVyeVJlcXVlc3QaJi5zY2FkYWJyaWRnZS5zaXRlY29tbWFuZC52MS5R",
"dWVyeVJlcGx5EmMKDUV4ZWN1dGVQYXJrZWQSKS5zY2FkYWJyaWRnZS5zaXRl",
"Y29tbWFuZC52MS5QYXJrZWRSZXF1ZXN0Gicuc2NhZGFicmlkZ2Uuc2l0ZWNv",
"bW1hbmQudjEuUGFya2VkUmVwbHkSYAoMRXhlY3V0ZVJvdXRlEiguc2NhZGFi",
"cmlkZ2Uuc2l0ZWNvbW1hbmQudjEuUm91dGVSZXF1ZXN0GiYuc2NhZGFicmlk",
"Z2Uuc2l0ZWNvbW1hbmQudjEuUm91dGVSZXBseRJ1Cg9UcmlnZ2VyRmFpbG92",
"ZXISMi5zY2FkYWJyaWRnZS5zaXRlY29tbWFuZC52MS5UcmlnZ2VyU2l0ZUZh",
"aWxvdmVyRHRvGi4uc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuU2l0ZUZh",
"aWxvdmVyQWNrRHRvQiuqAihaQi5NT00uV1cuU2NhZGFCcmlkZ2UuQ29tbXVu",
"aWNhdGlvbi5HcnBjYgZwcm90bzM="));
"dXRpb25faWQYByABKAkSHAoUcmVxdWlyZV9nb29kX3F1YWxpdHkYCCABKAgi",
"/wEKIlJvdXRlVG9XYWl0Rm9yQXR0cmlidXRlUmVzcG9uc2VEdG8SFgoOY29y",
"cmVsYXRpb25faWQYASABKAkSDwoHbWF0Y2hlZBgCIAEoCBI1CgV2YWx1ZRgD",
"IAEoCzImLnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLkxvb3NlVmFsdWUS",
"DwoHcXVhbGl0eRgEIAEoCRIRCgl0aW1lZF9vdXQYBSABKAgSDwoHc3VjY2Vz",
"cxgGIAEoCBIVCg1lcnJvcl9tZXNzYWdlGAcgASgJEi0KCXRpbWVzdGFtcBgI",
"IAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAiiQMKDFJvdXRlUmVx",
"dWVzdBJKCg1yb3V0ZV90b19jYWxsGAEgASgLMjEuc2NhZGFicmlkZ2Uuc2l0",
"ZWNvbW1hbmQudjEuUm91dGVUb0NhbGxSZXF1ZXN0RHRvSAASXQoXcm91dGVf",
"dG9fZ2V0X2F0dHJpYnV0ZXMYAiABKAsyOi5zY2FkYWJyaWRnZS5zaXRlY29t",
"bWFuZC52MS5Sb3V0ZVRvR2V0QXR0cmlidXRlc1JlcXVlc3REdG9IABJdChdy",
"b3V0ZV90b19zZXRfYXR0cmlidXRlcxgDIAEoCzI6LnNjYWRhYnJpZGdlLnNp",
"dGVjb21tYW5kLnYxLlJvdXRlVG9TZXRBdHRyaWJ1dGVzUmVxdWVzdER0b0gA",
"EmQKG3JvdXRlX3RvX3dhaXRfZm9yX2F0dHJpYnV0ZRgEIAEoCzI9LnNjYWRh",
"YnJpZGdlLnNpdGVjb21tYW5kLnYxLlJvdXRlVG9XYWl0Rm9yQXR0cmlidXRl",
"UmVxdWVzdER0b0gAQgkKB2NvbW1hbmQiiQMKClJvdXRlUmVwbHkSSwoNcm91",
"dGVfdG9fY2FsbBgBIAEoCzIyLnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYx",
"LlJvdXRlVG9DYWxsUmVzcG9uc2VEdG9IABJeChdyb3V0ZV90b19nZXRfYXR0",
"cmlidXRlcxgCIAEoCzI7LnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLlJv",
"dXRlVG9HZXRBdHRyaWJ1dGVzUmVzcG9uc2VEdG9IABJeChdyb3V0ZV90b19z",
"ZXRfYXR0cmlidXRlcxgDIAEoCzI7LnNjYWRhYnJpZGdlLnNpdGVjb21tYW5k",
"LnYxLlJvdXRlVG9TZXRBdHRyaWJ1dGVzUmVzcG9uc2VEdG9IABJlChtyb3V0",
"ZV90b193YWl0X2Zvcl9hdHRyaWJ1dGUYBCABKAsyPi5zY2FkYWJyaWRnZS5z",
"aXRlY29tbWFuZC52MS5Sb3V0ZVRvV2FpdEZvckF0dHJpYnV0ZVJlc3BvbnNl",
"RHRvSABCBwoFcmVwbHkiQQoWVHJpZ2dlclNpdGVGYWlsb3ZlckR0bxIWCg5j",
"b3JyZWxhdGlvbl9pZBgBIAEoCRIPCgdzaXRlX2lkGAIgASgJIm0KElNpdGVG",
"YWlsb3ZlckFja0R0bxIWCg5jb3JyZWxhdGlvbl9pZBgBIAEoCRIQCghhY2Nl",
"cHRlZBgCIAEoCBIWCg50YXJnZXRfYWRkcmVzcxgDIAEoCRIVCg1lcnJvcl9t",
"ZXNzYWdlGAQgASgJKssBChNEZXBsb3ltZW50U3RhdHVzRHRvEiUKIURFUExP",
"WU1FTlRfU1RBVFVTX0RUT19VTlNQRUNJRklFRBAAEiEKHURFUExPWU1FTlRf",
"U1RBVFVTX0RUT19QRU5ESU5HEAESJQohREVQTE9ZTUVOVF9TVEFUVVNfRFRP",
"X0lOX1BST0dSRVNTEAISIQodREVQTE9ZTUVOVF9TVEFUVVNfRFRPX1NVQ0NF",
"U1MQAxIgChxERVBMT1lNRU5UX1NUQVRVU19EVE9fRkFJTEVEEAQqxAEKEkJy",
"b3dzZU5vZGVDbGFzc0R0bxIlCiFCUk9XU0VfTk9ERV9DTEFTU19EVE9fVU5T",
"UEVDSUZJRUQQABIgChxCUk9XU0VfTk9ERV9DTEFTU19EVE9fT0JKRUNUEAES",
"IgoeQlJPV1NFX05PREVfQ0xBU1NfRFRPX1ZBUklBQkxFEAISIAocQlJPV1NF",
"X05PREVfQ0xBU1NfRFRPX01FVEhPRBADEh8KG0JST1dTRV9OT0RFX0NMQVNT",
"X0RUT19PVEhFUhAEKqECChRCcm93c2VGYWlsdXJlS2luZER0bxInCiNCUk9X",
"U0VfRkFJTFVSRV9LSU5EX0RUT19VTlNQRUNJRklFRBAAEjAKLEJST1dTRV9G",
"QUlMVVJFX0tJTkRfRFRPX0NPTk5FQ1RJT05fTk9UX0ZPVU5EEAESNAowQlJP",
"V1NFX0ZBSUxVUkVfS0lORF9EVE9fQ09OTkVDVElPTl9OT1RfQ09OTkVDVEVE",
"EAISKQolQlJPV1NFX0ZBSUxVUkVfS0lORF9EVE9fTk9UX0JST1dTQUJMRRAD",
"EiMKH0JST1dTRV9GQUlMVVJFX0tJTkRfRFRPX1RJTUVPVVQQBBIoCiRCUk9X",
"U0VfRkFJTFVSRV9LSU5EX0RUT19TRVJWRVJfRVJST1IQBSqqAgobUmVhZFRh",
"Z1ZhbHVlc0ZhaWx1cmVLaW5kRHRvEjAKLFJFQURfVEFHX1ZBTFVFU19GQUlM",
"VVJFX0tJTkRfRFRPX1VOU1BFQ0lGSUVEEAASOQo1UkVBRF9UQUdfVkFMVUVT",
"X0ZBSUxVUkVfS0lORF9EVE9fQ09OTkVDVElPTl9OT1RfRk9VTkQQARI9CjlS",
"RUFEX1RBR19WQUxVRVNfRkFJTFVSRV9LSU5EX0RUT19DT05ORUNUSU9OX05P",
"VF9DT05ORUNURUQQAhIsCihSRUFEX1RBR19WQUxVRVNfRkFJTFVSRV9LSU5E",
"X0RUT19USU1FT1VUEAMSMQotUkVBRF9UQUdfVkFMVUVTX0ZBSUxVUkVfS0lO",
"RF9EVE9fU0VSVkVSX0VSUk9SEAQqkwIKFFZlcmlmeUZhaWx1cmVLaW5kRHRv",
"EicKI1ZFUklGWV9GQUlMVVJFX0tJTkRfRFRPX1VOU1BFQ0lGSUVEEAASJwoj",
"VkVSSUZZX0ZBSUxVUkVfS0lORF9EVE9fVU5SRUFDSEFCTEUQARInCiNWRVJJ",
"RllfRkFJTFVSRV9LSU5EX0RUT19BVVRIX0ZBSUxFRBACEjEKLVZFUklGWV9G",
"QUlMVVJFX0tJTkRfRFRPX1VOVFJVU1RFRF9DRVJUSUZJQ0FURRADEiMKH1ZF",
"UklGWV9GQUlMVVJFX0tJTkRfRFRPX1RJTUVPVVQQBBIoCiRWRVJJRllfRkFJ",
"TFVSRV9LSU5EX0RUT19TRVJWRVJfRVJST1IQBSrlAQoaU3RvcmVBbmRGb3J3",
"YXJkQ2F0ZWdvcnlEdG8SLgoqU1RPUkVfQU5EX0ZPUldBUkRfQ0FURUdPUllf",
"RFRPX1VOU1BFQ0lGSUVEEAASMgouU1RPUkVfQU5EX0ZPUldBUkRfQ0FURUdP",
"UllfRFRPX0VYVEVSTkFMX1NZU1RFTRABEi8KK1NUT1JFX0FORF9GT1JXQVJE",
"X0NBVEVHT1JZX0RUT19OT1RJRklDQVRJT04QAhIyCi5TVE9SRV9BTkRfRk9S",
"V0FSRF9DQVRFR09SWV9EVE9fQ0FDSEVEX0RCX1dSSVRFEAMqaAoNQWxhcm1T",
"dGF0ZUR0bxIfChtBTEFSTV9TVEFURV9EVE9fVU5TUEVDSUZJRUQQABIaChZB",
"TEFSTV9TVEFURV9EVE9fQUNUSVZFEAESGgoWQUxBUk1fU1RBVEVfRFRPX05P",
"Uk1BTBACKrkBCg1BbGFybUxldmVsRHRvEh8KG0FMQVJNX0xFVkVMX0RUT19V",
"TlNQRUNJRklFRBAAEhgKFEFMQVJNX0xFVkVMX0RUT19OT05FEAESFwoTQUxB",
"Uk1fTEVWRUxfRFRPX0xPVxACEhsKF0FMQVJNX0xFVkVMX0RUT19MT1dfTE9X",
"EAMSGAoUQUxBUk1fTEVWRUxfRFRPX0hJR0gQBBIdChlBTEFSTV9MRVZFTF9E",
"VE9fSElHSF9ISUdIEAUqkgEKDEFsYXJtS2luZER0bxIeChpBTEFSTV9LSU5E",
"X0RUT19VTlNQRUNJRklFRBAAEhsKF0FMQVJNX0tJTkRfRFRPX0NPTVBVVEVE",
"EAESIAocQUxBUk1fS0lORF9EVE9fTkFUSVZFX09QQ19VQRACEiMKH0FMQVJN",
"X0tJTkRfRFRPX05BVElWRV9NWF9BQ0NFU1MQAyroAQoTQWxhcm1TaGVsdmVT",
"dGF0ZUR0bxImCiJBTEFSTV9TSEVMVkVfU1RBVEVfRFRPX1VOU1BFQ0lGSUVE",
"EAASJAogQUxBUk1fU0hFTFZFX1NUQVRFX0RUT19VTlNIRUxWRUQQARIrCidB",
"TEFSTV9TSEVMVkVfU1RBVEVfRFRPX09ORV9TSE9UX1NIRUxWRUQQAhIoCiRB",
"TEFSTV9TSEVMVkVfU1RBVEVfRFRPX1RJTUVEX1NIRUxWRUQQAxIsCihBTEFS",
"TV9TSEVMVkVfU1RBVEVfRFRPX1BFUk1BTkVOVF9TSEVMVkVEEAQyhAUKElNp",
"dGVDb21tYW5kU2VydmljZRJsChBFeGVjdXRlTGlmZWN5Y2xlEiwuc2NhZGFi",
"cmlkZ2Uuc2l0ZWNvbW1hbmQudjEuTGlmZWN5Y2xlUmVxdWVzdBoqLnNjYWRh",
"YnJpZGdlLnNpdGVjb21tYW5kLnYxLkxpZmVjeWNsZVJlcGx5EmAKDEV4ZWN1",
"dGVPcGNVYRIoLnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLk9wY1VhUmVx",
"dWVzdBomLnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLk9wY1VhUmVwbHkS",
"YAoMRXhlY3V0ZVF1ZXJ5Eiguc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEu",
"UXVlcnlSZXF1ZXN0GiYuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuUXVl",
"cnlSZXBseRJjCg1FeGVjdXRlUGFya2VkEikuc2NhZGFicmlkZ2Uuc2l0ZWNv",
"bW1hbmQudjEuUGFya2VkUmVxdWVzdBonLnNjYWRhYnJpZGdlLnNpdGVjb21t",
"YW5kLnYxLlBhcmtlZFJlcGx5EmAKDEV4ZWN1dGVSb3V0ZRIoLnNjYWRhYnJp",
"ZGdlLnNpdGVjb21tYW5kLnYxLlJvdXRlUmVxdWVzdBomLnNjYWRhYnJpZGdl",
"LnNpdGVjb21tYW5kLnYxLlJvdXRlUmVwbHkSdQoPVHJpZ2dlckZhaWxvdmVy",
"EjIuc2NhZGFicmlkZ2Uuc2l0ZWNvbW1hbmQudjEuVHJpZ2dlclNpdGVGYWls",
"b3ZlckR0bxouLnNjYWRhYnJpZGdlLnNpdGVjb21tYW5kLnYxLlNpdGVGYWls",
"b3ZlckFja0R0b0IrqgIoWkIuTU9NLldXLlNjYWRhQnJpZGdlLkNvbW11bmlj",
"YXRpb24uR3JwY2IGcHJvdG8z"));
descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
new pbr::FileDescriptor[] { global::Google.Protobuf.WellKnownTypes.TimestampReflection.Descriptor, global::Google.Protobuf.WellKnownTypes.DurationReflection.Descriptor, global::Google.Protobuf.WellKnownTypes.WrappersReflection.Descriptor, global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.SitestreamReflection.Descriptor, },
new pbr::GeneratedClrTypeInfo(new[] {typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.DeploymentStatusDto), typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.BrowseNodeClassDto), typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.BrowseFailureKindDto), typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.ReadTagValuesFailureKindDto), typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.VerifyFailureKindDto), typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.StoreAndForwardCategoryDto), typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AlarmStateDto), typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AlarmLevelDto), typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AlarmKindDto), typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.AlarmShelveStateDto), }, null, new pbr::GeneratedClrTypeInfo[] {
@@ -598,7 +599,7 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.ParkedRequest), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.ParkedRequest.Parser, new[]{ "ParkedMessageQuery", "ParkedMessageRetry", "ParkedMessageDiscard", "RetryParkedOperation", "DiscardParkedOperation" }, new[]{ "Command" }, null, null, null),
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.ParkedReply), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.ParkedReply.Parser, new[]{ "ParkedMessageQuery", "ParkedMessageRetry", "ParkedMessageDiscard", "ParkedOperationAction" }, new[]{ "Reply" }, null, null, null),
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.RouteToCallRequestDto), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.RouteToCallRequestDto.Parser, new[]{ "CorrelationId", "InstanceUniqueName", "ScriptName", "Parameters", "Timestamp", "ParentExecutionId", "Trace" }, null, null, null, null),
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.RouteToCallResponseDto), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.RouteToCallResponseDto.Parser, new[]{ "CorrelationId", "Success", "ReturnValue", "ErrorMessage", "Timestamp", "ChildExecutionId", "TraceEvents" }, null, null, null, null),
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.RouteToCallResponseDto), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.RouteToCallResponseDto.Parser, new[]{ "CorrelationId", "Success", "ReturnValue", "ErrorMessage", "Timestamp", "ChildExecutionId", "TraceEvents", "Truncated", "LogsTruncated" }, null, null, null, null),
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.RouteToGetAttributesRequestDto), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.RouteToGetAttributesRequestDto.Parser, new[]{ "CorrelationId", "InstanceUniqueName", "AttributeNames", "Timestamp", "ParentExecutionId" }, null, null, null, null),
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.RouteToGetAttributesResponseDto), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.RouteToGetAttributesResponseDto.Parser, new[]{ "CorrelationId", "Values", "Success", "ErrorMessage", "Timestamp" }, null, null, null, null),
new pbr::GeneratedClrTypeInfo(typeof(global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.RouteToSetAttributesRequestDto), global::ZB.MOM.WW.ScadaBridge.Communication.Grpc.RouteToSetAttributesRequestDto.Parser, new[]{ "CorrelationId", "InstanceUniqueName", "AttributeValues", "Timestamp", "ParentExecutionId", "Trace" }, null, null, null, new pbr::GeneratedClrTypeInfo[] { null, }),
@@ -28991,6 +28992,8 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
timestamp_ = other.timestamp_ != null ? other.timestamp_.Clone() : null;
childExecutionId_ = other.childExecutionId_;
traceEvents_ = other.traceEvents_.Clone();
truncated_ = other.truncated_;
logsTruncated_ = other.logsTruncated_;
_unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
}
@@ -29097,6 +29100,34 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
get { return traceEvents_; }
}
/// <summary>Field number for the "truncated" field.</summary>
public const int TruncatedFieldNumber = 8;
private bool truncated_;
/// <summary>
/// Flow Debugger (Gitea #54): the site's inline trace collector dropped step rows (truncated)
/// or log-line rows (logs_truncated). proto3 default false from an untraced run or older site.
/// </summary>
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public bool Truncated {
get { return truncated_; }
set {
truncated_ = value;
}
}
/// <summary>Field number for the "logs_truncated" field.</summary>
public const int LogsTruncatedFieldNumber = 9;
private bool logsTruncated_;
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public bool LogsTruncated {
get { return logsTruncated_; }
set {
logsTruncated_ = value;
}
}
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
public override bool Equals(object other) {
@@ -29119,6 +29150,8 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
if (!object.Equals(Timestamp, other.Timestamp)) return false;
if (ChildExecutionId != other.ChildExecutionId) return false;
if(!traceEvents_.Equals(other.traceEvents_)) return false;
if (Truncated != other.Truncated) return false;
if (LogsTruncated != other.LogsTruncated) return false;
return Equals(_unknownFields, other._unknownFields);
}
@@ -29133,6 +29166,8 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
if (timestamp_ != null) hash ^= Timestamp.GetHashCode();
if (ChildExecutionId.Length != 0) hash ^= ChildExecutionId.GetHashCode();
hash ^= traceEvents_.GetHashCode();
if (Truncated != false) hash ^= Truncated.GetHashCode();
if (LogsTruncated != false) hash ^= LogsTruncated.GetHashCode();
if (_unknownFields != null) {
hash ^= _unknownFields.GetHashCode();
}
@@ -29176,6 +29211,14 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
output.WriteString(ChildExecutionId);
}
traceEvents_.WriteTo(output, _repeated_traceEvents_codec);
if (Truncated != false) {
output.WriteRawTag(64);
output.WriteBool(Truncated);
}
if (LogsTruncated != false) {
output.WriteRawTag(72);
output.WriteBool(LogsTruncated);
}
if (_unknownFields != null) {
_unknownFields.WriteTo(output);
}
@@ -29211,6 +29254,14 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
output.WriteString(ChildExecutionId);
}
traceEvents_.WriteTo(ref output, _repeated_traceEvents_codec);
if (Truncated != false) {
output.WriteRawTag(64);
output.WriteBool(Truncated);
}
if (LogsTruncated != false) {
output.WriteRawTag(72);
output.WriteBool(LogsTruncated);
}
if (_unknownFields != null) {
_unknownFields.WriteTo(ref output);
}
@@ -29240,6 +29291,12 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
size += 1 + pb::CodedOutputStream.ComputeStringSize(ChildExecutionId);
}
size += traceEvents_.CalculateSize(_repeated_traceEvents_codec);
if (Truncated != false) {
size += 1 + 1;
}
if (LogsTruncated != false) {
size += 1 + 1;
}
if (_unknownFields != null) {
size += _unknownFields.CalculateSize();
}
@@ -29277,6 +29334,12 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
ChildExecutionId = other.ChildExecutionId;
}
traceEvents_.Add(other.traceEvents_);
if (other.Truncated != false) {
Truncated = other.Truncated;
}
if (other.LogsTruncated != false) {
LogsTruncated = other.LogsTruncated;
}
_unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
}
@@ -29330,6 +29393,14 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
traceEvents_.AddEntriesFrom(input, _repeated_traceEvents_codec);
break;
}
case 64: {
Truncated = input.ReadBool();
break;
}
case 72: {
LogsTruncated = input.ReadBool();
break;
}
}
}
#endif
@@ -29383,6 +29454,14 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Grpc {
traceEvents_.AddEntriesFrom(ref input, _repeated_traceEvents_codec);
break;
}
case 64: {
Truncated = input.ReadBool();
break;
}
case 72: {
LogsTruncated = input.ReadBool();
break;
}
}
}
}
@@ -86,6 +86,7 @@ public class RouteHelper
private readonly INodeIdentityProvider? _nodeIdentity;
private readonly bool _trace;
private readonly Action<AuditEvent>? _traceSink;
private readonly Action<bool, bool>? _siteTruncationSink;
private readonly IReadOnlySet<string>? _permittedSiteIdentifiers;
private readonly bool _captureBodies;
private readonly ILogger? _logger;
@@ -107,7 +108,7 @@ public class RouteHelper
INodeIdentityProvider? nodeIdentity = null,
ILogger<RouteHelper>? logger = null)
: this(instanceLocator, instanceRouter, CancellationToken.None, CancellationToken.None, parentExecutionId: null,
auditWriter, nodeIdentity, trace: false, traceSink: null, permittedSiteIdentifiers: null, captureBodies: true, logger)
auditWriter, nodeIdentity, trace: false, traceSink: null, permittedSiteIdentifiers: null, captureBodies: true, logger, siteTruncationSink: null)
{
}
@@ -123,7 +124,8 @@ public class RouteHelper
Action<AuditEvent>? traceSink,
IReadOnlySet<string>? permittedSiteIdentifiers,
bool captureBodies,
ILogger? logger)
ILogger? logger,
Action<bool, bool>? siteTruncationSink)
{
_permittedSiteIdentifiers = permittedSiteIdentifiers;
_instanceLocator = instanceLocator;
@@ -135,6 +137,7 @@ public class RouteHelper
_nodeIdentity = nodeIdentity;
_trace = trace;
_traceSink = traceSink;
_siteTruncationSink = siteTruncationSink;
_captureBodies = captureBodies;
_logger = logger;
}
@@ -150,7 +153,7 @@ public class RouteHelper
/// <returns>A new <see cref="RouteHelper"/> inheriting the given deadline token.</returns>
public RouteHelper WithDeadline(CancellationToken deadlineToken) =>
new(_instanceLocator, _instanceRouter, deadlineToken, _requestAbortedToken, _parentExecutionId,
_auditWriter, _nodeIdentity, _trace, _traceSink, _permittedSiteIdentifiers, _captureBodies, _logger);
_auditWriter, _nodeIdentity, _trace, _traceSink, _permittedSiteIdentifiers, _captureBodies, _logger, _siteTruncationSink);
/// <summary>
/// Returns a <see cref="RouteHelper"/> carrying the raw request-abort
@@ -164,7 +167,7 @@ public class RouteHelper
/// <returns>A new <see cref="RouteHelper"/> carrying the given request-abort token.</returns>
public RouteHelper WithRequestAborted(CancellationToken requestAbortedToken) =>
new(_instanceLocator, _instanceRouter, _deadlineToken, requestAbortedToken, _parentExecutionId,
_auditWriter, _nodeIdentity, _trace, _traceSink, _permittedSiteIdentifiers, _captureBodies, _logger);
_auditWriter, _nodeIdentity, _trace, _traceSink, _permittedSiteIdentifiers, _captureBodies, _logger, _siteTruncationSink);
/// <summary>
/// Audit Log (ParentExecutionId): returns a <see cref="RouteHelper"/> whose
@@ -179,7 +182,7 @@ public class RouteHelper
/// <returns>A new <see cref="RouteHelper"/> carrying the given parent execution id.</returns>
public RouteHelper WithParentExecutionId(Guid? parentExecutionId) =>
new(_instanceLocator, _instanceRouter, _deadlineToken, _requestAbortedToken, parentExecutionId,
_auditWriter, _nodeIdentity, _trace, _traceSink, _permittedSiteIdentifiers, _captureBodies, _logger);
_auditWriter, _nodeIdentity, _trace, _traceSink, _permittedSiteIdentifiers, _captureBodies, _logger, _siteTruncationSink);
/// <summary>
/// Flow Debugger (design 2026-09-05 §5.2): returns a <see cref="RouteHelper"/> whose routed
@@ -190,7 +193,7 @@ public class RouteHelper
/// <returns>A new <see cref="RouteHelper"/> carrying the flag.</returns>
public RouteHelper WithTrace(bool trace) =>
new(_instanceLocator, _instanceRouter, _deadlineToken, _requestAbortedToken, _parentExecutionId,
_auditWriter, _nodeIdentity, trace, _traceSink, _permittedSiteIdentifiers, _captureBodies, _logger);
_auditWriter, _nodeIdentity, trace, _traceSink, _permittedSiteIdentifiers, _captureBodies, _logger, _siteTruncationSink);
/// <summary>
/// Flow Debugger (design 2026-09-05 §7.1): returns a <see cref="RouteHelper"/> that also
@@ -199,10 +202,15 @@ public class RouteHelper
/// audit store. Null clears the sink.
/// </summary>
/// <param name="traceSink">Receives the audit events in emission order; null = none.</param>
/// <param name="siteTruncationSink">
/// Gitea #54: receives <c>(truncated, logsTruncated)</c> from a routed reply whose SITE-side
/// inline collector dropped rows, so the caller can label a short list as short. Called only
/// when at least one flag is set; null = none.
/// </param>
/// <returns>A new <see cref="RouteHelper"/> carrying the sink.</returns>
public RouteHelper WithTraceSink(Action<AuditEvent>? traceSink) =>
public RouteHelper WithTraceSink(Action<AuditEvent>? traceSink, Action<bool, bool>? siteTruncationSink = null) =>
new(_instanceLocator, _instanceRouter, _deadlineToken, _requestAbortedToken, _parentExecutionId,
_auditWriter, _nodeIdentity, _trace, traceSink, _permittedSiteIdentifiers, _captureBodies, _logger);
_auditWriter, _nodeIdentity, _trace, traceSink, _permittedSiteIdentifiers, _captureBodies, _logger, siteTruncationSink);
/// <summary>
/// Flow Debugger outbound calls (plan 2026-09-06, D5/D7): the trace sink this helper carries,
@@ -236,7 +244,7 @@ public class RouteHelper
/// <returns>A new <see cref="RouteHelper"/> carrying the scope.</returns>
public RouteHelper WithSiteScope(IReadOnlySet<string>? permittedSiteIdentifiers) =>
new(_instanceLocator, _instanceRouter, _deadlineToken, _requestAbortedToken, _parentExecutionId,
_auditWriter, _nodeIdentity, _trace, _traceSink, permittedSiteIdentifiers, _captureBodies, _logger);
_auditWriter, _nodeIdentity, _trace, _traceSink, permittedSiteIdentifiers, _captureBodies, _logger, _siteTruncationSink);
/// <summary>
/// Arch-review F11: returns a <see cref="RouteHelper"/> whose always-on <c>RouteCall</c> row
@@ -251,7 +259,7 @@ public class RouteHelper
/// <returns>A new <see cref="RouteHelper"/> carrying the flag.</returns>
public RouteHelper WithBodyCapture(bool captureBodies) =>
new(_instanceLocator, _instanceRouter, _deadlineToken, _requestAbortedToken, _parentExecutionId,
_auditWriter, _nodeIdentity, _trace, _traceSink, _permittedSiteIdentifiers, captureBodies, _logger);
_auditWriter, _nodeIdentity, _trace, _traceSink, _permittedSiteIdentifiers, captureBodies, _logger, _siteTruncationSink);
/// <summary>
/// Creates a route target for the specified instance.
@@ -262,7 +270,7 @@ public class RouteHelper
{
return new RouteTarget(
instanceCode, _instanceLocator, _instanceRouter, _deadlineToken, _requestAbortedToken, _parentExecutionId,
_auditWriter, _nodeIdentity, _trace, _traceSink, _permittedSiteIdentifiers, _captureBodies, _logger);
_auditWriter, _nodeIdentity, _trace, _traceSink, _permittedSiteIdentifiers, _captureBodies, _logger, _siteTruncationSink);
}
}
@@ -281,6 +289,7 @@ public class RouteTarget
private readonly INodeIdentityProvider? _nodeIdentity;
private readonly bool _trace;
private readonly Action<AuditEvent>? _traceSink;
private readonly Action<bool, bool>? _siteTruncationSink;
private readonly IReadOnlySet<string>? _permittedSiteIdentifiers;
private readonly bool _captureBodies;
private readonly ILogger? _logger;
@@ -301,6 +310,7 @@ public class RouteTarget
/// <param name="permittedSiteIdentifiers">Arch-review F2: site identifiers this call may reach; null = unscoped.</param>
/// <param name="captureBodies">Arch-review F11: false omits the request/response summary on the <c>RouteCall</c> row.</param>
/// <param name="logger">Arch-review F8: logs a Warning when the best-effort <c>RouteCall</c> audit write faults; null = silent.</param>
/// <param name="siteTruncationSink">Gitea #54: receives a routed reply's site-side truncation flags; null = none.</param>
internal RouteTarget(
string instanceCode,
IInstanceLocator instanceLocator,
@@ -314,7 +324,8 @@ public class RouteTarget
Action<AuditEvent>? traceSink = null,
IReadOnlySet<string>? permittedSiteIdentifiers = null,
bool captureBodies = true,
ILogger? logger = null)
ILogger? logger = null,
Action<bool, bool>? siteTruncationSink = null)
{
_permittedSiteIdentifiers = permittedSiteIdentifiers;
_instanceCode = instanceCode;
@@ -327,6 +338,7 @@ public class RouteTarget
_nodeIdentity = nodeIdentity;
_trace = trace;
_traceSink = traceSink;
_siteTruncationSink = siteTruncationSink;
_captureBodies = captureBodies;
_logger = logger;
}
@@ -382,6 +394,7 @@ public class RouteTarget
EmitRouteCall(scriptName, normalized, startedUtc, sw.ElapsedMilliseconds, response.Success,
response.ReturnValue, response.ErrorMessage, response.ChildExecutionId, response.TraceEvents);
ReportSiteTruncation(response);
if (!response.Success)
{
@@ -463,6 +476,23 @@ public class RouteTarget
}
}
/// <summary>
/// Gitea #54: forwards a routed reply's site-side truncation flags to the sink. Best-effort —
/// a misbehaving sink never fails the routed call.
/// </summary>
private void ReportSiteTruncation(RouteToCallResponse response)
{
if (_siteTruncationSink is null || !(response.Truncated || response.LogsTruncated)) return;
try
{
_siteTruncationSink(response.Truncated, response.LogsTruncated);
}
catch (Exception)
{
// Best-effort, like the trace sink.
}
}
private static string? TraceJson(object? value)
{
if (value is null) return null;
@@ -174,7 +174,19 @@ public sealed class FlowDebugRunner
// only known once the script runs. So the operator's scope is pushed into the
// RouteHelper and enforced at route time, before any site traffic.
var scope = await ResolvePermittedSiteIdentifiersAsync(sp, user, ct).ConfigureAwait(false);
var route = sp.GetRequiredService<RouteHelper>().WithTraceSink(evt => collector.Add(evt, lowPriority: FlowStepProjector.IsLogLine(evt))).WithSiteScope(scope);
// Gitea #54: a routed site run whose own collector dropped rows reports it here; the flags
// are OR-ed into the result so the page labels the short site lane as truncated.
var siteTruncated = 0;
var siteLogsTruncated = 0;
var route = sp.GetRequiredService<RouteHelper>()
.WithTraceSink(
evt => collector.Add(evt, lowPriority: FlowStepProjector.IsLogLine(evt)),
(truncated, logsTruncated) =>
{
if (truncated) Interlocked.Exchange(ref siteTruncated, 1);
if (logsTruncated) Interlocked.Exchange(ref siteLogsTruncated, 1);
})
.WithSiteScope(scope);
// Step-logs D7: a ScriptLog row rides the collector's low-priority lane so the step rows
// emitted at run end are never crowded out of the inline copy (Replay stays complete).
var executor = sp.GetRequiredService<InboundScriptExecutor>();
@@ -235,7 +247,8 @@ public sealed class FlowDebugRunner
return new FlowDebugRunResult(
root, result.Success, result.ErrorMessage, result.Success ? null : result.ErrorCode,
ordered, Truncated: collector.Truncated, startedAt, finishedAt, LogsTruncated: collector.LogsTruncated);
ordered, Truncated: collector.Truncated || Volatile.Read(ref siteTruncated) == 1, startedAt, finishedAt,
LogsTruncated: collector.LogsTruncated || Volatile.Read(ref siteLogsTruncated) == 1);
}
/// <summary>
@@ -438,8 +451,10 @@ public sealed class FlowDebugRunner
return new FlowDebugRunResult(
root, response.Success, response.ErrorMessage,
response.Success ? null : faultCode ?? CallFailedCode,
ordered, Truncated: collector.Truncated, startedAt, DateTimeOffset.UtcNow,
LogsTruncated: collector.LogsTruncated);
// Gitea #54: OR in the SITE collector's flags — central's collector only sees the
// rows the site sent, so a site run that dropped rows used to look complete.
ordered, Truncated: collector.Truncated || response.Truncated, startedAt, DateTimeOffset.UtcNow,
LogsTruncated: collector.LogsTruncated || response.LogsTruncated);
}
/// <summary>
@@ -3357,7 +3357,10 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers
// Flow Debugger (design §5.4): the site run's id and, for a traced
// run, its ScriptTrace events — returned inline so a Live run is
// instant. Null from an untraced run.
result.ExecutionId, result.TraceEvents);
result.ExecutionId, result.TraceEvents,
// Gitea #54: the site collector's truncation flags ride along so a
// short inline list is labelled short on the Flow Debugger page.
result.Truncated, result.LogsTruncated);
}
return new RouteToCallResponse(
request.CorrelationId, false, null,
@@ -452,9 +452,8 @@ internal static class ScriptRunLauncher
// Send result to requester if this was an Ask-based call
if (!replyTo.IsNobody())
{
replyTo.Tell(new ScriptCallResult(
correlationId, true, state.ReturnValue, null,
context.ExecutionId, collector?.Complete()));
replyTo.Tell(BuildCallResult(
correlationId, true, state.ReturnValue, null, context.ExecutionId, collector));
}
// Operational `script` event — execution completed successfully. Same gating as
@@ -493,8 +492,8 @@ internal static class ScriptRunLauncher
if (!replyTo.IsNobody())
{
replyTo.Tell(new ScriptCallResult(
correlationId, false, null, errorMsg, context?.ExecutionId, collector?.Complete()));
replyTo.Tell(BuildCallResult(
correlationId, false, null, errorMsg, context?.ExecutionId, collector));
}
completionTarget.Tell(new ScriptActor.ScriptExecutionCompleted(scriptName, false, errorMsg));
@@ -523,8 +522,8 @@ internal static class ScriptRunLauncher
if (!replyTo.IsNobody())
{
replyTo.Tell(new ScriptCallResult(
correlationId, false, null, errorMsg, context?.ExecutionId, collector?.Complete()));
replyTo.Tell(BuildCallResult(
correlationId, false, null, errorMsg, context?.ExecutionId, collector));
}
completionTarget.Tell(new ScriptActor.ScriptExecutionCompleted(scriptName, false, errorMsg));
@@ -540,6 +539,22 @@ internal static class ScriptRunLauncher
}
}
/// <summary>
/// Builds the Ask reply for a run. Closes the inline collector FIRST, then reads its
/// truncation flags, so a site run whose collector dropped rows tells the caller its
/// <c>TraceEvents</c> list is short instead of returning a silently short list (Gitea #54).
/// </summary>
private static ScriptCallResult BuildCallResult(
string correlationId, bool success, object? returnValue, string? error,
Guid? executionId, FlowTraceCollector? collector)
{
var traceEvents = collector?.Complete();
return new ScriptCallResult(
correlationId, success, returnValue, error, executionId, traceEvents,
Truncated: collector?.Truncated ?? false,
LogsTruncated: collector?.LogsTruncated ?? false);
}
/// <summary>
/// Arch-review F7: a traced run that never launched (shed at the in-flight cap, or
/// suppressed by <c>MinTimeBetweenRuns</c>) still leaves a <c>Failed</c>
@@ -203,7 +203,10 @@ internal static class SiteCommandSamples
// ── Route replies ──
Add(new RouteToCallResponse("corr-12", true, 17L, null, T1, G1, TraceSamples()),
new RouteToCallResponse("corr-12", false, null, "script faulted", T2));
new RouteToCallResponse("corr-12", false, null, "script faulted", T2),
// Gitea #54: fields 8/9 — the site collector's truncation flags.
new RouteToCallResponse("corr-12", true, 17L, null, T1, G1, TraceSamples(), Truncated: true, LogsTruncated: false),
new RouteToCallResponse("corr-12", true, 17L, null, T1, G1, TraceSamples(), Truncated: false, LogsTruncated: true));
Add(new RouteToGetAttributesResponse("corr-13", Parameters(), true, null, T1),
new RouteToGetAttributesResponse("corr-13", new Dictionary<string, object?>(), false, "no instance", T2));
Add(new RouteToSetAttributesResponse("corr-14", true, null, T1),
@@ -130,6 +130,45 @@ public class RouteHelperTraceTests
Assert.Same(siteEvents[1], sunk[2]);
}
/// <summary>
/// Gitea #54: a routed reply carrying the site collector's truncation flags is forwarded to the
/// truncation sink, and the sink survives the helper's clone chain (WithDeadline et al.).
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task TruncationSink_ReceivesTheReplysSiteTruncationFlags_ThroughClones()
{
var child = Guid.NewGuid();
_router.RouteToCallAsync("SiteA", Arg.Any<RouteToCallRequest>(), Arg.Any<CancellationToken>())
.Returns(ci => new RouteToCallResponse(((RouteToCallRequest)ci[1]).CorrelationId, true, null, null,
DateTimeOffset.UtcNow, child, [SiteEvent(child)], Truncated: false, LogsTruncated: true));
var reports = new List<(bool, bool)>();
await Helper()
.WithTraceSink(_ => { }, (t, lt) => reports.Add((t, lt)))
.WithTrace(true)
.WithDeadline(CancellationToken.None)
.WithSiteScope(null)
.To("inst-1").Call("doWork");
Assert.Equal([(false, true)], reports);
}
/// <summary>Gitea #54 control: an untruncated reply does not call the truncation sink.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task TruncationSink_IsNotCalled_ForAnUntruncatedReply()
{
_router.RouteToCallAsync("SiteA", Arg.Any<RouteToCallRequest>(), Arg.Any<CancellationToken>())
.Returns(ci => new RouteToCallResponse(((RouteToCallRequest)ci[1]).CorrelationId, true, null, null,
DateTimeOffset.UtcNow));
var calls = 0;
await Helper().WithTraceSink(_ => { }, (_, _) => calls++).To("inst-1").Call("doWork");
Assert.Equal(0, calls);
}
/// <summary>Verifies that a routing failure writes a failed row and still throws.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
@@ -307,6 +307,38 @@ public class FlowDebugRunnerTests
Assert.Contains(_central, e => e.AsRow().Kind == AuditKind.RouteCall);
}
/// <summary>
/// Gitea #54: on the method entry the routed reply reaches the runner through RouteHelper's
/// sink, so the site collector's truncation flags must ride that path too.
/// </summary>
/// <param name="truncated">The site's step-row truncation flag.</param>
/// <param name="logsTruncated">The site's log-row truncation flag.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
[Theory]
[InlineData(true, false)]
[InlineData(false, true)]
[InlineData(false, false)]
public async Task RunMethod_OrsTheRoutedSitesTruncationFlagsIntoTheResult(bool truncated, bool logsTruncated)
{
var method = new ApiMethod("M", "x") { Id = 7, TimeoutSeconds = 5 };
_apiRepo.GetApiMethodByIdAsync(7, Arg.Any<CancellationToken>()).Returns(method);
_locator.GetSiteIdForInstanceAsync("inst", Arg.Any<CancellationToken>()).Returns("site-a");
var child = Guid.NewGuid();
var siteRun = ScadaBridgeAuditEventFactory.Create(AuditChannel.ScriptTrace, AuditKind.ScriptRun, AuditStatus.Delivered,
executionId: child, target: "work");
_router.RouteToCallAsync("site-a", Arg.Any<RouteToCallRequest>(), Arg.Any<CancellationToken>())
.Returns(ci => new RouteToCallResponse(((RouteToCallRequest)ci[1]).CorrelationId, true, 7, null,
DateTimeOffset.UtcNow, child, [siteRun], truncated, logsTruncated));
_executor.RegisterHandler("M", ctx => ctx.Route.To("inst").Call("work"));
var result = await Runner().RunMethodAsync(_services.BuildServiceProvider(),
new FlowDebugRunMethodCommand(7, null), Deployer, CancellationToken.None);
Assert.True(result.Completed, result.Error);
Assert.Equal(truncated, result.Truncated);
Assert.Equal(logsTruncated, result.LogsTruncated);
}
/// <summary>Verifies that a site clock behind central still returns the sink events in emission order.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
@@ -908,6 +940,35 @@ public class FlowDebugRunnerTests
Assert.Equal("OtherScript", result.Events[1].Target);
}
/// <summary>
/// Gitea #54: a call-kind script run whose SITE collector dropped rows reports truncation on
/// the result, even though central's own collector saw only the (short) list the site sent.
/// </summary>
/// <param name="truncated">The site's step-row truncation flag.</param>
/// <param name="logsTruncated">The site's log-row truncation flag.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
[Theory]
[InlineData(true, false)]
[InlineData(false, true)]
[InlineData(false, false)]
public async Task RunScript_CallKind_OrsTheSitesTruncationFlagsIntoTheResult(bool truncated, bool logsTruncated)
{
WireScript(CallScript("Manual", """{"type":"object","properties":{"n":{"type":"integer"}},"required":["n"]}""", timeout: 5));
var child = Guid.NewGuid();
var siteRun = ScadaBridgeAuditEventFactory.Create(AuditChannel.ScriptTrace, AuditKind.ScriptRun, AuditStatus.Delivered,
executionId: child, target: "Manual");
_router.RouteToCallAsync("site-a", Arg.Any<RouteToCallRequest>(), Arg.Any<CancellationToken>())
.Returns(ci => new RouteToCallResponse(((RouteToCallRequest)ci[1]).CorrelationId, true, 42, null,
DateTimeOffset.UtcNow, child, [siteRun], truncated, logsTruncated));
var result = await Runner().RunScriptAsync(_services.BuildServiceProvider(),
new FlowDebugRunScriptCommand(5, "Manual", Json("{\"n\":3}"), null), Deployer, CancellationToken.None);
Assert.True(result.Completed, result.Error);
Assert.Equal(truncated, result.Truncated);
Assert.Equal(logsTruncated, result.LogsTruncated);
}
/// <summary>Verifies that a call-kind script run routes a traced call and returns the root route-call row followed by the site rows.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
@@ -46,9 +46,9 @@ public class DeploymentManagerRouteTraceTests : TestKit, IDisposable
TestLocalDb.DeleteFiles(path);
}
private IActorRef CreateDeploymentManager() =>
private IActorRef CreateDeploymentManager(SiteRuntimeOptions? options = null) =>
ActorOf(Props.Create(() => new DeploymentManagerActor(
_storage, _compilationService, _sharedScriptLibrary, null, new SiteRuntimeOptions(),
_storage, _compilationService, _sharedScriptLibrary, null, options ?? new SiteRuntimeOptions(),
NullLogger<DeploymentManagerActor>.Instance,
dclManager: null, healthCollector: null, serviceProvider: null, loggerFactory: null, configFetcher: null)));
@@ -56,12 +56,17 @@ public class DeploymentManagerRouteTraceTests : TestKit, IDisposable
{
InstanceUniqueName = instance,
Attributes = [new ResolvedAttribute { CanonicalName = "Mode", Value = "Auto", DataType = "String" }],
Scripts = [new ResolvedScript { CanonicalName = "DoWork", Code = "await Instance.SetAttribute(\"Mode\", \"Manual\"); return 7;" }],
Scripts =
[
new ResolvedScript { CanonicalName = "DoWork", Code = "await Instance.SetAttribute(\"Mode\", \"Manual\"); return 7;" },
// Gitea #54: more log lines than the inline collector's low-priority share (450).
new ResolvedScript { CanonicalName = "Chatty", Code = "for (var i = 0; i < 600; i++) Log.Info(\"line \" + i); return 1;" },
],
});
private async Task<IActorRef> DeployedManager(string instance)
private async Task<IActorRef> DeployedManager(string instance, SiteRuntimeOptions? options = null)
{
var actor = CreateDeploymentManager();
var actor = CreateDeploymentManager(options);
await Task.Delay(500);
actor.Tell(new DeployInstanceCommand("dep", instance, "sha256:x", ConfigJson(instance), "admin", DateTimeOffset.UtcNow));
ExpectMsg<DeploymentStatusResponse>(TimeSpan.FromSeconds(5));
@@ -96,6 +101,30 @@ public class DeploymentManagerRouteTraceTests : TestKit, IDisposable
Assert.All(rows, r => Assert.Equal(response.ChildExecutionId, r.ExecutionId));
}
/// <summary>
/// Gitea #54: a traced routed run whose inline collector dropped log rows carries
/// <c>LogsTruncated</c> on the <see cref="RouteToCallResponse"/>, so central can label the short
/// site lane; a run that fit reports neither flag.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task RouteCall_WithTrace_CarriesTheSiteCollectorsTruncationFlags()
{
var actor = await DeployedManager("ChattyPump", new SiteRuntimeOptions { MaxLogLinesPerRun = 1000 });
actor.Tell(new RouteToCallRequest("rc-t", "ChattyPump", "Chatty", null, DateTimeOffset.UtcNow, Trace: true));
var chatty = ExpectMsg<RouteToCallResponse>(TimeSpan.FromSeconds(10));
Assert.True(chatty.Success, chatty.ErrorMessage);
Assert.True(chatty.LogsTruncated);
Assert.False(chatty.Truncated);
actor.Tell(new RouteToCallRequest("rc-q", "ChattyPump", "DoWork", null, DateTimeOffset.UtcNow, Trace: true));
var quiet = ExpectMsg<RouteToCallResponse>(TimeSpan.FromSeconds(10));
Assert.True(quiet.Success, quiet.ErrorMessage);
Assert.False(quiet.LogsTruncated);
Assert.False(quiet.Truncated);
}
/// <summary>Verifies that an untraced route call returns the execution id but no trace events.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
@@ -161,4 +161,38 @@ public class ScriptRunLauncherTraceTests : TestKit, IDisposable
Assert.All(logs, e => Assert.True(rows.ToList().IndexOf(e) < anchorIndex));
Assert.Equal(logs.Count, anchorIndex);
}
// ── (d) the reply reports the site collector's truncation (Gitea #54) ────────
/// <summary>
/// Verifies that a traced run whose inline collector dropped log-line rows says so on the
/// reply (<c>LogsTruncated</c>), instead of handing the caller a silently short list.
/// </summary>
[Fact]
public void TracedRun_WhoseCollectorDroppedLogRows_ReportsLogsTruncated()
{
// 600 lines is under the per-run line cap set here (1000) but over the collector's
// low-priority event share (DefaultMaxEvents - LowPriorityReserveEvents = 450).
var result = RunTraced(
"for (var i = 0; i < 600; i++) Log.Info(\"line \" + i); return null;",
Options(maxLogLines: 1000));
Assert.True(result.Success);
Assert.True(result.LogsTruncated);
Assert.False(result.Truncated);
Assert.True(result.TraceEvents!.Count < 600);
// Step rows still fit: the ScriptRun anchor is present.
ScriptRunRow(result);
}
/// <summary>Positive control: a small traced run reports neither truncation flag.</summary>
[Fact]
public void TracedRun_WithinTheCollectorCaps_ReportsNoTruncation()
{
var result = RunTraced("Log.Info(\"one\"); return null;", Options());
Assert.True(result.Success);
Assert.False(result.LogsTruncated);
Assert.False(result.Truncated);
}
}