Files
swarm-house/docs/02-architecture.md
eSlider 523e15f490 docs: adopt read-only SQL-over-SSH as the peer query decision
Record the full reasoning path (GraphQL service, Arrow Flight, raw SSH,
forced-command synthesis) and specify read-only enforcement as five
fail-safe layers: key-per-operation, SELECT-only statement gate,
read-only OS user, engine hardening, resource caps. Promote 'SQL is
the contract' to a top-level design principle.
2026-07-08 13:29:41 +01:00

5.2 KiB

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).

graph TB
    subgraph drone [Single drone — Docker Compose under systemd]
        subgraph ingestion [Layer 1 — Ingestion]
            SENSORS["sensor-ingest<br/>ROS 2 topics / drivers → stream"]
            VIDEO["video-analytics<br/>YOLO-like detector, GPU"]
        end
        subgraph storage [Layer 2 — Storage and transform]
            WRITER["parquet-writer<br/>stream → current/ blocks"]
            SEALER["sealer<br/>compact closed windows"]
            DUCK["DuckDB<br/>in-process SQL over Parquet"]
            NVME[("NVMe<br/>Hive-partitioned Parquet")]
        end
        subgraph serving [Layer 3 — Serving and sync]
            HOOK["event hook<br/>fires on new derived data"]
            PUB["state publisher<br/>pub/sub broadcast"]
            MINIO["MinIO<br/>derived datasets bucket"]
            QAPI["query API<br/>peer data requests"]
        end
    end

    SENSORS --> WRITER
    VIDEO -->|detections| WRITER
    WRITER --> NVME
    SEALER --> NVME
    DUCK --> NVME
    WRITER -->|derived rows| HOOK
    HOOK --> PUB
    HOOK --> MINIO
    QAPI --> DUCK
    PUB -.->|mesh| PEERS["peer drones"]
    MINIO -.->|replication| PEERS
    QAPI -.->|on demand| PEERS

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). 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).
  • 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, MinIO upload, or a local mission-logic callback. Nothing polls.
  • The state publisher broadcasts compact position/attitude/detection payloads over the mesh pub/sub (transport analysis in 04 — Swarm sync).
  • Bulk sync pulls sealed derived partitions from peers over persistent SSH (rsync delta transfer); MinIO remains optional where an S3 API is wanted.
  • Peer queries are read-only DuckDB SQL over SSH forced commands — SELECT-only gate, read-only OS user, columnar responses (the design decision and its reasoning are in 04).

Communication planes

Three isolated planes with different lifecycles:

graph LR
    subgraph planes [Communication planes]
        C2["C2 control plane<br/>mission intent, narrow, in production"]
        DATA["swarm data plane<br/>state broadcast + sync, in production"]
        DEV["dev/debug plane<br/>live telemetry, bench only"]
    end
    OPERATOR["supervising operator<br/>(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.