Phase21 API Symmetry#114
Open
JustinKovacich wants to merge 9 commits into
Open
Conversation
Generic letter `S` was overloaded — meant `Spawner` in `ClientDeps` and `SubscriptionHandle` in `ServerDeps` / `Server` / `ServerHandles` / all server impl blocks. This made downstream wrappers maintaining bounds against both sides duplicate work and couldn't read the two type signatures as parallel. Renames: - `ClientDeps`: generic `S` → `Sp` (Spawner). Generic order `<F, S, Tm, R, I>` → `<F, Tm, R, I, Sp>`. Field order reordered to match (factory, timer, e2e_registry, interface, spawner) so shared infrastructure (F, Tm, R) is positionally aligned with ServerDeps. - `ServerDeps`: generic `S` → `Sub` (SubscriptionHandle). Order already canonical (F, Tm, R, Sub). - `ServerHandles`: rename `S` → `Sub`. Order unchanged. - `Server` struct: generic `S` → `Sub`. Order `<R, S, F, Tm, H, Hsd, Hep>` → `<F, Tm, R, Sub, H, Hsd, Hep>`, matching ServerDeps. Default `Hep = Arc<EventPublisher<…>>` updated. - All four `impl<…> Server<…>` blocks updated. Where-clauses reordered to match the new generic order. - The tokio-only impl block at line 270 reordered its concrete type list `<R-conc, S-conc, F-conc, Tm-conc>` → `<F-conc, Tm-conc, R-conc, S-conc>`. - `Client::new_with_spawner_and_loopback<S>` → `<Sp>` (no ambiguity, but consistent). - `Client::new_with_deps<F, S, Tm>` → `<F, Tm, Sp>`. Internal `EventPublisher<R, S, H, T>` letter unchanged — it has no Spawner, so no collision exists inside that type. Internal `bind_dispatch::SpawnerDispatch<F, S>` is `pub(super)`, also unchanged. Affected user-facing type spellings (4 sites with explicit generics): bare_metal_server example, three integration tests spelling out `Server<...>` in concrete form, plus a `TestServer` type alias each in `src/server/mod.rs` and `tests/client_server.rs`. Verification: - `cargo check --workspace --all-features` clean. - Standalone bare-metal canary: `cargo build -p bare_metal_client` and `cargo build -p bare_metal_server` clean. - `cargo test --workspace --all-features --lib --tests -- --test-threads=1` — 528 lib + 26 integration tests pass. (Parallel flake unrelated to this change, pre-existing per `project_phase20_cleanup_complete.md`.) - `cargo run -p embassy_net_client` — live host-loopback wire-test green; SD `OfferService(0x5BAA)` exchanged end-to-end. Phase 21 sub-task 21a per `phase_21_api_symmetry.md`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds two convenience layers on top of the existing struct-literal
ClientDeps / ServerDeps construction route, both purely additive:
1. `ClientDeps::tokio(interface)` and `ServerDeps::tokio()` —
constructors that produce a fully-defaulted deps struct using
the tokio infrastructure types (TokioTransport, TokioTimer,
TokioSpawner, plus fresh Arc<Mutex<E2ERegistry>> /
Arc<RwLock<…>> handles). Gated to client-tokio / server-tokio
respectively.
2. Field-by-field fluent builders (`with_factory`, `with_timer`,
`with_e2e_registry`, `with_interface`, `with_spawner` on
ClientDeps; same minus the side-only fields on ServerDeps).
Each returns a new Deps with that single generic parameter
updated, leaving the others intact. Available under the base
client / server features.
The motivating use case is the mid-tier customization that has no
escape hatch today: tokio transport + tokio time + custom
spawner. Previously this required hand-assembling the full
ClientDeps struct literal with all 5 fields. With 21c:
let deps = ClientDeps::tokio(addr).with_spawner(MySpawner);
The struct-literal path stays available; nothing existing is
removed or deprecated.
Doc-tests added on each new public surface. The Server doc-test
binds the result to `Server<_, _, _, _>` so the struct's defaults
on H/Hsd/Hep kick in (those defaults don't apply at method-call
sites, only at type-position elision).
Pre-existing failure `lib.rs - transport (line 309)` is unchanged
by this commit (reproduced on stashed tip pre-21c).
Verification:
- `cargo check --workspace --all-features` clean.
- `cargo test --doc --all-features` — 11 passed (was 9), 1
pre-existing failure unchanged.
- `cargo test --workspace --all-features --lib --tests
-- --test-threads=1` — 528 lib + 26 integration tests pass.
- `cargo run -p embassy_net_client` — live host-loopback wire-test
green.
Phase 21 sub-task 21c per `phase_21_api_symmetry.md`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds chainable `with_*` setters on `ServerConfig` so callers can override individual fields starting from `ServerConfig::new(...)` without rewriting the full struct literal. Pure additive — public fields and the existing constructor stay available. New methods: - `with_major_version(u8)` — defaults to 1. - `with_minor_version(u32)` — defaults to 0. - `with_ttl(u32)` — TTL in seconds, defaults to 3. - `with_event_group(u16)` — append-only; panics on capacity (matches the implicit contract of the public `event_group_ids` field). - `try_with_event_group(u16) -> Result<Self, Self>` — fallible variant; on `Err` returns the unmodified config (heapless::Vec guarantees push doesn't mutate on overflow). No `Default` impl — `service_id` / `instance_id` have no sensible defaults. Two new unit tests: - `server_config_builder_chain_overrides_each_field` — exercises the chain end-to-end. - `server_config_try_with_event_group_rejects_at_capacity` — locks in the no-mutation-on-overflow contract. Verification: - `cargo check --workspace --all-features` clean. - `cargo test --workspace --all-features --lib --tests -- --test-threads=1` — 530 lib (+2) + 26 integration tests pass. Phase 21 sub-task 21e per `phase_21_api_symmetry.md`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds `client::ClientChannelTypes<P>`, a marker trait whose where-clause enumerates the seven `OneshotPooled` / `BoundedPooled` / `UnboundedPooled` entries that `define_static_channels!` must declare for `Client` to compile against the resulting factory. A blanket impl makes any `ChannelFactory` that satisfies all seven automatically satisfy the trait. Today the trait is discoverability-only — stable Rust does not elaborate where-clause bounds on a trait, so each `impl<…> Client<…>` block must still repeat the seven bounds inline. The trait's value right now is rustdoc surface: one page enumerates the required pool entries with a copy-pasteable shape, and it is reachable from three places: - Crate root (re-exported in `lib.rs`). - The `define_static_channels!` macro doc, via a new "Required entries for Client" cross-link. - The trait page itself documents the elaboration limitation honestly so users know not to depend on it as a generic bound. Also re-exports `ControlMessage`, `SendMessage`, `ReceivedMessage` at crate root so the channel-pool item types are reachable as `simple_someip::ControlMessage` etc., not only via `simple_someip::client::ControlMessage`. (`ClientUpdate` was already re-exported.) Replaces the previous comment-block documentation in `src/client/mod.rs` (lines 67-87) — the trait's rustdoc carries the same content in a discoverable location. When stable Rust gains where-clause elaboration on traits, the per-impl-block repetition collapses to a single `C: ClientChannelTypes<P>` supertrait without changing the outward contract; the trait was designed for that future. Verification: - `cargo check --workspace --all-features` clean. - `cargo test --doc --all-features` — 11 passed (unchanged by 21d), 1 pre-existing failure (`lib.rs - transport (line 309)`) still pre-existing. - `cargo test --workspace --all-features --lib --tests -- --test-threads=1` — 530 lib + 26 integration tests pass. Phase 21 sub-task 21d per `phase_21_api_symmetry.md`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses the HIGH and MEDIUM findings from the adversarial review of 21a/21c/21d/21e (full review captured in conversation; key findings below). No new behavior, no further architecture; pure surface hygiene + scope-completion. ## HIGH 1. **server/mod.rs: stale `S` in `publisher()` rustdoc.** 21a renamed the Subscription generic to `Sub` everywhere except this single docstring on the most-read accessor. Now reads `Arc<EventPublisher <R, Sub, H, T>>` consistently. 2. **`with_ttl` takes `Duration`.** 21e shipped `with_ttl(u32)` which leaves units ambiguous at the call site. Now takes `core::time::Duration`; sub-second precision truncates (rounded down per `Duration::as_secs`); over-`u32::MAX`-seconds saturates. Matches the original scoping-doc intent. Two unit tests lock the truncation + saturation behavior. 3. **Client struct asymmetry rationale.** Reviewer flagged that `Client<MessageDefinitions, R, I, C>` doesn't carry F/Tm/Sp the way `Server<F, Tm, R, Sub, …>` does. The asymmetry is structural (Client's value retains no transport/timer/spawner reference once constructed), not an oversight. Now documented as such on Client's struct rustdoc with explicit cross-reference to `ServerDeps`. 4. **`#[must_use]` on `try_with_event_group`.** Every other consuming setter on `ServerConfig` is `#[must_use]`; this one was missed. 5. **`ServerConfig::new` widened.** F5's stated complaint was the noisy 4-arg `new(interface, port, sid, iid)`. 21e shipped only the non-noisy override setters; the four-arg `new` remained. Now: `new(service_id, instance_id)` with `Ipv4Addr::UNSPECIFIED` / port `0` defaults, plus `with_interface` / `with_local_port` setters. Docstring is explicit that the defaults are dev-friendly, not production-ready. Net call-site change: 43 sites across 10 files rewritten via `ServerConfig::new(IFACE, PORT, SID, IID)` → `ServerConfig::new(SID, IID).with_interface(IFACE).with_local_port(PORT)`. Pure semver-major mechanical rewrite; no behavior change. ## MEDIUM 6. **`ClientChannelTypes` no longer at crate root.** Reviewer flagged that re-exporting at crate root tempts users into `fn f<C: ClientChannelTypes<P>>()` which fails on stable Rust's trait-elaboration limit. Trait stays in `client::` (so `define_static_channels!` users still find it via the macro cross-link), but does not appear in `simple_someip::*` autocomplete. Comment in `lib.rs` records the rationale. 7. **`ControlMessage`/`SendMessage`/`ReceivedMessage` no longer at crate root.** Same reasoning — they are implementation-detail-with-a-public-name (reachable for the `define_static_channels!` macro at `simple_someip::client::*`) rather than first-class crate-API types. Crate-root re-export would lock their shape into the public-API contract. 8. **`with_spawner` split into `with_spawner` (Send) + `with_local_spawner` (Local).** 21c's unbounded `with_spawner<Sp2>` deferred the `Spawner` / `LocalSpawner` bound check to `Client::new_with_deps`, producing diagnostics that pointed at the wrong call site. Now each `with_*` enforces its bound at the builder method, so type errors surface at the actual mistake. ## NOT addressed (reviewer flagged, deferred) - Reordering `Client<MessageDefinitions, R, I, C>` itself — see #3 above; documented as deliberate. - 21c integration test for `ClientDeps::tokio()` + `with_*` — doc-tests cover type-composition; behavioral coverage can wait. - 21e duplicate-event-group + panic-message tests — tracked, low risk. ## Verification - `cargo check --workspace --all-features` clean. - `cargo test --workspace --all-features --lib --tests -- --test-threads=1` — 532 lib (+2 from new with_ttl tests) + 26 integration tests pass. - `cargo test --doc --all-features` — 12 passed (was 11; +1 from new `ServerConfig::new` doc-example). Pre-existing `lib.rs - transport (line 309)` failure unchanged. - `cargo run -p embassy_net_client` — live host-loopback wire-test green. Phase 21 sub-task cleanup per `phase_21_api_symmetry.md`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
21b — Server::new and the *_with_deps variants now return a (Server, ServerHandles, run-future) tuple mirroring Client::new. The single returned run-future drives the receive loop *and* the SD OfferService announcement loop concurrently via select; the dispatcher topology where a co-located Client emits the offers opts in via ServerConfig::with_announce(false) instead of "just don't call this method." Server::announcement_loop / announcement_loop_local and the AtomicBool latch they used to fight over are removed — single entry point makes the double-spawn failure mode structurally impossible. The previous ServerHandles deps-bundle is renamed to ServerStorage; the ServerHandles name now belongs to the post-construction accessor struct. Server::set_local_port also went away — the constructor's bind-time back-fill made it vestigial and mutating it post-bind would lie to peers. 21F — SubscriptionHandle's subscribe / unsubscribe futures are promoted from RPIT to named GATs (SubscribeFuture<'a> / UnsubscribeFuture<'a>) so Server::run can declare + Send + 'static explicitly with for<'a> Sub::SubscribeFuture<'a>: Send bounds in its where clause. Compile errors for tokio::spawn-vs-!Send-handle mismatches now surface at the library boundary instead of inside tokio::spawn's bound check. for_each_subscriber stayed RPIT (no Server::run-side Send-bound user). Constructors call a private run_inner that's auto-trait-inferred so embassy paths (EmbassyNetSocket: !Sync) keep using the constructors. Run-loop logic moved out of &self methods on Server into free async fns in src/server/runtime.rs; Server::run / run_with_buffers clone the cheap shared-handles into an async move so the returned future is independent of the &self borrow. The cancel-safety SAFETY comment on the recv_from select is preserved at the new call site. Net diff: -254 lines despite adding GATs, the new submodule, and a behavioral test for with_announce(false) suppression. 543 tests pass (518 lib + 5 embassy-net + 11 client_server integration + 9 bare-metal/static-channels witnesses + a 0-test smoke + 3 ignored vsomeip docker-deps). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Phase 21 finalizes the 0.8.0 API ergonomics/symmetry pass by reshaping the Server constructor/run surface to mirror Client, aligning generic ordering, and improving configurability + docs for static channels and deps bundles.
Changes:
- Reshapes
Server::new*to return(Server, ServerHandles, run_future)and folds SD announcements into the combined run future (withServerConfig::with_announce(false)to suppress). - Promotes
SubscriptionHandle::{subscribe,unsubscribe}return types to GATs (SubscribeFuture/UnsubscribeFuture) to makeSendbounds explicit at the API boundary. - Adds fluent builders (
ServerConfig::with_*,ClientDeps::tokio()/ServerDeps::tokio()+with_*) and improves discoverability docs for static channel requirements.
Reviewed changes
Copilot reviewed 15 out of 16 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/vsomeip_sd_compat.rs | Updates SD compatibility tests to spawn the new combined server run future. |
| tests/client_server.rs | Updates test helpers/call sites for new Server::new tuple return and config builder. |
| tests/bare_metal_server.rs | Migrates mock SubscriptionHandle impl to GAT futures; adapts to new server constructor return. |
| tests/bare_metal_e2e.rs | Same migration as above for the bare-metal E2E test harness. |
| src/static_channels/mod.rs | Adds rustdoc guidance pointing users to ClientChannelTypes requirements. |
| src/server/subscription_manager.rs | Introduces GATs on SubscriptionHandle + updates tokio/bare-metal impls accordingly. |
| src/server/sd_state.rs | Updates unit tests to use new ServerConfig builder API. |
| src/server/runtime.rs | New module extracting server runtime loops (recv, announce, SD handling) as free async fns. |
| src/server/mod.rs | Major server API reshape: new ServerHandles, ServerStorage rename, new run/run_with_buffers futures, fluent ServerConfig. |
| src/lib.rs | Updates public re-exports (ServerStorage) and documents intentional non-reexports on client channel types. |
| src/client/mod.rs | Adds ClientChannelTypes marker trait + tokio-defaulted ClientDeps and fluent builders; reorders deps generics. |
| simple-someip-embassy-net/tests/loopback.rs | Adapts embassy-net loopback tests to the new server run model and SubscriptionHandle GATs. |
| examples/embassy_net_client/src/main.rs | Updates embassy-net example to spawn server via run_with_buffers and new config builder. |
| examples/client_server/src/main.rs | Updates dispatcher-topology example to with_announce(false) and constructor-returned run future. |
| examples/bare_metal_server/src/main.rs | Updates bare-metal server example to spawn the combined run future. |
| CHANGELOG.md | Documents the 0.8.0 Phase 21 breaking changes and migration guidance. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Nothing in the 0.7.0→0.8.0 stretch was ever published (no v0.8.0 git tag, no crates.io release), so phase 20 cleanup + phase 21 ergonomics merge into the 0.8.0 baseline rather than getting their own version bump. The "Breaking" framing in the phase 21 section was relative to a transient in-tree state nobody saw, so it's been retitled around 0.7.0 (the actual external baseline) where it remained meaningful, and dropped where it didn't. Section structure: [0.8.0] now contains the phase 21 narrative sub-section followed by Added/Changed/Fixed clusters labeled by era (phase 20 cleanup vs. phase 17 baseline) so the chronological context is still visible to a reader walking the file top-down. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
40f3470 to
03132d8
Compare
Five comments, four substantive fixes + one prose tweak.
Multi-spawn race latch on Server::run / run_with_buffers: a caller
who spawned the constructor's run-future *and* called server.run()
or server.run_with_buffers() afterward would have two run-futures
racing on the same SD/unicast sockets and the SD session counter,
silently corrupting wire output. Adds an Arc<AtomicBool> `started`
field to Server (initialised false in all 4 constructor paths) and
a compare_exchange first-poll latch in both run_inner and
run_with_buffers — the second-to-be-polled future short-circuits
with Err(Error::InvalidUsage("server_already_running")). New unit
test second_run_future_returns_already_running locks both paths.
TransportSocket::recv_from contract: the cancel-safety requirement
the server runtime depends on lives on the trait now (its own
"# Cancel safety" rustdoc section), not as a free-floating remark
inside runtime::recv_loop. The previous in-source comment also
claimed "must stay FusedFuture + Unpin" which is wrong — the
futures are pinned in place via pin_mut! and don't need Unpin.
Comment rewritten to match.
ClientChannelTypes rustdoc: said the trait "is re-exported at
crate root" but it intentionally isn't (only at
crate::client::ClientChannelTypes — the crate root re-export was
deliberately dropped during the 21d cleanup to avoid tempting
generic users into the bound-elaboration cliff). Doc updated.
vsomeip_sd_compat tests: removed the no-op announce_handle shim
introduced during the 21b run-loop reshape. Announcements are now
folded into the combined run-future spawned as server_handle, so
keeping a dummy handle around made later abort/diagnostic logic
misleading.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`tracing-core` 0.1.33+ declares `extern crate alloc;` unconditionally
(`tracing-core-0.1.36/src/lib.rs:150`), so any target whose sysroot
ships `core` only — e.g. the AURIX TriCore LLVM-IR proxy used by the
halo build — fails to compile the dep tree even though simple-someip's
own bare-metal paths never touch alloc. Move tracing behind a feature
so those targets can opt out; std users keep tracing automatically via
the `std → tracing` implication so behavior is unchanged on host.
Internal call sites route through a new private `crate::log` module:
when `tracing` is on it re-exports `tracing::{debug,error,info,trace,
warn}` verbatim; when off it expands to a single `noop!` macro that
wraps `core::format_args!` in `if false { … }`. The dead block keeps
captured variables borrowed (no spurious `unused_variables` lints) and
optimizes out, leaving zero log code at the linker.
Six sites in `server/runtime.rs` were using tracing's structured-field
DSL (`error = %e, "msg"`); rewritten to plain format-args
(`"msg: {e}"`) since the no-op shim's `format_args!` can't accept the
DSL. Equivalent for log purposes.
Verified:
- `cargo tree --target thumbv7em-none-eabihf --no-default-features
--features client,server,bare_metal --edges normal` shows zero
tracing-related entries (the failing extern crate alloc no longer
reachable).
- cortex-m4f release rlib still has zero `__rust_alloc` references.
- 532 lib + 20 integration tests pass under default features.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 21 is the API ergonomics pass that closes out 0.8.0 (audit was run in late April against
9b4b4c8; six sub-tasks shipped over the back half of phase development). It does not introduce new functionality — every change is about making the existingClient/Server/*Deps/transportsurface easier to read, harder to misuse, and consistent between the two sides.The motivating audit found five concrete frictions: (1)
ClientandServerhad divergent generic-parameter orders that punished anyone maintaining bounds against both; (2)Server::newwas anasync fnreturningSelf, so callers had to remember to also spawnserver.run()andserver.announcement_loop()separately, and forgetting either was silent; (3) mid-tierDepscustomization required hand-assembling the full struct literal even to swap one field; (4) the channel typesdefine_static_channels!needs were documented only in source comments and example code; (5)ServerConfigassembly was struct-literal only. All five are fixed.The work is bundled into 0.8.0 because no v0.8.0 tag was ever cut and nothing was published, so the "breaking" framing in any
internal phase 21 commit is breaking against transient in-tree state nobody saw — the only baseline that matters externally is 0.7.0.
Headline API changes (vs. 0.7.0)
Server::newreturns a tuple now — same shape asClient::new:The single returned run-future drives both the receive loop and the SD OfferService announcement loop concurrently via select. The dispatcher topology where a co-located Client emits the offers (used in examples/client_server) opts in via ServerConfig::with_announce(false) instead of "just don't call announcement_loop."
SubscriptionHandle::subscribe / unsubscribe are now GATs — SubscribeFuture<'a> / UnsubscribeFuture<'a> named associated types instead of RPIT. Lets Server::run declare + Send + 'static explicitly with for<'a> Sub::SubscribeFuture<'a>: Send bounds, so tokio::spawn-vs-!Send-handle mismatches surface at the library boundary instead of inside tokio::spawn's bound check.
Generic-parameter order aligned — Client<MessageDefinitions, F, Tm, R, I, Sp, C> and Server<F, Tm, R, Sub, H, Hsd, Hep> now share F/Tm/R in the same positions. Letter-collisions between Spawner (S on Client) and SubscriptionHandle (S on Server) resolved with Sp and Sub respectively.
Tokio-defaulted Deps builders — ClientDeps::tokio() / ServerDeps::tokio() produce a fully-defaulted bundle; chain with_factory / with_timer / with_e2e_registry / with_subscriptions / with_spawner / with_local_spawner to override individual fields without spelling the rest out.
ServerConfig fluent builder —
ServerConfig::new(svc, inst).with_interface(…).with_local_port(…).with_ttl(…).with_event_group(…).with_announce(…). Existing struct-literal route stays open; builder is the recommended path in docs.Discoverable channel types — new ClientChannelTypes trait alias + client::channels rustdoc surface for the types define_static_channels! populates.
Smaller surface deletions
config.local_port before Server::new returns, so post-construction mutation would lie to peers.
Architectural cleanup
The receive loop, announcement loop, and SD-message handling are factored out of &self methods on Server into free async fns in src/server/runtime.rs. Server::run clones the cheap shared-handles into an async move so the returned future is independent of the &self borrow — that's what lets the constructor return (Server, ServerHandles, run-future) without aliasing the same struct.
Net diff for the run-loop work alone: −254 lines despite adding GATs, a new submodule, a behavioral test for with_announce(false) suppression, and the post-review fixes (cancel-safety SAFETY comment restored, ServerHandles re-exported, doctest examples switched from ignore to no_run).
Out of scope (parked)
profile someday).
Test plan
client_server integration + 9 bare-metal/static-channels witnesses + 1 smoke, 543 passing, 3 ignored vsomeip docker-deps.