A federated edge-to-cloud telemetry pipeline for scientific sensor networks.
Tideway ingests high-rate sensor data at edge nodes, compresses and processes it locally, and forwards it across federated zones over delay-tolerant transport — treating disconnected operation as a first-class case, not an error path. It targets deployments where bandwidth is scarce, links are intermittent, and the centralised-cloud model fails for cost, latency, or reachability reasons. When a zone is partitioned it keeps ingesting, querying, and detecting anomalies locally; when the link returns, nodes reconcile and transfer only the difference.
Research-stage, single-maintainer project, built in Rust. The design is recorded as 17 Architecture Decision Records, each grounded in a 23-paper reference corpus. See
docs/for the full design and.claude/ROADMAP.mdfor milestone status.
Six functional domains across three tiers (edge node → zone gateway → cloud):
sensors
│ high-rate ingest, reorder, compress, anomaly-score, durably store
▼
┌──────────────┐ delay-tolerant ┌──────────────┐ windowed-aggregate ┌──────────────┐
│ edge node │ bundles (BPv7-ish) │ zone gateway │ query push-down │ cloud │
│ tideway-edge │ ───────────────────▶ │tideway-gateway│ ◀────────────────────── │ tideway-cloud │
└──────────────┘ reconcile-on- └──────────────┘ signed responses └──────────────┘
reconnect (only the difference crosses the wire)
- A — Storage & ingest: append-only event log on a pure-Rust LSM, content-ID dedup (exactly-once at rest), time-indexed CQRS read model, at-rest compression.
- B — Sync & reconciliation: rateless-IBLT set reconciliation over content IDs; CRDTs for control-plane state.
- C — Edge anomaly detection: streaming robust statistics (median/MAD); quality annotations ride alongside telemetry, never mutating measurements.
- D — Federated learning: FedDrift-lineage drift detection + weighted-average model aggregation, tolerant of node dropouts.
- E — Federated query: windowed-aggregate push-down from cloud to zones; partition returns explicitly-labelled partial results.
- F — Observability: Tideway-native span/metric records that buffer on partition and reconstruct/export (OTLP-shaped) on reconnect.
Full detail: docs/02-architecture-overview.md.
Requires the pinned Rust toolchain (rust-toolchain.toml, 1.94.1). Build & test:
cargo test --workspace # ~710 tests
cargo build --workspace --binsEdge tier — ingest a synthetic multi-sensor stream offline, then query it:
cargo run -p tideway-edge -- --sources 3 --per-source 4000 --window 32 --batch 256Re-running with the same --path/--seed re-ingests identical records; the
content-addressed log deduplicates them (exactly-once at rest).
Zone gateway — two nodes converge after a partition, transferring only the difference (two terminals):
# print each node's key for out-of-band exchange, then:
tideway-gateway --listen 127.0.0.1:9000 --path /tmp/a --seed <hexA> --peer-key <keyB> --shared 1000 --unique 100
tideway-gateway --connect 127.0.0.1:9000 --path /tmp/b --seed <hexB> --peer-key <keyA> --shared 1000 --unique 100Cloud query — fan a windowed aggregate out to zone gateways and merge:
# zone side (one per gateway):
tideway-gateway --serve-query 127.0.0.1:9001 --path /tmp/a --seed <hexA> --peer-key <cloudKey> --shared 50
# cloud side:
tideway-cloud --seed <cloudHex> --zone <gwKeyA>:127.0.0.1:9001 --from 0 --to 1000 --aggregate sumA zone that doesn't answer is labelled a partial result (with last-known freshness) rather than failing the query.
This is the portfolio-facing path through the system. It uses the three public binaries for the tier demos, then points to focused E2E tests for the library-first domains.
cargo test --workspace
cargo build --workspace --binsThe workspace uses the pinned toolchain in rust-toolchain.toml. CI also runs
cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings,
cargo deny check, cargo audit, and cargo llvm-cov --workspace --summary-only.
Run one edge node with a deterministic multi-sensor stream:
cargo run --quiet -p tideway-edge -- \
--path /tmp/tideway-demo/edge \
--sources 3 \
--per-source 4000 \
--window 32 \
--batch 256What this demonstrates:
- ingest runs fully offline;
- out-of-window late samples are accounted for and merged back, not dropped;
- records are durably committed to the append-only store;
- the time-indexed read model answers local windowed queries.
Typical output:
tideway-edge: ingested 12000 samples offline into /tmp/tideway-demo/edge
in=12000 emitted=9953 late=2047 records=40
query_all: 40 records across 3 sources
recent window [3599, 3999] (start-in-window):
sensor-2: 1 records
sensor-1: 0 records
sensor-0: 1 records
Re-run the same command against the same --path and --seed to exercise
content-addressed deduplication: identical records are not appended twice.
Use two terminals. These fixed seeds are demo-only; they intentionally put secrets on the command line.
# Terminal A
cargo run --quiet -p tideway-gateway -- \
--listen 127.0.0.1:19000 \
--path /tmp/tideway-demo/gw-a \
--seed a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 \
--peer-key 55154f42065ea5a1bea05463826be2684eb92df92c100027aabaae57ca554207 \
--eid 1 \
--peer-eid 2 \
--shared 1000 \
--unique 100# Terminal B
cargo run --quiet -p tideway-gateway -- \
--connect 127.0.0.1:19000 \
--path /tmp/tideway-demo/gw-b \
--seed b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2 \
--peer-key bc7cbcb5636375fa1d82434d466724d92377f53b980695dd49d26d0ce12205a5 \
--eid 2 \
--peer-eid 1 \
--shared 1000 \
--unique 100What this demonstrates:
- each gateway starts with overlapping but diverged record sets;
- the TCP DTN path reconciles content IDs after reconnect;
- only the difference crosses the link;
- each side converges to the union.
Typical output on each side:
local records: 2
--- converge complete ---
records sent: 1
records received: 1
of which duplicate: 0
coded symbols: 128
bytes on wire: 11658
local records: 2 → 3
Run the same two commands again. The second pass should be a fixpoint:
records sent: 0
records received: 0
local records: 3 → 3
Start two query-serving zones:
# Terminal A
cargo run --quiet -p tideway-gateway -- \
--serve-query 127.0.0.1:19001 \
--path /tmp/tideway-demo/zone-a \
--seed a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1 \
--peer-key 814722de71c5b14e748dff322ae7f7c415cee558766495292cd6c4c0a6a9df28 \
--eid 1 \
--shared 64 \
--unique 32# Terminal B
cargo run --quiet -p tideway-gateway -- \
--serve-query 127.0.0.1:19002 \
--path /tmp/tideway-demo/zone-b \
--seed b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2 \
--peer-key 814722de71c5b14e748dff322ae7f7c415cee558766495292cd6c4c0a6a9df28 \
--eid 2 \
--shared 64 \
--unique 32Query both zones from the cloud coordinator:
cargo run --quiet -p tideway-cloud -- \
--seed eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee \
--zone bc7cbcb5636375fa1d82434d466724d92377f53b980695dd49d26d0ce12205a5:127.0.0.1:19001 \
--zone 55154f42065ea5a1bea05463826be2684eb92df92c100027aabaae57ca554207:127.0.0.1:19002 \
--from 0 \
--to 1000 \
--aggregate sumTypical output:
query 1: SumI64
aggregate: SumI64(5024)
completeness: complete (2 zone(s) answered)
Stop Terminal B, then run a partitioned query with B's endpoint unavailable:
cargo run --quiet -p tideway-cloud -- \
--seed eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee \
--zone bc7cbcb5636375fa1d82434d466724d92377f53b980695dd49d26d0ce12205a5:127.0.0.1:19001 \
--zone 55154f42065ea5a1bea05463826be2684eb92df92c100027aabaae57ca554207:127.0.0.1:19999 \
--from 0 \
--to 1000 \
--aggregate sum \
--query-id 2Typical output:
query 2: SumI64
aggregate: SumI64(2512)
completeness: PARTIAL — 1 of 2 zone(s) missing:
zone 0d5e8c45d185f6812a60d63c6b9177615d883ea2bb06055532c0ae05ffb5be75 missing; never answered
That is the intended disconnected-query behavior: the coordinator returns the reachable aggregate immediately and labels the missing zone instead of hiding the gap or failing the whole query.
These domains are implemented as libraries and integrated through E2E tests rather than separate daemons:
cargo test -p tideway-quality --test quality_e2e -- --nocapture
cargo test -p tideway-fl --test fl_round_e2e -- --nocapture
cargo test -p tideway-obs --test observability_e2e -- --nocapture
cargo test -p tideway-cloud --test daemon_query_e2e -- --nocapture
cargo test -p tideway-gateway --test gateway_e2e -- converge_after_partition_transfers_only_the_difference --nocaptureThose tests demonstrate anomaly annotations that preserve telemetry, federated learning rounds that tolerate zone dropout and drift reassignment, observability records that buffer through partition and export OTLP-shaped JSON, cloud query complete/partial behavior over TCP, and gateway reconciliation over the DTN path.
Optional performance evidence:
cargo test -p tideway-store --test throughput -- --ignored --nocapture
cargo test -p tideway-query --test scale -- --ignored --nocapture- ~710 tests pass; 88.73% line coverage (
cargo llvm-cov, workspace-wide). - Ingest: ~14–17M samples/s single-threaded — ~14–17× the provisional 10⁶/s target, well inside a 1–4 GiB memory envelope.
- Storage: ~29M samples/s (~557 MiB/s) writing batched record-frames, and it
recovers every
Sync'd record after a hard crash — pure-Rust LSM (fjall), no FFI. - Reconciliation: rateless IBLT transfers ≈ O(difference) bytes regardless of dataset size (~93 B per differing record at 0.1% divergence), estimate-free.
- Rust throughout, no FFI,
#![forbid(unsafe_code)]across the workspace; CI runs fmt / clippy-D warnings/ test +cargo deny+cargo audit+ coverage.
crates/ 11 domain library crates (tideway-core, -codec, -store, -ingest, -sec,
-recon, -dtn, -query, -obs, -quality, -fl)
bins/ 3 tier binaries: tideway-edge · tideway-gateway · tideway-cloud
docs/ requirements, architecture, glossary, design docs, and 17 ADRs
spikes/ throwaway decision-closing benchmark harnesses (kept as gate evidence)
Research-stage; all six domains across all three tiers are built and tested, and all 17 ADRs are Accepted. Deferred by design (and why) is tracked in the roadmap: deployment-gated tuning (no real deployment exists yet), explicitly-dropped acceleration (XDP/FPGA, contrary to the modest-hardware assumption), and hardening the cooperative-zone threat model excludes (Byzantine peers, extra transports).
- Design index & reading order:
docs/README.md - Requirements & scope:
docs/01-requirements-and-scope.md - Architecture:
docs/02-architecture-overview.md - Decisions:
docs/adr/· Roadmap:.claude/ROADMAP.md
Apache-2.0 OR MIT.