KrillApp.Trigger.Color nodes never fired downstream observers regardless of whether the observed COLOR DataPoint’s value fell inside the configured RGB bounding box. Static threshold triggers (HighThreshold, LowThreshold) worked correctly.
ServerTriggerProcessor.process() opened with val meta = node.meta as TriggerMetaData. For a Color trigger, node.meta is ColorTriggerMetaData — a different data class that also implements SourceMetaData but is not a subtype of TriggerMetaData. The cast throws ClassCastException, which launchProcessing catches and routes to nodeManager.failed() — the trigger silently enters NodeState.ERROR on every invocation and never calls succeeded(). The when (node.type) block with its else -> {} arm was never reached.
In server/src/jvmMain/kotlin/krill/zone/server/krillapp/trigger/ServerTriggerProcessor.kt: added an early-return branch at the top of the launchProcessing block that handles KrillApp.Trigger.Color before the TriggerMetaData cast. The branch casts node.meta to ColorTriggerMetaData, reads the source node’s snapshot.value as a packed-RGB Long (0xRRGGBB decimal string), unpacks the R/G/B channels via bit shifts, and calls nodeManager.succeeded(node) when all three channels fall within their configured [min, max] ranges. Added import krill.zone.shared.krillapp.trigger.color.* to the processor’s imports.
KrillApp.Trigger.*) with a distinct MetaData class (not TriggerMetaData), the server processor’s early as TriggerMetaData cast is a ClassCastException waiting to happen. Any processor shared across trigger types should branch on node.type before casting node.meta to a concrete type.else -> {} arm in ServerTriggerProcessor should be replaced with an arm that logs a warning for truly unknown types, so the failure surface is visible at runtime rather than silent.ServerColorTriggerProcessorTest cover in-range fire, out-of-range no-fire, and invalid-value no-fire.