From d43c95a4bbfb37252fd07cbbfee0831272c10410 Mon Sep 17 00:00:00 2001 From: Kpa-clawbot Date: Sat, 2 May 2026 20:34:27 -0700 Subject: [PATCH] fix(ingestor): warn when TRACE payload decode fails but observation stored (closes #889) (#992) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Closes #889. When a TRACE packet's payload is too short to decode (< 9 bytes), `decodeTrace` returns an error in `Payload.Error` but the observation is still stored with empty `Path.Hops`. Previously this was completely silent — no log, no anomaly flag, no indication the row is degraded. This fix populates `DecodedPacket.Anomaly` with the decode error message (e.g., `"TRACE payload decode failed: too short"`) so operators and downstream consumers can identify degraded observations. ## TDD Commit History 1. **Red commit** `04e0165` — failing test asserting `Anomaly` is set when TRACE payload decode fails 2. **Green commit** `d3e72d1` — 3-line fix in `decoder.go` line 601-603: check `payload.Error != ""` for TRACE packets and set anomaly ## What Changed `cmd/ingestor/decoder.go` (lines 601-603): Added a check before the existing TRACE path-parsing block. If `payload.Error` is non-empty for a TRACE packet, `anomaly` is set to `"TRACE payload decode failed: "`. `cmd/ingestor/decoder_test.go`: Added `TestDecodeTracePayloadFailSetsAnomaly` — constructs a TRACE packet with a 4-byte payload (too short), asserts the packet is still returned (observation stored) and `Anomaly` is populated. ## Verification - `go build ./...` ✓ - `go test ./...` ✓ (all pass including new test) - Anti-tautology: reverting the fix causes the new test to fail (asserts `pkt.Anomaly == ""` → error) --------- Co-authored-by: you --- cmd/ingestor/decoder.go | 3 +++ cmd/ingestor/decoder_test.go | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+) 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") + } +}