Commit Graph
258 Commits
Author SHA1 Message Date
gadgethd 92f2f11dfd fix: honor chart analytics timeout 2026-08-09 15:20:51 +00:00
gadgethd dbe1d304ff perf: publish path learning deltas 2026-08-09 15:09:18 +00:00
gadgethd 4933928cf3 fix: serve charts during generation refresh 2026-08-09 14:53:09 +00:00
gadgethd 61d3466a4a fix: bound multibyte backfill by chunk 2026-08-09 14:30:47 +00:00
gadgethd b5e329eb66 perf: persist multibyte chart facts 2026-08-09 14:20:09 +00:00
gadgethd f3ec9a2e5b ops(hopreach): weekly Sunday 01:00 schedule (Ben decision) 2026-08-09 14:06:09 +00:00
gadgethd 2a4dbe586e fix: prevent chart regen on warm restart 2026-08-09 13:35:58 +00:00
gadgethd 2d9e46b198 ops: identify database clients and bound health retention 2026-08-09 13:20:31 +00:00
gadgethd 2e76daf37a perf: batch health and synthetic persistence 2026-08-09 13:20:16 +00:00
gadgethd 3e26280ed4 perf: batch and resume path history generation 2026-08-09 13:20:05 +00:00
gadgethd c3f64d5be3 ops: alert on mqtt ingest saturation 2026-08-09 13:16:54 +00:00
gadgethd d4b843c15c fix(newuser): resolve data-plane container names at runtime
The compose split (2026-08-09) renamed the data plane to meshcore-infra-*
containers; the hardcoded meshcore-analytics-mosquitto-1/-timescaledb-1
references broke user creation. Resolve both container names from docker
ps at script start (infra names preferred, generic suffix fallback).
2026-08-09 03:29:45 +00:00
gadgethd 8c5e1c86c3 Split data-plane services into infra project 2026-08-09 03:25:09 +00:00
gadgethd 8b902dfb3f feat(mqtt): add #crawley and #repeaters hashtag channels
SHA256("#name")[:16] derived secrets; registry now covers both. Ships
with the next backend deploy.
2026-08-09 02:50:08 +00:00
gadgethd 47811106d2 feat(site): add /contact page with operator Discord + email, footer link
Contact page (site footer -> Contact) with Discord sudogadget and
ukmesh@proton.me, plus an observer-onboarding blurb. Wired into
publicRoutes (sitemap: true), lazy-loaded ContactPage chunk, and the
shared SiteLayout footer (app + website surfaces). Re-baselined
website-live-manifest.json for the new website image (e5db6790).
2026-08-09 02:45:59 +00:00
gadgethd 119eaf529d fix(ws): replace per-row canonical_node_id calls with alias joins in initial-state feed queries
The cold WS initial-state fetch (fetchInitialState -> getRecentPackets +
getRecentMessages) called meshcore_canonical_node_id() ~100k times per
fetch (per-row subquery against node_identity_aliases). 6.97s cold on the
2-vCPU box; under load it exceeded the 8s WS_INITIAL_STATE_DB_TIMEOUT_MS
abort, so the map never received an initial snapshot (empty map +
synthetic_check_failed). Replace the per-row function calls with
node_identity_aliases LEFT JOINs (COALESCE(alias.canonical_node_id,
upper(btrim(node_id))) - exact function semantics, table ~20 rows).
Measured: full getRecentMessages 6968ms -> 1772ms (4x); getRecentPackets
same pattern. Also raise the WS_INITIAL_STATE_DB_TIMEOUT_MS env cap
9s -> 60s (default stays 8s; deploy sets 30s) so slow-but-working fetches
complete and warm the cache instead of aborting forever.
281/281 backend tests pass; tsc clean. Live: initial_state 502ms warm,
all synthetic checks green.
2026-08-09 02:43:44 +00:00
gadgethd 8981979e22 fix(mqtt): verify broker ACL reload acknowledgement 2026-08-09 02:32:09 +00:00
gadgethd 395baa2581 fix(newuser): reload mosquitto after credential create/delete and release flock during discovery watch
Two live-E2E bugs:
1. mosquitto only re-reads password_file on SIGHUP; the reconciler reloads
   every ~60s but a device may connect before then and gets CONNACK 135.
   The script now sends kill -HUP 1 after mosquitto_passwd -b and -D so
   credentials are valid/revoked immediately.
2. The flock was held through the entire discovery watch (up to hours),
   blocking every other newuser run. Release before the passive log-poll
   and re-acquire before the mutation phase.
2026-08-09 02:09:08 +00:00
gadgethd f03f57f886 Add first-contact node key discovery 2026-08-09 01:33:37 +00:00
gadgethd 1f1c2bbb47 fix(ws): bound cold viable-link snapshot query
Root cause: node_identity_nodes was estimated at one scoped row, so the combined two-endpoint filter formed a nested-loop Cartesian search and issued roughly 174 million composite link-index probes. The 14-row radio-report table was not the bottleneck.

