echo6-docs/vault/projects/meshai-region-routing-plan.md
echo6-autocommit ef8b1e0bd9 auto: docs sync 2026-07-13T12:00:23+00:00
Files changed: engine/.embcache.json engine/changelog.md engine/lint-report.md vault/.trash/2026-06-19.md vault/docs/hardware/environment.md vault/docs/hardware/ip-allocation.md vault/docs/matrix/archivist.md vault/docs/matrix/matrix_host.md vault/docs/matrix/mautrix_signal.md vault/docs/matrix/synapse.md vault/docs/matrix/synapse_retention_discovery.md vault/docs/navi/cc-rules.md vault/docs/navi/deployment.md vault/docs/navi/themes.md vault/docs/services/ots-setup.md vault/docs/services/services.md vault/docs/services/usenet.md vault/docs/software/authentik.md vault/docs/software/caddy.md vault/docs/software/central.md vault/docs/software/dns.md vault/docs/software/geo-tools.md vault/docs/software/navi.md vault/docs/software/recon.md vault/docs/software/searxng.md vault/glossary.md vault/notes/echo6-landing-page-data-export.md vault/notes/ia-download-queue.md vault/projects/advbbs-project.md vault/projects/argus.md vault/projects/deploy-livesync.md vault/projects/fleet-patch-audit.md vault/projects/fleet-platform-baseline.md vault/projects/matrix-synapse-deployment.md vault/projects/meshai-config-hot-apply.md vault/projects/meshai-region-routing-plan.md vault/projects/meshai.md vault/projects/meshcore-transport.md vault/projects/meshtastic-headscale-runbook.md vault/projects/mmud-project.md vault/projects/nominatim-v5-reimport.md vault/runbooks/add-peertube-channel.md vault/runbooks/authentik-access-groups.md vault/runbooks/authentik-create-invitation.md vault/runbooks/authentik-oidc-application.md vault/runbooks/authentik-upgrade.md vault/runbooks/central-deploy-cutover.md vault/runbooks/ct-runbook.md vault/runbooks/edge2-access-reference.md vault/runbooks/expose-service-contabo.md vault/runbooks/expose-service-edge2.md vault/runbooks/expose-service-home.md vault/runbooks/fleet-magicdns-resolved-migration.md vault/runbooks/headless-browser-page-verification.md vault/runbooks/headscale-oidc-boot-order.md vault/runbooks/headscale-onboard-node.md vault/runbooks/ia-cli-reference.md vault/runbooks/ia-download-mirror.md vault/runbooks/idahomesh-bridge-setup.md vault/runbooks/idahomesh-vpn-device-setup.md vault/runbooks/lxc-service-migration.md vault/runbooks/mailcow-create-mailbox.md vault/runbooks/meshai-prod-compose-override.md vault/runbooks/meshmonitor-password-reset.md vault/runbooks/meshtastic-sidecar-node.md vault/runbooks/meshtasticd-sim-nodes-runbook.md vault/runbooks/nordvpn-lxc.md vault/runbooks/peertube-remote-runner.md vault/runbooks/pg-backup.md vault/runbooks/pi-nas-omv-runbook.md vault/runbooks/pipeline-patterns.md vault/runbooks/proxmox-create-ubuntu-vm.md vault/runbooks/proxmox-onboard-node.md vault/runbooks/pymc-repeater-kiss-tnc-reenumeration.md vault/runbooks/recon-operations.md vault/runbooks/recon-service-integration.md vault/runbooks/syncthing-add-node.md vault/runbooks/toc-cortex-pve9.2-update.md vault/session-resume/SESSION-HANDOFF-meshai-test.md
2026-07-13 12:00:23 +00:00

13 KiB
Raw Blame History

title type tags aliases related updated status
meshai Region × Family Routing — Implementation Plan project
mesh
meshai-config-hot-apply
meshai-prod-compose-override
meshcore-transport
meshai
SESSION-HANDOFF-meshai-test
2026-07-13 proposed

