* Fix analysis state, worker observability, feed layout and maintenance (#113)
* fix(analysis): restore tested retired workload policy
* fix(owner): prewarm last-hop cache with bounded progress reporting
* fix(metrics): use real link worker heartbeat for queue alerts
* fix(feed): measure virtual rows and preserve scroll anchors
* fix(ops): reject Compose metadata drift before replacement
* fix(maintenance): archive inactive nodes across roles and bridge states
* docs(review): record validation and heartbeat audit findings
* fix(maintenance): retain private identities during stale cleanup
* fix(ops): consume Compose service output before adoption checks
* test(owner): cover prewarm scheduling recovery and shutdown
* test(e2e): isolate server ports and Vite dependency caches
* test(feed): cover empty filters and arrivals at the top
* test(map): budget two cold bootstraps in mode persistence check
* docs(review): record continuation regressions and final verification
* fix(alerts): match queue heartbeats to their scrape reporter
* test(feed): preserve packet anchors across scroll-mode changes
* test(owner): verify incremental prewarm cache refreshes
* fix(maintenance): preserve packet privacy fence during public cleanup
* fix(maintenance): recheck inactive sightings before archive and delete
* fix(owner): serve fresh cache hits during background prewarm
* test(map): give each viewport its own startup budget
* test(map): load valid terrain and coverage tiles in local fixtures
* fix(metrics): export unknown heartbeat when Redis initialization fails
* test(path): control worker-pool deadlines independently of thread startup
* test(e2e): await owner and map readiness before state assertions
* test(feed): check fractional viewport and spacer invariants
* docs(review): record green expanded verification checkpoint
* fix(maintenance): acquire visibility before node locks and skip busy rows
* test(maintenance): retain concurrent RF-only activity in native fixtures
* fix(owner): replace regrouped last-hop buckets during refresh
* test(owner): verify warm regrouping with actual last-hop SQL
* test(maintenance): retain concurrently committed node consent
* test(maintenance): avoid a waiting consent-trigger deadlock
* test(feed): verify responsive anchoring in Firefox and WebKit
* docs(review): reconcile completed checks and remote CI evidence
* docs(review): correct checkpoint wording
* test(maintenance): verify privacy fences on native PostgreSQL
* test(health): verify link heartbeat publisher lifecycle
* docs(review): record stop-signal handoff and remaining verification
---------
Co-authored-by: gadgethd <111318106+gadgethd@users.noreply.github.com>
* fix(feed): preserve scroll anchors across scroll-mode flips
React 19.3 fires the window scroll listener before React commits its new
row range, so updateViewport could overwrite the last known content offset
with the new-mode artifact (0) before the anchor-preserving layout effect
ran — dropping the feed to the top on a responsive resize.
Detect the scroll-mode flip (internal container <-> document scrolling) in
updateViewport and transfer the last known content offset into the new mode
before the anchor effect evaluates.
Verified: feed-virtualizer e2e (both viewports) and the full public-desktop
suite under react 19.2.8 and 19.3.0.
* fix(#553): sync replace-container test harness
* test(ops): sync replace-container harness with adoption + infra flows (#553)
Union of both branch evolutions: Compose adoption drills (check-compose-adoption
staging, MOCK_ADOPTION_* modes, adoption/long-service/overlay cases) plus the
infra-compose staging and timescaledb resolver drill assertions. Fixes the
Workers-and-Compose gate failing on this PR.
Verified locally: 16/16 drills pass.
---------
Co-authored-by: gadgethd <111318106+gadgethd@users.noreply.github.com>
Co-authored-by: hermes-gadget <284726080+hermes-gadget@users.noreply.github.com>
* feat(ops): auto-rebaseline owner ACL inventory
* docs(ops): handoff owner baseline autorebaseline
* ops: add reviewed owner-authorization baseline refresh script
scripts/refresh-owner-baseline.sh regenerates the owner inventory baseline
with the running backend image, validates it against the live inventory
(fail-closed), backs up .env before repointing, and can recreate the backend.
It refuses to overwrite an existing baseline and keeps the previous file for
rollback. Adds the matching operations runbook section.
* test(ops): cover refresh baseline validation output
---------
Co-authored-by: gadgethd <111318106+gadgethd@users.noreply.github.com>
* 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>
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).
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.
Consolidates the stacked backend, privacy, network-intelligence, frontend, operations, mobile, and owner-cache changes after resolving main conflicts and passing the full CI suite.