Skip to content

feat(runtime): per-component Builder traits and BuilderContext#190

Open
mfw78 wants to merge 1 commit into
feat/m0-task-executorfrom
feat/m0-component-builders
Open

feat(runtime): per-component Builder traits and BuilderContext#190
mfw78 wants to merge 1 commit into
feat/m0-task-executorfrom
feat/m0-component-builders

Conversation

@mfw78

@mfw78 mfw78 commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #90.

Introduces a per-component ComponentBuilder trait and a BuilderContext, plus concrete builders for the provider pool, local store, and (in the cow extension crate) the reference order book, assembled by a ComponentsBuilder that composition roots call instead of the previous four scattered open calls.

This is part of the epic #79 train and merges in stack order, on top of the injectable TaskExecutor from #89.

Changes

  • Added BuilderContext<'a> { config: &EngineConfig, data_dir: &Path } and trait ComponentBuilder { type Output; fn build(self, ctx) -> impl Future<Output = anyhow::Result<Output>> + Send } in crates/nexum-runtime/src/host/component/builder.rs.
  • Added core builders: ProviderPoolBuilder (wraps ProviderPool::from_config) and LocalStoreBuilder (creates data_dir and opens data_dir/local-store.redb), plus a no-op impl ComponentBuilder for () for the empty-extension lattice.
  • Added ComponentsBuilder<C, S, E> whose build::<T: RuntimeTypes> drives the three builders against the Chain/Store/Ext lattice and sizes a fresh LogPipeline from limits.logs().
  • Added ReferenceExtBuilder in crates/shepherd-cow-host/src/ext_cow.rs, wrapping OrderBookPool::from_config. This stays in the cow extension crate, not core, matching the existing crate boundary (OrderBookPool is owned by shepherd-cow-host; the core runtime carries no cow dependency).
  • Updated crates/nexum-cli/src/launch.rs to build components via ComponentsBuilder::new(ProviderPoolBuilder, LocalStoreBuilder, ReferenceExtBuilder).build::<ReferenceTypes>(&ctx).
  • Updated crates/nexum-runtime/examples/embed.rs to use the core-only lattice, ComponentsBuilder::new(ProviderPoolBuilder, LocalStoreBuilder, ()), retaining the embedder's log read handle via components.logs.clone().

Test plan

  • cargo fmt --all --check
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • cargo test --workspace --all-features --no-fail-fast (all green, zero failed, zero skipped)
  • Boot and dispatch end-to-end tests confirmed running (not silently skipped), including e2e_supervisor_boots_example_module, e2e_balance_tracker_block_dispatch, e2e_ethflow_watcher_log_dispatch, e2e_stop_loss_block_dispatch, e2e_twap_monitor_block_dispatch, twap_monitor_without_cow_extension_fails_to_boot, and the cow_boot suite
  • embed.rs example builds against the core-only (cow-free) lattice

Notes for reviewer

build is impl Future<Output = ...> + Send rather than a bare synchronous function, because ProviderPool::from_config genuinely dials WS transports and needs to be async. This follows the existing ChainProvider idiom elsewhere in the codebase and keeps builders usable from a spawned task; the synchronous builders (LocalStore, cow) simply have await-free bodies.

One ordering change worth flagging explicitly: previously create_dir_all(data_dir) ran before the provider pool was opened; now the provider pool builder runs first and LocalStoreBuilder creates the data directory as part of opening the store. Neither the provider pool nor the cow extension touches data_dir, and the directory is still created before the cow extension opens its own state, so this reordering is benign, but it is a real behavioural change from a pure move and is called out here rather than left implicit.

Follow-ups intentionally left out of scope for this PR:

  • Threading ComponentsBuilder into the epic's declarative type-state RuntimeBuilder, so the builder assembly and the executor choice from supervisor: introduce TaskExecutor, thread it through stream openers #89 meet in one place rather than at the imperative composition roots.
  • Threading a chosen TaskExecutor from the launcher rather than having bootstrap::run hard-pick TokioExecutor.
  • A typed BuildError enum (thiserror) at the ComponentBuilder seam instead of anyhow, for structured boot-failure classification.
  • Letting ComponentsBuilder optionally accept a pre-built LogPipeline so an embedder can inject a custom sink instead of always getting the in-memory default.

AI Assistance: Claude Code (Opus 4.8 implementation and adversarial review, Sonnet 5 PR authoring) used for the full change.

Backends were opened by scattered from_config and open calls in the
composition roots. Introduce a ComponentBuilder trait taking a shared
BuilderContext (loaded config plus resolved data directory) and wrap the
provider pool, LocalStore, and the cow orderbook pool as builders. A
ComponentsBuilder assembles the core seams plus the lattice Ext payload
into a Components bundle, sizing the log pipeline from limits.logs.

The cow builder lives in shepherd-cow-host because the cow cone belongs
to that extension crate, not the core runtime. The CLI launch path and
the embed example now drive the ComponentsBuilder rather than opening
each backend by hand.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant