docs(alarms): describe the real StreamAlarms open sequence, and close TST-16
The final integration review's non-blocker reservations, all documentation or comment truth except one test arm. The alarm feed opens provider_status -> snapshot_status -> cached active_alarm -> snapshot_complete, which is what GatewayAlarmMonitor has done since the snapshot_status frame landed. Two places still described the old order: docs/Grpc.md said provider_status arrived *after* the initial snapshot, contradicting its own snapshot_status section two paragraphs down, and AlarmFeedMessage's leading proto comment named neither status frame at all. Both now state the sequence the monitor emits, so a client author reading either one gets the frame order right. The proto comment change flows through the generated trees (Contracts, Go, Java) and the client descriptor set; the Rust vendored copy stays byte-identical to canonical. Python's generator does not carry proto comments into its output, so it has no delta. AlarmsHubPublisherTests' valueless-payload case covered snapshot_complete and provider_status but not snapshot_status, leaving the newest arm unpinned against the redaction switch that must ignore it. Added. WnWrapAlarmConsumer's ack comment led with the 2026-05-01 reading that -55 tracks the 8-arg overload, then refuted itself six lines later with the 2026-08-18 probe. It now leads with the observation labelled as narrower than it reads -- mirroring the correction already in docs/AlarmClientDiscovery.md -- so the block argues one thing: the 6-arg call site stays for parity, and rc semantics are per the probe. A paragraph orphaned by an earlier splice is rewrapped. Comment interior only; the file compiles on Windows. TST-16 gets a dated closure note rather than a rewrite: the flag it called dead was implemented 2026-08-18. GatewayDashboardDesign's /browse paragraph gains the failed-read carve-out GatewayConfiguration already documented, so the two agree that a failed read keeps its - placeholder.
This commit is contained in:
@@ -355,6 +355,8 @@ If TST-02's interim mitigation (flip retention off) is chosen instead of impleme
|
||||
|
||||
**Verification.** `dotnet build src/ZB.MOM.WW.MxGateway.Server`; test toggling the flag suppresses/shows values; docs match.
|
||||
|
||||
**Closed 2026-08-18 — implemented (the Design's recommended path).** The flag is no longer dead: it now gates value display on all three seams that carry a tag value. `Dashboard/DashboardLiveDataService.cs` substitutes `[redacted]` at the service boundary for each successfully read `/browse` tag (a failed read keeps its `-` placeholder, so the error row and the redaction cannot contradict each other); `Dashboard/Hubs/AlarmsHubPublisher.cs` clears `current_value`/`limit_value` from a **deep-cloned** `AlarmFeedMessage` before broadcasting to `/hubs/alarms`, and `DashboardEventBroadcaster` does the same for the events-hub mirror — clones, because the sources fan out to gRPC `StreamAlarms`/`StreamEvents` and the replay ring, so no gRPC client is affected by this dashboard-display flag. The flag is **kept**, default still `false`. The stale prose the Finding flagged is gone: `docs/GatewayConfiguration.md` and `docs/GatewayDashboardDesign.md` now describe the real behaviour. Status tracked in `00-tracking.md` (already **Done**).
|
||||
|
||||
---
|
||||
|
||||
## TST-17 — Vendor-gated alarm parity residuals silently lossy `Medium` · `—`
|
||||
|
||||
@@ -7342,9 +7342,13 @@ func (x *StreamAlarmsRequest) GetAlarmFilterPrefix() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// One message on the StreamAlarms feed. The stream opens with one
|
||||
// `active_alarm` per currently-active alarm, then a single
|
||||
// `snapshot_complete`, then a `transition` for every subsequent change.
|
||||
// One message on the StreamAlarms feed. The stream opens with a
|
||||
// `provider_status` (the current provider mode), then a `snapshot_status`
|
||||
// (whether the cached active set may be incomplete), then one `active_alarm`
|
||||
// per currently-active alarm, then a single `snapshot_complete`. After that
|
||||
// come the live frames: a `transition` for every subsequent alarm change,
|
||||
// interleaved with a further `provider_status` on each failover/failback and a
|
||||
// further `snapshot_status` on each change of the truncation verdict.
|
||||
type AlarmFeedMessage struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// Types that are valid to be assigned to Payload:
|
||||
|
||||
@@ -89203,9 +89203,13 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
|
||||
}
|
||||
/**
|
||||
* <pre>
|
||||
* One message on the StreamAlarms feed. The stream opens with one
|
||||
* `active_alarm` per currently-active alarm, then a single
|
||||
* `snapshot_complete`, then a `transition` for every subsequent change.
|
||||
* One message on the StreamAlarms feed. The stream opens with a
|
||||
* `provider_status` (the current provider mode), then a `snapshot_status`
|
||||
* (whether the cached active set may be incomplete), then one `active_alarm`
|
||||
* per currently-active alarm, then a single `snapshot_complete`. After that
|
||||
* come the live frames: a `transition` for every subsequent alarm change,
|
||||
* interleaved with a further `provider_status` on each failover/failback and a
|
||||
* further `snapshot_status` on each change of the truncation verdict.
|
||||
* </pre>
|
||||
*
|
||||
* Protobuf type {@code mxaccess_gateway.v1.AlarmFeedMessage}
|
||||
@@ -89739,9 +89743,13 @@ public final class MxaccessGateway extends com.google.protobuf.GeneratedFile {
|
||||
}
|
||||
/**
|
||||
* <pre>
|
||||
* One message on the StreamAlarms feed. The stream opens with one
|
||||
* `active_alarm` per currently-active alarm, then a single
|
||||
* `snapshot_complete`, then a `transition` for every subsequent change.
|
||||
* One message on the StreamAlarms feed. The stream opens with a
|
||||
* `provider_status` (the current provider mode), then a `snapshot_status`
|
||||
* (whether the cached active set may be incomplete), then one `active_alarm`
|
||||
* per currently-active alarm, then a single `snapshot_complete`. After that
|
||||
* come the live frames: a `transition` for every subsequent alarm change,
|
||||
* interleaved with a further `provider_status` on each failover/failback and a
|
||||
* further `snapshot_status` on each change of the truncation verdict.
|
||||
* </pre>
|
||||
*
|
||||
* Protobuf type {@code mxaccess_gateway.v1.AlarmFeedMessage}
|
||||
|
||||
Binary file not shown.
@@ -1003,9 +1003,13 @@ message StreamAlarmsRequest {
|
||||
string alarm_filter_prefix = 2;
|
||||
}
|
||||
|
||||
// One message on the StreamAlarms feed. The stream opens with one
|
||||
// `active_alarm` per currently-active alarm, then a single
|
||||
// `snapshot_complete`, then a `transition` for every subsequent change.
|
||||
// One message on the StreamAlarms feed. The stream opens with a
|
||||
// `provider_status` (the current provider mode), then a `snapshot_status`
|
||||
// (whether the cached active set may be incomplete), then one `active_alarm`
|
||||
// per currently-active alarm, then a single `snapshot_complete`. After that
|
||||
// come the live frames: a `transition` for every subsequent alarm change,
|
||||
// interleaved with a further `provider_status` on each failover/failback and a
|
||||
// further `snapshot_status` on each change of the truncation verdict.
|
||||
message AlarmFeedMessage {
|
||||
oneof payload {
|
||||
// Part of the initial active-alarm snapshot (ConditionRefresh).
|
||||
|
||||
@@ -520,7 +520,9 @@ panel. The panel shows each subscribed tag's live value, MXAccess data type,
|
||||
quality and source timestamp, refreshed every two seconds — but the value column
|
||||
obeys `Dashboard:ShowTagValues` like every other dashboard value surface. With
|
||||
the flag false (the default) `DashboardLiveDataService` hands the page
|
||||
`[redacted]` in place of the formatted value; data type, quality, source
|
||||
`[redacted]` in place of the formatted value of each **successfully read** tag —
|
||||
a failed read keeps its `-` placeholder, since there was no value to suppress —
|
||||
while data type, quality, source
|
||||
timestamp and any read error are untouched, so the panel still answers "is this
|
||||
tag advising and healthy" without disclosing the value. The substitution happens
|
||||
at the service, not in the page: one decision point, and the value never enters
|
||||
|
||||
+5
-4
@@ -114,10 +114,11 @@ message AlarmProviderStatus {
|
||||
}
|
||||
```
|
||||
|
||||
The gateway emits `provider_status` once when a client first subscribes
|
||||
(immediately after the initial snapshot and before the first live transition)
|
||||
and again on every failover or failback. A late-joining client therefore
|
||||
always learns the current provider mode without waiting for the next switch.
|
||||
The gateway emits `provider_status` once when a client first subscribes — as
|
||||
the **first** frame on the stream, before the `snapshot_status` frame and
|
||||
before any cached `active_alarm` — and again on every failover or failback. A
|
||||
late-joining client therefore learns the current provider mode before it sees a
|
||||
single alarm, without waiting for the next switch.
|
||||
|
||||
#### Snapshot completeness on the alarm feed
|
||||
|
||||
|
||||
@@ -28513,9 +28513,13 @@ namespace ZB.MOM.WW.MxGateway.Contracts.Proto {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// One message on the StreamAlarms feed. The stream opens with one
|
||||
/// `active_alarm` per currently-active alarm, then a single
|
||||
/// `snapshot_complete`, then a `transition` for every subsequent change.
|
||||
/// One message on the StreamAlarms feed. The stream opens with a
|
||||
/// `provider_status` (the current provider mode), then a `snapshot_status`
|
||||
/// (whether the cached active set may be incomplete), then one `active_alarm`
|
||||
/// per currently-active alarm, then a single `snapshot_complete`. After that
|
||||
/// come the live frames: a `transition` for every subsequent alarm change,
|
||||
/// interleaved with a further `provider_status` on each failover/failback and a
|
||||
/// further `snapshot_status` on each change of the truncation verdict.
|
||||
/// </summary>
|
||||
[global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
|
||||
public sealed partial class AlarmFeedMessage : pb::IMessage<AlarmFeedMessage>
|
||||
|
||||
@@ -1003,9 +1003,13 @@ message StreamAlarmsRequest {
|
||||
string alarm_filter_prefix = 2;
|
||||
}
|
||||
|
||||
// One message on the StreamAlarms feed. The stream opens with one
|
||||
// `active_alarm` per currently-active alarm, then a single
|
||||
// `snapshot_complete`, then a `transition` for every subsequent change.
|
||||
// One message on the StreamAlarms feed. The stream opens with a
|
||||
// `provider_status` (the current provider mode), then a `snapshot_status`
|
||||
// (whether the cached active set may be incomplete), then one `active_alarm`
|
||||
// per currently-active alarm, then a single `snapshot_complete`. After that
|
||||
// come the live frames: a `transition` for every subsequent alarm change,
|
||||
// interleaved with a further `provider_status` on each failover/failback and a
|
||||
// further `snapshot_status` on each change of the truncation verdict.
|
||||
message AlarmFeedMessage {
|
||||
oneof payload {
|
||||
// Part of the initial active-alarm snapshot (ConditionRefresh).
|
||||
|
||||
@@ -119,13 +119,21 @@ public sealed class AlarmsHubPublisherTests
|
||||
},
|
||||
};
|
||||
|
||||
AlarmFeedMessage snapshotStatus = new()
|
||||
{
|
||||
SnapshotStatus = new AlarmSnapshotStatus { Truncated = true },
|
||||
};
|
||||
|
||||
CapturingHubContext hubContext = await RunPublisherAsync(
|
||||
showTagValues: false,
|
||||
snapshotComplete,
|
||||
providerStatus);
|
||||
providerStatus,
|
||||
snapshotStatus);
|
||||
|
||||
Assert.Same(snapshotComplete, hubContext.Sent[0]);
|
||||
Assert.Same(providerStatus, hubContext.Sent[1]);
|
||||
Assert.Same(snapshotStatus, hubContext.Sent[2]);
|
||||
Assert.True(hubContext.Sent[2].SnapshotStatus.Truncated);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -345,23 +345,28 @@ public sealed class WnWrapAlarmConsumer : IMxAccessAlarmConsumer
|
||||
?? throw new InvalidOperationException(
|
||||
"Cannot acknowledge: WnWrapAlarmConsumer was disposed or has not been subscribed yet.");
|
||||
|
||||
// Empirically (live dev-rig 2026-05-01): the IwwAlarmConsumer2
|
||||
// 8-arg AlarmAckByName returns -55 on this AVEVA build (looks like
|
||||
// a stub) and the legacy 6-arg IwwAlarmConsumer.AlarmAckByName
|
||||
// returns 0, which is why the 6-arg overload is the one called here.
|
||||
// A zero return is NOT evidence the acknowledgement was applied: the
|
||||
// 2026-08-18 probe acked a real, freshly raised alarm six ways and
|
||||
// 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 that rig the ack is accepted and then inert, and the
|
||||
// -55 tracks the consumer rather than the overload — both overloads
|
||||
// return -55 against a SetXmlAlarmQuery-applied consumer and 0 against
|
||||
// the ack-only one. Whether any wnwrap ack reaches the alarm-history
|
||||
// path is therefore unverified; see docs/AlarmProbeFindings.md.
|
||||
// Operator-domain
|
||||
// and operator-full-name fields are accepted by the proto contract
|
||||
// for forward-compat but are not propagated to AVEVA today —
|
||||
// wrapped in the 6-arg call so domain/full-name go to the
|
||||
// The original observation (live dev-rig 2026-05-01) was narrower than
|
||||
// it read: the IwwAlarmConsumer2 8-arg AlarmAckByName returned -55
|
||||
// "and looked like a stub" while the legacy 6-arg
|
||||
// IwwAlarmConsumer.AlarmAckByName returned 0, which is how the 6-arg
|
||||
// overload came to be the one called here. The 2026-08-18 probe
|
||||
// corrected both halves. The -55 tracks the *consumer*, not the
|
||||
// overload: both overloads return -55 against a
|
||||
// SetXmlAlarmQuery-applied consumer and 0 against the ack-only one
|
||||
// used above. And a zero return is NOT evidence the acknowledgement
|
||||
// was applied — the probe acked a real, freshly raised alarm six ways
|
||||
// and 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 that rig the ack is accepted and then inert, so
|
||||
// whether any wnwrap ack reaches the alarm-history path is
|
||||
// unverified; see docs/AlarmProbeFindings.md and the 2026-08-18
|
||||
// correction in docs/AlarmClientDiscovery.md. The 6-arg call site
|
||||
// below therefore stays as-is for MXAccess parity — the choice is no
|
||||
// longer justified by the rc, and rc semantics are per the probe.
|
||||
//
|
||||
// Operator-domain and operator-full-name fields are accepted by the
|
||||
// proto contract for forward-compat but are not propagated to AVEVA
|
||||
// today — wrapped in the 6-arg call so domain/full-name go to the
|
||||
// alarm-history operator-name field via the szOprName parameter.
|
||||
// Suppress unused-warning explicitly:
|
||||
_ = ackOperatorDomain;
|
||||
|
||||
Reference in New Issue
Block a user