feat(contracts): carry operator credentials on alarm acknowledgement
Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT
This commit is contained in:
Binary file not shown.
@@ -391,10 +391,13 @@ message AlarmFailoverConfig {
|
||||
int32 failback_stable_probes = 3; // clean probes before switching back (>=1)
|
||||
}
|
||||
|
||||
// Acknowledge a single alarm by its GUID. Operator identity fields are
|
||||
// recorded atomically with the ack transition in the alarm-history log.
|
||||
// The reply's hresult / native_status surfaces AVEVA's
|
||||
// AlarmAckByGUID return code.
|
||||
// Acknowledge a single alarm by its GUID. The worker resolves the GUID to the
|
||||
// alarm's Galaxy reference from its active-alarm snapshot and acknowledges it
|
||||
// by writing `comment` to `<TAGNAME>.AckMsg` over MXAccess after
|
||||
// AuthenticateUser as the operator, so the alarm history records the real
|
||||
// operator. The reply's hresult / native_status is 0 when the write was
|
||||
// applied (or sent but unconfirmed), otherwise the AuthenticateUser HRESULT or
|
||||
// the OnWriteComplete status detail.
|
||||
message AcknowledgeAlarmCommand {
|
||||
// Canonical 8-4-4-4-12 GUID string (e.g. "BCC47053-9542-4D65-BDAA-BCDEA6A32A73").
|
||||
string alarm_guid = 1;
|
||||
@@ -403,6 +406,10 @@ message AcknowledgeAlarmCommand {
|
||||
string operator_node = 4;
|
||||
string operator_domain = 5;
|
||||
string operator_full_name = 6;
|
||||
// Operator credential used for AuthenticateUser before the AckMsg write.
|
||||
// Implementations must keep this field out of logs, metrics labels, command
|
||||
// lines, and diagnostics.
|
||||
string operator_password = 7;
|
||||
}
|
||||
|
||||
// Snapshot the currently-active alarm set. Optional filter prefix scopes
|
||||
@@ -414,9 +421,7 @@ message QueryActiveAlarmsCommand {
|
||||
|
||||
// Acknowledge a single alarm by its (name, provider, group) tuple. Used
|
||||
// when the public RPC's AlarmFullReference (Provider!Group.Tag) cannot
|
||||
// be resolved to a GUID directly. The worker invokes
|
||||
// wwAlarmConsumerClass.AlarmAckByName which reaches the same alarm
|
||||
// history path as AlarmAckByGUID.
|
||||
// be resolved to a GUID directly. The worker acknowledges by writing `comment` to `<alarm_name>.AckMsg` (Galaxy provider only) after AuthenticateUser as the operator.
|
||||
message AcknowledgeAlarmByNameCommand {
|
||||
// Tag/alarm name (e.g. "TestMachine_001.TestAlarm001"). Tag itself
|
||||
// may contain dots; the gateway-side parser splits on the first dot
|
||||
@@ -431,6 +436,10 @@ message AcknowledgeAlarmByNameCommand {
|
||||
string operator_node = 6;
|
||||
string operator_domain = 7;
|
||||
string operator_full_name = 8;
|
||||
// Operator credential used for AuthenticateUser before the AckMsg write.
|
||||
// Implementations must keep this field out of logs, metrics labels, command
|
||||
// lines, and diagnostics.
|
||||
string operator_password = 9;
|
||||
}
|
||||
|
||||
message UnsubscribeBulkCommand {
|
||||
@@ -725,11 +734,11 @@ message DrainEventsReply {
|
||||
// Reply payload for AcknowledgeAlarmCommand AND
|
||||
// AcknowledgeAlarmByNameCommand — both ack command kinds reuse this
|
||||
// payload case (`MxCommandReply.acknowledge_alarm`); there is no
|
||||
// dedicated by-name reply case. Surfaces AVEVA's native ack return
|
||||
// code (AlarmAckByGUID for the GUID arm, AlarmAckByName for the
|
||||
// by-name arm); 0 means success. The MxCommandReply's hresult field
|
||||
// dedicated by-name reply case. Surfaces the ack outcome: 0 when the
|
||||
// AckMsg write was applied or sent but unconfirmed; otherwise the
|
||||
// AuthenticateUser HRESULT or the OnWriteComplete status detail. The MxCommandReply's hresult field
|
||||
// carries the same value and is preferred for protocol consumers —
|
||||
// this payload exists so the gateway-side WorkerAlarmRpcDispatcher
|
||||
// this payload exists so GatewayAlarmMonitor
|
||||
// can echo native_status into AcknowledgeAlarmReply.hresult without
|
||||
// unpacking the outer envelope.
|
||||
message AcknowledgeAlarmReplyPayload {
|
||||
@@ -987,6 +996,15 @@ message AcknowledgeAlarmRequest {
|
||||
// Operator principal performing the acknowledgement. The lmxopcua side
|
||||
// resolves this from the OPC UA session prior to invoking the RPC.
|
||||
string operator_user = 5;
|
||||
// Operator's Galaxy (or OS) password. The gateway authenticates as
|
||||
// operator_user with it before writing the acknowledgement so the alarm
|
||||
// history records the real operator. May be empty for accounts without a
|
||||
// password. Credential-sensitive: implementations must keep this field out
|
||||
// of logs, metrics labels, command lines, and diagnostics.
|
||||
string operator_password = 6;
|
||||
// Optional domain for OS-authenticated galaxies. When set the gateway
|
||||
// authenticates as "<operator_domain>\<operator_user>".
|
||||
string operator_domain = 7;
|
||||
}
|
||||
|
||||
message AcknowledgeAlarmReply {
|
||||
@@ -997,8 +1015,7 @@ message AcknowledgeAlarmReply {
|
||||
ProtocolStatus protocol_status = 3;
|
||||
// Native ack return code echoed from the worker. The worker carries the
|
||||
// ack outcome as a single int32 (AcknowledgeAlarmReplyPayload.native_status,
|
||||
// = AlarmAckByName / AlarmAckByGUID return code; 0 = success); the gateway's
|
||||
// WorkerAlarmRpcDispatcher copies that value here. This is the authoritative
|
||||
// 0 = applied or sent; otherwise the AuthenticateUser HRESULT or OnWriteComplete detail); GatewayAlarmMonitor copies that value here. This is the authoritative
|
||||
// ack-outcome field for the public RPC. Absent only when the worker reply
|
||||
// omitted the value entirely (a protocol violation).
|
||||
optional int32 hresult = 4;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -391,10 +391,13 @@ message AlarmFailoverConfig {
|
||||
int32 failback_stable_probes = 3; // clean probes before switching back (>=1)
|
||||
}
|
||||
|
||||
// Acknowledge a single alarm by its GUID. Operator identity fields are
|
||||
// recorded atomically with the ack transition in the alarm-history log.
|
||||
// The reply's hresult / native_status surfaces AVEVA's
|
||||
// AlarmAckByGUID return code.
|
||||
// Acknowledge a single alarm by its GUID. The worker resolves the GUID to the
|
||||
// alarm's Galaxy reference from its active-alarm snapshot and acknowledges it
|
||||
// by writing `comment` to `<TAGNAME>.AckMsg` over MXAccess after
|
||||
// AuthenticateUser as the operator, so the alarm history records the real
|
||||
// operator. The reply's hresult / native_status is 0 when the write was
|
||||
// applied (or sent but unconfirmed), otherwise the AuthenticateUser HRESULT or
|
||||
// the OnWriteComplete status detail.
|
||||
message AcknowledgeAlarmCommand {
|
||||
// Canonical 8-4-4-4-12 GUID string (e.g. "BCC47053-9542-4D65-BDAA-BCDEA6A32A73").
|
||||
string alarm_guid = 1;
|
||||
@@ -403,6 +406,10 @@ message AcknowledgeAlarmCommand {
|
||||
string operator_node = 4;
|
||||
string operator_domain = 5;
|
||||
string operator_full_name = 6;
|
||||
// Operator credential used for AuthenticateUser before the AckMsg write.
|
||||
// Implementations must keep this field out of logs, metrics labels, command
|
||||
// lines, and diagnostics.
|
||||
string operator_password = 7;
|
||||
}
|
||||
|
||||
// Snapshot the currently-active alarm set. Optional filter prefix scopes
|
||||
@@ -414,9 +421,7 @@ message QueryActiveAlarmsCommand {
|
||||
|
||||
// Acknowledge a single alarm by its (name, provider, group) tuple. Used
|
||||
// when the public RPC's AlarmFullReference (Provider!Group.Tag) cannot
|
||||
// be resolved to a GUID directly. The worker invokes
|
||||
// wwAlarmConsumerClass.AlarmAckByName which reaches the same alarm
|
||||
// history path as AlarmAckByGUID.
|
||||
// be resolved to a GUID directly. The worker acknowledges by writing `comment` to `<alarm_name>.AckMsg` (Galaxy provider only) after AuthenticateUser as the operator.
|
||||
message AcknowledgeAlarmByNameCommand {
|
||||
// Tag/alarm name (e.g. "TestMachine_001.TestAlarm001"). Tag itself
|
||||
// may contain dots; the gateway-side parser splits on the first dot
|
||||
@@ -431,6 +436,10 @@ message AcknowledgeAlarmByNameCommand {
|
||||
string operator_node = 6;
|
||||
string operator_domain = 7;
|
||||
string operator_full_name = 8;
|
||||
// Operator credential used for AuthenticateUser before the AckMsg write.
|
||||
// Implementations must keep this field out of logs, metrics labels, command
|
||||
// lines, and diagnostics.
|
||||
string operator_password = 9;
|
||||
}
|
||||
|
||||
message UnsubscribeBulkCommand {
|
||||
@@ -725,11 +734,11 @@ message DrainEventsReply {
|
||||
// Reply payload for AcknowledgeAlarmCommand AND
|
||||
// AcknowledgeAlarmByNameCommand — both ack command kinds reuse this
|
||||
// payload case (`MxCommandReply.acknowledge_alarm`); there is no
|
||||
// dedicated by-name reply case. Surfaces AVEVA's native ack return
|
||||
// code (AlarmAckByGUID for the GUID arm, AlarmAckByName for the
|
||||
// by-name arm); 0 means success. The MxCommandReply's hresult field
|
||||
// dedicated by-name reply case. Surfaces the ack outcome: 0 when the
|
||||
// AckMsg write was applied or sent but unconfirmed; otherwise the
|
||||
// AuthenticateUser HRESULT or the OnWriteComplete status detail. The MxCommandReply's hresult field
|
||||
// carries the same value and is preferred for protocol consumers —
|
||||
// this payload exists so the gateway-side WorkerAlarmRpcDispatcher
|
||||
// this payload exists so GatewayAlarmMonitor
|
||||
// can echo native_status into AcknowledgeAlarmReply.hresult without
|
||||
// unpacking the outer envelope.
|
||||
message AcknowledgeAlarmReplyPayload {
|
||||
@@ -987,6 +996,15 @@ message AcknowledgeAlarmRequest {
|
||||
// Operator principal performing the acknowledgement. The lmxopcua side
|
||||
// resolves this from the OPC UA session prior to invoking the RPC.
|
||||
string operator_user = 5;
|
||||
// Operator's Galaxy (or OS) password. The gateway authenticates as
|
||||
// operator_user with it before writing the acknowledgement so the alarm
|
||||
// history records the real operator. May be empty for accounts without a
|
||||
// password. Credential-sensitive: implementations must keep this field out
|
||||
// of logs, metrics labels, command lines, and diagnostics.
|
||||
string operator_password = 6;
|
||||
// Optional domain for OS-authenticated galaxies. When set the gateway
|
||||
// authenticates as "<operator_domain>\<operator_user>".
|
||||
string operator_domain = 7;
|
||||
}
|
||||
|
||||
message AcknowledgeAlarmReply {
|
||||
@@ -997,8 +1015,7 @@ message AcknowledgeAlarmReply {
|
||||
ProtocolStatus protocol_status = 3;
|
||||
// Native ack return code echoed from the worker. The worker carries the
|
||||
// ack outcome as a single int32 (AcknowledgeAlarmReplyPayload.native_status,
|
||||
// = AlarmAckByName / AlarmAckByGUID return code; 0 = success); the gateway's
|
||||
// WorkerAlarmRpcDispatcher copies that value here. This is the authoritative
|
||||
// 0 = applied or sent; otherwise the AuthenticateUser HRESULT or OnWriteComplete detail); GatewayAlarmMonitor copies that value here. This is the authoritative
|
||||
// ack-outcome field for the public RPC. Absent only when the worker reply
|
||||
// omitted the value entirely (a protocol violation).
|
||||
optional int32 hresult = 4;
|
||||
|
||||
@@ -22,4 +22,10 @@ public static class WorkerLaunchEnvironment
|
||||
|
||||
/// <summary>Maximum alarm records requested in one current-alarm fetch.</summary>
|
||||
public const string MaxAlarmsPerFetch = "MXGATEWAY_ALARM_MAX_ALARMS_PER_FETCH";
|
||||
|
||||
/// <summary>
|
||||
/// MXAccess client name the worker's alarm-ack writer registers with. The
|
||||
/// Historian records it as the acknowledgement's OperatorNode.
|
||||
/// </summary>
|
||||
public const string AlarmAckClientName = "MXGATEWAY_ALARM_ACK_CLIENT_NAME";
|
||||
}
|
||||
|
||||
@@ -310,11 +310,47 @@ public sealed class ProtobufContractRoundTripTests
|
||||
AlarmFullReference = "Tank01.Level.HiHi",
|
||||
Comment = "shift handover",
|
||||
OperatorUser = "operator2",
|
||||
OperatorPassword = "s3cret",
|
||||
OperatorDomain = "CORP",
|
||||
};
|
||||
|
||||
var parsed = AcknowledgeAlarmRequest.Parser.ParseFrom(original.ToByteArray());
|
||||
|
||||
Assert.Equal(original, parsed);
|
||||
Assert.Equal("s3cret", parsed.OperatorPassword);
|
||||
Assert.Equal("CORP", parsed.OperatorDomain);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the worker ack commands round-trip the operator password.</summary>
|
||||
[Fact]
|
||||
public void AcknowledgeAlarmCommands_RoundTripOperatorPassword()
|
||||
{
|
||||
MxCommand byGuid = new()
|
||||
{
|
||||
Kind = MxCommandKind.AcknowledgeAlarm,
|
||||
AcknowledgeAlarmCommand = new AcknowledgeAlarmCommand
|
||||
{
|
||||
AlarmGuid = "BCC47053-9542-4D65-BDAA-BCDEA6A32A73",
|
||||
OperatorUser = "alice",
|
||||
OperatorDomain = "CORP",
|
||||
OperatorPassword = "s3cret",
|
||||
},
|
||||
};
|
||||
MxCommand byName = new()
|
||||
{
|
||||
Kind = MxCommandKind.AcknowledgeAlarmByName,
|
||||
AcknowledgeAlarmByNameCommand = new AcknowledgeAlarmByNameCommand
|
||||
{
|
||||
AlarmName = "TestMachine_001.TestAlarm001",
|
||||
ProviderName = "Galaxy",
|
||||
GroupName = "TestArea",
|
||||
OperatorUser = "alice",
|
||||
OperatorPassword = "s3cret",
|
||||
},
|
||||
};
|
||||
|
||||
Assert.Equal("s3cret", MxCommand.Parser.ParseFrom(byGuid.ToByteArray()).AcknowledgeAlarmCommand.OperatorPassword);
|
||||
Assert.Equal("s3cret", MxCommand.Parser.ParseFrom(byName.ToByteArray()).AcknowledgeAlarmByNameCommand.OperatorPassword);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that AcknowledgeAlarmReply round-trips with status, hresult, and diagnostics.</summary>
|
||||
|
||||
Reference in New Issue
Block a user