syntax = "proto3";

package mxaccess_gateway.v1;

option csharp_namespace = "ZB.MOM.WW.MxGateway.Contracts.Proto";

import "google/protobuf/duration.proto";
import "google/protobuf/timestamp.proto";

// Wire-compatibility policy (ProtobufStyleGuide): this contract evolves
// additively only. Never renumber or repurpose an existing field number or
// enum value. When a field or enum value is removed, add a `reserved` range
// (and `reserved` name) covering it in the same change so a future editor
// cannot accidentally reuse the retired tag.

// Public client API for MXAccess sessions hosted by the gateway.
service MxAccessGateway {
  rpc OpenSession(OpenSessionRequest) returns (OpenSessionReply);
  rpc CloseSession(CloseSessionRequest) returns (CloseSessionReply);
  rpc Invoke(MxCommandRequest) returns (MxCommandReply);
  rpc StreamEvents(StreamEventsRequest) returns (stream MxEvent);
  rpc AcknowledgeAlarm(AcknowledgeAlarmRequest) returns (AcknowledgeAlarmReply);
  // Session-less central alarm feed. The stream opens with the current
  // active-alarm snapshot (one `active_alarm` per alarm), then a single
  // `snapshot_complete`, then a `transition` for every subsequent change.
  // Served by the gateway's always-on alarm monitor; any number of clients
  // fan out from the single monitor without opening a worker session.
  rpc StreamAlarms(StreamAlarmsRequest) returns (stream AlarmFeedMessage);
  // Point-in-time snapshot of the currently-active alarm set served from the
  // gateway's always-on alarm monitor cache (session-less). Used after a
  // reconnect to seed Part 9 client state, or to reconcile alarms that may
  // have been missed during a transport blip. Streamed so callers can
  // begin processing without buffering the full set.
  // `QueryActiveAlarmsRequest.alarm_filter_prefix` optionally narrows the
  // snapshot to alarms whose `alarm_full_reference` starts with the given
  // prefix; an empty prefix returns the full set.
  rpc QueryActiveAlarms(QueryActiveAlarmsRequest) returns (stream ActiveAlarmSnapshot);
}

// Public request shape for QueryActiveAlarms.
// Clients may leave `session_id` empty; the gateway currently ignores it and
// serves the session-less central-monitor cache. A future version may use it
// to scope the snapshot to one session.
message QueryActiveAlarmsRequest {
  string session_id = 1;
  string client_correlation_id = 2;
  // Optional filter: when non-empty, only snapshots whose alarm_full_reference
  // starts with this string are returned.
  string alarm_filter_prefix = 3;
}

message OpenSessionRequest {
  string requested_backend = 1;
  string client_session_name = 2;
  string client_correlation_id = 3;
  google.protobuf.Duration command_timeout = 4;
}

message OpenSessionReply {
  string session_id = 1;
  string backend_name = 2;
  int32 worker_process_id = 3;
  uint32 worker_protocol_version = 4;
  repeated string capabilities = 5;
  google.protobuf.Duration default_command_timeout = 6;
  ProtocolStatus protocol_status = 7;
  // Public gateway contract version implemented by this endpoint. Clients use
  // this value to reject incompatible generated-code inputs before issuing
  // command-specific MXAccess calls.
  uint32 gateway_protocol_version = 8;
}

message CloseSessionRequest {
  string session_id = 1;
  string client_correlation_id = 2;
}

message CloseSessionReply {
  string session_id = 1;
  SessionState final_state = 2;
  ProtocolStatus protocol_status = 3;
}

message StreamEventsRequest {
  string session_id = 1;
  uint64 after_worker_sequence = 2;
}

message MxCommandRequest {
  string session_id = 1;
  string client_correlation_id = 2;
  MxCommand command = 3;
}

message MxCommand {
  MxCommandKind kind = 1;

  oneof payload {
    RegisterCommand register = 10;
    UnregisterCommand unregister = 11;
    AddItemCommand add_item = 12;
    AddItem2Command add_item2 = 13;
    RemoveItemCommand remove_item = 14;
    AdviseCommand advise = 15;
    UnAdviseCommand un_advise = 16;
    AdviseSupervisoryCommand advise_supervisory = 17;
    AddBufferedItemCommand add_buffered_item = 18;
    SetBufferedUpdateIntervalCommand set_buffered_update_interval = 19;
    SuspendCommand suspend = 20;
    ActivateCommand activate = 21;
    WriteCommand write = 22;
    Write2Command write2 = 23;
    WriteSecuredCommand write_secured = 24;
    WriteSecured2Command write_secured2 = 25;
    AuthenticateUserCommand authenticate_user = 26;
    ArchestrAUserToIdCommand archestra_user_to_id = 27;
    AddItemBulkCommand add_item_bulk = 28;
    AdviseItemBulkCommand advise_item_bulk = 29;
    RemoveItemBulkCommand remove_item_bulk = 30;
    UnAdviseItemBulkCommand un_advise_item_bulk = 31;
    SubscribeBulkCommand subscribe_bulk = 32;
    UnsubscribeBulkCommand unsubscribe_bulk = 33;
    SubscribeAlarmsCommand subscribe_alarms = 34;
    UnsubscribeAlarmsCommand unsubscribe_alarms = 35;
    AcknowledgeAlarmCommand acknowledge_alarm_command = 36;
    QueryActiveAlarmsCommand query_active_alarms_command = 37;
    AcknowledgeAlarmByNameCommand acknowledge_alarm_by_name_command = 38;
    WriteBulkCommand write_bulk = 39;
    Write2BulkCommand write2_bulk = 40;
    WriteSecuredBulkCommand write_secured_bulk = 41;
    WriteSecured2BulkCommand write_secured2_bulk = 42;
    ReadBulkCommand read_bulk = 43;
    PingCommand ping = 100;
    GetSessionStateCommand get_session_state = 101;
    GetWorkerInfoCommand get_worker_info = 102;
    DrainEventsCommand drain_events = 103;
    ShutdownWorkerCommand shutdown_worker = 104;
  }
}

