# 02 — Architecture ## On-board: three layers, one Compose file Every drone runs the same three-layer data plane. **Docker Compose under systemd is the only on-board orchestrator.** Cluster orchestrators (Kubernetes, Swarm mode) assume a stable control plane and continuous connectivity — in an ad-hoc mesh with intermittent links that is an anti-pattern. Each drone is fully autonomous; coordination happens through data exchange, not through a shared control plane. Lightweight Kubernetes (k3s) is used on the ground only (warehouse, CI runners, simulation farm — see [06 — Environments](06-environments.md)). ```mermaid graph TB subgraph drone [Single drone — Docker Compose under systemd] subgraph ingestion [Layer 1 — Ingestion] SENSORS["sensor-ingest
ROS 2 topics / drivers → stream"] VIDEO["video-analytics
YOLO-like detector, GPU"] end subgraph storage [Layer 2 — Storage and transform] WRITER["parquet-writer
stream → current/ blocks"] SEALER["sealer
compact closed windows"] DUCK["DuckDB
in-process SQL over Parquet"] NVME[("NVMe
Hive-partitioned Parquet")] end subgraph serving [Layer 3 — Serving and sync] HOOK["event hook
fires on new derived data"] PUB["state publisher
pub/sub broadcast"] BULK["bulk sync
rsync over SSH"] QAPI["query API
SQL-over-SSH"] end end MINIO[("MinIO — ground warehouse T3
already in the stack")] SENSORS --> WRITER VIDEO -->|detections| WRITER WRITER --> NVME SEALER --> NVME DUCK --> NVME WRITER -->|derived rows| HOOK HOOK --> PUB HOOK --> BULK QAPI --> DUCK PUB -.->|mesh pose| PEERS["peer drones"] BULK -.->|sealed partitions| PEERS QAPI -.->|on demand| PEERS NVME -->|post-flight offload| MINIO ``` **MinIO** is already in the stack and is the **default ground warehouse (T3)** behind offload — keep it, do not rip it out. Default sketch: **not on the drone** (CPU/RAM budget); on-board MinIO remains an allowed exception if Compose already depends on an S3 API. **In-flight peer bulk sync is SSH/rsync** (or team-standard MinIO replication if that is already how ops works) — never the 5 Hz pose path. See [00 — Executive](00-executive.md) and [04 — Swarm sync](04-swarm-sync.md). ### Layer 1 — Ingestion - `sensor-ingest` subscribes to sensor sources (ROS 2 topics where available, raw drivers otherwise) and normalizes them into typed streams: IMU, barometer, temperature, LiDAR, RSSI, power, and so on. - `video-analytics` runs a **YOLO-like object detector** on the camera stream. The model is deliberately treated as a swappable, versioned artifact (weights differ per mission and improve over time — see [11 — CI/CD & delivery](11-cicd-delivery.md)). GPU access via nvidia-container-toolkit; ARM64 builds use the vendor's L4T-class base images. - Both services emit rows, not files. Timestamps are `int64` epoch **nanoseconds** end to end (native for ROS 2; IMU-class rates make milliseconds insufficient). ### Layer 2 — Storage and transform - `parquet-writer` appends incoming rows into small per-minute Parquet blocks under the partition's `current/` directory (details in [03 — Data platform](03-data-platform.md)). - `sealer` compacts each closed window into one ZSTD-compressed file and enforces retention quotas on the NVMe. - DuckDB runs **in-process** inside whichever service needs SQL — there is no database server to babysit. ### Layer 3 — Serving and sync - The **event hook** is the on-board "lambda": when the writer lands new *derived* rows (state, detections), it triggers registered actions — broadcast, bulk-sync hint, or a local mission-logic callback. Nothing polls. - The **state publisher** broadcasts compact position/attitude/detection payloads over the mesh pub/sub (UDP in the PoC; Zenoh proposed — [04](04-swarm-sync.md)). - **Bulk sync** pulls sealed derived partitions from peers over persistent SSH (rsync delta transfer). If the team already operates MinIO replication for that job, reuse it — do not invent a second path. Pose traffic never goes through MinIO. - **Peer queries** are read-only DuckDB SQL — HTTP explorer gate in the PoC; **SSH forced commands** proposed for flight (SELECT-only gate, read-only OS user, columnar responses — [04](04-swarm-sync.md)). - **MinIO** sits on the **ground** as T3 by default ([00 — Executive](00-executive.md)); on-board MinIO is an explicit exception, not the baseline. ## Communication planes Three isolated planes with different lifecycles: ```mermaid graph LR subgraph planes [Communication planes] C2["C2 control plane
mission intent, narrow, in production"] DATA["swarm data plane
state broadcast + sync, in production"] DEV["dev/debug plane
live telemetry, bench only"] end OPERATOR["supervising operator
(man-in-the-loop)"] --> C2 C2 --> SWARM["swarm"] SWARM <--> DATA DEV -.->|absent from production builds| SWARM ``` | Plane | Purpose | Bandwidth | In production | | --- | --- | --- | --- | | **C2 control plane** | Deliver declarative mission goals and boundaries; receive high-level status. Human supervises intent, not actions | Very low | Yes | | **Swarm data plane** | State broadcast, derived-data replication, peer queries | The scarce resource; budget per message class | Yes | | **Dev/debug plane** | Full live telemetry for bench development and HIL tests | High | **No — physically absent from production builds** | The dev plane is not "disabled by config": production images and radio profiles simply do not contain it. That removes an entire attack surface instead of guarding it. ## Out of scope (consumed as services) Flight control, trajectory planning, mission logic, and model training sit **on top of** this platform: they subscribe to the event hook, query DuckDB, and read the warehouse. Their internals do not affect the platform design beyond the data contracts defined here.