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

90 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: meshai Region × Family Routing — Implementation Plan
type: project
tags:
- mesh
aliases: []
related:
- [[meshai-config-hot-apply]]
- [[meshai-prod-compose-override]]
- [[meshcore-transport]]
- [[meshai]]
- [[SESSION-HANDOFF-meshai-test]]
updated: 2026-07-13
status: 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 config**`coverage` 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).