fix(alarms,acl): one condition event per Part 9 method call; gate Server-object model-change/audit events (#587, #588)
#587 — a client Acknowledge/Confirm/AddComment/Shelve/Enable/Disable emitted no condition event: the SDK's ReportStateChange is gated on AutoReportStateChanges (never set), and the engine re-projection found the node already in the new state and was suppressed by the delta-gate. WireMethodTransitionReporting now wraps each method's SDK handler and, on Good, reports exactly once via ReportConditionEvent, under Lock so the routed re-projection cannot race in ahead of the SDK applying the state. Independent of the SDK's imprecise AreEventsMonitored bookkeeping. The shelve methods' state is applied by the wrapper (the SDK never applies it). Native conditions' Confirmed/Shelving are method-owned (NativeAlarmProjector's constants would re-report every ack and undo every shelve). MaxTimeShelved set to int.MaxValue ms at materialise — Create() left it 0 and every TimedShelve failed BadShelvingTimeOutOfRange. Wrong "SDK auto-fires (E2)" comments fixed. #588 — with ACL on, GeneralModelChange and AuditWriteUpdate events no longer go to Server.ReportEvent (ungated to every i=2253 item): ReportToServerObject delivers them per item, a model change narrowed to the Affected nodes the session may Browse (dropped when none), an audit write only with Read on its source plus the SDK audit/encryption guard. Flag off: unchanged. #586 review: materialise raises only the new condition's monitored unit (was a full O(N) reconcile per condition ⇒ O(N²) deploys under Lock). Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
+47
-5
@@ -371,11 +371,53 @@ hosted service.
|
||||
`OnTimedUnshelve` (the SDK's automatic unshelve timer) bypasses the
|
||||
operator gate — it is system-initiated.
|
||||
|
||||
`WriteAlarmCondition` fires the Part 9 condition event only when the
|
||||
incoming state differs from the node's current live state (delta-gate),
|
||||
preventing the double-emit that would otherwise occur when the SDK
|
||||
auto-applies the acked state and the engine re-projection fires a
|
||||
duplicate event immediately after.
|
||||
### One condition event per method call (Gitea #587)
|
||||
|
||||
Every accepted Part 9 method call — Acknowledge, Confirm, AddComment,
|
||||
OneShotShelve, TimedShelve, Unshelve, Enable, Disable, on a native or a
|
||||
scripted condition — reports **exactly one** condition event carrying the
|
||||
new state, to folder subscribers and (through the ACL) Server-object
|
||||
subscribers alike. A refused call (bad status, e.g. a denied gate, an
|
||||
already-acknowledged condition, Enable/Disable on a native condition)
|
||||
reports nothing.
|
||||
|
||||
- **The SDK does not report it.** `ConditionState.ReportStateChange` is
|
||||
gated on `AutoReportStateChanges`, which this repo leaves `false` — so
|
||||
before #587 a client Acknowledge produced no condition event at all (an
|
||||
older comment claimed "the SDK auto-fires its own event"; it never did).
|
||||
Instead the node manager wraps each method's SDK handler
|
||||
(`WireMethodTransitionReporting`) and, once the handler returns Good,
|
||||
reports the condition itself. That report does not depend on the SDK's
|
||||
`AreEventsMonitored` bookkeeping, which is imprecise for conditions
|
||||
materialised under an already-subscribed folder.
|
||||
- **The SDK's shelve methods never apply shelving state** (they only call
|
||||
`OnShelve`), so the wrapper applies it — with the same
|
||||
`(shelved, oneShot, 0)` mapping the projection uses — before reporting.
|
||||
- **The re-projection does not duplicate it.** The engine's (or driver's)
|
||||
answer to the routed command reaches `WriteAlarmCondition`'s delta-gate,
|
||||
which compares against the node's current state — already the new state —
|
||||
and suppresses. The whole method call runs under the node-manager lock, so
|
||||
a fast re-projection cannot slip in between the SDK applying the state and
|
||||
the report.
|
||||
- **Native conditions: Confirmed and Shelving are method-owned.**
|
||||
`NativeAlarmProjector` models neither (it always says `Confirmed = true`,
|
||||
`Unshelved`), while the SDK's Acknowledge sets `Confirmed = false`. So for a
|
||||
native condition `WriteAlarmCondition` neither projects nor compares those
|
||||
two fields — otherwise the driver's echo of an ack would report a second
|
||||
event, and the next driver transition would undo (and report) a local
|
||||
shelve. A native condition starts Confirmed; Confirm and the shelve methods
|
||||
are still not routed to the driver (local only, as before), but they now
|
||||
stick. Scripted conditions are unchanged: the engine models both fields.
|
||||
- **TimedShelve works.** `Create()` left `MaxTimeShelved` at 0, and the SDK
|
||||
rejects any shelving time above it, so every client TimedShelve failed with
|
||||
`BadShelvingTimeOutOfRange`. It is now `int.MaxValue` ms (≈24.8 days — the
|
||||
longest the SDK's own shelving timer can hold).
|
||||
- Method **audit** events (`AuditConditionAcknowledgeEventType`, …) are still
|
||||
the SDK's, delivered only while the server is auditing over an encrypted
|
||||
channel.
|
||||
|
||||
Engine-driven transitions are unaffected: `WriteAlarmCondition` still fires
|
||||
whenever the incoming state differs from the node's current state.
|
||||
|
||||
### AdminUI path
|
||||
|
||||
|
||||
Reference in New Issue
Block a user