feat(management): actor dispatch for BrowseNode/SearchAddressSpace/VerifyEndpoint — CLI parity (arch-review C4)
Made CommunicationService.BrowseNodeAsync/SearchAddressSpaceAsync/VerifyEndpointAsync virtual to support the test seam (mirrors existing virtual WriteTagAsync). Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
This commit is contained in:
@@ -363,7 +363,7 @@ public class CommunicationService
|
||||
/// <param name="command">The OPC UA browse command.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The browse result (children + truncation flag + structured failure).</returns>
|
||||
public Task<BrowseNodeResult> BrowseNodeAsync(
|
||||
public virtual Task<BrowseNodeResult> BrowseNodeAsync(
|
||||
string siteId,
|
||||
BrowseNodeCommand command,
|
||||
CancellationToken cancellationToken = default)
|
||||
@@ -385,7 +385,7 @@ public class CommunicationService
|
||||
/// <param name="command">The verify-endpoint command (connection name + protocol + serialized config).</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The verification result (success or classified failure, with a captured certificate when the failure is an untrusted certificate).</returns>
|
||||
public Task<VerifyEndpointResult> VerifyEndpointAsync(
|
||||
public virtual Task<VerifyEndpointResult> VerifyEndpointAsync(
|
||||
string siteId,
|
||||
VerifyEndpointCommand command,
|
||||
CancellationToken cancellationToken = default)
|
||||
@@ -407,7 +407,7 @@ public class CommunicationService
|
||||
/// <param name="command">The address-space search command (connection name + query + depth/result caps).</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The search result (matches + cap-reached flag + structured failure).</returns>
|
||||
public Task<SearchAddressSpaceResult> SearchAddressSpaceAsync(
|
||||
public virtual Task<SearchAddressSpaceResult> SearchAddressSpaceAsync(
|
||||
string siteId,
|
||||
SearchAddressSpaceCommand command,
|
||||
CancellationToken cancellationToken = default)
|
||||
|
||||
@@ -243,6 +243,11 @@ public class ManagementActor : ReceiveActor
|
||||
or CreateTemplateFolderCommand or RenameTemplateFolderCommand
|
||||
or MoveTemplateFolderCommand or ReorderTemplateFolderCommand or DeleteTemplateFolderCommand
|
||||
or MoveTemplateToFolderCommand
|
||||
// OPC UA / MxGateway design-time remote queries (arch-review C4):
|
||||
// browse the address space, run a bounded search, and probe/verify an
|
||||
// endpoint. Read-only against the live site server; gated Design to
|
||||
// match the connection editor these back.
|
||||
or BrowseNodeCommand or SearchAddressSpaceCommand or VerifyEndpointCommand
|
||||
or ExportBundleCommand => DesignerOnly,
|
||||
|
||||
// Transport import operations (mirror the Central UI gating:
|
||||
@@ -435,6 +440,9 @@ public class ManagementActor : ReceiveActor
|
||||
|
||||
// Remote Queries
|
||||
QueryEventLogsCommand cmd => await HandleQueryEventLogs(sp, cmd, user),
|
||||
BrowseNodeCommand cmd => await HandleBrowseNode(sp, cmd, user),
|
||||
SearchAddressSpaceCommand cmd => await HandleSearchAddressSpace(sp, cmd, user),
|
||||
VerifyEndpointCommand cmd => await HandleVerifyEndpoint(sp, cmd, user),
|
||||
QueryParkedMessagesCommand cmd => await HandleQueryParkedMessages(sp, cmd, user),
|
||||
RetryParkedMessageCommand cmd => await HandleRetryParkedMessage(sp, cmd, user),
|
||||
DiscardParkedMessageCommand cmd => await HandleDiscardParkedMessage(sp, cmd, user),
|
||||
@@ -2914,6 +2922,39 @@ public class ManagementActor : ReceiveActor
|
||||
return await commService.QueryEventLogsAsync(cmd.SiteIdentifier, request);
|
||||
}
|
||||
|
||||
// OPC UA / MxGateway design-time remote queries (arch-review C4). These
|
||||
// handlers give the management API/CLI parity with the Central UI, which
|
||||
// already reaches these site actors directly. SiteIdentifier is REQUIRED —
|
||||
// the actor must know which site to route to — and site-scope is enforced
|
||||
// before relaying, exactly like HandleQueryEventLogs.
|
||||
|
||||
private static async Task<object?> HandleBrowseNode(IServiceProvider sp, BrowseNodeCommand cmd, AuthenticatedUser user)
|
||||
{
|
||||
var site = cmd.SiteIdentifier
|
||||
?? throw new ManagementCommandException("SiteIdentifier is required when browsing an address space via the management API.");
|
||||
await EnforceSiteScopeForIdentifier(sp, user, site);
|
||||
var commService = sp.GetRequiredService<CommunicationService>();
|
||||
return await commService.BrowseNodeAsync(site, cmd);
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleSearchAddressSpace(IServiceProvider sp, SearchAddressSpaceCommand cmd, AuthenticatedUser user)
|
||||
{
|
||||
var site = cmd.SiteIdentifier
|
||||
?? throw new ManagementCommandException("SiteIdentifier is required when searching an address space via the management API.");
|
||||
await EnforceSiteScopeForIdentifier(sp, user, site);
|
||||
var commService = sp.GetRequiredService<CommunicationService>();
|
||||
return await commService.SearchAddressSpaceAsync(site, cmd);
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleVerifyEndpoint(IServiceProvider sp, VerifyEndpointCommand cmd, AuthenticatedUser user)
|
||||
{
|
||||
var site = cmd.SiteIdentifier
|
||||
?? throw new ManagementCommandException("SiteIdentifier is required when verifying an endpoint via the management API.");
|
||||
await EnforceSiteScopeForIdentifier(sp, user, site);
|
||||
var commService = sp.GetRequiredService<CommunicationService>();
|
||||
return await commService.VerifyEndpointAsync(site, cmd);
|
||||
}
|
||||
|
||||
private static async Task<object?> HandleQueryParkedMessages(IServiceProvider sp, QueryParkedMessagesCommand cmd, AuthenticatedUser user)
|
||||
{
|
||||
await EnforceSiteScopeForIdentifier(sp, user, cmd.SiteIdentifier);
|
||||
|
||||
Reference in New Issue
Block a user