Files
tines/AGENTS.md
T
kyleandClaude Opus 5.5 0cb1ad5360 docs: sweep README, CHANGELOG, THREAT_MODEL and AGENTS after the 1.0 review fixes
- CHANGELOG: tines-core 1.0.0 and the hosts' 0.4.0 are separate,
  unreleased sections. Every core API break is listed (the nine
  pub(crate) items, the private modules and the frame_sender path, the
  local constants per decision K, non_exhaustive types, store_mut). The
  new names (WallClockStore, Node::set_wall_clock, peer_time_estimate,
  flush, IdentityChanged) are called out. The host section covers
  tines::trust, the config changes, decision J, snapshots, and the
  review fixes.
- THREAT_MODEL: F6, F8, F9 and F13 are fixed with their commits. F18 is
  partly fixed: the store side and the flush on first trusted time are
  done, but timed and shutdown flushes are still open. F19 now records
  decision J and the plausibility bounds, and what remains. §9, §10 and
  §11 are updated to match.
- README: Dest and sig in the observation table; flush and is_empty in
  the store table; only the public wire constants are listed; a new
  "Trusted time" section with the config keys and the systemd ordering;
  the MSRV and fuzz CI jobs; frame_sender's new path.
- AGENTS: 1.0 is implemented but not yet published. The constants list
  shows the pub(crate) split, PEER_IDLE_MS, MIN_MTU's role and where
  MAX_FRAGMENTS lives. Notes the hidden conformance module.
- tines-bt README: frame_sender's path, and its own MAX_PEERS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:44:09 -07:00

8.4 KiB
Raw Permalink Blame History

AGENTS.md — tines

Instructions for AI agents working on tines: opportunistic shared memory for mostly-offline robots. Read this before touching code.

What tines is

Small robots collect facts about the world while mostly offline. When two of them happen to meet in range, they swap what they know so each one's picture of the world improves. No robot depends on receiving anything, but there is no reason not to share.

tines is the library that does this: a store of timestamped-by-age observations, a belief layer derived from them, and a delay-tolerant exchange protocol over any datagram link. Read the full intent, goals, anti-goals, and architecture in the spec first:

  • Core design (authoritative): docs/superpowers/specs/2026-10-16-tines-design.md
  • Addressed delivery (authoritative): docs/superpowers/specs/2026-10-18-tines-addressed-delivery.md
  • Fleet deployment v1 (authoritative): docs/superpowers/specs/2026-10-18-tines-fleet-deployment-design.md — supersedes the addressed-delivery spec's epidemic forwarding (custody chain with handoff receipts).
  • Fleet fixes (done): docs/superpowers/specs/2026-10-19-tines-fleet-fixes.md — security-assessment findings F1–F5 and F17.
  • tines-core 1.0 (implemented on feat/tines-core-1-0; publish pending): docs/superpowers/specs/2026-10-18-tines-1-0-design.md — wire version, public API, signature slot, core hardening (§10).
  • Host hardening (next, hosts): docs/superpowers/specs/2026-10-19-tines-host-hardening.md — findings F10–F12.
  • Implementation plans: docs/superpowers/plans/

The plans argue from the specs. Where a plan is silent, the spec rules. Do not edit the specs or the plans on your own initiative — they are design history, not source. Change one only to record something Kyle has discussed and agreed, and record it as a dated decision in that document. Anything else that needs changing in a spec goes to Kyle first. README.md is the user-facing overview and should track the code. THREAT_MODEL.md is the living security assessment: when a change fixes or changes a finding, update its status there (§1 and §10) with the commit. CHANGELOG.md records each release.

Architecture (three layers)

 memory — "my priors": beliefs per topic, derived from storage under a policy
 storage — append-only log of raw observations; local trimming policy
 comms   — contact-driven exchange of observations; transport trait + adapters

Only observations cross the wire. Memory is a pure function of storage plus a policy and is never transmitted. See the spec's "Architecture" section for the Observation, Store, BeliefPolicy, Transport, and exchange protocol details.

Workflow

Changes are TDD: write the failing test → see it fail → implement → see it pass → commit.

  1. Keep the public API small. New public names in tines-core (types, trait methods, functions) need a reason; call them out when you add them.
  2. Before committing, run the full gate, which is what CI runs: sh scripts/ci.sh (fmt, clippy and rustdoc with warnings as errors, tests with and without alloc, and the thumbv7em-none-eabihf build).
  3. Use conventional commit messages (fix(core): …, ci: …, docs: …).

The Node facade is core, not application glue: an application on any target should only need to construct a Node with its NodeId, store, and transport, call poll() in its loop, and observe() when it sees something.

