Domain · Architecture

Oya Domain — Architecture

Oya is organised as five cooperating layers.

9sections14 minread1diagrams

On this page

Architectural overview of Oya, the embodied-robotics "hive" domain: a performance-critical Rust engine, napi/wasm FFI bridges with bit-for-bit Rust↔TypeScript parity, a canonical contracts surface, a TypeScript platform layer, and fifteen Fastify control/coordination services — with an honest account of what is built versus legacy versus planned.


Oya is the Oshun platform's domain for embodied autonomous robots operating as one coordinated fleet — what the code consistently calls the embodied hive. A hive is a heterogeneous mix of physical machines: aerial drones, wheeled ground robots, stair-climbers, floor-care units (vacuum/mop), and manipulator-equipped robots, all sharing one spatial world model, one safety envelope, and one market-based task allocator. Named after the Yoruba goddess of winds, storms, and transformation, Oya spans the full stack from real-time flight control and ISO/TS 15066 human-robot safety up to fleet economics and a home-robot product surface (eldercare, pet care, security, ambient sensing).

This is a substantial, genuinely implemented system — roughly a thousand tracked source files. An earlier generation of the domain was a single large TypeScript library, @oya/core (≈101 modules of drone primitives). That library still exists, but the code now describes it as the layer the services are migrating off of: the performance-critical core has been ported to a Rust engine, and the services call that engine through FFI bridges. The previous version of this page described Oya as "entirely a library domain… no running applications or services yet, all TypeScript." That is no longer true and this rewrite corrects it: there are 15 services under apps/oya/, a 21-crate Rust workspace under libs/oya/engine/, and a canonical contracts package — all with tests.


The Shape of the System#

Oya is organised as five cooperating layers. Control and data flow downward from the product/service tier, through the typed platform libraries and the canonical contracts, across the FFI seam, into the Rust engine — and telemetry flows back up. The hard architectural rule visible throughout the code is a separation between coordination and control: the fleet orchestrator assigns work and reserves space-time but never emits a motor command; only the flight gateway and on-robot control loops energise actuators, behind a fail-closed gate.

flowchart TB subgraph svc["Service tier — apps/oya/ (15 Fastify services)"] ctl["§4.1 control/safety plane\nflight-gateway · assistant · director · perception"] coord["§4.2 coordination/application plane\nfleet-orchestrator · mission · energy · dock ·\nfloorcare · home-map · hri · manipulation ·\nambient · eldercare · pet"] end subgraph libs["TypeScript platform — libs/oya/*"] core2["fastify-core · service-lib · sdk"] plat["database · event-publisher/handlers ·\nprivacy · security · maintenance · readiness-gates"] legacy["legacy: @oya/core (101 mods) +\nsibling readiness evaluators"] end contracts["@oshun/contracts/oya\n(canonical .strict() wire schemas)"] subgraph ffi["FFI seam — libs/oya/{node-bridge,wasm-bridge}"] napi["napi-rs → .node (Node services)"] wasm["wasm-bindgen → wasm32 (operator dashboard)"] end engine["Rust engine — libs/oya/engine/ (21 crates)\noya-types · math · estimation · swarm · mavlink ·\ncontrol · navigation · perception · cinematography ·\ndsp · floorcare · energy · safety · fleet · mapping ·\nscenegraph · manipulation · locomotion · ambient · comms"] ctl --> contracts coord --> contracts ctl --> core2 coord --> core2 coord --> plat contracts --> napi core2 --> napi napi --> engine wasm --> engine contracts -. "schema parity" .- engine napi -. "differential parity test" .- legacy

Layer 1 — The Rust Engine (libs/oya/engine/)#

The engine is the authoritative, performance-critical core. It is a per-domain Cargo workspace (there is no monorepo-root Rust workspace; this mirrors libs/maya/engine-core), declared in libs/oya/engine/Cargo.toml with 21 member crates, resolver = "2", and a release profile tuned for embedded/real-time use (lto = true, codegen-units = 1). Each crate is greenfield Rust written against plain f64/SI types and serde; several carry their own benches/, tests/, and examples/.

The crates group by responsibility:

