9.6 KiB
9.6 KiB
Changelog
All notable changes to the tines crates. Versions follow Semantic Versioning; before 1.0, a minor bump may break the API.
1.0.0 — 2026-10-21
tines-core 1.0: the wire format is frozen (wire v1), the public API is
narrowed and documented, and the core hardening from the 1.0 spec is in.
This release ships the fleet-deployment milestone (handoff receipts) and the
remaining fleet fixes (F1–F5, F6, F8, F9, F13, F17; see THREAT_MODEL.md).
Breaking (from 0.3.0)
- Wire v1 (tines-core). Every frame now leads with a
WIRE_VERSIONbyte (4). 0.x-shaped frames are rejected asWireError::VersionMismatch. Destreplacesto/lifetime_ms(tines-core). Addressed delivery now takesdest: Option<Dest>whereDest { to, lifetime_ms }; the lifetime is excluded from observation identity.- Signature slot (tines-core).
Observationcarries a reservedsig: Option<Signature>(algorithm + up to 64 bytes). 1.0 enforces the wire shape but does not sign or verify (1.1). - Public API narrowing (tines-core). Nine items moved
pub(crate), eleven types are#[non_exhaustive](build via constructors, not struct literals).FrameandWireErrorare now public: the wire format is the 1.x compatibility contract. Storetrait settled (tines-core).flush(&mut self, now)andis_emptyjoined the trait;Node::store_mutis gone. A new opt-inWallClockStoretrait carries the trusted-time operations (set_wall_clock,peer_time_estimate) that onlyLogStoreimplements.NodeEvent+ counted drops (tines-core). Exchange outcomes surface asNodeEvent(Delivered,Dropped { reason, count }) throughNode::drain_events;DropReasonisVersionMismatch,Malformed, orIdentityChanged.Node::pollis the only pump (tines-core). The separate custody re-offer pass folded intopoll.- Log format v2 (tines-store).
FORMAT_VERSIONbumped for the newObservationencoding; opening an older log is a hard error that names the fix (delete the log). - Quarantine + wall-clock fold (tines-store).
LogStore::open(path, now)takes no wall time; observations held across a power-off are quarantined until the host feeds trusted time viaset_wall_clock. - Trusted wall time in the hosts (tines). A
trusted_clock = "system"config flag or a peer-derived estimate (smallestobserved_at + age_ms) anchors the wall clock once; observations carryobserved_at: Noneuntil then. - MSRV (tines-core).
rust-version = "1.82"; CI builds and tests tines-core alone on it.
Added
- Handoff receipts (tines-core). The new
Ackframe (the lastFramevariant) carries up toACK_IDS_CAP(90) ids. Acks are sent as the exchange runs and are accepted only from a peer the id was sent to during the current contact.- An ack for an addressed observation moves custody: the sender releases
its copy (
Store::release) and never offers it again. - An ack for a broadcast is confidence only. It surfaces as a
NodeEvent<A>::Delivered { peer, id }from the newNode::drain_events. - Broadcast confirmations and counted drops both come through
Node::drain_events; a confirmed broadcast arrives asNodeEvent::Delivered, and a frame the peer dropped arrives asNodeEvent::Dropped { reason, count }(§3).
- An ack for an addressed observation moves custody: the sender releases
its copy (
- Counted drop reporting (tines-core). A frame the peer could not use is no
dropped silently: the exchange counts it under a
DropReason(VersionMismatchorMalformed) keyed by the peer's transport address and reports the count throughNode::drain_eventsasNodeEvent::Dropped. Counters are held in a bounded table (consts::DROP_TABLE_CAP) with a single overflow slot for the case where the table is full, so an adversarial peer can't spin forever — it just reportsaddr: None. Expired addressed observations dropped by policy are not counted. - New constants:
ACK_IDS_CAP,EVENT_QUEUE_CAP,BCAST_SENT_CAP,ACK_BUFFER_CAP. wire::frame_sender(bytes) -> Option<NodeId>: a frame's claimed sender, for transports that bind a connection to an identity.Node::store_mut, so a host can call backend methods such asLogStore::flush.- New host crates:
tines-store:LogStore, a persistent append-only backend.tines-bt: a BLE transport over BlueZ.tines: a standalone service with Unix-socket JSON IPC.tines-ros2: a ROS2 node; rclrs is behind theros2feature.
Changed
- Addressed delivery is a custody chain. It used to be epidemic forwarding. A message has one custodian at a time, which hands it on and releases it when the next holder acks. A lost ack can leave two custodians for a while; a message is never dropped by the protocol.
- Bounce records carry a direction, so only records of an actual send validate an ack.
LogStorelog format v1 (incompatible; there's no migration, so delete old logs):- The file starts with a
"TINESLOG"header and a format version. - Each record has a sync marker, a length and a CRC-32C.
- Ids are recomputed on load, not stored.
- The file starts with a
LogStorepersists a boot counter, andfirst_seq()returnsboot_count << 32. Both hosts seedNode::newfrom it, and thefirst_seqconfig field is gone.LogStore::releasewrites aReleaserecord immediately.flushwrites a clock marker, so ages carry across a restart.LogStore::opentakes an exclusive lock and creates the file0600.- tines-bt rewrite:
- L2CAP SeqPacket channels, one frame per packet.
- Every node advertises and scans; the smaller
NodeIddials and the larger accepts. - Connections are bound to the advertised
NodeId, and frames whose claimed sender differs are dropped. - Connections emit
ContactandLost.
Fixed
- A receiver whose store returned
Err(for exampleFull) still acked, so the sender released custody of a message nobody kept. Now nothing is acked unless it was stored, addressed or broadcast (F1). - The standalone service never polled its
Node, so it never exchanged anything. It now runs the exchange on a poll loop (F2). - tines-bt had no listener, lost frame boundaries on a stream socket, and stamped every frame with the configured peer (F3).
- Sequence numbers repeated after every restart, and released flags were never persisted (F4).
- One corrupt record in the middle of the log truncated everything after it. Recovery now skips bad records and truncates only a torn tail, with an fsync of the file and its directory (F5).
- The service's tick was wall-clock seconds. Both hosts now use one monotonic millisecond clock (F17).
Known issues
- The service talks to exactly one configured
peer. The multi-peer allowlist the fleet-fixes spec describes isn't wired up (F3). - Neither host flushes on a timer, and tines-ros2 never flushes or trims (F18).
0.3.0 — 2026-10-18
Breaking
Storenow requiresrelease(&mut self, id)andis_released(&self, id) -> bool. Implementations ofStore— including test doubles and future backends — must provide them.
Added
Store::releasemarks an observation as sent, so it is no longer offered for exchange.MemStoretracks a per-entryreleasedflag, and itsfor_each_addressed_idskips released ids as an optimization (the exchange predicate remains the load-bearing check). Releasing an id that is not stored is a no-op.
0.2.1 — 2026-10-18
Changed
- Package metadata only:
repository,documentation,readme,keywordsandcategories, shared through[workspace.package]. The README now appears on crates.io. - Each crate now ships the Apache-2.0
LICENSEtext.
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 lastnext_frameafter a failed send.Node::pollcalls it, so aBusyorUnreachableframe is retried instead of being lost.Store::for_each_addressed_id, with a default that visits every id.MemStoreoverrides it, so custody no longer reads every broadcast observation on each poll.Bloom::fold, andBloom::from_partsnow accepts any power-of-two length up toSUMMARY_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_flightis nowOption<InFlight>(wasOption<(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
allocfeature; doc warnings fail the build;--lockedeverywhere; toolchain pinned to 1.99.0; actions pinned to commit SHAs.
0.1.0
Initial release: tines-core and tines-sim.