Files
tines/CHANGELOG.md
T
2026-10-22 08:44:09 -07:00

9.6 KiB
Raw Blame History

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_VERSION byte (4). 0.x-shaped frames are rejected as WireError::VersionMismatch.
  • Dest replaces to/lifetime_ms (tines-core). Addressed delivery now takes dest: Option<Dest> where Dest { to, lifetime_ms }; the lifetime is excluded from observation identity.
  • Signature slot (tines-core). Observation carries a reserved sig: 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). Frame and WireError are now public: the wire format is the 1.x compatibility contract.
  • Store trait settled (tines-core). flush(&mut self, now) and is_empty joined the trait; Node::store_mut is gone. A new opt-in WallClockStore trait carries the trusted-time operations (set_wall_clock, peer_time_estimate) that only LogStore implements.
  • NodeEvent + counted drops (tines-core). Exchange outcomes surface as NodeEvent (Delivered, Dropped { reason, count }) through Node::drain_events; DropReason is VersionMismatch, Malformed, or IdentityChanged.
  • Node::poll is the only pump (tines-core). The separate custody re-offer pass folded into poll.
  • Log format v2 (tines-store). FORMAT_VERSION bumped for the new Observation encoding; 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 via set_wall_clock.
  • Trusted wall time in the hosts (tines). A trusted_clock = "system" config flag or a peer-derived estimate (smallest observed_at + age_ms) anchors the wall clock once; observations carry observed_at: None until then.
  • MSRV (tines-core). rust-version = "1.82"; CI builds and tests tines-core alone on it.

Added

  • Handoff receipts (tines-core). The new Ack frame (the last Frame variant) carries up to ACK_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 new Node::drain_events.
    • Broadcast confirmations and counted drops both come through Node::drain_events; a confirmed broadcast arrives as NodeEvent::Delivered, and a frame the peer dropped arrives as NodeEvent::Dropped { reason, count } (§3).
  • Counted drop reporting (tines-core). A frame the peer could not use is no dropped silently: the exchange counts it under a DropReason (VersionMismatch or Malformed) keyed by the peer's transport address and reports the count through Node::drain_events as NodeEvent::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 reports addr: 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 as LogStore::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 the ros2 feature.

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.
  • LogStore log 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.
  • LogStore persists a boot counter, and first_seq() returns boot_count << 32. Both hosts seed Node::new from it, and the first_seq config field is gone.
  • LogStore::release writes a Release record immediately. flush writes a clock marker, so ages carry across a restart.
  • LogStore::open takes an exclusive lock and creates the file 0600.
  • tines-bt rewrite:
    • L2CAP SeqPacket channels, one frame per packet.
    • Every node advertises and scans; the smaller NodeId dials and the larger accepts.
    • Connections are bound to the advertised NodeId, and frames whose claimed sender differs are dropped.
    • Connections emit Contact and Lost.

Fixed

  • A receiver whose store returned Err (for example Full) 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

  • Store now requires release(&mut self, id) and is_released(&self, id) -> bool. Implementations of Store — including test doubles and future backends — must provide them.

Added

  • Store::release marks an observation as sent, so it is no longer offered for exchange. MemStore tracks a per-entry released flag, and its for_each_addressed_id skips 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, keywords and categories, shared through [workspace.package]. The README now appears on crates.io.
  • Each crate now ships the Apache-2.0 LICENSE text.

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.