Foundations. oya-types (crates/oya-types/src/lib.rs) is the type vocabulary: branded UUID-v4 IDs (DroneId, SwarmId, MissionId, WaypointId), geodetic/quaternion/Euler math types, the three coordinate frames (GPSCoordinates, LocalCoordinates, BodyCoordinates), the flight/state/ airspace enums, and the composite records (Telemetry, Mission, FlightPlan, GeofenceZone, …). Critically, every type derives serde with #[serde(rename_all = "camelCase")] so its JSON wire format is identical to the TypeScript @oya/core/contracts payloads, and its validators (is_valid_uuid_v4, Quaternion::is_unit, is_valid range checks) mirror the Zod schemas — its test module explicitly "re-proves the TypeScript schema's accept/reject behavior." oya-math supplies the numeric toolkit: Vincenty and Haversine geodesy, coordinate transforms, kinematics, CRC, lookup tables, and a simd module.

Estimation & navigation. oya-estimation (IMU/GNSS/VIO/INS fusion, Mahony AHRS, calibration, ZUPT, event-camera, gps_security), oya-navigation (A*, minimum-snap trajectories, potential-field and sampling planners, social-aware avoidance), oya-mapping (TSDF, occupancy grids, pose-graph SLAM, relocalization, multi-floor/multi-session, fiducials), and oya-scenegraph (the hierarchical Building → Floor → Room → Object scene graph plus a freshness-tracked spatial memory with a fail-loud QueryResult).

Multi-robot coordination. oya-swarm implements reciprocal collision avoidance in depth — orca, orca3d, nh_orca, rvo, buffered Voronoi, collision-cone and collision-probability, consensus, formations, the Hungarian assignment, and task_allocation. oya-fleet holds the market allocator (CBBA auction, coalition formation, MAPF). oya-comms provides the hive's nervous system: gossip, a bandwidth-aware scheduler, descriptor exchange, access control, and PTP/IEEE-1588 timesync.

Control & protocol. oya-control runs the flight/motion stack — SO(3) attitude control, velocity/position loops, control allocation, motor-failure compensation, feedforward, and a full mission lifecycle (mission_modes, mission_rth, mission_validate). oya-mavlink is a from-scratch MAVLink2 implementation: codec, message signing (sha256/signing), the mission/ parameter/FTP/camera/gimbal sub-protocols, PX4 and ArduPilot dialects, a router, a tx_queue, and reliability/high-latency handling (with a fuzz_decode test).

Perception, cinematography, audio. oya-perception (filters, two-view and multi-view geometry, reconstruction, tracking), oya-cinematography (shot library, camera-MAPF, multicam orchestration, continuity, trajectory generation — consumed by the dashboard for path preview), and oya-dsp (AEC, beamforming, FFT, VAD, wake-word, barge-in — the audio front-end for human-robot interaction, with a real-time audio-path test).

Embodiment & home autonomy. This is what makes Oya a hive rather than a drone library: oya-manipulation (IK, grasp synthesis, grip control, whole-body control, visual servoing, real-time action chunking), oya-locomotion (locomotion-mode arbitration, morphing/reconfiguration, stair-climbing, telescoping/foldable arms, perch-latching, dock reservation, cost-of-transport, a capability passport), oya-floorcare (boustrophedon coverage-path planning over a cellular decomposition, dynamic anytime replanning, multi-robot Voronoi partition with failure-reabsorption, carpet detection, dirt-adaptive cleaning), oya-energy (SOC/SOH, battery-swap, charge-banding, dock sizing, grid scheduling, perch overwatch, wear), and oya-ambient (mmWave-radar CFAR, range-Doppler, CSI, UWB, sensor-fusion presence detection).

Safety. oya-safety (crates/oya-safety/src/lib.rs) is the functional-safety heart: ISO/TS 15066 speed-and-separation monitoring (ssm, the protective separation Sp = Sh + Sr + Ss + C + Zd + Zr), power-and-force limiting (pfl, reduced mass, transient contact energy, per-body-region limits), a control-barrier-function shield (cbf, min-norm velocity projection into the safe set), indoor flight_safety guards, edge_safety (cliff/stair/threshold drop detection), and child_safety interlocks — every one fail-closed. oya-integration-tests exercises the crates together through a pipeline.rs.


Layer 2 — FFI Bridges and Rust↔TS Parity#