enum MxCommandKind {
  MX_COMMAND_KIND_UNSPECIFIED = 0;
  MX_COMMAND_KIND_REGISTER = 1;
  MX_COMMAND_KIND_UNREGISTER = 2;
  MX_COMMAND_KIND_ADD_ITEM = 3;
  MX_COMMAND_KIND_ADD_ITEM2 = 4;
  MX_COMMAND_KIND_REMOVE_ITEM = 5;
  MX_COMMAND_KIND_ADVISE = 6;
  MX_COMMAND_KIND_UN_ADVISE = 7;
  MX_COMMAND_KIND_ADVISE_SUPERVISORY = 8;
  MX_COMMAND_KIND_ADD_BUFFERED_ITEM = 9;
  MX_COMMAND_KIND_SET_BUFFERED_UPDATE_INTERVAL = 10;
  MX_COMMAND_KIND_SUSPEND = 11;
  MX_COMMAND_KIND_ACTIVATE = 12;
  MX_COMMAND_KIND_WRITE = 13;
  MX_COMMAND_KIND_WRITE2 = 14;
  MX_COMMAND_KIND_WRITE_SECURED = 15;
  MX_COMMAND_KIND_WRITE_SECURED2 = 16;
  MX_COMMAND_KIND_AUTHENTICATE_USER = 17;
  MX_COMMAND_KIND_ARCHESTRA_USER_TO_ID = 18;
  MX_COMMAND_KIND_ADD_ITEM_BULK = 19;
  MX_COMMAND_KIND_ADVISE_ITEM_BULK = 20;
  MX_COMMAND_KIND_REMOVE_ITEM_BULK = 21;
  MX_COMMAND_KIND_UN_ADVISE_ITEM_BULK = 22;
  MX_COMMAND_KIND_SUBSCRIBE_BULK = 23;
  MX_COMMAND_KIND_UNSUBSCRIBE_BULK = 24;
  MX_COMMAND_KIND_SUBSCRIBE_ALARMS = 25;
  MX_COMMAND_KIND_UNSUBSCRIBE_ALARMS = 26;
  MX_COMMAND_KIND_ACKNOWLEDGE_ALARM = 27;
  MX_COMMAND_KIND_QUERY_ACTIVE_ALARMS = 28;
  MX_COMMAND_KIND_ACKNOWLEDGE_ALARM_BY_NAME = 29;
  MX_COMMAND_KIND_WRITE_BULK = 30;
  MX_COMMAND_KIND_WRITE2_BULK = 31;
  MX_COMMAND_KIND_WRITE_SECURED_BULK = 32;
  MX_COMMAND_KIND_WRITE_SECURED2_BULK = 33;
  MX_COMMAND_KIND_READ_BULK = 34;
  MX_COMMAND_KIND_PING = 100;
  MX_COMMAND_KIND_GET_SESSION_STATE = 101;
  MX_COMMAND_KIND_GET_WORKER_INFO = 102;
  MX_COMMAND_KIND_DRAIN_EVENTS = 103;
  MX_COMMAND_KIND_SHUTDOWN_WORKER = 104;
}

message RegisterCommand {
  string client_name = 1;
}

message UnregisterCommand {
  int32 server_handle = 1;
}

message AddItemCommand {
  int32 server_handle = 1;
  string item_definition = 2;
}

message AddItem2Command {
  int32 server_handle = 1;
  string item_definition = 2;
  string item_context = 3;
}

message RemoveItemCommand {
  int32 server_handle = 1;
  int32 item_handle = 2;
}

message AdviseCommand {
  int32 server_handle = 1;
  int32 item_handle = 2;
}

message UnAdviseCommand {
  int32 server_handle = 1;
  int32 item_handle = 2;
}

message AdviseSupervisoryCommand {
  int32 server_handle = 1;
  int32 item_handle = 2;
}

message AddBufferedItemCommand {
  int32 server_handle = 1;
  string item_definition = 2;
  string item_context = 3;
}

message SetBufferedUpdateIntervalCommand {
  int32 server_handle = 1;
  int32 update_interval_milliseconds = 2;
}

message SuspendCommand {
  int32 server_handle = 1;
  int32 item_handle = 2;
}

message ActivateCommand {
  int32 server_handle = 1;
  int32 item_handle = 2;
}

// The unary reply's statuses field carries the correlated OnWriteComplete
// outcome when it arrives within the worker's bounded wait — see
// MxCommandReply.statuses.
message WriteCommand {
  int32 server_handle = 1;
  int32 item_handle = 2;
  MxValue value = 3;
  int32 user_id = 4;
}

// Same statuses correlation as WriteCommand.
message Write2Command {
  int32 server_handle = 1;
  int32 item_handle = 2;
  MxValue value = 3;
  MxValue timestamp_value = 4;
  int32 user_id = 5;
}

// The unary reply's statuses field carries the correlated OnWriteComplete
// outcome when it arrives within the worker's bounded wait — see
// MxCommandReply.statuses.
message WriteSecuredCommand {
  int32 server_handle = 1;
  int32 item_handle = 2;
  int32 current_user_id = 3;
  int32 verifier_user_id = 4;
  // Credential-sensitive write value. Implementations must not log this field
  // unless an explicit redacted value-logging path is enabled.
  MxValue value = 5;
}

// The unary reply's statuses field carries the correlated OnWriteComplete
// outcome when it arrives within the worker's bounded wait — see
// MxCommandReply.statuses.
message WriteSecured2Command {
  int32 server_handle = 1;
  int32 item_handle = 2;
  int32 current_user_id = 3;
  int32 verifier_user_id = 4;
  // Credential-sensitive write value. Implementations must not log this field
  // unless an explicit redacted value-logging path is enabled.
  MxValue value = 5;
  MxValue timestamp_value = 6;
}

message AuthenticateUserCommand {
  int32 server_handle = 1;
  string verify_user = 2;
  // Raw MXAccess credential. Implementations must keep this field out of logs,
  // metrics labels, command lines, and diagnostics.
  string verify_user_password = 3;
}

message ArchestrAUserToIdCommand {
  int32 server_handle = 1;
  string user_id_guid = 2;
}

message AddItemBulkCommand {
  int32 server_handle = 1;
  repeated string tag_addresses = 2;
}

message AdviseItemBulkCommand {
  int32 server_handle = 1;
  repeated int32 item_handles = 2;
}

message RemoveItemBulkCommand {
  int32 server_handle = 1;
  repeated int32 item_handles = 2;
}

message UnAdviseItemBulkCommand {
  int32 server_handle = 1;
  repeated int32 item_handles = 2;
}

message SubscribeBulkCommand {
  int32 server_handle = 1;
  repeated string tag_addresses = 2;
}

