docs: fleet console, bus transport, widened IPC

This commit is contained in:
2026-10-25 01:24:38 -07:00
parent 2a917088f7
commit 6c6729324e
4 changed files with 146 additions and 13 deletions
+44 -5
View File
@@ -190,9 +190,9 @@ fixed in core; see `THREAT_MODEL.md`.
## Hosts 0.4.0 — Unreleased ## Hosts 0.4.0 — Unreleased
`tines`, `tines-store`, `tines-bt`, `tines-ros2` and `tines-sim` move to 0.4.0 `tines`, `tines-store`, `tines-bt`, `tines-bus`, `tines-ros2` and
and depend on `tines-core = "1"`. Like tines-core 1.0.0, nothing here is `tines-sim` move to 0.4.0 and depend on `tines-core = "1"`. Like tines-core
tagged or published yet. 1.0.0, nothing here is tagged or published yet.
The 0.3.0 release shipped only `tines-core` and `tines-sim`. The four other The 0.3.0 release shipped only `tines-core` and `tines-sim`. The four other
host crates were added afterwards, during the fleet milestone, and carried the host crates were added afterwards, during the fleet milestone, and carried the
@@ -339,8 +339,9 @@ milestone's host work (F2–F5, F17).
### Known issues ### Known issues
- The service talks to exactly one configured `peer`. The multi-peer - The service talks to exactly one configured `peer` on the BT transport.
allowlist the fleet-fixes spec describes isn't wired up (F3). The multi-peer allowlist the fleet-fixes spec describes isn't wired up
(F3). The new bus transport drops the limit, as a test medium only.
- Neither host flushes on a timer or at shutdown (F18). - Neither host flushes on a timer or at shutdown (F18).
- The service trims and flushes only after 64 poll rounds that each stored - The service trims and flushes only after 64 poll rounds that each stored
something new. something new.
@@ -348,6 +349,44 @@ milestone's host work (F2–F5, F17).
- Peer time is unauthenticated until 1.1. An adopted time isn't corrected - Peer time is unauthenticated until 1.1. An adopted time isn't corrected
while the host runs (F19). while the host runs (F19).
### Fleet console (unreleased, on `feat/fleet-console`)
The desktop console for driving a containerized fleet, and the host pieces
it rides on:
- **`tines-bus`** (new workspace crate): a host-only transport over a
hub-and-spoke Unix-socket daemon (`tines-busd`). `Addr = NodeId`, like
the BT transport. A client registers its `NodeId`, then sends data
envelopes carrying a 32-byte routing header (`from`, `to`); the daemon
drops any envelope whose `from` isn't that socket's registered id or
whose `to` isn't registered, and forwards the frame verbatim. Envelopes
are length-capped. A bus peer loses the BT transport's single-peer
limit — any registered peer is reachable. It is a test medium for
simulated fleets, not a deployment transport.
- **Transport choice in the service** (tines): config `transport` is
`"bt"` (default) or `"bus"`, with `bus_socket` naming the daemon's
socket. `HostTransport` wraps both, so the service runs BLE or the bus
from the same config file.
- **Widened IPC** (tines): `Observe` gains `to` / `lifetime_ms` for
addressed observations (both set together or neither); new `List`
(observation ids, with a `truncated` flag at the cap) and `Events`
(contacts, drops and drop counts) requests. The new request kinds keep
the existing error posture: a bad request gets an `Error` response,
never a panic or a partial write.
- **Decision C** (core): the additive `NodeEvent::Contact` /
`NodeEvent::Lost` variants, so hosts see peers come and go. `Contact`
fires only for a newly added peer; idle eviction stays silent. The only
sanctioned core change for the console.
- **Fleet console** (`tines-gui`, standalone crate outside the workspace):
a launcher (create a fleet, add nodes, start/stop containers, open node
windows) and a per-container node window (status strip, observation
send, belief read, events feed) driving the service IPC. BT mode runs a
privileged container with the host adapter passed through; sim mode runs
N containers over the bus daemon. Fleet state lives under
`$XDG_STATE_HOME/tines-console/fleets/<fleet>/`, created `0700`.
- **Runtime image** (`Dockerfile.tines`): the two-stage container for the
service plus `tines-busd`; the GUI drives docker as a CLI subprocess.
## 0.3.0 — 2026-10-18 ## 0.3.0 — 2026-10-18
### Breaking ### Breaking
+51 -4
View File
@@ -560,8 +560,9 @@ A robot runs either the service or the ROS2 node, not both: each owns its
- Connections produce `Contact` and `Lost` events. See - Connections produce `Contact` and `Lost` events. See
[`tines-bt/README.md`](tines-bt/README.md). [`tines-bt/README.md`](tines-bt/README.md).
- **`tines`**: the standalone service, for robots without ROS2. - **`tines`**: the standalone service, for robots without ROS2.
- It runs the exchange on a poll loop and answers `Observe`, `Belief`, - It runs the exchange on a poll loop and answers `Observe` (broadcast or
`Events` and `Status` requests on a Unix socket. addressed), `Belief`, `Events`, `List` and `Status` requests on a Unix
socket.
- It's configured by a JSON file (below). Library users call - It's configured by a JSON file (below). Library users call
`tines::serve(node, socket_path, clock, poll_interval_ms, `tines::serve(node, socket_path, clock, poll_interval_ms,
peer_time_window_ms)`. peer_time_window_ms)`.
@@ -578,9 +579,11 @@ typo can't silently fall back to a default.
| Key | Default | Meaning | | Key | Default | Meaning |
|---------------------------|--------------|---------| |---------------------------|--------------|---------|
| `node_id` | required | this robot's `NodeId`, 32 hex chars | | `node_id` | required | this robot's `NodeId`, 32 hex chars |
| `peer` | required | the one peer's `NodeId`, 32 hex chars | | `peer` | bt only | the one peer's `NodeId`, 32 hex chars |
| `log_path` | required | the `LogStore` log file | | `log_path` | required | the `LogStore` log file |
| `socket_path` | required | the IPC Unix socket | | `socket_path` | required | the IPC Unix socket |
| `transport` | `"bt"` | `"bt"` for BLE, or `"bus"` for the host bus transport |
| `bus_socket` | bus only | the bus daemon's Unix socket |
| `poll_interval_ms` | 20 | exchange poll cadence | | `poll_interval_ms` | 20 | exchange poll cadence |
| `trusted_clock` | unset | `"system"` to trust the system clock; the only accepted value | | `trusted_clock` | unset | `"system"` to trust the system clock; the only accepted value |
| `peer_time_window_ms` | 10000 | how long to collect peer time estimates before adopting the latest | | `peer_time_window_ms` | 10000 | how long to collect peer time estimates before adopting the latest |
@@ -635,7 +638,8 @@ Peer time is unauthenticated until 1.1, and an adopted time isn't corrected
while the host runs; see F19 in [`THREAT_MODEL.md`](THREAT_MODEL.md). while the host runs; see F19 in [`THREAT_MODEL.md`](THREAT_MODEL.md).
**Current limits** (tracked in [`THREAT_MODEL.md`](THREAT_MODEL.md)): **Current limits** (tracked in [`THREAT_MODEL.md`](THREAT_MODEL.md)):
- The service talks to one configured `peer`. - On the BT transport the service talks to one configured `peer`; the bus
transport (simulated fleets) reaches any peer registered with the daemon.
- The hosts don't flush on a timer or at shutdown. The service flushes after - The hosts don't flush on a timer or at shutdown. The service flushes after
64 productive poll rounds, and both hosts flush when they adopt trusted 64 productive poll rounds, and both hosts flush when they adopt trusted
time. time.
@@ -644,6 +648,44 @@ while the host runs; see F19 in [`THREAT_MODEL.md`](THREAT_MODEL.md).
The two-device BLE check in `tines-bt/README.md` hasn't been recorded yet. The two-device BLE check in `tines-bt/README.md` hasn't been recorded yet.
## The fleet console (lab desktop)
`tines-gui/` (a standalone crate outside the workspace, like `fuzz/`) is
the desktop console: an orchestrator that runs a whole fleet in containers.
It is never a node host — the GUI embeds no `Node` and no radio runs in the
GUI process; every node lives in a container the GUI drives over the
service IPC.
- **Launcher** (`tines-gui` with no arguments): create a fleet, add nodes,
start/stop containers, open a node window.
- **Node window** (`tines-gui --node <dir> <container-id> bt|sim`): one
window per container — a status strip, observation send (broadcast or
addressed, with a lifetime), belief read, and a live events feed
(contacts, drops). It speaks the service IPC; nothing else.
- **Modes.** BT mode runs one privileged container with the host's
Bluetooth adapter passed through, so that node reaches real robots. Sim
mode runs N containers over the bus transport (below), with no radio.
- Fleet state lives under
`$XDG_STATE_HOME/tines-console/fleets/<fleet>/`, created `0700`: one
directory per node with its config, log and IPC socket.
### The bus transport
`tines-bus` (a workspace member) is a host-only transport for simulated
fleets: a hub-and-spoke daemon (`tines-busd`) that containers dial over
Unix sockets. Its `Addr` is the `NodeId`, like the BT transport.
- A client registers its `NodeId` with the daemon, then sends data
envelopes carrying a 32-byte routing header (`from`, `to`). The daemon
drops any envelope whose `from` isn't that socket's registered id or
whose `to` isn't registered, and forwards the frame verbatim — it never
decodes frame contents.
- Envelopes are length-capped; an oversized one is a protocol violation.
- The bus is a **test medium**, not a deployment transport: no encryption,
no authentication beyond the registration check. The service picks it
with `transport = "bus"` plus `bus_socket`, which also drops the BT
transport's single-peer limit — any registered peer is reachable.
## Crate layout ## Crate layout
``` ```
@@ -658,9 +700,14 @@ tines/
tines-store/ std: LogStore, the persistent append-only backend. tines-store/ std: LogStore, the persistent append-only backend.
tines-bt/ std: the BLE transport (BlueZ via bluer). tines-bt/ std: the BLE transport (BlueZ via bluer).
tines/ std: the standalone service with Unix-socket IPC. tines/ std: the standalone service with Unix-socket IPC.
tines-bus/ std: the host bus transport — a hub-and-spoke Unix-socket
daemon (`tines-busd`) for simulated fleets.
tines-ros2/ std: the ROS2 node (rclrs behind the `ros2` feature). tines-ros2/ std: the ROS2 node (rclrs behind the `ros2` feature).
``` ```
`tines-gui/` sits outside the workspace (like `fuzz/`): the desktop fleet
console. Build it with `cargo test --manifest-path tines-gui/Cargo.toml`.
The public constants, in `tines_core::consts`, are the wire constants. They The public constants, in `tines_core::consts`, are the wire constants. They
are frozen for wire v1: changing one is a `WIRE_VERSION` bump. are frozen for wire v1: changing one is a `WIRE_VERSION` bump.
+48 -1
View File
@@ -3,7 +3,11 @@
- **Assessed:** `master` at `3b1a644` (tines-core / tines-store / tines-bt / - **Assessed:** `master` at `3b1a644` (tines-core / tines-store / tines-bt /
tines-ros2 / tines 0.3.0, fleet-deployment milestone mostly landed) tines-ros2 / tines 0.3.0, fleet-deployment milestone mostly landed)
- **Date:** 2026-10-19 - **Date:** 2026-10-19
- **Updated:** 2026-10-21 at `411daa0` (`feat/tines-core-1-0`), after the - **Updated:** 2026-10-25, at `2a917088` (`feat/fleet-console`), after the
fleet console work: F3's single-peer limit is now BT-only (the new bus
transport drops it, as a test medium); F11 gains the widened IPC; F20
(bus daemon) added. Earlier updates: 2026-10-21 at
`411daa0` (`feat/tines-core-1-0`), after the
tines-core 1.0 review fixes landed. Earlier update: 2026-10-20 at tines-core 1.0 review fixes landed. Earlier update: 2026-10-20 at
`89041ea`, after the fleet fixes. The findings keep their original text; `89041ea`, after the fleet fixes. The findings keep their original text;
each fixed one ends with a **Fixed** or **Partly fixed** line. §6 adds each fixed one ends with a **Fixed** or **Partly fixed** line. §6 adds
@@ -240,6 +244,11 @@ for a poll loop, `docs/superpowers/plans/2026-10-18-tines-fleet-deployment.md:20
- No BLE security level is requested (unchanged; 1.1). - No BLE security level is requested (unchanged; 1.1).
- No two-radio check of the SeqPacket path is recorded yet. - No two-radio check of the SeqPacket path is recorded yet.
tines-bt/README.md gives the procedure but no result. tines-bt/README.md gives the procedure but no result.
- *At `2a917088` (fleet console):* the new bus transport (tines-bus)
drops the single-peer limit — bus peers reach any id registered with
the daemon. That is a **test medium** for simulated fleets over host
Unix sockets, not a radio deployment; on real radios the single-peer
limit stands until the allowlist work lands.
**Later (1.1):** authenticate the link (spec §2b). Advertised NodeIds are **Later (1.1):** authenticate the link (spec §2b). Advertised NodeIds are
unauthenticated, so the binding only becomes meaningful once there's a unauthenticated, so the binding only becomes meaningful once there's a
@@ -420,6 +429,11 @@ re-queues what was already sent.
an idle client blocks everyone. An `accept` error ends the daemon. An an idle client blocks everyone. An `accept` error ends the daemon. An
oversized frame (> 1 MiB) gets an error reply without consuming its body, oversized frame (> 1 MiB) gets an error reply without consuming its body,
which desynchronizes the stream (contrary to the module docs). which desynchronizes the stream (contrary to the module docs).
- *At `2a917088` (fleet console):* the IPC widened — `Observe` gained
`to`/`lifetime_ms`, and `List` and `Events` were added — without
changing this posture. The new request kinds validate their inputs, cap
their outputs (`List` truncates with a flag), and answer a bad request
with an `Error` response, never a panic or a partial write.
- **Fix:** - **Fix:**
- Create the socket in a `0700` runtime directory with mode `0600` (or a - Create the socket in a `0700` runtime directory with mode `0600` (or a
group-restricted mode). group-restricted mode).
@@ -626,6 +640,38 @@ authenticates that estimate.
`time-sync.target`). Signing and verification in 1.1 close the forged `time-sync.target`). Signing and verification in 1.1 close the forged
case. case.
### F20 — The bus daemon is a host attack surface (Medium, A3)
*Added 2026-10-25, with the fleet console's bus transport (tines-bus).*
The bus daemon (`tines-busd`) stands in for the radio in simulated fleets:
N service containers dial one daemon over host Unix sockets and exchange
tines frames through it. It is host-local infrastructure and was built
with the same distrust of input as the IPC server:
- **Registration is explicit.** A client registers its `NodeId` before
sending data, and the daemon drops (and counts) any envelope whose
`from` isn't that socket's registered id, or whose `to` isn't a
registered id.
- **Envelopes are length-capped** at `MAX_FRAME + 32` header bytes
(`MAX_PAYLOAD`); an oversized length header is a protocol violation that
closes the connection.
- **The daemon decodes nothing beyond the 32-byte routing header.** Frame
contents are forwarded verbatim, so a malformed frame is the receiving
node's problem, exactly as on the radio.
- **The fleet directory is `0700`** (tines-gui `config::fleet_dir` and
per-node dirs), so node sockets, logs and configs under it are private
to the owning user.
- **Effect (A3):** another local user can neither read nor join a fleet
(directory permissions), nor spoof frames through the daemon (the
registration check). Root can do anything on the host, as always. The
daemon is bench equipment: it carries no encryption and authenticates
nothing, and must not become a deployment transport.
- **Open:** the daemon rate-limits nothing. A buggy or hostile local
client can flood envelopes at the fan-out rate. Accepted for the test
medium; F10-style quotas would be the fix if the daemon ever left the
bench.
## 7. Threats by STRIDE, mapped to findings ## 7. Threats by STRIDE, mapped to findings
| | Threat | Status today | Findings | Planned mitigation | | | Threat | Status today | Findings | Planned mitigation |
@@ -708,6 +754,7 @@ agreed fuzz budget hasn't been run and recorded yet.
| F10–F12 | Host hardening, before a robot leaves the lab | `docs/superpowers/specs/2026-10-19-tines-host-hardening.md` | Open | | F10–F12 | Host hardening, before a robot leaves the lab | `docs/superpowers/specs/2026-10-19-tines-host-hardening.md` | Open |
| F6, F8, F9, F13 | tines-core 1.0 | `docs/superpowers/specs/2026-10-18-tines-1-0-design.md` §10 | Fixed (§1); F13's Reassembler displacement accepted | | F6, F8, F9, F13 | tines-core 1.0 | `docs/superpowers/specs/2026-10-18-tines-1-0-design.md` §10 | Fixed (§1); F13's Reassembler displacement accepted |
| F19 | Hosts and tines-store (decision J, plausibility bounds); authentication in 1.1 | 1.0 spec §9, decision J; §2a | Partly mitigated (§1) | | F19 | Hosts and tines-store (decision J, plausibility bounds); authentication in 1.1 | 1.0 spec §9, decision J; §2a | Partly mitigated (§1) |
| F20 | Fleet console: the bus daemon's posture (registration, caps, 0700 dirs) is built in; per-peer rate limits are open | fleet console design | Mitigated by design (§6) |
| F7, F14, §8 | 1.1 (threat model, link authentication, signing) | 1.0 spec §2a, §2b | Open | | F7, F14, §8 | 1.1 (threat model, link authentication, signing) | 1.0 spec §2a, §2b | Open |
| F15, F16 | Accepted for now | — | — | | F15, F16 | Accepted for now | — | — |
+3 -3
View File
@@ -1,9 +1,9 @@
//! The tines bus transport: exchange wire frames between nodes over a host //! The tines bus transport: exchange wire frames between nodes over a host
//! IPC bus (Unix sockets), identity-preserving (`Addr = NodeId`). //! IPC bus (Unix sockets), identity-preserving (`Addr = NodeId`).
//! //!
//! [`BusTransport`] is the tines [`Transport`] implementation; [`daemon`] is //! [`BusTransport`] is the tines [`tines_core::Transport`] implementation;
//! the hub that routes frames between all connected nodes. The two sides of //! [`daemon`] is the hub that routes frames between all connected nodes. The
//! a fleet talk through one shared daemon socket. //! two sides of a fleet talk through one shared daemon socket.
#![deny(missing_docs)] #![deny(missing_docs)]