Answers the two questions left open on #888 after #893.
INGESTOR_NODE_ID was required for PROTOCOL=reticulum because a passive
announce listener has no handshake revealing "our" node id. The premise
holds; the conclusion did not follow. The ingestor now owns an RNS
identity, generated on first run and stored 0600 as potato_mesh_identity
inside the app-owned RETICULUM_CONFIG_DIR, and derives its !xxxxxxxx
through the same identity-hash mapping applied to every peer, so its own
row is shaped like any other Reticulum row. A plain PROTOCOL=reticulum
deployment brings up the heartbeat, and so the reticulum packets/hour
scope, unaided; the variable still overrides, which also means no key is
generated for a deployment that does not need one. The identity is local
and never announced, so nothing new transmits. Persist-or-nothing is
deliberate: an unstorable identity would be regenerated per start and
each generation is a different node id, filing one orphan ingestor row
per restart, so the provider reports no id and warns instead. An existing
but unreadable key file is never overwritten. The startup warning #890
emitted for an unset INGESTOR_NODE_ID is removed.
CONNECTION and RETICULUM_CONFIG_DIR/RETICULUM_INTERFACES are disjoint
rather than in conflict: CONNECTION names one serial, TCP, or BLE
endpoint and an RNS stack has no such thing, which is why connect() has
always ignored it. What was wrong was the silence, since the image sets a
serial default for every protocol. connect() now says once at info that
the value is dropped and names the two variables that do apply. The image
default stays: removing it would change how meshtastic and meshcore
containers resolve their port, and a Reticulum follow-up is the wrong
change to carry that.
SPEC RN9/RN10, ACCEPTANCE RN-A9/RN-A10; CONTRACTS, README and
.env.example reconciled. 100% line and branch coverage on the provider;
pytest 1442 passed, rspec 1717 examples 0 failures.
* fix reticulum protocol loose ends from #888 review
* data: fix quoted Compose defaults blacking out every channel
* data,web: fix four defects found reviewing this PR
The periodic activity announcement was gated only by RX_ONLY, which
defaults to off, so a stock ingestor deployed to feed a map starts
broadcasting on its community's default channel without the operator
having chosen to. The only opt-out, RX_ONLY=1, also silences the
MeshCore telemetry polls, so "ingest but stay quiet" was not expressible.
Add an ANNOUNCE boolean (default 0) as an opt-in gate on top of RX_ONLY,
which keeps its meaning as the global TX kill switch and overrides
ANNOUNCE=1. Amend SPEC MA7 (which had explicitly ruled out a separate
enable env) and MA-A7 with the reasoning, document both flags in the
README env table — the RX_ONLY row did not mention the announcement at
all — and add gate unit tests for all four combinations plus the
missing-attribute fail-closed path.
Adds an optional passive UDP transport (TRANSPORT=udp) that reads the node's
"Mesh via UDP" LAN multicast instead of holding the radio's single API/serial
slot, filters to the PRIMARY channel by channel-hash (fail-closed), decrypts
with the default PSK, and enriches payloads to match the API-path packet shape
so the collector receives identical records. Implemented as a MeshProtocol
provider; 100% unit coverage on new modules; real-capture fixtures included
(node IDs/GPS anonymized). See PR description for details.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* Fix regression where Meshcore chat senders show as Meshtastic
* Address review feedback for protocol misclassification fix
- ingest.rb: exclude wrapper ``protocol`` key from /api/nodes batch-limit
count so the documented 1000-node maximum still applies after the
Python ingestor started stamping protocol at the wrapper level.
- Drop plan-file references from production and test comments per the
repo guidelines; the why is already explained inline.
* Address protocol-fallback review feedback
- Neighbor placeholder now inherits the source node's protocol from the
surrounding /api/neighbors entry, so the badge tracks the radio the
peer lives on instead of collapsing to the neutral "Unknown" label
(review item #1).
- resolve_record_protocol logs one warn_log line when an explicit
protocol stamp is rejected as malformed, making misbehaving custom
protocol adapters visible in the operator log instead of silently
falling back (review item #3).
* Extract buildNodePlaceholder helper for testability
The neighbor placeholder logic in main.js lives inside an untested
closure, so codecov reported the protocol-propagation lines as
uncovered. Extract the small placeholder builder into long-link-router
so it can be unit tested directly; the closure-internal call site stays
trivial (one factory call + one fallback call).
* data: refactor 4/7 interfaces
* data: address PR #775 review feedback
Fix the two CI test regressions caused by the package split:
- ``factory._load_ble_interface`` no longer keeps a stale module-level
``BLEInterface`` cache that survived ``monkeypatch`` teardown across
tests. The package-level attribute is now the single cache; the
``factory.py`` global was removed. This unblocks
``test_load_ble_interface_sets_global``.
- ``interfaces/__init__.py`` re-resolves ``SerialInterface`` and
``TCPInterface`` from ``meshtastic.*`` at package-load time so that a
test that pops ``data.mesh_ingestor.interfaces`` from ``sys.modules``
and re-imports picks up the freshly registered classes rather than
whatever a cached ``factory.py`` first resolved. This unblocks
``test_interfaces_patch_handles_preimported_serial``.
Restore 100% patch coverage on the interfaces subpackage by:
- Adding tests for previously uncovered, testable paths:
``_extract_host_node_id(None)``, ``_ensure_channel_metadata``,
``_normalise_nodeinfo_packet`` (None input + dict-conversion fallback),
``_resolve_lora_message`` (radio_section paths), ``_modem_preset``
(preset attr fallback + unparseable value), ``_camelcase_enum_name``
separator-only input, ``_region_frequency`` no-digit enum name,
``_ensure_radio_metadata`` unresolvable-message path, plus the
unknown-section recursive branch of ``_candidate_node_id``.
- Marking genuinely unreachable defensive branches with
``pragma: no cover`` (BLE receive loop body, upstream API regression
guards, patch re-entry guard, unreachable ``NoAvailableMeshInterface``
fallback).
* data: refactor 3/7 protocols
* data: address PR #774 review feedback
- Rewrite the parents[4] path comment in protocols/meshcore/debug_log.py
to clearly explain why the index changed from parents[3] (the original
pre-split index) without contradicting the code.
- Add tests covering the six lines flagged uncovered by codecov:
* _process_self_info host-position branch (handlers.py:78)
* on_contact_msg early-return for missing text/sender_ts (handlers.py:278)
* close() RuntimeError swallow when loop closes mid-call (interface.py:155-156)
* _run_meshcore wrapper around _ensure_channel_names failure (runner.py:131-132)
Restores 100% patch coverage on the meshcore package.
Backfill v0.6.1 CHANGELOG entry (previously undocumented),
add v0.6.2 entry covering 9 commits since v0.6.1, and bump
the version string across data, web, matrix, and app along
with the README docker-pull examples.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>