// Provider selection / current provider for the alarm feed. The zero value
// has two distinct meanings depending on the use site:
//   - As SubscribeAlarmsCommand.forced_mode, UNSPECIFIED means auto: alarmmgr
//     primary with subtag fallback.
//   - As a provenance value (OnAlarmTransitionEvent.source_provider,
//     ActiveAlarmSnapshot.source_provider, OnAlarmProviderModeChangedEvent.mode,
//     AlarmProviderStatus.mode), the worker always emits ALARMMGR or SUBTAG and
//     never UNSPECIFIED; clients should treat a UNSPECIFIED provenance value as
//     "unknown / not yet determined".
enum AlarmProviderMode {
  ALARM_PROVIDER_MODE_UNSPECIFIED = 0;
  ALARM_PROVIDER_MODE_ALARMMGR = 1;
  ALARM_PROVIDER_MODE_SUBTAG = 2;
}

// Subscribe the worker's alarm consumer to an AVEVA alarm provider.
// Subscription expression follows the canonical
// `\\<machine>\Galaxy!<area>` format (literal "Galaxy" provider). The
// worker spins up a wnwrapConsumer-backed subscription on its STA on
// first call; subsequent calls are an error (use UnsubscribeAlarms then
// SubscribeAlarms to reconfigure).
message SubscribeAlarmsCommand {
  string subscription_expression = 1;
  // UNSPECIFIED = auto-failover/failback. ALARMMGR/SUBTAG force one provider.
  AlarmProviderMode forced_mode = 2;
  // Subtag watch-list resolved by the gateway (GR SQL + config). Empty in pure
  // alarmmgr mode; in subtag mode it bounds what the consumer can observe.
  repeated AlarmSubtagTarget watch_list = 3;
  AlarmFailoverConfig failover = 4;
}

// Tear down the worker's alarm consumer. No-op if no subscription is
// currently active.
message UnsubscribeAlarmsCommand {
}

// One alarm attribute the subtag fallback consumer advises. Addresses are full
// MXAccess item references the worker passes straight to AddItem.
message AlarmSubtagTarget {
  string alarm_full_reference = 1;     // e.g. "Galaxy!Area.Tank01.Level.HiHi"
  string source_object_reference = 2;  // e.g. "Tank01"
  string active_subtag = 3;            // item address of the in-alarm boolean
  string acked_subtag = 4;             // item address of the acknowledged boolean
  string ack_comment_subtag = 5;       // writable ack-comment attribute (ack write target)
  string priority_subtag = 6;          // optional severity source; empty if absent
}

message AlarmFailoverConfig {
  int32 consecutive_failure_threshold = 1;   // wnwrap COM failures before switching (>=1)
  int32 failback_probe_interval_seconds = 2; // probe cadence while degraded (>=1)
  int32 failback_stable_probes = 3;          // clean probes before switching back (>=1)
}

// Acknowledge a single alarm by its GUID. Operator identity fields are
// recorded atomically with the ack transition in the alarm-history log.
// The reply's hresult / native_status surfaces AVEVA's
// AlarmAckByGUID return code.
message AcknowledgeAlarmCommand {
  // Canonical 8-4-4-4-12 GUID string (e.g. "BCC47053-9542-4D65-BDAA-BCDEA6A32A73").
  string alarm_guid = 1;
  string comment = 2;
  string operator_user = 3;
  string operator_node = 4;
  string operator_domain = 5;
  string operator_full_name = 6;
}

// Snapshot the currently-active alarm set. Optional filter prefix scopes
// the snapshot to alarms whose alarm_full_reference starts with the
// supplied string (matches QueryActiveAlarmsRequest.alarm_filter_prefix).
message QueryActiveAlarmsCommand {
  string alarm_filter_prefix = 1;
}

// Acknowledge a single alarm by its (name, provider, group) tuple. Used
// when the public RPC's AlarmFullReference (Provider!Group.Tag) cannot
// be resolved to a GUID directly. The worker invokes
// wwAlarmConsumerClass.AlarmAckByName which reaches the same alarm
// history path as AlarmAckByGUID.
message AcknowledgeAlarmByNameCommand {
  // Tag/alarm name (e.g. "TestMachine_001.TestAlarm001"). Tag itself
  // may contain dots; the gateway-side parser splits on the first dot
  // after the '!' separator.
  string alarm_name = 1;
  // AVEVA alarm-provider name (literal "Galaxy" for ArchestrA Galaxies).
  string provider_name = 2;
  // Area/group name (e.g. "TestArea").
  string group_name = 3;
  string comment = 4;
  string operator_user = 5;
  string operator_node = 6;
  string operator_domain = 7;
  string operator_full_name = 8;
}

message UnsubscribeBulkCommand {
  int32 server_handle = 1;
  repeated int32 item_handles = 2;
}

// Bulk Write — sequential MXAccess Write per entry, on the worker's STA.
// MXAccess has no native bulk write; each entry round-trips through the same
// single-item Write path the gateway uses today. Per-item failures appear as
// BulkWriteResult entries with `was_successful = false` and never throw.
message WriteBulkCommand {
  int32 server_handle = 1;
  repeated WriteBulkEntry entries = 2;
}

message WriteBulkEntry {
  int32 item_handle = 1;
  MxValue value = 2;
  int32 user_id = 3;
}

// Bulk Write2 — sequential MXAccess Write2 (timestamped) per entry.
message Write2BulkCommand {
  int32 server_handle = 1;
  repeated Write2BulkEntry entries = 2;
}

message Write2BulkEntry {
  int32 item_handle = 1;
  MxValue value = 2;
  MxValue timestamp_value = 3;
  int32 user_id = 4;
}

// Bulk WriteSecured — sequential MXAccess WriteSecured per entry.
// Credential-sensitive values (`value`) MUST be kept out of logs, metrics
// labels, command lines, and diagnostics — same redaction rules as the
// single-item WriteSecured contract.
message WriteSecuredBulkCommand {
  int32 server_handle = 1;
  repeated WriteSecuredBulkEntry entries = 2;
}

message WriteSecuredBulkEntry {
  int32 item_handle = 1;
  int32 current_user_id = 2;
  int32 verifier_user_id = 3;
  // Credential-sensitive write value. Implementations must not log this field
  // unless an explicit redacted value-logging path is enabled.
  MxValue value = 4;
}

// Bulk WriteSecured2 — sequential MXAccess WriteSecured2 (timestamped) per
// entry. Same redaction rules apply.
message WriteSecured2BulkCommand {
  int32 server_handle = 1;
  repeated WriteSecured2BulkEntry entries = 2;
}

