phoxal 0.41.0

Phoxal - production-oriented autonomous robot framework: the runtime engine and model (the api contract tree lives in phoxal-api, the typed bus in phoxal-bus).
Documentation
//! # phoxal
//!
//! A production-oriented framework for autonomous robots.
//!
//! Phoxal gives a robot a small, strongly-typed core: a contract bus over
//! [Zenoh](https://zenoh.io), train-selected concrete API contracts,
//! and a
//! participant authoring model where one struct plus a couple of attribute
//! macros is a complete service, driver, tool, or simulator. The framework owns the
//! awkward parts - argument parsing, bus connection, scheduling, query serving,
//! shutdown, and health - so the code you write is the robot's behavior, not its
//! plumbing.
//!
//! Three ideas hold it together:
//!
//! - **A typed contract bus.** Every message is a plain serde body bound to one
//!   version-qualified contract name. Handles are body-typed
//!   ([`StatePublisher<T>`](bus::StatePublisher),
//!   [`Subscriber<T>`](bus::Subscriber), [`Latest<T>`](bus::Latest),
//!   [`Querier<Req, Resp>`](bus::Querier)), so the compiler - not a late check -
//!   rejects sending the wrong type on a topic. Publishing is additionally
//!   gated by the contract's *temporal* role: the robot time a publisher can
//!   express is fixed by what the contract is, so a participant cannot stamp an
//!   instant it never reached.
//! - **One train-selected API facade.** Official participants import
//!   `phoxal::api`, which names the complete concrete revision selected by the
//!   locked framework train. Contract identity is realized on the wire by the
//!   revision-qualified key (D1); there is no `schema_id`.
//! - **Participants are authored, not wired.** You write a `Config` struct, an
//!   `Api` handle struct, a state struct, and an `impl`;
//!   [`#[derive(Config)]`](derive@Config) / [`#[derive(Api)]`](derive@Api) plus
//!   [`#[phoxal::service|driver|simulator|tool]`](macro@service) and
//!   [`#[phoxal::behavior]`](macro@behavior) derive the static metadata, and
//!   [`run`] turns the type into a binary. Use `service` for ordinary robot
//!   participants, `driver` for a participant launched once per
//!   `robot.components` entry, `tool` for host-side utilities, and `simulator`
//!   for simulation-only participants.
//!
//! ## Author a participant
//!
//! A participant is a `Config` struct, an `Api` struct of typed bus handles, a
//! state struct, and one annotated inherent `impl`. This is the whole
//! getting-started surface:
//!
//! ```ignore
//! use phoxal::api;
//! use phoxal::prelude::*;
//!
//! #[derive(serde::Deserialize, phoxal::Config)]
//! struct Config {}
//!
//! #[derive(phoxal::Api)]
//! struct Api {
//!     state:  Latest<api::drive::State>,            // keep-last view of the drive state
//!     target: CommandPublisher<api::drive::Target>, // commanded drive target
//! }
//!
//! #[phoxal::service(id = "avoid-obstacles")]
//! struct AvoidObstacles;
//!
//! #[phoxal::behavior]
//! impl AvoidObstacles {
//!     #[setup]
//!     async fn setup(ctx: &mut SetupContext<Self>, _config: Self::Config) -> Result<(Self, Self::Api)> {
//!         Ok((Self, Self::Api {
//!             state:  ctx.latest(api::topic::new().drive().state()).await?,
//!             target: ctx.command_publisher(api::topic::new().drive().target()).await?,
//!         }))
//!     }
//!
//!     #[step(hz = 50)]
//!     async fn step(&mut self, api: &mut Self::Api, step: StepContext) -> Result<()> {
//!         api.target.send(api::drive::Target {
//!             linear_x_mps: 0.2,
//!             angular_z_radps: 0.0,
//!             curvature_limit_radpm: None,
//!         })?;
//!         Ok(())
//!     }
//! }
//!
//! fn main() -> phoxal::Result<()> { phoxal::run::<AvoidObstacles>() }
//! ```
//!
//! What each piece does:
//!
//! - `use phoxal::api;` brings the versioned API module into scope;
//!   `Api` struct fields name train-selected bodies (`api::drive::Target`)
//!   directly, with no participant-local version attribute to keep in sync.
//! - `#[derive(phoxal::Api)]` derives the bus-facing contract surface from the
//!   `Api` struct's handle fields (the role-gated publishers,
//!   [`Latest<T>`](bus::Latest), [`Subscriber<T>`](bus::Subscriber),
//!   [`Querier<Req, Resp>`](bus::Querier), `Server<Req, Resp>`).
//! - `#[phoxal::service(id = "…")]` links the participant state struct to its
//!   `Config`/`Api` types and records its identity.
//! - All handles are built in `#[setup]` from api-local topic builders
//!   (`api::topic::new().drive().state()`) and returned as the `Api` value
//!   alongside the participant state.
//! - `#[step(hz = ...)]` is the scheduled control loop; the runner owns timing and
//!   delivers the step token via [`StepContext`](participant::StepContext), and
//!   `&mut Self::Api` alongside `&mut self`. Query servers use `#[server]` /
//!   `#[server_snapshot]`, and `#[shutdown]` runs graceful cleanup before the bus
//!   closes.
//! - `fn main() -> phoxal::Result<()> { phoxal::run::<R>() }` is the default
//!   blocking entrypoint. For a custom Tokio main, call
//!   [`phoxal::tokio::run::<R>().await`](tokio::run).
//!
//! The four authoring kinds share the same metadata path but describe different
//! runtime roles:
//!
//! - [`macro@service`] is the ordinary typed participant surface.
//! - [`macro@driver`] is launched once per `robot.components` entry. Only a
//!   driver can call
//!   [`SetupContextDriverExt::component`](participant::SetupContextDriverExt::component)
//!   to read the bound component instance.
//! - [`macro@tool`] is for host-side utilities that inspect the robot model
//!   through
//!   [`SetupContextApiExt::robot`](participant::SetupContextApiExt::robot).
//!   Privileged raw-bus access lives under [`raw`] so it is never part of the
//!   default checked participant surface. A tool joins the execution, not the
//!   clock: it can observe and command, and nothing on its surface hands it a
//!   [`RobotInstant`](bus::RobotInstant) - see [`raw`]'s docs for where that
//!   boundary is a compiler rule and where it is a convention.
//! - [`macro@simulator`] is a normal participant for simulation-only processes.
//!   It carries a distinct kind and marker for simulation clock ownership.
//!
//! Worked examples live in `phoxal/examples/`.
//!
//! ## Where to look next
//!
//! - The `phoxal-api` crate (`phoxal::api`, …) - the versioned API
//!   modules: version-local wire bodies, the [`ApiVersion`](bus::ApiVersion) /
//!   [`ContractBody`](bus::ContractBody) traits, and the api-local topic builders,
//!   all generated by [`phoxal_api_tree!`](macro@phoxal_macros::phoxal_api_tree).
//!   A participant imports it directly with `use phoxal::api as api;`.
//!   The runner also links it for framework-owned out-of-band infrastructure
//!   contracts such as bus logs.
//! - [`prelude`] - everything a participant author imports with
//!   `use phoxal::prelude::*;`: the handle types, [`SetupContext`](participant::SetupContext) /
//!   [`StepContext`](participant::StepContext), and [`Result`].
//! - [`mod@participant`] - the authoring surface behind the macros: the static metadata
//!   traits, the contexts, the clock and scheduler, and the runner
//!   ([`run`] / [`tokio::run`]).
//! - [`bus`] - the typed contract vocabulary normal participants need: the
//!   key scheme, MessagePack codec, [`BusMetadata`](bus::BusMetadata) attachment,
//!   the four non-interchangeable time types, body-typed handles, and
//!   side-branded [`Topic`](bus::Topic) values.
//! - [`raw`] - the explicit privileged/tooling surface for opening a raw bus,
//!   accessing the underlying session, or embedding runtimes on a caller-owned
//!   bus.
//! - [`model`] - the authored manifest schemas (`robot.yaml`, `structure.urdf`,
//!   `component.yaml`, …) that participants and the CLI parse.
//! - The **official service set** ships alongside this crate in the workspace
//!   `service/` tree (`drive`, `localize`, `map`, `safety`, …): full platform
//!   participants authored on exactly this surface, useful as reference reading.

