Symptom

Found while building a demo swarm, not by a field report. A HighThreshold / LowThreshold trigger wired with the same node as both sources and inputs never fires — no error, no warning, no visual hint on the canvas. The trigger’s own meta.snapshot.value visibly updates on every source invocation (so it looks alive), the downstream observer never runs, and node.error stays empty.

Wiring the same node to both sources and inputs is also the documented recipe for every other observer type in the system — logic gates, lambdas, lamps — so it is the natural next step to try on a threshold, and it is exactly wrong there.

Root cause

ServerTriggerProcessor.process() resolves the threshold (“limit”) by looking for an inputs entry whose node type carries the @DataSource annotation:

1
2
3
4
val triggerSnapshot =
    inputs.firstOrNull { it.type.javaClass.isAnnotationPresent(DataSource::class.java) }
        ?.let { input -> (input.meta as SourceMetaData).snapshot }
        ?: meta.snapshot

and separately resolves the sample from by — the node that invoked the trigger:

1
val sample = (source.meta as SourceMetaData).snapshot.value.toDoubleOrNull() ?: 0.0

If the same node is wired as both sources (so it invokes the trigger, i.e. becomes by) and inputs (so it is read back as the limit), threshold and sample are read from the same snapshot on the same invocation — they are always equal. sample > threshold (and sample < threshold) can then never be true, so the trigger can never cross. Nothing rejects or flags this wiring; it type-checks and persists like any other configuration.

Fix

server/src/jvmMain/kotlin/krill/zone/server/krillapp/trigger/ServerTriggerProcessor.kt now excludes any inputs entry whose identity equals the invoking source (by) before searching for the @DataSource limit:

1
meta.inputs.filterNot { it == by }.forEach { ... }

A self-referential input can never be a meaningful limit, so this falls back to the trigger’s own static meta.snapshot — the same behavior as leaving inputs empty — instead of silently comparing the sample to itself.

Prevention