diff --git a/cmd/ingestor/decoder.go b/cmd/ingestor/decoder.go index f28454e3..56ab95b4 100644 --- a/cmd/ingestor/decoder.go +++ b/cmd/ingestor/decoder.go @@ -599,6 +599,9 @@ func DecodePacket(hexString string, channelKeys map[string]string, validateSigna // We expose hopsCompleted (count of SNR bytes) so consumers can distinguish // how far the trace got vs the full intended route. var anomaly string + if header.PayloadType == PayloadTRACE && payload.Error != "" { + anomaly = fmt.Sprintf("TRACE payload decode failed: %s", payload.Error) + } if header.PayloadType == PayloadTRACE && payload.PathData != "" { // Flag anomalous routing — firmware only sends TRACE as DIRECT if header.RouteType != RouteDirect && header.RouteType != RouteTransportDirect { diff --git a/cmd/ingestor/decoder_test.go b/cmd/ingestor/decoder_test.go index f49b25dc..c781b4aa 100644 --- a/cmd/ingestor/decoder_test.go +++ b/cmd/ingestor/decoder_test.go @@ -1926,3 +1926,24 @@ func TestDecodePathFromRawHex_Transport(t *testing.T) { } } } + +func TestDecodeTracePayloadFailSetsAnomaly(t *testing.T) { + // Issue #889: TRACE packet with payload too short to decode (< 9 bytes) + // should still return a DecodedPacket (observation stored) but with Anomaly + // set to warn operators that the decode was degraded. + // Packet: header 0x26 (TRACE+DIRECT), pathByte 0x00, payload 4 bytes (too short). + pkt, err := DecodePacket("2600aabbccdd", nil, false) + if err != nil { + t.Fatalf("DecodePacket error: %v", err) + } + if pkt.Payload.Type != "TRACE" { + t.Fatalf("payload type=%s, want TRACE", pkt.Payload.Type) + } + if pkt.Payload.Error == "" { + t.Fatal("expected payload.Error to indicate decode failure") + } + // The key assertion: Anomaly must be set when TRACE decode fails + if pkt.Anomaly == "" { + t.Error("expected Anomaly to be set when TRACE payload decode fails but observation is stored") + } +}