refactor: rename ScadaLink → ZB.MOM.WW.ScadaBridge (code + projects + namespaces)

Solution + 23 src projects + 26 test projects renamed; folders, csproj,
namespaces, and ScadaLinkDbContext/ScadaBridgeDbContext class updated.
ActorSystem "scadalink" → "scadabridge", Akka seed-node URLs migrated.
SQL roles/logins, LDAP domains, CLI command name, and CLI config dir
(~/.scadalink → ~/.scadabridge) also renamed.

Build green; 5 Host.Tests fail awaiting SQL login rename in next commit.
Pre-existing StaleTagMonitor timing flakes unchanged.

Rename script committed at tools/rename-to-scadabridge.sh.
This commit is contained in:
Joseph Doherty
2026-05-28 09:37:45 -04:00
parent 6d87ee3c3b
commit 7b0b9c7365
1531 changed files with 11180 additions and 11054 deletions
@@ -0,0 +1,8 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Artifacts;
public record ArtifactDeploymentResponse(
string DeploymentId,
string SiteId,
bool Success,
string? ErrorMessage,
DateTimeOffset Timestamp);
@@ -0,0 +1,8 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Artifacts;
public record DataConnectionArtifact(
string Name,
string Protocol,
string? PrimaryConfigurationJson,
string? BackupConfigurationJson,
int FailoverRetryCount = 3);
@@ -0,0 +1,7 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Artifacts;
public record DatabaseConnectionArtifact(
string Name,
string ConnectionString,
int MaxRetries,
TimeSpan RetryDelay);
@@ -0,0 +1,11 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Artifacts;
public record DeployArtifactsCommand(
string DeploymentId,
IReadOnlyList<SharedScriptArtifact>? SharedScripts,
IReadOnlyList<ExternalSystemArtifact>? ExternalSystems,
IReadOnlyList<DatabaseConnectionArtifact>? DatabaseConnections,
IReadOnlyList<NotificationListArtifact>? NotificationLists,
IReadOnlyList<DataConnectionArtifact>? DataConnections,
IReadOnlyList<SmtpConfigurationArtifact>? SmtpConfigurations,
DateTimeOffset Timestamp);
@@ -0,0 +1,8 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Artifacts;
public record ExternalSystemArtifact(
string Name,
string EndpointUrl,
string AuthType,
string? AuthConfiguration,
string? MethodDefinitionsJson);
@@ -0,0 +1,5 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Artifacts;
public record NotificationListArtifact(
string Name,
IReadOnlyList<string> RecipientEmails);
@@ -0,0 +1,7 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Artifacts;
public record SharedScriptArtifact(
string Name,
string Code,
string? ParameterDefinitions,
string? ReturnDefinition);
@@ -0,0 +1,11 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Artifacts;
public record SmtpConfigurationArtifact(
string Name,
string Server,
int Port,
string AuthMode,
string FromAddress,
string? Username,
string? Password,
string? OAuthConfig);
@@ -0,0 +1,20 @@
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit;
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Audit;
/// <summary>
/// Akka message sent to the central <c>AuditLogIngestActor</c> (Audit Log #23,
/// M2 site-sync pipeline) carrying a batch of <see cref="AuditEvent"/> rows
/// decoded by the <c>SiteStreamGrpcServer</c> from a site's
/// <c>IngestAuditEvents</c> gRPC RPC. The actor stamps
/// <see cref="AuditEvent.IngestedAtUtc"/> and writes the rows idempotently to
/// the central <c>AuditLog</c> table.
/// </summary>
/// <remarks>
/// Lives in <c>ZB.MOM.WW.ScadaBridge.Commons</c> rather than <c>ZB.MOM.WW.ScadaBridge.AuditLog</c>
/// because the gRPC server in <c>ZB.MOM.WW.ScadaBridge.Communication</c> needs to construct
/// it, and <c>ZB.MOM.WW.ScadaBridge.AuditLog</c> already references
/// <c>ZB.MOM.WW.ScadaBridge.Communication</c> (the proto DTOs live there). Putting the
/// message in Commons avoids a project-reference cycle.
/// </remarks>
public sealed record IngestAuditEventsCommand(IReadOnlyList<AuditEvent> Events);
@@ -0,0 +1,11 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Audit;
/// <summary>
/// Reply from the central <c>AuditLogIngestActor</c> for an
/// <see cref="IngestAuditEventsCommand"/>. <see cref="AcceptedEventIds"/> lists
/// every row the actor considers durably persisted at central — including ids
/// that were already present before the call (first-write-wins idempotency).
/// The gRPC handler echoes these ids back over the wire as the <c>IngestAck</c>
/// the site uses to flip rows to <c>Forwarded</c>.
/// </summary>
public sealed record IngestAuditEventsReply(IReadOnlyList<Guid> AcceptedEventIds);
@@ -0,0 +1,30 @@
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit;
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Audit;
/// <summary>
/// Akka message sent to the central <c>AuditLogIngestActor</c> (Audit Log #23 M3
/// Bundle D dual-write transaction) carrying a batch of combined audit +
/// site-call telemetry packets decoded by the <c>SiteStreamGrpcServer</c> from a
/// site's <c>IngestCachedTelemetry</c> gRPC RPC. For each entry the actor writes
/// the <see cref="AuditEvent"/> row AND the <see cref="SiteCall"/> upsert inside
/// a single MS SQL transaction — both succeed or both roll back, so the audit
/// and operational mirrors never drift mid-row.
/// </summary>
/// <remarks>
/// Lives in <c>ZB.MOM.WW.ScadaBridge.Commons</c> for the same reason as
/// <c>IngestAuditEventsCommand</c>: the gRPC server in
/// <c>ZB.MOM.WW.ScadaBridge.Communication</c> constructs it and <c>ZB.MOM.WW.ScadaBridge.AuditLog</c>
/// already references Communication. Putting the message in Commons avoids a
/// project-reference cycle.
/// </remarks>
public sealed record IngestCachedTelemetryCommand(IReadOnlyList<CachedTelemetryEntry> Entries);
/// <summary>
/// One lifecycle event of a cached call: the <see cref="AuditEvent"/> to insert
/// (idempotent on <see cref="AuditEvent.EventId"/>) plus the
/// <see cref="SiteCall"/> to upsert (monotonic on
/// <see cref="SiteCall.TrackedOperationId"/>). The two rows are paired so the
/// central dual-write transaction can commit them atomically.
/// </summary>
public sealed record CachedTelemetryEntry(AuditEvent Audit, SiteCall SiteCall);
@@ -0,0 +1,10 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Audit;
/// <summary>
/// Reply from the central <c>AuditLogIngestActor</c> for an
/// <see cref="IngestCachedTelemetryCommand"/>. <see cref="AcceptedEventIds"/>
/// lists every entry whose dual-write transaction (AuditLog INSERT + SiteCalls
/// UPSERT) committed; entries whose transaction rolled back are absent so the
/// site can leave the row Pending and retry on the next drain.
/// </summary>
public sealed record IngestCachedTelemetryReply(IReadOnlyList<Guid> AcceptedEventIds);
@@ -0,0 +1,166 @@
using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Audit;
/// <summary>
/// Site Calls UI -> Central: paginated, filtered query over the central
/// <c>SiteCalls</c> table (Site Call Audit #22). All filter fields are optional;
/// <see cref="StuckOnly"/> restricts results to stuck cached calls. Mirrors
/// <see cref="ZB.MOM.WW.ScadaBridge.Commons.Messages.Notification.NotificationOutboxQueryRequest"/>
/// but uses keyset paging (<see cref="AfterCreatedAtUtc"/> + <see cref="AfterId"/>)
/// to match the repository's <c>(CreatedAtUtc DESC, TrackedOperationId DESC)</c>
/// cursor, rather than page numbers.
/// </summary>
/// <remarks>
/// <see cref="ChannelFilter"/> matches the <c>SiteCall.Channel</c> column —
/// <c>"ApiOutbound"</c> or <c>"DbOutbound"</c> (the spec's <c>Kind</c> notion;
/// the entity exposes it as <c>Channel</c>). <see cref="TargetKeyword"/> is an
/// exact-match target filter, consistent with the repository's
/// <see cref="SiteCallQueryFilter.Target"/> predicate.
/// </remarks>
/// <param name="PageSize">
/// Requested page size. The actor clamps this to the <c>[1, 200]</c> range, so
/// the effective ceiling is 200 rows per page regardless of the value sent.
/// </param>
public sealed record SiteCallQueryRequest(
string CorrelationId,
string? StatusFilter,
string? SourceSiteFilter,
string? ChannelFilter,
string? TargetKeyword,
bool StuckOnly,
DateTime? FromUtc,
DateTime? ToUtc,
DateTime? AfterCreatedAtUtc,
Guid? AfterId,
int PageSize,
string? SourceNodeFilter = null);
/// <summary>
/// A single <c>SiteCalls</c> row summarised for the Site Calls UI grid. Carries
/// only the columns the <see cref="ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit.SiteCall"/>
/// entity genuinely exposes — there are no source-instance/script provenance
/// columns on that entity, so unlike
/// <see cref="ZB.MOM.WW.ScadaBridge.Commons.Messages.Notification.NotificationSummary"/>
/// none are surfaced here.
/// </summary>
/// <remarks>
/// <see cref="HttpStatus"/> is not called out in the Site Call Audit plan, but
/// it is a real (nullable) <see cref="ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit.SiteCall"/>
/// column — the last HTTP status code observed for the call — so it is surfaced
/// here for the grid; <c>null</c> for non-HTTP channels or before a first attempt.
/// </remarks>
public sealed record SiteCallSummary(
Guid TrackedOperationId,
string SourceSite,
string Channel,
string Target,
string Status,
int RetryCount,
string? LastError,
int? HttpStatus,
DateTime CreatedAtUtc,
DateTime UpdatedAtUtc,
DateTime? TerminalAtUtc,
bool IsStuck,
string? SourceNode = null);
/// <summary>
/// Central -> Site Calls UI: paginated response for a <see cref="SiteCallQueryRequest"/>.
/// The keyset cursor of the last row is echoed back as
/// <see cref="NextAfterCreatedAtUtc"/> + <see cref="NextAfterId"/> for the caller
/// to request the following page; both are <c>null</c> when the page was empty.
/// On a repository fault <see cref="Success"/> is <c>false</c>,
/// <see cref="ErrorMessage"/> carries the cause and <see cref="SiteCalls"/> is empty.
/// </summary>
public sealed record SiteCallQueryResponse(
string CorrelationId,
bool Success,
string? ErrorMessage,
IReadOnlyList<SiteCallSummary> SiteCalls,
DateTime? NextAfterCreatedAtUtc,
Guid? NextAfterId);
/// <summary>
/// Site Calls UI -> Central: request for the full detail of a single cached call,
/// for the report detail modal.
/// </summary>
public sealed record SiteCallDetailRequest(
string CorrelationId,
Guid TrackedOperationId);
/// <summary>
/// Central -> Site Calls UI: full detail for one cached call. On a repository
/// fault or missing row, <see cref="Success"/> is <c>false</c> /
/// <see cref="Detail"/> is <c>null</c> and <see cref="ErrorMessage"/> carries
/// the cause.
/// </summary>
public sealed record SiteCallDetailResponse(
string CorrelationId,
bool Success,
string? ErrorMessage,
SiteCallDetail? Detail);
/// <summary>
/// Full <c>SiteCalls</c> row detail for the report detail modal — every field
/// on the <see cref="ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit.SiteCall"/> entity,
/// including <see cref="LastError"/> and the <see cref="IngestedAtUtc"/>
/// timestamp the grid summary omits.
/// </summary>
public sealed record SiteCallDetail(
Guid TrackedOperationId,
string SourceSite,
string Channel,
string Target,
string Status,
int RetryCount,
string? LastError,
int? HttpStatus,
DateTime CreatedAtUtc,
DateTime UpdatedAtUtc,
DateTime? TerminalAtUtc,
DateTime IngestedAtUtc,
string? SourceNode = null);
/// <summary>
/// Site Calls UI -> Central: request for the global <c>SiteCalls</c> KPI summary.
/// Mirrors <see cref="ZB.MOM.WW.ScadaBridge.Commons.Messages.Notification.NotificationKpiRequest"/>.
/// </summary>
public sealed record SiteCallKpiRequest(
string CorrelationId);
/// <summary>
/// Central -> Site Calls UI: KPI summary for the Site Calls dashboard. On a
/// repository fault <see cref="Success"/> is <c>false</c>,
/// <see cref="ErrorMessage"/> carries the cause, and the KPI fields are
/// zeroed/<c>null</c>.
/// </summary>
public sealed record SiteCallKpiResponse(
string CorrelationId,
bool Success,
string? ErrorMessage,
int BufferedCount,
int ParkedCount,
int FailedLastInterval,
int DeliveredLastInterval,
TimeSpan? OldestPendingAge,
int StuckCount);
/// <summary>
/// Site Calls UI -> Central: request for the per-source-site <c>SiteCalls</c>
/// KPI breakdown. Mirrors
/// <see cref="ZB.MOM.WW.ScadaBridge.Commons.Messages.Notification.PerSiteNotificationKpiRequest"/>.
/// </summary>
public sealed record PerSiteSiteCallKpiRequest(
string CorrelationId);
/// <summary>
/// Central -> Site Calls UI: per-site KPI breakdown for the Site Calls KPIs
/// page. On a repository fault <see cref="Success"/> is <c>false</c>,
/// <see cref="ErrorMessage"/> carries the cause, and <see cref="Sites"/> is empty.
/// </summary>
public sealed record PerSiteSiteCallKpiResponse(
string CorrelationId,
bool Success,
string? ErrorMessage,
IReadOnlyList<SiteCallSiteKpiSnapshot> Sites);
@@ -0,0 +1,113 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Audit;
/// <summary>
/// Outcome of a Site Call Audit (#22) Retry/Discard relay — distinguishes the
/// three cases the Central UI Site Calls page must surface differently.
/// </summary>
/// <remarks>
/// The "site unreachable" case is deliberately separate from
/// <see cref="OperationFailed"/>: central is an eventually-consistent mirror,
/// not the source of truth, so a relay that never reaches the owning site is a
/// transient transport condition the operator can retry — not a failed
/// operation. The UI shows "site unreachable" rather than a generic error.
/// </remarks>
public enum SiteCallRelayOutcome
{
/// <summary>
/// The owning site received the relay command and applied the action to its
/// Store-and-Forward buffer (the parked cached call was reset to retry, or
/// discarded). The corrected state reaches central later via telemetry.
/// </summary>
Applied,
/// <summary>
/// The owning site received the relay command but found nothing to do — no
/// parked row matched the tracked id (already delivered/discarded, or no
/// longer <c>Parked</c>). A definitive answer from the site, not a failure.
/// </summary>
NotParked,
/// <summary>
/// The owning site could not be reached (offline / no ClusterClient route /
/// relay timed out). The action was NOT applied; the operator may retry once
/// the site is back online.
/// </summary>
SiteUnreachable,
/// <summary>
/// The owning site was reached but reported it could not apply the action
/// (its parked-message handler was unavailable or its store faulted).
/// </summary>
OperationFailed,
}
/// <summary>
/// Central UI → Site Call Audit: relay a Retry of a parked cached call to its
/// owning site. The owning site performs the actual retry on its
/// Store-and-Forward buffer — central never mutates the central <c>SiteCalls</c>
/// mirror row. Mirrors
/// <see cref="ZB.MOM.WW.ScadaBridge.Commons.Messages.Notification.RetryNotificationRequest"/>
/// but carries <see cref="SourceSite"/> (the relay target) and answers with a
/// distinct site-unreachable outcome.
/// </summary>
/// <param name="CorrelationId">Request correlation id, echoed on the response.</param>
/// <param name="TrackedOperationId">
/// The cached operation to retry — the PK of the central <c>SiteCalls</c> row
/// and the S&amp;F buffer message id at the owning site.
/// </param>
/// <param name="SourceSite">
/// The owning site (<c>SiteCall.SourceSite</c>) the relay is routed to.
/// </param>
public sealed record RetrySiteCallRequest(
string CorrelationId,
Guid TrackedOperationId,
string SourceSite);
/// <summary>
/// Site Call Audit → Central UI: result of a <see cref="RetrySiteCallRequest"/>.
/// </summary>
/// <param name="CorrelationId">Echoed request correlation id.</param>
/// <param name="Outcome">
/// The relay outcome — <see cref="SiteCallRelayOutcome.Applied"/>,
/// <see cref="SiteCallRelayOutcome.NotParked"/>,
/// <see cref="SiteCallRelayOutcome.SiteUnreachable"/> or
/// <see cref="SiteCallRelayOutcome.OperationFailed"/>.
/// </param>
/// <param name="Success">
/// Convenience flag — <c>true</c> only for <see cref="SiteCallRelayOutcome.Applied"/>.
/// </param>
/// <param name="SiteReachable">
/// <c>false</c> only for <see cref="SiteCallRelayOutcome.SiteUnreachable"/>; lets
/// the UI distinguish "site offline" from "operation failed" without switching
/// on the enum.
/// </param>
/// <param name="ErrorMessage">
/// Human-readable detail for a non-applied outcome; <c>null</c> on success.
/// </param>
public sealed record RetrySiteCallResponse(
string CorrelationId,
SiteCallRelayOutcome Outcome,
bool Success,
bool SiteReachable,
string? ErrorMessage);
/// <summary>
/// Central UI → Site Call Audit: relay a Discard of a parked cached call to its
/// owning site. See <see cref="RetrySiteCallRequest"/> for the source-of-truth
/// and routing rationale.
/// </summary>
public sealed record DiscardSiteCallRequest(
string CorrelationId,
Guid TrackedOperationId,
string SourceSite);
/// <summary>
/// Site Call Audit → Central UI: result of a <see cref="DiscardSiteCallRequest"/>.
/// Same shape as <see cref="RetrySiteCallResponse"/>.
/// </summary>
public sealed record DiscardSiteCallResponse(
string CorrelationId,
SiteCallRelayOutcome Outcome,
bool Success,
bool SiteReachable,
string? ErrorMessage);
@@ -0,0 +1,19 @@
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit;
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Audit;
/// <summary>
/// Akka message sent to the central <c>SiteCallAuditActor</c> (Site Call Audit
/// #22, Audit Log #23 M3 Bundle C) carrying one <see cref="SiteCall"/> row to
/// be persisted via <c>ISiteCallAuditRepository.UpsertAsync</c>. The repository
/// performs an insert-if-not-exists then monotonic update — duplicate gRPC
/// packets and reconciliation pulls can both feed the actor without rolling
/// state back.
/// </summary>
/// <remarks>
/// Lives in <c>ZB.MOM.WW.ScadaBridge.Commons</c> rather than <c>ZB.MOM.WW.ScadaBridge.SiteCallAudit</c>
/// so the gRPC server in <c>ZB.MOM.WW.ScadaBridge.Communication</c> can construct it
/// without taking a project reference on the actor's host project (Bundle D
/// adds the IngestCachedTelemetry RPC that will Tell this command).
/// </remarks>
public sealed record UpsertSiteCallCommand(SiteCall SiteCall);
@@ -0,0 +1,14 @@
using ZB.MOM.WW.ScadaBridge.Commons.Types;
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Audit;
/// <summary>
/// Reply from the central <c>SiteCallAuditActor</c> for an
/// <see cref="UpsertSiteCallCommand"/>. <see cref="Accepted"/> is <c>true</c>
/// when the upsert reached the repository without throwing (including the
/// monotonic-no-op case where the stored status' rank wins) and <c>false</c>
/// when persistence raised an exception. The actor itself stays alive in
/// either case — audit-write failures must NEVER abort the user-facing action
/// (Audit Log #23 §13).
/// </summary>
public sealed record UpsertSiteCallReply(TrackedOperationId TrackedOperationId, bool Accepted);
@@ -0,0 +1,6 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Communication;
public record RoutingMetadata(
string TargetSiteId,
string? TargetInstanceUniqueName,
string CorrelationId);
@@ -0,0 +1,6 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Communication;
public record SiteIdentity(
string SiteId,
string NodeHostname,
bool IsActive);
@@ -0,0 +1,12 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.DataConnection;
/// <summary>
/// Command to create a new data connection actor for a specific protocol.
/// Sent from DeploymentManagerActor to DCL Manager Actor.
/// </summary>
public record CreateConnectionCommand(
string ConnectionName,
string ProtocolType,
IDictionary<string, string> PrimaryConnectionDetails,
IDictionary<string, string>? BackupConnectionDetails = null,
int FailoverRetryCount = 3);
@@ -0,0 +1,14 @@
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.DataConnection;
/// <summary>
/// Health metrics for a single data connection, contributed to the site health report.
/// </summary>
public record DataConnectionHealthReport(
string ConnectionName,
ConnectionHealth Status,
int TotalSubscribedTags,
int ResolvedTags,
string ActiveEndpoint,
DateTimeOffset Timestamp);
@@ -0,0 +1,11 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.DataConnection;
/// <summary>
/// Request from an Instance Actor to subscribe to tag values through the DCL.
/// </summary>
public record SubscribeTagsRequest(
string CorrelationId,
string InstanceUniqueName,
string ConnectionName,
IReadOnlyList<string> TagPaths,
DateTimeOffset Timestamp);
@@ -0,0 +1,11 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.DataConnection;
/// <summary>
/// Response confirming tag subscription registration.
/// </summary>
public record SubscribeTagsResponse(
string CorrelationId,
string InstanceUniqueName,
bool Success,
string? ErrorMessage,
DateTimeOffset Timestamp);
@@ -0,0 +1,21 @@
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Protocol;
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.DataConnection;
/// <summary>
/// Published by DCL to an Instance Actor when a subscribed tag value changes.
/// </summary>
public record TagValueUpdate(
string ConnectionName,
string TagPath,
object? Value,
QualityCode Quality,
DateTimeOffset Timestamp);
/// <summary>
/// Published by DCL when connection state changes, causing bulk quality updates.
/// </summary>
public record ConnectionQualityChanged(
string ConnectionName,
QualityCode Quality,
DateTimeOffset Timestamp);
@@ -0,0 +1,10 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.DataConnection;
/// <summary>
/// Request from an Instance Actor to unsubscribe from all its tags when stopping.
/// </summary>
public record UnsubscribeTagsRequest(
string CorrelationId,
string InstanceUniqueName,
string ConnectionName,
DateTimeOffset Timestamp);
@@ -0,0 +1,21 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.DataConnection;
/// <summary>
/// Request to write a value to a device tag through the DCL.
/// Write failures are returned synchronously to the calling script.
/// </summary>
public record WriteTagRequest(
string CorrelationId,
string ConnectionName,
string TagPath,
object? Value,
DateTimeOffset Timestamp);
/// <summary>
/// Response for a device tag write operation.
/// </summary>
public record WriteTagResponse(
string CorrelationId,
bool Success,
string? ErrorMessage,
DateTimeOffset Timestamp);
@@ -0,0 +1,5 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.DebugView;
public record DebugSnapshotRequest(
string InstanceUniqueName,
string CorrelationId);
@@ -0,0 +1,9 @@
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Streaming;
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.DebugView;
public record DebugViewSnapshot(
string InstanceUniqueName,
IReadOnlyList<AttributeValueChanged> AttributeValues,
IReadOnlyList<AlarmStateChanged> AlarmStates,
DateTimeOffset SnapshotTimestamp);
@@ -0,0 +1,5 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.DebugView;
public record SubscribeDebugViewRequest(
string InstanceUniqueName,
string CorrelationId);
@@ -0,0 +1,5 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.DebugView;
public record UnsubscribeDebugViewRequest(
string InstanceUniqueName,
string CorrelationId);
@@ -0,0 +1,9 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Deployment;
public record DeployInstanceCommand(
string DeploymentId,
string InstanceUniqueName,
string RevisionHash,
string FlattenedConfigurationJson,
string DeployedBy,
DateTimeOffset Timestamp);
@@ -0,0 +1,13 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Deployment;
/// <summary>
/// Central→site query for the currently-applied deployment state of a single
/// instance. Issued by the Deployment Manager before a re-deploy when a prior
/// deployment record is stuck <c>InProgress</c> or <c>Failed</c> due to a
/// timeout, so the site's actual state can be reconciled against the target
/// revision before re-sending a deployment ("Deployment Identity &amp; Idempotency").
/// </summary>
public record DeploymentStateQueryRequest(
string CorrelationId,
string InstanceUniqueName,
DateTimeOffset Timestamp);
@@ -0,0 +1,15 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Deployment;
/// <summary>
/// Site→central response carrying the instance's currently-applied deployment
/// state. If <see cref="IsDeployed"/> is <c>false</c> the instance has no
/// deployed configuration at the site and <see cref="AppliedDeploymentId"/> /
/// <see cref="AppliedRevisionHash"/> are <c>null</c>.
/// </summary>
public record DeploymentStateQueryResponse(
string CorrelationId,
string InstanceUniqueName,
bool IsDeployed,
string? AppliedDeploymentId,
string? AppliedRevisionHash,
DateTimeOffset Timestamp);
@@ -0,0 +1,10 @@
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Deployment;
public record DeploymentStatusResponse(
string DeploymentId,
string InstanceUniqueName,
DeploymentStatus Status,
string? ErrorMessage,
DateTimeOffset Timestamp);
@@ -0,0 +1,6 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Deployment;
public record DeploymentValidationResult(
bool IsValid,
IReadOnlyList<string> Errors,
IReadOnlyList<string> Warnings);
@@ -0,0 +1,7 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Deployment;
public record FlattenedConfigurationSnapshot(
string InstanceUniqueName,
string RevisionHash,
string ConfigurationJson,
DateTimeOffset GeneratedAt);
@@ -0,0 +1,7 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Health;
public record HeartbeatMessage(
string SiteId,
string NodeHostname,
bool IsActive,
DateTimeOffset Timestamp);
@@ -0,0 +1,3 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Health;
public record NodeStatus(string Hostname, bool IsOnline, string Role);
@@ -0,0 +1,53 @@
using ZB.MOM.WW.ScadaBridge.Commons.Types;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Health;
public record SiteHealthReport(
string SiteId,
long SequenceNumber,
DateTimeOffset ReportTimestamp,
IReadOnlyDictionary<string, ConnectionHealth> DataConnectionStatuses,
IReadOnlyDictionary<string, TagResolutionStatus> TagResolutionCounts,
int ScriptErrorCount,
int AlarmEvaluationErrorCount,
IReadOnlyDictionary<string, int> StoreAndForwardBufferDepths,
int DeadLetterCount,
int DeployedInstanceCount,
int EnabledInstanceCount,
int DisabledInstanceCount,
string NodeRole = "Unknown",
string NodeHostname = "",
IReadOnlyDictionary<string, string>? DataConnectionEndpoints = null,
IReadOnlyDictionary<string, TagQualityCounts>? DataConnectionTagQuality = null,
int ParkedMessageCount = 0,
IReadOnlyList<NodeStatus>? ClusterNodes = null,
// Audit Log (#23) M2 Bundle G: per-interval count of FallbackAuditWriter
// primary failures (SQLite throws routed to the drop-oldest ring). Surfaces
// a sustained audit-write outage on /monitoring/health. Defaults to 0 so
// existing producers / tests that don't construct the field stay valid.
int SiteAuditWriteFailures = 0,
// Audit Log (#23) M5 Bundle C: per-interval count of payload-filter
// redactor over-redactions (header / body / SQL parameter stages all
// throwing → field replaced with the "<redacted: redactor error>"
// marker). Surfaces a misconfigured / catastrophic regex on
// /monitoring/health. Defaults to 0 for back-compat with existing
// producers and tests that don't construct the field.
int AuditRedactionFailure = 0,
// Audit Log (#23) M6 Bundle E (T6): point-in-time snapshot of the
// site-local SQLite audit-log queue (pending count, oldest pending row,
// on-disk bytes). Populated by the site-side SiteAuditBacklogReporter
// hosted service every 30 s. Defaults to null so existing producers /
// tests that don't refresh the snapshot stay valid; the central health
// surface treats null as "no data yet" rather than a zeroed queue.
SiteAuditBacklogSnapshot? SiteAuditBacklog = null);
/// <summary>
/// Broadcast wrapper used between central nodes to keep per-node
/// CentralHealthAggregator state in sync. ClusterClient load-balances each
/// incoming SiteHealthReport to one central node; that node re-publishes
/// this wrapper on a DistributedPubSub topic so the peer node's aggregator
/// also processes the report (idempotently — sequence numbers guard against
/// double-counting).
/// </summary>
public record SiteHealthReportReplica(SiteHealthReport Report);
@@ -0,0 +1,3 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Health;
public record TagQualityCounts(int Good, int Bad, int Uncertain);
@@ -0,0 +1,3 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Health;
public record TagResolutionStatus(int TotalSubscribed, int SuccessfullyResolved);
@@ -0,0 +1,85 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.InboundApi;
/// <summary>
/// Request routed from Inbound API to a site to invoke a script on an instance.
/// Used by Route.To("instanceCode").Call("scriptName", params).
/// </summary>
/// <param name="ParentExecutionId">
/// Audit Log #23 (ParentExecutionId): the spawning execution's <c>ExecutionId</c>
/// — for an inbound-API-routed call this is the inbound request's per-request
/// execution id. The site records it as the routed script execution's
/// <c>ParentExecutionId</c> so a spawned execution points back at its spawner.
/// Additive trailing member — null for requests built before the field existed
/// or for routed calls with no spawning execution (e.g. the Central UI sandbox).
/// </param>
public record RouteToCallRequest(
string CorrelationId,
string InstanceUniqueName,
string ScriptName,
IReadOnlyDictionary<string, object?>? Parameters,
DateTimeOffset Timestamp,
Guid? ParentExecutionId = null);
/// <summary>
/// Response from a Route.To() call.
/// </summary>
public record RouteToCallResponse(
string CorrelationId,
bool Success,
object? ReturnValue,
string? ErrorMessage,
DateTimeOffset Timestamp);
/// <summary>
/// Request to read attribute(s) from a remote instance.
/// </summary>
/// <param name="ParentExecutionId">
/// Audit Log #23 (ParentExecutionId): mirrors <see cref="RouteToCallRequest.ParentExecutionId"/>.
/// For an inbound-API-routed read this is the inbound request's per-request execution id;
/// future site-side audit emission for routed reads can stamp it as <c>ParentExecutionId</c>
/// so the inbound→site execution-tree link survives the read path. Additive trailing
/// member — null for the Central UI sandbox path or for callers built before the field existed.
/// </param>
public record RouteToGetAttributesRequest(
string CorrelationId,
string InstanceUniqueName,
IReadOnlyList<string> AttributeNames,
DateTimeOffset Timestamp,
Guid? ParentExecutionId = null);
/// <summary>
/// Response containing attribute values from a remote instance.
/// </summary>
public record RouteToGetAttributesResponse(
string CorrelationId,
IReadOnlyDictionary<string, object?> Values,
bool Success,
string? ErrorMessage,
DateTimeOffset Timestamp);
/// <summary>
/// Request to write attribute(s) on a remote instance.
/// </summary>
/// <param name="ParentExecutionId">
/// Audit Log #23 (ParentExecutionId): mirrors <see cref="RouteToCallRequest.ParentExecutionId"/>.
/// For an inbound-API-routed write this is the inbound request's per-request execution id;
/// site-side audit emission for the underlying device / static-attribute write can stamp
/// it as <c>ParentExecutionId</c> so the inbound→site execution-tree link survives the
/// write path. Additive trailing member — null for the Central UI sandbox path or for
/// callers built before the field existed.
/// </param>
public record RouteToSetAttributesRequest(
string CorrelationId,
string InstanceUniqueName,
IReadOnlyDictionary<string, string> AttributeValues,
DateTimeOffset Timestamp,
Guid? ParentExecutionId = null);
/// <summary>
/// Response confirming attribute writes on a remote instance.
/// </summary>
public record RouteToSetAttributesResponse(
string CorrelationId,
bool Success,
string? ErrorMessage,
DateTimeOffset Timestamp);
@@ -0,0 +1,25 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Instance;
/// <summary>
/// Request to get the current value of an attribute from an Instance Actor.
/// Uses the Ask pattern for system boundaries; Tell pattern is preferred for hot-path.
/// </summary>
public record GetAttributeRequest(
string CorrelationId,
string InstanceUniqueName,
string AttributeName,
DateTimeOffset Timestamp);
/// <summary>
/// Response containing the current value and quality of an attribute.
/// Quality is "Good", "Bad", or "Uncertain".
/// Data-sourced attributes start at "Uncertain" until the first DCL value update arrives.
/// </summary>
public record GetAttributeResponse(
string CorrelationId,
string InstanceUniqueName,
string AttributeName,
object? Value,
bool Found,
string Quality,
DateTimeOffset Timestamp);
@@ -0,0 +1,22 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Instance;
/// <summary>
/// Batch request to get multiple attribute values from an Instance Actor.
/// Used by Route.To().GetAttributes() in Inbound API.
/// </summary>
public record GetAttributesBatchRequest(
string CorrelationId,
string InstanceUniqueName,
IReadOnlyList<string> AttributeNames,
DateTimeOffset Timestamp);
/// <summary>
/// Batch response containing multiple attribute values.
/// </summary>
public record GetAttributesBatchResponse(
string CorrelationId,
string InstanceUniqueName,
IReadOnlyDictionary<string, object?> Values,
bool Success,
string? ErrorMessage,
DateTimeOffset Timestamp);
@@ -0,0 +1,21 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Instance;
/// <summary>
/// Batch command to set multiple attribute values on an Instance Actor.
/// Used by Route.To().SetAttributes() in Inbound API.
/// </summary>
public record SetAttributesBatchCommand(
string CorrelationId,
string InstanceUniqueName,
IReadOnlyDictionary<string, string> AttributeValues,
DateTimeOffset Timestamp);
/// <summary>
/// Batch response confirming multiple attribute writes.
/// </summary>
public record SetAttributesBatchResponse(
string CorrelationId,
string InstanceUniqueName,
bool Success,
string? ErrorMessage,
DateTimeOffset Timestamp);
@@ -0,0 +1,23 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Instance;
/// <summary>
/// Command to set a static attribute value on an Instance Actor.
/// Updates in-memory state and persists the override to SQLite.
/// </summary>
public record SetStaticAttributeCommand(
string CorrelationId,
string InstanceUniqueName,
string AttributeName,
string Value,
DateTimeOffset Timestamp);
/// <summary>
/// Response confirming that a static attribute was set.
/// </summary>
public record SetStaticAttributeResponse(
string CorrelationId,
string InstanceUniqueName,
string AttributeName,
bool Success,
string? ErrorMessage,
DateTimeOffset Timestamp);
@@ -0,0 +1,13 @@
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit;
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Integration;
/// <summary>
/// Audit Log (#23) telemetry envelope sent from a site to central over gRPC.
/// At-least-once delivery; central is idempotent on <see cref="AuditEvent.EventId"/>.
/// See Component-AuditLog.md "Ingestion" for the handoff contract.
/// </summary>
public sealed record AuditTelemetryEnvelope(
Guid EnvelopeId,
string SourceSiteId,
IReadOnlyList<AuditEvent> Events);
@@ -0,0 +1,34 @@
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit;
using ZB.MOM.WW.ScadaBridge.Commons.Types;
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Integration;
/// <summary>
/// Combined audit + operational telemetry packet for cached outbound calls
/// (Audit Log #23 / M3). The site emits one packet per lifecycle event
/// — <c>Submit</c> (Audit kind <c>CachedSubmit</c>), per-attempt
/// <c>ApiCallCached</c>/<c>DbWriteCached</c>, terminal <c>CachedResolve</c> —
/// and central writes the <see cref="AuditEvent"/> row plus the
/// <see cref="SiteCallOperational"/> upsert in one MS SQL transaction. Two
/// payload shapes ride on a single wire message so the same on-the-wire batch
/// can carry mixed lifecycle events without the central dual-write needing a
/// second RPC for the operational state.
/// </summary>
/// <remarks>
/// <para>
/// Both inner records carry the same <c>TrackedOperationId</c> — the
/// idempotency key end-to-end. The <see cref="AuditEvent.CorrelationId"/>
/// pattern (used by Audit Log #23 to thread cached-call rows together) is
/// honoured by the site emitter; the packet itself is shape-only and makes no
/// independent correlation guarantees.
/// </para>
/// <para>
/// Additive-only per Commons REQ-COM-5a (M2 reviewer note) — this is a new
/// message, not a rename of any existing M2 envelope.
/// </para>
/// </remarks>
/// <param name="Audit">The Audit Log #23 row to insert at central.</param>
/// <param name="Operational">The <c>SiteCalls</c> upsert mirroring this lifecycle event.</param>
public sealed record CachedCallTelemetry(
AuditEvent Audit,
SiteCallOperational Operational);
@@ -0,0 +1,14 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Integration;
/// <summary>
/// Request routed from central to site to invoke an integration method
/// (external system call or notification) on behalf of the central UI or API.
/// </summary>
public record IntegrationCallRequest(
string CorrelationId,
string SiteId,
string InstanceUniqueName,
string TargetSystemName,
string MethodName,
IReadOnlyDictionary<string, object?> Parameters,
DateTimeOffset Timestamp);
@@ -0,0 +1,12 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Integration;
/// <summary>
/// Response for an integration call routed through central-site communication.
/// </summary>
public record IntegrationCallResponse(
string CorrelationId,
string SiteId,
bool Success,
string? ResultJson,
string? ErrorMessage,
DateTimeOffset Timestamp);
@@ -0,0 +1,11 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Integration;
/// <summary>
/// Audit Log (#23) periodic reconciliation pull request: central asks a site for
/// audit events since the given UTC watermark, up to <paramref name="BatchSize"/>.
/// Acts as the fallback when streaming telemetry is lost. See Component-AuditLog.md "Ingestion".
/// </summary>
public sealed record PullAuditEventsRequest(
string SourceSiteId,
DateTime SinceUtc,
int BatchSize);
@@ -0,0 +1,12 @@
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit;
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Integration;
/// <summary>
/// Audit Log (#23) periodic reconciliation pull response: the next batch of site
/// audit events plus a <paramref name="MoreAvailable"/> flag signalling the caller
/// to advance the watermark and pull again. See Component-AuditLog.md "Ingestion".
/// </summary>
public sealed record PullAuditEventsResponse(
IReadOnlyList<AuditEvent> Events,
bool MoreAvailable);
@@ -0,0 +1,6 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Lifecycle;
public record DeleteInstanceCommand(
string CommandId,
string InstanceUniqueName,
DateTimeOffset Timestamp);
@@ -0,0 +1,6 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Lifecycle;
public record DisableInstanceCommand(
string CommandId,
string InstanceUniqueName,
DateTimeOffset Timestamp);
@@ -0,0 +1,6 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Lifecycle;
public record EnableInstanceCommand(
string CommandId,
string InstanceUniqueName,
DateTimeOffset Timestamp);
@@ -0,0 +1,8 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Lifecycle;
public record InstanceLifecycleResponse(
string CommandId,
string InstanceUniqueName,
bool Success,
string? ErrorMessage,
DateTimeOffset Timestamp);
@@ -0,0 +1,6 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
public record QueryAuditLogCommand(
string? User = null, string? EntityType = null, string? Action = null,
DateTimeOffset? From = null, DateTimeOffset? To = null,
int Page = 1, int PageSize = 50);
@@ -0,0 +1,7 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
public record ListDataConnectionsCommand(int? SiteId = null);
public record GetDataConnectionCommand(int DataConnectionId);
public record CreateDataConnectionCommand(int SiteId, string Name, string Protocol, string? PrimaryConfiguration, string? BackupConfiguration = null, int FailoverRetryCount = 3);
public record UpdateDataConnectionCommand(int DataConnectionId, string Name, string Protocol, string? PrimaryConfiguration, string? BackupConfiguration = null, int FailoverRetryCount = 3);
public record DeleteDataConnectionCommand(int DataConnectionId);
@@ -0,0 +1,7 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
public record ListDatabaseConnectionsCommand;
public record GetDatabaseConnectionCommand(int DatabaseConnectionId);
public record CreateDatabaseConnectionDefCommand(string Name, string ConnectionString);
public record UpdateDatabaseConnectionDefCommand(int DatabaseConnectionId, string Name, string ConnectionString);
public record DeleteDatabaseConnectionDefCommand(int DatabaseConnectionId);
@@ -0,0 +1,3 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
public record DebugSnapshotCommand(int InstanceId);
@@ -0,0 +1,5 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
public record MgmtDeployArtifactsCommand(int? SiteId = null);
public record QueryDeploymentsCommand(int? InstanceId = null, string? Status = null, int Page = 1, int PageSize = 50);
public record GetDeploymentDiffCommand(int InstanceId);
@@ -0,0 +1,14 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
public record ListExternalSystemsCommand;
public record GetExternalSystemCommand(int ExternalSystemId);
public record CreateExternalSystemCommand(string Name, string EndpointUrl, string AuthType, string? AuthConfiguration);
public record UpdateExternalSystemCommand(int ExternalSystemId, string Name, string EndpointUrl, string AuthType, string? AuthConfiguration);
public record DeleteExternalSystemCommand(int ExternalSystemId);
// External System Methods
public record ListExternalSystemMethodsCommand(int ExternalSystemId);
public record GetExternalSystemMethodCommand(int MethodId);
public record CreateExternalSystemMethodCommand(int ExternalSystemId, string Name, string HttpMethod, string Path, string? ParameterDefinitions = null, string? ReturnDefinition = null);
public record UpdateExternalSystemMethodCommand(int MethodId, string? Name = null, string? HttpMethod = null, string? Path = null, string? ParameterDefinitions = null, string? ReturnDefinition = null);
public record DeleteExternalSystemMethodCommand(int MethodId);
@@ -0,0 +1,4 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
public record GetHealthSummaryCommand;
public record GetSiteHealthCommand(string SiteIdentifier);
@@ -0,0 +1,7 @@
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);
public record DeleteApiMethodCommand(int ApiMethodId);
@@ -0,0 +1,38 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
public record ListInstancesCommand(int? SiteId = null, int? TemplateId = null, string? SearchTerm = null);
public record GetInstanceCommand(int InstanceId);
public record CreateInstanceCommand(string UniqueName, int TemplateId, int SiteId, int? AreaId = null);
public record MgmtDeployInstanceCommand(int InstanceId);
public record MgmtEnableInstanceCommand(int InstanceId);
public record MgmtDisableInstanceCommand(int InstanceId);
public record MgmtDeleteInstanceCommand(int InstanceId);
/// <summary>
/// A single attribute-to-data-connection binding carried by
/// <see cref="SetConnectionBindingsCommand"/>. This is a named record (not a
/// <c>ValueTuple</c>) so it serializes with stable, named JSON properties and can
/// evolve additively per REQ-COM-5a.
/// </summary>
public record ConnectionBinding(string AttributeName, int DataConnectionId);
public record SetConnectionBindingsCommand(int InstanceId, IReadOnlyList<ConnectionBinding> Bindings);
public record SetInstanceOverridesCommand(int InstanceId, IReadOnlyDictionary<string, string?> Overrides);
public record SetInstanceAreaCommand(int InstanceId, int? AreaId);
/// <summary>
/// Sets (or upserts) a per-instance alarm override. For HiLo trigger types the
/// <c>TriggerConfigurationOverride</c> JSON is merged setpoint-by-setpoint with
/// the inherited config; for binary trigger types it replaces the whole config.
/// Either field is optional — pass null to leave it unchanged.
/// </summary>
public record SetInstanceAlarmOverrideCommand(
int InstanceId,
string AlarmCanonicalName,
string? TriggerConfigurationOverride,
int? PriorityLevelOverride);
public record DeleteInstanceAlarmOverrideCommand(
int InstanceId,
string AlarmCanonicalName);
public record ListInstanceAlarmOverridesCommand(int InstanceId);
@@ -0,0 +1,76 @@
using System.Collections.Frozen;
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
/// <summary>
/// Bidirectional name registry for management command records. The registry contains
/// exactly the non-abstract <c>*Command</c> types declared in the
/// <c>ZB.MOM.WW.ScadaBridge.Commons.Messages.Management</c> namespace; these are the commands that
/// travel over the HTTP / ClusterClient management boundary.
/// </summary>
/// <remarks>
/// <see cref="Resolve"/> and <see cref="GetCommandName"/> are symmetric:
/// <c>Resolve(GetCommandName(t))</c> returns <c>t</c> for every type
/// <see cref="GetCommandName"/> accepts. <see cref="GetCommandName"/> rejects any type
/// the registry does not contain rather than computing an unresolvable name.
/// </remarks>
public static class ManagementCommandRegistry
{
private static readonly FrozenDictionary<string, Type> Commands = BuildRegistry();
/// <summary>
/// Names keyed by command type, for the reverse lookup. Keeps
/// <see cref="GetCommandName"/> in lock-step with the forward registry.
/// </summary>
private static readonly FrozenDictionary<Type, string> NamesByType =
Commands.ToFrozenDictionary(kv => kv.Value, kv => kv.Key);
/// <summary>
/// Resolves a management command wire name to its CLR type, or null if not registered.
/// </summary>
/// <param name="commandName">The wire name of the management command (without the "Command" suffix).</param>
public static Type? Resolve(string commandName)
{
return Commands.GetValueOrDefault(commandName);
}
/// <summary>
/// Returns the registered wire name for a management command type.
/// </summary>
/// <param name="commandType">The CLR type of the management command to look up.</param>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="commandType"/> is not a registered management
/// command — i.e. not a non-abstract <c>*Command</c> type in the
/// <c>ZB.MOM.WW.ScadaBridge.Commons.Messages.Management</c> namespace. This keeps the method
/// symmetric with <see cref="Resolve"/>: it never yields a name that
/// <see cref="Resolve"/> cannot turn back into the same type.
/// </exception>
public static string GetCommandName(Type commandType)
{
ArgumentNullException.ThrowIfNull(commandType);
if (NamesByType.TryGetValue(commandType, out var name))
return name;
throw new ArgumentException(
$"'{commandType.FullName}' is not a registered management command. " +
$"Management commands must be non-abstract '*Command' records declared in " +
$"the '{typeof(ManagementEnvelope).Namespace}' namespace.",
nameof(commandType));
}
private static FrozenDictionary<string, Type> BuildRegistry()
{
var ns = typeof(ManagementEnvelope).Namespace!;
var assembly = typeof(ManagementEnvelope).Assembly;
return assembly.GetTypes()
.Where(t => t.Namespace == ns
&& t.Name.EndsWith("Command", StringComparison.Ordinal)
&& !t.IsAbstract)
.ToFrozenDictionary(
t => t.Name[..^"Command".Length],
t => t,
StringComparer.OrdinalIgnoreCase);
}
}
@@ -0,0 +1,11 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
public record AuthenticatedUser(
string Username, string DisplayName,
string[] Roles, string[] PermittedSiteIds);
public record ManagementEnvelope(AuthenticatedUser User, object Command, string CorrelationId);
public record ManagementSuccess(string CorrelationId, string JsonData);
public record ManagementError(string CorrelationId, string Error, string ErrorCode);
public record ManagementUnauthorized(string CorrelationId, string Message);
@@ -0,0 +1,9 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
public record ListNotificationListsCommand;
public record GetNotificationListCommand(int NotificationListId);
public record CreateNotificationListCommand(string Name, IReadOnlyList<string> RecipientEmails);
public record UpdateNotificationListCommand(int NotificationListId, string Name, IReadOnlyList<string> RecipientEmails);
public record DeleteNotificationListCommand(int NotificationListId);
public record ListSmtpConfigsCommand;
public record UpdateSmtpConfigCommand(int SmtpConfigId, string Server, int Port, string AuthMode, string FromAddress, string? TlsMode = null, string? Credentials = null);
@@ -0,0 +1,6 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
public record QueryEventLogsCommand(string SiteIdentifier, string? EventType = null, string? Severity = null, string? Keyword = null, DateTimeOffset? From = null, DateTimeOffset? To = null, int Page = 1, int PageSize = 50, string? InstanceName = null);
public record QueryParkedMessagesCommand(string SiteIdentifier, int Page = 1, int PageSize = 50);
public record RetryParkedMessageCommand(string SiteIdentifier, string MessageId);
public record DiscardParkedMessageCommand(string SiteIdentifier, string MessageId);
@@ -0,0 +1,14 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
public record ListApiKeysCommand;
public record CreateApiKeyCommand(string Name);
public record DeleteApiKeyCommand(int ApiKeyId);
public record ListRoleMappingsCommand;
public record CreateRoleMappingCommand(string LdapGroupName, string Role);
public record UpdateRoleMappingCommand(int MappingId, string LdapGroupName, string Role);
public record DeleteRoleMappingCommand(int MappingId);
public record UpdateApiKeyCommand(int ApiKeyId, bool IsEnabled);
public record ListScopeRulesCommand(int MappingId);
public record AddScopeRuleCommand(int MappingId, int SiteId);
public record DeleteScopeRuleCommand(int ScopeRuleId);
public record ResolveRolesCommand(IReadOnlyList<string> LdapGroups);
@@ -0,0 +1,7 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
public record ListSharedScriptsCommand;
public record GetSharedScriptCommand(int SharedScriptId);
public record CreateSharedScriptCommand(string Name, string Code, string? ParameterDefinitions, string? ReturnDefinition);
public record UpdateSharedScriptCommand(int SharedScriptId, string Name, string Code, string? ParameterDefinitions, string? ReturnDefinition);
public record DeleteSharedScriptCommand(int SharedScriptId);
@@ -0,0 +1,11 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
public record ListSitesCommand;
public record GetSiteCommand(int SiteId);
public record CreateSiteCommand(string Name, string SiteIdentifier, string? Description, string? NodeAAddress = null, string? NodeBAddress = null, string? GrpcNodeAAddress = null, string? GrpcNodeBAddress = null);
public record UpdateSiteCommand(int SiteId, string Name, string? Description, string? NodeAAddress = null, string? NodeBAddress = null, string? GrpcNodeAAddress = null, string? GrpcNodeBAddress = null);
public record DeleteSiteCommand(int SiteId);
public record ListAreasCommand(int SiteId);
public record CreateAreaCommand(int SiteId, string Name, int? ParentAreaId);
public record DeleteAreaCommand(int AreaId);
public record UpdateAreaCommand(int AreaId, string Name);
@@ -0,0 +1,21 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
public record ListTemplatesCommand;
public record GetTemplateCommand(int TemplateId);
public record CreateTemplateCommand(string Name, string? Description, int? ParentTemplateId);
public record UpdateTemplateCommand(int TemplateId, string Name, string? Description, int? ParentTemplateId);
public record DeleteTemplateCommand(int TemplateId);
public record ValidateTemplateCommand(int TemplateId);
// Template member operations
public record AddTemplateAttributeCommand(int TemplateId, string Name, string DataType, string? Value, string? Description, string? DataSourceReference, bool IsLocked);
public record UpdateTemplateAttributeCommand(int AttributeId, string Name, string DataType, string? Value, string? Description, string? DataSourceReference, bool IsLocked);
public record DeleteTemplateAttributeCommand(int AttributeId);
public record AddTemplateAlarmCommand(int TemplateId, string Name, string TriggerType, int PriorityLevel, string? Description, string? TriggerConfiguration, bool IsLocked);
public record UpdateTemplateAlarmCommand(int AlarmId, string Name, string TriggerType, int PriorityLevel, string? Description, string? TriggerConfiguration, bool IsLocked);
public record DeleteTemplateAlarmCommand(int AlarmId);
public record AddTemplateScriptCommand(int TemplateId, string Name, string Code, string? TriggerType, string? TriggerConfiguration, bool IsLocked, string? ParameterDefinitions = null, string? ReturnDefinition = null);
public record UpdateTemplateScriptCommand(int ScriptId, string Name, string Code, string? TriggerType, string? TriggerConfiguration, bool IsLocked, string? ParameterDefinitions = null, string? ReturnDefinition = null);
public record DeleteTemplateScriptCommand(int ScriptId);
public record AddTemplateCompositionCommand(int TemplateId, string InstanceName, int ComposedTemplateId);
public record DeleteTemplateCompositionCommand(int CompositionId);
@@ -0,0 +1,8 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
public record ListTemplateFoldersCommand;
public record CreateTemplateFolderCommand(string Name, int? ParentFolderId);
public record RenameTemplateFolderCommand(int FolderId, string NewName);
public record MoveTemplateFolderCommand(int FolderId, int? NewParentFolderId);
public record DeleteTemplateFolderCommand(int FolderId);
public record MoveTemplateToFolderCommand(int TemplateId, int? NewFolderId);
@@ -0,0 +1,62 @@
using ZB.MOM.WW.ScadaBridge.Commons.Types.Transport;
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
/// <summary>
/// Exports a bundle. Names rather than IDs in the selection so test scripts can
/// be written without an ID lookup step. <c>All=true</c> overrides the per-type
/// name lists and exports every entity of every supported type.
/// </summary>
public sealed record ExportBundleCommand(
bool All,
IReadOnlyList<string>? TemplateNames,
IReadOnlyList<string>? SharedScriptNames,
IReadOnlyList<string>? ExternalSystemNames,
IReadOnlyList<string>? DatabaseConnectionNames,
IReadOnlyList<string>? NotificationListNames,
IReadOnlyList<string>? SmtpConfigurationNames,
IReadOnlyList<string>? ApiKeyNames,
IReadOnlyList<string>? ApiMethodNames,
bool IncludeDependencies,
string? Passphrase,
string SourceEnvironment);
/// <summary>
/// Bundle body returned as base64-encoded ZIP. <see cref="ByteCount"/> is the
/// pre-encoded size for sanity checks against the configured bundle cap.
/// </summary>
public sealed record ExportBundleResult(
string Base64Bundle,
int ByteCount);
/// <summary>
/// Loads a bundle and returns its preview without applying anything. Useful
/// for tests that want to assert on the diff shape before committing.
/// </summary>
public sealed record PreviewBundleCommand(
string Base64Bundle,
string? Passphrase);
public sealed record PreviewBundleResult(
IReadOnlyList<ImportPreviewItem> Items,
int AddCount,
int ModifiedCount,
int IdenticalCount,
int BlockerCount);
/// <summary>
/// Loads, previews, and applies a bundle in a single call. The diff is built
/// internally; every <see cref="ConflictKind.Modified"/> row is resolved with
/// <see cref="DefaultConflictPolicy"/>, every <see cref="ConflictKind.New"/>
/// row gets <see cref="ResolutionAction.Add"/>, every
/// <see cref="ConflictKind.Identical"/> row gets <see cref="ResolutionAction.Skip"/>,
/// and any <see cref="ConflictKind.Blocker"/> row fails the call.
/// <para>
/// Valid <see cref="DefaultConflictPolicy"/> values: <c>"skip"</c>,
/// <c>"overwrite"</c>, <c>"rename"</c>. Rename mints a unique suffix per row.
/// </para>
/// </summary>
public sealed record ImportBundleCommand(
string Base64Bundle,
string? Passphrase,
string DefaultConflictPolicy);
@@ -0,0 +1,83 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Notification;
/// <summary>
/// Site -> Central: submit a notification for central delivery.
/// Fire-and-forget with ack; the site retries until a <see cref="NotificationSubmitAck"/> is received.
/// </summary>
/// <param name="OriginExecutionId">
/// The originating script execution's <c>ExecutionId</c> (Audit Log #23). Stamped at
/// <c>Notify.Send</c> time and carried, inside the serialized payload, through the site
/// store-and-forward buffer so the central dispatcher can echo it onto the
/// <c>NotifyDeliver</c> audit rows. Additive trailing member — null for messages built
/// before the field existed, or for notifications raised outside a script execution.
/// </param>
/// <param name="OriginParentExecutionId">
/// The originating routed script execution's <c>ParentExecutionId</c> (Audit Log #23).
/// Stamped at <c>Notify.Send</c> time and carried, inside the serialized payload, through
/// the site store-and-forward buffer so the central dispatcher can echo it onto the
/// <c>NotifyDeliver</c> audit rows. Additive trailing member — null for messages built
/// before the field existed, or for non-routed runs.
/// </param>
/// <param name="SourceNode">
/// The cluster node on which the notification was emitted — `node-a` / `node-b` for site
/// submissions, `central-a` / `central-b` for central-originated rows. Stamped by the
/// emitting node from <c>INodeIdentityProvider</c> and carried, inside the serialized
/// payload, through the site store-and-forward buffer so the central dispatcher can
/// persist it on the <c>Notifications</c> row and echo it onto the <c>NotifyDeliver</c>
/// audit rows. Additive trailing member — null for messages built before the field
/// existed.
/// </param>
public record NotificationSubmit(
string NotificationId,
string ListName,
string Subject,
string Body,
string SourceSiteId,
string? SourceInstanceId,
string? SourceScript,
DateTimeOffset SiteEnqueuedAt,
Guid? OriginExecutionId = null,
Guid? OriginParentExecutionId = null,
string? SourceNode = null);
/// <summary>
/// Central -> Site: ack sent after the notification row is persisted.
/// Idempotent — safe to re-send for the same <see cref="NotificationId"/>.
/// </summary>
public record NotificationSubmitAck(
string NotificationId,
bool Accepted,
string? Error);
/// <summary>
/// Site -> Central: query the central delivery status for a <see cref="NotificationId"/>.
/// </summary>
public record NotificationStatusQuery(
string CorrelationId,
string NotificationId);
/// <summary>
/// Central -> Site: response carrying the current delivery status for a queried notification.
/// </summary>
public record NotificationStatusResponse(
string CorrelationId,
bool Found,
string Status,
int RetryCount,
string? LastError,
DateTimeOffset? DeliveredAt);
/// <summary>
/// Notification Outbox: the delivery status of a notification, as returned to a
/// script by <c>Notify.Status(id)</c>.
///
/// <see cref="Status"/> is either a central status (<c>Pending</c>, <c>Retrying</c>,
/// <c>Delivered</c>, <c>Parked</c>, <c>Discarded</c>), the site-local <c>Forwarding</c>
/// state (the notification is still buffered at the site and has not yet been
/// forwarded/acked), or <c>Unknown</c> (no central row and not buffered locally).
/// </summary>
public record NotificationDeliveryStatus(
string Status,
int RetryCount,
string? LastError,
DateTimeOffset? DeliveredAt);
@@ -0,0 +1,161 @@
using ZB.MOM.WW.ScadaBridge.Commons.Types.Notifications;
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Notification;
/// <summary>
/// Outbox UI -> Central: paginated, filtered query over the notification outbox.
/// All filter fields are optional; <see cref="StuckOnly"/> restricts results to stuck notifications.
/// </summary>
public record NotificationOutboxQueryRequest(
string CorrelationId,
string? StatusFilter,
string? TypeFilter,
string? SourceSiteFilter,
string? ListNameFilter,
bool StuckOnly,
string? SubjectKeyword,
DateTimeOffset? From,
DateTimeOffset? To,
int PageNumber,
int PageSize,
string? SourceNodeFilter = null);
/// <summary>
/// A single notification row summarised for outbox UI display.
/// </summary>
public record NotificationSummary(
string NotificationId,
string Type,
string ListName,
string Subject,
string Status,
int RetryCount,
string? LastError,
string SourceSiteId,
string? SourceInstanceId,
DateTimeOffset CreatedAt,
DateTimeOffset? DeliveredAt,
bool IsStuck,
string? SourceNode = null);
/// <summary>
/// Central -> Outbox UI: paginated response for a <see cref="NotificationOutboxQueryRequest"/>.
/// </summary>
public record NotificationOutboxQueryResponse(
string CorrelationId,
bool Success,
string? ErrorMessage,
IReadOnlyList<NotificationSummary> Notifications,
int TotalCount);
/// <summary>
/// Outbox UI -> Central: request to immediately retry delivery of a notification.
/// </summary>
public record RetryNotificationRequest(
string CorrelationId,
string NotificationId);
/// <summary>
/// Central -> Outbox UI: result of a <see cref="RetryNotificationRequest"/>.
/// </summary>
public record RetryNotificationResponse(
string CorrelationId,
bool Success,
string? ErrorMessage);
/// <summary>
/// Outbox UI -> Central: request to discard (cancel) a pending or stuck notification.
/// </summary>
public record DiscardNotificationRequest(
string CorrelationId,
string NotificationId);
/// <summary>
/// Central -> Outbox UI: result of a <see cref="DiscardNotificationRequest"/>.
/// </summary>
public record DiscardNotificationResponse(
string CorrelationId,
bool Success,
string? ErrorMessage);
/// <summary>
/// Outbox UI -> Central: request for the full detail of a single notification
/// (including Body and resolved recipients), for the report detail modal.
/// </summary>
public record NotificationDetailRequest(
string CorrelationId,
string NotificationId);
/// <summary>
/// Central -> Outbox UI: full detail for one notification. On a repository fault or
/// missing row, Success is false / Detail is null and ErrorMessage carries the cause.
/// </summary>
public record NotificationDetailResponse(
string CorrelationId,
bool Success,
string? ErrorMessage,
NotificationDetail? Detail);
/// <summary>
/// Full notification detail for the report detail modal — everything in the grid's
/// NotificationSummary plus Body, ResolvedTargets (recipients), TypeData, SourceScript,
/// and the additional lifecycle timestamps.
/// </summary>
public record NotificationDetail(
string NotificationId,
string Type,
string ListName,
string Subject,
string Body,
string Status,
int RetryCount,
string? LastError,
string? ResolvedTargets,
string? TypeData,
string SourceSiteId,
string? SourceInstanceId,
string? SourceScript,
DateTimeOffset SiteEnqueuedAt,
DateTimeOffset CreatedAt,
DateTimeOffset? LastAttemptAt,
DateTimeOffset? NextAttemptAt,
DateTimeOffset? DeliveredAt,
string? SourceNode = null);
/// <summary>
/// Outbox UI -> Central: request for the notification outbox KPI summary.
/// </summary>
public record NotificationKpiRequest(
string CorrelationId);
/// <summary>
/// Central -> Outbox UI: KPI summary for the notification outbox dashboard.
/// On a repository fault <see cref="Success"/> is <c>false</c>, <see cref="ErrorMessage"/>
/// carries the cause, and the KPI fields are zeroed/<c>null</c>.
/// </summary>
public record NotificationKpiResponse(
string CorrelationId,
bool Success,
string? ErrorMessage,
int QueueDepth,
int StuckCount,
int ParkedCount,
int DeliveredLastInterval,
TimeSpan? OldestPendingAge);
/// <summary>
/// Outbox UI -> Central: request for the per-source-site notification outbox KPI breakdown.
/// </summary>
public record PerSiteNotificationKpiRequest(
string CorrelationId);
/// <summary>
/// Central -> Outbox UI: per-site KPI breakdown for the Notification KPIs page.
/// On a repository fault <see cref="Success"/> is <c>false</c>, <see cref="ErrorMessage"/>
/// carries the cause, and <see cref="Sites"/> is empty.
/// </summary>
public record PerSiteNotificationKpiResponse(
string CorrelationId,
bool Success,
string? ErrorMessage,
IReadOnlyList<SiteNotificationKpiSnapshot> Sites);
@@ -0,0 +1,19 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.RemoteQuery;
/// <summary>
/// Request to query site event logs from central.
/// Supports filtering by event type, severity, instance, time range, and keyword search.
/// Uses keyset pagination via continuation token (last event ID).
/// </summary>
public record EventLogQueryRequest(
string CorrelationId,
string SiteId,
DateTimeOffset? From,
DateTimeOffset? To,
string? EventType,
string? Severity,
string? InstanceId,
string? KeywordFilter,
long? ContinuationToken,
int PageSize,
DateTimeOffset Timestamp);
@@ -0,0 +1,28 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.RemoteQuery;
/// <summary>
/// A single event log entry returned from a site query.
/// </summary>
public record EventLogEntry(
long Id,
DateTimeOffset Timestamp,
string EventType,
string Severity,
string? InstanceId,
string Source,
string Message,
string? Details);
/// <summary>
/// Response containing paginated event log entries from a site.
/// Uses keyset pagination: ContinuationToken is the last event ID in the result set.
/// </summary>
public record EventLogQueryResponse(
string CorrelationId,
string SiteId,
IReadOnlyList<EventLogEntry> Entries,
long? ContinuationToken,
bool HasMore,
bool Success,
string? ErrorMessage,
DateTimeOffset Timestamp);
@@ -0,0 +1,18 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.RemoteQuery;
/// <summary>
/// Request to permanently discard a parked message at a site.
/// </summary>
public record ParkedMessageDiscardRequest(
string CorrelationId,
string SiteId,
string MessageId,
DateTimeOffset Timestamp);
/// <summary>
/// Response from discarding a parked message.
/// </summary>
public record ParkedMessageDiscardResponse(
string CorrelationId,
bool Success,
string? ErrorMessage = null);
@@ -0,0 +1,11 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.RemoteQuery;
/// <summary>
/// Request to query parked (permanently failed) store-and-forward messages at a site.
/// </summary>
public record ParkedMessageQueryRequest(
string CorrelationId,
string SiteId,
int PageNumber,
int PageSize,
DateTimeOffset Timestamp);
@@ -0,0 +1,29 @@
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.RemoteQuery;
/// <summary>
/// Response containing parked store-and-forward messages from a site.
/// </summary>
public record ParkedMessageEntry(
string MessageId,
string TargetSystem,
string MethodName,
string ErrorMessage,
int AttemptCount,
DateTimeOffset OriginalTimestamp,
DateTimeOffset LastAttemptTimestamp,
int MaxAttempts = 0,
StoreAndForwardCategory Category = StoreAndForwardCategory.ExternalSystem,
string? OriginInstance = null);
public record ParkedMessageQueryResponse(
string CorrelationId,
string SiteId,
IReadOnlyList<ParkedMessageEntry> Messages,
int TotalCount,
int PageNumber,
int PageSize,
bool Success,
string? ErrorMessage,
DateTimeOffset Timestamp);
@@ -0,0 +1,18 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.RemoteQuery;
/// <summary>
/// Request to retry a parked message at a site (move back to pending queue).
/// </summary>
public record ParkedMessageRetryRequest(
string CorrelationId,
string SiteId,
string MessageId,
DateTimeOffset Timestamp);
/// <summary>
/// Response from retrying a parked message.
/// </summary>
public record ParkedMessageRetryResponse(
string CorrelationId,
bool Success,
string? ErrorMessage = null);
@@ -0,0 +1,75 @@
using ZB.MOM.WW.ScadaBridge.Commons.Types;
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.RemoteQuery;
/// <summary>
/// Central → site relay command: retry a parked cached operation
/// (<c>ExternalSystem.CachedCall</c> / <c>Database.CachedWrite</c>) on the
/// owning site's Store-and-Forward buffer. Sent over the command/control
/// channel by <c>SiteCallAuditActor</c> when an operator clicks Retry on a
/// <c>Parked</c> Site Call row in the Central UI.
/// </summary>
/// <remarks>
/// <para>
/// The site is the source of truth for cached-call status — central never
/// mutates the central <c>SiteCalls</c> mirror row directly. This command asks
/// the site to reset its own parked row back to <c>Pending</c> so the S&amp;F
/// retry sweep attempts delivery again; the corrected state then flows back to
/// central via the normal cached-call telemetry path.
/// </para>
/// <para>
/// The cached call's S&amp;F buffer message id is the
/// <see cref="TrackedOperationId"/> itself (the tracked id is supplied as the
/// buffered row's id at enqueue time), so the site can resolve the parked row
/// directly from <see cref="TrackedOperationId"/>. A retry on a row that is not
/// actually <c>Parked</c> is a safe no-op at the site — the ack reports
/// <c>Applied=false</c> rather than corrupting a non-parked row.
/// </para>
/// <para>
/// This is a plain record carrying only ids, so it lives in Commons (no
/// <c>IActorRef</c> field). It mirrors <see cref="ParkedMessageRetryRequest"/>
/// but keys on <see cref="TrackedOperationId"/> rather than the opaque S&amp;F
/// message-id string.
/// </para>
/// </remarks>
public sealed record RetryParkedOperation(
string CorrelationId,
TrackedOperationId TrackedOperationId);
/// <summary>
/// Central → site relay command: discard a parked cached operation on the
/// owning site's Store-and-Forward buffer. Sent over the command/control
/// channel by <c>SiteCallAuditActor</c> when an operator clicks Discard on a
/// <c>Parked</c> Site Call row in the Central UI. See
/// <see cref="RetryParkedOperation"/> for the source-of-truth and message-id
/// rationale; Discard marks the operation terminally <c>Discarded</c> at the
/// site by removing the parked S&amp;F buffer row.
/// </summary>
public sealed record DiscardParkedOperation(
string CorrelationId,
TrackedOperationId TrackedOperationId);
/// <summary>
/// Site → central ack for a <see cref="RetryParkedOperation"/> /
/// <see cref="DiscardParkedOperation"/> relay command. The site replies this
/// after applying (or safely no-op-ing) the action against its own
/// Store-and-Forward buffer.
/// </summary>
/// <param name="CorrelationId">Correlation id of the originating relay command.</param>
/// <param name="Applied">
/// <c>true</c> when the parked operation was found and the action was applied;
/// <c>false</c> when no parked row matched the <see cref="RetryParkedOperation.TrackedOperationId"/>
/// (already delivered, discarded, never cached, or not in a <c>Parked</c>
/// state). A <c>false</c> ack is a definitive "nothing to do" answer from the
/// site — it is NOT a transport failure, so the relay must distinguish it from
/// a site-unreachable timeout.
/// </param>
/// <param name="ErrorMessage">
/// Populated only when the site could not apply the action (e.g. the parked
/// message handler is not available, or the S&amp;F store faulted); <c>null</c>
/// on a clean <c>Applied=true</c>/<c>Applied=false</c> outcome.
/// </param>
public sealed record ParkedOperationActionAck(
string CorrelationId,
bool Applied,
string? ErrorMessage = null);
@@ -0,0 +1,17 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.ScriptExecution;
/// <param name="ParentExecutionId">
/// Audit Log #23 (ParentExecutionId): the spawning execution's <c>ExecutionId</c>.
/// For an inbound-API-routed call this is the inbound request's per-request
/// execution id (carried in from <c>RouteToCallRequest.ParentExecutionId</c>);
/// the routed script execution records it as its <c>ParentExecutionId</c> so a
/// spawned execution points back at its spawner. Additive trailing member —
/// null for normal (tag-change / timer-triggered) runs, nested <c>Script.Call</c>
/// invocations, and any request built before the field existed.
/// </param>
public record ScriptCallRequest(
string ScriptName,
IReadOnlyDictionary<string, object?>? Parameters,
int CurrentCallDepth,
string CorrelationId,
Guid? ParentExecutionId = null);
@@ -0,0 +1,7 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.ScriptExecution;
public record ScriptCallResult(
string CorrelationId,
bool Success,
object? ReturnValue,
string? ErrorMessage);
@@ -0,0 +1,29 @@
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Streaming;
public record AlarmStateChanged(
string InstanceUniqueName,
string AlarmName,
AlarmState State,
int Priority,
DateTimeOffset Timestamp) : ISiteStreamEvent
{
/// <summary>
/// Severity level when <see cref="State"/> is <see cref="AlarmState.Active"/>.
/// Always <see cref="AlarmLevel.None"/> for binary trigger types
/// (ValueMatch, RangeViolation, RateOfChange); set by the HiLo trigger
/// type to one of Low/LowLow/High/HighHigh based on the crossed setpoint.
/// Added as an init-property so existing positional constructors still
/// work — message contract evolves additively.
/// </summary>
public AlarmLevel Level { get; init; } = AlarmLevel.None;
/// <summary>
/// Optional per-band operator message (e.g., "Coolant critically low —
/// shut down"). Set by HiLo triggers when the per-setpoint message is
/// configured; otherwise empty. Notification routing and UI tooltips may
/// surface this to operators.
/// </summary>
public string Message { get; init; } = string.Empty;
}
@@ -0,0 +1,9 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Streaming;
public record AttributeValueChanged(
string InstanceUniqueName,
string AttributePath,
string AttributeName,
object? Value,
string Quality,
DateTimeOffset Timestamp) : ISiteStreamEvent;
@@ -0,0 +1,11 @@
namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Streaming;
/// <summary>
/// Marker interface for events published to the site-wide stream
/// (attribute value changes and alarm state changes).
/// </summary>
public interface ISiteStreamEvent
{
/// <summary>The unique name of the instance that produced this event.</summary>
string InstanceUniqueName { get; }
}