feat(wire): route_to_call trace flag + child_execution_id + trace_events; route_to_set_attributes trace (additive proto + mapper)

Flow Debugger (design 2026-09-05 §5.2/§5.4): site_command.proto gains trace=7 on
RouteToCallRequestDto, child_execution_id=6 + repeated sitestream.AuditEventDto trace_events=7
on RouteToCallResponseDto, trace=6 on RouteToSetAttributesRequestDto (imports
Protos/sitestream.proto the way central_control.proto does). Mapper round-trips them; an
empty trace_events list maps to null so an untraced/older site reads as 'not traced'.
Vendored C# regenerated via docker/regen-proto.sh (csproj untouched); golden samples extended.

Claude-Session: https://claude.ai/code/session_01GSLkkHKz9JrAtmJjmQK3cP
This commit is contained in:
Joseph Doherty
2026-09-05 23:52:50 -04:00
parent 2c86adb782
commit f15d0db0ad
5 changed files with 792 additions and 483 deletions
@@ -2420,7 +2420,8 @@ public static class SiteCommandDtoMapper
ScriptName = request.ScriptName,
Parameters = LooseValueCodec.ToProtoMapOrNull(request.Parameters),
Timestamp = Ts(request.Timestamp),
ParentExecutionId = GuidToWire(request.ParentExecutionId)
ParentExecutionId = GuidToWire(request.ParentExecutionId),
Trace = request.Trace
};
}
@@ -2437,7 +2438,8 @@ public static class SiteCommandDtoMapper
dto.ScriptName,
LooseValueCodec.FromProtoMapOrNull(dto.Parameters),
Dto(dto.Timestamp),
GuidFromWire(dto.ParentExecutionId));
GuidFromWire(dto.ParentExecutionId),
dto.Trace);
}
/// <summary>Projects a <see cref="RouteToGetAttributesRequest"/> onto the wire.</summary>
@@ -2485,7 +2487,8 @@ public static class SiteCommandDtoMapper
CorrelationId = request.CorrelationId,
InstanceUniqueName = request.InstanceUniqueName,
Timestamp = Ts(request.Timestamp),
ParentExecutionId = GuidToWire(request.ParentExecutionId)
ParentExecutionId = GuidToWire(request.ParentExecutionId),
Trace = request.Trace
};
foreach (var (key, value) in request.AttributeValues)
@@ -2508,7 +2511,8 @@ public static class SiteCommandDtoMapper
dto.InstanceUniqueName,
dto.AttributeValues.ToDictionary(kv => kv.Key, kv => kv.Value),
Dto(dto.Timestamp),
GuidFromWire(dto.ParentExecutionId));
GuidFromWire(dto.ParentExecutionId),
dto.Trace);
}
/// <summary>Projects a <see cref="RouteToWaitForAttributeRequest"/> onto the wire.</summary>
@@ -2564,14 +2568,24 @@ public static class SiteCommandDtoMapper
{
ArgumentNullException.ThrowIfNull(response);
return new RouteToCallResponseDto
var dto = new RouteToCallResponseDto
{
CorrelationId = response.CorrelationId,
Success = response.Success,
ReturnValue = LooseValueCodec.ToProtoOrNull(response.ReturnValue),
ErrorMessage = OrEmpty(response.ErrorMessage),
Timestamp = Ts(response.Timestamp)
Timestamp = Ts(response.Timestamp),
// Flow Debugger (design 2026-09-05 §5.4): additive fields 6/7.
ChildExecutionId = GuidToWire(response.ChildExecutionId)
};
if (response.TraceEvents is { Count: > 0 } traceEvents)
{
foreach (var evt in traceEvents)
{
dto.TraceEvents.Add(AuditEventDtoMapper.ToDto(evt));
}
}
return dto;
}
/// <summary>Rehydrates a <see cref="RouteToCallResponse"/> from the wire.</summary>
@@ -2586,7 +2600,11 @@ public static class SiteCommandDtoMapper
dto.Success,
LooseValueCodec.FromProto(dto.ReturnValue),
NullIfEmpty(dto.ErrorMessage),
Dto(dto.Timestamp));
Dto(dto.Timestamp),
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());
}
/// <summary>Projects a <see cref="RouteToGetAttributesResponse"/> onto the wire.</summary>
@@ -5,6 +5,9 @@ package scadabridge.sitecommand.v1;
import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";
import "google/protobuf/wrappers.proto";
// Flow Debugger (design 2026-09-05 §5.4): RouteToCallResponseDto carries the site's
// ScriptTrace rows inline as the AuditEventDto sitestream.proto already defines.
import "Protos/sitestream.proto";
// ─────────────────────────────────────────────────────────────────────────────
// Site command plane (central → site) — the gRPC replacement for the per-site
@@ -765,6 +768,9 @@ message RouteToCallRequestDto {
LooseValueMap parameters = 4; // absent => null (distinct from empty)
google.protobuf.Timestamp timestamp = 5;
string parent_execution_id = 6; // empty string represents null
// Flow Debugger trace flag (design 2026-09-05 §5.2). proto3 default false = untraced,
// which is what an older central sends and what an older site reads.
bool trace = 7;
}
message RouteToCallResponseDto {
@@ -773,6 +779,12 @@ message RouteToCallResponseDto {
LooseValue return_value = 3; // absent => null
string error_message = 4; // empty string represents null
google.protobuf.Timestamp timestamp = 5;
// Flow Debugger (design §5.4): the routed site run's ExecutionId ("D" GUID form;
// empty represents null — an older site never sets it).
string child_execution_id = 6;
// 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;
}
message RouteToGetAttributesRequestDto {
@@ -797,6 +809,8 @@ message RouteToSetAttributesRequestDto {
map<string, string> attribute_values = 3;
google.protobuf.Timestamp timestamp = 4;
string parent_execution_id = 5; // empty string represents null
// Flow Debugger trace flag (design 2026-09-05 §5.2/§5.3): arms the echo stamp.
bool trace = 6;
}
message RouteToSetAttributesResponseDto {
File diff suppressed because it is too large Load Diff