The engine reaches TypeScript through two thin bridges, each exposing a typed subset of the crates (not the whole engine):

  • libs/oya/node-bridge/ — a napi-rs crate (src/lib.rs) compiled to a native .node addon for the Node services. It re-exports geodesy (vincenty_distance, haversine_distance, bearing), euler_to_quaternion, crc32, and the 2-D ORCA orca_half_plane, each delegating straight to the engine crates. Its header is explicit about intent: this is "the production Rust↔TypeScript seam: the apps/oya/* services call these native functions instead of the legacy @oya/core TypeScript."
  • libs/oya/wasm-bridge/ — a wasm-bindgen crate (src/lib.rs) compiled to wasm32-unknown-unknown for the browser operator dashboard. It exposes the same geodesy/quaternion/ORCA functions plus the plotting surface the dashboard needs: cinematography camera-path samplers (orbit_camera_path, crane_camera_path, helix_camera_path, bezier_camera_path) and oya-navigation minimum-snap evaluation, all returning flat Float64Array buffers with documented *_STRIDE column layouts to avoid per-point allocation. Absent optional angles are encoded as NaN, never a fabricated 0.0.

Because both bridges delegate to the same crates, the napi addon, the wasm module, and the legacy TS produce bit-for-bit identical values. That equivalence is enforced, not assumed: libs/oya/engine/parity/check.mjs is a differential test that runs the Rust producer (cargo run --example parity_dump) and the TypeScript producer (@oya/core via ts_dump.ts) over identical inputs and asserts every named float agrees to relative error ≤ 1e-12 (or absolute ≤ 1e-9 near zero) and every integer (CRC) matches exactly, exiting non-zero to gate CI. The bridges' own tests pin oracle values (e.g. London→Paris Vincenty ≈ 343923.12 m).


Layer 3 — Canonical Contracts (@oshun/contracts/oya)#

libs/contracts/src/oya/ is the single source of truth for everything that crosses a wire or process boundary. Its index.ts documents the surface and its purpose: schemas "coherent with the Rust engine/hive types in libs/oya/engine … so payloads validate identically on both sides." Every object schema is .strict(), so an unexpected key is a loud rejection rather than a silently dropped field. The modules:

  • primitives.tsVec3, the unit-quaternion QuaternionSchema (norm ≈ 1, tolerance 0.01, matching oya_types::Quaternion::is_unit), GpsCoordinate, the branded DroneId/MissionId/DockId, and the Capability enum (fly, roll, grasp, climb_stairs, vacuum, mop, camera) — the vocabulary the allocator arbitrates over.
  • telemetry.ts — the downlink Telemetry snapshot and the uplink ControlCommand (flight-mode + setpoint vocabulary mirroring oya-control).
  • mission.ts, dock.ts, capability.ts — missions/waypoints/flight plans; dock slot-occupancy and charge handshake (mirroring oya-energy); and the §15.7 hot-swap payload capability passport (mass/CoM/power budget + the capabilities a payload grants when bound).
  • fleet.tsFleetAgent, FleetTask, FleetState, TaskBid, Allocation, and the hard capability gate bidIsCapabilityValid(agent, bid) — the contract-layer mirror of FleetAgent::can_service in oya-fleet.
  • world-model.ts — the scene graph and the fail-loud discriminated QueryResult union: Fresh (a confident remembered value), Stale (an honest "not sure anymore, last known at tick N"), or Unknown (never observed). The store "never fabricates a confident answer for a decayed memory; that refusal is the contract."
  • sensor.ts — observations are compressed descriptors (embeddings, occupancy patches, beamformed spectra) with PTP-disciplined timestamps; there is deliberately no raw-frame field (bandwidth + privacy).
  • safety.ts — the ISO/TS 15066 SafetyEnvelope (per-body-region forceLimitsN, ssmMinSeparationM) and the EStopState (nominal/soft_stop/hard_stop).

