Cleanup: remove 23 stale planning, report, audit, and summary markdown
files from the root directory. These were artifacts from the early UKMesh
migration planning phase and are no longer needed. README.md retained.
- clientErrors.ts: window.onerror/unhandledrejection for BOTH domains via main.tsx
(was app-only in App.tsx); resource-failure classification; telemetry=off gate;
console.error hook (kind warning) with terrain-tiles noise filter; page field
now pathname-only (no query strings)
- PrivacyPage at /privacy linked from footer + cookie banner; docs/privacy.md LIA
- retention: packet_decryptions added to row-table retention targets; packets
180d->30d in lifecycle policies; migration 050 schedules timescaledb retention
jobs (packets 30d, node_status 180d, node_neighbor 7d) that never existed
- migration 050: packet_paths hypertable (content-stripped path store, no
retention) + backfill of 2.56M path-bearing packets + ingest dual-write
- Tabs now stay on one line (horizontal scroll) with 40px tap targets ≤480px
- Peak hour card renders a compact local date+time (fmtPeakHour, unit tested)
instead of the raw ISO timestamp that broke mid-string
- Chart header wraps cleanly on narrow screens; stat cards get tighter
mobile padding + overflow-wrap on values; refresh row wraps aligned
Removing .mobile-controls from flow auto-placed map-layer into the empty
auto row (height 0), blanking the map on ≤640px. Shell is now a 2-row
grid (48px topbar + 1fr map); legend and activity-replay chips sit below
the floating Layers/Live bar.
- App: opening the Live feed chip also enables filters.livePackets if off.
- mobile.spec.ts: LOS width assertion updated for the menu grid layout; new
test covers floating-bar default, feed chip toggle, layers menu toggles
and Escape close.
- MobileControls: stacked top panel replaced with a floating bar (Layers menu
toggle + Live feed chip) and a collapsible menu sheet: layers grid, view
modes, LOS tools, node search, watchlist. Closes on Esc/outside tap.
- App: mobileFeedOpen state drives data-feed-open so the packet feed stays
hidden on mobile until the Live chip is pressed (desktop unchanged).
- RfCoverageStatus: details dl + note wrap behind a Show/Hide toggle on
mobile only; gradient + tier buttons stay visible.
- map-app.css: new ≤640px block (floating bar, menu sheet, feed gating,
coverage details); removed superseded .mobile-controls/__primary rules.
* fix(owner): Wave 3 mediums — password handling, deploy rollback, newuser races, session revocation
- BUG-017: stop trimming MQTT passwords (frontend + backend). A valid
broker password may begin/end with whitespace; trimming made it
permanently unauthenticatable. Username trimming unchanged.
- BUG-013: deploy-website.sh now set -Eeuo pipefail with an EXIT rollback
trap armed only after the pin mutates; container-down and bundle-mismatch
paths fail loudly and restore the old pin + verify the restored service.
- BUG-012: newuser.sh installs an EXIT trap (ERR does not fire on explicit
exit 1 from die()) so every post-mutation failure rolls back; rollback is
idempotent and a no-op before any mutation flag. Disarm points clear EXIT.
- BUG-011: newuser.sh re-reads + re-validates OWNER_MQTT_USERNAME_MAP under
the re-acquired lock after the long unlocked discovery window, then merges
the new grant into the current map — concurrent provisioning runs can no
longer be overwritten by a stale snapshot.
- BUG-010: owner sessions are now v3 cookies carrying a credential
generation (Redis-backed). When the broker rejects a previously-valid
password (revocation detected), the generation bumps and every older
session is rejected on the next request. TTL shortened 30d -> 7d.
Verified: tsc clean, 310/310 backend tests pass, bash -n on both scripts.
* fix(alert-receiver): durable alert delivery with bounded retry + dead-letter (BUG-014)
Forwarding was fire-and-forget: failures logged asynchronously after HTTP 202,
/healthz stayed green, and alerts could be archived to the local JSONL while
operators never saw them. Now:
- Every receipt is enqueued for delivery with bounded exponential backoff
(ALERT_FORWARD_MAX_ATTEMPTS=5, base 1s, cap 60s) and dead-lettered to
receipts.jsonl.dead after exhausting attempts.
- /healthz keeps returning 200 (compose wget healthcheck must not restart
the container) but the body reports degraded status + detail; new /readyz
returns 503 when ALERT_FORWARD_URL is unset (archive-only), no successful
forward since startup, or alerts stuck undelivered >5min.
Verified: tsc clean, 310/310 tests pass.
* fix(ci): classify reviewed public channel keys + fixtures in gitleaks (BUG-009)
The nightly full-history secret scan flagged 42 findings spanning the
documented community channel keys (VALIDATED_CHANNELS — intentionally
public, each verified to decrypt real UK Mesh group text), fake test
fixtures, and a deployed-commit SHA in the website live manifests. That
made a genuine credential easy to dismiss among expected hits.
- Rule-scoped allowlists with exact fingerprints (regexTarget: secret,
anchored full-value matches) for the Public channel key, test fixtures
(0123456789... / abcdef0123...), and the manifest commit SHA.
- Structure-exact line allowlist for channelRegistry.ts VALIDATED_CHANNELS.
- Verified locally with gitleaks 8.24.3 full-history scan: 42 -> 0 findings.
* fix(viewshed): side-effect completion markers prevent skipped link jobs (BUG-006)
store_coverage() commits on an autocommit connection, then the worker queues
physical-link jobs and publishes Redis notifications. A Redis failure after
the DB commit NACKed the job; on retry already_calculated() saw the coverage
row and returned early — link work and frontend notifications were skipped
forever.
- Record a Redis completion marker (viewshed:side-effects:<node>) only after
EVERY side effect succeeds.
- On the already_calculated early return, a missing marker triggers an
idempotent replay: link jobs are re-enqueued from the stored node position
(admission is idempotent) and coverage_update/node_upsert notifications are
re-published from the stored coverage row.
- Redis read failures are treated as incomplete (replay attempt re-raises and
NACKs rather than silently skipping).
- Added tests/test_side_effect_markers.py (5 tests) with a conftest that stubs
osgeo/psycopg2 so pure-logic worker tests run without GDAL (CI keeps real
GDAL via setdefault). Verified: 5/5 new tests pass; full suite 34 passed,
2 GDAL-required terrain tests fail only in stub env (pass in CI image).
---------
Co-authored-by: hermes-gadget <hermes-gadget@users.noreply.github.com>
* fix: migrate frontend dependencies for #61
* fix(frontend): maplibre 6 migration — react-is peer dep, worker resolution, e2e timing
- Add react-is ^19.2.8 as a direct dependency: recharts 3 declares it as
a peer dep (recharts 2 bundled it), so vite's dep optimizer 500'd on
'Failed to resolve import "react-is"' and the whole dashboard failed
to mount (error boundary).
- Exclude maplibre-gl from vite optimizeDeps: v6 loads its worker via
new URL(..., import.meta.url); pre-bundling rewrote that to
node_modules/.vite/deps/maplibre-gl-worker.mjs (404) killing the
worker and all custom raster protocols (hopreach-rf tiles never
fetched) in dev.
- Harden two dashboard e2e assertions from the default 5s to 15s:
maplibre 6's larger ESM bundle makes initial map mount slower under
4-worker parallel CI load; the app behavior is unchanged (verified
vs main's pass).
Verified locally: 84/84 unit tests, tsc + vite build, full e2e
(public-desktop + dashboard-desktop + dashboard-mobile) 27 passed.
---------
Co-authored-by: gadgethd <111318106+gadgethd@users.noreply.github.com>
Co-authored-by: hermes-gadget <hermes-gadget@users.noreply.github.com>
* fix(ci): pin gitleaks-action to valid commit after upstream force-push
Upstream deleted dcedce43 (force-push), so every ci.yml run fails at
workflow-parse with 0 jobs. The dependabot actions-group bump (#30)
contains this fix but cannot merge whole: its docker/* actions require
node24, which GitHub runners do not support yet. Pin gitleaks alone
(ff98106e is node20).
* fix(ci): downgrade v7 actions to node20-compatible versions
actions/checkout@v7, setup-node@v7, setup-python@v7 require node24,
which GitHub-hosted runners do not support yet — every ci.yml run has
failed at workflow-parse (0 jobs) since the Aug 3 v7 bump. Pin to the
latest node20 versions (checkout v4, setup-node v4, setup-python v5).
Also pins gitleaks-action to a valid commit (upstream force-pushed away
dcedce43).
* ci: noop retrigger
* fix(ci): escape literal ${{ in bash string so GitHub parser accepts workflow
The 'Validate tracked build and Compose inventory' step matched literal
${{ inside run-block strings, which GitHub's expression parser reads as
the start of an expression (${{'* is invalid) — the workflow fails at
parse time with 0 jobs. Split the literal as '$''{{' (bash concatenates
adjacent quoted strings at runtime; the source no longer contains a
contiguous ${{).
* fix(ci): resolve all three pre-existing ukmesh CI failures
Backend (packetBatchWriteCoalesce):
- 045: restore nodes_public_visibility_generation trigger — 015 is
superseded by 044 on fresh DBs so the trigger was never created,
leaving generation/visibility_generation out of sync and public
packet reads returning empty.
- 046: restore packet privacy classification — the 042 fence design
assumes sync_private_node_prefixes rewrites packets on privacy
change, but 026's version only maintains prefixes. Node flips to
private left old packets visible; direct SQL inserts were never
classified. Restore the packet rewrite in the sync trigger and add
a BEFORE INSERT trigger mirroring the batch path's is_private /
visibility_ok computation.
- packetBatch.integration.test.ts: exclude the prefix-cache refresh
query from statementCount (matches unit-test convention).
Frontend e2e:
- public.spec.ts: assert the TopologyMap component's actual labels
('Geographic repeater topology map', '2 mapped repeaters · 2
observed relationships') instead of the pre-map SVG graph labels.
Workers (pip install):
- viewshed-worker: bump numpy 1.26.4→2.3.5, scipy 1.13.1→1.16.3,
psycopg2-binary 2.9.10→2.9.11 (cp314 wheels); the base gdal image
ships Python 3.14 so the old pins had no wheels. Inherit shapely
from the image's python3-shapely apt package (no cp314 wheel
exists; source builds are GEOS 3.14-incompatible).
* fix(ci): restore data-plane services for compose validation and smoke test
Commit 8c5e1c8 split the long-lived data-plane services (timescaledb,
mosquitto, redis, mosquitto-reloader) into the external meshcore-infra
project on the live host, but the Workers-and-Compose CI job still
asserted their presence in the app compose project (max_worker_processes
check, inventory check, smoke-test execs). CI had been broken since
Aug 3 so the mismatch was never caught.
Add docker-compose.ci.yml — a CI-only overlay restoring the four
data-plane services from their pre-split definitions — and point every
compose invocation in the Workers-and-Compose job at
'-f docker-compose.yml -f docker-compose.ci.yml'.
Validated locally: merged config parses, the jq assertions and the full
inventory (db-migrate backend app-ukmesh website-ukmesh website-dev
mesh-health-check mosquitto-reloader link-worker link-backfill-worker
hopreach timescaledb mosquitto) all pass.
* fix(ci): include dev profile in compose inventory check
website-dev is a dev-profile service; the inventory gate compared the
base-config service list, so the merged stack never matched. This gate
has been red since the workflow was first written (CI parse-broken from
Aug 3 until the gitleaks pin landed).
* fix(ci): db-migrate must wait for timescaledb health
The Aug 9 data-plane split removed db-migrate's depends_on along with
the timescaledb service definition, so the migration runner raced the
fresh database init in the empty-volume smoke test and failed on the
not-yet-created base schema. Restore the pre-split condition:
timescaledb service_healthy.
* fix(ci): restore internal TCP MQTT listener for smoke test + exporter
Commit 40e843f dropped the 1883 TCP listener, breaking the CI smoke
(mosquitto_pub targets 1883) and the Prometheus mosquitto exporter.
The listener is internal-only: compose never publishes 1883 on the
host, and the password/ACL policy still applies.
---------
Co-authored-by: gadgethd <111318106+gadgethd@users.noreply.github.com>