feat(inbound-api): ApiMethod.TraceAllCalls (entity, migration, mgmt command, CLI --trace-all-calls)

Flow Debugger (design 2026-09-05 §5.2): bit NOT NULL DEFAULT 0 column + EF migration
20260906034113 with its docs/plans/sql production twin. CreateApiMethodCommand gains
TraceAllCalls=false; UpdateApiMethodCommand gains bool? TraceAllCalls (null = unchanged so an
older CLI never resets it). Management result echoes the flag; CLI create/update expose
--trace-all-calls; README carries the ~7-rows-per-call warning.

Claude-Session: https://claude.ai/code/session_01GSLkkHKz9JrAtmJjmQK3cP
This commit is contained in:
Joseph Doherty
2026-09-05 23:42:52 -04:00
parent febb268d89
commit 23157d2bcf
11 changed files with 2327 additions and 7 deletions
@@ -60,6 +60,10 @@ public static class ApiMethodCommands
timeoutOption.DefaultValueFactory = _ => 30;
var parametersOption = new Option<string?>("--parameters") { Description = "Parameter definitions JSON" };
var returnDefOption = new Option<string?>("--return-def") { Description = "Return type definition" };
var traceAllCallsOption = new Option<bool>("--trace-all-calls")
{
Description = "Trace every production call to this method for the Flow Debugger (adds ~7 audit rows per call; low-volume integration methods only)"
};
var cmd = new Command("create") { Description = "Create an API method" };
cmd.Add(nameOption);
@@ -67,6 +71,7 @@ public static class ApiMethodCommands
cmd.Add(timeoutOption);
cmd.Add(parametersOption);
cmd.Add(returnDefOption);
cmd.Add(traceAllCallsOption);
cmd.SetAction(async (ParseResult result) =>
{
var name = result.GetValue(nameOption)!;
@@ -74,9 +79,10 @@ public static class ApiMethodCommands
var timeout = result.GetValue(timeoutOption);
var parameters = result.GetValue(parametersOption);
var returnDef = result.GetValue(returnDefOption);
var traceAllCalls = result.GetValue(traceAllCallsOption);
return await CommandHelpers.ExecuteCommandAsync(
result, urlOption, formatOption, usernameOption, passwordOption,
new CreateApiMethodCommand(name, script, timeout, parameters, returnDef));
new CreateApiMethodCommand(name, script, timeout, parameters, returnDef, traceAllCalls));
});
return cmd;
}
@@ -89,6 +95,11 @@ public static class ApiMethodCommands
timeoutOption.DefaultValueFactory = _ => 30;
var parametersOption = new Option<string?>("--parameters") { Description = "Parameter definitions JSON" };
var returnDefOption = new Option<string?>("--return-def") { Description = "Return type definition" };
// bool? so an update that omits the flag leaves the stored value unchanged.
var traceAllCallsOption = new Option<bool?>("--trace-all-calls")
{
Description = "Trace every production call to this method for the Flow Debugger (true/false; omitted = unchanged; adds ~7 audit rows per call)"
};
var cmd = new Command("update") { Description = "Update an API method" };
cmd.Add(idOption);
@@ -96,6 +107,7 @@ public static class ApiMethodCommands
cmd.Add(timeoutOption);
cmd.Add(parametersOption);
cmd.Add(returnDefOption);
cmd.Add(traceAllCallsOption);
cmd.SetAction(async (ParseResult result) =>
{
var id = result.GetValue(idOption);
@@ -103,9 +115,10 @@ public static class ApiMethodCommands
var timeout = result.GetValue(timeoutOption);
var parameters = result.GetValue(parametersOption);
var returnDef = result.GetValue(returnDefOption);
var traceAllCalls = result.GetValue(traceAllCallsOption);
return await CommandHelpers.ExecuteCommandAsync(
result, urlOption, formatOption, usernameOption, passwordOption,
new UpdateApiMethodCommand(id, script, timeout, parameters, returnDef));
new UpdateApiMethodCommand(id, script, timeout, parameters, returnDef, traceAllCalls));
});
return cmd;
}
+4 -2
View File
@@ -2109,7 +2109,7 @@ scadabridge --url <url> api-method get --id <int>
Create a new inbound API method.
```sh
scadabridge --url <url> api-method create --name <string> --script <string> [--timeout <int>] [--parameters <json>] [--return-def <json>]
scadabridge --url <url> api-method create --name <string> --script <string> [--timeout <int>] [--parameters <json>] [--return-def <json>] [--trace-all-calls]
```
| Option | Required | Default | Description |
@@ -2119,6 +2119,7 @@ scadabridge --url <url> api-method create --name <string> --script <string> [--t
| `--timeout` | no | `30` | Timeout in seconds |
| `--parameters` | no | — | Parameter definitions JSON |
| `--return-def` | no | — | Return type definition JSON |
| `--trace-all-calls` | no | off | Trace every production call for the Flow Debugger. **Adds ~7 audit rows per call** — low-volume integration methods only |
#### `api-method update`
@@ -2126,7 +2127,7 @@ Update an inbound API method. An update **replaces** the whole entity — every
field below must be supplied, even if unchanged.
```sh
scadabridge --url <url> api-method update --id <int> --script <string> [--timeout <int>] [--parameters <json>] [--return-def <json>]
scadabridge --url <url> api-method update --id <int> --script <string> [--timeout <int>] [--parameters <json>] [--return-def <json>] [--trace-all-calls <true|false>]
```
| Option | Required | Default | Description |
@@ -2136,6 +2137,7 @@ scadabridge --url <url> api-method update --id <int> --script <string> [--timeou
| `--timeout` | no | `30` | Timeout in seconds |
| `--parameters` | no | — | Parameter definitions JSON |
| `--return-def` | no | — | Return type definition JSON |
| `--trace-all-calls` | no | unchanged | `true`/`false` — trace every production call for the Flow Debugger. **Adds ~7 audit rows per call.** Omitted = the stored value is left as-is (the one field the replace-all rule does not apply to) |
Script changes take effect immediately — the updated code is recompiled in-memory on the active central node. No restart is required.
@@ -15,6 +15,12 @@ public class ApiMethod
/// <summary>Gets or sets the script execution timeout in seconds.</summary>
public int TimeoutSeconds { get; set; }
/// <summary>
/// When true every production call to this method is fully traced (Flow Debugger design
/// 2026-09-05 §5.2); intended for low-volume integration methods — ~7 audit rows per call.
/// </summary>
public bool TraceAllCalls { get; set; }
/// <summary>Initializes a new <see cref="ApiMethod"/> with the required name and script.</summary>
/// <param name="name">The method name (used as the route segment).</param>
/// <param name="script">The C# script body to execute.</param>
@@ -2,6 +2,8 @@ namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
public record ListApiMethodsCommand;
public record GetApiMethodCommand(int ApiMethodId);
public record CreateApiMethodCommand(string Name, string Script, int TimeoutSeconds, string? ParameterDefinitions, string? ReturnDefinition);
public record UpdateApiMethodCommand(int ApiMethodId, string Script, int TimeoutSeconds, string? ParameterDefinitions, string? ReturnDefinition);
/// <param name="TraceAllCalls">Flow Debugger (design 2026-09-05 §5.2): trace every production call to this method. Additive; defaults to off.</param>
public record CreateApiMethodCommand(string Name, string Script, int TimeoutSeconds, string? ParameterDefinitions, string? ReturnDefinition, bool TraceAllCalls = false);
/// <param name="TraceAllCalls">Flow Debugger (design 2026-09-05 §5.2): <c>null</c> leaves the stored value unchanged so an older CLI never resets it.</param>
public record UpdateApiMethodCommand(int ApiMethodId, string Script, int TimeoutSeconds, string? ParameterDefinitions, string? ReturnDefinition, bool? TraceAllCalls = null);
public record DeleteApiMethodCommand(int ApiMethodId);
@@ -31,6 +31,11 @@ public class ApiMethodConfiguration : IEntityTypeConfiguration<ApiMethod>
builder.Property(m => m.ReturnDefinition)
.HasMaxLength(4000);
// Flow Debugger (design 2026-09-05 §5.2): bit NOT NULL DEFAULT 0.
builder.Property(m => m.TraceAllCalls)
.IsRequired()
.HasDefaultValue(false);
builder.HasIndex(m => m.Name).IsUnique();
}
}
@@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
{
/// <inheritdoc />
public partial class AddApiMethodTraceAllCalls : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "TraceAllCalls",
table: "ApiMethods",
type: "bit",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "TraceAllCalls",
table: "ApiMethods");
}
}
}
@@ -511,6 +511,11 @@ namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Migrations
b.Property<int>("TimeoutSeconds")
.HasColumnType("int");
b.Property<bool>("TraceAllCalls")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false);
b.HasKey("Id");
b.HasIndex("Name")
@@ -3447,7 +3447,8 @@ public class ManagementActor : ReceiveActor
{
TimeoutSeconds = cmd.TimeoutSeconds,
ParameterDefinitions = cmd.ParameterDefinitions,
ReturnDefinition = cmd.ReturnDefinition
ReturnDefinition = cmd.ReturnDefinition,
TraceAllCalls = cmd.TraceAllCalls
};
await repo.AddApiMethodAsync(method);
await repo.SaveChangesAsync();
@@ -3468,6 +3469,8 @@ public class ManagementActor : ReceiveActor
method.TimeoutSeconds = cmd.TimeoutSeconds;
method.ParameterDefinitions = cmd.ParameterDefinitions;
method.ReturnDefinition = cmd.ReturnDefinition;
if (cmd.TraceAllCalls is { } traceAllCalls)
method.TraceAllCalls = traceAllCalls;
await repo.UpdateApiMethodAsync(method);
await repo.SaveChangesAsync();
// The persisted DB row is authoritative: on a compile failure the method returns
@@ -3531,6 +3534,7 @@ public class ManagementActor : ReceiveActor
method.ParameterDefinitions,
method.ReturnDefinition,
method.TimeoutSeconds,
method.TraceAllCalls,
compileWarning,
};