phoxal 0.13.0

Phoxal — production-oriented autonomous robot framework (engine, model, typed bus, contracts).
Documentation
//! The single API layer (D60/D61).
//!
//! API versions are **dated modules** (`phoxal::api::y2026_1`, …). Each carries a
//! zero-variant marker `enum Api {}` implementing [`ApiVersion`], the
//! version-local wire bodies (plain serde structs/enums — the wire payload has no
//! `{"v":…}` version tag, D62), their [`ContractBody`] impls, and an api-local
//! `topic` builder (`api::topic::new().drive().state()`).
//!
//! A runtime authors against exactly one of these modules
//! (`use phoxal::api::y2026_1 as api;`) and declares it on the derive
//! (`#[phoxal(api = y2026_1)]`); every handle body is bound
//! `ContractBody<Api = R::Api>`, so a body from another API version is a compile
//! error (D59/D60).

use phoxal_macros::phoxal_api_tree;

/// Marker trait identifying one dated API version (D60). The `ID` is the dated
/// module name (`"y2026_1"`); it is the canonical version identity, carried in
/// bus metadata — never in the wire body or the topic key (D62).
pub trait ApiVersion: 'static {
    /// The dated API-version identifier, e.g. `"y2026_1"`.
    const ID: &'static str;
}

/// A version-local wire body: a plain serde type bound to exactly one
/// [`ApiVersion`] and one contract family/topic (D61).
///
/// The macro-generated bodies impl this. Handles, `SetupContext` builders, and
/// the `#[derive(Runtime)]` assertions all key off `Api`/`FAMILY`/`TOPIC`.
pub trait ContractBody:
    serde::Serialize + serde::de::DeserializeOwned + Clone + Send + Sync + 'static
{
    /// The one API version this body belongs to.
    type Api: ApiVersion;
    /// Canonical contract family id, e.g. `"drive::State"`.
    const FAMILY: &'static str;
    /// Versionless topic key, e.g. `"drive/state"`.
    const TOPIC: &'static str;
}

phoxal_api_tree! {
    version y2026_1 {
        drive {
            /// Why actuation authority is in its current state.
            enum StopReason {
                NoTarget,
                EmergencyStop,
                Fault,
            }

            /// Whether the drive is actively commanding the actuators.
            enum ActuatorAuthority {
                Active,
                Stopped,
            }

            /// A requested or limited planar velocity.
            struct Target {
                linear_x_mps: f32,
                angular_z_radps: f32,
            }

            /// The drive runtime's published control state.
            struct State {
                target: Target,
                limited_target: Target,
                actuator_authority: ActuatorAuthority,
                stop_reason: Option<StopReason>,
            }

            topic target: pubsub Target;
            topic state: pubsub State;
        }

        motor {
            /// A per-actuator command.
            enum Command {
                Velocity(f32),
                Torque(f32),
                Stop,
            }

            topic command: pubsub Command;
        }

        component {
            /// A command to a specific component-instance capability, addressed by
            /// a dynamic per-instance key (D17/D38: framework-runtime / driver
            /// territory). The runtime fills `{instance}`/`{capability}` from its
            /// manifest-declared component bindings.
            enum MotorCommand {
                Velocity(f32),
                Torque(f32),
                Stop,
            }

            /// Per-encoder sample on a dynamic per-instance key.
            struct EncoderSample {
                position_rad: f64,
                velocity_radps: f32,
            }

            topic motor_command(instance, capability): pubsub MotorCommand
                = "component/{instance}/motor/{capability}/command";
            topic encoder_sample(instance, capability): pubsub EncoderSample
                = "component/{instance}/encoder/{capability}/sample";
        }

        odometry {
            /// A planar pose + twist estimate in the odometry frame.
            struct State {
                x_m: f64,
                y_m: f64,
                yaw_rad: f64,
                linear_x_mps: f32,
                angular_z_radps: f32,
            }

            topic state: pubsub State;
        }

        localize {
            /// A planar localization estimate in the map frame.
            struct LocalizationState {
                x_m: f64,
                y_m: f64,
                yaw_rad: f64,
                confidence: f32,
            }

            topic state: pubsub LocalizationState;
        }

        presence {
            /// Per-participant liveness + readiness beacon.
            enum Readiness {
                NotStarted,
                Initializing,
                Ready,
                Degraded,
                Failed,
            }

            struct Heartbeat {
                participant: String,
                readiness: Readiness,
            }

            topic heartbeat: pubsub Heartbeat;
        }

        map {
            /// A published map revision marker.
            struct Revision {
                revision: u64,
                resolution_m: f32,
            }

            /// Request a rectangular submap window (map-frame metres).
            struct SubmapRequest {
                min_x_m: f64,
                min_y_m: f64,
                max_x_m: f64,
                max_y_m: f64,
            }

            /// An occupancy-grid window: row-major cells, 0..=100 + 255 = unknown.
            struct SubmapResponse {
                width: u32,
                height: u32,
                resolution_m: f32,
                cells: Vec<u8>,
            }

            topic revision: pubsub Revision;
            topic submap: query SubmapRequest => SubmapResponse;
        }

        asset {
            /// Fetch a stored asset by path.
            struct GetRequest {
                path: String,
            }

            /// The asset bytes, a not-found marker, or a rejected path.
            enum GetResponse {
                Found { bytes: Vec<u8> },
                Missing,
                InvalidPath,
            }

            topic get: query GetRequest => GetResponse;
        }
    }

    // A second API version that inherits y2026_1 (D61). Unchanged families/types
    // are re-emitted *fresh* under `y2026_2` — same `FAMILY`/`TOPIC`, a different
    // `Api` marker. A type with no changed dependency is wire-identical to its
    // y2026_1 counterpart; a type that *contains* an overridden type changes with
    // it. Here `drive::Target` is overridden, so the inherited `drive::State`
    // (which embeds `Target`) reflects the new `Target` in y2026_2.
    version y2026_2 extends y2026_1 {
        drive {
            /// y2026_2 adds an optional curvature limit to the drive target.
            struct Target {
                linear_x_mps: f32,
                angular_z_radps: f32,
                curvature_limit_radpm: Option<f32>,
            }

            topic target: pubsub Target;
        }

        battery {
            /// Battery state — a family that exists only from y2026_2 on.
            struct State {
                voltage_v: f32,
                current_a: f32,
                charge_ratio: f32,
            }

            topic state: pubsub State;
        }

        safety {
            /// The robot's overall safety posture, worst-concern-wins. A family
            /// that exists only from y2026_2 on (it consumes `battery`, which is
            /// itself y2026_2-only).
            enum Level {
                /// No active concerns.
                Nominal,
                /// A degraded condition that does not (yet) require stopping.
                Warning,
                /// Actuation authority must be revoked immediately.
                EmergencyStop,
            }

            /// A specific reason the safety monitor raised the posture.
            enum Concern {
                BatteryLow,
                BatteryCritical,
                DriveFault,
            }

            /// The published safety posture: the worst level across all active
            /// concerns, plus the concerns that drove it.
            struct Status {
                level: Level,
                concerns: Vec<Concern>,
            }

            topic state: pubsub Status;
        }
    }
}

#[cfg(test)]
mod tests;