On pi-krill: a CronTimer (every 5 s) → Calculation (“Counter + 1”) → DataPoint “Counter” wired with the Calculation as its source. In the ClientScreen node graph the Cron pulsed, the Counter pulsed and incremented correctly, but the Calculation node never showed the activity color pulse — every cycle, deterministically. The functional chain worked; only the Calc’s visual was missing. Survived the Phase 3 “centralized pulse” work, which was supposed to make every source-invoked node pulse.
Two independent server→client SSE streams exist:
/sse ← ServerNodeManager._nodeUpdates (full Node objects)/events ← EventFlowContainer.events (Event objects)The ClientScreen graph renders activity only from /events
(EventClient → EventType.STATE_CHANGE → ClientNodeManager.update →
post → showActivity). No client consumes /sse at all (UI uses
/events; the peer connector uses /trust; the peer-/sse claim in
PEER_TRUST_ARCHITECTURE.md is stale doc unmatched by code).
broadcastActivityPulse (added in the uniform-source-invocation work)
emitted the PROCESSING pulse only into _nodeUpdates//sse — a
stream nothing reads — and never called EventFlowContainer. Cron and
DataPoint masked the gap because their own state/snapshot changes flow
through ServerNodeManager.update(), which does post a STATE_CHANGE
Event (but only when state != NONE && origin.state != state && Δt > 1s).
A Calculation’s compute path is updateMetaData() → update(state = NONE),
so that guard is false → it emits zero /events events per cycle.
Net: the Calc’s only possible activity signal went to a dead stream.
broadcastActivityPulse now also posts the pulse onto EventFlowContainer
via tryPostEvent(Event(id, STATE_CHANGE, StateChangeEventPayload(PROCESSING)))
— the loss-tolerant API explicitly documented for cosmetic pulses — so it
travels the /events stream the graph actually consumes. The dead
_nodeUpdates.emit is retained for now (the /sse stream is unconsumed
dead infrastructure pending a separate audit/removal, kept out of this
fix’s scope). Regression test in UniformSourcePulseTest asserts
executeSources produces a STATE_CHANGE(PROCESSING) Event on
EventFlowContainer.events for the subscriber (the real dispatch path),
not just on nodeUpdates.
Same class as feedback-verify-dispatch-entry-point: a refactor that
changes how/where a signal is delivered must be verified against the
stream the consumer actually reads, end-to-end — not just asserted at the
producer. The Phase 3 test (executeSources emits a PROCESSING pulse)
collected nodeUpdates, a stream with no consumer, so it passed while
the feature was non-functional. Rule: a “client-visible” assertion must
target the same stream/endpoint the client subscribes to. When a codebase
has parallel broadcast channels (/sse Node stream vs /events Event
stream), a feature touching one must state which the client reads and test
against that one. Bonus signal that was missed: EventFlowContainer
already had a tryPostEvent documented “for PROCESSING pulses” — an
unused, purpose-built API is evidence the producer is wired to the wrong
channel.