Skip to content

apomesh-substrate-net

The shared HTTP egress primitive for the substrate implementation planes. This crate owns one thing — the canonical reqwest::Client constructor whose TLS trust anchors are the webpki-roots Mozilla CA bundle compiled into the binary, not the host OS trust store. Every substrate HTTP caller composes it rather than calling reqwest::Client::new() directly. It is a leaf: it depends on no apomesh crate, so editing it recompiles its consumers while editing a consumer never recompiles it. Reach for it whenever substrate code needs an outbound HTTP client.

  • Layer: support — a workspace utility leaf that sits beside the layering, not a plane of its own. See Control & execution planes for the planes that make the outbound calls it backs.
  • Depends on: no workspace crates — only the HTTP/TLS trees (reqwest with the rustls feature, rustls, webpki-roots). This leaf-ness is deliberate.
  • Consumed by: apomesh-substrate-llm (the LLM + embedding provider clients) and apomesh-substrate-tools (the web tools) both build their reqwest clients on it.
  • The webpki-roots TLS trust config — a private webpki_tls_config builds a rustls ClientConfig from the binary- bundled Mozilla CA root set, reusing a process-installed crypto provider or falling back to aws-lc-rs. Server authentication only (with_no_client_auth) — unrelated to the worker/orchestrator mTLS path.
  • The client constructorswebpki_client / webpki_client_builder (unbounded) and webpki_unary_client / webpki_unary_client_builder (pre-bounded with the unary deadline). The builders let a consumer chain a redirect policy, User-Agent, or default headers before build.
  • UNARY_REQUEST_TIMEOUT — a 120-second total-request deadline for a substrate unary HTTP call. Without it, a stalled upstream (TCP up, bytes never flowing) wedges a dispatch forever; a wedged unary call maps at the provider boundary to a typed, retry-eligible error and replans. It is a total-request bound and therefore MUST NOT be used on the streaming SSE long-poll, whose body legitimately outlives it — that path keeps the unbounded webpki_client.

The binary-bundled roots matter because substrate HTTP clients are often rebuilt inside the bubblewrap jail (the wrapped web-search tool and web_fetch’s page-qualifier reconstruct their provider in the sandboxed tool-runner). A plain reqwest::Client::new() there panics with “No CA certificates were loaded from the system” because rustls’s platform verifier finds no host trust store; the bundled roots remove that dependency entirely.

This crate does not own outbound-request policy beyond TLS trust and the unary deadline — no SSRF allow/deny filtering and no proxy configuration live here. Those, where they apply, belong to the calling plane, not this leaf.

Verified against lib.rs:

  • webpki_client() -> reqwest::Client — the drop-in, unbounded replacement for reqwest::Client::new(); used by the streaming path.
  • webpki_client_builder() -> reqwest::ClientBuilder — the same trust config, returned as a builder to chain further steps.
  • webpki_unary_client() -> reqwest::Client — the unary-bounded client (carries UNARY_REQUEST_TIMEOUT); used by non-streaming dispatch / batch paths.
  • webpki_unary_client_builder() -> reqwest::ClientBuilder — the unary-bounded builder counterpart.
  • UNARY_REQUEST_TIMEOUT: Duration — the 120-second total-request deadline the unary constructors apply.
use apomesh_substrate_net::{webpki_unary_client_builder, webpki_client};
// A unary caller: bounded client with a custom User-Agent chained on.
let unary = webpki_unary_client_builder()
.user_agent("apomesh-substrate/llm")
.build()
.expect("webpki-roots reqwest client config is valid");
// A streaming caller: unbounded, so an SSE long-poll is not cut off.
let streaming = webpki_client();
# let _ = (unary, streaming);