// Generated macro output refers to the framework as `::phoxal::…`; make that path
// resolve to this crate so the engine's participant derives and
// `#[phoxal::behavior]` macro work when invoked inside the engine (e.g. the
// crate's own tests), the
// same as in downstream service crates. Only the in-crate test build units expand
// macros to `::phoxal::…`, so the alias is needed only under `cfg(test)`; gating
// it there keeps the non-test build free of an unused `extern crate` (no need for
// an `allow(unused_extern_crates)`).
#[cfg(test)]
extern crate self as phoxal;

pub mod behavior;
pub mod check;
pub mod infrastructure;
pub mod model;
pub mod participant;
pub mod suite;
pub mod util;

/// The concrete framework API revision selected by this release train.
pub use phoxal_api::latest as api;

/// Typed contract and handle vocabulary for normal participant authoring.
///
/// This module deliberately excludes the raw session-owning bus types
/// (`Bus`, `BusConfig`, `BusHealth`, `IncomingQuery`, `ServerQueryable`). Checked
/// participants build IO through [`participant::SetupContext`] and the api-local
/// topic builders; privileged tools, bridges, and framework tests that need raw
/// access use [`raw`] instead.
pub mod bus {
    pub use phoxal_bus::{
        ApiVersion, AskQuery, BusError, BusMetadata, CaptureStamp, Codec, CodecError, CodecId,
        CommandContract, CommandPublisher, ContractBody, DEFAULT_QUERY_TIMEOUT, DiagnosticContract,
        DiagnosticPublisher, ExecutionId, LEASE_TRACE_TARGET, Latest, Lease, LeaseDecision,
        LeaseRejection, LocalInstant, MeasurementContract, MeasurementPublisher, MessagePack,
        Observed, OwnerCap, ProducerFence, ProducerId, Publish, Querier, QueryCode, QueryError,
        QueryFailure, Result, RobotInstant, ServeQuery, ServerResult, StateContract,
        StatePublisher, StepStamp, StepToken, Subscribe, Subscriber, TimeWindow, TimelineAuthority,
        TimelineId, TimelineMismatch, Topic, TopicKind, TopicRole, WallTimestamp, WildcardPublish,
        WorldStepToken, encoding_string,
    };
}

