Repository navigation
Add experimental self-hosted one-way push on Android - #1631
Conversation
# Conflicts: # android/app/src/main/java/com/noop/ui/AppRoot.kt # android/app/src/main/java/com/noop/ui/SettingsScreen.kt # android/app/src/main/res/values-de/strings.xml # android/app/src/main/res/values-es/strings.xml # android/app/src/main/res/values-fr/strings.xml # android/app/src/main/res/values-pl/strings.xml # android/app/src/main/res/values-pt-rPT/strings.xml # android/app/src/main/res/values-zh/strings.xml
|
One follow-up on the Experimental lifecycle:
Also, when would you prefer iOS parity: only after the Android implementation has graduated, or as a separate follow-up once the protocol and Android client have had some field testing? I am happy to keep this PR Android-only and follow whichever sequencing best protects the current sync path. |
|
Reviewed this against the conditions in #1314 rather than the summary, and the safety-critical parts hold up — I checked each one in the code:
One small thing, and I want to be clear it's a nit rather than a blocker.
return capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)That is satisfied by a metered Wi-Fi — a phone hotspot, or a network the user has marked metered — so it isn't quite the "Wi-Fi-gated" promise the summary and the UI make. A user reasonably reads "Wi-Fi" as "this won't spend my mobile data", and on a hotspot that reading is wrong. The exposure is genuinely narrow, which is why I'd treat it as minor: a private-IP receiver isn't reachable over a hotspot anyway, so it fails rather than uploads, and payloads are gzipped and incremental. It only really bites with a public HTTPS receiver on a metered link. Two lines make the code match the promise:
Separately, and for @ryanbr rather than for you: #1314 asked to keep the first PR small — the pusher behind the toggle plus the format doc. This carries rolling-window replacement, deletion propagation, daily window hashes, capability negotiation and an encrypted progress store as well. All coherent and tested, but it's a lot more surface to judge "stays quiet and reliable in the background" against, so worth a deliberate call on scope. |
|
Update pushed in I addressed the network-policy finding by making it an explicit Wi-Fi only setting, enabled by default:
The endpoint policy already supports the deployment case I use: public HTTPS domains are allowed. Only public/plain HTTP is rejected; numeric private, loopback, link-local, and ULA addresses remain permitted over HTTP, including when reached through a VPN. I added regression coverage for settings persistence/defaults, scheduler constraints, runtime gating, and the endpoint boundary. The full On scope: the protocol surface is deliberately fairly complete for 1.0. I would rather make the initial version internally coherent and durable than introduce several protocol revisions immediately afterward that every receiver implementation would then need to track. The feature remains default-off and Experimental, so Android field experience can still drive lifecycle/UI decisions without destabilizing the 1.0 wire contract. |
The conflict was semantic, not textual. Since this branch was cut, main documented a third network path — the update check — and gave it §1.1c, the same number this branch assigns to self-hosted push. Resolved by renumbering push to §1.1d (8 references) and leaving §1.1c to the update check, then correcting the counts main and this branch each state from their own side: four network paths total, of which three are opt-in. Main's absolute claims had to be weakened rather than kept, because push makes them false: "your data never leaves the device", "only two kinds of network request", and the Coach being "the one feature that can ever send anything about your data". Each now says nothing leaves unless the user switches on a feature that sends it. The threat-model egress row deliberately still reads "three explicit paths" — main excludes the update check there too, since it sends nothing about the user.
Summary
Adds an Android-first, Experimental export path for keeping a fresh copy of NOOP data on a machine the user owns.
After a productive strap offload, NOOP schedules an independent Wi-Fi-gated worker that sends fresh local data without blocking BLE offload, persistence, analytics, or the UI. App launch and a manual catch-up action provide recovery paths.
This follows the boundary agreed in #1314:
docs/SCOPE.mdrecords this narrow Experimental exception.What it adds
PUSH_PROTOCOL1.0 with authenticated capability discovery and a fixed client-side stream registry.The protocol is receiver-neutral: NOOP commits to an interoperable contract, not to a particular server implementation.
Security and failure boundaries
Non-goals
.noopbak, arbitrary-file, or debug-bundle upload in this first version.Possible follow-ups
The same explicitly configured self-hosted destination could later gain separate, capability-gated
support for uploading
.noopbaksnapshots and for a generic artifact store used by manually shareddebug logs or diagnostic bundles. Those are deliberately not part of this PR or
PUSH_PROTOCOL1.0.Tests
The Android Full Debug unit suite passes:
Coverage includes default-off and stale-worker gates, endpoint policy and rotation, capability negotiation, stream allowlisting, cursor replay, mutable replacement and deletion, multipart retries, acknowledgements, payload bounds, structured failures, WorkManager serialization, and productive-offload triggering.
Closes #1314
Closes #1622