Commit Graph

32 Commits

Author SHA1 Message Date
Kpa-clawbot
6ec00a5aab test(go): fix failing test + add 18 tests, coverage 91.5% → 92.3%
Fix TestStoreGetAnalyticsChannelsNumericHash: corrected observation
transmission_id references (3,4,5 → 4,5,6) and changed assertion to
only check numeric-hash channels (seed data CHAN lacks channelHash).

New tests covering gaps:
- resolvePayloadTypeName unknown type fallback
- Cache hit paths for Topology, HashSizes, Channels
- GetChannelMessages edge cases (empty, offset, default limit)
- filterPackets: empty region, since/until, hash-only fast path,
  observer+type combo, node filter
- GetNodeHashSizeInfo: short hex, bad hex, bad JSON, missing pubKey,
  public_key field, flip-flop inconsistency detection
- handleResolveHops: empty param, empty hop skip, nonexistent prefix
- handleObservers error path (closed DB)
- handleAnalyticsChannels DB fallback (no store)
- GetChannelMessages dedup/repeats
- transmissionsForObserver from-slice filter path
- GetPerfStoreStats advertByObserver count
- handleAudioLabBuckets query error path

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 13:13:31 -07:00
Kpa-clawbot
8f712774ae fix: WS broadcast missing decoded fields + cache analytics endpoints
#156: The Go WebSocket broadcast from IngestNewFromDB was missing the
'decoded' field (with header.payloadTypeName) that live.js needs to
display packet types. Added decoded object with payloadTypeName
resolution, plus observer_id, observer_name, snr, rssi, path_json,
and observation_count fields to match the Node.js broadcast shape.

#157-160: Analytics endpoints (hash-sizes, topology, channels) were
recomputing everything on every request. Added:
- TTL caching (15s) for topology, hash-sizes, and channels endpoints
  (matching the existing RF cache pattern)
- Cached node list + prefix map shared across analytics (30s TTL)
- Lazy-cached parsed path JSON on StoreTx (parse once, read many)
- Cache invalidation on new data ingestion
- Global payloadTypeNames map (avoids per-call allocation)

Fixes #156, fixes #157, fixes #158, fixes #159, fixes #160

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 12:17:26 -07:00
Kpa-clawbot
1457795e3e fix: analytics channels + uniqueNodes mismatch
fixes #154: Go analytics channels showed single 'ch?' because
channelHash is a JSON number (from decoder.js) but the Go struct
declared it as string. json.Unmarshal failed on every packet.
Changed to interface{} with proper type conversion. Also fixed
chKey to use hash (not name) for grouping, matching Node.js.

fixes #155: uniqueNodes in topology analytics used hop resolution
count (phantom hops inflated it). Both Node.js and Go now use
db.getStats().totalNodes (7-day active window), matching /api/stats.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 12:07:50 -07:00
Kpa-clawbot
2f5404edc3 fix: close last parity gaps in /api/perf and /api/nodes/:pubkey
- db.go: Add freelistMB (PRAGMA freelist_count * page_size) and walPages
  (PRAGMA wal_checkpoint(PASSIVE)) to GetDBSizeStats
- store.go: Add advertByObserver count to GetPerfStoreStats indexes
  (count distinct pubkeys with ADVERT observations)
- db.go: Add getObservationsForTransmissions helper; enrich
  GetRecentTransmissionsForNode results with observations array,
  _parsedPath, and _parsedDecoded
- db_test.go: Add second ADVERT with different hash_size to seed data
  so hash_sizes_seen is populated; enrich decoded_json with full
  ADVERT fields; update count assertions for new seed row

