fix(central-ui): Flow Debugger Replay opens to OperationalAudit roles; Live run and Retry/Discard stay Deployer-only (arch-review F3)
Design D6 / §9 says a Live run is a Deployer act while Replay needs only
OperationalAudit, but the page and all three FlowDebug* commands were gated on
Deployer. The Audit Log and Execution Chain pages -- themselves OperationalAudit
-- show an "Open in Flow Debugger" drill-in that an auditor could not follow.
Implements the split:
- New `RequireFlowDebugger` policy over `FlowDebuggerAccessRoles`
{Deployer, Administrator, Viewer}; the page now carries it instead of
`RequireDeployment`.
- `FlowDebugReplayCommand` takes the same role set in the ManagementActor
matrix; `FlowDebugRunMethodCommand` / `FlowDebugRunValueChangeCommand` stay
DeployerOnly.
- The page resolves `_canLive` (Deployer) in OnInitializedAsync and, without it,
pins the mode to Replay, hides the entry-point and mode toggles, the Run
button and the Live pickers, honours only the `?executionId=` drill-in, and
leaves the parked-node Retry/Discard callbacks unset so those buttons never
render. `CanRun`, `SetMode(Live)` and `ActOnStepAsync` repeat the check so the
split does not rest on markup alone.
The NavMenu entry stays inside the Deployer-gated Deployment section -- auditors
arrive through the audit drill-ins, which is the point of the finding.
Claude-Session: https://claude.ai/code/session_01NcMG6xXdsJbmD2wYAgkK29
This commit is contained in:
@@ -1264,6 +1264,44 @@ public class AuthorizationPolicyTests
|
||||
Assert.False(await EvaluatePolicy(AuthorizationPolicies.RequireSecuredWriteAccess, principal));
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// Flow Debugger page access (arch-review F3 / design D6). Replay is an
|
||||
// audit read, so opening the page is any-of {Deployer, Administrator,
|
||||
// Viewer} — the OperationalAudit roles plus Deployer. The Live run
|
||||
// controls are separately gated on Deployer by the page and, server-side,
|
||||
// by the ManagementActor role matrix; Designer gets neither.
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
[Theory]
|
||||
[InlineData("Deployer")]
|
||||
[InlineData("Administrator")]
|
||||
[InlineData("Viewer")]
|
||||
public async Task RequireFlowDebugger_ReplayCapableRoles_Satisfy(string role)
|
||||
{
|
||||
var principal = CreatePrincipal(new[] { role });
|
||||
Assert.True(await EvaluatePolicy(AuthorizationPolicies.RequireFlowDebugger, principal));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Designer")]
|
||||
[InlineData("Operator")]
|
||||
[InlineData("Verifier")]
|
||||
public async Task RequireFlowDebugger_OtherRoles_Denied(string role)
|
||||
{
|
||||
var principal = CreatePrincipal(new[] { role });
|
||||
Assert.False(await EvaluatePolicy(AuthorizationPolicies.RequireFlowDebugger, principal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RequireFlowDebugger_DoesNotImplyDeployment()
|
||||
{
|
||||
// A Viewer may open the page (Replay) but must not satisfy the policy the
|
||||
// Live run controls are gated on.
|
||||
var viewer = CreatePrincipal(new[] { Roles.Viewer });
|
||||
Assert.True(await EvaluatePolicy(AuthorizationPolicies.RequireFlowDebugger, viewer));
|
||||
Assert.False(await EvaluatePolicy(AuthorizationPolicies.RequireDeployment, viewer));
|
||||
}
|
||||
|
||||
private static ClaimsPrincipal CreatePrincipal(string[] roles, string[]? siteIds = null)
|
||||
{
|
||||
var claims = new List<Claim>();
|
||||
|
||||
Reference in New Issue
Block a user