Split the scoped-node and viable-link reads, intersect endpoints in memory, and preserve the existing all-time neighbor SUM/MAX fields. Thread an 8s AbortSignal through every initial-state database read so a pathological cold fetch releases its gate slot before the 10s synthetic deadline.

Evidence:
- before EXPLAIN (plan only): Nested Loop with node_identity_links_pkey and net_nodes rows=1
- fixed EXPLAIN ANALYZE (ARRAY['ukmesh']): 258.325ms + 61.944ms = 320.269ms server time; 0.52s psql wall
- production getViableLinks('ukmesh'): 20,448 rows in 312.1ms
- semantics check: 20,448 links; neighbor rows=11, reports=15, best SNR=12 dB
- npm test: 280 passed, 0 failed
- npx tsc --noEmit: PASS
2026-08-09 01:14:14 +00:00
gadgethd ee64ad81cb Add VPS MQTT user provisioning script 2026-08-09 01:01:53 +00:00
gadgethd a5c9001f8a chore: ignore .env.bak-* (pre-migration sweep) 2026-08-08 16:46:50 +01:00
gadgethd 40e843ff72 chore: save pending state — pathing experiment reports, stats & mosquitto updates (pre-migration sweep) 2026-08-08 16:46:49 +01:00
gadgethd 4af343f60d release: deploy HEAD website+backend to prod (stats charts regen 15min cap, live manifest re-baseline, test origin allowlist, rollback pins) 2026-08-08 01:11:40 +00:00
gadgethd ac4ee2afec ops: staging manifest for HEAD website build (review on test.ukmesh.com) 2026-08-08 00:37:03 +00:00
gadgethd 5210aba5e3 ops: capture live website state + drift guard + safe deploy script (website reproducibility) 2026-08-08 00:36:22 +00:00
gadgethd 049b063a8c seo: static JSON-LD, sitemap lastmod, og-image for public website (crawler-only, no UI change) 2026-08-08 00:16:20 +00:00
gadgethd 99d444e200 fix(tools): backfillStatsRollups hourly phase counts every received packet
The hourly rollup rebuild was filtering source rows with
PUBLIC_PACKET_PRIVACY_SQL, so a backfill reproduced the public-only
rollup (~26k/24h) even after the incremental writer (packetBatch.ts)
was changed to count all rows (~101k/24h). The tool's hourly apply
slice + dry-run + catchUpCurrentHour now have NO privacy filter,
matching the runtime writer. The daily max-hop and observer-region
phases KEEP the privacy filter - their runtime writers still filter.
2026-08-07 16:36:20 +00:00
gadgethd 5732520e5c fix(stats): chart rollup counts every received packet - charts now match the totals
The hourly/daily chart series come from packet_hourly_stats, whose
incremental writer (packetBatch.ts) filtered WHERE path_is_valid AND
NOT is_private - so the charts only ever showed public-valid rows
(~26k/24h) while the totals now count all observations (~101k/24h).
Per Ben's spec (every packet received by every observer counts), the
rollup must include private rows too.

Changes:
- packetBatch.ts: rollup INSERT drops the path_is_valid/is_private
  filter (keeps the meshcore-test topic exclusion) - all rows counted
- statsRepository.ts: aggregate raw-slice CTEs (fresh hour + rollup
  gap) use totalFilters; legacy/observer chart series (perHour,
  perDay, types, hops, routes, transports) use totalFilters; region
  summary counts all rows
- rollup history will be rebuilt via stats:backfill-rollups (the
  backfill tool already counts all rows)

Verified: totals ~101k/24h; charts will sum to the same after backfill.
2026-08-07 16:20:09 +00:00
gadgethd 4a2d69bc58 fix(stats): packet counts = total observations (all rows, duplicates included)
Per Ben's spec the "Observed packets" totals must count EVERY packet
received by EVERY observer - duplicate receptions of the same packet by
multiple stations each count. Reverts the COUNT(DISTINCT packet_hash)
semantics from 8dff7d7 and additionally drops the privacy visibility
filter from the totals so they match the chart rollup path (which has
never been privacy-filtered - aggregateScope filters by network only).

Changes:
- packetsDay, totalPackets24h, totalPackets7d: COUNT(*) over all rows
  (network/test-topic scoped, rx_node_id present), no visibility filter
- channelTraffic: COUNT(*) all rows (percentages stay consistent with
  the all-rows total denominator)
- networkFilters(): new includePrivacy option (default true) so total
  queries can opt out of visibility conditions
- legacy/observer-scoped chart series back to COUNT(*) (row semantics
  everywhere; transportCodes alias fix from 40be809 retained)
- tests: includePrivacy coverage added

Verified live: 24h total ~99k rows (vs 25k privacy-filtered, vs 5.8k
distinct) - consistent with the hourly chart series which already
counted all rows.
2026-08-07 15:47:11 +00:00
gadgethd 40be809330 fix(stats): transportCodes legacy query missing packets alias; channelTraffic count scoped to carried column 2026-08-07 14:17:00 +00:00
gadgethd 8dff7d79d0 fix(stats): count distinct packets (not observation rows) in all packet-count stats
The packets table stores one row per observer per packet, so COUNT(*)
overcounted by the average observation multiplicity (~4.3x public, ~9.5x
overall). A packet heard by 5 stations counted 5 times.

