Compare commits
10 Commits
1ea6f60ea2
...
90331b622c
| Author | SHA1 | Date | |
|---|---|---|---|
| 90331b622c | |||
| ad30adca2f | |||
| 4d4f443c27 | |||
| c7483615cf | |||
| d13144a9d7 | |||
| 155c9d619f | |||
| 51b0a8f55f | |||
| e50ec493cb | |||
| 312e072070 | |||
| fab600d3b0 |
@@ -156,6 +156,16 @@ poll. Treat the set as possibly incomplete rather than reconciling deletions
|
||||
from it. It is set-level degraded status, not a comment on the record's own
|
||||
fidelity, and is distinct from `Degraded` (the subtag fallback provider).
|
||||
|
||||
`StreamAlarmsAsync` also carries that completeness verdict at feed level, as an
|
||||
`AlarmFeedMessage.PayloadOneofCase.SnapshotStatus` frame whose
|
||||
`SnapshotStatus.Truncated` is true while the monitor's cached set derives from a
|
||||
truncated fetch. One arrives at stream open (after the `ProviderStatus` frame,
|
||||
before the cached `ActiveAlarm` frames) so a late joiner learns the current
|
||||
verdict, then one on every verdict change — including the clearing frame sent
|
||||
when the gateway's alarm monitor restarts and drops a truncated verdict. Track
|
||||
it if you need set completeness on a live feed without polling
|
||||
`QueryActiveAlarmsAsync`.
|
||||
|
||||
`MxGatewaySession.CloseAsync` is explicit and idempotent. Repeated calls return
|
||||
the first `CloseSessionReply` instead of sending another close request.
|
||||
|
||||
|
||||
@@ -1546,8 +1546,9 @@ public static class MxGatewayClientCli
|
||||
/// <summary>
|
||||
/// Renders one <see cref="AlarmFeedMessage"/> for the human-readable
|
||||
/// (non-JSON) stream-alarms output, distinguishing the <c>payload</c> oneof
|
||||
/// arms: a snapshot active alarm, the snapshot-complete sentinel, or a live
|
||||
/// transition.
|
||||
/// arms: a snapshot active alarm, the snapshot-complete sentinel, a live
|
||||
/// transition, the provider-mode status, or the feed-level
|
||||
/// snapshot-completeness status.
|
||||
/// </summary>
|
||||
private static string FormatAlarmFeedMessage(AlarmFeedMessage feedMessage)
|
||||
{
|
||||
@@ -1559,6 +1560,10 @@ public static class MxGatewayClientCli
|
||||
$"snapshot-complete {feedMessage.SnapshotComplete}",
|
||||
AlarmFeedMessage.PayloadOneofCase.Transition =>
|
||||
$"transition {ProtobufJsonFormatter.Format(feedMessage.Transition)}",
|
||||
AlarmFeedMessage.PayloadOneofCase.ProviderStatus =>
|
||||
$"provider-status {ProtobufJsonFormatter.Format(feedMessage.ProviderStatus)}",
|
||||
AlarmFeedMessage.PayloadOneofCase.SnapshotStatus =>
|
||||
$"snapshot-status {ProtobufJsonFormatter.Format(feedMessage.SnapshotStatus)}",
|
||||
_ => $"unknown-payload {feedMessage.PayloadCase}",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -153,6 +153,15 @@ deletions from it. It is set-level degraded status, not a comment on the
|
||||
record's own fidelity, and is distinct from `Degraded` (the subtag fallback
|
||||
provider).
|
||||
|
||||
`StreamAlarms` also carries that completeness verdict at feed level, as a frame
|
||||
whose `GetSnapshotStatus()` is non-nil and whose `GetTruncated()` is true while
|
||||
the monitor's cached set derives from a truncated fetch. One arrives at stream
|
||||
open (after the `GetProviderStatus()` frame, before the cached
|
||||
`GetActiveAlarm()` frames) so a late joiner learns the current verdict, then one
|
||||
on every verdict change — including the clearing frame sent when the gateway's
|
||||
alarm monitor restarts and drops a truncated verdict. Track it if you need set
|
||||
completeness on a live feed without polling `QueryActiveAlarms`.
|
||||
|
||||
## Write Semantics And Common Pitfalls
|
||||
|
||||
These are MXAccess parity behaviors that surprise new callers. The gateway
|
||||
|
||||
@@ -1097,7 +1097,8 @@ func runStreamAlarms(ctx context.Context, args []string, stdout, stderr io.Write
|
||||
|
||||
// formatAlarmFeedMessage renders one AlarmFeedMessage in the CLI's plain-text
|
||||
// output style, distinguishing the active-alarm snapshot, snapshot-complete
|
||||
// sentinel, and transition cases of the message's payload oneof.
|
||||
// sentinel, transition, provider-status, and snapshot-status cases of the
|
||||
// message's payload oneof.
|
||||
func formatAlarmFeedMessage(message *mxgateway.AlarmFeedMessage) string {
|
||||
switch {
|
||||
case message.GetActiveAlarm() != nil:
|
||||
@@ -1108,6 +1109,11 @@ func formatAlarmFeedMessage(message *mxgateway.AlarmFeedMessage) string {
|
||||
case message.GetTransition() != nil:
|
||||
transition := message.GetTransition()
|
||||
return fmt.Sprintf("transition %s kind=%s severity=%d", transition.GetAlarmFullReference(), transition.GetTransitionKind(), transition.GetSeverity())
|
||||
case message.GetProviderStatus() != nil:
|
||||
status := message.GetProviderStatus()
|
||||
return fmt.Sprintf("provider-status mode=%s degraded=%t reason=%q", status.GetMode(), status.GetDegraded(), status.GetReason())
|
||||
case message.GetSnapshotStatus() != nil:
|
||||
return fmt.Sprintf("snapshot-status truncated=%t", message.GetSnapshotStatus().GetTruncated())
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
@@ -7353,6 +7353,7 @@ type AlarmFeedMessage struct {
|
||||
// *AlarmFeedMessage_SnapshotComplete
|
||||
// *AlarmFeedMessage_Transition
|
||||
// *AlarmFeedMessage_ProviderStatus
|
||||
// *AlarmFeedMessage_SnapshotStatus
|
||||
Payload isAlarmFeedMessage_Payload `protobuf_oneof:"payload"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -7431,6 +7432,15 @@ func (x *AlarmFeedMessage) GetProviderStatus() *AlarmProviderStatus {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *AlarmFeedMessage) GetSnapshotStatus() *AlarmSnapshotStatus {
|
||||
if x != nil {
|
||||
if x, ok := x.Payload.(*AlarmFeedMessage_SnapshotStatus); ok {
|
||||
return x.SnapshotStatus
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type isAlarmFeedMessage_Payload interface {
|
||||
isAlarmFeedMessage_Payload()
|
||||
}
|
||||
@@ -7457,6 +7467,13 @@ type AlarmFeedMessage_ProviderStatus struct {
|
||||
ProviderStatus *AlarmProviderStatus `protobuf:"bytes,4,opt,name=provider_status,json=providerStatus,proto3,oneof"`
|
||||
}
|
||||
|
||||
type AlarmFeedMessage_SnapshotStatus struct {
|
||||
// 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.
|
||||
SnapshotStatus *AlarmSnapshotStatus `protobuf:"bytes,5,opt,name=snapshot_status,json=snapshotStatus,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*AlarmFeedMessage_ActiveAlarm) isAlarmFeedMessage_Payload() {}
|
||||
|
||||
func (*AlarmFeedMessage_SnapshotComplete) isAlarmFeedMessage_Payload() {}
|
||||
@@ -7465,6 +7482,8 @@ func (*AlarmFeedMessage_Transition) isAlarmFeedMessage_Payload() {}
|
||||
|
||||
func (*AlarmFeedMessage_ProviderStatus) isAlarmFeedMessage_Payload() {}
|
||||
|
||||
func (*AlarmFeedMessage_SnapshotStatus) isAlarmFeedMessage_Payload() {}
|
||||
|
||||
type AlarmProviderStatus struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Mode AlarmProviderMode `protobuf:"varint,1,opt,name=mode,proto3,enum=mxaccess_gateway.v1.AlarmProviderMode" json:"mode,omitempty"`
|
||||
@@ -7533,6 +7552,63 @@ func (x *AlarmProviderStatus) GetSince() *timestamppb.Timestamp {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
type AlarmSnapshotStatus struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// 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.
|
||||
Truncated bool `protobuf:"varint,1,opt,name=truncated,proto3" json:"truncated,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *AlarmSnapshotStatus) Reset() {
|
||||
*x = AlarmSnapshotStatus{}
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[87]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *AlarmSnapshotStatus) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*AlarmSnapshotStatus) ProtoMessage() {}
|
||||
|
||||
func (x *AlarmSnapshotStatus) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[87]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use AlarmSnapshotStatus.ProtoReflect.Descriptor instead.
|
||||
func (*AlarmSnapshotStatus) Descriptor() ([]byte, []int) {
|
||||
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{87}
|
||||
}
|
||||
|
||||
func (x *AlarmSnapshotStatus) GetTruncated() bool {
|
||||
if x != nil {
|
||||
return x.Truncated
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type MxStatusProxy struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// Mirrors the `success` member of the MXAccess MXSTATUS_PROXY struct
|
||||
@@ -7557,7 +7633,7 @@ type MxStatusProxy struct {
|
||||
|
||||
func (x *MxStatusProxy) Reset() {
|
||||
*x = MxStatusProxy{}
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[87]
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[88]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -7569,7 +7645,7 @@ func (x *MxStatusProxy) String() string {
|
||||
func (*MxStatusProxy) ProtoMessage() {}
|
||||
|
||||
func (x *MxStatusProxy) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[87]
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[88]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -7582,7 +7658,7 @@ func (x *MxStatusProxy) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use MxStatusProxy.ProtoReflect.Descriptor instead.
|
||||
func (*MxStatusProxy) Descriptor() ([]byte, []int) {
|
||||
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{87}
|
||||
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{88}
|
||||
}
|
||||
|
||||
func (x *MxStatusProxy) GetSuccess() int32 {
|
||||
@@ -7660,7 +7736,7 @@ type MxValue struct {
|
||||
|
||||
func (x *MxValue) Reset() {
|
||||
*x = MxValue{}
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[88]
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[89]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -7672,7 +7748,7 @@ func (x *MxValue) String() string {
|
||||
func (*MxValue) ProtoMessage() {}
|
||||
|
||||
func (x *MxValue) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[88]
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[89]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -7685,7 +7761,7 @@ func (x *MxValue) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use MxValue.ProtoReflect.Descriptor instead.
|
||||
func (*MxValue) Descriptor() ([]byte, []int) {
|
||||
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{88}
|
||||
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{89}
|
||||
}
|
||||
|
||||
func (x *MxValue) GetDataType() MxDataType {
|
||||
@@ -7908,7 +7984,7 @@ type MxArray struct {
|
||||
|
||||
func (x *MxArray) Reset() {
|
||||
*x = MxArray{}
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[89]
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[90]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -7920,7 +7996,7 @@ func (x *MxArray) String() string {
|
||||
func (*MxArray) ProtoMessage() {}
|
||||
|
||||
func (x *MxArray) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[89]
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[90]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -7933,7 +8009,7 @@ func (x *MxArray) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use MxArray.ProtoReflect.Descriptor instead.
|
||||
func (*MxArray) Descriptor() ([]byte, []int) {
|
||||
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{89}
|
||||
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{90}
|
||||
}
|
||||
|
||||
func (x *MxArray) GetElementDataType() MxDataType {
|
||||
@@ -8117,7 +8193,7 @@ type MxSparseArray struct {
|
||||
|
||||
func (x *MxSparseArray) Reset() {
|
||||
*x = MxSparseArray{}
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[90]
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[91]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -8129,7 +8205,7 @@ func (x *MxSparseArray) String() string {
|
||||
func (*MxSparseArray) ProtoMessage() {}
|
||||
|
||||
func (x *MxSparseArray) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[90]
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[91]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -8142,7 +8218,7 @@ func (x *MxSparseArray) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use MxSparseArray.ProtoReflect.Descriptor instead.
|
||||
func (*MxSparseArray) Descriptor() ([]byte, []int) {
|
||||
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{90}
|
||||
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{91}
|
||||
}
|
||||
|
||||
func (x *MxSparseArray) GetElementDataType() MxDataType {
|
||||
@@ -8176,7 +8252,7 @@ type MxSparseElement struct {
|
||||
|
||||
func (x *MxSparseElement) Reset() {
|
||||
*x = MxSparseElement{}
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[91]
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[92]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -8188,7 +8264,7 @@ func (x *MxSparseElement) String() string {
|
||||
func (*MxSparseElement) ProtoMessage() {}
|
||||
|
||||
func (x *MxSparseElement) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[91]
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[92]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -8201,7 +8277,7 @@ func (x *MxSparseElement) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use MxSparseElement.ProtoReflect.Descriptor instead.
|
||||
func (*MxSparseElement) Descriptor() ([]byte, []int) {
|
||||
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{91}
|
||||
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{92}
|
||||
}
|
||||
|
||||
func (x *MxSparseElement) GetIndex() uint32 {
|
||||
@@ -8227,7 +8303,7 @@ type BoolArray struct {
|
||||
|
||||
func (x *BoolArray) Reset() {
|
||||
*x = BoolArray{}
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[92]
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[93]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -8239,7 +8315,7 @@ func (x *BoolArray) String() string {
|
||||
func (*BoolArray) ProtoMessage() {}
|
||||
|
||||
func (x *BoolArray) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[92]
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[93]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -8252,7 +8328,7 @@ func (x *BoolArray) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use BoolArray.ProtoReflect.Descriptor instead.
|
||||
func (*BoolArray) Descriptor() ([]byte, []int) {
|
||||
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{92}
|
||||
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{93}
|
||||
}
|
||||
|
||||
func (x *BoolArray) GetValues() []bool {
|
||||
@@ -8271,7 +8347,7 @@ type Int32Array struct {
|
||||
|
||||
func (x *Int32Array) Reset() {
|
||||
*x = Int32Array{}
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[93]
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[94]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -8283,7 +8359,7 @@ func (x *Int32Array) String() string {
|
||||
func (*Int32Array) ProtoMessage() {}
|
||||
|
||||
func (x *Int32Array) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[93]
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[94]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -8296,7 +8372,7 @@ func (x *Int32Array) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use Int32Array.ProtoReflect.Descriptor instead.
|
||||
func (*Int32Array) Descriptor() ([]byte, []int) {
|
||||
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{93}
|
||||
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{94}
|
||||
}
|
||||
|
||||
func (x *Int32Array) GetValues() []int32 {
|
||||
@@ -8315,7 +8391,7 @@ type Int64Array struct {
|
||||
|
||||
func (x *Int64Array) Reset() {
|
||||
*x = Int64Array{}
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[94]
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[95]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -8327,7 +8403,7 @@ func (x *Int64Array) String() string {
|
||||
func (*Int64Array) ProtoMessage() {}
|
||||
|
||||
func (x *Int64Array) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[94]
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[95]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -8340,7 +8416,7 @@ func (x *Int64Array) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use Int64Array.ProtoReflect.Descriptor instead.
|
||||
func (*Int64Array) Descriptor() ([]byte, []int) {
|
||||
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{94}
|
||||
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{95}
|
||||
}
|
||||
|
||||
func (x *Int64Array) GetValues() []int64 {
|
||||
@@ -8359,7 +8435,7 @@ type FloatArray struct {
|
||||
|
||||
func (x *FloatArray) Reset() {
|
||||
*x = FloatArray{}
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[95]
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[96]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -8371,7 +8447,7 @@ func (x *FloatArray) String() string {
|
||||
func (*FloatArray) ProtoMessage() {}
|
||||
|
||||
func (x *FloatArray) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[95]
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[96]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -8384,7 +8460,7 @@ func (x *FloatArray) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use FloatArray.ProtoReflect.Descriptor instead.
|
||||
func (*FloatArray) Descriptor() ([]byte, []int) {
|
||||
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{95}
|
||||
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{96}
|
||||
}
|
||||
|
||||
func (x *FloatArray) GetValues() []float32 {
|
||||
@@ -8403,7 +8479,7 @@ type DoubleArray struct {
|
||||
|
||||
func (x *DoubleArray) Reset() {
|
||||
*x = DoubleArray{}
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[96]
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[97]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -8415,7 +8491,7 @@ func (x *DoubleArray) String() string {
|
||||
func (*DoubleArray) ProtoMessage() {}
|
||||
|
||||
func (x *DoubleArray) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[96]
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[97]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -8428,7 +8504,7 @@ func (x *DoubleArray) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use DoubleArray.ProtoReflect.Descriptor instead.
|
||||
func (*DoubleArray) Descriptor() ([]byte, []int) {
|
||||
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{96}
|
||||
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{97}
|
||||
}
|
||||
|
||||
func (x *DoubleArray) GetValues() []float64 {
|
||||
@@ -8447,7 +8523,7 @@ type StringArray struct {
|
||||
|
||||
func (x *StringArray) Reset() {
|
||||
*x = StringArray{}
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[97]
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[98]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -8459,7 +8535,7 @@ func (x *StringArray) String() string {
|
||||
func (*StringArray) ProtoMessage() {}
|
||||
|
||||
func (x *StringArray) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[97]
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[98]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -8472,7 +8548,7 @@ func (x *StringArray) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use StringArray.ProtoReflect.Descriptor instead.
|
||||
func (*StringArray) Descriptor() ([]byte, []int) {
|
||||
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{97}
|
||||
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{98}
|
||||
}
|
||||
|
||||
func (x *StringArray) GetValues() []string {
|
||||
@@ -8491,7 +8567,7 @@ type TimestampArray struct {
|
||||
|
||||
func (x *TimestampArray) Reset() {
|
||||
*x = TimestampArray{}
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[98]
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[99]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -8503,7 +8579,7 @@ func (x *TimestampArray) String() string {
|
||||
func (*TimestampArray) ProtoMessage() {}
|
||||
|
||||
func (x *TimestampArray) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[98]
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[99]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -8516,7 +8592,7 @@ func (x *TimestampArray) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use TimestampArray.ProtoReflect.Descriptor instead.
|
||||
func (*TimestampArray) Descriptor() ([]byte, []int) {
|
||||
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{98}
|
||||
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{99}
|
||||
}
|
||||
|
||||
func (x *TimestampArray) GetValues() []*timestamppb.Timestamp {
|
||||
@@ -8535,7 +8611,7 @@ type RawArray struct {
|
||||
|
||||
func (x *RawArray) Reset() {
|
||||
*x = RawArray{}
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[99]
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[100]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -8547,7 +8623,7 @@ func (x *RawArray) String() string {
|
||||
func (*RawArray) ProtoMessage() {}
|
||||
|
||||
func (x *RawArray) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[99]
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[100]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -8560,7 +8636,7 @@ func (x *RawArray) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use RawArray.ProtoReflect.Descriptor instead.
|
||||
func (*RawArray) Descriptor() ([]byte, []int) {
|
||||
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{99}
|
||||
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{100}
|
||||
}
|
||||
|
||||
func (x *RawArray) GetValues() [][]byte {
|
||||
@@ -8580,7 +8656,7 @@ type ProtocolStatus struct {
|
||||
|
||||
func (x *ProtocolStatus) Reset() {
|
||||
*x = ProtocolStatus{}
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[100]
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[101]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -8592,7 +8668,7 @@ func (x *ProtocolStatus) String() string {
|
||||
func (*ProtocolStatus) ProtoMessage() {}
|
||||
|
||||
func (x *ProtocolStatus) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[100]
|
||||
mi := &file_mxaccess_gateway_proto_msgTypes[101]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -8605,7 +8681,7 @@ func (x *ProtocolStatus) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use ProtocolStatus.ProtoReflect.Descriptor instead.
|
||||
func (*ProtocolStatus) Descriptor() ([]byte, []int) {
|
||||
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{100}
|
||||
return file_mxaccess_gateway_proto_rawDescGZIP(), []int{101}
|
||||
}
|
||||
|
||||
func (x *ProtocolStatus) GetCode() ProtocolStatusCode {
|
||||
@@ -9115,20 +9191,23 @@ const file_mxaccess_gateway_proto_rawDesc = "" +
|
||||
"session_id\"y\n" +
|
||||
"\x13StreamAlarmsRequest\x122\n" +
|
||||
"\x15client_correlation_id\x18\x01 \x01(\tR\x13clientCorrelationId\x12.\n" +
|
||||
"\x13alarm_filter_prefix\x18\x02 \x01(\tR\x11alarmFilterPrefix\"\xbf\x02\n" +
|
||||
"\x13alarm_filter_prefix\x18\x02 \x01(\tR\x11alarmFilterPrefix\"\x94\x03\n" +
|
||||
"\x10AlarmFeedMessage\x12M\n" +
|
||||
"\factive_alarm\x18\x01 \x01(\v2(.mxaccess_gateway.v1.ActiveAlarmSnapshotH\x00R\vactiveAlarm\x12-\n" +
|
||||
"\x11snapshot_complete\x18\x02 \x01(\bH\x00R\x10snapshotComplete\x12M\n" +
|
||||
"\n" +
|
||||
"transition\x18\x03 \x01(\v2+.mxaccess_gateway.v1.OnAlarmTransitionEventH\x00R\n" +
|
||||
"transition\x12S\n" +
|
||||
"\x0fprovider_status\x18\x04 \x01(\v2(.mxaccess_gateway.v1.AlarmProviderStatusH\x00R\x0eproviderStatusB\t\n" +
|
||||
"\x0fprovider_status\x18\x04 \x01(\v2(.mxaccess_gateway.v1.AlarmProviderStatusH\x00R\x0eproviderStatus\x12S\n" +
|
||||
"\x0fsnapshot_status\x18\x05 \x01(\v2(.mxaccess_gateway.v1.AlarmSnapshotStatusH\x00R\x0esnapshotStatusB\t\n" +
|
||||
"\apayload\"\xb7\x01\n" +
|
||||
"\x13AlarmProviderStatus\x12:\n" +
|
||||
"\x04mode\x18\x01 \x01(\x0e2&.mxaccess_gateway.v1.AlarmProviderModeR\x04mode\x12\x1a\n" +
|
||||
"\bdegraded\x18\x02 \x01(\bR\bdegraded\x12\x16\n" +
|
||||
"\x06reason\x18\x03 \x01(\tR\x06reason\x120\n" +
|
||||
"\x05since\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\x05since\"\xbe\x02\n" +
|
||||
"\x05since\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\x05since\"3\n" +
|
||||
"\x13AlarmSnapshotStatus\x12\x1c\n" +
|
||||
"\ttruncated\x18\x01 \x01(\bR\ttruncated\"\xbe\x02\n" +
|
||||
"\rMxStatusProxy\x12\x18\n" +
|
||||
"\asuccess\x18\x01 \x01(\x05R\asuccess\x12A\n" +
|
||||
"\bcategory\x18\x02 \x01(\x0e2%.mxaccess_gateway.v1.MxStatusCategoryR\bcategory\x12D\n" +
|
||||
@@ -9364,7 +9443,7 @@ func file_mxaccess_gateway_proto_rawDescGZIP() []byte {
|
||||
}
|
||||
|
||||
var file_mxaccess_gateway_proto_enumTypes = make([]protoimpl.EnumInfo, 10)
|
||||
var file_mxaccess_gateway_proto_msgTypes = make([]protoimpl.MessageInfo, 101)
|
||||
var file_mxaccess_gateway_proto_msgTypes = make([]protoimpl.MessageInfo, 102)
|
||||
var file_mxaccess_gateway_proto_goTypes = []any{
|
||||
(MxCommandKind)(0), // 0: mxaccess_gateway.v1.MxCommandKind
|
||||
(AlarmProviderMode)(0), // 1: mxaccess_gateway.v1.AlarmProviderMode
|
||||
@@ -9463,29 +9542,30 @@ var file_mxaccess_gateway_proto_goTypes = []any{
|
||||
(*StreamAlarmsRequest)(nil), // 94: mxaccess_gateway.v1.StreamAlarmsRequest
|
||||
(*AlarmFeedMessage)(nil), // 95: mxaccess_gateway.v1.AlarmFeedMessage
|
||||
(*AlarmProviderStatus)(nil), // 96: mxaccess_gateway.v1.AlarmProviderStatus
|
||||
(*MxStatusProxy)(nil), // 97: mxaccess_gateway.v1.MxStatusProxy
|
||||
(*MxValue)(nil), // 98: mxaccess_gateway.v1.MxValue
|
||||
(*MxArray)(nil), // 99: mxaccess_gateway.v1.MxArray
|
||||
(*MxSparseArray)(nil), // 100: mxaccess_gateway.v1.MxSparseArray
|
||||
(*MxSparseElement)(nil), // 101: mxaccess_gateway.v1.MxSparseElement
|
||||
(*BoolArray)(nil), // 102: mxaccess_gateway.v1.BoolArray
|
||||
(*Int32Array)(nil), // 103: mxaccess_gateway.v1.Int32Array
|
||||
(*Int64Array)(nil), // 104: mxaccess_gateway.v1.Int64Array
|
||||
(*FloatArray)(nil), // 105: mxaccess_gateway.v1.FloatArray
|
||||
(*DoubleArray)(nil), // 106: mxaccess_gateway.v1.DoubleArray
|
||||
(*StringArray)(nil), // 107: mxaccess_gateway.v1.StringArray
|
||||
(*TimestampArray)(nil), // 108: mxaccess_gateway.v1.TimestampArray
|
||||
(*RawArray)(nil), // 109: mxaccess_gateway.v1.RawArray
|
||||
(*ProtocolStatus)(nil), // 110: mxaccess_gateway.v1.ProtocolStatus
|
||||
(*durationpb.Duration)(nil), // 111: google.protobuf.Duration
|
||||
(*timestamppb.Timestamp)(nil), // 112: google.protobuf.Timestamp
|
||||
(*AlarmSnapshotStatus)(nil), // 97: mxaccess_gateway.v1.AlarmSnapshotStatus
|
||||
(*MxStatusProxy)(nil), // 98: mxaccess_gateway.v1.MxStatusProxy
|
||||
(*MxValue)(nil), // 99: mxaccess_gateway.v1.MxValue
|
||||
(*MxArray)(nil), // 100: mxaccess_gateway.v1.MxArray
|
||||
(*MxSparseArray)(nil), // 101: mxaccess_gateway.v1.MxSparseArray
|
||||
(*MxSparseElement)(nil), // 102: mxaccess_gateway.v1.MxSparseElement
|
||||
(*BoolArray)(nil), // 103: mxaccess_gateway.v1.BoolArray
|
||||
(*Int32Array)(nil), // 104: mxaccess_gateway.v1.Int32Array
|
||||
(*Int64Array)(nil), // 105: mxaccess_gateway.v1.Int64Array
|
||||
(*FloatArray)(nil), // 106: mxaccess_gateway.v1.FloatArray
|
||||
(*DoubleArray)(nil), // 107: mxaccess_gateway.v1.DoubleArray
|
||||
(*StringArray)(nil), // 108: mxaccess_gateway.v1.StringArray
|
||||
(*TimestampArray)(nil), // 109: mxaccess_gateway.v1.TimestampArray
|
||||
(*RawArray)(nil), // 110: mxaccess_gateway.v1.RawArray
|
||||
(*ProtocolStatus)(nil), // 111: mxaccess_gateway.v1.ProtocolStatus
|
||||
(*durationpb.Duration)(nil), // 112: google.protobuf.Duration
|
||||
(*timestamppb.Timestamp)(nil), // 113: google.protobuf.Timestamp
|
||||
}
|
||||
var file_mxaccess_gateway_proto_depIdxs = []int32{
|
||||
111, // 0: mxaccess_gateway.v1.OpenSessionRequest.command_timeout:type_name -> google.protobuf.Duration
|
||||
111, // 1: mxaccess_gateway.v1.OpenSessionReply.default_command_timeout:type_name -> google.protobuf.Duration
|
||||
110, // 2: mxaccess_gateway.v1.OpenSessionReply.protocol_status:type_name -> mxaccess_gateway.v1.ProtocolStatus
|
||||
112, // 0: mxaccess_gateway.v1.OpenSessionRequest.command_timeout:type_name -> google.protobuf.Duration
|
||||
112, // 1: mxaccess_gateway.v1.OpenSessionReply.default_command_timeout:type_name -> google.protobuf.Duration
|
||||
111, // 2: mxaccess_gateway.v1.OpenSessionReply.protocol_status:type_name -> mxaccess_gateway.v1.ProtocolStatus
|
||||
9, // 3: mxaccess_gateway.v1.CloseSessionReply.final_state:type_name -> mxaccess_gateway.v1.SessionState
|
||||
110, // 4: mxaccess_gateway.v1.CloseSessionReply.protocol_status:type_name -> mxaccess_gateway.v1.ProtocolStatus
|
||||
111, // 4: mxaccess_gateway.v1.CloseSessionReply.protocol_status:type_name -> mxaccess_gateway.v1.ProtocolStatus
|
||||
17, // 5: mxaccess_gateway.v1.MxCommandRequest.command:type_name -> mxaccess_gateway.v1.MxCommand
|
||||
0, // 6: mxaccess_gateway.v1.MxCommand.kind:type_name -> mxaccess_gateway.v1.MxCommandKind
|
||||
18, // 7: mxaccess_gateway.v1.MxCommand.register:type_name -> mxaccess_gateway.v1.RegisterCommand
|
||||
@@ -9527,30 +9607,30 @@ var file_mxaccess_gateway_proto_depIdxs = []int32{
|
||||
60, // 43: mxaccess_gateway.v1.MxCommand.get_worker_info:type_name -> mxaccess_gateway.v1.GetWorkerInfoCommand
|
||||
61, // 44: mxaccess_gateway.v1.MxCommand.drain_events:type_name -> mxaccess_gateway.v1.DrainEventsCommand
|
||||
62, // 45: mxaccess_gateway.v1.MxCommand.shutdown_worker:type_name -> mxaccess_gateway.v1.ShutdownWorkerCommand
|
||||
98, // 46: mxaccess_gateway.v1.WriteCommand.value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
98, // 47: mxaccess_gateway.v1.Write2Command.value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
98, // 48: mxaccess_gateway.v1.Write2Command.timestamp_value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
98, // 49: mxaccess_gateway.v1.WriteSecuredCommand.value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
98, // 50: mxaccess_gateway.v1.WriteSecured2Command.value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
98, // 51: mxaccess_gateway.v1.WriteSecured2Command.timestamp_value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
99, // 46: mxaccess_gateway.v1.WriteCommand.value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
99, // 47: mxaccess_gateway.v1.Write2Command.value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
99, // 48: mxaccess_gateway.v1.Write2Command.timestamp_value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
99, // 49: mxaccess_gateway.v1.WriteSecuredCommand.value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
99, // 50: mxaccess_gateway.v1.WriteSecured2Command.value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
99, // 51: mxaccess_gateway.v1.WriteSecured2Command.timestamp_value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
1, // 52: mxaccess_gateway.v1.SubscribeAlarmsCommand.forced_mode:type_name -> mxaccess_gateway.v1.AlarmProviderMode
|
||||
43, // 53: mxaccess_gateway.v1.SubscribeAlarmsCommand.watch_list:type_name -> mxaccess_gateway.v1.AlarmSubtagTarget
|
||||
44, // 54: mxaccess_gateway.v1.SubscribeAlarmsCommand.failover:type_name -> mxaccess_gateway.v1.AlarmFailoverConfig
|
||||
50, // 55: mxaccess_gateway.v1.WriteBulkCommand.entries:type_name -> mxaccess_gateway.v1.WriteBulkEntry
|
||||
98, // 56: mxaccess_gateway.v1.WriteBulkEntry.value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
99, // 56: mxaccess_gateway.v1.WriteBulkEntry.value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
52, // 57: mxaccess_gateway.v1.Write2BulkCommand.entries:type_name -> mxaccess_gateway.v1.Write2BulkEntry
|
||||
98, // 58: mxaccess_gateway.v1.Write2BulkEntry.value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
98, // 59: mxaccess_gateway.v1.Write2BulkEntry.timestamp_value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
99, // 58: mxaccess_gateway.v1.Write2BulkEntry.value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
99, // 59: mxaccess_gateway.v1.Write2BulkEntry.timestamp_value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
54, // 60: mxaccess_gateway.v1.WriteSecuredBulkCommand.entries:type_name -> mxaccess_gateway.v1.WriteSecuredBulkEntry
|
||||
98, // 61: mxaccess_gateway.v1.WriteSecuredBulkEntry.value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
99, // 61: mxaccess_gateway.v1.WriteSecuredBulkEntry.value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
56, // 62: mxaccess_gateway.v1.WriteSecured2BulkCommand.entries:type_name -> mxaccess_gateway.v1.WriteSecured2BulkEntry
|
||||
98, // 63: mxaccess_gateway.v1.WriteSecured2BulkEntry.value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
98, // 64: mxaccess_gateway.v1.WriteSecured2BulkEntry.timestamp_value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
111, // 65: mxaccess_gateway.v1.ShutdownWorkerCommand.grace_period:type_name -> google.protobuf.Duration
|
||||
99, // 63: mxaccess_gateway.v1.WriteSecured2BulkEntry.value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
99, // 64: mxaccess_gateway.v1.WriteSecured2BulkEntry.timestamp_value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
112, // 65: mxaccess_gateway.v1.ShutdownWorkerCommand.grace_period:type_name -> google.protobuf.Duration
|
||||
0, // 66: mxaccess_gateway.v1.MxCommandReply.kind:type_name -> mxaccess_gateway.v1.MxCommandKind
|
||||
110, // 67: mxaccess_gateway.v1.MxCommandReply.protocol_status:type_name -> mxaccess_gateway.v1.ProtocolStatus
|
||||
98, // 68: mxaccess_gateway.v1.MxCommandReply.return_value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
97, // 69: mxaccess_gateway.v1.MxCommandReply.statuses:type_name -> mxaccess_gateway.v1.MxStatusProxy
|
||||
111, // 67: mxaccess_gateway.v1.MxCommandReply.protocol_status:type_name -> mxaccess_gateway.v1.ProtocolStatus
|
||||
99, // 68: mxaccess_gateway.v1.MxCommandReply.return_value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
98, // 69: mxaccess_gateway.v1.MxCommandReply.statuses:type_name -> mxaccess_gateway.v1.MxStatusProxy
|
||||
64, // 70: mxaccess_gateway.v1.MxCommandReply.register:type_name -> mxaccess_gateway.v1.RegisterReply
|
||||
65, // 71: mxaccess_gateway.v1.MxCommandReply.add_item:type_name -> mxaccess_gateway.v1.AddItemReply
|
||||
66, // 72: mxaccess_gateway.v1.MxCommandReply.add_item2:type_name -> mxaccess_gateway.v1.AddItem2Reply
|
||||
@@ -9575,24 +9655,24 @@ var file_mxaccess_gateway_proto_depIdxs = []int32{
|
||||
78, // 91: mxaccess_gateway.v1.MxCommandReply.session_state:type_name -> mxaccess_gateway.v1.SessionStateReply
|
||||
79, // 92: mxaccess_gateway.v1.MxCommandReply.worker_info:type_name -> mxaccess_gateway.v1.WorkerInfoReply
|
||||
80, // 93: mxaccess_gateway.v1.MxCommandReply.drain_events:type_name -> mxaccess_gateway.v1.DrainEventsReply
|
||||
97, // 94: mxaccess_gateway.v1.SuspendReply.status:type_name -> mxaccess_gateway.v1.MxStatusProxy
|
||||
97, // 95: mxaccess_gateway.v1.ActivateReply.status:type_name -> mxaccess_gateway.v1.MxStatusProxy
|
||||
98, // 94: mxaccess_gateway.v1.SuspendReply.status:type_name -> mxaccess_gateway.v1.MxStatusProxy
|
||||
98, // 95: mxaccess_gateway.v1.ActivateReply.status:type_name -> mxaccess_gateway.v1.MxStatusProxy
|
||||
72, // 96: mxaccess_gateway.v1.BulkSubscribeReply.results:type_name -> mxaccess_gateway.v1.SubscribeResult
|
||||
97, // 97: mxaccess_gateway.v1.BulkWriteResult.statuses:type_name -> mxaccess_gateway.v1.MxStatusProxy
|
||||
98, // 97: mxaccess_gateway.v1.BulkWriteResult.statuses:type_name -> mxaccess_gateway.v1.MxStatusProxy
|
||||
74, // 98: mxaccess_gateway.v1.BulkWriteReply.results:type_name -> mxaccess_gateway.v1.BulkWriteResult
|
||||
98, // 99: mxaccess_gateway.v1.BulkReadResult.value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
112, // 100: mxaccess_gateway.v1.BulkReadResult.source_timestamp:type_name -> google.protobuf.Timestamp
|
||||
97, // 101: mxaccess_gateway.v1.BulkReadResult.statuses:type_name -> mxaccess_gateway.v1.MxStatusProxy
|
||||
99, // 99: mxaccess_gateway.v1.BulkReadResult.value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
113, // 100: mxaccess_gateway.v1.BulkReadResult.source_timestamp:type_name -> google.protobuf.Timestamp
|
||||
98, // 101: mxaccess_gateway.v1.BulkReadResult.statuses:type_name -> mxaccess_gateway.v1.MxStatusProxy
|
||||
76, // 102: mxaccess_gateway.v1.BulkReadReply.results:type_name -> mxaccess_gateway.v1.BulkReadResult
|
||||
9, // 103: mxaccess_gateway.v1.SessionStateReply.state:type_name -> mxaccess_gateway.v1.SessionState
|
||||
83, // 104: mxaccess_gateway.v1.DrainEventsReply.events:type_name -> mxaccess_gateway.v1.MxEvent
|
||||
91, // 105: mxaccess_gateway.v1.QueryActiveAlarmsReplyPayload.snapshots:type_name -> mxaccess_gateway.v1.ActiveAlarmSnapshot
|
||||
2, // 106: mxaccess_gateway.v1.MxEvent.family:type_name -> mxaccess_gateway.v1.MxEventFamily
|
||||
98, // 107: mxaccess_gateway.v1.MxEvent.value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
112, // 108: mxaccess_gateway.v1.MxEvent.source_timestamp:type_name -> google.protobuf.Timestamp
|
||||
97, // 109: mxaccess_gateway.v1.MxEvent.statuses:type_name -> mxaccess_gateway.v1.MxStatusProxy
|
||||
112, // 110: mxaccess_gateway.v1.MxEvent.worker_timestamp:type_name -> google.protobuf.Timestamp
|
||||
112, // 111: mxaccess_gateway.v1.MxEvent.gateway_receive_timestamp:type_name -> google.protobuf.Timestamp
|
||||
99, // 107: mxaccess_gateway.v1.MxEvent.value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
113, // 108: mxaccess_gateway.v1.MxEvent.source_timestamp:type_name -> google.protobuf.Timestamp
|
||||
98, // 109: mxaccess_gateway.v1.MxEvent.statuses:type_name -> mxaccess_gateway.v1.MxStatusProxy
|
||||
113, // 110: mxaccess_gateway.v1.MxEvent.worker_timestamp:type_name -> google.protobuf.Timestamp
|
||||
113, // 111: mxaccess_gateway.v1.MxEvent.gateway_receive_timestamp:type_name -> google.protobuf.Timestamp
|
||||
84, // 112: mxaccess_gateway.v1.MxEvent.replay_gap:type_name -> mxaccess_gateway.v1.ReplayGap
|
||||
85, // 113: mxaccess_gateway.v1.MxEvent.on_data_change:type_name -> mxaccess_gateway.v1.OnDataChangeEvent
|
||||
86, // 114: mxaccess_gateway.v1.MxEvent.on_write_complete:type_name -> mxaccess_gateway.v1.OnWriteCompleteEvent
|
||||
@@ -9601,68 +9681,69 @@ var file_mxaccess_gateway_proto_depIdxs = []int32{
|
||||
89, // 117: mxaccess_gateway.v1.MxEvent.on_alarm_transition:type_name -> mxaccess_gateway.v1.OnAlarmTransitionEvent
|
||||
90, // 118: mxaccess_gateway.v1.MxEvent.on_alarm_provider_mode_changed:type_name -> mxaccess_gateway.v1.OnAlarmProviderModeChangedEvent
|
||||
7, // 119: mxaccess_gateway.v1.OnBufferedDataChangeEvent.data_type:type_name -> mxaccess_gateway.v1.MxDataType
|
||||
99, // 120: mxaccess_gateway.v1.OnBufferedDataChangeEvent.quality_values:type_name -> mxaccess_gateway.v1.MxArray
|
||||
99, // 121: mxaccess_gateway.v1.OnBufferedDataChangeEvent.timestamp_values:type_name -> mxaccess_gateway.v1.MxArray
|
||||
100, // 120: mxaccess_gateway.v1.OnBufferedDataChangeEvent.quality_values:type_name -> mxaccess_gateway.v1.MxArray
|
||||
100, // 121: mxaccess_gateway.v1.OnBufferedDataChangeEvent.timestamp_values:type_name -> mxaccess_gateway.v1.MxArray
|
||||
3, // 122: mxaccess_gateway.v1.OnAlarmTransitionEvent.transition_kind:type_name -> mxaccess_gateway.v1.AlarmTransitionKind
|
||||
112, // 123: mxaccess_gateway.v1.OnAlarmTransitionEvent.original_raise_timestamp:type_name -> google.protobuf.Timestamp
|
||||
112, // 124: mxaccess_gateway.v1.OnAlarmTransitionEvent.transition_timestamp:type_name -> google.protobuf.Timestamp
|
||||
98, // 125: mxaccess_gateway.v1.OnAlarmTransitionEvent.current_value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
98, // 126: mxaccess_gateway.v1.OnAlarmTransitionEvent.limit_value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
113, // 123: mxaccess_gateway.v1.OnAlarmTransitionEvent.original_raise_timestamp:type_name -> google.protobuf.Timestamp
|
||||
113, // 124: mxaccess_gateway.v1.OnAlarmTransitionEvent.transition_timestamp:type_name -> google.protobuf.Timestamp
|
||||
99, // 125: mxaccess_gateway.v1.OnAlarmTransitionEvent.current_value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
99, // 126: mxaccess_gateway.v1.OnAlarmTransitionEvent.limit_value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
1, // 127: mxaccess_gateway.v1.OnAlarmTransitionEvent.source_provider:type_name -> mxaccess_gateway.v1.AlarmProviderMode
|
||||
1, // 128: mxaccess_gateway.v1.OnAlarmProviderModeChangedEvent.mode:type_name -> mxaccess_gateway.v1.AlarmProviderMode
|
||||
112, // 129: mxaccess_gateway.v1.OnAlarmProviderModeChangedEvent.at:type_name -> google.protobuf.Timestamp
|
||||
112, // 130: mxaccess_gateway.v1.ActiveAlarmSnapshot.original_raise_timestamp:type_name -> google.protobuf.Timestamp
|
||||
113, // 129: mxaccess_gateway.v1.OnAlarmProviderModeChangedEvent.at:type_name -> google.protobuf.Timestamp
|
||||
113, // 130: mxaccess_gateway.v1.ActiveAlarmSnapshot.original_raise_timestamp:type_name -> google.protobuf.Timestamp
|
||||
4, // 131: mxaccess_gateway.v1.ActiveAlarmSnapshot.current_state:type_name -> mxaccess_gateway.v1.AlarmConditionState
|
||||
112, // 132: mxaccess_gateway.v1.ActiveAlarmSnapshot.last_transition_timestamp:type_name -> google.protobuf.Timestamp
|
||||
98, // 133: mxaccess_gateway.v1.ActiveAlarmSnapshot.current_value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
98, // 134: mxaccess_gateway.v1.ActiveAlarmSnapshot.limit_value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
113, // 132: mxaccess_gateway.v1.ActiveAlarmSnapshot.last_transition_timestamp:type_name -> google.protobuf.Timestamp
|
||||
99, // 133: mxaccess_gateway.v1.ActiveAlarmSnapshot.current_value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
99, // 134: mxaccess_gateway.v1.ActiveAlarmSnapshot.limit_value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
1, // 135: mxaccess_gateway.v1.ActiveAlarmSnapshot.source_provider:type_name -> mxaccess_gateway.v1.AlarmProviderMode
|
||||
110, // 136: mxaccess_gateway.v1.AcknowledgeAlarmReply.protocol_status:type_name -> mxaccess_gateway.v1.ProtocolStatus
|
||||
97, // 137: mxaccess_gateway.v1.AcknowledgeAlarmReply.status:type_name -> mxaccess_gateway.v1.MxStatusProxy
|
||||
111, // 136: mxaccess_gateway.v1.AcknowledgeAlarmReply.protocol_status:type_name -> mxaccess_gateway.v1.ProtocolStatus
|
||||
98, // 137: mxaccess_gateway.v1.AcknowledgeAlarmReply.status:type_name -> mxaccess_gateway.v1.MxStatusProxy
|
||||
91, // 138: mxaccess_gateway.v1.AlarmFeedMessage.active_alarm:type_name -> mxaccess_gateway.v1.ActiveAlarmSnapshot
|
||||
89, // 139: mxaccess_gateway.v1.AlarmFeedMessage.transition:type_name -> mxaccess_gateway.v1.OnAlarmTransitionEvent
|
||||
96, // 140: mxaccess_gateway.v1.AlarmFeedMessage.provider_status:type_name -> mxaccess_gateway.v1.AlarmProviderStatus
|
||||
1, // 141: mxaccess_gateway.v1.AlarmProviderStatus.mode:type_name -> mxaccess_gateway.v1.AlarmProviderMode
|
||||
112, // 142: mxaccess_gateway.v1.AlarmProviderStatus.since:type_name -> google.protobuf.Timestamp
|
||||
5, // 143: mxaccess_gateway.v1.MxStatusProxy.category:type_name -> mxaccess_gateway.v1.MxStatusCategory
|
||||
6, // 144: mxaccess_gateway.v1.MxStatusProxy.detected_by:type_name -> mxaccess_gateway.v1.MxStatusSource
|
||||
7, // 145: mxaccess_gateway.v1.MxValue.data_type:type_name -> mxaccess_gateway.v1.MxDataType
|
||||
112, // 146: mxaccess_gateway.v1.MxValue.timestamp_value:type_name -> google.protobuf.Timestamp
|
||||
99, // 147: mxaccess_gateway.v1.MxValue.array_value:type_name -> mxaccess_gateway.v1.MxArray
|
||||
100, // 148: mxaccess_gateway.v1.MxValue.sparse_array_value:type_name -> mxaccess_gateway.v1.MxSparseArray
|
||||
7, // 149: mxaccess_gateway.v1.MxArray.element_data_type:type_name -> mxaccess_gateway.v1.MxDataType
|
||||
102, // 150: mxaccess_gateway.v1.MxArray.bool_values:type_name -> mxaccess_gateway.v1.BoolArray
|
||||
103, // 151: mxaccess_gateway.v1.MxArray.int32_values:type_name -> mxaccess_gateway.v1.Int32Array
|
||||
104, // 152: mxaccess_gateway.v1.MxArray.int64_values:type_name -> mxaccess_gateway.v1.Int64Array
|
||||
105, // 153: mxaccess_gateway.v1.MxArray.float_values:type_name -> mxaccess_gateway.v1.FloatArray
|
||||
106, // 154: mxaccess_gateway.v1.MxArray.double_values:type_name -> mxaccess_gateway.v1.DoubleArray
|
||||
107, // 155: mxaccess_gateway.v1.MxArray.string_values:type_name -> mxaccess_gateway.v1.StringArray
|
||||
108, // 156: mxaccess_gateway.v1.MxArray.timestamp_values:type_name -> mxaccess_gateway.v1.TimestampArray
|
||||
109, // 157: mxaccess_gateway.v1.MxArray.raw_values:type_name -> mxaccess_gateway.v1.RawArray
|
||||
7, // 158: mxaccess_gateway.v1.MxSparseArray.element_data_type:type_name -> mxaccess_gateway.v1.MxDataType
|
||||
101, // 159: mxaccess_gateway.v1.MxSparseArray.elements:type_name -> mxaccess_gateway.v1.MxSparseElement
|
||||
98, // 160: mxaccess_gateway.v1.MxSparseElement.value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
112, // 161: mxaccess_gateway.v1.TimestampArray.values:type_name -> google.protobuf.Timestamp
|
||||
8, // 162: mxaccess_gateway.v1.ProtocolStatus.code:type_name -> mxaccess_gateway.v1.ProtocolStatusCode
|
||||
11, // 163: mxaccess_gateway.v1.MxAccessGateway.OpenSession:input_type -> mxaccess_gateway.v1.OpenSessionRequest
|
||||
13, // 164: mxaccess_gateway.v1.MxAccessGateway.CloseSession:input_type -> mxaccess_gateway.v1.CloseSessionRequest
|
||||
16, // 165: mxaccess_gateway.v1.MxAccessGateway.Invoke:input_type -> mxaccess_gateway.v1.MxCommandRequest
|
||||
15, // 166: mxaccess_gateway.v1.MxAccessGateway.StreamEvents:input_type -> mxaccess_gateway.v1.StreamEventsRequest
|
||||
92, // 167: mxaccess_gateway.v1.MxAccessGateway.AcknowledgeAlarm:input_type -> mxaccess_gateway.v1.AcknowledgeAlarmRequest
|
||||
94, // 168: mxaccess_gateway.v1.MxAccessGateway.StreamAlarms:input_type -> mxaccess_gateway.v1.StreamAlarmsRequest
|
||||
10, // 169: mxaccess_gateway.v1.MxAccessGateway.QueryActiveAlarms:input_type -> mxaccess_gateway.v1.QueryActiveAlarmsRequest
|
||||
12, // 170: mxaccess_gateway.v1.MxAccessGateway.OpenSession:output_type -> mxaccess_gateway.v1.OpenSessionReply
|
||||
14, // 171: mxaccess_gateway.v1.MxAccessGateway.CloseSession:output_type -> mxaccess_gateway.v1.CloseSessionReply
|
||||
63, // 172: mxaccess_gateway.v1.MxAccessGateway.Invoke:output_type -> mxaccess_gateway.v1.MxCommandReply
|
||||
83, // 173: mxaccess_gateway.v1.MxAccessGateway.StreamEvents:output_type -> mxaccess_gateway.v1.MxEvent
|
||||
93, // 174: mxaccess_gateway.v1.MxAccessGateway.AcknowledgeAlarm:output_type -> mxaccess_gateway.v1.AcknowledgeAlarmReply
|
||||
95, // 175: mxaccess_gateway.v1.MxAccessGateway.StreamAlarms:output_type -> mxaccess_gateway.v1.AlarmFeedMessage
|
||||
91, // 176: mxaccess_gateway.v1.MxAccessGateway.QueryActiveAlarms:output_type -> mxaccess_gateway.v1.ActiveAlarmSnapshot
|
||||
170, // [170:177] is the sub-list for method output_type
|
||||
163, // [163:170] is the sub-list for method input_type
|
||||
163, // [163:163] is the sub-list for extension type_name
|
||||
163, // [163:163] is the sub-list for extension extendee
|
||||
0, // [0:163] is the sub-list for field type_name
|
||||
97, // 141: mxaccess_gateway.v1.AlarmFeedMessage.snapshot_status:type_name -> mxaccess_gateway.v1.AlarmSnapshotStatus
|
||||
1, // 142: mxaccess_gateway.v1.AlarmProviderStatus.mode:type_name -> mxaccess_gateway.v1.AlarmProviderMode
|
||||
113, // 143: mxaccess_gateway.v1.AlarmProviderStatus.since:type_name -> google.protobuf.Timestamp
|
||||
5, // 144: mxaccess_gateway.v1.MxStatusProxy.category:type_name -> mxaccess_gateway.v1.MxStatusCategory
|
||||
6, // 145: mxaccess_gateway.v1.MxStatusProxy.detected_by:type_name -> mxaccess_gateway.v1.MxStatusSource
|
||||
7, // 146: mxaccess_gateway.v1.MxValue.data_type:type_name -> mxaccess_gateway.v1.MxDataType
|
||||
113, // 147: mxaccess_gateway.v1.MxValue.timestamp_value:type_name -> google.protobuf.Timestamp
|
||||
100, // 148: mxaccess_gateway.v1.MxValue.array_value:type_name -> mxaccess_gateway.v1.MxArray
|
||||
101, // 149: mxaccess_gateway.v1.MxValue.sparse_array_value:type_name -> mxaccess_gateway.v1.MxSparseArray
|
||||
7, // 150: mxaccess_gateway.v1.MxArray.element_data_type:type_name -> mxaccess_gateway.v1.MxDataType
|
||||
103, // 151: mxaccess_gateway.v1.MxArray.bool_values:type_name -> mxaccess_gateway.v1.BoolArray
|
||||
104, // 152: mxaccess_gateway.v1.MxArray.int32_values:type_name -> mxaccess_gateway.v1.Int32Array
|
||||
105, // 153: mxaccess_gateway.v1.MxArray.int64_values:type_name -> mxaccess_gateway.v1.Int64Array
|
||||
106, // 154: mxaccess_gateway.v1.MxArray.float_values:type_name -> mxaccess_gateway.v1.FloatArray
|
||||
107, // 155: mxaccess_gateway.v1.MxArray.double_values:type_name -> mxaccess_gateway.v1.DoubleArray
|
||||
108, // 156: mxaccess_gateway.v1.MxArray.string_values:type_name -> mxaccess_gateway.v1.StringArray
|
||||
109, // 157: mxaccess_gateway.v1.MxArray.timestamp_values:type_name -> mxaccess_gateway.v1.TimestampArray
|
||||
110, // 158: mxaccess_gateway.v1.MxArray.raw_values:type_name -> mxaccess_gateway.v1.RawArray
|
||||
7, // 159: mxaccess_gateway.v1.MxSparseArray.element_data_type:type_name -> mxaccess_gateway.v1.MxDataType
|
||||
102, // 160: mxaccess_gateway.v1.MxSparseArray.elements:type_name -> mxaccess_gateway.v1.MxSparseElement
|
||||
99, // 161: mxaccess_gateway.v1.MxSparseElement.value:type_name -> mxaccess_gateway.v1.MxValue
|
||||
113, // 162: mxaccess_gateway.v1.TimestampArray.values:type_name -> google.protobuf.Timestamp
|
||||
8, // 163: mxaccess_gateway.v1.ProtocolStatus.code:type_name -> mxaccess_gateway.v1.ProtocolStatusCode
|
||||
11, // 164: mxaccess_gateway.v1.MxAccessGateway.OpenSession:input_type -> mxaccess_gateway.v1.OpenSessionRequest
|
||||
13, // 165: mxaccess_gateway.v1.MxAccessGateway.CloseSession:input_type -> mxaccess_gateway.v1.CloseSessionRequest
|
||||
16, // 166: mxaccess_gateway.v1.MxAccessGateway.Invoke:input_type -> mxaccess_gateway.v1.MxCommandRequest
|
||||
15, // 167: mxaccess_gateway.v1.MxAccessGateway.StreamEvents:input_type -> mxaccess_gateway.v1.StreamEventsRequest
|
||||
92, // 168: mxaccess_gateway.v1.MxAccessGateway.AcknowledgeAlarm:input_type -> mxaccess_gateway.v1.AcknowledgeAlarmRequest
|
||||
94, // 169: mxaccess_gateway.v1.MxAccessGateway.StreamAlarms:input_type -> mxaccess_gateway.v1.StreamAlarmsRequest
|
||||
10, // 170: mxaccess_gateway.v1.MxAccessGateway.QueryActiveAlarms:input_type -> mxaccess_gateway.v1.QueryActiveAlarmsRequest
|
||||
12, // 171: mxaccess_gateway.v1.MxAccessGateway.OpenSession:output_type -> mxaccess_gateway.v1.OpenSessionReply
|
||||
14, // 172: mxaccess_gateway.v1.MxAccessGateway.CloseSession:output_type -> mxaccess_gateway.v1.CloseSessionReply
|
||||
63, // 173: mxaccess_gateway.v1.MxAccessGateway.Invoke:output_type -> mxaccess_gateway.v1.MxCommandReply
|
||||
83, // 174: mxaccess_gateway.v1.MxAccessGateway.StreamEvents:output_type -> mxaccess_gateway.v1.MxEvent
|
||||
93, // 175: mxaccess_gateway.v1.MxAccessGateway.AcknowledgeAlarm:output_type -> mxaccess_gateway.v1.AcknowledgeAlarmReply
|
||||
95, // 176: mxaccess_gateway.v1.MxAccessGateway.StreamAlarms:output_type -> mxaccess_gateway.v1.AlarmFeedMessage
|
||||
91, // 177: mxaccess_gateway.v1.MxAccessGateway.QueryActiveAlarms:output_type -> mxaccess_gateway.v1.ActiveAlarmSnapshot
|
||||
171, // [171:178] is the sub-list for method output_type
|
||||
164, // [164:171] is the sub-list for method input_type
|
||||
164, // [164:164] is the sub-list for extension type_name
|
||||
164, // [164:164] is the sub-list for extension extendee
|
||||
0, // [0:164] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_mxaccess_gateway_proto_init() }
|
||||
@@ -9752,8 +9833,9 @@ func file_mxaccess_gateway_proto_init() {
|
||||
(*AlarmFeedMessage_SnapshotComplete)(nil),
|
||||
(*AlarmFeedMessage_Transition)(nil),
|
||||
(*AlarmFeedMessage_ProviderStatus)(nil),
|
||||
(*AlarmFeedMessage_SnapshotStatus)(nil),
|
||||
}
|
||||
file_mxaccess_gateway_proto_msgTypes[88].OneofWrappers = []any{
|
||||
file_mxaccess_gateway_proto_msgTypes[89].OneofWrappers = []any{
|
||||
(*MxValue_BoolValue)(nil),
|
||||
(*MxValue_Int32Value)(nil),
|
||||
(*MxValue_Int64Value)(nil),
|
||||
@@ -9765,7 +9847,7 @@ func file_mxaccess_gateway_proto_init() {
|
||||
(*MxValue_RawValue)(nil),
|
||||
(*MxValue_SparseArrayValue)(nil),
|
||||
}
|
||||
file_mxaccess_gateway_proto_msgTypes[89].OneofWrappers = []any{
|
||||
file_mxaccess_gateway_proto_msgTypes[90].OneofWrappers = []any{
|
||||
(*MxArray_BoolValues)(nil),
|
||||
(*MxArray_Int32Values)(nil),
|
||||
(*MxArray_Int64Values)(nil),
|
||||
@@ -9781,7 +9863,7 @@ func file_mxaccess_gateway_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_mxaccess_gateway_proto_rawDesc), len(file_mxaccess_gateway_proto_rawDesc)),
|
||||
NumEnums: 10,
|
||||
NumMessages: 101,
|
||||
NumMessages: 102,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
|
||||
@@ -125,6 +125,16 @@ deletions from it. It is set-level degraded status, not a comment on the
|
||||
record's own fidelity, and is distinct from `getDegraded()` (the subtag
|
||||
fallback provider).
|
||||
|
||||
`streamAlarms` also carries that completeness verdict at feed level, as a
|
||||
message whose `getPayloadCase()` is `SNAPSHOT_STATUS` and whose
|
||||
`getSnapshotStatus().getTruncated()` is true while the monitor's cached set
|
||||
derives from a truncated fetch. One arrives at stream open (after the
|
||||
`PROVIDER_STATUS` frame, before the cached `ACTIVE_ALARM` frames) so a late
|
||||
joiner learns the current verdict, then one on every verdict change — including
|
||||
the clearing frame sent when the gateway's alarm monitor restarts and drops a
|
||||
truncated verdict. Track it if you need set completeness on a live feed without
|
||||
polling `queryActiveAlarms`.
|
||||
|
||||
## Write Semantics And Common Pitfalls
|
||||
|
||||
These are MXAccess parity behaviors that surprise new callers. The gateway
|
||||
|
||||
+1019
-198
File diff suppressed because it is too large
Load Diff
+8
-2
@@ -47,8 +47,9 @@ import java.util.function.Consumer;
|
||||
import mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmReply;
|
||||
import mxaccess_gateway.v1.MxaccessGateway.AcknowledgeAlarmRequest;
|
||||
import mxaccess_gateway.v1.MxaccessGateway.ActiveAlarmSnapshot;
|
||||
import mxaccess_gateway.v1.MxaccessGateway.AlarmProviderStatus;
|
||||
import mxaccess_gateway.v1.MxaccessGateway.AlarmFeedMessage;
|
||||
import mxaccess_gateway.v1.MxaccessGateway.AlarmProviderStatus;
|
||||
import mxaccess_gateway.v1.MxaccessGateway.AlarmSnapshotStatus;
|
||||
import mxaccess_gateway.v1.MxaccessGateway.BulkReadResult;
|
||||
import mxaccess_gateway.v1.MxaccessGateway.BulkWriteResult;
|
||||
import mxaccess_gateway.v1.MxaccessGateway.CloseSessionRequest;
|
||||
@@ -2282,7 +2283,8 @@ public final class MxGatewayCli implements Callable<Integer> {
|
||||
/**
|
||||
* Renders one {@link AlarmFeedMessage} in the CLI's plain-text output
|
||||
* style, distinguishing the active-alarm snapshot, snapshot-complete
|
||||
* sentinel, and transition cases of the message's {@code payload} oneof.
|
||||
* sentinel, transition, provider-status, and snapshot-status cases of the
|
||||
* message's {@code payload} oneof.
|
||||
*/
|
||||
private static String formatAlarmFeedMessage(AlarmFeedMessage message) {
|
||||
return switch (message.getPayloadCase()) {
|
||||
@@ -2307,6 +2309,10 @@ public final class MxGatewayCli implements Callable<Integer> {
|
||||
"provider-status mode=%s degraded=%b reason=%s",
|
||||
status.getMode().name(), status.getDegraded(), status.getReason());
|
||||
}
|
||||
case SNAPSHOT_STATUS -> {
|
||||
AlarmSnapshotStatus status = message.getSnapshotStatus();
|
||||
yield String.format("snapshot-status truncated=%b", status.getTruncated());
|
||||
}
|
||||
case PAYLOAD_NOT_SET -> "unknown";
|
||||
};
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -122,6 +122,16 @@ poll. Treat the set as possibly incomplete rather than reconciling deletions
|
||||
from it. It is set-level degraded status, not a comment on the record's own
|
||||
fidelity, and is distinct from `degraded` (the subtag fallback provider).
|
||||
|
||||
`stream_alarms` also carries that completeness verdict at feed level, as a
|
||||
message whose `WhichOneof("payload")` is `snapshot_status` and whose
|
||||
`snapshot_status.truncated` is true while the monitor's cached set derives from
|
||||
a truncated fetch. One arrives at stream open (after the `provider_status`
|
||||
frame, before the cached `active_alarm` frames) so a late joiner learns the
|
||||
current verdict, then one on every verdict change — including the clearing frame
|
||||
sent when the gateway's alarm monitor restarts and drops a truncated verdict.
|
||||
Track it if you need set completeness on a live feed without polling
|
||||
`query_active_alarms`.
|
||||
|
||||
Canceling a Python task cancels the client-side gRPC call or stream wait. It
|
||||
does not abort an in-flight MXAccess COM call inside the worker process.
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -132,6 +132,15 @@ poll. Treat the set as possibly incomplete rather than reconciling deletions
|
||||
from it. It is set-level degraded status, not a comment on the record's own
|
||||
fidelity, and is distinct from `degraded` (the subtag fallback provider).
|
||||
|
||||
`AlarmFeedStream` also carries that completeness verdict at feed level, as an
|
||||
`alarm_feed_message::Payload::SnapshotStatus` frame whose `truncated` field is
|
||||
true while the monitor's cached set derives from a truncated fetch. One arrives
|
||||
at stream open (after the `ProviderStatus` frame, before the cached
|
||||
`ActiveAlarm` frames) so a late joiner learns the current verdict, then one on
|
||||
every verdict change — including the clearing frame sent when the gateway's
|
||||
alarm monitor restarts and drops a truncated verdict. Track it if you need set
|
||||
completeness on a live feed without polling `query_active_alarms`.
|
||||
|
||||
The session helpers keep MXAccess handles visible:
|
||||
|
||||
```rust
|
||||
|
||||
@@ -2231,7 +2231,7 @@ fn event_value_to_json(value: &ProtoMxValue) -> Value {
|
||||
}
|
||||
|
||||
/// Render a streamed [`AlarmFeedMessage`] as a terse one-line summary that
|
||||
/// distinguishes the four `payload` oneof cases.
|
||||
/// distinguishes the five `payload` oneof cases.
|
||||
fn alarm_feed_message_summary(message: &AlarmFeedMessage) -> String {
|
||||
match &message.payload {
|
||||
Some(alarm_feed_message::Payload::ActiveAlarm(snapshot)) => {
|
||||
@@ -2259,6 +2259,9 @@ fn alarm_feed_message_summary(message: &AlarmFeedMessage) -> String {
|
||||
status.reason
|
||||
)
|
||||
}
|
||||
Some(alarm_feed_message::Payload::SnapshotStatus(status)) => {
|
||||
format!("snapshot-status truncated={}", status.truncated)
|
||||
}
|
||||
None => "(empty)".to_owned(),
|
||||
}
|
||||
}
|
||||
@@ -2308,6 +2311,11 @@ fn alarm_feed_message_to_json(message: &AlarmFeedMessage) -> Value {
|
||||
})),
|
||||
}
|
||||
}),
|
||||
Some(alarm_feed_message::Payload::SnapshotStatus(status)) => json!({
|
||||
"snapshotStatus": {
|
||||
"truncated": status.truncated,
|
||||
}
|
||||
}),
|
||||
None => Value::Null,
|
||||
}
|
||||
}
|
||||
@@ -2961,4 +2969,23 @@ mod tests {
|
||||
assert_eq!(provider["reason"], "alarmmgr unavailable");
|
||||
assert_eq!(provider["since"]["seconds"], 1_777_995_000_i64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alarm_feed_snapshot_status_renders_in_summary_and_json() {
|
||||
use zb_mom_ww_mxgateway_client::generated::mxaccess_gateway::v1::{
|
||||
alarm_feed_message, AlarmFeedMessage, AlarmSnapshotStatus,
|
||||
};
|
||||
|
||||
let message = AlarmFeedMessage {
|
||||
payload: Some(alarm_feed_message::Payload::SnapshotStatus(
|
||||
AlarmSnapshotStatus { truncated: true },
|
||||
)),
|
||||
};
|
||||
|
||||
let summary = super::alarm_feed_message_summary(&message);
|
||||
assert_eq!(summary, "snapshot-status truncated=true");
|
||||
|
||||
let value = super::alarm_feed_message_to_json(&message);
|
||||
assert_eq!(value["snapshotStatus"]["truncated"], true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1018,6 +1018,10 @@ message AlarmFeedMessage {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1028,6 +1032,23 @@ message AlarmProviderStatus {
|
||||
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
|
||||
|
||||
@@ -764,6 +764,17 @@ The v2 8-arg method returns -55 on this AVEVA build regardless of
|
||||
operator-identity inputs — looks like a stub. The v1 6-arg method
|
||||
works. Production `WnWrapAlarmConsumer.AcknowledgeByName` calls the
|
||||
6-arg overload and discards the proto's `domain` + `full_name` fields.
|
||||
|
||||
**Correction, 2026-08-18** (`docs/AlarmProbeFindings.md`). Both halves
|
||||
of the paragraph above are narrower than they read. The -55 tracks the
|
||||
*consumer*, not the overload: against the `SetXmlAlarmQuery`-applied
|
||||
reader both the 6-arg and 8-arg forms return -55, and against the
|
||||
ack-only consumer both return 0. And "works" means "returns 0" only —
|
||||
a probe that raised a real alarm and then acked it six ways watched the
|
||||
snapshot stay `UNACK_ALM`, `OPERATOR_NAME` stay empty, and the alarm
|
||||
extension's own `.Acked` attribute stay `False` for 16 s after each
|
||||
call. On this rig the wnwrap ack is accepted and then inert, so a zero
|
||||
return code must not be read as an applied acknowledgement.
|
||||
The proto contract keeps the 8 fields for forward compatibility if
|
||||
AVEVA fixes the v2 method later.
|
||||
|
||||
|
||||
+176
-32
@@ -1,7 +1,8 @@
|
||||
# Alarm Probe Findings
|
||||
|
||||
`WnWrapAlarmConsumer` rests on two assumptions that no unit test can settle, because both
|
||||
are properties of AVEVA's alarm provider rather than of our code:
|
||||
`WnWrapAlarmConsumer` rested on two assumptions that no unit test can settle, because both
|
||||
are properties of AVEVA's alarm provider rather than of our code. The second is now settled
|
||||
and one half of the first is; the questions are stated here as they were asked:
|
||||
|
||||
1. **GUID identity.** The snapshot diff in `ComputeTransitions` keys on the alarm record's
|
||||
`GUID`. If wnwrap mints a fresh GUID when an alarm changes state, a single
|
||||
@@ -13,23 +14,27 @@ are properties of AVEVA's alarm provider rather than of our code:
|
||||
rather than the records-in-reply count, truncation detection can become exact instead of
|
||||
conservative, and the bounded staleness `ApplySnapshotUpdate` accepts goes away.
|
||||
|
||||
This document records what a live probe run against the dev rig (`DESKTOP-6JL3KKO`,
|
||||
2026-08-17) could and could not establish, so the next attempt starts from the blocker
|
||||
rather than rediscovering it.
|
||||
This document records what live probe runs against the dev rig (`DESKTOP-6JL3KKO`,
|
||||
2026-08-17 and 2026-08-18) could and could not establish, so the next attempt starts from
|
||||
the blocker rather than rediscovering it.
|
||||
|
||||
## Outcome
|
||||
|
||||
| Question | Status |
|
||||
|---|---|
|
||||
| GUID stable across polls and `ALM → RTN` | Answered — yes, by the 2026-05-01 capture in `AlarmClientDiscovery.md` |
|
||||
| GUID stable across `UNACK → ACK`, and across clear-then-re-raise | **Open** |
|
||||
| `COUNT` = total active vs records-in-reply under a capped fetch | **Open** |
|
||||
| GUID stable across polls and `ALM → RTN` | Answered — yes (2026-05-01 capture in `AlarmClientDiscovery.md`, reconfirmed 2026-08-18) |
|
||||
| GUID stable across clear-then-re-raise | Answered 2026-08-18 — **no**, a re-raise mints a new GUID |
|
||||
| GUID stable across `UNACK → ACK` | **Open** — the rig cannot be driven into an acknowledged state at all |
|
||||
| `COUNT` = total active vs records-in-reply under a capped fetch | Answered 2026-08-18 — **records in the reply** |
|
||||
|
||||
Both open questions are blocked by the same thing: the rig has no active alarm and cannot
|
||||
be driven into one over MXAccess, so there is no alarm instance whose GUID can be followed
|
||||
through an acknowledge and no population large enough to overflow a capped fetch.
|
||||
The 2026-08-17 run below is kept because it is the record of the wrong-verb blocker. The
|
||||
2026-08-18 run cleared that blocker with `AuthenticateUser` + `WriteSecured` and answered
|
||||
two of the three questions; the acknowledge leg is now blocked on something narrower and
|
||||
different, described in "Second attempt".
|
||||
|
||||
## Why The Rig Cannot Raise An Alarm
|
||||
## First attempt (2026-08-17): plain `Write`
|
||||
|
||||
### Why The Rig Could Not Raise An Alarm
|
||||
|
||||
The rig is otherwise healthy, which is what makes the blocker specific rather than a
|
||||
general "nothing works":
|
||||
@@ -64,7 +69,8 @@ for the whole probe session.
|
||||
|
||||
### Unblocking
|
||||
|
||||
Any one of these makes both questions answerable, in rough order of cost:
|
||||
Any one of these makes both questions answerable, in rough order of cost. The second one
|
||||
is what the 2026-08-18 run did, and it worked:
|
||||
|
||||
- Re-enable the System Platform script that flips `TestMachine_001.TestAlarm001`
|
||||
(referenced throughout `AlarmClientDiscovery.md`). It writes from inside the engine, so
|
||||
@@ -78,7 +84,7 @@ Three separate objects are wired to the same alarm UDA name, so once writes land
|
||||
`maxAlmCnt` of 1 or 2 forces truncation against three active alarms and answers the `COUNT`
|
||||
question in the same run.
|
||||
|
||||
## Evidence
|
||||
### Evidence
|
||||
|
||||
Snapshot payload, identical at every cap (1, 2, and 1024) and at every poll across the
|
||||
~100-second session:
|
||||
@@ -98,26 +104,164 @@ The probe used for the run was a throwaway file in the windev CI clone
|
||||
harness it was modelled on is
|
||||
`src/ZB.MOM.WW.MxGateway.Worker.Tests/Probes/WnWrapConsumerProbeTests.cs`.
|
||||
|
||||
## Implications
|
||||
## Second attempt (2026-08-18): secured-write verb
|
||||
|
||||
### Transition identity
|
||||
The findings above named the fix in their own "Unblocking" list, and it holds:
|
||||
`AuthenticateUser` + `WriteSecured` raises the alarms that plain `Write` could not touch.
|
||||
The rig's alarm attributes are not unreachable — they are secured, and the 2026-08-17 probe
|
||||
used the wrong verb.
|
||||
|
||||
`ComputeTransitions` keying on GUID is safe for the raise and clear legs, which is the
|
||||
evidence `AlarmClientDiscovery.md` already carries. The acknowledge leg — the one where a
|
||||
re-minted GUID would corrupt the feed, because an ack is the state change most likely to
|
||||
create a new record in a provider that models acknowledgement as a separate event — is
|
||||
still assumed rather than observed. Nothing here justifies changing the diff, but the
|
||||
assumption should not be described in code as established.
|
||||
### Method
|
||||
|
||||
### Truncation detection
|
||||
Three throwaway probes in the windev CI clone (`C:\build\mxaccessgw-ci`, detached at
|
||||
`ab3ff16`), each an x86 net48 xUnit fact running on a dedicated STA with a
|
||||
`PeekMessage`/`DispatchMessage` pump so MXAccess events deliver:
|
||||
|
||||
`IsTruncatedFetch` stays as written. Tightening it to an exact test requires knowing that
|
||||
`COUNT` reports the total, and this run cannot show that. The conservative rule keeps its
|
||||
justification: at the cap, treating a complete fetch as truncated costs one poll of
|
||||
staleness, while treating a truncated fetch as complete broadcasts clears for every alarm
|
||||
past the cap.
|
||||
1. secured write + both GUID legs + the capped-fetch question,
|
||||
2. the acknowledge surface, exhaustively,
|
||||
3. the acknowledge question re-asked through an independent observer.
|
||||
|
||||
The one substantive correction is to the phrasing rather than the logic. The reply is not
|
||||
featureless — it carries a `COUNT` attribute the parser currently ignores. Whether that
|
||||
attribute is a usable "more available" signal is unverified, not absent, and the comments
|
||||
in `WnWrapAlarmConsumer` now say so.
|
||||
All three used `LMXProxyServerClass` directly for the write path and a
|
||||
`wwAlarmConsumerClass` pair — a reader with `SetXmlAlarmQuery` applied and an ack-only
|
||||
consumer without it — mirroring `WnWrapAlarmConsumer`'s own two-consumer arrangement. The
|
||||
subscription was `\\DESKTOP-6JL3KKO\Galaxy!TestArea`, matching the 2026-08-17 run.
|
||||
`MXGATEWAY_LIVE_MXACCESS_WRITE_SECURED_USER` / `_PASSWORD` are set at neither machine nor
|
||||
user scope on the box, so the probes used the default rig identity, `Administrator` with an
|
||||
empty password. All three files were deleted afterwards and the clone is a clean tree.
|
||||
|
||||
### The secured write lands
|
||||
|
||||
Same session, same item, one after the other — the control reproduces 2026-08-17 exactly
|
||||
and the secured verb succeeds:
|
||||
|
||||
```
|
||||
Write 'TestMachine_001.TestAlarm001' <- True
|
||||
WRITE-COMPLETE hLMX=1 hItem=1 statuses=[success=0 category=SecurityError detectedBy=RespondingAutomationObject detail=1008 text=]
|
||||
observed 'TestMachine_001.TestAlarm001' = False
|
||||
|
||||
AuthenticateUser -> userId=1
|
||||
WriteSecured 'TestMachine_001.TestAlarm001' <- True currentUserId=1 verifierUserId=0
|
||||
DATA-CHANGE hItem=1 value=True quality=192
|
||||
WRITE-COMPLETE hLMX=1 hItem=1 statuses=[success=-1 category=Ok detectedBy=RespondingAutomationObject detail=0 text=]
|
||||
observed 'TestMachine_001.TestAlarm001' = True
|
||||
```
|
||||
|
||||
`AuthenticateUser("Administrator", "")` resolves to user id 1, matching the value
|
||||
`WorkerLiveMxAccessSmokeTests` records. No verifier is needed: `verifierUserId=0` is
|
||||
accepted. All six subsequent raise/clear writes across the three objects behaved the same
|
||||
way, so the path is reliable rather than a one-off.
|
||||
|
||||
### Q2 — `ALARM_RECORDS/@COUNT` reports the reply, not the total
|
||||
|
||||
With all three `TestMachine_00{1,2,3}.TestAlarm001` alarms active, the same subscription
|
||||
fetched at three caps back to back:
|
||||
|
||||
```
|
||||
FETCH three-active cap=1024 ALARM_RECORDS/@COUNT=3 elementCount=3
|
||||
FETCH three-active cap=1 ALARM_RECORDS/@COUNT=1 elementCount=1
|
||||
FETCH three-active cap=2 ALARM_RECORDS/@COUNT=2 elementCount=2
|
||||
```
|
||||
|
||||
`@COUNT` tracked the cap, not the population. It equals the element count in every reply,
|
||||
including the two that are provably truncated — the galaxy held three active alarms while
|
||||
`@COUNT` read 1 and 2. The attribute therefore carries no "more available" information, and
|
||||
the hypothesis that it might report the total is refuted rather than merely untested.
|
||||
|
||||
`IsTruncatedFetch` stays exactly as written, and this is now settled rather than deferred:
|
||||
there is no exact truncation signal to switch to. Tightening it is **not** a future task.
|
||||
The conservative rule keeps its original justification — at the cap, treating a complete
|
||||
fetch as truncated costs one poll of staleness, while treating a truncated fetch as
|
||||
complete broadcasts clears for every alarm past the cap.
|
||||
|
||||
### Q1 — clear-then-re-raise mints a new GUID
|
||||
|
||||
`TestMachine_001.TestAlarm001` was raised, cleared, and re-raised through `WriteSecured`,
|
||||
with a full snapshot fetch after each leg:
|
||||
|
||||
```
|
||||
after-raise TAG=TestMachine_001.TestAlarm001 GUID=72B84A6E7BA74D42B611EB5393626F79 STATE=UNACK_ALM VALUE=true
|
||||
after-clear-001 TAG=TestMachine_001.TestAlarm001 GUID=72B84A6E7BA74D42B611EB5393626F79 STATE=UNACK_RTN VALUE=false
|
||||
after-reraise TAG=TestMachine_001.TestAlarm001 GUID=2394FEAA94774154AAA97C3AC29CA51E STATE=UNACK_ALM VALUE=true
|
||||
```
|
||||
|
||||
Two facts, one confirming and one new. The `ALM → RTN` leg holds its GUID, reconfirming the
|
||||
2026-05-01 capture against a write-driven rather than script-driven transition. The re-raise
|
||||
does not: the returned record keeps the old GUID and the new active condition arrives as a
|
||||
separate record under a fresh one.
|
||||
|
||||
That is the correct reading for `ComputeTransitions` rather than a problem for it. The old
|
||||
GUID leaving the active set is a real Clear and the new GUID is a real Raise, because they
|
||||
are two alarm instances. The diff needed no change.
|
||||
|
||||
### Q1 — the acknowledge leg is still unobserved, for a new reason
|
||||
|
||||
The blocker moved. The rig can now be driven into an alarm; it cannot be driven out of
|
||||
`UNACK_ALM`. Every acknowledge surface `wwAlarmConsumerClass` exposes was tried against a
|
||||
freshly raised alarm, each followed by 16 seconds of polling:
|
||||
|
||||
| Attempt | Result |
|
||||
|---|---|
|
||||
| `AlarmAckByName` 6-arg on the ack-only consumer, node = machine | `rc=0`, state unchanged |
|
||||
| same, node empty | `rc=0`, state unchanged |
|
||||
| same, name as `Galaxy!TestArea.TestMachine_001.TestAlarm001` | `rc=0`, state unchanged |
|
||||
| same, operator name `Administrator` | `rc=0`, state unchanged |
|
||||
| `AlarmAckByName` 6-arg on the reader consumer (`SetXmlAlarmQuery` applied) | `rc=-55`, state unchanged |
|
||||
| `AlarmAckByName` 8-arg (v2) on the ack-only consumer | `rc=0`, state unchanged |
|
||||
|
||||
The `rc=-55` on the `SetXmlAlarmQuery`-applied consumer reproduces the 2026-05-01 finding
|
||||
that motivated the two-consumer split, so the ack-only consumer was correctly provisioned;
|
||||
the `rc=0` returns are the ones that go nowhere.
|
||||
|
||||
A third probe removed the last doubt by watching the alarm extension's own `.Acked`
|
||||
attribute over MXAccess — an observer entirely independent of the wnwrap snapshot:
|
||||
|
||||
```
|
||||
VALUES after-raise: 'TestMachine_001.TestAlarm001.Acked' = False
|
||||
AlarmAckByName -> rc=0
|
||||
VALUES after-wnwrap-ack: 'TestMachine_001.TestAlarm001.Acked' = False
|
||||
XML after-wnwrap-ack: STATE=UNACK_ALM OPRNAME= OPRNODE=
|
||||
```
|
||||
|
||||
Nothing moves: not the extension attribute, not the snapshot `STATE`, not `OPERATOR_NAME`.
|
||||
`rc=0` from wnwrap means the call was accepted, not that an acknowledgement was applied.
|
||||
|
||||
Acknowledging by writing the extension attribute directly is not an alternative — the
|
||||
attribute is not writable, and says so with an operational rather than a security failure,
|
||||
for both verbs:
|
||||
|
||||
```
|
||||
WriteSecured 'TestMachine_001.TestAlarm001.Acked' <- true
|
||||
WRITE-COMPLETE statuses=[success=0 category=MxCategoryOperationalError detectedBy=MxSourceRespondingAutomationObject detail=1007]
|
||||
Write 'TestMachine_001.TestAlarm001.Acked' <- true
|
||||
WRITE-COMPLETE statuses=[success=0 category=MxCategoryOperationalError detectedBy=MxSourceRespondingAutomationObject detail=1007]
|
||||
```
|
||||
|
||||
`detail=1007` from the responding automation object, unchanged by authentication, is a
|
||||
read-only attribute rather than a permission refusal — the `1008` `SecurityError` the alarm
|
||||
UDA itself returns is what a permission refusal looks like on this rig. So the ack has no
|
||||
MXAccess-side entry point and the wnwrap-side entry point is inert.
|
||||
|
||||
#### Remaining unblock paths for the acknowledge leg
|
||||
|
||||
- Acknowledge from the System Platform side — the IDE's alarm client, InTouch, or an
|
||||
ArchestrA graphic bound to the alarm — and watch the snapshot from a running probe. This
|
||||
proves whether wnwrap's `STATE` ever reports `ACK_ALM` at all, which is the actual
|
||||
question; the ack API being inert may be a wnwrap defect layered on top of a snapshot
|
||||
that would report the state correctly.
|
||||
- Check whether `alarmmgr` on this rig is configured with an alarm-acknowledgement security
|
||||
requirement that the wnwrap consumer, which passes an operator *name* string and no
|
||||
authenticated identity, cannot meet. If so, ack over wnwrap is not merely untested here
|
||||
but unavailable by configuration, and the gateway's `AcknowledgeByName` path needs the
|
||||
same treatment on any customer galaxy configured that way.
|
||||
- If neither lands, the acknowledge leg stays assumed. It is worth restating that this is a
|
||||
documentation gap, not a correctness one: a re-minted GUID on acknowledge would produce a
|
||||
spurious Clear plus a spurious Raise, which is the same shape the now-observed re-raise
|
||||
behaviour produces and which `ComputeTransitions` already handles as two instances.
|
||||
|
||||
### Rig state left behind
|
||||
|
||||
The three `TestMachine_00{1,2,3}.TestAlarm001` UDAs are back to `false` and their
|
||||
`.InAlarm` subtags read `false`, but each leaves a `UNACK_RTN` record in the wnwrap
|
||||
snapshot, since nothing can acknowledge them away. `SnapshotActiveAlarms` counts only
|
||||
`UNACK_ALM` and `ACK_ALM` as active, so these are inert for the gateway; they will clear on
|
||||
the next `alarmmgr` restart.
|
||||
|
||||
@@ -197,7 +197,10 @@ Tags are set at key creation with
|
||||
`apikey create-key --dashboard-tags team-a,team-b` (repeatable; segments are
|
||||
trimmed and de-duplicated ordinal-ignore-case). The dashboard API Keys page sets
|
||||
them too: its create form has a **Dashboard tags** field alongside the data-access
|
||||
constraints, split on the same separators the other constraint fields use.
|
||||
constraints, split on the same separators the other constraint fields use and
|
||||
de-duplicated ordinal-ignore-case as the CLI does. The two differ on one point: a
|
||||
stray separator drops an empty segment silently on the form rather than failing
|
||||
the command, because a browser form has no exit code to fail with.
|
||||
|
||||
That page's constraints column names `dashboard_tags` like any other member. It
|
||||
has to: `IsEmpty` counts the tags, so a key whose only recorded policy is a
|
||||
|
||||
@@ -226,13 +226,14 @@ payload carries the flag as well because a prefix filter (or an empty galaxy) ca
|
||||
leave zero records, and a truncated fetch with nothing to report still has to say
|
||||
so.
|
||||
|
||||
The **detection heuristic is unchanged**: `IsTruncatedFetch` remains
|
||||
`fetchedRecordCount >= maxAlarmsPerFetch`. The live probe run for this work could
|
||||
not verify whether `ALARM_RECORDS/@COUNT` reports the total active count or only
|
||||
the records in the reply (`docs/AlarmProbeFindings.md`), and an exact-looking
|
||||
signal derived from an unverified attribute is worse than an honest heuristic —
|
||||
it would read as precise while being wrong in the one direction that matters.
|
||||
Switching to `@COUNT` stays blocked on probe evidence.
|
||||
The **detection heuristic is unchanged, and now permanently**: `IsTruncatedFetch`
|
||||
remains `fetchedRecordCount >= maxAlarmsPerFetch`. The 2026-08-18 probe settled
|
||||
what the earlier run could not: with three alarms active, `ALARM_RECORDS/@COUNT`
|
||||
read 1 at a cap of 1 and 2 at a cap of 2, so it reports the records in the reply
|
||||
rather than the total active count (`docs/AlarmProbeFindings.md`). There is no
|
||||
exact signal to switch to — `@COUNT` cannot distinguish a capped fetch from a
|
||||
complete one — so the heuristic is the design rather than a placeholder waiting
|
||||
on evidence.
|
||||
|
||||
The flag is **not latched**. It is replaced by each fetch's verdict, so the first
|
||||
sub-cap fetch clears it, and `GatewayAlarmMonitor.ClearCache` drops it with the
|
||||
|
||||
@@ -192,7 +192,7 @@ events (a "gap") and must re-snapshot; whatever is still retained is replayed.
|
||||
| `MxGateway:Dashboard:SnapshotIntervalMilliseconds` | `1000` | Dashboard snapshot refresh interval used by the snapshot SignalR hub and the pages that subscribe to it. |
|
||||
| `MxGateway:Dashboard:RecentFaultLimit` | `100` | Maximum number of fault summaries projected into each dashboard snapshot. |
|
||||
| `MxGateway:Dashboard:RecentSessionLimit` | `200` | Maximum number of session summaries projected into each dashboard snapshot. |
|
||||
| `MxGateway:Dashboard:ShowTagValues` | `false` | Controls whether tag values reach the dashboard at all. `false` (the default) suppresses them on all three seams that carry one: (1) the **events hub mirror** — `DashboardEventBroadcaster` blanks `MxEvent.value` plus the alarm body's `current_value`/`limit_value` from a deep-cloned copy before it reaches any hub subscriber (see `docs/GatewayDashboardDesign.md`'s `EventsHub` row); (2) the **alarms hub** — `AlarmsHubPublisher` clears `current_value`/`limit_value` from a deep-cloned copy of each `AlarmFeedMessage`, on both value-bearing payload arms (`transition` and `active_alarm`), before broadcasting to `/hubs/alarms`; (3) the **`/browse` live-value panel** — `DashboardLiveDataService` substitutes the literal `[redacted]` for each tag's value text. Both hub redactions clone: the source `MxEvent` is shared with the gRPC event stream and the replay ring, and the source `AlarmFeedMessage` fans out to gRPC `StreamAlarms` subscribers, so neither is mutated in place and **no gRPC client is affected by this flag** — it is a dashboard-display control only. Everything that is not the value survives on every seam: tag reference, alarm reference/severity/state/operator, data type, quality, status, and timestamps still render, so the dashboard stays diagnostic without disclosing process values. This is one of two independent layers: `IDashboardSessionAcl` decides *which* sessions a caller may subscribe to at all (see `GroupToTag` / `UntaggedSessionVisibility` below), while this flag decides what a permitted subscriber sees. Setting it `true` exposes tag values to everyone the ACL admits — every Administrator, plus each Viewer holding a matching tag — and, because the alarms hub is session-less, exposes alarm values to every dashboard client that can reach `/hubs/alarms`. |
|
||||
| `MxGateway:Dashboard:ShowTagValues` | `false` | Controls whether tag values reach the dashboard at all. `false` (the default) suppresses them on all three seams that carry one: (1) the **events hub mirror** — `DashboardEventBroadcaster` blanks `MxEvent.value` plus the alarm body's `current_value`/`limit_value` from a deep-cloned copy before it reaches any hub subscriber (see `docs/GatewayDashboardDesign.md`'s `EventsHub` row); (2) the **alarms hub** — `AlarmsHubPublisher` clears `current_value`/`limit_value` from a deep-cloned copy of each `AlarmFeedMessage`, on both value-bearing payload arms (`transition` and `active_alarm`), before broadcasting to `/hubs/alarms`; (3) the **`/browse` live-value panel** — `DashboardLiveDataService` substitutes the literal `[redacted]` for the value text of each **successfully read** tag; a failed read keeps its `-` placeholder (there was no value to suppress, and the row's error is left untouched, so the two cannot contradict each other). Both hub redactions clone: the source `MxEvent` is shared with the gRPC event stream and the replay ring, and the source `AlarmFeedMessage` fans out to gRPC `StreamAlarms` subscribers, so neither is mutated in place and **no gRPC client is affected by this flag** — it is a dashboard-display control only. Everything that is not the value survives on every seam: tag reference, alarm reference/severity/state/operator, data type, quality, status, and timestamps still render, so the dashboard stays diagnostic without disclosing process values. This is one of two independent layers: `IDashboardSessionAcl` decides *which* sessions a caller may subscribe to at all (see `GroupToTag` / `UntaggedSessionVisibility` below), while this flag decides what a permitted subscriber sees. Setting it `true` exposes tag values to everyone the ACL admits — every Administrator, plus each Viewer holding a matching tag — and, because the alarms hub is session-less, exposes alarm values to every dashboard client that can reach `/hubs/alarms`. |
|
||||
| `MxGateway:Dashboard:GroupToRole` | _(empty)_ | LDAP group → dashboard role mapping. Keys are LDAP group names (short CN or full DN — leading-RDN match). Values must be `Administrator` (read/write, API-key CRUD) or `Viewer` (read-only) — matched ordinally by the startup validator, so the spelling is exact and `Admin` is rejected. A user whose LDAP groups don't intersect this map cannot sign in; with no mapping at all, only the loopback bypass admits anyone. |
|
||||
| `MxGateway:Dashboard:GroupToTag` | _(empty)_ | LDAP group → dashboard visibility tags. Keys follow the same convention as `GroupToRole` (short CN or full DN — leading-RDN match, case-insensitive); values are tag lists. A dashboard user's granted tag set is the union over the groups they belong to; an unmapped group contributes nothing. **Visibility only:** tags scope which sessions' event streams a Viewer may observe on the dashboard — they never grant or deny data access, which stays with the API key's scopes and constraints. Independent of `GroupToRole`: a group may appear in either map, both, or neither. Empty (the default) means Viewers hold no tags, so under the default `UntaggedSessionVisibility` they observe no session's events. |
|
||||
| `MxGateway:Dashboard:UntaggedSessionVisibility` | `AdminOnly` | Who may observe a session that carries no tags (its owning API key declared none). `AdminOnly` (default, fail-closed) restricts untagged sessions to dashboard Administrators. `AllViewers` shows them to every Viewer — opt-in for a single-tenant deployment that wants the pre-tag behaviour. Administrators always see every session regardless of tags. |
|
||||
|
||||
@@ -273,7 +273,7 @@ bearer). Each hub class is `[Authorize(Policy = HubClientsPolicy)]`.
|
||||
| Hub | Path | Producer | Payload | Routing |
|
||||
|---|---|---|---|---|
|
||||
| `DashboardSnapshotHub` | `/hubs/snapshot` | `DashboardSnapshotPublisher` (BackgroundService consuming `IDashboardSnapshotService.WatchSnapshotsAsync`) | `DashboardSnapshot` | Sent to all connected clients on every snapshot tick, but only while at least one client is connected (see "Idle gating" below); new connections receive the current snapshot synchronously in `OnConnectedAsync`. |
|
||||
| `AlarmsHub` | `/hubs/alarms` | `AlarmsHubPublisher` (BackgroundService consuming `IGatewayAlarmService.StreamAsync(filter: null)`) | `AlarmFeedMessage` (`active_alarm` / `snapshot_complete` / `transition`) | Connected clients auto-join `__alarms__`; all clients receive every message. Publisher auto-reconnects every 5s on stream faults. The alarm value fields (`current_value` / `limit_value`) are stripped from a deep-cloned copy of the message when `Dashboard:ShowTagValues` is false (the default) — on both value-bearing payload arms, so neither a live transition nor a snapshot record leaks a process value; every other field still renders. The source message is never mutated: it fans out from the same feed to gRPC `StreamAlarms` subscribers, which this dashboard-display flag does not govern. Arms carrying no value are forwarded as-is, uncloned. |
|
||||
| `AlarmsHub` | `/hubs/alarms` | `AlarmsHubPublisher` (BackgroundService consuming `IGatewayAlarmService.StreamAsync(filter: null)`) | `AlarmFeedMessage` (`active_alarm` / `snapshot_complete` / `transition` / `provider_status` / `snapshot_status`) | Connected clients auto-join `__alarms__`; all clients receive every message. Publisher auto-reconnects every 5s on stream faults. The alarm value fields (`current_value` / `limit_value`) are stripped from a deep-cloned copy of the message when `Dashboard:ShowTagValues` is false (the default) — on both value-bearing payload arms, so neither a live transition nor a snapshot record leaks a process value; every other field still renders. The source message is never mutated: it fans out from the same feed to gRPC `StreamAlarms` subscribers, which this dashboard-display flag does not govern. Arms carrying no value are forwarded as-is, uncloned. |
|
||||
| `EventsHub` | `/hubs/events` | `DashboardEventBroadcaster` invoked by each session's internal dashboard-mirror subscriber on its `SessionEventDistributor` (registered when the session becomes Ready) | `MxEvent` | Clients call `SubscribeSession(sessionId)` to join `session:{id}`, which also registers them in `EventsHubViewerRegistry` — the mirror is gated on that registry, which counts hub and in-process viewers alike (see "Mirror gating" below). The dashboard is a first-class distributor subscriber, so it receives the session's events whether or not a gRPC client is streaming. It sees RAW session events — not the per-gRPC-subscriber `AfterWorkerSequence` filtering that `EventStreamService` applies at its own boundary — because the dashboard is a separate LDAP-authenticated monitoring view meant to show the session's full event activity. Tag values are stripped from the mirrored `MxEvent` copy by `DashboardEventBroadcaster` when `Dashboard:ShowTagValues` is false (the default) — event metadata (tag reference, quality, status, timestamps) still renders, but the value fields are blanked, so no value leaks through this seam. `SubscribeSession` is gated by `IDashboardSessionAcl` (SEC-25 / TST-15): a denied caller gets a `HubException`, is not joined to the group, and is not registered as a viewer, so the mirror stays off for a session nobody is legitimately watching. The same ACL gates the in-process seam the session-details page uses, so neither path is the weaker one. Value redaction remains an independent layer — it bounds what a *permitted* subscriber sees. |
|
||||
|
||||
### Default cadences
|
||||
@@ -652,6 +652,11 @@ Show read-only effective configuration:
|
||||
- auth mode,
|
||||
- SQLite auth database path with sensitive parts redacted if needed,
|
||||
- dashboard enabled state,
|
||||
- the LDAP group mappings that decide what a signed-in user may see —
|
||||
`Dashboard:GroupToRole` (group → `Administrator` / `Viewer`) and
|
||||
`Dashboard:GroupToTag` (group → the session tags it grants),
|
||||
- `Dashboard:UntaggedSessionVisibility`, which decides who sees a session whose
|
||||
owning API key carries no dashboard tags,
|
||||
- protocol version.
|
||||
|
||||
Do not show API key secrets or pepper values.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"planPath": "docs/plans/2026-08-17-followup-closeout.md",
|
||||
"tasks": [
|
||||
{"id": 1, "subject": "Task 1: Feed-level alarm-truncation signal (proto + gateway)", "status": "pending"},
|
||||
{"id": 2, "subject": "Task 2: Five-client rollout of snapshot_status", "status": "pending", "blockedBy": [1, 3, 4]},
|
||||
{"id": 3, "subject": "Task 3: Complete ShowTagValues coverage — alarms hub + /browse", "status": "pending"},
|
||||
{"id": 1, "subject": "Task 1: Feed-level alarm-truncation signal (proto + gateway)", "status": "completed", "commits": ["fccf753", "312e072"], "review": "Spec compliant; code review Approved, proto-comment Minor fixed"},
|
||||
{"id": 2, "subject": "Task 2: Five-client rollout of snapshot_status", "status": "completed", "commits": ["c748361", "4d4f443"], "review": "Spec compliant; code review Approved, Java import + Rust render-test nits fixed; full five-toolchain matrix ran locally"},
|
||||
{"id": 3, "subject": "Task 3: Complete ShowTagValues coverage — alarms hub + /browse", "status": "completed", "commits": ["eff17d1", "1ea6f60", "e50ec49"], "review": "Spec compliant; code review Approved, failed-read placeholder fix re-review Approved"},
|
||||
{"id": 4, "subject": "Task 4: Codegen guard — reverse-direction Check 3 + stale-note correction", "status": "completed", "commit": "711793f", "review": "Approved (code review, no issues)"},
|
||||
{"id": 5, "subject": "Task 5: Settings page — GroupToTag + UntaggedSessionVisibility rows", "status": "completed", "commits": ["094f2ff", "ff16c69"], "review": "Approved; comparer-propagation fix applied and re-verified"},
|
||||
{"id": 6, "subject": "Task 6: ApiKeysPage — DashboardTags in ConstraintText + create-form input", "status": "pending"},
|
||||
{"id": 6, "subject": "Task 6: ApiKeysPage — DashboardTags in ConstraintText + create-form input", "status": "completed", "commits": ["c037d99", "fab600d"], "review": "Spec compliant; code review Approved after dedupe/help-text fix, re-review Approved"},
|
||||
{"id": 7, "subject": "Task 7: Wnwrap alarm-probe retry — secured-write path (windev)", "status": "pending"},
|
||||
{"id": 8, "subject": "Task 8: Windev full verification", "status": "pending", "blockedBy": [1, 2, 3, 4, 5, 6, 7]},
|
||||
{"id": 9, "subject": "Task 9: Bookkeeping — follow-ups closure + plan record", "status": "pending", "blockedBy": [8]}
|
||||
|
||||
+4
-3
@@ -265,9 +265,10 @@ means "this active set may be incomplete", not "this record is unreliable" —
|
||||
it is independent of the subtag-fallback `degraded` field above. It is not
|
||||
latched: the first fetch that comes back under the cap is complete, restores
|
||||
absence authority, and clears it. Detection remains the record-count heuristic;
|
||||
the reply's `ALARM_RECORDS/@COUNT` attribute would make the test exact only if
|
||||
it reported the total active count rather than the records in the reply, which
|
||||
a live probe could not discriminate (see `docs/AlarmProbeFindings.md`).
|
||||
the reply's `ALARM_RECORDS/@COUNT` attribute would have made the test exact only
|
||||
if it reported the total active count, and the 2026-08-18 live probe observed it
|
||||
reporting the records in the reply instead (see `docs/AlarmProbeFindings.md`), so
|
||||
there is no exact alternative.
|
||||
|
||||
Forced modes are available via `MxGateway:Alarms:Fallback:Mode`:
|
||||
`ForceAlarmManager` disables failover; `ForceSubtag` forces the standby
|
||||
|
||||
@@ -29318,7 +29318,10 @@ namespace ZB.MOM.WW.MxGateway.Contracts.Proto {
|
||||
/// 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 observed at reconcile. Mirrors the per-record
|
||||
/// 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.
|
||||
/// </summary>
|
||||
|
||||
@@ -1035,7 +1035,10 @@ message AlarmProviderStatus {
|
||||
// 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 observed at reconcile. Mirrors the per-record
|
||||
// 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 {
|
||||
|
||||
@@ -121,7 +121,8 @@ else
|
||||
aria-describedby="dashboardTagsHelp"
|
||||
@bind="CreateModel.DashboardTags" @bind:event="oninput"></textarea>
|
||||
<div id="dashboardTagsHelp" class="form-text small">
|
||||
Comma- or newline-separated; mirrors <code>apikey create-key --dashboard-tags</code>.
|
||||
Split on the same separators as the constraint fields above, and
|
||||
de-duplicated case-insensitively — two spellings of a tag are one grant.
|
||||
Matched case-insensitively against the viewer grants in
|
||||
<code>Dashboard:GroupToTag</code>. Scopes dashboard event visibility only —
|
||||
never what the key may read, write, or browse. Empty leaves the key's sessions
|
||||
@@ -478,7 +479,7 @@ else
|
||||
{
|
||||
// Init-only rather than positional (it was bolted onto the record after the
|
||||
// eight-member constructor shipped), so it is attached here instead.
|
||||
DashboardTags = ParseList(CreateModel.DashboardTags),
|
||||
DashboardTags = ParseDashboardTags(CreateModel.DashboardTags),
|
||||
});
|
||||
|
||||
return true;
|
||||
@@ -575,6 +576,18 @@ else
|
||||
}
|
||||
}
|
||||
|
||||
// Dashboard tags alone are de-duplicated ordinal-ignore-case, which is how the enforcement site
|
||||
// compares them — two spellings are one grant, and ApiKeyAdminCommandLineParser.ParseDashboardTags
|
||||
// collapses them the same way, first spelling winning. Persisting both would not change who can
|
||||
// see what (GatewaySession holds its tags in a case-insensitive set) but the constraints column
|
||||
// would report one grant twice, and a security grant that reads as two is the wrong kind of
|
||||
// wrong on an audit surface. The other five list fields keep ParseList's verbatim behaviour:
|
||||
// their globs are matched literally, so near-duplicates there are not necessarily the same rule.
|
||||
private static IReadOnlyList<string> ParseDashboardTags(string? value)
|
||||
{
|
||||
return [.. ParseList(value).Distinct(StringComparer.OrdinalIgnoreCase)];
|
||||
}
|
||||
|
||||
private static IReadOnlyList<string> ParseList(string? value)
|
||||
{
|
||||
return (value ?? string.Empty)
|
||||
|
||||
@@ -111,6 +111,72 @@ public sealed class ApiKeysPageDashboardTagsTests
|
||||
Assert.False(request.Constraints.HasWriteConstraints);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Two spellings of one tag persist as one grant, matching how
|
||||
/// <c>ApiKeyAdminCommandLineParser.ParseDashboardTags</c> collapses them and how the
|
||||
/// enforcement site compares them.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Enforcement would survive the duplicate — a session holds its tags in a
|
||||
/// case-insensitive set — but the API Keys page's constraints column would render
|
||||
/// <c>dashboard_tags=[team-a, TEAM-A]</c> and report one grant as two, which is the
|
||||
/// reading an audit surface can least afford.
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public void TryBuildCreateRequest_DeduplicatesDashboardTagsIgnoringCase()
|
||||
{
|
||||
ApiKeysPage page = new();
|
||||
page.CreateModel.DashboardTags = "team-a, TEAM-A, team-a";
|
||||
|
||||
bool built = page.TryBuildCreateRequest(out DashboardApiKeyManagementRequest? request, out string? error);
|
||||
|
||||
Assert.True(built, error);
|
||||
Assert.NotNull(request);
|
||||
|
||||
// First spelling typed wins, so what the operator wrote is what the audit surface shows.
|
||||
Assert.Equal(["team-a"], request.Constraints.DashboardTags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A dedupe that spanned fields would be a bug of its own: the five glob lists are matched
|
||||
/// literally, so near-duplicates there are not necessarily the same rule and must survive
|
||||
/// verbatim.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void TryBuildCreateRequest_DoesNotDeduplicateTheOtherConstraintLists()
|
||||
{
|
||||
ApiKeysPage page = new();
|
||||
page.CreateModel.ReadSubtrees = "Area1, AREA1";
|
||||
|
||||
bool built = page.TryBuildCreateRequest(out DashboardApiKeyManagementRequest? request, out string? error);
|
||||
|
||||
Assert.True(built, error);
|
||||
Assert.NotNull(request);
|
||||
Assert.Equal(["Area1", "AREA1"], request.Constraints.ReadSubtrees);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>Reset</c> clears the new field, so the next key minted in the same dialog does not
|
||||
/// inherit the previous key's tag grant.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Reset_ClearsDashboardTagsAlongsideTheOtherConstraintFields()
|
||||
{
|
||||
ApiKeysPage page = new();
|
||||
page.CreateModel.DashboardTags = "team-a";
|
||||
|
||||
// A sibling field pins the assertion to Reset itself: were Reset a no-op, both would
|
||||
// survive and the failure would name the method rather than the one line.
|
||||
page.CreateModel.ReadSubtrees = "Area1";
|
||||
page.CreateModel.KeyId = "leftover";
|
||||
|
||||
page.CreateModel.Reset();
|
||||
|
||||
Assert.Equal(string.Empty, page.CreateModel.DashboardTags);
|
||||
Assert.Equal(string.Empty, page.CreateModel.ReadSubtrees);
|
||||
Assert.Equal(string.Empty, page.CreateModel.KeyId);
|
||||
}
|
||||
|
||||
/// <summary>An empty field leaves the key untagged rather than inventing a tag.</summary>
|
||||
[Fact]
|
||||
public void TryBuildCreateRequest_WhenDashboardTagsIsBlank_LeavesTheKeyUntagged()
|
||||
|
||||
@@ -429,10 +429,12 @@ public sealed class WnWrapAlarmConsumer : IMxAccessAlarmConsumer
|
||||
// have exactly that many active alarms. Treat the ambiguous case as
|
||||
// truncated: the false-positive cost is a snapshot that stays stale for
|
||||
// one poll, the false-negative cost is every alarm past the cap reading
|
||||
// as cleared. (The reply's ALARM_RECORDS/@COUNT attribute is a
|
||||
// candidate exact signal, but only if it reports the total rather than
|
||||
// the records in the reply — untested on a live rig, see
|
||||
// docs/AlarmProbeFindings.md.)
|
||||
// as cleared. (The reply's ALARM_RECORDS/@COUNT attribute was the
|
||||
// candidate exact signal; the 2026-08-18 live probe settled it — with
|
||||
// three alarms active, @COUNT read 3, 1, and 2 at caps 1024, 1, and 2,
|
||||
// matching the records in the reply every time. It reports the reply,
|
||||
// not the total, so it cannot tell a capped fetch from a complete one.
|
||||
// See docs/AlarmProbeFindings.md.)
|
||||
bool truncated = IsTruncatedFetch(fetchedRecordCount, maxAlarmsPerFetch);
|
||||
|
||||
IReadOnlyList<MxAlarmTransitionEvent> transitions =
|
||||
@@ -491,12 +493,14 @@ public sealed class WnWrapAlarmConsumer : IMxAccessAlarmConsumer
|
||||
/// offers no confirmed "more available" flag, so a reply at exactly the
|
||||
/// cap is indistinguishable from a galaxy that happens to hold exactly
|
||||
/// that many active alarms; both are treated as truncated. The reply
|
||||
/// root carries an <c>ALARM_RECORDS/@COUNT</c> attribute that would make
|
||||
/// the test exact if it reported the total active count rather than the
|
||||
/// records in this reply; a live probe could not discriminate the two
|
||||
/// (see <c>docs/AlarmProbeFindings.md</c>), so the count is deliberately
|
||||
/// not trusted here. Exposed as <c>internal static</c> so the rule is
|
||||
/// unit-testable without the wnwrapConsumer COM object.
|
||||
/// root carries an <c>ALARM_RECORDS/@COUNT</c> attribute that would have
|
||||
/// made the test exact had it reported the total active count; the
|
||||
/// 2026-08-18 live probe observed it tracking the records in the reply
|
||||
/// instead (three alarms active returned @COUNT 1 at cap 1 and 2 at cap
|
||||
/// 2), so it carries no "more available" information and is deliberately
|
||||
/// not trusted here (see <c>docs/AlarmProbeFindings.md</c>). Exposed as
|
||||
/// <c>internal static</c> so the rule is unit-testable without the
|
||||
/// wnwrapConsumer COM object.
|
||||
/// </summary>
|
||||
/// <param name="fetchedRecordCount">ALARM records the reply carried.</param>
|
||||
/// <param name="maxAlarmsPerFetch">The cap that was passed to the fetch.</param>
|
||||
@@ -660,10 +664,17 @@ public sealed class WnWrapAlarmConsumer : IMxAccessAlarmConsumer
|
||||
/// <em>instance</em> rather than the state it is in: a re-minted
|
||||
/// GUID would read as the old alarm vanishing and a new one
|
||||
/// appearing, i.e. a spurious Clear plus a spurious Raise. Live
|
||||
/// capture confirms stability across the active→returned leg only
|
||||
/// (<c>docs/AlarmClientDiscovery.md</c>); the acknowledge leg and
|
||||
/// re-raise-after-clear are assumed, not observed, because the dev
|
||||
/// rig's alarm attributes reject unauthenticated writes — see
|
||||
/// capture confirms stability across the active→returned leg
|
||||
/// (<c>docs/AlarmClientDiscovery.md</c>, reconfirmed 2026-08-18).
|
||||
/// Re-raise-after-clear was observed on 2026-08-18 and does
|
||||
/// <em>not</em> reuse the GUID: clearing left the returned record in
|
||||
/// place under its original GUID and the re-raise replaced it with a
|
||||
/// freshly minted one. That is the intended reading — the old
|
||||
/// instance ended and a new one began — so the diff emits a Clear
|
||||
/// plus a Raise, which is what happened. The acknowledge leg remains
|
||||
/// assumed rather than observed: on the dev rig every ack surface is
|
||||
/// inert (<c>AlarmAckByName</c> returns 0 and nothing moves), so no
|
||||
/// record can be driven into an acknowledged state — see
|
||||
/// <c>docs/AlarmProbeFindings.md</c>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
|
||||
Reference in New Issue
Block a user