You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Sibling of #51. #51 gives stringly/anyhow surfaces typed error enums. This issue is the complement: the typed enums already exist across L1/L2/L3, but their cross-seam conversions are not wired, so callers bridge by hand with .map_err(...). Right-size each seam's return type and add the missing From impls so ? just works. Rooted on origin/dev/m1 (f9022398), pre-cleave flat monorepo. Note: videre-sdk is still nexum-venue-sdk on dev/m1 until nullislabs/shepherd#454 merges.
Problem
Seam methods return one error type while their callers need another, and no From edge is wired, so every call site hand-writes a bridge. The user's canonical case is submit(): VenueClient::submit returns VenueError, IntentClient::submit returns ClientError, but ClientError::Venue(VenueError) has no conversion, so five IntentClient/Quoted methods carry .map_err(ClientError::Venue).
The typed errors are correct; only the From edges are missing. The reference wiring already in-tree is nexum-sdkChainError (host.rs:123-171): two #[from] in, one From<ChainError> for Fault out, so strategies aggregate chain calls with bare ?.
cow_orderbook.rs:148, 150, 171 re-wrap errors that already have #[from].
L1 example modules on_block export and the config_err/address shims.
Proposal
Right-size nothing that is already correct (every seam return type stays); add the missing From edges and delete the hand bridges.
1. From<VenueError> for ClientError (headline)
VenueError is a wit-bindgen enum with no Display and no std::error::Error impl, so #[from] will not compile (thiserror #[from] implies #[source], which requires Error). Keep the variant as-is and add a hand-written From:
// crates/nexum-venue-sdk/src/client.rs#[error("venue error: {0:?}")]Venue(VenueError),// unchanged: no #[from], no #[source]implFrom<VenueError>forClientError{fnfrom(e:VenueError) -> Self{ClientError::Venue(e)}}
Deletes all 5 .map_err(ClientError::Venue). cow-venue::CowClient (returns ClientError verbatim) inherits the fix.
2. From<ActorFault> for VenueError in videre-host
videre-host owns its own bindgen VenueError (distinct from nexum_venue_sdk::VenueError), so this From<Foreign> for Local is orphan-legal here. venue_fault is a total uniform fold (VenueError::Unavailable(format!("adapter {fault}"))), so a From with the same body lets self.actor.call(..).await? fold at all 5 seams; delete the helper.
3. cow_orderbook.rs redundant wraps -> bare ?
Network(#[from] reqwest::Error) (:196) and Decode(#[from] serde_json::Error) (:198) already exist. Replace .map_err(CowApiError::Network) (:148, :150) and .map_err(CowApiError::Decode) (:171) with ?. Keep :132BadPath(format!("{path:?}: {e}")) (adds path context).
4. Module on_block edge -> ?; Ok(())
modules/examples/{balance-tracker:61, http-probe:70, price-alert:78}/src/lib.rs end with strategy::on_block(...).map_err(Into::into). The SDK host::Fault -> wire Fault fold is a structural 7-case enum fold, From-backed, and ? applies it. stop-loss/src/lib.rs:68 already proves the shape:
builder.rs:55 -> Ok(ProviderPool::from_config(ctx.config).await?) (anyhow blanket From). Supervisor .map_err(Error::from)? sites are dead (? already applies From); delete. These are the anyhow surface and overlap #51's remit.
6. Optional: From<{FetchError,ConfigError,AddressParse}> for host::Fault in nexum-sdk
Kills the duplicated config_err (two modules) and the invalid_input(e.to_string()) address shim. All three are L1-internal, orphan-legal, no new deps. Keep the context-injecting closures (fetch_err(url, &e) adds the URL; stop-loss/strategy.rs:190 adds a bespoke message) as closures.
Legitimate map_err to keep
Do not "fix" these. The conversion must exist; the spelling is correct.
WIT/ABI lowering: shepherd-cow-host/src/ext_cow.rs:218, 237 (cow_error_to_wit, a real lossy 7-to-3 projection), the generated mirror glue in nexum-sdk/src/wit_bindgen_macro.rs and shepherd-sdk/src/wit_bindgen_macro.rs. Per-cdylib bindgen types forbid a shared From. This is the ratified Fault-boundary pattern.
Semantic classifiers: host/error.rsFrom<ProviderError> for ChainError (JSON-RPC code -> variant) and From<StorageError> for Fault; faults.rs:61From<host::Fault> for VenueError (caller-shaped faults -> retryable Unavailable, deliberately lossy, hand From not #[from]).
#[source]-with-context leaf construction: provider_pool.rs (chain/url fields), local_store_redb.rs (per-op tag), BuildError::{Chain,Store,Ext,Logs} at builder.rs:178-181 (four slots, one anyhow::Error source, #[from] cannot disambiguate), actor.rs:101-102 (Trap carries a mark_dead() side effect), classification_data.rs:69 (stringify to keep Clone+Eq).
Semantic dispatch: run.rs:138 match on CowApiError, registry.rs:432 charge-then-rethrow.
Cross-repo coordination
The orphan rule is per-crate, so no proposed From became illegal from the three-repo cleave. With the cleave done, the coordination shape is:
The cleanup deletes helpers (venue_fault, config_err, the .map_err closures) and rewrites call sites spanning L1+L2+L3. The L1 conversions and deletions land in this repo first; the L2 and L3 call-site rewrites follow as separate downstream PRs merged in dependency order (L1 -> L2 -> L3) with matching rev-pin bumps between each.
transport.rs folds (optional) convert L2-local sources into L1 targets, coupling L2 to the exact L1 error shape. With no single-workspace cargo check any more, the L2 PR's own build against the pinned L1 rev is what proves those Froms resolve and the deleted helpers' call sites still compile.
The #[from]-won't-compile-on-VenueError fact and the two-VenueError distinction (guest SDK vs videre-host bindgen) still hold and should be regression-guarded per repo.
Timing
The three-repo cleave is done. The mechanism (right-sized seam types and the L1-side From impls) lands in this repo; the videre-host and module call-site conversions follow as separate downstream PRs with rev-pin bumps in dependency order (L1 -> L2 -> L3).
Acceptance criteria
From<VenueError> for ClientError added (hand-written, not #[from]); the 5 .map_err(ClientError::Venue) sites use bare ?.
From<ActorFault> for VenueError added in videre-host; venue_fault deleted, 5 seams use ?.
cow_orderbook.rs:148/150/171 use bare ? over the existing #[from].
No removable .map_err(Into::into) remains at the module on_block edge or builder.rs:55.
? works across the L1/L2/L3 seams with no hand bridge for a conversion a From can express.
Every remaining map_err is either an ABI-boundary lowering (WIT Fault/cow-api-error) or a documented semantic remap / context-add / #[source] leaf construction.
Problem
Seam methods return one error type while their callers need another, and no
Fromedge is wired, so every call site hand-writes a bridge. The user's canonical case issubmit():VenueClient::submitreturnsVenueError,IntentClient::submitreturnsClientError, butClientError::Venue(VenueError)has no conversion, so fiveIntentClient/Quotedmethods carry.map_err(ClientError::Venue).The typed errors are correct; only the
Fromedges are missing. The reference wiring already in-tree isnexum-sdkChainError(host.rs:123-171): two#[from]in, oneFrom<ChainError> for Faultout, so strategies aggregate chain calls with bare?.Error topology today
Fault(wire, 7-case)nexum-runtime/src/bindings.rshost::Fault(SDK-neutral)nexum-sdk/src/host.rs:33From<FetchError/ConfigError/AddressParse>ChainErrornexum-sdk/src/host.rs:123FetchError/ConfigError/AddressParsenexum-sdk/src/{http,config,address}.rsFrom<_> for Fault; hand-folded per moduleVenueError(bindgen, noDisplay)nexum-venue-sdk/src/bindings.rsFroms wired; outbound intoClientErrormissingClientErrornexum-venue-sdk/src/client.rs:129Body(#[from])ok;Venue(VenueError)has noFromActorFault->VenueError(videre-host)videre-host/src/registry.rsvenue_faulthelper, 5 sitesCowApiError(host)shepherd-cow-host/src/cow_orderbook.rs:186#[from]on Network/Decode already, but still hand-wrappedSeams that mismatch and hand-bridge today:
IntentClient::{quote,submit,status,cancel}+Quoted::submitreturnClientError, innerVenueClient::*returnVenueError, noFrom. (client.rs:65, 78, 85, 92, 118)VenueInvoker::{derive_header,quote,submit,status,cancel}returnVenueError, inneractor.callreturnsActorFault, folded byvenue_fault. (registry.rs:201, 211, 224, 234, 244)cow_orderbook.rs:148, 150, 171re-wrap errors that already have#[from].on_blockexport and theconfig_err/address shims.Proposal
Right-size nothing that is already correct (every seam return type stays); add the missing
Fromedges and delete the hand bridges.1.
From<VenueError> for ClientError(headline)VenueErroris a wit-bindgen enum with noDisplayand nostd::error::Errorimpl, so#[from]will not compile (thiserror#[from]implies#[source], which requiresError). Keep the variant as-is and add a hand-writtenFrom:Deletes all 5
.map_err(ClientError::Venue).cow-venue::CowClient(returnsClientErrorverbatim) inherits the fix.2.
From<ActorFault> for VenueErrorin videre-hostvidere-hostowns its own bindgenVenueError(distinct fromnexum_venue_sdk::VenueError), so thisFrom<Foreign> for Localis orphan-legal here.venue_faultis a total uniform fold (VenueError::Unavailable(format!("adapter {fault}"))), so aFromwith the same body letsself.actor.call(..).await?fold at all 5 seams; delete the helper.3.
cow_orderbook.rsredundant wraps -> bare?Network(#[from] reqwest::Error)(:196) andDecode(#[from] serde_json::Error)(:198) already exist. Replace.map_err(CowApiError::Network)(:148, :150) and.map_err(CowApiError::Decode)(:171) with?. Keep:132BadPath(format!("{path:?}: {e}"))(adds path context).4. Module
on_blockedge ->?; Ok(())modules/examples/{balance-tracker:61, http-probe:70, price-alert:78}/src/lib.rsend withstrategy::on_block(...).map_err(Into::into). The SDKhost::Fault-> wireFaultfold is a structural 7-case enum fold,From-backed, and?applies it.stop-loss/src/lib.rs:68already proves the shape:Cosmetic, lowest priority; the conversion stays, only the explicit
map_errgoes.5.
builder.rs:55+ supervisor.map_err(Error::from)?builder.rs:55->Ok(ProviderPool::from_config(ctx.config).await?)(anyhow blanketFrom). Supervisor.map_err(Error::from)?sites are dead (?already appliesFrom); delete. These are the anyhow surface and overlap #51's remit.6. Optional:
From<{FetchError,ConfigError,AddressParse}> for host::Faultin nexum-sdkKills the duplicated
config_err(two modules) and theinvalid_input(e.to_string())address shim. All three are L1-internal, orphan-legal, no new deps. Keep the context-injecting closures (fetch_err(url, &e)adds the URL;stop-loss/strategy.rs:190adds a bespoke message) as closures.Legitimate map_err to keep
Do not "fix" these. The conversion must exist; the spelling is correct.
shepherd-cow-host/src/ext_cow.rs:218, 237(cow_error_to_wit, a real lossy 7-to-3 projection), the generated mirror glue innexum-sdk/src/wit_bindgen_macro.rsandshepherd-sdk/src/wit_bindgen_macro.rs. Per-cdylib bindgen types forbid a sharedFrom. This is the ratifiedFault-boundary pattern.host/error.rsFrom<ProviderError> for ChainError(JSON-RPC code -> variant) andFrom<StorageError> for Fault;faults.rs:61From<host::Fault> for VenueError(caller-shaped faults -> retryableUnavailable, deliberately lossy, handFromnot#[from]).cow_orderbook.rs:132BadPath,http-probe/strategy.rs:46fetch_err(URL injection).#[source]-with-context leaf construction:provider_pool.rs(chain/url fields),local_store_redb.rs(per-op tag),BuildError::{Chain,Store,Ext,Logs}atbuilder.rs:178-181(four slots, oneanyhow::Errorsource,#[from]cannot disambiguate),actor.rs:101-102(Trapcarries amark_dead()side effect),classification_data.rs:69(stringify to keepClone+Eq).run.rs:138match onCowApiError,registry.rs:432charge-then-rethrow.Cross-repo coordination
The orphan rule is per-crate, so no proposed
Frombecame illegal from the three-repo cleave. With the cleave done, the coordination shape is:venue_fault,config_err, the.map_errclosures) and rewrites call sites spanning L1+L2+L3. The L1 conversions and deletions land in this repo first; the L2 and L3 call-site rewrites follow as separate downstream PRs merged in dependency order (L1 -> L2 -> L3) with matching rev-pin bumps between each.transport.rsfolds (optional) convert L2-local sources into L1 targets, coupling L2 to the exact L1 error shape. With no single-workspacecargo checkany more, the L2 PR's own build against the pinned L1 rev is what proves thoseFroms resolve and the deleted helpers' call sites still compile.#[from]-won't-compile-on-VenueErrorfact and the two-VenueErrordistinction (guest SDK vs videre-host bindgen) still hold and should be regression-guarded per repo.Timing
The three-repo cleave is done. The mechanism (right-sized seam types and the L1-side
Fromimpls) lands in this repo; the videre-host and module call-site conversions follow as separate downstream PRs with rev-pin bumps in dependency order (L1 -> L2 -> L3).Acceptance criteria
From<VenueError> for ClientErroradded (hand-written, not#[from]); the 5.map_err(ClientError::Venue)sites use bare?.From<ActorFault> for VenueErroradded in videre-host;venue_faultdeleted, 5 seams use?.cow_orderbook.rs:148/150/171use bare?over the existing#[from]..map_err(Into::into)remains at the moduleon_blockedge orbuilder.rs:55.?works across the L1/L2/L3 seams with no hand bridge for a conversion aFromcan express.map_erris either an ABI-boundary lowering (WITFault/cow-api-error) or a documented semantic remap / context-add /#[source]leaf construction.