Converted to COUNT(DISTINCT packet_hash) on every surface that presents a
"packets" number:
- /api/stats summary packetsDay (app + home dashboard card)
- charts summary totalPackets24h / totalPackets7d (StatsPage cards)
- legacy/observer-scoped chart series: packetsPerHour, packetsPerDay,
  packetTypes, hopDistribution, routeTypes, transportCodes
- channelTraffic channel counts (keeps allPct consistent with the
  distinct denominator)

The canonical rollup path (packet_hourly_stats, incrementally maintained)
remains row-based - it cannot maintain distinct counts incrementally and
stays as the trend-shape source for the public charts.

Verified: public distinct 24h = 5,835 vs 25,236 public rows vs 99,079
total rows (74% of traffic is from privacy-marked nodes).
2026-08-07 14:06:28 +00:00
gadgethd 8bc4b0dce6 fix(mqtt): accept official firmware neighbours payload shape (UK envelope + snr_db/heard_secs_ago) 2026-08-07 11:42:11 +00:00
gadgethd c122762df9 fix(nodes): restore node links query - alias identity source table for network filters
df7cfbe migrated listNodeLinks to node_identity_nodes but kept the
un-aliased filters.nodes fragment, whose EXISTS subquery references
nodes.node_id with no nodes table in scope, so every GET /nodes/:id/links
500'd (missing FROM-clause entry for table nodes) and the repeater page
showed no simulated neighbours for any node. Use filters.nodesAlias like
the sibling identity queries do.
2026-08-07 11:32:32 +00:00
gadgethd 9f6065a93c fix(deploy): align live overlay static IPs with the VPS 172.30.0.0/24 external network
The overlay still carried pre-migration 172.18.30.x addresses; the VPS
meshcore-analytics_default bridge is 172.30.0.0/24 (containers already run
on 172.30.0.x). Stale IPs made compose unable to (re)create app-ukmesh
(no subnet contains 172.18.30.10).
2026-08-07 11:16:13 +00:00
gadgethd 5b4da86e05 fix(mqtt,stats): accept UK neighbours topic spelling; render chart times in viewer local tz
- topic.ts/brokerLog.ts/client.ts: accept the neighbours suffix (official
  MeshCore firmware publishes the UK spelling; our MQTT fork uses neighbors)
- aclManager.ts: render + verify BOTH spellings in owner ACL grants (ukmesh
  and test scopes); inventoryOwnerAuthorization regex updated to match
- statsService.ts: chart bucket labels are now machine-readable ISO instead
  of server-timezone HH:MM/en-GB strings (backend container runs UTC;
  viewers saw UTC hours)
- StatsPage.tsx: axis ticks + tooltips + summaries formatted via
  statsTimeFormat.ts (Intl local timezone, raw fallback for legacy cached
  snapshots) + unit tests
2026-08-07 11:13:12 +00:00
gadgethd a1b2ae8c9a fix(acl): render test-scope topic grants for owner devices (meshcore-test/+/<id>/...) so test-network observers can publish 2026-08-07 02:30:20 +00:00
gadgethd f9d531784b Grant hermes-test owner ACL access 2026-08-07 00:55:58 +00:00
gadgethd 33bfdc1c6d Expose owner node telemetry and heard neighbors 2026-08-07 00:55:51 +00:00
gadgethd 55d1d319dd Add network-scoped neighbor telemetry ingest 2026-08-07 00:55:40 +00:00
gadgethd 2a24a14478 fix(ingest): store test-marker IATA traffic under the isolated test network scope 2026-08-07 00:06:37 +00:00
gadgethd 8306cd770c fix(paths): split multibyte-paths into two simple queries joined in JS (timeout → 0.3s) 2026-08-06 22:40:37 +00:00
gadgethd 991b99fef7 fix(frontend): keep mobile map controls in flow 2026-08-06 21:47:20 +00:00
gadgethd eb4201b9dd fix(frontend): resolve mobile map and owner layout defects 2026-08-06 21:34:07 +00:00
gadgethd 7fc2fb03ef revert(owner): drop card-click hint line 2026-08-06 21:11:56 +00:00
gadgethd 14cdb96189 feat(owner): clickable identity cards replace node dropdown selector 2026-08-06 21:08:49 +00:00
gadgethd b33696442b fix(owner): index-driven canonical member filters for owner live data (timeout → <500ms battery) 2026-08-06 20:20:13 +00:00
gadgethd 9d8a07a368 docs(spring-clean): record all 18 items complete + verification 2026-08-06 16:02:08 +00:00
gadgethd 2c5316a22f docs(topology): record final healthy check 2026-08-06 14:04:17 +00:00
gadgethd 4e75b1f8a9 docs(topology): record spring-clean item 11 2026-08-06 14:01:44 +00:00