Design: settle reviewer questions (NodeId, clock, Addr, convergence, v1 scope)

This commit is contained in:
2026-10-16 14:50:39 -07:00
parent f73f9247ed
commit 76f5a6bc77
@@ -111,10 +111,21 @@ Decisions:
- `id` is a hash over `(observer, time, topic, payload)`. Two robots that - `id` is a hash over `(observer, time, topic, payload)`. Two robots that
observe the same thing produce two observations; de-duplication is observe the same thing produce two observations; de-duplication is
by identity, not by meaning. Union is therefore idempotent and by identity, not by meaning. Union is therefore idempotent and
order-independent. order-independent. An observation is an *event*, not a fact: the same
- `time` is the observer's local clock. `tines` does not attempt clock robot seeing the same chair twice is two observations. Nothing in the
sync. Cross-observer recency comparison is a memory-layer policy design requires an ID to be reproducible across reboots.
question and must tolerate skew. - `observer` (`NodeId`) is a 128-bit value supplied by the application
when the node is constructed. It must be stable across reboots; the
core does not provision or persist it. Adapters may derive it from a
hardware unique ID (MCU chip UID, Jetson serial) or read it from
config. *(My call.)*
- `time` is the observer's local clock, as `u64` milliseconds since the
Unix epoch, plus a `ClockQuality` tag: `Synced` (GPS/NTP), `FreeRunning`
(set once, drifting), or `BootRelative` (no RTC; ms since boot). The
tag travels with the observation so the memory layer can decide how
much to trust cross-observer recency. `tines` does not attempt clock
sync. *(My call: the tag is the minimum needed to make a
`BootRelative` MCU observation honest rather than silently wrong.)*
- `topic` is a short byte string. Its structure (hierarchy, separators) - `topic` is a short byte string. Its structure (hierarchy, separators)
is application-defined but `tines` supports prefix matching for is application-defined but `tines` supports prefix matching for
trimming and queries. trimming and queries.
@@ -205,6 +216,10 @@ Decisions:
no async in the core.)* no async in the core.)*
- Contact detection is the transport's job (beacon heard, link up, sim - Contact detection is the transport's job (beacon heard, link up, sim
says "in range"). The core reacts to `Contact` and `Lost`. says "in range"). The core reacts to `Contact` and `Lost`.
- `Addr` is a routing handle only; the core never maps it to a `NodeId`.
Every protocol frame carries the sender's `NodeId` in its header, and
per-peer exchange state is keyed by `NodeId`. An address that changes
mid-contact (DHCP, roaming) therefore costs nothing. *(My call.)*
- Adapters planned: UDP broadcast/unicast over ad-hoc Wi-Fi (Jetson), - Adapters planned: UDP broadcast/unicast over ad-hoc Wi-Fi (Jetson),
serial-attached LoRa/packet radio, in-process simulator. serial-attached LoRa/packet radio, in-process simulator.
@@ -239,6 +254,13 @@ Decisions:
discarded, never stored. discarded, never stored.
- Rate: the core emits at most one frame per `poll` so the application - Rate: the core emits at most one frame per `poll` so the application
controls pacing and duty cycle. controls pacing and duty cycle.
- **Convergence is best-effort per contact and eventual over repeated
contacts.** A single contact makes no completeness promise; it may be
cut off at any frame. The guarantee is: given enough cumulative
contact time and non-permanent loss, two nodes' stores converge
(modulo trimming). No "final authoritative diff" or end-of-contact
handshake exists. This follows directly from sharing being
non-load-bearing. *(My call, consistent with the stated intent.)*
#### Simulator #### Simulator
@@ -259,8 +281,8 @@ The simulator is also the test harness for the protocol.
``` ```
tines/ tines/
tines-core/ no_std; Observation, Store trait, Transport trait, tines-core/ no_std; Observation, Store trait, Transport trait,
BeliefPolicy trait, exchange state machine, BeliefPolicy trait, exchange state machine, Node
in-memory Store, reference policies facade, in-memory Store, reference policies
tines-sim/ std; simulator, contact traces, property tests tines-sim/ std; simulator, contact traces, property tests
tines-udp/ std; UDP adapter (Jetson) tines-udp/ std; UDP adapter (Jetson)
tines-serial/ no_std-compatible; framed serial adapter tines-serial/ no_std-compatible; framed serial adapter
@@ -268,6 +290,25 @@ tines/
tines-cli/ std; inspect a store, replay a trace, run a node tines-cli/ std; inspect a store, replay a trace, run a node
``` ```
The `Node` facade — the thing that owns a `Store`, a `Transport`, and the
exchange state machine, and exposes `observe()`, `poll()`, and
`belief()` — is core, not application glue. An application on any
target should need only: construct a `Node` with its `NodeId`, store,
and transport; call `poll()` in its loop; call `observe()` when it sees
something.
## v1 scope (my call)
Ships: `tines-core`, `tines-sim`, `tines-udp`, `tines-sqlite`,
`tines-cli`. The simulator's convergence property tests pass; two Jetsons
(or two Linux processes) on one Wi-Fi segment exchange observations via
UDP and persist them in SQLite.
Deferred: `tines-serial` and the MCU flash store. The `no_std` guarantee
is enforced in v1 by building `tines-core` for a `thumbv7em-none-eabihf`
target in CI with the in-memory store, so the MCU path is not an
afterthought even though no MCU adapter ships.
## Left to the builder ## Left to the builder
- Hash function for `ObservationId` (should be cheap on MCU; BLAKE2s or - Hash function for `ObservationId` (should be cheap on MCU; BLAKE2s or