message WriteSecured2BulkEntry {
  int32 item_handle = 1;
  int32 current_user_id = 2;
  int32 verifier_user_id = 3;
  // Credential-sensitive write value. Implementations must not log this field
  // unless an explicit redacted value-logging path is enabled.
  MxValue value = 4;
  MxValue timestamp_value = 5;
}

// Bulk Read — snapshot the current value for each requested tag. MXAccess COM
// has no synchronous Read; the worker implements ReadBulk as:
//
//   - If the tag is already in the session's item registry AND that item is
//     currently advised AND the worker has a cached OnDataChange for it, the
//     reply returns the cached value WITHOUT modifying the existing
//     subscription (was_cached = true).
//   - Otherwise the worker takes the snapshot lifecycle itself: AddItem +
//     Advise, wait up to `timeout_ms` for the first OnDataChange, then
//     UnAdvise + RemoveItem before returning. The session is left exactly
//     as it was before the call (was_cached = false).
//
// `timeout_ms == 0` uses the gateway-configured default (1000 ms).
message ReadBulkCommand {
  int32 server_handle = 1;
  repeated string tag_addresses = 2;
  uint32 timeout_ms = 3;
}

message PingCommand {
  string message = 1;
}

message GetSessionStateCommand {
}

message GetWorkerInfoCommand {
}

message DrainEventsCommand {
  uint32 max_events = 1;
}

message ShutdownWorkerCommand {
  google.protobuf.Duration grace_period = 1;
}

message MxCommandReply {
  string session_id = 1;
  string correlation_id = 2;
  MxCommandKind kind = 3;
  ProtocolStatus protocol_status = 4;
  // HRESULT captured from MXAccess or a COM exception. This remains separate
  // from gateway protocol status so MXAccess parity details are not hidden by
  // transport failures.
  optional int32 hresult = 5;
  MxValue return_value = 6;
  // Correlated per-item outcome rows. For WRITE / WRITE2 / WRITE_SECURED /
  // WRITE_SECURED2 replies the worker holds the reply for a bounded window
  // (default 1.5 s, MXGATEWAY_WORKER_WRITE_COMPLETION_WAIT_MS) waiting for
  // the matching MXAccess OnWriteComplete callback and copies its status rows
  // here, so statuses[0] carries the real MXAccess commit outcome (success OR
  // failure) while protocol_status/hresult still describe command acceptance
  // only. Empty statuses on a write reply means the completion did not arrive
  // within the window — the write is unconfirmed, not failed. Correlation is
  // best-effort per (server_handle, item_handle): MXAccess's callback carries
  // no transaction id, so concurrent writes to the same item within the
  // window can swap rows. The OnWriteComplete event still flows on the event
  // stream unchanged. Bulk write kinds and all non-write kinds leave this
  // field as before.
  repeated MxStatusProxy statuses = 7;
  string diagnostic_message = 8;

  oneof payload {
    RegisterReply register = 20;
    AddItemReply add_item = 21;
    AddItem2Reply add_item2 = 22;
    AddBufferedItemReply add_buffered_item = 23;
    SuspendReply suspend = 24;
    ActivateReply activate = 25;
    AuthenticateUserReply authenticate_user = 26;
    ArchestrAUserToIdReply archestra_user_to_id = 27;
    BulkSubscribeReply add_item_bulk = 28;
    BulkSubscribeReply advise_item_bulk = 29;
    BulkSubscribeReply remove_item_bulk = 30;
    BulkSubscribeReply un_advise_item_bulk = 31;
    BulkSubscribeReply subscribe_bulk = 32;
    BulkSubscribeReply unsubscribe_bulk = 33;
    // Reply payload for BOTH MX_COMMAND_KIND_ACKNOWLEDGE_ALARM (by GUID)
    // and MX_COMMAND_KIND_ACKNOWLEDGE_ALARM_BY_NAME. There is intentionally
    // no by-name-specific reply case: the by-name ack carries no outcome
    // detail beyond the native ack return code, so the worker reuses this
    // `acknowledge_alarm` payload for both command kinds (the worker's
    // MxAccessCommandExecutor sets `acknowledge_alarm` for the by-name arm
    // too). Consumers must dispatch on MxCommandReply.kind, not on the
    // payload case, to tell the two acks apart. The top-level `hresult`
    // mirrors AcknowledgeAlarmReplyPayload.native_status and is preferred.
    AcknowledgeAlarmReplyPayload acknowledge_alarm = 34;
    QueryActiveAlarmsReplyPayload query_active_alarms = 35;
    BulkWriteReply write_bulk = 36;
    BulkWriteReply write2_bulk = 37;
    BulkWriteReply write_secured_bulk = 38;
    BulkWriteReply write_secured2_bulk = 39;
    BulkReadReply read_bulk = 40;
    SessionStateReply session_state = 100;
    WorkerInfoReply worker_info = 101;
    DrainEventsReply drain_events = 102;
  }
}

message RegisterReply {
  int32 server_handle = 1;
}

message AddItemReply {
  int32 item_handle = 1;
}

message AddItem2Reply {
  int32 item_handle = 1;
}

message AddBufferedItemReply {
  int32 item_handle = 1;
}

message SuspendReply {
  MxStatusProxy status = 1;
}

message ActivateReply {
  MxStatusProxy status = 1;
}

message AuthenticateUserReply {
  int32 user_id = 1;
}

message ArchestrAUserToIdReply {
  int32 user_id = 1;
}

message SubscribeResult {
  int32 server_handle = 1;
  string tag_address = 2;
  int32 item_handle = 3;
  bool was_successful = 4;
  string error_message = 5;
}

message BulkSubscribeReply {
  repeated SubscribeResult results = 1;
}

// Per-item result for the four bulk write families. `item_handle` mirrors the
// request entry's item_handle so callers can correlate inputs to outputs even
// when the gateway's per-entry `IConstraintEnforcer.CheckWriteHandleAsync`
// filter (see `MxAccessGatewayService.ReplaceWriteBulkEntries` and
// `docs/Authorization.md`) dropped some entries before reaching the worker.
// Per-item failures populate `error_message` + `hresult` and never raise —
// callers iterate and inspect each entry.
message BulkWriteResult {
  int32 server_handle = 1;
  int32 item_handle = 2;
  bool was_successful = 3;
  optional int32 hresult = 4;
  repeated MxStatusProxy statuses = 5;
  string error_message = 6;
}

message BulkWriteReply {
  repeated BulkWriteResult results = 1;
}

