Root cause: enrichObs() looked up the parent transmission via byTxID,
but if the tx had been evicted from memory, the lookup returned nil and
hash/payload_type/route_type/decoded_json were omitted from the response.
Fix:
- enrichObs: fall back to DB lookup (GetTransmissionByID) when the
in-memory tx is missing, ensuring hash and all tx fields are always
present in the response
- GetNodeHealth recentPackets: defensive filter skips entries with nil
hash or timestamp (belt-and-suspenders)
- Observer analytics recentPackets: skip entries missing hash
- Nil-safe: no panic when store has no DB reference
Tests: 4 new tests covering the DB fallback, nil-DB edge case, and
GetNodeHealth field invariants.
Closes#871
Refs #857