chore: release 0.2.0; document the store contract and on_send_error
CI / ci (push) Canceled after 0s
CI / ci (push) Canceled after 0s
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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<InFlight>` (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`.
|
||||||
Generated
+2
-2
@@ -548,7 +548,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tines-core"
|
name = "tines-core"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"blake2",
|
"blake2",
|
||||||
"heapless",
|
"heapless",
|
||||||
@@ -558,7 +558,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tines-sim"
|
name = "tines-sim"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proptest",
|
"proptest",
|
||||||
"rand 0.8.8",
|
"rand 0.8.8",
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ hear each other.
|
|||||||
- [Addressed delivery](#addressed-delivery)
|
- [Addressed delivery](#addressed-delivery)
|
||||||
- [Usage](#usage)
|
- [Usage](#usage)
|
||||||
- [Writing a transport](#writing-a-transport)
|
- [Writing a transport](#writing-a-transport)
|
||||||
|
- [Writing a store](#writing-a-store)
|
||||||
- [The simulator](#the-simulator)
|
- [The simulator](#the-simulator)
|
||||||
- [Crate layout](#crate-layout)
|
- [Crate layout](#crate-layout)
|
||||||
- [Building and CI](#building-and-ci)
|
- [Building and CI](#building-and-ci)
|
||||||
@@ -350,6 +351,35 @@ pub trait Transport {
|
|||||||
- **Report back-pressure honestly.** Return `SendError::Busy` when the radio
|
- **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
|
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.
|
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
|
## The simulator
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tines-core"
|
name = "tines-core"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "Apache-2.0"
|
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."
|
description = "Opportunistic shared memory for mostly-offline robots: a no_std core of content-addressed observations, gossip, and store-and-forward exchange."
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tines-sim"
|
name = "tines-sim"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
description = "Discrete-time simulator for tines: contact traces, convergence scenarios, and property tests."
|
description = "Discrete-time simulator for tines: contact traces, convergence scenarios, and property tests."
|
||||||
authors = ["K. Isom"]
|
authors = ["K. Isom"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tines-core = { path = "../tines-core", version = "0.1.0" }
|
tines-core = { path = "../tines-core", version = "0.2.0" }
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|||||||
Reference in New Issue
Block a user