Symptom

Krill had two parallel node-on-node dispatch mechanisms: a structural, local-only push (executeChildren() / forceExecuteChildren() — parent stamps its children, verb = actionOf(parent)) and an identity-based, cross-server-capable pull (executeSources() — but it applied the receiver’s verb, actionOf(target)). A Button could not RESET a HighThreshold because the threshold was a tree-locked child of a DataPoint, and the only cross-server path applied the wrong node’s verb.

Root cause

Design, not a bug: the verb was conflated with hierarchy (push path) and with the receiver (pull path). Phase 1 (add-node-action-verb) shipped the verb vocabulary but transported it by stamping the receiver’s NodeState.RESET — a durable intent smuggled through a transient state.

Fix

SDK 0.0.24 universalised TargetingNodeMetaData (every meta type now carries sources/targets/executionSource/nodeAction) and added SourceTriggerPayload + EventType.SOURCE_TRIGGERED. On the krill side: executeChildren/forceExecuteChildren were removed; executeSources() now builds a SourceTriggerPayload(originator.id(), actionOf(originator)) and wakes each subscriber via a new KrillApp.wakeFromSource()NodeProcessor.onSourceTrigger(node, trigger) seam (the emit/post router was refactored to a single KrillApp.processor() lookup so the two paths can never diverge). doNodeVerbOnTarget no longer stamps NodeState.RESET — it is EXECUTE-only self-execution; the verb only ever reaches a receiver through a source read. Per-processor value-gating lives in onSourceTrigger (e.g. ServerTaskListProcessor: Pin source + RESET + value HIGH ⇒ reset tasks). New child nodes default sources += parentIdentity + a firing executionSource in ServerNodeManager.update()’s creation branch, preserving the “drop-under-a-parent” UX. Phase-1 RESET regression tests were rewritten against the new seam, not deleted.

Prevention