/// Explicit raw/permissive bus surface for privileged participants, tooling,
/// bridges, and framework tests.
///
/// Importing this module is the conscious opt-in. The ordinary
/// `phoxal::prelude::*` and [`bus`] module do not expose raw session/open APIs.
/// `Tool` participants are emitted as `participant_class = "privileged"`; the
/// graph checker still includes their contracts, but never lets their raw
/// access satisfy checked topology.
///
/// # Robot time: what the types enforce, and what they do not
///
/// Raw access reaches the session, the subscription handles, the querier, and
/// the command/diagnostic publishers - everything a tool or bridge needs to
/// watch a robot and act on it. Commands carry no production instant at all, so
/// nothing here lets a tool *date* anything by publishing one.
///
/// Publishing checked state or a measurement is different: it needs a
/// [`StepToken`](phoxal_bus::StepToken), a
/// [`WorldStepToken`](phoxal_bus::WorldStepToken), or a
/// [`CaptureStamp`](phoxal_bus::CaptureStamp). The runner mints the first from
/// each step it actually reaches, and the second comes from a
/// [`TimelineAuthority`](phoxal_bus::TimelineAuthority) the world-authority
/// participant holds. In ordinary authoring there is no way to obtain either
/// one out of thin air, and the sealed [`StepStamp`](phoxal_bus::StepStamp)
/// trait plus the role markers make using the wrong one a compile error.
///
/// **That is a strong convention, not a sealed boundary, and this module will
/// not pretend otherwise.** `RobotInstant`, `StepToken`, and the role
/// publishers are defined in `phoxal-bus` while the runner that mints them
/// lives here, so their constructors have to be `pub` for the runner to call
/// them - and Rust has no visibility level between "this crate" and "the
/// world". They are `#[doc(hidden)]` and named to be conspicuous
/// (`__mint`), which makes fabricating robot time something a participant can
/// only do on purpose, in code that says so. The alternative - folding the api,
/// the bus, and the runtime into one crate so the constructors could be
/// `pub(crate)` - buys a compiler-enforced guarantee at the cost of the crate
/// split; it is a deliberate open question, not an oversight (#952 section D).
pub mod raw {
    pub use crate::participant::runner::{run_with_bus, run_with_bus_clock};
    pub use phoxal_bus::*;
}

