From 1d243222ef464a2e4c95cbce9e7a4ecd166534db Mon Sep 17 00:00:00 2001 From: gadgethd Date: Wed, 14 Oct 2026 01:48:11 +0000 Subject: [PATCH] =?UTF-8?q?chore(sync):=20reconcile=20with=20production=20?= =?UTF-8?q?deploy=20tree=20=E2=80=94=20features,=20docs,=20plans=20gitigno?= =?UTF-8?q?red?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bring main up to date with the live UKMesh production tree, reconciled per-file (tree-wins for deployed features; main-wins where main evolved past the tree's lineage; hand-merges for mixed files). Verified locally on the deploy host: backend + frontend typechecks, backend unit suite (543 pass / 0 fail), frontend suite (147 pass / 0 fail), OpenAPI contract check, frontend production build, scripts typecheck. Highlights: - Deployed features land: tags + recent tags, owner public feed, Viterbi path prewarm and prior rebuilds, region participation, owner packet-sharing JWT scope, pathing docs, README updates. - Main-side hardening retained: CI RAM steps + smoke cleanup trap, hardened scripts, gitleaks/logging rules, stale-MQTT observer protections, alert-receiver queue module, health-status refactor with tree retention additions (owner_packet_share_deliveries + public_feed_deliveries @30d, node_status_samples @30d — matching live TimescaleDB policies). - Credential-generation client close restored on the tree's ownerAccess so the operator revocation tool (owner-auth:revoke) keeps working. - Migrations renumbered to follow main's 058/059 ledger migrations: 055_public_feed, 060_region_scope_participation, 061_packet_share_owner_scope_and_jwt; migration-registry test assertions updated to the final numbering. - Planning/report artifacts removed from the repo; local plans/, *.bak* patterns gitignored (deploy tree tidied to match). --- .env.example | 40 +- .github/workflows/ci.yml | 7 + .gitignore | 10 + .gitleaks.toml | 35 + DB-AUDIT-FIX-2026-08-13.md | 230 ---- FEED-HISTORY-2026-08-06-bot.png | Bin 177726 -> 0 bytes README.md | 173 ++- SEO-ROLLBACK-2026-08-08.txt | 20 - SPRING-DASHBOARD-2026-08-06.png | Bin 201775 -> 0 bytes SPRING-INSTALL-AUDIT-2026-08-06.png | Bin 317635 -> 0 bytes SPRING-MAPFEED-2026-08-06.png | Bin 390806 -> 0 bytes SPRING-PAGES-2026-08-06.png | Bin 126761 -> 0 bytes SPRING-TOPOLOGY-2026-08-06.png | Bin 257664 -> 0 bytes TERRAIN-V6-REPORT.md | 88 -- anubis/botPolicy.yaml | 28 + backend/backfill-region-names.mjs | 222 ++++ backend/package-lock.json | 46 +- backend/package.json | 4 + backend/src/api/contracts.test.ts | 19 - backend/src/api/contracts.ts | 211 +++- backend/src/api/routes.ts | 23 +- .../api/routes/auditwave.integration.test.ts | 180 +++ .../api/routes/auditwave.performance.test.ts | 93 ++ backend/src/api/routes/exports.test.ts | 51 +- backend/src/api/routes/exports.ts | 2 +- backend/src/api/routes/misc.test.ts | 61 - backend/src/api/routes/misc.ts | 51 +- backend/src/api/routes/nodeLinks.test.ts | 56 + backend/src/api/routes/nodeStatus.ts | 32 +- backend/src/api/routes/owner.ts | 191 ++- .../src/api/routes/ownerPacketSharing.test.ts | 98 +- .../src/api/routes/ownerPublicFeed.test.ts | 185 +++ .../src/api/routes/productFeatures.test.ts | 82 ++ backend/src/api/routes/productFeatures.ts | 2 +- backend/src/api/routes/regions.test.ts | 166 +++ backend/src/api/routes/regions.ts | 408 +++++++ .../routes/rfValidation.integration.test.ts | 22 + backend/src/api/routes/rfValidation.test.ts | 112 ++ backend/src/api/routes/rfValidation.ts | 34 +- backend/src/cache/boundedTtlMap.test.ts | 35 - backend/src/cache/boundedTtlMap.ts | 12 +- backend/src/cache/policyRegistry.ts | 6 + backend/src/db/advertCount.test.ts | 2 - backend/src/db/dataLifecycle.ts | 11 +- backend/src/db/index.ts | 109 +- backend/src/db/migrations/055_public_feed.sql | 50 + .../060_region_scope_participation.sql | 53 + .../061_packet_share_owner_scope_and_jwt.sql | 10 + backend/src/db/nodeHistory.test.ts | 37 + backend/src/db/ownerAuth.ts | 49 +- .../src/db/packetBatch.integration.test.ts | 3 + backend/src/db/schema/base.sql | 24 +- backend/src/health/status.ts | 21 +- backend/src/index.ts | 53 +- backend/src/metrics.ts | 19 + backend/src/mqtt/aclManager.test.ts | 54 + backend/src/mqtt/aclManager.ts | 51 +- backend/src/mqtt/client.ts | 7 + backend/src/mqtt/decodePacket.test.ts | 11 +- backend/src/mqtt/topic.test.ts | 3 - backend/src/owner/ownerAccess.ts | 76 +- backend/src/owner/ownerAclReconciler.ts | 55 +- backend/src/owner/ownerDashboard.test.ts | 32 + backend/src/owner/ownerDashboard.ts | 11 +- backend/src/owner/ownerLastHopPrewarm.test.ts | 202 ---- backend/src/owner/ownerLastHopPrewarm.ts | 197 ++- .../owner/ownerRepository.integration.test.ts | 93 ++ backend/src/owner/ownerRepository.ts | 140 ++- backend/src/owner/ownerService.test.ts | 207 +--- backend/src/owner/ownerService.ts | 180 ++- backend/src/owner/packetShareAuth.test.ts | 52 + backend/src/owner/packetShareAuth.ts | 56 + backend/src/owner/packetShareConfig.test.ts | 37 + backend/src/owner/packetShareConfig.ts | 47 +- .../src/owner/packetShareForwarder.test.ts | 194 +++ backend/src/owner/packetShareForwarder.ts | 182 ++- .../packetShareRepository.integration.test.ts | 63 + backend/src/owner/packetShareRepository.ts | 37 +- backend/src/owner/publicFeedConfig.test.ts | 71 ++ backend/src/owner/publicFeedConfig.ts | 200 +++ backend/src/owner/publicFeedForwarder.test.ts | 131 ++ backend/src/owner/publicFeedForwarder.ts | 305 +++++ .../src/owner/publicFeedRepository.test.ts | 63 + backend/src/owner/publicFeedRepository.ts | 450 +++++++ backend/src/path-beta/championPriors.test.ts | 30 + backend/src/path-beta/championPriors.ts | 73 ++ backend/src/path-beta/resolveWorker.ts | 6 +- backend/src/path-beta/resolver.ts | 187 ++- backend/src/path-beta/resolverContext.test.ts | 52 + backend/src/path-beta/sharedDecoder.test.ts | 74 ++ backend/src/path-beta/sharedDecoder.ts | 166 ++- backend/src/path-beta/types.ts | 5 + backend/src/path-beta/workerPool.test.ts | 20 +- backend/src/path-core/decoder.test.ts | 32 + backend/src/path-core/decoder.ts | 92 +- backend/src/path-lazy/evaluate.ts | 5 +- backend/src/path-lazy/lazyResolver.ts | 4 +- backend/src/path-learning/rebuild.test.ts | 14 +- backend/src/path-learning/rebuild.ts | 32 +- backend/src/path-shared/scoring.ts | 8 +- backend/src/platform/config/pathing.ts | 2 +- backend/src/regions/memberShare.test.ts | 31 + backend/src/regions/memberShare.ts | 23 + backend/src/regions/regionNames.ts | 55 + backend/src/regions/scopeDecode.test.ts | 74 ++ backend/src/regions/scopeDecode.ts | 39 + .../src/repositories/networkAnalysis.test.ts | 86 ++ backend/src/repositories/networkAnalysis.ts | 179 ++- .../nodeLinks.integration.test.ts | 150 +++ backend/src/repositories/nodes.test.ts | 42 +- backend/src/repositories/nodes.ts | 88 +- .../productFeatures.integration.test.ts | 170 +++ .../src/repositories/productFeatures.test.ts | 22 +- backend/src/repositories/productFeatures.ts | 50 +- backend/src/spam/cluster.test.ts | 31 + backend/src/spam/cluster.ts | 112 +- backend/src/spam/config.ts | 16 + backend/src/spam/normalize.test.ts | 19 +- backend/src/spam/normalize.ts | 50 + backend/src/stats/statsRepository.test.ts | 98 -- backend/src/stats/statsRepository.ts | 228 +--- .../src/tools/inventoryOwnerAuthorization.ts | 58 +- backend/src/tools/pathAccuracy.test.ts | 22 + backend/src/tools/pathAccuracy.ts | 342 ++++++ backend/src/tools/prepareScotmesh.ts | 48 + backend/src/tools/provisionScotmesh.ts | 61 + .../src/tools/rebuildRegionParticipation.ts | 405 +++++++ backend/src/ws/server.ts | 2 + backup-receipts/latest.json | 24 +- backup-receipts/latest.json.sig | Bin 384 -> 384 bytes docker-compose.yml | 40 +- docs/db-lifecycle.md | 98 +- docs/decryption.md | 18 +- docs/openapi.yaml | 1074 +++++++++++++++++ docs/operations.md | 150 +-- docs/pathing-accuracy-2026-09-05.json | 374 ++++++ docs/pathing-accuracy-2026-09-05.md | 173 +++ docs/pathing.md | 154 ++- docs/privacy.md | 19 +- docs/runbook-backup-restore.md | 6 - docs/runbook-release-rollback.md | 9 - docs/runbook-srtm-alerts-observers.md | 14 +- docs/spam-detection.md | 45 +- frontend/package-lock.json | 23 + frontend/package.json | 1 + frontend/playwright.config.ts | 30 +- frontend/public/sw-runtime.js | 14 +- frontend/src/App.tsx | 3 + .../components/ConfirmedNeighbours.test.ts | 77 ++ .../src/components/ConfirmedNeighbours.tsx | 123 ++ frontend/src/components/Map/RegionsMap.tsx | 342 ++++++ .../src/components/Map/regionOverlay.test.ts | 109 ++ frontend/src/components/Map/regionOverlay.ts | 99 ++ frontend/src/components/PacketFeed.tsx | 7 +- .../owner/OwnerPacketSharingSection.test.ts | 91 ++ .../components/owner/OwnerPortalSections.tsx | 210 ++++ .../owner/OwnerPublicFeedSection.test.ts | 67 + frontend/src/config/publicRoutes.ts | 9 +- frontend/src/hooks/packetFeed.ts | 55 + .../src/hooks/packetPathOverlayUtils.test.ts | 18 + frontend/src/hooks/packetPathOverlayUtils.ts | 27 +- frontend/src/hooks/useMessageTags.ts | 49 + frontend/src/hooks/useNodes.test.ts | 21 - frontend/src/hooks/useNodes.ts | 43 +- frontend/src/hooks/usePacketDetailData.ts | 3 + frontend/src/hooks/useVisibilityPoll.test.ts | 65 + frontend/src/hooks/useVisibilityPoll.ts | 22 +- frontend/src/main.tsx | 2 + frontend/src/pages/OpenSourcePage.tsx | 1 + .../src/pages/OwnerPortalPage.browser.test.ts | 112 ++ frontend/src/pages/OwnerPortalPage.tsx | 396 +++++- frontend/src/pages/RegionsPage.tsx | 380 ++++++ .../src/pages/TopologyPage.browser.test.ts | 71 ++ frontend/src/pages/TopologyPage.test.ts | 52 + frontend/src/pages/TopologyPage.tsx | 104 +- frontend/src/pages/docs-pages.css | 4 - frontend/src/pages/owner-portal.css | 323 +++++ .../src/pages/owner/OwnerHeardNeighbors.tsx | 19 +- .../src/pages/owner/OwnerPanelState.test.ts | 49 + frontend/src/pages/owner/OwnerPanelState.tsx | 28 + .../src/pages/owner/OwnerPortalCharts.tsx | 176 ++- .../src/pages/owner/ownerPortalModel.test.ts | 12 - frontend/src/pages/owner/ownerPortalModel.ts | 152 ++- .../src/pages/owner/packetShareModel.test.ts | 54 + frontend/src/pages/regions-page.css | 79 ++ frontend/src/pages/regionsModel.test.ts | 138 +++ frontend/src/pages/regionsModel.ts | 167 +++ .../src/pages/ukmesh/PacketDetailPanel.tsx | 27 + frontend/src/pages/ukmesh/UKFeedPage.tsx | 62 +- frontend/src/pages/ukmesh/UKLayout.tsx | 2 +- .../src/pages/ukmesh/UKRepeaterSearchPage.tsx | 48 +- frontend/src/pages/ukmesh/feed-page.css | 27 + frontend/src/pages/ukmesh/feedModel.ts | 14 +- frontend/src/styles/map-app.css | 25 + frontend/test/e2e/accessibility.spec.ts | 7 +- frontend/test/e2e/dashboard.spec.ts | 79 +- frontend/test/e2e/mobile.spec.ts | 77 +- frontend/test/e2e/owner.spec.ts | 39 +- frontend/vite.config.ts | 21 +- tagger-worker/Dockerfile | 8 + tagger-worker/tagger_worker.py | 347 ++++++ .../tests/test_side_effect_markers.py | 68 -- 202 files changed, 14102 insertions(+), 2760 deletions(-) delete mode 100644 DB-AUDIT-FIX-2026-08-13.md delete mode 100644 FEED-HISTORY-2026-08-06-bot.png delete mode 100644 SEO-ROLLBACK-2026-08-08.txt delete mode 100644 SPRING-DASHBOARD-2026-08-06.png delete mode 100644 SPRING-INSTALL-AUDIT-2026-08-06.png delete mode 100644 SPRING-MAPFEED-2026-08-06.png delete mode 100644 SPRING-PAGES-2026-08-06.png delete mode 100644 SPRING-TOPOLOGY-2026-08-06.png delete mode 100644 TERRAIN-V6-REPORT.md create mode 100644 backend/backfill-region-names.mjs create mode 100644 backend/src/api/routes/auditwave.integration.test.ts create mode 100644 backend/src/api/routes/auditwave.performance.test.ts create mode 100644 backend/src/api/routes/nodeLinks.test.ts create mode 100644 backend/src/api/routes/ownerPublicFeed.test.ts create mode 100644 backend/src/api/routes/productFeatures.test.ts create mode 100644 backend/src/api/routes/regions.test.ts create mode 100644 backend/src/api/routes/regions.ts create mode 100644 backend/src/api/routes/rfValidation.integration.test.ts create mode 100644 backend/src/api/routes/rfValidation.test.ts create mode 100644 backend/src/db/migrations/055_public_feed.sql create mode 100644 backend/src/db/migrations/060_region_scope_participation.sql create mode 100644 backend/src/db/migrations/061_packet_share_owner_scope_and_jwt.sql create mode 100644 backend/src/db/nodeHistory.test.ts delete mode 100644 backend/src/owner/ownerLastHopPrewarm.test.ts create mode 100644 backend/src/owner/ownerRepository.integration.test.ts create mode 100644 backend/src/owner/packetShareAuth.test.ts create mode 100644 backend/src/owner/packetShareAuth.ts create mode 100644 backend/src/owner/packetShareRepository.integration.test.ts create mode 100644 backend/src/owner/publicFeedConfig.test.ts create mode 100644 backend/src/owner/publicFeedConfig.ts create mode 100644 backend/src/owner/publicFeedForwarder.test.ts create mode 100644 backend/src/owner/publicFeedForwarder.ts create mode 100644 backend/src/owner/publicFeedRepository.test.ts create mode 100644 backend/src/owner/publicFeedRepository.ts create mode 100644 backend/src/path-beta/championPriors.test.ts create mode 100644 backend/src/path-beta/championPriors.ts create mode 100644 backend/src/regions/memberShare.test.ts create mode 100644 backend/src/regions/memberShare.ts create mode 100644 backend/src/regions/regionNames.ts create mode 100644 backend/src/regions/scopeDecode.test.ts create mode 100644 backend/src/regions/scopeDecode.ts create mode 100644 backend/src/repositories/networkAnalysis.test.ts create mode 100644 backend/src/repositories/nodeLinks.integration.test.ts create mode 100644 backend/src/repositories/productFeatures.integration.test.ts create mode 100644 backend/src/tools/pathAccuracy.test.ts create mode 100644 backend/src/tools/pathAccuracy.ts create mode 100644 backend/src/tools/prepareScotmesh.ts create mode 100644 backend/src/tools/provisionScotmesh.ts create mode 100644 backend/src/tools/rebuildRegionParticipation.ts create mode 100644 docs/pathing-accuracy-2026-09-05.json create mode 100644 docs/pathing-accuracy-2026-09-05.md create mode 100644 frontend/src/components/ConfirmedNeighbours.test.ts create mode 100644 frontend/src/components/ConfirmedNeighbours.tsx create mode 100644 frontend/src/components/Map/RegionsMap.tsx create mode 100644 frontend/src/components/Map/regionOverlay.test.ts create mode 100644 frontend/src/components/Map/regionOverlay.ts create mode 100644 frontend/src/components/owner/OwnerPacketSharingSection.test.ts create mode 100644 frontend/src/components/owner/OwnerPublicFeedSection.test.ts create mode 100644 frontend/src/hooks/useMessageTags.ts create mode 100644 frontend/src/pages/OwnerPortalPage.browser.test.ts create mode 100644 frontend/src/pages/RegionsPage.tsx create mode 100644 frontend/src/pages/TopologyPage.browser.test.ts create mode 100644 frontend/src/pages/TopologyPage.test.ts create mode 100644 frontend/src/pages/owner/OwnerPanelState.test.ts create mode 100644 frontend/src/pages/owner/OwnerPanelState.tsx create mode 100644 frontend/src/pages/owner/packetShareModel.test.ts create mode 100644 frontend/src/pages/regions-page.css create mode 100644 frontend/src/pages/regionsModel.test.ts create mode 100644 frontend/src/pages/regionsModel.ts create mode 100644 tagger-worker/Dockerfile create mode 100644 tagger-worker/tagger_worker.py diff --git a/.env.example b/.env.example index 7e51ccd..759234f 100644 --- a/.env.example +++ b/.env.example @@ -26,6 +26,26 @@ POSTGRES_MAX_WORKER_PROCESSES=24 # Internal docker network URL for Mosquitto WebSocket listener MQTT_BROKER_URL=ws://mosquitto:9001 MQTT_CLIENT_ID=meshcore-analytics-ingest +MESHCORE_SITE_ID=ukmesh + +# Site-to-site owner packet sharing is available to every authenticated owner. +# It does not enable a destination. Each owner/node selection starts off until +# that owner turns it on. Destination rows are operator-managed in PostgreSQL; +# leave the encryption key empty until a destination is provisioned. +OWNER_PACKET_SHARE_ENCRYPTION_KEY= +OWNER_PACKET_SHARE_POLL_INTERVAL_MS=5000 +OWNER_PACKET_SHARE_BATCH_SIZE=25 +# Owner dashboard RX-strength cache warmer. Keeps the last-hop series cached for +# every active MQTT owner's nodes (7d and 30d windows) so the owner dashboard +# chart never pays for a cold scan on open. Interval in ms (invalid or <= 0 +# disables); NODE_DELAY paces the per-node loop within a pass. +OWNER_LAST_HOP_PREWARM_INTERVAL_MS=900000 +OWNER_LAST_HOP_PREWARM_NODE_DELAY_MS=150 +# Public feed endpoint settings. This is the authentication-locked UKmesh +# Mosquitto endpoint; opted-in active owners use their existing MQTT login for +# a read-only ACL grant. Account toggles remain off by default. +PUBLIC_FEED_PUBLISH_ENABLED=false +PUBLIC_FEED_BROKER_URL= # Credentials for the backend MQTT client MQTT_USERNAME=backend @@ -76,7 +96,7 @@ HEALTH_LOG_BUDGET_BYTES=21474836480 DATA_LIFECYCLE_COMPRESSION_ENABLED=false DATA_LIFECYCLE_RETENTION_ENABLED=false DATA_LIFECYCLE_COMPRESSION_TARGETS=packets,packet_paths,node_status_samples,node_neighbor_samples -DATA_LIFECYCLE_RETENTION_TARGETS=packets,worker_health_snapshots,frontend_error_events,owner_alert_deliveries,operational_check_results,observer_region_packet_sightings,observer_region_observer_sightings,link_job_commits,ml_model_variant_packet_results,packet_decryptions,observer_registration_requests,operator_audit_events +DATA_LIFECYCLE_RETENTION_TARGETS=packets,worker_health_snapshots,frontend_error_events,owner_alert_deliveries,owner_packet_share_deliveries,public_feed_deliveries,operational_check_results,observer_region_packet_sightings,observer_region_observer_sightings,link_job_commits,ml_model_variant_packet_results,packet_decryptions,observer_registration_requests,operator_audit_events BACKUP_RECEIPT_HOST_DIR=./backup-receipts DATA_LIFECYCLE_RECEIPT_MAX_AGE_DAYS=7 @@ -164,6 +184,12 @@ OWNER_ACL_UNMANAGED_USERS=backend,test,test2 # Explicit exception list for intentionally revoked/disabled accounts that # should retain an empty broker stanza during apply cutover. OWNER_ACL_ALLOW_EMPTY_USERS= +# Operator-approved read-only subscribers (comma-separated MQTT usernames). +# These accounts receive only the curated public packet namespace for opted-in, +# active owners; they never receive publish permission. +OWNER_ACL_PUBLIC_FEED_READ_USERS= +# Internal backend publish namespace for the curated public feed. +OWNER_ACL_SYSTEM_READWRITE_TOPICS=public/# # Set these after exporting the reviewed pre-reset inventory. Readiness fails # closed if database counts/grants, generations, or ACL readback differ. OWNER_AUTH_INVENTORY_HOST_DIR=./backup-receipts @@ -199,6 +225,9 @@ SPAM_MESSAGE_MAX_NORMALIZED_CHARS=512 SPAM_MESSAGE_MAX_INCIDENTS=100 SPAM_MESSAGE_MAX_CANDIDATE_CLUSTERS=64 SPAM_MESSAGE_MAX_EVIDENCE_MEMBERS=256 +# Per-run work budget for the spam analyzer. Quiet days finish in ~4s; busy +# spam days need ~30-60s (relay-path origin resolution per public incident). +# Raise (e.g. 60000) if the analyzer log shows status=timed_out. SPAM_MESSAGE_ANALYSIS_BUDGET_MS=5000 SPAM_MESSAGE_DB_TIMEOUT_MS=15000 STATS_UNIQUE_INFLIGHT_MAX=32 @@ -279,6 +308,15 @@ SPAM_MESSAGE_PUBLIC_MIN_SCORE=0.5 # sustained automated flood and scored up (unless it is plain test/connectivity # traffic). Catches templated floods even when they embed the word "test". SPAM_MESSAGE_FLOOD_MIN_MESSAGES=25 +# Rotating-ID botnet detection: many single-shot, machine-named sender IDs +# blasting the same text in bursts (see docs/spam-detection.md). A cluster +# qualifies when >= MIN_SENDERS distinct senders, >= SINGLE_SHOT_SHARE of them +# appear exactly once in the window, >= MACHINE_NAME_SHARE have keygen-style +# names, and >= MIN_BURST_SENDERS land inside one burst window. +SPAM_MESSAGE_ROTATING_MIN_SENDERS=5 +SPAM_MESSAGE_ROTATING_SINGLE_SHOT_SHARE=0.8 +SPAM_MESSAGE_ROTATING_MACHINE_NAME_SHARE=0.5 +SPAM_MESSAGE_ROTATING_MIN_BURST_SENDERS=3 # Channel labels excluded from detection (comma-separated). The test channel # carries repeated test traffic by design. NOTE: a flood that also hits other # channels (e.g. Public) is still caught via those copies. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 47389e9..47733f5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -225,6 +225,13 @@ jobs: publisher_user="ci-e2e-publisher" publisher_password="ci-e2e-publisher-password" observer="CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC" + cleanup() { + docker compose -f docker-compose.yml -f docker-compose.ci.yml \ + --project-name "$project" \ + --profile dev \ + down --volumes --remove-orphans + } + trap cleanup EXIT # Create each Argon2 entry independently. Updating an existing # password file with this image is not reliable on hosted runners. docker run --rm \ diff --git a/.gitignore b/.gitignore index 8da2091..52203e5 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,9 @@ __pycache__/ # Runtime logs (operational, not for source control) *.log +# Frozen local path evaluation data and per-packet predictions +/artifacts/path-accuracy/ + # Claude context file (may contain deployment-specific details) CLAUDE.md AI_MEMORY.md @@ -62,3 +65,10 @@ local-vs-github-pr-plan.md terrain-tiles/ /.env.bak-* +# Encrypted deployment-specific relay identities. +.private-config/ + +# Local planning documents / reports (tidy folder, never committed) +/plans/ +*.bak +*.bak-* diff --git a/.gitleaks.toml b/.gitleaks.toml index 4dd28ff..e623993 100644 --- a/.gitleaks.toml +++ b/.gitleaks.toml @@ -2,3 +2,38 @@ title = "UK Mesh gitleaks configuration" [extend] useDefault = true + +[[rules]] +id = "generic-api-key" + +[[rules.allowlists]] +description = "Documented MeshCore public-channel interoperability key" +paths = ['''^backend/backfill-transport-codes\.mjs$'''] +regexTarget = "line" +regexes = ['''(?i)const DEFAULT_PUBLIC_CHANNEL_KEY\s*=\s*'[0-9a-f]{32}'\s*;'''] +condition = "AND" + +[[rules.allowlists]] +description = "Reviewed public/community MeshCore channel secrets (channelRegistry.ts VALIDATED_CHANNELS) — each entry verified to decrypt real UK Mesh group text; classified intentionally public in the channel registry" +paths = ['''^backend/src/mqtt/channelRegistry\.ts$'''] +regexTarget = "line" +regexes = ['''\{ name: '[A-Za-z0-9_-]+',\s+secret: '[0-9a-f]{32}'\s*\},?'''] +condition = "AND" + +[[rules.allowlists]] +description = "Documented Public channel key in any file/historical commit (exact fingerprint)" +regexTarget = "secret" +regexes = ['''^8b3387e9c5cdea6ac9e5edbaa115cd72$'''] + +[[rules.allowlists]] +description = "Fake test fixtures (exact fingerprints)" +regexTarget = "secret" +regexes = [ + '''^0123456789abcdef0123456789abcdef$''', + '''^abcdef0123456789$''', +] + +[[rules.allowlists]] +description = "Deployed-commit SHA in live website manifests" +regexTarget = "secret" +regexes = ['''^bef003f072283fd5e3f14b29890f7610adf3fb9808e8c6e700bd48d3e7a8bc87$'''] diff --git a/DB-AUDIT-FIX-2026-08-13.md b/DB-AUDIT-FIX-2026-08-13.md deleted file mode 100644 index ecc2d2b..0000000 --- a/DB-AUDIT-FIX-2026-08-13.md +++ /dev/null @@ -1,230 +0,0 @@ -# ukmesh database reset-readiness fix report — 2026-08-13 - -## Verdict - -**Fresh-DB acceptance: PASS.** A database created from empty storage on the -CI/restore pinned TimescaleDB 2.25.1 digest applied the complete 49-file chain -(the 48 audited entries plus `051_reset_readiness.sql`), passed the full object -assertions, and treated a second migration run as a no-op. - -**Production reset authorization: BLOCKED only on independent off-host -publication.** The current encrypted local backup and isolated restore are -verified. The VPS has no distinct mounted off-host target, so the reset must not -start until Ben mounts and attests one and `sync-latest.sh` reports `verified`. -The local sync drill is useful evidence for the script, but is not represented -as off-host evidence. - -No production migration, SQL write, service restart, or production `compose up` -was performed. The only live-stack operations were read-only database dumps, -supported Redis/Mosquitto persistence checkpoints, and the read-only owner -inventory export. `.env` grant values were not changed. - -## Commits - -- `e7d16d4` — `fix(db): make reset schema and lifecycle reproducible` -- `c79f71e` — `fix(owner): preserve authorization inventory across reset` -- `e3a8ba3` — `fix(backup): restore split-stack recovery workflow` - -These commits are local only and were not pushed. - -## Finding status - -### F1 / G1 — identity materialized views: FIXED (`e7d16d4`) - -Migration 051 safely replaces fresh ordinary views with populated materialized -views, while treating already-materialized live objects as no-ops. It creates -the unique sighting `(node_id, network)` and link `(node_a_id, node_b_id)` -indexes, the reversed sighting index, and both link endpoint indexes. Fresh-DB -tests assert `pg_matviews`, all five definitions, and successful concurrent -refreshes of both views. - -### F2 / G5 — split-stack backup and recovery: REVISED (`e3a8ba3`) - -`backup.sh` now resolves TimescaleDB, Redis, and Mosquitto through the explicit -`meshcore-infra` project/directory. The encrypted recovery archive contains the -base/live/CI/infra-client/phase-4 app Compose files and a separate protected -infra configuration archive. Broker config is captured through its container, -and restore streams verified state into isolated named volumes, avoiding host -permission/bind differences. - -`sync-latest.sh` performs signed-receipt selection, full archive checksum -verification, partial-directory staging, atomic publication, a checksum -manifest, and post-publication verification. A same-filesystem functional drill -returned `verified` and its exact test directory was deleted. This finding is -REVISED rather than FIXED because no genuinely off-host mount exists yet. - -Canonical local backup evidence (secret recovery material, not committed): - -- ID: `backup-20260813T031400Z-2c1dd39b7173` -- completed: `2026-08-13T03:18:09Z` -- live schema captured: 50 -- encrypted bytes: 1,196,412,781 -- SHA-256: `6466077779be65d3f843843b3fbf57cd33abad671391c5744e93d3a0fe1911a7` -- datasets: analytics, owner auth, Redis, Mosquitto, app configuration, infra configuration -- exact-artifact isolated restore: PASS on the pinned 2.25.1 digest -- restored/current schema: 51; RTO: 414s; RPO at verification: 527s -- signed receipt: `backup-receipts/latest.json` (signature independently verified) - -The receipt's `source_revision` is the mandated pre-fix HEAD `2c1dd39` because -all gates were run before the local commits. The bundled scripts and migrations -are the exact working-tree content that passed the drill. - -### F3 / G3 — owner grant preservation: REVISED (`c79f71e`, `e3a8ba3`) - -The protected, gitignored baseline is -`/home/ben/ukmesh/backups/owner-grants-2026-08-13T021734Z.json` (mode 0600, -content hash `2d795007c523316cb4967262f89020813c213c727653fdabdfcd130fa0463fd6`). -It contains every reviewed active grant: 27 accounts, 44 grants, 5 -`operator-config`, 36 `operator-database`, and 3 legacy/null-method. Desired, -rendered, and applied ACL generations match and `lastError` is null. - -`owner-auth:inventory` exports/loads a checksum-bearing baseline and -`--require-complete` rejects count, method, config generation, ACL generation, -ACL readback, or last-error drift. `/readyz` applies the same baseline gate. -The recovery bundle includes `owner-auth.dump`; the runbook restores that dump, -mounts the baseline read-only, validates it before public admission, and leaves -`OWNER_MQTT_USERNAME_MAP` unchanged. - -The three legacy/null-method grants are deliberately preserved, not silently -dropped or promoted. Ben must choose promote-to-`operator-database` or revoke; -until then the recorded disposition is -`preserve-unmodified-pending-owner-review`. This outstanding policy choice does -not risk grant loss during a restore. - -### F4 / G4 — packet-path privacy transitions: FIXED (`e7d16d4`) - -Read/derivation SQL now evaluates path privacy from current privacy tables. -Because compatibility flags remain materialized, migration 051 also adds -ingest and node-transition rematerialization triggers plus receiver, source, -and path-prefix transition indexes. Integration tests cover public→private and -private→public both before and after source-packet deletion. - -### F5 / G2 — reproducible compression/capacity state: FIXED (`e7d16d4`) - -Migration 051 enables and schedules compression for all four hypertables: - -- `packets`: compress after 14 days; hard row retention at 30 days -- `packet_paths`: compress after 14 days; **no retention policy** -- `node_status_samples`: compress after 14 days -- `node_neighbor_samples`: compress after 1 day (7-day retention) - -The source registry uses the same values and keeps compression-only targets -separate from deletable targets. `packet_paths` is segmented by network and -ordered by descending time. Health metrics and alerts cover 30-day path row -rate, bytes-per-row/total size, and overdue uncompressed chunks. Fresh tests -assert exact Timescale settings/jobs and the packet-path no-retention invariant. - -### F6 — shared observation identity and post-retention reader: FIXED (`e7d16d4`) - -The classified source CTE generates one UUID used by both packet inserts. -Migration 051 repairs recent natural-key mismatches. Multibyte reconstruction -and observation-ID backfill now source durable rows from `packet_paths`. -Integration proves shared IDs, idempotency, and successful fact reconstruction -after the source packet is deleted. - -### F7 — reachable row-table retention: FIXED (`e7d16d4`, `c79f71e`) - -The closed worker allowlist, lifecycle declarations, Compose/default env wiring, -and tests now include `observer_registration_requests` at 365 days with an -explicit terminal-only predicate (`rejected`, `expired`, `provisioned`) and -`operator_audit_events` at 730 days. Migration 051 adds bounded-delete indexes. - -### F8 — indexed deterministic decryption retention: FIXED (`e7d16d4`) - -Migration 051 adds `packet_decryptions_created_at_idx`. The bounded worker -orders by timestamp, `tableoid`, and `ctid`, and deletes the exact selected -physical rows. Integration covers expired/retained rows and verifies the index -appears in the execution plan. - -### F9 — full fresh-DB/CI assertions: FIXED (`e7d16d4`) - -Fresh integration now checks all hypertables and chunk intervals, compression -and retention jobs, packet-path constraints/indexes/no-retention, materialized -views/indexes/concurrent refresh, every checksummed ledger row, rollback on -ledger failure, and second-run idempotency. Ingest coverage uses a non-null -two-byte path and asserts observation identity, nullable observer idempotency, -privacy transitions, packet-retention survival, and path-fact rebuilding. The -Compose MQTT fixture/readback now includes `packet_paths`. - -The audited request said 48 ledger entries; the correct current assertion is -49 because migration 051 was added and all previous 48 remain asserted. - -### F10 — migration transaction ownership: FIXED (`e7d16d4`) - -Migration 050 no longer contains file-level `BEGIN`/`COMMIT`. The runner owns -the transaction. An injected failure at the migration-050 ledger insert proves -its schema and policy changes roll back before a normal resume. - -The approved original migration-050 checksum is handled as one explicit shipped -content revision; arbitrary drift still fails closed. - -### F11 — hard 30-day packet bound: FIXED (`e7d16d4`) - -Packet chunks are one day and the closed health-worker target performs bounded -exact-row cleanup at 30 days. The integration test asserts the oldest retained -packet lies inside the boundary. Privacy/lifecycle documentation now describes -the hard bound rather than a 30–37-day chunk-bounded approximation. - -### F12 — telemetry-off coverage: FIXED (`e7d16d4`) - -Tests cover no `window`, `?telemetry=off`, non-off values, suppressed `fetch`, -and suppressed error/rejection/console handler installation. No production -frontend source was changed. - -## Required pre-reset gates - -1. Identity materialized-view migration: **PASS**. -2. Current post-split backup and independent restore: **PASS locally**; - independent off-host publication: **BLOCKED — mount required**. -3. Preserve every owner grant: **PASS**; all 44 are in both `owner-auth.dump` - and the protected baseline. Legacy disposition awaits Ben's policy decision. -4. Packet-path privacy rematerialization/current read derivation: **PASS**. -5. Migration-defined compression/capacity state plus empty-volume assertions on - TimescaleDB 2.25.1: **PASS**. - -Do not reset until gate 2's off-host sub-gate passes. - -## Acceptance evidence - -- Backend: **314/314 PASS**, TypeScript typecheck PASS, OpenAPI contract PASS - (62 API and 11 operator routes). -- Frontend: **96/96 PASS**, production build PASS; telemetry cases included. -- Fresh pinned TimescaleDB integration: **2/2 PASS**, no skips. -- App live-overlay Compose config: PASS. -- `meshcore-infra` Compose config: PASS. -- Prometheus rules: **24 rules PASS** under `promtool`. -- Backup receipt signature and 1.196 GB archive SHA-256: PASS. -- Exact canonical artifact restore: PASS; migration/readiness/integrity/owner - lookup checks all `passed`. -- Second migration run: no-op; ledger count 49 with no null checksums. -- Throwaway containers, networks, volumes, images, decrypted data, and local - sync test copy: removed. -- `.audit-input`: deleted. - -## Ben decisions and remaining operation - -1. Mount the true off-host target at `/mnt/offsite/meshcore`, create its reviewed - `.meshcore-offsite-target` attestation, copy the public receipt verification - key there or into the reviewed key location, then run `sync-latest.sh` without - `BACKUP_SYNC_ALLOW_LOCAL_TEST`. Verify `latest.sha256` independently. -2. Move/recoverably escrow the locally generated drill keys currently protected - under `/home/ben/ukmesh/backup-keys`; do not rely on the same VPS as the sole - key store. -3. Decide whether the three preserved legacy/null-method grants should be - promoted to `operator-database` or revoked. The reset path preserves them in - either case until an explicit decision. -4. Compression is resolved at 14 days for packets, packet paths, and status - samples, and 1 day for neighbor samples. Packet retention is an exact hard - 30-day bound. Change these only through a reviewed migration and matching - lifecycle registry update. - -No crontab was installed. If Ben approves `/mnt/offsite/meshcore` as the actual -mount and the current key locations, these are the exact proposed lines: - -```cron -17 2 * * * /usr/bin/flock -n /home/ben/ukmesh/backups/.backup.lock /usr/bin/env BACKUP_OUTPUT_DIR=/home/ben/ukmesh/backups BACKUP_ALLOW_LOCAL_STAGING=true BACKUP_ENCRYPTION_CERT=/home/ben/ukmesh/backup-keys/encrypt.pem BACKUP_RECEIPT_SIGNING_KEY=/home/ben/ukmesh/backup-keys/receipt-signing.pem MESHCORE_INFRA_DIR=/home/ben/ukmesh/meshcore-infra MESHCORE_INFRA_PROJECT_NAME=meshcore-infra /home/ben/ukmesh/meshcore-analytics/scripts/backup.sh >>/home/ben/ukmesh/backups/backup.log 2>&1 -47 3 * * * /usr/bin/flock -n /home/ben/ukmesh/backups/.sync.lock /usr/bin/env BACKUP_SOURCE_DIR=/home/ben/ukmesh/backups BACKUP_SYNC_TARGET_DIR=/mnt/offsite/meshcore BACKUP_RECEIPT_VERIFY_KEY=/home/ben/ukmesh/backup-keys/receipt-verify.pem /home/ben/ukmesh/meshcore-analytics/scripts/sync-latest.sh >>/home/ben/ukmesh/backups/sync.log 2>&1 -``` - -Before installing them, Ben should also approve backup-generation pruning; -neither script deletes historical recovery points by design. diff --git a/FEED-HISTORY-2026-08-06-bot.png b/FEED-HISTORY-2026-08-06-bot.png deleted file mode 100644 index 1b5742761301da8f77fc9348afe4d9d78a3c82c1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 177726 zcmcG#XIN8B_wWlgL`4(@Ly_e7l zB|sn{$%)VX-0%6n=eo{^b6xM5Pdk~lXZEbUW@hc*TJu9wU4iV*(>p{&L}W^e@3e@B zZoMHQy1sq;8sQ&On*0eOq6b7u@7{j!$=F}KYdn62lRhFxW21g|KXFn|STzqSP-9Bu>mqmWofM}QP~IQisgM~(0h|CFX;=NtcC|GX

=EfRq_%vh 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 s8!GSNcA@w8=Ui5q@X//packets` with the normal MeshCore JSON +payload. The canonical ingest namespace remains private to its owners and the +backend. + +An owner who has opted in and remains active receives one read-only ACL filter +for `public/+/+/packets`, so the owner can use the existing MQTT username and +password used to publish observer packets. A named subscriber configured in +`OWNER_ACL_PUBLIC_FEED_READ_USERS` receives the same read-only filter without +publish permission. The read grant is removed when the owner disables the +switch or has no eligible packet activity for 24 hours. The dashboard lists +the curated filter and explains that packets are observable over the internet +and that the site operator cannot verify what external subscribers do with the +data. +The public endpoint is deployment configuration and is not stored with +credentials in source control. + +For an ACL-based broker, the intended public-feed permission shape is: + +```text +user +topic read public/+/+/packets +``` + +Only the internal backend account receives `topic readwrite public/#` so it can +republish approved packets. The generated owner ACL applies directly to the +Mosquitto broker managed by this deployment. + +External subscriber accounts remain operator-managed and read-only. The +backend never receives their passwords and cannot inspect what a subscriber +does after receiving a packet. + +Authenticated destinations also set `broker_username` and the encrypted +`broker_password_ciphertext` value from the command above. + +--- + ## Mosquitto Setup Mosquitto is configured for WebSocket-only access with password authentication. @@ -350,19 +499,13 @@ MeshCore Devices ## Data Retention -- The reviewed core telemetry windows are 30 days for raw packets, 180 days - for status samples, and seven days for neighbour samples. Content-stripped - `packet_paths` remain available for path reconstruction and are never - retention-deleted. Privacy-safe aggregates and current node/link/model state - remain longer lived. -- Migration 050 declares the core Timescale retention policies. Repairing a - missing policy and enabling compression require a fresh signed - backup/restore receipt and per-table approval. When lifecycle actions are - enabled, startup fails closed if required core targets are missing from the - configured allowlists. -- Expired forensic data is available only by restoring a suitable pre-expiry - backup into an isolated, read-only environment; do not restore it over - production. +- Compression and destructive retention ship disabled. Raw packets and status + samples remain intact until a table-specific, backup- and restore-gated + lifecycle rollout is approved. +- The proposed raw retention window is 180 days, preserving the longest + 120-day learner dependency. Privacy-safe hourly/daily aggregates and current + node/link/model state remain longer lived. Legacy `node_coverage` is retained + for one release as inactive rollback data only. - Operational row-table cleanup is bounded and runs only when both `DATA_LIFECYCLE_RETENTION_ENABLED=true` and the exact table appears in `DATA_LIFECYCLE_RETENTION_TARGETS`. diff --git a/SEO-ROLLBACK-2026-08-08.txt b/SEO-ROLLBACK-2026-08-08.txt deleted file mode 100644 index 73828a2..0000000 --- a/SEO-ROLLBACK-2026-08-08.txt +++ /dev/null @@ -1,20 +0,0 @@ -=== UK Mesh deploy rollback pins — 2026-08-08 (full history) === -Restore: sed -i 's|^=.*|=|' .env && docker compose -f docker-compose.yml -f docker-compose.live.yml up -d --no-deps - -WEBSITE_IMAGE (website-ukmesh): - PRE-SEO (original live) sha256:148e80fbc02c24cc30981d5e57af4dff108ba409cdd93fd2cf3b1e4e0cecd585 [retagged ukmesh-website:live-orig] - SEO delta (00:15) sha256:5843f3aff6382be0c8f1105f967321cadfa1c9989906360e610498f966ab16bb - HEAD release (01:0x) sha256:0e37443ee557a66689de0fb64fc9541eb7ef7264b06bb98e9cdf80b22cd032b3 [CURRENT] - -BACKEND_IMAGE (backend): - PRE (original) meshcore-analytics-backend:dc12587f672d0f6edd8ea88246b7cbae0ceac9a306e56e2648610a287a0286ef (digest 87074c1ebafe) - HEAD release (00:5x) sha256:1468726cfbd446b52e44b2b5b0c943342a6c138514e4ade54f06cb6cb5498aa5 [CURRENT] - -WEBSITE_DEV_IMAGE (website-dev): - PRE sha256:1483797133b859019f93b58d6c2d42b35fcf80d708f56da3b668938fbbf21b11 - CURRENT (HEAD build) sha256:0e37443ee557a66689de0fb64fc9541eb7ef7264b06bb98e9cdf80b22cd032b3 - -Config changes today: - .env ALLOWED_ORIGINS += https://test.ukmesh.com (backup .env.bak-allowed-origins-*) - docker-compose.yml backend DATABASE_STATEMENT_TIMEOUT_MS 300000 -> 900000 (charts regen) - .env STATS_AGGREGATE_READS_ENABLED=true (was already true) diff --git a/SPRING-DASHBOARD-2026-08-06.png b/SPRING-DASHBOARD-2026-08-06.png deleted file mode 100644 index 404cc7f0b17de7dc452a208f2aabb6570d4e9076..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 201775 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 diff --git a/SPRING-INSTALL-AUDIT-2026-08-06.png b/SPRING-INSTALL-AUDIT-2026-08-06.png deleted file mode 100644 index 5c9adbda290cf1c0eca0f18a1b3533fdb9683bcf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 317635 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

H9K`0$B^1QKv^~RGe$ZdF|e$9nSnuf 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�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

a;o*j`l9qeN^OHUL2F=lQ{zwQmem^Y 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'}u-}UNW3m_;`GcV27lOP7e_1_K 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≥vdleZ|h~B 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�nxTP_TmiSqlAKBZ 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

eObjGom2pWO5>5v2UW=dZ=j@CEHN%-Nyl(RH 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⤅e`=}r-u$GEqL;N#MbDl)~JpN!7~mn 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%0DF!w(STB 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 diff --git a/TERRAIN-V6-REPORT.md b/TERRAIN-V6-REPORT.md deleted file mode 100644 index 98e30b2..0000000 --- a/TERRAIN-V6-REPORT.md +++ /dev/null @@ -1,88 +0,0 @@ -# MapLibre 6 terrain regression report - -Date: 2026-08-13 UTC -Status: fixed and deployed to `app-ukmesh`, `website-ukmesh`, and `website-dev` - -## Root cause and fix - -MapLibre GL 6's terrain tile-pyramid selection produced a single raised DEM square when the local `raster-dem` source explicitly declared `minzoom: 5`. Upgrading MapLibre from 6.2.0 to 6.3.0 and correcting `tileSize` from 512 to the tiles' actual 256 pixels were necessary source corrections, but did not resolve the rendering defect by themselves. - -The decisive bare-MapLibre test rendered continuous multi-tile terrain with this source specification: - -- MapLibre GL 6.3.0 -- `tileSize: 256` -- `encoding: "terrarium"` -- `maxzoom: 12` -- no explicit `minzoom` -- terrain exaggeration 3 - -Removing only `minzoom: 5` from the application source fixed the full application while retaining hillshade, sky/atmosphere, and the deck.gl overlays. MapLibre can now select the required parent/child DEM pyramid levels itself. - -## Code changes - -Commit `18aed54ba0b4fd451c26ae6dc8fe687e07a51f1a` (`fix(map): restore multi-tile terrain on MapLibre 6`): - -- upgraded `maplibre-gl` from 6.2.0 to 6.3.0 in `frontend/package.json` and `frontend/package-lock.json`; -- changed `TERRAIN_DEM_SOURCE.tileSize` from 512 to 256; -- changed the two DEM source error-handler parameter types to `maplibregl.ErrorEvent`, preserving their behavior. - -Commit `d87b3d39c1a3f6a9ca742062515a2d316581ff3e` (`fix(map): let MapLibre select terrain DEM pyramid`): - -- removed the explicit `TERRAIN_DEM_SOURCE.minzoom` value. - -No backend code, CSS, hillshade behavior, sky behavior, or deck.gl behavior changed in the production fix. - -## Hypothesis protocol - -1. H1, shared hillshade source: removing both hillshade layer paths still failed to show continuous relief. Eliminated. -2. H2, sky/atmosphere: additionally removing `map.setSky(...)` still failed. Eliminated. -3. H3, deck.gl `MapboxOverlay`: additionally omitting both live overlay controllers still failed to provide the required continuous visual result. The diagnostic candidate was not a fixed first-loaded tile: center/candidate changed from `11/1017/652` / `11/1016/651`, to `11/1017/652` / `11/1017/651` after panning, and to `12/2035/1304` / `12/2035/1304` after zooming. Without hillshade the visual delta was weak, so this was not accepted as a fix. -4. H4, bare MapLibre 6.3: the first protocol variant, `tileSize=256`, absent `minzoom`, Terrarium encoding, exaggeration 3, rendered multi-tile terrain. Per the stop-on-success protocol, later variants were not run. - -The H4 camera-center tiles changed from `10/508/326` to `10/509/325` on pan and `11/1018/651` on zoom. Terrain-related pixel changes covered 16 distinct z10 tiles and non-null `queryTerrainElevation` samples covered 19 distinct z10 tiles. Therefore the rendered relief tracked the visible pyramid across many tiles; it was neither a center-tile-only bug nor a fixed first-loaded/cache-order tile. - -## Frontend gates - -Run from `frontend/` against the final source revision: - -- `npx tsc --noEmit`: passed cleanly. -- `npm test`: passed, 94/94 tests. -- `npm run build`: passed. -- CSS lint: the repository's known pre-existing duplicate-selector failures remain in untouched CSS (`owner-portal.css` and sibling files). `git diff --name-only` confirmed those files were not part of this change; no unrelated CSS was modified. - -## Images and deployment - -All images contain MapLibre 6.3.0 in `assets/maplibre-gl-OTXnCXpW.js` and carry source revision `d87b3d39c1a3f6a9ca742062515a2d316581ff3e`. - -| Service | Build tag | Pinned image digest | -| --- | --- | --- | -| `app-ukmesh` | `meshcore-analytics-app:v6fix-final` | `sha256:620db704760dbe91f471ce984c9805ec2257a13aa4097bb634beffbe07ccc386` | -| `website-ukmesh` | `meshcore-analytics-website:v6fix-final` | `sha256:2779d7c5da0062e8a2a5d4e2aea10ed66faaf0e492646a677a0eb30fa4a92b18` | -| `website-dev` | `meshcore-analytics-website-dev:v6fix-final` | `sha256:3674270ba54601e33ea693e95235791929f0bfd34b791cdbd016e8904a6c79e3` | - -The app was recreated with `up -d --no-deps app-ukmesh`. Both website services were deployed with `scripts/deploy-website.sh --force`; their drift checks, digest pins, recreation, and served-bundle verification passed. No other service was recreated. - -The refreshed manifests were captured at 2026-08-13T00:06:18Z. Their follow-up drift checks report 90/90 identical files for production and 76/76 identical files for staging. - -## Runtime proof - -The final diagnostic against `http://localhost:3003` recorded 15/15 terrain tile responses as HTTP 200, all `image/png`, across zoom levels 5, 9, 10, and 11. It recorded no failed responses, decode errors, or MapLibre terrain errors. The only browser noise was the expected local WebSocket 403 response. - -The final Teesside capture was taken three seconds after enabling terrain with RF Coverage disabled. It shows continuous relief across multiple adjacent terrain tiles without the former isolated hard-edged square. - -Public gates: - -- `https://ukmesh.com`: HTTP 200, `text/html` -- `https://test.ukmesh.com`: HTTP 200, `text/html` -- `https://ukmesh.com/terrain-tiles/5/15/9.png`: HTTP 200, `image/png` -- `https://test.ukmesh.com/terrain-tiles/5/15/9.png`: HTTP 200, `image/png` - -## Evidence - -- Decisive deployed Teesside screenshot: `/home/ben/ukmesh/meshcore-analytics/terrain-v6-evidence/terrain-v6-teesside-final.png` -- Final standard diagnostic screenshot: `/home/ben/ukmesh/meshcore-analytics/terrain-v6-evidence/terrain-diag2.png` -- Successful H4 screenshot: `/home/ben/ukmesh/meshcore-analytics/terrain-v6-evidence/h4-ts256-minabs-terr-e3-after.png` -- H4 measurements: `/home/ben/ukmesh/meshcore-analytics/terrain-v6-evidence/h4-ts256-minabs-terr-e3.json` -- H3 center/pan/zoom captures: `/home/ben/ukmesh/meshcore-analytics/terrain-v6-evidence/terrain-h3-center.png`, `terrain-h3-pan.png`, and `terrain-h3-zoom.png` - -All commits are local only; nothing was pushed. diff --git a/anubis/botPolicy.yaml b/anubis/botPolicy.yaml index 48688c6..dd1059d 100644 --- a/anubis/botPolicy.yaml +++ b/anubis/botPolicy.yaml @@ -21,6 +21,34 @@ bots: path_regex: ^/terrain-tiles/ action: ALLOW + # ── Link-preview bots: allow so shared links render embeds ──────────────── + # Discordbot/LinkedInBot UAs contain "Mozilla", which otherwise trips the + # generic-browser WEIGH rule (+10 -> challenge they cannot solve). + # These agents only fetch public pages for unfurling; UA-only ALLOW is fine. + # NOTE: AI-search carve-out (OAI-SearchBot etc.) intentionally not enabled; + # see FIX-PACK section 2.2 in ~/.review-notes/nemesh-seo-2026-10-11/ to enable. + - name: allow-link-preview-bots + user_agent_regex: >- + (?i)Discordbot|LinkedInBot|Slackbot|Twitterbot|TelegramBot|WhatsApp|facebookexternalhit|Facebot|Pinterestbot|redditbot + action: ALLOW + + # ── IndexNow key file: always allow ─────────────────────────────────────── + # Search engines fetch /assets/<32-hex>.txt from arbitrary IPs/UAs to verify + # site ownership for IndexNow submissions; those fetchers carry "Mozilla" + # in the UA and would otherwise be challenged (returning HTML instead of + # the key, which fails verification with a 422). Static file, exact pattern. + - name: allow-indexnow-key + path_regex: ^/(assets/)?[0-9a-f]{32}\.txt$ + action: ALLOW + + # ── Google tooling: GSC URL Inspection, Rich Results test, PageSpeed Insights ── + # These fetchers run from Google infra but not necessarily from the published + # googlebot IP ranges, so the UA+IP-gated googlebot allow rule can miss them. + - name: allow-google-tools + user_agent_regex: >- + (?i)Google-InspectionTool|Chrome-Lighthouse|Google-PageSpeedInsights|Google-Structured-Data-Testing-Tool|Google-Site-Verification + action: ALLOW + # ── Built-in deny rules for pathological bots ────────────────────────────── - import: (data)/bots/_deny-pathological.yaml - import: (data)/bots/aggressive-brazilian-scrapers.yaml diff --git a/backend/backfill-region-names.mjs b/backend/backfill-region-names.mjs new file mode 100644 index 0000000..cd1b812 --- /dev/null +++ b/backend/backfill-region-names.mjs @@ -0,0 +1,222 @@ +/** + * One-shot backfill: re-probe the REGION NAME for packets that already have + * transport_codes populated but region_scope NULL — i.e. history that predates + * a MESHCORE_REGION_NAMES extension. + * + * Companion to backfill-transport-codes.mjs: that script only processes rows + * where transport_codes IS NULL (all already filled), so extending the region + * name list needs this re-probe pass instead. + * + * Run from backend/: + * node backfill-region-names.mjs + * + * Env vars used (same as backend): + * DATABASE_URL, MESHCORE_CHANNEL_SECRETS, MESHCORE_REGION_NAMES + * + * Safe to re-run: only touches rows with region_scope IS NULL; decode is + * deterministic; window matches the original backfill (31 days). + */ + +import pg from 'pg'; +import { createRequire } from 'module'; +const require = createRequire(import.meta.url); + +const { + MeshCoreDecoder, + transportCodeMatchesRegion, +} = require('@michaelhart/meshcore-decoder'); + +const { Pool } = pg; + +// ── Config ────────────────────────────────────────────────────────────────── + +const DATABASE_URL = process.env.DATABASE_URL; +if (!DATABASE_URL) { + console.error('DATABASE_URL env var is required'); + process.exit(1); +} + +// This is the documented MeshCore public-channel material, not an application +// credential. Private/custom channel material must only enter through the +// environment and must never be committed. +const DEFAULT_PUBLIC_CHANNEL_KEY = '8b3387e9c5cdea6ac9e5edbaa115cd72'; +const CHANNEL_SECRETS = [DEFAULT_PUBLIC_CHANNEL_KEY]; +if (process.env.MESHCORE_CHANNEL_SECRETS) { + for (const entry of process.env.MESHCORE_CHANNEL_SECRETS.split(',').map((s) => s.trim()).filter(Boolean)) { + const colon = entry.indexOf(':'); + CHANNEL_SECRETS.push(colon > 0 ? entry.slice(colon + 1) : entry); + } +} + +const keyStore = MeshCoreDecoder.createKeyStore({ channelSecrets: CHANNEL_SECRETS }); + +const REGION_NAMES = (process.env.MESHCORE_REGION_NAMES ?? 'Europe,Global') + .split(',').map((s) => s.trim()).filter(Boolean) + .map((name) => (name.startsWith('#') ? name : `#${name}`)); + +console.log(`[backfill-names] region probe list: ${REGION_NAMES.join(', ')}`); + +// ── Decoder helpers (mirrors backfill-transport-codes.mjs) ────────────────── + +function hexToBytes(rawHex) { + const hex = rawHex.trim(); + if (!hex || hex.length % 2 !== 0) return null; + return Buffer.from(hex, 'hex'); +} + +function bytesToHex(buf) { + return buf.toString('hex').toUpperCase(); +} + +function parseTransportCodes(rawHex) { + const bytes = hexToBytes(rawHex); + if (!bytes || bytes.length < 2) return null; + + const routeType = bytes[0] & 0x03; + if (routeType !== 0 && routeType !== 3) return null; + + if (bytes.length < 5) return null; // 1 route byte + 4 transport code bytes + const tcHex = bytesToHex(bytes.subarray(1, 5)); + + // Decode with compat wrapper to get payload.raw for region probing + // Build compat raw hex: rewrite path length byte to the old 1-byte encoding + const encodedLengthByte = bytes[5]; + if (encodedLengthByte === undefined) return { tcHex, payloadRaw: null, payloadType: null }; + + const pathHashCount = encodedLengthByte & 0x3f; + const pathHashSize = (encodedLengthByte >> 6) + 1; + if (pathHashSize > 3) return { tcHex, payloadRaw: null, payloadType: null }; + + const pathByteLength = pathHashCount * pathHashSize; + if (pathByteLength > 63) return { tcHex, payloadRaw: null, payloadType: null }; + + // Build compat hex for the library decoder (replaces encoded length byte with plain byte count) + const compat = Buffer.from(bytes); + compat[5] = pathByteLength; + const compatHex = bytesToHex(compat); + + let decoded; + try { + decoded = MeshCoreDecoder.decode(compatHex, { keyStore }); + } catch { + return { tcHex, payloadRaw: null, payloadType: null }; + } + + return { + tcHex, + payloadRaw: decoded?.payload?.raw ?? null, + payloadType: decoded?.payloadType ?? null, + }; +} + +function probeRegion(tcHex, payloadType, payloadRaw) { + if (!tcHex || payloadType == null || !payloadRaw) return null; + const tcBuf = Buffer.from(tcHex, 'hex'); + if (tcBuf.length < 2) return null; + const tc0 = tcBuf[0] | (tcBuf[1] << 8); + for (const name of REGION_NAMES) { + if (transportCodeMatchesRegion(name, payloadType, payloadRaw, tc0)) { + return name; + } + } + return null; +} + +// ── Main ──────────────────────────────────────────────────────────────────── + +const pool = new Pool({ connectionString: DATABASE_URL }); + +async function run() { + const { rows } = await pool.query( + `SELECT DISTINCT ON (packet_hash) packet_hash, raw_hex + FROM packets + WHERE route_type IN (0, 3) + AND raw_hex IS NOT NULL + AND raw_hex != '' + AND transport_codes IS NOT NULL + AND region_scope IS NULL + AND time > NOW() - INTERVAL '31 days' + ORDER BY packet_hash, time DESC`, + ); + + console.log(`[backfill-names] ${rows.length} distinct packets to re-probe`); + if (rows.length === 0) { await pool.end(); return; } + + let processed = 0; + let matches = 0; + let noCode = 0; + let totalRowsUpdated = 0; + const byName = new Map(); + const BATCH = 200; + + for (let i = 0; i < rows.length; i += BATCH) { + const batch = rows.slice(i, i + BATCH); + const values = []; + + for (const row of batch) { + const result = parseTransportCodes(row.raw_hex); + if (!result) { noCode++; continue; } + + const region = probeRegion(result.tcHex, result.payloadType, result.payloadRaw); + if (!region) continue; + + matches++; + byName.set(region, (byName.get(region) ?? 0) + 1); + values.push({ hash: row.packet_hash, region }); + } + + if (values.length > 0) { + const hashes = values.map((v) => v.hash); + const regions = values.map((v) => v.region); + + const client = await pool.connect(); + try { + await client.query('SET timescaledb.max_tuples_decompressed_per_dml_transaction = 0'); + const r = await client.query( + `UPDATE packets AS p + SET region_scope = v.region + FROM unnest($1::text[], $2::text[]) AS v(hash, region) + WHERE p.packet_hash = v.hash + AND p.route_type IN (0, 3) + AND p.region_scope IS NULL`, + [hashes, regions], + ); + totalRowsUpdated += r.rowCount ?? 0; + } finally { + client.release(); + } + } + + processed += batch.length; + if (processed % 1000 === 0 || processed === rows.length) { + console.log(`[backfill-names] ${processed}/${rows.length} probed — ${matches} matches, ${totalRowsUpdated} rows updated`); + } + } + + console.log(`[backfill-names] done. ${matches} packets matched a region, ${noCode} undecodable.`); + if (byName.size > 0) { + console.log('[backfill-names] matches by name:'); + for (const [name, n] of [...byName.entries()].sort((a, b) => b[1] - a[1])) { + console.log(` ${name}: ${n} packets`); + } + } + + // Summary by region (31-day window, all rows) + const { rows: summary } = await pool.query( + `SELECT region_scope, COUNT(*) AS n + FROM packets + WHERE route_type IN (0, 3) + AND region_scope IS NOT NULL + AND time > NOW() - INTERVAL '31 days' + GROUP BY region_scope + ORDER BY n DESC`, + ); + if (summary.length > 0) { + console.log('[backfill-names] full region breakdown:'); + for (const r of summary) console.log(` ${r.region_scope}: ${r.n}`); + } + + await pool.end(); +} + +run().catch((err) => { console.error('[backfill-names] fatal:', err); process.exit(1); }); diff --git a/backend/package-lock.json b/backend/package-lock.json index f3e83c6..e6a04c8 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -561,10 +561,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.3.tgz", - "integrity": "sha512-dsqMQQoeTLqu9wynDD00q573mNzso3IdQOAfHRJqLCcmCFPoGo9A1bDpUcv/9tnKpErQWv9uKeGfl37EIS02Yg==", - "license": "MIT", + "version": "26.6.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.5.tgz", + "integrity": "sha512-uMzsv9stDdofIZBx4UGh4Z9ZgXnGC9seRDscilBcTHAeOFk67MZg/k9bWVfO+bk4qZmG+l52hRnLwUiH2LnJTg==", "dependencies": { "undici-types": "~8.9.0" } @@ -574,7 +573,6 @@ "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.23.1.tgz", "integrity": "sha512-fKVHpikPdg4GKks3JuLEhvwSyvwzF23hnabPy6DD8ljVbC7+6J5dQzdv4arV6jqq57djnMgs1HKBxX4P8aBI3A==", "dev": true, - "license": "MIT", "dependencies": { "@types/node": "*", "pg-protocol": "*", @@ -1272,7 +1270,6 @@ "version": "1.8.2", "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.2.tgz", "integrity": "sha512-o8vI5RE5A6EVVOd9o41jKp41aJom+QTEO/Bx8MYNjexMo/Bv2WOjUfZr+aL0WnYSgymUy6zeguqLTsIhV0gMvQ==", - "license": "MIT", "dependencies": { "bytes": "3.1.2", "compressible": "~2.0.18", @@ -1394,7 +1391,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" @@ -1575,10 +1571,9 @@ } }, "node_modules/express-rate-limit": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.7.0.tgz", - "integrity": "sha512-hOwV7WOxXfjRpAM1DSJWZDXx3GhplwD8IfwuwvogD8i1Qnkgosw/H45s4ZnFAUHDAhPjlY9hLBvJhKmGMyY26g==", - "license": "MIT", + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.7.1.tgz", + "integrity": "sha512-2FXbqkygil3mZiXzEtM0IjE/UBmBNot0J1GEVrJT4JbrvwOSoEtqtO+wHbiMQELWe1eNvHoYRg2dkjOid+6JkA==", "dependencies": { "debug": "^4.4.3", "ip-address": "^10.2.0" @@ -1924,9 +1919,9 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/ip-address": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.3.tgz", - "integrity": "sha512-A1kdq/tSb5QjvKvAMgIoEvDBIgL7qaqVP/jkvSwYYRZ9iEzvPpopxp2wQfu3SuZRHtpHNxMn8Fs0bS+gf5Xmwg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.4.0.tgz", + "integrity": "sha512-oSK96Grm3aP6OrS263xVxbNDGVL7rzBtYdpGqlDG8iQdoenDoTs/nkki+DflYbAEE8Xl6o5YxhxlrKvI3nqKXQ==", "engines": { "node": ">= 12" } @@ -2020,7 +2015,6 @@ "version": "5.16.0", "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-5.16.0.tgz", "integrity": "sha512-G0VsEtuJz5sJUgZJaaADMVfW7SIFPzkIuHR+d6rk+tBeSwdCfd9BaIRycRfd2AbyFDEmLlFM+SDQeti/+IbOnw==", - "license": "MIT", "dependencies": { "@types/readable-stream": "^4.0.21", "@types/ws": "^8.18.1", @@ -2339,25 +2333,22 @@ } }, "node_modules/proxy-addr": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", - "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" }, "engines": { "node": ">= 0.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" } }, "node_modules/qs": { - "version": "6.16.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", - "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "license": "BSD-3-Clause", "dependencies": { "es-define-property": "^1.0.1", "side-channel": "^1.1.1" @@ -2724,7 +2715,6 @@ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.15.tgz", "integrity": "sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==", "dev": true, - "license": "MIT", "dependencies": { "esbuild": "~0.28.0" }, @@ -2812,8 +2802,7 @@ "node_modules/undici-types": { "version": "8.9.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", - "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", - "license": "MIT" + "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==" }, "node_modules/unpipe": { "version": "1.0.0", @@ -2890,7 +2879,6 @@ "version": "8.22.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.22.0.tgz", "integrity": "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==", - "license": "MIT", "engines": { "node": ">=10.0.0" }, diff --git a/backend/package.json b/backend/package.json index 215279c..57127ae 100644 --- a/backend/package.json +++ b/backend/package.json @@ -8,6 +8,7 @@ "dev": "tsx watch src/index.ts", "spam:recompute": "node dist/tools/recomputeSpamSuspects.js", "spam:messages": "node dist/tools/recomputeSpamMessages.js", + "regions:rebuild": "node dist/tools/rebuildRegionParticipation.js", "db:migrate": "node dist/tools/migrate.js", "stats:backfill-rollups": "node dist/tools/backfillStatsRollups.js", "stats:backfill-multibyte": "node dist/tools/backfillMultibytePathFacts.js", @@ -16,8 +17,11 @@ "owner-auth:inventory": "node dist/tools/inventoryOwnerAuthorization.js", "owner-auth:revoke": "node dist/tools/revokeOwnerSessions.js", "owner-packet-share:encrypt-secret": "node dist/tools/encryptPacketShareSecret.js", + "owner-packet-share:prepare-scotmesh": "node dist/tools/prepareScotmesh.js", + "owner-packet-share:provision-scotmesh": "node dist/tools/provisionScotmesh.js", "analysis:cleanup-orphans": "node dist/tools/cleanupOrphanedAnalysisRuns.js", "bench:decode": "tsx src/tools/benchmarkPacketDecode.ts", + "path:accuracy": "tsx src/tools/pathAccuracy.ts", "load:realtime": "tsx src/tools/loadRealtime.ts", "test": "node --import tsx --test $(find src -name '*.test.ts' ! -name '*.integration.test.ts' -print) && bash ../scripts/test-alert-receiver.sh", "test:analysis-integration": "node --import tsx --test src/analysis/runState.integration.test.ts", diff --git a/backend/src/api/contracts.test.ts b/backend/src/api/contracts.test.ts index cfc1d18..b7e5a8b 100644 --- a/backend/src/api/contracts.test.ts +++ b/backend/src/api/contracts.test.ts @@ -57,22 +57,3 @@ test('slow path contracts cover status, mode query, and pending response', () => true, ); }); - -test('owner packet-sharing contracts cover bounded owner settings and destination metadata', () => { - const get = API_CONTRACTS.find((entry) => entry.method === 'GET' && entry.path === '/owner/packet-sharing'); - assert.equal(get?.access, 'owner'); - assert.deepEqual(get?.queryParameters?.[0]?.['schema'], { - type: 'string', - pattern: '^[0-9A-Fa-f]{64}$', - }); - assert.deepEqual((get?.responseSchema?.['required'] as string[] | undefined)?.sort(), [ - 'destinations', 'enabled', 'featureEnabled', 'nodeId', - ]); - - const post = API_CONTRACTS.find((entry) => entry.method === 'POST' && entry.path === '/owner/packet-sharing'); - assert.equal(post?.access, 'owner'); - assert.deepEqual((post?.requestSchema?.['required'] as string[] | undefined)?.sort(), [ - 'destinationIds', 'enabled', 'nodeId', - ]); - assert.equal(post?.additionalResponses?.['409']?.responseSchema['properties'] != null, true); -}); diff --git a/backend/src/api/contracts.ts b/backend/src/api/contracts.ts index 4d5c76f..25bea4d 100644 --- a/backend/src/api/contracts.ts +++ b/backend/src/api/contracts.ts @@ -119,6 +119,138 @@ const OWNER_PACKET_SHARING_SCHEMA = { additionalProperties: false, }; +const OWNER_PUBLIC_FEED_SCHEMA = { + type: 'object', + required: [ + 'featureEnabled', + 'configured', + 'brokerUrl', + 'enabled', + 'nodeCount', + 'eligible', + 'recentPacketCount', + 'topicFilters', + 'lastForwardedAt', + ], + properties: { + featureEnabled: { type: 'boolean' }, + configured: { type: 'boolean' }, + brokerUrl: { type: ['string', 'null'], format: 'uri', maxLength: 2_048 }, + enabled: { type: 'boolean' }, + nodeCount: { type: 'integer', minimum: 0 }, + eligible: { type: 'boolean' }, + recentPacketCount: { type: 'integer', minimum: 0 }, + topicFilters: { + type: 'array', + maxItems: 1, + items: { type: 'string', pattern: '^public/\\+/\\+/packets$' }, + }, + lastForwardedAt: { type: ['string', 'null'], format: 'date-time' }, + }, + additionalProperties: false, +}; + +const REGION_MEMBER_SCHEMA = { + type: 'object', + required: ['nodeId', 'name', 'role', 'lat', 'lon', 'packets', 'isBridge'], + properties: { + nodeId: { type: 'string', pattern: '^[0-9A-F]{64}$' }, + name: { type: ['string', 'null'] }, + role: { type: ['integer', 'null'] }, + lat: { type: ['number', 'null'], minimum: -90, maximum: 90 }, + lon: { type: ['number', 'null'], minimum: -180, maximum: 180 }, + packets: { type: 'integer', minimum: 0 }, + isBridge: { type: 'boolean' }, + }, + additionalProperties: false, +}; + +const REGION_SUMMARY_SCHEMA = { + type: 'object', + required: ['generatedAt', 'windowDays', 'regions'], + properties: { + generatedAt: { type: ['string', 'null'], format: 'date-time' }, + windowDays: { type: ['integer', 'null'], minimum: 1, maximum: 365 }, + regions: { + type: 'array', + items: { + type: 'object', + required: ['name', 'packetsTotal', 'memberCount', 'repeaterMemberCount', 'confidence', 'members'], + properties: { + name: { type: 'string', minLength: 1, maxLength: 64 }, + packetsTotal: { type: 'integer', minimum: 0 }, + memberCount: { type: 'integer', minimum: 0 }, + repeaterMemberCount: { type: 'integer', minimum: 0 }, + confidence: { type: 'string', enum: ['confirmed', 'probable', 'single'] }, + firstSeen: { type: ['string', 'null'], format: 'date-time' }, + lastSeen: { type: ['string', 'null'], format: 'date-time' }, + members: { type: 'array', items: REGION_MEMBER_SCHEMA }, + }, + additionalProperties: false, + }, + }, + }, + additionalProperties: false, +}; + +const REGION_REPEATERS_SCHEMA = { + type: 'object', + required: ['generatedAt', 'repeaters'], + properties: { + generatedAt: { type: ['string', 'null'], format: 'date-time' }, + repeaters: { + type: 'array', + items: { + type: 'object', + required: ['nodeId', 'status', 'evidence', 'scopedPackets', 'unscopedPackets7d', 'regions'], + properties: { + nodeId: { type: 'string', pattern: '^[0-9A-F]{64}$' }, + name: { type: ['string', 'null'] }, + role: { type: ['integer', 'null'] }, + lat: { type: ['number', 'null'], minimum: -90, maximum: 90 }, + lon: { type: ['number', 'null'], minimum: -180, maximum: 180 }, + status: { type: 'string', enum: ['green', 'red'] }, + evidence: { type: 'string' }, + scopedPackets: { type: 'integer', minimum: 0 }, + unscopedPackets7d: { type: 'integer', minimum: 0 }, + regions: { type: 'object', additionalProperties: { type: 'integer', minimum: 0 } }, + }, + additionalProperties: false, + }, + }, + }, + additionalProperties: false, +}; + +const REGION_SCOPE_SCHEMA = { + type: 'object', + required: ['nodeId', 'status', 'evidence', 'scopedPackets', 'unscopedPackets7d', 'regions'], + properties: { + nodeId: { type: 'string', pattern: '^[0-9A-F]{64}$' }, + name: { type: ['string', 'null'] }, + role: { type: ['integer', 'null'] }, + lat: { type: ['number', 'null'], minimum: -90, maximum: 90 }, + lon: { type: ['number', 'null'], minimum: -180, maximum: 180 }, + status: { type: 'string', enum: ['green', 'red', 'unobserved'] }, + evidence: { type: 'string' }, + scopedPackets: { type: 'integer', minimum: 0 }, + unscopedPackets7d: { type: 'integer', minimum: 0 }, + regions: { + type: 'array', + items: { + type: 'object', + required: ['region', 'packets'], + properties: { + region: { type: 'string', minLength: 1, maxLength: 64 }, + packets: { type: 'integer', minimum: 0 }, + }, + additionalProperties: false, + }, + }, + }, + additionalProperties: false, +}; + function humanize(path: string): string { return path .replace(/^\/v1\//, '') @@ -172,6 +304,9 @@ const PUBLIC_GET = [ '/planned-nodes', '/radio-history', '/radio-stats', + '/regions/repeaters', + '/regions/summary', + '/repeaters/:id/scope', '/repeaters/firmware', '/rf-validation', '/runtime-config', @@ -183,6 +318,7 @@ const PUBLIC_GET = [ '/spam/suspects', '/stats', '/stats/charts', + '/tags/recent', '/topology', '/v1', '/v1/exports/nodes.:format', @@ -197,6 +333,7 @@ const OWNER_GET = [ '/owner/live', '/owner/live-last-hop', '/owner/packet-sharing', + '/owner/public-feed', '/owner/session', ] as const; @@ -211,6 +348,7 @@ export const API_CONTRACTS: readonly ApiContract[] = [ '/owner/login', '/owner/logout', '/owner/packet-sharing', + '/owner/public-feed', ], 'owner'), ...contracts('DELETE', ['/coverage/planned/:planId'], 'public'), ...contracts('DELETE', ['/owner/alert-rules/:id'], 'owner'), @@ -239,6 +377,33 @@ export const API_CONTRACTS: readonly ApiContract[] = [ }, }; } + if (contract.path === '/regions/summary') { + return { + ...contract, + summary: 'Read per-region scoped-traffic participation summary', + responseSchema: REGION_SUMMARY_SCHEMA, + }; + } + if (contract.path === '/regions/repeaters') { + return { + ...contract, + summary: 'List repeaters classified by region-capability', + responseSchema: REGION_REPEATERS_SCHEMA, + }; + } + if (contract.path === '/repeaters/:id/scope') { + return { + ...contract, + summary: 'Read one repeater\'s region scoping detail', + responseSchema: REGION_SCOPE_SCHEMA, + additionalResponses: { + '404': { + description: 'The repeater is not known to the region-scope tables', + responseSchema: ERROR_SCHEMA, + }, + }, + }; + } if (contract.path === '/planned-nodes') { return { ...contract, summary: 'List explicitly published planned nodes', responseSchema: PLANNED_NODE_PAGE_SCHEMA }; } @@ -294,11 +459,6 @@ export const API_CONTRACTS: readonly ApiContract[] = [ }; } if (contract.path === '/owner/packet-sharing' && contract.method === 'POST') { - const destinationIdsSchema = { - type: 'array', - maxItems: 32, - items: { type: 'string', pattern: '^[a-z0-9][a-z0-9_-]{0,63}$' }, - }; return { ...contract, summary: 'Update owner packet-sharing settings', @@ -308,7 +468,11 @@ export const API_CONTRACTS: readonly ApiContract[] = [ properties: { nodeId: { type: 'string', pattern: '^[0-9A-Fa-f]{64}$' }, enabled: { type: 'boolean' }, - destinationIds: destinationIdsSchema, + destinationIds: { + type: 'array', + maxItems: 32, + items: { type: 'string', pattern: '^[a-z0-9][a-z0-9_-]{0,63}$' }, + }, }, additionalProperties: false, }, @@ -318,7 +482,11 @@ export const API_CONTRACTS: readonly ApiContract[] = [ properties: { nodeId: { type: 'string', pattern: '^[0-9A-Fa-f]{64}$' }, enabled: { type: 'boolean' }, - destinationIds: destinationIdsSchema, + destinationIds: { + type: 'array', + maxItems: 32, + items: { type: 'string', pattern: '^[a-z0-9][a-z0-9_-]{0,63}$' }, + }, }, additionalProperties: false, }, @@ -330,6 +498,35 @@ export const API_CONTRACTS: readonly ApiContract[] = [ }, }; } + if (contract.path === '/owner/public-feed') { + return { + ...contract, + summary: contract.method === 'GET' + ? 'Read owner public-feed settings' + : 'Update owner public-feed settings', + ...(contract.method === 'GET' + ? { responseSchema: OWNER_PUBLIC_FEED_SCHEMA } + : { + requestSchema: { + type: 'object', + required: ['enabled'], + properties: { enabled: { type: 'boolean' } }, + additionalProperties: false, + }, + responseSchema: OWNER_PUBLIC_FEED_SCHEMA, + additionalResponses: { + '409': { + description: 'The public feed is not configured or the owner has no eligible packet activity', + responseSchema: ERROR_SCHEMA, + }, + '503': { + description: 'The broker ACL could not be updated', + responseSchema: ERROR_SCHEMA, + }, + }, + }), + }; + } return contract; }); diff --git a/backend/src/api/routes.ts b/backend/src/api/routes.ts index a0ffa76..2747a55 100644 --- a/backend/src/api/routes.ts +++ b/backend/src/api/routes.ts @@ -25,6 +25,7 @@ import { getPacketDetail, getPublicVisibilityGeneration, getChannelMessageHistory, + getRecentMessageTags, getRecentPacketEvents, getRecentPackets, analyticsQuery, @@ -63,6 +64,7 @@ import radioRoutes from './routes/radio.js'; import { registerCoverageRoutes } from './routes/coverage.js'; import { registerPlannedCoverageRoutes } from './routes/plannedCoverage.js'; import { registerOwnerRoutes } from './routes/owner.js'; +import { reconcileOwnerAuthorization } from '../owner/ownerAclReconciler.js'; import { registerPathingRoutes } from './routes/pathing.js'; import { registerStatsRoutes } from './routes/stats.js'; import { registerTelemetryRoutes } from './routes/telemetry.js'; @@ -70,6 +72,7 @@ import { createNodeRepository } from '../repositories/nodes.js'; import { createPlannedCoverageRepository } from '../repositories/plannedCoverage.js'; import { registerSpamRoutes } from './routes/spam.js'; import { registerTopologyRoutes } from './routes/topology.js'; +import { registerRegionsRoutes } from './routes/regions.js'; import { registerActivityTimelineRoutes } from './routes/activityTimeline.js'; import { registerRfValidationRoutes } from './routes/rfValidation.js'; import { registerExportRoutes } from './routes/exports.js'; @@ -86,7 +89,6 @@ import { assertUniqueRouteRegistry } from './routeRegistry.js'; import { assertContractCoverage } from './contracts.js'; import { ApiInputError, wrapAsyncHandlers } from './errors.js'; import { withHeavyWorkAdmission } from '../analysis/heavyWorkAdmission.js'; -import { getOwnerAuthorizationSnapshot } from '../db/ownerAuth.js'; const router = Router(); // Anonymous cross-network aggregation is not a public API capability. Operator @@ -140,8 +142,9 @@ async function requireOwnerSession(req: Request, res: Response): Promise (await getOwnerAuthorizationSnapshot()) - .filter((owner) => owner.isActive && owner.nodeIds.length > 0), }); - -export const startOwnerLastHopPrewarm = () => ownerLastHopPrewarm.start(); -export const stopOwnerLastHopPrewarm = () => ownerLastHopPrewarm.stop(); registerPathingRoutes(router, { pathBetaLimiter: PATH_BETA_LIMITER, pathLearningLimiter: PATH_LEARNING_LIMITER, @@ -257,6 +257,11 @@ registerTopologyRoutes(router, { getPublicVisibilityGeneration, limiter: EXPENSIVE_LIMITER, }); +registerRegionsRoutes(router, { + query, + networkFilters, + limiter: EXPENSIVE_LIMITER, +}); registerActivityTimelineRoutes(router, { query, networkFilters, diff --git a/backend/src/api/routes/auditwave.integration.test.ts b/backend/src/api/routes/auditwave.integration.test.ts new file mode 100644 index 0000000..4eda65f --- /dev/null +++ b/backend/src/api/routes/auditwave.integration.test.ts @@ -0,0 +1,180 @@ +import assert from 'node:assert/strict'; +import { execFile } from 'node:child_process'; +import { once } from 'node:events'; +import type { AddressInfo } from 'node:net'; +import test, { type TestContext } from 'node:test'; +import { promisify } from 'node:util'; +import pg, { type QueryResultRow } from 'pg'; +import express, { Router, type RequestHandler } from 'express'; +import { getNodeHistory } from '../../db/index.js'; +import { rfValidationRows } from '../../repositories/networkAnalysis.js'; +import { networkFilters } from '../utils/networkFilters.js'; +import { registerExportRoutes } from './exports.js'; + +// Real PostgreSQL evaluation of application SQL, with every application +// relation shadowed by a CTE. SELECT only; no fixture tables or DB writes. +const sqlTest = process.env['AUDITWAVE_SQL_TESTS'] === '1' ? test : test.skip; +const execFileAsync = promisify(execFile); +const key = (c: string) => c.repeat(64); +const A = key('A'), B = key('B'), C = key('C'), D = key('D'), F = key('F'); +function literal(value: unknown): string { + if (Array.isArray(value)) return `ARRAY[${value.map(literal).join(',')}]::text[]`; + if (typeof value === 'number') { assert.ok(Number.isFinite(value)); return String(value); } + return `'${String(value).replace(/'/g, "''")}'`; +} +function node(node_id: string, overrides: Record = {}) { + return { node_id, name: `Repeater ${node_id[0]}`, role: 2, network: 'ukmesh', + lat: 51, lon: -1, iata: 'LHR', last_seen: '2026-09-07T12:00:00Z', hardware_model: null, ...overrides }; +} +function packet(overrides: Record = {}) { + return { age: 3600, packet_hash: 'repeated', rx_node_id: A, src_node_id: B, + network: 'ukmesh', topic: 'ukmesh/LHR/observer/packets', topic_prefix: 'ukmesh', + visibility_ok: true, is_private: false, packet_type: 4, hop_count: 2, + rssi: null, snr: null, payload: { direction: 'rx' }, ...overrides }; +} +function link(node_a_id: string, node_b_id: string, overrides: Record = {}) { + return { node_a_id, node_b_id, age: 3600, observed_count: 1, + multibyte_observed_count: 0, itm_path_loss_db: 120, itm_viable: true, force_viable: false, ...overrides }; +} +type Fixture = { + packets?: ReturnType[]; + links?: ReturnType[]; + nodes?: ReturnType[]; + stalePrivacy?: boolean; + sighted?: boolean; +}; +function fixtureQuery(fixture: Fixture) { + return async (sql: string, params: unknown[] = []): Promise<{ rows: T[] }> => { + const bound = sql.replace(/\$(\d+)\b/g, (_p, index: string) => literal(params[Number(index) - 1])); + const fixtureSql = `WITH + packets AS ( + SELECT p.*, NOW() - age * INTERVAL '1 second' AS time + FROM jsonb_to_recordset(${literal(JSON.stringify(fixture.packets ?? []))}::jsonb) AS p( + age double precision, packet_hash text, rx_node_id text, src_node_id text, + network text, topic text, topic_prefix text, visibility_ok boolean, is_private boolean, + packet_type integer, hop_count integer, rssi double precision, snr double precision, payload jsonb) + ), node_identity_aliases AS ( + SELECT * FROM (VALUES (${literal(F)},${literal(A)})) a(source_node_id,canonical_node_id) + ), node_identity_nodes AS ( + SELECT * FROM jsonb_to_recordset(${literal(JSON.stringify(fixture.nodes ?? [node(A), node(B), node(C), node(D)]))}::jsonb) + AS n(node_id text,name text,role integer,network text,lat double precision,lon double precision, + iata text,last_seen timestamptz,hardware_model text) + ), node_identity_sightings AS ( + SELECT ${literal(B)} AS node_id,'ukmesh'::text AS network,NOW() AS last_seen_at + WHERE ${fixture.sighted ? 'TRUE' : 'FALSE'} + ), node_links AS ( + SELECT l.*, NOW() - age * INTERVAL '1 second' AS last_observed + FROM jsonb_to_recordset(${literal(JSON.stringify(fixture.links ?? []))}::jsonb) AS l( + node_a_id text,node_b_id text,age double precision,observed_count integer, + multibyte_observed_count integer,itm_path_loss_db double precision,itm_viable boolean,force_viable boolean) + ), packet_visibility_materialization_state AS ( + SELECT TRUE AS singleton,1 AS visibility_generation + ), public_visibility_state AS ( + SELECT TRUE AS singleton,${fixture.stalePrivacy ? 2 : 1} AS generation + ) SELECT COALESCE(jsonb_agg(to_jsonb(result)),'[]'::jsonb) FROM (${bound}) result;`; + const child = execFileAsync('docker', ['exec', '-i', '-e', + 'PGOPTIONS=-c default_transaction_read_only=on -c statement_timeout=4500', + 'meshcore-infra-timescaledb-1', 'psql', '-X', '-U', 'meshcore', '-d', 'meshcore', + '-v', 'ON_ERROR_STOP=1', '-At'], { timeout: 6_000, maxBuffer: 4 * 1024 * 1024 }); + child.child.stdin!.end(fixtureSql); + return { rows: JSON.parse((await child).stdout) as T[] }; + }; +} +async function history(t: TestContext, fixture: Fixture, id = A, network = 'ukmesh') { + const mocked = t.mock.method(pg.Pool.prototype, 'query', fixtureQuery(fixture)); + try { return await getNodeHistory(id, 24, network); } + finally { mocked.mock.restore(); } +} + +sqlTest('history selects RX and origin aliases once per observation, retains repeated hashes and canonicalizes source ids', async (t) => { + const rows = await history(t, { packets: [ + packet({ packet_hash: 'rx' }), packet({ packet_hash: 'src', rx_node_id: B, src_node_id: A }), + packet({ packet_hash: 'alias-rx', rx_node_id: F }), + packet({ packet_hash: 'alias-src', rx_node_id: B, src_node_id: F }), + packet({ packet_hash: 'self', rx_node_id: F, src_node_id: A }), + packet({ packet_hash: 'same' }), packet({ packet_hash: 'same', age: 3700 }), + packet({ packet_hash: 'unrelated', rx_node_id: B, src_node_id: C }), + ] }, ` ${F.toLowerCase()} `); + assert.equal(rows.length, 7); + assert.equal(rows.filter(r => r.packet_hash === 'self').length, 1); + assert.equal(rows.filter(r => r.packet_hash === 'same').length, 2); + assert.equal(rows.find(r => r.packet_hash === 'alias-src')!.src_node_id, A); + assert.deepEqual(Object.keys(rows[0]!).sort(), ['time','packet_hash','src_node_id','topic','packet_type','hop_count','rssi','snr','payload'].sort()); +}); + +sqlTest('history enforces the exact time window, public network compatibility and privacy for either direction', async (t) => { + for (const direction of ['rx', 'src']) { + const row = (change: Record) => packet({ ...(direction === 'src' ? { src_node_id: F, rx_node_id: B } : {}), ...change }); + const packets = [row({}), row({ network: 'northeast' }), row({ network: 'teesside' }), row({ age: 0 }), + row({ age: 86400 }), row({ age: -1 }), row({ network: 'test' }), row({ network: 'other' }), + row({ topic_prefix: 'meshcore-test' }), row({ topic_prefix: null, topic: 'meshcore-test/LHR/x/packets' }), + row({ visibility_ok: false }), row({ visibility_ok: null }), row({ is_private: true })]; + assert.equal((await history(t, { packets })).length, 4); + assert.deepEqual(await history(t, { packets, stalePrivacy: true }), []); + } + assert.equal((await history(t, { packets: [packet({ network: 'test', topic_prefix: 'meshcore-test' })] }, A, 'test')).length, 1); +}); + +sqlTest('history keeps the newest 500 observations and returns an honest empty set', async (t) => { + const rows = await history(t, { packets: Array.from({ length: 510 }, (_, i) => packet({ age: i, packet_hash: String(i) })) }); + assert.equal(rows.length, 500); + assert.equal(rows[0]!.packet_hash, '0'); + assert.equal(rows[499]!.packet_hash, '499'); + assert.deepEqual(await history(t, { packets: [packet()] }, key('0')), []); + assert.deepEqual(await history(t, { packets: [] }), []); +}); + +sqlTest('RF query combines aliases and preserves mismatch, override and stale weak-evidence classifications', async () => { + const { rows } = await rfValidationRows(fixtureQuery({ links: [ + link(A, B, { observed_count: 5, multibyte_observed_count: 1, itm_viable: false, itm_path_loss_db: 150 }), + link(B, F, { observed_count: 7, multibyte_observed_count: 2, itm_viable: false, itm_path_loss_db: 140 }), + link(A, C, { force_viable: true }), + link(B, C, { age: 8 * 86400, observed_count: 2 }), + link(A, D), + link(C, D, { age: 30 * 86400 }), + link(A, F, { observed_count: 1000, itm_viable: false }), + ] }), networkFilters('ukmesh'), 100); + assert.equal(rows.length, 4); + assert.deepEqual(rows.map(r => r.classification), ['observed_unexpected','operator_override','weak_model_evidence','match']); + assert.equal(rows[0]!.observed_count, 12); + assert.equal(rows[0]!.multibyte_observed_count, 3); + assert.equal(rows[0]!.itm_path_loss_db, 140); + assert.equal(rows[0]!.node_a_id, A); + assert.equal(rows[0]!.node_b_id, B); +}); + +sqlTest('RF scopes both endpoints before its limit, allowing recent sightings but excluding private, test and non-repeater nodes', async () => { + const fixture: Fixture = { nodes: [node(A), node(B, { network: 'other' }), node(C, { name: '🚫 Private' }), node(D, { role: 1 })], + links: [link(A, C, { observed_count: 99999, itm_viable: false }), link(A, D, { observed_count: 99999 }), link(A, B)], sighted: true }; + const { rows } = await rfValidationRows(fixtureQuery(fixture), networkFilters('ukmesh'), 1); + assert.equal(rows.length, 1); + assert.equal(rows[0]!.node_b_id, B); + assert.deepEqual((await rfValidationRows(fixtureQuery({ ...fixture, sighted: false }), networkFilters('ukmesh'), 1)).rows, []); + assert.deepEqual((await rfValidationRows(fixtureQuery({ ...fixture, nodes: [node(A), node(B, { network: 'test' })] }), networkFilters('ukmesh'), 1)).rows, []); +}); + +sqlTest('exports execute real SQL with the correct alias and omit private, unpositioned and out-of-scope nodes', async () => { + const app = express(), router = Router(); + const limiter: RequestHandler = (_req, _res, next) => next(); + registerExportRoutes(router, { networkFilters, + exportLimiter: limiter as Parameters[1]['exportLimiter'], + query: fixtureQuery({ nodes: [node(A), node(B, { network: 'other' }), node(C, { name: '🚫 Private' }), + node(D, { network: 'test' }), node(F, { lat: null })], sighted: true }), + }); + app.use(router); + const server = app.listen(0, '127.0.0.1'); + await once(server, 'listening'); + try { + const base = `http://127.0.0.1:${(server.address() as AddressInfo).port}`; + for (const format of ['csv', 'geojson']) { + const response = await fetch(`${base}/v1/exports/nodes.${format}?network=ukmesh&limit=3`); + assert.equal(response.status, 200); + const body = await response.text(); + assert.ok(body.includes(A) && body.includes(B)); + for (const id of [C, D, F]) assert.ok(!body.includes(id)); + } + } finally { + server.closeAllConnections(); + await new Promise((resolve) => server.close(() => resolve())); + } +}); diff --git a/backend/src/api/routes/auditwave.performance.test.ts b/backend/src/api/routes/auditwave.performance.test.ts new file mode 100644 index 0000000..b2ada5b --- /dev/null +++ b/backend/src/api/routes/auditwave.performance.test.ts @@ -0,0 +1,93 @@ +import assert from 'node:assert/strict'; +import { execFile } from 'node:child_process'; +import { mkdir, writeFile } from 'node:fs/promises'; +import { once } from 'node:events'; +import type { AddressInfo } from 'node:net'; +import test from 'node:test'; +import { promisify } from 'node:util'; +import pg, { type QueryResultRow } from 'pg'; +import express, { Router, type RequestHandler } from 'express'; +import { getNodeHistory } from '../../db/index.js'; +import { rfValidationRows } from '../../repositories/networkAnalysis.js'; +import { networkFilters } from '../utils/networkFilters.js'; +import { registerExportRoutes } from './exports.js'; + +const perfTest = process.env['AUDITWAVE_PERF_TESTS'] === '1' ? test : test.skip; +const execFileAsync = promisify(execFile); +const evidence = '../.fix-backups-auditwave-20260907/evidence'; +const summaries: unknown[] = []; +function literal(value: unknown): string { + if (Array.isArray(value)) return `ARRAY[${value.map(literal).join(',')}]::text[]`; + if (typeof value === 'number') { assert.ok(Number.isFinite(value)); return String(value); } + return `'${String(value).replace(/'/g, "''")}'`; +} +async function select(sql: string) { + const child = execFileAsync('docker', ['exec', '-i', '-e', + 'PGOPTIONS=-c default_transaction_read_only=on -c statement_timeout=4500', + 'meshcore-infra-timescaledb-1', 'psql', '-X', '-U', 'meshcore', '-d', 'meshcore', + '-v', 'ON_ERROR_STOP=1', '-At'], { timeout: 6_000, maxBuffer: 12 * 1024 * 1024 }); + child.child.stdin!.end(sql); + return JSON.parse((await child).stdout); +} +function measuredQuery(label: string) { + let calls = 0; + return async (sql: string, params: unknown[] = []): Promise<{ rows: T[] }> => { + const name = `${label}-${++calls}`; + const bound = sql.replace(/\$(\d+)\b/g, (_p, index: string) => literal(params[Number(index) - 1])); + await mkdir(evidence, { recursive: true }); + await writeFile(`${evidence}/${name}.sql`, bound); + const plan = await select(`EXPLAIN (ANALYZE, BUFFERS, SETTINGS, FORMAT JSON) ${bound}`); + await writeFile(`${evidence}/${name}-explain.json`, JSON.stringify(plan, null, 2)); + const rows = await select(`SELECT COALESCE(jsonb_agg(to_jsonb(result)),'[]'::jsonb) FROM (${bound}) result`); + const summary = { name, rows: rows.length, planningMs: plan[0]['Planning Time'], executionMs: plan[0]['Execution Time'] }; + summaries.push(summary); + await writeFile(`${evidence}/performance-summary.json`, JSON.stringify(summaries, null, 2)); + console.log(JSON.stringify(summary)); + assert.ok(plan[0]['Execution Time'] < 4500); + return { rows }; + }; +} + +perfTest('EXPLAIN actual RF repository queries on the live read-only database', { timeout: 20_000 }, async () => { + const { rows } = await rfValidationRows(measuredQuery('rf'), networkFilters('ukmesh'), 250); + assert.equal(rows.length, 250); + assert.ok(rows.some(row => row.classification === 'observed_unexpected')); +}); + +perfTest('EXPLAIN actual history queries for Lyneham, Gadget and an empty id, including maximum window', { timeout: 40_000 }, async (t) => { + const gadgetRows = await select("SELECT jsonb_agg(n) FROM (SELECT node_id,name FROM node_identity_nodes WHERE name ILIKE 'Gadget-RPT%') n"); + assert.ok(gadgetRows.length > 0); + for (const [name, id, hours] of [ + ['lyneham', 'C42E3FE2D99234E0E41967C1B70E49FA6F955AD5B8448A06EC2978AB0FF9E79E', 24], + ['gadget', gadgetRows[0].node_id, 24], + ['empty', '0'.repeat(64), 24], + ['lyneham-max-window', 'C42E3FE2D99234E0E41967C1B70E49FA6F955AD5B8448A06EC2978AB0FF9E79E', 672], + ] as const) { + const mocked = t.mock.method(pg.Pool.prototype, 'query', measuredQuery(`history-${name}`)); + try { + const rows = await getNodeHistory(id, hours, 'ukmesh'); + assert.ok(rows.length <= 500); + if (name === 'empty') assert.equal(rows.length, 0); + else assert.ok(rows.length > 0); + } finally { mocked.mock.restore(); } + } +}); + +perfTest('EXPLAIN actual node export SQL including its 5000-node maximum', { timeout: 15_000 }, async () => { + const app = express(), router = Router(); + const limiter: RequestHandler = (_req, _res, next) => next(); + registerExportRoutes(router, { networkFilters, + exportLimiter: limiter as Parameters[1]['exportLimiter'], query: measuredQuery('exports') }); + app.use(router); + const server = app.listen(0, '127.0.0.1'); + await once(server, 'listening'); + try { + const response = await fetch(`http://127.0.0.1:${(server.address() as AddressInfo).port}/v1/exports/nodes.geojson?network=ukmesh&limit=5000`); + assert.equal(response.status, 200); + const body = await response.json() as { features: unknown[] }; + assert.ok(body.features.length > 0 && body.features.length <= 5000); + } finally { + server.closeAllConnections(); + await new Promise((resolve) => server.close(() => resolve())); + } +}); diff --git a/backend/src/api/routes/exports.test.ts b/backend/src/api/routes/exports.test.ts index cc62518..91aeb45 100644 --- a/backend/src/api/routes/exports.test.ts +++ b/backend/src/api/routes/exports.test.ts @@ -1,6 +1,10 @@ import assert from 'node:assert/strict'; import test from 'node:test'; -import { csvCell } from './exports.js'; +import { once } from 'node:events'; +import type { AddressInfo } from 'node:net'; +import express, { Router, type RequestHandler } from 'express'; +import { networkFilters } from '../utils/networkFilters.js'; +import { csvCell, registerExportRoutes } from './exports.js'; test('csvCell escapes delimiters, quotes, and newlines', () => { assert.equal(csvCell('simple'), 'simple'); @@ -9,3 +13,48 @@ test('csvCell escapes delimiters, quotes, and newlines', () => { assert.equal(csvCell('line one\nline two'), '"line one\nline two"'); assert.equal(csvCell(null), ''); }); + +test('node exports use the sightings alias and retain CSV/GeoJSON contracts', async () => { + const app = express(); + const router = Router(); + const row = { + node_id: 'A'.repeat(64), name: 'Repeater, "Alpha"', lat: 51.5, lon: -1.2, + role: 2, iata: 'LHR', last_seen: '2026-09-07T12:00:00Z', hardware_model: null, + }; + const limiter: RequestHandler = (_req, _res, next) => next(); + registerExportRoutes(router, { + networkFilters, + exportLimiter: limiter as Parameters[1]['exportLimiter'], + query: async (sql, params) => { + assert.match(sql, /FROM node_identity_nodes AS nodes/); + assert.match(sql, /s.node_id = nodes.node_id/); + assert.match(sql, /name NOT LIKE '%🚫%'/); + assert.match(sql, /lat IS NOT NULL AND lon IS NOT NULL/); + assert.match(sql, /LIMIT \$2/); + assert.deepEqual(params, [['ukmesh', 'northeast', 'teesside'], 3]); + return { rows: [row] as never[] }; + }, + }); + app.use('/api', router); + const server = app.listen(0, '127.0.0.1'); + await once(server, 'listening'); + try { + const base = `http://127.0.0.1:${(server.address() as AddressInfo).port}`; + const csv = await fetch(`${base}/api/v1/exports/nodes.csv?network=ukmesh&limit=3`); + assert.equal(csv.status, 200); + assert.match(csv.headers.get('content-type')!, /text\/csv/); + assert.equal(csv.headers.get('x-api-version'), '1'); + assert.equal(await csv.text(), `node_id,name,lat,lon,role,iata,last_seen,hardware_model\n${row.node_id},"Repeater, ""Alpha""",51.5,-1.2,2,LHR,2026-09-07T12:00:00Z,\n`); + const geo = await fetch(`${base}/api/v1/exports/nodes.geojson?network=ukmesh&limit=3`); + assert.equal(geo.status, 200); + assert.match(geo.headers.get('content-type')!, /application\/geo\+json/); + const body = await geo.json() as { type: string; features: Array<{ geometry: unknown; properties: { node_id: string } }> }; + assert.equal(body.type, 'FeatureCollection'); + assert.equal(body.features.length, 1); + assert.deepEqual(body.features[0]!.geometry, { type: 'Point', coordinates: [-1.2, 51.5] }); + assert.equal(body.features[0]!.properties.node_id, row.node_id); + } finally { + server.closeAllConnections(); + await new Promise((resolve) => server.close(() => resolve())); + } +}); diff --git a/backend/src/api/routes/exports.ts b/backend/src/api/routes/exports.ts index ec2cea1..9062454 100644 --- a/backend/src/api/routes/exports.ts +++ b/backend/src/api/routes/exports.ts @@ -118,7 +118,7 @@ export function registerExportRoutes(router: Router, deps: Deps): void { role: number | null; iata: string | null; last_seen: string | null; hardware_model: string | null; }>( `SELECT node_id, name, lat, lon, role, iata, last_seen::text, hardware_model - FROM node_identity_nodes + FROM node_identity_nodes AS nodes WHERE lat IS NOT NULL AND lon IS NOT NULL AND (name IS NULL OR name NOT LIKE '%🚫%') ${filters.nodes} diff --git a/backend/src/api/routes/misc.test.ts b/backend/src/api/routes/misc.test.ts index c948e98..5b167a1 100644 --- a/backend/src/api/routes/misc.test.ts +++ b/backend/src/api/routes/misc.test.ts @@ -37,64 +37,3 @@ test('channel feed history route forwards its bounded channel and scope', async server.close(); } }); - -test('public MQTT node status omits owner-only and unknown telemetry fields', async () => { - const storedRow = { - node_id: 'b'.repeat(64), - name: 'Public MQTT node', - last_seen: '2026-09-23T12:00:00.000Z', - battery_mv: 3900, - uptime_secs: 4200, - channel_utilization: 8, - air_util_tx: 4, - rx_air_secs: 18, - tx_air_secs: 9, - packets_24h: '12', - hardware_model: 'private model', - firmware_version: 'private firmware', - stats: { - wifi_ssid: 'private wifi', - reset_reason: 'private reset detail', - fs_free_bytes: 1234, - mqtt: { broker_uri: 'mqtts://private', broker_username: 'private-user' }, - future_owner_field: 'private future value', - }, - future_status_field: 'private future value', - }; - const router = Router(); - registerMiscRoutes(router, { - query: async (text) => { - assert.doesNotMatch(text, /nss\.stats/); - return { rows: [storedRow as unknown as T] }; - }, - getRecentPackets: async () => [], - getRecentPacketEvents: async () => [], - getPacketDetail: async () => null, - getChannelMessageHistory: async () => [], - getPublicVisibilityGeneration: async () => 1, - packetDetailLimiter: passThroughLimiter as Parameters[1]['packetDetailLimiter'], - }); - const app = express(); - app.use(router); - const server = app.listen(0, '127.0.0.1'); - await once(server, 'listening'); - try { - const { port } = server.address() as AddressInfo; - const response = await fetch(`http://127.0.0.1:${port}/mqtt-nodes?network=ukmesh`); - assert.equal(response.status, 200); - assert.deepEqual(await response.json(), [{ - node_id: storedRow.node_id, - name: storedRow.name, - last_seen: storedRow.last_seen, - battery_mv: storedRow.battery_mv, - uptime_secs: storedRow.uptime_secs, - channel_utilization: storedRow.channel_utilization, - air_util_tx: storedRow.air_util_tx, - rx_air_secs: storedRow.rx_air_secs, - tx_air_secs: storedRow.tx_air_secs, - packets_24h: storedRow.packets_24h, - }]); - } finally { - server.close(); - } -}); diff --git a/backend/src/api/routes/misc.ts b/backend/src/api/routes/misc.ts index 6e13b0d..75a3c3f 100644 --- a/backend/src/api/routes/misc.ts +++ b/backend/src/api/routes/misc.ts @@ -16,7 +16,6 @@ import { decodePlannedNodeCursor, listPublicPlannedNodes, } from '../../repositories/plannedNodes.js'; -import { toPublicMqttNodeStatusDTO } from '../publicStatus.js'; import { readRecentWithPending } from '../../ram/liveRead.js'; type QueryFn = ( @@ -33,7 +32,22 @@ type GetRecentPacketsFn = ( readQuery?: QueryFn, overlapHashes?: string[], ) => Promise; -type GetRecentPacketEventsFn = (limit: number, network?: string, observer?: string) => Promise; +type GetRecentPacketEventsFn = ( + limit: number, + network?: string, + observer?: string, + region?: string, +) => Promise; + +// Region scope filter (e.g. 'eng-ne' / '#eng-ne' -> stored form '#eng-ne'). +const REGION_SCOPE_PATTERN = /^[a-z0-9][a-z0-9_-]{0,31}$/; + +function normalizeRegionScope(value: unknown): string | undefined { + if (typeof value !== 'string') return undefined; + const bare = value.trim().toLowerCase().replace(/^#/, ''); + if (!REGION_SCOPE_PATTERN.test(bare)) return undefined; + return `#${bare}`; +} type GetPacketDetailFn = (hash: string, network?: string) => Promise; type GetChannelMessageHistoryFn = ( channel: string, @@ -41,6 +55,7 @@ type GetChannelMessageHistoryFn = ( network?: string, observer?: string, ) => Promise; +type GetRecentMessageTagsFn = (limit: number, network?: string) => Promise; type MiscRouteDeps = { query: QueryFn; @@ -48,6 +63,7 @@ type MiscRouteDeps = { getRecentPacketEvents: GetRecentPacketEventsFn; getPacketDetail: GetPacketDetailFn; getChannelMessageHistory: GetChannelMessageHistoryFn; + getRecentMessageTags: GetRecentMessageTagsFn; getPublicVisibilityGeneration: () => Promise; packetDetailLimiter: ReturnType; }; @@ -59,6 +75,7 @@ export function registerMiscRoutes(router: Router, deps: MiscRouteDeps): void { getRecentPacketEvents, getPacketDetail, getChannelMessageHistory, + getRecentMessageTags, getPublicVisibilityGeneration, packetDetailLimiter, } = deps; @@ -83,6 +100,7 @@ export function registerMiscRoutes(router: Router, deps: MiscRouteDeps): void { max: 1000, }); const raw = parseBoolean(req.query['raw'], { name: 'raw' }); + const region = normalizeRegionScope(req.query['region']); const fields = parseEnum(req.query['fields'], { name: 'fields', values: ['slim', 'full'] as const, @@ -92,7 +110,7 @@ export function registerMiscRoutes(router: Router, deps: MiscRouteDeps): void { const network = resolvePublicNetworkScope(req.query['network'], req.headers); const observer = normalizeObserverQuery(req.query['observer']); const packets = raw - ? await getRecentPacketEvents(limit, network, observer) + ? await getRecentPacketEvents(limit, network, observer, region) : await readRecentWithPending( (overlapHashes) => getRecentPackets( limit, network, observer, fields, AbortSignal.timeout(30_000), undefined, overlapHashes, @@ -131,6 +149,27 @@ export function registerMiscRoutes(router: Router, deps: MiscRouteDeps): void { } }); + // Message tags (kind / speaker / topic / flags) for live feed enrichment. + // Tags are written ~2s after a packet by tagger-worker; the frontend patches + // rows by hash. Public visibility + network scope enforced in the query. + router.get('/tags/recent', async (req, res) => { + const limit = parseBoundedInteger(req.query['limit'], { + name: 'limit', + defaultValue: 200, + min: 1, + max: 500, + }); + try { + const network = resolvePublicNetworkScope(req.query['network'], req.headers); + const tags = await getRecentMessageTags(limit, network); + res.setHeader('Cache-Control', 'no-store'); + res.json(tags); + } catch (err) { + console.error('[api] GET /tags/recent', (err as Error).message); + res.status(500).json({ error: 'Internal server error' }); + } + }); + router.get('/packets/:hash', packetDetailLimiter, async (req, res) => { const hash = parseHexIdentifier(req.params['hash'], { name: 'hash', @@ -228,6 +267,7 @@ export function registerMiscRoutes(router: Router, deps: MiscRouteDeps): void { air_util_tx: number | null; rx_air_secs: number | null; tx_air_secs: number | null; + stats: Record | null; packets_24h: string; }>( @@ -241,6 +281,7 @@ export function registerMiscRoutes(router: Router, deps: MiscRouteDeps): void { nss.air_util_tx, nss.rx_air_secs, nss.tx_air_secs, + nss.stats, COALESCE(pc.packet_count, 0) AS packets_24h FROM node_identity_status_samples nss LEFT JOIN node_identity_nodes n ON n.node_id = nss.node_id @@ -258,9 +299,7 @@ export function registerMiscRoutes(router: Router, deps: MiscRouteDeps): void { ORDER BY nss.node_id, COALESCE(nss.uptime_secs, 0) DESC, nss.time DESC`, params, ); - res.json(result.rows - .filter((r) => Number(r.packets_24h) > 0) - .map(toPublicMqttNodeStatusDTO)); + res.json(result.rows.filter((r) => Number(r.packets_24h) > 0)); } catch (err) { console.error('[api] GET /mqtt-nodes', (err as Error).message); res.status(500).json({ error: 'Internal server error' }); diff --git a/backend/src/api/routes/nodeLinks.test.ts b/backend/src/api/routes/nodeLinks.test.ts new file mode 100644 index 0000000..abdcde5 --- /dev/null +++ b/backend/src/api/routes/nodeLinks.test.ts @@ -0,0 +1,56 @@ +import assert from 'node:assert/strict'; +import { once } from 'node:events'; +import type { AddressInfo } from 'node:net'; +import test from 'node:test'; +import express, { Router } from 'express'; +import { rateLimit } from 'express-rate-limit'; +import { networkFilters } from '../utils/networkFilters.js'; +import { createNodeRepository, type NodeLinkRow } from '../../repositories/nodes.js'; +import { registerNodeRoutes } from './nodes.js'; + +test('links endpoint serializes additive fields and retains them on a cache hit', async () => { + const nodeId = 'A'.repeat(64); + const rows: NodeLinkRow[] = [{ + peer_id: 'B'.repeat(64), peer_name: 'Peer', observed_count: 2000, + itm_path_loss_db: 112.1, count_this_to_peer: 700, count_peer_to_this: 1300, + last_observed: '2026-09-07T12:00:00.123Z', recent_count_72h: 0, + }, { + peer_id: 'C'.repeat(64), peer_name: null, observed_count: 20, + itm_path_loss_db: null, count_this_to_peer: 10, count_peer_to_this: 10, + last_observed: null, recent_count_72h: 12, + }]; + let calls = 0; + const repository = createNodeRepository(async () => ({ rows: [] })); + repository.listNodeLinks = async (id, scope) => { + calls += 1; + assert.equal(id.toUpperCase(), nodeId); + assert.deepEqual(scope.params, [['ukmesh', 'northeast', 'teesside']]); + return rows; + }; + const app = express(); + const router = Router(); + registerNodeRoutes(router, { + nodeRepository: repository, + getNodes: async () => [], getNodeHistory: async () => [], getNodeAdverts: async () => [], + getTopAdvertingRepeaters: async () => [], requireLocalOnly: () => false, + networkFilters, getPublicVisibilityGeneration: async () => 1, + inferredNodesCache: new Map(), inferredNodesInflight: new Map(), inferredNodesCacheTtlMs: 60000, + nodeLinksCache: new Map(), nodeLinksInflight: new Map(), nodeLinksCacheTtlMs: 60000, + nodesLimiter: rateLimit({ windowMs: 60000, limit: 100 }), + }); + app.use('/api', router); + const server = app.listen(0, '127.0.0.1'); + await once(server, 'listening'); + try { + const base = `http://127.0.0.1:${(server.address() as AddressInfo).port}`; + for (const id of [nodeId.toLowerCase(), nodeId]) { + const response = await fetch(`${base}/api/nodes/${id}/links?network=ukmesh`); + assert.equal(response.status, 200); + assert.deepEqual(await response.json(), rows); + } + assert.equal(calls, 1); + } finally { + server.closeAllConnections(); + await new Promise((resolve, reject) => server.close(error => error ? reject(error) : resolve())); + } +}); diff --git a/backend/src/api/routes/nodeStatus.ts b/backend/src/api/routes/nodeStatus.ts index df881dc..095642c 100644 --- a/backend/src/api/routes/nodeStatus.ts +++ b/backend/src/api/routes/nodeStatus.ts @@ -7,10 +7,6 @@ import { parseBoundedInteger, parseBoundedString, } from '../utils/input.js'; -import { - toPublicLatestNodeStatusDTO, - type PublicLatestNodeStatusDTO, -} from '../publicStatus.js'; const router = Router(); @@ -30,11 +26,28 @@ router.get('/node-status/latest', async (req, res) => { } const identityWhereClause = `WHERE ${identityConditions.join(' AND ')}`; - const result = await query( + const result = await query<{ + time: string; + node_id: string; + network: string | null; + battery_mv: number | null; + uptime_secs: number | null; + tx_air_secs: number | null; + rx_air_secs: number | null; + channel_utilization: number | null; + air_util_tx: number | null; + stats: Record | null; + name: string | null; + iata: string | null; + hardware_model: string | null; + firmware_version: string | null; + }>( `WITH identity_sources AS MATERIALIZED ( SELECT n.node_id AS canonical_id, n.name, n.iata, + n.hardware_model, + n.firmware_version, source.source_node_id FROM node_identity_nodes n CROSS JOIN LATERAL unnest(n.identity_source_ids) AS source(source_node_id) @@ -50,12 +63,15 @@ router.get('/node-status/latest', async (req, res) => { nss.rx_air_secs, nss.channel_utilization, nss.air_util_tx, + nss.stats, s.name, - s.iata + s.iata, + s.hardware_model, + s.firmware_version FROM identity_sources s JOIN LATERAL ( SELECT time, network, battery_mv, uptime_secs, tx_air_secs, - rx_air_secs, channel_utilization, air_util_tx + rx_air_secs, channel_utilization, air_util_tx, stats FROM node_status_samples WHERE node_id = s.source_node_id AND network = ANY($1::text[]) @@ -69,7 +85,7 @@ router.get('/node-status/latest', async (req, res) => { params, ); - res.json(result.rows.map(toPublicLatestNodeStatusDTO)); + res.json(result.rows); } catch (err) { console.error('[api] GET /node-status/latest', (err as Error).message); res.status(500).json({ error: 'Internal server error' }); diff --git a/backend/src/api/routes/owner.ts b/backend/src/api/routes/owner.ts index 3e82b66..4e80612 100644 --- a/backend/src/api/routes/owner.ts +++ b/backend/src/api/routes/owner.ts @@ -1,7 +1,6 @@ import type { Request, Response, Router } from 'express'; import { createOwnerRepository } from '../../owner/ownerRepository.js'; import { createOwnerService } from '../../owner/ownerService.js'; -import { createOwnerLastHopPrewarm, type OwnerLastHopPrewarmTarget } from '../../owner/ownerLastHopPrewarm.js'; import type { OwnerSession } from '../../owner/ownerSession.js'; import { createCsrfToken, readCookie, requireDoubleSubmitCsrf } from '../../security/operatorAuth.js'; import { resolveWebhookTarget } from '../../security/outboundWebhook.js'; @@ -11,6 +10,11 @@ import { } from '../../owner/packetShareConfig.js'; import { hasPacketShareEncryptionKey } from '../../owner/packetShareCrypto.js'; import { createPacketShareRepository, type PacketShareTransactionFn } from '../../owner/packetShareRepository.js'; +import { + getPublicFeedConfig, + PUBLIC_FEED_TOPIC_FILTER, +} from '../../owner/publicFeedConfig.js'; +import { createPublicFeedRepository } from '../../owner/publicFeedRepository.js'; import { deliverDueOwnerAlerts, queueOwnerTestDelivery, @@ -40,8 +44,11 @@ type IsSecureRequestFn = (req: Request) => boolean; type GetOwnerSessionFn = (req: Request) => OwnerSession | null; type RequireOwnerSessionFn = (req: Request, res: Response) => Promise; type GetOwnerCredentialGenerationFn = (mqttUsername: string) => Promise; +type ReconcileOwnerAuthorizationFn = () => Promise; type QueryFn = (text: string, params?: unknown[]) => Promise<{ rows: T[] }>; +type OwnerService = ReturnType; + type OwnerRouteDeps = { ownerCookieName: string; ownerLiveCacheTtlMs: number; @@ -63,16 +70,19 @@ type OwnerRouteDeps = { requireOwnerSession: RequireOwnerSessionFn; getOwnerCredentialGeneration: GetOwnerCredentialGenerationFn; invalidateOwnerNodeIdCache: (mqttUsername: string) => void; + reconcileOwnerAuthorization?: ReconcileOwnerAuthorizationFn; query: QueryFn; withTransaction?: PacketShareTransactionFn; - loadPrewarmOwners: () => Promise; }; -export function registerOwnerRoutes(router: Router, deps: OwnerRouteDeps) { +let registeredOwnerService: OwnerService | null = null; + +export function registerOwnerRoutes(router: Router, deps: OwnerRouteDeps): void { const repository = createOwnerRepository({ query: deps.query, }); const packetShareRepository = createPacketShareRepository(deps.query, deps.withTransaction); + const publicFeedRepository = createPublicFeedRepository(deps.query, deps.withTransaction); const service = createOwnerService({ ownerLiveCacheTtlMs: deps.ownerLiveCacheTtlMs, @@ -86,11 +96,7 @@ export function registerOwnerRoutes(router: Router, deps: OwnerRouteDeps) { repository, invalidateOwnerNodeIdCache: deps.invalidateOwnerNodeIdCache, }); - const prewarm = createOwnerLastHopPrewarm({ - loadOwners: deps.loadPrewarmOwners, - refresh: (owner, nodeId) => service.getOwnerLastHopStrength(owner.nodeIds, nodeId, true), - concurrency: process.env['OWNER_LAST_HOP_PREWARM_CONCURRENCY'], - }); + registeredOwnerService = service; const csrfCookieName = 'meshcore_owner_csrf'; const csrfProtection = requireDoubleSubmitCsrf(csrfCookieName); @@ -248,10 +254,16 @@ export function registerOwnerRoutes(router: Router, deps: OwnerRouteDeps) { maxLength: 64, pattern: /^[0-9a-fA-F]{64}$/, })?.toUpperCase(); + const requestedWindow = parseBoundedString(req.query['window'], { + name: 'window', + maxLength: 4, + pattern: /^(7d|30d)$/, + }); + const windowDays = requestedWindow === '30d' ? 30 : 7; try { const ownedNodeIds = await deps.requireOwnerSession(req, res); if (!ownedNodeIds) return; - res.json(await service.getOwnerLastHopStrength(ownedNodeIds, requestedNodeId)); + res.json(await service.getOwnerLastHopStrength(ownedNodeIds, requestedNodeId, windowDays)); } catch (err) { const message = (err as Error).message; if (message === 'NO_OWNED_NODES') { @@ -268,39 +280,32 @@ export function registerOwnerRoutes(router: Router, deps: OwnerRouteDeps) { }); router.get('/owner/packet-sharing', async (req, res) => { - let requestedNodeId: string | undefined; - try { - requestedNodeId = parseBoundedString(req.query['nodeId'], { - name: 'nodeId', - maxLength: 64, - pattern: /^[0-9a-fA-F]{64}$/, - })?.toUpperCase(); - } catch { - res.status(400).json({ error: 'Invalid packet sharing settings' }); - return; - } + const requestedNodeId = parseBoundedString(req.query['nodeId'], { + name: 'nodeId', + maxLength: 64, + pattern: /^[0-9a-fA-F]{64}$/, + })?.toUpperCase(); try { const ownedNodeIds = await deps.requireOwnerSession(req, res); if (!ownedNodeIds) return; const session = deps.getOwnerSession(req)!; - const ownedNodeIdSet = new Set(ownedNodeIds.map((nodeId) => nodeId.toUpperCase())); - const nodeId = requestedNodeId ?? ownedNodeIds[0]?.toUpperCase(); - if (!nodeId || !ownedNodeIdSet.has(nodeId)) { + const nodeId = requestedNodeId ?? ownedNodeIds[0]; + if (!nodeId || !ownedNodeIds.includes(nodeId)) { res.status(403).json({ error: 'Node is not owned by this session' }); return; } const [destinations, selectedIds, lastForwardedAt] = await Promise.all([ - packetShareRepository.listDestinations(), + packetShareRepository.listDestinations(session.mqttUsername, nodeId), packetShareRepository.listOwnerRules(session.mqttUsername, nodeId), packetShareRepository.listLastForwardedAt(session.mqttUsername, nodeId), ]); - const selected = new Set(selectedIds); + const selected = new Set(selectedIds.filter((id) => destinations.some((destination) => destination.destination_id === id))); const encryptionKeyAvailable = hasPacketShareEncryptionKey(); res.setHeader('Cache-Control', 'no-store'); res.json({ nodeId, featureEnabled: true, - enabled: selectedIds.length > 0, + enabled: selected.size > 0, destinations: destinations.map((destination) => publicDestinationView( destination, selected.has(destination.destination_id), @@ -329,38 +334,29 @@ export function registerOwnerRoutes(router: Router, deps: OwnerRouteDeps) { enabled?: unknown; destinationIds?: unknown; }; - if (Object.keys(rawBody).some((key) => !['nodeId', 'enabled', 'destinationIds'].includes(key))) { - res.status(400).json({ error: 'Invalid packet sharing settings' }); - return; - } - const nodeId = typeof body.nodeId === 'string' ? body.nodeId.trim().toUpperCase() : ''; - const ownedNodeIdSet = new Set(ownedNodeIds.map((ownedNodeId) => ownedNodeId.toUpperCase())); + const nodeId = String(body.nodeId ?? '').trim().toUpperCase(); const rawDestinationIds = body.destinationIds; const enabled = body.enabled; - if (!Array.isArray(rawDestinationIds) - || rawDestinationIds.length > 32 - || !rawDestinationIds.every((value) => typeof value === 'string') + const destinationIds = Array.isArray(rawDestinationIds) && rawDestinationIds.every((value) => typeof value === 'string') + ? Array.from(new Set(rawDestinationIds.map((value) => value.trim().toLowerCase()))) + : []; + if ( + !/^[0-9A-F]{64}$/.test(nodeId) + || !ownedNodeIds.includes(nodeId) || typeof enabled !== 'boolean' + || !Array.isArray(rawDestinationIds) + || !rawDestinationIds.every((value) => typeof value === 'string') + || destinationIds.length > 32 + || destinationIds.some((destinationId) => !/^[a-z0-9][a-z0-9_-]{0,63}$/.test(destinationId)) ) { res.status(400).json({ error: 'Invalid packet sharing settings' }); return; } - const destinationIds = Array.from(new Set(rawDestinationIds.map((value) => value.trim().toLowerCase()))); - if (!/^[0-9A-F]{64}$/.test(nodeId) - || destinationIds.some((destinationId) => !/^[a-z0-9][a-z0-9_-]{0,63}$/.test(destinationId))) { - res.status(400).json({ error: 'Invalid packet sharing settings' }); - return; - } - if (!ownedNodeIdSet.has(nodeId)) { - res.status(403).json({ error: 'Node is not owned by this session' }); - return; - } - const destinations = await packetShareRepository.listDestinations(); + const destinations = await packetShareRepository.listDestinations(session.mqttUsername, nodeId); const byId = new Map(destinations.map((destination) => [destination.destination_id, destination])); - const encryptionKeyAvailable = hasPacketShareEncryptionKey(); - if (enabled && destinationIds.some((destinationId) => { + if (destinationIds.some((destinationId) => { const destination = byId.get(destinationId); - return !destination || !destinationConfigured(destination, encryptionKeyAvailable); + return !destination || (enabled && !destinationConfigured(destination, hasPacketShareEncryptionKey())); })) { res.status(409).json({ error: 'One or more selected destinations are not configured' }); return; @@ -382,6 +378,95 @@ export function registerOwnerRoutes(router: Router, deps: OwnerRouteDeps) { } }); + router.get('/owner/public-feed', async (req, res) => { + try { + const ownedNodeIds = await deps.requireOwnerSession(req, res); + if (!ownedNodeIds) return; + const session = deps.getOwnerSession(req)!; + const status = await publicFeedRepository.accountStatus( + session.mqttUsername, + ownedNodeIds, + ); + const config = getPublicFeedConfig(); + const topicFilters = config.configured ? [PUBLIC_FEED_TOPIC_FILTER] : []; + res.setHeader('Cache-Control', 'no-store'); + res.json({ + featureEnabled: true, + configured: config.configured, + brokerUrl: config.brokerUrl, + enabled: status.enabled, + nodeCount: status.nodeCount, + eligible: status.eligible, + recentPacketCount: status.recentPacketCount, + topicFilters, + lastForwardedAt: status.lastForwardedAt, + }); + } catch (error) { + console.error('[api] GET /owner/public-feed', (error as Error).message); + res.status(500).json({ error: 'Internal server error' }); + } + }); + + router.post('/owner/public-feed', csrfProtection, async (req, res) => { + try { + const ownedNodeIds = await deps.requireOwnerSession(req, res); + if (!ownedNodeIds) return; + const session = deps.getOwnerSession(req)!; + const rawBody = req.body as unknown; + if (!rawBody || typeof rawBody !== 'object' || Array.isArray(rawBody)) { + res.status(400).json({ error: 'Invalid public feed settings' }); + return; + } + const enabled = (rawBody as { enabled?: unknown }).enabled; + if (typeof enabled !== 'boolean') { + res.status(400).json({ error: 'Invalid public feed settings' }); + return; + } + const config = getPublicFeedConfig(); + const currentStatus = await publicFeedRepository.accountStatus( + session.mqttUsername, + ownedNodeIds, + ); + if (enabled && !currentStatus.eligible) { + res.status(409).json({ error: 'Publish at least one eligible packet in the last 24 hours before enabling the public feed' }); + return; + } + if (enabled && !config.configured) { + res.status(409).json({ error: 'Public feed is not configured by the operator' }); + return; + } + await publicFeedRepository.setEnabled(session.mqttUsername, ownedNodeIds, enabled); + if (deps.reconcileOwnerAuthorization) { + try { + await deps.reconcileOwnerAuthorization(); + } catch (error) { + console.error('[api] public-feed ACL reconciliation failed', (error as Error).message); + res.status(503).json({ error: 'Public feed access could not be updated yet' }); + return; + } + } + const status = await publicFeedRepository.accountStatus( + session.mqttUsername, + ownedNodeIds, + ); + const topicFilters = config.configured ? [PUBLIC_FEED_TOPIC_FILTER] : []; + res.status(200).json({ + featureEnabled: true, + configured: config.configured, + brokerUrl: config.brokerUrl, + enabled: status.enabled, + nodeCount: status.nodeCount, + eligible: status.eligible, + recentPacketCount: status.recentPacketCount, + topicFilters, + lastForwardedAt: status.lastForwardedAt, + }); + } catch (error) { + console.error('[api] POST /owner/public-feed', (error as Error).message); + res.status(500).json({ error: 'Internal server error' }); + } + }); + router.get('/owner/alert-rules', async (req, res) => { try { const ownedNodeIds = await deps.requireOwnerSession(req, res); @@ -554,5 +639,11 @@ export function registerOwnerRoutes(router: Router, deps: OwnerRouteDeps) { }); res.json({ ok: true }); }); - return prewarm; +} + +// Exposes the registered owner service so the app bootstrap can start background +// work (e.g. the owner last-hop cache prewarm) against the same instance the +// routes use. Returns null when the routes have not been registered yet. +export function getRegisteredOwnerService(): OwnerService | null { + return registeredOwnerService; } diff --git a/backend/src/api/routes/ownerPacketSharing.test.ts b/backend/src/api/routes/ownerPacketSharing.test.ts index 9df24d1..3e46aab 100644 --- a/backend/src/api/routes/ownerPacketSharing.test.ts +++ b/backend/src/api/routes/ownerPacketSharing.test.ts @@ -23,7 +23,6 @@ const passThroughLimiter: RequestHandler = (_req, _res, next) => next(); function ownerRouteDeps( query: (sql: string, params?: unknown[]) => Promise<{ rows: QueryResultRow[] }>, mqttUsername = 'node1', - requireOwnerSession: (req: import('express').Request, res: import('express').Response) => Promise = async () => [nodeId], ) { return { ownerCookieName: 'meshcore_owner_session', @@ -43,7 +42,7 @@ function ownerRouteDeps( encryptOwnerSession: () => 'session', isSecureRequest: () => false, getOwnerSession: () => ({ v: 3 as const, mqttUsername, exp: Date.now() + 60_000, gen: 0 }), - requireOwnerSession, + requireOwnerSession: async () => [nodeId], getOwnerCredentialGeneration: async () => 0, invalidateOwnerNodeIdCache: () => undefined, query, @@ -55,7 +54,8 @@ async function withOwnerApi( query: (sql: string, params?: unknown[]) => Promise<{ rows: QueryResultRow[] }>, check: (baseUrl: string, writes: string[]) => Promise, selectedDestinationRows: QueryResultRow[] = [{ destination_id: 'remote-site' }], - requireOwnerSession?: (req: import('express').Request, res: import('express').Response) => Promise, + mqttUsername = 'node1', + destinationRows: QueryResultRow[] = [destination], ) { const writes: string[] = []; const router = Router(); @@ -69,13 +69,13 @@ async function withOwnerApi( : { rows: [] }; } if (sql.includes('FROM owner_packet_share_destinations')) { - return { rows: [destination] }; + return { rows: destinationRows }; } if (sql.includes('FROM owner_packet_share_rules')) { return { rows: selectedDestinationRows }; } return query(sql, params); - }, 'node1', requireOwnerSession)); + }, mqttUsername)); const app = express(); app.use(express.json()); app.use('/api', router); @@ -136,19 +136,6 @@ test('owner packet-sharing settings expose destinations and persist a selected d assert.equal(postResponse.status, 200); assert.deepEqual(await postResponse.json(), { nodeId, enabled: true, destinationIds: ['remote-site'] }); assert.equal(writes.length, 3); - - const disableResponse = await fetch(`${baseUrl}/api/owner/packet-sharing`, { - method: 'POST', - headers: { - 'content-type': 'application/json', - cookie: 'meshcore_owner_csrf=test-token', - 'x-csrf-token': 'test-token', - }, - body: JSON.stringify({ nodeId, enabled: false, destinationIds: [] }), - }); - assert.equal(disableResponse.status, 200); - assert.deepEqual(await disableResponse.json(), { nodeId, enabled: false, destinationIds: [] }); - assert.equal(writes.length, 5); }); }); @@ -191,39 +178,54 @@ test('owner packet-sharing settings are available to every authenticated owner a } }); -test('owner packet-sharing rejects nodes outside the authenticated session', async () => { - await withOwnerApi(async () => ({ rows: [] }), async (baseUrl, writes) => { - const response = await fetch(`${baseUrl}/api/owner/packet-sharing?nodeId=${'B'.repeat(64)}`); - assert.equal(response.status, 403); - assert.deepEqual(await response.json(), { error: 'Node is not owned by this session' }); - assert.equal(writes.length, 0); - }); -}); +const scotmesh = { + ...destination, + destination_id: 'scotmesh', + display_name: 'ScotMesh', + broker_url: 'wss://scotmesh-mqtt.mm7roq.compute.oarc.uk:443/', + topic_template: 'meshcore/MME/{observerId}/packets', + allowed_owner_username: 'node1', + allowed_node_id: nodeId, + auth_mode: 'meshcore_jwt', + auth_audience: 'scotmesh-mqtt.mm7roq.compute.oarc.uk', + tx_mode: 'advert', +}; -test('owner packet-sharing requires an authenticated session', async () => { +test('ScotMesh is visible to node1 while pending signing credentials', async () => { await withOwnerApi(async () => ({ rows: [] }), async (baseUrl) => { - const response = await fetch(`${baseUrl}/api/owner/packet-sharing`); - assert.equal(response.status, 401); - assert.deepEqual(await response.json(), { error: 'Unauthorized' }); - }, [{ destination_id: 'remote-site' }], async (_req, res) => { - res.status(401).json({ error: 'Unauthorized' }); - return null; - }); + const response = await fetch(`${baseUrl}/api/owner/packet-sharing?nodeId=${nodeId}`); + const body = await response.json(); + assert.equal(response.status, 200); + assert.equal(body.destinations[0].id, 'scotmesh'); + assert.equal(body.destinations[0].configured, false); + assert.equal(body.destinations[0].selected, false); + assert.equal(JSON.stringify(body).includes('broker_'), false); + }, [], 'node1', [scotmesh]); }); -test('owner packet-sharing rejects malformed settings without database writes', async () => { - await withOwnerApi(async () => ({ rows: [] }), async (baseUrl, writes) => { - const response = await fetch(`${baseUrl}/api/owner/packet-sharing`, { - method: 'POST', - headers: { - 'content-type': 'application/json', - cookie: 'meshcore_owner_csrf=test-token', - 'x-csrf-token': 'test-token', - }, - body: JSON.stringify({ nodeId, enabled: true, destinationIds: ['remote-site'], unexpected: true }), - }); - assert.equal(response.status, 400); - assert.deepEqual(await response.json(), { error: 'Invalid packet sharing settings' }); - assert.equal(writes.length, 0); +for (const username of ['node2', 'gnomemoe', 'NODE1', 'node1-other']) { + test(`ScotMesh is absent from ${username}'s API response, including stale selections`, async () => { + await withOwnerApi(async () => ({ rows: [] }), async (baseUrl) => { + const response = await fetch(`${baseUrl}/api/owner/packet-sharing?nodeId=${nodeId}`); + assert.equal(response.status, 200); + assert.equal(response.headers.get('cache-control'), 'no-store'); + assert.deepEqual(await response.json(), { nodeId, featureEnabled: true, enabled: false, destinations: [] }); + for (const enabled of [true, false]) { + const save = await fetch(`${baseUrl}/api/owner/packet-sharing`, { + method: 'POST', + headers: { 'content-type': 'application/json', cookie: 'meshcore_owner_csrf=token', 'x-csrf-token': 'token' }, + body: JSON.stringify({ nodeId, enabled, destinationIds: ['scotmesh'] }), + }); + assert.equal(save.status, 409); + assert.deepEqual(await save.json(), { error: 'One or more selected destinations are not configured' }); + } + }, [{ destination_id: 'scotmesh' }], username, [scotmesh]); }); +} + +test('ScotMesh is absent for a different node owned by node1', async () => { + await withOwnerApi(async () => ({ rows: [] }), async (baseUrl) => { + const response = await fetch(`${baseUrl}/api/owner/packet-sharing?nodeId=${nodeId}`); + assert.deepEqual((await response.json()).destinations, []); + }, [], 'node1', [{ ...scotmesh, allowed_node_id: 'C'.repeat(64) }]); }); diff --git a/backend/src/api/routes/ownerPublicFeed.test.ts b/backend/src/api/routes/ownerPublicFeed.test.ts new file mode 100644 index 0000000..7cfb7ce --- /dev/null +++ b/backend/src/api/routes/ownerPublicFeed.test.ts @@ -0,0 +1,185 @@ +import assert from 'node:assert/strict'; +import { once } from 'node:events'; +import type { AddressInfo } from 'node:net'; +import test from 'node:test'; +import express, { Router, type RequestHandler } from 'express'; +import type { QueryResultRow } from 'pg'; +import { registerOwnerRoutes } from './owner.js'; + +const nodeId = 'A'.repeat(64); +const passThroughLimiter: RequestHandler = (_req, _res, next) => next(); + +function ownerRouteDeps( + query: (sql: string, params?: unknown[]) => Promise<{ rows: QueryResultRow[] }>, + reconcileOwnerAuthorization?: () => Promise, +) { + return { + ownerCookieName: 'meshcore_owner_session', + ownerLiveCacheTtlMs: 1_000, + ownerLiveCache: new Map(), + ownerDashboardCacheTtlMs: 1_000, + ownerLastHopCacheTtlMs: 1_000, + ownerSessionTtlMs: 60_000, + mqttUsernameMaxLen: 128, + mqttPasswordMaxLen: 128, + ownerLoginLimiter: passThroughLimiter, + hasControlChars: () => false, + verifyMqttCredentials: async () => true, + resolveOwnerNodeIds: async () => [nodeId], + autoLinkOwnerNodeIds: async () => [nodeId], + buildOwnerDashboard: async () => ({ nodes: [] }), + encryptOwnerSession: () => 'session', + isSecureRequest: () => false, + getOwnerSession: () => ({ v: 3 as const, mqttUsername: 'node1', exp: Date.now() + 60_000, gen: 0 }), + requireOwnerSession: async () => [nodeId], + getOwnerCredentialGeneration: async () => 0, + invalidateOwnerNodeIdCache: () => undefined, + reconcileOwnerAuthorization, + query, + withTransaction: async (work: (runQuery: typeof query) => Promise) => work(query), + }; +} + +async function withOwnerApi( + query: (sql: string, params?: unknown[]) => Promise<{ rows: QueryResultRow[] }>, + check: (baseUrl: string) => Promise, + reconcileOwnerAuthorization?: () => Promise, +) { + const router = Router(); + registerOwnerRoutes(router, ownerRouteDeps(query, reconcileOwnerAuthorization)); + const app = express(); + app.use(express.json()); + app.use('/api', router); + const server = app.listen(0, '127.0.0.1'); + await once(server, 'listening'); + try { + await check(`http://127.0.0.1:${(server.address() as AddressInfo).port}`); + } finally { + server.closeAllConnections(); + await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve())); + } +} + +test('public feed settings are account-level and default off', async () => { + await withOwnerApi(async (sql) => { + if (sql.includes('COALESCE(settings.enabled')) { + return { rows: [{ enabled: false, last_forwarded_at: null }] }; + } + return { rows: [] }; + }, async (baseUrl) => { + const response = await fetch(`${baseUrl}/api/owner/public-feed`); + assert.equal(response.status, 200); + assert.equal(response.headers.get('cache-control'), 'no-store'); + assert.deepEqual(await response.json(), { + featureEnabled: true, + configured: false, + brokerUrl: null, + enabled: false, + nodeCount: 1, + eligible: false, + recentPacketCount: 0, + topicFilters: [], + lastForwardedAt: null, + }); + }); +}); + +test('public feed rejects enabling until the operator supplies a broker configuration', async () => { + await withOwnerApi(async (sql) => { + if (sql.includes('COALESCE(settings.enabled')) { + return { rows: [{ enabled: false, recent_packet_count: 1, last_forwarded_at: null }] }; + } + return { rows: [] }; + }, async (baseUrl) => { + const response = await fetch(`${baseUrl}/api/owner/public-feed`, { + method: 'POST', + headers: { + 'content-type': 'application/json', + cookie: 'meshcore_owner_csrf=test-token', + 'x-csrf-token': 'test-token', + }, + body: JSON.stringify({ enabled: true }), + }); + assert.equal(response.status, 409); + assert.deepEqual(await response.json(), { error: 'Public feed is not configured by the operator' }); + }); +}); + +test('public feed rejects enabling when the owner has no eligible packet activity', async () => { + await withOwnerApi(async (sql) => { + if (sql.includes('COALESCE(settings.enabled')) { + return { rows: [{ enabled: false, recent_packet_count: 0, last_forwarded_at: null }] }; + } + return { rows: [] }; + }, async (baseUrl) => { + const response = await fetch(`${baseUrl}/api/owner/public-feed`, { + method: 'POST', + headers: { + 'content-type': 'application/json', + cookie: 'meshcore_owner_csrf=test-token', + 'x-csrf-token': 'test-token', + }, + body: JSON.stringify({ enabled: true }), + }); + assert.equal(response.status, 409); + assert.deepEqual(await response.json(), { + error: 'Publish at least one eligible packet in the last 24 hours before enabling the public feed', + }); + }); +}); + +test('public feed persists the account toggle and current owned-node mapping', async () => { + const previous = { ...process.env }; + Object.assign(process.env, { + PUBLIC_FEED_PUBLISH_ENABLED: 'true', + PUBLIC_FEED_BROKER_URL: 'mqtts://broker.example.test:8883', + PUBLIC_FEED_BROKER_USERNAME: 'publisher', + PUBLIC_FEED_BROKER_PASSWORD: 'unit-test-secret', + }); + let enabled = false; + const writes: string[] = []; + let aclReconciliations = 0; + try { + await withOwnerApi(async (sql) => { + if (sql.includes('COALESCE(settings.enabled')) { + return { rows: [{ enabled, recent_packet_count: 1, last_forwarded_at: null }] }; + } + if (sql.startsWith('INSERT INTO owner_public_feed_settings') + || sql.startsWith('DELETE FROM owner_public_feed_nodes') + || sql.startsWith('INSERT INTO owner_public_feed_nodes')) { + writes.push(sql); + if (sql.startsWith('INSERT INTO owner_public_feed_settings')) enabled = true; + } + return { rows: [] }; + }, async (baseUrl) => { + const response = await fetch(`${baseUrl}/api/owner/public-feed`, { + method: 'POST', + headers: { + 'content-type': 'application/json', + cookie: 'meshcore_owner_csrf=test-token', + 'x-csrf-token': 'test-token', + }, + body: JSON.stringify({ enabled: true }), + }); + assert.equal(response.status, 200); + assert.deepEqual(await response.json(), { + featureEnabled: true, + configured: true, + brokerUrl: 'mqtts://broker.example.test:8883', + enabled: true, + nodeCount: 1, + eligible: true, + recentPacketCount: 1, + topicFilters: ['public/+/+/packets'], + lastForwardedAt: null, + }); + assert.equal(writes.length, 3); + }, async () => { aclReconciliations += 1; }); + assert.equal(aclReconciliations, 1); + } finally { + for (const key of Object.keys(process.env)) { + if (!(key in previous)) delete process.env[key]; + } + Object.assign(process.env, previous); + } +}); diff --git a/backend/src/api/routes/productFeatures.test.ts b/backend/src/api/routes/productFeatures.test.ts new file mode 100644 index 0000000..9b7c8ec --- /dev/null +++ b/backend/src/api/routes/productFeatures.test.ts @@ -0,0 +1,82 @@ +import assert from 'node:assert/strict'; +import { once } from 'node:events'; +import type { AddressInfo } from 'node:net'; +import test from 'node:test'; +import express, { Router } from 'express'; +import type { QueryResultRow } from 'pg'; +import type { QueryFn } from '../../repositories/productFeatures.js'; +import { registerProductFeatureRoutes } from './productFeatures.js'; + +const nodeA = 'A'.repeat(64); +const nodeB = 'B'.repeat(64); + +async function withHistoryApi( + visibleIds: string[], + points: QueryResultRow[], + check: (baseUrl: string, historyParams: unknown[][]) => Promise, +) { + const historyParams: unknown[][] = []; + const query: QueryFn = async (sql: string, params?: unknown[]) => { + if (sql.includes('FROM node_identity_nodes')) { + return { rows: visibleIds.map((node_id) => ({ node_id })) as unknown as T[] }; + } + historyParams.push(params ?? []); + return { rows: points as T[] }; + }; + const app = express(); + const router = Router(); + registerProductFeatureRoutes(router, query); + app.use('/api', router); + const server = app.listen(0, '127.0.0.1'); + await once(server, 'listening'); + try { + await check(`http://127.0.0.1:${(server.address() as AddressInfo).port}`, historyParams); + } finally { + server.closeAllConnections(); + await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve())); + } +} + +test('link history preserves its response contract and forwards the public packet scope', async () => { + const points = [{ time: '2026-09-07T12:00:00.123Z', snr: -7, rssi: -99, path_loss: 110, sample_count: 1 }]; + await withHistoryApi([nodeA, nodeB], points, async (baseUrl, params) => { + const response = await fetch(`${baseUrl}/api/links/${nodeB.toLowerCase()}:${nodeA.toLowerCase()}/history`); + assert.equal(response.status, 200); + assert.equal(response.headers.get('cache-control'), 'public, max-age=60, stale-while-revalidate=300'); + assert.deepEqual(await response.json(), { linkId: `${nodeA}:${nodeB}`, network: 'ukmesh', hours: 72, points }); + assert.deepEqual(params, [[nodeA, nodeB, '72', ['ukmesh', 'northeast', 'teesside']]]); + }); +}); + +test('a fabricated pairing of visible nodes with no traffic returns HTTP 200 and empty points', async () => { + await withHistoryApi([nodeA, nodeB], [], async (baseUrl, params) => { + const response = await fetch(`${baseUrl}/api/links/${nodeA}:${nodeB}/history?network=test&hours=168`); + assert.equal(response.status, 200); + assert.deepEqual(await response.json(), { linkId: `${nodeA}:${nodeB}`, network: 'test', hours: 168, points: [] }); + assert.deepEqual(params, [[nodeA, nodeB, '168', ['test']]]); + }); +}); + +test('history never reads packets when either endpoint is outside visibility scope', async () => { + await withHistoryApi([nodeA], [], async (baseUrl, params) => { + const response = await fetch(`${baseUrl}/api/links/${nodeA}:${nodeB}/history?hours=72`); + assert.equal(response.status, 404); + assert.deepEqual(await response.json(), { error: 'Link not found' }); + assert.deepEqual(params, []); + }); +}); + +test('history rejects windows above 168 hours before querying packets', async () => { + type Handler = (req: unknown, res: unknown) => Promise; + const handlers = new Map(); + const router = { + get: (path: string, handler: Handler) => handlers.set(path, handler), + post: () => undefined, + } as unknown as Router; + registerProductFeatureRoutes(router, async () => { + assert.fail('Invalid windows must not reach the database'); + }); + await assert.rejects(handlers.get('/links/:id/history')!({ + params: { id: `${nodeA}:${nodeB}` }, query: { hours: '169' }, + }, {}), /hours must be between 1 and 168/); +}); diff --git a/backend/src/api/routes/productFeatures.ts b/backend/src/api/routes/productFeatures.ts index 9d3ffb6..e071d67 100644 --- a/backend/src/api/routes/productFeatures.ts +++ b/backend/src/api/routes/productFeatures.ts @@ -85,7 +85,7 @@ export function registerProductFeatureRoutes(router: Router, query: QueryFn): vo res.status(404).json({ error: 'Link not found' }); return; } - const result = await linkHistoryRows(query, nodeA, nodeB, hours); + const result = await linkHistoryRows(query, nodeA, nodeB, hours, networks); res.setHeader('Cache-Control', 'public, max-age=60, stale-while-revalidate=300'); res.json({ linkId: `${nodeA}:${nodeB}`, network, hours, points: result.rows }); } catch (error) { diff --git a/backend/src/api/routes/regions.test.ts b/backend/src/api/routes/regions.test.ts new file mode 100644 index 0000000..2beda89 --- /dev/null +++ b/backend/src/api/routes/regions.test.ts @@ -0,0 +1,166 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { + confidenceFor, + isWithinUkDisplayEnvelope, + shapeRegionRepeatersPayload, + shapeRegionScopePayload, + shapeRegionSummaryPayload, + type RegionMemberRow, + type RegionStatRow, +} from './regions.js'; + +const NODE_ID = 'A'.repeat(64); +const PRIVATE_ID = 'B'.repeat(64); +const OTHER_ID = 'C'.repeat(64); + +test('confidenceFor applies confirmed / probable / single tiers', () => { + assert.equal(confidenceFor(12), 'confirmed'); + assert.equal(confidenceFor(4), 'confirmed'); + assert.equal(confidenceFor(3), 'probable'); + assert.equal(confidenceFor(2), 'probable'); + assert.equal(confidenceFor(1), 'single'); + assert.equal(confidenceFor(0), 'single'); +}); + +test('isWithinUkDisplayEnvelope keeps UK positions and rejects foreign, junk and placeholder coordinates', () => { + assert.equal(isWithinUkDisplayEnvelope(54.2, -1.2), true); + assert.equal(isWithinUkDisplayEnvelope(54.6, -5.9), true); + assert.equal(isWithinUkDisplayEnvelope(60.1, -1.3), true); + assert.equal(isWithinUkDisplayEnvelope(59.2, 9.5), false); + assert.equal(isWithinUkDisplayEnvelope(0, 0), false); + assert.equal(isWithinUkDisplayEnvelope(1910, -1426), false); + assert.equal(isWithinUkDisplayEnvelope(null, -1), false); +}); + +test('shapeRegionSummaryPayload nests members, applies confidence and drops opted-out nodes', () => { + const payload = shapeRegionSummaryPayload( + { finished_at: new Date('2026-10-06T12:00:00.000Z'), window_days: 92 }, + [ + { + region: 'yorkshire', packets_total: 8967, member_count: 117, repeater_member_count: 116, + first_seen: new Date('2026-09-05T00:01:44.000Z'), last_seen: new Date('2026-10-06T00:00:00.000Z'), + }, + { + region: 'ren', packets_total: 4, member_count: 23, repeater_member_count: 21, + first_seen: null, last_seen: null, + }, + ], + [ + { region: 'yorkshire', node_id: NODE_ID, packets: 7416, name: 'Hub One', role: 2, lat: 53.9, lon: -1.7 }, + { region: 'yorkshire', node_id: PRIVATE_ID, packets: 900, name: 'Opted Out 🚫', role: 2, lat: 54.1, lon: -1.2 }, + { region: 'yorkshire', node_id: OTHER_ID, packets: 120, name: null, role: null, lat: null, lon: null }, + { region: 'yorkshire', node_id: 'D'.repeat(64), packets: 50, name: 'Nordic Node', role: 2, lat: 59.2, lon: 9.5 }, + { region: 'yorkshire', node_id: 'E'.repeat(64), packets: 40, name: 'Null Island', role: 2, lat: 0, lon: 0 }, + ], + ); + assert.equal(payload.generatedAt, '2026-10-06T12:00:00.000Z'); + assert.equal(payload.windowDays, 92); + assert.equal(payload.regions.length, 2); + const yorkshire = payload.regions[0]!; + assert.equal(yorkshire.name, 'yorkshire'); + assert.equal(yorkshire.confidence, 'confirmed'); + assert.equal(yorkshire.firstSeen, '2026-09-05T00:01:44.000Z'); + assert.equal(yorkshire.members.length, 2); + assert.deepEqual(yorkshire.members.map((member) => member.nodeId), [NODE_ID, OTHER_ID]); + assert.equal(yorkshire.members.every((member) => member.isBridge === false), true); + assert.equal(payload.regions[1]?.confidence, 'confirmed'); +}); + +test('shapeRegionSummaryPayload tolerates a missing run row', () => { + const payload = shapeRegionSummaryPayload(null, [], []); + assert.equal(payload.generatedAt, null); + assert.equal(payload.windowDays, null); + assert.deepEqual(payload.regions, []); +}); + +test('shapeRegionRepeatersPayload filters opted-out nodes and normalises regions json', () => { + const payload = shapeRegionRepeatersPayload( + { finished_at: '2026-10-06T12:00:00.000Z', window_days: 92 }, + [ + { + node_id: NODE_ID, status: 'green', evidence: 'scoped', scoped_packets: 7781, unscoped_packets_7d: 123, + regions: { yorkshire: 7416, 'eng-ne': 220, junk: 0 as unknown as number }, name: 'Hub One', role: 2, lat: 53.9, lon: -1.7, + }, + { + node_id: PRIVATE_ID, status: 'green', evidence: 'scoped', scoped_packets: 10, unscoped_packets_7d: 2, + regions: { yorkshire: 10 }, name: 'Hidden 🚫', role: 2, lat: 54, lon: -1, + }, + { + node_id: OTHER_ID, status: 'red', evidence: 'unscoped-only', scoped_packets: 0, unscoped_packets_7d: 44, + regions: {}, name: 'Quiet Repeater', role: 2, lat: 53, lon: -2, + }, + { + node_id: 'F'.repeat(64), status: 'red', evidence: 'unscoped-only', scoped_packets: 0, unscoped_packets_7d: 900, + regions: {}, name: 'DK_7620_Lemvig_R02', role: 2, lat: 56.52, lon: 8.22, + }, + ], + ); + assert.equal(payload.repeaters.length, 2); + assert.deepEqual(payload.repeaters[0]?.regions, { yorkshire: 7416, 'eng-ne': 220 }); + assert.equal(payload.repeaters[1]?.status, 'red'); + assert.equal(payload.repeaters[1]?.evidence, 'unscoped-only'); +}); + +test('shapeRegionScopePayload reports green, unobserved and missing states', () => { + const green = shapeRegionScopePayload( + null, + { + node_id: NODE_ID, status: 'green', evidence: 'scoped', scoped_packets: 6816, unscoped_packets_7d: 54, + regions: { yorkshire: 6816 }, name: 'Hub One', role: 2, lat: 53.9, lon: -1.7, + }, + [{ region: 'yorkshire', packets: 6816 }, { region: 'eng-ne', packets: 33 }], + ); + assert.equal(green?.status, 'green'); + assert.deepEqual(green?.regions, [{ region: 'yorkshire', packets: 6816 }, { region: 'eng-ne', packets: 33 }]); + + const unobserved = shapeRegionScopePayload( + { node_id: OTHER_ID, name: 'New Repeater', role: 2, lat: 52, lon: -1 }, + null, + [], + ); + assert.equal(unobserved?.status, 'unobserved'); + assert.equal(unobserved?.scopedPackets, 0); + assert.deepEqual(unobserved?.regions, []); + + const redacted = shapeRegionScopePayload( + null, + { + node_id: PRIVATE_ID, status: 'red', evidence: 'unscoped-only', scoped_packets: 0, unscoped_packets_7d: 7, + regions: {}, name: 'Hidden 🚫', role: 2, lat: 54, lon: -1, + }, + [], + ); + assert.equal(redacted?.name, 'Private Node'); + assert.equal(redacted?.lat, null); + assert.equal(redacted?.lon, null); + assert.equal(redacted?.nodeId, PRIVATE_ID); + + assert.equal(shapeRegionScopePayload(null, null, []), null); +}); + +test('shapeRegionSummaryPayload flags universal bridge members at the 12-region threshold', () => { + const stats: RegionStatRow[] = []; + const members: RegionMemberRow[] = []; + for (let index = 0; index < 12; index += 1) { + const region = `bridge-${index}`; + stats.push({ + region, packets_total: 10, member_count: 2, repeater_member_count: 2, + first_seen: null, last_seen: null, + }); + members.push({ region, node_id: NODE_ID, packets: 10, name: 'Universal Hub', role: 2, lat: 54, lon: -1 }); + } + for (let index = 0; index < 11; index += 1) { + const region = `local-${index}`; + stats.push({ + region, packets_total: 5, member_count: 1, repeater_member_count: 1, + first_seen: null, last_seen: null, + }); + members.push({ region, node_id: OTHER_ID, packets: 5, name: 'Local Repeater', role: 2, lat: 53, lon: -2 }); + } + const payload = shapeRegionSummaryPayload(null, stats, members); + const bridgeRegion = payload.regions.find((region) => region.name === 'bridge-0'); + const localRegion = payload.regions.find((region) => region.name === 'local-0'); + assert.equal(bridgeRegion?.members[0]?.isBridge, true); + assert.equal(localRegion?.members[0]?.isBridge, false); +}); diff --git a/backend/src/api/routes/regions.ts b/backend/src/api/routes/regions.ts new file mode 100644 index 0000000..c59d197 --- /dev/null +++ b/backend/src/api/routes/regions.ts @@ -0,0 +1,408 @@ +import type { Router } from 'express'; +import type { QueryResultRow } from 'pg'; +import { resolvePublicNetworkScope } from '../../http/requestScope.js'; +import type { NetworkFilters } from '../utils/networkFilters.js'; +import { isPrivateNode } from '../utils/privateNode.js'; + +type QueryFn = ( + text: string, + params?: unknown[], +) => Promise<{ rows: T[] }>; + +export type RegionConfidence = 'confirmed' | 'probable' | 'single'; + +export type RegionMemberDto = { + nodeId: string; + name: string | null; + role: number | null; + lat: number | null; + lon: number | null; + packets: number; + isBridge: boolean; +}; + +export type RegionSummaryDto = { + name: string; + packetsTotal: number; + memberCount: number; + repeaterMemberCount: number; + confidence: RegionConfidence; + firstSeen: string | null; + lastSeen: string | null; + members: RegionMemberDto[]; +}; + +export type RegionRepeaterDto = { + nodeId: string; + name: string | null; + role: number | null; + lat: number | null; + lon: number | null; + status: 'green' | 'red'; + evidence: string; + scopedPackets: number; + unscopedPackets7d: number; + regions: Record; +}; + +export type RegionScopeDto = { + nodeId: string; + name: string | null; + role: number | null; + lat: number | null; + lon: number | null; + status: 'green' | 'red' | 'unobserved'; + evidence: string; + scopedPackets: number; + unscopedPackets7d: number; + regions: Array<{ region: string; packets: number }>; +}; + +/** + * Evidence tiers for a region's decoded packet count. The 2026-10 audit + * calibrated the chance-match floor at ~0.2 false hits per candidate name over + * the whole corpus: >=4 matches is cryptographically solid, 2-3 is plausible + * but not proven, a single match may be chance. + */ +export function confidenceFor(packetsTotal: number): RegionConfidence { + if (packetsTotal >= 4) return 'confirmed'; + if (packetsTotal >= 2) return 'probable'; + return 'single'; +} + +/** + * Display envelope for the UK regions layer. The mesh dataset also carries + * nodes from other deployments (Nordic SkyMesh, continental Europe) and a + * small number of junk/duplicate rows with placeholder coordinates, so the + * public UK map only renders nodes inside the UK. Deliberately coarse + * (GB + Northern Ireland); the Channel Islands are excluded. + */ +export const UK_DISPLAY_ENVELOPE = { + minLat: 49.5, + maxLat: 61.2, + minLon: -8.8, + maxLon: 2.2, +} as const; + +export function isWithinUkDisplayEnvelope(lat: number | null, lon: number | null): boolean { + if (lat == null || lon == null) return false; + if (!Number.isFinite(lat) || !Number.isFinite(lon)) return false; + // Reject null-island and similar placeholder coordinates. + if (Math.abs(lat) < 1e-6 && Math.abs(lon) < 1e-6) return false; + return lat >= UK_DISPLAY_ENVELOPE.minLat && lat <= UK_DISPLAY_ENVELOPE.maxLat + && lon >= UK_DISPLAY_ENVELOPE.minLon && lon <= UK_DISPLAY_ENVELOPE.maxLon; +} + +/** True only for rows carrying coordinates that fall outside the UK envelope. */ +function excludedByLocation(lat: number | null, lon: number | null): boolean { + return lat != null && lon != null && !isWithinUkDisplayEnvelope(lat, lon); +} + +interface RunRow extends QueryResultRow { + finished_at: Date | string | null; + window_days: number | null; +} + +export interface RegionStatRow extends QueryResultRow { + region: string; + packets_total: number; + member_count: number; + repeater_member_count: number; + first_seen: Date | string | null; + last_seen: Date | string | null; +} + +export interface RegionMemberRow extends QueryResultRow { + region: string; + node_id: string; + packets: number; + name: string | null; + role: number | null; + lat: number | null; + lon: number | null; +} + +export interface RegionRepeaterRow extends QueryResultRow { + node_id: string; + status: string; + evidence: string; + scoped_packets: number; + unscoped_packets_7d: number; + regions: Record | null; + name: string | null; + role: number | null; + lat: number | null; + lon: number | null; +} + +export interface RegionScopeNodeRow extends QueryResultRow { + node_id: string; + name: string | null; + role: number | null; + lat: number | null; + lon: number | null; +} + +interface ScopeMemberRow extends QueryResultRow { + region: string; + packets: number; +} + +function isoOrNull(value: Date | string | null | undefined): string | null { + if (value == null) return null; + const date = value instanceof Date ? value : new Date(value); + return Number.isFinite(date.getTime()) ? date.toISOString() : null; +} + +function numberOrNull(value: number | null | undefined): number | null { + return value == null ? null : Number(value); +} + +function normalizeRegions(value: Record | null): Record { + // node-postgres parses jsonb, but be defensive about non-numeric members. + const regions: Record = {}; + for (const [name, count] of Object.entries(value ?? {})) { + const numeric = Number(count); + if (Number.isFinite(numeric) && numeric > 0) regions[name] = numeric; + } + return regions; +} + +/** + * Universal "bridge" repeaters are configured to relay scoped traffic for most + * of the network's regions (the live distribution puts them at 17+ regions, + * with a clean gap down to 11 for ordinary multi-region repeaters), so their + * position carries no signal about any single region's footprint. Region + * shapes exclude bridge members; member lists keep them — they are real relays. + */ +export const BRIDGE_MIN_REGIONS = 12; + +export function shapeRegionSummaryPayload( + run: RunRow | null, + stats: RegionStatRow[], + memberRows: RegionMemberRow[], +): { generatedAt: string | null; windowDays: number | null; regions: RegionSummaryDto[] } { + const membersByRegion = new Map(); + for (const row of memberRows) { + if (isPrivateNode(row.name)) continue; + if (excludedByLocation(row.lat, row.lon)) continue; + const list = membersByRegion.get(row.region) ?? []; + list.push({ + nodeId: row.node_id, + name: row.name, + role: numberOrNull(row.role), + lat: numberOrNull(row.lat), + lon: numberOrNull(row.lon), + packets: Number(row.packets), + isBridge: false, + }); + membersByRegion.set(row.region, list); + } + // A node appears at most once per region list, so counting occurrences + // across lists yields its distinct-region membership count. + const regionsPerNode = new Map(); + for (const list of membersByRegion.values()) { + for (const member of list) { + regionsPerNode.set(member.nodeId, (regionsPerNode.get(member.nodeId) ?? 0) + 1); + } + } + for (const list of membersByRegion.values()) { + for (const member of list) { + member.isBridge = (regionsPerNode.get(member.nodeId) ?? 0) >= BRIDGE_MIN_REGIONS; + } + } + return { + generatedAt: isoOrNull(run?.finished_at), + windowDays: run?.window_days == null ? null : Number(run.window_days), + regions: stats.map((row) => ({ + name: row.region, + packetsTotal: Number(row.packets_total), + memberCount: Number(row.member_count), + repeaterMemberCount: Number(row.repeater_member_count), + confidence: confidenceFor(Number(row.packets_total)), + firstSeen: isoOrNull(row.first_seen), + lastSeen: isoOrNull(row.last_seen), + members: membersByRegion.get(row.region) ?? [], + })), + }; +} + +export function shapeRegionRepeatersPayload( + run: RunRow | null, + rows: RegionRepeaterRow[], +): { generatedAt: string | null; repeaters: RegionRepeaterDto[] } { + return { + generatedAt: isoOrNull(run?.finished_at), + repeaters: rows + .filter((row) => !isPrivateNode(row.name)) + .filter((row) => !excludedByLocation(row.lat, row.lon)) + .map((row) => ({ + nodeId: row.node_id, + name: row.name, + role: numberOrNull(row.role), + lat: numberOrNull(row.lat), + lon: numberOrNull(row.lon), + status: row.status === 'green' ? ('green' as const) : ('red' as const), + evidence: row.evidence, + scopedPackets: Number(row.scoped_packets), + unscopedPackets7d: Number(row.unscoped_packets_7d), + regions: normalizeRegions(row.regions), + })), + }; +} + +export function shapeRegionScopePayload( + nodeRow: RegionScopeNodeRow | null, + statusRow: RegionRepeaterRow | null, + memberRows: ScopeMemberRow[], +): RegionScopeDto | null { + if (!nodeRow && !statusRow) return null; + const nodeId = (statusRow?.node_id ?? nodeRow?.node_id ?? '').toUpperCase(); + let name = statusRow?.name ?? nodeRow?.name ?? null; + let lat = statusRow?.lat ?? nodeRow?.lat ?? null; + let lon = statusRow?.lon ?? nodeRow?.lon ?? null; + if (isPrivateNode(name)) { + // Public geometry must never be derived from opted-out nodes. + name = 'Private Node'; + lat = null; + lon = null; + } + return { + nodeId, + name, + role: numberOrNull(statusRow?.role ?? nodeRow?.role), + lat: numberOrNull(lat), + lon: numberOrNull(lon), + status: statusRow ? (statusRow.status === 'green' ? 'green' : 'red') : 'unobserved', + evidence: statusRow?.evidence ?? 'none', + scopedPackets: Number(statusRow?.scoped_packets ?? 0), + unscopedPackets7d: Number(statusRow?.unscoped_packets_7d ?? 0), + regions: memberRows.map((row) => ({ region: row.region, packets: Number(row.packets) })), + }; +} + +type RegionsRouteDeps = { + query: QueryFn; + networkFilters: (network?: string, observer?: string) => NetworkFilters; + limiter: ReturnType; +}; + +const REGIONS_CACHE_CONTROL = 'public, max-age=300, stale-while-revalidate=900'; + +// Nodes can briefly hold more than one row per network; pick the most recently +// seen row for stable public metadata. The network predicate comes from the +// shared networkFilters helper so the public ukmesh scope keeps the historical +// northeast/teesside labels in scope (matching every other public route). +function nodeMetaLateral(alias: string, filters: NetworkFilters): string { + return `LEFT JOIN LATERAL ( + SELECT n.name, n.role, n.lat, n.lon + FROM nodes n + WHERE n.node_id = ${alias}.node_id ${filters.nodesAlias('n')} + ORDER BY n.last_seen DESC NULLS LAST + LIMIT 1 + ) meta ON TRUE`; +} + +export function registerRegionsRoutes(router: Router, deps: RegionsRouteDeps): void { + router.get('/regions/summary', deps.limiter, async (req, res) => { + try { + const network = resolvePublicNetworkScope(req.query['network'], req.headers); + const filters = deps.networkFilters(network); + const [run, stats, members] = await Promise.all([ + deps.query('SELECT finished_at, window_days FROM region_scope_run LIMIT 1'), + deps.query( + `SELECT region, packets_total, member_count, repeater_member_count, first_seen, last_seen + FROM region_scope_stats + ORDER BY packets_total DESC, region ASC`, + ), + deps.query( + `SELECT m.region, m.node_id, m.packets, meta.name, meta.role, meta.lat, meta.lon + FROM region_scope_members m + ${nodeMetaLateral('m', filters)} + ORDER BY m.region ASC, m.packets DESC, m.node_id ASC`, + [...filters.params], + ), + ]); + res.setHeader('Cache-Control', REGIONS_CACHE_CONTROL); + res.json(shapeRegionSummaryPayload(run.rows[0] ?? null, stats.rows, members.rows)); + } catch (err) { + console.error('[api] GET /regions/summary', (err as Error).message); + res.status(500).json({ error: 'Internal server error' }); + } + }); + + router.get('/regions/repeaters', deps.limiter, async (req, res) => { + try { + const network = resolvePublicNetworkScope(req.query['network'], req.headers); + const filters = deps.networkFilters(network); + const [run, repeaters] = await Promise.all([ + deps.query('SELECT finished_at, window_days FROM region_scope_run LIMIT 1'), + deps.query( + `SELECT s.node_id, s.status, s.evidence, s.scoped_packets, s.unscoped_packets_7d, s.regions, + meta.name, meta.role, meta.lat, meta.lon + FROM repeater_scope_status s + ${nodeMetaLateral('s', filters)} + ORDER BY s.scoped_packets DESC, s.node_id ASC`, + [...filters.params], + ), + ]); + res.setHeader('Cache-Control', REGIONS_CACHE_CONTROL); + res.json(shapeRegionRepeatersPayload(run.rows[0] ?? null, repeaters.rows)); + } catch (err) { + console.error('[api] GET /regions/repeaters', (err as Error).message); + res.status(500).json({ error: 'Internal server error' }); + } + }); + + router.get('/repeaters/:id/scope', deps.limiter, async (req, res) => { + const rawId = String(req.params['id'] ?? '').trim().toUpperCase(); + if (!/^[0-9A-F]{64}$/.test(rawId)) { + res.status(400).json({ error: 'Invalid repeater id' }); + return; + } + try { + const network = resolvePublicNetworkScope(req.query['network'], req.headers); + const filters = deps.networkFilters(network); + const idParam = `$${filters.params.length + 1}`; + const [status, members, node] = await Promise.all([ + deps.query( + `SELECT s.node_id, s.status, s.evidence, s.scoped_packets, s.unscoped_packets_7d, s.regions, + meta.name, meta.role, meta.lat, meta.lon + FROM repeater_scope_status s + ${nodeMetaLateral('s', filters)} + WHERE s.node_id = ${idParam}`, + [...filters.params, rawId], + ), + deps.query( + `SELECT region, packets + FROM region_scope_members + WHERE node_id = $1 + ORDER BY packets DESC, region ASC`, + [rawId], + ), + deps.query( + `SELECT n.node_id, n.name, n.role, n.lat, n.lon + FROM nodes n + WHERE n.node_id = ${idParam} ${filters.nodesAlias('n')} + ORDER BY n.last_seen DESC NULLS LAST + LIMIT 1`, + [...filters.params, rawId], + ), + ]); + const payload = shapeRegionScopePayload( + node.rows[0] ?? null, + status.rows[0] ?? null, + members.rows, + ); + if (!payload) { + res.status(404).json({ error: 'Repeater not found' }); + return; + } + res.setHeader('Cache-Control', REGIONS_CACHE_CONTROL); + res.json(payload); + } catch (err) { + console.error('[api] GET /repeaters/:id/scope', (err as Error).message); + res.status(500).json({ error: 'Internal server error' }); + } + }); +} diff --git a/backend/src/api/routes/rfValidation.integration.test.ts b/backend/src/api/routes/rfValidation.integration.test.ts new file mode 100644 index 0000000..e2f3f50 --- /dev/null +++ b/backend/src/api/routes/rfValidation.integration.test.ts @@ -0,0 +1,22 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; + +// Opt in only after the authorized backend deployment. This is a GET against +// the actual route and its bounded read-only pool, without any DB writes. +test('live UKMesh RF validation returns populated results within five seconds at every supported size', { + skip: process.env['AUDITWAVE_LIVE_TESTS'] !== '1', + timeout: 20_000, +}, async () => { + for (const limit of [25, 100, 250]) { + const startedAt = performance.now(); + const response = await fetch(`http://127.0.0.1:3000/api/rf-validation?network=ukmesh&limit=${limit}`, { + signal: AbortSignal.timeout(5_000), + }); + const body = await response.json() as { summary: { evaluated: number } }; + const elapsedMs = performance.now() - startedAt; + assert.equal(response.status, 200); + assert.ok(elapsedMs < 5_000, `RF validation took ${elapsedMs.toFixed(0)}ms`); + assert.ok(body.summary.evaluated > 0 && body.summary.evaluated <= limit); + console.log(JSON.stringify({ limit, elapsedMs, evaluated: body.summary.evaluated })); + } +}); diff --git a/backend/src/api/routes/rfValidation.test.ts b/backend/src/api/routes/rfValidation.test.ts new file mode 100644 index 0000000..4a86693 --- /dev/null +++ b/backend/src/api/routes/rfValidation.test.ts @@ -0,0 +1,112 @@ +import assert from 'node:assert/strict'; +import { once } from 'node:events'; +import type { AddressInfo } from 'node:net'; +import test from 'node:test'; +import express, { Router, type RequestHandler } from 'express'; +import { networkFilters } from '../utils/networkFilters.js'; +import { registerRfValidationRoutes, RF_VALIDATION_REQUEST_TIMEOUT_MS } from './rfValidation.js'; + +const passThroughLimiter: RequestHandler = (_req, _res, next) => next(); +type RfQuery = NonNullable[2]>; +async function start(query: RfQuery) { + const router = Router(); + registerRfValidationRoutes(router, { + limiter: passThroughLimiter as Parameters[1]['limiter'], + networkFilters, + query: async () => { throw new Error('Must not use the unbounded shared query'); }, + }, query); + const app = express(); + app.use(router); + const server = app.listen(0, '127.0.0.1'); + await once(server, 'listening'); + return { + url: `http://127.0.0.1:${(server.address() as AddressInfo).port}/rf-validation?network=ukmesh&limit=100`, + close: async () => { + server.closeAllConnections(); + await new Promise((resolve) => server.close(() => resolve())); + }, + }; +} + +for (const stage of [1, 2]) { + test(`RF validation propagates an HTTP disconnect during database read ${stage}`, { timeout: 3_000 }, async () => { + let calls = 0; + let resolveStarted!: () => void; + let resolveAborted!: () => void; + const started = new Promise((resolve) => { resolveStarted = resolve; }); + const aborted = new Promise((resolve) => { resolveAborted = resolve; }); + const server = await start(async (_sql, _params, signal) => { + assert.ok(signal); + if (++calls < stage) return { rows: [{ node_id: 'A', name: 'Alpha' }] as never[] }; + resolveStarted(); + return new Promise((_resolve, reject) => { + signal.addEventListener('abort', () => { + resolveAborted(); + reject(signal.reason); + }, { once: true }); + }); + }); + try { + const controller = new AbortController(); + const response = fetch(server.url, { signal: controller.signal }); + await started; + controller.abort(); + await assert.rejects(response, /aborted/i); + await aborted; + assert.equal(calls, stage); + } finally { await server.close(); } + }); +} + +test('RF deadline aborts the database operation and returns an uncached 503 within five seconds', { timeout: 6_000 }, async () => { + let aborted = false; + const server = await start(async (_sql, _params, signal) => new Promise((_resolve, reject) => { + signal!.addEventListener('abort', () => { aborted = true; reject(signal!.reason); }, { once: true }); + })); + try { + const started = performance.now(); + const response = await fetch(server.url); + assert.equal(response.status, 503); + assert.ok(aborted); + assert.ok(performance.now() - started < 5_000); + assert.equal(RF_VALIDATION_REQUEST_TIMEOUT_MS, 4_500); + assert.equal(response.headers.get('cache-control'), 'no-store'); + assert.equal(response.headers.get('retry-after'), '30'); + assert.match((await response.json() as { error: string }).error, /temporarily unavailable/); + } finally { await server.close(); } +}); + +test('RF server statement cancellation is reported as unavailable, not an empty successful panel', async () => { + const server = await start(async () => { throw Object.assign(new Error('statement cancelled'), { code: '57014' }); }); + try { + const response = await fetch(server.url); + assert.equal(response.status, 503); + assert.equal(response.headers.get('cache-control'), 'no-store'); + } finally { await server.close(); } +}); + +test('RF JSON keeps classification summaries and numeric counts, including an honest empty scope', async () => { + for (const empty of [false, true]) { + const server = await start(async (sql) => { + if (sql.includes('FROM node_identity_nodes')) { + return { rows: (empty ? [] : [{ node_id: 'A', name: 'Alpha' }, { node_id: 'B', name: null }]) as never[] }; + } + return { rows: [{ node_a_id: 'A', node_b_id: 'B', observed_count: '12', multibyte_observed_count: '3', + itm_path_loss_db: 130, itm_viable: false, force_viable: false, + last_observed: '2026-09-07T12:00:00Z', classification: 'observed_unexpected' }] as never[] }; + }); + try { + const response = await fetch(server.url); + assert.equal(response.status, 200); + const body = await response.json() as { windowDays: number; summary: { evaluated: number; observedUnexpected: number }; mismatches: Array<{ sourceName: string; observations: number; strongObservations: number }> }; + assert.equal(body.windowDays, 30); + assert.equal(body.summary.evaluated, empty ? 0 : 1); + assert.equal(body.summary.observedUnexpected, empty ? 0 : 1); + if (!empty) { + assert.equal(body.mismatches[0]!.sourceName, 'Alpha'); + assert.equal(body.mismatches[0]!.observations, 12); + assert.equal(body.mismatches[0]!.strongObservations, 3); + } + } finally { await server.close(); } + } +}); diff --git a/backend/src/api/routes/rfValidation.ts b/backend/src/api/routes/rfValidation.ts index f280be4..5e990fd 100644 --- a/backend/src/api/routes/rfValidation.ts +++ b/backend/src/api/routes/rfValidation.ts @@ -3,16 +3,21 @@ import type { QueryResultRow } from 'pg'; import { resolvePublicNetworkScope } from '../../http/requestScope.js'; import type { NetworkFilters } from '../utils/networkFilters.js'; import { parseBoundedInteger } from '../utils/input.js'; -import { rfValidationRows } from '../../repositories/networkAnalysis.js'; +import { queryRfValidation, rfValidationRows } from '../../repositories/networkAnalysis.js'; +export { RF_VALIDATION_DB_TIMEOUT_MS } from '../../repositories/networkAnalysis.js'; -type QueryFn = (text: string, params?: unknown[]) => Promise<{ rows: T[] }>; +type QueryFn = (text: string, params?: unknown[], signal?: AbortSignal) => Promise<{ rows: T[] }>; type Deps = { + // Kept compatible with the central route registration. RF reads use the + // dedicated read-only pool below instead of this general-purpose query. query: QueryFn; networkFilters: (network?: string, observer?: string) => NetworkFilters; limiter: ReturnType; }; -export function registerRfValidationRoutes(router: Router, deps: Deps): void { +export const RF_VALIDATION_REQUEST_TIMEOUT_MS = 4_500; + +export function registerRfValidationRoutes(router: Router, deps: Deps, rfQuery: QueryFn = queryRfValidation): void { router.get('/rf-validation', deps.limiter, async (req, res) => { const limit = parseBoundedInteger(req.query['limit'], { name: 'limit', @@ -20,10 +25,16 @@ export function registerRfValidationRoutes(router: Router, deps: Deps): void { min: 25, max: 250, }); + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(new Error('RF_VALIDATION_TIMEOUT')), RF_VALIDATION_REQUEST_TIMEOUT_MS); + const onClose = () => { + if (!res.writableEnded) controller.abort(new Error('RF_VALIDATION_DISCONNECTED')); + }; + res.once('close', onClose); try { const network = resolvePublicNetworkScope(req.query['network'], req.headers); const filters = deps.networkFilters(network); - const result = await rfValidationRows(deps.query, filters, limit); + const result = await rfValidationRows(rfQuery, filters, limit, controller.signal); const links = result.rows.map((row) => ({ source: row.node_a_id, target: row.node_b_id, @@ -42,7 +53,7 @@ export function registerRfValidationRoutes(router: Router, deps: Deps): void { res.json({ generatedAt: new Date().toISOString(), windowDays: 30, - methodology: 'Observed relay evidence compared with the stored ITM viability result; weak evidence is not treated as a proven model error.', + methodology: 'Relay links observed in the last 30 days, ranked by model disagreement and evidence strength. Stored observation counts and ITM results are combined across recently active identity aliases; weak evidence is not a proven model error.', summary: { evaluated: links.length, matches: links.length - mismatches.length, @@ -54,8 +65,19 @@ export function registerRfValidationRoutes(router: Router, deps: Deps): void { mismatches, }); } catch (err) { + if (res.destroyed) return; console.error('[api] GET /rf-validation', (err as Error).message); - res.status(500).json({ error: 'Internal server error' }); + res.setHeader('Cache-Control', 'no-store'); + if (controller.signal.aborted || (err as { code?: string }).code === '57014' + || /timeout|timed out/i.test((err as Error).message)) { + res.setHeader('Retry-After', '30'); + res.status(503).json({ error: 'RF validation is temporarily unavailable. Please retry shortly.' }); + } else { + res.status(500).json({ error: 'Internal server error' }); + } + } finally { + clearTimeout(timeout); + res.off('close', onClose); } }); } diff --git a/backend/src/cache/boundedTtlMap.test.ts b/backend/src/cache/boundedTtlMap.test.ts index 896dd4f..d18b996 100644 --- a/backend/src/cache/boundedTtlMap.test.ts +++ b/backend/src/cache/boundedTtlMap.test.ts @@ -60,38 +60,3 @@ test('bounded cache overwrite accounts weight once and reports hit/miss/eviction }); cache.shutdown(); }); - -test('sweeps respect replacement deadlines, access order, and backwards clock changes', () => { - let now = 100; - const cache = new BoundedTtlMap({ - maxEntries: 4, maxWeight: 100, ttlMs: 100, now: () => now, - }); - try { - cache.set('replaced', 'old'); - now = 150; - cache.set('replaced', 'new'); - now = 50; - cache.set('earlier', 'value'); - cache.get('replaced'); // LRU order must not determine expiry order. - now = 150; - cache.sweep(); - assert.equal(cache.has('earlier'), false); - assert.equal(cache.get('replaced'), 'new'); - now = 200; // The overwritten value's original expiry is harmless. - cache.sweep(); - assert.equal(cache.size, 1); - now = 250; - cache.sweep(); - assert.equal(cache.size, 0); - assert.equal(cache.weight(), 0); - assert.equal(cache.metrics().expiries, 2); - cache.clear(); - now = 0; - cache.set('after-clear', 'value'); - now = 100; - cache.sweep(); - assert.equal(cache.size, 0); - } finally { - cache.shutdown(); - } -}); diff --git a/backend/src/cache/boundedTtlMap.ts b/backend/src/cache/boundedTtlMap.ts index 0db8a7e..32b8df4 100644 --- a/backend/src/cache/boundedTtlMap.ts +++ b/backend/src/cache/boundedTtlMap.ts @@ -27,9 +27,6 @@ export class BoundedTtlMap extends Map { private readonly insertedAt = new Map(); private readonly weights = new Map(); private totalWeight = 0; - // A lower bound on the next expiry. Deletions may leave it conservatively - // early, but writes need not walk the whole cache while every entry is fresh. - private nextExpiryAt = Number.POSITIVE_INFINITY; private readonly timer: NodeJS.Timeout; private hits = 0; private misses = 0; @@ -93,9 +90,7 @@ export class BoundedTtlMap extends Map { this.sweep(); this.delete(key); super.set(key, value); - const insertedAt = this.options.now?.() ?? Date.now(); - this.insertedAt.set(key, insertedAt); - this.nextExpiryAt = Math.min(this.nextExpiryAt, insertedAt + this.options.ttlMs); + this.insertedAt.set(key, this.options.now?.() ?? Date.now()); this.weights.set(key, weight); this.totalWeight += weight; while (this.size > this.options.maxEntries || this.totalWeight > this.options.maxWeight) { @@ -127,21 +122,16 @@ export class BoundedTtlMap extends Map { this.insertedAt.clear(); this.weights.clear(); this.totalWeight = 0; - this.nextExpiryAt = Number.POSITIVE_INFINITY; this.recordMetric('cleared'); this.syncMetricGauges(); } sweep(now = this.options.now?.() ?? Date.now()): void { - if (now < this.nextExpiryAt) return; - this.nextExpiryAt = Number.POSITIVE_INFINITY; for (const [key, timestamp] of this.insertedAt) { if (now - timestamp >= this.options.ttlMs) { this.expiries += 1; this.recordMetric('expired'); this.delete(key); - } else { - this.nextExpiryAt = Math.min(this.nextExpiryAt, timestamp + this.options.ttlMs); } } } diff --git a/backend/src/cache/policyRegistry.ts b/backend/src/cache/policyRegistry.ts index a302dd2..7bfeaf8 100644 --- a/backend/src/cache/policyRegistry.ts +++ b/backend/src/cache/policyRegistry.ts @@ -123,6 +123,12 @@ export const CACHE_POLICY_REGISTRY: readonly CachePolicyRecord[] = Object.freeze scope: 'owner username', invalidation: 'authorization reconciliation + TTL', negativeCaching: 'empty verified grant list', singleFlight: 'ownerNodeIdInflight (cap 128)', }, + { + source: 'src/owner/ownerAccess.ts#authCache', + disposition: 'bounded-cache', maxEntries: 512, maxBytes: 2 << 20, ttlMs: 5 * 60_000, + scope: 'owner username + credential digest', invalidation: 'credential mismatch + TTL', + negativeCaching: 'positive results only', singleFlight: 'broker connection timeout', + }, { source: 'src/owner/ownerService.ts#ownerLastHopCache', disposition: 'bounded-cache', maxEntries: 1_024, maxBytes: 64 << 20, ttlMs: 60 * 60_000, diff --git a/backend/src/db/advertCount.test.ts b/backend/src/db/advertCount.test.ts index 17c54f9..cfdeaf3 100644 --- a/backend/src/db/advertCount.test.ts +++ b/backend/src/db/advertCount.test.ts @@ -11,7 +11,5 @@ test('advert count is folded into node UPSERT with durable canonical-hash dedupl assert.match(dbSource, /advert_count\s+= nodes\.advert_count \+ \(SELECT COUNT\(\*\) FROM advert_once\)/); assert.doesNotMatch(dbSource, /function incrementAdvertCount/); assert.match(mqttSource, /advertHash: canonicalPacketId/); - assert.match(mqttSource, /const advertSignatureValid = await verifyAdvertSignature\(decoded\)/); - assert.match(mqttSource, /if \(advertSignatureValid && network !== 'test'\)/); assert.doesNotMatch(mqttSource, /tryCountAdvert|countedAdvertHashes/); }); diff --git a/backend/src/db/dataLifecycle.ts b/backend/src/db/dataLifecycle.ts index d968008..0540a27 100644 --- a/backend/src/db/dataLifecycle.ts +++ b/backend/src/db/dataLifecycle.ts @@ -42,10 +42,10 @@ export const DATA_LIFECYCLE_POLICIES: readonly DataLifecyclePolicy[] = Object.fr { table: 'node_status_samples', timestampColumn: 'time', - retention: '180 days', + retention: '30 days', kind: 'hypertable', featureImpact: [ - 'owner status telemetry older than 180 days becomes restore-only', + 'owner status telemetry older than 30 days becomes restore-only', ], }, { @@ -113,6 +113,13 @@ export const DATA_LIFECYCLE_POLICIES: readonly DataLifecyclePolicy[] = Object.fr kind: 'row-table', featureImpact: ['terminal owner packet-sharing queue and audit rows older than 30 days are removed'], }, + { + table: 'public_feed_deliveries', + timestampColumn: 'created_at', + retention: '30 days', + kind: 'row-table', + featureImpact: ['terminal public-feed queue and audit rows older than 30 days are removed'], + }, { table: 'observer_registration_requests', timestampColumn: 'updated_at', diff --git a/backend/src/db/index.ts b/backend/src/db/index.ts index 9a51126..a856c39 100644 --- a/backend/src/db/index.ts +++ b/backend/src/db/index.ts @@ -146,6 +146,11 @@ async function queryPool( } } +export type DatabaseQueryFn = ( + text: string, + params?: unknown[], +) => Promise<{ rows: T[] }>; + export async function query( text: string, params?: unknown[], @@ -154,11 +159,6 @@ export async function query( return queryPool(pool, 'oltp', text, params, signal); } -export type DatabaseQueryFn = ( - text: string, - params?: unknown[], -) => Promise<{ rows: T[] }>; - export async function withTransaction(work: (transactionQuery: DatabaseQueryFn) => Promise): Promise { const client = await pool.connect(); const transactionQuery: DatabaseQueryFn = async ( @@ -644,13 +644,12 @@ export async function insertNodeStatusSample(sample: { channelUtilization?: number | null; airUtilTx?: number | null; stats?: Record | null; - status?: string | null; }): Promise { await pool.query( `WITH sample_insert AS ( INSERT INTO node_status_samples - (time, node_id, network, battery_mv, uptime_secs, tx_air_secs, rx_air_secs, channel_utilization, air_util_tx, stats, status) - VALUES (NOW(), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10) + (time, node_id, network, battery_mv, uptime_secs, tx_air_secs, rx_air_secs, channel_utilization, air_util_tx, stats) + VALUES (NOW(), $1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING time ) UPDATE nodes @@ -668,7 +667,6 @@ export async function insertNodeStatusSample(sample: { sample.channelUtilization ?? null, sample.airUtilTx ?? null, sample.stats ? JSON.stringify(sample.stats) : null, - sample.status ?? null, ], ); } @@ -735,17 +733,35 @@ export async function getNodes( export async function getNodeHistory(nodeId: string, hours = 24, network = 'ukmesh') { const scope = buildScopePlaceholders(3, network); + const aliases = nodeAliasArraySql('$1'); + // Both consumers describe general activity, including for observers that + // are also repeaters. Include receptions and known origins for every role. + // Drive each direction with the canonical alias array and an equality + // lookup: a combined OR/ANY lets LIMIT choose a scan of time-ordered traffic + // for quiet nodes. Per-alias limits keep both node/time indexes useful even + // at 28 days; the final limit retains the newest 500 observations overall. + const packetsFor = (column: 'rx_node_id' | 'src_node_id') => ` + SELECT p.time, p.packet_hash, + COALESCE(src_alias.canonical_node_id, UPPER(BTRIM(p.src_node_id))) AS src_node_id, + topic, packet_type, hop_count, rssi, snr, payload + FROM packets p + LEFT JOIN node_identity_aliases src_alias + ON src_alias.source_node_id = UPPER(BTRIM(p.src_node_id)) + WHERE p.${column} = requested.node_id + ${column === 'src_node_id' ? `AND NOT COALESCE(p.rx_node_id = ANY(${aliases}), FALSE)` : ''} + AND p.time > NOW() - INTERVAL '1 hour' * $2 + AND p.time <= NOW() + ${buildPacketScopeClause(scope, 'p', network)} + ${buildPublicPacketPrivacyClause('p')} + ORDER BY time DESC LIMIT 500`; const res = await pool.query( - `SELECT p.time, p.packet_hash, - COALESCE(src_alias.canonical_node_id, UPPER(BTRIM(p.src_node_id))) AS src_node_id, - topic, packet_type, hop_count, rssi, snr, payload - FROM packets p - LEFT JOIN node_identity_aliases src_alias - ON src_alias.source_node_id = UPPER(BTRIM(p.src_node_id)) - WHERE p.rx_node_id = ANY(${nodeAliasArraySql('$1')}) - AND p.time > NOW() - INTERVAL '1 hour' * $2 - ${buildPacketScopeClause(scope, 'p', network)} - ${buildPublicPacketPrivacyClause('p')} + `SELECT recent.* + FROM unnest(${aliases}) requested(node_id) + CROSS JOIN LATERAL ( + (${packetsFor('rx_node_id')}) + UNION ALL + (${packetsFor('src_node_id')}) + ) recent ORDER BY time DESC LIMIT 500`, [nodeId, hours, ...scope.params] ); @@ -880,9 +896,11 @@ export async function getRecentPackets( ${includeObservationIds ? `ps._observation_ids, rp.network AS _policy_network, (SELECT generation::text FROM public_visibility_state WHERE singleton) AS _policy_generation, rp._representative_rank, rp._representative_id,` : ''} - ps.observer_node_ids, ps.observer_iatas, ps.rx_count, ps.tx_count + ps.observer_node_ids, ps.observer_iatas, ps.rx_count, ps.tx_count, + mt.tags, mt.confidence AS tag_confidence FROM recent_packets rp LEFT JOIN packet_stats ps ON ps.packet_hash = rp.packet_hash + LEFT JOIN message_tags mt ON mt.packet_hash = rp.packet_hash ${overlapParam ? `WHERE rp.packet_hash = ANY(${overlapParam}::text[]) OR rp.packet_hash IN (SELECT packet_hash FROM recent_packets ORDER BY time DESC, packet_hash LIMIT $1)` : ''} ORDER BY rp.time DESC, rp.packet_hash @@ -960,9 +978,11 @@ export async function getRecentMessages( ${includeObservationIds ? `ms._observation_ids, m.network AS _policy_network, (SELECT generation::text FROM public_visibility_state WHERE singleton) AS _policy_generation, m._representative_rank, m._representative_id,` : ''} - ms.observer_node_ids, ms.observer_iatas, ms.rx_count, ms.tx_count + ms.observer_node_ids, ms.observer_iatas, ms.rx_count, ms.tx_count, + mt.tags, mt.confidence AS tag_confidence FROM recent_msgs m LEFT JOIN msg_stats ms ON ms.packet_hash = m.packet_hash + LEFT JOIN message_tags mt ON mt.packet_hash = m.packet_hash ${overlapParam ? `WHERE m.packet_hash = ANY(${overlapParam}::text[]) OR m.packet_hash IN (SELECT packet_hash FROM recent_msgs ORDER BY time DESC, packet_hash LIMIT $1)` : ''} ORDER BY m.time DESC, m.packet_hash @@ -973,6 +993,32 @@ export async function getRecentMessages( return res.rows; } +/** + * Recent message tags for live feed enrichment. Tags are produced by the + * tagger-worker a few seconds after a packet arrives, so the live WS feed can + * never carry them inline; the frontend polls this and patches rows by hash. + * Only tags whose packet is publicly visible and in scope are returned. + */ +export async function getRecentMessageTags(limit = 200, network?: string) { + const scope = buildScopePlaceholders(2, network); + const res = await query( + `SELECT mt.packet_hash, mt.tags, mt.confidence, mt.tagged_at + FROM message_tags mt + WHERE mt.tagged_at > NOW() - INTERVAL '24 hours' + AND EXISTS ( + SELECT 1 FROM packets p + WHERE p.packet_hash = mt.packet_hash + AND p.time > NOW() - INTERVAL '24 hours' + ${buildPacketScopeClause(scope, 'p', network)} + ${buildPublicPacketPrivacyClause('p')} + ) + ORDER BY mt.tagged_at DESC + LIMIT $1`, + [limit, ...scope.params], + ); + return res.rows; +} + /** * Fetch one channel's decrypted GroupText history. The WebSocket snapshot stays * intentionally recent and bounded; this demand-driven read reaches back 90 @@ -1082,8 +1128,10 @@ export async function getChannelMessageHistory( CASE WHEN m.rx_node_id IS NOT NULL THEN ARRAY[m.rx_node_id]::text[] ELSE ARRAY[]::text[] END AS observer_node_ids, CASE WHEN NULLIF(TRIM(m.iata), '') IS NOT NULL THEN ARRAY[m.iata]::text[] ELSE ARRAY[]::text[] END AS observer_iatas, CASE WHEN COALESCE(m.payload->>'direction', 'rx') <> 'tx' THEN 1 ELSE 0 END::int AS rx_count, - CASE WHEN COALESCE(m.payload->>'direction', 'rx') = 'tx' THEN 1 ELSE 0 END::int AS tx_count + CASE WHEN COALESCE(m.payload->>'direction', 'rx') = 'tx' THEN 1 ELSE 0 END::int AS tx_count, + mt.tags, mt.confidence AS tag_confidence FROM channel_candidates m + LEFT JOIN message_tags mt ON mt.packet_hash = m.packet_hash WHERE 1 = 1 ${buildPublicPacketPrivacyClause('m')} ORDER BY m.time DESC`, @@ -1100,9 +1148,14 @@ export async function getChannelMessageHistory( }); } -export async function getRecentPacketEvents(limit = 200, network?: string, observer?: string) { +export async function getRecentPacketEvents(limit = 200, network?: string, observer?: string, regionScope?: string) { const scope = buildScopePlaceholders(2, network, observer); const params: unknown[] = [limit, ...scope.params]; + let regionClause = ''; + if (regionScope) { + params.push(regionScope); + regionClause = `AND p.region_scope = $${params.length}`; + } const res = await pool.query( `SELECT p.time, p.packet_hash, @@ -1111,7 +1164,8 @@ export async function getRecentPacketEvents(limit = 200, network?: string, obser p.topic, p.iata, p.packet_type, p.hop_count, p.rssi, p.snr, p.payload, p.payload->>'_summary' AS summary, - p.advert_count, p.path_hashes, p.path_hash_size_bytes + p.advert_count, p.path_hashes, p.path_hash_size_bytes, + p.region_scope FROM packets p LEFT JOIN node_identity_aliases rx_alias ON rx_alias.source_node_id = UPPER(BTRIM(p.rx_node_id)) @@ -1119,6 +1173,7 @@ export async function getRecentPacketEvents(limit = 200, network?: string, obser ON src_alias.source_node_id = UPPER(BTRIM(p.src_node_id)) WHERE p.time > NOW() - INTERVAL '24 hours' ${buildPacketScopeClause(scope, 'p', network)} + ${regionClause} ${buildPublicPacketPrivacyClause('p')} ORDER BY p.time DESC LIMIT $1`, @@ -1133,8 +1188,10 @@ export async function getPacketDetail(hash: string, network = 'ukmesh') { pool.query( `SELECT p.time, p.packet_hash, p.rx_node_id, p.src_node_id, p.topic, p.iata, p.packet_type, p.route_type, p.hop_count, p.rssi, p.snr, - p.payload, p.path_hashes, p.path_hash_size_bytes, p.raw_hex + p.payload, p.path_hashes, p.path_hash_size_bytes, p.raw_hex, + mt.tags, mt.confidence AS tag_confidence FROM packets p + LEFT JOIN message_tags mt ON mt.packet_hash = p.packet_hash WHERE p.packet_hash = $1 ${buildPacketScopeClause(scope, 'p', network)} ${buildPublicPacketPrivacyClause('p')} @@ -1176,6 +1233,8 @@ export async function getPacketDetail(hash: string, network = 'ukmesh') { pathHashes: row.path_hashes as string[] | null, pathHashSizeBytes: row.path_hash_size_bytes as number | null, rawHex: row.raw_hex as string | null, + tags: (row.tags as Record | null) ?? null, + tagConfidence: (row.tag_confidence as Record | null) ?? null, observations: observations.rows.map((r) => ({ rxNodeId: r.rx_node_id as string | null, iata: r.iata as string | null, diff --git a/backend/src/db/migrations/055_public_feed.sql b/backend/src/db/migrations/055_public_feed.sql new file mode 100644 index 0000000..3869aa1 --- /dev/null +++ b/backend/src/db/migrations/055_public_feed.sql @@ -0,0 +1,50 @@ +-- Account-level opt-in for the aggregate public MeshCore MQTT feed. +-- Ownership is stored as a materialised mapping because the owner-auth +-- database is intentionally separate from the analytics database. The +-- backend refreshes this mapping from verified owner grants periodically. + +CREATE TABLE IF NOT EXISTS owner_public_feed_settings ( + owner_username TEXT PRIMARY KEY CHECK (char_length(owner_username) BETWEEN 1 AND 128), + enabled BOOLEAN NOT NULL DEFAULT FALSE, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE TABLE IF NOT EXISTS owner_public_feed_nodes ( + owner_username TEXT NOT NULL REFERENCES owner_public_feed_settings(owner_username) ON DELETE CASCADE, + node_id TEXT NOT NULL CHECK (node_id ~ '^[0-9A-F]{64}$'), + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + PRIMARY KEY (owner_username, node_id) +); + +CREATE INDEX IF NOT EXISTS owner_public_feed_nodes_node_idx + ON owner_public_feed_nodes (node_id, owner_username); + +CREATE TABLE IF NOT EXISTS public_feed_deliveries ( + id BIGSERIAL PRIMARY KEY, + node_id TEXT NOT NULL CHECK (node_id ~ '^[0-9A-F]{64}$'), + event_key TEXT NOT NULL UNIQUE, + packet_hash TEXT NOT NULL, + source_topic TEXT NOT NULL, + destination_topic TEXT NOT NULL, + payload JSONB NOT NULL, + status TEXT NOT NULL DEFAULT 'pending' + CHECK (status IN ('pending', 'delivering', 'succeeded', 'failed', 'dead_lettered')), + attempts INTEGER NOT NULL DEFAULT 0 CHECK (attempts >= 0 AND attempts <= 5), + next_attempt_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + last_attempt_at TIMESTAMPTZ, + delivered_at TIMESTAMPTZ, + last_error TEXT, + claim_token TEXT, + claim_expires_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS public_feed_deliveries_due_idx + ON public_feed_deliveries (next_attempt_at, id) + WHERE status IN ('pending', 'failed') AND attempts < 5; + +CREATE INDEX IF NOT EXISTS public_feed_deliveries_retention_idx + ON public_feed_deliveries (created_at, id) + WHERE status IN ('succeeded', 'dead_lettered'); diff --git a/backend/src/db/migrations/060_region_scope_participation.sql b/backend/src/db/migrations/060_region_scope_participation.sql new file mode 100644 index 0000000..5d69495 --- /dev/null +++ b/backend/src/db/migrations/060_region_scope_participation.sql @@ -0,0 +1,53 @@ +-- 057: region-scope participation tables. +-- +-- Derived data owned solely by the regions:rebuild tool +-- (src/tools/rebuildRegionParticipation.ts), which refreshes all four tables +-- wholesale inside one transaction. This migration only owns the schema and is +-- intentionally additive: no existing table is touched. +-- +-- Region names are public hashtag regions (#name) whose transport key is +-- SHA256("#name")[0:16]; attribution is a candidate-name test against each +-- scoped packet's transport code. Names are stored without the leading '#'. +-- A node is 'green' when it was observed relaying cryptographically scoped +-- traffic and 'red' when only unscoped relay evidence (or none) exists. + +CREATE TABLE IF NOT EXISTS region_scope_stats ( + region TEXT PRIMARY KEY CHECK (char_length(region) BETWEEN 1 AND 64), + packets_total INTEGER NOT NULL DEFAULT 0 CHECK (packets_total >= 0), + member_count INTEGER NOT NULL DEFAULT 0 CHECK (member_count >= 0), + repeater_member_count INTEGER NOT NULL DEFAULT 0 CHECK (repeater_member_count >= 0), + first_seen TIMESTAMPTZ, + last_seen TIMESTAMPTZ +); + +CREATE TABLE IF NOT EXISTS region_scope_members ( + region TEXT NOT NULL REFERENCES region_scope_stats(region) ON DELETE CASCADE, + node_id TEXT NOT NULL CHECK (node_id ~ '^[0-9A-F]{64}$'), + packets INTEGER NOT NULL DEFAULT 0 CHECK (packets >= 0), + PRIMARY KEY (region, node_id) +); + +CREATE INDEX IF NOT EXISTS region_scope_members_node_idx + ON region_scope_members (node_id); + +CREATE TABLE IF NOT EXISTS repeater_scope_status ( + node_id TEXT PRIMARY KEY CHECK (node_id ~ '^[0-9A-F]{64}$'), + status TEXT NOT NULL CHECK (status IN ('green', 'red')), + evidence TEXT NOT NULL CHECK (evidence IN ('scoped', 'unscoped-only', 'none')), + scoped_packets INTEGER NOT NULL DEFAULT 0 CHECK (scoped_packets >= 0), + unscoped_packets_7d INTEGER NOT NULL DEFAULT 0 CHECK (unscoped_packets_7d >= 0), + regions JSONB NOT NULL DEFAULT '{}'::jsonb +); + +CREATE INDEX IF NOT EXISTS repeater_scope_status_status_idx + ON repeater_scope_status (status); + +-- Single-row run record: when the tool last refreshed the tables and over what +-- window. API responses surface this as generatedAt / windowDays. +CREATE TABLE IF NOT EXISTS region_scope_run ( + id BOOLEAN PRIMARY KEY DEFAULT TRUE CHECK (id), + finished_at TIMESTAMPTZ NOT NULL, + window_days INTEGER NOT NULL CHECK (window_days BETWEEN 1 AND 365), + scoped_packets INTEGER NOT NULL CHECK (scoped_packets >= 0), + decoded_packets INTEGER NOT NULL CHECK (decoded_packets >= 0) +); diff --git a/backend/src/db/migrations/061_packet_share_owner_scope_and_jwt.sql b/backend/src/db/migrations/061_packet_share_owner_scope_and_jwt.sql new file mode 100644 index 0000000..ad6bea0 --- /dev/null +++ b/backend/src/db/migrations/061_packet_share_owner_scope_and_jwt.sql @@ -0,0 +1,10 @@ +-- NULL scopes preserve existing destinations available to every owner. +ALTER TABLE owner_packet_share_destinations + ADD COLUMN allowed_owner_username TEXT CHECK (char_length(allowed_owner_username) BETWEEN 1 AND 128), + ADD COLUMN allowed_node_id TEXT CHECK (allowed_node_id ~ '^[0-9A-F]{64}$'), + ADD COLUMN auth_mode TEXT NOT NULL DEFAULT 'password' CHECK (auth_mode IN ('password', 'meshcore_jwt')), + ADD COLUMN auth_audience TEXT CHECK (char_length(auth_audience) BETWEEN 1 AND 253), + ADD COLUMN tx_mode TEXT NOT NULL DEFAULT 'on' CHECK (tx_mode IN ('on', 'advert')); + +-- Provision restricted destinations only after deploying the scoped backend. +-- The preceding backend lists all rows and cannot enforce these scopes. diff --git a/backend/src/db/nodeHistory.test.ts b/backend/src/db/nodeHistory.test.ts new file mode 100644 index 0000000..122f23e --- /dev/null +++ b/backend/src/db/nodeHistory.test.ts @@ -0,0 +1,37 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import pg from 'pg'; +import { getNodeHistory } from './index.js'; + +test('history retains its DTO, indexed alias predicates, time cap and fail-closed privacy in both directions', async (t) => { + const rows = [{ time: '2026-09-07T12:00:00Z', packet_hash: '1234', src_node_id: 'B'.repeat(64), + topic: 'ukmesh/LHR/observer/packets', packet_type: 4, hop_count: 1, rssi: -99, snr: 3, payload: {} }]; + let calls = 0; + t.mock.method(pg.Pool.prototype, 'query', async (sql: string, params: unknown[]) => { + calls += 1; + assert.match(sql, /WHERE p.rx_node_id = requested.node_id/); + assert.match(sql, /WHERE p.src_node_id = requested.node_id/); + assert.match(sql, /FROM unnest\(ARRAY\(/); + assert.match(sql, /UNION ALL/); + assert.match(sql, /NOT COALESCE\(p.rx_node_id = ANY\(ARRAY\(/); + assert.doesNotMatch(sql, /(?:UPPER|BTRIM)\(p\.(?:rx|src)_node_id\)\s*=\s*ANY/); + assert.match(sql, /canonical_node_id = alias.canonical_node_id/); + assert.match(sql, /p.time > NOW\(\) - INTERVAL '1 hour' \* \$2/); + assert.match(sql, /p.time <= NOW\(\)/); + assert.match(sql, /p.network = ANY\(\$3\)/); + assert.match(sql, /meshcore-test/); + assert.match(sql, /p.visibility_ok IS TRUE/); + assert.match(sql, /p.is_private IS NOT TRUE/); + assert.match(sql, /cached_visibility.visibility_generation = current_visibility.generation/); + assert.match(sql, /ORDER BY time DESC LIMIT 500/); + assert.deepEqual(params, ['A'.repeat(64), 24, ['ukmesh', 'northeast', 'teesside']]); + return { rows }; + }); + assert.equal(await getNodeHistory('A'.repeat(64), 24, 'ukmesh'), rows); + assert.equal(calls, 1); +}); + +test('history returns an empty array unchanged', async (t) => { + t.mock.method(pg.Pool.prototype, 'query', async () => ({ rows: [] })); + assert.deepEqual(await getNodeHistory('0'.repeat(64)), []); +}); diff --git a/backend/src/db/ownerAuth.ts b/backend/src/db/ownerAuth.ts index e4b1050..23f0d46 100644 --- a/backend/src/db/ownerAuth.ts +++ b/backend/src/db/ownerAuth.ts @@ -3,7 +3,6 @@ import fs from 'node:fs'; import { randomUUID } from 'node:crypto'; import { resolveDbAssetPath } from './assets.js'; import { boundedIntegerSetting } from '../platform/config/boundedNumber.js'; -import { planOperatorConfiguredOwnerGrantSync } from '../owner/ownerGrantReconciliation.js'; const { Pool } = pg; const OWNER_DB_NAME = process.env['OWNER_POSTGRES_DB'] ?? 'meshcore_owner_auth'; @@ -347,43 +346,35 @@ export async function syncOperatorConfiguredOwnerGrants( generation: string, ): Promise { const normalized = grants.map((grant) => normalizeGrant(grant.mqttUsername, grant.nodeId)); + const desiredKeys = new Set(normalized.map((grant) => `${grant.mqttUsername}\0${grant.nodeId}`)); const current = await ownerPool.query<{ mqtt_username: string; node_id: string; - revoked_at: string | Date | null; + revoked_at: string | null; verification_method: string | null; grant_generation: string | null; - updated_at: string | Date | null; }>( - `SELECT mqtt_username, node_id, revoked_at, verification_method, grant_generation, updated_at + `SELECT mqtt_username, node_id, revoked_at, verification_method, grant_generation FROM owner_account_nodes`, ); - const actions = planOperatorConfiguredOwnerGrantSync( - normalized, - current.rows.map((row) => ({ - mqttUsername: row.mqtt_username, - nodeId: row.node_id, - revokedAt: row.revoked_at, - verificationMethod: row.verification_method, - grantGeneration: row.grant_generation, - updatedAt: row.updated_at, - })), - generation, - ); - - for (const action of actions) { - if (action.type === 'upsert') { - await upsertVerifiedOwnerGrant(action.grant.mqttUsername, action.grant.nodeId, { - verificationMethod: 'operator-config', - actor: 'config-reconciler', - reason: action.reauthorize ? 'restored from operator configuration' : undefined, - generation, - reauthorize: action.reauthorize, - }); - continue; - } - await revokeOwnerGrant(action.grant.mqttUsername, action.grant.nodeId, { + for (const grant of normalized) { + const row = current.rows.find((candidate) => + candidate.mqtt_username === grant.mqttUsername && candidate.node_id === grant.nodeId); + if (row?.revoked_at) continue; + if (row?.verification_method === 'operator-database') continue; + if (row?.verification_method === 'operator-config' && row.grant_generation === generation) continue; + await upsertVerifiedOwnerGrant(grant.mqttUsername, grant.nodeId, { + verificationMethod: 'operator-config', + actor: 'config-reconciler', + generation, + }); + } + for (const row of current.rows) { + if (row.verification_method !== 'operator-config' + || row.revoked_at + || desiredKeys.has(`${row.mqtt_username}\0${row.node_id}`)) continue; + await revokeOwnerGrant(row.mqtt_username, row.node_id, { actor: 'config-reconciler', reason: 'removed from operator configuration', generation, diff --git a/backend/src/db/packetBatch.integration.test.ts b/backend/src/db/packetBatch.integration.test.ts index a8221c5..2682516 100644 --- a/backend/src/db/packetBatch.integration.test.ts +++ b/backend/src/db/packetBatch.integration.test.ts @@ -136,10 +136,13 @@ test('base schema and every migration apply to a brand-new database', { '052_owner_packet_sharing.sql', '053_owner_packet_share_destination_metadata.sql', '054_owner_packet_share_default_off.sql', + '055_public_feed.sql', '056_async_privacy_rematerialization.sql', '057_node_status_sample_status.sql', '058_ingest_observation_ledger.sql', '059_ingest_ledger_historical_coverage.sql', + '060_region_scope_participation.sql', + '061_packet_share_owner_scope_and_jwt.sql', ]); assert.deepEqual(await runMigrations(freshPool), []); diff --git a/backend/src/db/schema/base.sql b/backend/src/db/schema/base.sql index bceaa4f..850b80d 100644 --- a/backend/src/db/schema/base.sql +++ b/backend/src/db/schema/base.sql @@ -1,5 +1,5 @@ -- MeshCore Analytics — Database Schema --- TimescaleDB base schema; retention is managed by versioned lifecycle policy. +-- TimescaleDB, no automatic data retention (all data kept indefinitely) CREATE EXTENSION IF NOT EXISTS timescaledb; @@ -61,7 +61,7 @@ ALTER TABLE nodes ADD COLUMN IF NOT EXISTS last_predicted_online_at TIMESTAMPTZ; ALTER TABLE nodes ADD COLUMN IF NOT EXISTS last_path_evidence_at TIMESTAMPTZ; ALTER TABLE nodes ADD COLUMN IF NOT EXISTS last_mqtt_observer_seen_at TIMESTAMPTZ; --- ─── Packets hypertable ────────────────────────────────────────────────── +-- ─── Packets hypertable (no retention — data kept indefinitely) ────────── CREATE TABLE IF NOT EXISTS packets ( time TIMESTAMPTZ NOT NULL DEFAULT NOW(), @@ -83,6 +83,23 @@ CREATE TABLE IF NOT EXISTS packets ( SELECT create_hypertable('packets', 'time', if_not_exists => TRUE); +-- Remove any existing retention policy so packets are kept indefinitely. +DO $$ +DECLARE + _job_id INTEGER; +BEGIN + SELECT job_id INTO _job_id + FROM timescaledb_information.jobs + WHERE proc_name = 'policy_retention' + AND config->>'hypertable_id' = ( + SELECT id::text FROM _timescaledb_catalog.hypertable WHERE table_name = 'packets' + ); + + IF _job_id IS NOT NULL THEN + PERFORM remove_retention_policy('packets'); + END IF; +END $$; + ALTER TABLE packets ADD COLUMN IF NOT EXISTS advert_count INTEGER; ALTER TABLE packets ADD COLUMN IF NOT EXISTS path_hashes TEXT[]; ALTER TABLE packets ADD COLUMN IF NOT EXISTS path_hash_size_bytes INTEGER; @@ -229,8 +246,7 @@ CREATE TABLE IF NOT EXISTS node_status_samples ( rx_air_secs BIGINT, channel_utilization DOUBLE PRECISION, air_util_tx DOUBLE PRECISION, - stats JSONB, - status TEXT + stats JSONB ); SELECT create_hypertable('node_status_samples', 'time', if_not_exists => TRUE); diff --git a/backend/src/health/status.ts b/backend/src/health/status.ts index 3f7993d..26f8504 100644 --- a/backend/src/health/status.ts +++ b/backend/src/health/status.ts @@ -50,7 +50,7 @@ type WorkerSnapshot = { }; export type RetentionTarget = { - table: 'packets' | 'worker_health_snapshots' | 'frontend_error_events' | 'owner_alert_deliveries' | 'operational_check_results' | 'observer_region_packet_sightings' | 'observer_region_observer_sightings' | 'link_job_commits' | 'ml_model_variant_packet_results' | 'packet_decryptions' | 'observer_registration_requests' | 'operator_audit_events'; + table: 'packets' | 'worker_health_snapshots' | 'frontend_error_events' | 'owner_alert_deliveries' | 'owner_packet_share_deliveries' | 'public_feed_deliveries' | 'operational_check_results' | 'observer_region_packet_sightings' | 'observer_region_observer_sightings' | 'link_job_commits' | 'ml_model_variant_packet_results' | 'packet_decryptions' | 'observer_registration_requests' | 'operator_audit_events' | 'node_status_samples'; timestampColumn: 'ts' | 'time' | 'created_at' | 'updated_at' | 'completed_at' | 'last_seen'; retention: string; batchSize: number; @@ -59,8 +59,9 @@ export type RetentionTarget = { export const RETENTION_TARGETS: readonly RetentionTarget[] = [ // Timescale drops complete chunks; this bounded boundary cleanup makes the - // packet-content 30-day privacy limit exact for the partial oldest chunk. + // listed 30-day privacy limits exact for the partial oldest chunk. { table: 'packets', timestampColumn: 'time', retention: '30 days', batchSize: 25_000 }, + { table: 'node_status_samples', timestampColumn: 'time', retention: '30 days', batchSize: 25_000 }, { table: 'worker_health_snapshots', timestampColumn: 'ts', retention: '7 days', batchSize: 10_000 }, { table: 'frontend_error_events', timestampColumn: 'time', retention: '30 days', batchSize: 2_000 }, { @@ -70,6 +71,20 @@ export const RETENTION_TARGETS: readonly RetentionTarget[] = [ batchSize: 1_000, extraPredicate: `(status = 'succeeded' OR attempts >= 5)`, }, + { + table: 'owner_packet_share_deliveries', + timestampColumn: 'created_at', + retention: '30 days', + batchSize: 2_000, + extraPredicate: `status IN ('succeeded', 'dead_lettered')`, + }, + { + table: 'public_feed_deliveries', + timestampColumn: 'created_at', + retention: '30 days', + batchSize: 2_000, + extraPredicate: `status IN ('succeeded', 'dead_lettered')`, + }, { table: 'operational_check_results', timestampColumn: 'ts', retention: '14 days', batchSize: 2_000 }, { table: 'observer_region_packet_sightings', timestampColumn: 'last_seen', retention: '8 days', batchSize: 25_000 }, { table: 'observer_region_observer_sightings', timestampColumn: 'last_seen', retention: '31 days', batchSize: 5_000 }, @@ -157,7 +172,7 @@ async function refreshCoreTelemetryCapacityMetrics(): Promise { WITH lifecycle_targets (table_name, retention, compress_after) AS ( VALUES ('packets', INTERVAL '30 days', INTERVAL '15 days'), - ('node_status_samples', INTERVAL '180 days', INTERVAL '15 days'), + ('node_status_samples', INTERVAL '30 days', INTERVAL '15 days'), ('node_neighbor_samples', INTERVAL '7 days', INTERVAL '2 days') ) SELECT target.table_name, diff --git a/backend/src/index.ts b/backend/src/index.ts index e0f24a0..b3deceb 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -9,9 +9,10 @@ import { closeOwnerAuthDb, getOwnerAclReadiness, getOwnerAuthorizationInventory, + getOwnerAuthorizationSnapshot, initOwnerAuthDb, } from './db/ownerAuth.js'; -import { getMqttRuntimeStatus, startMqttClient, stopMqttClient, onPacket, onNodeSeen, onNodeUpsert } from './mqtt/client.js'; +import { getMqttRuntimeStatus, publishMqtt, startMqttClient, stopMqttClient, onPacket, onNodeSeen, onNodeUpsert } from './mqtt/client.js'; import { startRamWriter, closeRamWriter } from './ram/writerRuntime.js'; import { getRamIngestMode } from './ram/protocol.js'; import { @@ -20,7 +21,7 @@ import { } from './mqtt/connectionMonitor.js'; import { initWebSocketServer, closeWebSocketServer, broadcastPacket, broadcastNodeUpdate, broadcastNodeUpsert } from './ws/server.js'; import { createPublicRamSource } from './ram/publicSource.js'; -import apiRoutes, { startOwnerLastHopPrewarm, stopOwnerLastHopPrewarm } from './api/routes.js'; +import apiRoutes from './api/routes.js'; import { startRegisteredStatsWarmup } from './api/routes/stats.js'; import { initSpamMessageAnalyzer, stopSpamMessageAnalyzer } from './spam/analyzer.js'; import { @@ -31,6 +32,8 @@ import { import { createBackendSiteRoutes } from './backend-site/routes.js'; import { isTrustedProxyPeer } from './http/trustedProxy.js'; import { startOwnerAuthorizationReconciler, stopOwnerAuthorizationReconciler } from './owner/ownerAclReconciler.js'; +import { createOwnerLastHopPrewarm } from './owner/ownerLastHopPrewarm.js'; +import { getRegisteredOwnerService } from './api/routes/owner.js'; import { getAnalysisWorkloadStates } from './analysis/runState.js'; import { applySecurityHeaders } from './security/operatorAuth.js'; import { closeMetricsServer, startMetricsServer } from './metricsServer.js'; @@ -55,6 +58,8 @@ import { readAclFile } from './mqtt/aclManager.js'; import { parseOwnerGrantConfig } from './owner/ownerGrantConfig.js'; import { createPacketShareForwarder } from './owner/packetShareForwarder.js'; import { createPacketShareRepository } from './owner/packetShareRepository.js'; +import { createPublicFeedForwarder } from './owner/publicFeedForwarder.js'; +import { createPublicFeedRepository } from './owner/publicFeedRepository.js'; import { buildOwnerInventoryBaseline, loadOwnerInventoryBaseline, @@ -78,10 +83,23 @@ const SHUTDOWN_DEADLINE_MS = Math.min( Math.max(1_000, Number(process.env['SHUTDOWN_DEADLINE_MS'] ?? 30_000) || 30_000), ); const NODE_IDENTITY_REFRESH_INTERVAL_MS = 30 * 60_000; +const OWNER_LAST_HOP_PREWARM_INTERVAL_MS = Number(process.env['OWNER_LAST_HOP_PREWARM_INTERVAL_MS'] ?? 15 * 60_000); +const OWNER_LAST_HOP_PREWARM_NODE_DELAY_MS = Number(process.env['OWNER_LAST_HOP_PREWARM_NODE_DELAY_MS'] ?? 150); const lifecycle = new LifecycleCoordinator(SHUTDOWN_DEADLINE_MS); const packetShareForwarder = createPacketShareForwarder({ repository: createPacketShareRepository(query), }); +const publicFeedForwarder = createPublicFeedForwarder({ + repository: createPublicFeedRepository(query), + getOwnerAuthorizationSnapshot, + // The public namespace is on the same authenticated Mosquitto instance as + // ingest. Reusing this connection avoids a second credentialed publisher + // and keeps the public namespace behind the backend ACL only. + publisherFactory: () => ({ + publish: publishMqtt, + close: async () => undefined, + }), +}); let shutdownExitCode = 0; let shutdownDeadlineAt = 0; function shutdownTimeLeft(): number { @@ -89,6 +107,7 @@ function shutdownTimeLeft(): number { } let forceCloseHttpConnections = () => {}; let nodeIdentityRefreshTimer: NodeJS.Timeout | null = null; +let ownerLastHopPrewarmStop: (() => void) | null = null; lifecycle.register({ name: 'queue-admission', @@ -113,7 +132,10 @@ lifecycle.register({ lifecycle.register({ name: 'owner-last-hop-prewarm', stage: 10, - close: stopOwnerLastHopPrewarm, + close: () => { + ownerLastHopPrewarmStop?.(); + ownerLastHopPrewarmStop = null; + }, }); lifecycle.register({ name: 'mqtt-ingest', @@ -140,6 +162,11 @@ lifecycle.register({ stage: 30, close: packetShareForwarder.stop, }); +lifecycle.register({ + name: 'public-feed-forwarder', + stage: 30, + close: publicFeedForwarder.stop, +}); lifecycle.register({ name: 'operator-operations', stage: 30, @@ -222,7 +249,21 @@ async function main() { startRegisteredStatsWarmup(); await initOwnerAuthDb(); await startOwnerAuthorizationReconciler(); - startOwnerLastHopPrewarm(); + // Keep the owner dashboard RX-strength cache warm for every active MQTT owner + // so the panel (and its 7d/30d toggle) never pays for a cold scan on open. + const ownerService = getRegisteredOwnerService(); + if (ownerService) { + ownerLastHopPrewarmStop = createOwnerLastHopPrewarm({ + listOwnerNodes: getOwnerAuthorizationSnapshot, + refreshLastHop: (ownedNodeIds, nodeId, windowDays, minAgeMs) => ( + ownerService.refreshOwnerLastHopStrength(ownedNodeIds, nodeId, windowDays, minAgeMs) + ), + intervalMs: OWNER_LAST_HOP_PREWARM_INTERVAL_MS, + nodeDelayMs: OWNER_LAST_HOP_PREWARM_NODE_DELAY_MS, + }); + } else { + console.warn('[owner-last-hop-prewarm] owner service is not registered; prewarm disabled'); + } // 2. Start the audit-only MQTT connection monitor. if (MQTT_INGEST_ENABLED) { @@ -238,6 +279,9 @@ async function main() { void packetShareForwarder.enqueuePacket(packet, upstreamEnvelope).catch((error: unknown) => { console.error('[packet-share] packet handling failed:', error instanceof Error ? error.message : error); }); + void publicFeedForwarder.enqueuePacket(packet, upstreamEnvelope).catch((error: unknown) => { + console.error('[public-feed] packet handling failed:', error instanceof Error ? error.message : error); + }); } // RAM mode defers commit-dependent link admission to the writer's // post-commit hook (the link worker reads packet rows from PostgreSQL). @@ -442,6 +486,7 @@ async function main() { startMetricsServer(METRICS_PORT); packetShareForwarder.start(); + publicFeedForwarder.start(); if (getRamIngestMode() === 'ram') { void (async () => { let delayMs = 1_000; diff --git a/backend/src/metrics.ts b/backend/src/metrics.ts index 406a34e..cef7dab 100644 --- a/backend/src/metrics.ts +++ b/backend/src/metrics.ts @@ -130,6 +130,12 @@ export const mqttIngestOutcomesTotal = new Counter({ registers: [metricsRegistry], }); +export const mqttIngestQueueDepth = new Gauge({ + name: 'meshcore_mqtt_ingest_queue_depth', + help: 'MQTT messages waiting in the bounded ingest queue.', + registers: [metricsRegistry], +}); + export const mqttIngestActive = new Gauge({ name: 'meshcore_mqtt_ingest_active', help: 'MQTT messages currently being processed before acknowledgement.', @@ -149,6 +155,19 @@ export const packetShareQueueDepth = new Gauge({ registers: [metricsRegistry], }); +export const publicFeedOutcomesTotal = new Counter({ + name: 'meshcore_public_feed_outcomes_total', + help: 'Public MeshCore feed decisions and delivery outcomes.', + labelNames: ['outcome'] as const, + registers: [metricsRegistry], +}); + +export const publicFeedQueueDepth = new Gauge({ + name: 'meshcore_public_feed_queue_depth', + help: 'Public MeshCore feed deliveries currently claimed for processing.', + registers: [metricsRegistry], +}); + export const dbQueriesTotal = new Counter({ name: 'meshcore_db_queries_total', help: 'Database queries by pool and bounded outcome.', diff --git a/backend/src/mqtt/aclManager.test.ts b/backend/src/mqtt/aclManager.test.ts index adbcc57..07ba821 100644 --- a/backend/src/mqtt/aclManager.test.ts +++ b/backend/src/mqtt/aclManager.test.ts @@ -128,6 +128,60 @@ test('renderer grants only bounded broker uptime reads to the configured monitor ); }); +test('public-feed opt-in adds one curated read filter without granting public publish', () => { + const rendered = renderOwnerAcl('', [ + { + mqttUsername: 'active-owner', + nodeIds: [NODE_ID], + publicFeedRead: true, + }, + ], []); + + assert.match(rendered.content, /user active-owner\ntopic read public\/\+\/\+\/packets/); + assert.doesNotMatch(rendered.content, /topic read meshcore\/\+\/\+\/packets/); + assert.doesNotMatch(rendered.content, /topic (?:write|readwrite) public\/\+\/\+\/packets/); + assert.deepEqual(rendered.semantic, [{ + mqttUsername: 'active-owner', + nodeIds: [NODE_ID], + publicFeedRead: true, + }]); + validateRenderedOwnerAcl(rendered.content, rendered); +}); + +test('an explicit read-only subscriber replaces a legacy aggregate wildcard', () => { + const rendered = renderOwnerAcl( + 'user soulway\ntopic read meshcore/+/+/packets\n', + [{ + mqttUsername: 'soulway', + nodeIds: [], + publicFeedRead: true, + }], + ['soulway'], + ['soulway'], + ); + + assert.match(rendered.content, /user soulway\ntopic read public\/\+\/\+\/packets/); + assert.doesNotMatch(rendered.content, /topic read meshcore\/\+\/\+\/packets/); + assert.doesNotMatch(rendered.content, /topic write|topic readwrite/); + validateRenderedOwnerAcl(rendered.content, rendered); +}); + +test('only the retained backend account receives the public namespace write permission', () => { + const rendered = renderOwnerAcl( + 'user backend\ntopic readwrite meshcore/#\n', + [{ mqttUsername: 'soulway', nodeIds: [], publicFeedRead: true }], + ['backend'], + ['soulway'], + ['backend'], + ['public/#'], + ); + + assert.match(rendered.content, /user backend[\s\S]*topic readwrite public\/#/); + assert.match(rendered.content, /user soulway\ntopic read public\/\+\/\+\/packets/); + assert.doesNotMatch(rendered.content, /user soulway[\s\S]*topic (?:write|readwrite) public\/#/); + validateRenderedOwnerAcl(rendered.content, rendered); +}); + test('cutover validation blocks empty managed accounts unless explicitly reviewed', () => { const blocked = renderOwnerAcl('', [{ mqttUsername: 'revoked', nodeIds: [] }], []); assert.equal(blocked.validation.ok, false); diff --git a/backend/src/mqtt/aclManager.ts b/backend/src/mqtt/aclManager.ts index a6334d7..b25c980 100644 --- a/backend/src/mqtt/aclManager.ts +++ b/backend/src/mqtt/aclManager.ts @@ -3,7 +3,7 @@ import path from 'node:path'; import { createHash } from 'node:crypto'; import { ownerAclReloadTotal } from '../metrics.js'; -export const OWNER_ACL_RENDERER_VERSION = 'meshcore-owner-acl/v2-sys-read'; +export const OWNER_ACL_RENDERER_VERSION = 'meshcore-owner-acl/v5-public-feed-namespace'; const MANAGED_BEGIN = '# BEGIN MESHCORE OWNER ACL'; const MANAGED_END = '# END MESHCORE OWNER ACL'; const NODE_ID_RE = /^[0-9A-F]{64}$/; @@ -11,6 +11,8 @@ const NODE_ID_RE = /^[0-9A-F]{64}$/; export type OwnerAclGrant = { mqttUsername: string; nodeIds: string[]; + /** Grants read access to the curated public-feed namespace. */ + publicFeedRead?: boolean; }; export type AclDirectiveClass = @@ -40,6 +42,7 @@ export type OwnerAclRenderResult = { content: string; semantic: OwnerAclGrant[]; systemReadUsers: string[]; + systemReadwriteTopics: string[]; validation: { ok: boolean; ambiguousUsers: string[]; @@ -60,6 +63,13 @@ function normalizeNodeIds(nodeIds: string[]): string[] { return Array.from(new Set(nodeIds.map((value) => value.trim().toUpperCase()).filter((value) => NODE_ID_RE.test(value)))).sort(); } +function normalizeAclTopics(topics: Iterable): string[] { + return Array.from(new Set([...topics].map((value) => value.trim()).filter(Boolean).map((topic) => { + if (/\s|[\u0000\r\n]/.test(topic)) throw new Error(`INVALID_ACL_TOPIC:${topic}`); + return topic; + }))).sort(); +} + function stripManagedSection(content: string): { content: string; found: boolean; errors: string[] } { const lines = content.replace(/\r\n/g, '\n').split('\n'); const output: string[] = []; @@ -142,6 +152,9 @@ function buildManagedSection( for (const grant of grants) { lines.push(`user ${grant.mqttUsername}`); if (systemReadUsers.has(grant.mqttUsername)) lines.push('topic read $SYS/broker/uptime'); + if (grant.publicFeedRead) { + lines.push('topic read public/+/+/packets'); + } for (const nodeId of grant.nodeIds) { lines.push(`topic write meshcore/+/${nodeId}/packets`); lines.push(`topic write meshcore/+/${nodeId}/status`); @@ -168,17 +181,23 @@ export function renderOwnerAcl( unmanagedUsers: Iterable, allowedEmptyUsers: Iterable = [], systemReadUsers: Iterable = [], + systemReadwriteTopics: Iterable = [], ): OwnerAclRenderResult { const normalized = grants.map((grant) => ({ mqttUsername: normalizeUsername(grant.mqttUsername), nodeIds: normalizeNodeIds(grant.nodeIds), + ...(grant.publicFeedRead ? { + publicFeedRead: true, + } : {}), })); // An explicit operator grant is the declaration that an account has become // managed. This permits a staged owner to remain in the unmanaged list // until its grant is reviewed, without requiring a live .env edit alongside // the grant. const configuredUsernames = new Set( - normalized.filter((grant) => grant.nodeIds.length > 0).map((grant) => grant.mqttUsername), + normalized + .filter((grant) => grant.nodeIds.length > 0 || grant.publicFeedRead) + .map((grant) => grant.mqttUsername), ); const unmanaged = new Set( [...unmanagedUsers] @@ -194,12 +213,14 @@ export function renderOwnerAcl( [...systemReadUsers].map((value) => normalizeUsername(value)), )).sort(); const systemReadUserSet = new Set(normalizedSystemReadUsers); + const normalizedSystemReadwriteTopics = normalizeAclTopics(systemReadwriteTopics); const generation = createHash('sha256') .update(JSON.stringify({ renderer: OWNER_ACL_RENDERER_VERSION, grants: sortedNormalized, systemReadUsers: normalizedSystemReadUsers, + systemReadwriteTopics: normalizedSystemReadwriteTopics, })) .digest('hex'); const stripped = stripManagedSection(existingContent); @@ -233,6 +254,13 @@ export function renderOwnerAcl( ) { retainedLines.push('topic read $SYS/broker/uptime'); } + if (systemReadUserSet.has(stanza.username)) { + for (const topic of normalizedSystemReadwriteTopics) { + if (!retainedLines.some((line) => line.trim() === `topic readwrite ${topic}`)) { + retainedLines.push(`topic readwrite ${topic}`); + } + } + } output.push(...retainedLines); while (output.at(-1)?.trim() === '') output.pop(); } @@ -247,6 +275,7 @@ export function renderOwnerAcl( content, semantic: sortedNormalized, systemReadUsers: normalizedSystemReadUsers, + systemReadwriteTopics: normalizedSystemReadwriteTopics, validation: { ok: parsed.errors.length === 0 && parsed.duplicateUsers.length === 0 @@ -270,6 +299,12 @@ export function validateRenderedOwnerAcl(content: string, expected: OwnerAclRend throw new Error('OWNER_ACL_MANAGED_MARKERS_MISSING'); } for (const grant of expected.semantic) { + const stanza = parseAcl(content).stanzas.find((candidate) => candidate.username === grant.mqttUsername); + if (grant.publicFeedRead) { + if (!stanza?.directives.some((directive) => directive.line === 'topic read public/+/+/packets')) { + throw new Error(`OWNER_ACL_PUBLIC_FEED_READ_MISMATCH:${grant.mqttUsername}`); + } + } for (const nodeId of grant.nodeIds) { if (!content.includes(`topic write meshcore/+/${nodeId}/packets`) || !content.includes(`topic write meshcore/+/${nodeId}/status`) @@ -289,6 +324,14 @@ export function validateRenderedOwnerAcl(content: string, expected: OwnerAclRend throw new Error(`OWNER_ACL_SYSTEM_READ_MISMATCH:${username}`); } } + for (const username of expected.systemReadUsers) { + const stanza = parseAcl(content).stanzas.find((candidate) => candidate.username === username); + for (const topic of expected.systemReadwriteTopics) { + if (!stanza?.directives.some((directive) => directive.line === `topic readwrite ${topic}`)) { + throw new Error(`OWNER_ACL_SYSTEM_READWRITE_MISMATCH:${username}:${topic}`); + } + } + } } export function getNodeIdsForUserInAcl(content: string, mqttUsername: string): string[] { @@ -308,10 +351,10 @@ export function userExistsInAclContent(content: string, mqttUsername: string): b export function updateUserAclContent(content: string, mqttUsername: string, nodeIds: string[]): string { const parsed = parseAcl(content); - const grants = parsed.stanzas + const grants: OwnerAclGrant[] = parsed.stanzas .filter((stanza) => stanza.username !== mqttUsername) .map((stanza) => ({ mqttUsername: stanza.username, nodeIds: getNodeIdsForUserInAcl(content, stanza.username) })); - grants.push({ mqttUsername, nodeIds }); + grants.push({ mqttUsername, nodeIds, publicFeedRead: false }); return renderOwnerAcl('', grants, [], grants.filter((grant) => grant.nodeIds.length === 0).map((grant) => grant.mqttUsername)).content; } diff --git a/backend/src/mqtt/client.ts b/backend/src/mqtt/client.ts index bc47640..dc2cf46 100644 --- a/backend/src/mqtt/client.ts +++ b/backend/src/mqtt/client.ts @@ -370,6 +370,13 @@ function reportMqttIngestFailure(client: MqttClient, error: Error): void { } +/** Publish through the authenticated ingest connection on the internal broker. */ +export async function publishMqtt(topic: string, payload: string): Promise { + const client = mqttClients.find((candidate) => candidate.connected); + if (!client) throw new Error("MQTT_PUBLISH_NOT_CONNECTED"); + await client.publishAsync(topic, payload, { qos: 0, retain: false }); +} + export async function startMqttClient(): Promise { if (mqttStopping) return; assertSupportedIngestConnections(MQTT_INGEST_CONNECTIONS); diff --git a/backend/src/mqtt/decodePacket.test.ts b/backend/src/mqtt/decodePacket.test.ts index dac45ca..18f9ef3 100644 --- a/backend/src/mqtt/decodePacket.test.ts +++ b/backend/src/mqtt/decodePacket.test.ts @@ -1,10 +1,9 @@ import assert from 'node:assert/strict'; import test from 'node:test'; import { MeshCoreDecoder } from '@michaelhart/meshcore-decoder'; -import { decodePacketCompat, verifyAdvertSignature } from './decodePacket.js'; +import { decodePacketCompat } from './decodePacket.js'; const keyStore = MeshCoreDecoder.createKeyStore({ channelSecrets: [] }); -const VALID_ADVERT = '11007E7662676F7F0850A8A355BAAFBFC1EB7B4174C340442D7D7161C9474A2C94006CE7CF682E58408DD8FCC51906ECA98EBF94A037886BDADE7ECD09FD92B839491DF3809C9454F5286D1D3370AC31A34593D569E9A042A3B41FD331DFFB7E18599CE1E60992A076D50238C5B8F85757375354522F50756765744D65736820436F75676172'; test('preserves native two-byte path decoding', () => { // Header: ACK/Flood; path length 0x42 = two 2-byte hashes. @@ -56,11 +55,3 @@ test('does not expose metadata or an identity for malformed framing', () => { assert.equal(oddLength.metadataValid, false); assert.equal(oddLength.canonicalPacketId, undefined); }); - -test('only a valid Ed25519 advert may establish node identity', async () => { - const valid = decodePacketCompat(VALID_ADVERT, keyStore); - assert.equal(await verifyAdvertSignature(valid.decoded), true); - - const tampered = decodePacketCompat(`${VALID_ADVERT.slice(0, -2)}00`, keyStore); - assert.equal(await verifyAdvertSignature(tampered.decoded), false); -}); diff --git a/backend/src/mqtt/topic.test.ts b/backend/src/mqtt/topic.test.ts index 5ea9efb..dab309d 100644 --- a/backend/src/mqtt/topic.test.ts +++ b/backend/src/mqtt/topic.test.ts @@ -47,9 +47,6 @@ test('stores test-marker IATAs under the isolated test network scope', () => { test('rejects malformed and unsupported MQTT topics before persistence', () => { assert.equal(parseMqttTopic('meshcore/lhr/short/packets', prefixes, blocked), null); assert.equal(parseMqttTopic(`meshcore/lhr/${observer}/other`, prefixes, blocked), null); - // Raw packet bytes are the `raw` field in a /packets JSON envelope; /raw is - // not an ingest topic and must remain outside the observer topic contract. - assert.equal(parseMqttTopic(`meshcore/lhr/${observer}/raw`, prefixes, blocked), null); assert.equal(parseMqttTopic(`unknown/lhr/${observer}/packets`, prefixes, blocked), null); assert.equal(parseMqttTopic(`meshcore/lhr/${observer}/packets/extra`, prefixes, blocked), null); }); diff --git a/backend/src/owner/ownerAccess.ts b/backend/src/owner/ownerAccess.ts index 4d47d26..626d0aa 100644 --- a/backend/src/owner/ownerAccess.ts +++ b/backend/src/owner/ownerAccess.ts @@ -1,5 +1,5 @@ import mqtt from 'mqtt'; -import { randomBytes } from 'node:crypto'; +import { createHash, randomBytes } from 'node:crypto'; import { Redis } from 'ioredis'; import { BoundedTtlMap } from '../cache/boundedTtlMap.js'; import { getOwnerNodeIdsForUsername } from '../db/ownerAuth.js'; @@ -9,7 +9,6 @@ import { getRedisConnectionOptions, getRedisUrl } from '../platform/config/redis import { reconcileOwnerAuthorization } from './ownerAclReconciler.js'; import { groupOwnerNodes, type OwnerDashboardRow } from './ownerDashboard.js'; import { parseOwnerGrantConfig } from './ownerGrantConfig.js'; -import { createMqttCredentialVerifier } from './mqttCredentialVerifier.js'; function normalizeNodeIds(nodeIds: string[]): string[] { return Array.from(new Set( @@ -94,12 +93,50 @@ export async function autoLinkOwnerNodeIds(mqttUsername: string): Promise({ + name: 'owner_auth', + maxEntries: Number(process.env['OWNER_AUTH_CACHE_MAX_ENTRIES'] ?? 512), + maxWeight: 2 * 1024 * 1024, + ttlMs: AUTH_CACHE_TTL_MS, +}); + +function credentialHash(mqttUsername: string, mqttPassword: string): string { + return createHash('sha256').update(`${mqttUsername}\u0000${mqttPassword}`).digest('hex'); +} + +export function verifyMqttCredentials(mqttUsername: string, mqttPassword: string): Promise { + const hash = credentialHash(mqttUsername, mqttPassword); + const cached = authCache.get(mqttUsername); + if (cached && cached.credentialHash === hash && Date.now() - cached.ts < AUTH_CACHE_TTL_MS) { + return Promise.resolve(true); + } + return verifyMqttCredentialsViaBroker(mqttUsername, mqttPassword).then((ok) => { + if (ok) { + authCache.set(mqttUsername, { credentialHash: hash, ts: Date.now() }); + } else if (cached && cached.credentialHash === hash) { + // Same credential that previously worked now rejected (revoked) — drop it + // AND bump the credential generation so every live owner session minted + // under the old password is invalidated immediately. + authCache.delete(mqttUsername); + void bumpOwnerCredentialGeneration(mqttUsername); + } + return ok; + }); +} // ---- Credential generation (BUG-010) ---- -// Owner sessions record the credential generation that minted them. The -// operator revocation command bumps this Redis value after a password reset, -// and every authenticated owner request compares it with the cookie generation. +// Owner sessions are stateless signed cookies with a 30-day TTL. To make +// password revocation effective immediately, every session records the +// credential generation that minted it. When the broker rejects a previously +// valid password (the only signal the backend has that a password changed), +// the generation is bumped in Redis and all older sessions stop validating. const OWNER_CRED_GEN_KEY = (username: string) => `owner:credential-gen:${username}`; const OWNER_CRED_GEN_TTL_MS = 90 * 24 * 60 * 60 * 1000; @@ -117,28 +154,27 @@ function getCredGenRedis(): Redis { export async function getOwnerCredentialGeneration(mqttUsername: string): Promise { try { const value = await getCredGenRedis().get(OWNER_CRED_GEN_KEY(mqttUsername)); - if (value === null) return 0; - const parsed = Number(value); - if (!Number.isSafeInteger(parsed) || parsed < 0) { - throw new Error('INVALID_OWNER_CREDENTIAL_GENERATION'); - } - return parsed; + const parsed = value === null ? NaN : Number(value); + return Number.isFinite(parsed) && parsed > 0 ? parsed : 0; } catch (error) { - console.error('[owner-credgen] read failed', error instanceof Error ? error.message : error); - throw new Error('OWNER_CREDENTIAL_GENERATION_UNAVAILABLE', { cause: error }); + console.error('[owner-credgen] read failed, treating as 0', error instanceof Error ? error.message : error); + return 0; } } /** * Bump the credential generation, invalidating every session minted under an - * older password. Errors propagate so an operator reset cannot report success - * unless revocation was recorded. + * older password. Failures are logged but non-fatal: the next login still + * succeeds and re-records the current generation. */ export async function bumpOwnerCredentialGeneration(mqttUsername: string): Promise { - const redis = getCredGenRedis(); - const key = OWNER_CRED_GEN_KEY(mqttUsername); - await redis.incr(key); - await redis.pexpire(key, OWNER_CRED_GEN_TTL_MS); + try { + const redis = getCredGenRedis(); + await redis.incr(OWNER_CRED_GEN_KEY(mqttUsername)); + await redis.pexpire(OWNER_CRED_GEN_KEY(mqttUsername), OWNER_CRED_GEN_TTL_MS); + } catch (error) { + console.error('[owner-credgen] bump failed', error instanceof Error ? error.message : error); + } } export async function closeOwnerCredentialGenerationClient(): Promise { diff --git a/backend/src/owner/ownerAclReconciler.ts b/backend/src/owner/ownerAclReconciler.ts index a791dd0..29521a2 100644 --- a/backend/src/owner/ownerAclReconciler.ts +++ b/backend/src/owner/ownerAclReconciler.ts @@ -1,4 +1,5 @@ import fs from 'node:fs'; +import { query } from '../db/index.js'; import { getOwnerAuthorizationSnapshot, saveOwnerAclArtifact, @@ -14,6 +15,10 @@ import { writeAclAtomically, } from '../mqtt/aclManager.js'; import { ownerGrantConfigGeneration, parseOwnerGrantConfig } from './ownerGrantConfig.js'; +import { getPublicFeedConfig } from './publicFeedConfig.js'; +import { + getActivePublicFeedReaderUsernames, +} from './publicFeedRepository.js'; export type OwnerAclMode = 'shadow' | 'apply'; @@ -37,6 +42,20 @@ function allowedEmptyAclUsers(): string[] { .filter(Boolean); } +function configuredPublicFeedReaders(): string[] { + return String(process.env['OWNER_ACL_PUBLIC_FEED_READ_USERS'] ?? '') + .split(',') + .map((value) => value.trim()) + .filter(Boolean); +} + +function systemReadwriteAclTopics(): string[] { + return String(process.env['OWNER_ACL_SYSTEM_READWRITE_TOPICS'] ?? 'public/#') + .split(',') + .map((value) => value.trim()) + .filter(Boolean); +} + let inFlight: Promise | null = null; let reconcileTimer: NodeJS.Timeout | null = null; @@ -49,16 +68,42 @@ export function reconcileOwnerAuthorization(): Promise { await syncOperatorConfiguredOwnerGrants(configured, configGeneration); const snapshot = await getOwnerAuthorizationSnapshot(); + const publicFeedConfig = getPublicFeedConfig(); + const publicFeedReaders = publicFeedConfig.configured + ? await getActivePublicFeedReaderUsernames(query) + : new Set(); + const explicitPublicFeedReaders = configuredPublicFeedReaders(); + const explicitPublicFeedReaderSet = new Set(explicitPublicFeedReaders); + const grants = snapshot.map((account) => { + const publicFeedRead = explicitPublicFeedReaderSet.has(account.mqttUsername) + || (account.isActive && publicFeedReaders.has(account.mqttUsername)); + return { + mqttUsername: account.mqttUsername, + nodeIds: account.isActive ? account.nodeIds : [], + ...(publicFeedRead ? { publicFeedRead: true } : {}), + }; + }); + const snapshotUsernames = new Set(grants.map((grant) => grant.mqttUsername)); + for (const mqttUsername of explicitPublicFeedReaders) { + if (snapshotUsernames.has(mqttUsername)) continue; + // An explicit subscriber is managed even when the public feed is + // temporarily empty or disabled. That removes any stale broad ACL and + // leaves the account read-only with no topic access until a node is + // explicitly opted in and active. + grants.push({ + mqttUsername, + nodeIds: [], + publicFeedRead: true, + }); + } const currentContent = readAclFile(); const rendered = renderOwnerAcl( currentContent, - snapshot.map((account) => ({ - mqttUsername: account.mqttUsername, - nodeIds: account.isActive ? account.nodeIds : [], - })), + grants, unmanagedAclUsers(), - allowedEmptyAclUsers(), + [...allowedEmptyAclUsers(), ...explicitPublicFeedReaders], [String(process.env['MQTT_USERNAME'] ?? 'backend').trim() || 'backend'], + systemReadwriteAclTopics(), ); await updateOwnerAclState({ desiredGeneration: rendered.generation }); await saveOwnerAclArtifact({ diff --git a/backend/src/owner/ownerDashboard.test.ts b/backend/src/owner/ownerDashboard.test.ts index 68015b8..41f50cf 100644 --- a/backend/src/owner/ownerDashboard.test.ts +++ b/backend/src/owner/ownerDashboard.test.ts @@ -71,3 +71,35 @@ test('owner dashboard keeps unnamed canonical identities separate', () => { assert.deepEqual(nodes.map((node) => node.canonicalId).sort(), [first, second].sort()); assert.deepEqual(nodes.map((node) => node.members), [[first], [second]].sort()); }); + +test('owner dashboard prefers the freshest authorized key when same-name keys are both owned', () => { + // Regression (2026-09-18, Thinford Repeater): two same-name keys are both + // authorized; owner auth returns node ids ORDER BY node_id ASC and the stale + // key sorts first. The dashboard must select the freshest member so the live + // endpoints load the active key's data instead of the dead key's zeros. + const staleKey = '35CD1CDD'.repeat(8); + const liveKey = 'F518585D'.repeat(8); + + const nodes = groupOwnerNodes([ + row({ + canonical_id: liveKey, + name: 'Thinford Repeater', + members: [liveKey], + last_seen: '2026-09-18T11:38:09Z', + advert_count: 2147, + }), + row({ + canonical_id: staleKey, + name: 'Thinford Repeater', + members: [staleKey], + last_seen: '2026-04-14T08:54:50Z', + advert_count: 0, + }), + ], [staleKey, liveKey].sort()); + + assert.equal(nodes.length, 1); + assert.equal(nodes[0].node_id, liveKey); + assert.equal(nodes[0].canonicalId, liveKey); + assert.deepEqual(nodes[0].members, [liveKey, staleKey].sort()); + assert.equal(nodes[0].advert_count, 2147); +}); diff --git a/backend/src/owner/ownerDashboard.ts b/backend/src/owner/ownerDashboard.ts index 0706ad3..f0ae012 100644 --- a/backend/src/owner/ownerDashboard.ts +++ b/backend/src/owner/ownerDashboard.ts @@ -55,7 +55,7 @@ function compareRows(left: OwnerDashboardRow, right: OwnerDashboardRow): number * rows are also combined here because owner authorization can retain older * keys that the evidence-based global merge intentionally leaves separate. * Their keys stay visible in `members`, while the freshest row supplies the - * display position and metadata. + * display position, metadata, and the authorized key used by the live endpoints. */ export function groupOwnerNodes( rows: OwnerDashboardRow[], @@ -84,8 +84,13 @@ export function groupOwnerNodes( const ordered = [...group].sort(compareRows); const representative = ordered[0]!; const members = Array.from(new Set(group.flatMap((row) => row.members))).sort(); - const accessNodeId = authorized.find((nodeId) => members.includes(nodeId)) - ?? representative.canonical_id; + // owner-dashboard-freshest-member: prefer the freshest row that has an + // authorized key so the live endpoints load the most recently seen member + // (owner authorization can retain older keys that sort first by node_id). + const accessNodeId = ordered.reduce( + (found, row) => found ?? authorized.find((nodeId) => row.members.includes(nodeId)) ?? null, + null, + ) ?? representative.canonical_id; return { node_id: accessNodeId, diff --git a/backend/src/owner/ownerLastHopPrewarm.test.ts b/backend/src/owner/ownerLastHopPrewarm.test.ts deleted file mode 100644 index 95ffcef..0000000 --- a/backend/src/owner/ownerLastHopPrewarm.test.ts +++ /dev/null @@ -1,202 +0,0 @@ -import assert from 'node:assert/strict'; -import test from 'node:test'; -import { - createOwnerLastHopPrewarm, - ownerLastHopPrewarmConcurrency, -} from './ownerLastHopPrewarm.js'; - -function capturedLog() { - const entries: Array<{ level: string; message: string; detail: unknown }> = []; - return { - entries, - log: { - log(message: string, detail: unknown) { entries.push({ level: 'log', message, detail }); }, - warn(message: string, detail: unknown) { entries.push({ level: 'warn', message, detail }); }, - }, - }; -} - -test('prewarm concurrency defaults to two and cannot exceed the DB load cap', () => { - for (const value of [undefined, null, '', 'invalid', NaN, Infinity]) { - assert.equal(ownerLastHopPrewarmConcurrency(value), 2); - } - for (const value of [0, -1, 1, '1', 1.9]) assert.equal(ownerLastHopPrewarmConcurrency(value), 1); - for (const value of [2, '2', 12]) assert.equal(ownerLastHopPrewarmConcurrency(value), 2); -}); - -test('a pass counts completed owners and nodes, logs every ten, and continues after failures', async () => { - const captured = capturedLog(); - let active = 0; - let peak = 0; - const refreshed: string[] = []; - const prewarm = createOwnerLastHopPrewarm({ - concurrency: 99, - log: captured.log, - loadOwners: async () => [ - { mqttUsername: 'a', nodeIds: Array.from({ length: 8 }, (_, i) => String(i)) }, - { mqttUsername: 'b', nodeIds: Array.from({ length: 8 }, (_, i) => String(i + 8)) }, - { mqttUsername: 'c', nodeIds: Array.from({ length: 7 }, (_, i) => String(i + 16)) }, - { mqttUsername: 'empty', nodeIds: [] }, - ], - refresh: async (_owner, nodeId) => { - active++; - peak = Math.max(peak, active); - await new Promise((resolve) => setImmediate(resolve)); - active--; - refreshed.push(nodeId); - if (nodeId === '4' || nodeId === '18') throw new Error('test refresh failed'); - }, - }); - assert.deepEqual(await prewarm.run(), { - owners: 3, ownersTotal: 3, nodes: 23, nodesTotal: 23, refreshed: 21, failed: 2, - }); - assert.equal(peak, 2); - assert.equal(new Set(refreshed).size, 23); - assert.deepEqual(captured.entries.filter((entry) => entry.message.endsWith('progress')).map((entry) => entry.detail), [ - { owners: 1, ownersTotal: 3, nodes: 10, nodesTotal: 23, refreshed: 9, failed: 1 }, - { owners: 2, ownersTotal: 3, nodes: 20, nodesTotal: 23, refreshed: 18, failed: 2 }, - ]); -}); - -test('overlapping callers share one pass and duplicate nodes are refreshed once per owner', async () => { - let release!: () => void; - const gate = new Promise((resolve) => { release = resolve; }); - let loads = 0; - let calls = 0; - const prewarm = createOwnerLastHopPrewarm({ - log: capturedLog().log, - loadOwners: async () => { loads++; return [{ mqttUsername: 'a', nodeIds: ['one', 'one'] }]; }, - refresh: async () => { calls++; await gate; }, - }); - const first = prewarm.run(); - assert.equal(prewarm.run(), first); - await new Promise((resolve) => setImmediate(resolve)); - assert.equal(loads, 1); - assert.equal(calls, 1); - release(); - assert.equal((await first).refreshed, 1); -}); - -test('slow refresh warns while the refresh is still running, including one that fails', async (t) => { - t.mock.timers.enable({ apis: ['setTimeout'] }); - const captured = capturedLog(); - let clock = 0; - let rejectRefresh!: (error: Error) => void; - const gate = new Promise((_resolve, reject) => { rejectRefresh = reject; }); - const prewarm = createOwnerLastHopPrewarm({ - now: () => clock, - log: captured.log, - loadOwners: async () => [{ mqttUsername: 'a', nodeIds: ['slow'] }], - refresh: () => gate, - }); - const result = prewarm.run(); - await Promise.resolve(); - clock = 20_001; - t.mock.timers.tick(20_001); - assert.equal(captured.entries.filter((entry) => entry.message.includes('exceeds 20s')).length, 1); - rejectRefresh(new Error('late failure')); - assert.equal((await result).failed, 1); - assert.equal(captured.entries.filter((entry) => entry.message.includes('exceeds 20s')).length, 1); -}); - -test('stopping drains current refreshes and prevents further admissions', async () => { - let release!: () => void; - const gate = new Promise((resolve) => { release = resolve; }); - let calls = 0; - const prewarm = createOwnerLastHopPrewarm({ - concurrency: 1, - log: capturedLog().log, - loadOwners: async () => [{ mqttUsername: 'a', nodeIds: ['one', 'two'] }], - refresh: async () => { calls++; await gate; }, - }); - const result = prewarm.run(); - await Promise.resolve(); - const stopping = prewarm.stop(); - release(); - await stopping; - assert.equal(calls, 1); - assert.deepEqual(await result, { - owners: 0, ownersTotal: 1, nodes: 1, nodesTotal: 2, refreshed: 1, failed: 0, - }); - await assert.rejects(prewarm.run(), /PREWARM_STOPPED/); -}); - -test('scheduled passes wait for completion before starting their interval and start is idempotent', async (t) => { - t.mock.timers.enable({ apis: ['setTimeout'] }); - let release!: () => void; - const gate = new Promise((resolve) => { release = resolve; }); - let loads = 0; - let refreshes = 0; - const prewarm = createOwnerLastHopPrewarm({ - log: capturedLog().log, - loadOwners: async () => { loads++; return [{ mqttUsername: 'a', nodeIds: ['one'] }]; }, - refresh: async () => { refreshes++; await gate; }, - }); - t.after(() => prewarm.stop()); - prewarm.start(100); - prewarm.start(100); - await new Promise((resolve) => setImmediate(resolve)); - t.mock.timers.tick(1_000); - assert.equal(loads, 1, 'a long pass cannot overlap or accumulate interval ticks'); - assert.equal(refreshes, 1); - release(); - await new Promise((resolve) => setImmediate(resolve)); - t.mock.timers.tick(99); - assert.equal(loads, 1, 'the interval begins when the pass completes'); - t.mock.timers.tick(1); - await new Promise((resolve) => setImmediate(resolve)); - assert.equal(loads, 2); - assert.equal(refreshes, 2); - await prewarm.stop(); - t.mock.timers.tick(1_000); - assert.equal(loads, 2, 'shutdown cancels the next scheduled pass'); -}); - -test('a failed owner enumeration logs the failure and the next scheduled pass recovers', async (t) => { - t.mock.timers.enable({ apis: ['setTimeout'] }); - const captured = capturedLog(); - let loads = 0; - let refreshes = 0; - const prewarm = createOwnerLastHopPrewarm({ - log: captured.log, - loadOwners: async () => { - if (++loads === 1) throw new Error('owner inventory unavailable'); - return [{ mqttUsername: 'a', nodeIds: ['one'] }]; - }, - refresh: async () => { refreshes++; }, - }); - t.after(() => prewarm.stop()); - prewarm.start(100); - await new Promise((resolve) => setImmediate(resolve)); - assert.equal(refreshes, 0); - assert.deepEqual(captured.entries, [{ - level: 'warn', message: '[owner-last-hop-prewarm] pass failed', detail: 'owner inventory unavailable', - }]); - t.mock.timers.tick(100); - await new Promise((resolve) => setImmediate(resolve)); - assert.equal(loads, 2); - assert.equal(refreshes, 1); - assert.deepEqual(captured.entries.at(-1)?.detail, { - owners: 1, ownersTotal: 1, nodes: 1, nodesTotal: 1, refreshed: 1, failed: 0, - }); -}); - -test('shutdown during owner enumeration drains the pass without admitting a refresh', async () => { - let release!: (owners: Array<{ mqttUsername: string; nodeIds: string[] }>) => void; - const inventory = new Promise>((resolve) => { release = resolve; }); - let refreshes = 0; - const prewarm = createOwnerLastHopPrewarm({ - log: capturedLog().log, - loadOwners: () => inventory, - refresh: async () => { refreshes++; }, - }); - const pass = prewarm.run(); - const stopping = prewarm.stop(); - release([{ mqttUsername: 'a', nodeIds: ['one', 'two'] }]); - await stopping; - assert.equal(refreshes, 0); - assert.deepEqual(await pass, { - owners: 0, ownersTotal: 1, nodes: 0, nodesTotal: 2, refreshed: 0, failed: 0, - }); - await assert.rejects(prewarm.run(), /PREWARM_STOPPED/); -}); diff --git a/backend/src/owner/ownerLastHopPrewarm.ts b/backend/src/owner/ownerLastHopPrewarm.ts index dfbefcb..057f17a 100644 --- a/backend/src/owner/ownerLastHopPrewarm.ts +++ b/backend/src/owner/ownerLastHopPrewarm.ts @@ -1,122 +1,107 @@ -export type OwnerLastHopPrewarmTarget = { - mqttUsername: string; - nodeIds: string[]; +// Background cache warmer for the owner dashboard "RX Strength by Last Hop" panel. +// +// Every pass it refreshes the last-hop series cached for each node of every +// active MQTT owner, for both the 7d and 30d windows, so any owner opening the +// dashboard — on either time window — hits a warm cache instead of paying for a +// cold scan on their own request. The pass is sequential with a small delay +// between nodes to keep database load gentle, and individual failures are +// logged and skipped so one bad node can never stall the whole pass. +export type OwnerLastHopPrewarmDeps = { + listOwnerNodes: () => Promise>; + refreshLastHop: (ownedNodeIds: string[], nodeId: string, windowDays: number, minAgeMs: number) => Promise; + intervalMs: number; + nodeDelayMs: number; + windows?: number[]; }; -export type OwnerLastHopPrewarmProgress = { - owners: number; - ownersTotal: number; - nodes: number; - nodesTotal: number; - refreshed: number; - failed: number; -}; +const DEFAULT_WINDOWS = [7, 30]; +const INITIAL_DELAY_MS = 20_000; -export function ownerLastHopPrewarmConcurrency(value: unknown): number { - if (value == null || value === '') return 2; - const parsed = Number(value); - return Number.isFinite(parsed) ? Math.max(1, Math.min(2, Math.trunc(parsed))) : 2; +function sleep(ms: number): Promise { + return new Promise((resolve) => { setTimeout(resolve, ms); }); } -type PrewarmDeps = { - loadOwners: () => Promise; - refresh: (owner: OwnerLastHopPrewarmTarget, nodeId: string) => Promise; - concurrency?: unknown; - now?: () => number; - log?: Pick; -}; +export function createOwnerLastHopPrewarm(deps: OwnerLastHopPrewarmDeps): () => void { + if (!Number.isFinite(deps.intervalMs) || deps.intervalMs <= 0) { + console.warn('[owner-last-hop-prewarm] disabled (invalid interval)'); + return () => undefined; + } -/** One pass at a time, at most two node refreshes, and no overlapping schedules. */ -export function createOwnerLastHopPrewarm(deps: PrewarmDeps) { - const concurrency = ownerLastHopPrewarmConcurrency(deps.concurrency); - const now = deps.now ?? Date.now; - const log = deps.log ?? console; - let inFlight: Promise | null = null; - let timer: ReturnType | null = null; - let started = false; + const windows = deps.windows && deps.windows.length > 0 ? deps.windows : DEFAULT_WINDOWS; + // Entries refreshed within half a pass interval are left alone; anything older + // is rebuilt (incrementally when a previous series exists, fully when cold). + const minAgeMs = Math.max(30_000, Math.floor(deps.intervalMs / 2)); let stopped = false; + let running = false; + let initialTimer: NodeJS.Timeout | null = null; + let intervalTimer: NodeJS.Timeout | null = null; - async function pass(): Promise { - const owners = (await deps.loadOwners()) - .map((owner) => ({ ...owner, nodeIds: [...new Set(owner.nodeIds)] })) - .filter((owner) => owner.nodeIds.length > 0); - const jobs = owners.flatMap((owner) => owner.nodeIds.map((nodeId) => ({ owner, nodeId }))); - const remaining = new Map(owners.map((owner) => [owner, owner.nodeIds.length])); - const progress: OwnerLastHopPrewarmProgress = { - owners: 0, ownersTotal: owners.length, nodes: 0, nodesTotal: jobs.length, - refreshed: 0, failed: 0, - }; - log.log('[owner-last-hop-prewarm] started', { ...progress, concurrency }); - let next = 0; - async function work() { - while (!stopped && next < jobs.length) { - const { owner, nodeId } = jobs[next++]!; - const startedAt = now(); - let warned = false; - const warnSlow = () => { - warned = true; - log.warn('[owner-last-hop-prewarm] refresh exceeds 20s', { - owner: owner.mqttUsername, nodeId, elapsedMs: now() - startedAt, - }); - }; - const slowTimer = setTimeout(warnSlow, 20_000); - slowTimer.unref(); - try { - await deps.refresh(owner, nodeId); - progress.refreshed++; - } catch (error) { - progress.failed++; - log.warn('[owner-last-hop-prewarm] refresh failed', { - owner: owner.mqttUsername, nodeId, - error: error instanceof Error ? error.message : String(error), - }); - } finally { - clearTimeout(slowTimer); - if (!warned && now() - startedAt > 20_000) warnSlow(); - progress.nodes++; - const left = remaining.get(owner)! - 1; - remaining.set(owner, left); - if (left === 0) progress.owners++; - if (progress.nodes % 10 === 0 && progress.nodes < progress.nodesTotal) { - log.log('[owner-last-hop-prewarm] progress', { ...progress }); + async function runPass(): Promise { + if (stopped || running) return; + running = true; + const startedAt = Date.now(); + let owners = 0; + let nodes = 0; + let refreshed = 0; + let skipped = 0; + let failed = 0; + try { + const active = (await deps.listOwnerNodes()).filter((account) => account.isActive && account.nodeIds.length > 0); + owners = active.length; + for (const account of active) { + if (stopped) break; + for (const nodeId of account.nodeIds) { + if (stopped) break; + nodes += 1; + for (const windowDays of windows) { + try { + const didRefresh = await deps.refreshLastHop(account.nodeIds, nodeId, windowDays, minAgeMs); + if (didRefresh) refreshed += 1; + else skipped += 1; + } catch (error) { + failed += 1; + console.warn('[owner-last-hop-prewarm] refresh failed', { + mqttUsername: account.mqttUsername, + nodeId, + windowDays, + error: error instanceof Error ? error.message : String(error), + }); + } } + if (deps.nodeDelayMs > 0) await sleep(deps.nodeDelayMs); } } - } - await Promise.all(Array.from({ length: concurrency }, () => work())); - log.log(stopped ? '[owner-last-hop-prewarm] stopped' : '[owner-last-hop-prewarm] complete', { ...progress }); - return progress; - } - - function run(): Promise { - if (inFlight) return inFlight; - if (stopped) return Promise.reject(new Error('OWNER_LAST_HOP_PREWARM_STOPPED')); - inFlight = pass().finally(() => { inFlight = null; }); - return inFlight; - } - - function start(intervalMs = 30 * 60_000): void { - if (started || stopped) return; - started = true; - const scheduled = () => { - void run().catch((error: unknown) => { - log.warn('[owner-last-hop-prewarm] pass failed', error instanceof Error ? error.message : String(error)); - }).finally(() => { - if (!stopped) { - timer = setTimeout(scheduled, intervalMs); - timer.unref(); - } + console.log('[owner-last-hop-prewarm] pass complete', { + owners, + nodes, + refreshed, + skipped, + failed, + durationMs: Date.now() - startedAt, }); - }; - scheduled(); + } catch (error) { + console.warn('[owner-last-hop-prewarm] pass failed', error instanceof Error ? error.message : error); + } finally { + running = false; + } } - async function stop(): Promise { + console.log('[owner-last-hop-prewarm] enabled', { + intervalMs: deps.intervalMs, + nodeDelayMs: deps.nodeDelayMs, + windows, + minAgeMs, + }); + initialTimer = setTimeout(() => { void runPass(); }, INITIAL_DELAY_MS); + initialTimer.unref(); + intervalTimer = setInterval(() => { void runPass(); }, deps.intervalMs); + intervalTimer.unref(); + + return () => { stopped = true; - if (timer) clearTimeout(timer); - timer = null; - await inFlight?.catch(() => undefined); - } - - return { run, start, stop }; + if (initialTimer) clearTimeout(initialTimer); + if (intervalTimer) clearInterval(intervalTimer); + initialTimer = null; + intervalTimer = null; + }; } diff --git a/backend/src/owner/ownerRepository.integration.test.ts b/backend/src/owner/ownerRepository.integration.test.ts new file mode 100644 index 0000000..238289f --- /dev/null +++ b/backend/src/owner/ownerRepository.integration.test.ts @@ -0,0 +1,93 @@ +import assert from 'node:assert/strict'; +import { execFile } from 'node:child_process'; +import test from 'node:test'; +import { promisify } from 'node:util'; +import { createOwnerRepository } from './ownerRepository.js'; + +// SELECT-only fixtures shadow every source relation. No data or DDL writes. +const sqlTest = process.env['OWNER_SQL_TESTS'] === '1' ? test : test.skip; +const run = promisify(execFile); +const owner = 'A'.repeat(64), alias = 'F'.repeat(64), peer = 'B'.repeat(64), unrelated = 'C'.repeat(64); +const literal = (value: unknown) => `'${String(value).replace(/'/g, "''")}'`; +const packet = (overrides: Record = {}) => ({ age: 1, rx_node_id_raw: owner, src_node_id: peer, packet_hash: 'hash', packet_type: 4, route_type: 1, hop_count: 0, rssi: -100, snr: 5, payload: {}, ...overrides }); +const sample = (overrides: Record = {}) => ({ age: 1, node_id: owner, network: 'ukmesh', neighbors: [{ id: peer }], stats: { battery_mv: 4100 }, ...overrides }); + +async function execute(index: number, fixtures: { packets?: unknown[]; samples?: unknown[] }, requested = owner) { + const queries: string[] = []; + await createOwnerRepository({ query: async text => { queries.push(text); return { rows: [] }; } }).fetchOwnerLiveData(requested); + // Resolve only the selected test key without reading production mappings. + const canonical = requested.trim().toUpperCase() === alias ? owner : requested.trim().toUpperCase(); + const sql = queries[index]!.replaceAll('meshcore_canonical_node_id($1)', literal(canonical)).replaceAll('$1', literal(requested)); + const fixture = `WITH + node_identity_aliases AS (SELECT ${literal(alias)}::text AS source_node_id, ${literal(owner)}::text AS canonical_node_id), + node_identity_nodes AS ( + SELECT node_id, 'fixture-' || LEFT(node_id, 1) AS name, 'ukmesh'::text AS network, + 'MME'::text AS iata, 54.5::double precision AS lat, -1.3::double precision AS lon + FROM unnest(ARRAY[${literal(owner)},${literal(peer)}]::text[]) AS n(node_id) + ), node_identity_packets AS ( + SELECT p.*, NOW() - age * INTERVAL '1 second' AS time + FROM jsonb_to_recordset(${literal(JSON.stringify(fixtures.packets ?? []))}::jsonb) AS p( + age double precision, rx_node_id_raw text, src_node_id text, packet_hash text, + packet_type integer, route_type integer, hop_count integer, payload jsonb, + rssi double precision, snr double precision + ) + ), fixture_samples AS ( + SELECT s.*, NOW() - age * INTERVAL '1 second' AS time + FROM jsonb_to_recordset(${literal(JSON.stringify(fixtures.samples ?? []))}::jsonb) AS s( + age double precision, node_id text, network text, neighbors jsonb, stats jsonb, + battery_mv double precision, uptime_secs bigint, tx_air_secs double precision, + rx_air_secs double precision, channel_utilization double precision, air_util_tx double precision + ) + ), node_neighbor_samples AS (SELECT * FROM fixture_samples), + node_status_samples AS (SELECT * FROM fixture_samples) + SELECT COALESCE(jsonb_agg(to_jsonb(result)), '[]'::jsonb) FROM (${sql}) result;`; + const child = run('docker', ['exec', '-i', 'meshcore-infra-timescaledb-1', 'psql', '-X', '-U', 'meshcore', '-d', 'meshcore', '-At', '-v', 'ON_ERROR_STOP=1'], { timeout: 20_000, maxBuffer: 4 * 1024 * 1024 }); + child.child.stdin!.end(fixture); + return JSON.parse((await child).stdout) as Array>; +} + +sqlTest('packet feed merges aliases, deduplicates newest hashes and caps nine before enrichment', async () => { + const rows = await execute(2, { packets: [ + ...Array.from({ length: 12 }, (_, i) => packet({ age: i + 10, packet_hash: String(i), rx_node_id_raw: i % 2 ? alias : owner })), + packet({ age: 1, packet_hash: '0', rx_node_id_raw: alias, payload: { decrypted: { text: 'latest duplicate', sender: 'Alice' } } }), + packet({ age: 0, packet_hash: 'unrelated', rx_node_id_raw: unrelated }), packet({ age: 31 * 86400, packet_hash: 'expired' }), + ] }); + assert.equal(rows.length, 9); + assert.deepEqual(rows.map(row => row.packet_hash), ['0','1','2','3','4','5','6','7','8']); + assert.equal(rows[0]!.body, 'latest duplicate'); + assert.equal(rows[0]!.sender, 'Alice'); + assert.equal(rows[0]!.src_node_name, 'fixture-B'); + assert.deepEqual(await execute(2, { packets: [packet()] }, unrelated), []); +}); + +sqlTest('packet budget is global across aliases and null hashes retain fallback deduplication', async () => { + const rows = await execute(2, { packets: [ + ...Array.from({ length: 5000 }, (_, i) => packet({ age: i / 100, packet_hash: 'duplicate', rx_node_id_raw: i % 2 ? alias : owner })), + packet({ age: 100, packet_hash: 'outside-global-budget' }), + ] }); + assert.deepEqual(rows.map(row => row.packet_hash), ['duplicate']); + const nulls = await execute(2, { packets: [packet({ packet_hash: null }), packet({ packet_hash: null, rx_node_id_raw: alias }), packet({ packet_hash: null, payload: { decrypted: { text: 'different' } } })] }); + assert.equal(nulls.length, 2); +}); + +sqlTest('neighbours select newest alias report, preserve empty reports and bound time and network', async () => { + const rows = await execute(7, { samples: [sample({ age: 50 }), sample({ node_id: alias, age: 5, neighbors: [] }), sample({ node_id: unrelated, age: 0 }), sample({ network: 'test', age: 0 }), sample({ age: 8 * 86400 })] }, alias); + assert.equal(rows.length, 1); + assert.deepEqual(rows[0]!.neighbors, []); + assert.deepEqual(await execute(7, { samples: [sample({ age: 7 * 86400 }), sample({ network: 'test' }), sample({ node_id: unrelated })] }), []); +}); + +sqlTest('telemetry keeps raw identities in 24 hours, chronological order and nullable fields', async () => { + const rows = await execute(6, { samples: [sample({ age: 50, stats: { battery_mv: 4050 } }), sample({ age: 10, node_id: alias, stats: {}, battery_mv: 4200 }), sample({ age: 1, node_id: alias, stats: { battery_mv: 'unknown' } }), sample({ age: 86400 }), sample({ node_id: unrelated }), sample({ network: 'test' })] }); + assert.deepEqual(rows.map(row => row.battery_mv), [4050, 4200, null]); + assert.ok(Date.parse(rows[0]!.time) < Date.parse(rows[2]!.time)); + assert.deepEqual(await execute(6, { samples: [sample()] }, unrelated), []); +}); + +sqlTest('direct evidence excludes relays, self, tx reports, unrelated receivers and expired packets', async () => { + const rows = await execute(1, { packets: [packet(), packet({ rx_node_id_raw: alias, rssi: -90, snr: 1 }), packet({ hop_count: 1 }), packet({ src_node_id: owner }), packet({ payload: { direction: 'tx' } }), packet({ rx_node_id_raw: unrelated }), packet({ age: 86400 })] }); + assert.equal(rows.length, 1); + assert.equal(rows[0]!.packets_24h, 2); + assert.equal(rows[0]!.avg_rssi, -95); + assert.equal(rows[0]!.avg_snr, 3); +}); diff --git a/backend/src/owner/ownerRepository.ts b/backend/src/owner/ownerRepository.ts index 8416c12..782a38a 100644 --- a/backend/src/owner/ownerRepository.ts +++ b/backend/src/owner/ownerRepository.ts @@ -14,15 +14,17 @@ export type OwnerRepository = ReturnType; export function createOwnerRepository(deps: OwnerRepositoryDeps) { const { query } = deps; - async function fetchLastHopStrength(rxNodeIds: string[], allOwnerNodeIds: string[], since?: string) { + async function fetchLastHopStrength(rxNodeIds: string[], allOwnerNodeIds: string[], since?: string, windowDays = 7, bucketHours = 1) { // $1 = rxNodeIds (the selected node to filter received packets) // $2 = allOwnerNodeIds (all owned nodes to exclude from last-hop analysis) - // $3 = since (optional time filter) + // $3 = since (optional time filter) or windowDays for the default rolling window + // $4 = bucket size in hours: hourly for the 7d window, 12-hour chunks for 30d const params: unknown[] = [rxNodeIds, allOwnerNodeIds]; const timeFilter = since ? `p.time >= $3::timestamptz` - : `p.time > NOW() - INTERVAL '7 days'`; - if (since) params.push(since); + : `p.time > NOW() - (($3::int) * INTERVAL '1 day')`; + params.push(since ?? windowDays); + params.push(Math.max(1, Math.trunc(bucketHours))); return query<{ bucket: string; @@ -143,7 +145,7 @@ export function createOwnerRepository(deps: OwnerRepositoryDeps) { ), classified AS ( SELECT - time_bucket('1 hour', op.time)::text AS bucket, + time_bucket((($4::int) * INTERVAL '1 hour'), op.time)::text AS bucket, CASE WHEN op.hop_count = 0 AND op.src_node_id IS NOT NULL @@ -242,7 +244,7 @@ export function createOwnerRepository(deps: OwnerRepositoryDeps) { `SELECT node_id, name, network, iata, advert_count, last_seen, lat, lon, role, identity_source_ids AS members FROM node_identity_nodes - WHERE node_id = meshcore_canonical_node_id($1) + WHERE node_id = (SELECT meshcore_canonical_node_id($1)) LIMIT 1`, [selectedNodeId], ), @@ -255,6 +257,8 @@ export function createOwnerRepository(deps: OwnerRepositoryDeps) { lon: number | null; packets_24h: number; last_seen: string | null; + avg_rssi: number | null; + avg_snr: number | null; }>( `SELECT p.src_node_id AS node_id, @@ -264,7 +268,9 @@ export function createOwnerRepository(deps: OwnerRepositoryDeps) { n.lat, n.lon, COUNT(*)::int AS packets_24h, - MAX(p.time)::text AS last_seen + MAX(p.time)::text AS last_seen, + AVG(p.rssi)::double precision AS avg_rssi, + AVG(p.snr)::double precision AS avg_snr FROM node_identity_packets p LEFT JOIN node_identity_nodes n ON n.node_id = p.src_node_id WHERE p.rx_node_id_raw IN ( @@ -274,6 +280,7 @@ export function createOwnerRepository(deps: OwnerRepositoryDeps) { WHERE canonical_node_id = meshcore_canonical_node_id($1) ) AND p.hop_count = 0 + AND COALESCE(p.payload->>'direction', 'rx') <> 'tx' AND p.src_node_id IS NOT NULL AND p.src_node_id <> meshcore_canonical_node_id($1) AND p.time > NOW() - INTERVAL '24 hours' @@ -293,7 +300,12 @@ export function createOwnerRepository(deps: OwnerRepositoryDeps) { sender: string | null; body: string | null; }>( - `WITH recent AS ( + `WITH owner_ids AS MATERIALIZED ( + SELECT meshcore_canonical_node_id($1) AS node_id + UNION + SELECT source_node_id FROM node_identity_aliases + WHERE canonical_node_id = meshcore_canonical_node_id($1) + ), recent AS MATERIALIZED ( SELECT p.time, p.packet_type, @@ -302,14 +314,14 @@ export function createOwnerRepository(deps: OwnerRepositoryDeps) { p.packet_hash, p.src_node_id, p.payload - FROM node_identity_packets p - WHERE p.rx_node_id_raw IN ( - SELECT meshcore_canonical_node_id($1) - UNION - SELECT source_node_id FROM node_identity_aliases - WHERE canonical_node_id = meshcore_canonical_node_id($1) - ) - AND p.time > NOW() - INTERVAL '30 days' + FROM owner_ids + CROSS JOIN LATERAL ( + SELECT p.* FROM node_identity_packets p + WHERE p.rx_node_id_raw = owner_ids.node_id + AND p.time > NOW() - INTERVAL '30 days' + ORDER BY p.time DESC + LIMIT 5000 + ) p ORDER BY p.time DESC LIMIT 5000 ), @@ -338,6 +350,13 @@ export function createOwnerRepository(deps: OwnerRepositoryDeps) { ORDER BY recent.time DESC ) AS rn FROM recent + ), latest AS MATERIALIZED ( + -- Deduplicate and cap the feed before enriching names. Joining every + -- ranked packet to the identity view first created millions of pairs. + SELECT * FROM ranked + WHERE rn = 1 + ORDER BY time DESC + LIMIT 9 ) SELECT r.time::text AS time, @@ -355,11 +374,9 @@ export function createOwnerRepository(deps: OwnerRepositoryDeps) { r.payload->'decoded'->>'text', r.payload->>'message' ) AS body - FROM ranked r + FROM latest r LEFT JOIN node_identity_nodes src ON src.node_id = r.src_node_id - WHERE r.rn = 1 - ORDER BY r.time DESC - LIMIT 9`, + ORDER BY r.time DESC`, [selectedNodeId], ), query<{ @@ -595,11 +612,16 @@ export function createOwnerRepository(deps: OwnerRepositoryDeps) { WHEN jsonb_typeof(stats->'tx_publish_calls') = 'number' THEN (stats->>'tx_publish_calls')::double precision ELSE NULL END AS tx_publish_calls - FROM node_identity_status_samples - WHERE node_id = meshcore_canonical_node_id($1) + FROM node_status_samples + WHERE node_id IN ( + SELECT meshcore_canonical_node_id($1) + UNION + SELECT source_node_id FROM node_identity_aliases + WHERE canonical_node_id = meshcore_canonical_node_id($1) + ) AND network = COALESCE( (SELECT network FROM node_identity_nodes - WHERE node_id = meshcore_canonical_node_id($1) + WHERE node_id = (SELECT meshcore_canonical_node_id($1)) LIMIT 1), 'ukmesh' ) @@ -608,53 +630,37 @@ export function createOwnerRepository(deps: OwnerRepositoryDeps) { [selectedNodeId], ), query<{ - id: string; - rssi: number | null; - snr: number | null; - last_seen: string | null; - sample_time: string; + time: string; + neighbors: unknown; }>( - `WITH latest AS ( - SELECT time, neighbors - FROM node_neighbor_samples - WHERE meshcore_canonical_node_id(node_id) = meshcore_canonical_node_id($1) - AND network = COALESCE( - (SELECT network FROM node_identity_nodes - WHERE node_id = meshcore_canonical_node_id($1) - LIMIT 1), - 'ukmesh' - ) - ORDER BY time DESC + `WITH owner_ids AS MATERIALIZED ( + SELECT meshcore_canonical_node_id($1) AS node_id + UNION + SELECT source_node_id FROM node_identity_aliases + WHERE canonical_node_id = meshcore_canonical_node_id($1) + ), latest AS ( + -- Probe the (node_id, time) index per identity member, including + -- absent publishers, instead of canonicalising the entire history. + SELECT sample.time, sample.neighbors + FROM owner_ids + CROSS JOIN LATERAL ( + SELECT time, neighbors + FROM node_neighbor_samples + WHERE node_id = owner_ids.node_id + AND time > NOW() - INTERVAL '7 days' + AND network = COALESCE( + (SELECT network FROM node_identity_nodes + WHERE node_id = (SELECT meshcore_canonical_node_id($1)) + LIMIT 1), + 'ukmesh' + ) + ORDER BY time DESC + LIMIT 1 + ) sample + ORDER BY sample.time DESC LIMIT 1 ) - SELECT - COALESCE(item->>'id', item->>'node_id', item->>'pubkey', item->>'public_key') AS id, - CASE - WHEN jsonb_typeof(item->'rssi') = 'number' THEN (item->>'rssi')::double precision - WHEN jsonb_typeof(item->'RSSI') = 'number' THEN (item->>'RSSI')::double precision - ELSE NULL - END AS rssi, - CASE - WHEN jsonb_typeof(item->'snr') = 'number' THEN (item->>'snr')::double precision - WHEN jsonb_typeof(item->'SNR') = 'number' THEN (item->>'SNR')::double precision - WHEN jsonb_typeof(item->'snr_db') = 'number' THEN (item->>'snr_db')::double precision - ELSE NULL - END AS snr, - COALESCE( - CASE WHEN jsonb_typeof(item->'last_seen') IN ('string', 'number') THEN item->>'last_seen' ELSE NULL END, - CASE WHEN jsonb_typeof(item->'lastSeen') IN ('string', 'number') THEN item->>'lastSeen' ELSE NULL END, - CASE WHEN jsonb_typeof(item->'heard_secs_ago') = 'number' - THEN (latest.time - ((item->>'heard_secs_ago')::double precision * INTERVAL '1 second'))::text - ELSE NULL END - ) AS last_seen, - latest.time::text AS sample_time - FROM latest - CROSS JOIN LATERAL jsonb_array_elements( - CASE WHEN jsonb_typeof(latest.neighbors) = 'array' THEN latest.neighbors ELSE '[]'::jsonb END - ) AS neighbor(item) - WHERE COALESCE(item->>'id', item->>'node_id', item->>'pubkey', item->>'public_key') IS NOT NULL - ORDER BY last_seen DESC NULLS LAST - LIMIT 32`, + SELECT time::text AS time, neighbors FROM latest`, [selectedNodeId], ), query<{ packets_24h: number }>( diff --git a/backend/src/owner/ownerService.test.ts b/backend/src/owner/ownerService.test.ts index 12d238a..27030ff 100644 --- a/backend/src/owner/ownerService.test.ts +++ b/backend/src/owner/ownerService.test.ts @@ -1,177 +1,52 @@ import assert from 'node:assert/strict'; import test from 'node:test'; -import { createOwnerService } from './ownerService.js'; -import { createOwnerRepository } from './ownerRepository.js'; +import { buildOwnerHeardNeighbors } from './ownerService.js'; -function lastHopService(fetch: ReturnType['fetchLastHopStrength']) { - const repository = createOwnerRepository({ query: async () => ({ rows: [] }) }); - repository.fetchLastHopStrength = fetch; - return createOwnerService({ - repository, - ownerLiveCache: new Map(), - ownerLiveCacheTtlMs: 15_000, - ownerDashboardCacheTtlMs: 20_000, - ownerLastHopCacheTtlMs: 60_000, - verifyMqttCredentials: async () => true, - resolveOwnerNodeIds: async () => [], - autoLinkOwnerNodeIds: async () => [], - buildOwnerDashboard: async () => ({ nodes: [] }), - invalidateOwnerNodeIdCache: () => {}, +const time = '2026-09-08T01:00:00Z'; +const direct = [{ node_id: 'direct', last_seen: time, avg_rssi: -103.5, avg_snr: 7.25 }]; + +test('absent neighbour publisher uses real direct packet signal with explicit provenance', () => { + assert.deepEqual(buildOwnerHeardNeighbors(undefined, direct), { + heardNeighbors: [{ id: 'direct', last_seen_at: '2026-09-08T01:00:00.000Z', rssi: -103.5, snr: 7.25, sampled_at: null }], + heardNeighborsSource: 'direct_packets', heardNeighborsSampledAt: null, }); -} - -test('last-hop prewarm and foreground reads share one in-flight refresh', async () => { - let calls = 0; - let release!: () => void; - const gate = new Promise((resolve) => { release = resolve; }); - const service = lastHopService(async () => { calls++; await gate; return { rows: [] }; }); - const prewarm = service.getOwnerLastHopStrength(['a'], 'a', true); - const foreground = service.getOwnerLastHopStrength(['a'], 'a'); - assert.equal(calls, 1); - release(); - assert.deepEqual(await prewarm, { points: [] }); - assert.deepEqual(await foreground, { points: [] }); - await service.getOwnerLastHopStrength(['a'], 'a'); - assert.equal(calls, 1); - await service.getOwnerLastHopStrength(['a'], 'a', true); - assert.equal(calls, 2); + assert.equal(buildOwnerHeardNeighbors(undefined, []).heardNeighborsSource, 'none'); + assert.equal(buildOwnerHeardNeighbors({ time, neighbors: null }, direct).heardNeighborsSource, 'direct_packets'); }); -test('last-hop cache respects the complete ownership exclusion scope', async () => { - const scopes: string[][] = []; - const service = lastHopService(async (_nodeIds, ownedNodeIds) => { scopes.push(ownedNodeIds); return { rows: [] }; }); - await service.getOwnerLastHopStrength(['a', 'b'], 'a'); - await service.getOwnerLastHopStrength(['a', 'c'], 'a'); - await service.getOwnerLastHopStrength(['b', 'a'], 'a'); - assert.deepEqual(scopes, [['a', 'b'], ['a', 'c']]); - await assert.rejects(service.getOwnerLastHopStrength(['b'], 'a', true), /NODE_NOT_OWNED/); +test('published lists, including empty lists, take precedence over direct packet fallback', () => { + const empty = buildOwnerHeardNeighbors({ time, neighbors: [] }, direct); + assert.equal(empty.heardNeighborsSource, 'reported'); + assert.equal(empty.heardNeighborsSampledAt, '2026-09-08T01:00:00.000Z'); + assert.deepEqual(empty.heardNeighbors, []); + const result = buildOwnerHeardNeighbors({ time, neighbors: [{ pubkey: 'reported', RSSI: -95, SNR: 0 }] }, direct); + assert.equal(result.heardNeighborsSource, 'reported'); + assert.deepEqual(result.heardNeighbors.map(n => n.id), ['reported']); + assert.equal(result.heardNeighbors[0]!.snr, 0); }); -test('a failed last-hop refresh releases single-flight state for retry', async () => { - let calls = 0; - const service = lastHopService(async () => { - if (++calls === 1) throw new Error('query failed'); - return { rows: [] }; - }); - await assert.rejects(service.getOwnerLastHopStrength(['a'], 'a', true), /query failed/); - assert.deepEqual(await service.getOwnerLastHopStrength(['a'], 'a', true), { points: [] }); - assert.equal(calls, 2); +test('neighbour timestamps normalize ISO, epoch seconds, milliseconds and relative ages before sorting', () => { + const epoch = Date.parse(time) / 1_000; + const result = buildOwnerHeardNeighbors({ time, neighbors: [ + { id: 'iso', last_seen: '2026-09-08T00:00:00-01:00', rssi: 'not a number' }, + { node_id: 'seconds', lastSeen: epoch - 1, snr_db: 1 }, + { public_key: 'milliseconds', last_seen: (epoch - 2) * 1_000 }, + { id: 'relative', heard_secs_ago: 3 }, + { id: 'unknown', last_seen: 'invalid', rssi: Infinity, snr: null }, + { id: 'fallback-timestamp', last_seen: 'invalid', lastSeen: String(epoch - 4) }, + { id: 'negative-age', heard_secs_ago: -10 }, + null, [], 'invalid', {}, { id: '' }, + ] }, direct); + assert.deepEqual(result.heardNeighbors.map(n => n.id), ['iso', 'seconds', 'milliseconds', 'relative', 'fallback-timestamp', 'unknown', 'negative-age']); + assert.equal(result.heardNeighbors[0]!.rssi, null); + assert.equal(result.heardNeighbors[3]!.last_seen_at, '2026-09-08T00:59:57.000Z'); + assert.equal(result.heardNeighbors[5]!.last_seen_at, null); + assert.equal(result.heardNeighbors[5]!.rssi, null); }); -function lastHopRow(bucket: string, sampleCount = 1) { - return { bucket, last_hop_node_id: 'peer', last_hop_name: 'Peer', resolution: 'resolved' as const, - avg_snr: 4, avg_rssi: -90, sample_count: sampleCount }; -} - -test('a warm refresh updates the current bucket, retains history and trims the rolling seven-day window', async (t) => { - const now = Date.parse('2026-10-02T12:00:00Z'); - t.mock.timers.enable({ apis: ['Date'], now }); - const expiring = new Date(now - 7 * 24 * 60 * 60_000 + 1_000).toISOString(); - const previous = '2026-10-02T10:00:00.000Z'; - const latest = '2026-10-02T11:00:00.000Z'; - const since: Array = []; - const service = lastHopService(async (_nodes, _scope, cursor) => { - since.push(cursor); - return { rows: cursor === undefined - ? [lastHopRow(expiring), lastHopRow(previous), lastHopRow(latest)] - : [lastHopRow(latest, 9), lastHopRow('2026-10-02T12:00:00.000Z', 2)] }; - }); - assert.equal((await service.getOwnerLastHopStrength(['a'], 'a')).points.length, 3); - t.mock.timers.tick(2_000); - const refreshed = await service.getOwnerLastHopStrength(['a'], 'a', true); - assert.deepEqual(since, [undefined, latest]); - assert.deepEqual(refreshed.points.map(point => [point.bucket, point.sampleCount]), [ - [previous, 1], [latest, 9], ['2026-10-02T12:00:00.000Z', 2], - ]); - assert.deepEqual(await service.getOwnerLastHopStrength(['a'], 'a'), refreshed); - assert.equal(since.length, 2, 'the completed warm refresh serves the next foreground read'); -}); - -test('warm last-hop refreshes replace groups when peer names or resolution change', async (t) => { - t.mock.timers.enable({ apis: ['Date'], now: Date.parse('2026-10-02T12:00:00Z') }); - const previous = '2026-10-02T10:00:00.000Z'; - const latest = '2026-10-02T11:00:00.000Z'; - const historical = lastHopRow(previous, 3); - const renamed = { ...lastHopRow(latest, 4), last_hop_name: 'Renamed peer' }; - const resolved = { ...lastHopRow(latest, 2), last_hop_node_id: 'new-peer', last_hop_name: 'New peer' }; - const service = lastHopService(async (_nodes, _scope, cursor) => ({ rows: cursor === undefined - ? [historical, lastHopRow(latest, 4), { - ...lastHopRow(latest, 2), last_hop_node_id: null, last_hop_name: 'Unresolved', resolution: 'unresolved', - }] - : [renamed, resolved] })); - await service.getOwnerLastHopStrength(['a'], 'a'); - const refreshed = await service.getOwnerLastHopStrength(['a'], 'a', true); - assert.deepEqual(refreshed.points.map(point => [point.bucket, point.lastHopNodeId, point.lastHopName, point.resolution, point.sampleCount]), [ - [previous, 'peer', 'Peer', 'resolved', 3], - [latest, 'peer', 'Renamed peer', 'resolved', 4], - [latest, 'new-peer', 'New peer', 'resolved', 2], - ]); - assert.deepEqual(await service.getOwnerLastHopStrength(['a'], 'a'), refreshed); -}); - -test('an empty warm last-hop result removes obsolete current groups and preserves earlier history', async (t) => { - t.mock.timers.enable({ apis: ['Date'], now: Date.parse('2026-10-02T12:00:00Z') }); - const previous = '2026-10-02T10:00:00.000Z'; - const latest = '2026-10-02T11:00:00.000Z'; - const service = lastHopService(async (_nodes, _scope, cursor) => ({ rows: cursor === undefined - ? [lastHopRow(previous, 3), lastHopRow(latest, 2)] : [] })); - await service.getOwnerLastHopStrength(['a'], 'a'); - const refreshed = await service.getOwnerLastHopStrength(['a'], 'a', true); - assert.deepEqual(refreshed.points.map(point => [point.bucket, point.sampleCount]), [[previous, 3]]); -}); - -test('a failed warm refresh preserves the prior foreground cache and allows a later retry', async (t) => { - t.mock.timers.enable({ apis: ['Date'], now: Date.parse('2026-10-02T12:00:00Z') }); - let calls = 0; - const service = lastHopService(async () => { - if (++calls === 2) throw new Error('warm query failed'); - return { rows: [lastHopRow('2026-10-02T11:00:00.000Z', calls)] }; - }); - const cached = await service.getOwnerLastHopStrength(['a'], 'a'); - await assert.rejects(service.getOwnerLastHopStrength(['a'], 'a', true), /warm query failed/); - assert.deepEqual(await service.getOwnerLastHopStrength(['a'], 'a'), cached); - assert.equal(calls, 2, 'failed refreshes must not evict useful cached data'); - assert.equal((await service.getOwnerLastHopStrength(['a'], 'a', true)).points[0]?.sampleCount, 3); -}); - -test('expired last-hop caches start a fresh bounded-window query instead of reusing an old cursor', async (t) => { - t.mock.timers.enable({ apis: ['Date'], now: Date.parse('2026-10-02T12:00:00Z') }); - const since: Array = []; - const service = lastHopService(async (_nodes, _scope, cursor) => { - since.push(cursor); - return { rows: [lastHopRow('2026-10-02T11:00:00.000Z')] }; - }); - await service.getOwnerLastHopStrength(['a'], 'a'); - t.mock.timers.tick(60_000); - await service.getOwnerLastHopStrength(['a'], 'a', true); - assert.deepEqual(since, [undefined, undefined]); -}); - -test('fresh foreground last-hop data stays responsive while a background warm refresh is pending', async (t) => { - t.mock.timers.enable({ apis: ['Date'], now: Date.parse('2026-10-02T12:00:00Z') }); - let release!: () => void; - const gate = new Promise(resolve => { release = resolve; }); - let calls = 0; - const service = lastHopService(async () => { - const count = ++calls; - if (count === 2) await gate; - return { rows: [lastHopRow('2026-10-02T11:00:00.000Z', count)] }; - }); - const cached = await service.getOwnerLastHopStrength(['a'], 'a'); - const warming = service.getOwnerLastHopStrength(['a'], 'a', true); - t.after(async () => { release(); await warming; }); - const joinedWarm = service.getOwnerLastHopStrength(['a'], 'a', true); - let foregroundSettled = false; - const foreground = service.getOwnerLastHopStrength(['a'], 'a').then(result => { - foregroundSettled = true; - return result; - }); - await new Promise(resolve => setImmediate(resolve)); - assert.equal(foregroundSettled, true, 'a fresh cache hit must not wait for a slow background query'); - assert.deepEqual(await foreground, cached); - assert.equal(calls, 2, 'overlapping warm requests still share the one refresh'); - release(); - assert.deepEqual(await joinedWarm, await warming); - assert.equal((await service.getOwnerLastHopStrength(['a'], 'a')).points[0]?.sampleCount, 2); - assert.equal(calls, 2); +test('neighbour cap keeps the latest 32 rather than the first 32 reported entries', () => { + const result = buildOwnerHeardNeighbors({ time, neighbors: Array.from({ length: 40 }, (_, i) => ({ id: String(i), heard_secs_ago: 39 - i })) }, []); + assert.equal(result.heardNeighbors.length, 32); + assert.equal(result.heardNeighbors[0]!.id, '39'); + assert.equal(result.heardNeighbors[31]!.id, '8'); }); diff --git a/backend/src/owner/ownerService.ts b/backend/src/owner/ownerService.ts index 83c32ac..7c1f6bd 100644 --- a/backend/src/owner/ownerService.ts +++ b/backend/src/owner/ownerService.ts @@ -58,6 +58,47 @@ function neighborTimestamp(value: unknown): string | null { return isoTimestamp(value); } +export function buildOwnerHeardNeighbors( + sample: { time: string; neighbors: unknown } | undefined, + directPeers: Array<{ node_id: string; last_seen: string | null; avg_rssi: number | null; avg_snr: number | null }>, +) { + const sampledAt = isoTimestamp(sample?.time); + const reported = sample !== undefined && Array.isArray(sample.neighbors); + // An explicitly empty published list is authoritative. The fallback is only + // for nodes that have not supplied a usable list in the reporting window. + const neighbors = reported ? (sample.neighbors as unknown[]).flatMap((entry) => { + if (!entry || typeof entry !== 'object' || Array.isArray(entry)) return []; + const item = entry as Record; + const id = [item['id'], item['node_id'], item['pubkey'], item['public_key']] + .find((value): value is string => typeof value === 'string' && value.trim() !== ''); + if (!id) return []; + const signal = (...values: unknown[]) => values.find((value): value is number => typeof value === 'number' && Number.isFinite(value)) ?? null; + const age = signal(item['heard_secs_ago']); + const relativeTime = sampledAt && age !== null && age >= 0 + ? neighborTimestamp(Date.parse(sampledAt) / 1_000 - age) + : null; + return [{ + id, + rssi: signal(item['rssi'], item['RSSI']), + snr: signal(item['snr'], item['SNR'], item['snr_db']), + last_seen_at: neighborTimestamp(item['last_seen']) ?? neighborTimestamp(item['lastSeen']) ?? relativeTime, + sampled_at: sampledAt, + }]; + }) : directPeers.map((peer) => ({ + id: peer.node_id, + rssi: nullableNumber(peer.avg_rssi), + snr: nullableNumber(peer.avg_snr), + last_seen_at: isoTimestamp(peer.last_seen), + sampled_at: null, + })); + neighbors.sort((a, b) => (Date.parse(b.last_seen_at ?? '') || 0) - (Date.parse(a.last_seen_at ?? '') || 0)); + return { + heardNeighbors: neighbors.slice(0, 32), + heardNeighborsSource: reported ? 'reported' as const : directPeers.length > 0 ? 'direct_packets' as const : 'none' as const, + heardNeighborsSampledAt: reported ? sampledAt : null, + }; +} + type OwnerServiceDeps = { ownerLiveCacheTtlMs: number; ownerLiveCache: Map; @@ -90,13 +131,6 @@ export function createOwnerService(deps: OwnerServiceDeps) { maxWeight: 64 * 1024 * 1024, ttlMs: ownerLastHopCacheTtlMs, }); - const ownerLastHopInflight = new Map>(); - - function lastHopCacheKey(nodeId: string, ownedNodeIds: string[]): string { - // Last-hop analysis excludes every node owned by this owner, so a shared - // node must never reuse a result from a different ownership scope. - return `${nodeId}|${[...new Set(ownedNodeIds)].sort().join(',')}`; - } const ownerDashboardCache = new BoundedTtlMap Date.parse(point.bucket) >= cutoff) .sort((a, b) => a.bucket.localeCompare(b.bucket)); } - function replaceLastHopPoints(existing: LastHopStrengthPoint[], recent: LastHopStrengthPoint[], since: string): LastHopStrengthPoint[] { - // The inclusive query recomputes every group from this bucket onward. Peer - // renames, resolution changes or removals must replace the old groups too. - const cursor = Date.parse(since); - return trimLastHopPoints([ - ...existing.filter((point) => Date.parse(point.bucket) < cursor), - ...recent, - ]); + function mergeLastHopPoints(existing: LastHopStrengthPoint[], recent: LastHopStrengthPoint[], windowDays: number): LastHopStrengthPoint[] { + const merged = new Map(); + for (const point of existing) { + merged.set(`${point.bucket}|${point.lastHopNodeId ?? ''}|${point.lastHopName}|${point.resolution}`, point); + } + for (const point of recent) { + merged.set(`${point.bucket}|${point.lastHopNodeId ?? ''}|${point.lastHopName}|${point.resolution}`, point); + } + return trimLastHopPoints(Array.from(merged.values()), windowDays); } function latestBucketOf(points: LastHopStrengthPoint[]): string | null { return points.length > 0 ? points[points.length - 1]!.bucket : null; } - async function buildOwnerLastHopResponse(nodeId: string, allOwnedNodeIds: string[], since?: string): Promise { - const lastHopStrengthResult = await repository.fetchLastHopStrength([nodeId], allOwnedNodeIds, since); + // Longer windows aggregate into larger chunks: the 30d series renders as + // 12-hour buckets (~60 points) instead of ~720 hourly points per repeater. + function lastHopBucketHours(windowDays: number): number { + return windowDays > 7 ? 12 : 1; + } + + async function buildOwnerLastHopResponse(nodeId: string, allOwnedNodeIds: string[], since?: string, windowDays = 7): Promise { + const lastHopStrengthResult = await repository.fetchLastHopStrength([nodeId], allOwnedNodeIds, since, windowDays, lastHopBucketHours(windowDays)); return mapLastHopRows(lastHopStrengthResult.rows); } @@ -283,20 +324,7 @@ export function createOwnerService(deps: OwnerServiceDeps) { last_seen: row.last_seen ? new Date(row.last_seen).toISOString() : null, })); - const heardNeighbors = heardNeighborsResult.rows - .map((row) => ({ - id: row.id, - rssi: nullableNumber(row.rssi), - snr: nullableNumber(row.snr), - last_seen_at: neighborTimestamp(row.last_seen), - sampled_at: isoTimestamp(row.sample_time), - })) - .sort((a, b) => { - const aTime = a.last_seen_at ? Date.parse(a.last_seen_at) : 0; - const bTime = b.last_seen_at ? Date.parse(b.last_seen_at) : 0; - return bTime - aTime; - }) - .slice(0, 32); + const neighborData = buildOwnerHeardNeighbors(heardNeighborsResult.rows[0], filteredIncomingRows); const linkHealth = linkHealthResult.rows.map((row) => ({ ...row, @@ -517,7 +545,7 @@ export function createOwnerService(deps: OwnerServiceDeps) { advertTrend24h, telemetry24h, status, - heardNeighbors, + ...neighborData, packetsSent24h: Number(packetsSentResult.rows[0]?.packets_24h ?? 0), packetsReceived24h: Number(packetsReceivedResult.rows[0]?.packets_24h ?? 0), alerts, @@ -530,7 +558,7 @@ export function createOwnerService(deps: OwnerServiceDeps) { return responseData; } - async function getOwnerLastHopStrength(ownedNodeIds: string[], requestedNodeId?: string, refresh = false): Promise { + async function getOwnerLastHopStrength(ownedNodeIds: string[], requestedNodeId?: string, windowDays = 7): Promise { if (ownedNodeIds.length < 1) { throw new Error('NO_OWNED_NODES'); } @@ -542,35 +570,62 @@ export function createOwnerService(deps: OwnerServiceDeps) { throw new Error('NODE_NOT_OWNED'); } - const cacheKey = lastHopCacheKey(selectedNodeId, ownedNodeIds); - // Serve fresh foreground hits while the background pass fills a replacement. + // Window is part of the cache key: the 7d and 30d series build and merge + // independently instead of trimming each other's points. + const cacheKey = `${windowDays}d:${selectedNodeId}`; const cacheEntry = ownerLastHopCache.get(cacheKey); - if (!refresh && cacheEntry && Date.now() - cacheEntry.ts < ownerLastHopCacheTtlMs) { + if (cacheEntry && Date.now() - cacheEntry.ts < ownerLastHopCacheTtlMs) { return cacheEntry.data; } - const inFlight = ownerLastHopInflight.get(cacheKey); - if (inFlight) return inFlight; - const load = async () => { - let responseData: OwnerLastHopResponse; - if (!cacheEntry || !cacheEntry.latestBucket) { - responseData = await buildOwnerLastHopResponse(selectedNodeId, ownedNodeIds); - } else { - const recent = await buildOwnerLastHopResponse(selectedNodeId, ownedNodeIds, cacheEntry.latestBucket); - responseData = { - points: replaceLastHopPoints(cacheEntry.data.points, recent.points, cacheEntry.latestBucket), - }; - } - ownerLastHopCache.set(cacheKey, { - ts: Date.now(), data: responseData, latestBucket: latestBucketOf(responseData.points), - }); - return responseData; - }; - const tracked = load().finally(() => { - if (ownerLastHopInflight.get(cacheKey) === tracked) ownerLastHopInflight.delete(cacheKey); + let responseData: OwnerLastHopResponse; + if (!cacheEntry || !cacheEntry.latestBucket) { + responseData = await buildOwnerLastHopResponse(selectedNodeId, ownedNodeIds, undefined, windowDays); + } else { + const recent = await buildOwnerLastHopResponse(selectedNodeId, ownedNodeIds, cacheEntry.latestBucket, windowDays); + responseData = { + points: mergeLastHopPoints(cacheEntry.data.points, recent.points, windowDays), + }; + } + + ownerLastHopCache.set(cacheKey, { + ts: Date.now(), + data: responseData, + latestBucket: latestBucketOf(responseData.points), }); - ownerLastHopInflight.set(cacheKey, tracked); - return tracked; + return responseData; + } + + // Forced refresh used by the background prewarm loop: rebuilds (or incrementally + // extends) the cached last-hop series for one node/window pair without the + // cache-freshness shortcut, so warm entries never expire out of the bounded TTL. + // Returns false when a recent entry was left untouched because of minAgeMs. + async function refreshOwnerLastHopStrength(ownedNodeIds: string[], nodeId: string, windowDays = 7, minAgeMs = 0): Promise { + if (!ownedNodeIds.includes(nodeId)) { + throw new Error('NODE_NOT_OWNED'); + } + const cacheKey = `${windowDays}d:${nodeId}`; + const cacheEntry = ownerLastHopCache.get(cacheKey); + if (cacheEntry && minAgeMs > 0 && Date.now() - cacheEntry.ts < minAgeMs) { + return false; + } + + let responseData: OwnerLastHopResponse; + if (cacheEntry && cacheEntry.latestBucket) { + const recent = await buildOwnerLastHopResponse(nodeId, ownedNodeIds, cacheEntry.latestBucket, windowDays); + responseData = { + points: mergeLastHopPoints(cacheEntry.data.points, recent.points, windowDays), + }; + } else { + responseData = await buildOwnerLastHopResponse(nodeId, ownedNodeIds, undefined, windowDays); + } + + ownerLastHopCache.set(cacheKey, { + ts: Date.now(), + data: responseData, + latestBucket: latestBucketOf(responseData.points), + }); + return true; } function clearOwnerSession(mqttUsername: string): void { @@ -579,7 +634,11 @@ export function createOwnerService(deps: OwnerServiceDeps) { ownerDashboardCache.delete(cacheKey); for (const nodeId of nodeIds) { ownerLiveCache.delete(nodeId); - ownerLastHopCache.delete(lastHopCacheKey(nodeId, nodeIds)); + for (const lastHopKey of Array.from(ownerLastHopCache.keys())) { + if (lastHopKey.endsWith(`:${nodeId}`)) { + ownerLastHopCache.delete(lastHopKey); + } + } } invalidateOwnerNodeIdCache(mqttUsername); } @@ -589,6 +648,7 @@ export function createOwnerService(deps: OwnerServiceDeps) { getSessionDashboard, getOwnerLiveData, getOwnerLastHopStrength, + refreshOwnerLastHopStrength, clearOwnerSession, }; } diff --git a/backend/src/owner/packetShareAuth.test.ts b/backend/src/owner/packetShareAuth.test.ts new file mode 100644 index 0000000..d98c2e6 --- /dev/null +++ b/backend/src/owner/packetShareAuth.test.ts @@ -0,0 +1,52 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { verifyAuthToken } from '@michaelhart/meshcore-decoder'; +import { generatePacketShareIdentity, packetShareCredentials } from './packetShareAuth.js'; +import { decryptPacketShareSecret, encryptPacketShareSecret } from './packetShareCrypto.js'; +import { destinationConfigured, type PacketShareDestinationRow } from './packetShareConfig.js'; + +const encryptionKey = 'test-packet-share-encryption-key'; + +test('generated MeshCore relay keys sign verifiable, renewable ScotMesh JWTs', async () => { + const identity = await generatePacketShareIdentity(encryptionKey); + assert.match(identity.username, /^v1_[0-9A-F]{64}$/); + assert.match(decryptPacketShareSecret(identity.privateKeyCiphertext, encryptionKey), /^[0-9a-f]{128}$/); + const destination: PacketShareDestinationRow = { + destination_id: 'scotmesh', display_name: 'ScotMesh', + broker_url: 'wss://scotmesh-mqtt.mm7roq.compute.oarc.uk:443/', + topic_template: 'meshcore/MME/{observerId}/packets', + broker_username: identity.username, broker_password_ciphertext: identity.privateKeyCiphertext, + auth_mode: 'meshcore_jwt', auth_audience: 'scotmesh-mqtt.mm7roq.compute.oarc.uk', enabled: true, + }; + assert.equal(destinationConfigured(destination), true); + const now = Math.floor(Date.now() / 1_000); + const credentials = await packetShareCredentials(destination, now, encryptionKey); + const claims = await verifyAuthToken(credentials.password!, identity.username.slice(3)); + assert.equal(claims?.aud, destination.auth_audience); + assert.equal(claims?.publicKey, identity.username.slice(3)); + assert.equal(claims?.iat, now); + assert.equal(claims?.exp, now + 3_600); + const renewed = await packetShareCredentials(destination, now + 60, encryptionKey); + assert.notEqual(renewed.password, credentials.password); + assert.equal(renewed.expiresAt, now + 3_660); + const otherIdentity = await generatePacketShareIdentity(encryptionKey); + await assert.rejects(packetShareCredentials({ ...destination, broker_username: otherIdentity.username }, now, encryptionKey), /does not match/); + assert.equal(destinationConfigured({ ...destination, auth_audience: 'another-broker.example' }), false); + assert.equal(destinationConfigured({ ...destination, broker_url: 'ws://scotmesh-mqtt.mm7roq.compute.oarc.uk:443/' }), false); + assert.equal(destinationConfigured(destination, false), false); +}); + +test('existing password and anonymous destinations keep their authentication', async () => { + const destination: PacketShareDestinationRow = { + destination_id: 'remote', display_name: 'Remote', broker_url: 'mqtts://example.test:8883', + topic_template: 'packets', broker_username: 'owner', + broker_password_ciphertext: encryptPacketShareSecret(' existing-password ', encryptionKey), enabled: true, + }; + const credentials = await packetShareCredentials(destination, undefined, encryptionKey); + assert.equal(credentials.username, 'owner'); + assert.equal(credentials.password, ' existing-password '); + assert.equal(credentials.expiresAt, Infinity); + const anonymous = await packetShareCredentials({ ...destination, broker_username: null, broker_password_ciphertext: null }); + assert.equal(anonymous.username, undefined); + assert.equal(anonymous.password, undefined); +}); diff --git a/backend/src/owner/packetShareAuth.ts b/backend/src/owner/packetShareAuth.ts new file mode 100644 index 0000000..e146c5f --- /dev/null +++ b/backend/src/owner/packetShareAuth.ts @@ -0,0 +1,56 @@ +import { createHash, randomBytes } from 'node:crypto'; +import { createAuthToken, Utils } from '@michaelhart/meshcore-decoder'; +import type { PacketShareDestinationRow } from './packetShareConfig.js'; +import { decryptPacketShareSecret, encryptPacketShareSecret } from './packetShareCrypto.js'; + +const TOKEN_TTL_SECONDS = 3_600; + +/** orlp/ed25519 uses a clamped SHA-512 expansion of a random 32-byte seed. */ +export async function generatePacketShareIdentity(encryptionKey?: string): Promise<{ + username: string; + privateKeyCiphertext: string; +}> { + const privateKey = createHash('sha512').update(randomBytes(32)).digest(); + privateKey[0] = privateKey[0]! & 248; + privateKey[31] = (privateKey[31]! & 63) | 64; + try { + const privateKeyHex = privateKey.toString('hex'); + const publicKey = (await Utils.derivePublicKey(privateKeyHex)).toUpperCase(); + return { + username: `v1_${publicKey}`, + privateKeyCiphertext: encryptPacketShareSecret(privateKeyHex, encryptionKey), + }; + } finally { + privateKey.fill(0); + } +} + +export async function packetShareCredentials( + destination: PacketShareDestinationRow, + nowSeconds = Math.floor(Date.now() / 1_000), + encryptionKey?: string, +): Promise<{ username?: string; password?: string; expiresAt: number }> { + const username = destination.broker_username?.trim() || undefined; + const secret = destination.broker_password_ciphertext + ? decryptPacketShareSecret(destination.broker_password_ciphertext, encryptionKey) + : undefined; + if (destination.auth_mode !== 'meshcore_jwt') { + return { username, password: secret, expiresAt: Infinity }; + } + const privateKey = secret?.trim(); + if (!username || !/^v1_[0-9A-F]{64}$/.test(username) + || !privateKey || !/^[0-9a-fA-F]{128}$/.test(privateKey) + || !destination.auth_audience) throw new Error('MeshCore signing credentials are incomplete'); + const publicKey = username.slice(3); + if ((await Utils.derivePublicKey(privateKey)).toUpperCase() !== publicKey) { + throw new Error('MeshCore signing key does not match the public key'); + } + const expiresAt = nowSeconds + TOKEN_TTL_SECONDS; + const password = await createAuthToken({ + publicKey, + aud: destination.auth_audience, + iat: nowSeconds, + exp: expiresAt, + }, privateKey, publicKey); + return { username, password, expiresAt }; +} diff --git a/backend/src/owner/packetShareConfig.test.ts b/backend/src/owner/packetShareConfig.test.ts index 2377750..51eb5ee 100644 --- a/backend/src/owner/packetShareConfig.test.ts +++ b/backend/src/owner/packetShareConfig.test.ts @@ -3,9 +3,12 @@ import test from 'node:test'; import { buildPacketSharePayload, destinationConfigured, + destinationAcceptsPacket, + destinationAllowedForOwner, isValidPacketShareWebsiteUrl, isForwardablePacket, renderPacketShareTopic, + renderDestinationTopic, } from './packetShareConfig.js'; import { decryptPacketShareSecret, encryptPacketShareSecret } from './packetShareCrypto.js'; import type { LivePacket } from '../types/index.js'; @@ -111,3 +114,37 @@ test('packet share secrets round-trip with authenticated encryption', () => { assert.equal(decryptPacketShareSecret(encrypted, 'test-encryption-key'), 'broker-password'); assert.throws(() => decryptPacketShareSecret(encrypted, 'wrong-encryption-key')); }); + +test('account and observer restrictions require exact matches', () => { + const scope = { allowed_owner_username: 'node1', allowed_node_id: packet.rxNodeId }; + assert.equal(destinationAllowedForOwner(scope, 'node1', packet.rxNodeId!), true); + assert.equal(destinationAllowedForOwner(scope, 'node2', packet.rxNodeId!), false); + assert.equal(destinationAllowedForOwner(scope, 'NODE1', packet.rxNodeId!), false); + assert.equal(destinationAllowedForOwner(scope, 'node1', 'C'.repeat(64)), false); +}); + +test('advert TX mode preserves RX but rejects other transmissions and relayed adverts', () => { + const destination = { tx_mode: 'advert' as const }; + assert.equal(destinationAcceptsPacket(destination, { ...packet, direction: 'rx' }, {}), true); + assert.equal(destinationAcceptsPacket(destination, { ...packet, direction: 'tx' }, {}), false); + assert.equal(destinationAcceptsPacket(destination, { ...packet, packetType: 4, direction: 'tx', srcNodeId: packet.rxNodeId }, {}), true); + assert.equal(destinationAcceptsPacket(destination, { ...packet, packetType: 4, direction: 'tx', srcNodeId: 'C'.repeat(64) }, {}), false); + assert.equal(destinationAcceptsPacket(destination, packet, { direction: 'tx' }), false); + assert.equal(destinationAcceptsPacket(destination, { ...packet, direction: 'unknown' }, {}), false); +}); + +test('ScotMesh uses MME and the relay identity in the MQTT topic and origin_id', () => { + const relayPublicKey = 'D'.repeat(64); + const destination = { + destination_id: 'scotmesh', display_name: 'ScotMesh', enabled: true, + broker_url: 'wss://scotmesh-mqtt.mm7roq.compute.oarc.uk:443/', + topic_template: 'meshcore/MME/{observerId}/packets', + broker_username: `v1_${relayPublicKey}`, broker_password_ciphertext: null, + auth_mode: 'meshcore_jwt' as const, + }; + assert.equal(renderDestinationTopic(destination, packet), `meshcore/MME/${relayPublicKey}/packets`); + const payload = buildPacketSharePayload(packet, { raw: '0102', origin: 'Gadget-RPT-TBeam', origin_id: packet.rxNodeId }, destination); + assert.equal(payload.origin_id, relayPublicKey); + assert.equal(payload.origin, 'Gadget-RPT-TBeam'); + assert.equal(payload.raw, '0102'); +}); diff --git a/backend/src/owner/packetShareConfig.ts b/backend/src/owner/packetShareConfig.ts index 5dad676..92c58ce 100644 --- a/backend/src/owner/packetShareConfig.ts +++ b/backend/src/owner/packetShareConfig.ts @@ -15,6 +15,11 @@ export type PacketShareDestinationRow = { topic_template: string | null; broker_username: string | null; broker_password_ciphertext: string | null; + allowed_owner_username?: string | null; + allowed_node_id?: string | null; + auth_mode?: 'password' | 'meshcore_jwt'; + auth_audience?: string | null; + tx_mode?: 'on' | 'advert'; enabled: boolean; }; @@ -79,7 +84,7 @@ export function isValidPacketShareTopicTemplate(template: string | null): boolea } export function destinationConfigured( - destination: Pick, + destination: Pick, encryptionKeyAvailable = true, ): boolean { if (!destination.enabled @@ -89,9 +94,37 @@ export function destinationConfigured( const password = destination.broker_password_ciphertext?.trim() || ''; if (Boolean(username) !== Boolean(password)) return false; if (password && (!encryptionKeyAvailable || !isPacketShareSecretCiphertext(password))) return false; + if (destination.auth_mode === 'meshcore_jwt') { + const url = new URL(destination.broker_url!.trim()); + if (url.protocol !== 'wss:' + || destination.auth_audience !== url.hostname + || !/^v1_[0-9A-F]{64}$/.test(username) + || !password) return false; + } return true; } +export function destinationAllowedForOwner( + destination: Pick, + ownerUsername: string, + nodeId: string, +): boolean { + return (destination.allowed_owner_username == null || destination.allowed_owner_username === ownerUsername) + && (destination.allowed_node_id == null || destination.allowed_node_id === nodeId); +} + +/** Match mqtt.tx advert: receive capture stays enabled; TX includes only own adverts. */ +export function destinationAcceptsPacket( + destination: Pick, + packet: LivePacket, + upstreamEnvelope: Record, +): boolean { + if (destination.tx_mode !== 'advert') return true; + const direction = String(packet.direction ?? upstreamEnvelope['direction'] ?? 'rx').trim().toLowerCase(); + if (direction === 'rx') return true; + return direction === 'tx' && packet.packetType === 4 && packet.srcNodeId === packet.rxNodeId; +} + export function renderPacketShareTopic(template: string, packet: LivePacket): string { if (!isValidPacketShareTopicTemplate(template)) { throw new Error('invalid packet share destination topic'); @@ -111,6 +144,13 @@ export function renderPacketShareTopic(template: string, packet: LivePacket): st return rendered; } +export function renderDestinationTopic(destination: PacketShareDestinationRow, packet: LivePacket): string { + const observerId = destination.auth_mode === 'meshcore_jwt' + ? destination.broker_username?.slice(3) + : packet.rxNodeId; + return renderPacketShareTopic(destination.topic_template!, { ...packet, rxNodeId: observerId }); +} + function copyEnvelopeValue(target: PacketShareForwardPayload, source: Record, key: string): void { const value = source[key]; if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { @@ -138,6 +178,7 @@ function packetDateParts(packet: LivePacket, timestamp: unknown): { time: string export function buildPacketSharePayload( packet: LivePacket, upstreamEnvelope: Record, + destination?: PacketShareDestinationRow, ): PacketShareForwardPayload { const envelope: PacketShareForwardPayload = {}; for (const key of [ @@ -150,10 +191,12 @@ export function buildPacketSharePayload( if (!('timestamp' in envelope)) envelope.timestamp = new Date(packet.ts).toISOString(); if (!('hash' in envelope)) envelope.hash = packet.packetHash; if (!('type' in envelope)) envelope.type = 'PACKET'; - if (!('direction' in envelope)) envelope.direction = 'rx'; + if (!('direction' in envelope)) envelope.direction = packet.direction === 'tx' ? 'tx' : 'rx'; if (!('origin_id' in envelope) && packet.rxNodeId) envelope.origin_id = packet.rxNodeId; if (!('packet_type' in envelope) && packet.packetType != null) envelope.packet_type = String(packet.packetType); if (!('len' in envelope) && typeof envelope.raw === 'string') envelope.len = String(Math.floor(envelope.raw.length / 2)); + // JWT brokers authorize both the topic and origin_id against the relay identity. + if (destination?.auth_mode === 'meshcore_jwt') envelope.origin_id = destination.broker_username!.slice(3); const dateParts = packetDateParts(packet, envelope.timestamp); if (dateParts.time) envelope.time = dateParts.time; diff --git a/backend/src/owner/packetShareForwarder.test.ts b/backend/src/owner/packetShareForwarder.test.ts index 25b2abb..637a073 100644 --- a/backend/src/owner/packetShareForwarder.test.ts +++ b/backend/src/owner/packetShareForwarder.test.ts @@ -3,8 +3,23 @@ import test from 'node:test'; import type { LivePacket } from '../types/index.js'; import type { PacketSharePublisher } from './packetShareForwarder.js'; import { createPacketShareForwarder } from './packetShareForwarder.js'; +import { generatePacketShareIdentity } from './packetShareAuth.js'; import type { PacketShareRepository } from './packetShareRepository.js'; +async function withGuard(promise: Promise, label: string): Promise { + let timer: NodeJS.Timeout | undefined; + try { + return await Promise.race([ + promise, + new Promise((_resolve, reject) => { + timer = setTimeout(() => reject(new Error(`${label} hung`)), 5_000); + }), + ]); + } finally { + if (timer) clearTimeout(timer); + } +} + const packet: LivePacket = { id: 'packet-event', packetHash: 'B'.repeat(16), @@ -97,3 +112,182 @@ test('forwarder does not queue a privacy-rejected packet', async () => { await forwarder.stop(); assert.equal(targets, 0); }); + +test('forwarder rejects a stale queued ScotMesh delivery for another account or observer', async () => { + let published = 0; + let failed = 0; + let claimed = false; + const repository = { + reclaimExpiredClaims: async () => 0, + claimDue: async () => { + if (claimed) return []; + claimed = true; + return [ + { id: '1', owner_username: 'node2', node_id: packet.rxNodeId, destination_id: 'scotmesh', attempts: 1 }, + { id: '2', owner_username: 'node1', node_id: 'C'.repeat(64), destination_id: 'scotmesh', attempts: 1 }, + ]; + }, + getDestination: async () => ({ allowed_owner_username: 'node1', allowed_node_id: packet.rxNodeId }), + markFailed: async () => { failed += 1; }, + } as unknown as PacketShareRepository; + const forwarder = createPacketShareForwarder({ repository, publisherFactory: () => { + published += 1; + throw new Error('unauthorized destination reached publisher'); + } }); + await forwarder.processDue(); + await forwarder.stop(); + assert.equal(failed, 2); + assert.equal(published, 0); +}); + +test('ScotMesh queues only the scoped repeater and applies advert TX mode with its relay identity', async () => { + const encryptionKey = 'test-packet-share-encryption-key'; + const previousKey = process.env['OWNER_PACKET_SHARE_ENCRYPTION_KEY']; + process.env['OWNER_PACKET_SHARE_ENCRYPTION_KEY'] = encryptionKey; + try { + const identity = await generatePacketShareIdentity(encryptionKey); + const target = { + destination_id: 'scotmesh', display_name: 'ScotMesh', enabled: true, + broker_url: 'wss://scotmesh-mqtt.mm7roq.compute.oarc.uk:443/', + topic_template: 'meshcore/MME/{observerId}/packets', + broker_username: identity.username, broker_password_ciphertext: identity.privateKeyCiphertext, + allowed_owner_username: 'node1', allowed_node_id: packet.rxNodeId, + owner_username: 'node1', node_id: packet.rxNodeId, + auth_mode: 'meshcore_jwt' as const, auth_audience: 'scotmesh-mqtt.mm7roq.compute.oarc.uk', tx_mode: 'advert' as const, + }; + const queued: Array<{ destinationTopic: string; payload: Record }> = []; + const repository = { + activeTargets: async () => [target, { ...target, owner_username: 'node2' }], + enqueueDelivery: async (input: typeof queued[number]) => { queued.push(input); return true; }, + reclaimExpiredClaims: async () => 0, + claimDue: async () => [], + } as unknown as PacketShareRepository; + const forwarder = createPacketShareForwarder({ repository }); + await forwarder.enqueuePacket({ ...packet, direction: 'rx' }, { raw: '0102' }); + await forwarder.enqueuePacket({ ...packet, direction: 'tx' }, { raw: '0102' }); + await forwarder.enqueuePacket({ ...packet, direction: 'tx', packetType: 4, srcNodeId: packet.rxNodeId }, { raw: '0102' }); + await forwarder.enqueuePacket({ ...packet, direction: 'tx', packetType: 4, srcNodeId: 'C'.repeat(64) }, { raw: '0102' }); + await forwarder.enqueuePacket({ ...packet, rxNodeId: 'C'.repeat(64) }, { raw: '0102' }); + await forwarder.stop(); + assert.equal(queued.length, 2); + for (const row of queued) { + assert.equal(row.destinationTopic, `meshcore/MME/${identity.username.slice(3)}/packets`); + assert.equal(row.payload['origin_id'], identity.username.slice(3)); + } + assert.equal(queued[0]?.payload['direction'], 'rx'); + assert.equal(queued[1]?.payload['direction'], 'tx'); + } finally { + if (previousKey === undefined) delete process.env['OWNER_PACKET_SHARE_ENCRYPTION_KEY']; + else process.env['OWNER_PACKET_SHARE_ENCRYPTION_KEY'] = previousKey; + } +}); + +const wedgeDestination = { + destination_id: 'remote-site', + display_name: 'Remote site', + broker_url: 'mqtts://remote.example.test:8883', + topic_template: 'replicated/{network}/{observerId}/packets', + broker_username: null, + broker_password_ciphertext: null, + enabled: true, +}; + +const wedgeRow = { + id: '1', + owner_username: 'node1', + node_id: packet.rxNodeId!, + destination_id: 'remote-site', + event_key: 'k', + packet_hash: packet.packetHash, + source_topic: packet.topic, + destination_topic: `replicated/ukmesh/${'A'.repeat(64)}/packets`, + payload: { raw: '0102' } as Record, + attempts: 1, +}; + +test('forwarder survives a hung publish: times out, fails for retry, then delivers on a fresh publisher', async () => { + let claims = 0; + const failed: Array<{ id: string; message: string }> = []; + const succeeded: string[] = []; + const repository = { + reclaimExpiredClaims: async () => 0, + claimDue: async () => { + claims += 1; + return claims <= 2 ? [wedgeRow] : []; + }, + getDestination: async () => wedgeDestination, + markSucceeded: async (id: string) => { succeeded.push(id); }, + markFailed: async (id: string, _claimToken: string, _attempts: number, message: string) => { + failed.push({ id, message }); + }, + } as unknown as PacketShareRepository; + const published: string[] = []; + let factories = 0; + const forwarder = createPacketShareForwarder({ + repository, + publisherFactory: () => { + factories += 1; + if (factories === 1) { + return { + // Settles only after the publish timeout has fired: the late + // rejection must be swallowed, not crash the process. + publish: () => new Promise((_resolve, reject) => { + setTimeout(() => reject(new Error('late publish failure')), 50); + }), + close: async () => undefined, + }; + } + return { + publish: async (topic: string) => { published.push(topic); }, + close: async () => undefined, + }; + }, + publishAckTimeoutMs: 20, + deliverTimeoutMs: 2_000, + }); + + await withGuard(forwarder.processDue(), 'first processDue'); + assert.equal(failed.length, 1, 'hung publish must fail the delivery after the timeout'); + assert.match(failed[0]!.message, /publish timed out/); + await new Promise((resolve) => setTimeout(resolve, 80)); + + await withGuard(forwarder.processDue(), 'second processDue'); + await forwarder.stop(); + + assert.equal(factories, 2, 'retry must use a fresh publisher'); + assert.equal(succeeded.length, 1, 'retry should deliver'); + assert.equal(published.length, 1); + assert.equal(published[0], `replicated/ukmesh/${'A'.repeat(64)}/packets`); +}); + +test('forwarder aborts a delivery past the per-row deadline even when the publisher never settles', async () => { + let claims = 0; + const failed: Array<{ id: string; message: string }> = []; + const repository = { + reclaimExpiredClaims: async () => 0, + claimDue: async () => { + claims += 1; + return claims === 1 ? [wedgeRow] : []; + }, + getDestination: async () => wedgeDestination, + markSucceeded: async () => undefined, + markFailed: async (id: string, _claimToken: string, _attempts: number, message: string) => { + failed.push({ id, message }); + }, + } as unknown as PacketShareRepository; + const forwarder = createPacketShareForwarder({ + repository, + publisherFactory: () => ({ + publish: () => new Promise(() => undefined), + close: async () => undefined, + }), + publishAckTimeoutMs: 10_000, + deliverTimeoutMs: 30, + }); + + await withGuard(forwarder.processDue(), 'processDue'); + await forwarder.stop(); + + assert.equal(failed.length, 1, 'the per-row deadline must release the claim'); + assert.match(failed[0]!.message, /delivery timed out/); +}); diff --git a/backend/src/owner/packetShareForwarder.ts b/backend/src/owner/packetShareForwarder.ts index 6251233..29fdc37 100644 --- a/backend/src/owner/packetShareForwarder.ts +++ b/backend/src/owner/packetShareForwarder.ts @@ -4,19 +4,24 @@ import type { LivePacket } from '../types/index.js'; import { buildPacketSharePayload, destinationConfigured, + destinationAcceptsPacket, + destinationAllowedForOwner, isForwardablePacket, isValidPacketShareBrokerUrl, packetShareEventKey, - renderPacketShareTopic, + renderDestinationTopic, type PacketShareDestinationRow, } from './packetShareConfig.js'; -import { decryptPacketShareSecret, hasPacketShareEncryptionKey } from './packetShareCrypto.js'; +import { hasPacketShareEncryptionKey } from './packetShareCrypto.js'; +import { packetShareCredentials } from './packetShareAuth.js'; import type { PacketShareDeliveryRow, PacketShareRepository } from './packetShareRepository.js'; import { packetShareOutcomesTotal, packetShareQueueDepth } from '../metrics.js'; const DEFAULT_POLL_INTERVAL_MS = 5_000; const DEFAULT_BATCH_SIZE = 25; const PUBLISH_TIMEOUT_MS = 10_000; +const DEFAULT_PUBLISH_ACK_TIMEOUT_MS = 10_000; +const DEFAULT_DELIVER_TIMEOUT_MS = 30_000; export type PacketSharePublisher = { publish: (topic: string, payload: string) => Promise; @@ -32,6 +37,21 @@ function positiveIntegerSetting(name: string, fallback: number, max: number): nu return Number.isFinite(parsed) && parsed > 0 ? Math.min(max, Math.floor(parsed)) : fallback; } +// A hung publisher promise must never be able to block the single-flight +// delivery loop. The 2026-10-12 wedge: a QoS-1 publish on a silently-dead +// connection never settled, freezing delivery for every destination until +// the process was restarted. +function withTimeout(promise: Promise, timeoutMs: number, message: string): Promise { + return new Promise((resolve, reject) => { + const timer = setTimeout(() => reject(new Error(`${message} (after ${timeoutMs}ms)`)), timeoutMs); + timer.unref(); + promise.then( + (value) => { clearTimeout(timer); resolve(value); }, + (error) => { clearTimeout(timer); reject(error); }, + ); + }); +} + function destinationUrl(destination: PacketShareDestinationRow): URL { if (!destination.broker_url) throw new Error('destination broker URL is missing'); if (!isValidPacketShareBrokerUrl(destination.broker_url)) { @@ -47,12 +67,18 @@ function destinationFingerprint(destination: PacketShareDestinationRow): string destination.topic_template, destination.broker_username, destination.broker_password_ciphertext, + destination.auth_mode, + destination.auth_audience, ]); } -function connectMqttPublisher(destination: PacketShareDestinationRow): PacketSharePublisher { +function connectMqttPublisher( + destination: PacketShareDestinationRow, + publishAckTimeoutMs: number, +): PacketSharePublisher { let client: MqttClient | null = null; let connecting: Promise | null = null; + let credentialsExpireAt = Infinity; const closeClient = async (force = true): Promise => { const current = client; @@ -62,51 +88,56 @@ function connectMqttPublisher(destination: PacketShareDestinationRow): PacketSha }; const connect = async (): Promise => { - if (client?.connected) return client; + if (client?.connected && Date.now() / 1_000 < credentialsExpireAt - 60) return client; if (connecting) return connecting; - const url = destinationUrl(destination); - const encryptedPassword = destination.broker_password_ciphertext?.trim() || ''; - const password = encryptedPassword - ? decryptPacketShareSecret(encryptedPassword) - : undefined; - const created = mqtt.connect(url.toString(), { - clientId: `meshcore-share-${randomUUID().replaceAll('-', '').slice(0, 24)}`, - username: destination.broker_username?.trim() || undefined, - password, - clean: true, - reconnectPeriod: 0, - connectTimeout: PUBLISH_TIMEOUT_MS, - }); - client = created; - created.on('error', (error) => { - console.error(`[packet-share] MQTT destination ${destination.destination_id} error:`, error.message); - }); - connecting = new Promise((resolve, reject) => { - let settled = false; - let timer: NodeJS.Timeout; - const cleanup = () => { - clearTimeout(timer); - created.removeListener('connect', onConnect); - created.removeListener('close', onClose); - }; - const finish = (error?: Error) => { - if (settled) return; - settled = true; - cleanup(); - if (error) { - if (client === created) client = null; - created.end(true); - reject(error); - } else { - resolve(created); - } - }; - const onConnect = () => finish(); - const onClose = () => finish(new Error('destination MQTT connection closed before connect')); - timer = setTimeout(() => finish(new Error('destination MQTT connection timed out')), PUBLISH_TIMEOUT_MS); - created.once('connect', onConnect); - created.once('close', onClose); - }).finally(() => { + connecting = (async () => { + if (client) { + const expired = client; + client = null; + await expired.endAsync(true).catch(() => undefined); + } + const url = destinationUrl(destination); + const credentials = await packetShareCredentials(destination); + credentialsExpireAt = credentials.expiresAt; + const created = mqtt.connect(url.toString(), { + clientId: `meshcore-share-${randomUUID().replaceAll('-', '').slice(0, 24)}`, + username: credentials.username, + password: credentials.password, + clean: true, + reconnectPeriod: 0, + connectTimeout: PUBLISH_TIMEOUT_MS, + }); + client = created; + created.on('error', (error) => { + console.error(`[packet-share] MQTT destination ${destination.destination_id} error:`, error.message); + }); + return new Promise((resolve, reject) => { + let settled = false; + let timer: NodeJS.Timeout; + const cleanup = () => { + clearTimeout(timer); + created.removeListener('connect', onConnect); + created.removeListener('close', onClose); + }; + const finish = (error?: Error) => { + if (settled) return; + settled = true; + cleanup(); + if (error) { + if (client === created) client = null; + created.end(true); + reject(error); + } else { + resolve(created); + } + }; + const onConnect = () => finish(); + const onClose = () => finish(new Error('destination MQTT connection closed before connect')); + timer = setTimeout(() => finish(new Error('destination MQTT connection timed out')), PUBLISH_TIMEOUT_MS); + created.once('connect', onConnect); + created.once('close', onClose); + }); + })().finally(() => { connecting = null; }); return connecting; @@ -115,7 +146,18 @@ function connectMqttPublisher(destination: PacketShareDestinationRow): PacketSha return { async publish(topic: string, payload: string): Promise { const current = await connect(); - await current.publishAsync(topic, payload, { qos: 1 }); + try { + await withTimeout( + current.publishAsync(topic, payload, { qos: 1 }), + publishAckTimeoutMs, + 'destination MQTT publish timed out', + ); + } catch (error) { + // A publish left in an unknown state must not linger: discard the + // client so the next attempt reconnects with fresh credentials. + await closeClient(true); + throw error; + } if (!current.connected) { await closeClient(); throw new Error('destination MQTT connection closed after publish'); @@ -130,10 +172,19 @@ export type PacketShareForwarderDeps = { publisherFactory?: PacketSharePublisherFactory; pollIntervalMs?: number; batchSize?: number; + publishAckTimeoutMs?: number; + deliverTimeoutMs?: number; }; export function createPacketShareForwarder(deps: PacketShareForwarderDeps) { - const publisherFactory = deps.publisherFactory ?? connectMqttPublisher; + const publishAckTimeoutMs = deps.publishAckTimeoutMs ?? positiveIntegerSetting( + 'OWNER_PACKET_SHARE_PUBLISH_TIMEOUT_MS', DEFAULT_PUBLISH_ACK_TIMEOUT_MS, 120_000, + ); + const deliverTimeoutMs = deps.deliverTimeoutMs ?? positiveIntegerSetting( + 'OWNER_PACKET_SHARE_DELIVER_TIMEOUT_MS', DEFAULT_DELIVER_TIMEOUT_MS, 600_000, + ); + const publisherFactory: PacketSharePublisherFactory = deps.publisherFactory + ?? ((destination) => connectMqttPublisher(destination, publishAckTimeoutMs)); const pollIntervalMs = deps.pollIntervalMs ?? positiveIntegerSetting( 'OWNER_PACKET_SHARE_POLL_INTERVAL_MS', DEFAULT_POLL_INTERVAL_MS, 60_000, ); @@ -167,13 +218,15 @@ export function createPacketShareForwarder(deps: PacketShareForwarderDeps) { if (targets.length < 1) return; for (const target of targets) { + if (!destinationAllowedForOwner(target, target.owner_username, packet.rxNodeId!) + || !destinationAcceptsPacket(target, packet, upstreamEnvelope)) continue; if (!destinationConfigured(target, hasPacketShareEncryptionKey())) { packetShareOutcomesTotal.inc({ outcome: 'unconfigured' }); continue; } let destinationTopic: string; try { - destinationTopic = renderPacketShareTopic(target.topic_template!, packet); + destinationTopic = renderDestinationTopic(target, packet); } catch (error) { packetShareOutcomesTotal.inc({ outcome: 'invalid_destination' }); console.error('[packet-share] invalid destination topic:', error instanceof Error ? error.message : error); @@ -181,7 +234,7 @@ export function createPacketShareForwarder(deps: PacketShareForwarderDeps) { } try { const eventKey = packetShareEventKey(target.owner_username, target.destination_id, packet); - const payload = buildPacketSharePayload(packet, upstreamEnvelope); + const payload = buildPacketSharePayload(packet, upstreamEnvelope, target); const inserted = await deps.repository.enqueueDelivery({ ownerUsername: target.owner_username, nodeId: packet.rxNodeId!, @@ -202,14 +255,19 @@ export function createPacketShareForwarder(deps: PacketShareForwarderDeps) { async function deliver(row: PacketShareDeliveryRow, claimToken: string): Promise { const destination = await deps.repository.getDestination(row.destination_id); - if (!destination || !destinationConfigured(destination, hasPacketShareEncryptionKey())) { + if (!destination || !destinationAllowedForOwner(destination, row.owner_username, row.node_id) + || !destinationConfigured(destination, hasPacketShareEncryptionKey())) { await deps.repository.markFailed(row.id, claimToken, row.attempts, 'destination is missing or incomplete'); packetShareOutcomesTotal.inc({ outcome: 'delivery_error' }); return; } try { const publisher = await publisherFor(destination); - await publisher.publish(row.destination_topic, JSON.stringify(row.payload)); + await withTimeout( + publisher.publish(row.destination_topic, JSON.stringify(row.payload)), + publishAckTimeoutMs, + 'destination MQTT publish timed out', + ); await deps.repository.markSucceeded(row.id, claimToken); packetShareOutcomesTotal.inc({ outcome: 'delivered' }); } catch (error) { @@ -236,7 +294,23 @@ export function createPacketShareForwarder(deps: PacketShareForwarderDeps) { const claimToken = randomUUID(); const rows = await deps.repository.claimDue(batchSize, claimToken); packetShareQueueDepth.set(rows.length); - await Promise.all(rows.map((row) => deliver(row, claimToken))); + await Promise.all(rows.map(async (row) => { + try { + await withTimeout( + deliver(row, claimToken), + deliverTimeoutMs, + `delivery timed out (row ${row.id}, ${row.destination_id})`, + ); + } catch (error) { + // Last-resort guard: a delivery that never settled must not wedge + // the loop. Release the claim so the row retries on a backoff. + const message = error instanceof Error ? error.message : String(error); + console.error('[packet-share] delivery aborted:', message); + await deps.repository.markFailed(row.id, claimToken, row.attempts, message) + .catch(() => undefined); + packetShareOutcomesTotal.inc({ outcome: 'delivery_error' }); + } + })); } catch (error) { packetShareOutcomesTotal.inc({ outcome: 'worker_error' }); console.error('[packet-share] worker error:', error instanceof Error ? error.message : error); diff --git a/backend/src/owner/packetShareRepository.integration.test.ts b/backend/src/owner/packetShareRepository.integration.test.ts new file mode 100644 index 0000000..fa2a8df --- /dev/null +++ b/backend/src/owner/packetShareRepository.integration.test.ts @@ -0,0 +1,63 @@ +import assert from 'node:assert/strict'; +import fs from 'node:fs/promises'; +import { randomUUID } from 'node:crypto'; +import test from 'node:test'; +import pg from 'pg'; +import { createPacketShareRepository } from './packetShareRepository.js'; +import type { DatabaseQueryFn } from '../db/index.js'; +import type { LivePacket } from '../types/index.js'; + +const databaseUrl = process.env['PACKET_SHARE_TEST_DATABASE_URL']; + +test('PostgreSQL enforces ScotMesh owner and observer scope for reads, rules and queue insertion', { + skip: !databaseUrl, +}, async () => { + const schema = `packet_share_test_${randomUUID().replaceAll('-', '')}`; + const pool = new pg.Pool({ connectionString: databaseUrl, max: 1, options: `-c search_path=${schema}` }); + const nodeId = 'A'.repeat(64); + const packet: LivePacket = { + id: 'event', packetHash: 'B'.repeat(16), rxNodeId: nodeId, + topic: `meshcore/LHR/${nodeId}/packets`, ts: Date.now(), visibilityOk: true, + }; + try { + await pool.query(`CREATE SCHEMA ${schema}`); + for (const migration of [ + '052_owner_packet_sharing.sql', '053_owner_packet_share_destination_metadata.sql', + '054_owner_packet_share_default_off.sql', '061_packet_share_owner_scope_and_jwt.sql', + ]) { + await pool.query(await fs.readFile(new URL(`../db/migrations/${migration}`, import.meta.url), 'utf8')); + } + await pool.query( + `INSERT INTO owner_packet_share_destinations (destination_id, display_name, enabled, allowed_owner_username, allowed_node_id) + VALUES ('scotmesh', 'ScotMesh', TRUE, 'node1', $1), ('existing', 'Existing', TRUE, NULL, NULL)`, [nodeId], + ); + const query: DatabaseQueryFn = async (sql, params) => pool.query(sql, params); + const repository = createPacketShareRepository(query); + assert.deepEqual((await repository.listDestinations('node1', nodeId)).map((row) => row.destination_id), ['existing', 'scotmesh']); + assert.deepEqual((await repository.listDestinations('node2', nodeId)).map((row) => row.destination_id), ['existing']); + assert.deepEqual((await repository.listDestinations('node1', 'C'.repeat(64))).map((row) => row.destination_id), ['existing']); + assert.deepEqual(await repository.replaceOwnerRules('node2', nodeId, ['scotmesh'], true), []); + assert.deepEqual(await repository.replaceOwnerRules('node1', 'C'.repeat(64), ['scotmesh'], true), []); + assert.deepEqual(await repository.replaceOwnerRules('node1', nodeId, ['scotmesh'], true), ['scotmesh']); + // Simulate old rules created before scope enforcement; they must not become targets. + await pool.query( + `INSERT INTO owner_packet_share_rules (owner_username, node_id, destination_id, enabled) + VALUES ('node2', $1, 'scotmesh', TRUE), ('node1', $2, 'scotmesh', TRUE)`, [nodeId, 'C'.repeat(64)], + ); + assert.deepEqual(await repository.listOwnerRules('node2', nodeId), []); + assert.deepEqual((await repository.activeTargets(nodeId)).map((row) => row.owner_username), ['node1']); + assert.deepEqual(await repository.activeTargets('C'.repeat(64)), []); + for (const [ownerUsername, observer, expected] of [ + ['node1', nodeId, true], ['node2', nodeId, false], ['node1', 'C'.repeat(64), false], + ] as const) { + assert.equal(await repository.enqueueDelivery({ + ownerUsername, nodeId: observer, destinationId: 'scotmesh', + eventKey: `${ownerUsername}:${observer}`, packet: { ...packet, rxNodeId: observer }, + destinationTopic: `meshcore/MME/${observer}/packets`, payload: { raw: '0102' }, + }), expected); + } + } finally { + await pool.query(`DROP SCHEMA IF EXISTS ${schema} CASCADE`); + await pool.end(); + } +}); diff --git a/backend/src/owner/packetShareRepository.ts b/backend/src/owner/packetShareRepository.ts index d9c7c6c..4d35a42 100644 --- a/backend/src/owner/packetShareRepository.ts +++ b/backend/src/owner/packetShareRepository.ts @@ -1,6 +1,6 @@ import type { DatabaseQueryFn } from '../db/index.js'; import type { LivePacket } from '../types/index.js'; -import type { PacketShareDestinationRow, PacketShareTarget } from './packetShareConfig.js'; +import { destinationAllowedForOwner, type PacketShareDestinationRow, type PacketShareTarget } from './packetShareConfig.js'; export type PacketShareQueryFn = DatabaseQueryFn; export type PacketShareTransactionFn = (work: (query: PacketShareQueryFn) => Promise) => Promise; @@ -22,22 +22,27 @@ export function createPacketShareRepository( query: PacketShareQueryFn, transaction?: PacketShareTransactionFn, ) { - async function listDestinations(): Promise { + async function listDestinations(ownerUsername: string, nodeId: string): Promise { const result = await query( `SELECT destination_id, display_name, website_url, description, broker_url, topic_template, - broker_username, broker_password_ciphertext, enabled + broker_username, broker_password_ciphertext, enabled, + allowed_owner_username, allowed_node_id, auth_mode, auth_audience, tx_mode FROM owner_packet_share_destinations + WHERE (allowed_owner_username IS NULL OR allowed_owner_username = $1) + AND (allowed_node_id IS NULL OR allowed_node_id = $2) ORDER BY display_name, destination_id`, + [ownerUsername, nodeId], ); - return result.rows; + return result.rows.filter((row) => destinationAllowedForOwner(row, ownerUsername, nodeId)); } async function getDestination(destinationId: string): Promise { const result = await query( `SELECT destination_id, display_name, website_url, description, broker_url, topic_template, - broker_username, broker_password_ciphertext, enabled + broker_username, broker_password_ciphertext, enabled, + allowed_owner_username, allowed_node_id, auth_mode, auth_audience, tx_mode FROM owner_packet_share_destinations WHERE destination_id = $1`, [destinationId], @@ -47,10 +52,13 @@ export function createPacketShareRepository( async function listOwnerRules(ownerUsername: string, nodeId: string): Promise { const result = await query<{ destination_id: string }>( - `SELECT destination_id - FROM owner_packet_share_rules - WHERE owner_username = $1 AND node_id = $2 AND enabled = TRUE - ORDER BY destination_id`, + `SELECT rules.destination_id + FROM owner_packet_share_rules rules + JOIN owner_packet_share_destinations destination USING (destination_id) + WHERE rules.owner_username = $1 AND rules.node_id = $2 AND rules.enabled = TRUE + AND (destination.allowed_owner_username IS NULL OR destination.allowed_owner_username = $1) + AND (destination.allowed_node_id IS NULL OR destination.allowed_node_id = $2) + ORDER BY rules.destination_id`, [ownerUsername, nodeId], ); return result.rows.map((row) => row.destination_id); @@ -77,17 +85,20 @@ export function createPacketShareRepository( destination.website_url, destination.description, destination.broker_url, destination.topic_template, destination.broker_username, destination.broker_password_ciphertext, - destination.enabled + destination.enabled, destination.allowed_owner_username, destination.allowed_node_id, + destination.auth_mode, destination.auth_audience, destination.tx_mode FROM owner_packet_share_rules rules JOIN owner_packet_share_destinations destination ON destination.destination_id = rules.destination_id WHERE rules.node_id = $1 AND rules.enabled = TRUE AND destination.enabled = TRUE + AND (destination.allowed_owner_username IS NULL OR destination.allowed_owner_username = rules.owner_username) + AND (destination.allowed_node_id IS NULL OR destination.allowed_node_id = rules.node_id) ORDER BY destination.destination_id`, [nodeId], ); - return result.rows; + return result.rows.filter((row) => destinationAllowedForOwner(row, row.owner_username, row.node_id)); } async function replaceOwnerRules( @@ -122,6 +133,8 @@ export function createPacketShareRepository( FROM owner_packet_share_destinations WHERE destination_id = ANY($3::text[]) AND enabled = TRUE + AND (allowed_owner_username IS NULL OR allowed_owner_username = $1) + AND (allowed_node_id IS NULL OR allowed_node_id = $2) RETURNING destination_id`, [ownerUsername, nodeId, destinationIds], ); @@ -158,6 +171,8 @@ export function createPacketShareRepository( AND rules.destination_id = $3 AND rules.enabled = TRUE AND destination.enabled = TRUE + AND (destination.allowed_owner_username IS NULL OR destination.allowed_owner_username = rules.owner_username) + AND (destination.allowed_node_id IS NULL OR destination.allowed_node_id = rules.node_id) ) ON CONFLICT (event_key) DO NOTHING RETURNING id`, diff --git a/backend/src/owner/publicFeedConfig.test.ts b/backend/src/owner/publicFeedConfig.test.ts new file mode 100644 index 0000000..a046eee --- /dev/null +++ b/backend/src/owner/publicFeedConfig.test.ts @@ -0,0 +1,71 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import type { LivePacket } from '../types/index.js'; +import { + buildPublicFeedPacket, + getPublicFeedConfig, + isValidPublicFeedBrokerUrl, + publicFeedPacketIsEligible, +} from './publicFeedConfig.js'; + +const packet: LivePacket = { + id: 'public-feed-event', + packetHash: 'B'.repeat(16), + rxNodeId: 'A'.repeat(64), + topic: `meshcore/LHR/${'A'.repeat(64)}/packets`, + iata: 'LHR', + network: 'ukmesh', + packetType: 5, + visibilityOk: true, + ts: Date.parse('2026-09-20T12:00:00Z'), +}; + +test('public feed configuration requires the explicit operator switch and broker endpoint', () => { + assert.equal(getPublicFeedConfig({}).configured, false); + assert.equal(getPublicFeedConfig({ + PUBLIC_FEED_PUBLISH_ENABLED: 'true', + PUBLIC_FEED_BROKER_URL: 'mqtts://broker.example.test:8883', + }).configured, true); + assert.equal(getPublicFeedConfig({ + PUBLIC_FEED_PUBLISH_ENABLED: 'true', + PUBLIC_FEED_BROKER_URL: 'mqtts://user:pass@broker.example.test:8883', + }).configured, false); +}); + +test('public feed serializer emits CoreScope topic and numeric JSON fields without a wrapper', () => { + const outbound = buildPublicFeedPacket(packet, { + raw: '04719600004AA03688E8F83CEEB7B40DE40D9E2DB29FED705273C5C1FF3DBC375E5916AB5F24649A54AA7BCF3664', + origin: '49Segre-Room-Obs', + timestamp: '2026-09-20T12:00:00.000Z', + SNR: '12.2', + RSSI: '-30', + score: '1000', + direction: 'rx', + body: 'decoded content must not cross the public-feed boundary', + }); + assert.equal(outbound.topic, `public/LHR/${'A'.repeat(64)}/packets`); + assert.deepEqual(outbound.payload, { + raw: '04719600004AA03688E8F83CEEB7B40DE40D9E2DB29FED705273C5C1FF3DBC375E5916AB5F24649A54AA7BCF3664', + origin: '49Segre-Room-Obs', + timestamp: '2026-09-20T12:00:00.000Z', + SNR: 12.2, + RSSI: -30, + score: 1000, + direction: 'rx', + }); +}); + +test('public feed serializer rejects bare or malformed raw packets and invalid regions', () => { + assert.throws(() => buildPublicFeedPacket(packet, { raw: 'not-hex' })); + assert.throws(() => buildPublicFeedPacket(packet, { raw: 'ABC' })); + assert.throws(() => buildPublicFeedPacket({ ...packet, iata: 'unknown region' }, { raw: '0102' })); + assert.equal(publicFeedPacketIsEligible({ ...packet, visibilityOk: false }), false); + assert.equal(publicFeedPacketIsEligible({ ...packet, iata: undefined }), false); +}); + +test('public feed broker validation accepts MQTT transports without embedded credentials', () => { + assert.equal(isValidPublicFeedBrokerUrl('mqtts://broker.example.test:8883'), true); + assert.equal(isValidPublicFeedBrokerUrl('wss://broker.example.test/mqtt'), true); + assert.equal(isValidPublicFeedBrokerUrl('mqtts://user:pass@broker.example.test:8883'), false); + assert.equal(isValidPublicFeedBrokerUrl('https://broker.example.test'), false); +}); diff --git a/backend/src/owner/publicFeedConfig.ts b/backend/src/owner/publicFeedConfig.ts new file mode 100644 index 0000000..c76f3b6 --- /dev/null +++ b/backend/src/owner/publicFeedConfig.ts @@ -0,0 +1,200 @@ +import type { LivePacket } from '../types/index.js'; + +const PUBLIC_FEED_PROTOCOLS = new Set(['mqtt:', 'mqtts:', 'ws:', 'wss:']); +const MAX_RAW_HEX_LENGTH = 128 * 1024; +const MAX_PAYLOAD_BYTES = 64 * 1024; +const MAX_ORIGIN_LENGTH = 256; + +/** Curated namespace exposed to authenticated public-feed subscribers. */ +export const PUBLIC_FEED_TOPIC_FILTER = 'public/+/+/packets'; +const PUBLIC_FEED_TOPIC_RE = /^public\/[A-Z0-9]{2,8}\/[0-9A-F]{64}\/packets$/; + +export type PublicFeedConfig = { + operatorEnabled: boolean; + configured: boolean; + brokerUrl: string | null; + /** + * Kept for backwards-compatible config parsing. The public feed is served + * by the UKmesh broker itself, so it never uses a separate publisher + * credential. + */ + username: string | null; + password: string | null; +}; + +export type PublicFeedPacketPayload = { + raw: string; + origin?: string; + timestamp?: string; + SNR?: number; + RSSI?: number; + score?: number; + direction?: 'rx' | 'tx'; +}; + +export type PublicFeedPacket = { + topic: string; + payload: PublicFeedPacketPayload; +}; + +function envBoolean(value: string | undefined, fallback: boolean): boolean { + if (value == null || value.trim() === '') return fallback; + return ['1', 'true', 'yes', 'on'].includes(value.trim().toLowerCase()); +} + +function validBrokerUrl(rawUrl: string): boolean { + try { + const url = new URL(rawUrl); + return PUBLIC_FEED_PROTOCOLS.has(url.protocol) + && Boolean(url.hostname) + && !url.username + && !url.password; + } catch { + return false; + } +} + +export function getPublicFeedConfig(env: NodeJS.ProcessEnv = process.env): PublicFeedConfig { + const operatorEnabled = envBoolean(env['PUBLIC_FEED_PUBLISH_ENABLED'], false); + const brokerUrl = String(env['PUBLIC_FEED_BROKER_URL'] ?? '').trim() || null; + const username = String(env['PUBLIC_FEED_BROKER_USERNAME'] ?? '').trim() || null; + const password = env['PUBLIC_FEED_BROKER_PASSWORD'] ?? null; + const configured = operatorEnabled + && brokerUrl !== null + && validBrokerUrl(brokerUrl); + return { + operatorEnabled, + configured, + brokerUrl, + username, + password, + }; +} + +function finiteNumber(value: unknown): number | undefined { + if (typeof value === 'number') return Number.isFinite(value) ? value : undefined; + if (typeof value !== 'string' || value.trim() === '') return undefined; + const parsed = Number(value); + return Number.isFinite(parsed) ? parsed : undefined; +} + +function boundedNumber(value: unknown, minimum: number, maximum: number): number | undefined { + const parsed = finiteNumber(value); + return parsed !== undefined && parsed >= minimum && parsed <= maximum ? parsed : undefined; +} + +function timestampValue(value: unknown, fallbackMs: number): string { + const candidate = typeof value === 'string' || typeof value === 'number' + ? new Date(value) + : new Date(fallbackMs); + const date = Number.isFinite(candidate.getTime()) ? candidate : new Date(fallbackMs); + if (!Number.isFinite(date.getTime())) throw new Error('public feed packet timestamp is invalid'); + return date.toISOString(); +} + +function packetRegion(packet: LivePacket): string { + const region = String(packet.iata ?? '').trim().toUpperCase(); + if (!/^[A-Z0-9]{2,8}$/.test(region)) { + throw new Error('public feed packet region is invalid'); + } + return region; +} + +function packetObserverId(packet: LivePacket): string { + const observerId = String(packet.rxNodeId ?? '').trim().toUpperCase(); + if (!/^[0-9A-F]{64}$/.test(observerId)) { + throw new Error('public feed packet observer ID is invalid'); + } + return observerId; +} + +function packetRaw(upstreamEnvelope: Record): string { + const raw = typeof upstreamEnvelope['raw'] === 'string' + ? upstreamEnvelope['raw'].trim().toUpperCase() + : ''; + if ( + raw.length < 2 + || raw.length > MAX_RAW_HEX_LENGTH + || raw.length % 2 !== 0 + || !/^[0-9A-F]+$/.test(raw) + ) { + throw new Error('public feed packet raw bytes are missing or invalid'); + } + return raw; +} + +function copyOrigin(payload: PublicFeedPacketPayload, value: unknown): void { + if (typeof value !== 'string') return; + const origin = value.trim().slice(0, MAX_ORIGIN_LENGTH); + if (origin) payload.origin = origin; +} + +function copyRadioMetric( + payload: PublicFeedPacketPayload, + key: 'SNR' | 'RSSI' | 'score', + value: unknown, + minimum: number, + maximum: number, +): void { + const parsed = boundedNumber(value, minimum, maximum); + if (parsed !== undefined) payload[key] = parsed; +} + +/** + * Produce the canonical CoreScope-compatible public packet envelope. Only the + * original raw packet and bounded radio metadata are copied; decoded content + * and the private relay wrapper never cross this boundary. + */ +export function buildPublicFeedPacket( + packet: LivePacket, + upstreamEnvelope: Record, +): PublicFeedPacket { + const payload: PublicFeedPacketPayload = { + raw: packetRaw(upstreamEnvelope), + timestamp: timestampValue(upstreamEnvelope['timestamp'], packet.ts), + }; + copyOrigin(payload, upstreamEnvelope['origin']); + copyRadioMetric(payload, 'SNR', upstreamEnvelope['SNR'], -200, 200); + copyRadioMetric(payload, 'RSSI', upstreamEnvelope['RSSI'], -200, 100); + copyRadioMetric(payload, 'score', upstreamEnvelope['score'], -1_000_000, 1_000_000); + + const direction = typeof upstreamEnvelope['direction'] === 'string' + ? upstreamEnvelope['direction'].trim().toLowerCase() + : typeof packet.direction === 'string' + ? packet.direction.trim().toLowerCase() + : 'rx'; + payload.direction = direction === 'tx' ? 'tx' : 'rx'; + + if (Buffer.byteLength(JSON.stringify(payload), 'utf8') > MAX_PAYLOAD_BYTES) { + throw new Error('public feed packet payload exceeds configured limit'); + } + + return { + topic: `public/${packetRegion(packet)}/${packetObserverId(packet)}/packets`, + payload, + }; +} + +export function isPublicFeedPacketTopic(topic: string): boolean { + return PUBLIC_FEED_TOPIC_RE.test(topic.trim()); +} + +export function publicFeedEventKey(packet: LivePacket): string { + const observerId = packetObserverId(packet); + return `${observerId}:${packet.packetHash}`; +} + +export function publicFeedPacketIsEligible(packet: LivePacket): boolean { + return packet.visibilityOk === true + && packet.network !== 'test' + && typeof packet.rxNodeId === 'string' + && /^[0-9A-F]{64}$/.test(packet.rxNodeId) + && /^[0-9A-F]{16,128}$/.test(packet.packetHash) + && /^[A-Z0-9]{2,8}$/.test(String(packet.iata ?? '').trim().toUpperCase()); +} + +export function isValidPublicFeedBrokerUrl(rawUrl: string | null | undefined): boolean { + return typeof rawUrl === 'string' + && rawUrl.trim().length > 0 + && validBrokerUrl(rawUrl.trim()); +} diff --git a/backend/src/owner/publicFeedForwarder.test.ts b/backend/src/owner/publicFeedForwarder.test.ts new file mode 100644 index 0000000..068f527 --- /dev/null +++ b/backend/src/owner/publicFeedForwarder.test.ts @@ -0,0 +1,131 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import type { LivePacket } from '../types/index.js'; +import { createPublicFeedForwarder, type PublicFeedPublisher } from './publicFeedForwarder.js'; +import type { PublicFeedRepository } from './publicFeedRepository.js'; + +const packet: LivePacket = { + id: 'public-feed-event', + packetHash: 'B'.repeat(16), + rxNodeId: 'A'.repeat(64), + topic: `meshcore/LHR/${'A'.repeat(64)}/packets`, + iata: 'LHR', + network: 'ukmesh', + packetType: 5, + visibilityOk: true, + ts: Date.parse('2026-09-20T12:00:00Z'), +}; + +function configuredEnv(): Record { + return { + PUBLIC_FEED_PUBLISH_ENABLED: 'true', + PUBLIC_FEED_BROKER_URL: 'mqtts://broker.example.test:8883', + PUBLIC_FEED_BROKER_USERNAME: 'publisher', + PUBLIC_FEED_BROKER_PASSWORD: 'secret', + }; +} + +test('public feed queues one aggregate CoreScope packet and publishes it through the MQTT seam', async () => { + const previous = { ...process.env }; + Object.assign(process.env, configuredEnv()); + const enqueued: Array> = []; + const published: Array<{ topic: string; payload: string }> = []; + let claimed = false; + const repository = { + hasActiveNode: async () => true, + enqueueDelivery: async (input: Record) => { + enqueued.push(input); + return true; + }, + syncOwnership: async () => undefined, + accountStatus: async () => ({ enabled: true, nodeCount: 1, lastForwardedAt: null }), + setEnabled: async () => undefined, + discardInactiveDeliveries: async () => 0, + reclaimExpiredClaims: async () => 0, + claimDue: async () => { + if (claimed || enqueued.length < 1) return []; + claimed = true; + const row = enqueued[0]!; + return [{ + id: '1', + node_id: packet.rxNodeId!, + event_key: String(row['eventKey']), + packet_hash: packet.packetHash, + source_topic: packet.topic, + destination_topic: String(row['destinationTopic']), + payload: row['payload'] as Record, + attempts: 1, + }]; + }, + markSucceeded: async () => undefined, + markFailed: async () => undefined, + markDiscarded: async () => undefined, + } as unknown as PublicFeedRepository; + const publisher: PublicFeedPublisher = { + publish: async (topic, payload) => { published.push({ topic, payload }); }, + close: async () => undefined, + }; + const forwarder = createPublicFeedForwarder({ + repository, + getOwnerAuthorizationSnapshot: async () => [], + publisherFactory: () => publisher, + }); + + try { + await forwarder.enqueuePacket(packet, { + raw: '0102', + origin: 'Test observer', + SNR: '12.2', + RSSI: '-30', + score: '1000', + direction: 'rx', + body: 'private decoded text', + }); + await forwarder.processDue(); + await forwarder.stop(); + } finally { + for (const key of Object.keys(process.env)) { + if (!(key in previous)) delete process.env[key]; + } + Object.assign(process.env, previous); + } + + assert.equal(enqueued.length, 1); + assert.equal(published.length, 1); + assert.equal(published[0]?.topic, `public/LHR/${'A'.repeat(64)}/packets`); + const outbound = JSON.parse(published[0]!.payload) as Record; + assert.equal(outbound['raw'], '0102'); + assert.equal(outbound['SNR'], 12.2); + assert.equal(outbound['RSSI'], -30); + assert.equal(outbound['score'], 1000); + assert.equal('body' in outbound, false); + assert.equal('relay' in outbound, false); + assert.equal('packet' in outbound, false); +}); + +test('public feed does not enqueue a packet while the broker is not operator-enabled', async () => { + const previous = { ...process.env }; + delete process.env.PUBLIC_FEED_PUBLISH_ENABLED; + delete process.env.PUBLIC_FEED_BROKER_URL; + delete process.env.PUBLIC_FEED_BROKER_USERNAME; + delete process.env.PUBLIC_FEED_BROKER_PASSWORD; + let enqueues = 0; + const repository = { + hasActiveNode: async () => { throw new Error('must not check ownership when unconfigured'); }, + enqueueDelivery: async () => { enqueues++; return true; }, + } as unknown as PublicFeedRepository; + const forwarder = createPublicFeedForwarder({ + repository, + getOwnerAuthorizationSnapshot: async () => [], + }); + try { + await forwarder.enqueuePacket(packet, { raw: '0102' }); + await forwarder.stop(); + } finally { + for (const key of Object.keys(process.env)) { + if (!(key in previous)) delete process.env[key]; + } + Object.assign(process.env, previous); + } + assert.equal(enqueues, 0); +}); diff --git a/backend/src/owner/publicFeedForwarder.ts b/backend/src/owner/publicFeedForwarder.ts new file mode 100644 index 0000000..c839eef --- /dev/null +++ b/backend/src/owner/publicFeedForwarder.ts @@ -0,0 +1,305 @@ +import mqtt, { type MqttClient } from 'mqtt'; +import { randomUUID } from 'node:crypto'; +import type { LivePacket } from '../types/index.js'; +import { + buildPublicFeedPacket, + getPublicFeedConfig, + isPublicFeedPacketTopic, + publicFeedEventKey, + publicFeedPacketIsEligible, + type PublicFeedConfig, +} from './publicFeedConfig.js'; +import type { PublicFeedDeliveryRow, PublicFeedRepository, PublicFeedAuthorizationSnapshot } from './publicFeedRepository.js'; +import { publicFeedOutcomesTotal, publicFeedQueueDepth } from '../metrics.js'; + +const DEFAULT_POLL_INTERVAL_MS = 5_000; +const DEFAULT_BATCH_SIZE = 25; +const DEFAULT_OWNERSHIP_REFRESH_INTERVAL_MS = 15_000; +const PUBLISH_TIMEOUT_MS = 10_000; + +export type PublicFeedPublisher = { + publish: (topic: string, payload: string) => Promise; + close: () => Promise; +}; + +export type PublicFeedPublisherFactory = (config: PublicFeedConfig) => PublicFeedPublisher; + +function positiveIntegerSetting(name: string, fallback: number, max: number): number { + const parsed = Number(process.env[name]); + return Number.isFinite(parsed) && parsed > 0 ? Math.min(max, Math.floor(parsed)) : fallback; +} + +function configUrl(config: PublicFeedConfig): URL { + if (!config.brokerUrl) throw new Error('public feed broker URL is missing'); + const url = new URL(config.brokerUrl); + if (!['mqtt:', 'mqtts:', 'ws:', 'wss:'].includes(url.protocol) || !url.hostname) { + throw new Error('public feed broker URL is invalid'); + } + return url; +} + +function configFingerprint(config: PublicFeedConfig): string { + return JSON.stringify([config.brokerUrl, config.username, config.password]); +} + +function connectPublicFeedPublisher(config: PublicFeedConfig): PublicFeedPublisher { + let client: MqttClient | null = null; + let connecting: Promise | null = null; + + const closeClient = async (force = true): Promise => { + const current = client; + client = null; + connecting = null; + if (current) await current.endAsync(force).catch(() => undefined); + }; + + const connect = async (): Promise => { + if (client?.connected) return client; + if (connecting) return connecting; + const url = configUrl(config); + if (!config.username || !config.password) throw new Error('public feed publisher credentials are missing'); + const created = mqtt.connect(url.toString(), { + clientId: `meshcore-public-feed-${randomUUID().replaceAll('-', '').slice(0, 24)}`, + username: config.username, + password: config.password, + clean: true, + reconnectPeriod: 0, + connectTimeout: PUBLISH_TIMEOUT_MS, + }); + client = created; + created.on('error', (error) => { + console.error('[public-feed] MQTT publisher error:', error.message); + }); + connecting = new Promise((resolve, reject) => { + let settled = false; + let timer: NodeJS.Timeout; + const cleanup = () => { + clearTimeout(timer); + created.removeListener('connect', onConnect); + created.removeListener('close', onClose); + }; + const finish = (error?: Error) => { + if (settled) return; + settled = true; + cleanup(); + if (error) { + if (client === created) client = null; + created.end(true); + reject(error); + } else { + resolve(created); + } + }; + const onConnect = () => finish(); + const onClose = () => finish(new Error('public feed MQTT connection closed before connect')); + timer = setTimeout(() => finish(new Error('public feed MQTT connection timed out')), PUBLISH_TIMEOUT_MS); + created.once('connect', onConnect); + created.once('close', onClose); + }).finally(() => { + connecting = null; + }); + return connecting; + }; + + return { + async publish(topic: string, payload: string): Promise { + const current = await connect(); + // CoreScope subscribes at QoS 0. The feed is an observation stream, so + // retaining messages or requesting broker redelivery is undesirable. + await current.publishAsync(topic, payload, { qos: 0, retain: false }); + if (!current.connected) { + await closeClient(); + throw new Error('public feed MQTT connection closed after publish'); + } + }, + close: closeClient, + }; +} + +export type PublicFeedForwarderDeps = { + repository: PublicFeedRepository; + getOwnerAuthorizationSnapshot: () => Promise; + publisherFactory?: PublicFeedPublisherFactory; + pollIntervalMs?: number; + batchSize?: number; + ownershipRefreshIntervalMs?: number; +}; + +export function createPublicFeedForwarder(deps: PublicFeedForwarderDeps) { + const publisherFactory = deps.publisherFactory ?? connectPublicFeedPublisher; + const pollIntervalMs = deps.pollIntervalMs ?? positiveIntegerSetting( + 'PUBLIC_FEED_POLL_INTERVAL_MS', DEFAULT_POLL_INTERVAL_MS, 60_000, + ); + const batchSize = deps.batchSize ?? positiveIntegerSetting( + 'PUBLIC_FEED_BATCH_SIZE', DEFAULT_BATCH_SIZE, 100, + ); + const ownershipRefreshIntervalMs = deps.ownershipRefreshIntervalMs ?? positiveIntegerSetting( + 'PUBLIC_FEED_OWNERSHIP_REFRESH_INTERVAL_MS', DEFAULT_OWNERSHIP_REFRESH_INTERVAL_MS, 300_000, + ); + const publishers = new Map(); + let pollTimer: NodeJS.Timeout | null = null; + let ownershipTimer: NodeJS.Timeout | null = null; + let processing: Promise | null = null; + let ownershipRefresh: Promise | null = null; + let stopping = false; + + async function publisherFor(config: PublicFeedConfig): Promise { + const fingerprint = configFingerprint(config); + const existing = publishers.get('public-feed'); + if (existing?.fingerprint === fingerprint) return existing.publisher; + if (existing) { + publishers.delete('public-feed'); + await existing.publisher.close().catch(() => undefined); + } + const created = publisherFactory(config); + publishers.set('public-feed', { publisher: created, fingerprint }); + return created; + } + + async function refreshOwnership(): Promise { + if (stopping) return; + if (ownershipRefresh) { + await ownershipRefresh; + return; + } + ownershipRefresh = (async () => { + try { + const snapshot = await deps.getOwnerAuthorizationSnapshot(); + await deps.repository.syncOwnership(snapshot); + } catch (error) { + publicFeedOutcomesTotal.inc({ outcome: 'ownership_sync_error' }); + console.error('[public-feed] ownership refresh failed:', error instanceof Error ? error.message : error); + } finally { + ownershipRefresh = null; + } + })(); + await ownershipRefresh; + } + + async function enqueuePacket(packet: LivePacket, upstreamEnvelope: Record): Promise { + if (!publicFeedPacketIsEligible(packet)) { + publicFeedOutcomesTotal.inc({ outcome: 'skipped' }); + return; + } + const config = getPublicFeedConfig(); + if (!config.configured) { + publicFeedOutcomesTotal.inc({ outcome: 'unconfigured' }); + return; + } + if (!await deps.repository.hasActiveNode(packet.rxNodeId!)) return; + + let outbound: ReturnType; + try { + outbound = buildPublicFeedPacket(packet, upstreamEnvelope); + } catch (error) { + publicFeedOutcomesTotal.inc({ outcome: 'invalid_packet' }); + console.error('[public-feed] packet rejected:', error instanceof Error ? error.message : error); + return; + } + + try { + const inserted = await deps.repository.enqueueDelivery({ + nodeId: packet.rxNodeId!, + eventKey: publicFeedEventKey(packet), + packet, + destinationTopic: outbound.topic, + payload: outbound.payload, + }); + publicFeedOutcomesTotal.inc({ outcome: inserted ? 'queued' : 'duplicate' }); + } catch (error) { + publicFeedOutcomesTotal.inc({ outcome: 'queue_error' }); + console.error('[public-feed] queue error:', error instanceof Error ? error.message : error); + return; + } + void processDue(); + } + + async function deliver(row: PublicFeedDeliveryRow, claimToken: string): Promise { + // Drop deliveries created by an older deployment before they can publish + // to the canonical ingest namespace. The curated public namespace is the + // only destination this worker is allowed to emit. + if (!isPublicFeedPacketTopic(row.destination_topic)) { + await deps.repository.markDiscarded(row.id, claimToken, 'public feed destination namespace is invalid'); + publicFeedOutcomesTotal.inc({ outcome: 'invalid_destination' }); + return; + } + if (!await deps.repository.hasActiveNode(row.node_id)) { + await deps.repository.markDiscarded(row.id, claimToken, 'public feed is no longer enabled for this node'); + publicFeedOutcomesTotal.inc({ outcome: 'disabled' }); + return; + } + const config = getPublicFeedConfig(); + if (!config.configured) { + await deps.repository.markFailed(row.id, claimToken, row.attempts, 'public feed broker is not configured'); + publicFeedOutcomesTotal.inc({ outcome: 'unconfigured' }); + return; + } + try { + const publisher = await publisherFor(config); + await publisher.publish(row.destination_topic, JSON.stringify(row.payload)); + await deps.repository.markSucceeded(row.id, claimToken); + publicFeedOutcomesTotal.inc({ outcome: 'delivered' }); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + const entry = publishers.get('public-feed'); + if (entry) { + publishers.delete('public-feed'); + await entry.publisher.close().catch(() => undefined); + } + await deps.repository.markFailed(row.id, claimToken, row.attempts, message); + publicFeedOutcomesTotal.inc({ outcome: 'delivery_error' }); + } + } + + async function processDue(): Promise { + if (stopping) return; + if (processing) { + await processing; + return; + } + processing = (async () => { + try { + await deps.repository.discardInactiveDeliveries(); + await deps.repository.reclaimExpiredClaims(); + if (!getPublicFeedConfig().configured) return; + const claimToken = randomUUID(); + const rows = await deps.repository.claimDue(batchSize, claimToken); + publicFeedQueueDepth.set(rows.length); + await Promise.all(rows.map((row) => deliver(row, claimToken))); + } catch (error) { + publicFeedOutcomesTotal.inc({ outcome: 'worker_error' }); + console.error('[public-feed] worker error:', error instanceof Error ? error.message : error); + } finally { + publicFeedQueueDepth.set(0); + processing = null; + } + })(); + await processing; + } + + function start(): void { + if (pollTimer || stopping) return; + stopping = false; + pollTimer = setInterval(() => void processDue(), pollIntervalMs); + pollTimer.unref(); + ownershipTimer = setInterval(() => void refreshOwnership(), ownershipRefreshIntervalMs); + ownershipTimer.unref(); + void refreshOwnership(); + void processDue(); + } + + async function stop(): Promise { + stopping = true; + if (pollTimer) clearInterval(pollTimer); + if (ownershipTimer) clearInterval(ownershipTimer); + pollTimer = null; + ownershipTimer = null; + if (processing) await processing.catch(() => undefined); + if (ownershipRefresh) await ownershipRefresh.catch(() => undefined); + const current = [...publishers.values()].map(({ publisher }) => publisher); + publishers.clear(); + await Promise.all(current.map((publisher) => publisher.close().catch(() => undefined))); + } + + return { enqueuePacket, processDue, refreshOwnership, start, stop }; +} diff --git a/backend/src/owner/publicFeedRepository.test.ts b/backend/src/owner/publicFeedRepository.test.ts new file mode 100644 index 0000000..a8ae229 --- /dev/null +++ b/backend/src/owner/publicFeedRepository.test.ts @@ -0,0 +1,63 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { + createPublicFeedRepository, + getActivePublicFeedNodeIds, + getActivePublicFeedReaderUsernames, +} from './publicFeedRepository.js'; + +const nodeId = 'A'.repeat(64); + +test('active public-feed readers require opt-in and a visible packet in the last 24 hours', async () => { + let sql = ''; + const readers = await getActivePublicFeedReaderUsernames(async (text) => { + sql = text; + return { rows: [{ owner_username: 'alice' }] }; + }); + + assert.deepEqual([...readers], ['alice']); + assert.match(sql, /settings\.enabled = TRUE/); + assert.match(sql, /packet\.time > NOW\(\) - INTERVAL '24 hours'/); + assert.match(sql, /packet\.visibility_ok IS TRUE/); + assert.match(sql, /packet\.is_private IS NOT TRUE/); + assert.match(sql, /packet\.network IS DISTINCT FROM 'test'/); +}); + +test('active public-feed node IDs require opt-in and recent eligible activity', async () => { + let sql = ''; + const nodeIds = await getActivePublicFeedNodeIds(async (text) => { + sql = text; + return { rows: [{ node_id: 'b'.repeat(64) }, { node_id: 'A'.repeat(64) }] }; + }); + + assert.deepEqual(nodeIds, ['A'.repeat(64), 'B'.repeat(64)]); + assert.match(sql, /settings\.enabled = TRUE/); + assert.match(sql, /packet\.time > NOW\(\) - INTERVAL '24 hours'/); + assert.match(sql, /packet\.visibility_ok IS TRUE/); + assert.match(sql, /packet\.is_private IS NOT TRUE/); + assert.match(sql, /packet\.network IS DISTINCT FROM 'test'/); +}); + +test('owner public-feed status reports recent eligibility separately from opt-in state', async () => { + let params: unknown[] | undefined; + const repository = createPublicFeedRepository(async (sql, queryParams) => { + assert.match(sql, /recent_activity/); + params = queryParams; + return { + rows: [{ + enabled: true, + recent_packet_count: 2, + last_forwarded_at: '2026-09-20T12:00:00.000Z', + }], + }; + }); + + assert.deepEqual(await repository.accountStatus('alice', [nodeId]), { + enabled: true, + nodeCount: 1, + eligible: true, + recentPacketCount: 2, + lastForwardedAt: '2026-09-20T12:00:00.000Z', + }); + assert.deepEqual(params, ['alice', [nodeId]]); +}); diff --git a/backend/src/owner/publicFeedRepository.ts b/backend/src/owner/publicFeedRepository.ts new file mode 100644 index 0000000..84be856 --- /dev/null +++ b/backend/src/owner/publicFeedRepository.ts @@ -0,0 +1,450 @@ +import type { DatabaseQueryFn } from '../db/index.js'; +import type { LivePacket } from '../types/index.js'; + +export type PublicFeedQueryFn = DatabaseQueryFn; +export type PublicFeedTransactionFn = (work: (query: PublicFeedQueryFn) => Promise) => Promise; + +export type PublicFeedAuthorizationSnapshot = { + mqttUsername: string; + isActive: boolean; + nodeIds: string[]; +}; + +export type PublicFeedDeliveryRow = { + id: string; + node_id: string; + event_key: string; + packet_hash: string; + source_topic: string; + destination_topic: string; + payload: Record; + attempts: number; +}; + +const NORMALIZED_NODE_ID_RE = /^[0-9A-F]{64}$/; + +function normalizedNodeIds(nodeIds: string[]): string[] { + return Array.from(new Set( + nodeIds + .map((nodeId) => nodeId.trim().toUpperCase()) + .filter((nodeId) => NORMALIZED_NODE_ID_RE.test(nodeId)), + )); +} + +/** + * A public-feed reader is granted only while its opted-in account has produced + * at least one public packet observation in the rolling 24-hour window. This + * set is consumed by the broker ACL reconciler; it is deliberately derived + * from the materialised verified owner mapping rather than owner credentials. + */ +export async function getActivePublicFeedReaderUsernames( + query: PublicFeedQueryFn, +): Promise> { + const result = await query<{ owner_username: string }>( + `SELECT DISTINCT feed_node.owner_username + FROM owner_public_feed_nodes feed_node + JOIN owner_public_feed_settings settings + ON settings.owner_username = feed_node.owner_username + AND settings.enabled = TRUE + WHERE EXISTS ( + SELECT 1 + FROM packets packet + WHERE packet.rx_node_id = feed_node.node_id + AND packet.time > NOW() - INTERVAL '24 hours' + AND packet.network IS DISTINCT FROM 'test' + AND packet.visibility_ok IS TRUE + AND packet.is_private IS NOT TRUE + )`, + ); + return new Set(result.rows.map((row) => row.owner_username)); +} + +/** + * Return the observer IDs that are both opted in and currently contributing + * eligible public packets. The forwarder uses this set to decide which + * observers may be republished into the curated public namespace. + */ +export async function getActivePublicFeedNodeIds( + query: PublicFeedQueryFn, +): Promise { + const result = await query<{ node_id: string }>( + `SELECT DISTINCT feed_node.node_id + FROM owner_public_feed_nodes feed_node + JOIN owner_public_feed_settings settings + ON settings.owner_username = feed_node.owner_username + AND settings.enabled = TRUE + WHERE EXISTS ( + SELECT 1 + FROM packets packet + WHERE packet.rx_node_id = feed_node.node_id + AND packet.time > NOW() - INTERVAL '24 hours' + AND packet.network IS DISTINCT FROM 'test' + AND packet.visibility_ok IS TRUE + AND packet.is_private IS NOT TRUE + ) + ORDER BY feed_node.node_id`, + ); + return normalizedNodeIds(result.rows.map((row) => row.node_id)).sort(); +} + +export function createPublicFeedRepository( + query: PublicFeedQueryFn, + transaction?: PublicFeedTransactionFn, +) { + async function accountStatus(ownerUsername: string, nodeIds: string[]): Promise<{ + enabled: boolean; + nodeCount: number; + eligible: boolean; + recentPacketCount: number; + lastForwardedAt: string | null; + }> { + const activeNodeIds = normalizedNodeIds(nodeIds); + const result = await query<{ + enabled: boolean | null; + recent_packet_count: number | string | null; + last_forwarded_at: Date | string | null; + }>( + `WITH recent_activity AS ( + SELECT COUNT(*)::int AS recent_packet_count + FROM packets packet + WHERE packet.rx_node_id = ANY($2::text[]) + AND packet.time > NOW() - INTERVAL '24 hours' + AND packet.network IS DISTINCT FROM 'test' + AND packet.visibility_ok IS TRUE + AND packet.is_private IS NOT TRUE + ) + SELECT COALESCE(settings.enabled, FALSE) AS enabled, + recent_activity.recent_packet_count, + MAX(delivery.delivered_at) AS last_forwarded_at + FROM (SELECT $1::text AS owner_username) requested + CROSS JOIN recent_activity + LEFT JOIN owner_public_feed_settings settings + ON settings.owner_username = requested.owner_username + LEFT JOIN owner_public_feed_nodes feed_node + ON feed_node.owner_username = requested.owner_username + LEFT JOIN public_feed_deliveries delivery + ON delivery.node_id = feed_node.node_id + AND delivery.status = 'succeeded' + AND delivery.delivered_at IS NOT NULL + GROUP BY settings.enabled, recent_activity.recent_packet_count`, + [ownerUsername, activeNodeIds], + ); + const row = result.rows[0]; + const recentPacketCount = Number(row?.recent_packet_count ?? 0); + return { + enabled: row?.enabled === true, + nodeCount: nodeIds.length, + eligible: recentPacketCount > 0, + recentPacketCount: Number.isFinite(recentPacketCount) ? recentPacketCount : 0, + lastForwardedAt: row?.last_forwarded_at + ? new Date(row.last_forwarded_at).toISOString() + : null, + }; + } + + async function activeNodeIds(): Promise { + return getActivePublicFeedNodeIds(query); + } + + async function setEnabled( + ownerUsername: string, + nodeIds: string[], + enabled: boolean, + ): Promise { + const update = async (runQuery: PublicFeedQueryFn): Promise => { + await runQuery( + `INSERT INTO owner_public_feed_settings (owner_username, enabled, updated_at) + VALUES ($1, $2, NOW()) + ON CONFLICT (owner_username) DO UPDATE SET + enabled = EXCLUDED.enabled, + updated_at = NOW()`, + [ownerUsername, enabled], + ); + await runQuery( + `DELETE FROM owner_public_feed_nodes + WHERE owner_username = $1`, + [ownerUsername], + ); + if (enabled && nodeIds.length > 0) { + await runQuery( + `INSERT INTO owner_public_feed_nodes (owner_username, node_id) + SELECT $1, node_id + FROM unnest($2::text[]) AS selected(node_id) + ON CONFLICT (owner_username, node_id) DO NOTHING`, + [ownerUsername, nodeIds], + ); + } + if (!enabled) { + await runQuery( + `UPDATE public_feed_deliveries delivery + SET status = 'dead_lettered', + last_error = 'public feed disabled', + claim_token = NULL, + claim_expires_at = NULL, + updated_at = NOW() + WHERE delivery.status IN ('pending', 'failed') + AND NOT EXISTS ( + SELECT 1 + FROM owner_public_feed_nodes active_node + WHERE active_node.node_id = delivery.node_id + )`, + ); + } + }; + if (transaction) { + await transaction(update); + return; + } + await update(query); + } + + /** + * Rebuild the materialised owner-to-node mapping from the verified owner + * database. The snapshot is supplied by the owner-auth module so this + * analytics database never becomes an independent source of ownership. + */ + async function syncOwnership(snapshot: PublicFeedAuthorizationSnapshot[]): Promise { + const pairSet = new Set(); + const usernames: string[] = []; + const nodeIds: string[] = []; + for (const account of snapshot) { + if (!account.isActive) continue; + const username = account.mqttUsername.trim(); + if (!username) continue; + for (const rawNodeId of account.nodeIds) { + const nodeId = rawNodeId.trim().toUpperCase(); + const pair = `${username}\u0000${nodeId}`; + if (!/^[0-9A-F]{64}$/.test(nodeId) || pairSet.has(pair)) continue; + pairSet.add(pair); + usernames.push(username); + nodeIds.push(nodeId); + } + } + + const update = async (runQuery: PublicFeedQueryFn): Promise => { + await runQuery( + `DELETE FROM owner_public_feed_nodes feed_node + WHERE NOT EXISTS ( + SELECT 1 + FROM unnest($1::text[], $2::text[]) AS requested(owner_username, node_id) + WHERE requested.owner_username = feed_node.owner_username + AND requested.node_id = feed_node.node_id + ) + OR NOT EXISTS ( + SELECT 1 + FROM owner_public_feed_settings settings + WHERE settings.owner_username = feed_node.owner_username + AND settings.enabled = TRUE + )`, + [usernames, nodeIds], + ); + if (usernames.length > 0) { + await runQuery( + `INSERT INTO owner_public_feed_nodes (owner_username, node_id) + SELECT requested.owner_username, requested.node_id + FROM unnest($1::text[], $2::text[]) AS requested(owner_username, node_id) + JOIN owner_public_feed_settings settings + ON settings.owner_username = requested.owner_username + AND settings.enabled = TRUE + ON CONFLICT (owner_username, node_id) DO NOTHING`, + [usernames, nodeIds], + ); + } + await runQuery( + `UPDATE public_feed_deliveries delivery + SET status = 'dead_lettered', + last_error = 'public feed ownership changed', + claim_token = NULL, + claim_expires_at = NULL, + updated_at = NOW() + WHERE delivery.status IN ('pending', 'failed') + AND NOT EXISTS ( + SELECT 1 + FROM owner_public_feed_nodes active_node + WHERE active_node.node_id = delivery.node_id + )`, + ); + }; + if (transaction) { + await transaction(update); + return; + } + await update(query); + } + + async function hasActiveNode(nodeId: string): Promise { + const result = await query( + `SELECT 1 + FROM owner_public_feed_nodes feed_node + JOIN owner_public_feed_settings settings + ON settings.owner_username = feed_node.owner_username + WHERE feed_node.node_id = $1 + AND settings.enabled = TRUE + LIMIT 1`, + [nodeId], + ); + return result.rows.length > 0; + } + + async function enqueueDelivery(input: { + nodeId: string; + eventKey: string; + packet: LivePacket; + destinationTopic: string; + payload: Record; + }): Promise { + const result = await query( + `INSERT INTO public_feed_deliveries ( + node_id, event_key, packet_hash, source_topic, destination_topic, payload + ) + SELECT $1, $2, $3, $4, $5, $6::jsonb + WHERE EXISTS ( + SELECT 1 + FROM owner_public_feed_nodes feed_node + JOIN owner_public_feed_settings settings + ON settings.owner_username = feed_node.owner_username + WHERE feed_node.node_id = $1 + AND settings.enabled = TRUE + ) + ON CONFLICT (event_key) DO NOTHING + RETURNING id`, + [ + input.nodeId, + input.eventKey, + input.packet.packetHash, + input.packet.topic, + input.destinationTopic, + JSON.stringify(input.payload), + ], + ); + return result.rows.length > 0; + } + + async function reclaimExpiredClaims(now = new Date()): Promise { + const result = await query<{ count: string }>( + `WITH reclaimed AS ( + UPDATE public_feed_deliveries + SET status = CASE WHEN attempts >= 5 THEN 'dead_lettered' ELSE 'failed' END, + next_attempt_at = NOW(), + claim_token = NULL, + claim_expires_at = NULL, + updated_at = NOW() + WHERE status = 'delivering' + AND claim_expires_at < $1 + RETURNING id + ) SELECT COUNT(*)::text AS count FROM reclaimed`, + [now], + ); + return Number(result.rows[0]?.count ?? 0); + } + + async function discardInactiveDeliveries(): Promise { + const result = await query<{ id: string }>( + `UPDATE public_feed_deliveries delivery + SET status = 'dead_lettered', + last_error = 'public feed is no longer enabled for this node', + claim_token = NULL, + claim_expires_at = NULL, + updated_at = NOW() + WHERE delivery.status IN ('pending', 'failed') + AND NOT EXISTS ( + SELECT 1 + FROM owner_public_feed_nodes feed_node + JOIN owner_public_feed_settings settings + ON settings.owner_username = feed_node.owner_username + WHERE feed_node.node_id = delivery.node_id + AND settings.enabled = TRUE + ) + RETURNING delivery.id::text`, + ); + return result.rows.length; + } + + async function claimDue(limit: number, claimToken: string): Promise { + const result = await query( + `WITH due AS ( + SELECT delivery.id + FROM public_feed_deliveries delivery + WHERE delivery.status IN ('pending', 'failed') + AND delivery.attempts < 5 + AND delivery.next_attempt_at <= NOW() + AND EXISTS ( + SELECT 1 + FROM owner_public_feed_nodes feed_node + JOIN owner_public_feed_settings settings + ON settings.owner_username = feed_node.owner_username + WHERE feed_node.node_id = delivery.node_id + AND settings.enabled = TRUE + ) + ORDER BY delivery.next_attempt_at, delivery.id + FOR UPDATE SKIP LOCKED + LIMIT $1 + ) + UPDATE public_feed_deliveries delivery + SET status = 'delivering', + attempts = delivery.attempts + 1, + last_attempt_at = NOW(), + claim_token = $2, + claim_expires_at = NOW() + INTERVAL '30 seconds', + updated_at = NOW() + FROM due + WHERE delivery.id = due.id + RETURNING delivery.id::text, delivery.node_id, delivery.event_key, + delivery.packet_hash, delivery.source_topic, + delivery.destination_topic, delivery.payload, delivery.attempts`, + [limit, claimToken], + ); + return result.rows; + } + + async function markSucceeded(id: string, claimToken: string): Promise { + await query( + `UPDATE public_feed_deliveries + SET status = 'succeeded', delivered_at = NOW(), last_error = NULL, + claim_token = NULL, claim_expires_at = NULL, updated_at = NOW() + WHERE id = $1 AND claim_token = $2`, + [id, claimToken], + ); + } + + async function markFailed(id: string, claimToken: string, attempts: number, error: string): Promise { + const terminal = attempts >= 5; + const delaySeconds = Math.min(300, 2 ** Math.max(0, attempts - 1)); + await query( + `UPDATE public_feed_deliveries + SET status = $3, + next_attempt_at = NOW() + ($4::text || ' seconds')::interval, + last_error = $5, claim_token = NULL, claim_expires_at = NULL, + updated_at = NOW() + WHERE id = $1 AND claim_token = $2`, + [id, claimToken, terminal ? 'dead_lettered' : 'failed', String(delaySeconds), error.slice(0, 500)], + ); + } + + async function markDiscarded(id: string, claimToken: string, error: string): Promise { + await query( + `UPDATE public_feed_deliveries + SET status = 'dead_lettered', last_error = $3, + claim_token = NULL, claim_expires_at = NULL, updated_at = NOW() + WHERE id = $1 AND claim_token = $2`, + [id, claimToken, error.slice(0, 500)], + ); + } + + return { + accountStatus, + activeNodeIds, + setEnabled, + syncOwnership, + hasActiveNode, + enqueueDelivery, + reclaimExpiredClaims, + discardInactiveDeliveries, + claimDue, + markSucceeded, + markFailed, + markDiscarded, + }; +} + +export type PublicFeedRepository = ReturnType; diff --git a/backend/src/path-beta/championPriors.test.ts b/backend/src/path-beta/championPriors.test.ts new file mode 100644 index 0000000..d377b3e --- /dev/null +++ b/backend/src/path-beta/championPriors.test.ts @@ -0,0 +1,30 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { buildChampionPriors, loadChampionPriors } from './championPriors.js'; + +test('combines regional counts and keeps position indices aligned with source edges', () => { + const priors = buildChampionPriors({ + positionPrefixes: [{ prefix: 'aa', position: 0, node_id: 'AA01', count: '3' }, { prefix: 'AA', position: 1, node_id: 'AA01', count: 2 }], + corridors: [{ src_node_id: 'SRC', rx_node_id: 'RX', position: 0, node_id: 'AA01', count: 3 }], + transitions: [{ from_node_id: 'AA01', to_node_id: 'BB01', count: 5 }, { from_node_id: 'AA01', to_node_id: 'BB01', count: '7' }], + positionTransitions: [{ position: 0, from_node_id: 'SRC', to_node_id: 'AA01', count: 3 }, { position: 1, from_node_id: 'AA01', to_node_id: 'BB01', count: 2 }], + }); + assert.equal(priors.prefixCounts.get('AA|AA01'), 5); + assert.equal(priors.positionPrefixCounts.get('AA|0|AA01'), 3); + assert.equal(priors.transitionCounts.get('AA01|BB01'), 12); + assert.equal(priors.corridorCounts.get('SRC|RX|0|AA01'), 3); + assert.equal(priors.positionTransitionCounts.get('0|SRC|AA01'), 3); + assert.equal(priors.verifiedEdgeCounts.get('AA01|BB01'), 2); + assert.equal(priors.verifiedEdgeCounts.has('BB01|AA01'), false); +}); + +test('loads all four count tables and fails visibly if a bounded model would be truncated', async () => { + const tables: string[] = []; + await loadChampionPriors('ukmesh', async (sql, params) => { + assert.equal(params[0], 'ukmesh'); + tables.push(sql.match(/FROM (\w+)/)![1]!); + return { rows: [] }; + }); + assert.deepEqual(tables.sort(), ['path_corridor_priors', 'path_position_prefix_priors', 'path_position_transition_priors', 'path_transition_priors']); + await assert.rejects(loadChampionPriors('ukmesh', async () => ({ rows: new Array(100_001) })), /PATH_CHAMPION_PRIOR_LIMIT/); +}); diff --git a/backend/src/path-beta/championPriors.ts b/backend/src/path-beta/championPriors.ts new file mode 100644 index 0000000..148bfa0 --- /dev/null +++ b/backend/src/path-beta/championPriors.ts @@ -0,0 +1,73 @@ +/** Count-based evidence published by the multibyte path learner. */ +export type ChampionPriorRows = { + positionPrefixes: { prefix: string; position: number; node_id: string; count: number | string }[]; + corridors: { src_node_id: string; rx_node_id: string; position: number; node_id: string; count: number | string }[]; + transitions: { from_node_id: string; to_node_id: string; count: number | string }[]; + positionTransitions: { position: number; from_node_id: string; to_node_id: string; count: number | string }[]; +}; + +export type ChampionPriors = { + prefixCounts: Map; + positionPrefixCounts: Map; + corridorCounts: Map; + transitionCounts: Map; + positionTransitionCounts: Map; + verifiedEdgeCounts: Map; +}; + +type PriorQuery = >( + sql: string, params: unknown[], signal?: AbortSignal, +) => Promise<{ rows: T[] }>; + +/** Loaded once per cached resolver context, never once per packet. */ +export async function loadChampionPriors(network: string, query: PriorQuery, signal?: AbortSignal): Promise { + // Refuse an incomplete model instead of silently removing rare choices. + const limit = 100_000; + const read = async >(sql: string): Promise => { + const result = await query(`${sql} LIMIT $2`, [network, limit + 1], signal); + if (result.rows.length > limit) throw new Error('PATH_CHAMPION_PRIOR_LIMIT'); + return result.rows; + }; + const [positionPrefixes, corridors, transitions, positionTransitions] = await Promise.all([ + read( + 'SELECT prefix, position, node_id, count FROM path_position_prefix_priors WHERE network = $1'), + read( + 'SELECT src_node_id, rx_node_id, position, node_id, count FROM path_corridor_priors WHERE network = $1'), + read( + 'SELECT from_node_id, to_node_id, count FROM path_transition_priors WHERE network = $1'), + read( + 'SELECT position, from_node_id, to_node_id, count FROM path_position_transition_priors WHERE network = $1'), + ]); + return buildChampionPriors({ positionPrefixes, corridors, transitions, positionTransitions }); +} + +export function buildChampionPriors(rows: ChampionPriorRows): ChampionPriors { + const result: ChampionPriors = { + prefixCounts: new Map(), + positionPrefixCounts: new Map(), + corridorCounts: new Map(), + transitionCounts: new Map(), + positionTransitionCounts: new Map(), + verifiedEdgeCounts: new Map(), + }; + const add = (map: Map, key: string, value: number | string) => { + const count = Number(value); + if (Number.isFinite(count) && count > 0) map.set(key, (map.get(key) ?? 0) + count); + }; + for (const row of rows.positionPrefixes) { + const prefix = row.prefix.toUpperCase().slice(0, 2); + add(result.prefixCounts, `${prefix}|${row.node_id}`, row.count); + add(result.positionPrefixCounts, `${prefix}|${row.position}|${row.node_id}`, row.count); + } + for (const row of rows.corridors) { + add(result.corridorCounts, `${row.src_node_id}|${row.rx_node_id}|${row.position}|${row.node_id}`, row.count); + } + for (const row of rows.transitions) { + add(result.transitionCounts, `${row.from_node_id}|${row.to_node_id}`, row.count); + } + for (const row of rows.positionTransitions) { + add(result.positionTransitionCounts, `${row.position}|${row.from_node_id}|${row.to_node_id}`, row.count); + add(result.verifiedEdgeCounts, `${row.from_node_id}|${row.to_node_id}`, row.count); + } + return result; +} diff --git a/backend/src/path-beta/resolveWorker.ts b/backend/src/path-beta/resolveWorker.ts index babd981..60692d3 100644 --- a/backend/src/path-beta/resolveWorker.ts +++ b/backend/src/path-beta/resolveWorker.ts @@ -4,9 +4,7 @@ * keeping the main event loop free during CPU-heavy path computation. */ import { parentPort } from 'worker_threads'; -import { query } from '../db/index.js'; -import { lazyResolvePath } from '../path-lazy/lazyResolver.js'; -import { resolveBetaPathForPacketHash, resolveMultiObserverBetaPath } from './resolver.js'; +import { resolveBetaPathForPacketHash, resolveMultiObserverBetaPath, resolveLazyPathForPacketHash } from './resolver.js'; import { getHeldPath, setHeldPath, type HeldPathEntry } from './resolveCache.js'; if (!parentPort) throw new Error('resolveWorker must run as a worker thread'); @@ -26,7 +24,7 @@ parentPort.on('message', (msg: WorkerMessage) => { const stickyMap = msg.stickyMap ? new Map(Object.entries(msg.stickyMap)) : undefined; const heldPath = msg.heldPath ?? getHeldPath(msg.packetHash, msg.network); const run = msg.type === 'resolveLazy' - ? lazyResolvePath(msg.packetHash, msg.network, query) + ? resolveLazyPathForPacketHash(msg.packetHash, msg.network) : msg.type === 'resolveMulti' ? resolveMultiObserverBetaPath(msg.packetHash, msg.network, stickyMap, msg.stickyAgeFraction, { heldPath }) : resolveBetaPathForPacketHash( diff --git a/backend/src/path-beta/resolver.ts b/backend/src/path-beta/resolver.ts index 277ce6e..c5503f8 100644 --- a/backend/src/path-beta/resolver.ts +++ b/backend/src/path-beta/resolver.ts @@ -33,6 +33,13 @@ import type { PathPacket, } from './types.js'; import type { HeldPathEntry } from './resolveCache.js'; +import { loadChampionPriors } from './championPriors.js'; +import type { LazyPath, LazyPathNode, LazyPathResult } from '../path-lazy/lazyResolver.js'; +import { expandResolverScope } from '../networks.js'; + +function resolverScope(network: string): string[] | null { + return network === 'all' ? null : expandResolverScope(network); +} const contextCache = new BoundedTtlMap({ name: 'path_context', @@ -96,7 +103,7 @@ async function buildLearningModel( network: string, signal?: AbortSignal, ): Promise { - const [prefixRows, transitionRows, edgeRows, motifRows, calibrationRows] = await Promise.all([ + const [prefixRows, transitionRows, edgeRows, motifRows, calibrationRows, champion] = await Promise.all([ query( `SELECT prefix, receiver_region, prev_prefix, node_id, count FROM path_prefix_priors @@ -157,6 +164,7 @@ async function buildLearningModel( [network], signal, ), + loadChampionPriors(network, query, signal), ]); const prefixProbabilities = buildOneBytePrefixProbabilities(prefixRows.rows); @@ -208,6 +216,7 @@ async function buildLearningModel( const calibration = calibrationRows.rows[0]; return { + champion, prefixProbabilities, transitionProbabilities, edgeScores, @@ -258,9 +267,9 @@ async function loadContext( query( `SELECT node_id, name, lat, lon, iata, role, elevation_m, last_seen::text AS last_seen FROM nodes - WHERE ($1 = 'all' OR network = $1) + WHERE ($1::text[] IS NULL OR network = ANY($1)) AND (name IS NULL OR name NOT LIKE '%🚫%')`, - [network], + [resolverScope(network)], options?.signal, ), query<{ @@ -281,8 +290,8 @@ async function loadContext( WHERE (nl.itm_viable IS NOT NULL OR nl.force_viable = true) AND (a.name IS NULL OR a.name NOT LIKE '%🚫%') AND (b.name IS NULL OR b.name NOT LIKE '%🚫%') - AND ($1 = 'all' OR (a.network = $1 AND b.network = $1))`, - [network], + AND ($1::text[] IS NULL OR (a.network = ANY($1) AND b.network = ANY($1)))`, + [resolverScope(network)], options?.signal, ), buildLearningModel(network, options?.signal), @@ -340,32 +349,36 @@ function matchesObserverPathHash(rx: MeshNode | null, hash: string | undefined): return Boolean(normalized && nodePathHash(rx.node_id, normalized) === normalized); } -function trimObserverTerminalHop(hops: string[], rx: MeshNode | null): string[] { +function trimObserverTerminalHop(hops: string[], rx: MeshNode | null, packet: PathPacket): string[] { + // RX paths describe the transmitting relays. A short-hash collision with + // the receiver is not evidence that it appended itself to that path. + if (packet.direction === 'rx') return hops; if (!rx || rx.role !== 2 || hops.length <= 1) return hops; return matchesObserverPathHash(rx, hops[hops.length - 1]) ? hops.slice(0, -1) : hops; } -function preparePacketObservation(packet: PathPacket, rx: MeshNode | null): PreparedPacketObservation { - const hashes = (packet.path_hashes ?? []).map(normalizePathHash).filter(Boolean); +export function preparePacketObservation(packet: PathPacket, rx: MeshNode | null): PreparedPacketObservation { + const hashes = (packet.path_hashes ?? []).map(normalizePathHash); const expectedHexLength = packet.path_hash_size_bytes == null ? null : packet.path_hash_size_bytes * 2; - const validatedHashes = expectedHexLength == null - ? hashes - : hashes.filter((hash) => hash.length === expectedHexLength); + const invalidHashes = (expectedHexLength != null && ![2, 4, 6].includes(expectedHexLength)) + || hashes.some((hash) => !/^(?:[0-9A-F]{2}){1,3}$/.test(hash) + || (expectedHexLength != null && hash.length !== expectedHexLength)); const rawHops = packet.hop_count == null - ? validatedHashes - : validatedHashes.slice(0, Math.max(0, packet.hop_count)); + ? hashes + : hashes.slice(0, Math.max(0, packet.hop_count)); const ignoreForPathing = Boolean( - rx?.role === 2 + invalidHashes || (packet.route_type != null && packet.route_type !== 0 && packet.route_type !== 1) + || (packet.direction !== 'rx' && rx?.role === 2 && rawHops.length >= 3 && matchesObserverPathHash(rx, rawHops[0]) - && matchesObserverPathHash(rx, rawHops[rawHops.length - 1]), + && matchesObserverPathHash(rx, rawHops[rawHops.length - 1])), ); return { packet, rx, hashes, rawHops, - hops: trimObserverTerminalHop(rawHops, rx), + hops: trimObserverTerminalHop(rawHops, rx, packet), ignoreForPathing, }; } @@ -410,6 +423,26 @@ function toObserverEntry(prepared: PreparedPacketObservation): BetaObserverEntry }; } +/** Flood copies can reach the same observer along different routes. Keep + * those variants; collapsing them to one row loses valid multibyte evidence. */ +export function prepareObservationEntries(rows: readonly PathPacket[], context: BetaResolveContext): BetaObserverEntry[] { + const variants = new Map(); + const observers = new Set(); + for (const row of rows) { + if (!row.rx_node_id) continue; + const prepared = preparePacketObservation(row, context.nodesById.get(row.rx_node_id) ?? null); + if (prepared.ignoreForPathing || prepared.hops.length === 0 || prepared.hops.length >= MAX_BETA_HOPS) continue; + const entry = toObserverEntry(prepared); + if (!entry) continue; + observers.add(entry.observerId); + const key = `${entry.observerId}|${entry.hops.join(',')}`; + const previous = variants.get(key); + if (!previous || (!previous.packet.src_node_id && row.src_node_id)) variants.set(key, entry); + } + if (observers.size > PATH_MULTI_MAX_OBSERVERS) throw new Error('PATH_HISTORY_LIMIT'); + return [...variants.values()]; +} + function findSharedPathPrefix(pathsByObserver: readonly string[][]): string[] { if (pathsByObserver.length === 0) return []; const first = pathsByObserver[0]!; @@ -654,10 +687,10 @@ export async function resolveBetaPathForPacketHash( ): Promise { throwIfResolutionAborted(options); const packetResult = await query( - `SELECT packet_hash, rx_node_id, src_node_id, packet_type, hop_count, path_hashes, path_hash_size_bytes + `SELECT packet_hash, rx_node_id, src_node_id, packet_type, route_type, payload->>'direction' AS direction, hop_count, path_hashes, path_hash_size_bytes FROM packets WHERE packet_hash = $1 - AND ($2 = 'all' OR network = $2) + AND ($2::text[] IS NULL OR network = ANY($2)) ${observer ? 'AND rx_node_id = $3' : ''} AND NOT EXISTS ( SELECT 1 FROM nodes private_node @@ -679,7 +712,7 @@ export async function resolveBetaPathForPacketHash( hop_count ASC NULLS LAST, time ASC LIMIT 32`, - observer ? [packetHash, network, observer] : [packetHash, network], + observer ? [packetHash, resolverScope(network), observer] : [packetHash, resolverScope(network)], options?.signal, ); if (packetResult.rows.length === 0) return null; @@ -731,7 +764,7 @@ export async function resolveBetaPathForPacketHash( : null; const projection = projectCanonicalPathForObserver(selectedEntry, decoded, context, source); const payload = payloadFromProjection(packetHash, selectedEntry, projection, { - canonicalPath: buildCanonicalPath(decoded), + canonicalPath: buildCanonicalPath(decoded).slice(0, selectedEntry.hops.length), observers: [{ observerId: selectedEntry.observerId }], network, }); @@ -829,21 +862,7 @@ async function resolveMultiObserverBetaPathFromRows( stickyAgeFraction?: number, options?: PathResolutionOptions, ): Promise { - const byObserver = new Map(); - for (const row of rows) { - if (!row.rx_node_id) continue; - const prepared = preparePacketObservation(row, context.nodesById.get(row.rx_node_id) ?? null); - if (prepared.ignoreForPathing) continue; - const previous = byObserver.get(row.rx_node_id); - byObserver.set(row.rx_node_id, previous ? mergeObserverEvidence(previous, prepared) : prepared); - } - if (byObserver.size > PATH_MULTI_MAX_OBSERVERS) throw new Error('PATH_HISTORY_LIMIT'); - - const entries = [...byObserver.values()] - .map(toObserverEntry) - .filter((entry): entry is BetaObserverEntry => ( - entry !== null && entry.hops.length > 0 && entry.hops.length < MAX_BETA_HOPS - )); + const entries = prepareObservationEntries(rows, context); if (entries.length === 0) return null; const groups = groupCompatibleObservations(entries); @@ -886,8 +905,8 @@ async function resolveMultiObserverBetaPathFromRows( : null; const projection = projectCanonicalPathForObserver(entry, decoded, context, source); const payload = payloadFromProjection(packetHash, entry, projection, { - canonicalPath: buildCanonicalPath(decoded), - observers: group.members.map((member) => ({ observerId: member.observerId })), + canonicalPath: buildCanonicalPath(decoded).slice(0, entry.hops.length), + observers: [...new Set(group.members.map((member) => member.observerId))].map((observerId) => ({ observerId })), network, }); results.push(maskResolvedPayload(payload, hiddenCoordMask)); @@ -913,10 +932,10 @@ async function resolveMultiObserverBetaPathFromRows( ok: true, packetHash, network, - observerCount: entries.length, + observerCount: new Set(entries.map((entry) => entry.observerId)).size, sharedPrefixLength, canonicalPath: firstCanonicalPath, - observers: entries.map((entry) => ({ observerId: entry.observerId })), + observers: [...new Set(entries.map((entry) => entry.observerId))].map((observerId) => ({ observerId })), confidence: results[0]?.confidence ?? null, results, ...(regionLinks.length > 0 ? { regionLinks } : {}), @@ -924,19 +943,15 @@ async function resolveMultiObserverBetaPathFromRows( }; } -export async function resolveMultiObserverBetaPath( - packetHash: string, - network: string, - stickyMap?: Map, - stickyAgeFraction?: number, - options?: PathResolutionOptions, -): Promise { +async function loadMultiObserverInputs( + packetHash: string, network: string, options?: PathResolutionOptions, +): Promise<{ rows: PathPacket[]; context: BetaResolveContext } | null> { throwIfResolutionAborted(options); const allResult = await query( - `SELECT packet_hash, rx_node_id, src_node_id, packet_type, hop_count, path_hashes, path_hash_size_bytes + `SELECT packet_hash, rx_node_id, src_node_id, packet_type, route_type, payload->>'direction' AS direction, hop_count, path_hashes, path_hash_size_bytes FROM packets WHERE packet_hash = $1 - AND ($2 = 'all' OR network = $2) + AND ($2::text[] IS NULL OR network = ANY($2)) AND time >= NOW() - ($3::int * INTERVAL '1 hour') AND rx_node_id IS NOT NULL AND NOT EXISTS ( @@ -959,7 +974,7 @@ export async function resolveMultiObserverBetaPath( hop_count ASC NULLS LAST, time ASC LIMIT $4`, - [packetHash, network, PATH_MULTI_HISTORY_WINDOW_HOURS, PATH_MULTI_MAX_SCAN_ROWS + 1], + [packetHash, resolverScope(network), PATH_MULTI_HISTORY_WINDOW_HOURS, PATH_MULTI_MAX_SCAN_ROWS + 1], options?.signal, ); if (allResult.rows.length === 0) return null; @@ -971,11 +986,23 @@ export async function resolveMultiObserverBetaPath( signal: options?.signal, }); throwIfResolutionAborted(options); + return { rows: allResult.rows, context }; +} + +export async function resolveMultiObserverBetaPath( + packetHash: string, + network: string, + stickyMap?: Map, + stickyAgeFraction?: number, + options?: PathResolutionOptions, +): Promise { + const inputs = await loadMultiObserverInputs(packetHash, network, options); + if (!inputs) return null; return resolveMultiObserverBetaPathFromRows( packetHash, network, - allResult.rows, - context, + inputs.rows, + inputs.context, stickyMap, stickyAgeFraction, options, @@ -987,6 +1014,54 @@ export type MultiObserverPathBatch = { limitedPacketHashes: Set; }; +/** The packet detail view and live overlay share candidates, priors and decode. */ +export async function resolveLazyPathForPacketHash( + packetHash: string, network: string, options?: PathResolutionOptions, +): Promise { + const inputs = await loadMultiObserverInputs(packetHash, network, options); + if (!inputs) return null; + const { context } = inputs; + const entries = prepareObservationEntries(inputs.rows, context); + return decodeLazyPathEntries(packetHash, entries, context, options); +} + +export function decodeLazyPathEntries( + packetHash: string, entries: readonly BetaObserverEntry[], context: BetaResolveContext, + options?: PathResolutionOptions, +): LazyPathResult | null { + const observerCount = new Set(entries.map((entry) => entry.observerId)).size; + const paths: LazyPath[] = []; + for (const group of groupCompatibleObservations(entries)) { + throwIfResolutionAborted(options); + const decoded = decodeBetaCanonicalGroup(group, context); + const canonicalPath: LazyPathNode[] = buildCanonicalPath(decoded).map((hop) => ({ + ...hop, + appearances: new Set(group.members.filter((member) => member.hops.length > hop.position).map((member) => member.observerId)).size, + totalObservations: observerCount, + isObserver: false, + })); + for (const member of group.members) { + // Each observer's route ends at its own reception. Interleaving other + // observers into one polyline invents observer-to-relay radio hops. + const observerPath = canonicalPath.slice(0, member.hops.length); + observerPath.push({ + position: member.hops.length, hash: member.observerId, nodeId: member.observerId, + name: member.rx.name, lat: member.rx.lat, lon: member.rx.lon, + appearances: 1, totalObservations: observerCount, ambiguous: false, isObserver: true, + }); + paths.push({ + canonicalPath: observerPath, + coordinates: observerPath.filter((hop) => hop.lat != null && hop.lon != null) + .map((hop) => [hop.lat!, hop.lon!]), + matchedHops: observerPath.filter((hop) => !hop.isObserver && hop.nodeId != null && !hop.ambiguous).length, + totalHops: member.hops.length, + observerIds: [member.observerId], + }); + } + } + return paths.length > 0 ? { packetHash, observerCount, paths } : null; +} + export async function createMultiObserverPathBatchResolver( network: string, visibilityGeneration: number, @@ -1014,17 +1089,17 @@ export async function createMultiObserverPathBatchResolver( if (packetHashes.length === 0) return { results, limitedPacketHashes }; const packetResult = await namedQuery( - 'path-history-observations-v1', + 'path-history-observations-v2', `SELECT observation.packet_hash, observation.rx_node_id, observation.src_node_id, - observation.packet_type, observation.hop_count, observation.path_hashes, + observation.packet_type, observation.route_type, observation.direction, observation.hop_count, observation.path_hashes, observation.path_hash_size_bytes FROM unnest($1::text[]) WITH ORDINALITY AS requested(packet_hash, selection_order) CROSS JOIN LATERAL ( - SELECT p.packet_hash, p.rx_node_id, p.src_node_id, p.packet_type, + SELECT p.packet_hash, p.rx_node_id, p.src_node_id, p.packet_type, p.route_type, p.payload->>'direction' AS direction, p.hop_count, p.path_hashes, p.path_hash_size_bytes, p.time FROM packets p WHERE p.packet_hash = requested.packet_hash - AND ($2 = 'all' OR p.network = $2) + AND ($2::text[] IS NULL OR p.network = ANY($2)) AND p.time >= $3 AND p.time <= $4 AND p.rx_node_id IS NOT NULL @@ -1055,7 +1130,7 @@ export async function createMultiObserverPathBatchResolver( CASE WHEN observation.src_node_id IS NOT NULL THEN 1 ELSE 0 END DESC, observation.hop_count ASC NULLS LAST, observation.time ASC`, - [packetHashes, network, windowStart, windowEnd, PATH_MULTI_MAX_SCAN_ROWS + 1], + [packetHashes, resolverScope(network), windowStart, windowEnd, PATH_MULTI_MAX_SCAN_ROWS + 1], batchSignal, ); batchSignal?.throwIfAborted(); diff --git a/backend/src/path-beta/resolverContext.test.ts b/backend/src/path-beta/resolverContext.test.ts index 42568a0..c1f99b2 100644 --- a/backend/src/path-beta/resolverContext.test.ts +++ b/backend/src/path-beta/resolverContext.test.ts @@ -4,9 +4,14 @@ import { buildOneBytePrefixProbabilities, canReusePathContext, mergeObserverEvidence, + preparePacketObservation, + prepareObservationEntries, + decodeLazyPathEntries, type PreparedPacketObservation, } from './resolver.js'; import type { PathPacket } from './types.js'; +import type { BetaResolveContext, MeshNode } from './types.js'; +import { buildChampionPriors } from './championPriors.js'; function prepared(size: number, hops: string[]): PreparedPacketObservation { const packet: PathPacket = { @@ -31,6 +36,53 @@ test('retains same-observer multibyte anchors while keeping the longer path tail assert.equal(merged.packet.path_hash_size_bytes, 1, 'the longer observation remains the projection row'); }); +const receiver: MeshNode = { node_id: 'AA0011', name: null, lat: 51, lon: 1, role: 2, iata: null, elevation_m: null, last_seen: null }; + +test('RX prefix collisions retain the final relay; TX strips the appended observer', () => { + const raw = { ...prepared(1, ['BB', 'AA']).packet, rx_node_id: receiver.node_id }; + assert.deepEqual(preparePacketObservation({ ...raw, direction: 'rx' }, receiver).hops, ['BB', 'AA']); + assert.deepEqual(preparePacketObservation({ ...raw, direction: 'tx' }, receiver).hops, ['BB']); +}); + +test('direct-routing destinations and malformed middle hashes cannot become observed relay chains', () => { + assert.equal(preparePacketObservation({ ...prepared(1, ['AA', 'BB']).packet, route_type: 2 }, receiver).ignoreForPathing, true); + const bad = preparePacketObservation(prepared(1, ['AA', 'zz', 'BB']).packet, receiver); + assert.equal(bad.ignoreForPathing, true); + assert.equal(bad.rawHops.length, 3); +}); + +test('keeps distinct routes at the same observer while de-duplicating identical receptions', () => { + const a = { ...prepared(3, ['BB0011', 'CC0011']).packet, rx_node_id: receiver.node_id }; + const b = { ...a, path_hashes: ['BB0022', 'CC0022'] }; + const entries = prepareObservationEntries([a, b, a], { + nodesById: new Map([[receiver.node_id, receiver]]), + } as BetaResolveContext); + assert.equal(entries.length, 2); + assert.deepEqual(entries.map((entry) => entry.hops), [a.path_hashes, b.path_hashes]); + const long = { ...a, path_hash_size_bytes: 1, hop_count: 40, path_hashes: new Array(40).fill('BB') }; + assert.equal(prepareObservationEntries([long], { nodesById: new Map([[receiver.node_id, receiver]]) } as BetaResolveContext)[0]?.hops.length, 40); +}); + +test('packet-detail projections end at their own observers without creating cross-observer edges', () => { + const relayA = { ...receiver, node_id: 'BB0011' }; + const relayB = { ...receiver, node_id: 'CC0022' }; + const rxLong = { ...receiver, node_id: 'DD0033' }; + const ctx: BetaResolveContext = { + loadedAt: 0, visibilityGeneration: 0, + nodesById: new Map([relayA, relayB, receiver, rxLong].map((node) => [node.node_id, node])), + repeaterNodes: [relayA, relayB, receiver, rxLong], linkMetrics: new Map(), + learningModel: { prefixProbabilities: new Map(), transitionProbabilities: new Map(), edgeScores: new Map(), motifProbabilities: new Map(), + confidenceScale: 1, confidenceBias: 0, bucketHours: 6, + champion: buildChampionPriors({ positionPrefixes: [], corridors: [], transitions: [], positionTransitions: [] }) }, + }; + const short = { ...prepared(3, ['BB0011']).packet, rx_node_id: receiver.node_id, direction: 'rx' }; + const long = { ...prepared(3, ['BB0011', 'CC0022']).packet, rx_node_id: rxLong.node_id, direction: 'rx' }; + const result = decodeLazyPathEntries('packet', prepareObservationEntries([short, long], ctx), ctx)!; + assert.equal(result.observerCount, 2); + assert.deepEqual(result.paths.find((path) => path.observerIds[0] === receiver.node_id)?.canonicalPath.map((node) => node.nodeId), ['BB0011', receiver.node_id]); + assert.deepEqual(result.paths.find((path) => path.observerIds[0] === rxLong.node_id)?.canonicalPath.map((node) => node.nodeId), ['BB0011', 'CC0022', rxLong.node_id]); +}); + test('collapses multibyte path facts into calibrated one-byte prefix priors', () => { const probabilities = buildOneBytePrefixProbabilities([ { prefix: 'AA01', receiver_region: 'LON', prev_prefix: 'BB01', node_id: 'node-a', count: 3 }, diff --git a/backend/src/path-beta/sharedDecoder.test.ts b/backend/src/path-beta/sharedDecoder.test.ts index 8fd8f3c..8f784f7 100644 --- a/backend/src/path-beta/sharedDecoder.test.ts +++ b/backend/src/path-beta/sharedDecoder.test.ts @@ -9,6 +9,7 @@ import { type BetaSharedDecode, } from './sharedDecoder.js'; import type { BetaResolveContext, MeshNode, PathPacket } from './types.js'; +import { buildChampionPriors } from './championPriors.js'; const meshNode = (node_id: string, lat: number, lon: number, iata: string | null = null): MeshNode => ({ node_id, @@ -21,6 +22,79 @@ const meshNode = (node_id: string, lat: number, lon: number, iata: string | null last_seen: null, }); +test('source and learned route-position evidence resolve an otherwise equal one-byte collision', () => { + const source = meshNode('SRC', 51, 1); + const good = meshNode('AA01', 51.1, 1); + const other = meshNode('AA02', 51.1, 1); + const observer = entry('RX', ['AA']); + observer.rx = meshNode('RX', 51.2, 1); + observer.packet.src_node_id = source.node_id; + const ctx = context([source, other, good, observer.rx]); + ctx.learningModel.champion = buildChampionPriors({ + positionPrefixes: [{ prefix: 'AA', position: 0, node_id: good.node_id, count: 10 }], + corridors: [{ src_node_id: 'SRC', rx_node_id: 'RX', position: 0, node_id: good.node_id, count: 4 }], + transitions: [{ from_node_id: 'SRC', to_node_id: good.node_id, count: 10 }], + positionTransitions: [{ position: 0, from_node_id: 'SRC', to_node_id: good.node_id, count: 10 }], + }); + assert.equal(decodeBetaCanonicalGroup({ canonicalHashes: ['AA'], members: [observer] }, ctx).hops.get(0)?.nodeId, good.node_id); +}); + +test('confirmed identities survive missing coordinates and terrain disagreement', () => { + const absent = meshNode('AA0001', 0, 0); + const remote = meshNode('BB0002', 57, 1); + const observer = entry('RX', ['AA0001', 'BB0002']); + observer.rx = meshNode('RX', 51, 1); + const ctx = context([absent, remote, observer.rx]); + const result = decodeBetaCanonicalGroup({ canonicalHashes: observer.hops, members: [observer] }, ctx); + assert.deepEqual([...result.hops.values()].map((hop) => hop.nodeId), ['AA0001', 'BB0002']); + assert.equal(result.hops.get(0)?.lat, null); + assert.equal(result.hops.get(0)?.lon, null); +}); + +test('later evidence can refine a held hop at the beginning of a long route', () => { + const nodes = ['AA01', 'AA02', 'BB01', 'CC01', 'DD01'].map((id) => meshNode(id, 51, 1)); + const observer = entry('RX', ['AA', 'BB', 'CC', 'DD']); + observer.rx = meshNode('RX', 51.1, 1); + const ctx = context([...nodes, observer.rx]); + ctx.learningModel.prefixProbabilities.set('unknown|AA||AA02', 1); + const result = decodeBetaCanonicalGroupWithHeldPath({ canonicalHashes: observer.hops, members: [observer] }, ctx, + ['AA01', 'BB01', 'CC01', 'DD01']); + assert.equal(result.refined, true); + assert.equal(result.hops.get(0)?.nodeId, 'AA02'); +}); + +test('a missing-position candidate needs a real directed edge to its observer', () => { + const missing = meshNode('AA01', 0, 0); + const plausible = meshNode('AA02', 51.1, 1); + const observer = entry('RX', ['AA']); + observer.rx = meshNode('RX', 51.2, 1); + const ctx = context([missing, plausible, observer.rx]); + const rows = { + positionPrefixes: [{ prefix: 'AA', position: 0, node_id: 'AA01', count: 1000 }], + corridors: [], transitions: [], positionTransitions: [], + }; + ctx.learningModel.champion = buildChampionPriors(rows); + assert.equal(decodeBetaCanonicalGroup({ canonicalHashes: ['AA'], members: [observer] }, ctx).hops.get(0)?.nodeId, 'AA02'); + ctx.learningModel.champion = buildChampionPriors({ ...rows, transitions: [{ from_node_id: 'AA01', to_node_id: 'RX', count: 10 }] }); + const hop = decodeBetaCanonicalGroup({ canonicalHashes: ['AA'], members: [observer] }, ctx).hops.get(0); + assert.equal(hop?.nodeId, 'AA01'); + assert.equal(hop?.lat, null); +}); + +test('a relay sharing the source prefix does not erase the source endpoint', () => { + const source = meshNode('AA0001', 51, 1); + const relay = meshNode('AA0002', 51.1, 1); + const observer = entry('RX', ['AA']); + observer.rx = meshNode('RX', 51.2, 1); + const ctx = context([source, relay, observer.rx]); + const decoded: BetaSharedDecode = { + canonicalHashes: ['AA'], hopConfidences: new Map([[0, 1]]), + hops: new Map([[0, { hash: 'AA', nodeId: relay.node_id, name: relay.name, lat: relay.lat, lon: relay.lon, margin: 1, ambiguous: false }]]), + }; + assert.deepEqual(projectCanonicalPathForObserver(observer, decoded, ctx, source).purplePath, + [[51, 1], [51.1, 1], [51.2, 1]]); +}); + function packet(rx_node_id: string, path_hashes: string[], packet_type = 0): PathPacket { return { packet_hash: 'packet', diff --git a/backend/src/path-beta/sharedDecoder.ts b/backend/src/path-beta/sharedDecoder.ts index a8ae2f6..1ccb8c6 100644 --- a/backend/src/path-beta/sharedDecoder.ts +++ b/backend/src/path-beta/sharedDecoder.ts @@ -1,6 +1,7 @@ import { buildObserverBounds, decodePathWithScore, + distanceKm, evaluatePhysicalHop, indexCandidatesByHash, scoreFixedPath, @@ -17,9 +18,11 @@ import { ML_DOMINANT_THRESHOLD, NULL_BASELINE, SCORE, + CORRIDOR_INTERPOLATION_KM, + OBSERVER_DISTANCE_DECAY_KM, } from '../path-shared/scoring.js'; import { nodePathHash, normalizePathHash } from '../path-hash/utils.js'; -import { clamp, hasCoords, linkKey } from './geometry.js'; +import { clamp, hasCoords, isValidMapCoord, linkKey } from './geometry.js'; import type { BetaResolveContext, MeshNode, PathPacket } from './types.js'; export type BetaObserverEntry = { @@ -207,16 +210,27 @@ export function decodeBetaCanonicalGroup( .filter(hasCoords) .map((node) => ({ lat: node.lat!, lon: node.lon! })); if (sourceNode) observerCoordinates.push({ lat: sourceNode.lat!, lon: sourceNode.lon! }); - const bounds = buildObserverBounds(observerCoordinates, pathingConfig.maxHopKm); - const candidateEvidence: CandidateNodeEvidence[] = context.repeaterNodes.map((node) => ({ + // A flood can wander many hops beyond the receiving observer's region. + // A one-hop region bound silently discarded the start of long routes. + const bounds = buildObserverBounds(observerCoordinates, pathingConfig.maxHopKm * Math.max(1, canonicalHashes.length)); + const candidateNodes = [...context.repeaterNodes]; + if (context.learningModel.champion) { + for (const node of context.nodesById.values()) { + if (!isValidMapCoord(node.lat, node.lon) && (node.role === null || node.role === 2) + && context.learningModel.champion.prefixCounts.has(`${node.node_id.slice(0, 2).toUpperCase()}|${node.node_id}`)) { + candidateNodes.push(node); + } + } + } + const candidateEvidence: CandidateNodeEvidence[] = candidateNodes.map((node) => ({ nodeId: node.node_id, name: node.name, - lat: node.lat, - lon: node.lon, + lat: hasCoords(node) ? node.lat : null, + lon: hasCoords(node) ? node.lon : null, elevationM: node.elevation_m, })); - const candidatesByHash = indexCandidatesByHash(canonicalHashes, candidateEvidence, bounds); - const confirmed = confirmedMultibyteAssignments(group, context.repeaterNodes); + const candidatesByHash = indexCandidatesByHash(canonicalHashes, candidateEvidence, bounds, Boolean(context.learningModel.champion)); + const confirmed = confirmedMultibyteAssignments(group, [...context.nodesById.values()]); // A confirmed relay may sit outside the observer-only candidate bound on a // long path. Preserve that direct evidence explicitly before locking it. for (const [position, nodeId] of confirmed) { @@ -242,6 +256,19 @@ export function decodeBetaCanonicalGroup( const bucketHours = context.learningModel.bucketHours || 6; const bucket = currentHourBucket(bucketHours); const stickyStrength = clamp(1 - Number(stickyAgeFraction ?? 0), 0, 1); + const champion = context.learningModel.champion; + const confirmedEdges = new Set(); + for (let position = 1; position < canonicalHashes.length; position++) { + const from = confirmed.get(position - 1); + const to = confirmed.get(position); + if (from && to) confirmedEdges.add(`${from}|${to}`); + } + for (const member of group.members) { + const first = confirmed.get(0); + const last = confirmed.get(member.hops.length - 1); + if (first && member.packet.src_node_id) confirmedEdges.add(`${member.packet.src_node_id}|${first}`); + if (last) confirmedEdges.add(`${last}|${member.observerId}`); + } const evidence: PathDecoderEvidence = { weights: SCORE, @@ -259,10 +286,13 @@ export function decodeBetaCanonicalGroup( nodeId: sourceNode.node_id, elevationM: sourceNode.elevation_m, } : undefined, - endpointTransitionsGateOnly: true, + endpointTransitionsGateOnly: !champion, + scoreDirectAnchorTransitions: Boolean(champion), + requireUnpositionedTransitionEvidence: Boolean(champion), directAnchors: (position) => anchors.get(position) ?? [], prefixProbability: (nodeId, hash, previousHash) => { const prefix = normalizePathHash(hash).slice(0, 2); + if (champion) return champion.prefixCounts.get(`${prefix}|${nodeId}`) ?? 0; const previous = normalizePathHash(previousHash).slice(0, 2); const learned = regionalMaximum(regions, (region) => ( context.learningModel.prefixProbabilities.get(`${region}|${prefix}|${previous}|${nodeId}`) @@ -271,14 +301,55 @@ export function decodeBetaCanonicalGroup( const sticky = stickyMap?.get(prefix) === nodeId ? stickyStrength : 0; return Math.max(learned, sticky); }, + positionPrefixCount: (position, nodeId, hash) => ( + champion?.positionPrefixCounts.get(`${hash.slice(0, 2)}|${position}|${nodeId}`) ?? 0 + ), + corridorPriorCount: (position, nodeId) => { + let best = 0; + for (const member of group.members) { + if (position >= member.hops.length || !member.packet.src_node_id) continue; + best = Math.max(best, champion?.corridorCounts.get( + `${member.packet.src_node_id}|${member.observerId}|${position}|${nodeId}`, + ) ?? 0); + } + return best; + }, + observerDistance: (candidate) => { + if (!champion) return 0; + let nearest = Infinity; + for (const member of group.members) { + if (hasCoords(member.rx)) nearest = Math.min(nearest, distanceKm(candidate, { lat: member.rx.lat!, lon: member.rx.lon! })); + } + return Number.isFinite(nearest) ? -nearest / OBSERVER_DISTANCE_DECAY_KM : 0; + }, + corridorInterpolation: (position, _pathLength, candidate) => { + if (!champion || !sourceNode) return 0; + let nearest = Infinity; + for (const member of group.members) { + if (position >= member.hops.length || !hasCoords(member.rx)) continue; + const fraction = member.hops.length <= 1 ? 0.5 : position / (member.hops.length - 1); + nearest = Math.min(nearest, distanceKm(candidate, { + lat: sourceNode.lat! + (member.rx.lat! - sourceNode.lat!) * fraction, + lon: sourceNode.lon! + (member.rx.lon! - sourceNode.lon!) * fraction, + })); + } + return Number.isFinite(nearest) ? -nearest / CORRIDOR_INTERPOLATION_KM : 0; + }, mlPrefixScore: () => 0, directedEdgeScore: (fromNodeId, toNodeId) => regionalMaximum(regions, (region) => ( context.learningModel.edgeScores.get(`${region}|${bucket}|${fromNodeId}|${toNodeId}`) ?? context.learningModel.edgeScores.get(`${region}|-1|${fromNodeId}|${toNodeId}`) )), - transitionProbability: (fromNodeId, toNodeId) => regionalMaximum(regions, (region) => ( + transitionProbability: (fromNodeId, toNodeId) => champion + ? champion.transitionCounts.get(`${fromNodeId}|${toNodeId}`) ?? 0 + : regionalMaximum(regions, (region) => ( context.learningModel.transitionProbabilities.get(`${region}|${fromNodeId}|${toNodeId}`) )), + positionConditionalTransition: (position, fromNodeId, toNodeId) => ( + champion?.positionTransitionCounts.get(`${position}|${fromNodeId}|${toNodeId}`) ?? 0 + ), + isItmViable: (fromNodeId, toNodeId) => champion != null + && context.linkMetrics.get(linkKey(fromNodeId, toNodeId))?.itm_viable === true, motifProbability: (fromNodeId, toNodeId) => regionalMaximum(regions, (region) => ( context.learningModel.motifProbabilities.get(`${region}|${bucket}|2|${fromNodeId}>${toNodeId}`) ?? context.learningModel.motifProbabilities.get(`${region}|-1|2|${fromNodeId}>${toNodeId}`) @@ -303,7 +374,11 @@ export function decodeBetaCanonicalGroup( antennaHeightM: pathingConfig.defaultAntennaHeightM, }); return { - possible: physics.possible, + // Radio observations identify an actual edge. Terrain and current + // coordinates are estimates and cannot disprove that identity. + possible: confirmedEdges.has(`${from.nodeId}|${to.nodeId}`) + || (champion?.verifiedEdgeCounts.get(`${from.nodeId}|${to.nodeId}`) ?? 0) >= 2 + || physics.possible, score: pathingConfig.physicsSoftMarginWeight * physics.softMargin, }; }, @@ -311,6 +386,19 @@ export function decodeBetaCanonicalGroup( const decoded = decodePathWithScore(canonicalHashes, evidence); const hops = decoded.hops; + // Identity in a collision-free multibyte observation is direct evidence, + // even when the node has since moved or has no usable map coordinates. + // Preserve those IDs if missing geometry prevents a complete trellis solve. + for (const [position, nodeId] of confirmed) { + const node = context.nodesById.get(nodeId); + if (!node) continue; + hops.set(position, { + hash: canonicalHashes[position]!, nodeId, name: node.name, + lat: hasCoords(node) ? node.lat : null, + lon: hasCoords(node) ? node.lon : null, + margin: Infinity, ambiguous: false, + }); + } const hopConfidences = new Map(); for (const [position, hop] of hops) hopConfidences.set(position, marginConfidence(hop)); return { @@ -326,6 +414,7 @@ export function decodeBetaCanonicalGroup( function hardHopPossible(from: MeshNode, to: MeshNode, context: BetaResolveContext): boolean { if (!hasCoords(from) || !hasCoords(to)) return false; + if ((context.learningModel.champion?.verifiedEdgeCounts.get(`${from.node_id}|${to.node_id}`) ?? 0) >= 2) return true; return evaluatePhysicalHop( { lat: from.lat!, lon: from.lon!, elevationM: from.elevation_m }, { lat: to.lat!, lon: to.lon!, elevationM: to.elevation_m }, @@ -378,27 +467,30 @@ function materializeHeldDecode( group: BetaCanonicalGroup, context: BetaResolveContext, nodeIds: readonly string[], + evidence: BetaSharedDecode, ): BetaSharedDecode { const canonicalHashes = group.canonicalHashes.map(normalizePathHash).filter(Boolean); const hops = new Map(); const hopConfidences = new Map(); for (let position = 0; position < canonicalHashes.length; position++) { const node = context.nodesById.get(nodeIds[position]!); + const current = evidence.hops.get(position); + const unchanged = current?.nodeId === node?.node_id; hops.set(position, { hash: canonicalHashes[position]!, nodeId: node?.node_id ?? null, name: node?.name ?? null, lat: node?.lat ?? null, lon: node?.lon ?? null, - margin: Infinity, - ambiguous: false, + margin: unchanged ? current!.margin : 0, + ambiguous: !unchanged || current!.ambiguous, }); - hopConfidences.set(position, node ? 1 : 0); + hopConfidences.set(position, unchanged ? evidence.hopConfidences.get(position) ?? 0 : 0); } - return { canonicalHashes, hops, hopConfidences, score: 0 }; + return { canonicalHashes, hops, hopConfidences, score: evidence.baselineScore }; } -/** Hold a physical path and refine only the two observer-nearest positions. */ +/** Reconsider the whole chain when later observers supply better evidence. */ export function decodeBetaCanonicalGroupWithHeldPath( group: BetaCanonicalGroup, context: BetaResolveContext, @@ -415,35 +507,18 @@ export function decodeBetaCanonicalGroupWithHeldPath( }; } - let current = [...heldNodeIds]; - let refined = false; - const lastPosition = current.length - 1; - for (let position = lastPosition; position >= Math.max(0, lastPosition - 1); position--) { - const fixed = new Map(); - current.forEach((nodeId, index) => { if (index !== position) fixed.set(index, nodeId); }); - const candidate = decodeBetaCanonicalGroup( - group, - context, - stickyMap, - stickyAgeFraction, - fixed, - current, - ); - const candidateHop = candidate.hops.get(position); - if (candidateHop?.nodeId - && candidateHop.nodeId !== current[position] - && Number.isFinite(candidate.score) - && Number.isFinite(candidate.baselineScore) - && candidate.score! >= candidate.baselineScore! + HELD_PATH_REFINEMENT_MARGIN) { - current[position] = candidateHop.nodeId; - refined = true; - } + const candidate = decodeBetaCanonicalGroup(group, context, stickyMap, stickyAgeFraction, undefined, heldNodeIds); + const changed = heldNodeIds.some((nodeId, position) => candidate.hops.get(position)?.nodeId !== nodeId); + if (changed && Number.isFinite(candidate.score) + && (!Number.isFinite(candidate.baselineScore) + || candidate.score! >= candidate.baselineScore! + HELD_PATH_REFINEMENT_MARGIN)) { + return { ...candidate, held: true, physical: true, refined: true }; } return { - ...materializeHeldDecode(group, context, current), + ...materializeHeldDecode(group, context, heldNodeIds, candidate), held: true, physical: true, - refined, + refined: false, }; } @@ -454,11 +529,10 @@ type PositionedPoint = { receiver: boolean; }; -function shouldPrependSource(entry: BetaObserverEntry, source: MeshNode | null): source is MeshNode { - if (!hasCoords(source)) return false; - if (entry.packet.packet_type === 4) return true; - const firstHash = entry.hops[0]; - return Boolean(firstHash && nodePathHash(source.node_id, firstHash) !== normalizePathHash(firstHash)); +function shouldPrependSource(source: MeshNode | null, decoded: BetaSharedDecode): source is MeshNode { + // A one-byte prefix match is not an identity match. Only omit the source + // when the first resolved relay already represents that exact node. + return hasCoords(source) && decoded.hops.get(0)?.nodeId !== source.node_id; } /** Project one observer's DTO path from its group's canonical decode without inventing fallback edges. */ @@ -469,7 +543,7 @@ export function projectCanonicalPathForObserver( source: MeshNode | null, ): BetaPathProjection { const points: PositionedPoint[] = []; - if (shouldPrependSource(entry, source)) { + if (shouldPrependSource(source, decoded)) { points.push({ coordinate: [source.lat!, source.lon!], nodeId: source.node_id, diff --git a/backend/src/path-beta/types.ts b/backend/src/path-beta/types.ts index 0d0320a..3989e01 100644 --- a/backend/src/path-beta/types.ts +++ b/backend/src/path-beta/types.ts @@ -1,3 +1,5 @@ +import type { ChampionPriors } from './championPriors.js'; + export type MeshNode = { node_id: string; name: string | null; @@ -21,6 +23,7 @@ export type LinkMetrics = { }; export type PathLearningModel = { + champion?: ChampionPriors; prefixProbabilities: Map; transitionProbabilities: Map; edgeScores: Map; @@ -32,6 +35,8 @@ export type PathLearningModel = { export type PathPacket = { + direction?: string | null; + route_type?: number | null; packet_hash: string; rx_node_id: string | null; src_node_id: string | null; diff --git a/backend/src/path-beta/workerPool.test.ts b/backend/src/path-beta/workerPool.test.ts index 230b24d..6bc77a8 100644 --- a/backend/src/path-beta/workerPool.test.ts +++ b/backend/src/path-beta/workerPool.test.ts @@ -29,23 +29,13 @@ test('worker pool bounds queued interactive work without losing accepted jobs', } }); -test('worker pool timeout includes queue and execution time and replaces the worker', { timeout: 10_000 }, async (t) => { - // Advance the pool deadline deterministically; replacement thread startup - // is not a 750ms performance assertion on a shared test host. - t.mock.timers.enable({ apis: ['setTimeout'] }); +test('worker pool timeout includes queue and execution time and replaces the worker', async () => { const pool = new WorkerPool(fixtureUrl, 1, 1, 1, 750); try { - const active = pool.run({ value: 'late', delayMs: 1_500 }); - const queued = pool.run({ value: 'queued', delayMs: 1 }); - const expired = Promise.all([ - assert.rejects(active, WorkerPoolTimeoutError), - assert.rejects(queued, WorkerPoolTimeoutError), - ]); - t.mock.timers.tick(749); - assert.deepEqual(pool.snapshot(), { active: 1, interactiveQueued: 1, backgroundQueued: 0 }); - t.mock.timers.tick(1); - await expired; - assert.deepEqual(pool.snapshot(), { active: 0, interactiveQueued: 0, backgroundQueued: 0 }); + await assert.rejects( + pool.run({ value: 'late', delayMs: 1_500 }), + WorkerPoolTimeoutError, + ); assert.equal(await pool.run({ value: 'recovered', delayMs: 1 }), 'recovered'); } finally { await pool.close(); diff --git a/backend/src/path-core/decoder.test.ts b/backend/src/path-core/decoder.test.ts index fba4a29..e79ccb1 100644 --- a/backend/src/path-core/decoder.test.ts +++ b/backend/src/path-core/decoder.test.ts @@ -5,6 +5,8 @@ import { decodePath, evaluatePhysicalHop, indexCandidatesByHash, + decodePathWithScore, + scoreFixedPath, type CandidateNode, type PathDecoderEvidence, } from './decoder.js'; @@ -25,6 +27,36 @@ const node = (nodeId: string, lat: number, lon: number): CandidateNode => ({ lon, }); +test('indexes every matching width, de-duplicates repeated hashes and preserves elevation', () => { + const indexed = indexCandidatesByHash(['AA', 'AABBCC', 'AA'], [ + { nodeId: 'AABBCC00', name: null, lat: 51, lon: 0, elevationM: 456 }, + ], null); + assert.equal(indexed.get('AA')?.length, 1); + assert.equal(indexed.get('AABBCC')?.[0]?.elevationM, 456); +}); + +test('direct radio evidence can override a geographic cutoff without bypassing other gates', () => { + const decoded = decodePath(['AA', 'BB'], evidence(new Map([ + ['AA', [node('A', 51, 1)]], ['BB', [node('B', 53, 1), node('C', 54, 1)]], + ]), { maxHopKm: 100, physicalTransition: (from, to) => ({ possible: from.nodeId === 'A' && to.nodeId === 'B', score: 0 }) })); + assert.deepEqual([...decoded.values()].map((hop) => hop.nodeId), ['A', 'B']); +}); + +test('cached transitions preserve the complete chain score and score each edge once', () => { + const counts = new Map(); + const ev = evidence(new Map([ + ['AA', [node('A', 51, 1), node('B', 51, 1)]], + ['BB', [node('C', 51.1, 1), node('D', 51.1, 1)]], + ]), { directedEdgeScore: (a, b) => { + const key = `${a}|${b}`; + counts.set(key, (counts.get(key) ?? 0) + 1); + return 0; + } }); + const result = decodePathWithScore(['AA', 'BB'], ev); + assert.deepEqual([...counts.values()], [1, 1, 1, 1]); + assert.equal(result.score, scoreFixedPath(['AA', 'BB'], [...result.hops.values()].map((h) => h.nodeId!), ev)); +}); + function evidence( candidatesByHash: ReadonlyMap, overrides: Partial = {}, diff --git a/backend/src/path-core/decoder.ts b/backend/src/path-core/decoder.ts index 937880f..b6544b7 100644 --- a/backend/src/path-core/decoder.ts +++ b/backend/src/path-core/decoder.ts @@ -74,7 +74,7 @@ export type PathDecoderEvidence = { unresolvedBaseline: number; ambiguityDelta: number; maxColumnCandidates: number; - candidatesByHash: ReadonlyMap; + candidatesByHash: ReadonlyMap; directAnchors: (position: number, hash: string) => readonly ObserverAnchor[]; sourceAnchor?: ObserverAnchor; terminalAnchors?: readonly ObserverAnchor[]; @@ -88,6 +88,10 @@ export type PathDecoderEvidence = { ) => { possible: boolean; score: number }; /** Endpoint anchors use only hard gates plus the bounded physics tiebreaker. */ endpointTransitionsGateOnly?: boolean; + /** Include directed evidence for each directly receiving observer. */ + scoreDirectAnchorTransitions?: boolean; + /** Missing coordinates require observed directed edges on both sides. */ + requireUnpositionedTransitionEvidence?: boolean; prefixProbability: (nodeId: string, hash: string, prevHash: string | null) => number; positionPrefixCount?: (position: number, nodeId: string, hash: string) => number; corridorPriorCount?: (position: number, nodeId: string) => number; @@ -213,24 +217,34 @@ export function indexCandidatesByHash( hashes: readonly string[], candidates: readonly CandidateNodeEvidence[], bounds: GeographicBounds | null, -): Map { - const result = new Map(); + includeUnpositioned = false, +): Map { + const result = new Map(); + const hashesByByte = new Map(); + for (const hash of new Set(hashes)) { + const byte = hash.slice(0, 2).toUpperCase(); + const values = hashesByByte.get(byte) ?? []; + values.push(hash); + hashesByByte.set(byte, values); + } for (const candidate of candidates) { - if (candidate.lat == null || candidate.lon == null - || candidate.lat === 0 || candidate.lon === 0) continue; - const point = { lat: candidate.lat, lon: candidate.lon }; - if (bounds && !isInBounds(point, bounds)) continue; + const positioned = !(candidate.lat == null || candidate.lon == null + || !Number.isFinite(candidate.lat) || !Number.isFinite(candidate.lon) + || (candidate.lat === 0 && candidate.lon === 0)); + if (!positioned && !includeUnpositioned) continue; + const point = { lat: candidate.lat!, lon: candidate.lon! }; + if (positioned && bounds && !isInBounds(point, bounds)) continue; const upperNodeId = candidate.nodeId.toUpperCase(); - for (const hash of hashes) { + for (const hash of hashesByByte.get(upperNodeId.slice(0, 2)) ?? []) { if (!upperNodeId.startsWith(hash)) continue; if (!result.has(hash)) result.set(hash, []); result.get(hash)!.push({ nodeId: candidate.nodeId, name: candidate.name, - lat: candidate.lat, - lon: candidate.lon, + lat: positioned ? candidate.lat : null, + lon: positioned ? candidate.lon : null, + elevationM: candidate.elevationM, }); - break; } } return result; @@ -249,9 +263,16 @@ function emissionScore( const positioned = candidate.lat != null && candidate.lon != null; let directPhysicsScore = 0; - if (anchors.length > 0) { - if (!positioned) return -Infinity; - if (minDistanceToSet({ lat: candidate.lat!, lon: candidate.lon! }, anchors) > evidence.maxHopKm) { + if (anchors.length > 0 && !positioned) { + if (!evidence.requireUnpositionedTransitionEvidence || !evidence.scoreDirectAnchorTransitions) return -Infinity; + directPhysicsScore = Math.max(...anchors.map((anchor) => endpointTransitionScore( + candidate, endpointCandidate(anchor), evidence, position + 1, + ))); + if (!Number.isFinite(directPhysicsScore)) return -Infinity; + } + if (anchors.length > 0 && positioned) { + if (!evidence.physicalTransition + && minDistanceToSet({ lat: candidate.lat!, lon: candidate.lon! }, anchors) > evidence.maxHopKm) { return -Infinity; } if (evidence.physicalTransition) { @@ -262,7 +283,9 @@ function emissionScore( endpointCandidate(anchor) as CandidateNode, distanceKm(candidate as CandidateNode, anchor), ); - if (physics.possible) bestPhysics = Math.max(bestPhysics, physics.score); + if (physics.possible) bestPhysics = Math.max(bestPhysics, evidence.scoreDirectAnchorTransitions + ? endpointTransitionScore(candidate, endpointCandidate(anchor), evidence, position + 1) + : physics.score); } if (!isFinite(bestPhysics)) return -Infinity; directPhysicsScore = bestPhysics; @@ -296,7 +319,7 @@ function emissionScore( * Math.log1p(Math.max(0, evidence.corridorPriorCount(position, candidate.nodeId))); } - if (evidence.weights.corridorInterpolation !== 0 && evidence.corridorInterpolation) { + if (positioned && evidence.weights.corridorInterpolation !== 0 && evidence.corridorInterpolation) { score += evidence.weights.corridorInterpolation * evidence.corridorInterpolation(position, pathLength, candidate as CandidateNode); } @@ -310,13 +333,16 @@ function transitionScore( evidence: PathDecoderEvidence, ): number { if (previous.nodeId === null || current.nodeId === null) return 0; + if (evidence.requireUnpositionedTransitionEvidence + && (previous.lat == null || previous.lon == null || current.lat == null || current.lon == null) + && evidence.transitionProbability(previous.nodeId, current.nodeId) <= 0) return -Infinity; let score = 0; if (previous.lat != null && previous.lon != null && current.lat != null && current.lon != null) { const distance = distanceKm( { lat: previous.lat, lon: previous.lon }, { lat: current.lat, lon: current.lon }, ); - if (distance > evidence.maxHopKm) return -Infinity; + if (!evidence.physicalTransition && distance > evidence.maxHopKm) return -Infinity; if (evidence.physicalTransition) { const physics = evidence.physicalTransition( previous as CandidateNode, @@ -355,11 +381,12 @@ function endpointTransitionScore( from: DecoderCandidate, to: DecoderCandidate, evidence: PathDecoderEvidence, + position = 0, ): number { - if (!evidence.endpointTransitionsGateOnly) return transitionScore(0, from, to, evidence); + if (!evidence.endpointTransitionsGateOnly) return transitionScore(position, from, to, evidence); if (from.lat == null || from.lon == null || to.lat == null || to.lon == null) return -Infinity; const distance = distanceKm(from as CandidateNode, to as CandidateNode); - if (distance > evidence.maxHopKm) return -Infinity; + if (!evidence.physicalTransition && distance > evidence.maxHopKm) return -Infinity; if (!evidence.physicalTransition) return 0; const physics = evidence.physicalTransition(from as CandidateNode, to as CandidateNode, distance); return physics.possible ? physics.score : -Infinity; @@ -493,6 +520,19 @@ export function decodePathWithScore( ))); } + // Forward and backward passes use identical transitions. Cache each edge + // once: terrain checks, geographic distances and learned lookups dominate + // the cost of decoding densely colliding one-byte columns. + const transitions: number[][][] = columns.map((column, position) => position === 0 ? [] + : columns[position - 1]!.map(() => new Array(column.length).fill(NaN))); + const edgeScore = (position: number, previous: number, current: number): number => { + const row = transitions[position]![previous]!; + if (Number.isNaN(row[current])) row[current] = transitionScore( + position, columns[position - 1]![previous]!, columns[position]![current]!, evidence, + ); + return row[current]!; + }; + // Best prefix score ending at columns[position][candidate]. const forward: number[][] = columns.map((column) => new Array(column.length).fill(-Infinity)); const previousCandidate: number[][] = columns.map((column) => new Array(column.length).fill(-1)); @@ -512,12 +552,7 @@ export function decodePathWithScore( let bestPrevious = -1; for (let previous = 0; previous < columns[position - 1]!.length; previous++) { if (!isFinite(forward[position - 1]![previous]!)) continue; - const transition = transitionScore( - position, - columns[position - 1]![previous]!, - columns[position]![current]!, - evidence, - ); + const transition = edgeScore(position, previous, current); if (!isFinite(transition)) continue; const total = forward[position - 1]![previous]! + transition; if (total > best) { @@ -547,12 +582,7 @@ export function decodePathWithScore( for (let next = 0; next < columns[position + 1]!.length; next++) { const nextEmission = emissions[position + 1]![next]!; if (!isFinite(nextEmission) || !isFinite(backward[position + 1]![next]!)) continue; - const transition = transitionScore( - position + 1, - columns[position]![current]!, - columns[position + 1]![next]!, - evidence, - ); + const transition = edgeScore(position + 1, current, next); if (!isFinite(transition)) continue; const total = transition + nextEmission + backward[position + 1]![next]!; if (total > best) best = total; diff --git a/backend/src/path-lazy/evaluate.ts b/backend/src/path-lazy/evaluate.ts index 08e5c3b..68dbc40 100644 --- a/backend/src/path-lazy/evaluate.ts +++ b/backend/src/path-lazy/evaluate.ts @@ -1,5 +1,7 @@ /** - * Gold-packet accuracy harness for all path-resolution jobs. + * Historical path diagnostics, retained for debugging old jobs. + * This harness does not enforce a held-out split and filters long routes. + * Use tools/pathAccuracy.ts for reproducible accuracy comparisons. * * Ground truth: multibyte packets (path_hash_size_bytes > 1) whose every relay * hash uniquely resolves to exactly one positioned node in the network scope. @@ -585,6 +587,7 @@ function printStratification( } async function main() { + console.warn('[evaluate] Historical diagnostics only; use npm run path:accuracy for held-out accuracy measurements.'); const network = process.argv[2] ?? 'ukmesh'; const sampleSize = Number(process.argv[3] ?? 400); const selectedJobs = selectedJobNames(); diff --git a/backend/src/path-lazy/lazyResolver.ts b/backend/src/path-lazy/lazyResolver.ts index 8a17b09..8f75912 100644 --- a/backend/src/path-lazy/lazyResolver.ts +++ b/backend/src/path-lazy/lazyResolver.ts @@ -1,5 +1,7 @@ /** - * Lazy path resolver — called after propagation has settled. + * Previous lazy resolver, retained for read-only accuracy comparisons. + * Production packet details use resolveLazyPathForPacketHash and the same + * evidence provider as the live overlays; this implementation is not routed. * * Uses path_hashes embedded in packets by relaying nodes, combined with * geographic anchoring from MQTT observer positions, to reconstruct the diff --git a/backend/src/path-learning/rebuild.test.ts b/backend/src/path-learning/rebuild.test.ts index 9462aac..703ae95 100644 --- a/backend/src/path-learning/rebuild.test.ts +++ b/backend/src/path-learning/rebuild.test.ts @@ -1,7 +1,8 @@ import assert from 'node:assert/strict'; import { readFile } from 'node:fs/promises'; import test from 'node:test'; -import { pathLearningInputHash } from './rebuild.js'; +import { pathLearningInputHash, resolvePathForPacket } from './rebuild.js'; +import { buildNodePathHashIndex } from '../path-hash/utils.js'; const baseInput = { modelNetwork: 'ukmesh', @@ -35,6 +36,17 @@ test('path-learning input hash fences bounds, source rows, and privacy generatio }), baseline); }); +test('training keeps original positions across missing relays and preserves verified loops', () => { + const a = { node_id: 'AA0011', lat: 51, lon: 1, elevation_m: null, iata: null }; + const b = { ...a, node_id: 'BB0022' }; + const rx = { ...a, node_id: 'CC0033' }; + const index = buildNodePathHashIndex([a, b, rx]); + const gap = resolvePathForPacket(['AA0011', 'FFFFFF', 'BB0022'], undefined, rx, index, new Set()); + assert.deepEqual(gap.map((hop) => hop.position), [0, 2]); + const loop = resolvePathForPacket(['AA0011', 'BB0022', 'AA0011'], undefined, rx, index, new Set()); + assert.deepEqual(loop.map((hop) => hop.node.node_id), ['AA0011', 'BB0022', 'AA0011']); +}); + test('rebuild has no full-table-per-network replacement path and retains all fallback', async () => { const source = await readFile(new URL('./rebuild.ts', import.meta.url), 'utf8'); assert.doesNotMatch(source, /DELETE FROM path_[a-z_]+_priors WHERE network = \$1/); diff --git a/backend/src/path-learning/rebuild.ts b/backend/src/path-learning/rebuild.ts index b4cb80f..85863ad 100644 --- a/backend/src/path-learning/rebuild.ts +++ b/backend/src/path-learning/rebuild.ts @@ -32,6 +32,7 @@ type LearningNode = { }; type LearningPacket = { + direction?: string | null; time: string; rx_node_id: string; src_node_id: string | null; @@ -47,6 +48,7 @@ type LearningLink = { }; type ResolvedHop = { + position: number; prefix: string; node: LearningNode; isUnique: boolean; @@ -91,7 +93,7 @@ const MAX_CORRIDOR_CHOICES_PER_GROUP = 16; const MAX_POSITION_TRANSITIONS_PER_GROUP = 16; const HOUR_BUCKET_SIZE = 6; const PREFIX_AMBIGUITY_RADIUS_KM = 45; -const PATH_LEARNING_ALGORITHM_VERSION = 'path-learning-v4-delta'; +const PATH_LEARNING_ALGORITHM_VERSION = 'path-learning-v5-contiguous'; type PrefixPriorWriteRow = { prefix: string; @@ -218,7 +220,7 @@ function localPrefixAmbiguityPenalty( return clamp(raw * 0.12, 0, 0.24); } -function resolvePathForPacket( +export function resolvePathForPacket( pathHashes: string[], srcNode: LearningNode | undefined, rxNode: LearningNode, @@ -232,7 +234,7 @@ function resolvePathForPacket( for (let i = pathHashes.length - 1; i >= 0; i--) { const prefix = normalizePathHash(pathHashes[i]); const allCandidates = getNodesForPathHash(pathHashIndex, prefix); - const candidates = allCandidates.filter((n) => !visited.has(n.node_id)); + const candidates = allCandidates.filter((n) => allCandidates.length === 1 || !visited.has(n.node_id)); if (candidates.length === 0) continue; let best: LearningNode | null = null; @@ -251,7 +253,7 @@ function resolvePathForPacket( } if (best) { - resolved.unshift({ prefix, node: best, isUnique: allCandidates.length === 1 }); + resolved.unshift({ position: i, prefix, node: best, isUnique: allCandidates.length === 1 }); visited.add(best.node_id); prev = best; } @@ -500,6 +502,7 @@ export async function rebuildPathLearningModels(): Promise { AND path_hashes IS NOT NULL AND cardinality(path_hashes) > 0 AND path_hash_size_bytes >= 2 + AND route_type IN (0, 1) AND mod(hashtext(packet_hash)::bigint + 2147483648, 100) < ${TRAINING_SPLIT_PERCENT} ORDER BY network LIMIT 33`, @@ -592,12 +595,13 @@ async function rebuildNetworkUnderLease( const packetsResult = await analyticsQuery( `SELECT DISTINCT ON (packet_hash, rx_node_id, src_node_id, path_hashes) - time, rx_node_id, src_node_id, path_hashes + time, rx_node_id, src_node_id, path_hashes, payload->>'direction' AS direction FROM packets WHERE rx_node_id IS NOT NULL AND path_hashes IS NOT NULL AND cardinality(path_hashes) > 0 AND path_hash_size_bytes >= 2 + AND route_type IN (0, 1) AND time > $${sourceNetwork ? 2 : 1}::timestamptz AND time <= $${sourceNetwork ? 3 : 2}::timestamptz AND mod(hashtext(packet_hash)::bigint + 2147483648, 100) < ${TRAINING_SPLIT_PERCENT} @@ -708,7 +712,7 @@ async function rebuildNetworkUnderLease( if (hashes.length === 0) continue; const rx = nodesById.get(packet.rx_node_id); if (!rx) continue; - if (hashes.length > 0 && rx.node_id.toUpperCase().startsWith(hashes[hashes.length - 1]!)) { + if (packet.direction !== 'rx' && hashes.length > 0 && rx.node_id.toUpperCase().startsWith(hashes[hashes.length - 1]!)) { hashes = hashes.slice(0, -1); } if (hashes.length === 0) continue; @@ -722,9 +726,13 @@ async function rebuildNetworkUnderLease( const ts = new Date(packet.time); const bucket = hourBucket(ts); - const fullNodes: LearningPathNode[] = [ + const resolvedByPosition = new Map(resolved.map((hop) => [hop.position, hop])); + const fullNodes: (LearningPathNode | null)[] = [ ...(src ? [{ node: src, isVerified: true }] : []), - ...resolved.map((hop) => ({ node: hop.node, isVerified: hop.isUnique })), + ...hashes.map((_, position) => { + const hop = resolvedByPosition.get(position); + return hop ? { node: hop.node, isVerified: hop.isUnique } : null; + }), { node: rx, isVerified: true }, ]; let successfulEdges = 0; @@ -732,7 +740,7 @@ async function rebuildNetworkUnderLease( for (let i = 0; i < fullNodes.length - 1; i++) { const from = fullNodes[i]!; const to = fullNodes[i + 1]!; - if (!from.isVerified || !to.isVerified) continue; + if (!from?.isVerified || !to?.isVerified) continue; totalEdges++; if (confirmedLinks.has(linkKey(from.node.node_id, to.node.node_id))) successfulEdges++; } @@ -746,7 +754,7 @@ async function rebuildNetworkUnderLease( for (let i = 0; i < resolved.length; i++) { const hop = resolved[i]!; if (!hop.isUnique) continue; - const prevPrefix = i > 0 ? resolved[i - 1]!.prefix : ''; + const prevPrefix = hop.position > 0 ? hashes[hop.position - 1]! : ''; const prefixGroup = `${hop.prefix}|${region}|${prevPrefix}`; const choiceKey = `${prefixGroup}|${hop.node.node_id}`; increment(prefixChoiceCounts, choiceKey); @@ -783,7 +791,7 @@ async function rebuildNetworkUnderLease( for (let i = 0; i < fullNodes.length - 1; i++) { const from = fullNodes[i]!; const to = fullNodes[i + 1]!; - if (!from.isVerified || !to.isVerified) continue; + if (!from?.isVerified || !to?.isVerified) continue; const group = `${from.node.node_id}|${region}`; const edgeKey = `${group}|${to.node.node_id}`; increment(transitionCounts, edgeKey); @@ -802,7 +810,7 @@ async function rebuildNetworkUnderLease( if (i < fullNodes.length - 2) { const next = fullNodes[i + 2]!; - if (!next.isVerified) continue; + if (!next?.isVerified) continue; const motif3Group = `${region}|${bucket}|${from.node.node_id}>${to.node.node_id}`; const motif3Key = `${motif3Group}|${from.node.node_id}>${to.node.node_id}>${next.node.node_id}`; increment(motif3Counts, motif3Key); diff --git a/backend/src/path-shared/scoring.ts b/backend/src/path-shared/scoring.ts index cb32407..7c1893c 100644 --- a/backend/src/path-shared/scoring.ts +++ b/backend/src/path-shared/scoring.ts @@ -1,16 +1,14 @@ /** * Shared scoring weights and prior key-formats for the path resolvers. * - * The shared decoder scores candidate hops with prefix/transition/edge/motif - * priors, ML scores, node_links, and hop distance. The lazy resolver uses it - * today; the beta resolvers will adopt it in later extraction phases. + * All production path surfaces use the shared decoder and count-based + * multibyte priors, observer anchors, geographic and terrain evidence. * * This module is the single source of truth for: * - the prior key formats (must match how `path-learning/rebuild.ts` stores them); * - the relative scoring weights used by the Viterbi decoder. * - * `path-beta/resolver.ts` predates this module and still has its own inline - * weights; later phases will converge it here. New code should import from here. + * Evidence providers should import this table rather than duplicating weights. */ /** Generous but realistic upper bound for a single LoRa hop, in km. */ diff --git a/backend/src/platform/config/pathing.ts b/backend/src/platform/config/pathing.ts index 4ba3f4d..a72ae12 100644 --- a/backend/src/platform/config/pathing.ts +++ b/backend/src/platform/config/pathing.ts @@ -1,5 +1,5 @@ export const pathingConfig = { - maxBetaHops: 25, + maxBetaHops: 64, purpleThreshold: 0.45, earthEffectiveRadiusM: 6_371_000 / (1 - 0.25), defaultAntennaHeightM: 15, diff --git a/backend/src/regions/memberShare.test.ts b/backend/src/regions/memberShare.test.ts new file mode 100644 index 0000000..4a21da9 --- /dev/null +++ b/backend/src/regions/memberShare.test.ts @@ -0,0 +1,31 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { MEMBER_MIN_SHARE_PCT, meetsMemberShare } from './memberShare.js'; + +test('MEMBER_MIN_SHARE_PCT is the calibrated 10% floor', () => { + assert.equal(MEMBER_MIN_SHARE_PCT, 10); +}); + +test('meetsMemberShare accepts the floor exactly and anything above it', () => { + assert.equal(meetsMemberShare(10, 100), true); + assert.equal(meetsMemberShare(11, 100), true); + assert.equal(meetsMemberShare(1, 10), true); + // yorkshire-scale: 9599 packets, floor lands at 960 + assert.equal(meetsMemberShare(960, 9599), true); + assert.equal(meetsMemberShare(7065, 9599), true); +}); + +test('meetsMemberShare rejects token relays below the floor', () => { + assert.equal(meetsMemberShare(9, 100), false); + assert.equal(meetsMemberShare(959, 9599), false); + assert.equal(meetsMemberShare(1, 1000), false); + assert.equal(meetsMemberShare(0, 100), false); +}); + +test('meetsMemberShare is defensive about degenerate inputs', () => { + assert.equal(meetsMemberShare(5, 0), false); + assert.equal(meetsMemberShare(0, 0), false); + assert.equal(meetsMemberShare(Number.NaN, 100), false); + assert.equal(meetsMemberShare(5, Number.NaN), false); + assert.equal(meetsMemberShare(5, Number.POSITIVE_INFINITY), false); +}); diff --git a/backend/src/regions/memberShare.ts b/backend/src/regions/memberShare.ts new file mode 100644 index 0000000..3d70d90 --- /dev/null +++ b/backend/src/regions/memberShare.ts @@ -0,0 +1,23 @@ +/** + * Membership floor for the region-scope participation tables. + * + * A node only counts as a member of a region when it relayed at least + * MEMBER_MIN_SHARE_PCT percent of that region's scoped packets. Nodes that + * merely pick up the odd scoped packet (a handful out of thousands) are not + * part of a region's relay pattern: including them diluted member lists and + * stretched the map hulls. Calibrated against the 2026-10 live corpus: the + * 10% floor removed ~36% of member rows and cleaned 7 of 24 hulls without + * dropping any region's shape. + */ +export const MEMBER_MIN_SHARE_PCT = 10; + +/** + * True when `memberPackets` is at least MEMBER_MIN_SHARE_PCT% of + * `regionPacketsTotal`. Integer-safe (no float division) and defensive about + * degenerate inputs. + */ +export function meetsMemberShare(memberPackets: number, regionPacketsTotal: number): boolean { + if (!Number.isFinite(memberPackets) || !Number.isFinite(regionPacketsTotal)) return false; + if (regionPacketsTotal <= 0) return false; + return memberPackets * 100 >= regionPacketsTotal * MEMBER_MIN_SHARE_PCT; +} diff --git a/backend/src/regions/regionNames.ts b/backend/src/regions/regionNames.ts new file mode 100644 index 0000000..cbe4738 --- /dev/null +++ b/backend/src/regions/regionNames.ts @@ -0,0 +1,55 @@ +// Canonical probe list for MeshCore region-scope decoding on the UK mesh. +// +// Public hashtag regions derive their transport key from the region name alone +// (firmware TransportKeyStore::getAutoKeyFor() = SHA256("#name")[0:16]), so a +// packet's transport code can be attributed by testing candidate names — there +// is no key material involved and no reverse mapping. Names that never match +// simply stay unattributed; the rebuild tool logs the unknown share so the list +// can be extended as new regions appear. +// +// The list covers every region name observed on the ukmesh corpus plus the +// wider vocabulary used by neighbouring networks (CoreScope, ScotMesh). Order +// is normalised to sorted() at module load so probing is deterministic: if two +// names ever produced the same transport code the earlier name wins. + +const OBSERVED_AND_CANDIDATE_NAMES = ` +yorkshire northwest eng-ne eng-yh eng-yorkshire gb uk ren durham eng-tyneside-admin +wales-admin northumbria-admin eng-cum eng-oxf-admin sta-admin ioi eng-cam northwest-admin +north-west eng-nw-admin nw bristol bristol-admin eng-bristol bath avon wessex west-country +westcountry south-west southwest south-coast bournemouth southampton portsmouth brighton +plymouth exeter dorset-admin cornwall-admin devon-admin somerset-admin norwich norfolk-admin +suffolk-admin ipswich east-anglia cambridge peterborough lincoln lincolnshire-admin hull +grimsby scunthorpe doncaster sheffield leeds york bradford halifax huddersfield wakefield +barnsley rotherham chesterfield manchester merseyside liverpool lancashire-admin bolton +wigan preston blackpool carlisle chester crewe warrington stockport oldham rochdale burnley +blackburn teesside middlesbrough darlington sunderland gateshead newcastle tyneside +northumberland eng-tyne eng-tees eng-ncl eng-dur eng-nwk eng-nwest eng-northwest north-east +northeast northeast-admin eng-ne-admin yorkshire-admin selkirkshire cleveland west-yorkshire +south-yorkshire north-yorkshire east-yorkshire ceredigion south-east southeast +southeast-admin east-midlands west-midlands midlands the-midlands mids nw-england +eng-north-west north-west-england norwest wirral cheshire-admin stoke staffordshire-admin +shropshire-admin derbyshire-admin nottinghamshire-admin leicestershire-admin +worcestershire-admin warwickshire-admin hertfordshire-admin bedfordshire-admin +buckinghamshire-admin oxfordshire-admin berkshire-admin hampshire-admin surrey-admin +kent-admin sussex-admin essex-admin sw-admin +sco wls iom fif hig tay edi noc cen gla fal dun per inv sti ioi-admin europe global +`; + +export const REGION_NAMES: readonly string[] = [ + ...new Set(OBSERVED_AND_CANDIDATE_NAMES.split(/\s+/).filter(Boolean)), +].sort(); + +/** + * Build the effective probe list: canonical names plus any extras from a + * comma-separated env value (same convention as the ingest-side + * MESHCORE_REGION_NAMES probe). Extra names keep their case — region keys are + * case-sensitive — and the leading '#' is tolerated for convenience. + */ +export function regionProbeNamesWithEnv(envValue: string | undefined): string[] { + const extra = (envValue ?? '') + .split(',') + .map((value) => value.trim()) + .filter(Boolean) + .map((name) => (name.startsWith('#') ? name.slice(1) : name)); + return [...new Set([...REGION_NAMES, ...extra])].sort(); +} diff --git a/backend/src/regions/scopeDecode.test.ts b/backend/src/regions/scopeDecode.test.ts new file mode 100644 index 0000000..f31b24a --- /dev/null +++ b/backend/src/regions/scopeDecode.test.ts @@ -0,0 +1,74 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { MeshCoreDecoder } from '@michaelhart/meshcore-decoder'; +import { decodePacketCompat } from '../mqtt/decodePacket.js'; +import { REGION_NAMES } from './regionNames.js'; +import { decodeRegionForPacket, transportCode0Of } from './scopeDecode.js'; + +// Real route-type 0/3 frames captured from the ukmesh corpus. Each was +// verified against the live database region_scope column and the +// @michaelhart/meshcore-decoder library during the 2026-10 region-scope audit. +const SAMPLES: Array<{ region: string; raw: string }> = [ + { + region: 'eng-ne', + raw: '04313C000081F51858D28648D4184042345B3FD371B1E3B17B1D86B1F7', + }, + { + region: 'yorkshire', + raw: '14CD3D0000854D52E4A8AB2FBBC5B1BBDE0C8C197F805A2BC47E8F027BED00573888281E957B3DEE0496717D73274BD5877B39047BE57D9FE91E09E08345F9818829DDD1750C3D3CC44A708ECB7931CE6DAA2275E9E11F386D677348E375CD419A327D6AC77EE68267BC1FB08CC4732EEDB6EED385D9764022ADC98C5397DA100AFB4A8160A6DAABB59806C96E4C0D9CF0C66948404FF8FA', + }, + { + region: 'eng-yh', + raw: '08F5FE0000818C197F65378C55A3A9D3BE9347E808CA4D012626A27E4701E12EAE0210BCBD0F4C7EC378552F9A', + }, + { + region: 'gb', + raw: '141E380000416D11118EEA9A9C8AAE7EA6AB13A622CCA86A3BFD1CD8F98FC7DF8FF5FE3344DF7B230A6A0891362A348567669EE0A7A3F9229884F51D4A6A8EA833999DA6E5D36B16CDC1FA3C52D391D8BBECE77280C26A70A3C7804FAB3F343A1342EF8CA0C2423A10498CBE460D3C3503546D303E5102037FB87A3C57E4673B576FEAD716ED94756EE7F1', + }, +]; + +// Documented MeshCore public channel material, not an application credential. +const keyStore = MeshCoreDecoder.createKeyStore({ + channelSecrets: ['8b3387e9c5cdea6ac9e5edbaa115cd72'], +}); + +test('transportCode0Of reads the first little-endian uint16 and rejects malformed input', () => { + assert.equal(transportCode0Of('CD3D0000'), 0x3dcd); + assert.equal(transportCode0Of('01FF'), 0xff01); + assert.equal(transportCode0Of(''), null); + assert.equal(transportCode0Of('CD3'), null); + assert.equal(transportCode0Of('ZZZZ'), null); +}); + +test('decodeRegionForPacket attributes captured scoped packets to their region', () => { + for (const sample of SAMPLES) { + const compat = decodePacketCompat(sample.raw, keyStore); + assert.equal(compat.metadataValid, true, `${sample.region}: frame must decode cleanly`); + assert.ok(compat.transportCodes, `${sample.region}: transport codes must be present`); + const tc0 = transportCode0Of(compat.transportCodes); + assert.notEqual(tc0, null); + const decoded = compat.decoded; + assert.ok(decoded?.payload?.raw, `${sample.region}: payload raw must be present`); + const name = decodeRegionForPacket( + REGION_NAMES, + decoded.payloadType, + decoded.payload.raw, + tc0, + ); + assert.equal(name, sample.region); + } +}); + +test('decodeRegionForPacket returns null when no candidate matches or inputs are missing', () => { + const [first] = SAMPLES; + const compat = decodePacketCompat(first.raw, keyStore); + assert.ok(compat.decoded?.payload?.raw); + const tc0 = transportCode0Of(compat.transportCodes!); + assert.equal( + decodeRegionForPacket(['definitely-not-a-region'], compat.decoded.payloadType, compat.decoded.payload.raw, tc0), + null, + ); + assert.equal(decodeRegionForPacket(REGION_NAMES, null, compat.decoded.payload.raw, tc0), null); + assert.equal(decodeRegionForPacket(REGION_NAMES, compat.decoded.payloadType, null, tc0), null); + assert.equal(decodeRegionForPacket(REGION_NAMES, compat.decoded.payloadType, compat.decoded.payload.raw, null), null); +}); diff --git a/backend/src/regions/scopeDecode.ts b/backend/src/regions/scopeDecode.ts new file mode 100644 index 0000000..dfb1488 --- /dev/null +++ b/backend/src/regions/scopeDecode.ts @@ -0,0 +1,39 @@ +import { transportCodeMatchesRegion } from '@michaelhart/meshcore-decoder'; + +/** + * First transport code (uint16, little-endian) from a 4-byte transport-codes + * hex string, as extracted from a route-type 0/3 packet. Returns null when the + * hex is too short or malformed. + */ +export function transportCode0Of(transportCodesHex: string): number | null { + const hex = transportCodesHex.trim(); + if (!/^[0-9A-Fa-f]{4,}$/.test(hex)) return null; + const low = Number.parseInt(hex.slice(0, 2), 16); + const high = Number.parseInt(hex.slice(2, 4), 16); + return low | (high << 8); +} + +/** + * Attribute a decoded scoped packet (route 0/3, transport codes present) to one + * of the candidate public region names, or null when no candidate matches. + * + * The match is `HMAC-SHA256(SHA256("#name")[0:16], payloadType || payload)` + * truncated to a little-endian uint16, compared against the packet's first + * transport code — the same derivation the firmware uses when flooding within + * a region, executed per candidate name (see @michaelhart/meshcore-decoder + * region-transport, which mirrors TransportKeyStore::getAutoKeyFor()). + */ +export function decodeRegionForPacket( + probeNames: readonly string[], + payloadType: number | null | undefined, + payloadRawHex: string | null | undefined, + transportCode0: number | null, +): string | null { + if (transportCode0 == null || payloadType == null || !payloadRawHex) return null; + for (const name of probeNames) { + if (transportCodeMatchesRegion(name, payloadType, payloadRawHex, transportCode0)) { + return name; + } + } + return null; +} diff --git a/backend/src/repositories/networkAnalysis.test.ts b/backend/src/repositories/networkAnalysis.test.ts new file mode 100644 index 0000000..25de5c8 --- /dev/null +++ b/backend/src/repositories/networkAnalysis.test.ts @@ -0,0 +1,86 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import type { QueryResultRow } from 'pg'; +import { networkFilters } from '../api/utils/networkFilters.js'; +import { rfValidationRows, queryRfValidation, rfValidationPool, RF_VALIDATION_DB_TIMEOUT_MS } from './networkAnalysis.js'; + +test('RF validation scopes endpoints without joining both identity-node projections', async () => { + const statements: string[] = []; + const controller = new AbortController(); + const query = async ( + sql: string, + _params?: unknown[], + signal?: AbortSignal, + ): Promise<{ rows: T[] }> => { + assert.equal(signal, controller.signal); + statements.push(sql); + if (sql.includes('FROM node_identity_nodes')) { + return { rows: [ + { node_id: 'A', name: 'Alpha' }, + { node_id: 'B', name: 'Bravo' }, + { node_id: 'C', name: 'Charlie' }, + ] as unknown as T[] }; + } + return { rows: [ + { + node_a_id: 'A', node_b_id: 'OUTSIDE', observed_count: '99', + multibyte_observed_count: '5', itm_path_loss_db: 100, + itm_viable: false, force_viable: false, + last_observed: '2026-08-20 10:00:00+00', classification: 'observed_unexpected', + }, + { + node_a_id: 'A', node_b_id: 'B', observed_count: '20', + multibyte_observed_count: '2', itm_path_loss_db: 110, + itm_viable: false, force_viable: false, + last_observed: '2026-08-20 09:00:00+00', classification: 'observed_unexpected', + }, + { + node_a_id: 'B', node_b_id: 'C', observed_count: '10', + multibyte_observed_count: '1', itm_path_loss_db: 120, + itm_viable: true, force_viable: false, + last_observed: '2026-08-20 08:00:00+00', classification: 'match', + }, + ] as unknown as T[] }; + }; + + const result = await rfValidationRows(query, networkFilters('ukmesh'), 1, controller.signal); + assert.equal(statements.length, 2); + assert.doesNotMatch(statements[1]!, /JOIN\s+node_identity_nodes/i); + assert.deepEqual(result.rows, [{ + node_a_id: 'A', node_b_id: 'B', observed_count: '20', + multibyte_observed_count: '2', itm_path_loss_db: 110, + itm_viable: false, force_viable: false, + last_observed: '2026-08-20 09:00:00+00', classification: 'observed_unexpected', + name_a: 'Alpha', name_b: 'Bravo', + }]); +}); + +test('empty RF scope does not run a link scan', async () => { + let calls = 0; + assert.deepEqual(await rfValidationRows(async () => { calls += 1; return { rows: [] }; }, networkFilters('test'), 100), { rows: [] }); + assert.equal(calls, 1); +}); + +test('RF pool has an independent server timeout and read-only sessions', () => { + assert.equal(rfValidationPool.options.statement_timeout, RF_VALIDATION_DB_TIMEOUT_MS); + assert.equal(RF_VALIDATION_DB_TIMEOUT_MS, 3_500); + assert.equal(rfValidationPool.options.max, 2); + assert.match(rfValidationPool.options.options!, /default_transaction_read_only=on/); +}); + +test('RF cancellation destroys only its checked-out connection; successful reads release normally', async (t) => { + const released: Array = []; + let pending = false; + t.mock.method(rfValidationPool, 'connect', async () => ({ + release: (destroy?: boolean) => released.push(destroy), + query: async () => pending ? new Promise(() => {}) : { rows: [{ ok: true }] }, + })); + assert.deepEqual(await queryRfValidation('SELECT true AS ok'), { rows: [{ ok: true }] }); + const controller = new AbortController(); + pending = true; + const result = queryRfValidation('SELECT true AS ok', [], controller.signal); + await new Promise((resolve) => setImmediate(resolve)); + controller.abort(new Error('disconnected')); + await assert.rejects(result, /disconnected/); + assert.deepEqual(released, [false, true]); +}); diff --git a/backend/src/repositories/networkAnalysis.ts b/backend/src/repositories/networkAnalysis.ts index fe21792..e393055 100644 --- a/backend/src/repositories/networkAnalysis.ts +++ b/backend/src/repositories/networkAnalysis.ts @@ -1,9 +1,11 @@ -import type { QueryResultRow } from 'pg'; +import pg, { type QueryResultRow } from 'pg'; +import { databaseConfig } from '../platform/config/database.js'; import type { NetworkFilters } from '../api/utils/networkFilters.js'; type QueryFn = ( text: string, params?: unknown[], + signal?: AbortSignal, ) => Promise<{ rows: T[] }>; export type TopologyRow = { @@ -330,57 +332,150 @@ export async function standaloneTopologyRows(query: QueryFn, filters: NetworkFil ); } +export const RF_VALIDATION_DB_TIMEOUT_MS = 3_500; + +// RF validation must not inherit the shared pool's potentially unlimited +// analytics timeout. This small, lazy pool is read-only and bounds execution +// on the server even if the requesting browser or backend disconnects. +export const rfValidationPool = new pg.Pool({ + connectionString: process.env['DATABASE_URL'], + application_name: `${databaseConfig.applicationName}-rf-validation`, + options: `-c default_transaction_read_only=on${databaseConfig.schema ? ` -c search_path=${databaseConfig.schema},public` : ''}`, + max: 2, + connectionTimeoutMillis: 500, + idleTimeoutMillis: 10_000, + allowExitOnIdle: true, + statement_timeout: RF_VALIDATION_DB_TIMEOUT_MS, + query_timeout: RF_VALIDATION_DB_TIMEOUT_MS + 250, +}); +rfValidationPool.on('error', (error) => { + console.error('[rf-validation] database connection error', error.message); +}); + +export async function queryRfValidation( + text: string, + params?: unknown[], + signal?: AbortSignal, +): Promise<{ rows: T[] }> { + signal?.throwIfAborted(); + const client = await rfValidationPool.connect(); + let released = false; + let onAbort: (() => void) | undefined; + try { + signal?.throwIfAborted(); + const aborted = new Promise((_resolve, reject) => { + if (!signal) return; + onAbort = () => { + // Destroy rather than return a connection with an in-flight query. + // The server timeout remains the independent cancellation backstop. + released = true; + client.release(true); + reject(signal.reason); + }; + signal.addEventListener('abort', onAbort, { once: true }); + }); + return await Promise.race([client.query(text, params), aborted]); + } finally { + if (onAbort) signal?.removeEventListener('abort', onAbort); + if (!released) client.release(signal?.aborted === true); + } +} + +type RfValidationRow = { + node_a_id: string; + node_b_id: string; + name_a: string | null; + name_b: string | null; + observed_count: string | number; + multibyte_observed_count: string | number; + itm_path_loss_db: number | null; + itm_viable: boolean | null; + force_viable: boolean; + last_observed: string; + classification: string; +}; + export async function rfValidationRows( query: QueryFn, filters: NetworkFilters, limit: number, -) { - const limitParam = `$${filters.params.length + 1}`; - return query<{ - node_a_id: string; - node_b_id: string; - name_a: string | null; - name_b: string | null; - observed_count: string; - multibyte_observed_count: string; - itm_path_loss_db: number | null; - itm_viable: boolean | null; - force_viable: boolean; - last_observed: string; - classification: string; - }>( - `SELECT - nl.node_a_id, nl.node_b_id, a.name AS name_a, b.name AS name_b, - nl.observed_count, nl.multibyte_observed_count, nl.itm_path_loss_db, - nl.itm_viable, nl.force_viable, nl.last_observed::text, + signal?: AbortSignal, +): Promise<{ rows: RfValidationRow[] }> { + // The identity-node view estimates ~1 eligible row where production has + // thousands. Joining it twice to identity links caused millions of nested + // loop comparisons. Resolve scope once, then pass the actual ids into the + // link query so its hash joins have accurate input cardinality. + const nodes = await query<{ node_id: string; name: string | null }>( + `SELECT n.node_id, n.name + FROM node_identity_nodes n + WHERE (n.role IS NULL OR n.role = 2) + AND (n.name IS NULL OR n.name NOT LIKE '%🚫%') + ${filters.nodesAlias('n')}`, + filters.params, + signal, + ); + signal?.throwIfAborted(); + const names = new Map(nodes.rows.map((node) => [node.node_id, node.name])); + if (names.size === 0) return { rows: [] }; + const result = await query>( + `WITH eligible_ids AS MATERIALIZED ( + SELECT unnest($1::text[]) AS node_id + ), recent_links AS MATERIALIZED ( + SELECT + COALESCE(la.canonical_node_id, UPPER(BTRIM(nl.node_a_id))) AS raw_a, + COALESCE(lb.canonical_node_id, UPPER(BTRIM(nl.node_b_id))) AS raw_b, + nl.observed_count, nl.multibyte_observed_count, nl.itm_path_loss_db, + nl.itm_viable, nl.force_viable, nl.last_observed + FROM node_links nl + LEFT JOIN node_identity_aliases la + ON la.source_node_id = UPPER(BTRIM(nl.node_a_id)) + LEFT JOIN node_identity_aliases lb + ON lb.source_node_id = UPPER(BTRIM(nl.node_b_id)) + WHERE nl.last_observed > NOW() - INTERVAL '30 days' + ), aggregated_links AS ( + SELECT LEAST(raw_a, raw_b) AS node_a_id, + GREATEST(raw_a, raw_b) AS node_b_id, + SUM(observed_count)::integer AS observed_count, + SUM(multibyte_observed_count)::integer AS multibyte_observed_count, + MIN(itm_path_loss_db) AS itm_path_loss_db, + BOOL_OR(itm_viable) AS itm_viable, + BOOL_OR(force_viable) AS force_viable, + MAX(last_observed) AS last_observed + FROM recent_links + WHERE raw_a <> raw_b + AND raw_a IN (SELECT node_id FROM eligible_ids) + AND raw_b IN (SELECT node_id FROM eligible_ids) + GROUP BY LEAST(raw_a, raw_b), GREATEST(raw_a, raw_b) + ) + SELECT node_a_id, node_b_id, + observed_count, multibyte_observed_count, itm_path_loss_db, + itm_viable, force_viable, last_observed::text, CASE - WHEN nl.force_viable THEN 'operator_override' - WHEN nl.itm_viable = false AND nl.multibyte_observed_count > 0 THEN 'observed_unexpected' - WHEN nl.itm_viable = false AND nl.observed_count >= 10 THEN 'observed_unexpected' - WHEN nl.itm_viable = true AND nl.observed_count <= 2 AND nl.last_observed < NOW() - INTERVAL '7 days' THEN 'weak_model_evidence' + WHEN force_viable THEN 'operator_override' + WHEN itm_viable = false AND multibyte_observed_count > 0 THEN 'observed_unexpected' + WHEN itm_viable = false AND observed_count >= 10 THEN 'observed_unexpected' + WHEN itm_viable = true AND observed_count <= 2 AND last_observed < NOW() - INTERVAL '7 days' THEN 'weak_model_evidence' ELSE 'match' END AS classification - FROM node_identity_links nl - JOIN node_identity_nodes a ON a.node_id = nl.node_a_id - JOIN node_identity_nodes b ON b.node_id = nl.node_b_id - WHERE nl.last_observed > NOW() - INTERVAL '30 days' - AND (a.name IS NULL OR a.name NOT LIKE '%🚫%') - AND (b.name IS NULL OR b.name NOT LIKE '%🚫%') - AND (a.role IS NULL OR a.role = 2) - AND (b.role IS NULL OR b.role = 2) - ${filters.nodesAlias('a')} - ${filters.nodesAlias('b')} + FROM aggregated_links ORDER BY CASE - WHEN nl.itm_viable = false AND nl.multibyte_observed_count > 0 THEN 0 - WHEN nl.force_viable THEN 1 - WHEN nl.itm_viable = false AND nl.observed_count >= 10 THEN 2 - WHEN nl.itm_viable = true AND nl.observed_count <= 2 THEN 3 + WHEN itm_viable = false AND multibyte_observed_count > 0 THEN 0 + WHEN force_viable THEN 1 + WHEN itm_viable = false AND observed_count >= 10 THEN 2 + WHEN itm_viable = true AND observed_count <= 2 THEN 3 ELSE 4 END, - nl.multibyte_observed_count DESC, - nl.observed_count DESC - LIMIT ${limitParam}`, - [...filters.params, limit], + multibyte_observed_count DESC, observed_count DESC, node_a_id, node_b_id + LIMIT $2`, + [[...names.keys()], Math.max(1, Math.min(250, Math.trunc(limit) || 100))], + signal, ); + signal?.throwIfAborted(); + // Enrich only the bounded result, and fail closed if an endpoint is absent + // from the public scope (including when a test/custom query supplies rows). + return { rows: result.rows + .filter((row) => names.has(row.node_a_id) && names.has(row.node_b_id)) + .slice(0, limit) + .map((row) => ({ ...row, name_a: names.get(row.node_a_id)!, name_b: names.get(row.node_b_id)! })) }; } diff --git a/backend/src/repositories/nodeLinks.integration.test.ts b/backend/src/repositories/nodeLinks.integration.test.ts new file mode 100644 index 0000000..678f9d1 --- /dev/null +++ b/backend/src/repositories/nodeLinks.integration.test.ts @@ -0,0 +1,150 @@ +import assert from 'node:assert/strict'; +import { execFile } from 'node:child_process'; +import test from 'node:test'; +import { promisify } from 'node:util'; +import { networkFilters } from '../api/utils/networkFilters.js'; +import { createNodeRepository } from './nodes.js'; + +// Opt-in PostgreSQL fixture execution: SELECT only, with all production +// relations shadowed by CTEs. No tables, functions or data are written. +const sqlTest = process.env['NODE_LINKS_SQL_TESTS'] === '1' ? test : test.skip; +const execFileAsync = promisify(execFile); +const source = 'A'.repeat(64); +const peer = 'B'.repeat(64); +const quietPeer = 'C'.repeat(64); +const sourceAlias = 'F'.repeat(64); +const peerAlias = '2'.repeat(64); +const unrelated = 'D'.repeat(64); + +function packet(overrides: Record = {}) { + return { + seconds_ago: 3600, rx_node_id: source, src_node_id: peer, + network: 'ukmesh', topic: 'ukmesh/MME/observer/packets', topic_prefix: 'ukmesh', + visibility_ok: true, is_private: false, packet_hash: 'same-hash', + hop_count: 4, snr: null, rssi: null, payload: { direction: 'rx' }, + ...overrides, + }; +} + +function literal(value: unknown): string { + if (Array.isArray(value)) return `ARRAY[${value.map(literal).join(',')}]::text[]`; + return `'${String(value).replace(/'/g, "''")}'`; +} + +async function links(packets: ReturnType[], options: { + request?: string; network?: string; stalePrivacy?: boolean; + sourceName?: string; peerName?: string; peerNetwork?: string; sighted?: boolean; +} = {}) { + const nodes = [ + { node_id: source, name: options.sourceName ?? 'Source', network: 'ukmesh' }, + { node_id: peer, name: options.peerName ?? 'Peer', network: options.peerNetwork ?? 'ukmesh' }, + { node_id: quietPeer, name: 'Quiet', network: 'ukmesh' }, + { node_id: unrelated, name: 'Test', network: 'test' }, + ]; + const aliases = [ + { source_node_id: sourceAlias, canonical_node_id: source }, + { source_node_id: peerAlias, canonical_node_id: peer }, + ]; + const identityLinks = [ + { node_a_id: source, node_b_id: peer, observed_count: 9999, + itm_viable: true, force_viable: false, itm_path_loss_db: 112.1, + count_a_to_b: 99, count_b_to_a: 9900, last_observed: '2026-09-01T12:34:56.789+01:00' }, + { node_a_id: quietPeer, node_b_id: source, observed_count: 50000, + itm_viable: false, force_viable: true, itm_path_loss_db: null, + count_a_to_b: 40000, count_b_to_a: 10000, last_observed: null }, + { node_a_id: source, node_b_id: unrelated, observed_count: 3, + itm_viable: false, force_viable: false, itm_path_loss_db: null, + count_a_to_b: 2, count_b_to_a: 1, last_observed: null }, + ]; + const calls: string[] = []; + const repo = createNodeRepository(async (sql, params) => { + calls.push(sql); + const bound = sql.replace(/\$(\d+)\b/g, (_placeholder, index: string) => literal(params![Number(index) - 1])); + const fixture = `WITH + packets AS ( + SELECT p.*, NOW() - seconds_ago * INTERVAL '1 second' AS time + FROM jsonb_to_recordset(${literal(JSON.stringify(packets))}::jsonb) AS p( + seconds_ago double precision, rx_node_id text, src_node_id text, + network text, topic text, topic_prefix text, visibility_ok boolean, + is_private boolean, packet_hash text, hop_count integer, + snr double precision, rssi double precision, payload jsonb + ) + ), node_identity_aliases AS ( + SELECT * FROM jsonb_to_recordset(${literal(JSON.stringify(aliases))}::jsonb) + AS a(source_node_id text, canonical_node_id text) + ), node_identity_nodes AS ( + SELECT * FROM jsonb_to_recordset(${literal(JSON.stringify(nodes))}::jsonb) + AS n(node_id text, name text, network text) + ), nodes AS (SELECT * FROM node_identity_nodes), + node_identity_links AS ( + SELECT * FROM jsonb_to_recordset(${literal(JSON.stringify(identityLinks))}::jsonb) + AS l(node_a_id text, node_b_id text, observed_count integer, + itm_viable boolean, force_viable boolean, itm_path_loss_db double precision, + count_a_to_b integer, count_b_to_a integer, last_observed timestamptz) + ), node_identity_sightings AS ( + SELECT ${literal(peer)}::text AS node_id, 'ukmesh'::text AS network, NOW() AS last_seen_at + WHERE ${options.sighted ? 'TRUE' : 'FALSE'} + ), packet_visibility_materialization_state AS ( + SELECT TRUE AS singleton, 1 AS visibility_generation + ), public_visibility_state AS ( + SELECT TRUE AS singleton, ${options.stalePrivacy ? 2 : 1} AS generation + ) + SELECT COALESCE(jsonb_agg(to_jsonb(result)), '[]'::jsonb) FROM (${bound}) result;`; + const child = execFileAsync('docker', [ + 'exec', '-i', 'meshcore-infra-timescaledb-1', 'psql', '-X', + '-U', 'meshcore', '-d', 'meshcore', '-v', 'ON_ERROR_STOP=1', '-At', + ], { timeout: 20_000, maxBuffer: 2 * 1024 * 1024 }); + child.child.stdin!.end(fixture); + return { rows: JSON.parse((await child).stdout) }; + }); + return { rows: await repo.listNodeLinks(options.request ?? source, networkFilters(options.network ?? 'ukmesh')), calls }; +} + +sqlTest('contacts aggregate both alias directions, relayed packets and repeated hashes; preserve ISO, orientation and quiet links', async () => { + const packets = [packet(), packet({ rx_node_id: peer, src_node_id: source }), + packet({ rx_node_id: sourceAlias, src_node_id: peerAlias }), + packet({ rx_node_id: peerAlias, src_node_id: sourceAlias }), + packet({ payload: { direction: 'tx' } })]; + const result = await links(packets); + assert.equal(result.rows.length, 2); + assert.equal(result.rows.find(r => r.peer_id === peer)!.recent_count_72h, 5); + assert.equal(result.rows.find(r => r.peer_id === peer)!.last_observed, '2026-09-01T11:34:56.789Z'); + assert.deepEqual(result.rows.find(r => r.peer_id === quietPeer), { + peer_id: quietPeer, peer_name: 'Quiet', observed_count: 50000, itm_path_loss_db: null, + count_this_to_peer: 10000, count_peer_to_this: 40000, last_observed: null, recent_count_72h: 0, + }); + assert.deepEqual((await links(packets, { request: ` ${sourceAlias.toLowerCase()} ` })).rows, result.rows); + assert.equal(result.calls.filter(sql => sql.includes('FROM packets p')).length, 1); +}); + +sqlTest('contacts enforce exact 72h boundaries, public network compatibility and privacy without requiring radio metrics', async () => { + const packets = [ + packet({ seconds_ago: 72 * 3600 - 1 }), packet({ seconds_ago: 0 }), + packet({ network: 'northeast' }), packet({ network: 'teesside' }), + packet({ seconds_ago: 72 * 3600 }), packet({ seconds_ago: -1 }), + packet({ src_node_id: null }), packet({ rx_node_id: null }), + packet({ src_node_id: unrelated }), packet({ rx_node_id: unrelated }), + packet({ network: 'test' }), packet({ network: 'other' }), + packet({ topic_prefix: 'meshcore-test' }), + packet({ topic_prefix: null, topic: 'meshcore-test/MME/observer/packets' }), + packet({ visibility_ok: false }), packet({ visibility_ok: null }), packet({ is_private: true }), + ]; + const result = await links(packets); + assert.equal(result.rows.find(r => r.peer_id === peer)!.recent_count_72h, 4); + assert.ok((await links(packets, { stalePrivacy: true })).rows.every(r => r.recent_count_72h === 0)); +}); + +sqlTest('contacts do not inherit the sparkline 1000-point cap', async () => { + const result = await links(Array.from({ length: 1005 }, () => packet())); + assert.equal(result.rows.find(r => r.peer_id === peer)!.recent_count_72h, 1005); +}); + +sqlTest('source and peer visibility, force viability and recent network sightings remain intact', async () => { + assert.equal((await links([packet()], { sourceName: '🚫 Source' })).calls.length, 1); + assert.deepEqual((await links([packet()], { sourceName: '🚫 Source' })).rows, []); + assert.deepEqual((await links([packet()], { network: 'test' })).rows, []); + assert.deepEqual((await links([packet()], { peerName: '🚫 Peer' })).rows.map(r => r.peer_id), [quietPeer]); + assert.deepEqual((await links([packet()], { peerNetwork: 'other' })).rows.map(r => r.peer_id), [quietPeer]); + assert.equal((await links([packet()], { peerNetwork: 'other', sighted: true })).rows.find(r => r.peer_id === peer)!.recent_count_72h, 1); + assert.deepEqual((await links([packet()], { peerNetwork: 'test', sighted: true })).rows.map(r => r.peer_id), [quietPeer]); +}); diff --git a/backend/src/repositories/nodes.test.ts b/backend/src/repositories/nodes.test.ts index 268a082..ba701ae 100644 --- a/backend/src/repositories/nodes.test.ts +++ b/backend/src/repositories/nodes.test.ts @@ -1,7 +1,8 @@ import assert from 'node:assert/strict'; import test from 'node:test'; import { networkFilters } from '../api/utils/networkFilters.js'; -import { createNodeRepository } from './nodes.js'; +import type { QueryResultRow } from 'pg'; +import { createNodeRepository, type NodeLinkRow } from './nodes.js'; test('public map projects path evidence as the effective seen and online state', async () => { let capturedSql = ''; @@ -34,3 +35,42 @@ test('public map projects path evidence as the effective seen and online state', 101, ]); }); + +test('node links preserve metrics and fetch one scoped aggregate for the entire peer set', async () => { + const nodeId = 'A'.repeat(64); + const { recent_count_72h, ...link } = { + peer_id: 'B'.repeat(64), peer_name: 'Peer', observed_count: 999, + itm_path_loss_db: 100, count_this_to_peer: 500, count_peer_to_this: 499, + last_observed: '2026-09-07T12:00:00.123Z', recent_count_72h: 0, + } satisfies NodeLinkRow; + const calls: Array<{ sql: string; params?: unknown[] }> = []; + const rows = [{ ...link, recent_count_72h }]; + const repository = createNodeRepository(async (sql: string, params?: unknown[]) => { + calls.push({ sql, params }); + return { rows: (calls.length === 1 ? [link] : rows) as unknown as T[] }; + }); + assert.deepEqual(await repository.listNodeLinks(nodeId, networkFilters('ukmesh')), rows); + assert.equal(calls.length, 2); + assert.match(calls[0]!.sql, /to_char\(rl.last_observed AT TIME ZONE 'UTC'/); + const aggregate = calls[1]!; + assert.deepEqual(aggregate.params, [['ukmesh', 'northeast', 'teesside'], nodeId, JSON.stringify([link])]); + assert.equal(aggregate.sql.match(/FROM packets p/g)?.length, 1); + assert.match(aggregate.sql, /COUNT\(\*\)::integer AS recent_count_72h/); + assert.match(aggregate.sql, /GROUP BY pa.peer_id/); + assert.match(aggregate.sql, /LEFT JOIN recent_counts/); + assert.match(aggregate.sql, /COALESCE\(rc.recent_count_72h, 0\)::integer/); + assert.match(aggregate.sql, /p.src_node_id IS NOT NULL/); + assert.match(aggregate.sql, /p.time > NOW\(\) - INTERVAL '72 hours'/); + assert.match(aggregate.sql, /p.time <= NOW\(\)/); + assert.match(aggregate.sql, /p.network = ANY\(\$1\)/); + assert.match(aggregate.sql, /p.visibility_ok IS TRUE/); + assert.match(aggregate.sql, /p.is_private IS NOT TRUE/); + assert.match(aggregate.sql, /cached_visibility.visibility_generation = current_visibility.generation/); +}); + +test('empty or invisible link sets never scan packets', async () => { + let calls = 0; + const repository = createNodeRepository(async () => { calls += 1; return { rows: [] }; }); + assert.deepEqual(await repository.listNodeLinks('A'.repeat(64), networkFilters('ukmesh')), []); + assert.equal(calls, 1); +}); diff --git a/backend/src/repositories/nodes.ts b/backend/src/repositories/nodes.ts index 4f33683..dfa3f55 100644 --- a/backend/src/repositories/nodes.ts +++ b/backend/src/repositories/nodes.ts @@ -1,5 +1,5 @@ import type { QueryResultRow } from 'pg'; -import type { NetworkFilters } from '../api/utils/networkFilters.js'; +import { nodeAliasArraySql, type NetworkFilters } from '../api/utils/networkFilters.js'; import { publicMapFreshPredicate } from '../nodes/publicMap.js'; import { nodeEffectiveLastSeenSql, @@ -25,6 +25,8 @@ export type NodeLinkRow = { itm_path_loss_db: number | null; count_this_to_peer: number; count_peer_to_this: number; + last_observed: string | null; + recent_count_72h: number; }; export type TestDiagnosticsData = { @@ -230,40 +232,102 @@ export function createNodeRepository(query: QueryFn): NodeRepository { async listNodeLinks(nodeId, filters) { const idParam = `$${filters.params.length + 1}`; - return (await query( - `WITH source_node AS MATERIALIZED ( + const links = (await query>( + `WITH requested_identity AS MATERIALIZED ( + SELECT COALESCE( + (SELECT canonical_node_id FROM node_identity_aliases + WHERE source_node_id = UPPER(BTRIM(${idParam}))), + UPPER(BTRIM(${idParam})) + ) AS node_id + ), link_nodes AS MATERIALIZED ( + SELECT node_id, name, network FROM node_identity_nodes + ), source_node AS MATERIALIZED ( SELECT node_id - FROM node_identity_nodes sn - WHERE node_id = meshcore_canonical_node_id(${idParam}) + FROM link_nodes sn + WHERE node_id = (SELECT node_id FROM requested_identity) AND (name IS NULL OR name NOT LIKE '%🚫%') ${filters.nodesAlias('sn')} ), relevant_links AS MATERIALIZED ( SELECT - CASE WHEN nl.node_a_id = meshcore_canonical_node_id(${idParam}) + CASE WHEN nl.node_a_id = (SELECT node_id FROM requested_identity) THEN nl.node_b_id ELSE nl.node_a_id END AS peer_id, nl.observed_count, + nl.last_observed, nl.itm_path_loss_db, - CASE WHEN nl.node_a_id = meshcore_canonical_node_id(${idParam}) + CASE WHEN nl.node_a_id = (SELECT node_id FROM requested_identity) THEN nl.count_a_to_b ELSE nl.count_b_to_a END AS count_this_to_peer, - CASE WHEN nl.node_a_id = meshcore_canonical_node_id(${idParam}) + CASE WHEN nl.node_a_id = (SELECT node_id FROM requested_identity) THEN nl.count_b_to_a ELSE nl.count_a_to_b END AS count_peer_to_this FROM node_identity_links nl - WHERE (nl.node_a_id = meshcore_canonical_node_id(${idParam}) - OR nl.node_b_id = meshcore_canonical_node_id(${idParam})) + WHERE (nl.node_a_id = (SELECT node_id FROM requested_identity) + OR nl.node_b_id = (SELECT node_id FROM requested_identity)) AND (nl.itm_viable = TRUE OR nl.force_viable = TRUE) AND EXISTS (SELECT 1 FROM source_node) ) SELECT rl.peer_id, peer.name AS peer_name, rl.observed_count, - rl.itm_path_loss_db, rl.count_this_to_peer, rl.count_peer_to_this + rl.itm_path_loss_db, rl.count_this_to_peer, rl.count_peer_to_this, + to_char(rl.last_observed AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"') AS last_observed FROM relevant_links rl - JOIN node_identity_nodes peer ON peer.node_id = rl.peer_id + JOIN link_nodes peer ON peer.node_id = rl.peer_id WHERE (peer.name IS NULL OR peer.name NOT LIKE '%🚫%') ${filters.nodesAlias('peer')} ORDER BY rl.observed_count DESC`, [...filters.params, nodeId], )).rows; + if (links.length === 0) return []; + + // Resolve the visible link set first. The materialized identity views are + // expensive; share their node projection above, then keep this one packet + // aggregate indexed and bounded independently of those view plans. + // last_observed is the worker's latest observation in either direction, + // not necessarily direct. Count all public packet rows for canonical src/rx + // pairs (including relayed packets), not distinct hashes or lifetime hits. + // The sparkline uses the same pairing, but additionally needs signal data + // and caps its points; neither restriction applies to this 72-hour count. + const linksParam = `$${filters.params.length + 2}`; + return (await query( + `WITH source_ids AS MATERIALIZED ( + SELECT ${nodeAliasArraySql(idParam)} AS ids + ), visible_links AS MATERIALIZED ( + SELECT * FROM jsonb_to_recordset(${linksParam}::jsonb) AS link( + peer_id text, peer_name text, observed_count integer, + itm_path_loss_db double precision, count_this_to_peer integer, + count_peer_to_this integer, last_observed text + ) + ), peer_aliases AS MATERIALIZED ( + SELECT vl.peer_id, UNNEST(${nodeAliasArraySql('vl.peer_id')}) AS source_node_id + FROM visible_links vl + ), peer_ids AS MATERIALIZED ( + SELECT ARRAY(SELECT source_node_id FROM peer_aliases) AS ids + ), recent_counts AS ( + SELECT pa.peer_id, COUNT(*)::integer AS recent_count_72h + FROM packets p + JOIN peer_aliases pa ON pa.source_node_id = CASE + WHEN p.rx_node_id = ANY((SELECT ids FROM source_ids)::text[]) + THEN p.src_node_id ELSE p.rx_node_id END + WHERE p.time > NOW() - INTERVAL '72 hours' + AND p.time <= NOW() + AND p.src_node_id IS NOT NULL + AND ( + (p.rx_node_id = ANY((SELECT ids FROM source_ids)::text[]) + AND p.src_node_id = ANY((SELECT ids FROM peer_ids)::text[])) + OR (p.src_node_id = ANY((SELECT ids FROM source_ids)::text[]) + AND p.rx_node_id = ANY((SELECT ids FROM peer_ids)::text[])) + ) + ${filters.packetsAlias('p')} + GROUP BY pa.peer_id + ) + SELECT vl.peer_id, vl.peer_name, vl.observed_count, + vl.itm_path_loss_db, vl.count_this_to_peer, vl.count_peer_to_this, + vl.last_observed, + COALESCE(rc.recent_count_72h, 0)::integer AS recent_count_72h + FROM visible_links vl + LEFT JOIN recent_counts rc ON rc.peer_id = vl.peer_id + ORDER BY vl.observed_count DESC`, + [...filters.params, nodeId, JSON.stringify(links)], + )).rows; }, }; } diff --git a/backend/src/repositories/productFeatures.integration.test.ts b/backend/src/repositories/productFeatures.integration.test.ts new file mode 100644 index 0000000..fadbba7 --- /dev/null +++ b/backend/src/repositories/productFeatures.integration.test.ts @@ -0,0 +1,170 @@ +import assert from 'node:assert/strict'; +import { execFile } from 'node:child_process'; +import test from 'node:test'; +import { promisify } from 'node:util'; +import { linkHistoryRows, type QueryFn } from './productFeatures.js'; + +// Opt-in, read-only PostgreSQL checks. Every relation used by the production +// query is shadowed by a VALUES/JSON fixture CTE; no tables or rows are written. +// LINK_HISTORY_SQL_TESTS=1 node --import tsx --test src/repositories/productFeatures.integration.test.ts +const sqlTest = process.env['LINK_HISTORY_SQL_TESTS'] === '1' ? test : test.skip; +const execFileAsync = promisify(execFile); +const nodeA = 'A'.repeat(64); +const nodeB = 'B'.repeat(64); +const aliasA = 'F'.repeat(64); +const aliasB = '2'.repeat(64); +const unrelated = 'C'.repeat(64); +const baseTime = Date.now() - 2 * 60 * 60_000; + +type Packet = { + time: string; + observation_id: string; + rx_node_id: string | null; + src_node_id: string | null; + network: string; + snr: number | null; + rssi: number | null; + payload: { direction?: string } | null; + visibility_ok: boolean | null; + is_private: boolean | null; + hop_count: number; +}; + +function packet(index: number, overrides: Partial = {}): Packet { + return { + time: new Date(baseTime + index * 1_000).toISOString(), + observation_id: `00000000-0000-0000-0000-${String(index).padStart(12, '0')}`, + rx_node_id: nodeA, + src_node_id: nodeB, + network: 'ukmesh', + snr: -index, + rssi: -99, + payload: { direction: 'rx' }, + visibility_ok: true, + is_private: false, + hop_count: 0, + ...overrides, + }; +} + +function literal(value: unknown): string { + if (Array.isArray(value)) return `ARRAY[${value.map(literal).join(',')}]::text[]`; + return `'${String(value).replace(/'/g, "''")}'`; +} + +async function history( + packets: Packet[], + options: { nodeA?: string; nodeB?: string; hours?: number; networks?: string[]; noPathLoss?: boolean; stalePrivacy?: boolean } = {}, +) { + const aliases = [ + { source_node_id: aliasA, canonical_node_id: nodeA }, + { source_node_id: aliasB, canonical_node_id: nodeB }, + ]; + const links = options.noPathLoss ? [] : [ + { node_a_id: nodeA, node_b_id: nodeB, itm_path_loss_db: 140 }, + { node_a_id: nodeB, node_b_id: aliasA, itm_path_loss_db: 110 }, + { node_a_id: aliasB, node_b_id: nodeA, itm_path_loss_db: 120 }, + { node_a_id: nodeA, node_b_id: unrelated, itm_path_loss_db: 10 }, + ]; + const query: QueryFn = async (sql, params) => { + const bound = sql.replace(/\$(\d+)\b/g, (_placeholder, index: string) => literal(params![Number(index) - 1])); + const fixtureSql = `WITH + packets AS ( + SELECT * FROM jsonb_to_recordset(${literal(JSON.stringify(packets))}::jsonb) AS p( + time timestamptz, observation_id uuid, rx_node_id text, src_node_id text, + network text, snr double precision, rssi double precision, payload jsonb, + visibility_ok boolean, is_private boolean, hop_count integer + ) + ), node_identity_aliases AS ( + SELECT * FROM jsonb_to_recordset(${literal(JSON.stringify(aliases))}::jsonb) + AS a(source_node_id text, canonical_node_id text) + ), node_links AS ( + SELECT * FROM jsonb_to_recordset(${literal(JSON.stringify(links))}::jsonb) + AS l(node_a_id text, node_b_id text, itm_path_loss_db double precision) + ), packet_visibility_materialization_state AS ( + SELECT TRUE AS singleton, 1 AS visibility_generation + ), public_visibility_state AS ( + SELECT TRUE AS singleton, ${options.stalePrivacy ? 2 : 1} AS generation + ) + SELECT COALESCE(jsonb_agg(to_jsonb(points)), '[]'::jsonb) + FROM (${bound}) points;`; + // Use stdin to keep large fixtures within the OS argument-size limit. + const child = execFileAsync('docker', [ + 'exec', '-i', 'meshcore-infra-timescaledb-1', + 'psql', '-X', '-U', 'meshcore', '-d', 'meshcore', '-v', 'ON_ERROR_STOP=1', '-At', + ], { maxBuffer: 2 * 1024 * 1024, timeout: 20_000 }); + child.child.stdin!.end(fixtureSql); + const { stdout } = await child; + return { rows: JSON.parse(stdout) }; + }; + return (await linkHistoryRows( + query, options.nodeA ?? nodeA, options.nodeB ?? nodeB, options.hours ?? 72, + options.networks ?? ['ukmesh', 'northeast', 'teesside'], + )).rows; +} + +sqlTest('packet history expands both aliases, includes relayed receptions and preserves ISO radio points', async () => { + const packets = [ + packet(1), + packet(2, { rx_node_id: nodeB, src_node_id: nodeA }), + packet(3, { rx_node_id: aliasA, src_node_id: aliasB, hop_count: 4 }), + packet(4, { rx_node_id: aliasB, src_node_id: aliasA, hop_count: 2 }), + ]; + const expected = packets.map(({ time, snr, rssi }) => ({ time, snr, rssi, path_loss: 110, sample_count: 1 })); + assert.deepEqual(await history(packets), expected); + assert.deepEqual(await history(packets, { nodeA: aliasA.toLowerCase(), nodeB: ` ${aliasB} ` }), expected); + assert.deepEqual(await history(packets, { nodeA: nodeB, nodeB: nodeA }), expected); +}); + +sqlTest('packet history excludes null sources, unrelated peers, TX, absent metrics and hidden receptions', async () => { + const good = packet(1, { payload: null, snr: 0, rssi: null }); + const rssiOnly = packet(2, { snr: null }); + const rows = await history([ + good, rssiOnly, + packet(3, { src_node_id: null }), + packet(4, { src_node_id: unrelated }), + packet(5, { rx_node_id: unrelated }), + packet(6, { rx_node_id: null }), + packet(7, { payload: { direction: 'tx' } }), + packet(8, { snr: null, rssi: null }), + packet(9, { visibility_ok: false }), + packet(10, { visibility_ok: null }), + packet(11, { is_private: true }), + ]); + assert.deepEqual(rows.map(({ time, snr, rssi }) => ({ time, snr, rssi })), + [good, rssiOnly].map(({ time, snr, rssi }) => ({ time, snr, rssi }))); + assert.deepEqual(await history([good], { stalePrivacy: true }), []); +}); + +sqlTest('packet history enforces network isolation and the requested time window', async () => { + const packets = [ + packet(1), + packet(2, { network: 'northeast' }), + packet(3, { network: 'teesside' }), + packet(4, { network: 'test' }), + packet(5, { time: new Date(Date.now() - 73 * 60 * 60_000).toISOString() }), + packet(6, { time: new Date(Date.now() + 60 * 60_000).toISOString() }), + ]; + assert.deepEqual((await history(packets)).map(({ snr }) => snr), [-1, -2, -3]); + assert.deepEqual((await history(packets, { networks: ['test'] })).map(({ snr }) => snr), [-4]); + assert.deepEqual(await history(packets, { hours: 1 }), []); + assert.deepEqual((await history(packets, { hours: 168 })).map(({ snr }) => snr), [-5, -1, -2, -3]); +}); + +sqlTest('packet history returns the latest 1000 observations in chronological order', async () => { + const packets = Array.from({ length: 1005 }, (_, index) => packet(index)); + const rows = await history(packets.reverse()); + assert.equal(rows.length, 1000); + assert.equal(rows[0].snr, -5); + assert.equal(rows.at(-1)!.snr, -1004); + assert.deepEqual(rows.map(({ time }) => time), [...rows.map(({ time }) => time)].sort()); + assert.ok(rows.every((row) => row.sample_count === 1)); +}); + +sqlTest('packet history keeps radio samples without a model link and returns empty for no traffic', async () => { + assert.deepEqual(await history([packet(1)], { noPathLoss: true }), [{ + time: packet(1).time, snr: -1, rssi: -99, path_loss: null, sample_count: 1, + }]); + assert.deepEqual(await history([]), []); + assert.deepEqual(await history([packet(1)], { nodeB: unrelated }), []); +}); diff --git a/backend/src/repositories/productFeatures.test.ts b/backend/src/repositories/productFeatures.test.ts index 9660222..570f456 100644 --- a/backend/src/repositories/productFeatures.test.ts +++ b/backend/src/repositories/productFeatures.test.ts @@ -1,6 +1,6 @@ import assert from 'node:assert/strict'; import test from 'node:test'; -import { visibleLinkNodeIds, type QueryFn } from './productFeatures.js'; +import { linkHistoryRows, visibleLinkNodeIds, type QueryFn } from './productFeatures.js'; test('public link history authorizes both endpoints through network and privacy scope', async () => { let capturedSql = ''; @@ -24,3 +24,23 @@ test('public link history authorizes both endpoints through network and privacy assert.match(capturedSql, /sighting\.last_seen_at > NOW\(\) - INTERVAL '30 days'/); assert.doesNotMatch(capturedSql, /SELECT .* FROM node_link_radio_reports/is); }); + +test('link history binds both identities, the time window and packet network scope', async () => { + const calls: Array<{ sql: string; params: unknown[] | undefined }> = []; + const query: QueryFn = async (sql, params) => { + calls.push({ sql, params }); + return { rows: [] }; + }; + const nodeA = 'A'.repeat(64); + const nodeB = 'B'.repeat(64); + + assert.deepEqual(await linkHistoryRows(query, nodeA, nodeB, 72, ['test']), { rows: [] }); + assert.equal(calls.length, 1); + assert.deepEqual(calls[0].params, [nodeA, nodeB, '72', ['test']]); + // Regressions here would either revive the orphaned source, hide aliases, + // or turn indexed packet lookups into per-row identity function calls. + assert.match(calls[0].sql, /FROM packets p/); + assert.match(calls[0].sql, /FROM node_identity_aliases alias/); + assert.doesNotMatch(calls[0].sql, /node_identity_link_radio_reports/); + assert.doesNotMatch(calls[0].sql, /meshcore_canonical_node_id\(p\./); +}); diff --git a/backend/src/repositories/productFeatures.ts b/backend/src/repositories/productFeatures.ts index 6f28762..1d3ec77 100644 --- a/backend/src/repositories/productFeatures.ts +++ b/backend/src/repositories/productFeatures.ts @@ -1,4 +1,5 @@ import type { QueryResultRow } from 'pg'; +import { nodeAliasArraySql, publicPacketPrivacySql } from '../api/utils/networkFilters.js'; export type QueryFn = ( text: string, @@ -102,7 +103,11 @@ export async function linkHistoryRows( nodeA: string, nodeB: string, hours: number, + networks: string[], ) { + // Resolve identities before scanning packets so both endpoint predicates can + // use the raw-key indexes. These are source/receiver receptions, including + // relayed packets; their SNR is not proof of a direct RF link between peers. return query<{ time: string; snr: number | null; @@ -110,17 +115,40 @@ export async function linkHistoryRows( path_loss: number | null; sample_count: number; }>( - `SELECT reports.last_seen::text AS time, reports.last_snr_db AS snr, - NULL::double precision AS rssi, links.itm_path_loss_db AS path_loss, - reports.sample_count - FROM node_identity_link_radio_reports reports - LEFT JOIN node_identity_links links - ON links.node_a_id = reports.node_a_id AND links.node_b_id = reports.node_b_id - WHERE reports.node_a_id = meshcore_canonical_node_id($1) - AND reports.node_b_id = meshcore_canonical_node_id($2) - AND reports.last_seen > NOW() - ($3::text || ' hours')::interval - ORDER BY reports.last_seen ASC`, - [nodeA, nodeB, String(hours)], + `WITH ids AS MATERIALIZED ( + SELECT ${nodeAliasArraySql('$1')} AS a_ids, + ${nodeAliasArraySql('$2')} AS b_ids + ), recent AS ( + SELECT p.time, p.observation_id, p.snr, p.rssi + FROM packets p + WHERE p.time > NOW() - ($3::text || ' hours')::interval + AND p.time <= NOW() + AND p.network = ANY($4::text[]) + AND p.src_node_id IS NOT NULL + AND ( + -- Scalar initplans keep the alias arrays in the packet index predicates. + (p.rx_node_id = ANY((SELECT a_ids FROM ids)::text[]) + AND p.src_node_id = ANY((SELECT b_ids FROM ids)::text[])) + OR (p.rx_node_id = ANY((SELECT b_ids FROM ids)::text[]) + AND p.src_node_id = ANY((SELECT a_ids FROM ids)::text[])) + ) + AND p.payload->>'direction' IS DISTINCT FROM 'tx' + AND (p.snr IS NOT NULL OR p.rssi IS NOT NULL) + AND ${publicPacketPrivacySql('p')} + ORDER BY p.time DESC, p.observation_id DESC + LIMIT 1000 + ), path_loss AS MATERIALIZED ( + -- Match the identity view's MIN without materializing every network link. + SELECT MIN(links.itm_path_loss_db) AS value + FROM node_links links CROSS JOIN ids + WHERE (links.node_a_id = ANY(ids.a_ids) AND links.node_b_id = ANY(ids.b_ids)) + OR (links.node_a_id = ANY(ids.b_ids) AND links.node_b_id = ANY(ids.a_ids)) + ) + SELECT to_char(recent.time AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"') AS time, + recent.snr, recent.rssi, path_loss.value AS path_loss, 1::integer AS sample_count + FROM recent CROSS JOIN path_loss + ORDER BY recent.time ASC, recent.observation_id ASC`, + [nodeA, nodeB, String(hours), networks], ); } diff --git a/backend/src/spam/cluster.test.ts b/backend/src/spam/cluster.test.ts index d012268..7ee35d3 100644 --- a/backend/src/spam/cluster.test.ts +++ b/backend/src/spam/cluster.test.ts @@ -268,3 +268,34 @@ test('canonical URL index prevents interleaved decoys evicting variant spam', () assert.ok(target); assert.equal(target.messageCount, 8); }); + +test('a rotating-ID botnet flood (many single-shot machine-named senders) is caught', () => { + // Real-world pattern (Oct 2026): the anti-Kirby spammer rotated through + // fresh keygen-style node identities, each posting the same slogan once. + // These are not "many independent people": every identity appears exactly + // once in the window and the names are machine-generated — so the cluster + // must qualify as an incident, not be dismissed as broad chatter. + const text = 'still clinging to the trademarks like a desperate leech'; + const ids = [ + '0CYT64KV', '1KG3RWU7', '16V0OP2W', '2H6WCDJW', '5ZA1J31M', + '6KE2E6P6', '7F32S5OK', 'C23R8J39', 'DP8INYG2', 'G6B8QHA3', + ]; + const records = ids.map((id, i) => rec(text, id, i)); + const incidents = clusterMessages(records, cfg()); + assert.equal(incidents.length, 1, 'the rotating-ID flood should form one incident'); + assert.equal(incidents[0]!.messageCount, ids.length); + assert.ok( + incidents[0]!.score >= DEFAULT_SPAM_MESSAGE_CONFIG.publicMinScore, + `expected score above the public floor, got ${incidents[0]!.score}`, + ); + assert.ok(incidents[0]!.reasons.some((r) => /single-shot/i.test(r))); +}); + +test('single-shot human chatter is still treated as broad chatter', () => { + // Guard for the rotating-identity exemption: many one-shot senders with + // human names (a meme / coordinated greeting) must stay chatter. + const text = 'happy birthday to the repeater that never sleeps'; + const names = ['Alice', 'Bob', 'Carol', 'Dave', 'Erin', 'Frank', 'Grace', 'Heidi']; + const records = names.map((n, i) => rec(text, n, i)); + assert.equal(clusterMessages(records, cfg()).length, 0); +}); diff --git a/backend/src/spam/cluster.ts b/backend/src/spam/cluster.ts index cacc02b..4f9bf74 100644 --- a/backend/src/spam/cluster.ts +++ b/backend/src/spam/cluster.ts @@ -2,7 +2,7 @@ import { createHash } from 'node:crypto'; import type { Incident, IncidentStatus, MessageRecord } from './types.js'; import type { SpamMessageConfig } from './config.js'; import { messageSimilarity, usernameSimilarity } from './similarity.js'; -import { normalizeUsername } from './normalize.js'; +import { normalizeUsername, looksMachineGeneratedName } from './normalize.js'; // Trivial / connectivity tokens that are normal mesh chatter, never spam on // their own. A cluster made up solely of these (and short) is not an incident. @@ -108,6 +108,27 @@ function maxBurstDensity(sorted: MessageRecord[], windowMs: number): number { return best; } +/** Max number of distinct non-empty senders inside any single sliding burst window. */ +function maxBurstSenders(sorted: MessageRecord[], windowMs: number): number { + const counts = new Map(); + let best = 0; + let start = 0; + for (let end = 0; end < sorted.length; end++) { + const key = normalizeUsername(sorted[end]!.sender) || '∅'; + counts.set(key, (counts.get(key) ?? 0) + 1); + while (sorted[end]!.observedAt - sorted[start]!.observedAt > windowMs) { + const k = normalizeUsername(sorted[start]!.sender) || '∅'; + const remaining = (counts.get(k) ?? 1) - 1; + if (remaining <= 0) counts.delete(k); + else counts.set(k, remaining); + start += 1; + } + const distinctInWindow = counts.size - (counts.has('∅') ? 1 : 0); + best = Math.max(best, distinctInWindow); + } + return best; +} + /** Summary of who is sending the messages in a cluster. */ interface SenderProfile { distinctSenders: number; @@ -115,13 +136,28 @@ interface SenderProfile { dominantShare: number; /** >=2 distinct senders that are fuzzy-similar variants of each other. */ rotatingSimilarNames: boolean; + /** Share of distinct senders appearing exactly once in the analysis window. */ + singleShotShare: number; + /** Share of distinct senders whose name looks machine-generated (keygen-style). */ + machineNameShare: number; + /** Most distinct senders seen inside any single burst window. */ + burstSenderCount: number; + /** Many single-shot, machine-named senders inside one burst = rotating-ID + * botnet, not broad human participation. */ + rotatingIdentities: boolean; } -function profileSenders(members: MessageRecord[], cfg: SpamMessageConfig): SenderProfile { +function profileSenders( + members: MessageRecord[], + cfg: SpamMessageConfig, + corpusSenderCounts: ReadonlyMap, +): SenderProfile { const counts = new Map(); + const rawByKey = new Map(); for (const m of members) { const key = normalizeUsername(m.sender) || '∅'; counts.set(key, (counts.get(key) ?? 0) + 1); + if (!rawByKey.has(key)) rawByKey.set(key, m.sender); } const distinct = [...counts.keys()].filter((k) => k !== '∅'); const distinctSenders = distinct.length || (counts.has('∅') ? 1 : 0); @@ -138,7 +174,38 @@ function profileSenders(members: MessageRecord[], cfg: SpamMessageConfig): Sende } } } - return { distinctSenders, dominantShare, rotatingSimilarNames }; + + // Rotating-identity (botnet) profile: how many of these "distinct senders" + // are actually single-shot, machine-named identities rather than people? + // A one-shot identity exists only for this flood, and a keygen-style name + // reinforces that the identity was generated, not typed by a human. + let singleShot = 0; + let machineNamed = 0; + for (const key of distinct) { + const corpusCount = corpusSenderCounts.get(key) ?? counts.get(key) ?? 1; + if (corpusCount <= 1) singleShot += 1; + if (looksMachineGeneratedName(rawByKey.get(key) ?? '')) machineNamed += 1; + } + const distinctCount = distinct.length; + const singleShotShare = distinctCount > 0 ? singleShot / distinctCount : 0; + const machineNameShare = distinctCount > 0 ? machineNamed / distinctCount : 0; + const burstSenderCount = maxBurstSenders(members, cfg.burstWindowMs); + + const rotatingIdentities = + distinctCount >= cfg.rotatingMinSenders && + singleShotShare >= cfg.rotatingSingleShotShare && + machineNameShare >= cfg.rotatingMachineNameShare && + burstSenderCount >= cfg.rotatingMinBurstSenders; + + return { + distinctSenders, + dominantShare, + rotatingSimilarNames, + singleShotShare, + machineNameShare, + burstSenderCount, + rotatingIdentities, + }; } /** Whether the canonical text is only trivial/connectivity tokens. */ @@ -194,17 +261,21 @@ function qualifies( if (isBenignChatter(canonical) && !hasUrl && !hasMarker) return false; // Many *dissimilar* people sending the same thing = legitimate (a meme, a - // greeting, a coordinated test) — not one actor abusing the network. + // greeting, a coordinated test) — not one actor abusing the network. The + // exception: "many senders" made of single-shot machine-named identities is + // a rotating-ID botnet, not people (each identity exists only for the flood). const broadChatter = sender.distinctSenders > cfg.maxIndependentSenders && !sender.rotatingSimilarNames && + !sender.rotatingIdentities && sender.dominantShare < cfg.dominantSenderShare; if (broadChatter) return false; const senderSuspicious = sender.distinctSenders <= 1 || sender.dominantShare >= cfg.dominantSenderShare || - sender.rotatingSimilarNames; + sender.rotatingSimilarNames || + sender.rotatingIdentities; // A few dissimilar senders, no link, no marker -> not enough to call spam. if (!senderSuspicious && !hasUrl && !hasMarker) return false; @@ -234,7 +305,8 @@ function scoreIncident( if (hasMarker) score += 0.25; if (urlRepeats) score += 0.2; else if (hasUrl) score += 0.1; - if (sender.rotatingSimilarNames) score += 0.25; + if (sender.rotatingIdentities) score += 0.3; + else if (sender.rotatingSimilarNames) score += 0.25; else if (sender.distinctSenders <= 1 && sender.dominantShare >= 0.8) score += 0.1; // Connectivity/test floods with no link, marker or name-rotation are noise, @@ -242,7 +314,8 @@ function scoreIncident( // Only content that is *dominated* by benign tokens counts (so a templated // flood that merely contains the word "test" is not let off the hook). const looksLikeTesting = - isBenignDominated(canonicalText) && !hasUrl && !hasMarker && !sender.rotatingSimilarNames; + isBenignDominated(canonicalText) && !hasUrl && !hasMarker && + !sender.rotatingSimilarNames && !sender.rotatingIdentities; // A sustained, high-volume repeat of the same templated message is itself a // strong automated-abuse signal, regardless of what it says — but we never @@ -258,7 +331,13 @@ function scoreIncident( reasons.push(`${count} near-duplicate messages`); reasons.push(`up to ${burst} within ${Math.round(cfg.burstWindowMs / 60000)} min`); if (isFlood) reasons.push('sustained high-volume flood'); - if (sender.rotatingSimilarNames) reasons.push(`${sender.distinctSenders} similar (rotating) sender names`); + if (sender.rotatingIdentities) { + reasons.push( + `${sender.distinctSenders} single-shot identities ` + + `(${Math.round(sender.machineNameShare * 100)}% machine-style names, ` + + `${sender.burstSenderCount} within ${Math.round(cfg.burstWindowMs / 60000)} min) posted near-identical text`, + ); + } else if (sender.rotatingSimilarNames) reasons.push(`${sender.distinctSenders} similar (rotating) sender names`); else if (sender.distinctSenders <= 1) reasons.push('single repeating sender'); if (urlRepeats) reasons.push('same link repeated across messages'); else if (hasUrl) reasons.push('contains a link'); @@ -268,7 +347,11 @@ function scoreIncident( return { score, reasons }; } -function finalizeIncident(members: MessageRecord[], cfg: SpamMessageConfig): Incident | null { +function finalizeIncident( + members: MessageRecord[], + cfg: SpamMessageConfig, + corpusSenderCounts: ReadonlyMap, +): Incident | null { const sorted = [...members].sort((a, b) => a.observedAt - b.observedAt); const rep = modalNormalized(sorted); const network = sorted[0]!.network; @@ -287,7 +370,7 @@ function finalizeIncident(members: MessageRecord[], cfg: SpamMessageConfig): Inc } const canonical = rep.norm.normalized; - const sender = profileSenders(sorted, cfg); + const sender = profileSenders(sorted, cfg, corpusSenderCounts); if (!qualifies(canonical, hasMarker, hasUrl, sender, cfg)) return null; const { score, reasons } = scoreIncident(sorted, hasMarker, hasUrl, sender, cfg); @@ -330,6 +413,13 @@ export function clusterMessages(records: MessageRecord[], cfg: SpamMessageConfig const sorted = [...eligible] .slice(0, cfg.maxMessagesPerRun) .sort((a, b) => a.observedAt - b.observedAt); + // Corpus-wide sender frequency within this analysis batch: rotating-ID + // detection needs to know whether each sender is a one-shot identity. + const corpusSenderCounts = new Map(); + for (const record of sorted) { + const key = normalizeUsername(record.sender) || '∅'; + corpusSenderCounts.set(key, (corpusSenderCounts.get(key) ?? 0) + 1); + } const open: OpenCluster[] = []; const closed: OpenCluster[] = []; const exactIndex = new Map(); @@ -445,7 +535,7 @@ export function clusterMessages(records: MessageRecord[], cfg: SpamMessageConfig if (cl.members.length < cfg.minTransmissions) continue; const sortedMembers = [...cl.members].sort((a, b) => a.observedAt - b.observedAt); if (maxBurstDensity(sortedMembers, cfg.burstWindowMs) < cfg.minBurst) continue; - const incident = finalizeIncident(cl.members, cfg); + const incident = finalizeIncident(cl.members, cfg, corpusSenderCounts); if (incident) incidents.push(incident); } diff --git a/backend/src/spam/config.ts b/backend/src/spam/config.ts index 23aa656..edfe0c1 100644 --- a/backend/src/spam/config.ts +++ b/backend/src/spam/config.ts @@ -62,6 +62,18 @@ export interface SpamMessageConfig { /** At/above this message count a qualified cluster is treated as a sustained * flood and scored up — an automated, high-volume abuse signal on its own. */ floodMinMessages: number; + /** Rotating-identity (botnet) detection: a cluster whose senders are many + * single-shot, machine-named identities posting within one burst is an + * automated flood, not broad human participation. Minimum distinct senders + * before the rotating-identity path can apply. */ + rotatingMinSenders: number; + /** ...share of those senders that must appear exactly once in the analysis + * window (a one-shot identity, not an established member). */ + rotatingSingleShotShare: number; + /** ...share of sender names that must look machine-generated (keygen-style). */ + rotatingMachineNameShare: number; + /** ...distinct senders required inside a single burst window. */ + rotatingMinBurstSenders: number; /** Channel labels excluded from spam detection (the test channel by design * carries repeated test traffic that is not abuse). */ excludeChannels: string[]; @@ -132,6 +144,10 @@ export function loadSpamMessageConfig(): SpamMessageConfig { maxIndependentSenders: envNum('SPAM_MESSAGE_MAX_INDEPENDENT_SENDERS', 4), dominantSenderShare: envNum('SPAM_MESSAGE_DOMINANT_SENDER_SHARE', 0.6), floodMinMessages: envNum('SPAM_MESSAGE_FLOOD_MIN_MESSAGES', 25), + rotatingMinSenders: envNum('SPAM_MESSAGE_ROTATING_MIN_SENDERS', 5), + rotatingSingleShotShare: envNum('SPAM_MESSAGE_ROTATING_SINGLE_SHOT_SHARE', 0.8), + rotatingMachineNameShare: envNum('SPAM_MESSAGE_ROTATING_MACHINE_NAME_SHARE', 0.5), + rotatingMinBurstSenders: envNum('SPAM_MESSAGE_ROTATING_MIN_BURST_SENDERS', 3), excludeChannels: (process.env['SPAM_MESSAGE_EXCLUDE_CHANNELS'] ?? 'test') .split(',') .map((s) => s.trim().toLowerCase()) diff --git a/backend/src/spam/normalize.test.ts b/backend/src/spam/normalize.test.ts index 01fab62..b0c6339 100644 --- a/backend/src/spam/normalize.test.ts +++ b/backend/src/spam/normalize.test.ts @@ -1,6 +1,6 @@ import { test } from 'node:test'; import assert from 'node:assert/strict'; -import { normalizeMessage, normalizeUsername, canonicalizeUrl, collapseRepeats, SPAM_MARKER } from './normalize.js'; +import { normalizeMessage, normalizeUsername, canonicalizeUrl, collapseRepeats, looksMachineGeneratedName, SPAM_MARKER } from './normalize.js'; test('lowercases, trims and collapses whitespace', () => { const n = normalizeMessage(' Hello WORLD '); @@ -57,3 +57,20 @@ test('handles null / empty input safely', () => { assert.deepEqual(n.tokens, []); assert.equal(normalizeUsername(undefined), ''); }); + +test('machine-generated names are detected (keygen-style botnet ids)', () => { + // Observed rotating-ID spammer style: separator-free base32-ish tokens. + for (const id of ['0CYT64KV', '1KG3RWU7', '43DBCEEF5C', '5ZA1J31M', '16V0OP2W', 'H5M6N2P8', 'BKGL7DMF', '8SPLFZAP']) { + assert.equal(looksMachineGeneratedName(id), true, `${id} should look machine-generated`); + } +}); + +test('human and amateur-radio handles are not machine-generated', () => { + for (const name of [ + 'M7GQW', 'MU6GBG', 'G0SYR', '2E0ABC', 'GW4ABC', 'GB3WF', + 'Rob M7JCN V3', 'Dave-S75', 'Grog 🚲', 'Simeon', 'Timberland', + 'Delta4zer0', 'eyrie', 'Alice', '12345678', 'test', + ]) { + assert.equal(looksMachineGeneratedName(name), false, `${name} should not look machine-generated`); + } +}); diff --git a/backend/src/spam/normalize.ts b/backend/src/spam/normalize.ts index 160108a..1bdc3bd 100644 --- a/backend/src/spam/normalize.ts +++ b/backend/src/spam/normalize.ts @@ -111,3 +111,53 @@ export function normalizeUsername(input: string | null | undefined): string { name = collapseRepeats(name); return name.replace(/\s+/g, ' ').trim(); } + +/** + * Heuristic: does a display name look machine-generated (a keygen-style random + * token) rather than a human handle? One signal among several for + * rotating-identity botnet detection — never sufficient on its own. + * + * Observed botnet style: separator-free base32-ish tokens such as + * "0CYT64KV", "1KG3RWU7", "43DBCEEF5C", "5ZA1J31M", "16V0OP2W". + * Human / amateur-radio handles ("M7GQW", "MU6GBG", "2E0ABC", "Rob M7JCN V3", + * "Dave-S75", "Grog 🚲") must NOT match: composite names carry separators and + * callsigns keep a single digit next to letters. + */ +export function looksMachineGeneratedName(input: string | null | undefined): boolean { + const raw = (input ?? '').normalize('NFKC').trim(); + if (raw.length === 0) return false; + + // Strip pictographic decoration, then require a single alphanumeric token: + // any remaining separator/punctuation marks the name as composite (human). + const stripped = raw + .replace(/[\p{Extended_Pictographic}\uFE0F\u200D]/gu, '') + .trim(); + if (stripped.length === 0) return false; + if (/[^A-Za-z0-9]/.test(stripped)) return false; + + const compact = stripped; + if (compact.length < 6 || compact.length > 14) return false; + + const upper = compact.toUpperCase(); + const letterChars = compact.replace(/[^A-Za-z]/g, ''); + const letters = upper.replace(/[^A-Z]/g, ''); + const digits = upper.replace(/[^0-9]/g, ''); + // At least one digit and mostly-uppercase token (keygen style); human-ish + // mixed case is rejected. One digit is enough — the botnet also uses + // single-digit ids like "BKGL7DMF"; callsign shapes are excluded below. + if (letters.length < 3 || digits.length === 0) return false; + + // Mostly-uppercase token (keygen style); human-ish mixed case is rejected. + const upperCount = (letterChars.match(/[A-Z]/g) ?? []).length; + if (upperCount / letterChars.length < 0.7) return false; + + // Amateur-callsign shapes (M7GQW, G0SYR, MU6GBG, 2E0ABC, GW4ABC) are human. + if (/^[A-Z]{1,3}[0-9][A-Z]{1,4}$/.test(upper)) return false; + if (/^[0-9][A-Z][0-9][A-Z]{1,3}$/.test(upper)) return false; + + // Keygen strings are vowel-poor. + const vowels = (letters.match(/[AEIOUY]/g) ?? []).length; + if (vowels / letters.length > 0.34) return false; + + return true; +} diff --git a/backend/src/stats/statsRepository.test.ts b/backend/src/stats/statsRepository.test.ts index 428f339..2434d60 100644 --- a/backend/src/stats/statsRepository.test.ts +++ b/backend/src/stats/statsRepository.test.ts @@ -2,25 +2,10 @@ import assert from 'node:assert/strict'; import { test } from 'node:test'; import type { QueryResultRow } from 'pg'; import { - bindChartSnapshot, compareAggregateShadowRows, createStatsRepository, } from './statsRepository.js'; -test('pins repeated chart NOW expressions to one timestamp and parameter', () => { - const asOf = new Date('2026-09-23T10:15:00.000Z'); - const snapshot = bindChartSnapshot( - "SELECT * FROM packets WHERE time > NOW() - INTERVAL '24 hours' AND time <= NOW()", - ['ukmesh'], - asOf, - ); - assert.equal( - snapshot.text, - "SELECT * FROM packets WHERE time > $2::timestamptz - INTERVAL '24 hours' AND time <= $2::timestamptz", - ); - assert.deepEqual(snapshot.params, ['ukmesh', asOf.toISOString()]); -}); - test('multibyte fact cutover removes both raw decode scans and binds the privacy generation', async () => { const calls: Array<{ text: string; params?: unknown[] }> = []; const query = async ( @@ -195,89 +180,6 @@ test('canonical charts coalesce six high-volume dimensions into one maintained a sql.includes("time_bucket('1 hour', p.time) AS bucket, COUNT(*)::int AS count"))); }); -test('per-network shadowing runs before reads are enabled and stays off for other networks', async () => { - const calls: Array<{ text: string; params?: unknown[] }> = []; - const query = async ( - text: string, - params?: unknown[], - ): Promise<{ rows: T[] }> => { - calls.push({ text, params }); - return { rows: [] }; - }; - const shadowRepository = createStatsRepository({ - query, - networkFilters: () => ({ - params: ['ukmesh'], - packets: 'AND network = $1', - packetsAlias: (alias: string) => `AND ${alias}.network = $1`, - nodes: 'AND network = $1', - nodesAlias: (alias: string) => `AND ${alias}.network = $1`, - }), - aggregateShadowNetworks: ['ukmesh'], - }); - - await shadowRepository.fetchChartsData('ukmesh', undefined); - assert.ok(calls.some((call) => call.text.includes('FROM packet_hourly_stats'))); - assert.ok(calls.some((call) => call.text.includes('WITH scoped AS MATERIALIZED'))); - assert.ok(calls.every((call) => !/\bNOW\(\)/i.test(call.text))); - - calls.length = 0; - const readRepository = createStatsRepository({ - query, - networkFilters: () => ({ - params: ['ukmesh'], - packets: 'AND network = $1', - packetsAlias: (alias: string) => `AND ${alias}.network = $1`, - nodes: 'AND network = $1', - nodesAlias: (alias: string) => `AND ${alias}.network = $1`, - }), - aggregateReadNetworks: ['ukmesh'], - }); - await readRepository.fetchChartsData('ukmesh', undefined); - assert.ok(calls.some((call) => call.text.includes('FROM packet_hourly_stats'))); - - calls.length = 0; - await readRepository.fetchChartsData('test', undefined); - assert.equal(calls.some((call) => call.text.includes('FROM packet_hourly_stats')), false); -}); - -test('Compose aggregate switch accepts a comma-separated network value', async () => { - const previousReads = process.env['STATS_AGGREGATE_READS_ENABLED']; - const previousShadow = process.env['STATS_AGGREGATE_SHADOW_ENABLED']; - process.env['STATS_AGGREGATE_READS_ENABLED'] = 'ukmesh'; - process.env['STATS_AGGREGATE_SHADOW_ENABLED'] = 'false'; - try { - const calls: string[] = []; - const query = async ( - text: string, - ): Promise<{ rows: T[] }> => { - calls.push(text); - return { rows: [] }; - }; - const repository = createStatsRepository({ - query, - networkFilters: () => ({ - params: ['ukmesh'], - packets: 'AND network = $1', - packetsAlias: (alias: string) => `AND ${alias}.network = $1`, - nodes: 'AND network = $1', - nodesAlias: (alias: string) => `AND ${alias}.network = $1`, - }), - }); - await repository.fetchChartsData('ukmesh', undefined); - assert.ok(calls.some((sql) => sql.includes('FROM packet_hourly_stats'))); - - calls.length = 0; - await repository.fetchChartsData('test', undefined); - assert.equal(calls.some((sql) => sql.includes('FROM packet_hourly_stats')), false); - } finally { - if (previousReads === undefined) delete process.env['STATS_AGGREGATE_READS_ENABLED']; - else process.env['STATS_AGGREGATE_READS_ENABLED'] = previousReads; - if (previousShadow === undefined) delete process.env['STATS_AGGREGATE_SHADOW_ENABLED']; - else process.env['STATS_AGGREGATE_SHADOW_ENABLED'] = previousShadow; - } -}); - test('map summary uses the same coordinate, role, and 14-day freshness rules as the map', async () => { const calls: Array<{ text: string; params?: unknown[] }> = []; const query = async ( diff --git a/backend/src/stats/statsRepository.ts b/backend/src/stats/statsRepository.ts index 4a18b66..847d919 100644 --- a/backend/src/stats/statsRepository.ts +++ b/backend/src/stats/statsRepository.ts @@ -15,7 +15,6 @@ import { ensureMultibyteFactsCoverWindow } from './multibytePathFacts.js'; type QueryFn = ( text: string, params?: unknown[], - signal?: AbortSignal, ) => Promise<{ rows: T[] }>; type StatsRepositoryDeps = { @@ -23,8 +22,6 @@ type StatsRepositoryDeps = { query: QueryFn; aggregateReadsEnabled?: boolean; aggregateShadowEnabled?: boolean; - aggregateReadNetworks?: readonly string[]; - aggregateShadowNetworks?: readonly string[]; }; export type StatsRepository = ReturnType; @@ -54,40 +51,6 @@ function normalizedDimensionKey(row: QueryResultRow): string { )); } -function settingInteger(name: string, fallback: number, minimum: number, maximum: number): number { - const raw = process.env[name]; - if (raw === undefined || raw.trim() === '') return fallback; - const parsed = Number(raw); - if (!Number.isSafeInteger(parsed) || parsed < minimum || parsed > maximum) { - console.error(`[stats-query-budget] invalid ${name}; using ${fallback}`); - return fallback; - } - return parsed; -} - -export function bindChartSnapshot( - text: string, - params: unknown[], - asOf: Date, -): { text: string; params: unknown[] } { - if (!/\bNOW\(\)/i.test(text)) return { text, params }; - const snapshotParam = `$${params.length + 1}::timestamptz`; - return { - text: text.replace(/\bNOW\(\)/gi, snapshotParam), - params: [...params, asOf.toISOString()], - }; -} - -function networkSwitches(...values: Array): Set { - return new Set( - values - .flatMap((raw) => String(raw ?? '').split(',')) - .map((network) => network.trim().toLowerCase()) - .filter((network) => network !== 'true' && network !== 'false') - .filter(Boolean), - ); -} - /** * Shadow reads use one pinned cutoff, but a packet transaction that commits * between the aggregate and legacy snapshots can still create a tiny count @@ -139,9 +102,6 @@ export function compareAggregateShadowRows( export function createStatsRepository(deps: StatsRepositoryDeps) { const { networkFilters } = deps; - const queryBudgetMs = settingInteger('STATS_QUERY_DURATION_BUDGET_MS', 60_000, 1_000, 600_000); - const queryTimeoutMs = settingInteger('STATS_CHART_QUERY_TIMEOUT_MS', 120_000, 1_000, 300_000); - const maxResultRows = settingInteger('STATS_CHART_MAX_RESULT_ROWS', 100_000, 1, 1_000_000); const queryConcurrency = Math.max( 1, Math.min(8, Math.trunc(Number(process.env['STATS_DB_QUERY_CONCURRENCY'] ?? 2) || 2)), @@ -169,56 +129,14 @@ export function createStatsRepository(deps: StatsRepositoryDeps) { const query: QueryFn = async ( text: string, params?: unknown[], - parentSignal?: AbortSignal, ) => { - const controller = new AbortController(); - const startedAt = performance.now(); - let timeoutFired = false; - let acquired = false; - const abortFromParent = () => controller.abort(parentSignal?.reason); - if (parentSignal?.aborted) abortFromParent(); - else parentSignal?.addEventListener('abort', abortFromParent, { once: true }); - const timeout = setTimeout(() => { - timeoutFired = true; - controller.abort(new Error(`STATS_QUERY_TIMEOUT_${queryTimeoutMs}MS`)); - }, queryTimeoutMs); + await acquireQuerySlot(); try { - await acquireQuerySlot(); - acquired = true; - controller.signal.throwIfAborted(); - const result = await deps.query(text, params, controller.signal); - const durationMs = Math.round(performance.now() - startedAt); - const source = text.match(/\bFROM\s+([a-z_][a-z0-9_.]*)/i)?.[1] ?? 'unknown'; - if (durationMs > queryBudgetMs || result.rows.length > maxResultRows) { - console.error('[stats-query-budget] exceeded', { - source, - durationMs, - durationBudgetMs: queryBudgetMs, - returnedRows: result.rows.length, - returnedRowBudget: maxResultRows, - }); - throw new Error('STATS_QUERY_BUDGET_EXCEEDED'); - } - return result; - } catch (error) { - const durationMs = Math.round(performance.now() - startedAt); - if (timeoutFired || durationMs > queryBudgetMs) { - const source = text.match(/\bFROM\s+([a-z_][a-z0-9_.]*)/i)?.[1] ?? 'unknown'; - console.error('[stats-query-budget] query failed after budget', { - source, - durationMs, - durationBudgetMs: queryBudgetMs, - queryTimeoutMs, - }); - } - throw error; + return await deps.query(text, params); } finally { - clearTimeout(timeout); - parentSignal?.removeEventListener('abort', abortFromParent); - if (acquired) releaseQuerySlot(); + releaseQuerySlot(); } }; - const limitedQuery: QueryFn = query; async function loadChartSnapshot(scope: string, visibilityGeneration: number) { return loadStoredChartSnapshot(query, scope, visibilityGeneration); @@ -239,13 +157,9 @@ export function createStatsRepository(deps: StatsRepositoryDeps) { ); } - async function fetchObserverRegionSummary( - network: string | undefined, - observer: string | undefined, - queryFn: QueryFn = query, - ) { + async function fetchObserverRegionSummary(network: string | undefined, observer: string | undefined) { const filters = networkFilters(network, observer, { includePrivacy: false }); - return queryFn(` + return query(` SELECT COALESCE(NULLIF(TRIM(UPPER(p.iata)), ''), 'UNK') AS iata, COUNT(DISTINCT p.packet_hash) FILTER (WHERE p.time > NOW() - INTERVAL '24 hours') AS packets_24h, @@ -315,28 +229,10 @@ export function createStatsRepository(deps: StatsRepositoryDeps) { transportCodes: QueryResultRow[]; }; - // Compose already forwards these two settings. Preserve `true` as global - // enablement while allowing a comma-separated per-network rollout. - const readSwitch = String(process.env['STATS_AGGREGATE_READS_ENABLED'] ?? '').trim().toLowerCase(); - const shadowSwitch = String(process.env['STATS_AGGREGATE_SHADOW_ENABLED'] ?? '').trim().toLowerCase(); - const aggregateReadNetworks = new Set([ - ...networkSwitches(process.env['STATS_AGGREGATE_READ_NETWORKS'], readSwitch), - ...(deps.aggregateReadNetworks ?? []).map((network) => network.trim().toLowerCase()).filter(Boolean), - ]); - const aggregateShadowNetworks = new Set([ - ...networkSwitches(process.env['STATS_AGGREGATE_SHADOW_NETWORKS'], shadowSwitch), - ...(deps.aggregateShadowNetworks ?? []).map((network) => network.trim().toLowerCase()).filter(Boolean), - ]); - const aggregateReadsEnabledFor = (network: string | undefined): boolean => ( - deps.aggregateReadsEnabled - ?? (readSwitch === 'true' - || aggregateReadNetworks.has((network ?? 'public').trim().toLowerCase())) - ); - const aggregateShadowEnabledFor = (network: string | undefined): boolean => ( - deps.aggregateShadowEnabled - ?? (shadowSwitch === 'true' - || aggregateShadowNetworks.has((network ?? 'public').trim().toLowerCase())) - ); + const aggregateReadsEnabled = deps.aggregateReadsEnabled + ?? process.env['STATS_AGGREGATE_READS_ENABLED'] === 'true'; + const aggregateShadowEnabled = deps.aggregateShadowEnabled + ?? process.env['STATS_AGGREGATE_SHADOW_ENABLED'] === 'true'; const aggregateShadowInFlight = new Map>(); const aggregateShadowLastStartedAt = new Map(); const aggregateShadowMinimumIntervalMs = 5 * 60_000; @@ -378,7 +274,6 @@ export function createStatsRepository(deps: StatsRepositoryDeps) { async function fetchAggregateChartParts( network: string | undefined, asOf: Date, - signal?: AbortSignal, ): Promise { const scope = aggregateScope(network); const filters = networkFilters(network, undefined); @@ -584,7 +479,6 @@ export function createStatsRepository(deps: StatsRepositoryDeps) { bounds.fullStart7d.toISOString(), bounds.fullEnd.toISOString(), ], - signal, ); const row = result.rows[0]; return { @@ -793,26 +687,6 @@ export function createStatsRepository(deps: StatsRepositoryDeps) { const filters = networkFilters(network, observer); const totalFilters = networkFilters(network, observer, { includePrivacy: false }); const aggregateAsOf = new Date(); - const chartAbort = new AbortController(); - const chartQuery: QueryFn = async ( - text: string, - params: unknown[] = filters.params, - signal: AbortSignal = chartAbort.signal, - ) => { - const snapshot = bindChartSnapshot(text, params, aggregateAsOf); - try { - return await limitedQuery(snapshot.text, snapshot.params, signal); - } catch (error) { - if (!chartAbort.signal.aborted) chartAbort.abort(error); - throw error; - } - }; - // Shadow the repository query helper for this function so every raw chart - // query, including observer summaries and fact readiness checks, shares one - // bounded asOf timestamp. - const query = chartQuery; - const aggregateReadsEnabled = aggregateReadsEnabledFor(network); - const aggregateShadowEnabled = aggregateShadowEnabledFor(network); const multibyteCutoffPlaceholder = `$${filters.params.length + 1}`; const multibyteGenerationPlaceholder = `$${filters.params.length + 2}`; const multibyteFactParams = [ @@ -944,31 +818,15 @@ export function createStatsRepository(deps: StatsRepositoryDeps) { ORDER BY b.day`, multibyteFactParams, ); - const aggregatePartsPromise = (aggregateReadsEnabled || aggregateShadowEnabled) && !observer - ? fetchAggregateChartParts(network, aggregateAsOf, chartAbort.signal) + const aggregatePartsPromise = aggregateReadsEnabled && !observer + ? fetchAggregateChartParts(network, aggregateAsOf) : null; - if (aggregatePartsPromise && aggregateReadsEnabled) { - void aggregatePartsPromise.catch((error: unknown) => { - if (!chartAbort.signal.aborted) chartAbort.abort(error); - }); - } - if (aggregatePartsPromise && aggregateShadowEnabled) { - void aggregatePartsPromise - .then((aggregate) => scheduleAggregateShadow(network, aggregateAsOf, aggregate)) - .catch((error: unknown) => { - console.warn('[stats-aggregate-shadow] aggregate fetch failed', { - network: network ?? 'public', - error: error instanceof Error ? error.message : String(error), - }); - }); - } - const aggregateReadPartsPromise = aggregateReadsEnabled ? aggregatePartsPromise : null; const aggregateRows = ( key: keyof AggregateChartParts, legacy: () => Promise<{ rows: QueryResultRow[] }>, ): Promise<{ rows: QueryResultRow[] }> => ( - aggregateReadPartsPromise - ? aggregateReadPartsPromise.then((parts) => ({ rows: parts[key] })) + aggregatePartsPromise + ? aggregatePartsPromise.then((parts) => ({ rows: parts[key] })) : legacy() ); @@ -1082,7 +940,7 @@ export function createStatsRepository(deps: StatsRepositoryDeps) { WHERE p.time > NOW() - INTERVAL '24 hours' AND p.src_node_id IS NOT NULL ${filters.packetsAlias('p')}) AS unique_radios_24h `, filters.params), - fetchObserverRegionSummary(network, observer, query), + fetchObserverRegionSummary(network, observer), query(` SELECT COALESCE(NULLIF(TRIM(UPPER(p.iata)), ''), 'UNK') AS iata, @@ -1207,6 +1065,12 @@ export function createStatsRepository(deps: StatsRepositoryDeps) { transportCodesResult, pathDecodeTrendResult, }; + if (aggregatePartsPromise && aggregateShadowEnabled) { + const aggregate = await aggregatePartsPromise; + // The comparison is bounded to the six switched dimensions, uses one + // materialized recent packet scan and runs off the response path. + scheduleAggregateShadow(network, aggregateAsOf, aggregate); + } return response; } @@ -1357,17 +1221,49 @@ export function createStatsRepository(deps: StatsRepositoryDeps) { ${filters.packetsAlias('p')} GROUP BY COALESCE(rx_alias.canonical_node_id, UPPER(BTRIM(p.rx_node_id))) ), + prefix_map AS MATERIALIZED ( + -- 3-byte path-hash prefix -> canonical node id, so relay evidence + -- (a repeater's prefix appearing in a packet's path_hashes) can be + -- attributed to the node that forwarded the packet. + SELECT LEFT(node_id, 6) AS prefix, node_id AS canonical_node_id + FROM node_identity_nodes + UNION + SELECT LEFT(source_node_id, 6) AS prefix, canonical_node_id + FROM node_identity_aliases + ), tx AS ( - SELECT COALESCE(src_alias.canonical_node_id, UPPER(BTRIM(p.src_node_id))) AS node_id, - COUNT(p.packet_hash)::text AS tx_24h, - MAX(p.time)::text AS last_tx - FROM packets p - LEFT JOIN node_identity_aliases src_alias - ON src_alias.source_node_id = UPPER(BTRIM(p.src_node_id)) - WHERE p.time > NOW() - INTERVAL '24 hours' - AND p.src_node_id IS NOT NULL - ${filters.packetsAlias('p')} - GROUP BY COALESCE(src_alias.canonical_node_id, UPPER(BTRIM(p.src_node_id))) + -- tx_24h = distinct packets the node transmitted in the last 24 hours: + -- * packets it originated (alias-resolved src rows), and + -- * packets it relayed (its 3-byte prefix present in path_hashes). + -- Counting originations only showed "tx 0" for repeaters that relayed + -- hundreds of packets/day but advertise rarely (2026-09-22 fix). + SELECT node_id, + COUNT(DISTINCT packet_hash)::text AS tx_24h, + MAX(time)::text AS last_tx + FROM ( + SELECT COALESCE(src_alias.canonical_node_id, UPPER(BTRIM(p.src_node_id))) AS node_id, + p.packet_hash, + p.time + FROM packets p + LEFT JOIN node_identity_aliases src_alias + ON src_alias.source_node_id = UPPER(BTRIM(p.src_node_id)) + WHERE p.time > NOW() - INTERVAL '24 hours' + AND p.src_node_id IS NOT NULL + ${filters.packetsAlias('p')} + UNION ALL + SELECT prefix_map.canonical_node_id AS node_id, + p.packet_hash, + p.time + FROM packets p + CROSS JOIN LATERAL unnest(p.path_hashes) AS path_element + JOIN prefix_map ON prefix_map.prefix = path_element + WHERE p.time > NOW() - INTERVAL '24 hours' + AND p.path_hash_size_bytes >= 3 + AND p.path_hashes IS NOT NULL + AND LENGTH(path_element) = 6 + ${filters.packetsAlias('p')} + ) attributed + GROUP BY node_id ) SELECT n.node_id, diff --git a/backend/src/tools/inventoryOwnerAuthorization.ts b/backend/src/tools/inventoryOwnerAuthorization.ts index 86e09d5..355678f 100644 --- a/backend/src/tools/inventoryOwnerAuthorization.ts +++ b/backend/src/tools/inventoryOwnerAuthorization.ts @@ -1,3 +1,7 @@ +import { + closeDb, + query, +} from '../db/index.js'; import { closeOwnerAuthDb, getOwnerAclReadiness, @@ -6,6 +10,10 @@ import { } from '../db/ownerAuth.js'; import { parseAcl, readAclFile, renderOwnerAcl } from '../mqtt/aclManager.js'; import { parseOwnerGrantConfig } from '../owner/ownerGrantConfig.js'; +import { getPublicFeedConfig } from '../owner/publicFeedConfig.js'; +import { + getActivePublicFeedReaderUsernames, +} from '../owner/publicFeedRepository.js'; import { buildOwnerInventoryBaseline, loadOwnerInventoryBaseline, @@ -27,18 +35,53 @@ async function main(): Promise { const aclContent = readAclFile(); const parsedAcl = parseAcl(aclContent); const configured = parseOwnerGrantConfig(String(process.env['OWNER_MQTT_USERNAME_MAP'] ?? '')); + const publicFeedConfig = getPublicFeedConfig(); + const publicFeedReaders = publicFeedConfig.configured + ? await getActivePublicFeedReaderUsernames(query) + : new Set(); + const explicitPublicFeedReaders = String(process.env['OWNER_ACL_PUBLIC_FEED_READ_USERS'] ?? '') + .split(',') + .map((value) => value.trim()) + .filter(Boolean); + const explicitPublicFeedReaderSet = new Set(explicitPublicFeedReaders); + const grants = snapshot.map((account) => { + const publicFeedRead = explicitPublicFeedReaderSet.has(account.mqttUsername) + || (account.isActive && publicFeedReaders.has(account.mqttUsername)); + return { + mqttUsername: account.mqttUsername, + nodeIds: account.isActive ? account.nodeIds : [], + ...(publicFeedRead ? { publicFeedRead: true } : {}), + }; + }); + const snapshotUsernames = new Set(grants.map((grant) => grant.mqttUsername)); + for (const mqttUsername of explicitPublicFeedReaders) { + if (snapshotUsernames.has(mqttUsername)) continue; + grants.push({ + mqttUsername, + nodeIds: [], + publicFeedRead: true, + }); + } const unmanagedUsers = String(process.env['OWNER_ACL_UNMANAGED_USERS'] ?? 'backend,test,test2') .split(',') .map((value) => value.trim()) .filter(Boolean); const rendered = renderOwnerAcl( aclContent, - snapshot.map((account) => ({ - mqttUsername: account.mqttUsername, - nodeIds: account.nodeIds, - })), + grants, unmanagedUsers, - String(process.env['OWNER_ACL_ALLOW_EMPTY_USERS'] ?? '').split(',').map((value) => value.trim()).filter(Boolean), + [ + ...String(process.env['OWNER_ACL_ALLOW_EMPTY_USERS'] ?? '') + .split(',') + .map((value) => value.trim()) + .filter(Boolean), + ...explicitPublicFeedReaders, + ], + [String(process.env['MQTT_USERNAME'] ?? 'backend').trim() || 'backend'], + String(process.env['OWNER_ACL_SYSTEM_READWRITE_TOPICS'] ?? 'public/#') + .split(',') + .map((value) => value.trim()) + .filter(Boolean), ); const configKeys = new Set(configured.map((grant) => `${grant.mqttUsername}\0${grant.nodeId}`)); const legacyAclGrants = parsedAcl.stanzas.flatMap((stanza) => @@ -117,4 +160,7 @@ main() console.error('[owner-auth-inventory]', error.message); process.exitCode = 1; }) - .finally(() => closeOwnerAuthDb().catch(() => undefined)); +.finally(async () => { + await closeDb().catch(() => undefined); + await closeOwnerAuthDb().catch(() => undefined); +}); diff --git a/backend/src/tools/pathAccuracy.test.ts b/backend/src/tools/pathAccuracy.test.ts new file mode 100644 index 0000000..9ee5cb8 --- /dev/null +++ b/backend/src/tools/pathAccuracy.test.ts @@ -0,0 +1,22 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { compareAccuracy } from './pathAccuracy.js'; + +const result = { + summary: { snapshotSha256: 'frozen', split: 'test', mode: 'single' }, + cases: [{ packetHash: 'packet', receiver: 'rx', source: null, truth: ['A', 'B'], prediction: ['A', 'C'] }], +}; + +test('accuracy comparisons reject changed snapshots, missing cases or changed labels', () => { + assert.throws(() => compareAccuracy(result, { ...result, summary: { ...result.summary, snapshotSha256: 'different' } }), /snapshotSha256/); + assert.throws(() => compareAccuracy(result, { ...result, cases: [] }), /cases/); + assert.throws(() => compareAccuracy(result, { ...result, cases: [{ ...result.cases[0]!, truth: ['A', 'C'] }] }), /ground truth/); +}); + +test('paired comparison counts a correction without excluding the previously wrong hop', () => { + const comparison = compareAccuracy(result, { ...result, cases: [{ ...result.cases[0]!, prediction: ['A', 'B'] }] }); + assert.equal(comparison.improved, 1); + assert.equal(comparison.regressed, 0); + assert.equal(comparison.hopGainPercentagePoints, 50); + assert.equal(comparison.routeGainPercentagePoints, 100); +}); diff --git a/backend/src/tools/pathAccuracy.ts b/backend/src/tools/pathAccuracy.ts new file mode 100644 index 0000000..fb18079 --- /dev/null +++ b/backend/src/tools/pathAccuracy.ts @@ -0,0 +1,342 @@ +/** Read-only, frozen-snapshot comparison of live decoders on hidden three-byte routes. + * + * Capture: node --env-file=../.env --import tsx src/tools/pathAccuracy.ts capture /tmp/path-snapshot.json + * Replay: node --import tsx src/tools/pathAccuracy.ts replay /tmp/path-snapshot.json /tmp/result.json [decoder.ts] [dev|test] + * A baseline decoder can be taken from a git archive with its relative imports intact. + */ +import { readFile, writeFile, mkdir, symlink } from 'node:fs/promises'; +import { createHash } from 'node:crypto'; +import { execFileSync } from 'node:child_process'; +import { resolve, join } from 'node:path'; +import { pathToFileURL, fileURLToPath } from 'node:url'; +import pg from 'pg'; +import { buildChampionPriors, type ChampionPriorRows } from '../path-beta/championPriors.js'; +import type { BetaResolveContext, MeshNode, PathPacket, LinkMetrics } from '../path-beta/types.js'; +import type { BetaObserverEntry } from '../path-beta/sharedDecoder.js'; +import { linkKey } from '../path-beta/geometry.js'; +import { expandResolverScope } from '../networks.js'; + +type Observation = PathPacket & { time: string; split: number; network: string }; +type Snapshot = { + capturedAt: string; + network: string; + windowDays: number; + calibration: Record[]; + nodes: MeshNode[]; + links: (LinkMetrics & { node_a_id: string; node_b_id: string })[]; + prefixes: { prefix: string; prev_prefix: string; receiver_region: string; node_id: string; count: number }[]; + transitions: { from_node_id: string; to_node_id: string; receiver_region: string; count: number; probability: number }[]; + priors: ChampionPriorRows; + observations: Observation[]; +}; + +async function capture(file: string) { + const client = new pg.Client({ + connectionString: process.env['DATABASE_URL'], + host: process.env['PGHOST'] ?? '127.0.0.1', + user: process.env['POSTGRES_USER'] ?? 'meshcore', + password: process.env['POSTGRES_PASSWORD'], + database: process.env['POSTGRES_DB'] ?? 'meshcore', + application_name: 'path-accuracy-readonly', + statement_timeout: 120_000, + }); + await client.connect(); + try { + await client.query('BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY'); + const network = process.env['PATH_ACCURACY_NETWORK'] ?? 'ukmesh'; + const scope = network === 'all' ? null : expandResolverScope(network); + const windowDays = Number(process.env['PATH_ACCURACY_DAYS'] ?? 14); + if (!Number.isInteger(windowDays) || windowDays < 1 || windowDays > 45) throw new Error('invalid window days'); + const rows = async (sql: string, params: unknown[] = [network]): Promise => + (await client.query(sql, params)).rows; + const capturedAt = (await rows<{ t: string }>('SELECT now()::text AS t', []))[0]!.t; + const calibration = await rows('SELECT * FROM path_model_calibration WHERE network = $1'); + const nodes = await rows(`SELECT node_id, name, lat, lon, iata, role, elevation_m, last_seen::text + FROM nodes WHERE ($1::text[] IS NULL OR network = ANY($1)) AND (name IS NULL OR name NOT LIKE '%🚫%') ORDER BY node_id`, [scope]); + const links = await rows(`SELECT nl.node_a_id, nl.node_b_id, nl.observed_count, + nl.multibyte_observed_count, nl.itm_path_loss_db, nl.itm_viable, nl.count_a_to_b, nl.count_b_to_a + FROM node_links nl JOIN nodes a ON a.node_id = nl.node_a_id JOIN nodes b ON b.node_id = nl.node_b_id + WHERE ($1::text[] IS NULL OR (a.network = ANY($1) AND b.network = ANY($1))) AND (nl.itm_viable IS NOT NULL OR nl.force_viable = true) + AND (a.name IS NULL OR a.name NOT LIKE '%🚫%') AND (b.name IS NULL OR b.name NOT LIKE '%🚫%')`, [scope]); + const prefixes = await rows(`SELECT prefix, prev_prefix, receiver_region, node_id, count + FROM path_prefix_priors WHERE network = $1 ORDER BY count DESC LIMIT 6000`); + const transitions = await rows(`SELECT from_node_id, to_node_id, receiver_region, count, probability + FROM path_transition_priors WHERE network = $1 ORDER BY count DESC`); + const priors: ChampionPriorRows = { + positionPrefixes: await rows('SELECT prefix, position, node_id, count FROM path_position_prefix_priors WHERE network = $1'), + corridors: await rows('SELECT src_node_id, rx_node_id, position, node_id, count FROM path_corridor_priors WHERE network = $1'), + transitions, + positionTransitions: await rows('SELECT position, from_node_id, to_node_id, count FROM path_position_transition_priors WHERE network = $1'), + }; + // The learner trains only on buckets 0..69. Split at packet hash so every + // observation of a held-out packet is excluded from all learned priors. + const gold = await rows<{ packet_hash: string }>(`SELECT DISTINCT packet_hash FROM packets + WHERE ($1::text[] IS NULL OR network = ANY($1)) AND time > now() - $2 * interval '1 day' + AND path_hash_size_bytes = 3 AND cardinality(path_hashes) >= 2 + AND mod(hashtext(packet_hash)::bigint + 2147483648, 100) >= 70`, [scope, windowDays]); + console.log(`capturing ${gold.length} held-out packet hashes`); + const observations = await rows(`SELECT time::text, network, packet_hash, rx_node_id, src_node_id, + packet_type, route_type, payload->>'direction' AS direction, hop_count, path_hashes, path_hash_size_bytes, + mod(hashtext(packet_hash)::bigint + 2147483648, 100)::int AS split + FROM packets WHERE ($1::text[] IS NULL OR network = ANY($1)) AND time > now() - $2 * interval '1 day' + AND packet_hash = ANY($3::text[]) ORDER BY time, packet_hash, rx_node_id`, + [scope, windowDays, gold.map((row) => row.packet_hash)]); + const snapshot: Snapshot = { capturedAt, network, windowDays, calibration, nodes, links, prefixes, transitions, priors, observations }; + await writeFile(file, JSON.stringify(snapshot)); + await client.query('COMMIT'); + console.log(JSON.stringify({ file, nodes: nodes.length, links: links.length, observations: observations.length })); + } finally { + await client.end(); + } +} + +async function archiveBaseline(directory: string, ref: string) { + const cwd = fileURLToPath(new URL('../../../', import.meta.url)); + const revision = execFileSync('git', ['rev-parse', '--verify', `${ref}^{commit}`], { cwd, encoding: 'utf8' }).trim(); + const destination = resolve(directory); + await mkdir(destination); + const archive = execFileSync('git', ['archive', revision, 'backend/src', 'backend/package.json'], { cwd, maxBuffer: 64 * 1024 * 1024 }); + execFileSync('tar', ['-x', '-C', destination], { input: archive }); + await symlink(fileURLToPath(new URL('../../node_modules', import.meta.url)), join(destination, 'backend/node_modules')); + const resolver = join(destination, 'backend/src/path-beta/resolver.ts'); + const original = await readFile(resolver, 'utf8'); + // Expose an existing pure function to the replay harness. Its body and all + // scoring/configuration files remain byte-for-byte at the baseline commit. + await writeFile(resolver, original.replace(/^function preparePacketObservation\(/m, 'export function preparePacketObservation(')); + const manifest = { revision, resolverSha256: createHash('sha256').update(original).digest('hex'), modification: 'export preparePacketObservation only' }; + await writeFile(join(destination, 'baseline.json'), JSON.stringify(manifest, null, 2)); + console.log(JSON.stringify({ directory: destination, ...manifest })); +} + +function makeContext(snapshot: Snapshot): BetaResolveContext { + const choiceCounts = new Map(); + const totals = new Map(); + for (const row of snapshot.prefixes) { + const group = `${row.receiver_region}|${row.prefix.toUpperCase().slice(0, 2)}|${row.prev_prefix.toUpperCase().slice(0, 2)}`; + const key = `${group}|${row.node_id}`; + choiceCounts.set(key, (choiceCounts.get(key) ?? 0) + Number(row.count)); + totals.set(group, (totals.get(group) ?? 0) + Number(row.count)); + } + const prefixProbabilities = new Map([...choiceCounts].map(([key, count]) => [key, count / totals.get(key.slice(0, key.lastIndexOf('|')))!])); + return { + loadedAt: Date.parse(snapshot.capturedAt), visibilityGeneration: 0, + nodesById: new Map(snapshot.nodes.map((node) => [node.node_id, node])), + repeaterNodes: snapshot.nodes.filter((n) => n.lat != null && n.lon != null && n.lat !== 0 && n.lon !== 0 && (n.role == null || n.role === 2)), + linkMetrics: new Map(snapshot.links.map((link) => [linkKey(link.node_a_id, link.node_b_id), link])), + learningModel: { + champion: buildChampionPriors(snapshot.priors), prefixProbabilities, + transitionProbabilities: new Map(snapshot.transitions.slice(0, 6000).map((r) => [`${r.receiver_region}|${r.from_node_id}|${r.to_node_id}`, Number(r.probability)])), + edgeScores: new Map(), motifProbabilities: new Map(), confidenceScale: 1, confidenceBias: 0, bucketHours: 6, + }, + }; +} + +/** Replay the former lazy SQL adapter against the same immutable evidence. */ +function snapshotQuery(snapshot: Snapshot, observations: Observation[]) { + const membership = new WeakMap>(); + return async (sql: string, params: unknown[] = []) => { + const includes = (ids: unknown, id: string) => { + if (!Array.isArray(ids)) return false; + let set = membership.get(ids); + if (!set) { set = new Set(ids); membership.set(ids, set); } + return set.has(id); + }; + if (/FROM packets\b/i.test(sql)) { + if (/SELECT DISTINCT rx_node_id, hop_count/i.test(sql)) return { rows: observations }; + const byObserver = new Map(); + for (const row of [...observations].sort((a, b) => (b.path_hashes?.length ?? 0) - (a.path_hashes?.length ?? 0))) { + if (!byObserver.has(row.rx_node_id)) byObserver.set(row.rx_node_id, row); + } + return { rows: [...byObserver.values()] }; + } + if (/FROM nodes\b/i.test(sql)) return { rows: snapshot.nodes.filter((node) => /node_id = ANY\(\$1\)/i.test(sql) + ? includes(params[0], node.node_id) && node.lat != null && node.lon != null && node.lat !== 0 && node.lon !== 0 + : params.slice(1, -1).some((prefix) => typeof prefix === 'string' && node.node_id.toUpperCase().startsWith(prefix.replace('%', '')))) }; + if (/FROM node_links\b/i.test(sql)) return { rows: snapshot.links.filter((link) => includes(params[0], link.node_a_id) && includes(params[0], link.node_b_id)) }; + if (/FROM path_position_prefix_priors\b/i.test(sql)) return { rows: snapshot.priors.positionPrefixes.filter((row) => includes(params[1], row.prefix)) }; + if (/FROM path_corridor_priors\b/i.test(sql)) return { rows: snapshot.priors.corridors.filter((row) => includes(params[1], row.src_node_id) && includes(params[2], row.rx_node_id)) }; + if (/FROM path_position_transition_priors\b/i.test(sql)) return { rows: snapshot.priors.positionTransitions.filter((row) => includes(params[1], row.from_node_id) && includes(params[1], row.to_node_id)) }; + if (/FROM path_transition_priors\b/i.test(sql)) { + const counts = new Map(); + for (const row of snapshot.transitions) { + if (!includes(params[1], row.from_node_id) || !includes(params[1], row.to_node_id)) continue; + const key = `${row.from_node_id}|${row.to_node_id}`; + const previous = counts.get(key); + counts.set(key, { from_node_id: row.from_node_id, to_node_id: row.to_node_id, count: (previous?.count ?? 0) + Number(row.count) }); + } + return { rows: [...counts.values()] }; + } + if (/FROM path_model_calibration\b/i.test(sql)) return { rows: snapshot.calibration }; + throw new Error(`Unimplemented snapshot query: ${sql}`); + }; +} + +async function replay(file: string, output: string, decoderFile?: string, split = 'dev') { + if (!['dev', 'test'].includes(split)) throw new Error('split must be dev or test'); + const raw = await readFile(file, 'utf8'); + const snapshot = JSON.parse(raw) as Snapshot; + const context = makeContext(snapshot); + const decoder = await import(pathToFileURL(resolve(decoderFile ?? 'src/path-beta/sharedDecoder.ts')).href) as + typeof import('../path-beta/sharedDecoder.js') & Partial; + const preparation = await import(new URL(decoder.lazyResolvePath ? '../path-beta/resolver.ts' : './resolver.ts', + pathToFileURL(resolve(decoderFile ?? 'src/path-beta/sharedDecoder.ts'))).href) as typeof import('../path-beta/resolver.js'); + const mode = process.env['PATH_ACCURACY_MODE'] === 'multi' ? 'multi' : 'single'; + const threeByteIndex = new Map(); + for (const node of snapshot.nodes) { + const hash = node.node_id.toUpperCase().slice(0, 6); + threeByteIndex.set(hash, [...(threeByteIndex.get(hash) ?? []), node]); + } + const byPacket = new Map(); + for (const obs of snapshot.observations) { + if ((obs.split < 85) !== (split === 'dev')) continue; + byPacket.set(obs.packet_hash, [...(byPacket.get(obs.packet_hash) ?? []), obs]); + } + let excluded = 0; + const cases: { packetHash: string; receiver: string; truth: string[]; prediction: (string | null)[]; ms: number; rawMs: number; rawCorrect: boolean; source: string | null }[] = []; + for (const [packetHash, observations] of byPacket) { + const candidates = observations.filter((row) => row.path_hash_size_bytes === 3) + .sort((a, b) => (b.path_hashes?.length ?? 0) - (a.path_hashes?.length ?? 0) || b.time.localeCompare(a.time)); + let selected: { obs: Observation; hashes: string[]; truth: string[]; rx: MeshNode } | undefined; + for (const obs of candidates) { + if (obs.route_type != null && obs.route_type !== 0 && obs.route_type !== 1) continue; + const rx = context.nodesById.get(obs.rx_node_id ?? ''); + if (!rx || rx.lat == null || rx.lon == null || rx.lat === 0 || rx.lon === 0) continue; + let hashes = (obs.path_hashes ?? []).map((h) => h.toUpperCase()); + if (hashes.some((h) => !/^[0-9A-F]{6}$/.test(h))) continue; + if (obs.direction !== 'rx' && hashes.length > 1 && rx.role === 2 && rx.node_id.toUpperCase().startsWith(hashes.at(-1)!)) hashes = hashes.slice(0, -1); + if (hashes.length < 2 || hashes.length > 25) continue; + const nodes = hashes.map((h) => threeByteIndex.get(h) ?? []); + if (nodes.some((matches) => matches.length !== 1 || matches[0]!.lat == null || matches[0]!.lon == null)) continue; + selected = { obs, rx, hashes, truth: nodes.map((n) => n[0]!.node_id) }; + break; + } + if (!selected) { excluded++; continue; } + const { obs, rx, truth } = selected; + const decode = async (size: number) => { + const visible = (mode === 'multi' ? observations.filter((row) => Math.abs(Date.parse(row.time) - Date.parse(obs.time)) <= 60_000) : [obs]) + .map((row) => ({ ...row, path_hashes: row.path_hashes?.map((h) => size === 1 ? h.slice(0, 2) : h) ?? null, + path_hash_size_bytes: size === 1 ? 1 : row.path_hash_size_bytes })); + if (decoder.lazyResolvePath) { + const query = snapshotQuery(snapshot, visible); + const result = await decoder.lazyResolvePath(packetHash, snapshot.network, + query as Parameters[2], { referenceTime: obs.time }); + const resolved = result?.paths.find((p) => p.observerIds.includes(rx.node_id)); + return new Map(resolved?.canonicalPath.filter((h) => !h.isObserver).map((h) => [h.position, h.nodeId]) ?? []); + } + const byObserver = new Map>(); + for (const row of visible) { + const observer = context.nodesById.get(row.rx_node_id ?? ''); + if (!observer || observer.lat == null || observer.lon == null) continue; + const prepared = preparation.preparePacketObservation(row, observer); + if (prepared.ignoreForPathing || prepared.hops.length === 0) continue; + const previous = byObserver.get(observer.node_id); + byObserver.set(observer.node_id, previous ? preparation.mergeObserverEvidence(previous, prepared) : prepared); + } + const entries: BetaObserverEntry[] = preparation.prepareObservationEntries + ? preparation.prepareObservationEntries(visible, context) + : [...byObserver.values()].map((p) => ({ + observerId: p.rx!.node_id, rx: p.rx!, hashes: p.hashes, hops: p.hops, packet: p.packet, + })); + const target = preparation.preparePacketObservation({ ...obs, path_hashes: obs.path_hashes?.map((h) => size === 1 ? h.slice(0, 2) : h) ?? null, + path_hash_size_bytes: size }, rx); + const groups = decoder.groupCompatibleObservations(entries); + const group = groups.find((g) => g.members.some((e) => e.observerId === rx.node_id + && e.hops.length === target.hops.length && e.hops.every((h, i) => h === target.hops[i]))) + ?? groups.find((g) => g.members.some((e) => e.observerId === rx.node_id)); + if (!group) return new Map(); + return new Map([...decoder.decodeBetaCanonicalGroup(group, context).hops] + .map(([position, hop]) => [position, hop.nodeId])); + }; + const start = performance.now(); + const result = await decode(1); + const ms = performance.now() - start; + const rawStart = performance.now(); + const rawResult = await decode(3); + const rawMs = performance.now() - rawStart; + cases.push({ packetHash, receiver: rx.node_id, source: obs.src_node_id, truth, + prediction: truth.map((_, i) => result.get(i) ?? null), ms, rawMs, + rawCorrect: truth.every((id, i) => rawResult.get(i) === id) }); + } + const routeCorrect = cases.filter((c) => c.truth.every((id, i) => c.prediction[i] === id)).length; + const totalHops = cases.reduce((n, c) => n + c.truth.length, 0); + const correctHops = cases.reduce((n, c) => n + c.truth.filter((id, i) => c.prediction[i] === id).length, 0); + const times = cases.map((c) => c.ms).sort((a, b) => a - b); + const summary = { snapshotSha256: createHash('sha256').update(raw).digest('hex'), capturedAt: snapshot.capturedAt, + decoder: decoderFile ?? 'current', split, mode, routes: cases.length, excluded, routeCorrect, totalHops, correctHops, + routeAccuracy: routeCorrect / cases.length, hopAccuracy: correctHops / totalHops, + rawThreeByteCorrect: cases.filter((c) => c.rawCorrect).length, + uniqueRoutes: new Set(cases.map((c) => c.truth.join('|'))).size, + medianMs: times[Math.floor(times.length * 0.5)], p95Ms: times[Math.floor(times.length * 0.95)], p99Ms: times[Math.floor(times.length * 0.99)] }; + const sourceRoot = new URL('../', pathToFileURL(resolve(decoderFile ?? 'src/path-beta/sharedDecoder.ts'))); + const sourceFiles = ['path-beta/sharedDecoder.ts', 'path-beta/resolver.ts', 'path-core/decoder.ts', 'path-shared/scoring.ts', 'platform/config/pathing.ts']; + const sourceHashes = Object.fromEntries(await Promise.all(sourceFiles.map(async (name) => [name, + createHash('sha256').update(await readFile(new URL(name, sourceRoot))).digest('hex')]))); + await writeFile(output, JSON.stringify({ summary, sourceHashes, cases }, null, 2)); + console.log(JSON.stringify(summary, null, 2)); +} + +type AccuracyCase = { packetHash: string; receiver: string; source: string | null; truth: string[]; prediction: (string | null)[] }; +type AccuracyResult = { summary: { snapshotSha256: string; split: string; mode: string; [key: string]: unknown }; cases: AccuracyCase[] }; + +export function compareAccuracy(baseline: AccuracyResult, current: AccuracyResult) { + for (const key of ['snapshotSha256', 'split', 'mode'] as const) { + if (baseline.summary[key] !== current.summary[key]) throw new Error(`comparison mismatch: ${key}`); + } + if (baseline.cases.length === 0 || baseline.cases.length !== current.cases.length) throw new Error('comparison mismatch: cases'); + const oldCases = new Map(baseline.cases.map((c) => [`${c.packetHash}|${c.receiver}`, c])); + let improved = 0; + let regressed = 0; + let hopGain = 0; + let totalHops = 0; + const routeClusters = new Map(); + const strata: Record = {}; + for (const next of current.cases) { + const previous = oldCases.get(`${next.packetHash}|${next.receiver}`); + if (!previous || JSON.stringify(previous.truth) !== JSON.stringify(next.truth)) throw new Error('comparison mismatch: ground truth'); + const before = previous.truth.every((id, i) => previous.prediction[i] === id); + const after = next.truth.every((id, i) => next.prediction[i] === id); + if (after && !before) improved++; + if (before && !after) regressed++; + totalHops += next.truth.length; + hopGain += next.truth.filter((id, i) => next.prediction[i] === id).length - previous.truth.filter((id, i) => previous.prediction[i] === id).length; + const key = next.truth.join('|'); + routeClusters.set(key, [...(routeClusters.get(key) ?? []), Number(after) - Number(before)]); + for (const label of [next.source ? 'known-source' : 'unknown-source', next.truth.length <= 3 ? 'hops-2-3' : next.truth.length <= 6 ? 'hops-4-6' : next.truth.length <= 12 ? 'hops-7-12' : 'hops-13+']) { + const bucket = strata[label] ?? { routes: 0, baselineCorrect: 0, currentCorrect: 0 }; + bucket.routes++; bucket.baselineCorrect += Number(before); bucket.currentCorrect += Number(after); + strata[label] = bucket; + } + } + // Resample unique relay chains, giving recurrent copies of the same route + // one vote. This is stricter than treating every packet as independent. + const means = [...routeClusters.values()].map((values) => values.reduce((a, b) => a + b, 0) / values.length); + let seed = 0x5eeda11; + const random = () => { seed ^= seed << 13; seed ^= seed >>> 17; seed ^= seed << 5; return (seed >>> 0) / 2 ** 32; }; + const bootstrap: number[] = []; + for (let iteration = 0; iteration < 2000; iteration++) { + let sum = 0; + for (let i = 0; i < means.length; i++) sum += means[Math.floor(random() * means.length)]!; + bootstrap.push(100 * sum / means.length); + } + bootstrap.sort((a, b) => a - b); + return { baseline: baseline.summary, current: current.summary, improved, regressed, + routeGainPercentagePoints: 100 * (improved - regressed) / current.cases.length, + hopGainPercentagePoints: 100 * hopGain / totalHops, + uniqueRouteGain95IntervalPercentagePoints: [bootstrap[50], bootstrap[1950]], strata }; +} + +async function main() { + const [command, file, output, decoder, split] = process.argv.slice(2); + if (!file) throw new Error('expected capture, baseline, replay or compare; see docs/pathing.md'); + if (command === 'capture') await capture(file); + else if (command === 'baseline' && output) await archiveBaseline(file, output); + else if (command === 'replay' && output) await replay(file, output, decoder === 'current' ? undefined : decoder, split); + else if (command === 'compare' && output && decoder) { + const result = compareAccuracy(JSON.parse(await readFile(file, 'utf8')), JSON.parse(await readFile(output, 'utf8'))); + await writeFile(decoder, JSON.stringify(result, null, 2)); + console.log(JSON.stringify(result, null, 2)); + if (result.routeGainPercentagePoints <= 0 || result.hopGainPercentagePoints <= 0) process.exitCode = 1; + } else throw new Error('invalid command'); +} + +if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) await main(); diff --git a/backend/src/tools/prepareScotmesh.ts b/backend/src/tools/prepareScotmesh.ts new file mode 100644 index 0000000..6baab13 --- /dev/null +++ b/backend/src/tools/prepareScotmesh.ts @@ -0,0 +1,48 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; +import { generatePacketShareIdentity } from '../owner/packetShareAuth.js'; +import { getOwnerNodeIdsForUsername, closeOwnerAuthDb } from '../db/ownerAuth.js'; + +async function main(): Promise { + const [nodeIdArgument, outputFile] = process.argv.slice(2); + const nodeId = nodeIdArgument?.trim().toUpperCase(); + if (!nodeId || !/^[0-9A-F]{64}$/.test(nodeId) || !outputFile) { + throw new Error('Usage: prepareScotmesh NODE_ID OUTPUT_FILE'); + } + try { + const nodeIds = await getOwnerNodeIdsForUsername('node1'); + if (!nodeIds.includes(nodeId)) throw new Error('Node is not verified for node1'); + // Opening exclusively prevents an accidental rerun from rotating an identity. + await fs.mkdir(path.dirname(outputFile), { recursive: true, mode: 0o700 }); + const file = await fs.open(outputFile, 'wx', 0o600); + try { + const identity = await generatePacketShareIdentity(); + const config = { + destination_id: 'scotmesh', + display_name: 'ScotMesh', + description: 'MME; received packets and own transmitted adverts', + broker_url: 'wss://scotmesh-mqtt.mm7roq.compute.oarc.uk:443/', + topic_template: 'meshcore/MME/{observerId}/packets', + allowed_owner_username: 'node1', + allowed_node_id: nodeId, + auth_mode: 'meshcore_jwt', + auth_audience: 'scotmesh-mqtt.mm7roq.compute.oarc.uk', + tx_mode: 'advert', + broker_username: identity.username, + broker_password_ciphertext: identity.privateKeyCiphertext, + enabled: true, + }; + await file.writeFile(`${JSON.stringify(config, null, 2)}\n`); + console.log(JSON.stringify({ destination: 'scotmesh', owner: 'node1', nodeId, username: identity.username, outputFile })); + } finally { + await file.close(); + } + } finally { + await closeOwnerAuthDb(); + } +} + +main().catch((error: unknown) => { + console.error('[scotmesh] preparation failed:', error instanceof Error ? error.message : error); + process.exitCode = 1; +}); diff --git a/backend/src/tools/provisionScotmesh.ts b/backend/src/tools/provisionScotmesh.ts new file mode 100644 index 0000000..d9cbc8d --- /dev/null +++ b/backend/src/tools/provisionScotmesh.ts @@ -0,0 +1,61 @@ +import fs from 'node:fs/promises'; +import pg from 'pg'; +import { closeOwnerAuthDb, getOwnerNodeIdsForUsername } from '../db/ownerAuth.js'; +import { databaseConfig } from '../platform/config/database.js'; +import { packetShareCredentials } from '../owner/packetShareAuth.js'; +import { destinationConfigured, type PacketShareDestinationRow } from '../owner/packetShareConfig.js'; +import { hasPacketShareEncryptionKey } from '../owner/packetShareCrypto.js'; + +async function main(): Promise { + const [inputFile] = process.argv.slice(2); + if (!inputFile) throw new Error('Usage: provisionScotmesh CONFIG_FILE'); + const pool = new pg.Pool({ + connectionString: process.env['DATABASE_URL'], + max: 1, + options: databaseConfig.schema ? `-c search_path=${databaseConfig.schema},public` : undefined, + }); + try { + const destination = JSON.parse(await fs.readFile(inputFile, 'utf8')) as PacketShareDestinationRow; + if (destination.destination_id !== 'scotmesh' + || destination.allowed_owner_username !== 'node1' + || !destination.allowed_node_id || !/^[0-9A-F]{64}$/.test(destination.allowed_node_id) + || destination.broker_url !== 'wss://scotmesh-mqtt.mm7roq.compute.oarc.uk:443/' + || destination.topic_template !== 'meshcore/MME/{observerId}/packets' + || destination.auth_mode !== 'meshcore_jwt' + || destination.auth_audience !== 'scotmesh-mqtt.mm7roq.compute.oarc.uk' + || destination.tx_mode !== 'advert' + || !destinationConfigured(destination, hasPacketShareEncryptionKey())) { + throw new Error('Invalid node1 ScotMesh configuration'); + } + if (!(await getOwnerNodeIdsForUsername('node1')).includes(destination.allowed_node_id)) { + throw new Error('Observer is no longer verified for node1'); + } + // Decrypt and validate the persisted identity before installing credentials. + await packetShareCredentials(destination); + await pool.query( + `INSERT INTO owner_packet_share_destinations ( + destination_id, display_name, description, broker_url, topic_template, + broker_username, broker_password_ciphertext, allowed_owner_username, + allowed_node_id, auth_mode, auth_audience, tx_mode, enabled + ) VALUES ('scotmesh', 'ScotMesh', $1, $2, $3, $4, $5, 'node1', $6, 'meshcore_jwt', $7, 'advert', TRUE) + ON CONFLICT (destination_id) DO UPDATE SET + display_name = EXCLUDED.display_name, description = EXCLUDED.description, + broker_url = EXCLUDED.broker_url, topic_template = EXCLUDED.topic_template, + broker_username = EXCLUDED.broker_username, broker_password_ciphertext = EXCLUDED.broker_password_ciphertext, + allowed_owner_username = EXCLUDED.allowed_owner_username, allowed_node_id = EXCLUDED.allowed_node_id, + auth_mode = EXCLUDED.auth_mode, auth_audience = EXCLUDED.auth_audience, + tx_mode = EXCLUDED.tx_mode, enabled = TRUE, updated_at = NOW()`, + [destination.description, destination.broker_url, destination.topic_template, + destination.broker_username, destination.broker_password_ciphertext, + destination.allowed_node_id, destination.auth_audience], + ); + console.log(JSON.stringify({ destination: 'scotmesh', owner: 'node1', nodeId: destination.allowed_node_id, iata: 'MME' })); + } finally { + await Promise.all([pool.end(), closeOwnerAuthDb()]); + } +} + +main().catch((error: unknown) => { + console.error('[scotmesh] provisioning failed:', error instanceof Error ? error.message : error); + process.exitCode = 1; +}); diff --git a/backend/src/tools/rebuildRegionParticipation.ts b/backend/src/tools/rebuildRegionParticipation.ts new file mode 100644 index 0000000..c01e105 --- /dev/null +++ b/backend/src/tools/rebuildRegionParticipation.ts @@ -0,0 +1,405 @@ +/** + * Rebuild the region-scope participation tables. + * + * For every distinct transport-routed (route 0/3) packet in the window: + * - decode the region via the candidate-name transport-code probe + * - collect relay path elements (union across ALL observation rows) + * - resolve elements to nodes (unique prefix match only; drop ambiguous) + * Then tally node -> region -> distinct packets, plus unscoped relay evidence + * (route 1, multibyte, 7d) for the green/red classification, and refresh: + * region_scope_stats, region_scope_members, repeater_scope_status, + * region_scope_run + * + * Membership floor: a node counts as a region member only when it relayed at + * least MEMBER_MIN_SHARE_PCT (regions/memberShare.ts, currently 10%) of that + * region's scoped packets. Token relays (the odd packet out of thousands) are + * excluded from member lists, member counts and per-node region maps so the + * region shapes and lists reflect each region's actual relay pattern. + * + * Run from backend/ (dist build): node dist/tools/rebuildRegionParticipation.js + * or via: npm run regions:rebuild + * + * Env: + * DATABASE_URL required (same as the backend) + * REGION_SCOPE_WINDOW_DAYS scoped window in days (default 92, 7..365) + * MESHCORE_REGION_NAMES extra probe names (comma-separated) + * MESHCORE_CHANNEL_SECRETS extra channel secrets for the decoder keyStore + * REGION_SCOPE_DRY_RUN=1 compute + log but do not write + * + * All four tables are refreshed wholesale inside ONE transaction (delete + + * insert), so readers never observe an empty or partial state. If the scoped + * corpus is non-empty but nothing decodes, the tool aborts without writing + * (a broken probe list must never wipe good data). + */ + +import pg from 'pg'; +import { MeshCoreDecoder } from '@michaelhart/meshcore-decoder'; +import { decodePacketCompat } from '../mqtt/decodePacket.js'; +import { MEMBER_MIN_SHARE_PCT, meetsMemberShare } from '../regions/memberShare.js'; +import { regionProbeNamesWithEnv } from '../regions/regionNames.js'; +import { decodeRegionForPacket, transportCode0Of } from '../regions/scopeDecode.js'; + +const { Pool } = pg; + +// Documented MeshCore public channel material, not an application credential. +const DEFAULT_PUBLIC_CHANNEL_KEY = '8b3387e9c5cdea6ac9e5edbaa115cd72'; +const DEFAULT_WINDOW_DAYS = 92; +const UNRESOLVED_ELEMENT_LOG_LIMIT = 12; + +interface ScopedPacketRow { + packet_hash: string; + t0: Date; + t1: Date; + raw: string | null; + elems: string[] | null; +} + +interface NodeMetaRow { + node_id: string; + role: number | null; +} + +interface UnscopedElementRow { + e: string; + c: string; +} + +function boundedWindowDays(raw: string | undefined): number { + const value = Number(raw ?? DEFAULT_WINDOW_DAYS); + if (!Number.isFinite(value)) return DEFAULT_WINDOW_DAYS; + return Math.max(7, Math.min(365, Math.trunc(value))); +} + +function buildChannelSecrets(): string[] { + const secrets = [DEFAULT_PUBLIC_CHANNEL_KEY]; + for (const entry of (process.env['MESHCORE_CHANNEL_SECRETS'] ?? '') + .split(',').map((value) => value.trim()).filter(Boolean)) { + const colon = entry.indexOf(':'); + secrets.push(colon > 0 ? entry.slice(colon + 1) : entry); + } + return secrets; +} + +function resolveElement(prefixIndex: Map, element: string): string | null { + const normalized = element.trim().toUpperCase(); + if (!/^[0-9A-F]{2,6}$/.test(normalized)) return null; + const candidates = prefixIndex.get(normalized); + if (!candidates || candidates.length === 0) return null; + const hits = candidates.filter((candidate) => candidate.startsWith(normalized)); + return hits.length === 1 ? hits[0] : null; +} + +async function main(): Promise { + if (!process.env['DATABASE_URL']) throw new Error('DATABASE_URL is required'); + const windowDays = boundedWindowDays(process.env['REGION_SCOPE_WINDOW_DAYS']); + const dryRun = process.env['REGION_SCOPE_DRY_RUN'] === '1'; + const probeNames = regionProbeNamesWithEnv(process.env['MESHCORE_REGION_NAMES']); + const keyStore = MeshCoreDecoder.createKeyStore({ channelSecrets: buildChannelSecrets() }); + + const pool = new Pool({ + connectionString: process.env['DATABASE_URL'], + application_name: 'meshcore-regions-rebuild', + max: 2, + statement_timeout: 0, + query_timeout: 0, + }); + + try { + const startedAt = Date.now(); + console.log(`[regions] probe list: ${probeNames.length} names; window ${windowDays}d${dryRun ? ' (dry run)' : ''}`); + + const { rows: scopedRows } = await pool.query( + `SELECT p.packet_hash, + min(p.time) AS t0, + max(p.time) AS t1, + (array_agg(p.raw_hex ORDER BY p.time DESC) + FILTER (WHERE p.route_type IN (0, 3) AND p.raw_hex <> ''))[1] AS raw, + (array_agg(DISTINCT e) FILTER (WHERE e IS NOT NULL))[1:80] AS elems + FROM packets p + LEFT JOIN LATERAL unnest(p.path_hashes) AS e ON TRUE + WHERE p.route_type IN (0, 3) + AND p.time > now() - make_interval(days => $1) + GROUP BY p.packet_hash`, + [windowDays], + ); + console.log(`[regions] scoped packets: ${scopedRows.length} (${Math.round((Date.now() - startedAt) / 1000)}s)`); + if (scopedRows.length === 0) throw new Error('no scoped packets found in window; refusing to wipe tables'); + + const regionTotals = new Map(); + const regionFirstSeen = new Map(); + const regionLastSeen = new Map(); + const packetRegion = new Map(); + let decodedCount = 0; + for (const row of scopedRows) { + let region: string | null = null; + if (row.raw) { + const compat = decodePacketCompat(row.raw, keyStore); + if (compat.metadataValid && compat.transportCodes) { + const tc0 = transportCode0Of(compat.transportCodes); + region = decodeRegionForPacket( + probeNames, + compat.decoded?.payloadType, + compat.decoded?.payload?.raw, + tc0, + ); + } + } + packetRegion.set(row.packet_hash, region); + if (region) { + decodedCount += 1; + regionTotals.set(region, (regionTotals.get(region) ?? 0) + 1); + const first = regionFirstSeen.get(region); + if (!first || row.t0 < first) regionFirstSeen.set(region, row.t0); + const last = regionLastSeen.get(region); + if (!last || row.t1 > last) regionLastSeen.set(region, row.t1); + } + } + const unknownCount = scopedRows.length - decodedCount; + console.log(`[regions] decoded ${decodedCount}/${scopedRows.length} (${unknownCount} unknown)`); + if (decodedCount === 0) throw new Error('no scoped packets decoded; refusing to wipe tables'); + + const { rows: nodeRows } = await pool.query( + 'SELECT node_id, role FROM nodes', + ); + const nodeRoles = new Map(); + const prefixIndex = new Map(); + for (const row of nodeRows) { + const nodeId = (row.node_id ?? '').trim().toUpperCase(); + if (!/^[0-9A-F]+$/.test(nodeId)) continue; + nodeRoles.set(nodeId, row.role); + for (const length of [2, 4, 6]) { + const key = nodeId.slice(0, length); + const list = prefixIndex.get(key); + if (list) list.push(nodeId); + else prefixIndex.set(key, [nodeId]); + } + } + + const scopedByNode = new Map>>(); + const scopedTotal = new Map>(); + const elementStats = new Map(); + const unresolvedSamples: string[] = []; + for (const row of scopedRows) { + const region = packetRegion.get(row.packet_hash) ?? null; + for (const element of row.elems ?? []) { + if (!element) continue; + const lengthKey = `elem_len_${element.length}`; + elementStats.set(lengthKey, (elementStats.get(lengthKey) ?? 0) + 1); + const nodeId = resolveElement(prefixIndex, element); + if (!nodeId) { + const unresolvedKey = `unresolved_len_${element.length}`; + elementStats.set(unresolvedKey, (elementStats.get(unresolvedKey) ?? 0) + 1); + if (unresolvedSamples.length < UNRESOLVED_ELEMENT_LOG_LIMIT) unresolvedSamples.push(element); + continue; + } + let total = scopedTotal.get(nodeId); + if (!total) { + total = new Set(); + scopedTotal.set(nodeId, total); + } + total.add(row.packet_hash); + if (region) { + let byRegion = scopedByNode.get(nodeId); + if (!byRegion) { + byRegion = new Map>(); + scopedByNode.set(nodeId, byRegion); + } + let hashes = byRegion.get(region); + if (!hashes) { + hashes = new Set(); + byRegion.set(region, hashes); + } + hashes.add(row.packet_hash); + } + } + } + console.log(`[regions] element stats: ${JSON.stringify(Object.fromEntries([...elementStats.entries()].sort()))}`); + + const { rows: unscopedRows } = await pool.query( + `SELECT e, count(DISTINCT p.packet_hash) AS c + FROM packets p, unnest(p.path_hashes) AS e + WHERE p.route_type = 1 + AND p.path_hash_size_bytes >= 2 + AND p.time > now() - interval '7 days' + AND length(e) >= 4 + GROUP BY e`, + ); + const unscoped = new Map(); + for (const row of unscopedRows) { + if (!row.e) continue; + const nodeId = resolveElement(prefixIndex, row.e); + if (nodeId) unscoped.set(nodeId, (unscoped.get(nodeId) ?? 0) + Number(row.c)); + } + console.log(`[regions] unscoped relay evidence (7d): ${unscoped.size} nodes, ${[...unscoped.values()].reduce((sum, value) => sum + value, 0)} relay counts`); + + // Region view (members = relays with resolved ids carrying at least + // MEMBER_MIN_SHARE_PCT of the region's scoped packets). + interface MemberOutput { + region: string; + nodeId: string; + packets: number; + role: number | null; + } + const members: MemberOutput[] = []; + const regionView = new Map(); + let memberRowsTotal = 0; + for (const [nodeId, byRegion] of scopedByNode) { + for (const [region, hashes] of byRegion) { + memberRowsTotal += 1; + if (!meetsMemberShare(hashes.size, regionTotals.get(region) ?? 0)) continue; + const role = nodeRoles.get(nodeId) ?? null; + members.push({ region, nodeId, packets: hashes.size, role }); + const view = regionView.get(region) ?? { memberCount: 0, repeaterMemberCount: 0 }; + view.memberCount += 1; + if (role === 2) view.repeaterMemberCount += 1; + regionView.set(region, view); + } + } + console.log(`[regions] member share floor >=${MEMBER_MIN_SHARE_PCT}%: kept ${members.length}/${memberRowsTotal} member rows`); + + // Node classification over every node with any relay evidence. + interface ClassificationOutput { + nodeId: string; + status: 'green' | 'red'; + evidence: 'scoped' | 'unscoped-only' | 'none'; + scopedPackets: number; + unscopedPackets7d: number; + regions: Record; + } + const classification: ClassificationOutput[] = []; + const candidateIds = new Set([...scopedTotal.keys(), ...unscoped.keys()]); + for (const nodeId of candidateIds) { + const scoped = scopedTotal.get(nodeId); + const byRegion = scopedByNode.get(nodeId); + const regions: Record = {}; + if (byRegion) { + for (const [region, hashes] of [...byRegion.entries()].sort((a, b) => b[1].size - a[1].size)) { + if (!meetsMemberShare(hashes.size, regionTotals.get(region) ?? 0)) continue; + regions[region] = hashes.size; + } + } + classification.push({ + nodeId, + status: scoped ? 'green' : 'red', + evidence: scoped ? 'scoped' : (unscoped.get(nodeId) ?? 0) > 0 ? 'unscoped-only' : 'none', + scopedPackets: scoped ? scoped.size : 0, + unscopedPackets7d: unscoped.get(nodeId) ?? 0, + regions, + }); + } + + const greenCount = classification.filter((entry) => entry.status === 'green').length; + const redCount = classification.length - greenCount; + console.log(`[regions] regions (scoped packets -> members >=${MEMBER_MIN_SHARE_PCT}%):`); + for (const [region, total] of [...regionTotals.entries()].sort((a, b) => b[1] - a[1])) { + const view = regionView.get(region) ?? { memberCount: 0, repeaterMemberCount: 0 }; + console.log(` ${region}: packets=${total} members=${view.memberCount} repeaters=${view.repeaterMemberCount}`); + } + console.log(`[regions] classification: green=${greenCount} red=${redCount}`); + if (unresolvedSamples.length > 0) { + console.log(`[regions] unresolved element samples: ${unresolvedSamples.join(', ')}`); + } + + if (dryRun) { + console.log(JSON.stringify({ + status: 'dry-run', + scopedPackets: scopedRows.length, + decodedPackets: decodedCount, + regions: regionTotals.size, + members: members.length, + classification: classification.length, + })); + return; + } + + const client = await pool.connect(); + try { + await client.query('BEGIN'); + await client.query('DELETE FROM region_scope_members'); + await client.query('DELETE FROM region_scope_stats'); + await client.query('DELETE FROM repeater_scope_status'); + await client.query('DELETE FROM region_scope_run'); + + const regionNames = [...regionTotals.keys()]; + await client.query( + `INSERT INTO region_scope_stats + (region, packets_total, member_count, repeater_member_count, first_seen, last_seen) + SELECT region, packets_total, member_count, repeater_member_count, first_seen, last_seen + FROM unnest($1::text[], $2::int[], $3::int[], $4::int[], $5::timestamptz[], $6::timestamptz[]) + AS t(region, packets_total, member_count, repeater_member_count, first_seen, last_seen)`, + [ + regionNames, + regionNames.map((region) => regionTotals.get(region) ?? 0), + regionNames.map((region) => regionView.get(region)?.memberCount ?? 0), + regionNames.map((region) => regionView.get(region)?.repeaterMemberCount ?? 0), + regionNames.map((region) => regionFirstSeen.get(region) ?? null), + regionNames.map((region) => regionLastSeen.get(region) ?? null), + ], + ); + + const MEMBER_BATCH = 2_000; + for (let offset = 0; offset < members.length; offset += MEMBER_BATCH) { + const batch = members.slice(offset, offset + MEMBER_BATCH); + await client.query( + `INSERT INTO region_scope_members (region, node_id, packets) + SELECT region, node_id, packets + FROM unnest($1::text[], $2::text[], $3::int[]) AS t(region, node_id, packets)`, + [ + batch.map((member) => member.region), + batch.map((member) => member.nodeId), + batch.map((member) => member.packets), + ], + ); + } + + const STATUS_BATCH = 2_000; + for (let offset = 0; offset < classification.length; offset += STATUS_BATCH) { + const batch = classification.slice(offset, offset + STATUS_BATCH); + await client.query( + `INSERT INTO repeater_scope_status + (node_id, status, evidence, scoped_packets, unscoped_packets_7d, regions) + SELECT node_id, status, evidence, scoped_packets, unscoped_packets_7d, regions::jsonb + FROM unnest($1::text[], $2::text[], $3::text[], $4::int[], $5::int[], $6::text[]) + AS t(node_id, status, evidence, scoped_packets, unscoped_packets_7d, regions)`, + [ + batch.map((entry) => entry.nodeId), + batch.map((entry) => entry.status), + batch.map((entry) => entry.evidence), + batch.map((entry) => entry.scopedPackets), + batch.map((entry) => entry.unscopedPackets7d), + batch.map((entry) => JSON.stringify(entry.regions)), + ], + ); + } + + await client.query( + `INSERT INTO region_scope_run (id, finished_at, window_days, scoped_packets, decoded_packets) + VALUES (TRUE, now(), $1, $2, $3)`, + [windowDays, scopedRows.length, decodedCount], + ); + await client.query('COMMIT'); + } catch (error) { + await client.query('ROLLBACK').catch(() => undefined); + throw error; + } finally { + client.release(); + } + + console.log(JSON.stringify({ + status: 'complete', + scopedPackets: scopedRows.length, + decodedPackets: decodedCount, + regions: regionTotals.size, + members: members.length, + classification: classification.length, + elapsedSeconds: Math.round((Date.now() - startedAt) / 1000), + })); + } finally { + await pool.end(); + } +} + +main().catch((error: unknown) => { + console.error('[regions] failed:', error instanceof Error ? error.message : error); + process.exit(1); +}); diff --git a/backend/src/ws/server.ts b/backend/src/ws/server.ts index 264f292..f0ef7b3 100644 --- a/backend/src/ws/server.ts +++ b/backend/src/ws/server.ts @@ -114,6 +114,8 @@ function slimInitialPacket(raw: InitialStateRow): InitialStateRow { path_hash_size_bytes: raw['path_hash_size_bytes'], rx_count: raw['rx_count'], tx_count: raw['tx_count'], + tags: raw['tags'], + tag_confidence: raw['tag_confidence'], }; } diff --git a/backup-receipts/latest.json b/backup-receipts/latest.json index d589130..b238c87 100644 --- a/backup-receipts/latest.json +++ b/backup-receipts/latest.json @@ -1,14 +1,14 @@ { "format": "meshcore-restore-receipt-v1", - "receipt_id": "restore-20260813T032656Z-20260813T031400Z-2c1dd39b7173", - "backup_id": "backup-20260813T031400Z-2c1dd39b7173", - "backup_completed_at": "2026-08-13T03:18:09.000Z", - "restore_started_at": "2026-08-13T03:20:02.000Z", - "restore_verified_at": "2026-08-13T03:26:56.000Z", - "restore_duration_seconds": 414, - "recovery_point_age_seconds": 527, - "archive_sha256": "6466077779be65d3f843843b3fbf57cd33abad671391c5744e93d3a0fe1911a7", - "source_revision": "2c1dd39b7173e2cb97164a6e9dbe9e1f600b6aaa", + "receipt_id": "restore-20260906T181708Z-20260906T173725Z-1eefb50b31fd", + "backup_id": "backup-20260906T173725Z-1eefb50b31fd", + "backup_completed_at": "2026-09-06T17:45:59.000Z", + "restore_started_at": "2026-09-06T18:06:24.000Z", + "restore_verified_at": "2026-09-06T18:17:08.000Z", + "restore_duration_seconds": 644, + "recovery_point_age_seconds": 1869, + "archive_sha256": "310356e22c0e260e78556efcbfd0a520e6dd2c4fc74c461e40460f3a8f0a550d", + "source_revision": "1eefb50b31fda8d79ef383a658764671c65cb46a", "schema_version": 51, "datasets": [ "analytics", @@ -21,11 +21,11 @@ "evidence": { "integrity": { "packet_sample_rows": 1000, - "packet_sample_md5": "d975a0fd34eee068df4eabf80f7d52ae", + "packet_sample_md5": "6dee09805b37d0fa114aecf0db10fac6", "node_sample_rows": 1000, - "node_sample_md5": "5d1a8fea57b96209ad404739fbaa6628" + "node_sample_md5": "9754a27fe3280d2c578dd75fdfaa2300" }, - "owner_lookup_count": 47 + "owner_lookup_count": 50 }, "checks": { "migrations": "passed", diff --git a/backup-receipts/latest.json.sig b/backup-receipts/latest.json.sig index b26b6742ee9cf0e645cc89fdcce237830e28ba51..786afe379449e1ba5685abd78b7f86108b6fc27b 100644 GIT binary patch literal 384 zcmV-`0e}7|(w-gDQs+^{YOMWYZGpY#ZNr6oQ>}K|Y1MgU8GK$&swN(~4fMI|5ynq? zFl_c3)9u;vH?djE#G)N~0=8&#yt*&x{Z&^ zv^f|)vRG$IV=ar>>$t=u(@?rw74+ zY6yi_@Dywzkba}(Iyso5F-%ZB?IlLU5|s7T3UcG_w+Faul)x_};LL^c?6=Qk_RNfy z-LO!SU^@=DV+GwnF5i%CX&JRELV-t@gG@Dqz+z^}3yP9cJN|SQ*_Rw`C%cZKX|Y20 z$ea(|cj-Gth#(apT^Sn=Bn}U!NCS9r_s8EwF|N&K7nIP_68L=sv{cKuMLnt1G%Frm?8 zw@MH>@G@(Qxh3E^Bt*+bgpjfCxR{fg4kAWZ6SkZse7ertUkYHkzYr2*I diff --git a/docker-compose.yml b/docker-compose.yml index 0fd9f4d..22b7b78 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -76,24 +76,36 @@ services: MQTT_USERNAME: ${MQTT_USERNAME:-backend} MQTT_PASSWORD: ${MQTT_PASSWORD} MQTT_CLIENT_ID: ${MQTT_CLIENT_ID:-meshcore-analytics-ingest} + MESHCORE_SITE_ID: ${MESHCORE_SITE_ID:-ukmesh} REDIS_URL: ${REDIS_URL:-redis://redis:6379} REDIS_PASSWORD: ${REDIS_PASSWORD:?REDIS_PASSWORD required} JWT_SECRET: ${JWT_SECRET:?JWT_SECRET required} PORT: ${PORT:-3000} METRICS_PORT: ${METRICS_PORT:-9091} MESHCORE_CHANNEL_SECRETS: ${MESHCORE_CHANNEL_SECRETS:-} + MESHCORE_REGION_NAMES: ${MESHCORE_REGION_NAMES:-eng-ne,Europe,Global} OWNER_DATABASE_URL: ${OWNER_DATABASE_URL:-postgresql://${POSTGRES_USER:-meshcore}:${POSTGRES_PASSWORD}@timescaledb:5432/${OWNER_POSTGRES_DB:-meshcore_owner_auth}} OWNER_MQTT_USERNAME_MAP: ${OWNER_MQTT_USERNAME_MAP:-} OWNER_AUTHORIZATION_MODE: ${OWNER_AUTHORIZATION_MODE:-shadow} OWNER_ACL_MODE: ${OWNER_ACL_MODE:-shadow} OWNER_ACL_UNMANAGED_USERS: ${OWNER_ACL_UNMANAGED_USERS:-backend,test,test2} OWNER_ACL_ALLOW_EMPTY_USERS: ${OWNER_ACL_ALLOW_EMPTY_USERS:-} + OWNER_ACL_PUBLIC_FEED_READ_USERS: ${OWNER_ACL_PUBLIC_FEED_READ_USERS:-} + OWNER_ACL_SYSTEM_READWRITE_TOPICS: ${OWNER_ACL_SYSTEM_READWRITE_TOPICS:-public/#} OWNER_AUTH_INVENTORY_BASELINE_PATH: ${OWNER_AUTH_INVENTORY_BASELINE_PATH:-} OWNER_ACL_SHADOW_PATH: /tmp/meshcore-owner-acl.shadow MOSQUITTO_ACL_PATH: /mosquitto/config/acl OWNER_ACL_RELOAD_URL: http://mosquitto-reloader:8080/reload OWNER_ACL_RELOAD_TOKEN: ${JWT_SECRET:?JWT_SECRET required} OWNER_COOKIE_SECRET: ${OWNER_COOKIE_SECRET:-} + OWNER_PACKET_SHARE_ENCRYPTION_KEY: ${OWNER_PACKET_SHARE_ENCRYPTION_KEY:-} + OWNER_PACKET_SHARE_POLL_INTERVAL_MS: ${OWNER_PACKET_SHARE_POLL_INTERVAL_MS:-5000} + OWNER_PACKET_SHARE_BATCH_SIZE: ${OWNER_PACKET_SHARE_BATCH_SIZE:-25} + # The public feed is the authentication-locked UKmesh Mosquitto endpoint. + # The URL is display/configuration metadata; packets already flow through + # this broker, and opted-in owners receive a read-only ACL grant. + PUBLIC_FEED_PUBLISH_ENABLED: ${PUBLIC_FEED_PUBLISH_ENABLED:-false} + PUBLIC_FEED_BROKER_URL: ${PUBLIC_FEED_BROKER_URL:-} OPERATOR_SITE_TOKEN: ${OPERATOR_SITE_TOKEN:?OPERATOR_SITE_TOKEN required} TRUSTED_PROXY_PEERS: ${TRUSTED_PROXY_PEERS:-172.30.0.10,172.30.0.11,172.30.0.12} ALLOWED_ORIGINS: ${ALLOWED_ORIGINS:-http://localhost:3003,http://localhost:3004,https://ukmesh.com,https://www.ukmesh.com,https://app.ukmesh.com,https://test.ukmesh.com} @@ -272,7 +284,7 @@ services: PRIVACY_REMAT_MAX_ATTEMPTS: ${PRIVACY_REMAT_MAX_ATTEMPTS:-8} HEALTH_VOLUME_PATHS: database=/health-volumes/database=${HEALTH_DATABASE_BUDGET_BYTES:-322122547200},redis=/health-volumes/redis=${HEALTH_REDIS_BUDGET_BYTES:-4294967296},terrain=/health-volumes/terrain=${HEALTH_TERRAIN_BUDGET_BYTES:-53687091200},logs=/health-volumes/logs=${HEALTH_LOG_BUDGET_BYTES:-21474836480} DATA_LIFECYCLE_RETENTION_ENABLED: "true" - DATA_LIFECYCLE_RETENTION_TARGETS: ${DATA_LIFECYCLE_RETENTION_TARGETS:-packets,worker_health_snapshots,frontend_error_events,owner_alert_deliveries,operational_check_results,observer_region_packet_sightings,observer_region_observer_sightings,link_job_commits,ml_model_variant_packet_results,packet_decryptions,observer_registration_requests,operator_audit_events} + DATA_LIFECYCLE_RETENTION_TARGETS: ${DATA_LIFECYCLE_RETENTION_TARGETS:-packets,worker_health_snapshots,frontend_error_events,owner_alert_deliveries,owner_packet_share_deliveries,public_feed_deliveries,operational_check_results,observer_region_packet_sightings,observer_region_observer_sightings,link_job_commits,ml_model_variant_packet_results,packet_decryptions,observer_registration_requests,operator_audit_events} NODE_ENV: production depends_on: db-migrate: @@ -283,6 +295,29 @@ services: - srtm_data:/health-volumes/terrain:ro - loki_data:/health-volumes/logs:ro + # Tags new public-channel messages with TypeSafe/Jev (shadow-mode trial, + # added 2026-09-17 by Hermes for Ben). Results land in message_tags; nothing + # user-visible reads it yet. Monitoring: tagger_state + container logs. + tagger-worker: + <<: [*internal-hosts, *application-hardening] + image: ${TAGGER_WORKER_IMAGE:-meshcore-analytics-tagger-worker:local} + build: + context: ./tagger-worker + dockerfile: Dockerfile + restart: always + logging: *json-log-limits + mem_limit: "256m" + memswap_limit: "256m" + cpus: 1.0 + environment: + DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@timescaledb:5432/${POSTGRES_DB:-meshcore} + DATABASE_APPLICATION_NAME: ukmesh-tagger-worker + TYPESAFE_API_KEY: ${TYPESAFE_API_KEY:-} + TAGGER_START: ${TAGGER_START:-} + TAGGER_POLL_S: ${TAGGER_POLL_S:-5} + TAGGER_CALL_FLOOR_S: ${TAGGER_CALL_FLOOR_S:-0.5} + PYTHONDONTWRITEBYTECODE: "1" + privacy-remat-worker: <<: [*internal-hosts, *application-hardening] image: ${BACKEND_IMAGE:-meshcore-analytics-backend:local} @@ -573,6 +608,7 @@ services: MQTT_WS_PATH: / MQTT_TLS: "false" DASH_BROKER_HOST: ${HEALTHCHECK_DASH_BROKER_HOST:-mqtt.ukmesh.com} + WEBSOCKET_ALLOWED_ORIGINS: ${HEALTHCHECK_WEBSOCKET_ALLOWED_ORIGINS:-https://healthcheck.ukmesh.com} TEST_CHANNEL_NAME: ${HEALTHCHECK_TEST_CHANNEL_NAME:-ukmeshtest} TEST_CHANNEL_SECRET: ${HEALTHCHECK_TEST_CHANNEL_SECRET:-} TEST_CHANNEL_SECRET_SOURCE_NAME: ${HEALTHCHECK_TEST_CHANNEL_SECRET_SOURCE_NAME:-test} @@ -935,7 +971,7 @@ services: volumes: - alert_receiver_data:/var/lib/meshcore-alerts healthcheck: - test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8080/healthz >/dev/null"] + test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8080/readyz >/dev/null"] interval: 30s timeout: 5s retries: 5 diff --git a/docs/db-lifecycle.md b/docs/db-lifecycle.md index 68133eb..bee4067 100644 --- a/docs/db-lifecycle.md +++ b/docs/db-lifecycle.md @@ -59,7 +59,7 @@ Content-bearing packets have a hard 30-day bound. Timescale drops complete chunks, while the health worker deletes exact expired rows from the partial boundary chunk in deterministic 25,000-row batches; new packet chunks are one day. Content-free path reconstruction reads `packet_paths`, so it continues -after the source packet expires. Status samples retain 180 days and neighbor +after the source packet expires. Status samples retain 30 days and neighbor samples seven days. Privacy-filtered aggregates, model parameters, current node/link/coverage state, and privacy state remain longer lived. @@ -73,100 +73,44 @@ It reports exact expired rows, expired/compressed Timescale chunks, relation bytes, oldest/newest timestamps, and the affected features for every target. The inventory can be expensive by design; run it away from peak ingest. -Compression and retention are separate, table-at-a-time changes. Migration -050 declares Timescale retention policies for packets, status samples, and -neighbour samples. The base schema previously removed the packet policy during -schema initialization; it no longer does. If a policy is absent, use the -receipt-gated apply command below after verifying a named backup and completing -an isolated restore drill. Compression remains a separate receipt-gated -operator action. The application fails startup when an enabled core lifecycle -action omits a required target. - -Each gated per-table apply command requires: +Compression and retention are separate, table-at-a-time changes. Both require: - a named, fresh database backup; -- a signed restore receipt verified within the configured receipt age (seven - days by default); +- a successful isolated restore verification from the last 30 days; - the target in `DATA_LIFECYCLE_RETENTION_TARGETS` for deletion or `DATA_LIFECYCLE_COMPRESSION_TARGETS` for compression; - the action flag set to `true`; and - an exact per-table approval argument. -The receipt path, detached signature, and verification key are mounted into -the backend as `DATA_LIFECYCLE_RESTORE_RECEIPT_PATH`, -`DATA_LIFECYCLE_RESTORE_RECEIPT_SIGNATURE_PATH`, and -`DATA_LIFECYCLE_RECEIPT_VERIFY_KEY_PATH`. Confirm those files are from the -completed isolated restore drill before proceeding. The signed receipt contains -the backup identifier and restore checks. - -First inspect the read-only inventory and record the named backup and restore -receipt for the change: +Example compression rollout: ```bash -docker compose exec backend npm run db:lifecycle -``` - -After approval, enable the reviewed core retention windows with one explicit -command per table. The packet content window is 30 days in the current -repository policy; status telemetry is 180 days and neighbour telemetry is -seven days. `packet_paths` is never a retention target: - -```bash -docker compose exec -e DATA_LIFECYCLE_RETENTION_ENABLED=true backend npm run db:lifecycle -- \ - --apply-retention --target=packets \ - --approve=apply-data-lifecycle-retention-packets -docker compose exec -e DATA_LIFECYCLE_RETENTION_ENABLED=true backend npm run db:lifecycle -- \ - --apply-retention --target=node_status_samples \ - --approve=apply-data-lifecycle-retention-node_status_samples -docker compose exec -e DATA_LIFECYCLE_RETENTION_ENABLED=true backend npm run db:lifecycle -- \ - --apply-retention --target=node_neighbor_samples \ - --approve=apply-data-lifecycle-retention-node_neighbor_samples -``` - -Apply compression per table after checking the inventory and database/WAL -headroom. The reviewed policy compresses packets, path rows, and status after -14 days, and neighbour samples after one day: - -```bash -docker compose exec -e DATA_LIFECYCLE_COMPRESSION_ENABLED=true backend npm run db:lifecycle -- \ +DATA_LIFECYCLE_COMPRESSION_ENABLED=true +DATA_LIFECYCLE_COMPRESSION_TARGETS=packets,packet_paths,node_status_samples,node_neighbor_samples +DATA_LIFECYCLE_BACKUP_REFERENCE=backup-20260729 +DATA_LIFECYCLE_RESTORE_VERIFIED_AT=2026-07-29T12:00:00Z +docker compose exec backend npm run db:lifecycle -- \ --apply-compression --target=packets \ --approve=apply-data-lifecycle-compression-packets -docker compose exec -e DATA_LIFECYCLE_COMPRESSION_ENABLED=true backend npm run db:lifecycle -- \ - --apply-compression --target=packet_paths \ - --approve=apply-data-lifecycle-compression-packet_paths -docker compose exec -e DATA_LIFECYCLE_COMPRESSION_ENABLED=true backend npm run db:lifecycle -- \ - --apply-compression --target=node_status_samples \ - --approve=apply-data-lifecycle-compression-node_status_samples -docker compose exec -e DATA_LIFECYCLE_COMPRESSION_ENABLED=true backend npm run db:lifecycle -- \ - --apply-compression --target=node_neighbor_samples \ - --approve=apply-data-lifecycle-compression-node_neighbor_samples ``` -Measure query CPU, ingest WAL, and storage after cold-chunk compression. Check -the read-only inventory and Prometheus chunk/byte gauges after each change. -Set the production configuration flags only after every required core target -is present; missing targets make the application fail closed. +Measure query CPU, ingest WAL, and storage after cold-chunk compression. Only +after aggregate cutover and another inventory may retention be enabled: -## Expired forensic data - -Expired packet content, status samples, and neighbour history are not served by -the production application. For forensic access, restore a named backup that -predates the expiry into an isolated restore environment, keep the database and -application read-only, disable MQTT ingest and all background writers, and -limit access to the authorized investigation. Never restore old data over the -production database or reconnect an isolated forensic copy to public APIs. -The backup and restore workflow is documented in -[`runbook-backup-restore.md`](runbook-backup-restore.md). - -Prometheus tracks retained hypertable bytes/chunks, expired chunks, -compression-overdue chunks, and host free-space trends. Follow the matching -first-response steps in [`operations.md`](operations.md) for growth, -retention, compression, or disk exhaustion alerts. +```bash +docker compose exec backend npm run db:lifecycle -- \ + --apply-retention --target=packets \ + --approve=apply-data-lifecycle-retention-packets +``` Hypertable retention uses a Timescale policy. Row-table deletion is bounded and performed by the health worker only for targets explicitly listed in `DATA_LIFECYCLE_RETENTION_TARGETS`. Failed/pending owner alert deliveries are -not discarded while they remain retryable. +not discarded while they remain retryable. Owner packet-sharing deliveries +follow the same rule and retain their bounded relay payload for 30 days after +they reach a terminal status. +Public-feed delivery rows follow the same rule and retain their bounded public +packet envelope for 30 days after reaching a terminal status. Compression state is migration-defined: `packets`, `packet_paths`, and `node_status_samples` compress after 14 days (matching the reviewed live diff --git a/docs/decryption.md b/docs/decryption.md index e6830c2..daff654 100644 --- a/docs/decryption.md +++ b/docs/decryption.md @@ -6,20 +6,16 @@ how historical traffic was retroactively decrypted. ## Key store - `backend/src/mqtt/channelRegistry.ts` is the single source of truth: - - `PUBLIC_CHANNELS` — 41 recovered/community-known values intentionally - classified as public and baked into the image. Each was validated against - real human-readable UK Mesh group text. + - `VALIDATED_CHANNELS` — default secrets baked into the image (39 entries + incl. Public; recovered 2026-08-06 and validated to decrypt real + human-readable group text). - `buildCombinedKeyStore()` — merges baked defaults with - `MESHCORE_CHANNEL_SECRETS` (managed environment configuration, with - comma-separated `name:hex` or bare hex entries; dedupes by secret). - Environment-supplied entries are classified as confidential; never commit - their values or copy them into `PUBLIC_CHANNELS`. + `MESHCORE_CHANNEL_SECRETS` (env, comma-separated `name:hex` or bare hex; + dedupes by secret) for secrets that shouldn't be committed. - `buildSummary()` / `identifyChannel()` — shared by ingest and offline tools. -- The repo is **public**. `PUBLIC_CHANNELS` is the explicit source classification; - the scanner baseline in `.gitleaksignore` records exact historical - commit/path/rule/line fingerprints for reviewed public values, test fixtures, - and generated revision identifiers. New findings remain blocking by default. +- ⚠️ The repo is **public**: keys in `channelRegistry.ts` are public. Keep + non-derivable/community keys in the env var only. - Reading env at startup only — after a change: `docker compose -f docker-compose.yml -f docker-compose.live.yml up -d --no-deps --force-recreate backend`. diff --git a/docs/openapi.yaml b/docs/openapi.yaml index b68b273..91afe4b 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -3822,6 +3822,421 @@ } } }, + "/api/owner/public-feed": { + "get": { + "summary": "Read owner public-feed settings", + "operationId": "get-owner-public-feed", + "tags": [ + "owner" + ], + "x-access": "owner", + "parameters": [], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "featureEnabled", + "configured", + "brokerUrl", + "enabled", + "nodeCount", + "eligible", + "recentPacketCount", + "topicFilters", + "lastForwardedAt" + ], + "properties": { + "featureEnabled": { + "type": "boolean" + }, + "configured": { + "type": "boolean" + }, + "brokerUrl": { + "type": [ + "string", + "null" + ], + "format": "uri", + "maxLength": 2048 + }, + "enabled": { + "type": "boolean" + }, + "nodeCount": { + "type": "integer", + "minimum": 0 + }, + "eligible": { + "type": "boolean" + }, + "recentPacketCount": { + "type": "integer", + "minimum": 0 + }, + "topicFilters": { + "type": "array", + "maxItems": 1, + "items": { + "type": "string", + "pattern": "^public/\\+/\\+/packets$" + } + }, + "lastForwardedAt": { + "type": [ + "string", + "null" + ], + "format": "date-time" + } + }, + "additionalProperties": false + } + } + } + }, + "400": { + "description": "Invalid or out-of-bounds request", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string" + } + }, + "additionalProperties": false + } + } + } + }, + "401": { + "description": "Authentication required", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string" + } + }, + "additionalProperties": false + } + } + } + }, + "403": { + "description": "Authentication or CSRF validation failed", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string" + } + }, + "additionalProperties": false + } + } + } + }, + "429": { + "description": "Rate limit exceeded", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string" + } + }, + "additionalProperties": false + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string" + } + }, + "additionalProperties": false + } + } + } + } + }, + "security": [ + { + "ownerSession": [] + } + ] + }, + "post": { + "summary": "Update owner public-feed settings", + "operationId": "post-owner-public-feed", + "tags": [ + "owner" + ], + "x-access": "owner", + "parameters": [], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "featureEnabled", + "configured", + "brokerUrl", + "enabled", + "nodeCount", + "eligible", + "recentPacketCount", + "topicFilters", + "lastForwardedAt" + ], + "properties": { + "featureEnabled": { + "type": "boolean" + }, + "configured": { + "type": "boolean" + }, + "brokerUrl": { + "type": [ + "string", + "null" + ], + "format": "uri", + "maxLength": 2048 + }, + "enabled": { + "type": "boolean" + }, + "nodeCount": { + "type": "integer", + "minimum": 0 + }, + "eligible": { + "type": "boolean" + }, + "recentPacketCount": { + "type": "integer", + "minimum": 0 + }, + "topicFilters": { + "type": "array", + "maxItems": 1, + "items": { + "type": "string", + "pattern": "^public/\\+/\\+/packets$" + } + }, + "lastForwardedAt": { + "type": [ + "string", + "null" + ], + "format": "date-time" + } + }, + "additionalProperties": false + } + } + } + }, + "400": { + "description": "Invalid or out-of-bounds request", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string" + } + }, + "additionalProperties": false + } + } + } + }, + "401": { + "description": "Authentication required", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string" + } + }, + "additionalProperties": false + } + } + } + }, + "403": { + "description": "Authentication or CSRF validation failed", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string" + } + }, + "additionalProperties": false + } + } + } + }, + "409": { + "description": "The public feed is not configured or the owner has no eligible packet activity", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string" + } + }, + "additionalProperties": false + } + } + } + }, + "429": { + "description": "Rate limit exceeded", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string" + } + }, + "additionalProperties": false + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string" + } + }, + "additionalProperties": false + } + } + } + }, + "503": { + "description": "The broker ACL could not be updated", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string" + } + }, + "additionalProperties": false + } + } + } + } + }, + "security": [ + { + "ownerSession": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "enabled" + ], + "properties": { + "enabled": { + "type": "boolean" + } + }, + "additionalProperties": false + } + } + } + } + } + }, "/api/owner/session": { "get": { "summary": "Read owner session", @@ -4984,6 +5399,584 @@ } } }, + "/api/regions/repeaters": { + "get": { + "summary": "List repeaters classified by region-capability", + "operationId": "get-regions-repeaters", + "tags": [ + "public" + ], + "x-access": "public", + "parameters": [], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "generatedAt", + "repeaters" + ], + "properties": { + "generatedAt": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "repeaters": { + "type": "array", + "items": { + "type": "object", + "required": [ + "nodeId", + "status", + "evidence", + "scopedPackets", + "unscopedPackets7d", + "regions" + ], + "properties": { + "nodeId": { + "type": "string", + "pattern": "^[0-9A-F]{64}$" + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "role": { + "type": [ + "integer", + "null" + ] + }, + "lat": { + "type": [ + "number", + "null" + ], + "minimum": -90, + "maximum": 90 + }, + "lon": { + "type": [ + "number", + "null" + ], + "minimum": -180, + "maximum": 180 + }, + "status": { + "type": "string", + "enum": [ + "green", + "red" + ] + }, + "evidence": { + "type": "string" + }, + "scopedPackets": { + "type": "integer", + "minimum": 0 + }, + "unscopedPackets7d": { + "type": "integer", + "minimum": 0 + }, + "regions": { + "type": "object", + "additionalProperties": { + "type": "integer", + "minimum": 0 + } + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false + } + } + } + }, + "400": { + "description": "Invalid or out-of-bounds request", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string" + } + }, + "additionalProperties": false + } + } + } + }, + "429": { + "description": "Rate limit exceeded", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string" + } + }, + "additionalProperties": false + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string" + } + }, + "additionalProperties": false + } + } + } + } + } + } + }, + "/api/regions/summary": { + "get": { + "summary": "Read per-region scoped-traffic participation summary", + "operationId": "get-regions-summary", + "tags": [ + "public" + ], + "x-access": "public", + "parameters": [], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "generatedAt", + "windowDays", + "regions" + ], + "properties": { + "generatedAt": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "windowDays": { + "type": [ + "integer", + "null" + ], + "minimum": 1, + "maximum": 365 + }, + "regions": { + "type": "array", + "items": { + "type": "object", + "required": [ + "name", + "packetsTotal", + "memberCount", + "repeaterMemberCount", + "confidence", + "members" + ], + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "packetsTotal": { + "type": "integer", + "minimum": 0 + }, + "memberCount": { + "type": "integer", + "minimum": 0 + }, + "repeaterMemberCount": { + "type": "integer", + "minimum": 0 + }, + "confidence": { + "type": "string", + "enum": [ + "confirmed", + "probable", + "single" + ] + }, + "firstSeen": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "lastSeen": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "members": { + "type": "array", + "items": { + "type": "object", + "required": [ + "nodeId", + "name", + "role", + "lat", + "lon", + "packets", + "isBridge" + ], + "properties": { + "nodeId": { + "type": "string", + "pattern": "^[0-9A-F]{64}$" + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "role": { + "type": [ + "integer", + "null" + ] + }, + "lat": { + "type": [ + "number", + "null" + ], + "minimum": -90, + "maximum": 90 + }, + "lon": { + "type": [ + "number", + "null" + ], + "minimum": -180, + "maximum": 180 + }, + "packets": { + "type": "integer", + "minimum": 0 + }, + "isBridge": { + "type": "boolean" + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false + } + } + } + }, + "400": { + "description": "Invalid or out-of-bounds request", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string" + } + }, + "additionalProperties": false + } + } + } + }, + "429": { + "description": "Rate limit exceeded", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string" + } + }, + "additionalProperties": false + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string" + } + }, + "additionalProperties": false + } + } + } + } + } + } + }, + "/api/repeaters/{id}/scope": { + "get": { + "summary": "Read one repeater's region scoping detail", + "operationId": "get-repeaters-id-scope", + "tags": [ + "public" + ], + "x-access": "public", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 200 + } + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "nodeId", + "status", + "evidence", + "scopedPackets", + "unscopedPackets7d", + "regions" + ], + "properties": { + "nodeId": { + "type": "string", + "pattern": "^[0-9A-F]{64}$" + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "role": { + "type": [ + "integer", + "null" + ] + }, + "lat": { + "type": [ + "number", + "null" + ], + "minimum": -90, + "maximum": 90 + }, + "lon": { + "type": [ + "number", + "null" + ], + "minimum": -180, + "maximum": 180 + }, + "status": { + "type": "string", + "enum": [ + "green", + "red", + "unobserved" + ] + }, + "evidence": { + "type": "string" + }, + "scopedPackets": { + "type": "integer", + "minimum": 0 + }, + "unscopedPackets7d": { + "type": "integer", + "minimum": 0 + }, + "regions": { + "type": "array", + "items": { + "type": "object", + "required": [ + "region", + "packets" + ], + "properties": { + "region": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "packets": { + "type": "integer", + "minimum": 0 + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false + } + } + } + }, + "400": { + "description": "Invalid or out-of-bounds request", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string" + } + }, + "additionalProperties": false + } + } + } + }, + "404": { + "description": "The repeater is not known to the region-scope tables", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string" + } + }, + "additionalProperties": false + } + } + } + }, + "429": { + "description": "Rate limit exceeded", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string" + } + }, + "additionalProperties": false + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string" + } + }, + "additionalProperties": false + } + } + } + } + } + } + }, "/api/repeaters/firmware": { "get": { "summary": "Read repeaters firmware", @@ -5897,6 +6890,87 @@ } } }, + "/api/tags/recent": { + "get": { + "summary": "Read tags recent", + "operationId": "get-tags-recent", + "tags": [ + "public" + ], + "x-access": "public", + "parameters": [], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "400": { + "description": "Invalid or out-of-bounds request", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string" + } + }, + "additionalProperties": false + } + } + } + }, + "429": { + "description": "Rate limit exceeded", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string" + } + }, + "additionalProperties": false + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string" + } + }, + "additionalProperties": false + } + } + } + } + } + } + }, "/api/telemetry/frontend-error": { "post": { "summary": "Create or run telemetry frontend error", diff --git a/docs/operations.md b/docs/operations.md index 4ce7fde..fac288e 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -119,7 +119,7 @@ cd backend npm run load:realtime -- --duration 30 --concurrency 25 --max-p95-ms 1500 ``` -Use `--mqtt-messages 5000` to exercise MQTT ingest backpressure with isolated, rejected test envelopes, and `--slow-ws-clients 10` to hold non-reading WebSocket clients during the run. MQTT mode uses `MQTT_BROKER_URL`, `MQTT_USERNAME`, and `MQTT_PASSWORD`; never point it at a broker outside the deployment under test. +Use `--mqtt-messages 5000` to exercise the bounded MQTT ingest queue with isolated, rejected test envelopes, and `--slow-ws-clients 10` to hold non-reading WebSocket clients during the run. MQTT mode uses `MQTT_BROKER_URL`, `MQTT_USERNAME`, and `MQTT_PASSWORD`; never point it at a broker outside the deployment under test. The internal HopReach compatibility load path is covered by `backend/src/api/hopreachCompatibility.test.ts`. Its UK-size test performs ten @@ -137,13 +137,8 @@ docker compose exec backend node dist/tools/backfillStatsRollups.js --apply --ho ``` The first command is a read-only inventory. The checkpoint and the rollup -slice commit together, so interruption resumes without double-counting. The -`STATS_AGGREGATE_READS_ENABLED` and `STATS_AGGREGATE_SHADOW_ENABLED` settings -accept comma-separated network names; `true` retains the all-network mode. -Start with `STATS_AGGREGATE_SHADOW_ENABLED=ukmesh` while reads remain disabled. -The bounded aggregate query and its legacy comparison run off the response path -even in this shadow-only mode. Set `STATS_AGGREGATE_READS_ENABLED=ukmesh` only -after backfill and a clean parity window. Once the +slice commit together, so interruption resumes without double-counting. Set +`STATS_AGGREGATE_READS_ENABLED=true` only after backfill. Once the aggregate-writing backend is live, reconcile the partial cutover hour: ```bash @@ -158,8 +153,8 @@ gap above 48 hours requires a newly reviewed historical backfill. Transactions that arrive during a rebuild increment the reconstructed rows after its lock is released. -A short validation window may set -`STATS_AGGREGATE_SHADOW_ENABLED=ukmesh`. The comparison pins one cutoff, scans +A short validation window may additionally set +`STATS_AGGREGATE_SHADOW_ENABLED=true`. The comparison pins one cutoff, scans the recent source rows once, and runs off the response path with one in-flight comparison per scope and a five-minute minimum interval. Dimension keys must match exactly. Count differences may be at most five packets or 0.1%, @@ -169,15 +164,6 @@ snapshots. Treat any `[stats-aggregate-shadow] mismatch` or back off. Shadow mode deliberately adds a raw validation scan and must not remain enabled. -Chart queries also share one `asOf` cutoff. `STATS_QUERY_DURATION_BUDGET_MS` -defaults to 60 seconds, `STATS_CHART_QUERY_TIMEOUT_MS` defaults to 120 seconds, -and `STATS_CHART_MAX_RESULT_ROWS` defaults to 100,000. A query that exceeds a -budget fails the chart refresh and is logged with a `[stats-query-budget]` -record; the existing stale snapshot remains available. The result-row limit is -an output-size guard. It does not measure rows scanned inside PostgreSQL, so -keep reviewing query plans and database scan telemetry before enabling reads on -additional networks. - Production aggregate reads combine maintained full-hour rows with raw boundary fragments, preserving the legacy exact rolling 24-hour/seven-day windows while keeping raw work bounded to partial hours. @@ -242,43 +228,6 @@ The public `/health` page polls the aggregated `/api/health` contract every minu - [SRTM cache, alert delivery, observer review, and planned publication](runbook-srtm-alerts-observers.md) - [Database compression and retention gates](db-lifecycle.md) -### Owner authorization baseline refresh - -`/readyz` fails closed when the current owner accounts, grants, configured grants, -or ACL generations differ from `OWNER_AUTH_INVENTORY_BASELINE_PATH`. This means -an owner grant or `OWNER_MQTT_USERNAME_MAP` entry changed after the baseline was -recorded. Drift is expected after every approved grant change and is not itself -an incident, but it must be reviewed before the baseline is refreshed. - -1. Identify the change from the audit trail and confirm it was approved: - - ```bash - docker exec -e PGOPTIONS='-c default_transaction_read_only=on' \ - meshcore-infra-timescaledb-1 psql -U meshcore -d meshcore_owner_auth -X \ - -c "SELECT occurred_at, event_type, source, actor FROM owner_grant_audit \ - WHERE occurred_at > now() - interval '30 days' ORDER BY occurred_at DESC" - ``` - -2. Generate and validate a fresh baseline with the running image (read-only - against the database; it only writes the new baseline file): - - ```bash - scripts/refresh-owner-baseline.sh - ``` - -3. Activate it after reviewing the printed counts/generations: - - ```bash - scripts/refresh-owner-baseline.sh --apply --recreate - curl -fsS http://127.0.0.1:3000/readyz - ``` - -The script refuses to overwrite an existing baseline, backs up `.env` before -editing, keeps the previous baseline for rollback, and validates the new file -against the live inventory before activation. To roll back, point -`OWNER_AUTH_INVENTORY_BASELINE_PATH` at the previous file and recreate the -backend. - Use the `/operations`, `/observer-registrations`, and operator audit pages for normal mutations. They enforce authorization, CSRF, typed confirmation, idempotency, capacity, and audit rules that direct Redis/SQL edits bypass. @@ -321,29 +270,6 @@ the reloader logs. Do not regenerate credentials over an existing set. If ACL reconciliation caused the alert, keep the last verified ACL and roll back that change. -### Denied PUBLISH on `/raw` (#269 / #213) - -**Disposition:** keep these denials. They are expected log noise from publishers -using a topic outside the supported observer contract; the evidence does not -show a packet-ingest gap. - -The B4 review brief reports about 233 Mosquitto `Denied PUBLISH` lines from -`ESP32_a806F8` for `meshcore/LBA/8CA23B78…/raw`, associated with a granted node, -plus a separate `meshmonitor-observer EXT` `/raw` family. The count and client -identities above come from that supplied log review; they were not re-counted -against the live broker during this code change. - -The renderer in `backend/src/mqtt/aclManager.ts` grants owner writes to -`packets`, `status`, `neighbors`, and `neighbours`. `backend/src/mqtt/topic.ts` -accepts those suffixes and rejects `/raw`. Packet bytes are carried in the -`raw` JSON field on the `/packets` envelope and decoded in -`backend/src/mqtt/client.ts`; there is no `/raw` topic consumer. Granting the -extra suffix would authorize a broker topic that the backend does not ingest. - -Reopen this ruling if a supported publisher is denied on `/packets`, or if the -ingest contract is deliberately extended with a `/raw` consumer. The topic -parser test keeps the current rejection explicit. - ### MeshIngestSilent Check backend MQTT readiness, broker connected-client events, public topic @@ -412,15 +338,6 @@ Inspect waiting/active connection metrics, slow route latency and PostgreSQL activity. Shed optional analysis/load, identify the bounded slow query, and avoid increasing pool size until connection and memory headroom are measured. -### DatabaseVacuumBacklog - -Inspect the `relname` table label, `n_dead_tup`, `n_live_tup`, and recent -autovacuum activity in PostgreSQL. A table with more than 50,000 dead tuples -is actionable even when its live-row estimate is small (as can happen for a -TimescaleDB chunk after deletes); run the reviewed bounded vacuum procedure and -confirm that both the alert and `/api/health` clear. Do not drop chunks or -delete the database volume as a response. - ### DatabaseWalRateHigh Identify current maintenance, backfill, compression or ingest activity. Pause @@ -434,36 +351,6 @@ databases and signed backups. Prune only explicitly identified unused build cache/images or let the bounded SRTM/log policies converge; never run a broad volume prune. -### HostDiskExhaustionForecast - -Treat a sustained forecast of disk exhaustion within 24 hours as an incident -before the low-space threshold fires. Identify the filesystem and its largest -consumers, compare the database hypertable and chunk growth gauges, pause -optional backfills, and preserve backup capacity. Do not remove database -chunks or volumes to make space; follow the signed backup and lifecycle gates. - -### CoreTelemetryGrowthHigh - -Compare the per-table byte and chunk gauges with the retention and compression -policies, then run the read-only `npm run db:lifecycle` inventory during a low -traffic window. Check for missed retention/compression jobs and forecast the -next 30 days of disk use. Changes to destructive retention require the current -signed backup/restore receipt and the per-table approval in `docs/db-lifecycle.md`. - -### CoreTelemetryRetentionOverdue - -Use read-only queries against `timescaledb_information.jobs` and -`timescaledb_information.chunks` to identify the missing or failing policy for -the labelled table. Verify backup and isolated restore evidence before running -the gated per-table lifecycle command. Keep expired forensic access isolated -and read-only; never expose restored expired rows through production APIs. - -### CoreTelemetryCompressionOverdue - -Inspect the labelled table's compression policy and Timescale job history, as -well as worker capacity and recent database/WAL pressure. Apply compression -only through the per-table, receipt-gated command in `docs/db-lifecycle.md`. - ### PacketPathsCapacity Compare the health-worker `meshcore_packet_paths_rows_30d`, @@ -473,16 +360,6 @@ is overdue, inspect the `packet_paths` compression job and Timescale worker headroom; never add a retention job for this table. For genuine growth, forecast the next 12 months against free disk before changing storage or ingest policy. -### Durable MQTT ingestion - -The MQTT client uses the library's inbound message callback for backpressure; -QoS 1 PUBACK follows successful database persistence. A database failure is -returned as a handler error, leaving the broker delivery unacknowledged for -redelivery and keeping readiness failed until a durable write succeeds. Inspect -the `failure`, `packet_persist_failure`, `status_persist_failure`, or -`neighbors_persist_failure` ingest outcomes and resolve the database issue; -do not clear the broker session or discard its inflight delivery. - ### BackupReceiptMissing Check the receipt mount, signature/key permissions, and backup job result. @@ -503,15 +380,16 @@ identify the malformed observer/client version without weakening validation. ## Spring-clean additions (2026-08-06) -### MQTT ingest resilience +### MQTT ingest resilience (`90d0dce`) -The MQTT client keeps a stable `clean=false` QoS-1 session and uses MQTT.js -message handling as the backpressure boundary. It sends PUBACK only after -packet, status, or neighbour persistence completes. Persistence failures are -propagated to MQTT.js and close the connection without PUBACK so the persistent -broker session redelivers the message. The process-local packet dedupe marker -is written only after packet-batch commit. The broker session is -`meshcore-analytics-ingest` (Mosquitto reports `c0`). +Ingest was silently lossy: failed packet-batch DB writes were logged + +DISCARDED (no retry), and the MQTT client used a CLEAN session (missed +messages on reconnect). Fix: idempotent transient batch retries + stable +`clean=false` QoS-1 MQTT session (broker sees `meshcore-analytics-ingest` +with `c0`) + ingest outcome/retry metrics. The backend does NOT dedupe +inserts (DISTINCT ON is readers/backfills only) — envelope-vs-packet count +differences vs services counting every MQTT message are expected, not loss. +Full audit: `INGEST-AUDIT-2026-08-06.md` in the repo root. ### Feed history contract diff --git a/docs/pathing-accuracy-2026-09-05.json b/docs/pathing-accuracy-2026-09-05.json new file mode 100644 index 0000000..5733df7 --- /dev/null +++ b/docs/pathing-accuracy-2026-09-05.json @@ -0,0 +1,374 @@ +{ + "baselineCommit": "1eefb50b31fda8d79ef383a658764671c65cb46a", + "snapshotSha256": "f92b5be6776f589ab3c237875c449989545ba56a6c15d9e0ec220b6cf2047632", + "capturedAt": "2026-09-05 01:18:07.69378+00", + "trainingBuckets": [ + 0, + 69 + ], + "developmentBuckets": [ + 70, + 84 + ], + "evaluationBuckets": [ + 85, + 99 + ], + "trainingSplitVerified": "Running worker had no PATH_LEARNING_TRAINING_SPLIT_PERCENT override; source default is 70.", + "readOnlySnapshot": true, + "comparisons": { + "lazy-single": { + "baseline": { + "snapshotSha256": "f92b5be6776f589ab3c237875c449989545ba56a6c15d9e0ec220b6cf2047632", + "capturedAt": "2026-09-05 01:18:07.69378+00", + "decoder": "1eefb50:lazy", + "split": "test", + "mode": "single", + "routes": 1377, + "excluded": 410, + "routeCorrect": 1035, + "totalHops": 9190, + "correctHops": 8351, + "routeAccuracy": 0.7516339869281046, + "hopAccuracy": 0.9087051142546246, + "rawThreeByteCorrect": 1184, + "uniqueRoutes": 1292, + "medianMs": 34.32457699999213, + "p95Ms": 64.22827799999504, + "p99Ms": 107.54097700000057 + }, + "current": { + "snapshotSha256": "f92b5be6776f589ab3c237875c449989545ba56a6c15d9e0ec220b6cf2047632", + "capturedAt": "2026-09-05 01:18:07.69378+00", + "decoder": "current", + "split": "test", + "mode": "single", + "routes": 1377, + "excluded": 410, + "routeCorrect": 1131, + "totalHops": 9190, + "correctHops": 8710, + "routeAccuracy": 0.8213507625272332, + "hopAccuracy": 0.9477693144722524, + "rawThreeByteCorrect": 1377, + "uniqueRoutes": 1292, + "medianMs": 11.225374000001466, + "p95Ms": 30.753426999999647, + "p99Ms": 41.991972999996506 + }, + "improved": 106, + "regressed": 10, + "routeGainPercentagePoints": 6.971677559912854, + "hopGainPercentagePoints": 3.9064200217627856, + "uniqueRouteGain95IntervalPercentagePoints": [ + 5.882352941176471, + 9.055727554179567 + ], + "strata": { + "known-source": { + "routes": 295, + "baselineCorrect": 227, + "currentCorrect": 234 + }, + "hops-7-12": { + "routes": 440, + "baselineCorrect": 284, + "currentCorrect": 338 + }, + "hops-2-3": { + "routes": 144, + "baselineCorrect": 131, + "currentCorrect": 130 + }, + "unknown-source": { + "routes": 1082, + "baselineCorrect": 808, + "currentCorrect": 897 + }, + "hops-4-6": { + "routes": 689, + "baselineCorrect": 597, + "currentCorrect": 604 + }, + "hops-13+": { + "routes": 104, + "baselineCorrect": 23, + "currentCorrect": 59 + } + } + }, + "lazy-multi": { + "baseline": { + "snapshotSha256": "f92b5be6776f589ab3c237875c449989545ba56a6c15d9e0ec220b6cf2047632", + "capturedAt": "2026-09-05 01:18:07.69378+00", + "decoder": "1eefb50:lazy", + "split": "test", + "mode": "multi", + "routes": 1377, + "excluded": 410, + "routeCorrect": 1078, + "totalHops": 9190, + "correctHops": 8617, + "routeAccuracy": 0.7828612926652142, + "hopAccuracy": 0.9376496191512513, + "rawThreeByteCorrect": 1246, + "uniqueRoutes": 1292, + "medianMs": 54.398984999992535, + "p95Ms": 150.54681999999957, + "p99Ms": 206.64727600000333 + }, + "current": { + "snapshotSha256": "f92b5be6776f589ab3c237875c449989545ba56a6c15d9e0ec220b6cf2047632", + "capturedAt": "2026-09-05 01:18:07.69378+00", + "decoder": "current", + "split": "test", + "mode": "multi", + "routes": 1377, + "excluded": 410, + "routeCorrect": 1135, + "totalHops": 9190, + "correctHops": 8716, + "routeAccuracy": 0.8242556281771968, + "hopAccuracy": 0.9484221980413493, + "rawThreeByteCorrect": 1377, + "uniqueRoutes": 1292, + "medianMs": 10.617765000000873, + "p95Ms": 30.299431999999797, + "p99Ms": 42.795510999999806 + }, + "improved": 71, + "regressed": 14, + "routeGainPercentagePoints": 4.139433551198257, + "hopGainPercentagePoints": 1.0772578890097932, + "uniqueRouteGain95IntervalPercentagePoints": [ + 3.018575851393189, + 5.804953560371517 + ], + "strata": { + "known-source": { + "routes": 295, + "baselineCorrect": 222, + "currentCorrect": 234 + }, + "hops-7-12": { + "routes": 440, + "baselineCorrect": 314, + "currentCorrect": 338 + }, + "hops-2-3": { + "routes": 144, + "baselineCorrect": 128, + "currentCorrect": 130 + }, + "unknown-source": { + "routes": 1082, + "baselineCorrect": 856, + "currentCorrect": 901 + }, + "hops-4-6": { + "routes": 689, + "baselineCorrect": 592, + "currentCorrect": 607 + }, + "hops-13+": { + "routes": 104, + "baselineCorrect": 44, + "currentCorrect": 60 + } + } + }, + "beta-single": { + "baseline": { + "snapshotSha256": "f92b5be6776f589ab3c237875c449989545ba56a6c15d9e0ec220b6cf2047632", + "capturedAt": "2026-09-05 01:18:07.69378+00", + "decoder": "1eefb50:beta", + "split": "test", + "mode": "single", + "routes": 1377, + "excluded": 410, + "routeCorrect": 479, + "totalHops": 9190, + "correctHops": 6315, + "routeAccuracy": 0.3478576615831518, + "hopAccuracy": 0.6871599564744287, + "rawThreeByteCorrect": 1165, + "uniqueRoutes": 1292, + "medianMs": 2.602124000000913, + "p95Ms": 9.193194999999832, + "p99Ms": 14.868608999999196 + }, + "current": { + "snapshotSha256": "f92b5be6776f589ab3c237875c449989545ba56a6c15d9e0ec220b6cf2047632", + "capturedAt": "2026-09-05 01:18:07.69378+00", + "decoder": "current", + "split": "test", + "mode": "single", + "routes": 1377, + "excluded": 410, + "routeCorrect": 1131, + "totalHops": 9190, + "correctHops": 8710, + "routeAccuracy": 0.8213507625272332, + "hopAccuracy": 0.9477693144722524, + "rawThreeByteCorrect": 1377, + "uniqueRoutes": 1292, + "medianMs": 11.225374000001466, + "p95Ms": 30.753426999999647, + "p99Ms": 41.991972999996506 + }, + "improved": 684, + "regressed": 32, + "routeGainPercentagePoints": 47.34931009440813, + "hopGainPercentagePoints": 26.06093579978237, + "uniqueRouteGain95IntervalPercentagePoints": [ + 45.04643962848297, + 51.0061919504644 + ], + "strata": { + "known-source": { + "routes": 295, + "baselineCorrect": 118, + "currentCorrect": 234 + }, + "hops-7-12": { + "routes": 440, + "baselineCorrect": 86, + "currentCorrect": 338 + }, + "hops-2-3": { + "routes": 144, + "baselineCorrect": 90, + "currentCorrect": 130 + }, + "unknown-source": { + "routes": 1082, + "baselineCorrect": 361, + "currentCorrect": 897 + }, + "hops-4-6": { + "routes": 689, + "baselineCorrect": 301, + "currentCorrect": 604 + }, + "hops-13+": { + "routes": 104, + "baselineCorrect": 2, + "currentCorrect": 59 + } + } + }, + "beta-multi": { + "baseline": { + "snapshotSha256": "f92b5be6776f589ab3c237875c449989545ba56a6c15d9e0ec220b6cf2047632", + "capturedAt": "2026-09-05 01:18:07.69378+00", + "decoder": "1eefb50:beta", + "split": "test", + "mode": "multi", + "routes": 1377, + "excluded": 410, + "routeCorrect": 485, + "totalHops": 9190, + "correctHops": 6318, + "routeAccuracy": 0.3522149600580973, + "hopAccuracy": 0.6874863982589772, + "rawThreeByteCorrect": 1142, + "uniqueRoutes": 1292, + "medianMs": 2.552361000000019, + "p95Ms": 6.777016999999432, + "p99Ms": 10.883194999998523 + }, + "current": { + "snapshotSha256": "f92b5be6776f589ab3c237875c449989545ba56a6c15d9e0ec220b6cf2047632", + "capturedAt": "2026-09-05 01:18:07.69378+00", + "decoder": "current", + "split": "test", + "mode": "multi", + "routes": 1377, + "excluded": 410, + "routeCorrect": 1135, + "totalHops": 9190, + "correctHops": 8716, + "routeAccuracy": 0.8242556281771968, + "hopAccuracy": 0.9484221980413493, + "rawThreeByteCorrect": 1377, + "uniqueRoutes": 1292, + "medianMs": 10.617765000000873, + "p95Ms": 30.299431999999797, + "p99Ms": 42.795510999999806 + }, + "improved": 679, + "regressed": 29, + "routeGainPercentagePoints": 47.20406681190995, + "hopGainPercentagePoints": 26.093579978237216, + "uniqueRouteGain95IntervalPercentagePoints": [ + 44.81424148606811, + 50.77399380804953 + ], + "strata": { + "known-source": { + "routes": 295, + "baselineCorrect": 118, + "currentCorrect": 234 + }, + "hops-7-12": { + "routes": 440, + "baselineCorrect": 90, + "currentCorrect": 338 + }, + "hops-2-3": { + "routes": 144, + "baselineCorrect": 89, + "currentCorrect": 130 + }, + "unknown-source": { + "routes": 1082, + "baselineCorrect": 367, + "currentCorrect": 901 + }, + "hops-4-6": { + "routes": 689, + "baselineCorrect": 304, + "currentCorrect": 607 + }, + "hops-13+": { + "routes": 104, + "baselineCorrect": 2, + "currentCorrect": 60 + } + } + } + }, + "productionSmoke": { + "readOnlyDatabaseFence": true, + "coldContextDetailMs": 3232.646745, + "oneByteWarmDetailMs": 286.86554200000046, + "threeByteWarmDetailMs": 500.3961670000008, + "oneByteObserverPaths": 7, + "threeByteObserverPaths": 19, + "allSingleMultiAndDetailResponsesPresent": true, + "observerProjectionChecksPassed": true, + "note": "Two packet smoke test during concurrent test/replay load; not a latency distribution." + }, + "validation": { + "backendBuild": "passed", + "backendTypecheck": "passed", + "frontendTypecheck": "passed", + "pathTests": { + "passed": 56, + "failed": 0 + }, + "frontendPathTests": { + "passed": 9, + "failed": 0 + }, + "fullBackendSuite": { + "passed": 336, + "failed": 3, + "preExistingUntrackedFailures": [ + "backend/src/analysis/runState.test.ts", + "backend/src/api/routes/rfValidation.test.ts", + "backend/src/repositories/networkAnalysis.test.ts" + ] + } + } +} diff --git a/docs/pathing-accuracy-2026-09-05.md b/docs/pathing-accuracy-2026-09-05.md new file mode 100644 index 0000000..1918b7e --- /dev/null +++ b/docs/pathing-accuracy-2026-09-05.md @@ -0,0 +1,173 @@ +# Path accuracy verification — 5 September 2026 + +The revised resolver is more accurate than both production algorithms at +commit `1eefb50b31fda8d79ef383a658764671c65cb46a` on the same held-out three-byte +flood observations degraded to one-byte prefixes. All production path views +now use the revised evidence provider. + +## Final results + +The evaluation contains **1,377 packets, 9,190 relay positions and 1,292 unique +relay chains**. Entire-route accuracy requires every relay identity to match; +unresolved positions count as errors. + +| Resolver | One-byte exact routes | One-byte correct hops | Original three-byte routes preserved | +| --- | ---: | ---: | ---: | +| Previous beta, single observer | 34.79% (479) | 68.72% (6,315) | 1,165 / 1,377 | +| Previous detail resolver, single observer | 75.16% (1,035) | 90.87% (8,351) | 1,184 / 1,377 | +| Revised, single observer | **82.14% (1,131)** | **94.78% (8,710)** | **1,377 / 1,377** | +| Previous beta, multiple observers | 35.22% (485) | 68.75% (6,318) | 1,142 / 1,377 | +| Previous detail resolver, multiple observers | 78.29% (1,078) | 93.76% (8,617) | 1,246 / 1,377 | +| Revised, multiple observers | **82.43% (1,135)** | **94.84% (8,716)** | **1,377 / 1,377** | + +Against the stronger previous multi-observer implementation, the revision +corrects **71 routes** and regresses **14**: a net gain of 57 routes, or +**19.1% fewer incorrect routes**. It corrects 99 additional relay positions. +The route gain is 4.14 percentage points; a deterministic bootstrap giving +each unique relay chain equal weight yields a 95% interval of **+3.02 to ++5.80 percentage points** for that unique-chain metric. This interval is not +an estimate of accuracy on every real one-byte packet. + +The multi-observer gain appears in every reported route-length and source +stratum. On 104 routes with at least 13 relays, exact matches rise from 44 to +60. On 1,082 packets without a known source, they rise from 856 to 901. +Some individual cases still regress; one-byte identities remain ambiguous. + +[Machine-readable aggregate results](pathing-accuracy-2026-09-05.json) include +all paired comparisons, counts, intervals and strata. + +## What changed + +- The live beta resolver previously omitted the learner's position-prefix, + corridor and position-transition tables and used transition probabilities + where the scoring weights expected counts. Those signals now reach the + shared decoder, including directed source and receiver edges. +- Candidate bounds grow with route length. Long flood routes no longer lose + their distant first relays to an observer-centered, one-hop bounding box. +- Unique multibyte identities survive missing coordinates and contradictory + terrain estimates. Repeated, verified directed training edges can override + geographic rejection of inferred links. Unpositioned inferred candidates + need directed link evidence and retain null coordinates. +- Mixed hash widths index every matching candidate, preserving elevation. + Forward/backward passes cache transition scores to keep inference bounded. +- RX prefix collisions no longer remove a real terminal relay. Direct-routing + instructions are excluded from observed flood paths. Invalid hashes cannot + shift later positions. One-byte processing supports up to 63 hops. +- Distinct flood copies heard by one observer are retained. Confirmed branches + stay separate; observer-specific projections stop at their own receivers. +- Map labels use each branch's identities, so co-located relays cannot borrow + another branch's label or collapse distinct decoded routes by coordinates. + A relay sharing the source's byte prefix no longer removes that source + endpoint from the projected path. +- Late evidence can refine any position in a held chain. Holding a choice + does not manufacture perfect confidence. +- Packet details, overlays, slow finalization and history batches share the + decoder and evidence provider. Historical UKMesh network labels remain in + scope. Public visibility checks remain in place. +- Training preserves gaps and verified loops, excludes direct-routing paths, + and uses RX/TX metadata. A missing relay cannot create a learned shortcut. + +## Method and reproducibility + +The read-only, repeatable-read snapshot was captured at +**2026-09-05 01:18:07.69378 UTC**, over the preceding 14 days. It includes +14,542 public node records, 155,581 link records, the published prior tables +and 39,786 observations belonging to 3,489 candidate held-out packet hashes. +UKMesh's historical `northeast` and `teesside` labels are included. + +Snapshot SHA-256: + +```text +f92b5be6776f589ab3c237875c449989545ba56a6c15d9e0ec220b6cf2047632 +``` + +The running learner used the default PostgreSQL hash buckets 0–69 for +training; its environment had no training-split override. Buckets 70–84 were +used for development and 85–99 for final evaluation. Both splits are disjoint +from training at packet-hash level, including every observer's copy of the +packet. The geographic and learned weights were not tuned on final results. + +Ground truth requires a three-byte **flood** route, uniquely matching public +node identities and a positioned observer. Nodes without current coordinates +remain eligible as known identities. Direct routes, missing/colliding node +identities and paths without sufficient usable truth exclude 410 of the +1,787 final candidate packet hashes. There is no distance-based exclusion. +This sample covers 2–21 relay positions; longer one-byte paths are supported +and tested structurally but cannot have equivalent three-byte paths beyond +the protocol's path-byte limit. + +Every one-byte replay truncates all visible sibling observations, not just the +target path. Multi mode admits siblings within 60 seconds on either side of +the selected observation. Both algorithms receive the same snapshot and +observation window. The archived baseline changes only the export visibility +of its existing packet-preparation helper, not its implementation or scores. +The former detail resolver's SQL adapter is replayed against the frozen +snapshot. Node IDs, rather than rendered coordinates, determine correctness. + +This is **packet-held-out validation using current metadata**, not a temporal +forecast or a recreation of past coordinates. Recurrent corridors may occur +in both training and evaluation. Learned priors are frozen identically for +both algorithms, and unique-chain bootstrap results reduce the influence of +recurrent route copies. It does not establish that every live one-byte path, +or every confidence value, has the reported accuracy. + +The local snapshot, per-packet results, comparisons, source hashes and test +logs are under `artifacts/path-accuracy/2026-09-05-final/` (ignored by Git). +The aggregate JSON linked above is tracked. [Pathing documentation](pathing.md) +contains capture, baseline archive, replay and comparison commands. The older +`path-lazy/evaluate.ts` is marked as historical diagnostics because its +original sampling did not enforce held-out membership. + +## Runtime and validation + +The revised offline decode measured **10.62 ms median, 30.30 ms p95 and +42.80 ms p99** in multi-observer mode. Single-observer p95 was 30.75 ms. +These measure decoding against a loaded context, including observation +preparation; they exclude database loading and network latency. Baseline lazy +replay timings include its snapshot-query adapter and should not be used as +a production latency comparison. + +A separate smoke test ran the actual database-backed single, multi and detail +resolver entry points with PostgreSQL enforcing +`default_transaction_read_only=on`. One-byte and three-byte samples produced +all three responses, with correct observer endpoints. Warm detail requests +were approximately 287 ms and 500 ms; the first request loading context took +3.23 seconds. These are two samples under concurrent test load, not latency +percentiles. The existing worker pool, context caching and propagation +settling window remain in use. + +Validation completed: + +- Backend TypeScript build/typecheck and frontend TypeScript check passed. +- **56 path, learning, cache, worker, DTO, API and accuracy-harness tests passed.** +- **9 relevant frontend path/rendering tests passed.** +- Full backend run: **336 passed, 3 failed** in pre-existing, untracked files. + `analysis/runState.test.ts` imports a missing `normalizeRetiredAnalysisState`; + `repositories/networkAnalysis.test.ts` expects an unprovided abort signal; + `api/routes/rfValidation.test.ts` waits indefinitely for that signal and its + test process was terminated after confirming the failed assertion. These + files were present before this work and were not modified. +- Database-backed smoke tests were read-only; no prior tables were rebuilt. + +Deployed live on **2026-09-05 at 02:03 UTC**. The backend, path-learning worker, +and app now run the reviewed path implementation. Both `ukmesh` and `all` +models have published `path-learning-v5-contiguous` priors. The accuracy comparison +above still uses the same frozen priors for both implementations. + +Production verification passed: six single/multi/detail API checks across public +one-byte and three-byte packets, all eight identities in the sampled three-byte +route, 64 served JS/CSS assets, public app readiness and HTML, and live packet +streaming through the app proxy. The private-packet probe returns 404. The API and +app are healthy, with no container restarts or OOM events after the final rollout. +Other running containers retained their original IDs. + +The rollout exposed an August owner-inventory snapshot that the current readiness +check rejected. A fresh inventory was validated against the unchanged pre-deploy +broker policy and pinned; the original snapshot was preserved. An initial smoke +probe accidentally selected a private packet and triggered the deployment guard's +rollback. Public samples were then checked in the release image before the +successful retry. No access grants were changed. + +Image IDs, checks, exact build-source archive, and rollback references are recorded +in [the local deployment evidence](../artifacts/path-accuracy/2026-09-05-deploy/completion.json) +and its adjacent `manifest.json`. Previous service images remain tagged for rollback. diff --git a/docs/pathing.md b/docs/pathing.md index 7b41640..5da9acb 100644 --- a/docs/pathing.md +++ b/docs/pathing.md @@ -1,69 +1,117 @@ # Pathing -## Purpose +Production packet details, single-observer overlays, multi-observer overlays, +slow finalization and history batches use `path-core/decoder.ts` and the +evidence provider in `path-beta/sharedDecoder.ts`. The previous lazy resolver +is retained only for accuracy comparisons; workers no longer route to it. -Pathing resolves packet routes into confident relay chains. The production -pipeline (2026-08) is the Viterbi champion (`vit_src`) from the pathing -experiment: it walks a candidate trellis built from 1-byte hop prefixes and -scores emissions/transitions from seven calibrated signals, including -positional prefix-frequency priors, corridor interpolation, and **ITM -terrain feasibility** (no hop is placed where line-of-sight physics rules it -out). Measured on the gold holdout set: **97.27% route accuracy / 99.24% -hop accuracy**. +The September 2026 changes and comparison against commit `1eefb50` are in +[the accuracy report](pathing-accuracy-2026-09-05.md). The previous 97.27% route / +99.24% hop claim came from a different, narrower experiment and should not be +used as the accuracy of today's traffic. -## Main layers +## Evidence and decoding -- `backend/src/path-beta/` - - resolver implementation (`resolver.ts`) — Viterbi candidate trellis - - worker pool - - geometry helpers (terrain-aware arcs) - - fallback logic -- `backend/src/pathing/` - - service/repository orchestration for API-facing pathing endpoints - - `pathingPublicDto.ts` — the public DTO projector. **Any new resolver - field must be added here or the API silently drops it.** -- `backend/src/pathing/pathingService.ts` - - cache and resolver orchestration +The learner builds priors from independently identified multibyte relays. +The live context loads position-prefix, source/receiver corridor, directed +transition and position-specific transition counts. The scoring weights use +counts, not transition probabilities. Source edges have transition position +0; the first relay-to-relay edge has position 1. Observer endpoint edges use +the same directed evidence. -## Production behaviour (2026-08-06) +A Viterbi trellis finds a coherent chain of node IDs matching the observed +prefixes. Geography, corridor interpolation, direct receiver anchors and ITM +viability contribute to ranking. Candidate bounds grow with path length so +long flood routes can extend beyond one hop from their observers. Columns +remain capped at 64 candidates. Forward and backward passes share cached +transition scores. Resolution remains in the bounded worker pool; network +context is cached for 15 minutes, and the propagation settling window remains. -- **Slow-mode resolution**: packets wait out their propagation window before - the path is finalised (`469c84e`), so late observations don't re-shape a - settled route. -- **Physics gates + held-path refinement** (`5dd15d9`): candidate hops that - violate physical feasibility are gated; held paths are refined in place. -- **Canonical path DTO**: `canonicalPath` is the authoritative route in API - responses; the frontend renders it as blue paths. Path identity colours - live-path arcs by the resolved packet (`f6f8290`). -- Terrain-aware live paths + airborne-hop markers (`a76ab1e`); live path arcs - stay above terrain (`7bcabcc`). +Collision-free two- and three-byte observations lock node identities. +Recorded identities survive absent/stale coordinates and contrary terrain +estimates. For inferred routes, repeatedly verified directed training edges +can override a geometric rejection; otherwise physics gates still apply. +A node without coordinates needs observed directed transitions to participate +in an inferred route. Its coordinates stay null so the map breaks the line. -## Evidence priorities +## Observation handling -In practice the resolver should rank evidence roughly like this: +Flood paths describe recorded relays; direct paths describe routing +instructions and are excluded from observed-route inference and training. +See the [MeshCore packet format](https://github.com/meshcore-dev/MeshCore/blob/main/docs/packet_format.md) +and [firmware path handling](https://github.com/meshcore-dev/MeshCore/blob/main/src/Mesh.cpp). -- physically plausible link support (ITM/LOS) -- multibyte path evidence -- radio-neighbour evidence -- weaker observational hints +RX observations retain their last relay even if its short prefix matches the +observer. TX observations remove an appended observer prefix. Legacy rows +without direction retain the previous terminal-trimming convention. +Malformed hashes reject an observation rather than shifting later hops. +Valid one-byte paths support the protocol's 63-hop count instead of stopping +after 24 hops. -## API ownership +An observer can hear several flood copies over different routes. Multi-observer +inference keeps those variants and de-duplicates identical receptions. +Compatible prefixes share a decode; conflicting multibyte routes stay separate. +A coarse observation compatible with several confirmed routes keeps its own +group. Each projected path ends at its own observer, without adding edges to +another observer's branch. +Map labels use each branch's own relay identities; coordinates alone cannot +identify co-located nodes. -- `backend/src/api/routes/pathing.ts` - - thin HTTP wrapper -- `backend/src/pathing/pathingService.ts` - - cache and resolver orchestration -- `backend/src/pathing/pathingRepository.ts` - - DB-backed path history and learning queries +A held path is reconsidered across the entire chain when evidence changes. +A better chain must clear the existing score margin; holding an assignment +does not turn its confidence into 100%. -## Contributor rule +The learner preserves original positions across missing or ambiguous relays +and cannot learn a shortcut across a missing relay. Its version is now +`path-learning-v5-contiguous`; the next normal rebuild republishes corrected +priors without a schema migration. -If a path looks wrong, first determine whether the issue is: +## Ownership and scope -- evidence weighting -- physical graph quality (ITM feasibility) -- ambiguous short-hash matching -- cache reuse -- a missing field in the public DTO projector (`pathingPublicDto.ts`) +- `path-beta/resolver.ts`: scoped evidence loading, observation preparation, + cached context and API projections. +- `path-beta/championPriors.ts`: count aggregation and bounded prior loading. +- `path-core/decoder.ts`: candidate indexing, scoring, Viterbi traversal and + ambiguity margins. +- `path-learning/rebuild.ts`: multibyte training and atomic publication. +- `pathing/pathingPublicDto.ts`: explicit public DTO projection. Add new + resolver fields here before clients can receive them. -Do not debug path rendering as if it were a frontend-only problem. +UKMesh retains historical `northeast` and `teesside` evidence through the +shared network scope. Private-node exclusions and visibility-generation cache +fences continue to apply. Test networks remain isolated. + +## Reproducing accuracy checks + +Run from `backend/`. Capture uses a repeatable-read, read-only transaction. +Replays are offline and do not update priors, online state or production +caches. Local snapshots and traces belong under `artifacts/path-accuracy/`, +which is ignored by Git. + +```sh +node --env-file=../.env --import tsx src/tools/pathAccuracy.ts capture /tmp/path-snapshot.json +npm run path:accuracy -- baseline /tmp/path-baseline 1eefb50 +npm run path:accuracy -- replay /tmp/path-snapshot.json /tmp/new.json current test +npm run path:accuracy -- replay /tmp/path-snapshot.json /tmp/old.json /tmp/path-baseline/backend/src/path-lazy/lazyResolver.ts test +npm run path:accuracy -- compare /tmp/old.json /tmp/new.json /tmp/comparison.json +``` + +Use `PATH_ACCURACY_MODE=multi` for multi-observer replay. Substitute +`path-beta/sharedDecoder.ts` in the baseline path to compare the old beta +resolver. `PATH_ACCURACY_DAYS` selects the capture window (default 14, maximum +45); `PATH_ACCURACY_NETWORK` defaults to `ukmesh`. + +The harness reserves PostgreSQL packet-hash buckets 70–84 for development and +85–99 for evaluation. **Verify that the running learner uses the default 70% +training split before capture**; training on more than 70% would contaminate +these held-out packets. Node/link metadata is a current snapshot, so this is +a packet-held-out comparison, not a historical simulation of metadata at the +time of each reception. + +Truth comes only from three-byte flood paths with uniquely matching public +node identities and a positioned observer. One-byte replay hides the extra +bytes in every sibling observation. Cases with unresolved or colliding truth +are reported as excluded; geographic distance does not exclude a route. +Comparisons require identical snapshots and labels, count unresolved guesses +as errors, report regressions and corrections, and bootstrap unique relay +chains rather than treating recurrent copies as independent routes. diff --git a/docs/privacy.md b/docs/privacy.md index ac70e40..bc50a82 100644 --- a/docs/privacy.md +++ b/docs/privacy.md @@ -36,6 +36,7 @@ The service stores radio traffic observed on the MeshCore network. |---|---|---| | `packets` — message content + raw bytes + metadata | **at most 30 days** (Timescale chunk policy + bounded exact-row boundary cleanup) | network diagnostics; content is not kept long-term | | `packet_decryptions` — full decrypted messages | **30 days** (operational retention loop) | diagnostics only | +| `owner_packet_share_deliveries` — validated packet payload and delivery metadata | **30 days after terminal delivery** (operational retention loop) | durable site-to-site delivery and deduplication; retryable rows remain until terminal | | `packet_paths` — path metadata (node IDs, timing, signal, hashes) | **indefinite, content-stripped** | raw pathing analytics is the product | | `multibyte_path_facts`, `multihop_paths_weekly`, `path_*_priors` — derived path stats | indefinite | derived, pseudonymous | | `packet_daily_stats` / `packet_hourly_stats` | indefinite | anonymous aggregates | @@ -64,23 +65,7 @@ The service stores radio traffic observed on the MeshCore network. deletion procedure in the ops playbook (rows by src/rx node id across packets, packet_paths, decryptions, path facts). -## 3. Device status telemetry - -Status samples retain a JSON telemetry object for the authenticated owner -portal. The unauthenticated `/node-status/latest` and `/mqtt-nodes` APIs expose -only their explicit public fields: node identity, sample/availability data, -battery, uptime, airtime, channel utilization, and recent packet counts. They -do not return the telemetry object or forward unrecognized fields. Wi-Fi -identifiers, MQTT broker/account details, reset and configuration fields, -firmware metadata, and filesystem details are available only through -owner-authenticated endpoints. Unrecognized keys stay in storage until they -are deliberately added to a response contract. - -Existing status samples do not need a database rewrite: public responses are -projected through the same field allowlist regardless of when the sample was -stored. - -## 4. Cookies +## 3. Cookies LocalStorage-only choices (cookie consent, theme). No third-party cookies. PECR does not apply to the mesh radio data (not a public electronic diff --git a/docs/runbook-backup-restore.md b/docs/runbook-backup-restore.md index df8cb1c..a473b3a 100644 --- a/docs/runbook-backup-restore.md +++ b/docs/runbook-backup-restore.md @@ -59,12 +59,6 @@ export MESHCORE_INFRA_PROJECT_NAME=meshcore-infra scripts/backup.sh ``` -`MESHCORE_INFRA_COMPOSE_FILE` can select a non-default external Compose file; -`MESHCORE_INFRA_PROJECT_DIR` can set its project directory explicitly. The -shared resolver checks that Compose project and requires one running container -for each exact `timescaledb`, `redis`, and `mosquitto` service. It does not -search container names across projects. - For the returned receipt and archive: ```bash diff --git a/docs/runbook-release-rollback.md b/docs/runbook-release-rollback.md index aeeba7c..d411347 100644 --- a/docs/runbook-release-rollback.md +++ b/docs/runbook-release-rollback.md @@ -45,8 +45,6 @@ export COSIGN_CERTIFICATE_OIDC_ISSUER='https://token.actions.githubusercontent.c export RESTORE_RECEIPT_PATH=/secure/receipts/latest.json export RESTORE_RECEIPT_VERIFY_KEY=/secure/receipts/verify.pem export RELEASE_RECEIPT_SIGNING_KEY=/secure/release/receipt-signing.pem -export MESHCORE_INFRA_DIR=/home/ben/ukmesh/meshcore-infra -export MESHCORE_INFRA_PROJECT_NAME=meshcore-infra scripts/replace-container.sh backend \ --image="$BACKEND_IMAGE" \ @@ -54,13 +52,6 @@ scripts/replace-container.sh backend \ --source-revision="$SOURCE_REVISION" ``` -If the infrastructure Compose file has another path, set -`MESHCORE_INFRA_COMPOSE_FILE` and, when needed, -`MESHCORE_INFRA_PROJECT_DIR`. The release script validates that external -project and resolves its single running `timescaledb` container before it -starts the migration job; the post-migration schema check uses that exact -container ID. - Repeat for each application service, passing its digest as `--image` and the same signed backend digest as `--backend-image`. The script verifies signatures and revision labels, runs migrations, starts the prior backend against the diff --git a/docs/runbook-srtm-alerts-observers.md b/docs/runbook-srtm-alerts-observers.md index 33f0ca0..c4e53d9 100644 --- a/docs/runbook-srtm-alerts-observers.md +++ b/docs/runbook-srtm-alerts-observers.md @@ -29,18 +29,8 @@ dead-letter reason. Alertmanager groups by alert name/severity, sends firing and resolved events to the internal receiver, and retains its own state for 120 hours. The receiver -stores bounded, rotated summaries in `alert_receiver_data`. When -`ALERT_FORWARD_URL` is configured, the receiver fsyncs each forwarding record -into `alerts.jsonl.queue` before returning HTTP 202, then retries with bounded -backoff and moves exhausted deliveries into a `.dead.json` record. Pending items -and the last successful delivery time survive receiver restarts. - -If `ALERT_FORWARD_URL` is unset or invalid, the receiver operates in archive-only -mode. It still accepts alerts after the local receipt is persisted, but both -`/healthz` and `/readyz` return 503 so Compose and operators see degraded -delivery. Health JSON includes `pending_count`, `queue_oldest_age_seconds`, -`dead_letter_count`, `last_success_at`, and `last_error`. Resolve any dead-letter -or aged-queue condition before treating a firing alert as delivered. +stores bounded, rotated summaries in `alert_receiver_data`; optional forwarding +uses `ALERT_FORWARD_URL`. ```bash docker compose exec -T alertmanager amtool \ diff --git a/docs/spam-detection.md b/docs/spam-detection.md index 67d130a..6d95169 100644 --- a/docs/spam-detection.md +++ b/docs/spam-detection.md @@ -100,8 +100,10 @@ A cluster only becomes an incident when it is genuine abuse, not normal traffic: *or* the spam marker. Trivial/connectivity messages (`test`, `ack`, `morning`, `gm`, …) never qualify on their own. - **Requires a suspicious sender pattern** — a single repeating sender, one - dominant sender, or **rotating similar usernames**. Many *dissimilar* people - sending the same thing is treated as broad legitimate chatter, not one abuser. + dominant sender, **rotating similar usernames**, or a **rotating-ID botnet** + (see below). Many *dissimilar* **people** sending the same thing is treated + as broad legitimate chatter, not one abuser — but "many senders" made of + single-shot machine-named identities is a botnet, not people. - The **test channel is excluded by default** (it carries repeated test traffic by design). Configurable via `SPAM_MESSAGE_EXCLUDE_CHANNELS`. A flood that *also* hits another channel (e.g. Public) is still caught via those copies. @@ -116,6 +118,29 @@ A cluster only becomes an incident when it is genuine abuse, not normal traffic: test/connectivity flood is never rewarded, so it stays below the public floor no matter how large it grows. +### Rotating-ID botnets + +A distinct abuse class: the **same canned line blasted from many fresh sender +IDs** — one message per ID, in timed bursts (a handful of IDs within minutes), +then quiet. Per-sender rate logic sees nothing; the tell is one text arriving +from many *one-shot* identities. A cluster is flagged `rotatingIdentities` +when ALL of: + +- ≥ `SPAM_MESSAGE_ROTATING_MIN_SENDERS` distinct senders, +- ≥ `SPAM_MESSAGE_ROTATING_SINGLE_SHOT_SHARE` of them appear exactly once in the + analysis window (one-shot identities, not established members), +- ≥ `SPAM_MESSAGE_ROTATING_MACHINE_NAME_SHARE` of the names look + machine-generated (`looksMachineGeneratedName` in `normalize.ts`: + separator-free base32-ish tokens, ≥1 digit, vowel-poor, mostly uppercase — + amateur-callsign shapes and human composites are excluded), and +- ≥ `SPAM_MESSAGE_ROTATING_MIN_BURST_SENDERS` distinct senders inside one burst + window. + +Flagged clusters are exempted from the broad-chatter rejection, scored +0.3, and +carry the reason "N single-shot identities (X% machine-style names, N within +10 min) posted near-identical text". First production catch: the Oct-2026 +anti-Kirby campaign — 9 incidents, 8–15 sender IDs each, confidence 0.54–0.81. + ## Incident lifecycle - **active / ongoing** while the last matching message is within @@ -145,6 +170,10 @@ All knobs live in `backend/src/spam/config.ts` and read from the environment. | `SPAM_MESSAGE_MAX_INDEPENDENT_SENDERS` | `4` | Above this (dissimilar) → broad chatter | | `SPAM_MESSAGE_DOMINANT_SENDER_SHARE` | `0.6` | One sender at this share qualifies | | `SPAM_MESSAGE_FLOOD_MIN_MESSAGES` | `25` | ≥ this many near-duplicates → sustained-flood boost | +| `SPAM_MESSAGE_ROTATING_MIN_SENDERS` | `5` | Min distinct senders for rotating-ID detection | +| `SPAM_MESSAGE_ROTATING_SINGLE_SHOT_SHARE` | `0.8` | Share of senders seen exactly once in the window | +| `SPAM_MESSAGE_ROTATING_MACHINE_NAME_SHARE` | `0.5` | Share of names that must look machine-generated | +| `SPAM_MESSAGE_ROTATING_MIN_BURST_SENDERS` | `3` | Min distinct senders inside one burst window | | `SPAM_MESSAGE_EXCLUDE_CHANNELS` | `test` | Channel labels skipped (comma list) | | `SPAM_MESSAGE_PUBLIC_MIN_SCORE` | `0.5` | Confidence floor for public list / status | | `SPAM_MESSAGE_ONGOING_WINDOW_MIN` | `30` | How long an incident stays "ongoing" | @@ -171,8 +200,9 @@ Two separate confidences are shown: - **Detection confidence** (incident `confidence`, 0–1) — how strongly the pattern resembles spam. Built from volume, burst density, sustained-flood - size, repeated links, the spam marker, and rotating sender names; content - dominated by connectivity/test tokens is dampened. `High ≥ 0.66`, + size, repeated links, the spam marker, rotating sender names, and + rotating-identity evidence; content dominated by connectivity/test tokens is + dampened. `High ≥ 0.66`, `Medium ≥ 0.4`, else `Low`. Only incidents at/above `SPAM_MESSAGE_PUBLIC_MIN_SCORE` appear publicly by default. - **Origin confidence** (origin `confidence` + `level`) — how trustworthy the @@ -248,6 +278,13 @@ docker compose exec backend node dist/tools/recomputeSpamMessages.js --hours 720 docker compose exec backend node dist/tools/recomputeSpamMessages.js --hours 720 --apply ``` +If the analyzer log shows `status=timed_out`, the run's work exceeded +`SPAM_MESSAGE_ANALYSIS_BUDGET_MS` — the 5 s default is tuned for quiet days; +busy spam days need ~30–60 s (each public incident resolves relay paths for +origin). Raise it in `.env` (e.g. `SPAM_MESSAGE_ANALYSIS_BUDGET_MS=60000`) and +recreate the backend. The recompute tool loads more rows than the analyzer and +may need `SPAM_MESSAGE_MAX_MESSAGES` raised for wide windows. + ## Tests Pure logic (normalization, fuzzy matching, username similarity, clustering, diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 9daa2eb..9f24a4f 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -14,6 +14,7 @@ "@deck.gl/mapbox": "^9.4.0", "fuse.js": "^7.5.0", "maplibre-gl": "^6.11.2", + "polygon-clipping": "^0.15.7", "react": "19.3.0", "react-aria-components": "^1.21.1", "react-dom": "19.3.0", @@ -2146,6 +2147,15 @@ "node": ">=20" } }, + "node_modules/polygon-clipping": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/polygon-clipping/-/polygon-clipping-0.15.7.tgz", + "integrity": "sha512-nhfdr83ECBg6xtqOAJab1tbksbBAOMUltN60bU+llHVOL0e5Onm1WpAXXWXVB39L8AJFssoIhEVuy/S90MmotA==", + "dependencies": { + "robust-predicates": "^3.0.2", + "splaytree": "^3.1.0" + } + }, "node_modules/postcss": { "version": "8.5.28", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", @@ -2390,6 +2400,11 @@ "protocol-buffers-schema": "^3.3.1" } }, + "node_modules/robust-predicates": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", + "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==" + }, "node_modules/rolldown": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.9.tgz", @@ -2439,6 +2454,14 @@ "node": ">=0.10.0" } }, + "node_modules/splaytree": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/splaytree/-/splaytree-3.2.3.tgz", + "integrity": "sha512-7OXrNWzy6CK+r7Ch9OLPBDTKfB6XlWHjX4P0RU5B3IgFuWPeYN0XtRtlexGRjgbQxpfaUve6jTAwBGWuGntz/w==", + "engines": { + "node": ">=18.20 || >=20" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 43edf13..bbc8d41 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -21,6 +21,7 @@ "@deck.gl/mapbox": "^9.4.0", "fuse.js": "^7.5.0", "maplibre-gl": "^6.11.2", + "polygon-clipping": "^0.15.7", "react": "19.3.0", "react-aria-components": "^1.21.1", "react-dom": "19.3.0", diff --git a/frontend/playwright.config.ts b/frontend/playwright.config.ts index 801a9c3..5058377 100644 --- a/frontend/playwright.config.ts +++ b/frontend/playwright.config.ts @@ -1,8 +1,4 @@ import { defineConfig, devices } from '@playwright/test'; -import { portBase, publicOrigin, dashboardOrigin } from './test/e2e/localServers.js'; - -// Parallel worktrees can select isolated ports instead of reusing a stale server. -const reuseExistingServer = !process.env['CI'] && !process.env['PLAYWRIGHT_PORT_BASE']; export default defineConfig({ testDir: './test/e2e', @@ -17,18 +13,18 @@ export default defineConfig({ projects: [ { name: 'public-desktop', - testMatch: /(?:accessibility|owner|public|feed-virtualizer)\.spec\.ts/, - use: { ...devices['Desktop Chrome'], baseURL: publicOrigin }, + testMatch: /(?:accessibility|owner|public)\.spec\.ts/, + use: { ...devices['Desktop Chrome'], baseURL: 'http://127.0.0.1:4173' }, }, { name: 'dashboard-desktop', testMatch: /dashboard\.spec\.ts/, - use: { ...devices['Desktop Chrome'], baseURL: dashboardOrigin }, + use: { ...devices['Desktop Chrome'], baseURL: 'http://127.0.0.1:4174' }, }, { name: 'dashboard-mobile', testMatch: /dashboard\.spec\.ts/, - use: { ...devices['Pixel 7'], baseURL: dashboardOrigin }, + use: { ...devices['Pixel 7'], baseURL: 'http://127.0.0.1:4174' }, }, { name: 'mobile-regression', @@ -38,19 +34,19 @@ export default defineConfig({ ], webServer: [ { - command: `VITE_APP_HOSTNAME=app.invalid VITE_SITE=ukmesh npm run dev -- --config playwright.vite.config.ts --host 127.0.0.1 --port ${portBase} --strictPort`, - port: portBase, - reuseExistingServer, + command: 'VITE_APP_HOSTNAME=app.invalid VITE_SITE=ukmesh npm run dev -- --host 127.0.0.1 --port 4173', + port: 4173, + reuseExistingServer: !process.env['CI'], }, { - command: `VITE_APP_HOSTNAME=127.0.0.1 VITE_SITE=ukmesh VITE_NETWORK=ukmesh VITE_RF_COVERAGE_ENABLED=true npm run dev -- --config playwright.vite.config.ts --host 127.0.0.1 --port ${portBase + 1} --strictPort`, - port: portBase + 1, - reuseExistingServer, + command: 'VITE_APP_HOSTNAME=127.0.0.1 VITE_SITE=ukmesh VITE_NETWORK=ukmesh VITE_RF_COVERAGE_ENABLED=true npm run dev -- --host 127.0.0.1 --port 4174', + port: 4174, + reuseExistingServer: !process.env['CI'], }, { - command: `VITE_APP_HOSTNAME=app.invalid VITE_SITE=dev npm run dev -- --config playwright.vite.config.ts --host 127.0.0.1 --port ${portBase + 2} --strictPort`, - port: portBase + 2, - reuseExistingServer, + command: 'VITE_APP_HOSTNAME=app.invalid VITE_SITE=dev npm run dev -- --host 127.0.0.1 --port 4175', + port: 4175, + reuseExistingServer: !process.env['CI'], }, ], }); diff --git a/frontend/public/sw-runtime.js b/frontend/public/sw-runtime.js index 88f2ba0..7bb7321 100644 --- a/frontend/public/sw-runtime.js +++ b/frontend/public/sw-runtime.js @@ -1,10 +1,10 @@ export const SW_CACHE_POLICY = Object.freeze({ - version: 'v7', - tileCache: 'meshcore-tiles-v7', - appCache: 'meshcore-app-v7', - metadataCache: 'meshcore-meta-v7', - previousTileCache: 'meshcore-tiles-v6', - previousAppCache: 'meshcore-app-v6', + version: 'v8', + tileCache: 'meshcore-tiles-v8', + appCache: 'meshcore-app-v8', + metadataCache: 'meshcore-meta-v8', + previousTileCache: 'meshcore-tiles-v7', + previousAppCache: 'meshcore-app-v7', maxTileEntries: 6_000, maxTileBytes: 96 * 1024 * 1024, pruneTargetRatio: 0.9, @@ -93,7 +93,7 @@ export function createTileMetadata(initial = []) { export function createServiceWorkerRuntime(environment) { const policy = { ...SW_CACHE_POLICY, ...(environment.policy ?? {}) }; const now = environment.now ?? Date.now; - const metadataUrl = new URL('/__meshcore_sw_metadata_v7__', environment.origin).toString(); + const metadataUrl = new URL('/__meshcore_sw_metadata_v8__', environment.origin).toString(); let metadata; let metadataState = { version: 1, diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 2308e8b..39312be 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -16,6 +16,7 @@ import { rfNodeCoverageState, useRfCoverage, type RfCoverageTierName } from './h import { useDashboardStats, type DashboardStats } from './hooks/useDashboardStats.js'; import { linkStateStore } from './hooks/useLinkState.js'; import { useAppMessageHandler } from './hooks/useAppMessageHandler.js'; +import { useMessageTags } from './hooks/useMessageTags.js'; import { HEATMAP_CAPABLE, PACKET_ARCS_CAPABLE, @@ -469,6 +470,8 @@ export const App: React.FC = () => { { network: networkFilter, observer: observerFilter }, scopeState.nodeEpoch, ); + // Live tag enrichment for the feed panel (tags land ~2s after a packet). + useMessageTags({ network: networkFilter, observer: observerFilter }, realtimeScopeKey); const wsState = wsConnection.readyState; wsConnectedRef.current = wsState === 'connected'; diff --git a/frontend/src/components/ConfirmedNeighbours.test.ts b/frontend/src/components/ConfirmedNeighbours.test.ts new file mode 100644 index 0000000..e841425 --- /dev/null +++ b/frontend/src/components/ConfirmedNeighbours.test.ts @@ -0,0 +1,77 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import React from 'react'; +import { renderToStaticMarkup } from 'react-dom/server'; +import { ConfirmedNeighbours, NeighbourContact, sortNeighbourLinks, timeAgo, type NodeLink } from './ConfirmedNeighbours.js'; + +const now = Date.parse('2026-09-07T12:00:00.000Z'); +const day = 86400_000; +const link: NodeLink = { + peer_id: 'A'.repeat(64), peer_name: 'Quiet repeater', observed_count: 107238, + itm_path_loss_db: 112.1, count_this_to_peer: 64146, count_peer_to_this: 43092, + last_observed: new Date(now - 2 * day).toISOString(), recent_count_72h: 0, +}; + +function contact(overrides: Partial = {}) { + return renderToStaticMarkup(React.createElement(NeighbourContact, { link: { ...link, ...overrides }, now })); +} + +test('last-contact dots follow the exact 24-hour and seven-day boundaries', () => { + for (const [age, colour, relative] of [ + [day - 1, 'online', '23h ago'], [day, 'amber', '1d ago'], + [7 * day, 'amber', '7d ago'], [7 * day + 1, 'danger', '7d ago'], + ] as const) { + const iso = new Date(now - age).toISOString(); + const html = contact({ last_observed: iso }); + assert.ok(html.includes(`background:var(--${colour})`)); + assert.ok(html.includes(`dateTime="${iso}"`)); + assert.ok(html.includes(relative)); + assert.match(html, /Last contact:/); + } +}); + +test('frequency depends only on recent contact, including zero with a high lifetime count', () => { + for (const [count, bucket] of [[0, 'RARE'], [1, 'OCCASIONAL'], [9, 'OCCASIONAL'], [10, 'ACTIVE'], [1001, 'ACTIVE']] as const) { + const html = contact({ recent_count_72h: count }); + assert.ok(html.includes(`>${bucket}`)); + assert.ok(html.includes(`${count} ${count === 1 ? 'contact' : 'contacts'} in last 72h`)); + } +}); + +test('unknown timestamps are neutral and clock skew never produces negative relative times', () => { + for (const last_observed of [null, '', 'invalid']) { + const html = contact({ last_observed }); + assert.match(html, /Last contact: Unknown/); + assert.ok(html.includes('background:var(--text-muted)')); + assert.doesNotMatch(html, / { + const input: NodeLink[] = [ + { ...link, peer_id: 'unknown', last_observed: null }, + { ...link, peer_id: 'frequent', recent_count_72h: 20 }, + { ...link, peer_id: 'newest', last_observed: new Date(now).toISOString(), recent_count_72h: 2 }, + ]; + const snapshot = structuredClone(input); + assert.deepEqual(sortNeighbourLinks(input, 'contact').map(x => x.peer_id), ['newest', 'frequent', 'unknown']); + assert.deepEqual(sortNeighbourLinks(input, 'frequency').map(x => x.peer_id), ['frequent', 'newest', 'unknown']); + assert.deepEqual(input, snapshot); + assert.deepEqual(sortNeighbourLinks([{ ...link, peer_id: 'b', last_observed: null }, { ...link, peer_id: 'a', last_observed: null }], 'contact').map(x => x.peer_id), ['a', 'b']); +}); + +test('card renders contact, lifetime context, sort labels, empty state and safe peer names', () => { + const html = renderToStaticMarkup(React.createElement(ConfirmedNeighbours, { links: [{ ...link, peer_name: '' }] })); + for (const text of ['107238× seen', '112 dB loss', 'TX: 64146', 'RX: 43092', 'RARE', 'Sort neighbours', 'Last contact', 'Most frequent', 'Contact includes relayed traffic.']) { + assert.ok(html.includes(text), text); + } + assert.ok(html.includes('<script>bad</script>')); + assert.ok(html.includes('flex-wrap:wrap')); + assert.ok(html.includes('min-width:0;overflow-wrap:anywhere')); + assert.match(renderToStaticMarkup(React.createElement(ConfirmedNeighbours, { links: [] })), /No neighbours found/); +}); diff --git a/frontend/src/components/ConfirmedNeighbours.tsx b/frontend/src/components/ConfirmedNeighbours.tsx new file mode 100644 index 0000000..aa6d70e --- /dev/null +++ b/frontend/src/components/ConfirmedNeighbours.tsx @@ -0,0 +1,123 @@ +import { useMemo, useState } from 'react'; + +export interface NodeLink { + peer_id: string; + peer_name: string | null; + observed_count: number; + itm_path_loss_db: number | null; + count_this_to_peer: number; + count_peer_to_this: number; + last_observed: string | null; + recent_count_72h: number; +} + +// Shared with the repeater page's existing last-seen and packet timestamps. +export function timeAgo(iso: string, now = Date.now()): string { + const timestamp = Date.parse(iso); + if (!Number.isFinite(timestamp)) return 'Unknown'; + const secs = Math.max(0, Math.floor((now - timestamp) / 1000)); + if (secs < 60) return `${secs}s ago`; + if (secs < 3600) return `${Math.floor(secs / 60)}m ago`; + if (secs < 86400) return `${Math.floor(secs / 3600)}h ago`; + return `${Math.floor(secs / 86400)}d ago`; +} + +function contactTime(link: NodeLink): number { + const timestamp = link.last_observed ? Date.parse(link.last_observed) : NaN; + return Number.isFinite(timestamp) ? timestamp : -Infinity; +} + +type NeighbourSort = 'contact' | 'frequency'; + +export function sortNeighbourLinks(links: NodeLink[], sort: NeighbourSort): NodeLink[] { + return [...links].sort((a, b) => { + if (sort === 'frequency' && a.recent_count_72h !== b.recent_count_72h) { + return b.recent_count_72h - a.recent_count_72h; + } + const aTime = contactTime(a); + const bTime = contactTime(b); + if (aTime !== bTime) return aTime > bTime ? -1 : 1; + return a.peer_id.localeCompare(b.peer_id); + }); +} + +export function NeighbourContact({ link, now = Date.now() }: { link: NodeLink; now?: number }) { + const timestamp = contactTime(link); + const known = Number.isFinite(timestamp); + const age = Math.max(0, now - timestamp); + const colour = !known ? 'var(--text-muted)' + : age < 86400_000 ? 'var(--online)' + : age <= 7 * 86400_000 ? 'var(--amber)' : 'var(--danger)'; + // Last contact is any-direction link-worker evidence, not "last direct". + // Recent counts are public src/rx packet rows, including relayed traffic. + // A healthy but quiet link is deliberately RARE even with many lifetime hits. + const frequency = link.recent_count_72h >= 10 ? 'ACTIVE' + : link.recent_count_72h >= 1 ? 'OCCASIONAL' : 'RARE'; + const frequencyColour = frequency === 'ACTIVE' ? 'var(--online)' + : frequency === 'OCCASIONAL' ? 'var(--amber)' : 'var(--text-muted)'; + + return ( +
+ + + + {frequency} + {link.recent_count_72h} {link.recent_count_72h === 1 ? 'contact' : 'contacts'} in last 72h + +
+ ); +} + +export function ConfirmedNeighbours({ links }: { links: NodeLink[] }) { + const [sort, setSort] = useState('contact'); + const sortedLinks = useMemo(() => sortNeighbourLinks(links, sort), [links, sort]); + + if (links.length === 0) { + return

No neighbours found for this node.

; + } + + return ( + <> +

+ Nodes with confirmed two-way communication. Contact includes relayed traffic. + {' '}Active: 10+ contacts in 72h · Occasional: 1–9 · Rare: 0. +

+ +
+ {sortedLinks.map(link => ( +
+
+ + {link.peer_name || `${link.peer_id.slice(0, 12)}...`} + + + {link.peer_id.slice(0, 16)}…{link.peer_id.slice(-8)} + +
+ +
+ {link.observed_count}× seen + {link.itm_path_loss_db !== null && · {Math.round(link.itm_path_loss_db)} dB loss} + · TX: {link.count_this_to_peer} + · RX: {link.count_peer_to_this} +
+
+ ))} +
+ + ); +} diff --git a/frontend/src/components/Map/RegionsMap.tsx b/frontend/src/components/Map/RegionsMap.tsx new file mode 100644 index 0000000..2e86d6a --- /dev/null +++ b/frontend/src/components/Map/RegionsMap.tsx @@ -0,0 +1,342 @@ +import { useCallback, useEffect, useState } from 'react'; +import * as maplibregl from 'maplibre-gl'; +import { MapLibreMap } from './MapLibreMap.js'; +import { DEFAULT_CENTER } from './mapConfig.js'; +import { + buildRegionAreasGeoJSON, + buildRegionRepeatersGeoJSON, + type RegionRepeaterFilters, +} from './regionOverlay.js'; +import type { RegionRepeater, RegionSummary } from '../../pages/regionsModel.js'; + +const REGION_AREAS_FILL_LAYER = 'region-areas-fill'; +const REGION_AREAS_LINE_LAYER = 'region-areas-line'; +const REGION_REPEATER_HIT_LAYER = 'region-repeater-hit'; +const REGION_REPEATER_HALO_LAYER = 'region-repeater-halo'; +const REGION_REPEATER_LAYER = 'region-repeater'; +const REGIONS_INITIAL_VIEW = { lat: DEFAULT_CENTER[0], lon: -2.3, zoom: 5.7 } as const; + +type RegionsMapProps = { + regions: readonly RegionSummary[]; + includeLowConfidence: boolean; + repeaters: readonly RegionRepeater[]; + shapesVisible: boolean; + repeatersVisible: boolean; + roleFilter: 'repeaters' | 'all'; + statusFilter: 'all' | 'green' | 'red'; + regionFilter: string | null; + memberIds: ReadonlySet | null; + selectedRegion: string | null; + selectedNodeId: string | null; + network?: string; + observer?: string; + privacyGeneration: number; + onRegionSelect: (region: string) => void; + onRepeaterSelect: (nodeId: string) => void; +}; + +type RegionsMapOverlayProps = Omit & { + map: maplibregl.Map; +}; + +function getFirstSymbolLayerId(map: maplibregl.Map): string | undefined { + for (const layer of map.getStyle().layers) { + if (layer.type === 'symbol') return layer.id; + } + return undefined; +} + +function addRegionsLayer(map: maplibregl.Map, layer: maplibregl.LayerSpecification): void { + if (map.getLayer(layer.id)) return; + const beforeId = getFirstSymbolLayerId(map); + if (beforeId) map.addLayer(layer, beforeId); + else map.addLayer(layer); +} + +const regionAreaBase = { + type: 'fill' as const, + source: 'region-areas', +}; + +const regionLineBase = { + type: 'line' as const, + source: 'region-areas', + layout: { + 'line-cap': 'round' as const, + 'line-join': 'round' as const, + }, +}; + +const regionRepeaterBase = { + type: 'circle' as const, + source: 'region-repeaters', +}; + +function RegionsMapOverlay({ + map, + regions, + repeaters, + shapesVisible, + repeatersVisible, + roleFilter, + statusFilter, + regionFilter, + memberIds, + selectedRegion, + selectedNodeId, + onRegionSelect, + onRepeaterSelect, +}: RegionsMapOverlayProps): null { + useEffect(() => { + map.addSource('region-areas', { + type: 'geojson', + data: { type: 'FeatureCollection', features: [] }, + }); + map.addSource('region-repeaters', { + type: 'geojson', + data: { type: 'FeatureCollection', features: [] }, + }); + + addRegionsLayer(map, { + ...regionAreaBase, + id: REGION_AREAS_FILL_LAYER, + paint: { + 'fill-color': ['get', 'color'], + 'fill-opacity': ['get', 'fill_opacity'], + }, + }); + addRegionsLayer(map, { + ...regionLineBase, + id: REGION_AREAS_LINE_LAYER, + paint: { + 'line-color': ['get', 'color'], + 'line-width': ['get', 'line_width'], + 'line-opacity': ['get', 'line_opacity'], + }, + }); + addRegionsLayer(map, { + ...regionRepeaterBase, + id: REGION_REPEATER_HIT_LAYER, + paint: { + 'circle-radius': ['interpolate', ['linear'], ['zoom'], 5, 9, 8, 11, 12, 15], + 'circle-color': '#ffffff', + 'circle-opacity': 0, + 'circle-stroke-opacity': 0, + }, + }); + addRegionsLayer(map, { + ...regionRepeaterBase, + id: REGION_REPEATER_HALO_LAYER, + paint: { + 'circle-radius': ['+', ['get', 'radius'], 5], + 'circle-color': [ + 'case', + ['get', 'selected'], '#f8fafc', + ['==', ['get', 'status'], 'green'], '#22c55e', + '#ef4444', + ], + 'circle-opacity': ['case', ['get', 'selected'], 0.3, 0.08], + 'circle-blur': 0.55, + }, + }); + addRegionsLayer(map, { + ...regionRepeaterBase, + id: REGION_REPEATER_LAYER, + paint: { + 'circle-radius': [ + 'interpolate', ['linear'], ['zoom'], + 5, ['case', ['==', ['get', 'status'], 'green'], 3.6, 3], + 8, ['case', ['==', ['get', 'status'], 'green'], 5.4, 4.2], + 12, ['case', ['==', ['get', 'status'], 'green'], 7.5, 6], + ], + 'circle-color': [ + 'case', + ['get', 'selected'], '#f8fafc', + ['==', ['get', 'status'], 'green'], '#22c55e', + '#ef4444', + ], + 'circle-opacity': [ + 'case', + ['get', 'selected'], 1, + ['==', ['get', 'tier'], 'confirmed'], 0.95, + ['==', ['get', 'tier'], 'probable'], 0.8, + ['==', ['get', 'tier'], 'single'], 0.55, + 0.72, + ], + 'circle-stroke-color': [ + 'case', + ['get', 'selected'], '#ffffff', + ['==', ['get', 'status'], 'green'], '#052e16', + '#450a0a', + ], + 'circle-stroke-width': ['case', ['get', 'selected'], 2.4, 1.1], + 'circle-stroke-opacity': 0.92, + }, + }); + + const handleRepeaterClick = (event: maplibregl.MapLayerMouseEvent) => { + const nodeId = event.features?.[0]?.properties?.['node_id']; + if (typeof nodeId === 'string' && nodeId.length > 0) onRepeaterSelect(nodeId); + }; + const handleAreaClick = (event: maplibregl.MapLayerMouseEvent) => { + // A repeater dot sitting inside a shape must win over the shape itself. + if (map.queryRenderedFeatures(event.point, { layers: [REGION_REPEATER_HIT_LAYER] }).length > 0) return; + const region = event.features?.[0]?.properties?.['region']; + if (typeof region === 'string' && region.length > 0) onRegionSelect(region); + }; + const handlePointerEnter = () => { map.getCanvas().style.cursor = 'pointer'; }; + const handlePointerLeave = () => { map.getCanvas().style.cursor = ''; }; + map.on('click', REGION_REPEATER_HIT_LAYER, handleRepeaterClick); + map.on('click', REGION_AREAS_FILL_LAYER, handleAreaClick); + map.on('mouseenter', REGION_REPEATER_HIT_LAYER, handlePointerEnter); + map.on('mouseleave', REGION_REPEATER_HIT_LAYER, handlePointerLeave); + map.on('mouseenter', REGION_AREAS_FILL_LAYER, handlePointerEnter); + map.on('mouseleave', REGION_AREAS_FILL_LAYER, handlePointerLeave); + + const navigation = new maplibregl.NavigationControl({ showCompass: false }); + map.addControl(navigation, 'top-right'); + + return () => { + map.off('click', REGION_REPEATER_HIT_LAYER, handleRepeaterClick); + map.off('click', REGION_AREAS_FILL_LAYER, handleAreaClick); + map.off('mouseenter', REGION_REPEATER_HIT_LAYER, handlePointerEnter); + map.off('mouseleave', REGION_REPEATER_HIT_LAYER, handlePointerLeave); + map.off('mouseenter', REGION_AREAS_FILL_LAYER, handlePointerEnter); + map.off('mouseleave', REGION_AREAS_FILL_LAYER, handlePointerLeave); + try { map.removeControl(navigation); } catch { /* map may already be removing */ } + try { + for (const layerId of [ + REGION_REPEATER_LAYER, + REGION_REPEATER_HALO_LAYER, + REGION_REPEATER_HIT_LAYER, + REGION_AREAS_LINE_LAYER, + REGION_AREAS_FILL_LAYER, + ]) { + if (map.getLayer(layerId)) map.removeLayer(layerId); + } + if (map.getSource('region-repeaters')) map.removeSource('region-repeaters'); + if (map.getSource('region-areas')) map.removeSource('region-areas'); + } catch { /* MapLibreMap owns final map teardown */ } + }; + }, [map, onRegionSelect, onRepeaterSelect]); + + useEffect(() => { + const areasSource = map.getSource('region-areas') as maplibregl.GeoJSONSource | undefined; + areasSource?.setData(buildRegionAreasGeoJSON(regions, selectedRegion)); + }, [map, regions, selectedRegion]); + + useEffect(() => { + const repeatersSource = map.getSource('region-repeaters') as maplibregl.GeoJSONSource | undefined; + if (!repeatersSource) return; + const filters: RegionRepeaterFilters = { + roleFilter, + statusFilter, + regionFilter, + memberIds, + selectedNodeId, + }; + const featureCollection = buildRegionRepeatersGeoJSON(repeaters, filters); + // Radius is a data-driven paint input, so carry a fixed per-feature radius. + repeatersSource.setData({ + ...featureCollection, + features: featureCollection.features.map((feature) => ({ + ...feature, + properties: { ...feature.properties, radius: feature.properties['status'] === 'green' ? 4.5 : 3.8 }, + })), + }); + }, [map, repeaters, roleFilter, statusFilter, regionFilter, memberIds, selectedNodeId]); + + useEffect(() => { + for (const [layerId, visible] of [ + [REGION_AREAS_FILL_LAYER, shapesVisible], + [REGION_AREAS_LINE_LAYER, shapesVisible], + [REGION_REPEATER_HIT_LAYER, repeatersVisible], + [REGION_REPEATER_HALO_LAYER, repeatersVisible], + [REGION_REPEATER_LAYER, repeatersVisible], + ] as const) { + if (map.getLayer(layerId)) { + map.setLayoutProperty(layerId, 'visibility', visible ? 'visible' : 'none'); + } + } + }, [map, shapesVisible, repeatersVisible]); + + return null; +} + +export function RegionsMap({ + regions, + includeLowConfidence, + repeaters, + shapesVisible, + repeatersVisible, + roleFilter, + statusFilter, + regionFilter, + memberIds, + selectedRegion, + selectedNodeId, + network, + observer, + privacyGeneration, + onRegionSelect, + onRepeaterSelect, +}: RegionsMapProps): React.ReactElement { + const [map, setMap] = useState(null); + const handleMapReady = useCallback((nextMap: maplibregl.Map) => setMap(nextMap), []); + const shapedRegions = regions.filter((region) => region.members.some((member) => member.lat != null && member.lon != null)).length; + const locatedRepeaters = repeaters.filter((repeater) => Number.isFinite(repeater.lat) && Number.isFinite(repeater.lon)).length; + + return ( +
+ + {map && ( + + )} +
+ Region scoping + {shapedRegions} region shapes · {locatedRepeaters} mapped repeaters + + {regionFilter + ? `Filtered to ${regionFilter}` + : includeLowConfidence + ? 'All observed regions' + : 'Established regions · low-confidence hidden'} + +
+
+ Region-capable repeater · solid = strong evidence + No scoped relay observed + Region footprint (convex hull of members relaying ≥10% of region traffic; bridge repeaters excluded) +
+
+ ); +} diff --git a/frontend/src/components/Map/regionOverlay.test.ts b/frontend/src/components/Map/regionOverlay.test.ts new file mode 100644 index 0000000..121a614 --- /dev/null +++ b/frontend/src/components/Map/regionOverlay.test.ts @@ -0,0 +1,109 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { buildRegionAreasGeoJSON, buildRegionRepeatersGeoJSON, type RegionRepeaterFilters } from './regionOverlay.js'; +import type { RegionRepeater, RegionSummary } from '../../pages/regionsModel.js'; + +function repeater(overrides: Partial = {}): RegionRepeater { + return { + nodeId: 'A'.repeat(64), + name: 'Repeater', + role: 2, + lat: 54, + lon: -1, + status: 'green', + evidence: 'scoped', + scopedPackets: 10, + unscopedPackets7d: 0, + regions: { yorkshire: 10 }, + ...overrides, + }; +} + +const baseFilters: RegionRepeaterFilters = { + roleFilter: 'all', + statusFilter: 'all', + regionFilter: null, + memberIds: null, + selectedNodeId: null, +}; + +test('buildRegionRepeatersGeoJSON filters by role, status, region and coordinates', () => { + const repeaters = [ + repeater(), + repeater({ nodeId: 'B'.repeat(64), role: 1, regions: { yorkshire: 5 } }), + repeater({ nodeId: 'C'.repeat(64), status: 'red', scopedPackets: 0, regions: {} }), + repeater({ nodeId: 'D'.repeat(64), lat: null, lon: null }), + ]; + const all = buildRegionRepeatersGeoJSON(repeaters, baseFilters); + assert.equal(all.features.length, 3); + assert.equal(all.features.some((feature) => feature.properties?.['node_id'] === 'D'.repeat(64)), false); + + const repeatersOnly = buildRegionRepeatersGeoJSON(repeaters, { ...baseFilters, roleFilter: 'repeaters' }); + assert.deepEqual(repeatersOnly.features.map((feature) => feature.properties?.['status']), ['green', 'red']); + + const greenOnly = buildRegionRepeatersGeoJSON(repeaters, { ...baseFilters, statusFilter: 'green' }); + assert.equal(greenOnly.features.length, 2); + + const byMembers = buildRegionRepeatersGeoJSON(repeaters, { + ...baseFilters, regionFilter: 'yorkshire', memberIds: new Set(['A'.repeat(64)]), + }); + assert.equal(byMembers.features.length, 1); + assert.equal(byMembers.features[0]?.properties?.['node_id'], 'A'.repeat(64)); + + const byRegionMap = buildRegionRepeatersGeoJSON(repeaters, { ...baseFilters, regionFilter: 'yorkshire' }); + assert.equal(byRegionMap.features.length, 2); +}); + +test('buildRegionRepeatersGeoJSON carries selection and tier properties', () => { + const collection = buildRegionRepeatersGeoJSON( + [repeater(), repeater({ nodeId: 'E'.repeat(64), scopedPackets: 1 })], + { ...baseFilters, selectedNodeId: 'A'.repeat(64) }, + ); + const selected = collection.features.find((feature) => feature.properties?.['node_id'] === 'A'.repeat(64)); + const single = collection.features.find((feature) => feature.properties?.['node_id'] === 'E'.repeat(64)); + assert.equal(selected?.properties?.['selected'], true); + assert.equal(selected?.properties?.['tier'], 'confirmed'); + assert.equal(single?.properties?.['tier'], 'single'); +}); + +test('buildRegionAreasGeoJSON skips regions without a ring and marks selection', () => { + const shapeable: RegionSummary = { + name: 'yorkshire', packetsTotal: 100, memberCount: 3, repeaterMemberCount: 3, + confidence: 'confirmed', firstSeen: null, lastSeen: null, + members: [ + { nodeId: 'A', name: 'A', role: 2, lat: 54, lon: -1, packets: 9 }, + { nodeId: 'B', name: 'B', role: 2, lat: 54.2, lon: -0.8, packets: 8 }, + { nodeId: 'C', name: 'C', role: 2, lat: 53.8, lon: -1.4, packets: 7 }, + ], + }; + const tooSmall: RegionSummary = { ...shapeable, name: 'micro', members: [shapeable.members[0]!] }; + const collection = buildRegionAreasGeoJSON([shapeable, tooSmall], 'yorkshire'); + assert.equal(collection.features.length, 1); + assert.equal(collection.features[0]?.properties?.['region'], 'yorkshire'); + assert.equal(collection.features[0]?.properties?.['selected'], true); + assert.match(String(collection.features[0]?.properties?.['color']), /^#[0-9a-f]{6}$/); + const ring = collection.features[0]?.geometry.coordinates[0]!; + assert.deepEqual(ring[0], ring[ring.length - 1]); +}); + +test('buildRegionAreasGeoJSON hides other regions when one is selected', () => { + const base: RegionSummary = { + name: 'yorkshire', packetsTotal: 100, memberCount: 3, repeaterMemberCount: 3, + confidence: 'confirmed', firstSeen: null, lastSeen: null, + members: [ + { nodeId: 'A', name: 'A', role: 2, lat: 54, lon: -1, packets: 9 }, + { nodeId: 'B', name: 'B', role: 2, lat: 54.2, lon: -0.8, packets: 8 }, + { nodeId: 'C', name: 'C', role: 2, lat: 53.8, lon: -1.4, packets: 7 }, + ], + }; + const northwest: RegionSummary = { + ...base, + name: 'northwest', + members: base.members.map((member) => ({ ...member, lat: member.lat! + 0.5, lon: member.lon! - 1 })), + }; + assert.equal(buildRegionAreasGeoJSON([base, northwest], null).features.length, 2); + const selectedOnly = buildRegionAreasGeoJSON([base, northwest], 'northwest'); + assert.equal(selectedOnly.features.length, 1); + assert.equal(selectedOnly.features[0]?.properties?.['region'], 'northwest'); + assert.equal(selectedOnly.features[0]?.properties?.['selected'], true); +}); diff --git a/frontend/src/components/Map/regionOverlay.ts b/frontend/src/components/Map/regionOverlay.ts new file mode 100644 index 0000000..fe32091 --- /dev/null +++ b/frontend/src/components/Map/regionOverlay.ts @@ -0,0 +1,99 @@ +import { + confidenceFor, + regionColor, + regionRing, + type RegionRepeater, + type RegionSummary, +} from '../../pages/regionsModel.js'; + +type RegionFeatureProperties = { + [key: string]: string | number | boolean | null; +}; + +export type RegionRepeaterFilters = { + roleFilter: 'repeaters' | 'all'; + statusFilter: 'all' | 'green' | 'red'; + regionFilter: string | null; + memberIds: ReadonlySet | null; + selectedNodeId: string | null; +}; + +function hasCoordinates(repeater: RegionRepeater): repeater is RegionRepeater & { lat: number; lon: number } { + const { lat, lon } = repeater; + return lat != null && lon != null + && Number.isFinite(lat) && Number.isFinite(lon) + && lat >= -90 && lat <= 90 + && lon >= -180 && lon <= 180 + && !(Math.abs(lat) < 1e-6 && Math.abs(lon) < 1e-6); +} + +export function buildRegionAreasGeoJSON( + regions: readonly RegionSummary[], + selectedRegion: string | null, +): GeoJSON.FeatureCollection { + // Selecting a region hides every other region's shape so the chosen + // footprint is unambiguous; clearing the selection restores all shapes. + const visibleRegions = selectedRegion + ? regions.filter((region) => region.name === selectedRegion) + : regions; + return { + type: 'FeatureCollection', + features: visibleRegions.flatMap((region) => { + const ring = regionRing(region); + if (!ring) return []; + const selected = region.name === selectedRegion; + return [{ + type: 'Feature' as const, + geometry: { type: 'Polygon' as const, coordinates: [ring] }, + properties: { + region: region.name, + color: regionColor(region.name), + selected, + fill_opacity: selected ? 0.3 : 0.13, + line_opacity: selected ? 1 : 0.55, + line_width: selected ? 2.4 : 1.3, + packets: region.packetsTotal, + members: region.memberCount, + }, + }]; + }), + }; +} + +export function buildRegionRepeatersGeoJSON( + repeaters: readonly RegionRepeater[], + filters: RegionRepeaterFilters, +): GeoJSON.FeatureCollection { + return { + type: 'FeatureCollection', + features: repeaters + .filter(hasCoordinates) + .filter((repeater) => { + if (filters.roleFilter === 'repeaters' && repeater.role !== 2) return false; + if (filters.statusFilter !== 'all' && repeater.status !== filters.statusFilter) return false; + if (filters.regionFilter) { + if (filters.memberIds) { + if (!filters.memberIds.has(repeater.nodeId)) return false; + } else if (!((repeater.regions[filters.regionFilter] ?? 0) > 0)) { + return false; + } + } + return true; + }) + .map((repeater) => ({ + type: 'Feature' as const, + geometry: { type: 'Point' as const, coordinates: [repeater.lon, repeater.lat] }, + properties: { + node_id: repeater.nodeId, + name: repeater.name, + label: repeater.name ?? repeater.nodeId.slice(0, 8), + status: repeater.status, + tier: repeater.status === 'green' ? confidenceFor(repeater.scopedPackets) : 'red', + scoped_packets: repeater.scopedPackets, + unscoped_packets_7d: repeater.unscopedPackets7d, + region_count: Object.keys(repeater.regions).length, + selected: repeater.nodeId === filters.selectedNodeId, + }, + })), + }; +} diff --git a/frontend/src/components/PacketFeed.tsx b/frontend/src/components/PacketFeed.tsx index e8d1ce6..288be05 100644 --- a/frontend/src/components/PacketFeed.tsx +++ b/frontend/src/components/PacketFeed.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useRef, useState } from 'react'; import { useMessages, useNodeMap } from '../hooks/useNodes.js'; -import { aggregatedPacketObserverIataLabel } from '../hooks/packetFeed.js'; +import { aggregatedPacketObserverIataLabel, messageTagKind, messageTagTitle } from '../hooks/packetFeed.js'; import { useOverlayStore } from '../store/overlayStore.js'; import { useWatchlist } from '../hooks/useWatchlist.js'; import type { AggregatedPacket } from '../hooks/useNodes.js'; @@ -91,6 +91,11 @@ const PacketFeedItem: React.FC = React.memo(({ } + {messageTagKind(p) && ( + + {messageTagKind(p)} + + )}
+ {TELEMETRY_SERIES.map((series) => ( { value={formatUptime(latestTelemetry?.uptimeSecs ?? null)} meta={latestTelemetry?.uptimeSecs == null ? 'No telemetry yet' : `${latestTelemetry.uptimeSecs}s reported`} /> - 0)} /> + + + 0)} compareSeries={compareChartSeries} /> +
@@ -385,9 +672,11 @@ export const OwnerPortalPage: React.FC = () => {

Node status

Latest nullable diagnostics reported by the node.

- {live?.status?.sampled_at ? fmtTs(live.status.sampled_at) : 'No sample'} + {live ? (live.status?.sampled_at ? fmtTs(live.status.sampled_at) : 'No sample') : '—'} - + + +
@@ -395,14 +684,18 @@ export const OwnerPortalPage: React.FC = () => {

Direct Sender Map

+
+

Alerts

+
+ {live?.alerts.length === 0 ?

No active alerts.

: null} {(live?.alerts ?? []).map((alert, idx) => (
{alert.level.toUpperCase()} @@ -410,15 +703,21 @@ export const OwnerPortalPage: React.FC = () => {
))}
+

Advert Trend

- + + {(live?.advertTrend24h ?? []).some((point) => point.adverts > 0) + ? + :

No adverts observed in the last 24 hours.

} +

RF Link Health

+
{(live?.linkHealth ?? []).slice(0, 8).map((link) => (
@@ -438,10 +737,12 @@ export const OwnerPortalPage: React.FC = () => {

No link health data has been calculated for this node yet.

) : null}
+

Direct Senders

+
{(live?.incomingPeers ?? []).slice(0, 8).map((peer) => (
@@ -459,6 +760,7 @@ export const OwnerPortalPage: React.FC = () => {

No direct sender nodes found in the last 24 hours.

) : null}
+
@@ -468,6 +770,7 @@ export const OwnerPortalPage: React.FC = () => {

Other nodes that received packets transmitted by this repeater (last 7d)

+
{(live?.heardBy ?? []).slice(0, 8).map((peer) => (
@@ -487,20 +790,24 @@ export const OwnerPortalPage: React.FC = () => {

No nodes have received packets from this node in the last 7 days.

) : null}
+

Heard neighbors

-

The latest neighbor sample, sorted by last-seen recency (up to 32 nodes).

+

Reported neighbors or directly received senders, sorted by last-seen recency (up to 32 nodes).

- + + +

Live Packets Received By {nodeRoleLabel(live?.ownerNode.role ?? null)}

+
{(live?.recentPackets ?? []).map((packet, idx) => (
@@ -518,11 +825,30 @@ export const OwnerPortalPage: React.FC = () => {
))} {(live?.recentPackets ?? []).length === 0 ? ( -

No packets received by this node yet.

+

No packets received by this node in the last 30 days.

) : null}
+
+ + {isPacketSharingOwner(ownerSessionKey) ? ( + + ) : null} + ) : null} diff --git a/frontend/src/pages/RegionsPage.tsx b/frontend/src/pages/RegionsPage.tsx new file mode 100644 index 0000000..7514d58 --- /dev/null +++ b/frontend/src/pages/RegionsPage.tsx @@ -0,0 +1,380 @@ +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { LoadingIndicator } from '../components/LoadingIndicator.js'; +import { RegionsMap } from '../components/Map/RegionsMap.js'; +import { getCurrentSite } from '../config/site.js'; +import type { SiteConfig } from '../config/site.js'; +import { useRuntimeFeatures } from '../config/runtimeFeatures.js'; +import { fetchJson, withScopeParams } from '../utils/api.js'; +import { + compactNumber, + confidenceFor, + formatGeneratedAt, + isEstablished, + memberIdSet, + regionColor, + regionsForDisplay, + type RegionRepeater, + type RegionRepeatersPayload, + type RegionSummary, + type RegionSummaryPayload, +} from './regionsModel.js'; +import './regions-page.css'; + +const CONFIDENCE_LABELS: Record = { + confirmed: 'Strong evidence — 4 or more scoped packets', + probable: 'Moderate evidence — 2-3 scoped packets', + single: 'Single observation — could be chance', +}; + +export const RegionsPage: React.FC = () => { + const site = getCurrentSite(); + const { privacyGeneration } = useRuntimeFeatures(); + const network = site.networkFilter ?? site.network; + const observer = site.observerId; + const [summary, setSummary] = useState(null); + const [repeatersPayload, setRepeatersPayload] = useState(null); + const [error, setError] = useState(null); + const [selectedRegion, setSelectedRegion] = useState(null); + const [selectedNodeId, setSelectedNodeId] = useState(null); + const [shapesVisible, setShapesVisible] = useState(true); + const [repeatersVisible, setRepeatersVisible] = useState(true); + const [roleFilter, setRoleFilter] = useState<'repeaters' | 'all'>('repeaters'); + const [statusFilter, setStatusFilter] = useState<'all' | 'green' | 'red'>('all'); + const [includeLowConfidence, setIncludeLowConfidence] = useState(false); + + useEffect(() => { + const controller = new AbortController(); + setSummary(null); + setRepeatersPayload(null); + setError(null); + setSelectedRegion(null); + setSelectedNodeId(null); + Promise.all([ + fetchJson( + withScopeParams('/api/regions/summary', { network, observer }), + { signal: controller.signal, cache: 'no-store' }, + { timeoutMs: 15_000, maxBytes: 8 * 1024 * 1024 }, + ), + fetchJson( + withScopeParams('/api/regions/repeaters', { network, observer }), + { signal: controller.signal, cache: 'no-store' }, + { timeoutMs: 15_000, maxBytes: 8 * 1024 * 1024 }, + ), + ]) + .then(([nextSummary, nextRepeaters]) => { + if (controller.signal.aborted) return; + setSummary(nextSummary); + setRepeatersPayload(nextRepeaters); + }) + .catch((reason: unknown) => { + if ((reason as DOMException).name !== 'AbortError') setError((reason as Error).message); + }); + return () => controller.abort(); + }, [network, observer, privacyGeneration]); + + const regions = useMemo(() => summary?.regions ?? [], [summary]); + const displayRegions = useMemo( + () => regionsForDisplay(regions, includeLowConfidence), + [regions, includeLowConfidence], + ); + const hiddenRegionCount = regions.length - displayRegions.length; + const repeaters = useMemo(() => repeatersPayload?.repeaters ?? [], [repeatersPayload]); + const stats = useMemo(() => { + let scopedPackets = 0; + let memberships = 0; + for (const region of regions) { + scopedPackets += region.packetsTotal; + memberships += region.memberCount; + } + let green = 0; + let red = 0; + for (const repeater of repeaters) { + if (repeater.status === 'green') green += 1; + else red += 1; + } + return { regions: regions.length, scopedPackets, memberships, green, red }; + }, [regions, repeaters]); + const selectedRegionData = useMemo( + () => regions.find((region) => region.name === selectedRegion) ?? null, + [regions, selectedRegion], + ); + const selectedRepeater = useMemo( + () => repeaters.find((repeater) => repeater.nodeId === selectedNodeId) ?? null, + [repeaters, selectedNodeId], + ); + const memberIds = useMemo( + () => (selectedRegionData ? memberIdSet(selectedRegionData) : null), + [selectedRegionData], + ); + const handleRegionSelect = useCallback((region: string) => { + setSelectedRegion(region); + setSelectedNodeId(null); + }, []); + const handleRepeaterSelect = useCallback((nodeId: string) => { + setSelectedNodeId(nodeId); + }, []); + const handleLowConfidenceToggle = useCallback((checked: boolean) => { + setIncludeLowConfidence(checked); + if (!checked) { + // A region that just became hidden must not stay selected. + setSelectedRegion((current) => { + const target = current ? regions.find((region) => region.name === current) : undefined; + return target && !isEstablished(target) ? null : current; + }); + } + }, [regions]); + const generatedLabel = formatGeneratedAt(summary?.generatedAt ?? null); + + return ( +
+
+
+

Network intelligence

+

MeshCore regions

+

+ MeshCore scoped flood traffic carries a per-region transport code. This map shows the + regions active on the UK mesh, the repeaters observed relaying scoped traffic (green), + and repeaters with no scoped relay observed (red). Attribution is a cryptographic + candidate-name test against the region transport code — private regions stay + unattributed by design. Region names are community-defined keys, so shapes show where + scoped traffic is relayed (bridge repeaters excluded) rather than official boundaries. +

+
+
+ + + + + + +
+
+ + {error &&
{error}
} + {!summary && !error && } + + {summary && ( + <> +
+
{stats.regions}observed regions
+
{compactNumber(stats.scopedPackets)}scoped packets
+
{compactNumber(stats.green)}region-capable nodes
+
{compactNumber(stats.red)}no scoped relay observed
+
{compactNumber(stats.memberships)}relay memberships
+
+ +
+ + + +
+ + )} +
+ ); +}; + +export function RepeaterPanel({ repeater, site, onClear }: { + repeater: RegionRepeater; + site: SiteConfig; + onClear: () => void; +}): React.ReactElement { + const regionEntries = Object.entries(repeater.regions).sort((a, b) => b[1] - a[1]).slice(0, 12); + const tier = repeater.status === 'green' ? confidenceFor(repeater.scopedPackets) : null; + const regionCount = Object.keys(repeater.regions).length; + return ( +
+

Selected repeater

+ {repeater.name ?? repeater.nodeId.slice(0, 12)} + + {repeater.status === 'green' ? 'Region-capable' : 'No scoped relay observed'} + + {repeater.status === 'green' ? ( + regionCount > 0 ? ( + + Relayed {compactNumber(repeater.scopedPackets)} cryptographically scoped packets + across {regionCount} region{regionCount === 1 ? '' : 's'}. + + ) : ( + + Relayed {compactNumber(repeater.scopedPackets)} cryptographically scoped packets, but no + single region exceeded the 10% membership floor. + + ) + ) : ( + + Unscoped relay evidence: {compactNumber(repeater.unscopedPackets7d)} packets in the last + 7 days. No region-scoped traffic was observed relaying through it. + + )} + {tier && {CONFIDENCE_LABELS[tier]}} + {regionEntries.length > 0 && ( +
+ {regionEntries.map(([name, packets]) => ( + {name} {compactNumber(packets)} + ))} +
+ )} + {repeater.lat != null && repeater.lon != null && ( + + Zoom to repeater on live map + + )} + +
+ ); +} + +export function RegionPanel({ region, onMemberSelect, onClear }: { + region: RegionSummary; + onMemberSelect: (nodeId: string) => void; + onClear: () => void; +}): React.ReactElement { + const topMembers = [...region.members].sort((a, b) => b.packets - a.packets).slice(0, 10); + return ( +
+

Selected region

+ {region.name} + {CONFIDENCE_LABELS[region.confidence]} + + {compactNumber(region.packetsTotal)} scoped packets · {region.memberCount} significant relay + members ({region.repeaterMemberCount} repeaters) + + {topMembers.length > 0 ? ( + <> + Top relay members +
    + {topMembers.map((member) => ( +
  1. + +
  2. + ))} +
+ + ) : ( + No significant relay members — no repeater relayed at least 10% of this region's traffic, or its relay hops could not be attributed. + )} +

+ Members are repeaters relaying at least 10% of this region's scoped packets; shapes are the + convex hull of their positions, excluding universal bridge repeaters that relay many regions — + an approximation of the region's active footprint. +

+ +
+ ); +} diff --git a/frontend/src/pages/TopologyPage.browser.test.ts b/frontend/src/pages/TopologyPage.browser.test.ts new file mode 100644 index 0000000..fd4a75f --- /dev/null +++ b/frontend/src/pages/TopologyPage.browser.test.ts @@ -0,0 +1,71 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { chromium, expect } from '@playwright/test'; + +const browserTest = process.env['AUDITWAVE_BROWSER_TESTS'] === '1' ? test : test.skip; +const topology = { + generatedAt: '2026-09-07T12:00:00Z', windowDays: 30, limited: false, + summary: { nodes: 2, links: 1, observations: 12, connectedComponents: 1, likelyBridges: 0, isolatedNodes: 0 }, + nodes: [ + { nodeId: 'A'.repeat(64), name: 'Alpha', lat: 52, lon: -1, degree: 1, observations: 12 }, + { nodeId: 'B'.repeat(64), name: 'Bravo', lat: 52.1, lon: -1.1, degree: 1, observations: 12 }, + ], + links: [{ source: 'A'.repeat(64), target: 'B'.repeat(64), observations: 12, strongObservations: 3, pathLossDb: 130, lastObserved: '2026-09-07T12:00:00Z' }], + analysis: { connectedComponents: 1, bridgeNodeIds: [], isolatedNodeIds: [] }, +}; +const rf = { + methodology: 'Observed relay evidence compared with stored ITM.', + summary: { evaluated: 1, matches: 0, mismatches: 1, observedUnexpected: 1, operatorOverrides: 0, weakModelEvidence: 0 }, + mismatches: [{ source: 'A', target: 'B', sourceName: 'Alpha', targetName: 'Bravo', + observations: 12, strongObservations: 3, pathLossDb: 130, classification: 'observed_unexpected' }], +}; + +for (const failure of ['http503', 'timeout'] as const) { + browserTest(`deployed topology retains graph and RF panel after ${failure}, then retry restores results`, { timeout: 35_000 }, async () => { + const browser = await chromium.launch({ headless: true, args: ['--use-angle=swiftshader', '--enable-unsafe-swiftshader'] }); + try { + const context = await browser.newContext({ serviceWorkers: 'block' }); + const page = await context.newPage(); + const errors: string[] = []; + page.on('pageerror', error => errors.push(error.message)); + let attempts = 0; + await context.route('**/*', async route => { + const url = new URL(route.request().url()); + // Only load assets from the authorized local website. All API data, + // map tiles and telemetry are intercepted; this sends no external calls. + if (url.hostname !== '127.0.0.1') { await route.abort(); return; } + if (url.pathname === '/api/topology') { await route.fulfill({ json: topology }); return; } + if (url.pathname === '/api/runtime-config') { + await route.fulfill({ json: { version: 1, packetArcs: false, heatmap: false, privacyGeneration: 0, refreshAfterSeconds: 300 } }); + return; + } + if (url.pathname === '/api/rf-validation') { + attempts += 1; + if (attempts === 1) { + if (failure === 'http503') await route.fulfill({ status: 503, json: { error: 'temporarily unavailable' } }); + // Leave the first request pending to exercise the real browser's + // AbortSignal timeout, not an artificial immediate network error. + return; + } + await route.fulfill({ json: rf }); + return; + } + if (url.pathname.startsWith('/api/')) { await route.fulfill({ status: 200, json: {} }); return; } + await route.continue(); + }); + await page.goto('http://127.0.0.1:3004/topology', { waitUntil: 'domcontentloaded' }); + await expect(page.getByRole('heading', { name: 'Predicted versus observed RF' })).toBeVisible(); + await expect(page.locator('.topology-page__workspace')).toBeVisible(); + await expect(page.locator('.topology-page__workspace canvas')).toBeVisible(); + if (failure === 'timeout') await expect(page.getByText('Loading RF validation…')).toBeVisible(); + await expect(page.getByRole('alert').filter({ hasText: 'RF validation is temporarily unavailable' })).toBeVisible({ timeout: 18_000 }); + await expect(page.locator('.topology-page__workspace')).toBeVisible(); + await page.getByRole('button', { name: 'Retry RF validation' }).click(); + await expect(page.getByRole('table', { name: 'RF model mismatches' })).toBeVisible(); + await expect(page.getByRole('cell', { name: '130.0 dB', exact: true })).toBeVisible(); + assert.equal(attempts, 2); + assert.deepEqual(errors, []); + await context.close(); + } finally { await browser.close(); } + }); +} diff --git a/frontend/src/pages/TopologyPage.test.ts b/frontend/src/pages/TopologyPage.test.ts new file mode 100644 index 0000000..37dfc79 --- /dev/null +++ b/frontend/src/pages/TopologyPage.test.ts @@ -0,0 +1,52 @@ +import assert from 'node:assert/strict'; +import { registerHooks } from 'node:module'; +import test from 'node:test'; +import React from 'react'; +import { renderToStaticMarkup } from 'react-dom/server'; + +// Render the real RF panel without loading the browser's map or Vite env. +// Request, retry and cleanup behavior is exercised in the browser test. +const hooks = registerHooks({ + resolve(specifier, context, nextResolve) { + if (specifier.endsWith('.css')) return { url: 'data:text/javascript,export default {}', shortCircuit: true }; + if (specifier === '../components/Map/TopologyMap.js') { + return { url: 'data:text/javascript,export const TopologyMap=()=>null', shortCircuit: true }; + } + if (specifier === '../config/site.js') { + return { url: 'data:text/javascript,export const getCurrentSite=()=>({network:"ukmesh"})', shortCircuit: true }; + } + return nextResolve(specifier, context); + }, +}); +const { RfValidationPanel } = await import('./TopologyPage.js'); +hooks.deregister(); +const render = (data: Parameters[0]['data'], unavailable = false) => ( + renderToStaticMarkup(React.createElement(RfValidationPanel, { data, unavailable, onRetry: () => {} })) +); + +test('RF panel remains visible while loading and on failure with an accessible retry action', () => { + const loading = render(null); + assert.match(loading, /Predicted versus observed RF/); + assert.match(loading, /Loading RF validation/); + const failed = render(null, true); + assert.match(failed, /Predicted versus observed RF/); + assert.match(failed, /role="alert"/); + assert.match(failed, /RF validation is temporarily unavailable/); + assert.match(failed, / + + ) : !data ? ( + + ) : ( + <> +
+
{data.summary.evaluated}links evaluated
+
{data.summary.observedUnexpected}unexpected observed links
+
{data.summary.operatorOverrides}operator overrides
+
{data.summary.weakModelEvidence}weak evidence
+
+ {data.mismatches.length > 0 && ( +
+
+ Link + Observations + Modelled path loss + Classification +
+ {data.mismatches.slice(0, 20).map((link) => ( +
+ {link.sourceName ?? link.source.slice(0, 8)} ↔ {link.targetName ?? link.target.slice(0, 8)} + {link.observations} obs · {link.strongObservations} strong + {link.pathLossDb == null ? 'No model dB' : `${link.pathLossDb.toFixed(1)} dB`} + {link.classification.replace(/_/g, ' ')} +
+ ))} +
+ )} + {data.summary.evaluated === 0 &&

No recent public repeater links are available for RF validation.

} + + )} + + ); +} diff --git a/frontend/src/pages/docs-pages.css b/frontend/src/pages/docs-pages.css index 8a3564f..9e99791 100644 --- a/frontend/src/pages/docs-pages.css +++ b/frontend/src/pages/docs-pages.css @@ -512,10 +512,6 @@ line-height: 1.45; } -.cookie-banner__body a { - color: var(--accent); -} - .cookie-banner__button { flex-shrink: 0; min-width: 88px; diff --git a/frontend/src/pages/owner-portal.css b/frontend/src/pages/owner-portal.css index fff350c..27351f0 100644 --- a/frontend/src/pages/owner-portal.css +++ b/frontend/src/pages/owner-portal.css @@ -31,6 +31,269 @@ color: #ff8f8f; margin-top: 10px; } +.owner-sharing-panel { + margin-top: 16px; +} +.owner-sharing-panel__intro { + min-width: 0; +} +.owner-sharing-panel__eyebrow { + margin: 0 0 5px; + color: var(--accent); + font-family: var(--font-mono); + font-size: 10px; + font-weight: 700; + letter-spacing: 0.12em; + text-transform: uppercase; +} +.owner-sharing-switch { + position: relative; + display: inline-flex; + align-items: center; + flex: 0 0 auto; + width: 52px; + height: 32px; + cursor: pointer; +} +.owner-sharing-switch input { + position: absolute; + width: 1px; + height: 1px; + opacity: 0; +} +.owner-sharing-switch__track { + position: relative; + display: block; + width: 52px; + height: 32px; + border: 1px solid var(--border); + border-radius: 999px; + background: rgba(107, 138, 170, 0.32); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.24); + transition: background 160ms ease, border-color 160ms ease, box-shadow 160ms ease; +} +.owner-sharing-switch__thumb { + position: absolute; + top: 3px; + left: 3px; + width: 24px; + height: 24px; + border-radius: 50%; + background: #fff; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.32); + transition: transform 160ms ease; +} +.owner-sharing-switch input:checked + .owner-sharing-switch__track { + border-color: var(--online); + background: var(--online); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--online) 16%, transparent), inset 0 1px 2px rgba(0, 0, 0, 0.12); +} +.owner-sharing-switch input:checked + .owner-sharing-switch__track .owner-sharing-switch__thumb { + transform: translateX(20px); +} +.owner-sharing-switch input:focus-visible + .owner-sharing-switch__track { + outline: 2px solid var(--accent); + outline-offset: 3px; +} +.owner-sharing-switch input:disabled + .owner-sharing-switch__track { + cursor: not-allowed; + opacity: 0.48; +} +.owner-sharing-state { + display: flex; + align-items: flex-start; + gap: 10px; + margin: 2px 0 16px; + padding: 12px 14px; + border: 1px solid var(--border); + border-radius: var(--radius); + background: rgba(107, 138, 170, 0.08); +} +.owner-sharing-state--on { + border-color: color-mix(in srgb, var(--online) 48%, var(--border)); + background: color-mix(in srgb, var(--online) 8%, transparent); +} +.owner-sharing-state__dot { + flex: 0 0 auto; + width: 8px; + height: 8px; + margin-top: 5px; + border-radius: 50%; + background: var(--text-muted); + box-shadow: 0 0 0 3px rgba(107, 138, 170, 0.12); +} +.owner-sharing-state--on .owner-sharing-state__dot { + background: var(--online); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--online) 16%, transparent); +} +.owner-sharing-state strong { + display: block; + color: var(--text-primary); + font-size: 13px; +} +.owner-sharing-state p { + margin: 3px 0 0; + color: var(--text-secondary); + font-size: 12px; + line-height: 1.4; +} +.owner-sharing-empty { + margin: 0 0 12px; +} +.owner-sharing-destinations { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 10px; +} +.owner-sharing-destination { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 12px; + border: 1px solid var(--border); + border-radius: var(--radius); + background: var(--bg-panel-alt); + color: var(--text-primary); +} +.owner-sharing-destination--pending { + opacity: 0.72; +} +.owner-sharing-destination__body { + display: grid; + gap: 3px; + min-width: 0; +} +.owner-sharing-destination__link { + overflow: hidden; + color: var(--accent); + font-weight: 600; + text-overflow: ellipsis; + text-decoration: none; + white-space: nowrap; +} +.owner-sharing-destination__link:hover, +.owner-sharing-destination__link:focus-visible { + text-decoration: underline; +} +.owner-sharing-destination small { + color: var(--text-secondary); + font-size: 11px; +} +.owner-sharing-destination:focus-within { + border-color: var(--border-bright); +} +.owner-sharing-saving { + margin-top: 14px; +} +.owner-sharing-error { + color: #ff8f8f; + margin-top: 10px; +} +.owner-public-feed-panel { + margin-top: 16px; +} +.owner-public-feed-switch-control { + display: flex; + align-items: center; + gap: 9px; + flex: 0 0 auto; +} +.owner-public-feed-switch__label { + color: var(--text-muted); + font-family: var(--font-mono); + font-size: 10px; + font-weight: 700; + letter-spacing: 0.1em; + text-transform: uppercase; +} +.owner-public-feed-switch { + margin-top: 0; +} +.owner-public-feed-warning { + display: grid; + gap: 4px; + margin: 0 0 14px; + padding: 11px 14px; + border: 1px solid color-mix(in srgb, var(--amber) 32%, var(--border)); + border-left: 3px solid var(--amber); + border-radius: var(--radius); + background: color-mix(in srgb, var(--amber) 6%, var(--bg-panel)); +} +.owner-public-feed-warning__title { + display: flex; + align-items: center; + gap: 8px; + color: var(--text-primary); + font-size: 12px; +} +.owner-public-feed-warning__title > span { + display: inline-grid; + place-items: center; + width: 16px; + height: 16px; + border: 1px solid color-mix(in srgb, var(--amber) 65%, transparent); + border-radius: 50%; + color: var(--amber); + font-family: var(--font-mono); + font-size: 11px; + font-weight: 700; +} +.owner-public-feed-warning strong { + color: var(--text-primary); +} +.owner-public-feed-warning p { + margin: 0; + color: var(--text-secondary); + font-size: 12px; + line-height: 1.45; +} +.owner-public-feed-details { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 10px; + padding-top: 13px; + border-top: 1px solid var(--border); + color: var(--text-secondary); + font-size: 11px; +} +.owner-public-feed-details__item { + display: grid; + gap: 3px; + min-width: 0; +} +.owner-public-feed-details__label { + color: var(--text-muted); + font-family: var(--font-mono); + font-size: 10px; + letter-spacing: 0.08em; + text-transform: uppercase; +} +.owner-public-feed-details code { + color: var(--accent); + overflow-wrap: anywhere; +} +/* Keep the iOS switch usable when the owner dashboard narrows to a phone. */ +@media (max-width: 480px) { + .owner-sharing-panel .owner-panel__head { + align-items: center; + } + .owner-sharing-panel__intro .prose-note { + max-width: 27ch; + } + .owner-sharing-destination { + align-items: flex-start; + } + .owner-public-feed-panel .owner-panel__head { + align-items: center; + } + .owner-public-feed-panel .owner-sharing-panel__intro .prose-note { + max-width: 27ch; + } + .owner-public-feed-details { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} .owner-head { display: flex; align-items: center; @@ -377,6 +640,31 @@ font-size: 10px; font-family: var(--font-mono); } +.owner-telemetry-metric__window-toggle { + display: inline-flex; + align-items: center; + gap: 4px; +} +.owner-telemetry-metric__window-btn { + border: 1px solid var(--border); + background: rgba(255,255,255,0.03); + color: var(--text-secondary); + border-radius: 999px; + padding: 1px 7px; + font-size: 10px; + line-height: 1.5; + font-family: var(--font-mono); + cursor: pointer; +} +.owner-telemetry-metric__window-btn:hover { + border-color: var(--border-bright); + color: var(--text-primary); +} +.owner-telemetry-metric__window-btn--active { + border-color: rgba(0,196,255,0.45); + background: rgba(0,196,255,0.12); + color: var(--text-primary); +} .owner-telemetry-metric__series-list { align-items: flex-start; min-width: 0; @@ -460,6 +748,7 @@ margin: 16px 0; } .owner-node-identity { + position: relative; padding: 12px; border: 1px solid var(--border); border-radius: var(--radius); @@ -481,6 +770,40 @@ .owner-node-identity--selected:hover { border-color: rgba(0, 196, 255, 0.8); } +.owner-node-identity__compare { + position: absolute; + top: 10px; + right: 10px; + width: 22px; + height: 22px; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0; + border: 1px solid var(--border); + border-radius: 999px; + background: var(--bg-panel); + color: var(--text-secondary); + font-size: 13px; + line-height: 1; + cursor: pointer; + transition: border-color 120ms ease, color 120ms ease, background-color 120ms ease; +} +.owner-node-identity__compare:hover { + border-color: var(--accent); + color: var(--text-primary); +} +.owner-node-identity__compare--active { + border-color: rgba(0, 196, 255, 0.8); + background: rgba(0, 196, 255, 0.16); + color: #7dd8ff; +} +.owner-node-identity--comparing { + border-color: rgba(0, 196, 255, 0.35); +} +.owner-node-identity:has(.owner-node-identity__compare) .owner-node-identity__head { + padding-right: 28px; +} .owner-node-identity__head { display: flex; justify-content: space-between; diff --git a/frontend/src/pages/owner/OwnerHeardNeighbors.tsx b/frontend/src/pages/owner/OwnerHeardNeighbors.tsx index faeb32e..5cb0cc1 100644 --- a/frontend/src/pages/owner/OwnerHeardNeighbors.tsx +++ b/frontend/src/pages/owner/OwnerHeardNeighbors.tsx @@ -6,7 +6,11 @@ function signalValue(value: number | null, suffix: string): string { return `${value.toFixed(1)} ${suffix}`; } -export const OwnerHeardNeighbors: React.FC<{ neighbors: HeardNeighbor[] }> = ({ neighbors }) => { +export const OwnerHeardNeighbors: React.FC<{ + neighbors: HeardNeighbor[]; + source?: 'reported' | 'direct_packets' | 'none'; + sampledAt?: string | null; +}> = ({ neighbors, source, sampledAt: reportedSampleTime }) => { const rows = useMemo( () => [...neighbors] .sort((a, b) => { @@ -18,19 +22,24 @@ export const OwnerHeardNeighbors: React.FC<{ neighbors: HeardNeighbor[] }> = ({ .slice(0, 32), [neighbors], ); - const sampledAt = rows.find((neighbor) => neighbor.sampled_at)?.sampled_at ?? null; + const sampledAt = reportedSampleTime ?? rows.find((neighbor) => neighbor.sampled_at)?.sampled_at ?? null; + const directPackets = source === 'direct_packets'; if (rows.length === 0) { - return

No neighbor sample has been received for this node yet.

; + return

{source === 'reported' + ? `The latest reported neighbor list is empty.${sampledAt ? ` Sampled ${new Date(sampledAt).toLocaleString()}.` : ''}` + : 'No neighbor list was reported in the last 7 days and no direct senders were observed in the last 24 hours.'}

; } return (
-
Latest sample: {sampledAt ? new Date(sampledAt).toLocaleString() : '—'}
+
{directPackets + ? 'No neighbor list reported in the last 7 days. Showing direct senders from received packets in the last 24 hours, with average signal strength.' + : `Latest reported sample: ${sampledAt ? new Date(sampledAt).toLocaleString() : '—'}`}
- + {rows.map((neighbor, index) => ( diff --git a/frontend/src/pages/owner/OwnerPanelState.test.ts b/frontend/src/pages/owner/OwnerPanelState.test.ts new file mode 100644 index 0000000..19c397b --- /dev/null +++ b/frontend/src/pages/owner/OwnerPanelState.test.ts @@ -0,0 +1,49 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import React from 'react'; +import { renderToStaticMarkup } from 'react-dom/server'; +import { OwnerPanelState } from './OwnerPanelState.js'; +import { OwnerHeardNeighbors } from './OwnerHeardNeighbors.js'; + +function panel(hasData: boolean, error: string | null, loading = false) { + return renderToStaticMarkup(React.createElement(OwnerPanelState, { + label: 'Direct Senders', hasData, error, loading, onRetry: () => {}, + children: React.createElement('p', null, 'No direct senders observed.'), + })); +} + +test('owner panels distinguish loading and unavailable from a successful empty result', () => { + assert.match(panel(false, null), /Loading Direct Senders/); + assert.doesNotMatch(panel(false, null), /No direct senders observed/); + assert.match(panel(false, 'timeout'), /role="alert"/); + assert.match(panel(false, 'timeout'), /Retry Direct Senders/); + assert.doesNotMatch(panel(false, 'timeout'), /No direct senders observed/); + assert.match(panel(true, null), /No direct senders observed/); + assert.doesNotMatch(panel(true, null), /Loading|temporarily unavailable/); +}); + +test('refresh failures keep prior content visibly marked as out of date', () => { + const html = panel(true, 'HTTP 503'); + assert.match(html, /last successful data/); + assert.match(html, /No direct senders observed/); + assert.match(html, /Retry Direct Senders/); +}); + +test('background refresh keeps successful content in place without adding a loading row', () => { + const html = panel(true, null, true); + assert.match(html, /No direct senders observed/); + assert.doesNotMatch(html, /Loading|Refreshing/); +}); + +test('neighbour panel describes packet fallback and does not misreport a published empty list', () => { + const html = renderToStaticMarkup(React.createElement(OwnerHeardNeighbors, { + source: 'direct_packets', neighbors: [{ id: 'peer', rssi: -95.5, snr: 0, last_seen_at: null, sampled_at: null }], + })); + assert.match(html, /received packets in the last 24 hours/); + assert.match(html, /Avg RSSI/); + assert.match(html, /-95.5 dBm/); + assert.match(html, /0.0 dB/); + const empty = renderToStaticMarkup(React.createElement(OwnerHeardNeighbors, { source: 'reported', neighbors: [] })); + assert.match(empty, /latest reported neighbor list is empty/); + assert.doesNotMatch(empty, /No neighbor list was reported/); +}); diff --git a/frontend/src/pages/owner/OwnerPanelState.tsx b/frontend/src/pages/owner/OwnerPanelState.tsx new file mode 100644 index 0000000..6efdac0 --- /dev/null +++ b/frontend/src/pages/owner/OwnerPanelState.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { LoadingIndicator } from '../../components/LoadingIndicator.js'; + +export type OwnerPanelStateProps = { + label: string; + hasData: boolean; + loading: boolean; + error: string | null; + onRetry: () => void; + children: React.ReactNode; +}; + +export function OwnerPanelState({ label, hasData, error, onRetry, children }: OwnerPanelStateProps) { + // Keep the last successful snapshot mounted during background polls. Adding + // a spinner above it changes the panel height and makes the whole dashboard + // jump on every refresh. + return <> + {error ? ( +
+

{label} is temporarily unavailable.{hasData ? ' Showing the last successful data; it may be out of date.' : ' Please try again shortly.'}

+ +
+ ) : !hasData ? ( + + ) : null} + {hasData ? children : null} + ; +} diff --git a/frontend/src/pages/owner/OwnerPortalCharts.tsx b/frontend/src/pages/owner/OwnerPortalCharts.tsx index b1250ef..e974222 100644 --- a/frontend/src/pages/owner/OwnerPortalCharts.tsx +++ b/frontend/src/pages/owner/OwnerPortalCharts.tsx @@ -1,15 +1,18 @@ import React, { useEffect, useMemo, useState } from 'react'; import { Area, AreaChart, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts'; import { + LAST_HOP_WINDOW_OPTIONS, OWNER_AXIS_COLOR as AXIS_COLOR, OWNER_LABEL_COLOR as LABEL_COLOR, OWNER_TOOLTIP_BG as TIP_BG, OWNER_TOOLTIP_BORDER as TIP_BORDER, formatCompactTs, formatDurationMs, + lastHopWindowLabel, readExcludedLastHopSeries, writeExcludedLastHopSeries, type LastHopStrengthPoint, + type LastHopWindow, type OwnerLiveResponse, } from './ownerPortalModel.js'; export const TrendBars: React.FC<{ points: Array<{ bucket: string; adverts: number }> }> = ({ points }) => { @@ -93,6 +96,25 @@ const OwnerTelemetryTooltip: React.FC<{ const LAST_HOP_COLORS = ['#00c4ff', '#6ddc7a', '#ff9f43', '#f472b6', '#a78bfa', '#facc15']; const MIN_LAST_HOP_SAMPLES = 10; +const LastHopWindowToggle: React.FC<{ + value: LastHopWindow; + onChange: (value: LastHopWindow) => void; +}> = ({ value, onChange }) => ( +
+ {LAST_HOP_WINDOW_OPTIONS.map((option) => ( + + ))} +
+); + const LastHopStrengthTooltip: React.FC<{ active?: boolean; payload?: Array<{ dataKey: string; value: number; color: string; payload: Record }>; @@ -118,7 +140,16 @@ const LastHopStrengthTooltip: React.FC<{ ); }; -export const LastHopStrengthChart: React.FC<{ nodeId: string; points: LastHopStrengthPoint[]; isPassiveRepeater?: boolean }> = ({ nodeId, points, isPassiveRepeater }) => { +export const LastHopStrengthChart: React.FC<{ + nodeId: string; + points: LastHopStrengthPoint[]; + timeWindow: LastHopWindow; + onTimeWindowChange: (value: LastHopWindow) => void; + isPassiveRepeater?: boolean; + compareSeries?: Array<{ id: string; name: string; points: LastHopStrengthPoint[] }>; +}> = ({ nodeId, points, timeWindow, onTimeWindowChange, isPassiveRepeater, compareSeries }) => { + const compareMode = Boolean(compareSeries && compareSeries.length >= 2); + const bucketLabel = timeWindow === '30d' ? '12h' : 'hourly'; const [selectedSeriesKey, setSelectedSeriesKey] = useState(null); const [excludedSeriesKeys, setExcludedSeriesKeys] = useState(() => readExcludedLastHopSeries(nodeId)); const visiblePoints = useMemo( @@ -133,53 +164,91 @@ export const LastHopStrengthChart: React.FC<{ nodeId: string; points: LastHopStr }, [nodeId]); const chartState = useMemo(() => { - const totals = new Map; resolution: LastHopStrengthPoint['resolution']; - }>(); - for (const point of visiblePoints) { - const key = point.lastHopNodeId ?? `unresolved:${point.lastHopName}`; - const existing = totals.get(key); - if (existing) { - existing.totalSamples += point.sampleCount; - existing.buckets.add(point.bucket); - existing.bucketCount = existing.buckets.size; - } else { - totals.set(key, { + }; + const totals = new Map(); + const bucketed = new Map>(); + const addSample = (bucket: string, key: string, value: number | null, samples: number) => { + const entry = bucketed.get(bucket) ?? { bucket }; + entry[key] = value; + entry[`${key}__samples`] = samples; + bucketed.set(bucket, entry); + }; + + if (compareMode && compareSeries) { + // Compare mode: one line per selected repeater. Each bucket value is the + // sample-weighted average SNR across that repeater's last-hop series. + const accumulators = new Map>(); + for (const node of compareSeries) { + const key = `cmp:${node.id}`; + const nodeTotals = totals.get(key) ?? { key, - label: point.lastHopName, - totalSamples: point.sampleCount, - bucketCount: 1, - buckets: new Set([point.bucket]), - resolution: point.resolution, - }); + label: node.name, + totalSamples: 0, + bucketCount: 0, + buckets: new Set(), + resolution: 'resolved' as const, + }; + totals.set(key, nodeTotals); + const perBucket = accumulators.get(key) ?? new Map(); + accumulators.set(key, perBucket); + for (const point of node.points) { + if (point.resolution === 'unresolved' || point.avgSnr == null) continue; + const samples = Math.max(1, point.sampleCount); + const acc = perBucket.get(point.bucket) ?? { sum: 0, samples: 0 }; + acc.sum += point.avgSnr * samples; + acc.samples += samples; + perBucket.set(point.bucket, acc); + nodeTotals.totalSamples += point.sampleCount; + nodeTotals.buckets.add(point.bucket); + } + nodeTotals.bucketCount = nodeTotals.buckets.size; + } + for (const [key, perBucket] of accumulators) { + for (const [bucket, acc] of perBucket) { + if (acc.samples < 1) continue; + addSample(bucket, key, Number((acc.sum / acc.samples).toFixed(2)), acc.samples); + } + } + } else { + for (const point of visiblePoints) { + const key = point.lastHopNodeId ?? `unresolved:${point.lastHopName}`; + const existing = totals.get(key); + if (existing) { + existing.totalSamples += point.sampleCount; + existing.buckets.add(point.bucket); + existing.bucketCount = existing.buckets.size; + } else { + totals.set(key, { + key, + label: point.lastHopName, + totalSamples: point.sampleCount, + bucketCount: 1, + buckets: new Set([point.bucket]), + resolution: point.resolution, + }); + } + addSample(point.bucket, key, point.avgSnr, point.sampleCount); } } const allSeries = Array.from(totals.values()) - .filter((series) => series.totalSamples >= MIN_LAST_HOP_SAMPLES) - .sort((a, b) => b.totalSamples - a.totalSamples || a.label.localeCompare(b.label)); + .filter((series) => (compareMode ? series.totalSamples > 0 : series.totalSamples >= MIN_LAST_HOP_SAMPLES)) + .sort(compareMode + ? (a, b) => a.label.localeCompare(b.label) + : (a, b) => b.totalSamples - a.totalSamples || a.label.localeCompare(b.label)); const includedSeries = allSeries.filter((series) => !excludedSet.has(series.key)); const selected = selectedSeriesKey ? includedSeries.filter((series) => series.key === selectedSeriesKey) - : includedSeries.slice(0, 6); - const selectedKeys = new Set(selected.map((series) => series.key)); + : includedSeries.slice(0, compareMode ? 8 : 6); const seriesByKey = new Map(allSeries.map((series) => [series.key, series] as const)); - const bucketed = new Map>(); - for (const point of visiblePoints) { - const key = point.lastHopNodeId ?? `unresolved:${point.lastHopName}`; - if (!selectedKeys.has(key)) continue; - const entry = bucketed.get(point.bucket) ?? { bucket: point.bucket }; - entry[key] = point.avgSnr; - entry[`${key}__samples`] = point.sampleCount; - bucketed.set(point.bucket, entry); - } - return { allSeries, includedSeries, @@ -188,7 +257,7 @@ export const LastHopStrengthChart: React.FC<{ nodeId: string; points: LastHopStr seriesByKey, data: Array.from(bucketed.values()).sort((a, b) => String(a.bucket).localeCompare(String(b.bucket))), }; - }, [excludedSet, selectedSeriesKey, visiblePoints]); + }, [compareMode, compareSeries, excludedSet, selectedSeriesKey, visiblePoints]); useEffect(() => { if (selectedSeriesKey && !chartState.allSeries.some((series) => series.key === selectedSeriesKey)) { @@ -203,8 +272,11 @@ export const LastHopStrengthChart: React.FC<{ nodeId: string; points: LastHopStr }, [excludedSet, selectedSeriesKey]); useEffect(() => { + // Compare-mode exclusions are session-only; the persisted list is keyed to + // last-hop series, not repeaters. + if (compareMode) return; writeExcludedLastHopSeries(nodeId, excludedSeriesKeys); - }, [excludedSeriesKeys, nodeId]); + }, [excludedSeriesKeys, nodeId, compareMode]); const toggleSeriesExclusion = (seriesKey: string) => { setExcludedSeriesKeys((current) => ( @@ -228,23 +300,27 @@ export const LastHopStrengthChart: React.FC<{ nodeId: string; points: LastHopStr
-

RX Strength by Last Hop (Node names are predicted based on the first two hex characters in the path)

-

Average SNR over the last 7 days

+

RX Strength by Last Hop {compareMode ? '(One line per selected repeater)' : '(Node names are predicted based on the first two hex characters in the path)'}

+

{compareMode ? 'Comparing the selected repeaters' : `Average SNR over the last ${lastHopWindowLabel(timeWindow)}`}

- {isPassiveRepeater - ? 'This node does not report received packets via MQTT — rx signal data is captured by its companion node' - : 'No last-hop repeaters with 10+ samples yet'} + {compareMode + ? 'No comparable RX samples for the selected repeaters yet' + : isPassiveRepeater + ? 'This node does not report received packets via MQTT — rx signal data is captured by its companion node' + : 'No last-hop repeaters with 10+ samples yet'}
- Last 7d + Minimum 10 samples
@@ -267,15 +343,19 @@ export const LastHopStrengthChart: React.FC<{ nodeId: string; points: LastHopStr
-

RX Strength by Last Hop (Node names are predicted based on the first two hex characters in the path)

-

{latestActive || (chartState.includedSeries.length > 0 ? 'Average SNR over the last 7 days' : 'All eligible repeaters are currently hidden')}

+

RX Strength by Last Hop {compareMode ? '(One line per selected repeater)' : '(Node names are predicted based on the first two hex characters in the path)'}

+

{compareMode + ? `Comparing ${chartState.includedSeries.length} repeater${chartState.includedSeries.length === 1 ? '' : 's'} · average SNR over the last ${lastHopWindowLabel(timeWindow)}` + : (latestActive || (chartState.includedSeries.length > 0 ? `Average SNR over the last ${lastHopWindowLabel(timeWindow)}` : 'All eligible repeaters are currently hidden'))}

{chartState.series.length}
{chartState.series.length > 0 && chartState.data.length > 0 ? ( @@ -310,11 +390,13 @@ export const LastHopStrengthChart: React.FC<{ nodeId: string; points: LastHopStr )}
- Last 7d + {selectedSeriesKey ? 'Focused on one repeater' - : `${chartState.includedSeries.length} shown of ${chartState.allSeries.length}`} + : compareMode + ? `${chartState.includedSeries.length} of ${chartState.allSeries.length} repeaters` + : `${chartState.includedSeries.length} shown of ${chartState.allSeries.length}`} {chartState.excludedCount > 0 ? ` · ${chartState.excludedCount} hidden` : ''}
@@ -350,7 +432,7 @@ export const LastHopStrengthChart: React.FC<{ nodeId: string; points: LastHopStr type="button" className={`owner-telemetry-metric__series-btn${selectedSeriesKey === series.key ? ' owner-telemetry-metric__series-btn--active' : ''}`} onClick={() => setSelectedSeriesKey(series.key)} - title={`${series.label} · ${series.bucketCount} hourly buckets · ${series.totalSamples} packets in last 7d`} + title={`${series.label} · ${series.bucketCount} ${bucketLabel} buckets · ${series.totalSamples} packets in last ${timeWindow}`} > {series.label} ({series.totalSamples}) diff --git a/frontend/src/pages/owner/ownerPortalModel.test.ts b/frontend/src/pages/owner/ownerPortalModel.test.ts index bdbeaf2..9711e48 100644 --- a/frontend/src/pages/owner/ownerPortalModel.test.ts +++ b/frontend/src/pages/owner/ownerPortalModel.test.ts @@ -9,20 +9,8 @@ import { isOwnerSessionResponse, isValidMapCoord, nodeRoleLabel, - ownerLoginCredentials, - ownerLoginValidationError, } from './ownerPortalModel.js'; -test('owner login trims only the username and preserves password whitespace', () => { - assert.equal(ownerLoginValidationError(' owner ', ' password '), null); - assert.deepEqual(ownerLoginCredentials(' owner ', ' password '), { - mqttUsername: 'owner', - mqttPassword: ' password ', - }); - assert.match(ownerLoginValidationError('owner', '\tpassword') ?? '', /invalid characters/); - assert.match(ownerLoginValidationError('owner', 'x'.repeat(129)) ?? '', /too long/); -}); - test('owner map coordinate policy rejects placeholders and invalid coordinates', () => { assert.equal(isValidMapCoord(51.5, -0.1), true); assert.equal(isValidMapCoord(0, 0), false); diff --git a/frontend/src/pages/owner/ownerPortalModel.ts b/frontend/src/pages/owner/ownerPortalModel.ts index f9ce3b3..5c344aa 100644 --- a/frontend/src/pages/owner/ownerPortalModel.ts +++ b/frontend/src/pages/owner/ownerPortalModel.ts @@ -21,24 +21,6 @@ export function nodeRoleLabel(role: number | null): string { return 'Repeater'; } -export type OwnerLoginCredentials = { mqttUsername: string; mqttPassword: string }; - -export function ownerLoginValidationError(mqttUsername: string, mqttPassword: string): string | null { - const normalizedUsername = mqttUsername.trim(); - if (!normalizedUsername || !mqttPassword) return 'Enter your MQTT username and password.'; - if (normalizedUsername.length > 128 || mqttPassword.length > 128) { - return 'MQTT username or password is too long.'; - } - if (/[\u0000-\u001F\u007F]/.test(normalizedUsername) || /[\u0000-\u001F\u007F]/.test(mqttPassword)) { - return 'MQTT username or password contains invalid characters.'; - } - return null; -} - -export function ownerLoginCredentials(mqttUsername: string, mqttPassword: string): OwnerLoginCredentials { - return { mqttUsername: mqttUsername.trim(), mqttPassword }; -} - export type OwnerDashboard = { nodes: OwnerNode[]; }; @@ -49,6 +31,39 @@ export type OwnerSessionResponse = { mqttUsername?: string | null; }; +export type OwnerPacketSharingDestination = { + id: string; + name: string; + websiteUrl: string | null; + description: string | null; + configured: boolean; + selected: boolean; + lastForwardedAt: string | null; +}; + +export type OwnerPacketSharing = { + nodeId: string; + featureEnabled: boolean; + enabled: boolean; + destinations: OwnerPacketSharingDestination[]; +}; + +export type OwnerPublicFeed = { + featureEnabled: boolean; + configured: boolean; + brokerUrl: string | null; + enabled: boolean; + nodeCount: number; + eligible: boolean; + recentPacketCount: number; + topicFilters: string[]; + lastForwardedAt: string | null; +}; + +export function isPacketSharingOwner(username: string | null | undefined): boolean { + return typeof username === 'string' && username.trim().length > 0; +} + const OWNER_SESSION_EVENT = 'meshcore-owner-session'; const LAST_HOP_EXCLUDED_COOKIE = 'meshcore-owner-last-hop-hidden-v1'; const LAST_HOP_COOKIE_MAX_AGE = 60 * 60 * 24 * 365; @@ -208,6 +223,8 @@ export type OwnerLiveResponse = { }>; status: OwnerStatus | null; heardNeighbors: HeardNeighbor[]; + heardNeighborsSource?: 'reported' | 'direct_packets' | 'none'; + heardNeighborsSampledAt?: string | null; packetsSent24h: number; packetsReceived24h: number; alerts: Array<{ level: 'info' | 'warn' | 'error'; message: string }>; @@ -224,12 +241,21 @@ export type LastHopStrengthPoint = { sampleCount: number; }; +export type LastHopWindow = '7d' | '30d'; + +export const LAST_HOP_WINDOW_OPTIONS: readonly LastHopWindow[] = ['7d', '30d']; + +export function lastHopWindowLabel(value: LastHopWindow): string { + return value === '30d' ? '30 days' : '7 days'; +} + export type OwnerLastHopStrengthResponse = { points: LastHopStrengthPoint[]; }; -// Scope: authenticated owner session + public privacy generation. This cache is -// cleared at logout/session expiry and bounded to 128 series or 12 MiB. +// Scope: authenticated owner session + public privacy generation + selected time +// window. This cache is cleared at logout/session expiry and bounded to 128 +// series or 12 MiB. export const lastHopSeriesCache = new ScopedCache({ name: 'owner-last-hop', ttlMs: 5 * 60_000, @@ -254,6 +280,92 @@ export function isOwnerSessionResponse(value: unknown): value is OwnerSessionRes && (value['mqttUsername'] == null || typeof value['mqttUsername'] === 'string'); } +export function isOwnerPacketSharingResponse(value: unknown): value is OwnerPacketSharing { + if (!isRecord(value) + || typeof value['nodeId'] !== 'string' + || !/^[0-9A-Fa-f]{64}$/.test(value['nodeId']) + || typeof value['featureEnabled'] !== 'boolean' + || typeof value['enabled'] !== 'boolean' + || !Array.isArray(value['destinations'])) return false; + return value['destinations'].every((destination) => isRecord(destination) + && typeof destination['id'] === 'string' + && /^[a-z0-9][a-z0-9_-]{0,63}$/.test(destination['id']) + && typeof destination['name'] === 'string' + && destination['name'].length >= 1 + && destination['name'].length <= 120 + && (destination['websiteUrl'] == null || isSafePacketShareWebsiteUrl(destination['websiteUrl'])) + && (destination['description'] == null || ( + typeof destination['description'] === 'string' + && destination['description'].length >= 1 + && destination['description'].length <= 160 + )) + && typeof destination['configured'] === 'boolean' + && typeof destination['selected'] === 'boolean' + && (destination['lastForwardedAt'] == null || ( + typeof destination['lastForwardedAt'] === 'string' + && destination['lastForwardedAt'].length <= 64 + && Number.isFinite(Date.parse(destination['lastForwardedAt'])) + ))); +} + +function isSafePacketShareWebsiteUrl(value: unknown): value is string { + if (typeof value !== 'string' || value.length < 1 || value.length > 2_048) return false; + try { + const url = new URL(value); + return url.protocol === 'https:' && Boolean(url.hostname) && !url.username && !url.password; + } catch { + return false; + } +} + +export async function fetchOwnerPacketSharing( + nodeId: string, + signal?: AbortSignal, +): Promise { + return fetchJson( + `/api/owner/packet-sharing?nodeId=${encodeURIComponent(nodeId)}`, + { cache: 'no-store', signal }, + { timeoutMs: 10_000, maxBytes: 128 * 1024, validate: isOwnerPacketSharingResponse }, + ); +} + +export function isOwnerPublicFeedResponse(value: unknown): value is OwnerPublicFeed { + return isRecord(value) + && typeof value['featureEnabled'] === 'boolean' + && typeof value['configured'] === 'boolean' + && (value['brokerUrl'] == null || ( + typeof value['brokerUrl'] === 'string' + && value['brokerUrl'].length <= 2_048 + )) + && typeof value['enabled'] === 'boolean' + && typeof value['nodeCount'] === 'number' + && Number.isInteger(value['nodeCount']) + && value['nodeCount'] >= 0 + && typeof value['eligible'] === 'boolean' + && typeof value['recentPacketCount'] === 'number' + && Number.isInteger(value['recentPacketCount']) + && value['recentPacketCount'] >= 0 + && Array.isArray(value['topicFilters']) + && value['topicFilters'].length <= 1 + && value['topicFilters'].every((topic) => ( + typeof topic === 'string' + && /^public\/\+\/\+\/packets$/.test(topic) + )) + && (value['lastForwardedAt'] == null || ( + typeof value['lastForwardedAt'] === 'string' + && value['lastForwardedAt'].length <= 64 + && Number.isFinite(Date.parse(value['lastForwardedAt'])) + )); +} + +export async function fetchOwnerPublicFeed(signal?: AbortSignal): Promise { + return fetchJson( + '/api/owner/public-feed', + { cache: 'no-store', signal }, + { timeoutMs: 10_000, maxBytes: 32 * 1024, validate: isOwnerPublicFeedResponse }, + ); +} + export function isOwnerLiveResponse(value: unknown): value is OwnerLiveResponse { if (!isRecord(value)) return false; const status = value['status']; diff --git a/frontend/src/pages/owner/packetShareModel.test.ts b/frontend/src/pages/owner/packetShareModel.test.ts new file mode 100644 index 0000000..d5c25a2 --- /dev/null +++ b/frontend/src/pages/owner/packetShareModel.test.ts @@ -0,0 +1,54 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { isOwnerPacketSharingResponse, isOwnerPublicFeedResponse, isPacketSharingOwner } from './ownerPortalModel.js'; + +const valid = { + nodeId: 'A'.repeat(64), + featureEnabled: true, + enabled: false, + destinations: [{ + id: 'remote-site', + name: 'Remote site', + websiteUrl: 'https://remote.example.test', + description: 'A remote packet site', + configured: true, + selected: false, + lastForwardedAt: null, + }], +}; + +test('owner packet-sharing response guard accepts the bounded settings DTO', () => { + assert.equal(isOwnerPacketSharingResponse(valid), true); + assert.equal(isOwnerPacketSharingResponse({ ...valid, nodeId: 'short' }), false); + assert.equal(isOwnerPacketSharingResponse({ ...valid, destinations: [{ ...valid.destinations[0], id: 'Remote Site' }] }), false); + assert.equal(isOwnerPacketSharingResponse({ ...valid, destinations: [{ ...valid.destinations[0], selected: 'yes' }] }), false); + assert.equal(isOwnerPacketSharingResponse({ ...valid, destinations: [{ ...valid.destinations[0], websiteUrl: 'javascript:alert(1)' }] }), false); + assert.equal(isOwnerPacketSharingResponse({ ...valid, destinations: [{ ...valid.destinations[0], lastForwardedAt: 'not-a-date' }] }), false); + assert.equal(isOwnerPacketSharingResponse({ ...valid, featureEnabled: 'true' }), false); +}); + +test('shows packet sharing for every authenticated owner account', () => { + assert.equal(isPacketSharingOwner('node1'), true); + assert.equal(isPacketSharingOwner('gnomemoe'), true); + assert.equal(isPacketSharingOwner('node2'), true); + assert.equal(isPacketSharingOwner('new-owner'), true); + assert.equal(isPacketSharingOwner(null), false); +}); + +test('owner public-feed response guard accepts the account-level settings DTO', () => { + const valid = { + featureEnabled: true, + configured: true, + brokerUrl: 'wss://mqtt.example.test:443', + enabled: false, + nodeCount: 2, + eligible: true, + recentPacketCount: 1, + topicFilters: ['public/+/+/packets'], + lastForwardedAt: null, + }; + assert.equal(isOwnerPublicFeedResponse(valid), true); + assert.equal(isOwnerPublicFeedResponse({ ...valid, nodeCount: -1 }), false); + assert.equal(isOwnerPublicFeedResponse({ ...valid, topicFilters: ['meshcore/#'] }), false); + assert.equal(isOwnerPublicFeedResponse({ ...valid, lastForwardedAt: 'invalid' }), false); +}); diff --git a/frontend/src/pages/regions-page.css b/frontend/src/pages/regions-page.css new file mode 100644 index 0000000..844b36d --- /dev/null +++ b/frontend/src/pages/regions-page.css @@ -0,0 +1,79 @@ +.regions-page { padding-top: 48px; padding-bottom: 64px; } +.regions-page__header { display: flex; align-items: flex-end; justify-content: space-between; gap: 32px; margin-bottom: 24px; } +.regions-page__header h1 { margin: 4px 0 10px; } +.regions-page__header p { max-width: 760px; color: var(--text-secondary); } +.regions-page__eyebrow { color: var(--accent) !important; font: 600 11px/1 var(--font-mono); letter-spacing: 0.13em; text-transform: uppercase; } +.regions-page__controls { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 14px; max-width: 420px; } +.regions-page__toggle { display: flex; align-items: center; gap: 8px; white-space: nowrap; color: var(--text-secondary); font-size: 12px; cursor: pointer; } +.regions-page__select { + appearance: none; + -webkit-appearance: none; + min-width: 128px; + padding: 8px 34px 8px 10px; + border: 1px solid rgba(107, 174, 210, 0.58); + border-radius: var(--radius); + background-color: #0d1828; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 14 14' fill='none' stroke='%23d6e8ff' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m3 5 4 4 4-4'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 10px center; + background-size: 14px; + color: #e8f4ff; + color-scheme: dark; + font: 12px var(--font-body); + cursor: pointer; +} +.regions-page__select:hover { border-color: var(--accent); background-color: #11243a; } +.regions-page__select:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } +.regions-page__select option { background: #0d1828; color: #e8f4ff; } +.regions-page__stats { display: grid; grid-template-columns: repeat(5, 1fr); gap: 10px; margin-bottom: 12px; } +.regions-page__stats > div { display: flex; flex-direction: column; padding: 14px 16px; background: var(--bg-panel); border: 1px solid var(--border); border-radius: var(--radius-lg); } +.regions-page__stats strong { color: var(--text-primary); font: 600 22px/1.2 var(--font-mono); } +.regions-page__stats span { margin-top: 4px; color: var(--text-muted); font-size: 11px; } +.regions-page__workspace { display: grid; grid-template-columns: minmax(0, 1fr) 280px; gap: 12px; } +.regions-page__map { position: relative; height: min(68vh, 680px); min-height: 520px; overflow: hidden; background: #07101d; border: 1px solid var(--border); border-radius: var(--radius-lg); } +.regions-page__map .map-area { height: 100%; } +.regions-page__map-meta { position: absolute; top: 14px; left: 14px; z-index: 2; display: grid; gap: 2px; max-width: min(330px, calc(100% - 28px)); padding: 9px 11px; border: 1px solid rgba(104, 177, 216, 0.3); border-radius: var(--radius); background: rgba(2, 8, 16, 0.84); box-shadow: 0 8px 20px rgba(0, 0, 0, 0.24); color: var(--text-muted); font: 10px/1.35 var(--font-mono); pointer-events: none; } +.regions-page__map-meta strong { color: var(--text-primary); font: 600 11px var(--font-mono); letter-spacing: 0.06em; text-transform: uppercase; } +.regions-page__map-legend { position: absolute; right: 14px; bottom: 14px; z-index: 2; display: grid; gap: 4px; max-width: min(390px, calc(100% - 28px)); padding: 9px 11px; border: 1px solid rgba(104, 177, 216, 0.3); border-radius: var(--radius); background: rgba(2, 8, 16, 0.84); box-shadow: 0 8px 20px rgba(0, 0, 0, 0.24); color: var(--text-muted); font: 10px/1.35 var(--font-mono); pointer-events: none; } +.regions-page__legend-swatch { display: inline-block; width: 9px; height: 9px; margin: 0 7px 2px 0; border-radius: 50%; background: #22c55e; vertical-align: middle; } +.regions-page__legend-swatch--green { background: #22c55e; } +.regions-page__legend-swatch--red { background: #ef4444; } +.regions-page__legend-swatch--area { width: 18px; height: 10px; border-radius: 2px; border: 1px solid #22d3ee; background: rgba(34, 211, 238, 0.22); } +.regions-page__map .maplibregl-ctrl-group { border: 1px solid rgba(104, 177, 216, 0.35); border-radius: var(--radius); background: rgba(2, 8, 16, 0.84); box-shadow: 0 8px 20px rgba(0, 0, 0, 0.24); } +.regions-page__map .maplibregl-ctrl button { filter: invert(0.9); } +.regions-page__side { background: #07101d; border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 18px; } +.regions-page__side h2 { margin: 0 0 12px; font-size: 15px; } +.regions-page__selected { display: flex; flex-direction: column; gap: 7px; color: var(--text-secondary); font-size: 12px; } +.regions-page__selected strong { color: var(--accent); font-size: 14px; overflow-wrap: anywhere; } +.regions-page__region-dot { display: inline-block; width: 9px; height: 9px; margin-right: 7px; border-radius: 50%; vertical-align: middle; } +.regions-page__badge { width: fit-content; padding: 5px 8px; border: 1px solid var(--border); border-radius: var(--radius); font: 10px/1.4 var(--font-mono); } +.regions-page__badge--green { color: #22c55e; border-color: rgba(34, 197, 94, 0.55); } +.regions-page__badge--red { color: #ef4444; border-color: rgba(239, 68, 68, 0.55); } +.regions-page__badge--muted { color: var(--text-muted); } +.regions-page__chips { display: flex; flex-wrap: wrap; gap: 6px; } +.regions-page__chip { padding: 4px 7px; border: 1px solid var(--border); border-radius: var(--radius); color: var(--text-secondary); font: 10px/1.3 var(--font-mono); } +.regions-page__chip strong { color: var(--text-primary); font-size: 10px; } +.regions-page__list { margin: 0; padding: 0; list-style: none; } +.regions-page__list li button { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 7px; width: 100%; padding: 8px 5px; border: 0; border-bottom: 1px solid var(--border); background: transparent; color: var(--text-secondary); text-align: left; cursor: pointer; } +.regions-page__list li button:hover { color: var(--accent); background: var(--bg-hover); } +.regions-page__list li span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.regions-page__list li strong { color: var(--text-primary); font: 11px var(--font-mono); } +.regions-page__list li i { display: inline-block; width: 8px; height: 8px; margin-right: 7px; border-radius: 50%; vertical-align: middle; } +.regions-page__low-tag { margin-left: 6px; padding: 1px 5px; border: 1px solid rgba(148, 163, 184, 0.42); border-radius: var(--radius); color: var(--text-muted); font: 9px/1.4 var(--font-mono); font-style: normal; letter-spacing: 0.08em; text-transform: uppercase; vertical-align: middle; } +.regions-page__note { margin-top: 4px; color: var(--text-muted); font-size: 10px; } +.regions-page__error { padding: 14px; border: 1px solid #7f1d1d; border-radius: var(--radius); color: #fca5a5; } +.regions-page__clear { margin-top: 8px; padding: 7px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg-active); color: var(--text-secondary); cursor: pointer; } + +@media (max-width: 900px) { + .regions-page__workspace { grid-template-columns: 1fr; } + .regions-page__stats { grid-template-columns: repeat(3, 1fr); } +} + +@media (max-width: 640px) { + .regions-page { padding-top: 28px; } + .regions-page__header { align-items: flex-start; flex-direction: column; gap: 12px; } + .regions-page__controls { justify-content: flex-start; } + .regions-page__stats { grid-template-columns: 1fr 1fr; } + .regions-page__map { min-height: 430px; height: 62vh; } + .regions-page__map-legend { right: 10px; bottom: 10px; max-width: calc(100% - 20px); } +} diff --git a/frontend/src/pages/regionsModel.test.ts b/frontend/src/pages/regionsModel.test.ts new file mode 100644 index 0000000..c7801c3 --- /dev/null +++ b/frontend/src/pages/regionsModel.test.ts @@ -0,0 +1,138 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { + confidenceFor, + convexHull, + isEstablished, + memberIdSet, + regionColor, + regionRing, + regionsForDisplay, + type RegionMember, + type RegionSummary, +} from './regionsModel.js'; + +function member( + nodeId: string, role: number | null, lat: number | null, lon: number | null, + packets = 5, isBridge = false, +): RegionMember { + return { nodeId, name: nodeId, role, lat, lon, packets, isBridge }; +} + +function region(members: RegionMember[], overrides: Partial = {}): RegionSummary { + return { + name: 'yorkshire', + packetsTotal: 100, + memberCount: members.length, + repeaterMemberCount: members.filter((entry) => entry.role === 2).length, + confidence: 'confirmed', + firstSeen: null, + lastSeen: null, + members, + ...overrides, + }; +} + +test('convexHull returns the square hull with interior points excluded', () => { + const hull = convexHull([[0, 0], [2, 0], [2, 2], [0, 2], [1, 1], [0.5, 1.5]]); + assert.equal(hull.length, 4); + const corners = new Set(hull.map(([x, y]) => `${x},${y}`)); + for (const corner of ['0,0', '2,0', '2,2', '0,2']) assert.ok(corners.has(corner), `missing ${corner}`); +}); + +test('convexHull collapses collinear and duplicate points', () => { + assert.deepEqual(convexHull([[0, 0], [1, 1], [2, 2], [1, 1]]), [[0, 0], [2, 2]]); + assert.deepEqual(convexHull([[5, 5]]), [[5, 5]]); + assert.deepEqual(convexHull([]), []); +}); + +test('regionRing builds a closed ring preferring repeater members', () => { + const ring = regionRing(region([ + member('A', 2, 54.0, -1.0), + member('B', 2, 54.2, -0.8), + member('C', 2, 53.8, -1.4), + member('D', 1, 60.0, 5.0), + ])); + assert.ok(ring); + assert.equal(ring.length, 4); + assert.deepEqual(ring[0], ring[ring.length - 1]); + for (const [, lat] of ring) assert.ok(lat < 55, 'far companion must not shape the repeater hull'); +}); + +test('regionRing falls back to all located members when repeaters are scarce', () => { + const ring = regionRing(region([ + member('A', 2, 54.0, -1.0), + member('B', 1, 54.2, -0.8), + member('C', 1, 53.8, -1.4), + ])); + assert.ok(ring); + assert.equal(ring.length, 4); +}); + +test('regionRing returns null for degenerate inputs', () => { + assert.equal(regionRing(region([member('A', 2, 54, -1), member('B', 2, 54.1, -0.9)])), null); + assert.equal(regionRing(region([ + member('A', 2, 54, -1), + member('B', 2, null, null), + member('C', 2, 54.02, -1.02), + ])), null); + assert.equal(regionRing(region([member('A', 2, 54, -1), member('B', 2, 54.1, -0.9), member('C', 2, 54.2, -0.8)])), null); +}); + +test('regionRing ignores null-island placeholder members', () => { + const ring = regionRing(region([ + member('A', 2, 54.0, -1.0), + member('B', 2, 54.2, -0.8), + member('C', 2, 53.8, -1.4), + member('JUNK', 2, 0, 0), + ])); + assert.ok(ring); + for (const [lon, lat] of ring) { + assert.ok(lat > 50 && lon < 0, 'placeholder coordinates must not shape the hull'); + } +}); + +test('regionColor is stable and well-formed', () => { + assert.equal(regionColor('yorkshire'), regionColor('yorkshire')); + assert.match(regionColor('eng-ne'), /^#[0-9a-f]{6}$/); +}); + +test('confidenceFor mirrors the backend evidence tiers', () => { + assert.equal(confidenceFor(9), 'confirmed'); + assert.equal(confidenceFor(4), 'confirmed'); + assert.equal(confidenceFor(3), 'probable'); + assert.equal(confidenceFor(2), 'probable'); + assert.equal(confidenceFor(1), 'single'); +}); + +test('memberIdSet collects every member id', () => { + const ids = memberIdSet(region([member('A', 2, 54, -1), member('B', 1, 53, -2)])); + assert.deepEqual([...ids].sort(), ['A', 'B']); +}); + +test('isEstablished and regionsForDisplay gate low-confidence regions', () => { + assert.equal(isEstablished({ packetsTotal: 3 }), true); + assert.equal(isEstablished({ packetsTotal: 2 }), false); + const strong = region([member('A', 2, 54, -1)], { name: 'yorkshire', packetsTotal: 100 }); + const weak = region([member('B', 2, 53, -2)], { name: 'micro', packetsTotal: 1 }); + assert.deepEqual(regionsForDisplay([strong, weak], false).map((entry) => entry.name), ['yorkshire']); + assert.deepEqual(regionsForDisplay([strong, weak], true).map((entry) => entry.name), ['yorkshire', 'micro']); +}); + +test('regionRing excludes universal bridge members from the hull', () => { + const ring = regionRing(region([ + member('A', 2, 54.0, -1.0), + member('B', 2, 54.2, -0.8), + member('C', 2, 53.8, -1.4), + member('HUB', 2, 60.0, 5.0, 9000, true), + ])); + assert.ok(ring); + for (const [, lat] of ring) assert.ok(lat < 55, 'bridge members must not shape the hull'); + + const allBridges = regionRing(region([ + member('A', 2, 54.0, -1.0, 5, true), + member('B', 2, 54.2, -0.8, 5, true), + member('C', 2, 53.8, -1.4, 5, true), + ])); + assert.equal(allBridges, null); +}); diff --git a/frontend/src/pages/regionsModel.ts b/frontend/src/pages/regionsModel.ts new file mode 100644 index 0000000..4e56eab --- /dev/null +++ b/frontend/src/pages/regionsModel.ts @@ -0,0 +1,167 @@ +// Region-scope model helpers for the public /regions page. +// +// Pure functions only — the map overlay builders and page components consume +// these; tests run them under node without a DOM. + +export type RegionConfidence = 'confirmed' | 'probable' | 'single'; + +export type RegionMember = { + nodeId: string; + name: string | null; + role: number | null; + lat: number | null; + lon: number | null; + packets: number; + /** True for universal bridge repeaters that relay most of the network's regions. */ + isBridge?: boolean; +}; + +export type RegionSummary = { + name: string; + packetsTotal: number; + memberCount: number; + repeaterMemberCount: number; + confidence: RegionConfidence; + firstSeen: string | null; + lastSeen: string | null; + members: RegionMember[]; +}; + +export type RegionRepeater = { + nodeId: string; + name: string | null; + role: number | null; + lat: number | null; + lon: number | null; + status: 'green' | 'red'; + evidence: string; + scopedPackets: number; + unscopedPackets7d: number; + regions: Record; +}; + +export type RegionSummaryPayload = { + generatedAt: string | null; + windowDays: number | null; + regions: RegionSummary[]; +}; + +export type RegionRepeatersPayload = { + generatedAt: string | null; + repeaters: RegionRepeater[]; +}; + +const REGION_PALETTE = [ + '#22d3ee', '#a78bfa', '#34d399', '#fbbf24', '#f472b6', '#60a5fa', + '#f87171', '#4ade80', '#facc15', '#c084fc', '#38bdf8', '#fb923c', +] as const; + +/** Stable per-region colour: same name always maps to the same palette entry. */ +export function regionColor(name: string): string { + let hash = 0; + for (let index = 0; index < name.length; index += 1) { + hash = (hash * 31 + name.charCodeAt(index)) >>> 0; + } + return REGION_PALETTE[hash % REGION_PALETTE.length]!; +} + +/** Mirrors the backend evidence tiers (>=4 confirmed, 2-3 probable, 1 single). */ +export function confidenceFor(packets: number): RegionConfidence { + if (packets >= 4) return 'confirmed'; + if (packets >= 2) return 'probable'; + return 'single'; +} + +/** + * Display threshold for the default map and list: regions with fewer than + * three decoded packets are chance-match grade — the 2026-10 calibration + * measured ~15% of candidate names picking up a single spurious hit over the + * whole corpus, so 1-2 observations stay hidden unless the user opts in. + */ +export const REGION_ESTABLISHED_MIN_PACKETS = 3; + +export function isEstablished(region: Pick): boolean { + return region.packetsTotal >= REGION_ESTABLISHED_MIN_PACKETS; +} + +/** Regions for the default view; low-confidence ones appear only when asked for. */ +export function regionsForDisplay( + regions: readonly RegionSummary[], + includeLowConfidence: boolean, +): RegionSummary[] { + return includeLowConfidence ? [...regions] : regions.filter(isEstablished); +} + +export function compactNumber(value: number): string { + return new Intl.NumberFormat('en-GB', { notation: 'compact', maximumFractionDigits: 1 }).format(value); +} + +export type Coordinate = [number, number]; + +function hasCoordinates(member: { lat: number | null; lon: number | null }): member is { lat: number; lon: number } { + const { lat, lon } = member; + return lat != null && lon != null + && Number.isFinite(lat) && Number.isFinite(lon) + && lat >= -90 && lat <= 90 + && lon >= -180 && lon <= 180 + && !(Math.abs(lat) < 1e-6 && Math.abs(lon) < 1e-6); +} + +/** + * Andrew's monotone chain over [lon, lat] points. Returns the hull vertices + * counter-clockwise without repeating the first point; collinear inputs yield + * fewer than three vertices (callers treat that as "no polygon"). + */ +export function convexHull(points: ReadonlyArray): Coordinate[] { + const unique = [...new Map(points.map(([x, y]) => [`${x},${y}`, [x, y] as Coordinate])).values()] + .sort((a, b) => a[0] - b[0] || a[1] - b[1]); + if (unique.length <= 2) return unique; + const cross = (o: Coordinate, a: Coordinate, b: Coordinate): number => + (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]); + const lower: Coordinate[] = []; + for (const point of unique) { + while (lower.length >= 2 && cross(lower[lower.length - 2]!, lower[lower.length - 1]!, point) <= 0) lower.pop(); + lower.push(point); + } + const upper: Coordinate[] = []; + for (let index = unique.length - 1; index >= 0; index -= 1) { + const point = unique[index]!; + while (upper.length >= 2 && cross(upper[upper.length - 2]!, upper[upper.length - 1]!, point) <= 0) upper.pop(); + upper.push(point); + } + lower.pop(); + upper.pop(); + return lower.concat(upper); +} + +/** + * Closed GeoJSON ring for a region: the convex hull of its resolved relay + * members (repeater-role members preferred). Universal bridge repeaters are + * excluded — they relay most regions' traffic, so their positions carry no + * signal about this region's footprint. Null when fewer than three located + * non-bridge members remain or the hull degenerates to a line. + */ +export function regionRing(region: RegionSummary): Coordinate[] | null { + const located = region.members.filter((member) => member.isBridge !== true && hasCoordinates(member)); + const repeaterMembers = located.filter((member) => member.role === 2); + const source = repeaterMembers.length >= 3 ? repeaterMembers : located; + if (source.length < 3) return null; + const hull = convexHull(source.map((member) => [member.lon, member.lat] as Coordinate)); + if (hull.length < 3) return null; + const ring = hull.map(([lon, lat]) => [Number(lon.toFixed(5)), Number(lat.toFixed(5))] as Coordinate); + return [...ring, ring[0]!]; +} + +/** Node ids of a region's resolved relay members, for map filtering. */ +export function memberIdSet(region: RegionSummary): Set { + return new Set(region.members.map((member) => member.nodeId)); +} + +export function formatGeneratedAt(value: string | null): string | null { + if (!value) return null; + const date = new Date(value); + if (!Number.isFinite(date.getTime())) return null; + return date.toLocaleString('en-GB', { + day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit', + }); +} diff --git a/frontend/src/pages/ukmesh/PacketDetailPanel.tsx b/frontend/src/pages/ukmesh/PacketDetailPanel.tsx index 4e6ac3a..bfc1ca3 100644 --- a/frontend/src/pages/ukmesh/PacketDetailPanel.tsx +++ b/frontend/src/pages/ukmesh/PacketDetailPanel.tsx @@ -3,6 +3,7 @@ import { LoadingIndicator } from '../../components/LoadingIndicator.js'; import type { MeshNode } from '../../hooks/useNodes.js'; import type { FeedPacket } from './UKFeedPage.js'; import { usePacketDetailData, type RadioState } from '../../hooks/usePacketDetailData.js'; +import type { MessageTags, TagConfidence } from '../../hooks/packetFeed.js'; import { PathMap, type LazyPath, @@ -335,6 +336,32 @@ export const PacketDetailPanel: React.FC<{ + {/* Message tags (tagger-worker / Jev) */} + {(() => { + const tags = (packet.tags ?? detail?.tags) as MessageTags | undefined | null; + if (!tags || Object.keys(tags).length === 0) return null; + const conf = (packet.tag_confidence ?? detail?.tagConfidence) as TagConfidence | undefined | null; + const chips: string[] = []; + if (tags.kind) chips.push(tags.kind); + if (tags.topic) chips.push(`topic: ${tags.topic}`); + if (tags.speaker) chips.push(`speaker: ${tags.speaker}`); + if ((tags.safety ?? 0) >= 0.5) chips.push('⚠ safety'); + if ((tags.directed ?? 0) >= 0.5) chips.push('directed'); + if ((tags.mentions_location ?? 0) >= 0.5) chips.push('mentions location'); + return ( +
+
+ Message tags{typeof conf?.kind === 'number' ? ` · kind ${(conf.kind * 100).toFixed(0)}%` : ''} +
+
+ {chips.map((chip) => ( + {chip} + ))} +
+
+ ); + })()} + {/* Observer table */} {detail?.observations && detail.observations.length > 0 && (
diff --git a/frontend/src/pages/ukmesh/UKFeedPage.tsx b/frontend/src/pages/ukmesh/UKFeedPage.tsx index c436839..1fa0806 100644 --- a/frontend/src/pages/ukmesh/UKFeedPage.tsx +++ b/frontend/src/pages/ukmesh/UKFeedPage.tsx @@ -1,6 +1,5 @@ import React, { useMemo, useCallback, useEffect, useState, useRef } from 'react'; import './feed-page.css'; -import { useMeasuredVirtualRows } from '../../hooks/useMeasuredVirtualRows.js'; import { getCurrentSite } from '../../config/site.js'; import { useWebSocket } from '../../hooks/useWebSocket.js'; import { @@ -10,6 +9,8 @@ import { } from '../../hooks/useNodes.js'; import { mapMessageRows, type RecentPacketRow } from '../../hooks/packetFeed.js'; import { useAppMessageHandler } from '../../hooks/useAppMessageHandler.js'; +import { useMessageTags } from '../../hooks/useMessageTags.js'; +import { messageTagKind, messageTagTitle } from '../../hooks/packetFeed.js'; import { ApiResponseError, chartStatsEndpoint, @@ -84,6 +85,8 @@ export const UKFeedPage: React.FC = () => { return requested && (/^(?:[0-9]{1,3}|unknown)$/.test(requested)) ? requested : null; }); const [now, setNow] = useState(() => Date.now()); + const [viewportHeight, setViewportHeight] = useState(() => window.innerHeight); + const [listScrollTop, setListScrollTop] = useState(0); const [detailOpen, setDetailOpen] = useState(false); const [historicalMessages, setHistoricalMessages] = useState([]); const [historyStatus, setHistoryStatus] = useState<'idle' | 'loading' | 'ready' | 'error'>('idle'); @@ -113,6 +116,9 @@ export const UKFeedPage: React.FC = () => { const wsConnection = useWebSocket(handleWSMessage, scope, scopeEpoch); + // Live tag enrichment: tags land ~2s after a packet, so patch rows by hash. + useMessageTags(scope, scopeKey); + useEffect(() => { historyRequestRef.current?.abort(); historyRequestRef.current = null; @@ -163,6 +169,12 @@ export const UKFeedPage: React.FC = () => { return () => window.clearInterval(timer); }, []); + useEffect(() => { + const handleResize = () => setViewportHeight(window.innerHeight); + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, []); + // Selected-packet path cache. Requests are demand-driven and scope-fenced. const [, setLazyCacheVersion] = useState(0); const settleTimerRef = useRef | null>(null); @@ -307,11 +319,21 @@ export const UKFeedPage: React.FC = () => { const recentPackets = useMemo(() => { return filteredPackets.slice(0, MAX_PACKETS); }, [filteredPackets]); - const rowKeys = useMemo( - () => recentPackets.map((packet) => `${scopeKey}:${packet.packet_hash}`), - [recentPackets, scopeKey], - ); - const virtualRows = useMeasuredVirtualRows(rowKeys, packetListRef); + const virtualRows = useMemo(() => { + // Rows are natural-height; tagged rows add one ~18px chips line under the + // meta section. Keep the estimate aligned with the taller (tagged) case so + // spacers never underestimate and rows cannot overlap (2026-09-17). + const rowHeight = 76; + const visibleHeight = Math.max(320, viewportHeight - 200); + const start = Math.max(0, Math.floor(listScrollTop / rowHeight) - 5); + const end = Math.min(recentPackets.length, start + Math.ceil(visibleHeight / rowHeight) + 10); + return { + rows: recentPackets.slice(start, end), + start, + top: start * rowHeight, + bottom: Math.max(0, (recentPackets.length - end) * rowHeight), + }; + }, [listScrollTop, recentPackets, viewportHeight]); // Always derive selectedPacket from the live list so new MQTT observers are picked up const selectedPacket = useMemo( @@ -609,19 +631,17 @@ export const UKFeedPage: React.FC = () => {
setListScrollTop(event.currentTarget.scrollTop)} > {recentPackets.length > 0 ? <> + {messageTagKind(packet) && ( +
+ + {messageTagKind(packet)} + +
+ )}

{packetSummary(packet, nodeMap)}

@@ -651,7 +678,8 @@ export const UKFeedPage: React.FC = () => {

No public packets have arrived yet.

)} - {/* Selected details live outside the measured packet list. */} + {/* Selected details live outside the fixed-height virtual rows. This + keeps expansion height from corrupting spacer calculations. */} {selectedPacket && (
@@ -676,6 +704,11 @@ export const UKFeedPage: React.FC = () => { {selectedPacket.hop_count} hop{selectedPacket.hop_count !== 1 ? 's' : ''} )} + {messageTagKind(selectedPacket) && ( + + {messageTagKind(selectedPacket)} + + )}

{packetSummary(selectedPacket, nodeMap)}

@@ -735,6 +768,11 @@ export const UKFeedPage: React.FC = () => { {selectedPacket.hop_count} hop{selectedPacket.hop_count !== 1 ? 's' : ''} )} + {messageTagKind(selectedPacket) && ( + + {messageTagKind(selectedPacket)} + + )}

{packetSummary(selectedPacket, nodeMap)}

diff --git a/frontend/src/pages/ukmesh/UKLayout.tsx b/frontend/src/pages/ukmesh/UKLayout.tsx index 30f9b6c..508da89 100644 --- a/frontend/src/pages/ukmesh/UKLayout.tsx +++ b/frontend/src/pages/ukmesh/UKLayout.tsx @@ -20,7 +20,7 @@ export const UKLayout: React.FC = () => { showFeed showRepeaterSearch showCompanion - showRegions={false} + showRegions showTopology showSpam showAbout={false} diff --git a/frontend/src/pages/ukmesh/UKRepeaterSearchPage.tsx b/frontend/src/pages/ukmesh/UKRepeaterSearchPage.tsx index e895e11..db8fcb1 100644 --- a/frontend/src/pages/ukmesh/UKRepeaterSearchPage.tsx +++ b/frontend/src/pages/ukmesh/UKRepeaterSearchPage.tsx @@ -5,6 +5,7 @@ import { useRuntimeFeatures } from '../../config/runtimeFeatures.js'; import { fetchJson, withScopeParams } from '../../utils/api.js'; import { ScopedCache } from '../../utils/scopedCache.js'; import { Combobox, type ComboboxOption } from '../../components/ui/Combobox.js'; +import { ConfirmedNeighbours, timeAgo, type NodeLink } from '../../components/ConfirmedNeighbours.js'; interface MeshNode { node_id: string; @@ -21,15 +22,6 @@ interface MeshNode { elevation_m?: number; } -interface NodeLink { - peer_id: string; - peer_name: string | null; - observed_count: number; - itm_path_loss_db: number | null; - count_this_to_peer: number; - count_peer_to_this: number; -} - interface PacketHistory { time: string; packet_hash: string; @@ -164,14 +156,6 @@ async function loadCompleteNodeSnapshot( throw new Error('Repeater index exceeded its page budget'); } -function timeAgo(iso: string): string { - const secs = Math.floor((Date.now() - new Date(iso).getTime()) / 1000); - if (secs < 60) return `${secs}s ago`; - if (secs < 3600) return `${Math.floor(secs / 60)}m ago`; - if (secs < 86400) return `${Math.floor(secs / 3600)}h ago`; - return `${Math.floor(secs / 86400)}d ago`; -} - function predictNextAdvert(packets: T[]): { nextAdvert: Date; avgInterval: number; samples: number } | null { if (packets.length < 2) return null; @@ -630,35 +614,7 @@ export const UKRepeaterSearchPage: React.FC = () => { Confirmed Neighbours {links.length > 0 && {links.length}} - {links.length === 0 ? ( -

No neighbours found for this node.

- ) : ( - <> -

Nodes with confirmed two-way communication

-
- {links.map(link => ( -
-
- - {link.peer_name || `${link.peer_id.slice(0, 12)}...`} - - - {link.peer_id.slice(0, 16)}…{link.peer_id.slice(-8)} - -
-
- Seen {link.observed_count}× - {link.itm_path_loss_db !== null && ( - · {Math.round(link.itm_path_loss_db)} dB loss - )} - · TX: {link.count_this_to_peer} - · RX: {link.count_peer_to_this} -
-
- ))} -
- - )} +
diff --git a/frontend/src/pages/ukmesh/feed-page.css b/frontend/src/pages/ukmesh/feed-page.css index 293dbfc..0b2d21e 100644 --- a/frontend/src/pages/ukmesh/feed-page.css +++ b/frontend/src/pages/ukmesh/feed-page.css @@ -1173,3 +1173,30 @@ border-bottom: 1px solid var(--border); } } + +/* ── Message tags (tagger-worker / Jev) ────────────────────────────────────── */ +.uk-feed-packet-row__tags { + display: flex; + flex-wrap: wrap; + gap: 4px; + margin: 2px 0 0; +} +.msg-tag-chip { + display: inline-flex; + align-items: center; + padding: 0 6px; + border-radius: 999px; + border: 1px solid rgba(38, 198, 162, 0.45); + background: rgba(38, 198, 162, 0.12); + color: #9fe8d6; + font-size: 10px; + line-height: 16px; + letter-spacing: 0.04em; + text-transform: uppercase; + white-space: nowrap; +} +.feed-detail__tags { + display: flex; + flex-wrap: wrap; + gap: 6px; +} diff --git a/frontend/src/pages/ukmesh/feedModel.ts b/frontend/src/pages/ukmesh/feedModel.ts index edaeaf0..650d9f7 100644 --- a/frontend/src/pages/ukmesh/feedModel.ts +++ b/frontend/src/pages/ukmesh/feedModel.ts @@ -1,4 +1,5 @@ import type { AggregatedPacket, MeshNode } from '../../hooks/useNodes.js'; +import type { MessageTags, TagConfidence } from '../../hooks/packetFeed.js'; import { ScopedCache } from '../../utils/scopedCache.js'; import type { LazyPathNode, LazyPathResult } from './PacketDetailPanel.js'; @@ -21,6 +22,8 @@ export type FeedPacket = { tx_count?: number; summary?: string | null; path_hashes?: string[] | null; + tags?: MessageTags; + tag_confidence?: TagConfidence; }; export const TYPE_LABELS: Record = { @@ -217,6 +220,8 @@ export function aggregatedPacketToFeedPacket(packet: AggregatedPacket): FeedPack tx_count: packet.txCount, summary: packet.summary ?? null, path_hashes: (packet.path as string[] | undefined) ?? null, + tags: packet.tags, + tag_confidence: packet.tagConfidence, }; } @@ -251,7 +256,7 @@ export function mergeFeedPacketObservations(current: FeedPacket, next: FeedPacke ...(next.observer_iatas ?? []), next.iata, ]); - return { + const merged: FeedPacket = { ...latest, first_seen_time: firstSeenCandidates.length > 0 ? new Date(Math.min(...firstSeenCandidates)).toISOString() @@ -267,6 +272,13 @@ export function mergeFeedPacketObservations(current: FeedPacket, next: FeedPacke rx_count: Math.max(current.rx_count ?? 0, next.rx_count ?? 0), tx_count: Math.max(current.tx_count ?? 0, next.tx_count ?? 0), }; + // Tags only exist once the tagger has run; keep the key absent when untagged + // so feed-row shapes stay stable for callers and tests. + const tags = latest.tags ?? current.tags ?? next.tags; + if (tags !== undefined) merged.tags = tags; + const tagConfidence = latest.tag_confidence ?? current.tag_confidence ?? next.tag_confidence; + if (tagConfidence !== undefined) merged.tag_confidence = tagConfidence; + return merged; } export function packetObserverIatas(packet: FeedPacket, nodeMap: Map): string[] { diff --git a/frontend/src/styles/map-app.css b/frontend/src/styles/map-app.css index 2a092a2..03320d2 100644 --- a/frontend/src/styles/map-app.css +++ b/frontend/src/styles/map-app.css @@ -2220,3 +2220,28 @@ top: calc(48px + 56px + 8px); } } + +/* ── Message tags (tagger-worker / Jev) ────────────────────────────────────── */ +.msg-tag-chip { + display: inline-flex; + align-items: center; + padding: 0 6px; + border-radius: 999px; + border: 1px solid rgba(38, 198, 162, 0.45); + background: rgba(38, 198, 162, 0.12); + color: #9fe8d6; + font-size: 10px; + line-height: 16px; + letter-spacing: 0.04em; + text-transform: uppercase; + white-space: nowrap; +} +.packet-item__tag { + margin-left: 2px; +} +.packet-item__tags { + display: flex; + flex-wrap: wrap; + gap: 4px; + margin-top: 2px; +} diff --git a/frontend/test/e2e/accessibility.spec.ts b/frontend/test/e2e/accessibility.spec.ts index a3c4ca3..8da4ea0 100644 --- a/frontend/test/e2e/accessibility.spec.ts +++ b/frontend/test/e2e/accessibility.spec.ts @@ -1,4 +1,3 @@ -import { dashboardOrigin } from './localServers.js'; import { expect, test, type Page, type Route } from '@playwright/test'; import AxeBuilder from '@axe-core/playwright'; @@ -258,7 +257,7 @@ for (const route of publicRoutes) { test('dashboard dialogs trap focus, close with Escape, and restore the trigger', async ({ page }) => { await installFixtures(page, false); - await page.goto(`${dashboardOrigin}/`); + await page.goto('http://127.0.0.1:4174/'); const initialDialog = page.getByRole('dialog', { name: 'Data disclaimer' }); await expect(initialDialog).toBeVisible(); @@ -336,7 +335,7 @@ test('repeater search exposes keyboard selection and tolerates IME composition', test('reduced-motion preference removes repeating CSS animation', async ({ page }) => { await page.emulateMedia({ reducedMotion: 'reduce' }); await installFixtures(page); - await page.goto(`${dashboardOrigin}/`); + await page.goto('http://127.0.0.1:4174/'); const repeating = await page.evaluate(() => Array.from(document.querySelectorAll('*')) .filter((element) => { const style = getComputedStyle(element); @@ -349,7 +348,7 @@ test('reduced-motion preference removes repeating CSS animation', async ({ page test('keyboard-only dashboard flow selects a node, reads details, and toggles a packet row', async ({ page }) => { await installFixtures(page); await installDashboardWebSocket(page); - await page.goto(`${dashboardOrigin}/`); + await page.goto('http://127.0.0.1:4174/'); const packet = page.locator('.packet-item').filter({ hasText: 'Keyboard fixture message' }); await expect(packet).toHaveAttribute('aria-label', /Pin GRP packet/); diff --git a/frontend/test/e2e/dashboard.spec.ts b/frontend/test/e2e/dashboard.spec.ts index 932ada8..8f7ad19 100644 --- a/frontend/test/e2e/dashboard.spec.ts +++ b/frontend/test/e2e/dashboard.spec.ts @@ -1,10 +1,38 @@ -import { expect, test } from '@playwright/test'; +import { expect, test, type Page } from '@playwright/test'; import AxeBuilder from '@axe-core/playwright'; -import { installMapRoutes, installTerrainRoutes, RF_COVERAGE_TILE } from './mapFixtures.js'; + +const TEST_MAP_STYLE = { + version: 8, + sources: { + openmaptiles: { + type: 'vector', + url: 'https://tiles.openfreemap.org/planet', + }, + }, + glyphs: 'https://tiles.openfreemap.org/fonts/{fontstack}/{range}.pbf', + layers: [ + { id: 'bg', type: 'background', paint: { 'background-color': '#080d14' } }, + ], +}; + +async function installMapRoutes(page: Page, delayPlanetMetadata = false) { + await page.route('https://tiles.openfreemap.org/**', async (route) => { + const pathname = new URL(route.request().url()).pathname; + if (pathname === '/styles/dark' || pathname === '/styles/positron') { + await route.fulfill({ json: TEST_MAP_STYLE }); + return; + } + if (pathname === '/planet' && delayPlanetMetadata) { + await new Promise((resolve) => setTimeout(resolve, 1_500)); + await route.continue(); + return; + } + await route.abort(); + }); +} test.beforeEach(async ({ page }) => { await installMapRoutes(page); - await installTerrainRoutes(page); await page.route('**/*basemaps.cartocdn.com/**', () => { throw new Error('CARTO tiles should not be requested after OpenFreeMap migration'); }); @@ -53,8 +81,7 @@ test.beforeEach(async ({ page }) => { }); let metaPolls = 0; await page.route('**/rf-coverage/meta.json*', async (route) => { - // Two fixture stages; later polls retain the completed stage and tile revision. - metaPolls = Math.min(metaPolls + 1, 2); + metaPolls += 1; const tiles = [{ image: 'tiles/standard/0-0.png', bounds: { South: 49, North: 61, West: -9, East: 3 } }]; if (metaPolls > 1) tiles.push({ image: 'tiles/standard/0-1.png', bounds: { South: 49, North: 61, West: 3, East: 4 } }); await route.fulfill({ json: { @@ -99,7 +126,7 @@ test.beforeEach(async ({ page }) => { } })); await page.route('**/rf-coverage/tiles/**/*.png*', (route) => route.fulfill({ contentType: 'image/png', - body: RF_COVERAGE_TILE, + body: Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M/wHwAF/gL+AvnqWQAAAABJRU5ErkJggg==', 'base64'), })); await page.route('**/api/**', async (route) => { const path = new URL(route.request().url()).pathname; @@ -133,9 +160,6 @@ test('HopReach tiles arrive progressively while the 4,600-node map remains inter { timeout: 15_000 }, ).toBe(true); await expect(page.getByRole('button', { name: 'Precision' })).toBeVisible({ timeout: 8_000 }); - if (testInfo.project.name === 'dashboard-mobile') { - await page.getByRole('button', { name: 'Close menu' }).click(); - } await page.getByRole('button', { name: 'Precision' }).click(); await expect(page.getByRole('button', { name: 'Precision' })).toHaveAttribute('aria-pressed', 'true'); await expect.poll( @@ -171,19 +195,6 @@ test('enabled HopReach coverage survives metadata winning the initial map-load r }); test('RF coverage remains available with 3D terrain', async ({ page }, testInfo) => { - // Real DEM decoding and software 3D rendering take longer than rejected HTML tiles. - test.setTimeout(60_000); - const tileErrors: string[] = []; - page.on('console', (message) => { - if (message.text().startsWith('[terrain] source error:') - || message.text().startsWith('[rf-coverage] unable to load source tile')) { - tileErrors.push(message.text()); - } - }); - const firstTerrainTile = page.waitForResponse((response) => - new URL(response.url()).pathname.startsWith('/terrain-tiles/')); - const firstCoverageTile = page.waitForResponse((response) => - new URL(response.url()).pathname.startsWith('/rf-coverage/tiles/')); await page.goto('/?layers=feed%2Cterrain%2Ccoverage', { waitUntil: 'domcontentloaded' }); await page.getByText('Live Map', { exact: true }).waitFor({ state: 'visible', timeout: 15_000 }); if (testInfo.project.name === 'dashboard-mobile') { @@ -195,24 +206,9 @@ test('RF coverage remains available with 3D terrain', async ({ page }, testInfo) await expect(coverage).toHaveAttribute('aria-pressed', 'true'); await expect(terrain).toHaveAttribute('aria-pressed', 'true'); await expect(page).toHaveURL(/layers=[^&]*terrain[^&]*coverage/); - const tileResponses = await Promise.all([firstTerrainTile, firstCoverageTile]); - for (const response of tileResponses) { - expect(response.status()).toBe(200); - expect(response.headers()['content-type']).toContain('image/png'); - } - const tileSizes = await page.evaluate((urls) => Promise.all(urls.map(async (url) => { - const image = await createImageBitmap(await (await fetch(url)).blob()); - const size = { width: image.width, height: image.height }; - image.close(); - return size; - })), tileResponses.map((response) => response.url())); - expect(tileSizes).toEqual([{ width: 256, height: 256 }, { width: 256, height: 256 }]); - expect(tileErrors).toEqual([]); }); -test('map modes update layers and produce a shareable URL', async ({ page }, testInfo) => { - // Two 4,600-node bootstraps and an axe scan share this budget, including cold Vite compilation. - test.setTimeout(45_000); +test('map modes update layers and produce a shareable URL', async ({ page }) => { await page.goto('/'); await expect(page.getByText('Live Map', { exact: true })).toBeVisible({ timeout: 15_000 }); const mapArea = page.locator('.map-area'); @@ -223,9 +219,6 @@ test('map modes update layers and produce a shareable URL', async ({ page }, tes .filter((violation) => violation.impact === 'critical' || violation.impact === 'serious') .map((violation) => ({ id: violation.id, targets: violation.nodes.map((node) => node.target.join(' ')) }))).toEqual([]); - if (testInfo.project.name === 'dashboard-mobile') { - await page.getByRole('button', { name: 'Layers' }).first().click(); - } const diagnose = page.getByRole('button', { name: 'Diagnose' }).first(); await diagnose.click(); await expect(diagnose).toHaveAttribute('aria-pressed', 'true'); @@ -234,9 +227,5 @@ test('map modes update layers and produce a shareable URL', async ({ page }, tes await expect(page).toHaveURL(/layers=.*clashes/); await page.reload(); - await expect(page.getByText('Live Map', { exact: true })).toBeVisible({ timeout: 15_000 }); - if (testInfo.project.name === 'dashboard-mobile') { - await page.getByRole('button', { name: 'Layers' }).first().click(); - } await expect(page.getByRole('button', { name: 'Diagnose' }).first()).toHaveAttribute('aria-pressed', 'true'); }); diff --git a/frontend/test/e2e/mobile.spec.ts b/frontend/test/e2e/mobile.spec.ts index cddac63..49212fc 100644 --- a/frontend/test/e2e/mobile.spec.ts +++ b/frontend/test/e2e/mobile.spec.ts @@ -1,6 +1,4 @@ -import { publicOrigin, dashboardOrigin, devOrigin } from './localServers.js'; import { expect, test, type Page } from '@playwright/test'; -import { installMapRoutes, installTerrainRoutes } from './mapFixtures.js'; const PHONE = { width: 375, height: 667 }; @@ -77,8 +75,6 @@ const repeaterNodes = Array.from({ length: 12 }, (_, index) => ({ })); async function installApiFixtures(page: Page) { - await installMapRoutes(page); - await installTerrainRoutes(page); await page.addInitScript(() => { localStorage.setItem('meshcore-cookie-consent-v1', '1'); localStorage.setItem('meshcore-disclaimer-dismissed', '1'); @@ -151,16 +147,16 @@ async function expectNoViewportOverflow(page: Page) { } const routedViews = [ - ['UK home', `${publicOrigin}/`], - ['UK feed', `${publicOrigin}/feed`], - ['UK repeaters', `${publicOrigin}/repeater`], - ['UK companions', `${publicOrigin}/companion`], - ['UK install', `${publicOrigin}/install`], - ['UK open source', `${publicOrigin}/open-source`], - ['UK stats', `${publicOrigin}/stats`], - ['UK login', `${publicOrigin}/login`], - ['UK docs', `${publicOrigin}/docs`], - ['test site home', `${devOrigin}/`], + ['UK home', 'http://127.0.0.1:4173/'], + ['UK feed', 'http://127.0.0.1:4173/feed'], + ['UK repeaters', 'http://127.0.0.1:4173/repeater'], + ['UK companions', 'http://127.0.0.1:4173/companion'], + ['UK install', 'http://127.0.0.1:4173/install'], + ['UK open source', 'http://127.0.0.1:4173/open-source'], + ['UK stats', 'http://127.0.0.1:4173/stats'], + ['UK login', 'http://127.0.0.1:4173/login'], + ['UK docs', 'http://127.0.0.1:4173/docs'], + ['test site home', 'http://127.0.0.1:4175/'], ] as const; for (const [name, url] of routedViews) { @@ -180,7 +176,7 @@ for (const [name, url] of routedViews) { test('shared navigation stays compact and dismisses without navigation', async ({ page }) => { await page.setViewportSize(PHONE); await installApiFixtures(page); - await page.goto(`${publicOrigin}/feed`, { waitUntil: 'networkidle' }); + await page.goto('http://127.0.0.1:4173/feed', { waitUntil: 'networkidle' }); const openMenu = page.getByRole('button', { name: 'Open menu' }); await openMenu.click(); @@ -200,7 +196,7 @@ test('shared navigation stays compact and dismisses without navigation', async ( test('shared navigation collapses before tablet links can wrap', async ({ page }) => { await page.setViewportSize({ width: 768, height: 667 }); await installApiFixtures(page); - await page.goto(`${publicOrigin}/feed`, { waitUntil: 'networkidle' }); + await page.goto('http://127.0.0.1:4173/feed', { waitUntil: 'networkidle' }); const menu = page.locator('#site-navigation'); const openMenu = page.getByRole('button', { name: 'Open menu' }); @@ -216,7 +212,7 @@ test('shared navigation collapses before tablet links can wrap', async ({ page } test('phone home and feed give primary content the full available width', async ({ page }) => { await page.setViewportSize(PHONE); await installApiFixtures(page); - await page.goto(`${publicOrigin}/`, { waitUntil: 'networkidle' }); + await page.goto('http://127.0.0.1:4173/', { waitUntil: 'networkidle' }); const introBox = await page.locator('.site-home__intro').boundingBox(); const panelBox = await page.locator('.site-home__panel').boundingBox(); @@ -227,7 +223,7 @@ test('phone home and feed give primary content the full available width', async )).toBe(1); await expectNoViewportOverflow(page); - await page.goto(`${publicOrigin}/feed`, { waitUntil: 'networkidle' }); + await page.goto('http://127.0.0.1:4173/feed', { waitUntil: 'networkidle' }); const channelsBox = await page.locator('.uk-feed-channels').boundingBox(); const chatBox = await page.locator('.uk-feed-chat').boundingBox(); expect(channelsBox?.height ?? PHONE.height).toBeLessThan(64); @@ -239,14 +235,14 @@ test('phone home and feed give primary content the full available width', async test('tablet home and feed avoid squeezed desktop columns', async ({ page }) => { await page.setViewportSize({ width: 768, height: 667 }); await installApiFixtures(page); - await page.goto(`${publicOrigin}/`, { waitUntil: 'networkidle' }); + await page.goto('http://127.0.0.1:4173/', { waitUntil: 'networkidle' }); expect(await page.locator('.site-home__grid').evaluate( (element) => getComputedStyle(element).gridTemplateColumns.split(' ').length, )).toBe(1); expect((await page.locator('.site-home__intro').boundingBox())?.width ?? 0).toBeGreaterThan(720); - await page.goto(`${publicOrigin}/feed`, { waitUntil: 'networkidle' }); + await page.goto('http://127.0.0.1:4173/feed', { waitUntil: 'networkidle' }); expect((await page.locator('.uk-feed-chat').boundingBox())?.width ?? 0).toBeGreaterThan(740); await expect(page.locator('.uk-feed-right')).toBeHidden(); }); @@ -254,7 +250,7 @@ test('tablet home and feed avoid squeezed desktop columns', async ({ page }) => test('map controls and disclaimer leave the map usable on a phone', async ({ page }) => { await page.setViewportSize(PHONE); await installApiFixtures(page); - await page.goto(`${dashboardOrigin}/`, { waitUntil: 'networkidle' }); + await page.goto('http://127.0.0.1:4174/', { waitUntil: 'networkidle' }); await page.getByRole('button', { name: /Layers/ }).click(); const lineOfSight = page.getByRole('button', { name: 'Line of sight' }); @@ -278,7 +274,7 @@ test('map controls and disclaimer leave the map usable on a phone', async ({ pag test('mobile map menu floats over the map; feed hides behind the Live chip', async ({ page }) => { await page.setViewportSize(PHONE); await installApiFixtures(page); - await page.goto(`${dashboardOrigin}/`, { waitUntil: 'networkidle' }); + await page.goto('http://127.0.0.1:4174/', { waitUntil: 'networkidle' }); // Default state: compact floating bar, packet feed hidden. const controlsBox = await page.locator('.mobile-controls').boundingBox(); @@ -305,17 +301,17 @@ test('mobile map menu floats over the map; feed hides behind the Live chip', asy await expectNoViewportOverflow(page); }); -for (const width of [320, 375, 640, 768, 1024, 1280, 1440]) { - test(`live map stays inside the ${width}px viewport and remains UK-only`, async ({ page }) => { - await installApiFixtures(page); - const apiRequests: string[] = []; - page.on('request', (request) => { - const url = new URL(request.url()); - if (url.pathname.startsWith('/api/')) apiRequests.push(url.href); - }); +test('live map stays inside every supported viewport and remains UK-only', async ({ page }) => { + await installApiFixtures(page); + const apiRequests: string[] = []; + page.on('request', (request) => { + const url = new URL(request.url()); + if (url.pathname.startsWith('/api/')) apiRequests.push(url.href); + }); + for (const width of [320, 375, 640, 768, 1024, 1280, 1440]) { await page.setViewportSize({ width, height: width <= 375 ? 667 : 800 }); - await page.goto(`${dashboardOrigin}/?network=teesside`, { waitUntil: 'networkidle' }); + await page.goto('http://127.0.0.1:4174/?network=teesside', { waitUntil: 'networkidle' }); await expect(page.locator('.map-area')).toBeVisible(); await expect(page.getByLabel('Network region')).toHaveCount(0); @@ -333,16 +329,17 @@ for (const width of [320, 375, 640, 768, 1024, 1280, 1440]) { expect(controlsBox!.x + controlsBox!.width).toBeLessThanOrEqual(width + 0.5); } await expectNoViewportOverflow(page); - expect(apiRequests.some((url) => new URL(url).searchParams.get('network') === 'ukmesh')).toBe(true); - expect(apiRequests.some((url) => new URL(url).searchParams.get('network') === 'teesside')).toBe(false); - expect(apiRequests.some((url) => new URL(url).pathname === '/api/observers/health')).toBe(false); - }); -} + } + + expect(apiRequests.some((url) => new URL(url).searchParams.get('network') === 'ukmesh')).toBe(true); + expect(apiRequests.some((url) => new URL(url).searchParams.get('network') === 'teesside')).toBe(false); + expect(apiRequests.some((url) => new URL(url).pathname === '/api/observers/health')).toBe(false); +}); test('repeater search results stay bounded and scroll inside the menu', async ({ page }) => { await page.setViewportSize(PHONE); await installApiFixtures(page); - await page.goto(`${publicOrigin}/repeater`, { waitUntil: 'networkidle' }); + await page.goto('http://127.0.0.1:4173/repeater', { waitUntil: 'networkidle' }); await page.getByRole('combobox', { name: 'Search repeaters' }).fill('Repeater'); const results = page.locator('.repeater-search-box__results'); @@ -359,7 +356,7 @@ test('repeater search results stay bounded and scroll inside the menu', async ({ test('stats path modal fits the viewport and keeps its close action reachable', async ({ page }) => { await page.setViewportSize(PHONE); await installApiFixtures(page); - await page.goto(`${publicOrigin}/stats`, { waitUntil: 'networkidle' }); + await page.goto('http://127.0.0.1:4173/stats', { waitUntil: 'networkidle' }); await page.getByRole('tab', { name: 'Paths' }).click(); await page.getByRole('button', { name: 'North -> South' }).click(); @@ -373,7 +370,7 @@ test('stats path modal fits the viewport and keeps its close action reachable', test('repeater-tree modal remains centred and fully reachable at every target width', async ({ page }) => { await installApiFixtures(page); - await page.goto(`${publicOrigin}/feed`, { waitUntil: 'networkidle' }); + await page.goto('http://127.0.0.1:4173/feed', { waitUntil: 'networkidle' }); await page.evaluate(() => { const overlay = document.createElement('div'); @@ -402,7 +399,7 @@ test('repeater-tree modal remains centred and fully reachable at every target wi test('narrow 320px pages keep primary content inside the viewport', async ({ page }) => { await page.setViewportSize({ width: 320, height: 568 }); await installApiFixtures(page); - await page.goto(`${publicOrigin}/`, { waitUntil: 'networkidle' }); + await page.goto('http://127.0.0.1:4173/', { waitUntil: 'networkidle' }); await expectNoViewportOverflow(page); const introBox = await page.locator('.site-home__intro').boundingBox(); expect(introBox?.width ?? 320).toBeLessThan(320); diff --git a/frontend/test/e2e/owner.spec.ts b/frontend/test/e2e/owner.spec.ts index 6809d17..ce9b57f 100644 --- a/frontend/test/e2e/owner.spec.ts +++ b/frontend/test/e2e/owner.spec.ts @@ -1,8 +1,21 @@ -import { expect, test } from '@playwright/test'; -import { installMapRoutes } from './mapFixtures.js'; +import { expect, test, type Page } from '@playwright/test'; const NODE_ID = 'A'.repeat(64); +const TEST_MAP_STYLE = { + version: 8, + sources: { + openmaptiles: { + type: 'vector', + url: 'https://tiles.openfreemap.org/planet', + }, + }, + glyphs: 'https://tiles.openfreemap.org/fonts/{fontstack}/{range}.pbf', + layers: [ + { id: 'bg', type: 'background', paint: { 'background-color': '#080d14' } }, + ], +}; + const dashboard = { nodes: [{ node_id: NODE_ID, @@ -19,13 +32,17 @@ const dashboard = { }], }; -test.beforeEach(async ({ page }) => { - await installMapRoutes(page); - await page.route('**/api/runtime-config', (route) => route.fulfill({ json: { - version: 1, packetArcs: false, heatmap: false, privacyGeneration: 1, refreshAfterSeconds: 30, - } })); +async function installMapRoutes(page: Page) { + await page.route('https://tiles.openfreemap.org/**', async (route) => { + const pathname = new URL(route.request().url()).pathname; + if (pathname === '/styles/dark' || pathname === '/styles/positron') { + await route.fulfill({ json: TEST_MAP_STYLE }); + return; + } + await route.abort(); + }); await page.route('**/*basemaps.cartocdn.com/**', (route) => route.abort()); -}); +} test('session polling does not reset the repeater owner content', async ({ page }) => { let sessionRequests = 0; @@ -33,6 +50,7 @@ test('session polling does not reset the repeater owner content', async ({ page let liveRequestLimit: number | null = null; await page.clock.install({ time: new Date('2026-07-16T12:00:00Z') }); + await installMapRoutes(page); await page.route('**/api/owner/session', async (route) => { sessionRequests += 1; await route.fulfill({ json: { ok: true, dashboard, mqttUsername: 'alpha-owner' } }); @@ -65,7 +83,7 @@ test('session polling does not reset the repeater owner content', async ({ page await page.goto('/login'); const identities = page.getByLabel('Owned repeater identities'); - await expect(identities.getByText('Alpha Repeater', { exact: true })).toBeVisible({ timeout: 15_000 }); + await expect(identities.getByText('Alpha Repeater', { exact: true })).toBeVisible(); await expect(page.locator('.owner-section-tabs')).toHaveCount(0); await expect(page.locator('.owner-settings')).toHaveCount(0); @@ -100,6 +118,7 @@ test('owner map construction remains one across repeated live polls', async ({ p } }).observe(document, { childList: true, subtree: true }); }); + await installMapRoutes(page); await page.route('**/api/owner/session', (route) => route.fulfill({ json: { ok: true, dashboard, mqttUsername: 'alpha-owner' }, })); @@ -124,7 +143,7 @@ test('owner map construction remains one across repeated live polls', async ({ p await page.route('**/api/owner/live-last-hop?**', (route) => route.fulfill({ json: { points: [] } })); await page.goto('/login'); - await expect(page.getByRole('heading', { name: 'Direct Sender Map' })).toBeVisible({ timeout: 15_000 }); + await expect(page.getByRole('heading', { name: 'Direct Sender Map' })).toBeVisible(); await expect(page.getByRole('heading', { name: 'Node Telemetry' })).toBeVisible(); await expect(page.locator('.owner-section-tabs')).toHaveCount(0); await expect(page.locator('.owner-settings')).toHaveCount(0); diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 67e471f..2fef6ae 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,4 +1,4 @@ -import { readFileSync } from 'node:fs'; +import { readFileSync, readdirSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { join } from 'node:path'; import { defineConfig, type Plugin } from 'vite'; @@ -11,21 +11,22 @@ import viteSeoPlugin from './src/plugins/vite-seo.js'; * worker asset is ever emitted. At runtime the browser requests * /assets/maplibre-gl-worker.mjs (which imports ./maplibre-gl-shared.mjs), * both 404 (nginx SPA fallback serves HTML), and the map renders dead (no - * tiles, no node dots). Emit the production worker and its shared module so - * the runtime requests resolve. The main module is already bundled by Vite; - * the development builds are not runtime dependencies of this worker. + * tiles, no node dots). Emit every .mjs from the maplibre dist so the runtime + * requests resolve. */ function maplibreWorkerPlugin(): Plugin { return { name: 'maplibre-worker-emit', generateBundle() { const distDir = fileURLToPath(new URL('./node_modules/maplibre-gl/dist', import.meta.url)); - for (const file of ['maplibre-gl-worker.mjs', 'maplibre-gl-shared.mjs']) { - this.emitFile({ - type: 'asset', - fileName: `assets/${file}`, - source: readFileSync(join(distDir, file)), - }); + for (const file of readdirSync(distDir)) { + if (file.endsWith('.mjs') && !file.endsWith('.map')) { + this.emitFile({ + type: 'asset', + fileName: `assets/${file}`, + source: readFileSync(join(distDir, file)), + }); + } } }, }; diff --git a/tagger-worker/Dockerfile b/tagger-worker/Dockerfile new file mode 100644 index 0000000..1887f14 --- /dev/null +++ b/tagger-worker/Dockerfile @@ -0,0 +1,8 @@ +FROM python:3.12-slim +RUN pip install --no-cache-dir psycopg2-binary==2.9.10 +WORKDIR /app +COPY tagger_worker.py /app/tagger_worker.py +ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 +RUN useradd -m -u 10001 tagger && chown -R tagger:tagger /app +USER tagger +CMD ["python3", "-u", "/app/tagger_worker.py"] diff --git a/tagger-worker/tagger_worker.py b/tagger-worker/tagger_worker.py new file mode 100644 index 0000000..ee5c548 --- /dev/null +++ b/tagger-worker/tagger_worker.py @@ -0,0 +1,347 @@ +#!/usr/bin/env python3 +"""UKMesh realtime message tagger - TypeSafe/Jev. + +Polls the packets table for new public-channel messages, asks Jev to tag each +one (speaker / kind / topic + flags), and stores the result in message_tags. +Tags are displayed on the public feed and app.ukmesh.com detail panels; private +or out-of-scope packets are never sent to the API (public visibility predicate + +network scope applied in the query). Monitoring data lives in tagger_state +(heartbeat + counters). + +Failure handling: a message that fails all inline API attempts is persisted to +tagger_retry and retried on every subsequent poll (up to TAGGER_MAX_RETRY_ATTEMPTS) +so a TypeSafe outage cannot silently drop tags (2026-09-17 hardening; the old +20s overlap window lost 6 messages during two 503 bursts). +""" +import json +import os +import signal +import sys +import time +import urllib.error +import urllib.request + +import psycopg2 + +API_URL = os.environ.get('TYPESAFE_API_URL', 'https://api.typesafe.ai/v1/systemone') +API_KEY = os.environ.get('TYPESAFE_API_KEY', '').strip() +DATABASE_URL = os.environ['DATABASE_URL'] +POLL_S = float(os.environ.get('TAGGER_POLL_S', '5')) +CALL_FLOOR_S = float(os.environ.get('TAGGER_CALL_FLOOR_S', '0.5')) +BATCH_LIMIT = int(os.environ.get('TAGGER_BATCH_LIMIT', '200')) +OVERLAP_S = int(os.environ.get('TAGGER_OVERLAP_S', '120')) +MAX_RETRY_ATTEMPTS = int(os.environ.get('TAGGER_MAX_RETRY_ATTEMPTS', '30')) +RETRY_BATCH = int(os.environ.get('TAGGER_RETRY_BATCH', '20')) +MODEL = os.environ.get('TYPESAFE_MODEL', 'jev-latest') +START = os.environ.get('TAGGER_START', '').strip() +NETWORKS = [n.strip() for n in os.environ.get('TAGGER_NETWORKS', 'ukmesh,northeast,teesside').split(',') if n.strip()] + +QUESTIONS = { + "speaker": {"type": "choice", "instructions": "Who most likely sent this message?", + "criteria": {"human": "A human operator typing a message", + "bot": "An automated bot or script", + "room-server": "A room server, repeater, or bridge auto-response", + "unclear": "Cannot tell from the message"}}, + "kind": {"type": "choice", "instructions": "What is the primary kind of this message?", + "criteria": {"chat": "Social conversation, greeting, banter, or sharing (links, pictures, files, images)", + "radio-check": "Checking whether/how their signal is received", + "test-ping": "A test or ping", + "ack": "Acknowledgement of a previous signal", + "position": "Sharing their whereabouts or movements (e.g. 'I am near X', 'heading to Y', 'going off air for a bit') - not announcements about infrastructure or network status", + "coverage": "Reporting range, hops, or propagation", + "help": "Asking for help or information from others", + "noise": "No meaningful content (gibberish, stray characters, emoji only)", + "spam": "Only unsolicited promotion, scams, or abuse. IMPORTANT: a bare link, picture, or file with no promotional text is NEVER spam (mesh users share links/images routinely); when choosing between spam and chat for a bare link, choose chat", + "automation": "Automated telemetry, home-automation, or sensor notification", + "command": "A command to a system or bot (e.g. !test, !path, !help)"}}, + "topic": {"type": "choice", "instructions": "What is this message mainly about?", + "criteria": {"weather": "Weather or conditions", + "meetups": "Meetups, events, or social plans", + "travel": "Travel, journeys, or being on the move", + "gear": "Radios, antennas, equipment, or tech gear", + "network": "Network coverage, repeaters, hops, or connectivity", + "help": "Getting help, support, or answering questions", + "humour": "Jokes, banter, or humour", + "general": "General chatter that fits nothing else"}}, + "mentions_location": {"type": "noul", "instructions": "Does the message mention a geographic place (town, area, or landmark)?"}, + "directed": {"type": "noul", "instructions": "Is the message addressed to a specific person or node (by name or @mention)?"}, + "safety": {"type": "noul", "instructions": "Is this message safety or emergency related?"}, +} + +DDL = """ +CREATE TABLE IF NOT EXISTS message_tags ( + packet_hash text PRIMARY KEY, + packet_time timestamptz NOT NULL, + tagged_at timestamptz NOT NULL DEFAULT now(), + model text, + latency_ms integer, + tags jsonb NOT NULL, + confidence jsonb +); +CREATE TABLE IF NOT EXISTS tagger_state ( + id int PRIMARY KEY CHECK (id = 1), + cursor_time timestamptz, + seen bigint NOT NULL DEFAULT 0, + tagged bigint NOT NULL DEFAULT 0, + errors bigint NOT NULL DEFAULT 0, + http_429 bigint NOT NULL DEFAULT 0, + last_error text, + avg_latency_ms_50 real, + started_at timestamptz NOT NULL DEFAULT now(), + updated_at timestamptz +); +INSERT INTO tagger_state (id) VALUES (1) ON CONFLICT DO NOTHING; +ALTER TABLE tagger_state ADD COLUMN IF NOT EXISTS retried bigint NOT NULL DEFAULT 0; +ALTER TABLE tagger_state ADD COLUMN IF NOT EXISTS given_up bigint NOT NULL DEFAULT 0; +CREATE TABLE IF NOT EXISTS tagger_retry ( + packet_hash text PRIMARY KEY, + packet_time timestamptz NOT NULL, + attempts int NOT NULL DEFAULT 0, + last_error text, + first_failed_at timestamptz NOT NULL DEFAULT now(), + exhausted boolean NOT NULL DEFAULT false +); +""" + +_running = True + + +def _stop(*_a): + global _running + _running = False + + +signal.signal(signal.SIGTERM, _stop) +signal.signal(signal.SIGINT, _stop) + + +def log(msg): + print(f"[tagger] {msg}", flush=True) + + +def api_tag(state): + payload = {'state': state, 'model': MODEL, 'questions': QUESTIONS} + req = urllib.request.Request(API_URL, data=json.dumps(payload).encode(), + headers={'Authorization': f'Bearer {API_KEY}', + 'Content-Type': 'application/json'}) + t0 = time.time() + with urllib.request.urlopen(req, timeout=90) as r: + resp = json.loads(r.read().decode()) + return resp, (time.time() - t0) * 1000.0 + + +def flatten(answers): + tags, conf = {}, {} + for k, v in (answers or {}).items(): + if not isinstance(v, dict): + continue + if 'choice' in v: + tags[k] = v.get('choice') + elif 'noul' in v: + tags[k] = v.get('noul') + elif 'score' in v: + tags[k] = v.get('score') + if 'confidence' in v: + conf[k] = v.get('confidence') + return tags, conf + + +def tag_message(cur, ph, ts): + """Tag one message with up to 3 inline attempts. Returns (ok, latency_ms, err).""" + cur.execute(""" + SELECT payload->'decrypted'->>'sender', payload->'decrypted'->>'message' + FROM packets WHERE packet_hash=%s LIMIT 1 + """, (ph,)) + row = cur.fetchone() + if row is None or row[1] is None: + return True, None, None # nothing taggable (row vanished / no message) + sender, message = row + state = f"Message from node '{sender or 'unknown'}': \"{(message or '')[:4000]}\"" + for attempt in (1, 2, 3): + try: + resp, ms = api_tag(state) + tags, conf = flatten(resp.get('answers')) + cur.execute("""INSERT INTO message_tags + (packet_hash, packet_time, tagged_at, model, latency_ms, tags, confidence) + VALUES (%s, %s, now(), %s, %s, %s, %s) + ON CONFLICT (packet_hash) DO NOTHING""", + (ph, ts, MODEL, int(ms), json.dumps(tags), json.dumps(conf))) + return True, ms, None + except urllib.error.HTTPError as e: + if e.code == 429: + cur.execute("UPDATE tagger_state SET http_429=http_429+1 WHERE id=1") + time.sleep(min(CALL_FLOOR_S * (2 ** attempt), 30)) + continue + err = f'http {e.code}: {e.read().decode()[:160]}' + time.sleep(2 * attempt) + except Exception as ex: # noqa: BLE001 + err = str(ex)[:200] + time.sleep(2 * attempt) + return False, None, err + + +def process_retries(cur): + """Retry previously failed messages. Returns (ok_count, fail_count, pending).""" + cur.execute(""" + SELECT packet_hash, packet_time, attempts FROM tagger_retry + WHERE NOT exhausted AND attempts < %s + ORDER BY first_failed_at ASC LIMIT %s + """, (MAX_RETRY_ATTEMPTS, RETRY_BATCH)) + due = cur.fetchall() + ok = fail = 0 + for ph, ts, attempts in due: + cur.execute("SELECT 1 FROM message_tags WHERE packet_hash=%s", (ph,)) + if cur.fetchone(): + cur.execute("DELETE FROM tagger_retry WHERE packet_hash=%s", (ph,)) + continue + good, ms, err = tag_message(cur, ph, ts) + if good: + cur.execute("DELETE FROM tagger_retry WHERE packet_hash=%s", (ph,)) + cur.execute("UPDATE tagger_state SET retried=retried+1 WHERE id=1") + ok += 1 + else: + fail += 1 + if attempts + 1 >= MAX_RETRY_ATTEMPTS: + cur.execute("""UPDATE tagger_retry SET attempts=attempts+1, last_error=%s, + exhausted=true WHERE packet_hash=%s""", (err, ph)) + cur.execute("UPDATE tagger_state SET given_up=given_up+1 WHERE id=1") + else: + cur.execute("""UPDATE tagger_retry SET attempts=attempts+1, last_error=%s + WHERE packet_hash=%s""", (err, ph)) + time.sleep(CALL_FLOOR_S) + cur.execute("SELECT count(*) FROM tagger_retry WHERE NOT exhausted") + pending = cur.fetchone()[0] + return ok, fail, pending + + +def main(): + if not API_KEY: + log('FATAL: TYPESAFE_API_KEY is empty') + sys.exit(2) + conn = psycopg2.connect(DATABASE_URL) + conn.autocommit = True + cur = conn.cursor() + cur.execute(DDL) + cur.execute("SELECT cursor_time FROM tagger_state WHERE id=1") + cursor = cur.fetchone()[0] + if cursor is None: + if START: + cur.execute("UPDATE tagger_state SET cursor_time=%s::timestamptz WHERE id=1", (START,)) + else: + cur.execute("UPDATE tagger_state SET cursor_time=now() - interval '5 minutes' WHERE id=1") + cur.execute("SELECT cursor_time FROM tagger_state WHERE id=1") + cursor = cur.fetchone()[0] + log(f"started; model={MODEL} poll={POLL_S}s floor={CALL_FLOOR_S}s overlap={OVERLAP_S}s " + f"networks={','.join(NETWORKS)} cursor={cursor.isoformat()}") + + lat_hist = [] + last_log = 0.0 + while _running: + loop_start = time.time() + try: + retry_ok, retry_fail, retry_pending = process_retries(cur) + + cur.execute(""" + SELECT * FROM ( + SELECT DISTINCT ON (packet_hash) + packet_hash, time, + payload->'decrypted'->>'sender' AS sender, + payload->'decrypted'->>'message' AS message + FROM packets + WHERE packet_type = 5 + AND payload->'decrypted'->>'message' IS NOT NULL + AND time > %s::timestamptz - make_interval(secs => %s) + AND time <= now() + AND network = ANY(%s::text[]) + AND visibility_ok IS TRUE + AND is_private IS NOT TRUE + AND EXISTS ( + SELECT 1 + FROM packet_visibility_materialization_state cached_visibility + JOIN public_visibility_state current_visibility + ON current_visibility.singleton = cached_visibility.singleton + WHERE cached_visibility.singleton = TRUE + AND cached_visibility.visibility_generation = current_visibility.generation + ) + ORDER BY packet_hash, time DESC + ) x ORDER BY time ASC LIMIT %s + """, (cursor, OVERLAP_S, NETWORKS, BATCH_LIMIT)) + rows = cur.fetchall() + new = errs = 0 + max_t = cursor + for ph, ts, sender, message in rows: + if ts > max_t: + max_t = ts + cur.execute("SELECT 1 FROM message_tags WHERE packet_hash=%s", (ph,)) + if cur.fetchone(): + continue + state = f"Message from node '{sender or 'unknown'}': \"{(message or '')[:4000]}\"" + ans, ms, err = None, None, None + for attempt in (1, 2, 3): + try: + resp, ms = api_tag(state) + ans = resp.get('answers') + break + except urllib.error.HTTPError as e: + if e.code == 429: + cur.execute("UPDATE tagger_state SET http_429=http_429+1 WHERE id=1") + time.sleep(min(CALL_FLOOR_S * (2 ** attempt), 30)) + continue + err = f'http {e.code}: {e.read().decode()[:160]}' + time.sleep(2 * attempt) + except Exception as ex: # noqa: BLE001 + err = str(ex)[:200] + time.sleep(2 * attempt) + if ans: + tags, conf = flatten(ans) + cur.execute("""INSERT INTO message_tags + (packet_hash, packet_time, tagged_at, model, latency_ms, tags, confidence) + VALUES (%s, %s, now(), %s, %s, %s, %s) + ON CONFLICT (packet_hash) DO NOTHING""", + (ph, ts, MODEL, int(ms), json.dumps(tags), json.dumps(conf))) + new += 1 + lat_hist.append(ms) + lat_hist = lat_hist[-50:] + else: + errs += 1 + cur.execute("UPDATE tagger_state SET errors=errors+1, last_error=%s WHERE id=1", (err,)) + # Persist for retry instead of letting the cursor move past it. + cur.execute("""INSERT INTO tagger_retry (packet_hash, packet_time, attempts, last_error) + VALUES (%s, %s, 1, %s) + ON CONFLICT (packet_hash) DO UPDATE + SET attempts=tagger_retry.attempts+1, last_error=EXCLUDED.last_error""", + (ph, ts, err)) + time.sleep(CALL_FLOOR_S) + if max_t and max_t != cursor: + cur.execute("UPDATE tagger_state SET cursor_time=%s WHERE id=1", (max_t,)) + cursor = max_t + avg = (sum(lat_hist) / len(lat_hist)) if lat_hist else None + cur.execute("""UPDATE tagger_state SET seen=seen+%s, tagged=tagged+%s, + avg_latency_ms_50=%s, updated_at=now() WHERE id=1""", + (new + errs, new, avg)) + if new or errs or retry_ok or retry_fail or (time.time() - last_log) > 120: + log(f"batch={len(rows)} new={new} errs={errs} retry_ok={retry_ok} " + f"retry_pending={retry_pending} avg50={avg and round(avg)}ms cursor={cursor.isoformat()}") + last_log = time.time() + if new + errs == 0 and retry_ok + retry_fail == 0: + time.sleep(POLL_S) + except Exception as ex: # noqa: BLE001 + log(f"loop error: {ex}") + try: + conn.rollback() + except Exception: # noqa: BLE001 + pass + time.sleep(POLL_S) + try: + conn = psycopg2.connect(DATABASE_URL) + conn.autocommit = True + cur = conn.cursor() + except Exception as ex2: # noqa: BLE001 + log(f"reconnect failed: {ex2}") + time.sleep(15) + _ = loop_start + log('shutting down') + + +if __name__ == '__main__': + main() diff --git a/viewshed-worker/tests/test_side_effect_markers.py b/viewshed-worker/tests/test_side_effect_markers.py index 12c2846..69766cd 100644 --- a/viewshed-worker/tests/test_side_effect_markers.py +++ b/viewshed-worker/tests/test_side_effect_markers.py @@ -25,23 +25,14 @@ class FakeRedis: def __init__(self): self.data = {} self.published = [] - self.publish_count = 0 - self.fail_publish_at = None - self.fail_marker_set = False def exists(self, key): return 1 if key in self.data else 0 def set(self, key, value, ex=None): - if self.fail_marker_set and key.startswith('viewshed:side-effects:'): - self.fail_marker_set = False - raise RuntimeError('injected completion-marker failure') self.data[key] = value def publish(self, channel, message): - self.publish_count += 1 - if self.publish_count == self.fail_publish_at: - raise RuntimeError('injected notification failure') self.published.append((channel, json.loads(message))) @@ -76,15 +67,6 @@ class FakeDb: return FakeCursor(self.rows) -class SequenceDb: - """Return one row set per cursor so process_job retry paths are realistic.""" - def __init__(self, cursor_rows): - self.cursor_rows = iter(cursor_rows) - - def cursor(self): - return FakeCursor(next(self.cursor_rows)) - - class SideEffectMarkerTest(unittest.TestCase): def test_marker_round_trip(self): r = FakeRedis() @@ -138,56 +120,6 @@ class SideEffectMarkerTest(unittest.TestCase): self.assertEqual(r.published, []) self.assertFalse(worker.side_effects_complete(r, 'E' * 64)) - def test_post_commit_faults_replay_on_retry_at_every_boundary(self): - """Coverage is already committed; any incomplete Redis effect must retry.""" - node = 'F' * 64 - geom = {'type': 'Polygon', 'coordinates': []} - strength = {'s1': {'type': 'Polygon', 'coordinates': []}} - - for boundary in ('link_admission', 'coverage_notification', 'node_notification', 'marker'): - with self.subTest(boundary=boundary): - r = FakeRedis() - r.fail_publish_at = { - 'coverage_notification': 1, - 'node_notification': 2, - }.get(boundary) - r.fail_marker_set = boundary == 'marker' - db = SequenceDb([ - [(None, 2, None)], # first delivery: node eligibility - [(geom, strength, 5_000.0, 12.0)], # replay: coverage row - [(54.0, -1.5)], # replay: node position - [(None, 2, None)], # retry: node eligibility - [(geom, strength, 5_000.0, 12.0)], # replay: coverage row - [(54.0, -1.5)], # replay: node position - ]) - enqueue_calls = 0 - - def enqueue(*args, **kwargs): - nonlocal enqueue_calls - enqueue_calls += 1 - if boundary == 'link_admission' and enqueue_calls == 1: - raise RuntimeError('injected link admission failure') - return 1 - - job = {'node_id': node, 'lat': 54.0, 'lon': -1.5} - with mock.patch.object(worker, 'already_calculated', return_value=True), \ - mock.patch.object(worker, 'WORKER_MODE', 'all'), \ - mock.patch.object(worker, 'enqueue_physical_link_jobs_for_node', side_effect=enqueue): - with self.assertRaises(RuntimeError): - worker.process_job(db, r, job) - self.assertFalse(worker.side_effects_complete(r, node)) - - # The next delivery takes the already-calculated branch and - # reconciles the missing side effects before returning. - worker.process_job(db, r, job) - - self.assertTrue(worker.side_effects_complete(r, node)) - expected_notifications = { - 'node_notification': 3, - 'marker': 4, - }.get(boundary, 2) - self.assertEqual(len(r.published), expected_notifications) - if __name__ == '__main__': unittest.main()
IDRSSISNRLast seen
ID{directPackets ? 'Avg RSSI' : 'RSSI'}{directPackets ? 'Avg SNR' : 'SNR'}Last seen
+ ) : null}
{node.name ?? 'Unnamed node'} {node.members.length} member key{node.members.length === 1 ? '' : 's'} @@ -334,6 +616,7 @@ export const OwnerPortalPage: React.FC = () => { ))}
+
{live?.ownerNode.name ?? 'Unnamed'}{nodeRoleLabel(live?.ownerNode.role ?? null)}
{live?.ownerNode.network ?? '-'}Network
@@ -348,7 +631,7 @@ export const OwnerPortalPage: React.FC = () => {
{live?.packetsSent24h ?? 0}Packets Sent (24h)
{live?.packetsReceived24h ?? 0}Packets Received (24h)
- {liveError ?

Live data error: {liveError}

: null} +
@@ -359,6 +642,7 @@ export const OwnerPortalPage: React.FC = () => {