Commit Graph

59 Commits

Author SHA1 Message Date
Kpa-clawbot
cdcaa476f2 rename: MeshCore Analyzer → CoreScope (Phase 1 — backend + infra)
Rename product branding, binary names, Docker images, container names,
Go modules, proto go_package, CI, manage.sh, and documentation.

Preserved (backward compat):
- meshcore.db database filename
- meshcore-data / meshcore-staging-data directory paths
- MQTT topics (meshcore/#, meshcore/+/+/packets, etc.)
- proto package namespace (meshcore.v1)
- localStorage keys

Changes by category:
- Go modules: github.com/corescope/{server,ingestor}
- Binaries: corescope-server, corescope-ingestor
- Docker images: corescope:latest, corescope-go:latest
- Containers: corescope-prod, corescope-staging, corescope-staging-go
- Supervisord programs: corescope, corescope-server, corescope-ingestor
- Branding: siteName, heroTitle, startup logs, fallback HTML
- Proto go_package: github.com/corescope/proto/v1
- CI: container refs, deploy path
- Docs: 8 markdown files updated

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-28 14:08:15 -07:00
KpaBap
8e18351c73 Merge pull request #221 from Kpa-clawbot/feat/telemetry-decode
feat: decode telemetry packets — battery voltage + temperature on nodes
2026-03-28 13:45:00 -07:00
Kpa-clawbot
b326e3f1a6 fix: pprof port conflict crashed Go server — non-fatal bind + separate ports
Server defaults to 6060, ingestor to 6061. Removed shared PPROF_PORT
env var. Bind failure logs warning instead of log.Fatal killing the process.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-28 13:01:41 -07:00
Kpa-clawbot
54cbc648e0 feat: decode telemetry from adverts — battery voltage + temperature on nodes
Sensor nodes embed telemetry (battery_mv, temperature_c) in their advert
appdata after the null-terminated name. This commit adds decoding and
storage for both the Go ingestor and Node.js backend.

Changes:
- decoder.go/decoder.js: Parse telemetry bytes from advert appdata
  (battery_mv as uint16 LE millivolts, temperature_c as int16 LE /100)
- db.go/db.js: Add battery_mv INTEGER and temperature_c REAL columns
  to nodes and inactive_nodes tables, with migration for existing DBs
- main.go/server.js: Update node telemetry on advert processing
- server db.go: Include battery_mv/temperature_c in node API responses
- Tests: Decoder telemetry tests (positive, negative temp, no telemetry),
  DB migration test, node telemetry update test, server API shape tests

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-28 12:07:42 -07:00
Kpa-clawbot
f374a4a775 fix: enforce consistent types between Go ingestor writes and server reads
Schema:
- observers.noise_floor: INTEGER → REAL (dBm has decimals)
- battery_mv, uptime_secs remain INTEGER (always whole numbers)

Ingestor write side (cmd/ingestor/db.go):
- UpsertObserver now accepts ObserverMeta with battery_mv (int),
  uptime_secs (int64), noise_floor (float64)
- COALESCE preserves existing values when meta is nil
- Added migration: cast integer noise_floor values to REAL

Ingestor MQTT handler (cmd/ingestor/main.go — already updated):
- extractObserverMeta extracts hardware fields from status messages
- battery_mv/uptime_secs cast via math.Round to int on write

Server read side (cmd/server/db.go):
- Observer.BatteryMv: *float64 → *int (matches INTEGER storage)
- Observer.UptimeSecs: *float64 → *int64 (matches INTEGER storage)
- Observer.NoiseFloor: *float64 (unchanged, matches REAL storage)
- GetObservers/GetObserverByID: use sql.NullInt64 intermediaries
  for battery_mv/uptime_secs, sql.NullFloat64 for noise_floor

Proto (proto/observer.proto — already correct):
- battery_mv: int32, uptime_secs: int64, noise_floor: double

Tests:
- TestUpsertObserverWithMeta: verifies correct SQLite types via typeof()
- TestUpsertObserverMetaPreservesExisting: nil-meta preserves values
- TestExtractObserverMeta: float-to-int rounding, empty message
- TestSchemaNoiseFloorIsReal: PRAGMA table_info validation
- TestObserverTypeConsistency: server reads typed values correctly
- TestObserverTypesInGetObservers: list endpoint type consistency

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-28 11:22:14 -07:00
Kpa-clawbot
6d31cb2ad6 feat: add pprof profiling controlled by ENABLE_PPROF env var
Add net/http/pprof support to both Go server (default port 6060) and
ingestor (default port 6061). Profiling is off by default — only
starts the pprof HTTP listener when ENABLE_PPROF=true.

PPROF_PORT env var overrides the default port for each binary.

Enable on staging-go in docker-compose with exposed ports 6060/6061.
Not enabled on prod.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-28 11:18:33 -07:00
Kpa-clawbot
1619f4857e fix: noise_floor/battery_mv/uptime_secs scanned as float64 to handle REAL values
SQLite stores these as REAL on some instances. Go *int scan silently
fails, dropping the entire observer row (404 on detail, missing from list).
Reported for YC-Base-Repeater and YC-Work-Repeater.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-28 11:04:49 -07:00
Kpa-clawbot
8a813ed87c perf: precompute distance analytics at ingest time, fixes #169
Replace expensive per-request distance computation (1.2s cold) with
precomputed distance index built during Load() and incrementally
updated on IngestNewFromDB/IngestNewObservations.

- Add distHopRecord/distPathRecord types for precomputed hop distances
- buildDistanceIndex() iterates all packets once during Load(), computing
  haversine distances and storing results in distHops/distPaths slices
- computeDistancesForTx() handles per-packet distance computation,
  shared between full rebuild and incremental ingest
- IngestNewFromDB appends distance records for new packets (no rebuild)
- IngestNewObservations triggers full rebuild only if paths changed
- computeAnalyticsDistance() now aggregates from precomputed records
  instead of re-iterating all packets with JSON parsing + haversine

Cold request path: ~10-20ms (filter + sort precomputed records)
vs previous: ~1.2s (iterate 30K+ packets, parse JSON, resolve hops,
compute haversine for each).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 22:54:03 -07:00
Kpa-clawbot
9ebfd40aa0 fix: filter garbage channel names from /api/channels, fixes #201
Channels with garbage-decrypted names (pre-#197 data still in DB) are now
filtered at the API level using the same non-printable character heuristic
from #197. Applied in both Node.js server.js and Go server (store.go, db.go).
No data is deleted — only filtered from API responses.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 22:49:45 -07:00
Kpa-clawbot
6dacdef6b8 perf: precompute subpath index at ingest time, fixes #168
The subpaths analytics endpoint iterated ALL packets on every cold query,
taking ~900ms.  The TTL cache only masked the problem.

Fix: maintain a precomputed raw-hop subpath index (map[string]int) that
is built once during Load() and incrementally updated during
IngestNewFromDB() and IngestNewObservations().

At query time the fast path iterates only unique raw subpaths (typically
a few thousand entries) instead of all packets (30K+), resolves hop
prefixes to names, and merges counts.  Region-filtered queries still
fall back to the O(N) path since they require per-transmission observer
checks.

Expected cold-hit improvement: ~900ms → <5ms for the common no-region
case.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 22:44:39 -07:00
Kpa-clawbot
520adcc6ab feat: move stale nodes to inactive_nodes table, fixes #202
- Create inactive_nodes table with identical schema to nodes
- Add retention.nodeDays config (default 7) in Node.js and Go
- On startup: move nodes not seen in N days to inactive_nodes
- Daily timer (24h setInterval / goroutine ticker) repeats the move
- Log 'Moved X nodes to inactive_nodes (not seen in N days)'
- All existing queries unchanged — they only read nodes table
- Add 14 new tests for moveStaleNodes in test-db.js
- Both Node (db.js/server.js) and Go (ingestor/server) implemented

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 22:43:53 -07:00
Kpa-clawbot
35b23de8a1 fix: #199 — resolve 5 Go test failures (golden fixtures, +Inf, chan marshal)
1. Update golden shapes.json goRuntime keys to match new struct fields
   (goroutines, heapAllocMB, heapSysMB, etc. replacing heapMB, sysMB, etc.)
2. Fix analytics_hash_sizes hourly element shape — use explicit keys instead
   of dynamicKeys to avoid flaky validation when map iteration picks 'hour'
   string value against number valueShape
3. Update TestPerfEndpoint to check new goRuntime field names
4. Guard +Inf in handlePerf: use safeAvg() instead of raw division that
   produces infinity when endpoint count is 0
5. Fix TestBroadcastMarshalError: use func(){} in map instead of chan int
   to avoid channel-related marshal errors in test output

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 22:21:33 -07:00
Kpa-clawbot
f1cb840b5a fix: prepend to byPayloadType in IngestNewFromDB to preserve newest-first order
IngestNewFromDB was appending new transmissions to byPayloadType slices,
breaking the newest-first ordering established by Load(). This caused
GetChannelMessages (which iterates backwards assuming newest-first) to
place newly ingested messages at the wrong position, making them invisible
when returning the latest messages from the tail.

Changed append to prepend, matching the existing s.packets prepend pattern
on line 881. Added regression test.

fixes #198

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 21:56:54 -07:00
Kpa-clawbot
3cd87d766e feat: in-memory store.GetNodeAnalytics + _parsedPath in txToMap
#195 — /api/nodes/:pubkey/analytics was hitting SQL (packets_v view)
for all queries. Added store.GetNodeAnalytics(pubkey, days) that uses
the byNode[pubkey] index + text search through decoded_json, computing
all analytics (timeline, SNR trend, type breakdown, observer coverage,
hop distribution, peer interactions, uptime heatmap, computed stats)
entirely in-memory. Route handler now uses store path when available,
falling back to SQL only when store is nil.

#196 — recentPackets from /api/nodes/:pubkey/health were missing the
_parsedPath field that Node.js includes (lazy-cached parsed path_json
array). Added _parsedPath to txToMap() output using txGetParsedPath(),
matching the Node.js packet shape.

fixes #195, fixes #196

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 21:53:21 -07:00
Kpa-clawbot
031c4dd2c2 fix: goRuntime perf fields match frontend expectations (goroutines, heapAllocMB, etc.)
Frontend reads goroutines/pauseTotalMs/lastPauseMs/heapAllocMB/heapSysMB/
heapInuseMB/heapIdleMB/numCPU but Go was returning heapMB/sysMB/numGoroutine/
gcPauseMs. All showed as undefined.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 21:38:13 -07:00
Kpa-clawbot
47ee63ed55 fix: #191 #192 #193 #194 — repeater-only collision matrix, expand=observations, store-based node health, goRuntime in perf
#191: Hash collision matrix now filters to role=repeater only (routing-relevant)
#192: expand=observations in /api/packets now returns full observation details (txToMap includes observations, stripped by default)
#193: /api/nodes/:pubkey/health uses in-memory PacketStore when available instead of slow SQL queries
#194: goRuntime (heapMB, sysMB, numGoroutine, numGC, gcPauseMs) restored in /api/perf response

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 21:25:19 -07:00
Kpa-clawbot
77988ded3e fix: #184-#189 — sanitize names, packetsLast24h, ReadMemStats cache, dup name indicator, heatmap warning
#184: Strip non-printable chars (<0x20 except tab/newline) from ADVERT
names in Go server decoder, Go ingestor decoder, and Node decoder.js.

#185: Add visual (N) badge next to node names when multiple nodes share
the same display name (case-insensitive). Shows in list, side pane, and
full detail page with 'also known as' links to other keys.

#186: Add packetsLast24h field to /api/stats response.

#187 #188: Cache runtime.ReadMemStats() with 5s TTL in Go server.

#189: Temporarily patch HTMLCanvasElement.prototype.getContext during
L.heatLayer().addTo(map) to pass { willReadFrequently: true }, preventing
Chrome console warning about canvas readback performance.

Tests: 10 new tests for buildDupNameMap + dupNameBadge (143 total frontend).
Cache busters bumped.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 20:50:08 -07:00
Kpa-clawbot
2435f2eaaf fix: observation timestamps, leaked fields, perf path normalization
- #178: Use strftime ISO 8601 format instead of datetime() for observation
  timestamps in all SQL queries (v3 + v2 views). Add normalizeTimestamp()
  helper for non-v3 paths that may store space-separated timestamps.

- #179: Strip internal fields (decoded_json, direction, payload_type,
  raw_hex, route_type, score, created_at) from ObservationResp. Only
  expose id, transmission_id, observer_id, observer_name, snr, rssi,
  path_json, timestamp — matching Node.js parity.

- #180: Remove _parsedDecoded and _parsedPath from node detail
  recentAdverts response. These internal/computed fields were leaking
  to the API. Updated golden shapes.json accordingly.

- #181: Use mux route template (GetPathTemplate) for perf stats path
  normalization, converting {param} to :param for Node.js parity.
  Fallback to hex regex for unmatched routes. Compile regexes once at
  package level instead of per-request.

fixes #178, fixes #179, fixes #180, fixes #181

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 18:09:36 -07:00
Kpa-clawbot
df63efa78d fix: poll new observations for existing transmissions (fixes #174)
The poller only queried WHERE t.id > sinceID, which missed new
observations added to transmissions already in the store. The trace
page was correct because it always queries the DB directly.

Add IngestNewObservations() that polls observations by o.id watermark,
adds them to existing StoreTx entries, re-picks best observation, and
invalidates analytics caches. The Poller now tracks both lastTxID and
lastObsID watermarks.

Includes tests for v3, v2, dedup, best-path re-pick, and
GetMaxObservationID.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 17:26:26 -07:00
Kpa-clawbot
59c225593f fix(go): resolve 6 backend issues — #165 #168 #169 #170 #171 #172
#165 — build_time in API: already implemented (BuildTime ldflags in
Dockerfile.go, main.go, StatsResponse, HealthResponse)

#168 — subpaths API slow: cache (subpathCache with TTL) and invalidation
already in place; verified working

#169 — distance API slow: cache (distCache with TTL) and invalidation
already in place; verified working

#170 — audio-lab/buckets: in-memory store path already implemented,
matching Node.js pktStore.packets iteration with type grouping and
size-distributed sampling

#171 — channels stale latest message: add companion bridge handling to
Go ingestor for meshcore/message/channel/<n> and meshcore/message/direct/<id>
MQTT topics. Stores decoded channel messages with type CHAN in decoded_json,
enabling the channels endpoint to find them. Also handles direct messages.

#172 — packets page not live-updating: add missing direction field to WS
broadcast packet map for full parity with txToMap/Node.js fullPacket shape.
WS broadcast shape verified correct (type, data.packet structure, timestamp,
payload_type, observer_id all present).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 16:06:24 -07:00
Kpa-clawbot
64bf3744e2 fix: channels stale latest message from observation-timestamp ordering, fixes #171
db.GetChannels() queried packets_v (observation-level rows) ordered by
observation timestamp and always overwrote lastMessage. When an older
message had a later re-observation, it would overwrite the correct
latest message with stale data.

Fix: query transmissions table directly (one row per unique message)
ordered by first_seen. This ensures lastMessage always reflects the
most recently sent message, not the most recently observed one.

Also fix db.GetChannelMessages() to use first_seen ordering with
schema-aware queries (v2/v3), and add missing distCache/subpathCache
invalidation on packet ingestion.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 16:01:54 -07:00
Kpa-clawbot
30bcfff45b fix(go): audio-lab/buckets use in-memory store, match Node.js behavior
Use s.store (in-memory PacketStore) instead of direct packets_v SQL query,
matching how the Node.js handler iterates pktStore.packets. This fixes the
endpoint returning empty buckets when packets_v view is missing or the DB
query fails silently.

Key changes:
- Group by decoded_json.type first, fall back to payloadTypeNames
- Evenly-spaced sampling (up to 8 per type) sorted by raw_hex length
- Use actual ObservationCount instead of hardcoded 1
- Reuse payloadTypeNames from store.go instead of duplicating
- Retain DB fallback path when store is nil

fixes #170

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 15:47:32 -07:00
Kpa-clawbot
e300228874 perf: add TTL cache for subpaths API + build timestamp in stats/health
- Add 15s TTL cache to GetAnalyticsSubpaths with composite key (region|minLen|maxLen|limit),
  matching the existing cache pattern used by RF, topology, hash, channel, and distance analytics.
  Cache hits return instantly vs 900ms+ computation. fixes #168

- Add BuildTime to /api/stats and /api/health responses, injected via ldflags at build time.
  Dockerfile.go now accepts BUILD_TIME build arg. fixes #165

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 15:44:06 -07:00
Kpa-clawbot
f54a10f04d fix(go): add timestamp field to WS broadcast packet — fixes #172
The Go server's WebSocket broadcast included first_seen but not
timestamp in the nested packet object. The frontend packets.js
filters on m.data.packet and reads p.timestamp for row insertion
and sorting. Without this field, live-updating silently failed
(rows inserted with undefined latest, breaking display).

Mirrors the pattern already used in txToMap() (store.go:1168)
which correctly emits both first_seen and timestamp.

Also updates websocket_test.go to assert timestamp presence
in broadcast data to prevent regression.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 15:40:57 -07:00
Kpa-clawbot
75b6dc5d9f perf: add TTL cache for /api/analytics/distance endpoint
The distance analytics endpoint was recomputing haversine distances on
every request (~1.22s). Add a 15s TTL cache following the same pattern
used by RF, topology, hash-sizes, and channels analytics endpoints.
Include distCache in cache stats size calculation.

fixes #169

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 15:38:43 -07:00
Kpa-clawbot
f55a3454aa feat(go): replace map[string]interface{} with typed Go structs in route handlers
Phase 1: Create cmd/server/types.go with ~80 typed response structs
matching all proto definitions. Every API response shape is now a
compile-time checked struct.

Phase 2: Rewire all route handlers in routes.go to construct typed
structs instead of map[string]interface{} for response building:
- /api/stats -> StatsResponse
- /api/health -> HealthResponse
- /api/perf -> PerfResponse
- /api/config/* -> typed config responses
- /api/nodes/* -> NodeListResponse, NodeDetailResponse, etc.
- /api/packets/* -> PacketListResponse, PacketDetailResponse
- /api/analytics/* -> RFAnalyticsResponse, TopologyResponse, etc.
- /api/observers/* -> ObserverListResponse, ObserverResp
- /api/channels/* -> ChannelListResponse, ChannelMessagesResponse
- /api/traces/* -> TraceResponse
- /api/resolve-hops -> ResolveHopsResponse
- /api/iata-coords -> IataCoordsResponse (typed IataCoord)
- /api/audio-lab/buckets -> AudioLabBucketsResponse
- WebSocket broadcast -> WSMessage struct
- SlowQuery tracking -> SlowQuery struct (was map)

Phase 3 (partial): Add typed store/db methods:
- PacketStore.GetCacheStatsTyped() -> CacheStats
- PacketStore.GetPerfStoreStatsTyped() -> PerfPacketStoreStats
- DB.GetDBSizeStatsTyped() -> SqliteStats

Remaining map usage is in store/db data flow (PacketResult.Packets
still uses maps) — these will be addressed in a follow-up.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 15:17:21 -07:00
Kpa-clawbot
8414015b2c fix: resolve 15 API contract violations in Go server
- Fix #11: Remove goRuntime and heapMB from /api/health response
- Fix #12: Remove status, uptimeHuman, websocket, goRuntime from /api/perf
- Fix #10: Add POST /api/perf/reset endpoint
- Fix #7: Return real IATA airport coordinates from /api/iata-coords
- Fix #8: Add POST /api/packets endpoint with decode+insert
- Fix #9: Add POST /api/decode endpoint
- Fix #1: Implement real SQL for hopDistribution, uptimeHeatmap,
  computedStats in /api/nodes/:pubkey/analytics
- Fix #2: Implement SQL fallback for /api/analytics/topology
- Fix #3: Implement real SQL queries for /api/nodes/:pubkey/paths
- Fix #4: Add per-observer breakdown in /api/nodes/bulk-health
- Fix #5: Implement SQL fallback for /api/analytics/distance
- Fix #6: Implement timeline, nodesTimeline, snrDistribution in
  /api/observers/:id/analytics

New file: cmd/server/decoder.go -- decoder from ingestor adapted for
server package (uses time.Unix instead of util.go helper)

fixes #163

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 14:45:29 -07:00
Kpa-clawbot
fc494962d1 fix(go): add broadcast diagnostic logging, rebuild fixes stale deployment
The Go staging packets page wasn't live-updating because the deployed
binary was stale (built before the #162 fix). Rebuilding from current
source fixed the issue — broadcasts now fire correctly.

Added two permanent diagnostic log lines:
- [poller] IngestNewFromDB: logs when new transmissions are found
- [broadcast] sending N packets to M clients: logs each broadcast batch

These log lines make it easy to verify the broadcast pipeline is working
and would have caught this stale-deployment issue immediately.

Verified on VM: WS clients receive packets with nested 'packet' field,
all Go tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 14:15:19 -07:00
Kpa-clawbot
95afaf2f0d fix(go): add nested packet field to WS broadcast, fixes #162
The frontend packets.js filters WS messages with m.data?.packet and
extracts m.data.packet for live rendering. Node's server.js includes
a packet sub-object (packet: fullPacket) in the broadcast data, but
Go's IngestNewFromDB built the data flat without a nested packet field.

This caused the Go staging packets page to never live-update via WS
even though messages were being sent — they were silently filtered out
by packets.js.

Fix: build the packet fields map separately, then create the broadcast
map with both top-level fields (for live.js) and nested packet (for
packets.js). Also fixes the fallback DB-direct poller path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-27 13:31:37 -07:00
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