fixes #151, fixes #152

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 11:57:35 -07:00
Kpa-clawbot
979b649028 fix(go): add missing packetStore fields to /api/perf (inMemory, maxPackets, etc.)
Frontend reads ps.inMemory.toLocaleString() which crashed because
the Go response was missing inMemory, sqliteOnly, maxPackets, maxMB,
evicted, inserts, queries fields. Added all + atomic counters.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 11:39:16 -07:00
Kpa-clawbot
47531e5487 Add golden fixture parity test suite — Go must match Node shapes
- Capture Node.js API response shapes from prod server as golden fixtures
- Store normalized shape schema in cmd/server/testdata/golden/shapes.json
  covering 16 endpoints: stats, nodes, packets (raw + grouped), observers,
  channels, channel_messages, analytics (rf, topology, hash-sizes, distance,
  subpaths), bulk-health, health, perf, and node detail
- Add parity_test.go with recursive shape validator:
  - TestParityShapes: validates Go response keys/types match Node golden
  - TestParityNodeDetail: validates node detail response shape
  - TestParityArraysNotNull: catches nil slices marshaled as null
  - TestParityHealthEngine: verifies Go identifies itself as engine=go
  - TestValidateShapeFunction: unit tests for the validator itself
- Add tools/check-parity.sh for live Node vs Go comparison on VM
- Shape spec handles dynamic-key objects (perObserverReach, perf.endpoints)
- Nullable fields properly marked (observer lat/lon, snr/rssi, hop names)

Current mismatches found (genuine Go bugs):
- /api/perf: packetStore missing 8 fields, sqlite missing 2 fields
- /api/nodes/{pubkey}: missing hash_sizes_seen, observations, _parsedPath,
  _parsedDecoded in node detail response

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 11:37:56 -07:00
Kpa-clawbot
f97b4c78ad fix: add missing status, uptimeHuman, websocket fields to Go /api/perf
The frontend perf dashboard crashes with toLocaleString error because
the Go /api/perf endpoint was missing fields that Node returns:
- status: "ok"
- uptimeHuman: formatted "Xh Ym" string
- websocket: { clients: N } via hub.ClientCount()

The Go /api/health endpoint already had all three fields.

