Expand description
The single API layer (D60/D61).
§Dated API versions
An API version is a dated module (phoxal::api::y2026_1, …) generated by
phoxal_api_tree!. Each version module carries:
- a zero-variant marker
enum Api {}implementingApiVersion, whoseApiVersion::IDis the dated module name ("y2026_1") - the canonical version identity; - the version-local wire bodies, one
pub modper contract node holding plain serde structs/enums and theirContractBodyimpls; - an api-local
topicbuilder rooted attopic::new().
Versions can build on earlier ones with version y2026_2 extends y2026_1:
every inherited node and type is re-emitted fresh under the new module - same
ContractBody::FAMILY and ContractBody::TOPIC, a different Api - so an
unchanged contract stays wire-identical while the compile-time Api bound stays
distinct. See phoxal_api_tree! for the inheritance and shape-identity rules.
§One API version per runtime, one per graph
A runtime authors against exactly one version module
(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 passing a body from another API version is a
compile error (D59/D60). At deploy time a single graph runs a single API
version: all of its runtimes share one Api, so contracts on the bus are
mutually consistent by construction (D59).
§Plain serde wire bodies, version in metadata
A wire body is just its serde encoding - there is no {"v":…} envelope or any
other version tag inside the payload (D62). The three pieces of routing
identity travel as bus metadata alongside the encoded body, not in it:
- the version (
ApiVersion::ID, e.g."y2026_1"), - the family (
ContractBody::FAMILY, e.g."drive::State"), - the codec that produced the bytes.
Keeping these out of the payload means the body bytes for an unchanged contract are identical across versions and codecs; the envelope/metadata layer owns version and family.
§Family and topic
Both identifiers are derived from the contract node’s path in the tree, never written by hand:
ContractBody::FAMILYis the::-joined node path plus the body type name, e.g.component::motor::Command. It excludes dynamic variables, so every instance of a per-instance node shares one family.ContractBody::TOPICis the versionless/-joined key with each dynamic node contributing a{var}placeholder, e.g.component/{instance}/motor/{capability}/command. A fully static path has a literal key (drive/state).
§The api-local topic builder
Each version module exposes a topic builder that mirrors the node tree:
api::topic::new() returns a root, one method per top-level node walks down the
tree, a dynamic node’s method takes its variable as impl Display, and a leaf
method binds the topic’s kind to its version-local body. For example
api::topic::new().drive().state() yields a Topic<PubSub<drive::State>> over
the static key drive/state, and
api::topic::new().component("base").motor("left").command() fills the dynamic
segments to produce component/base/motor/left/command. Because the builder is
generated from the same tree as FAMILY/TOPIC, the built key and the
documented key stay in lockstep.
Modules§
- y2026_1
- Dated API version
#id— version-local wire bodies + topics.
Traits§
- ApiVersion
- Marker trait identifying one dated API version (D60).
- Contract
Body - A version-local wire body: a plain serde type bound to exactly one
ApiVersionand one contract family/topic (D61).