004558c241
Inbound client acks now route through the engine (T18/T19). On a successful Acknowledge the T18 gate returns Good, so the SDK applies the acked state to the AlarmConditionState node and auto-fires its own condition event (E2) -- directly on the node, bypassing WriteAlarmCondition. The engine then re-projects that same transition through WriteAlarmCondition, which fired again (E3): a double-emit. Gate WriteAlarmCondition's ReportConditionEvent on a genuine delta computed against the node's CURRENT live state (read before projecting the snapshot), not a last-written cache (which would be stale, since the SDK-applied ack never went through this method). For a re-projected ack the snapshot equals the node's already-applied state -> no delta -> suppress E3. Genuine engine-driven transitions still differ -> fire. Compared fields (value-equality via AlarmConditionDelta record): Active, Acked, Confirmed, Enabled, Shelving (mapped from the shelving state machine), Severity (mapped through MapSeverity to match the bucket the node stores), Message. Optional Confirmed/Shelving fold to the node read-back default when the child is absent so they can't register a phantom delta. Tests prove both: suppression of the simulated inbound ack re-projection (EventId unchanged) and that genuine transitions fire while identical re-projections suppress; plus a direct unit test of the ShouldFireConditionEvent seam. 102/102 OpcUaServer.Tests green.