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
@@ -183,7 +183,8 @@ public class ManagementActor : ReceiveActor
var clientMessage = cause is ManagementCommandException
? cause.Message
: $"An internal error occurred (CorrelationId={correlationId}).";
return new ManagementError(correlationId, clientMessage, "COMMAND_FAILED");
var errorCode = cause is ManagementCommandException { Code: { Length: > 0 } code } ? code : "COMMAND_FAILED";
return new ManagementError(correlationId, clientMessage, errorCode);
}
// Static readonly role sets for the authorization gate. Single-role arms
@@ -304,6 +305,10 @@ public class ManagementActor : ReceiveActor
or RetryParkedMessageCommand or DiscardParkedMessageCommand
or DebugSnapshotCommand => DeployerOnly,
// Flow Debugger (design 2026-09-05 §9): Live runs execute scripts and write attributes
// on operator input, Replay reads the audit chain — Deployer, like the Debug View.
FlowDebugRunMethodCommand or FlowDebugRunValueChangeCommand or FlowDebugReplayCommand => DeployerOnly,
// Two-person secured write. Submit is an Operator action;
// approve/reject are Verifier actions. Separation of duties (a write may
// not be verified by its submitter) is enforced inside the handler — the
@@ -401,6 +406,11 @@ public class ManagementActor : ReceiveActor
DeleteDataConnectionCommand cmd => await HandleDeleteDataConnection(sp, cmd, user.Username),
MoveDataConnectionCommand cmd => await HandleMoveDataConnection(sp, cmd, user.Username),
// Flow Debugger (design 2026-09-05 §7.1) — one execution path for the UI and the CLI.
FlowDebugRunMethodCommand cmd => await sp.GetRequiredService<FlowDebug.FlowDebugRunner>().RunMethodAsync(sp, cmd, user, CancellationToken.None),
FlowDebugRunValueChangeCommand cmd => await sp.GetRequiredService<FlowDebug.FlowDebugRunner>().RunValueChangeAsync(sp, cmd, user, CancellationToken.None),
FlowDebugReplayCommand cmd => await sp.GetRequiredService<FlowDebug.FlowDebugRunner>().ReplayAsync(sp, cmd, CancellationToken.None),
// External Systems
ListExternalSystemsCommand => await HandleListExternalSystems(sp),
GetExternalSystemCommand cmd => await HandleGetExternalSystem(sp, cmd),
@@ -4207,4 +4217,16 @@ public class ManagementCommandException : Exception
/// <summary>Initializes a new instance with the given caller-safe error message.</summary>
/// <param name="message">The error message to surface verbatim to the HTTP/CLI caller.</param>
public ManagementCommandException(string message) : base(message) { }
/// <summary>
/// Initializes a new instance with a caller-safe message AND a machine-readable error code
/// that replaces the generic <c>COMMAND_FAILED</c> on the <c>ManagementError</c> reply
/// (Flow Debugger design 2026-09-05 §7.1: <c>STANDBY_NODE</c>, <c>NO_TRIGGER_WITHIN_WINDOW</c>).
/// </summary>
/// <param name="message">The error message to surface verbatim to the HTTP/CLI caller.</param>
/// <param name="code">The error code to surface as <c>ManagementError.ErrorCode</c>.</param>
public ManagementCommandException(string message, string code) : base(message) { Code = code; }
/// <summary>Optional machine-readable error code; null = <c>COMMAND_FAILED</c>.</summary>
public string? Code { get; }
}