// Per-tag result for ReadBulk. `was_cached` is true when the value came from
// an existing live subscription's last OnDataChange (the worker did not touch
// the subscription); false when the worker took the AddItem + Advise + wait +
// UnAdvise + RemoveItem snapshot lifecycle itself.
//
// On `was_successful = true`, `value`, `quality`, `source_timestamp`, and
// `statuses` carry the read data (from the cached subscription or the snapshot
// lifecycle, depending on `was_cached`) and `error_message` is empty. On
// `was_successful = false`, only `server_handle`, `tag_address`, `item_handle`
// (when allocated), `was_cached`, and `error_message` are populated; `value`,
// `quality`, `source_timestamp`, and `statuses` are left at their proto3
// defaults (null / 0 / null / empty) and must not be read as data — they are
// wire-indistinguishable from "value is null with quality bad" data and serve
// only as absent markers. ReadBulk has no `hresult` field by design (its
// outcomes are timeout / cache / lifecycle states, not MXAccess COM return
// codes — see `docs/DesignDecisions.md` "Bulk Command Family"). Per-tag
// failures populate `error_message` and never raise — callers iterate and
// inspect each entry.
message BulkReadResult {
  int32 server_handle = 1;
  string tag_address = 2;
  int32 item_handle = 3;
  bool was_successful = 4;
  bool was_cached = 5;
  MxValue value = 6;
  int32 quality = 7;
  google.protobuf.Timestamp source_timestamp = 8;
  repeated MxStatusProxy statuses = 9;
  string error_message = 10;
}

message BulkReadReply {
  repeated BulkReadResult results = 1;
}

message SessionStateReply {
  SessionState state = 1;
}

message WorkerInfoReply {
  int32 worker_process_id = 1;
  string worker_version = 2;
  string mxaccess_progid = 3;
  string mxaccess_clsid = 4;
}

message DrainEventsReply {
  // The reply is bounded by both a server-side count cap and the negotiated
  // worker-frame byte cap; a reply may therefore carry fewer events than
  // `max_events` and fewer than are queued. Callers drain iteratively until an
  // empty reply.
  repeated MxEvent events = 1;
}

// Reply payload for AcknowledgeAlarmCommand AND
// AcknowledgeAlarmByNameCommand — both ack command kinds reuse this
// payload case (`MxCommandReply.acknowledge_alarm`); there is no
// dedicated by-name reply case. Surfaces AVEVA's native ack return
// code (AlarmAckByGUID for the GUID arm, AlarmAckByName for the
// by-name arm); 0 means success. The MxCommandReply's hresult field
// carries the same value and is preferred for protocol consumers —
// this payload exists so the gateway-side WorkerAlarmRpcDispatcher
// can echo native_status into AcknowledgeAlarmReply.hresult without
// unpacking the outer envelope.
message AcknowledgeAlarmReplyPayload {
  int32 native_status = 1;
}

// Reply payload for QueryActiveAlarmsCommand. The worker walks
// IMxAccessAlarmConsumer.SnapshotActiveAlarms and packs each record as
// an ActiveAlarmSnapshot proto for the gateway-side ConditionRefresh
// stream.
message QueryActiveAlarmsReplyPayload {
  repeated ActiveAlarmSnapshot snapshots = 1;
  // True when the provider fetch backing this reply came back holding the
  // per-fetch cap (MxGateway:Alarms:MaxAlarmsPerFetch). The reply may then omit
  // active alarms, and the worker suspends its absence-implies-Clear inference
  // for that poll — so a reference missing from `snapshots` is not evidence the
  // alarm cleared. Carried on the payload as well as per-record because a
  // truncated fetch that filters down to zero records still has to say so.
  bool snapshot_truncated = 2;
}

message MxEvent {
  MxEventFamily family = 1;
  string session_id = 2;
  int32 server_handle = 3;
  int32 item_handle = 4;
  MxValue value = 5;
  int32 quality = 6;
  google.protobuf.Timestamp source_timestamp = 7;
  repeated MxStatusProxy statuses = 8;
  uint64 worker_sequence = 9;
  google.protobuf.Timestamp worker_timestamp = 10;
  google.protobuf.Timestamp gateway_receive_timestamp = 11;
  optional int32 hresult = 12;
  string raw_status = 13;
  // Gateway-synthesized reconnect-replay gap signal. Set ONLY on the single
  // sentinel MxEvent the gateway emits at the head of a StreamEvents stream
  // that was resumed via StreamEventsRequest.after_worker_sequence when the
  // requested sequence is older than the oldest event still retained in the
  // session replay ring (i.e. events were evicted and cannot be replayed).
  // On that sentinel, `family` is UNSPECIFIED, the `body` oneof is unset, and
  // no per-item fields (server_handle/item_handle/value/...) are populated;
  // clients MUST treat a present `replay_gap` as "you missed events — discard
  // local state and re-snapshot" and read `requested_after_sequence` /
  // `oldest_available_sequence` from it. Unset on every normal MXAccess event.
  // This field is ONLY ever set on events returned from the StreamEvents server
  // stream; it is ALWAYS unset on events in DrainEventsReply (the diagnostic
  // drain path never emits the sentinel).
  // Additive (proto3): existing clients that ignore this field continue to
  // deserialize the stream unchanged.
  optional ReplayGap replay_gap = 14;

  oneof body {
    OnDataChangeEvent on_data_change = 20;
    OnWriteCompleteEvent on_write_complete = 21;
    OperationCompleteEvent operation_complete = 22;
    OnBufferedDataChangeEvent on_buffered_data_change = 23;
    OnAlarmTransitionEvent on_alarm_transition = 24;
    OnAlarmProviderModeChangedEvent on_alarm_provider_mode_changed = 25;
  }
}

// Reconnect-replay gap signal carried by a sentinel MxEvent (MxEvent.replay_gap)
// when a client resumes StreamEvents via after_worker_sequence but the requested
// sequence predates the oldest event still held in the session replay ring.
// The events in the open interval (requested_after_sequence, oldest_available_sequence)
// were evicted from the ring and cannot be replayed, so the client must
// re-snapshot rather than assume a contiguous event history.
message ReplayGap {
  // The worker_sequence the client asked to resume after
  // (StreamEventsRequest.after_worker_sequence).
  uint64 requested_after_sequence = 1;
  // The oldest worker_sequence still retained in the replay ring and available
  // for replay. Events with worker_sequence in the open interval
  // (requested_after_sequence, oldest_available_sequence) were evicted and are
  // unrecoverable. oldest_available_sequence itself IS still retained: a client
  // that wishes to resume without incurring another gap MUST set
  // after_worker_sequence = oldest_available_sequence - 1 in the next
  // StreamEventsRequest, which will cause the server to replay starting at
  // oldest_available_sequence (the first retained event).
  // When nothing is retained (the replay ring is empty), this is the next sequence
  // that can be delivered — `highest observed + 1` — and the `oldest - 1` resume
  // formula remains valid: it resolves to the highest sequence already seen, so the
  // follow-up resume replays nothing, reports no gap, and every newer live event
  // passes. The interval evicted is unchanged.
  uint64 oldest_available_sequence = 2;
}

