Finding
The pipeline fans each GeoSignal down two independent, unsynchronized channels — the aggregator path (inner_tx, broadcast) and the grid path (signal_tx, mpsc) — then consumes them in one select! loop marked biased. biased forces the loop to poll the anomaly-drain branch (agg_rx) before the grid-ingest branch (signal_rx) on every iteration. When an anomaly arrives, convergence is evaluated against a grid that does not yet contain the signal that produced the anomaly, nor any co-located signals still queued in signal_rx.
Evidence
crates/semaino/src/pipeline.rs:169 declares biased;, ordering the two branches that follow. The anomaly branch at line 172 (Some(aggregated) = agg_rx.recv() => {) calls self.handle_aggregated(&aggregated), which runs self.grid.detect(...) (line 219). The grid is only mutated in the lower-priority branch at line 180 (self.grid.ingest(&s);). Because the same signal travels both paths concurrently, the aggregator can score it Anomalous and reach handle_aggregated while that signal (and earlier same-cell signals) are still buffered in signal_rx, un-ingested.
Why this matters
Multi-domain spatial convergence is the core detection product of this counter-surveillance tool (REQ-06): several distinct signal domains converging on one location is exactly the high-value event. Because detect() systematically runs one step behind ingestion, real convergences are under-counted at the moment they matter, so classify() downgrades Critical→High and Medium→Low (alert.rs:226-239). Worse, under a sustained burst of notable signals — an adversary flooding anomalous OTA frames is the documented threat — the biased branch keeps agg_rx hot and can starve the grid-ingest branch, blinding convergence detection precisely during an attack while signal_tx backs up.
Desired correction
Ingest the signal into the grid before (or in the same critical section as) evaluating convergence for the anomaly it produced — e.g. drive aggregation and grid ingestion from a single ordered stream, or carry the triggering signal/cell through the aggregated message so handle_aggregated ingests-then-detects rather than detecting against stale grid state. Remove the unconditional biased priority that guarantees the ingest path is always serviced last. Done when: a test that sends N co-located multi-domain signals immediately followed by an anomaly deterministically observes the convergence (correct severity) regardless of task-scheduling order.
Finding
The pipeline fans each
GeoSignaldown two independent, unsynchronized channels — the aggregator path (inner_tx, broadcast) and the grid path (signal_tx, mpsc) — then consumes them in oneselect!loop markedbiased.biasedforces the loop to poll the anomaly-drain branch (agg_rx) before the grid-ingest branch (signal_rx) on every iteration. When an anomaly arrives, convergence is evaluated against a grid that does not yet contain the signal that produced the anomaly, nor any co-located signals still queued insignal_rx.Evidence
crates/semaino/src/pipeline.rs:169declaresbiased;, ordering the two branches that follow. The anomaly branch at line 172 (Some(aggregated) = agg_rx.recv() => {) callsself.handle_aggregated(&aggregated), which runsself.grid.detect(...)(line 219). The grid is only mutated in the lower-priority branch at line 180 (self.grid.ingest(&s);). Because the same signal travels both paths concurrently, the aggregator can score it Anomalous and reachhandle_aggregatedwhile that signal (and earlier same-cell signals) are still buffered insignal_rx, un-ingested.Why this matters
Multi-domain spatial convergence is the core detection product of this counter-surveillance tool (REQ-06): several distinct signal domains converging on one location is exactly the high-value event. Because
detect()systematically runs one step behind ingestion, real convergences are under-counted at the moment they matter, soclassify()downgrades Critical→High and Medium→Low (alert.rs:226-239). Worse, under a sustained burst of notable signals — an adversary flooding anomalous OTA frames is the documented threat — the biased branch keepsagg_rxhot and can starve the grid-ingest branch, blinding convergence detection precisely during an attack whilesignal_txbacks up.Desired correction
Ingest the signal into the grid before (or in the same critical section as) evaluating convergence for the anomaly it produced — e.g. drive aggregation and grid ingestion from a single ordered stream, or carry the triggering signal/cell through the aggregated message so
handle_aggregatedingests-then-detects rather than detecting against stale grid state. Remove the unconditionalbiasedpriority that guarantees the ingest path is always serviced last. Done when: a test that sends N co-located multi-domain signals immediately followed by an anomaly deterministically observes the convergence (correct severity) regardless of task-scheduling order.