From 5c8bfe89ac4dd1ea1d00ca4b349651d5bfb4c802 Mon Sep 17 00:00:00 2001 From: "K. Isom" Date: Sun, 18 Oct 2026 05:24:30 -0700 Subject: [PATCH] chore: release 0.2.0; document the store contract and on_send_error Bump tines-core and tines-sim to 0.2.0 (Peer::in_flight changed type). Add CHANGELOG.md. README gains a "Writing a store" section and a note on calling Exchange::on_send_error when driving Exchange without Node. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 57 +++++++++++++++++++++++++++++++++++++++++++ Cargo.lock | 4 +-- README.md | 30 +++++++++++++++++++++++ tines-core/Cargo.toml | 2 +- tines-sim/Cargo.toml | 4 +-- 5 files changed, 92 insertions(+), 5 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9299f1c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,57 @@ +# Changelog + +All notable changes to the tines crates. Versions follow +[Semantic Versioning](https://semver.org/); before 1.0, a minor bump may break +the API. + +## 0.2.0 — 2026-10-18 + +Hardening from a full-codebase review. Wire-compatible with 0.1.0 except +where noted. + +### Added + +- `Exchange::on_send_error`: rolls back the last `next_frame` after a failed + send. `Node::poll` calls it, so a `Busy` or `Unreachable` frame is retried + instead of being lost. +- `Store::for_each_addressed_id`, with a default that visits every id. + `MemStore` overrides it, so custody no longer reads every broadcast + observation on each poll. +- `Bloom::fold`, and `Bloom::from_parts` now accepts any power-of-two length up + to `SUMMARY_BYTES`. Summaries are folded to fit the transport's MTU; a + 0.1.0 node rejects a folded summary, so mixed-version links only sync when + the MTU allows a full 512-byte filter. +- `InFlight`, describing a fragment stream in progress. + +### Changed + +- **Breaking:** `Peer::in_flight` is now `Option` (was + `Option<(ObservationId, u8)>`). +- Custody (addressed) and broadcast sending alternate per peer, and addressed + candidates are taken in rotation. +- Bounce suppression lasts one contact, as the addressed-delivery spec says. + +### Fixed + +- A fragment stream could panic, or send corrupt fragments, when the + observation's age changed encoded length mid-stream or the tick went + backwards. +- Summaries larger than the MTU stopped all broadcast sync on narrow links. +- A full bounce table silently stopped recording, so one addressed message + was re-sent on every poll and starved broadcast. +- An addressed send interrupted another peer's fragment stream, which was + then never finished. +- An addressed message that couldn't fit the MTU blocked every other + addressed message for that peer. +- A duplicate addressed message was forwarded back to the carrier that sent + it. + +### CI + +- Tests, clippy and docs also run without the `alloc` feature; doc warnings + fail the build; `--locked` everywhere; toolchain pinned to 1.99.0; actions + pinned to commit SHAs. + +## 0.1.0 + +Initial release: `tines-core` and `tines-sim`. diff --git a/Cargo.lock b/Cargo.lock index 29802c1..becf82a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -548,7 +548,7 @@ dependencies = [ [[package]] name = "tines-core" -version = "0.1.0" +version = "0.2.0" dependencies = [ "blake2", "heapless", @@ -558,7 +558,7 @@ dependencies = [ [[package]] name = "tines-sim" -version = "0.1.0" +version = "0.2.0" dependencies = [ "proptest", "rand 0.8.8", diff --git a/README.md b/README.md index d336fa1..0d46b9b 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ hear each other. - [Addressed delivery](#addressed-delivery) - [Usage](#usage) - [Writing a transport](#writing-a-transport) +- [Writing a store](#writing-a-store) - [The simulator](#the-simulator) - [Crate layout](#crate-layout) - [Building and CI](#building-and-ci) @@ -350,6 +351,35 @@ pub trait Transport { - **Report back-pressure honestly.** Return `SendError::Busy` when the radio can't take a frame right now; the exchange will offer the same frame again on the next poll. `TooLarge` tells the exchange to give that frame up. +- **Driving `Exchange` without `Node`?** `Exchange::next_frame` assumes the + frame it returns gets sent. If `send` fails, call + `Exchange::on_send_error(&err)` before the next `next_frame`, or the frame + is treated as delivered. `Node::poll` does this for you. + +## Writing a store + +`MemStore` (behind the default `alloc` feature) is the reference backend. A +flash or SQLite backend implements the `Store` trait: + +| Method | Contract | +|-------------------------|--------------------------------------------------------------------------| +| `insert` | `Ok(true)` if new; `Ok(false)` for a known id, leaving the existing entry (and its age) untouched; `Err(StoreError::Full)` when out of room | +| `contains` | whether an id is stored | +| `get` | the observation with `age_ms` advanced by `now − stored_at`, saturating if `now` is earlier | +| `len` | number of stored observations | +| `for_each_id` | every stored id, any order | +| `for_each_broadcast_id` | ids with `to == None`; these feed the summary | +| `for_each_addressed_id` | ids with `to == Some(_)`; scanned for custody on every poll | +| `for_each_topic` | age-adjusted observations whose topic starts with a prefix | +| `trim` | apply a `TrimPolicy` (per-topic, then age, then count); return how many were removed | + +The store must remember when each observation was inserted (`stored_at`, in +the caller's `Tick`s) so it can report current ages. + +`for_each_addressed_id` has a default implementation that visits every id, +which is correct but makes the exchange read every broadcast observation on +each poll just to find the few addressed ones. Override it if your backend can +filter by `to` cheaply, e.g. with an index or a separate table. ## The simulator diff --git a/tines-core/Cargo.toml b/tines-core/Cargo.toml index ac730d2..8033b82 100644 --- a/tines-core/Cargo.toml +++ b/tines-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tines-core" -version = "0.1.0" +version = "0.2.0" edition = "2021" license = "Apache-2.0" description = "Opportunistic shared memory for mostly-offline robots: a no_std core of content-addressed observations, gossip, and store-and-forward exchange." diff --git a/tines-sim/Cargo.toml b/tines-sim/Cargo.toml index 231d0d3..f111d4b 100644 --- a/tines-sim/Cargo.toml +++ b/tines-sim/Cargo.toml @@ -1,13 +1,13 @@ [package] name = "tines-sim" -version = "0.1.0" +version = "0.2.0" edition = "2021" license = "Apache-2.0" description = "Discrete-time simulator for tines: contact traces, convergence scenarios, and property tests." authors = ["K. Isom"] [dependencies] -tines-core = { path = "../tines-core", version = "0.1.0" } +tines-core = { path = "../tines-core", version = "0.2.0" } rand = "0.8" [dev-dependencies]