Expand description
The single API layer (D60/D61/D1).
This crate is the versioned API contract tree. It depends only on the
phoxal-bus ABI floor (the contract primitive traits and the
typed-topic builders) and the phoxal-macros proc-macros; it
does not depend on the phoxal engine. Normal participants import the
train-selected facade with use phoxal::api; concrete modules such as
phoxal_api::v0_1 remain available to compatibility adapters.
§Concrete API revisions
An API revision is a conventional vM_N module generated by
phoxal_api_tree!. Each version module carries:
- a zero-variant marker
enum Api {}implementingApiVersion, whoseApiVersion::IDis the concrete wire identity (for example"v0.1"); - the version-local wire bodies, one
pub modper contract node holding plain serde structs/enums and theirContractBodyimpls; - an api-local
topicbuilder rooted attopic::client().
From 1.0, published concrete revisions are immutable. Before 1.0 the
framework may make an approved in-place breaking edit without adding a shim
or a new revision; every participant on a robot must move as one train
because mixed pre-1.0 framework trains are unsupported. A child may extend
one earlier revision; the generator materializes the complete child tree
with its own identity. Exactly one latest alias is selected for each
framework train.
§Train-selected revision and per-contract identity
A participant declares its bus surface with a companion
#[derive(phoxal::Api)] handle struct.
Official handle fields name types through the complete train-selected facade.
The derive records each field’s resolved ContractBody::VERSION and
ContractBody::CONTRACT in the participant’s embedded metadata and does
not declare a participant-wide API version.
Across the graph, compatibility is name identity (D1) - two participants
interoperate on a contract iff they use the exact same version-qualified name
(v0.1::drive::Target), which is real on the wire because the revision
is folded into the key (ContractBody::TOPIC). There is no schema_id:
from 1.0 onward a stable contract type is immutable, so the name alone is
the whole identity. Before 1.0, that identity is train-scoped and an
in-place edit requires the whole robot graph to upgrade together.
§Plain serde wire bodies, provenance in metadata
A wire body is just its serde encoding - there is no {"v":…} envelope or any
other version tag inside the payload (D62). Identity lives entirely in the
Zenoh key (the version-qualified ContractBody::TOPIC); the bus metadata
alongside the encoded body carries only provenance (source + logical time) and
the codec that produced the bytes - never schema/family/version. Keeping
identity out of both the payload and the metadata means the body bytes for an
unchanged contract are identical across codecs, and a receiver’s per-key
subscription is the whole fast-reject.
§Topic
ContractBody::TOPIC is derived from the contract node’s path in the tree,
never written by hand: the version, then the /-joined node path plus the
topic leaf, with each dynamic node contributing a {var} placeholder, e.g.
v0.1/component/{instance}/motor/{capability}/command. A fully static path
has a literal key (v0.1/drive/state). Folding the revision into the key
(D1) is what makes two differently-versioned contracts physically distinct
Zenoh keys - they cannot collide, so there is no SCHEMA_ID/FAMILY needed to
disambiguate them.
§The api-local topic builder
Each version module exposes a topic builder that mirrors the node tree:
api::topic::client() 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 side-branded kind to its version-local body. For
example api::topic::client().drive().state() yields a
Topic<Subscribe<drive::State>> (the CLIENT observes the owner’s state) over
the version-qualified key v0.1/drive/state, and
api::topic::client().component("base").motor("left").command() fills the dynamic
segments to produce v0.1/component/base/motor/left/command. Because the
builder is generated from the same tree as TOPIC, the built key and the
documented key stay in lockstep.
§Owner side: topic::owner
The PUBLIC topic::client()... chain above is the client side. The matching
owner side lives at api::topic::owner()...:
the same node tree and keys, but the leaf brands flip so the owner gets the side
it must take - api::topic::owner().drive().state() is
Topic<Publish<drive::State>> (the owner publishes its telemetry), and
api::topic::owner().drive().target() is Topic<Subscribe<drive::Target>>
(the owner reads its command input). A query owner reaches its ServeQuery
brand the same way. The owner chain makes that ownership explicit; a
participant acquires the topics of its OWN node through it and everything it
consumes through the client chain.
Re-exports§
pub use v0_1 as latest;
Modules§
- v0_1
- Concrete API revision
v0.1- version-local wire bodies + topics.
Traits§
- ApiVersion
- The contract primitive traits, re-exported from the
phoxal-buscrate (the ABI floor) so they stay addressable atphoxal_api::ApiVersion/phoxal_api::ContractBody. - Contract
Body - The contract primitive traits, re-exported from the
phoxal-buscrate (the ABI floor) so they stay addressable atphoxal_api::ApiVersion/phoxal_api::ContractBody.