Skip to content

Commit 895f795

Browse files
authored
Merge pull request #365 from Offline-Protocol/docs/restructure-spec-threat-model-adrs
docs: replace the CLAUDE.md memory dump with a spec, threat model, ADRs and bridge contracts
2 parents f566af7 + 77f30c7 commit 895f795

63 files changed

Lines changed: 7190 additions & 1502 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 64 additions & 1419 deletions
Large diffs are not rendered by default.

CLAUDE.md

Lines changed: 120 additions & 63 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,20 @@ See the [docs/](docs/) directory for detailed guides:
213213
- [Telemetry](docs/telemetry.md)
214214
- [iOS Integration](docs/ios-integration.md) / [Android Integration](docs/android-integration.md)
215215

216+
Reference material for anyone implementing against the protocol or changing its
217+
behaviour:
218+
219+
- [Protocol Specification](docs/spec/README.md), the wire and behaviour contract,
220+
independent of this implementation
221+
- [Threat Model](docs/security/threat-model.md), including the residual risks
222+
stated plainly
223+
- [State Machines](docs/state-machines/README.md) for delivery, retries,
224+
sessions, groups, and transports
225+
- [Architecture Decision Records](docs/adr/README.md), why the non-obvious
226+
choices are what they are
227+
- [Bridge Contracts](docs/bridges/README.md) for Swift, Kotlin, Python, and
228+
TypeScript
229+
216230
## Development
217231

218232
```bash

bindings/react-native/android/src/main/java/com/offlineprotocol/MeshWakePolicy.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ object MeshWakePolicy {
116116
* A drift here fails silently in the worst way: React Native logs "No task
117117
* registered for key" to the device log and resolves nothing, the app sees
118118
* an opt-in that does nothing, and both sides still compile. Pinned by
119-
* `react_native_mesh_wake_task_key_matches_native` in the uniffi crate.
119+
* `react_native_mesh_wake_wiring_is_present` in the uniffi crate.
120120
*/
121121
const val TASK_KEY = "OfflineProtocolMeshWake"
122122

bindings/react-native/src/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export const ONE_SHOT_EVENT_TYPES = [
6767
* silently and in the worst possible way — React Native logs "No task
6868
* registered for key" to the device log, the app sees an opt-in that does
6969
* nothing, and both sides still compile — so it is pinned by a Rust guard
70-
* (`react_native_mesh_wake_task_key_matches_native` in
70+
* (`react_native_mesh_wake_wiring_is_present` in
7171
* `crates/offline-protocol-uniffi`).
7272
*/
7373
export const MESH_WAKE_TASK_KEY = 'OfflineProtocolMeshWake';

crates/offline-protocol-core/src/address.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,18 @@ pub enum AddressError {
9393
/// The two orders differ: the bech32 charset (`qpzry9x8gf2tvdw0s3jn54khce6mua7l`)
9494
/// is not monotonic in ASCII — value 4 renders as `y` (0x79) and value 5 as
9595
/// `9` (0x39) — and a string comparison would also weigh the checksum
96-
/// characters. Protocol tiebreakers (both-create session ownership, leave
97-
/// election, admin auto-promotion, fork leader) must therefore compare
98-
/// `Address` values, never their `Display` output.
96+
/// characters.
97+
///
98+
/// **Which order a tiebreaker uses is fixed per site, and the sites disagree.**
99+
/// Both-create session ownership compares `Address` values (hash bytes); group
100+
/// leave election, admin auto-promotion and fork leader election sort rendered
101+
/// strings. Each converges on its own, because every peer running that
102+
/// tiebreaker sorts the same way.
103+
///
104+
/// Do not "harmonize" one site onto the other order. Peers that changed and
105+
/// peers that did not would then elect different winners from identical input,
106+
/// with no way to detect the disagreement locally. Prefer hash-byte order for
107+
/// anything new. See `docs/adr/0003-self-certifying-addresses.md`.
99108
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
100109
pub struct Address {
101110
hash: [u8; Address::HASH_LEN],

crates/offline-protocol-router/src/dors.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -868,7 +868,7 @@ impl TransportSelector {
868868
//
869869
// The Internet baseline intentionally exceeds the 0–100 range of
870870
// offline transports so that the gap comfortably exceeds the default
871-
// switch hysteresis (10). Increasing hysteresis beyond ~20 may
871+
// switch hysteresis (15.0). Increasing hysteresis beyond ~20 may
872872
// prevent DORS from switching *to* Internet even when prefer_online
873873
// is set.
874874
//

crates/offline-protocol/src/group_mesh.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -839,7 +839,8 @@ pub(crate) struct PendingGroupMessage {
839839
pub(crate) buffered_at: Instant,
840840
/// Transport the frame arrived on, recorded so the drain can send the
841841
/// deferred delivery ACK directly on it once the message finally decrypts
842-
/// (see the deferred-ACK atom in CLAUDE.md). `None` for the relay path
842+
/// (see the deferred-acknowledgement atom in
843+
/// `docs/state-machines/delivery-and-acks.md`). `None` for the relay path
843844
/// (the relay sender is not ACK-gated) and for transport-less test enqueue
844845
/// — in both cases the drain ACK is a correct no-op.
845846
pub(crate) received_via: Option<TransportType>,

crates/offline-protocol/src/protocol/decryption_queue.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ pub(crate) struct PendingDecryptMessage {
2222
pub(crate) sequence: u64,
2323
pub(crate) message: Message,
2424
/// Transport the frame arrived on, when the caller knew it. Recorded so the
25-
/// drain can send the deferred delivery ACK directly (see the deferred-ACK
26-
/// design in CLAUDE.md) instead of waiting for the sender's next resend to
25+
/// drain can send the deferred delivery ACK directly (see the
26+
/// deferred-acknowledgement atom in
27+
/// `docs/state-machines/delivery-and-acks.md`) instead of waiting for the
28+
/// sender's next resend to
2729
/// hit the duplicate re-ACK path. `None` when the message was enqueued from
2830
/// a context with no transport (tests, or a re-queue by the handler during a
2931
/// drain, which re-processes with no live arrival transport) — the drain

crates/offline-protocol/src/protocol/pending_queue.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ impl OfflineProtocol {
2727
/// [`Self::enqueue_pending_decryption`] with the transport the frame arrived
2828
/// on, when the caller knows it. The transport is recorded on the pending
2929
/// entry so the drain can send the deferred delivery ACK directly instead of
30-
/// relying on the sender's next resend (see the deferred-ACK design in
31-
/// CLAUDE.md).
30+
/// relying on the sender's next resend (see the deferred-acknowledgement
31+
/// atom in `docs/state-machines/delivery-and-acks.md`).
3232
pub(super) fn enqueue_pending_decryption_via(
3333
&mut self,
3434
sender: &str,

0 commit comments

Comments
 (0)