The two things to know first
The trait is synchronous.ntl-core contains no async fn at all, holds
no async runtime, and reaches for no ambient clock or randomness. That is what
keeps it building for wasm32-unknown-unknown, which CI enforces on every
commit. There is no .await anywhere on this page, and the trait has no
start or stop.
No adapter is implemented. adapters/web2, adapters/web3 and
adapters/legacy each contain a single doc comment and no code, and all three
are publish = false. There is no Web2Adapter, no Web3Adapter, no
LegacyAdapter, and no node.register_adapter. Nothing on this page can be
configured or run today; what it gives you is the contract to implement.
The crate is ntl_core — note the underscore in Rust paths. Adapter and
AdapterHealth are re-exported at the root; ExternalPayload, Protocol and
AdapterCapabilities live in ntl_core::adapter.
The Adapter trait
Five methods, all synchronous, quoted fromruntime/ntl-core/src/adapter.rs:
crate::Result<T> is Result<T, ntl_core::Error>; translation failures are
Error::Adapter(String).
The Send + Sync bound and the &self receivers together mean an adapter
holding mutable state — a sequence counter, an injected Rng — needs interior
mutability. The implementation below uses a Mutex.
Types
All four are defined inruntime/ntl-core/src/adapter.rs and are shown here
with their doc comments elided.
ExternalPayload
The generic container for external protocol data.AdapterCapabilities
Declared by the adapter, not inferred by the runtime.Protocol
AdapterHealth
Implementing an adapter
Every sample below is compiled and executed byruntime/ntl-core/tests/api_reference_adapter.rs. If the trait changes, that
test stops building — which is the only way documentation like this stays true.
Since no adapter ships, that file is also the reference implementation.
The struct
ManualClock stands in for
whatever clock the host injects; a production adapter would hold
Arc<dyn Clock>.
ingest
An adapter builds an unsigned signal. It does not sign, and it does not choose the origin identity or the emission timestamp —Node::emit stamps
those. build_unsigned_with takes the clock and RNG explicitly for exactly
that reason.
Signal::data("...")
behaves everywhere else in the crate.
emit
The metadata methods
correlation: false unless the adapter really does hold the external
connection open and match responses by correlation_id; the contract attaches
requirements to that flag, including a timeout, in
spec/adapter-contract.
Using one
The trait is object-safe, so an adapter can be held as&dyn Adapter. Nothing
in ntl-core holds a registry of adapters — whoever owns the transport owns
the adapter and calls ingest and emit directly.
What is not here yet
The last three are places where
spec/adapter-contract is ahead of the code: it is
normative about a registration and lifecycle surface that does not exist in
Rust yet, and it will arrive with the first real adapter. Until then,
cargo doc --open and the test above are the authority for the Rust API.