hibana is a Rust 2024 #![no_std] / no_alloc oriented affine MPST runtime.
It has two public surfaces:
- App surface:
hibana::gplusEndpoint - Substrate surface:
hibana::substrate::programplushibana::substrate
Application code should stay on hibana::g and Endpoint. Protocol crates
use hibana::substrate::program and hibana::substrate to attach transport,
binding, resolver, and policy.
Everything else is lower layer.
hibana is for protocols that want one global choreography as the source of
truth and a small localside API at runtime.
The normal flow is:
- Write one choreography with
hibana::g - Let a protocol crate compose transport and appkit prefixes around it
- Receive an attached
Endpoint - Advance that endpoint with
flow().send(),recv(),offer(), anddecode()
What hibana gives you:
- typed projection from one global program
- compile-time checks for route shape, lane ownership, and composition errors
- fail-closed localside execution for label and payload mismatches
- a protocol-neutral core that does not bake QUIC, HTTP/3, or other wire terms into the crate root
std- enables transport/testing utilities and observability normalisers. The runtime remains slab-backed andno_allocoriented in both modes.
Write a choreography once with hibana::g:
use hibana::g;
let request_response = g::seq(
g::send::<g::Role<0>, g::Role<1>, g::Msg<1, u32>, 0>(),
g::send::<g::Role<1>, g::Role<0>, g::Msg<2, u32>, 0>(),
);After integration code returns an attached endpoint, app code stays on the localside core API:
use hibana::g;
let _sent = endpoint.flow::<g::Msg<1, u32>>()?.send(&7).await?;
let reply = endpoint.recv::<g::Msg<2, u32>>().await?;
let _ = reply;That is the intended app path: define a choreography, receive an endpoint, and drive it with the localside methods.
App authors should stay on hibana::g and Endpoint.
| Job | Public owner |
|---|---|
| Define choreography | hibana::g::{send, route, par, seq} |
| Mark a dynamic policy point | Program::policy::<POLICY_ID>() |
| Send a known message | flow().send() |
| Receive a known message | recv() |
| Observe a route choice | offer() |
| Receive the first message of the chosen route arm | decode() |
| Inspect a chosen branch | RouteBranch::{label, decode} |
App code does not call projection, attach, binding, transport, or resolver setup directly.
The public app language is fixed to:
hibana::g::{send, route, par, seq}Program::policy::<POLICY_ID>()RouteBranch::label()RouteBranch::decode()flow().send()offer()recv()decode()
g::route(left, right) is binary. g::par(left, right) is also binary and
requires disjoint (role, lane) ownership.
use hibana::g;
let left_arm = g::seq(
g::send::<g::Role<0>, g::Role<0>, g::Msg<10, ()>, 0>().policy::<7>(),
g::send::<g::Role<0>, g::Role<1>, g::Msg<11, [u8; 4]>, 0>(),
);
let right_arm = g::seq(
g::send::<g::Role<0>, g::Role<0>, g::Msg<12, ()>, 0>().policy::<7>(),
g::send::<g::Role<0>, g::Role<1>, g::Msg<13, u16>, 0>(),
);
let routed = g::route(left_arm, right_arm);
let parallel = g::par(
g::send::<g::Role<0>, g::Role<1>, g::Msg<20, u32>, 1>(),
g::send::<g::Role<0>, g::Role<1>, g::Msg<21, [u8; 8]>, 2>(),
);
let app = g::seq(routed, parallel);Rules worth remembering:
- annotate only the control point that actually needs dynamic policy
- duplicate route labels are compile errors
- empty
g::pararms are compile errors - overlapping
(role, lane)pairs ing::parare rejected - dynamic route does not silently appear at runtime; it must be explicit in the choreography
Each localside method has one job:
flow().send()for sends you already know staticallyrecv()for deterministic receivesoffer()when the next step is a route decisiondecode()when the chosen arm begins with a receivedrop(branch); endpoint.flow().send()when the chosen arm begins with a send
use hibana::g;
let outbound = endpoint.flow::<g::Msg<1, u32>>()?.send(&7).await?;
let inbound = endpoint.recv::<g::Msg<2, u32>>().await?;
let mut branch = endpoint.offer().await?;
match branch.label() {
30 => {
let payload = branch.decode::<g::Msg<30, [u8; 4]>>().await?;
let _ = (payload, outbound, inbound);
}
31 => {
drop(branch);
let () = endpoint.flow::<g::Msg<31, ()>>()?.send(&()).await?;
}
_ => unreachable!(),
}The app-facing owners at the crate root are:
hibana::SendResult<T>hibana::RecvResult<T>hibana::SendErrorhibana::RecvErrorhibana::RouteBranch
The public surface stays small because guarantees are pushed into the type system:
- projection stays typed through
RoleProgram<ROLE> g::routerejects duplicate labels and controller mismatches before runtimeg::parrejects empty fragments and role/lane overlap before runtime- localside runtime is fail-closed for label and payload mismatches
hibana is choreography-first.
The connection shape is always:
transport prefix -> appkit prefix -> user app
On the choreography side, that means:
g::seq(transport prefix, g::seq(appkit prefix, APP))
The intended split is:
- App code builds a local choreography term with
hibana::g - A protocol crate composes transport and appkit prefixes around that local term
- The protocol crate projects a typed role witness with
project(&program) - The protocol crate attaches transport, binding, and policy, then returns the first app endpoint
- App code continues only through the localside core API
This keeps the user-facing path small while preserving typed projection and a protocol-neutral core.
Program<Steps> stays public as the choreography witness, but on stable Rust
the canonical path is a local let choreography term rather than a named item
signature. Keep choreography terms local and project them immediately.
- The driver follows
offer(); it does not invent decisions on its own - Branch handling is just
match branch.label() - Use
branch.decode()when the chosen arm begins with a receive - Use
drop(branch); endpoint.flow().send()when the chosen arm begins with a send flow()andoffer()are preview-only; endpoint progress happens only whensend()ordecode()successfully consumes the preview- App code and generic driver logic do not call transport APIs directly
Route authority has exactly three public sources:
Ackfor already materialized canonical control decisionsResolverfor dynamic-route resolutionPollfor transport-observable static evidence
Important negative rule: hint labels and binding classifications are demux or readiness evidence, not a fourth authority source.
Loop meaning comes from metadata, not from special wire labels. Labels are representation; they are not semantic authority on their own.
hibanaの app surface では、lane の意味は固定されません- app lane ownership は protocol / appkit contract が決めます
- canonical な attach path では control traffic を lane
0に載せます - additional reserved lanes があるかどうかは transport / appkit 側の契約です
- bindings own demux and channel resolution, not route authority
hibanaruntime が app lane を推測したり吸収したりはしません- unknown lanes are errors once the binding / transport contract is fixed
PolicySignalsProvider::signals(slot)is the single public slot-input boundary- EPF executes inside the resolver slot; it is not a second public policy API
- fail-closed is the default for verifier, trap, or fuel failures
- policy distribution and activation belong to the integration layer, not to endpoint-local helpers
| Layer | Writes | Reads |
|---|---|---|
| Transport | yes | yes |
| Resolver | no | yes |
| EPF | no | yes |
| Binder | no | yes |
| Driver | no | no |
| Surface | Who uses it | Main owners |
|---|---|---|
| App surface | application code | hibana::g, Endpoint, RouteBranch, SendResult, RecvResult |
| Substrate surface | protocol implementations | hibana::substrate::program, hibana::substrate |
If a concept is not owned by one of the two surfaces above, treat it as lower layer rather than as part of the app-facing contract.
Protocol implementations use the same choreography language as applications. There is no second composition DSL.
use hibana::g;
use hibana::substrate::program::{RoleProgram, project};
let transport_prefix = g::seq(
g::send::<g::Role<0>, g::Role<1>, g::Msg<1, ()>, 0>(),
g::send::<g::Role<1>, g::Role<0>, g::Msg<2, ()>, 0>(),
);
let appkit_prefix =
g::send::<g::Role<0>, g::Role<1>, g::Msg<3, ()>, 0>();
let app = g::seq(
g::send::<g::Role<0>, g::Role<1>, g::Msg<10, u32>, 0>(),
g::send::<g::Role<1>, g::Role<0>, g::Msg<11, u32>, 0>(),
);
let program = g::seq(transport_prefix, g::seq(appkit_prefix, app));
let client: RoleProgram<0> = project(&program);The composed program witness is zero-sized. It exists to carry the typed
choreography into project(&program), not to serve as a reusable item-level
runtime artifact.
Protocol implementors use the protocol-neutral SPI:
hibana::gowns choreography compositionhibana::substrate::programowns typed projection and compile-time control-message typinghibana::substrateowns attach, enter, binding, resolver, policy, and transport seams- the root app surface does not expose
SessionKit,BindingSlot,RoleProgram, or typestate internals
The everyday protocol-side owners are:
hibana::substrate::program::{project, RoleProgram, MessageSpec, StaticControlDesc}hibana::substrate::SessionKithibana::substrate::{AttachError, CpError}hibana::substrate::ids::{EffIndex, Lane, RendezvousId, SessionId}hibana::substrate::Transporthibana::substrate::binding::{BindingSlot, NoBinding}hibana::substrate::policy::{LoopResolution, PolicySignalsProvider, ResolverContext, ResolverError, ResolverRef, RouteResolution}hibana::substrate::runtime::{Clock, Config, CounterClock, DefaultLabelUniverse, LabelUniverse}hibana::substrate::tap::TapEventhibana::substrate::cap::{CapShot, ControlResourceKind, GenericCapToken, Many, One, ResourceKind}hibana::substrate::wire::{Payload, WireEncode, WirePayload}hibana::substrate::transport::{Outgoing, TransportError}
Lower-level substrate buckets:
hibana::substrate::policy::{ContextId, ContextValue, PolicyAttrs, PolicySignals, PolicySlot}pluscore::*for fixed context-key idshibana::substrate::cap::advancedfor descriptor metadata plus the built-in route / loop control kindshibana::substrate::transport::advancedfor event-kind classification and metrics translation
Everything in this section is protocol-neutral. If a concept is protocol
specific, keep it outside hibana's public surface.
Control messages do not have a second public shortcut DSL.
Supported route/loop, topology, abort/state/tx, and management-policy
operations are expressed as ordinary g::send() steps whose message type
carries a capability token and its control kind directly. Capability
delegation stays on the lower-layer endpoint-token path; it is not a public
descriptor control message.
The key owners are:
g::Msg<LABEL, GenericCapToken<K>, K>for control messagesControlResourceKind::PATHfor local vs wire behaviorControlResourceKind::OPfor the atomic runtime actionMessageSpecandStaticControlDescfor descriptor-first lowering
Control-path rules are fixed:
K::PATH == ControlPath::Localis compile-time restricted to self-sendK::PATH == ControlPath::Wireis compile-time restricted to cross-role send- route/loop control ops are local-only and compile-time rejected on wire paths
AUTO_MINT_WIREonly enables endpoint-side auto-mint; explicit wire tokens may keep itfalse- the runtime executes exactly one
ControlOpbaked by projection
Wire auto-mint for TopologyBegin reads its extra operands from the
PolicySlot::Route input boundary:
TopologyBegin:input[0] = (dst_rv << 16) | dst_lane,input[1] = (old_gen << 16) | new_gen,input[2] = seq_tx,input[3] = seq_rxsrc_rvand the source lane always come from the attached endpoint. Dynamic resolver-backed policy for this control op remains rejected.
Core keeps only the generic control-capability mechanism plus the built-in
route / loop kinds under hibana::substrate::cap::advanced:
RouteDecisionKindLoopContinueKindLoopBreakKind
If a protocol needs a custom control kind, implement ResourceKind and
ControlResourceKind from hibana::substrate::cap, using
hibana::substrate::cap::advanced only for descriptor metadata constants and
built-in route / loop control kinds.
Control kinds are still just message types in the choreography:
use hibana::g;
use hibana::substrate::cap::{ControlResourceKind, GenericCapToken};
use hibana::substrate::cap::advanced::{
CAP_HANDLE_LEN, CapError, ControlOp, ControlPath, ControlScopeKind,
LoopContinueKind, ScopeId,
};
use hibana::substrate::ids::{Lane, SessionId};
let loop_continue = g::send::<
g::Role<0>,
g::Role<0>,
g::Msg<
{ <LoopContinueKind as ControlResourceKind>::LABEL },
GenericCapToken<LoopContinueKind>,
LoopContinueKind,
>,
0,
>();
struct CustomWireKind;
impl hibana::substrate::cap::ResourceKind for CustomWireKind {
type Handle = ();
const TAG: u8 = 0x90;
const NAME: &'static str = "CustomWire";
fn encode_handle(_: &Self::Handle) -> [u8; CAP_HANDLE_LEN] { [0; CAP_HANDLE_LEN] }
fn decode_handle(_: [u8; CAP_HANDLE_LEN]) -> Result<Self::Handle, CapError> { Ok(()) }
fn zeroize(_: &mut Self::Handle) {}
}
impl hibana::substrate::cap::ControlResourceKind for CustomWireKind {
const LABEL: u8 = 124;
const SCOPE: ControlScopeKind = ControlScopeKind::None;
const PATH: ControlPath = ControlPath::Wire;
const SHOT: hibana::substrate::cap::CapShot = hibana::substrate::cap::CapShot::Many;
const TAP_ID: u16 = 0x0300 + 124;
const OP: ControlOp = ControlOp::Fence;
const AUTO_MINT_WIRE: bool = false;
fn mint_handle(_: SessionId, _: Lane, _: ScopeId) -> Self::Handle { () }
}
let custom_wire = g::send::<
g::Role<0>,
g::Role<1>,
g::Msg<
{ <CustomWireKind as ControlResourceKind>::LABEL },
GenericCapToken<CustomWireKind>,
CustomWireKind,
>,
0,
>();Use AUTO_MINT_WIRE = true only for kinds whose wire token can be minted from
the endpoint's route-policy inputs and executed through the descriptor control
path. Otherwise, keep it false and send an explicit GenericCapToken<K>
payload.
Capability-building owners live in two layers:
hibana::substrate::cap::{One, Many}for affine shot disciplinehibana::substrate::cap::{CapShot, ResourceKind, ControlResourceKind}for runtime capability representationhibana::substrate::cap::advanced::{CAP_HANDLE_LEN, CapError, CapHeader, ControlOp, ControlPath, ControlScopeKind, LoopBreakKind, LoopContinueKind, RouteDecisionKind, ScopeId}for descriptor and built-in control metadata
hibana::substrate::Transport is the protocol-neutral I/O seam. It owns
poll_send, poll_recv, requeue, event draining, hint exposure, metrics, and
pacing updates. Public endpoint futures stay transport-erased; pending state and
wakers live in transport-owned handles or transport-owned shared state.
use core::task::{Context, Poll};
struct MyTransport;
impl hibana::substrate::Transport for MyTransport {
type Error = hibana::substrate::transport::TransportError;
type Tx<'a> = () where Self: 'a;
type Rx<'a> = () where Self: 'a;
type Metrics = ();
fn open<'a>(&'a self, _local_role: u8, _session_id: u32) -> (Self::Tx<'a>, Self::Rx<'a>) {
((), ())
}
fn poll_send<'a, 'f>(
&'a self,
_tx: &'a mut Self::Tx<'a>,
_outgoing: hibana::substrate::transport::Outgoing<'f>,
_cx: &mut Context<'_>,
) -> Poll<Result<(), Self::Error>>
where
'a: 'f,
{
Poll::Ready(Ok(()))
}
fn poll_recv<'a>(
&'a self,
_rx: &'a mut Self::Rx<'a>,
_cx: &mut Context<'_>,
) -> Poll<Result<hibana::substrate::wire::Payload<'a>, Self::Error>> {
static EMPTY: [u8; 0] = [];
Poll::Ready(Ok(hibana::substrate::wire::Payload::new(&EMPTY)))
}
fn cancel_send<'a>(&'a self, _tx: &'a mut Self::Tx<'a>) {}
fn requeue<'a>(&'a self, _rx: &'a mut Self::Rx<'a>) {}
fn drain_events(
&self,
emit: &mut dyn FnMut(hibana::substrate::transport::advanced::TransportEvent),
) {
emit(hibana::substrate::transport::advanced::TransportEvent::new(
hibana::substrate::transport::advanced::TransportEventKind::Ack,
10,
1200,
0,
));
}
fn recv_label_hint<'a>(&'a self, _rx: &'a Self::Rx<'a>) -> Option<u8> {
None
}
fn metrics(&self) -> Self::Metrics {
()
}
fn apply_pacing_update(&self, _interval_us: u32, _burst_bytes: u16) {}
}Transport rules:
recv()must yield borrowed payload viewsrecv()anddecode()return the decoded view chosen by the payload ownercancel_send()must discard transport-owned staged send state before retryrequeue()is how transport hands an unconsumed frame backdrain_events()feeds protocol-neutral transport observationrecv_label_hint()is a demux hint, not route authoritymetrics()returns packedPolicyAttrsthroughtransport::advanced::TransportMetrics
hibana::substrate::SessionKit::new(&clock) is the canonical starting point.
The borrowed, no_alloc-oriented path is the canonical substrate path: keep
the clock, config storage, projected program, and resolver state borrowed; add
rendezvous once; then enter().
let mut tap_buf = [hibana::substrate::tap::TapEvent::zero(); 128];
let mut slab = [0u8; 64 * 1024];
let clock = hibana::substrate::runtime::CounterClock::new();
let config = hibana::substrate::runtime::Config::new(&mut tap_buf, &mut slab);
let cluster: hibana::substrate::SessionKit<
'_,
MyTransport,
hibana::substrate::runtime::DefaultLabelUniverse,
hibana::substrate::runtime::CounterClock,
4,
> = hibana::substrate::SessionKit::new(&clock);
let transport = MyTransport;
let rv_id = cluster.add_rendezvous_from_config(config, transport)?;
let endpoint = cluster.enter(
rv_id,
hibana::substrate::ids::SessionId::new(1),
&CLIENT,
hibana::substrate::binding::NoBinding,
)?;Key points:
- the borrowed path is canonical even under
std - storage stays slab-backed, not heap-backed
- the same borrowed
RoleProgramcan be passed toset_resolver()andenter() Config::new(tap_buf, slab)allocates tap storage and the rendezvous slabConfig::with_lane_range(range)reserves lane space for the transport/appkit splitConfig::with_universe(universe)andConfig::with_clock(clock)install custom label-universe and clock owners- bootstrap failures use
hibana::substrate::CpErrorandhibana::substrate::AttachError
BindingSlot is the transport-adapter seam for framed streams, multiplexed
channels, and slot-scoped policy signals. It is also where protocol code
supplies PolicySignalsProvider.
BindingSlot is demux and transport observation only. It does not decide route
arms.
struct MyBinding {
signals: hibana::substrate::policy::PolicySignals<'static>,
}
impl hibana::substrate::policy::PolicySignalsProvider for MyBinding {
fn signals(
&self,
_slot: hibana::substrate::policy::PolicySlot,
) -> hibana::substrate::policy::PolicySignals<'_> {
self.signals
}
}
impl hibana::substrate::binding::BindingSlot for MyBinding {
fn poll_incoming_for_lane(
&mut self,
_logical_lane: u8,
) -> Option<hibana::substrate::binding::advanced::IncomingClassification> {
Some(hibana::substrate::binding::advanced::IncomingClassification {
label: 40,
instance: 0,
has_fin: false,
channel: hibana::substrate::binding::advanced::Channel::new(7),
})
}
fn on_recv<'a>(
&'a mut self,
_channel: hibana::substrate::binding::advanced::Channel,
scratch: &'a mut [u8],
) -> Result<
hibana::substrate::wire::Payload<'a>,
hibana::substrate::binding::advanced::TransportOpsError,
> {
scratch[..4].copy_from_slice(&[1, 2, 3, 4]);
Ok(hibana::substrate::wire::Payload::new(&scratch[..4]))
}
fn policy_signals_provider(
&self,
) -> Option<&dyn hibana::substrate::policy::PolicySignalsProvider> {
Some(self)
}
}Binding rules:
poll_incoming_for_lane()is lane-local demux onlyon_recv()reads from the already selected channel and returns a borrowed payload viewpolicy_signals_provider()is the only public input source for slot-scoped signals
Supporting binding owners:
binding::advanced::{Channel, ChannelDirection, ChannelKey}identify stream and channel endpointsbinding::advanced::ChannelStoreis the storage contract when the binding owns multiple channelsbinding::advanced::TransportOpsErroris the canonical binding-side I/O error
Dynamic policy stays explicit:
- annotate the choreography with
Program::policy::<POLICY_ID>() - register a resolver with
set_resolver::<POLICY_ID, ROLE>(...) - read inputs through
ResolverContext::input(index)and attrs throughResolverContext::attr(id) - return
Result<RouteResolution, ResolverError>for route resolvers andResult<LoopResolution, ResolverError>for loop resolvers
Dynamic policy is supported for route/loop decision points only. Other control ops are rejected at projection time.
ResolverContext is intentionally small: input(index) and attr(id) are the
only public accessors.
The public policy-owner surface is intentionally narrow:
hibana::substrate::policy::PolicySlotowns the generic slot identity- an external policy engine may execute inside the same resolver slot, but that
engine is not part of the
hibanacrate surface - policy execution is fail-closed; verifier, trap, and fuel failures reject rather than falling through
Useful policy owners and helpers:
ContextIdandContextValuefor fixed-width policy inputs and attrsPolicyAttrsfor the attribute bag copied into resolver contextPolicySignalsfor slot-scoped inputs delivered byPolicySignalsProviderResolverRef::route_state()/ResolverRef::loop_state()for borrowed-state resolversResolverRef::route_fn()/ResolverRef::loop_fn()for stateless callbackshibana::substrate::policy::core::*for fixed metadata such asRV_ID,SESSION_ID,LANE,QUEUE_DEPTH,SRTT_US,PTO_COUNT,IN_FLIGHT_BYTES, andTRANSPORT_ALGORITHM
Example resolver:
const POLICY_ID: u16 = 7;
struct RoutePolicy {
preferred_arm: u8,
}
fn route_resolver(
policy: &RoutePolicy,
ctx: hibana::substrate::policy::ResolverContext,
) -> Result<hibana::substrate::policy::RouteResolution, hibana::substrate::policy::ResolverError>
{
if ctx.input(0) != 0 {
return Ok(hibana::substrate::policy::RouteResolution::Arm(policy.preferred_arm));
}
if ctx
.attr(hibana::substrate::policy::core::QUEUE_DEPTH)
.is_some_and(|value| value.as_u32() > 128)
{
return Err(hibana::substrate::policy::ResolverError::Reject);
}
Ok(hibana::substrate::policy::RouteResolution::Defer { retry_hint: 1 })
}
let route_policy = RoutePolicy { preferred_arm: 1 };
cluster.set_resolver::<POLICY_ID, 0>(
rv_id,
&CLIENT,
hibana::substrate::policy::ResolverRef::route_state(&route_policy, route_resolver),
)?;hibana::substrate::wire::{Payload, WireEncode, WirePayload} is the canonical
payload seam.
hibana::substrate::transport::advanced::TransportEvent is the canonical
transport event seam. Event-kind classification and metrics translation live in
the same advanced bucket.
If a payload type crosses the wire and is not already a codec type, implement
WireEncode plus WirePayload. Borrowed payload views use
type Decoded<'a> = ...; fixed-width by-value payloads stay on the same
contract with type Decoded<'a> = Self. Fixed-size decoders are canonical:
they reject trailing bytes, and non-wire local route materialization uses
WirePayload::synthetic_payload() rather than accepting prefix-only payloads.
Transport telemetry is surfaced two ways:
- resolvers read transport attrs through
ResolverContext::attr()andhibana::substrate::policy::core::* - transports emit semantic events through
transport::advanced::TransportEvent; the kind classifier istransport::advanced::TransportEventKind - codec failures report through
CodecError - transport failures report through
TransportError transport::advanced::TransportMetricsturns implementation-specific counters intoPolicyAttrs
Example transport-attr access:
let mut attrs = hibana::substrate::policy::PolicyAttrs::EMPTY;
let _ = attrs.insert(
hibana::substrate::policy::core::QUEUE_DEPTH,
hibana::substrate::policy::ContextValue::from_u32(3),
);
let transport_event = hibana::substrate::transport::advanced::TransportEvent::new(
hibana::substrate::transport::advanced::TransportEventKind::Ack,
42,
1200,
0,
);
let _ = (
attrs
.get(hibana::substrate::policy::core::QUEUE_DEPTH)
.map(|value| value.as_u32()),
transport_event.packet_number(),
);PolicyAttrs is the packed transport-observation view that resolvers see:
- use
PolicyAttrs::get()withhibana::substrate::policy::core::*identifiers such asLATENCY_US,QUEUE_DEPTH,RETRANSMISSIONS,CONGESTION_WINDOW, andTRANSPORT_ALGORITHM - transports own metric collection and publish packed attrs through
transport::advanced::TransportMetrics::attrs()
hibana does not define a built-in management protocol.
If an integration needs policy distribution, tap streaming, or any other
cluster-management session, that choreography lives outside the hibana crate.
hibana only provides the neutral seams needed to compose, project, attach,
and drive that choreography.
Management-style usage still follows the normal substrate path:
use hibana::g;
use hibana::substrate::program::project;
let mgmt_prefix = build_management_prefix();
let app = build_app();
let program = g::seq(mgmt_prefix, app);
let controller_program: hibana::substrate::program::RoleProgram<0> = project(&program);
let cluster_program: hibana::substrate::program::RoleProgram<1> = project(&program);
let controller = cluster.enter(
rv_id,
sid,
&controller_program,
hibana::substrate::binding::NoBinding,
)?;
let cluster_role = cluster.enter(
rv_id,
sid,
&cluster_program,
hibana::substrate::binding::NoBinding,
)?;
let _ = (controller, cluster_role);The canonical local validation flow is:
bash ./.github/scripts/check_policy_surface_hygiene.sh
bash ./.github/scripts/check_lowering_hygiene.sh
bash ./.github/scripts/check_compiled_descriptor_authority.sh
bash ./.github/scripts/check_frozen_image_hygiene.sh
bash ./.github/scripts/check_exact_layout_hygiene.sh
bash ./.github/scripts/check_raw_future_hygiene.sh
bash ./.github/scripts/check_message_monomorphization_hygiene.sh
bash ./.github/scripts/check_message_heavy_matrix.sh
bash ./.github/scripts/check_surface_hygiene.sh
bash ./.github/scripts/check_public_surface_budget.sh
bash ./.github/scripts/check_boundary_contracts.sh
bash ./.github/scripts/check_plane_boundaries.sh
bash ./.github/scripts/check_mgmt_boundary.sh
bash ./.github/scripts/check_resolver_context_surface.sh
bash ./.github/scripts/check_warning_free.sh
bash ./.github/scripts/check_direct_projection_binary.sh
bash ./.github/scripts/check_no_std_build.sh
bash ./.github/scripts/check_pico_size_matrix.sh
cargo check --all-targets -p hibana
cargo check --no-default-features --lib -p hibana
cargo test -p hibana --features std
cargo test -p hibana --test ui --features std
cargo test -p hibana --test policy_replay --features std
cargo test -p hibana --test public_surface_guards --features std
cargo test -p hibana --test substrate_surface --features std
cargo test -p hibana --test docs_surface --features stdThese checks keep the public surface small, keep no_std healthy, and guard
the compile-time guarantees described above.
Before pushing, also verify these invariants:
hibana/src/**/*.rsstays protocol-neutral- route authority stays
Ack | Resolver | Poll - static unprojectable route stays compile-error, not runtime repair
- typed projection stays intact
- substrate names do not leak back into the app surface