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

  // Ack timestamp for the condition; null while unacknowledged and on computed alarms
  // (MES alarm-status API §6.4). The site stamps the SOURCE's own ack instant where the
  // protocol supplies one (OPC UA A&C AckedState/TransitionTime) and the DCL's observation
  // time of the ack transition where it does not (MxAccess Gateway).
  google.protobuf.Timestamp ack_time = 24;
}

// 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}). Rows are NOT
// flipped to Reconciled when they are served — only when a LATER pull's cursor
// proves central consumed them (see after_id), so a fault between the response
// leaving the site and central committing it re-serves the rows instead of
// silently losing them (at-least-once).
// 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;
  // Composite-keyset cursor (WP2.3), mirroring PullSiteCallsRequest.after_id:
  // the EventId ("D" GUID form) of the last row central has already CONSUMED at
  // since_utc. When set, the site returns only rows strictly after the composite
  // (OccurredAtUtc, EventId) pair — un-pinning a batch that would otherwise stall
  // when more than batch_size rows share one since_utc instant — AND treats the
  // cursor as proof of receipt: everything at or before it is flipped to
  // Reconciled. Empty (the proto3 string default) preserves the legacy inclusive
  // >= behaviour, under which only rows strictly older than since_utc are proven
  // received. Additive-only.
  string after_id = 3;
}

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;
}
