* 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.