enum MxEventFamily {
  MX_EVENT_FAMILY_UNSPECIFIED = 0;
  MX_EVENT_FAMILY_ON_DATA_CHANGE = 1;
  MX_EVENT_FAMILY_ON_WRITE_COMPLETE = 2;
  MX_EVENT_FAMILY_OPERATION_COMPLETE = 3;
  MX_EVENT_FAMILY_ON_BUFFERED_DATA_CHANGE = 4;
  MX_EVENT_FAMILY_ON_ALARM_TRANSITION = 5;
  MX_EVENT_FAMILY_ON_ALARM_PROVIDER_MODE_CHANGED = 6;
}

message OnDataChangeEvent {
}

message OnWriteCompleteEvent {
}

message OperationCompleteEvent {
}

message OnBufferedDataChangeEvent {
  MxDataType data_type = 1;
  MxArray quality_values = 2;
  MxArray timestamp_values = 3;
  int32 raw_data_type = 4;
}

// Carries a single MXAccess alarm transition (raise / acknowledge / clear /
// re-trigger) in native MXAccess terms. The Part 9 state machine + ACL +
// multi-source aggregation lives in lmxopcua's AlarmConditionService; the
// gateway is UA-agnostic and forwards the raw payload.
message OnAlarmTransitionEvent {
  // Fully-qualified alarm reference (e.g. "Tank01.Level.HiHi"). Stable across
  // transitions of the same condition; used by the lmxopcua side to correlate
  // raise/ack/clear into a single Part 9 condition.
  string alarm_full_reference = 1;

  // Galaxy-side source object reference (e.g. "Tank01"). Empty for alarms
  // that do not bind to a Galaxy object.
  string source_object_reference = 2;

  // MxAccess alarm-type qualifier (e.g. "AnalogLimitAlarm.HiHi", "DiscAlarm").
  string alarm_type_name = 3;

  // What kind of state change this event represents.
  AlarmTransitionKind transition_kind = 4;

  // Raw MXAccess severity value. Mapping to OPC UA 0-1000 happens server-side
  // in lmxopcua via MxAccessSeverityMapper; the gateway preserves the native
  // MXAccess scale.
  int32 severity = 5;

  // When the alarm originally entered the active state. Preserved across
  // acknowledge transitions so the Part 9 condition keeps the original raise
  // time. Unset on retrigger from a previously-cleared condition.
  google.protobuf.Timestamp original_raise_timestamp = 6;

  // When this specific transition occurred (raise time on Raise, ack time on
  // Acknowledge, clear time on Clear).
  google.protobuf.Timestamp transition_timestamp = 7;

  // Operator principal recorded by MXAccess on Acknowledge transitions.
  // Empty on raise / clear.
  string operator_user = 8;

  // Operator-supplied comment recorded by MXAccess on Acknowledge transitions.
  // Empty on raise / clear or when no comment was supplied.
  string operator_comment = 9;

  // MxAccess alarm category (taxonomy bucket configured in the Galaxy
  // template, e.g. "Process", "Safety", "Diagnostics").
  string category = 10;

  // Human-readable alarm description from the MxAccess alarm definition.
  string description = 11;

  // Current alarm value (the value of the source attribute at the moment of
  // transition). Optional; populated when MxAccess surfaces it.
  MxValue current_value = 12;

  // Limit/threshold value that triggered the transition for limit alarms.
  // Optional; populated for AnalogLimitAlarm-family transitions.
  MxValue limit_value = 13;

  // True when this transition came from the subtag-monitoring fallback rather
  // than the native alarmmgr provider — synthesized from data changes, reduced
  // fidelity (synthetic GUID, no native raise time).
  bool degraded = 14;
  // Which provider produced this transition.
  AlarmProviderMode source_provider = 15;
}

message OnAlarmProviderModeChangedEvent {
  AlarmProviderMode mode = 1;
  string reason = 2;
  int32 hresult = 3;   // COM HRESULT that triggered failover; 0 on failback
  google.protobuf.Timestamp at = 4;
}

enum AlarmTransitionKind {
  ALARM_TRANSITION_KIND_UNSPECIFIED = 0;
  ALARM_TRANSITION_KIND_RAISE = 1;
  ALARM_TRANSITION_KIND_ACKNOWLEDGE = 2;
  ALARM_TRANSITION_KIND_CLEAR = 3;
  ALARM_TRANSITION_KIND_RETRIGGER = 4;
}

// Snapshot of a currently-active MXAccess alarm condition, returned from a
// QueryActiveAlarms ConditionRefresh stream.
message ActiveAlarmSnapshot {
  string alarm_full_reference = 1;
  string source_object_reference = 2;
  string alarm_type_name = 3;
  int32 severity = 4;
  google.protobuf.Timestamp original_raise_timestamp = 5;
  AlarmConditionState current_state = 6;
  string category = 7;
  string description = 8;
  // When the most recent state transition occurred (last raise, last ack,
  // last clear).
  google.protobuf.Timestamp last_transition_timestamp = 9;
  // Operator who acknowledged the alarm if the current state is ActiveAcked.
  // Empty otherwise.
  string operator_user = 10;
  // Operator comment recorded with the most recent acknowledge if the current
  // state is ActiveAcked. Empty otherwise.
  string operator_comment = 11;
  MxValue current_value = 12;
  MxValue limit_value = 13;
  // True when this snapshot came from the subtag-monitoring fallback rather
  // than the native alarmmgr provider — synthesized from data changes, reduced
  // fidelity (synthetic GUID, no native raise time). Mirrors
  // OnAlarmTransitionEvent.degraded.
  bool degraded = 14;
  // Which provider produced this snapshot. Mirrors
  // OnAlarmTransitionEvent.source_provider; always ALARMMGR or SUBTAG on the
  // wire (never UNSPECIFIED).
  AlarmProviderMode source_provider = 15;
  // True when the provider fetch that produced this snapshot hit the per-fetch
  // cap: the snapshot set may omit active alarms, and the worker suspended its
  // absence-implies-Clear inference for that poll. Says nothing about THIS
  // record's fidelity — the record is as accurate as any other; it flags that
  // the set it belongs to is possibly incomplete. QueryActiveAlarms returns a
  // bare `stream ActiveAlarmSnapshot` with no envelope message, so a per-record
  // boolean is the only additive way to carry set-level degraded status on that
  // RPC. Distinct from `degraded`, which is about the subtag fallback provider.
  // Additive (proto3): clients that ignore it deserialize the stream unchanged.
  bool from_truncated_snapshot = 16;
}

