Files
echolot/docs/build-status.md
T
mrambossekandClaude Opus 5 60989b67ef prober build 4: newProcess fallback when the Shizuku UserService won't bind
Build 3 settled it: the Lenovo TB330FU/A15 never spawns the UserService
(two 25s bind attempts, binder alive, permission granted). Build 4 falls
back to the legacy Shizuku.newProcess remote-process API via reflection
and records exec_path in the evidence — whether that path works per
device is itself the capability question core-shizuku needs answered.

Also archives both build-3 reports: phone 7/7 incl. provoked NEIGH
transitions in ip_monitor; traceroute.udp4 handled a silent hop ("*")
correctly on both devices.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 15:10:58 +02:00

153 lines
11 KiB
Markdown
Raw 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.
# Echolot — build status & next steps
Last updated: 2026-07-29.
## Decided
- Name **Echolot**; domain echo-lot.app; scheme `echolot://`; namespace `app.echo_lot.*`
(hyphen→underscore; appIds/packages can't contain hyphens). Prober appId `app.echo_lot.prober`.
- Stack: native **Kotlin + Jetpack Compose**, no Flutter.
- Tiers: `app` (no root), **`shizuku` in v1** (wireless-ADB pairing), `root` future.
- **License (decided 2026-07-30):** all code **GPL-3.0-or-later**; the specs in `docs/` **CC-BY-4.0**.
Rationale: the moat is the no-root platform research, which is trivially liftable into a
proprietary repackage — copyleft is the only option that prevents that, and the F-Droid /
network-engineer audience reads GPL as a trust signal rather than friction. The specs go
permissive on purpose: a wire protocol only becomes a standard if anyone can implement it.
**Not AGPL for the server** — the ASP loophole it closes is speculative here, while blanket
corporate AGPL bans would hit exactly the enterprise network teams most likely to self-host.
Sole copyright holder, so relicensing the server to AGPL later stays possible.
Open: substitute a real legal copyright holder for "Echolot contributors" in the SPDX headers.
## Specs (in `docs/`, alongside this file)
- `feature-catalog-and-feasibility.md`, `measurement-schema.md`, `probe-protocol.md`. Considered draft-complete and reviewed by the user.
## Capability prober — DELIVERED (as zip, 2026-07-29)
Full Kotlin/Compose project scaffolded: `app.echo_lot.prober`, minSdk 26 / target+compile 35,
AGP 8.7.3, Kotlin 2.0.21, Compose BOM 2024.10, Shizuku api+provider 13.1.5, kotlinx-serialization.
Probes implemented: `link.snapshot`, `icmp.ping4/6` (unprivileged ICMP datagram), `sockopt.matrix`
(TTL/TOS/RECVERR/MTU_DISCOVER), `trace.errqueue_reachable`, `multinetwork.request_and_bind`,
`local.mdns_discover`, `peer.ble_advertise`, `shizuku.command_battery` (ip neigh / ip -6 route /
ip addr / ip monitor / dumpsys network_stack DHCP+IpClient / dumpsys wifi). JSON export via share
intent; results carry verdict + raw evidence.
Could NOT be compiled in the cloud sandbox: dl.google.com (Google Maven) and services.gradle.org
are proxy-blocked, and no device is reachable for on-device runs. Build + iterate locally
(Android Studio / Claude Code). Wrapper is pinned to Gradle 8.14.3.
## Website — `web/` (2026-07-30, LIVE at https://echo-lot.app)
Single-page site for echo-lot.app on Cloudflare Workers: static `public/` from the edge, a small
Worker for the stable short URLs (`/apk`, `/apk.sha256`, `/fdroid`, `/source`) and `/api/latest`.
Version info is resolved from the Gitea releases API at request time (edge-cached 5 min), so
tagging a release is the only publish step — the homepage shows the latest version/date/size via
progressive-enhancement JS and falls back to pre-release copy otherwise. Auto light/dark via
`prefers-color-scheme`: dark = sonar display, light = chart-recorder printout, same amber-accent
token system. Deploy: `npx wrangler deploy` from `web/`, or CI via
`.gitea/workflows/deploy-site.yml` (needs `CLOUDFLARE_API_TOKEN` + `CLOUDFLARE_ACCOUNT_ID`
secrets); full story in `web/README.md`. Branding applied (2026-07-30): real favicon/wordmark
copied into `web/public/assets/` (copies — `assets/branding/` is the source of truth), palette
switched to abyss/teal/amber with teal-as-instrument semantics, sonar-sweep hero replaced by a
Focus-lattice panel (rogue-DHCP finding vignette), tagline "measure, don't guess".
## Branding — `assets/branding/` (2026-07-30)
Identity chosen: the **Focus** mark — a quiet lattice of nodes with exactly one under examination
(amber) inside teal viewfinder brackets. Tone is surgical measurement, not scanning (sonar-sweep
visuals and the "sound out your network" tagline were explicitly rejected); tagline is
**"measure, don't guess"**. Palette: abyss `#071522`, tile `#0E2433`, instrument teal `#35E0C4`,
finding amber `#FFB454`, foam `#E8F4F2` — teal is always the instrument, the single amber point is
the finding. The wordmark is hand-drawn monoline SVG paths (zero font dependency); the second "o"
is the signature teal ring + amber ping. Assets: `icon.svg` (+ `icon.png` 512² — Gitea avatars reject SVG), `icon-adaptive-{foreground,background}.svg`
(108dp adaptive-icon layers, art inside the 66dp safe circle), `wordmark-on-{dark,light}.svg`,
`banner.svg` (1200×300, wired into the repo README), `social-preview.svg`/`.png` (1280×640, for the
Gitea/GitHub social-preview slot). The website's placeholder favicon/wordmark can now be replaced
from these.
## First device report — OnePlus 15, Android 16/SDK 36 (2026-07-30)
Archived at `echolot-prober/reports/CPH2747-android16-sdk36.json`. Collection workflow that works:
deploy over (wireless) adb, but the **user** starts the run and exports the JSON manually — probe
runs reliably kill the adb-over-wifi session (multinetwork/multicast churn the link adb rides on).
OS findings:
- `link.snapshot`, `icmp.ping4` (38 ms), `sockopt.matrix` 4/4, `peer.ble_advertise`: SUPPORTED.
- **`trace.errqueue_reachable` SUPPORTED** — full Os-API errqueue path exists on Android 16;
the C-over-JNI shim (next-steps #2) may be unnecessary on modern devices. Still needs a real
errqueue round-trip test, and older devices may differ.
- `icmp.ping6` EAGAIN — topology, not capability: wifi was v4-only, v6 only on cellular, socket
used the default network. Probe should bind per-network (also covers the ping4 case properly).
- `shizuku.command_battery` SUPPORTED, shell(2000): real `ip -6 route` RA data (lifetimes,
per-table), `ip monitor` NEIGH events, wifi dump with full DhcpResults. Vendor formats look
parseable.
- `local.mdns_discover`: lock acquired, discovery ran, 0 services in the 4 s window — needs a
longer window / known-good reference network before calling it more than formally SUPPORTED.
Prober bugs the run exposed (all fixed same day):
- rotation wiped results + cancelled runs → state moved to a ViewModel;
- `multinetwork.request_and_bind` ERROR → missing `CHANGE_NETWORK_STATE` in the manifest;
- `rtt_ms: "38,1"` → device locale leaked into report numbers, now `Locale.ROOT`;
- 3/7 Shizuku commands `SHIZUKU_BIND_TIMEOUT` → bind/unbind-per-command raced Shizuku; now one
bind per battery (`execBatch`).
Run 2 (same device, fixed APK — `…-run2.json`) confirmed all three fixes:
- `multinetwork.request_and_bind` **SUPPORTED** — wifi + cellular acquired and bound concurrently
(downKbps 39094 / 13231); requesting cellular even brought up a second, dual-stack PDN
(`rmnet_data2`, private v4 /30 + carrier-NAT DNS) next to the IPv6-only default PDN — exactly
the per-network behavior the production app needs.
- Shizuku battery **7/7** in 2.9 s (was 4/7 in 34 s): full ARP/NDP neighbor table, `ip addr`,
IpClient ProvisioningConfiguration incl. SSID/BSSID and APF caps (v6000, 4096 B).
- `rtt_ms: "32.0"` — locale fix holds.
Still open (probe improvements, not blockers): ping6 should bind per-network (still EAGAIN via
v4-only default wifi); mDNS window too short to see real services (0 found on a network that has
them); `ip monitor` 2 s window caught nothing on a quiet net — consider longer/event-triggered
capture; IP_MTU read via getsockoptInt reflection unavailable — reader belongs in the errqueue
shim if that lands.
## Go server skeleton — `server/` (2026-07-30)
Pure-Go stdlib implementation of the spec's core: control plane (enroll with single-use tokens,
profile with SPKI pin, sessions with the §2.4 HKDF key schedule) + UDP data plane (ELT1 header,
HMAC gate, 1024-wide anti-replay, ECHO_RESP with observation block, TIMESYNC, §3.4
anti-amplification). Wire format covered by `go test` (roundtrip, replay/bad-HMAC/unknown-prefix
silent drops); full enroll→profile→session flow smoke-tested live. Two run modes: **container**
(autodetected via /.dockerenv etc. or `--docker`; config from `ECHOLOT_*` env; distroless image,
`network_mode: host` required — Docker NAT would falsify the observed sources the protocol
measures) and **native** (`--install-systemd`/`--uninstall-systemd`, opt-in `--self-update`
against a Gitea releases API). CI: `.gitea/workflows/build-server.yml` — tests on push;
`server-v*` tags push the image to the Gitea registry + attach linux amd64/arm64 binaries that
self-update consumes. Not yet: TCP/TLS echo, STUN, canary DNS, actions, observations API,
real admin UI, self-update signature verification.
Prober build numbering: ascending `versionCode` shown on screen + as `proberBuild` in the JSON
report; current deployed build = **3**. Bump on every deployed change.
## Build-2 reports: OnePlus 15 (A16) + Lenovo TB330FU (A15) — 2026-07-30
Archived as `…-build2.json` in `echolot-prober/reports/`. The findings:
- **`traceroute.udp4` SUPPORTED on BOTH devices** (SDK 35 + 36): 6 real hops via pure-Kotlin
`Os.recvmsg(MSG_ERRQUEUE)` in ~250 ms, identical paths (A1 → Cloudflare). **The C-over-JNI
errqueue shim is dead** for Android 15+; keep it in mind only if pre-15 devices matter.
- Per-network ICMP works as designed: phone ping6 = "cellular only, default has no v6 path"
(SUPPORTED with topology evidence instead of the old bare ERROR); per-network RTTs
(wifi 20 ms vs cellular 201 ms on v4).
- **Network finding (dogfood!): hudeWLAN advertises a v6 default route via RA but hands out no
global v6 address** — both devices have only link-local + a ::/0 route on wifi. That's a
half-broken IPv6 config on the LAN, exactly the class of issue the product should flag.
- mDNS: meta-query returns 0 on BOTH devices (NsdManager limitation, now confirmed twice);
concrete `_http._tcp` finds 4 real services (3× "Magic 2 LAN", EPSON WF-7840) on both.
Production inventory must enumerate concrete types, not trust the meta-query.
- Tablet Shizuku: binder alive, permission granted, but UserService bind timed out (0/7) —
first-spawn dex extraction on slow storage suspected; build 3 raises the bind window to
25 s + one retry. Phone stays 7/7 with rich neighbor/RA/DHCP evidence.
**Build-3 verdict: not timing.** Both 25 s attempts timed out (50 s total) — the UserService
spawn genuinely fails on the Lenovo/A15. Build 4 adds a reflection fallback to the legacy
`Shizuku.newProcess` remote-process API when the bind fails; `exec_path` in the evidence says
which path ran. Whatever the outcome, core-shizuku must not assume UserService works
everywhere. (Phone build 3: still 7/7; `ip_monitor` now catches provoked NEIGH
PROBE→REACHABLE transitions, and a mid-path router dropping one TTL round showed the "*"
hop path works in traceroute.udp4.)
- `ip_monitor` returned no events this run even with the provoked gateway ping (gateway was
already REACHABLE, so no NEIGH transition happened). Evidence-dependent, not a bug.
## Next steps
1. Build locally, run on several physical devices (varied Android versions/vendors), collect the
JSON reports — especially the real per-device Shizuku dump formats.
2. If `trace.errqueue_reachable` = PARTIAL, add a C-over-JNI errqueue shim (`recvmsg`+cmsg parse)
as a `:native` module and a real `traceroute.udp4` probe.
3. Start the Go server skeleton (enrollment + profile + sessions + UDP echo with observation
blocks + canary-DNS reference records) per probe-protocol.md.
4. Fold confirmed capabilities into the production `core-probe` / `core-shizuku` modules.