The ESP32 observer firmware now emits a periodic /neighbors report carrying
the observer's own configured region scopes (`self`) plus, for each zero-hop
neighbor, the scopes fetched via an OTA scope query. This records that
CONFIRMED configuration into a new nodes.configured_scope column, kept
strictly separate from the existing inferred `default_scope` (observed advert
transport scope) and transported_scopes (transmissions.scope_name).
Provenance is modelled explicitly rather than overloading default_scope:
default_scope is overwritten on every advert observation, so writing neighbor
scopes there would let the next inferred observation clobber a confirmed
value. A dedicated configured_scope (+ configured_scope_at) column preserves
the distinction and structurally satisfies the report contract.
Report semantics honored:
- Only neighbors with status=="responded" update configured_scope. A timeout
is NOT evidence the scopes were cleared, so it never writes.
- Absence of a neighbor is never a signal: the report is size-capped and
truncates by ordering, so missing != gone — no deletes ever happen.
- A responded neighbor with empty scopes is a valid "no scopes configured"
statement and IS stored (the handler gates on status, not emptiness).
- self scopes are keyed by origin_id (the observer node pubkey) and need no
OTA query. Report pubkeys are uppercase; nodes.public_key is lowercase hex,
so keys are lowercased before the UPDATE. Unknown neighbors are a no-op
until a later advert creates the node.
- Out-of-order reports can't clobber newer data (last-write-wins on
configured_scope_at).
Changes:
- dbschema: additive ensureConfiguredScopeColumns migration on nodes +
inactive_nodes (marker nodes_configured_scope_v1), asserted via mustCol.
- ingestor: handleNeighborsReport dispatch on topic
meshcore/<region>/<observer_id>/neighbors (analogous to /status);
Store.UpdateNodeConfiguredScope writer.
- server: PRAGMA-detect configured_scope (hasConfiguredScope) and expose it +
configured_scope_at on the node read path.
- UI: node-detail (nodes.js + live.js) shows a "Configured scope" row marked
confirmed, with last-confirmed timestamp, distinct from the observed scope.
- tests: handleNeighborsReport (responded writes, timeout/absence never
clears, empty-responded stored, unknown no-op) + last-write-wins.
Topic format meshcore/<region>/<observer_id>/neighbors is assumed by analogy
to the /status topic; noted for reviewer confirmation against the firmware.
Co-Authored-By: Claude <noreply@anthropic.com>