enum AlarmConditionState {
  ALARM_CONDITION_STATE_UNSPECIFIED = 0;
  ALARM_CONDITION_STATE_ACTIVE = 1;
  ALARM_CONDITION_STATE_ACTIVE_ACKED = 2;
  ALARM_CONDITION_STATE_INACTIVE = 3;
}

message AcknowledgeAlarmRequest {
  // Retired: acknowledgement is session-less — it routes to the gateway's
  // central alarm monitor, not a client worker session.
  reserved 1;
  reserved "session_id";
  string client_correlation_id = 2;
  // Fully-qualified alarm reference matching OnAlarmTransitionEvent.alarm_full_reference.
  string alarm_full_reference = 3;
  // Operator-supplied comment forwarded to MXAccess.
  string comment = 4;
  // Operator principal performing the acknowledgement. The lmxopcua side
  // resolves this from the OPC UA session prior to invoking the RPC.
  string operator_user = 5;
}

message AcknowledgeAlarmReply {
  // Retired: see AcknowledgeAlarmRequest — acknowledgement is session-less.
  reserved 1;
  reserved "session_id";
  string correlation_id = 2;
  ProtocolStatus protocol_status = 3;
  // Native ack return code echoed from the worker. The worker carries the
  // ack outcome as a single int32 (AcknowledgeAlarmReplyPayload.native_status,
  // = AlarmAckByName / AlarmAckByGUID return code; 0 = success); the gateway's
  // WorkerAlarmRpcDispatcher copies that value here. This is the authoritative
  // ack-outcome field for the public RPC. Absent only when the worker reply
  // omitted the value entirely (a protocol violation).
  optional int32 hresult = 4;
  // Reserved for a structured MxStatusProxy view of the ack outcome. The
  // worker by-name/by-GUID ack path produces only the int32 return code
  // (see `hresult`), so the current gateway leaves this field UNSET on every
  // reply. Clients must read `hresult` (and `protocol_status`) for the ack
  // result and must not depend on `status` being populated.
  MxStatusProxy status = 5;
  string diagnostic_message = 6;
}

// Request to attach to the gateway's central alarm feed (StreamAlarms).
message StreamAlarmsRequest {
  string client_correlation_id = 1;
  // Optional alarm-reference prefix scoping the feed to an equipment
  // sub-tree. Empty streams every active alarm.
  string alarm_filter_prefix = 2;
}

// One message on the StreamAlarms feed. The stream opens with a
// `provider_status` (the current provider mode), then a `snapshot_status`
// (whether the cached active set may be incomplete), then one `active_alarm`
// per currently-active alarm, then a single `snapshot_complete`. After that
// come the live frames: a `transition` for every subsequent alarm change,
// interleaved with a further `provider_status` on each failover/failback and a
// further `snapshot_status` on each change of the truncation verdict.
message AlarmFeedMessage {
  oneof payload {
    // Part of the initial active-alarm snapshot (ConditionRefresh).
    ActiveAlarmSnapshot active_alarm = 1;
    // Sentinel: the initial snapshot is fully delivered and `transition`
    // messages follow. Always true when present.
    bool snapshot_complete = 2;
    // A live alarm state change (raise / acknowledge / clear).
    OnAlarmTransitionEvent transition = 3;
    // Provider-mode status. Emitted once on stream open and again on every
    // failover/failback so late joiners learn the current mode immediately.
    AlarmProviderStatus provider_status = 4;
    // Snapshot-completeness status. Emitted once on stream open and again on
    // every change of the truncation verdict, so late joiners learn whether the
    // feed's active-alarm set may be incomplete.
    AlarmSnapshotStatus snapshot_status = 5;
  }
}

message AlarmProviderStatus {
  AlarmProviderMode mode = 1;
  bool degraded = 2;            // true whenever mode == SUBTAG
  string reason = 3;            // human-readable switch reason
  google.protobuf.Timestamp since = 4;
}

// Feed-level snapshot-completeness status. Emitted once on StreamAlarms open
// (after the initial provider_status frame, before the cached active_alarm
// frames) so late joiners learn the current verdict, and again on every change
// of the truncation verdict — when a reconcile reports a different verdict, and
// when the gateway's alarm monitor restarts and drops a truncated verdict with
// the cache generation it described (feed subscribers outlive that monitor
// session, so they are sent the clearing frame). Mirrors the per-record
// ActiveAlarmSnapshot.from_truncated_snapshot caveat at feed level so live
// consumers can reason about completeness without polling QueryActiveAlarms.
message AlarmSnapshotStatus {
  // True while the monitor's cached active-alarm set derives from a truncated
  // (capped) worker fetch — the set may be missing alarms. Distinct from
  // provider degradation (AlarmProviderStatus.degraded), which describes the
  // fidelity of the records rather than the completeness of the set.
  bool truncated = 1;
}

message MxStatusProxy {
  // Mirrors the `success` member of the MXAccess MXSTATUS_PROXY struct
  // (a 16-bit signed value in the COM struct, widened to int32 on the
  // wire). Despite the name it is NOT a boolean — it is the raw numeric
  // indicator the worker reads off the COM struct without reinterpretation.
  // It is carried verbatim for diagnostics; the authoritative success/
  // failure of the operation is `category` (MX_STATUS_CATEGORY_OK marks
  // success), with `detail`, `diagnostic_text`, `raw_category`, and
  // `raw_detected_by` describing any non-OK outcome. Clients should branch
  // on `category`, not on a specific `success` value.
  int32 success = 1;
  MxStatusCategory category = 2;
  MxStatusSource detected_by = 3;
  int32 detail = 4;
  int32 raw_category = 5;
  int32 raw_detected_by = 6;
  string diagnostic_text = 7;
}

