app: long runs that watch, and a relay for the port that keeps moving

Long mode starts listeners at t=0 and keeps them running past the
battery: a network-change watcher that finally fills networks[].changes[]
(defined since the schema's first draft, never populated), an RSSI log, a
ping series giving loss and jitter over minutes, and mDNS listening for
the whole window. This is the class of fault a short run cannot see - a
link that drops for four seconds between two probes is reported healthy
by both of them. run.mode records which question was asked, because
silence means different things in the two modes.

The adb relay replaces the retired beacon: AdbRelay watches adbd's own
mDNS with the resolve-once discipline the beacon learned the hard way
(resolving re-arms adbd and pops a notification), a foreground service
keeps it alive with the screen off, and the heartbeat re-posts the cached
endpoint rather than re-resolving. It exists because mDNS does not cross
subnets and the wireless-debug port rotates every few minutes.

Also records why LLDP/CDP cannot follow SSDP into long mode: both are raw
L2 frames, so they need CAP_NET_RAW - root tier, not app, and Shizuku's
shell user does not have it either.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mrambossek
2026-08-02 14:43:33 +02:00
co-authored by Claude Opus 5
parent ae63bd7c7f
commit 0071e00003
24 changed files with 1834 additions and 107 deletions
+22
View File
@@ -38,6 +38,7 @@ Export encoding: UTF-8 JSON, gzip for files (`.echolot.json.gz`), share intent u
{
"id": "0198c5f2-...-uuidv7",
"trigger": "manual | scheduled | monitor | peer",
"mode": "short | long",
"started_at": "2026-07-29T14:03:21.114Z",
"ended_at": "2026-07-29T14:07:44.902Z",
"clock": {
@@ -63,6 +64,27 @@ Export encoding: UTF-8 JSON, gzip for files (`.echolot.json.gz`), share intent u
`tiers` records what was *available*; each test records what it *used*.
`mode` records how long the run watched, and it exists because **it changes what a reader may
conclude from absence**. A `short` run is a sequence of one-shot probes — each looks at the network
for a second or two and moves on — which characterises the network's *configuration* well and is
structurally blind to anything intermittent. A `long` run starts continuous listeners at t=0, runs
the same battery beside them, and keeps sampling until its window closes; the window's length is
recorded in the `params` of the tests the listeners produce, not here.
The consequence is asymmetric and matters more than the field looks. A finding is worth the same in
either mode: a drop that was observed, was observed. Silence is not. "No link changes were seen" is
evidence of a stable link after five minutes of watching and is evidence of nothing at all after a
thirty-second run, in which a link could drop and return between two consecutive probes without
leaving a mark anywhere in the document. Consumers — a diff between two runs, a dashboard counting
how often a fault occurs, a person reading one report — must therefore not treat the absence of a
time-dependent finding in a `short` run as its refutation, and must not compare the two modes as if
they had asked the same question. `connectivity.link_flapping` is the first finding that only a
`long` run can reach; `networks[].changes[]` (§4) is likewise populated only by a long run's
listener, and an empty `changes[]` in a short run means "not watched", never "nothing happened".
Absent `mode` means `short`: it was added after the first documents were written, and every one of
them was a battery of one-shot probes.
`constraints` records what was *prevented*. A constrained run is neither a failed run nor a normal
one, and the distinction has to survive into the data: a run taken through a VPN has the same shape
and the same green verdict as a clean run of a healthy network, so without this a reader — or a