# Changelog

All notable changes to peat-btle will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]


## [0.3.1] - 2026-05-02

ADR-059 Amendment 2 (Slice 1.b.4 host-side wiring) — adds the
UniFFI-exported `DecodedDocumentJsonCallback` so Kotlin / Swift hosts
can install a callback for inbound translator frames and forward each
decoded doc into peat-ffi's `publishDocument` with `origin="ble"`.

Amendment 2 corrected Amendment 1's "Lands in peat-ffi … Registered at
PeatNode construction" claim: peat-ffi never owns a `peat_btle::PeatMesh`
instance, so the wiring lives in the host (peat-atak-plugin et al.)
where the existing UniFFI / JNI split already holds handles to both
peat-btle's BLE stack and peat-ffi's `PeatNode`.

### Added

- **`crate::DecodedDocumentJsonCallback` UniFFI callback trait**
  (`#[uniffi::export(callback_interface)]`, gated on
  `mesh-translator` + `uniffi`). Same signature as the Rust-only
  `DecodedDocumentCallback` from 0.3.0 except the
  `peat_mesh::sync::Document` payload is serialized to JSON before
  invocation. Kotlin / Swift hosts implement this trait; the
  receive-dispatch fires it alongside the Rust-trait callback so both
  can be installed independently.

- **`PeatMesh::set_decoded_document_json_callback(Box<dyn DecodedDocumentJsonCallback>)`**
  on both the inner `peat_mesh::PeatMesh` and the UniFFI `PeatMesh`
  wrapper. Idempotent — replaces any prior installation. Internally
  stores as `Arc<dyn ...>` so the receive dispatch can snapshot-clone
  the handle out of the lock without holding it during user-supplied
  dispatch (same pattern as `decoded_document_callback`).

- **Receive-dispatch dual-firing**: `try_handle_translator_marker`
  invokes both callbacks (Rust-trait and UniFFI-JSON) when both are
  installed, each independently of the other. JSON serialization
  failures log + skip only the JSON path (the Rust-trait callback,
  if installed, still fires on the typed `Document`).

- **`PeatEvent::TranslatorNoCallback` suppression rule**: the event
  fires only when **both** callback slots are empty. Installing
  either callback suppresses it. Documents the release-skew window
  remains operator-observable while the JSON path is being rolled out.

- **Regression test**: `tests/reserved_marker_silent_drop.rs::json_callback_fires_and_suppresses_no_callback_event`
  installs a UniFFI JSON callback, sends a 0xB6 platforms frame, and
  asserts (a) the JSON callback received the doc with the correct
  collection name + peer, (b) the JSON payload round-trips through
  `serde_json::Value`, and (c) `TranslatorNoCallback` did not fire.
  Locks the both-callbacks-fire-independently and
  installed-suppresses-no-callback-event guarantees so a future
  refactor can't silently drop them.

### Operational notes

- Hosts upgrading from 0.3.0 to 0.3.1 do not need to re-flash any
  legacy peers — the wire format is unchanged. 0.3.1 is purely an
  additive API surface release: existing `PeatMesh` consumers see no
  behavioral change; consumers that install the new callback gain
  cross-transport receive-side ingest.