Hard constraints (non-negotiable in tines-core)

  • tines-core is #![no_std]; alloc is a cargo feature (default on). With --no-default-features it must build for thumbv7em-none-eabihf. Only MemStore may require alloc.
  • No async, no threads, no I/O, and no panic on untrusted input in core. Every frame from the wire is untrusted: malformed bytes are dropped, never unwrapped.
  • Core never maps a transport Addr to a NodeId; every frame carries the sender's NodeId.
  • Identity hash covers (observer, seq, topic, payload, to) only. age_ms, observed_at and lifetime_ms are excluded.
  • Addressed observations (to = Some) never appear in a summary Bloom filter, and an addressed observation past its lifetime_ms is never stored or forwarded.
  • first_seq passed to Node::new must not repeat across reboots (boot_count << 32, with a persisted boot counter; LogStore::first_seq provides it). A repeated seq after reboot collides in the ID hash and must dedupe silently (insert returns Ok(false)).
  • Receipts: a receive the store rejects (Err, e.g. Full) is never acked, addressed or broadcast; an addressed duplicate whose local copy is released is never acked; an Ack is accepted only from a peer the id was sent to this contact.
  • NodeId = [u8; 16], ObservationId = [u8; 16], Tick = u64 (local monotonic ms, supplied on every call).
  • 1.0 trait extension rule: any method added to Store, Transport or BeliefPolicy after 1.0 must have a default whose behaviour is safe — it may lose performance or telemetry but never data or custody. A method that can't have such a default is a 2.0 change (the rule release violated in 0.3.0).

Review Focus (each covered by a test; keep it that way)

  1. Reused seq after reboot → store dedupes, insert returns Ok(false).
  2. Oversized topic/payload at observe() → Err(TooLarge), never truncate/panic.
  3. Garbage frames → dropped, no panic, no store change.
  4. Frame from an address with no prior Contact → treated as implicit contact.
  5. Tick going backwards → age saturates at stored value, never underflow.
  6. Every emitted frame fits the transport's mtu(), including summaries.
  7. Store-full receive → no Ack; custody stays with the sender (unit test and the full-store sim property).
  8. Released addressed duplicate → no Ack.

Compile-time constants (tines-core/src/consts.rs)

Split into wire (frozen for wire v1 — changing one changes how a v1 peer decodes or interprets a frame) and local (never crosses the wire; may change in a minor release):

  • Wire (public): WIRE_VERSION=4, MAX_TOPIC=64, MAX_PAYLOAD=1024, MAX_FRAME=1500, SUMMARY_BYTES=512, ACK_IDS_CAP=90, MAX_SIG=64, BLOOM_K=3. Also wire, but private: MAX_FRAGMENTS=64, which lives in tines-core/src/frag.rs, not consts.rs.
  • Local (pub(crate), spec decision K: defaults that a later 1.x makes configurable): MAX_PEERS=8, SUMMARY_INTERVAL_MS=2000, REASSEMBLY_TIMEOUT_MS=5000, SEND_QUEUE_CAP=256, EVENT_QUEUE_CAP=64, DROP_TABLE_CAP=32, BCAST_SENT_CAP=256, ACK_BUFFER_CAP=256, PEER_IDLE_MS=30_000, and MIN_MTU=23. MIN_MTU is the lower bound of the fragmentation MTU sweep in tests (BLE ATT's default), not the smallest usable MTU; Transport::mtu's rustdoc states those floors (85 bytes for every valid observation).

Match the exact values above. A crate outside core that needs a matching number keeps its own constant (as tines-bt does for MAX_PEERS and MIN_MTU).

The conformance feature also compiles a #[doc(hidden)] tines_core::conformance module: byte-level frame encoders and decoders, and mirrors of the local limits, for the fuzz harness and tests outside core. It isn't supported public API and isn't covered by semver. Don't build on it outside the workspace and fuzz/.

Crate layout

tines/
  tines-core/   no_std core: Observation, Store/Transport/BeliefPolicy traits,
                exchange state machine, Node facade, in-memory store, reference
                policies
  tines-sim/    std: simulator, contact traces, convergence, relay and custody
                property tests
  tines-store/  std: LogStore, the persistent append-only backend
  tines-bt/     std: BLE transport over BlueZ (bluer)
  tines/        std: standalone service, Unix-socket JSON IPC
  tines-ros2/   std: ROS2 node; rclrs behind the `ros2` feature

Only tines-core is headed for 1.0; the host crates stay 0.x (1.0 spec, D4). They're std-only and may use threads and I/O, but they're still the trust boundary for radio, IPC and disk input (see THREAT_MODEL.md).

tines-ros2 with --features ros2 is outside the default gate: check it with the ROS2 Docker job (Dockerfile.ros2, scripts/check-ros2.sh).

Not started

tines-udp, tines-sqlite, tines-cli, and tines-serial (from the core spec's crate layout) belong to a future plan. Don't start them unless asked.