feat(management): FlowDebug run-method / run-value-change / replay commands (Deployer, active-node gated, inline trace)

Flow Debugger (design 2026-09-05 §7.1): FlowDebugRunMethodCommand / FlowDebugRunValueChangeCommand /
FlowDebugReplayCommand on the per-node ManagementActor (Deployer role; the two run commands
are long-running), all delegating to FlowDebugRunner. RunMethod refuses on a standby with
STANDBY_NODE (ManagementCommandException now carries an optional Code that replaces
COMMAND_FAILED), validates parameters through the real ParameterValidator/SchemaRefResolver
(made public), synthesizes the inbound row (actor = operator, Extra.source = flow-debugger)
and runs the method through InboundScriptExecutor with a trace-sink RouteHelper so the reply
carries the RouteCall row + the site's inline events. RunValueChange does a traced routed
write via IInstanceRouter, then polls IPullTraceEventsClient (interface moved to Commons so
ManagementService can consume it) until a ScriptRun parented to the root settles, else
NO_TRIGGER_WITHIN_WINDOW. Replay reads IAuditLogRepository.GetTraceEventsAsync.

Claude-Session: https://claude.ai/code/session_01GSLkkHKz9JrAtmJjmQK3cP
This commit is contained in:
Joseph Doherty
2026-09-06 00:04:18 -04:00
parent 9300bc1a4f
commit d11dcb77c5
11 changed files with 675 additions and 33 deletions
@@ -0,0 +1,27 @@
using ZB.MOM.WW.Audit;
namespace ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
/// <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);
}
@@ -0,0 +1,51 @@
using System.Text.Json;
using ZB.MOM.WW.Audit;
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
/// <summary>
/// Flow Debugger Live run of an inbound API method (design 2026-09-05 §7.1): executes the
/// method script on the ACTIVE central node with <paramref name="Parameters"/> as the request
/// body, tracing every routed site run, and returns the whole flow's audit events inline.
/// Deployer role; refuses on a standby with error code <c>STANDBY_NODE</c>.
/// </summary>
/// <param name="ApiMethodId">The method to run.</param>
/// <param name="Parameters">Flat JSON object of parameters (validated against the method's schema); null = none.</param>
public record FlowDebugRunMethodCommand(int ApiMethodId, JsonElement? Parameters);
/// <summary>
/// Flow Debugger Live run of a value-change trigger (design 2026-09-05 §5.3): writes
/// <paramref name="Value"/> to <paramref name="AttributeName"/> on the instance through the
/// traced routed-write path, then polls the site for the traced run(s) the change spawned.
/// </summary>
/// <param name="InstanceId">The instance to write to.</param>
/// <param name="AttributeName">Canonical attribute name.</param>
/// <param name="Value">Canonical string value to write.</param>
/// <param name="WaitSeconds">How long to wait for a spawned run before reporting <c>NO_TRIGGER_WITHIN_WINDOW</c>.</param>
public record FlowDebugRunValueChangeCommand(int InstanceId, string AttributeName, string Value, int WaitSeconds = 45);
/// <summary>
/// Flow Debugger Replay (design 2026-09-05 §7.1): returns the audit events of the execution
/// tree that <paramref name="ExecutionId"/> belongs to, from the central store.
/// </summary>
/// <param name="ExecutionId">Any execution id in the chain.</param>
public record FlowDebugReplayCommand(Guid ExecutionId);
/// <summary>The result of a Flow Debugger run or replay: the flow's audit events plus outcome metadata.</summary>
/// <param name="RootExecutionId">The root execution id (the inbound request / the value-change write).</param>
/// <param name="Completed">True when the run finished (or the replay found rows); false on timeout / no trigger.</param>
/// <param name="Error">Human-readable failure, null on success.</param>
/// <param name="ErrorCode">Machine-readable failure code, null on success.</param>
/// <param name="Events">The flow's audit events, oldest first — feed <c>FlowGraphBuilder.Build</c>.</param>
/// <param name="Truncated">True when the inline event list was capped (the audit rows remain complete).</param>
/// <param name="StartedAtUtc">When the run started.</param>
/// <param name="FinishedAtUtc">When the run finished (or the replay was served).</param>
public record FlowDebugRunResult(
Guid RootExecutionId,
bool Completed,
string? Error,
string? ErrorCode,
IReadOnlyList<AuditEvent> Events,
bool Truncated,
DateTimeOffset StartedAtUtc,
DateTimeOffset FinishedAtUtc);