fixes #150

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 11:33:55 -07:00
Kpa-clawbot
4101dc1715 test(go): push Go server test coverage from 77.9% to 91.4% (#145)
Add comprehensive coverage_test.go targeting all major coverage gaps:
- buildPacketWhere: all 8 filter types (0% -> covered)
- QueryMultiNodePackets: DB + store paths (0% -> covered)
- IngestNewFromDB: v3/v2 schema, dedup, defaults (0% -> covered)
- MaxTransmissionID: loaded + empty store (0% -> covered)
- handleBulkHealth DB fallback (24.4% -> covered)
- handleAnalyticsRF DB fallback (11.8% -> covered)
- handlePackets multi-node path (63.5% -> covered)
- handlePacketDetail no-store fallback (71.1% -> covered)
- handleAnalyticsChannels DB fallback (57.1% -> covered)
- detectSchema v2 path (30.8% -> covered)
- Store channel queries, analytics, helpers
- Prefix map resolve with GPS preference
- wsOrStatic, Poller, perfMiddleware slow queries
- Helper functions: pathLen, floatPtrOrNil, nilIfEmpty, etc.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 11:23:27 -07:00
Kpa-clawbot
5bb5bea444 fix(go): channels null arrays + hash size enrichment on nodes
- Fix #148: channels endpoint returned null for msgLengths when no
  decrypted messages exist. Initialize msgLengths as make([]int, 0)
  in store path and guard channels slice in DB fallback path.

- Fix #149: nodes endpoint always returned hash_size=null and
  hash_size_inconsistent=false. Add GetNodeHashSizeInfo() to
  PacketStore that scans advert packets to compute per-node hash
  size, flip-flop detection, and sizes_seen. Enrich nodes in both
  handleNodes and handleNodeDetail with computed hash data.

fixes #148, fixes #149

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 11:21:41 -07:00
Kpa-clawbot
407c49e017 fix(go): add eventLoop to /api/health with GC pause percentiles, fixes #147
Go's /api/health was missing the eventLoop object that Node.js provides.
The perf.js frontend reads health.eventLoop.p95Ms which crashed with
'Cannot read properties of undefined' when served by the Go server.

Adds eventLoop field using GC pause data from runtime.MemStats.PauseNs
(last 256 pauses) to compute p50Ms, p95Ms, p99Ms, currentLagMs, maxLagMs
— matching the Node.js response shape exactly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 11:04:43 -07:00
Kpa-clawbot
8a0f731452 fix: topology uniqueNodes counts only real nodes, not hop prefixes
The Go analytics topology endpoint was counting every unique hop string
from packet paths (including unresolved 1-byte hex prefixes) as a unique
node, inflating the count from ~540 to 6502. Now resolves each hop via
the prefix map and deduplicates by public key, matching the Node.js
behavior.

fixes #146

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 10:50:32 -07:00
Kpa-clawbot
93dbe0e909 fix(go): add runtime stats to /api/perf and /api/health, fixes #143
- /api/perf: add goRuntime (heap, GC, goroutines, CPU), packetStore
  stats (totalLoaded, observations, index sizes, estimatedMB),
  sqlite stats (dbSizeMB, walSizeMB, row counts), real RF cache
  hit/miss tracking, and endpoint sorting by total time spent
- /api/health: add memory.heapMB, goRuntime (goroutines, gcPauses,
  numCPU), real packetStore packet count and estimatedMB, real
  cache stats from RF cache; remove hardcoded-zero eventLoop
- store.go: add cacheHits/cacheMisses tracking in GetAnalyticsRF,
  GetPerfStoreStats() and GetCacheStats() methods
- db.go: add path field to DB struct, GetDBSizeStats() for file
  sizes and row counts
- Tests: verify new fields in health/perf endpoints, add
  TestGetDBSizeStats, wire up PacketStore in test server setup

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 10:45:00 -07:00
Kpa-clawbot
d7172961f4 fix(go): analytics endpoints parity — fixes #134, #135, #136, #137, #138, #140, #142
Implement all analytics endpoints from in-memory PacketStore instead of
returning stubs/empty data. Each handler now matches the Node.js response
shape field-by-field.

Endpoints fixed:
- /api/analytics/topology (#135): full hop distribution, top repeaters,
  top pairs, hops-vs-SNR, per-observer reachability, cross-observer
  comparison, best path analysis
- /api/analytics/distance (#137): haversine distance computation,
  category stats (R↔R, C↔R, C↔C), distance histogram, top hops/paths,
  distance over time
- /api/analytics/hash-sizes (#136): hash size distribution from raw_hex
  path byte parsing, hourly breakdown, top hops, multi-byte node tracking
- /api/analytics/hash-issues (#138): hash-sizes data now populated so
  frontend collision tab can compute inconsistent sizes and collision risk
- /api/analytics/route-patterns (#134): subpaths and subpath-detail now
  compute from in-memory store with hop resolution
- /api/nodes/bulk-health (#140): switched from N per-node SQL queries to
  in-memory PacketStore lookups with observer stats
- /api/channels (#142): response shape already correct via GetChannels;
  analytics/channels now returns topSenders, channelTimeline, msgLengths
- /api/analytics/channels: full channel analytics with sender tracking,
  timeline, and message length distribution

All handlers fall back to DB/stubs when store is nil (test compat).
All 42+ existing Go tests pass. go vet clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 10:23:11 -07:00
Kpa-clawbot
6cdbf7e3f6 perf(go): remove debug logging, update history
Remove temporary rf-cache debug logs. Update hicks history with
endpoint optimization learnings.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 09:49:46 -07:00
Kpa-clawbot
b42d7e3f14 debug: add RF cache logging
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 09:43:40 -07:00
Kpa-clawbot
6158734536 fix(go): fix RF cache - use separate mutex, TTL-only expiry
Previous approach invalidated cache on every ingest (every 1s with live
mesh data). Now uses TTL-only expiry (15s). Separate cache mutex avoids
data race with main store RWMutex.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 09:40:43 -07:00
Kpa-clawbot
0d9b535451 feat: add version and git commit to /api/stats and /api/health
Node.js: reads version from package.json, commit from .git-commit file
or git rev-parse --short HEAD at runtime, with unknown fallback.

Go: uses -ldflags build-time variables (Version, Commit) with fallback
to .git-commit file and git command at runtime.

Dockerfile: copies .git-commit if present (CI bakes it before build).
Dockerfile.go: passes APP_VERSION and GIT_COMMIT as build args to ldflags.
deploy.yml: writes GITHUB_SHA to .git-commit before docker build steps.
docker-compose.yml: passes build args to Go staging build.

Tests updated to verify version and commit fields in both endpoints.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 09:39:49 -07:00
Kpa-clawbot
10c672f8d7 perf(go): add TTL cache for RF analytics response
Cache the computed RF analytics result for 15 seconds.
1.2M observation scan takes ~140ms; cached response <1ms.
Cache invalidated when new packets are ingested.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 09:35:51 -07:00
Kpa-clawbot
73c1f6636e perf(go): optimize RF analytics inner loop
Move per-transmission work (hash indexing, type resolution, packet sizes)
outside the per-observation loop. Cache SNR dereference, pre-resolve type
name once per transmission. Reduces redundant map lookups from 1.2M to 52K.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 09:32:45 -07:00
Kpa-clawbot
a1e17ef171 feat: add engine identifier to /api/stats and /api/health
Both backends now return an 'engine' field ('node' or 'go') in
/api/stats and /api/health responses so the frontend can display
which backend is running.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 09:31:59 -07:00
Kpa-clawbot
876faa6e03 perf(go): optimize channels + RF with payload index and pre-allocation
- Add byPayloadType index to PacketStore for O(1) type-5 lookups
- Channels scan reduced from 52K to ~17K packets (3x fewer iterations)
- Use struct-based JSON decoding (avoids map[string]interface{} allocations)
- Pre-allocate snrVals/rssiVals/scatterAll with capacity hints for 1.2M obs
- Remove second-pass time.Parse loop (1.2M calls) in RF analytics
  Track min/max timestamps as strings during first pass instead
- Index also populated during IngestNewFromDB for new packets

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 09:27:38 -07:00
Kpa-clawbot
42afbb1398 perf(go): switch channels + RF analytics to in-memory store
Replace SQLite-backed handlers for /api/channels, /api/channels/:hash/messages,
/api/analytics/rf, and /api/analytics/channels with in-memory PacketStore queries.

Before (SQLite via packets_v VIEW on 1.2M rows):
  /api/channels              7.2s
  /api/channels/:hash/msgs   8.2s
  /api/analytics/rf           4.2s

After (in-memory scan of ~50K transmissions):
  Target: all under 100ms

Three new PacketStore methods:
- GetChannels(region) — filters payload_type 5 + decoded type CHAN
- GetChannelMessages(hash, limit, offset) — deduplicates by sender+hash
- GetAnalyticsRF(region) — full RF stats with histograms, scatter, per-type SNR

All handlers fall back to DB queries when store is nil (test compat).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 09:17:24 -07:00
Kpa-clawbot
afe16db960 feat(go-server): in-memory packet store — port of packet-store.js
Streams transmissions + observations from SQLite at startup into
5 indexed in-memory structures. QueryPackets and QueryGroupedPackets
now serve from RAM (<10ms) instead of hitting SQLite (2.3s).

- store.go: PacketStore with byHash, byTxID, byObsID, byObserver, byNode indexes
- main.go: create + load store at startup
- routes.go: dispatch to store for packet/stats endpoints
- websocket.go: poller ingests new transmissions into store

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 08:52:07 -07:00
Kpa-clawbot
616af26981 fix: Go ingestor normalize mqtt:// to tcp:// and mqtts:// to ssl:// for paho
Paho MQTT client uses tcp:// and ssl:// schemes, not mqtt:// and mqtts://.
Also properly configure TLS for mqtts connections with InsecureSkipVerify
when rejectUnauthorized is false.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 07:56:57 -07:00
Kpa-clawbot
dbac8e9d52 fix: Go server since/until filter uses observation timestamp, not first_seen
The frontend sends ISO timestamps to filter by observation time.
Go was filtering by transmission first_seen which missed packets
with recent observations but old first_seen. Now converts ISO to
unix epoch and queries the observations table directly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 07:53:59 -07:00
Kpa-clawbot
5c68605f2c feat(go-server): full API parity with Node.js server
Performance:
- QueryGroupedPackets: 8s → <100ms (transmissions table, not packets_v VIEW)

Field parity:
- /api/stats: totalNodes uses 7-day window, added totalNodesAllTime
- /api/stats: role counts filtered by 7-day (matching Node.js)
- /api/nodes: role counts use all-time (matching Node.js)
- /api/packets/🆔 path field returns parsed path_json hops
- /api/packets: added multi-node filter (?nodes=pk1,pk2)
- /api/observers: packetsLastHour, lat, lon, nodeRole computed
- /api/observers/🆔 packetsLastHour computed
- /api/nodes/bulk-health: per-node stats from SQL

Tests updated with dynamic timestamps for 7-day filter compat.
All tests pass, go vet clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 02:11:33 -07:00
Kpa-clawbot
e18a73e1f2 feat: Go server API parity with Node.js — response shapes, perf, computed fields
- Packets query rewired from packets_v VIEW (9s) to direct table joins (~50ms)
- Packet response: added first_seen, observation_count; removed created_at, score
- Node response: added last_heard, hash_size, hash_size_inconsistent
- Schema-aware v2/v3 detection for observer_idx vs observer_id
- All Go tests passing

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 01:50:46 -07:00
Kpa-clawbot
842b49e8c4 perf: fast-path count for unfiltered /api/packets (skip packets_v scan)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 01:29:22 -07:00
Kpa-clawbot
b2e6c8105b fix: handle WebSocket upgrade at root path (client connects to ws://host/)
Node.js upgrades WS at /, Go was only at /ws. Now the static file
handler checks for Upgrade header first and routes to WebSocket.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 01:25:35 -07:00
Kpa-clawbot
742ed86596 feat: add Go web server (cmd/server/) — full API + WebSocket + static files
35+ REST endpoints matching Node.js server, WebSocket broadcast,
static file serving with SPA fallback, config.json support.
Uses modernc.org/sqlite (pure Go, no CGO required).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 01:16:59 -07:00
Kpa-clawbot
e89c2bfe1f test: add comprehensive Go test coverage for ingestor (80%) and server (90%)
- ingestor: add config_test.go (LoadConfig, env overrides, legacy MQTT)
- ingestor: add main_test.go (toFloat64, firstNonEmpty, handleMessage, advertRole)
- ingestor: extend decoder_test.go (short buffer errors, edge cases, all payload types)
- ingestor: extend db_test.go (empty hash, timestamp updates, BuildPacketData, schema)
- server: add config_test.go (LoadConfig, LoadTheme, health thresholds, ResolveDBPath)
- server: add helpers_test.go (writeJSON/Error, queryInt, mergeMap, round, percentile, spaHandler)
- server: extend db_test.go (all query functions, filters, channel messages, node health)
- server: extend routes_test.go (all endpoints, error paths, analytics, observer analytics)
- server: extend websocket_test.go (multi-client, buffer full, poller cycle)

Coverage: ingestor 48% -> 80%, server 52% -> 90%

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 00:07:44 -07:00
Kpa-clawbot
488ead617d feat: add standalone Go MQTT ingestor (cmd/ingestor/)
First step of Go rewrite — separates MQTT ingestion from the Node.js
web server. Single static binary (no CGO) that connects to MQTT
brokers, decodes MeshCore packets, and writes to the shared SQLite DB.

Ported from JS:
- decoder.js → decoder.go (header, path, all payload types, adverts)
- computeContentHash → Go (SHA-256, path-independent)
- db.js v3 schema → db.go (transmissions, observations, nodes, observers)
- server.js MQTT logic → main.go (multi-broker, reconnect, IATA filter)

25 Go tests passing (golden fixtures from production + schema compat).
No existing JS files modified.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-26 23:22:26 -07:00