Skip to content

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

Merged
mfw78 merged 2 commits into
developfrom
feat/m0-component-builders
Jul 8, 2026
Merged

feat(runtime): per-component Builder traits and BuilderContext#190
mfw78 merged 2 commits into
developfrom
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.

@lgahdl lgahdl left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few observations from the review

Comment thread crates/nexum-runtime/src/host/component/builder.rs
Comment thread crates/nexum-runtime/src/host/component/builder.rs Outdated
Comment thread crates/nexum-runtime/src/host/component/builder.rs Outdated
Comment thread crates/nexum-runtime/src/host/component/builder.rs
Comment thread crates/nexum-runtime/src/host/component/builder.rs Outdated
Comment thread crates/shepherd-cow-host/src/ext_cow.rs
@mfw78
mfw78 force-pushed the feat/m0-task-executor branch from dcdd2a4 to a3c2289 Compare July 7, 2026 22:58
@mfw78
mfw78 force-pushed the feat/m0-component-builders branch from e849a5e to dad5acd Compare July 7, 2026 22:58
@mfw78
mfw78 force-pushed the feat/m0-task-executor branch from a3c2289 to 273345a Compare July 8, 2026 00:41
@mfw78
mfw78 force-pushed the feat/m0-component-builders branch from dad5acd to afd6cf4 Compare July 8, 2026 00:41
@mfw78
mfw78 force-pushed the feat/m0-task-executor branch from 273345a to 454986b Compare July 8, 2026 01:18
@mfw78
mfw78 force-pushed the feat/m0-component-builders branch from afd6cf4 to bf00d7d Compare July 8, 2026 01:18
@mfw78
mfw78 force-pushed the feat/m0-component-builders branch from dd9cbe7 to 246cae8 Compare July 8, 2026 02:03
Base automatically changed from feat/m0-task-executor to develop July 8, 2026 02:17
mfw78 added 2 commits July 8, 2026 02:18
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.
Address the review of the per-component builders.

LocalStoreBuilder::build called std::fs::create_dir_all and
LocalStore::open (which fsyncs on Database::create) directly inside an
async fn, stalling the executor thread for the syscalls. Wrap both in
tokio::task::spawn_blocking, cloning the data dir into the closure.

Docs: ComponentsBuilder::new said "Name the three component builders"
(command voice, and the count lies if a seam is added), now "Create a
new ComponentsBuilder". The module doc's migration aside ("used to open
through a scattered from_config") is dropped: it described the old shape,
not the current one.

A Self: Send supertrait on ComponentBuilder was suggested but not added:
build(self) moves self into the returned future, so the existing
`-> impl Future + Send` already forces Self: Send at every impl (a
non-Send builder fails to compile). Bounding the future is the precise
constraint; the supertrait would be redundant and slightly broader.
@mfw78
mfw78 force-pushed the feat/m0-component-builders branch from 246cae8 to cb3ad02 Compare July 8, 2026 02:19
@mfw78
mfw78 merged commit b4474ff into develop Jul 8, 2026
11 checks passed
@mfw78
mfw78 deleted the feat/m0-component-builders branch July 8, 2026 02:19
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.

runtime: per-component Builder traits and BuilderContext

2 participants