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
`tines`, `tines-store`, `tines-bt`, `tines-ros2` and `tines-sim` move to 0.4.0
and depend on `tines-core = "1"`. Like tines-core 1.0.0, nothing here is
tagged or published yet.
`tines`, `tines-store`, `tines-bt`, `tines-bus`, `tines-ros2` and
`tines-sim` move to 0.4.0 and depend on `tines-core = "1"`. Like tines-core
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
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
- The service talks to exactly one configured `peer`. The multi-peer
allowlist the fleet-fixes spec describes isn't wired up (F3).
- The service talks to exactly one configured `peer` on the BT transport.
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).
- The service trims and flushes only after 64 poll rounds that each stored
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
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
### 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
[`tines-bt/README.md`](tines-bt/README.md).
- **`tines`**: the standalone service, for robots without ROS2.
- It runs the exchange on a poll loop and answers `Observe`, `Belief`,
`Events` and `Status` requests on a Unix socket.
- It runs the exchange on a poll loop and answers `Observe` (broadcast or
addressed), `Belief`, `Events`, `List` and `Status` requests on a Unix
socket.
- It's configured by a JSON file (below). Library users call
`tines::serve(node, socket_path, clock, poll_interval_ms,
peer_time_window_ms)`.
@@ -578,9 +579,11 @@ typo can't silently fall back to a default.
| Key | Default | Meaning |
|---------------------------|--------------|---------|
| `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 |
| `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 |
| `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 |
@@ -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).
**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
64 productive poll rounds, and both hosts flush when they adopt trusted
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 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
```
@@ -658,9 +700,14 @@ tines/
tines-store/ std: LogStore, the persistent append-only backend.
tines-bt/ std: the BLE transport (BlueZ via bluer).
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-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
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 /
tines-ros2 / tines 0.3.0, fleet-deployment milestone mostly landed)
- **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
`89041ea`, after the fleet fixes. The findings keep their original text;
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 two-radio check of the SeqPacket path is recorded yet.
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
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
oversized frame (> 1 MiB) gets an error reply without consuming its body,
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:**
- Create the socket in a `0700` runtime directory with mode `0600` (or a
group-restricted mode).
@@ -626,6 +640,38 @@ authenticates that estimate.
`time-sync.target`). Signing and verification in 1.1 close the forged
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
| | 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 |
| 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) |
| 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 |
| 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
//! IPC bus (Unix sockets), identity-preserving (`Addr = NodeId`).
//!
//! [`BusTransport`] is the tines [`Transport`] implementation; [`daemon`] is
//! the hub that routes frames between all connected nodes. The two sides of
//! a fleet talk through one shared daemon socket.
//! [`BusTransport`] is the tines [`tines_core::Transport`] implementation;
//! [`daemon`] is the hub that routes frames between all connected nodes. The
//! two sides of a fleet talk through one shared daemon socket.
#![deny(missing_docs)]