meshai — Universal Region × Family Routing: Final Implementation Plan

Produced by a local multi-agent ultraplan (3 planners → 3 judges → synthesize → 3 red-team critics → finalize), verified against prod code at commit ceb95fb.

Scope principle (Matt, 2026-07-07): build the PLUMBING, not automation. meshai exposes the mechanism — region-tagged events + a compact region_routes matrix the dispatcher honors + a plain editor to set it. Matt does all configuration by hand: defines the region boxes, provisions the radio channels, fills each family×region→channel cell himself. meshai auto-creates nothing — no channels, no regions, no routes, no generated rule objects. "No sprawl" comes from the config being ONE compact object he edits, not from automation.

1. Verdict

GO, phased. Achievable, mostly config/GUI once the keystone code gap is closed: nothing currently writes event.region / event.regions (notifications/events.py:55-56 define them, the dispatcher reads them, zero writers exist). Close that and the existing region-scope machinery comes alive.

Three non-negotiables baked in:

  1. Matrix routing must resolve ABOVE the toggle min_severity floor. Satpass + most road/511 events are routine; the toggle floor defaults to priority (config.py:616) and runs before delivery-plan build. Left naive, "Magic Valley Ham Radio" and road routing silently do nothing.
  2. Meshtastic has a hard 8-channel ceiling (index 07); MeshCore routes by unbounded channel name. A 3×3 matrix is 9+ cells → the transports are asymmetric. Confront with an explicit MT-coarse / MC-fine policy.
  3. Everything ships behind default-inert flags on the GUARDED toggle path, never the unguarded rules path.

Effort: P1 = S (~½ day), P2 = S (~½ day), P3 = M (~23 days). P1 deploys dark, safe to soak indefinitely.

2. Keystone: region tagging (P1)

Region source = config.coverage.areas[].name (named bboxes). Reject mesh_intelligence.regions/RegionAnchor (centroid+radius, wrong for polygon events; its taggers are dead code).

  • config.py:980 add Coverage.region_tagging: bool = False. coverage is restart-required (config_routes.py:29-40) — flip needs a container restart.
  • coverage_area.py: add MonitoringArea.name: str|None=None; areas_from_config keeps the name; refactor the embedded geometry-extraction (:193-209) into a shared _event_geom_json; add an additive matching_area_names() (match-all, no early return — leaves classify_geom_areas gate byte-identical) + event_region_names().
  • coverage_filter.py: add region_tagging ctor flag; tag before the gate and only when not event.regions (never clobber satpass); wire in build_pipeline (pipeline/__init__.py:121).
  • Contract: regions = all intersecting named areas (config order, deduped); region = regions[0] (deterministic, keeps cooldown key stable); 0 matches / no geom / parse error ⇒ None/[] ⇒ identical to today.

3. Routing model: new compact primitive on the guarded toggle path