enum MxStatusCategory {
  MX_STATUS_CATEGORY_UNSPECIFIED = 0;
  MX_STATUS_CATEGORY_UNKNOWN = 1;
  MX_STATUS_CATEGORY_OK = 2;
  MX_STATUS_CATEGORY_PENDING = 3;
  MX_STATUS_CATEGORY_WARNING = 4;
  MX_STATUS_CATEGORY_COMMUNICATION_ERROR = 5;
  MX_STATUS_CATEGORY_CONFIGURATION_ERROR = 6;
  MX_STATUS_CATEGORY_OPERATIONAL_ERROR = 7;
  MX_STATUS_CATEGORY_SECURITY_ERROR = 8;
  MX_STATUS_CATEGORY_SOFTWARE_ERROR = 9;
  MX_STATUS_CATEGORY_OTHER_ERROR = 10;
}

enum MxStatusSource {
  MX_STATUS_SOURCE_UNSPECIFIED = 0;
  MX_STATUS_SOURCE_UNKNOWN = 1;
  MX_STATUS_SOURCE_REQUESTING_LMX = 2;
  MX_STATUS_SOURCE_RESPONDING_LMX = 3;
  MX_STATUS_SOURCE_REQUESTING_NMX = 4;
  MX_STATUS_SOURCE_RESPONDING_NMX = 5;
  MX_STATUS_SOURCE_REQUESTING_AUTOMATION_OBJECT = 6;
  MX_STATUS_SOURCE_RESPONDING_AUTOMATION_OBJECT = 7;
}

message MxValue {
  MxDataType data_type = 1;
  string variant_type = 2;
  bool is_null = 3;
  string raw_diagnostic = 4;
  int32 raw_data_type = 5;

  oneof kind {
    bool bool_value = 10;
    int32 int32_value = 11;
    int64 int64_value = 12;
    float float_value = 13;
    double double_value = 14;
    string string_value = 15;
    google.protobuf.Timestamp timestamp_value = 16;
    MxArray array_value = 17;
    bytes raw_value = 18;
    MxSparseArray sparse_array_value = 19;
  }
}

message MxArray {
  MxDataType element_data_type = 1;
  string variant_type = 2;
  repeated uint32 dimensions = 3;
  string raw_diagnostic = 4;
  int32 raw_element_data_type = 5;

  oneof values {
    BoolArray bool_values = 10;
    Int32Array int32_values = 11;
    Int64Array int64_values = 12;
    FloatArray float_values = 13;
    DoubleArray double_values = 14;
    StringArray string_values = 15;
    TimestampArray timestamp_values = 16;
    RawArray raw_values = 17;
  }
}

// Write-only sparse array value. The gateway expands this into a full,
// default-filled MxArray before forwarding to the worker; the worker never
// receives or produces it. Unmentioned indices take the element type's
// default (reset, NOT preserved).
message MxSparseArray {
  MxDataType element_data_type = 1;
  uint32 total_length = 2;
  repeated MxSparseElement elements = 3;
}

message MxSparseElement {
  uint32 index = 1;
  MxValue value = 2; // scalar
}

message BoolArray {
  repeated bool values = 1;
}

message Int32Array {
  repeated int32 values = 1;
}

message Int64Array {
  repeated int64 values = 1;
}

message FloatArray {
  repeated float values = 1;
}

message DoubleArray {
  repeated double values = 1;
}

message StringArray {
  repeated string values = 1;
}

message TimestampArray {
  repeated google.protobuf.Timestamp values = 1;
}

message RawArray {
  repeated bytes values = 1;
}

enum MxDataType {
  MX_DATA_TYPE_UNSPECIFIED = 0;
  MX_DATA_TYPE_UNKNOWN = 1;
  MX_DATA_TYPE_NO_DATA = 2;
  MX_DATA_TYPE_BOOLEAN = 3;
  MX_DATA_TYPE_INTEGER = 4;
  MX_DATA_TYPE_FLOAT = 5;
  MX_DATA_TYPE_DOUBLE = 6;
  MX_DATA_TYPE_STRING = 7;
  MX_DATA_TYPE_TIME = 8;
  MX_DATA_TYPE_ELAPSED_TIME = 9;
  MX_DATA_TYPE_REFERENCE_TYPE = 10;
  MX_DATA_TYPE_STATUS_TYPE = 11;
  MX_DATA_TYPE_ENUM = 12;
  MX_DATA_TYPE_SECURITY_CLASSIFICATION_ENUM = 13;
  MX_DATA_TYPE_DATA_QUALITY_TYPE = 14;
  MX_DATA_TYPE_QUALIFIED_ENUM = 15;
  MX_DATA_TYPE_QUALIFIED_STRUCT = 16;
  MX_DATA_TYPE_INTERNATIONALIZED_STRING = 17;
  MX_DATA_TYPE_BIG_STRING = 18;
  MX_DATA_TYPE_END = 19;
}

message ProtocolStatus {
  ProtocolStatusCode code = 1;
  string message = 2;
}

enum ProtocolStatusCode {
  PROTOCOL_STATUS_CODE_UNSPECIFIED = 0;
  PROTOCOL_STATUS_CODE_OK = 1;
  PROTOCOL_STATUS_CODE_INVALID_REQUEST = 2;
  PROTOCOL_STATUS_CODE_SESSION_NOT_FOUND = 3;
  PROTOCOL_STATUS_CODE_SESSION_NOT_READY = 4;
  PROTOCOL_STATUS_CODE_WORKER_UNAVAILABLE = 5;
  PROTOCOL_STATUS_CODE_TIMEOUT = 6;
  PROTOCOL_STATUS_CODE_CANCELED = 7;
  PROTOCOL_STATUS_CODE_PROTOCOL_VIOLATION = 8;
  PROTOCOL_STATUS_CODE_MXACCESS_FAILURE = 9;
}

enum SessionState {
  SESSION_STATE_UNSPECIFIED = 0;
  SESSION_STATE_CREATING = 1;
  SESSION_STATE_STARTING_WORKER = 2;
  SESSION_STATE_WAITING_FOR_PIPE = 3;
  SESSION_STATE_HANDSHAKING = 4;
  SESSION_STATE_INITIALIZING_WORKER = 5;
  SESSION_STATE_READY = 6;
  SESSION_STATE_CLOSING = 7;
  SESSION_STATE_CLOSED = 8;
  SESSION_STATE_FAULTED = 9;
}
