Files
ScadaBridge/src/ZB.MOM.WW.ScadaBridge.Communication/Protos/sitestream.proto
T
Joseph Doherty 9c631b47e1 feat(sitestream): add additive SubscribeSite alarm-only gRPC stream + server handler (plan #10 T2)
Adds an additive, site-wide, alarm-only gRPC stream backing the aggregated
Alarm Summary. Proto: new SiteStreamRequest { correlation_id } message + rpc
SubscribeSite(SiteStreamRequest) returns (stream SiteStreamEvent) on
SiteStreamService — purely additive, no field renumbering. Regenerated the
checked-in SiteStreamGrpc/*.cs.

Server: SubscribeInstance and the new SubscribeSite now delegate to a shared
RunSubscriptionStreamAsync helper (readiness/shutdown guards, correlation-id
validation, duplicate replacement, concurrency cap, bounded DropOldest channel,
relay actor, SiteConnectionOpened/Closed telemetry, guaranteed cleanup). The
only variation is the subscribe delegate: SubscribeSite calls
ISiteStreamSubscriber.SubscribeSiteAlarms (no per-instance filter). Added
SubscribeSiteAlarms to the ISiteStreamSubscriber contract (SiteStreamManager
already implements it from T1). StreamRelayActor reused unchanged — it already
drops IsConfiguredPlaceholder rows and maps the enriched AlarmStateUpdate.

Tests: SubscribeSite subscribes site alarms + removes on cancel, rejects unsafe
correlation ids, and relays a domain AlarmStateChanged as a proto
AlarmStateUpdate on the stream.

Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
2026-07-10 11:46:36 -04:00

199 lines
7.8 KiB
Protocol Buffer

syntax = "proto3";
option csharp_namespace = "ZB.MOM.WW.ScadaBridge.Communication.Grpc";
package sitestream;
import "google/protobuf/timestamp.proto";
import "google/protobuf/wrappers.proto"; // Int32Value
service SiteStreamService {
rpc SubscribeInstance(InstanceStreamRequest) returns (stream SiteStreamEvent);
// Site-wide, alarm-only live stream (aggregated Alarm Summary): every
// AlarmStateChanged for ALL instances on the site, no per-instance filter.
// Attribute updates are never carried on this stream.
rpc SubscribeSite(SiteStreamRequest) returns (stream SiteStreamEvent);
rpc IngestAuditEvents(AuditEventBatch) returns (IngestAck);
rpc IngestCachedTelemetry(CachedTelemetryBatch) returns (IngestAck);
rpc PullAuditEvents(PullAuditEventsRequest) returns (PullAuditEventsResponse);
rpc PullSiteCalls(PullSiteCallsRequest) returns (PullSiteCallsResponse);
}
message InstanceStreamRequest {
string correlation_id = 1;
string instance_unique_name = 2;
}
// Request for the site-wide, alarm-only SubscribeSite stream. Unlike
// InstanceStreamRequest there is NO instance filter — the stream carries alarm
// transitions for every instance on the site.
message SiteStreamRequest {
string correlation_id = 1;
}
message SiteStreamEvent {
string correlation_id = 1;
oneof event {
AttributeValueUpdate attribute_changed = 2;
AlarmStateUpdate alarm_changed = 3;
}
}
enum Quality {
QUALITY_UNSPECIFIED = 0;
QUALITY_GOOD = 1;
QUALITY_UNCERTAIN = 2;
QUALITY_BAD = 3;
}
enum AlarmStateEnum {
ALARM_STATE_UNSPECIFIED = 0;
ALARM_STATE_NORMAL = 1;
ALARM_STATE_ACTIVE = 2;
}
// Severity level for an active alarm. Binary trigger types (ValueMatch,
// RangeViolation, RateOfChange) always emit ALARM_LEVEL_NONE. The HiLo
// trigger type emits one of the directional values.
enum AlarmLevelEnum {
ALARM_LEVEL_NONE = 0;
ALARM_LEVEL_LOW = 1;
ALARM_LEVEL_LOW_LOW = 2;
ALARM_LEVEL_HIGH = 3;
ALARM_LEVEL_HIGH_HIGH = 4;
}
message AttributeValueUpdate {
string instance_unique_name = 1;
string attribute_path = 2;
string attribute_name = 3;
string value = 4;
Quality quality = 5;
google.protobuf.Timestamp timestamp = 6;
}
message AlarmStateUpdate {
string instance_unique_name = 1;
string alarm_name = 2;
AlarmStateEnum state = 3;
int32 priority = 4;
google.protobuf.Timestamp timestamp = 5;
AlarmLevelEnum level = 6; // ALARM_LEVEL_NONE for binary trigger types; set by HiLo.
string message = 7; // Optional per-band operator message; empty when unset.
// Native alarm enrichment (additive — computed alarms leave these at defaults).
// kind: "Computed" | "NativeOpcUa" | "NativeMxAccess".
string kind = 8;
bool active = 9; // unified condition: active vs inactive
bool acknowledged = 10; // acked vs unacked
bool confirmed = 11; // confirmed (false when not confirmable)
string shelve_state = 12; // Unshelved | OneShotShelved | TimedShelved | PermanentShelved
bool suppressed = 13;
string source_reference = 14; // native per-condition key; empty for computed
string alarm_type_name = 15;
string category = 16;
string operator_user = 17;
string operator_comment = 18;
google.protobuf.Timestamp original_raise_time = 19; // null when unknown
string current_value = 20;
string limit_value = 21;
string native_source_canonical_name = 22; // native binding canonical name; empty for computed
bool is_configured_placeholder = 23; // true for a quiet-binding placeholder row
}
// Audit Log (#23) telemetry: single lifecycle event ferried from a site SQLite
// hot-path row to central via IngestAuditEvents. Mirrors AuditEvent (Commons)
// minus the site-local ForwardState and the central IngestedAtUtc (set on ingest).
message AuditEventDto {
string event_id = 1;
google.protobuf.Timestamp occurred_at_utc = 2;
string channel = 3;
string kind = 4;
string correlation_id = 5; // empty string represents null
string source_site_id = 6;
string source_instance_id = 7;
string source_script = 8;
string actor = 9;
string target = 10;
string status = 11;
google.protobuf.Int32Value http_status = 12; // null when absent
google.protobuf.Int32Value duration_ms = 13;
string error_message = 14;
string error_detail = 15;
string request_summary = 16;
string response_summary = 17;
bool payload_truncated = 18;
string extra = 19;
string execution_id = 20; // empty string represents null
string parent_execution_id = 21; // empty string represents null
string source_node = 22; // empty string represents null
}
message AuditEventBatch { repeated AuditEventDto events = 1; }
message IngestAck { repeated string accepted_event_ids = 1; }
// Audit Log (#23) M3 cached-call combined telemetry: a single packet carries
// both the AuditEvent row to insert and the SiteCalls operational-state upsert
// for one lifecycle event of a cached outbound call. Central writes both rows
// in one MS SQL transaction so the audit and operational mirrors never drift.
message SiteCallOperationalDto {
string tracked_operation_id = 1; // GUID string ("D" format)
string channel = 2; // "ApiOutbound" | "DbOutbound"
string target = 3;
string source_site = 4;
string status = 5; // AuditStatus name
int32 retry_count = 6;
string last_error = 7; // empty when null
google.protobuf.Int32Value http_status = 8;
google.protobuf.Timestamp created_at_utc = 9;
google.protobuf.Timestamp updated_at_utc = 10;
google.protobuf.Timestamp terminal_at_utc = 11; // absent when not terminal
string source_node = 12; // empty string represents null
}
message CachedTelemetryPacket {
AuditEventDto audit_event = 1;
SiteCallOperationalDto operational = 2;
}
message CachedTelemetryBatch { repeated CachedTelemetryPacket packets = 1; }
// Audit Log (#23) M6 reconciliation pull: central→site request for any
// site-local AuditLog rows with OccurredAtUtc >= since_utc that have not yet
// been ingested centrally (ForwardState in {Pending, Forwarded}). The site
// flips returned rows to Reconciled after the response is on the wire.
// more_available signals batch_size was saturated so the caller knows to
// issue a follow-up pull with an advanced since_utc cursor.
message PullAuditEventsRequest {
google.protobuf.Timestamp since_utc = 1;
int32 batch_size = 2;
}
message PullAuditEventsResponse {
repeated AuditEventDto events = 1;
bool more_available = 2;
}
// Site Call Audit (#22) reconciliation pull: central→site request for any
// site-local operation-tracking rows whose UpdatedAtUtc >= since_utc — the
// self-heal feed that backfills the eventually-consistent central SiteCalls
// mirror when best-effort push telemetry is lost. Mirrors PullAuditEvents
// but is a SEPARATE RPC (the tracking store is the operational source of
// truth, distinct from the site audit queue). more_available signals
// batch_size was saturated so the caller advances since_utc and pulls again.
message PullSiteCallsRequest {
google.protobuf.Timestamp since_utc = 1;
int32 batch_size = 2;
// Composite-keyset cursor (Task 15): the TrackedOperationId ("D" GUID form) of
// the last row already consumed at since_utc. When set, the site returns only
// rows strictly after (since_utc, after_id) under a deterministic
// (UpdatedAtUtc, TrackedOperationId) order — un-pinning a batch that would
// otherwise stall when more than batch_size rows share one since_utc instant.
// Empty (the proto3 string default) preserves the legacy inclusive >= behaviour,
// so an older central that never sets it is unaffected. Additive-only.
string after_id = 3;
}
message PullSiteCallsResponse {
repeated SiteCallOperationalDto operationals = 1;
bool more_available = 2;
}