From d46aee1659a9a4f6833e5127f45293deaf920aba Mon Sep 17 00:00:00 2001 From: bahdotsh Date: Tue, 6 Oct 2026 23:35:19 +0530 Subject: [PATCH] chore(release): fold the post-merge fixes into 0.28.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 0.28.0 merged in d3aed186 but was never tagged or published, and #515, #516 and #518 landed after it under a new [Unreleased]. Move those entries into the 0.28.0 section, add them to the summary, and extend UPGRADING §26 with the Android neighbor_lost timing and the native_event_gap diagnostic. --- CHANGELOG.md | 145 +++++++++++++++++++++++----------------------- docs/UPGRADING.md | 9 ++- 2 files changed, 82 insertions(+), 72 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ff6f92d..09b440bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,75 +11,6 @@ This file holds unreleased changes and the current release. Older releases are archived by series under [docs/changelog/](docs/changelog/); see the [archive index](docs/changelog/README.md). -## [Unreleased] - -### Fixed - -- **Confirmation probes no longer crowd real messages out of the outbox.** A - session the peer never confirms is probed every five seconds, and each probe - used to enter the outbox with its own retry ladder on top of the unanswered - ones before it. Only a relay verdict backed that off, so a mesh-only link - never did. An account with ten such contacts stacked about two probes a - second, and once the outbox hit its 500-entry cap, eviction failed the user's - own messages with `Outbox capacity exceeded`. Seen on a device test between - an Android and an iPhone, offline over BLE. Each new probe now supersedes - the last, quietly and without counting against the carrier, whether the - periodic scan or the Welcome fast path sent it. Probes are no longer written - to storage, and those an older build wrote are dropped at restore. A peer - holds one probe in the outbox and is still probed on the same cadence. - -- **A message carried through a dense cluster no longer dies inside it** - ([#510](https://github.com/Offline-Protocol/offline-protocol-sdk/issues/510)). - A forwarder that received a second copy of a frame while its own forward - waited cancelled the forward, on the assumption that a neighbor's - transmission had covered the same ground. It had not: every carrier hands a - frame to a few chosen neighbors rather than broadcasting it. In a cluster - where everyone hears everyone, the one device beside the way out could - stand down after two copies reached it from neighbors that never chose that - way, and the frame died with its id suppressed on every member, so the - sender's retries could not rescue it. A copy now takes only the neighbor - that sent it off the forward's fan-out, and a forward is dropped only when - every neighbor it could go to has handed it a copy. The - `mesh_forwarding::everyone_hearing_everyone_does_not_multiply_the_traffic` - flake (about 2% of runs) went from 4 failures in 200 runs to none. The - worst-case cost is unchanged: still at most one fan-out per device, inside - the same per-second budgets. -- **Android reports a Bluetooth peer lost once no link to it is left** - ([#513](https://github.com/Offline-Protocol/offline-protocol-sdk/issues/513)). - When the other phone switched Bluetooth off, its links closed cleanly, and - this phone kept it a neighbour until the reconnect backoff gave up (over a - minute in the device test, about two in the worst case) or, for a peer - reached only through this phone's GATT server, until the transport stopped. - `neighbor_lost` now fires 15 seconds after the last link to the peer goes - down, unless one comes back first. Reconnect attempts continue during those - 15 seconds; once the peer is reported lost it returns through discovery and - is announced again, as after any other loss. A dial still in flight does - not count as a link. -- **Android Bluetooth messages no longer wait on a dial back to the sender** - ([#512](https://github.com/Offline-Protocol/offline-protocol-sdk/issues/512)). - A peer's writes reach this phone's GATT server from the peer's central-role - address. When nothing had mapped that address yet, the server queued the - fragments and dialled the address to read the peer's identity, and since - that address does not advertise, the dial took from seconds to about 40 s - (11 s at p95 in a 30-round soak, 40 s after a Bluetooth toggle). An Android - central now writes its identity assertion to a new optional GATT - characteristic, Hello (`6E400006-…`), before its first message write, and - the server binds the address it proves with the same verifier the Identity - read uses. A peer without the characteristic, an iPhone among them, is - resolved as before. [BLE framing](docs/spec/ble-framing.md) specifies it. - -### Added - -- **An event lost between the native module and JavaScript is reported** - (refs [#514](https://github.com/Offline-Protocol/offline-protocol-sdk/issues/514)). - Both native modules number every event they hand to JavaScript, and the SDK - emits a `diagnostic` event (`message: "native_event_gap"`, with the missing - range) when a number is skipped. A bridgeless React Native emit can fail - without the native side seeing it, and three device-test gaps (a receipt - with no `message_received`, a delivery with no `message_delivered`) could - not be placed in the core or the bridge. This does not recover a lost - event; it says which side lost it. - ## [0.28.0] — 2026-10-06 > **The peer-stream slot carries traffic on phones.** The mobile managers @@ -93,8 +24,10 @@ archived by series under [docs/changelog/](docs/changelog/); see the > **Bluetooth LE holds up where it used to give out.** Two phones in a room > full of other Bluetooth devices find each other, an iPhone's Bluetooth > power-cycle no longer strands either end, and Android notices Bluetooth -> switched off or a stack crash and relinks. `neighbor_lost` fires once, and -> only when nothing nearby reaches the peer. +> switched off or a stack crash and relinks. An Android phone takes a peer's +> first write at once instead of dialling the peer back to learn who it is. +> `neighbor_lost` fires once, and only when nothing nearby reaches the peer; +> on Android, 15 seconds after the last link closes. > > **The SDK ships outside React Native.** A release now builds, tests and > publishes a Swift package, an Android library @@ -108,6 +41,13 @@ archived by series under [docs/changelog/](docs/changelog/); see the > `KEY_PACKAGE_OUTSIDE_VALIDITY_WINDOW` instead of leaving messages queued > with nothing to say why. > +> **Messages are not lost on the way.** A frame forwarded through a cluster +> where every device hears every other no longer dies inside it, and a contact +> that never confirms its session no longer fills the outbox with probes until +> the user's own messages are evicted. React Native reports an event lost +> between the native module and JavaScript as a `native_event_gap` +> diagnostic. +> > **Breaking, narrowly:** Python's `InternetManager` requires `app_id=`, and > an exhaustive TypeScript `switch` over `SecurityWarningCode` needs the new > member. At run time, an iPhone on 0.28 does not see one on 0.27 or earlier @@ -117,6 +57,16 @@ archived by series under [docs/changelog/](docs/changelog/); see the ### Added +- **An event lost between the native module and JavaScript is reported** + (refs [#514](https://github.com/Offline-Protocol/offline-protocol-sdk/issues/514)). + Both native modules number every event they hand to JavaScript, and the SDK + emits a `diagnostic` event (`message: "native_event_gap"`, with the missing + range) when a number is skipped. A bridgeless React Native emit can fail + without the native side seeing it, and three device-test gaps (a receipt + with no `message_received`, a delivery with no `message_delivered`) could + not be placed in the core or the bridge. This does not recover a lost + event; it says which side lost it. + - **Android forms its Wi-Fi Direct group itself.** With `wifiDirect: { enabled: true, autoAccept: true }` on Android 10 and later, devices of the same app find each other over Wi-Fi P2P service discovery @@ -482,6 +432,59 @@ archived by series under [docs/changelog/](docs/changelog/); see the ### Fixed +- **Confirmation probes no longer crowd real messages out of the outbox.** A + session the peer never confirms is probed every five seconds, and each probe + used to enter the outbox with its own retry ladder on top of the unanswered + ones before it. Only a relay verdict backed that off, so a mesh-only link + never did. An account with ten such contacts stacked about two probes a + second, and once the outbox hit its 500-entry cap, eviction failed the user's + own messages with `Outbox capacity exceeded`. Seen on a device test between + an Android and an iPhone, offline over BLE. Each new probe now supersedes + the last, quietly and without counting against the carrier, whether the + periodic scan or the Welcome fast path sent it. Probes are no longer written + to storage, and those an older build wrote are dropped at restore. A peer + holds one probe in the outbox and is still probed on the same cadence. + +- **A message carried through a dense cluster no longer dies inside it** + ([#510](https://github.com/Offline-Protocol/offline-protocol-sdk/issues/510)). + A forwarder that received a second copy of a frame while its own forward + waited cancelled the forward, on the assumption that a neighbor's + transmission had covered the same ground. It had not: every carrier hands a + frame to a few chosen neighbors rather than broadcasting it. In a cluster + where everyone hears everyone, the one device beside the way out could + stand down after two copies reached it from neighbors that never chose that + way, and the frame died with its id suppressed on every member, so the + sender's retries could not rescue it. A copy now takes only the neighbor + that sent it off the forward's fan-out, and a forward is dropped only when + every neighbor it could go to has handed it a copy. The + `mesh_forwarding::everyone_hearing_everyone_does_not_multiply_the_traffic` + flake (about 2% of runs) went from 4 failures in 200 runs to none. The + worst-case cost is unchanged: still at most one fan-out per device, inside + the same per-second budgets. +- **Android reports a Bluetooth peer lost once no link to it is left** + ([#513](https://github.com/Offline-Protocol/offline-protocol-sdk/issues/513)). + When the other phone switched Bluetooth off, its links closed cleanly, and + this phone kept it a neighbour until the reconnect backoff gave up (over a + minute in the device test, about two in the worst case) or, for a peer + reached only through this phone's GATT server, until the transport stopped. + `neighbor_lost` now fires 15 seconds after the last link to the peer goes + down, unless one comes back first. Reconnect attempts continue during those + 15 seconds; once the peer is reported lost it returns through discovery and + is announced again, as after any other loss. A dial still in flight does + not count as a link. +- **Android Bluetooth messages no longer wait on a dial back to the sender** + ([#512](https://github.com/Offline-Protocol/offline-protocol-sdk/issues/512)). + A peer's writes reach this phone's GATT server from the peer's central-role + address. When nothing had mapped that address yet, the server queued the + fragments and dialled the address to read the peer's identity, and since + that address does not advertise, the dial took from seconds to about 40 s + (11 s at p95 in a 30-round soak, 40 s after a Bluetooth toggle). An Android + central now writes its identity assertion to a new optional GATT + characteristic, Hello (`6E400006-…`), before its first message write, and + the server binds the address it proves with the same verifier the Identity + read uses. A peer without the characteristic, an iPhone among them, is + resolved as before. [BLE framing](docs/spec/ble-framing.md) specifies it. + - **A key package refused by this device's clock is reported.** A peer's key package is valid from an hour before it was minted until 30 days after, judged by the receiver's clock. A device more than an hour behind a peer diff --git a/docs/UPGRADING.md b/docs/UPGRADING.md index 88bde4ac..1a229240 100644 --- a/docs/UPGRADING.md +++ b/docs/UPGRADING.md @@ -2466,7 +2466,14 @@ still linked over the other was reported lost. An app that removed a peer on `neighbor_lost` now keeps a reachable one; one that counted the events now sees one per departure. Bluetooth switched off, on this phone or by a stack restart, ends every Bluetooth link at once, so it then fires for each peer -nothing else reaches; Android used to report none of them. +nothing else reaches; Android used to report none of them. On Android it fires +15 seconds after the last link closes, unless one comes back first, rather +than when the reconnect backoff gives up (a minute or more). + +**React Native: a new `diagnostic` message.** `diagnostic` events with +`message: "native_event_gap"` report an event the native module sent that +never reached JavaScript. An app that treats every diagnostic as an error will +now see these; the event itself is not recovered. **`transport_switched` to Wi-Fi Direct follows a peer, not the layer.** It fires when the first Wi-Fi Direct link proves and, to `None`, when the last