Layer 4 — The TypeScript Platform (libs/oya/*)#

A set of focused libraries provides the service runtime on top of the contracts:

  • @oya/commonResult<T, E>, exact unit conversions, geo helpers, branded IDs, domain constants.
  • @oya/fastify-core — the shared service core: createOyaServer (typed error envelope, 404 handler, /health, request-id propagation, JSON body limit) and a testable registerGracefulShutdown. Every service is built on it.
  • @oya/service-lib — production-hardening primitives: a CircuitBreaker, retry/backoff, a config schema + loader, health aggregation, a Postgres DbConnectionManager over a mockable pool boundary, and fail-closed JWT auth middleware.
  • @oya/database — the persistence schema (§3.1): six tables — missions, flights, telemetry, maps, fleet_state, consumables — plus a query-executor and repository layer.
  • @oya/event-publisher / @oya/event-handlers — a typed event catalogue that validates each payload against its Zod schema before publish, and subscribers that reduce events into an injected in-memory store, gated by the latched per-drone e-stop.
  • @oya/privacy (§9.2) — a deterministic, fail-closed enforcement runtime: redact every unrecognised person by default, planner/perception no-record geofences via exact point-in-polygon, consent policy, and storage policy.
  • @oya/security (§9.3) — real node:crypto only: Ed25519 device identity, AES-256-GCM media encryption, HKDF-SHA256 per-home tenancy isolation, a fail-closed two-factor door-unlock gate, and cross-modal anti-spoofing.
  • @oya/maintenance (§9.4) — consumable-level accounting (filters, brushes, mop pads, charge-cycle budget) and the anti-bricking "dignity" guarantee.
  • @oya/readiness-gates (§10.2/§10.3) — ten anti-fabrication release gates (coverage-completeness, grasp-honesty, map-freshness, energy-reserve, safety-conformance, privacy-enforcement, offline-degradation, mode-economy, economic-viability, live-OTA-safety) plus post-update rollback. Each is a pure function returning { gate, status, reasons, metrics } that recomputes its invariant from first principles; runAllGates is a conjunction — the release is BLOCKED if any gate fails.
  • @oya/sdk (§3.1) — a thin, Zod-validated REST client for external consumers.

Legacy / prior generation. @oya/core (the 101-module TS drone library) and the single-file sibling readiness evaluators (@oya/flight-control, @oya/mission-planning, @oya/safety, @oya/swarm-intelligence, @oya/telemetry) remain in the tree. They are real and tested, but the bridge headers and contracts describe them as the surface being migrated off of — the Rust engine is now authoritative for the performance-critical math, with @oya/core retained as the parity oracle.


Layer 5 — The Service Tier (apps/oya/)#

Fifteen Fastify microservices, each built on createOyaServer, listening on the 401x/402x port band. They split along the coordination/control line the codebase is careful to maintain:

§4.1 — control & safety plane.

  • svc-flight-gateway — the secure-link control plane. A per-drone LinkRegistry (gateway.ts) owns one ArmingInterlock FSM and one signed MAVLink2 FrameBuilder per vehicle. Every outbound command must clear the fail-closed command-gate (command-gate.ts): a disarm (armed: false) is always allowed; an arming/motor command is accepted only when the interlock is Armed, the e-stop is nominal, and the drone is inside the geofence — with the default verdict being reject.
  • svc-assistant (§4.1) — goal decomposition, tool invocation, and free-form planning, every action mediated by a mandatory SafetyGate.
  • svc-director — DroneDirector live composition scoring, a preference-learning loop, and the Yemaya shot-list → flight bridge.
  • svc-perception — deterministic detection post-processing (NMS/thresholding/class-filter/bbox→world projection), a multi-object tracker, and map publishing.

§4.2 — coordination & application plane.

  • svc-fleet-orchestrator — the router (Open-RMF pattern, app.ts/ auction.ts). It ingests pose/battery/capability, runs a CBBA market auction (the SGA / diminishing-marginal-gain consensus-based bundle algorithm, mirroring oya-fleet::auction), and reserves shared resources in space-time via a TrafficSchedule. It "deliberately exposes no endpoint that returns a motor command, velocity setpoint, or actuator value." The auction is fully deterministic (id-ordered tie-breaks) and validates its output against AllocationSchema before returning — fail-loud if it ever produced an off-contract shape.
  • svc-mission — mission CRUD over a MissionRepository (on an injected QueryExecutor) plus energy-aware and follow-me planning.
  • svc-energy — dock registry / charge broker, fleet SOC/SOH aggregation, and return-to-dock decisions. svc-dock — the dock network, per-slot charging FSMs, perch-routine planning, and battery-swap orchestration.
  • svc-floorcare — coverage jobs, a dirt heatmap, a zone scheduler, and the omni-dock self-empty/refill cycle (over oya-floorcare).
  • svc-home-map — the scene store, spatial query, change tracking, and privacy zones (the service face of oya-scenegraph + @oya/privacy).
  • svc-hri — hive-wide turn-taking floor arbitration, mic-array speaker localization, per-resident persona/trust, and per-embodiment affect.
  • svc-manipulation — a grasp-confidence gate, an object-handoff coordinator, and per-embodiment VLA (vision-language-action) policy.
  • svc-ambient — a Matter/Thread device registry (capability-validated command dispatch) and sensing escalation.
  • svc-eldercare — multimodal fall-fusion with a trigger→confirm→escalate FSM, closed-loop medication adherence, engagement, and conversation.
  • svc-pet — weight-based pet identification, per-pet calorie budgeting, behaviour, and navigation policy.

Most services keep their state in in-memory, dependency-free, injectable collaborators so the same handler code runs under fastify.inject() in tests and in production with no test-only branch; persistence is defined in @oya/database and reached through injected executors where a service needs it (e.g. svc-mission).


Cross-Cutting Invariants and Failure Modes#

A competent engineer working on Oya must respect a handful of invariants that the architecture enforces in more than one place:

  1. The hard capability gate. A task carries the single Capability it requires; an agent may only bid on or win a task whose capability it holds. This rule lives identically in the contract (bidIsCapabilityValid), the TS auction (canService), and oya-fleet. A grasp task can never be assigned to a non-grasp robot.
  2. Coordination never actuates. The orchestrator assigns and reserves; only the flight gateway and on-robot control loops move motors, behind the fail-closed command gate. New endpoints must preserve this boundary.
  3. Fail-loud over fabrication. The world-model query union refuses to invent a confident answer for a decayed memory; the auction validates its own output against the schema; the readiness gates recompute invariants from first principles rather than trusting a claimed status. The sensor contract carries no raw frames, only descriptors.
  4. Determinism. Allocation, the gates, and the gateway decisions are pure, clock-free functions — identical inputs yield identical outputs regardless of ordering, which is what makes fastify.inject() assertions exact.
  5. Parity is enforced, not assumed. Rust ↔ wasm ↔ TS agree to 1e-12, gated by parity/check.mjs. Any change to engine math must keep the bridges and the @oya/core oracle in lock-step.
  6. Offline degradation & timing. The offline-degradation gate requires every safety-critical loop to survive cloud loss; oya-comms::timesync (PTP/IEEE-1588) makes observations comparable across the hive's clocks.

Cross-Domain Integration Boundaries and Extension Points#

Oya owns flight/actuation authorisation, mission execution, hive coordination, and robot safety policy; it consumes and exposes typed objects at its edges. The clearest live boundary is the Yemaya shot-list → flight bridge in svc-director, which turns a creative shot list into drone cinematography trajectories (built on oya-cinematography). The @oshun/contracts/oya package is the integration surface other domains import; the typed event catalogue in @oya/event-publisher is how Oya broadcasts telemetry, mission lifecycle, fleet-allocation changes, and e-stop events for cross-domain reaction.

Extension points follow the same grain: add a robot capability by extending the Capability enum (contract) and the locomotion/manipulation crates; add a new coordination behaviour as a §4.2 service on @oya/fastify-core with injectable state; add an engine capability as a crate plus a bridge function (and a parity oracle); add a release invariant as another @oya/readiness-gates evaluator folded into the runAllGates conjunction.


Status: Implemented vs Legacy vs Planned#

  • Implemented (real code, with tests): the 21-crate Rust engine, the napi/wasm bridges and the parity differential test, the @oshun/contracts/oya surface, the TypeScript platform libraries (fastify-core, service-lib, database, event-publisher/handlers, privacy, security, maintenance, readiness-gates, common, sdk), and all 15 apps/oya/ services.
  • Legacy / prior generation (retained): @oya/core (the 101-module TS drone library) and the single-file sibling readiness evaluators. These remain real and tested but are described in-code as the surface being migrated off of; the Rust engine is now authoritative and @oya/core serves as the parity oracle.
  • Honest caveats: the FFI bridges expose a typed subset of the engine (the geodesy/quaternion/ORCA/cinematography/min-snap functions read in node-bridge/wasm-bridge), not the entire crate surface — much engine logic is exercised in-crate and via the services rather than re-exported across the boundary. Service state is largely in-memory and injectable by design; @oya/database defines the persistence schema and is wired where a service needs durability. This document is grounded in reading the source, not in running the build, so it asserts no pass/coverage metrics; the parity test and the readiness gates are the mechanisms the repo uses to keep those claims honest in CI.