- Slice 1.b.4 host work (peat-atak-plugin's `BleDecodedDocumentBridge`)
  unblocks once 0.3.1 is on crates.io.


## [0.3.0] - 2026-05-01

ADR-059 Amendment 1 (Slice 1.b.3) — BLE receive-side wire format,
`DecodedDocumentCallback` trait, and translator-frame dispatch.
Required by peat-ffi Slice 1.b.4 to install the callback and thread
`Node::publish_with_origin(.., Some("ble"))` into the doc-store ingest
path. Closes the receive-side gap that ADR-059's "Inbound transport
bridge" pseudocode glossed over and unblocks the peat-atak-plugin
migration queue (#65 / #66 / #69 / #26 in that repo).

### Added

- **`mesh-translator` feature** (default-off, ADR-059): ships
  `BleTranslator` + the `peat_mesh::transport::Translator` impl so a
  peat-mesh-using consumer can plug peat-btle into
  `peat_mesh::transport::TransportManager` for cross-transport document
  bridging. Standalone BLE consumers (Bitchat-style apps, embedded
  sensors) compile peat-btle without the feature and never pull
  peat-mesh, serde_json, postcard, anyhow, or tracing into their dep
  graph. **peat-mesh is exact-pinned to `=0.9.0-rc.4`** to match the
  peat workspace pin — this pin must move in lockstep with the next
  peat-mesh release (peat-mesh release → peat workspace bump → peat-btle
  Cargo.toml bump → peat-btle release), otherwise downstream consumers
  pulling both peat and peat-btle with `mesh-translator` enabled will
  hit a resolver conflict.

- **ADR-059 Amendment 1 wire format** (`mesh-translator` feature):
  `TRANSLATOR_FRAME_MARKER = 0xB6` on the BLE wire, with a 1-byte
  collection code (`COLLECTION_CODE_TRACKS = 0x01`,
  `COLLECTION_CODE_PLATFORMS = 0x02`, `COLLECTION_CODE_ALERTS = 0x03`,
  `COLLECTION_CODE_CANNED_MESSAGES = 0x04`) and a postcard-encoded
  payload. Codes are immutable. 0xB6 sits above the ADR-001
  trust-architecture reservations (0xB3–0xB5 IDENTITY_ATTESTATION /
  REVOCATION / KEY_ROTATION) and below the 0xC0–0xCF `DocumentType`
  registry range. 0xB7–0xBF are reserved for future translator-frame
  variants and are silent-dropped on receive.

- **Symmetric encode/decode**: `BleTranslator::encode_outbound` now
  emits the fully-framed wire bytes; `decode_inbound` (and the new
  sync helper `decode_inbound_sync`) take payload-only bytes after
  peat-btle's receive dispatch strips marker + code. peat-btle is the
  single source of truth for the wire format in both directions —
  hosts (peat-ffi → peat-atak-plugin / wearos-tak-civ / ...) own no
  per-translator-frame logic and forward bytes through unchanged.

- **`DecodedDocumentCallback` trait** + `MeshDocument` re-export:
  invoked by peat-btle's GATT receive after a 0xB6 frame decodes
  successfully. peat-ffi (Slice 1.b.4) installs the callback and calls
  `Node::publish_with_origin(collection, doc, Some("ble"))` from inside
  it so origin threading prevents BLE echo in cross-transport fan-out.

- **`PeatMesh::set_decoded_document_callback`** + **`set_translator_config`**:
  setters for the receive-side callback and translator config (all
  gated by `mesh-translator`). The callback is `None` until installed —
  the deliberate Slice 1.b.3-ships-before-1.b.4 release-skew window.

- **`PeatEvent::TranslatorNoCallback { collection, peer }`**: emitted
  on the existing observer channel when a 0xB6 frame decodes
  successfully but no `DecodedDocumentCallback` is installed. Lets
  operators staging the rollout see the no-callback gap in real time
  without depending on `log::debug!` (disabled at default log levels).

- **Reserved-marker rejection** in `on_ble_data_received_anonymous`
  and identified-peer siblings: first-byte ∈ 0xB7..=0xBF is silent-
  dropped *before* fall-through to `merge_document`. Required to
  prevent the GCounter-pollution hazard documented in ADR-059
  Amendment 1 §"Backwards compatibility… 2" (a 0xB6+ payload whose
  bytes 8..11 fall ≤ `MAX_COUNTER_ENTRIES = 256` would otherwise be
  accepted as a valid-but-garbage `PeatDocument`; the (0,0)+None+None
  position broadcast is the concrete reachable instance).

  **Operational mandate**: deployments must roll out a peat-btle
  version with this rejection check to every BLE peer **before** any
  peer is upgraded to a version that emits 0xB6 frames. Pre-0.3.0
  peers do not provide a guaranteed silent-drop; the rejection check
  closes the hazard once rolled out.

- **Public marker constants** (`mesh-translator` feature):
  `TRANSLATOR_FRAME_MARKER`, `TRANSLATOR_RESERVED_MARKER_START`,
  `TRANSLATOR_RESERVED_MARKER_END` for in-tree dual-emit gating logic
  and tests. **Not** for host use — outbound framing lives entirely
  inside `BleTranslator::encode_outbound` per ADR-059 Amendment 1
  §"Outbound framing".

### Fixed

- **`mesh-translator` tracks decode**: missing or unparsable
  `ctx.local_wire_id` now returns `Err` instead of silently
  substituting `peripheral_id = 0`. The previous fallback collapsed
  every unknown-source BLE track onto `ble-00000000` — invisible from
  the receiver's perspective. The Err lets the inbound bridge's
  parse-error metric surface the misconfiguration loudly.

### Wire-format coordination

- 0.3.0 is the first peat-btle release that emits and recognizes 0xB6
  translator frames. Mixed-fleet rollouts see the dual-emit transition
  the amendment specifies: hosts that publish via the
  `mesh-translator` path emit *both* the 0xB6 translator frame and the
  matching legacy `PeatDocument`-shaped frame for the same logical
  doc, and Automerge merges the second receiver-side ingest as a
  no-op. Pre-0.3.0 peers see legacy frames as before; post-0.3.0
  peers see both. Operator-facing dual-emit toggle (`enable_legacy_emit`)
  ships in a follow-up release once the orchestrator-in-loop test
  fixtures land.


## [0.1.0] - 2026-02-12

### Added
- **Android**: High-priority sync mode for time-critical state updates
- **Android**: WearOS reliability improvements (reconnect on re-discovery, stale peer cleanup, address rotation handling, auto-reconnect with exponential backoff)
- **feather-sense**: Peat GATT server for WearTAK connectivity on nRF52840
- **feather-sense**: Pure Rust BLE advertising with nrf-sdc
- **feather-sense**: probe-rs build support and BLE target in Makefile
- **macOS**: GATT client with bidirectional sync
- BLE connection management infrastructure with UniFFI exports
- Range test node for WearTAK field testing
- Adafruit Feather Sense (nRF52840) example support
- CannedMessage CRDT document sync via delta mechanism
- Extensible document registry for CRDT sync
- nRF52840 sensor beacon example

### Fixed
- **Android**: Reconnect to peers when re-discovered after disconnect
- **Android**: Clean up stale connected peers and improve display names
- **Android**: Peer tracking with name-based deduplication
- **Android**: Handle WearOS BLE address rotation for stable mesh state
- **Android**: Prevent GATT server registration leaks
- **Android**: Prevent unwanted BLE pairing requests on Samsung devices
- **Apple**: CoreBluetooth memory management segfault
- **Linux**: BLE connection handling and advertisement improvements
- Relay deduplication and callsign cache
- CI checkout and format/clippy/macOS example gating fixes

### Changed
- **feather-sense**: Corrected GPIO P1 base address for raw_blinky

## [0.1.0-rc.30] - 2026-01-29

### Added
- `MembershipToken`: Lightweight authority-signed tokens (128 bytes) for constrained devices
  - Binds public_key to callsign with mesh_id and expiration
  - Wire format: `[pubkey:32][mesh_id:4][callsign:12][issued:8][expires:8][sig:64]`
- `SignedPayload`: Transport-agnostic signing utilities for BLE and WiFi/IP
  - Wire format: `[marker:1][payload:N][signature:64]`
- `IdentityRegistry` extended with callsign support:
  - `register_member()` validates and stores membership tokens
  - `get_callsign()` / `find_by_callsign()` for lookups
  - Persistence format v2 with backwards compatibility

## [0.1.0-rc.29] - 2026-01-27

### Added
- Functional BLE loopback test automation (kitlab ↔ Pi)
- `ble_responder` and `ble_test_client` example binaries
- Delta sync auto-registration on peer connect/disconnect
- `decrypt_only()` API for transport-only decryption

### Fixed
- UInt formatting in Android logs (`.toLong()` for `String.format`)
- Added `updatePeripheralState()` convenience method (ATAK team contribution)

### Changed
- CI now runs functional BLE test via SSH to Raspberry Pi

## [0.1.0-rc.28] - 2026-01-26

### Changed
- **BREAKING**: Migrated Android bindings from manual JNI to UniFFI
  - All Rust types now accessed via `uniffi.peat_btle` package
  - PeatMesh construction uses `newFromGenesis()` or `newWithPeripheral()` factory methods
  - Method parameters now use Kotlin unsigned types (UInt, ULong, UByte)
  - BLE callback timestamps require `.toULong()` conversion

### Added
- UniFFI bindings module (`src/uniffi_bindings.rs`) with full PeatMesh API
- Generated Kotlin bindings (`android/src/main/kotlin/uniffi/peat_btle/peat_btle.kt`)
- Chat methods exposed via UniFFI: `sendChat`, `sendChatReply`, `chatCount`, `getAllChatMessages`, `getChatMessagesSince`
- `updatePeripheralState` method for efficient encrypted state updates
- `deriveNodeIdFromMac` standalone function
- Peer state types via UniFFI: `ConnectionState`, `PeerConnectionState`, `StateCountSummary`, `FullStateCountSummary`, `IndirectPeer`, `ViaPeerRoute`
- Peer state methods: `getPeerConnectionState`, `getDegradedPeers`, `getLostPeers`, `getConnectionStateCounts`, `getIndirectPeers`, `getFullStateCounts`

### Fixed
- Removed JNI native method calls from callback proxies (`ScanCallbackProxy`, `GattCallbackProxy`, `AdvertiseCallbackProxy`) that caused `UnsatisfiedLinkError` at runtime

### Removed
- Manual JNI bridge (`src/platform/android/jni_bridge.rs`)
- JNI-based Kotlin files: `PeatMesh.kt`, `DeviceIdentity.kt`, `MeshGenesis.kt`, `IdentityAttestation.kt`
- JNI native method declarations from callback proxy classes
- `System.loadLibrary("peat_btle")` calls (UniFFI/JNA handles library loading automatically)
- `jni` and `ndk` crate dependencies

### Migration Guide
See `docs/UNIFFI_MIGRATION.md` for Android integration updates.

## [0.0.12] - 2026-01-19

### Added
- ADR-002: Mesh Provisioning and Node Onboarding architecture
- Codex.md with Radicle workflow guide and CI documentation
- Security implementation roadmap with 8 tracked issues

### Fixed
- Clippy warnings in linux adapter (derivable_impls, type_complexity, manual_strip, clone_on_copy)
- Range contains checks in CRDT validation
- linux_scanner example (rand dependency, callback types)
- Code formatting across multiple files

## [0.0.11] - 2026-01-18

### Added
- ChatCRDT for persistent mesh chat with reply threading
- Chat message deduplication in Android bindings
- MTU overflow protection (CHAT_SYNC_LIMIT=8)
- Profiling stress test example

### Fixed
- BLE MTU overflow crash with large chat histories
- Duplicate chat notifications in Android

## [0.1.0] - 2024-12-01

### Added
- Initial release
- Linux platform support with BlueZ/bluer
- Core BLE transport architecture
- BleAdapter trait for platform abstraction
- Peat beacon format and discovery protocol
- GATT service definition (0xF47A)
- Power profile management (Aggressive, Balanced, LowPower, UltraLow)
- Lightweight CRDT implementations (GCounter, LWWRegister, ORSet)
- Hierarchical mesh topology support
- Delta-based synchronization protocol
- Coded PHY support for extended range (BLE 5.0+)

### Platform Support
- Linux (BlueZ 5.48+) - Complete
- macOS (CoreBluetooth) - Complete
- iOS (CoreBluetooth) - Complete
- ESP32 (NimBLE) - Complete
- Android - In Progress
- Windows - Planned