/// The framework result type (`anyhow`-backed). Authoring code uses bare
/// `Result<T>` via the [`prelude`].
pub use anyhow::Result;

/// SemVer of the single framework train this facade was built from.
pub const VERSION: &str = env!("CARGO_PKG_VERSION");

/// The bare `#[phoxal::behavior]` attribute for a participant's inherent impl.
pub use phoxal_macros::behavior;

/// Derive the bus-facing contract surface from an `Api` handle struct's
/// fields. See `phoxal::participant::api`.
pub use phoxal_macros::Api;

/// Derive participant config identity from a `Config` struct (schema
/// materialization is a later slice - see
/// `phoxal::participant::api::ParticipantConfig`).
pub use phoxal_macros::Config;

/// Link a participant state struct to its `Config`/`Api` types as a checked
/// service.
pub use phoxal_macros::service;

/// Link a participant state struct to its `Config`/`Api` types as a
/// component driver.
pub use phoxal_macros::driver;

/// Link a participant state struct to its `Config`/`Api` types as a
/// simulation participant.
pub use phoxal_macros::simulator;

/// Link a participant state struct to its `Config` as a raw-bus tool (`Api`
/// defaults to `()` - tools stay raw-bus only).
pub use phoxal_macros::tool;

/// Run a participant (`#[phoxal::service|driver|simulator|tool]` +
/// `#[phoxal::behavior]`) to completion on a framework-owned blocking Tokio
/// runtime.
///
/// This is the default binary entrypoint:
/// `fn main() -> phoxal::Result<()> { phoxal::run::<Participant>() }`.
pub use participant::run;

/// Async host runner entrypoint for custom Tokio mains
/// (`phoxal::tokio::run::<Participant>().await`).
pub mod tokio {
    #[doc(inline)]
    pub use crate::participant::run_async as run;
}

/// Everything a participant author imports with `use phoxal::prelude::*;`.
pub mod prelude {
    pub use crate::Result;
    pub use crate::bus::{
        CaptureStamp, CommandPublisher, DiagnosticPublisher, Latest, Lease, LeaseDecision,
        LocalInstant, MeasurementPublisher, Observed, ProducerFence, Querier, QueryError,
        RobotInstant, ServerResult, StatePublisher, Subscriber, TimeWindow, TimelineId,
    };
    pub use crate::participant::{
        ManagedTaskPolicy, ResetContext, Server, SetupContext, SetupContextApiExt,
        SetupContextDriverExt, SetupContextSimulatorExt, SetupContextToolExt, ShutdownContext,
        Snapshot, StepContext,
    };
}