Add notifications.region_routes (a RegionRouteMatrix: enabled: bool=False, cells: dict = family→region→{mt, mc, min_severity}) and resolve it inside _dispatch_toggles above the severity/region gates.

  • Reject generated per-cell rules: the rules path is fully unguarded (no cold-start/staleness/dedup; rule.cooldown_minutes is dead code) → routing high-frequency families through it floods. It's also the O(R×F) object pile Matt wants gone.
  • Schema must be declared ABOVE NotificationsConfig (no from __future__ import annotations → nested-dataclass coercion fires at class-def; else NameError). Add an explicit hydration branch mirroring destinations (config.py:1149).
  • Dispatcher matrix branch (authoritative-on-match): resolves per event region → cell; per-cell min_severity (default routine) is the only floor; bypasses toggle.regions and toggle.min_severity (this is what lets routine families route); collapses same-channel overlap to one send but arms cooldown for every contributing region; _toggle_to_rule gains optional broadcast_channel/meshcore_channel overrides. _audit_route unchanged (one mesh_broadcasts_out row per transport).
  • Cooldown/dedup (matrix branch only; non-matrix path byte-identical): per-region cooldown key (toggle, category, region); per-(source, id, transport, channel) dedup (preserves retry when one region's channel fails + keeps fire growth stages flowing per channel). Honest caveat: toggle.cooldown_seconds defaults to 0 → out of the box matrix flood protection is dedup-only; rollout requires setting non-zero cooldown on high-frequency families (roads/511/wzdx). Keep fire cooldown=0 (dedup + growth suffix govern it).

4. Manual matrix editor (P3) — plumbing Matt fills in, no generation

This is a plain editor over the region_routes primitive, NOT a generator. It reads the current matrix, lets Matt assign cells by hand, and saves exactly what he entered. meshai computes/creates nothing — no rules synthesized, no channels/regions invented.

  • Backend (dashboard/api/notification_routes.py): GET /api/notifications/regions (lists the region names that exist — coverage area names + satpass observer labels — so the picker offers what Matt already defined; the single routing region source; repoint pickers off the mesh-health /api/regions). MUST read the SAVED (on-disk) coverage, not the in-memory configcoverage is restart-required, so the running config lags disk until restart; but adding/removing a routing column must NOT require a bot restart. Reading disk decouples the routing-UI region list (dynamic, instant) from the engine's coverage re-scoping (still restart-gated). Confirmed in preview: create/delete a region on the Coverage page → it appears/disappears in the matrix immediately, no restart; GET /api/notifications/region-routing (returns the matrix verbatim); POST /api/notifications/region-routing (plain save via explicit server-side read-modify-write — persists only what Matt entered; RMW because the section save rebuilds all of NotificationsConfig and would wipe toggles/rules/destinations otherwise; golden round-trip test is a release gate); optional read-only preview (shows which categories feed each family + the distinct MT-index count vs the 8 ceiling, so Matt sees what he still has to provision).
  • Frontend (UNVERIFIED — dashboard-frontend not in the code checkout; re-verify ChannelPicker/useFamilies/Notifications.tsx first): RegionRoutingMatrix.tsx tab — rows=families (from useFamilies), cols=regions (from /api/notifications/regions), each cell = MT ChannelPicker + MC ChannelPicker + min_severity (default routine) + enable; sparse persistence (only cells Matt fills). Optional manual convenience fills (apply-down-a-region, copy-a-region) are just clipboard helpers Matt chooses to click — never automatic. Live MT-budget guard (warn at 8 distinct MT indices).

5. MC + MT per cell + the Meshtastic 8-channel ceiling

Each cell carries both mt (index) and mc (name) in one object; dual transport = one cell. MT has a hard 8-slot firmware ceiling; MC names are unbounded. Matt's target (Roads/Fires/Ham × SC-ID/SW-ID/East-ID = 9+ cells) can't get distinct MT channels.

CHOSEN POLICY (Matt, 2026-07-07): MT axis = per-REGION "Alerts" channels; MC axis = fine per-region×family names. On Meshtastic, one catch-all channel per region — SWI Alerts, SCI Alerts, EI Alerts (+ Magic Valley) — each carrying ALL families for that region, families distinguished by the existing emoji/text prefix (🔥/🚧/🛰️). On MeshCore, keep the full region×family split by name. Keeps region separation on MT (what Matt wants), gives up only per-family separation on MT (MeshCore retains it). Budget: 34 region channels + primary + admin, comfortably under 8. Zero extra code — it's just how the MT column is filled: set each region's MT index once and use the "apply down the region" fill so every family in that region → that region's Alerts index; the mc side stays per-cell.

Channel provisioning is NOT eliminated — meshai only routes to channels; every named channel (the 34 MT region Alerts channels + each MC region×family name) must still be hand-created on the Meshtastic device + MeshCore companion and subscribers pointed at it. The matrix kills the rule-object sprawl, not radio-side setup; the preview endpoint lists exactly what to provision.

6. Satpass special-case

Satpass has no geometry → tag it upstream so the never-clobber rule protects it. Refactor env/satpass.py::to_event to bind-then-return, set regions=[entry_observer, exit_observer] (ordered, deduped; friendly labels already at satpass.py:164-165). Add "satpass" to VALID_TOGGLES (categories.py:26) — hard prerequisite: categories_for_toggle("satpass") returns [] today only because satpass isn't in the toggle set. Observer friendly names must exactly match coverage.areas[].name to line columns up (e.g. "Magic Valley"); unmatched labels render their own column, never dropped. Routine severity routes fine because the matrix resolves above the floor.

7. Migration & back-compat

No migration; three default-inert levers (MonitoringArea.name=None, region_tagging=False, RegionRouteMatrix.enabled=False). "Byte-identical" holds only while region_tagging=False. Flipping it on activates two behaviors with no code change:

  1. Dormant region-scope activation — every existing non-empty toggle.regions / rule.region_scope is dead today and activates. Rules path is unguarded → activated rules broadcast every sweep, replay backlog on restart.
  2. Cooldown cardinality shift — the non-matrix cooldown key goes from global "*" to per-region → more broadcasts for multi-region feeds on any toggle with cooldown_seconds>0.

Mandatory pre-flip audit (release gate): dump prod config; list/disable/re-point every non-empty rule.region_scope (unguarded) and review every toggle.regions; ensure no matrix-routed family is also routed by an enabled rule (exclusivity); confirm per-region cooldown splits are acceptable. This is why P1 ships dark and soaks.

8. Test + safe rollout on CT108

Unit: matching_area_names (in-one/overlap-ordered/on-edge/outside/null/unnamed), shared geom helper, coverage_filter tag+gate + off-parity + satpass-preserve, RegionRouteMatrix round-trip golden, region-routing-save golden (toggles/rules/destinations survive), dispatcher matrix cases (routine routes, overlap one-send-both-cooldowns, per-region cooldown independence, per-channel dedup retry, authoritative-on-match, no-cell fall-through, cold-start/staleness still drop). Note: test harness builds NO CoverageFilter — matrix tests set event.region/regions manually.

Live (pct exec 108 from root@utility, compose in /home/zvx/meshai/work/): (1) deploy P1 dark + restart → zero change; (2) run pre-flip audit; (3) flip region_tagging=True + restart, matrix disabled → confirm regions populate, soak; (4) enable matrix, ONE cell (roads × SC-ID → throwaway channel, enabled=false first, cooldown>0) → confirm one landing on both + cooldown_dropped increments; (5) point real cells one family at a time. Keep fire cooldown=0.

9. Phases

Phase Effort Ships
P1 keystone tagging (dark) S ~½d events carry region/regions; dormant scopes usable; zero routing change; safe soak
P2 region source + one cell e2e (guarded) S ~½d /api/notifications/regions + repoint pickers; satpass in VALID_TOGGLES + observer tagging; prove one family×region via toggle.destinations on a throwaway channel
P3 compact primitive + manual matrix editor M ~23d Matt fills the grid himself (dual MT+MC per cell); one compact object, no rule sprawl, nothing auto-created

10. Open decisions for Matt

  1. Meshtastic 8-channel policy — DECIDED: MT = per-region Alerts channels (SWI/SCI/EI [+ Magic Valley]), each carrying all families for its region; MC = fine per-region×family. Zero extra code (MT-column fill).
  2. Matrix severity semantics — accept cells route all events ≥ cell min_severity (default routine) to both transports, bypassing per-severity severity_channels (chattier, but makes routine routing work).
  3. Cooldown posture — non-zero on roads/511/wzdx before go-live; fire stays 0.
  4. Observer naming — satpass observers named to match coverage area names.

Files: config.py, coverage_area.py, notifications/pipeline/coverage_filter.py, notifications/pipeline/__init__.py:121, notifications/pipeline/dispatcher.py, notifications/categories.py:26, env/satpass.py, dashboard/api/notification_routes.py, dashboard/api/mesh_routes.py:254, dashboard-frontend/src/pages/RegionRoutingMatrix.tsx (new, unverified repo).