Skip to content

feat(runtime): type-state RuntimeBuilder, LaunchRuntime and RuntimeHandle#193

Open
mfw78 wants to merge 2 commits into
feat/m0-addonsfrom
feat/m0-runtime-builder
Open

feat(runtime): type-state RuntimeBuilder, LaunchRuntime and RuntimeHandle#193
mfw78 wants to merge 2 commits into
feat/m0-addonsfrom
feat/m0-runtime-builder

Conversation

@mfw78

@mfw78 mfw78 commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #92.

Introduces a type-state RuntimeBuilder, a LaunchRuntime trait, and a RuntimeHandle, replacing the ad hoc bootstrap sequence with a chain where a missing step is a compile error rather than a runtime surprise.

This is part of the epic #79 train and merges in stack order.

Changes

crates/nexum-runtime/src/builder.rs is new and holds the architectural centrepiece: LaunchContext carries the ambient launch inputs (executor, data directory, config), LaunchRuntime::launch is implemented once for AssembledRuntime and holds the single copy of the imperative launch sequence (install add-ons, build engine and linker, boot the supervisor, open subscriptions, spawn the event loop), and RuntimeHandle wraps the spawned task handle together with a graceful shutdown trigger.

The type-state chain runs RuntimeBuilder::new(config) through with_types, then the optional with_extensions and with_module_source, then with_components, then with_add_ons, and finally launch. Each stage is its own struct, so a caller cannot skip a required step and still compile.

crates/nexum-runtime/src/bootstrap.rs is rewritten to forward pre-built backends into AssembledRuntime and LaunchContext, then call launch().await?.wait().await. Its signature and behaviour are unchanged, so examples/embed.rs needed no changes.

crates/nexum-cli/src/launch.rs now expresses run_from_config as a single expression over the builder chain.

The event loop is spawned through the executor rather than awaited inline, so the handle can hold a genuine task handle alongside the shutdown trigger. The OS-signal future is selected against the programmatic oneshot trigger inside the spawned task, so CLI blocking behaviour is unchanged. wait() treats a missing join reason (panic or abort) as an error, matching the prior inline-await panic propagation, and there is no Drop impl: dropping the handle fires the trigger through the oneshot sender's drop, which is documented on the type.

Test plan

  • cargo fmt --all --check
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • cargo test --workspace --all-features, including the wasm-backed end-to-end fixtures (cow_boot, e2e_twap_monitor_block_dispatch, e2e_ethflow_watcher_log_dispatch, e2e_stop_loss_block_dispatch) built and run rather than skipped

Notes for reviewer

The issue names with_components(CB) as taking a builder, but bootstrap::run, embed, and the existing tests all hold pre-built Components. Rather than duplicate the launch sequence for both shapes, AssembledRuntime carries pre-built backends and owns the imperative body, while the type-state builder's launch builds components first through ComponentsBuilder and then constructs an AssembledRuntime internally.

with_extensions and with_module_source are additions beyond the three methods named in the issue: run_from_config needs extensions and the CLI's positional wasm or manifest override, and the issue's method list was illustrative rather than exhaustive.

Two follow-ups are deliberately left out of scope. examples/embed.rs still calls bootstrap::run directly and retains components.logs manually; moving it to the builder would need RuntimeHandle to expose a log read-handle, which is a small API addition better done separately. Once external callers move to the builder or LaunchRuntime directly, bootstrap::run could be deleted in favour of one launch surface, since it is now a thin shim over AssembledRuntime and LaunchRuntime.

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

mfw78 added 2 commits July 5, 2026 14:57
…ndle

Add a type-state RuntimeBuilder that accumulates the config, the
RuntimeTypes lattice, extensions, the component builders and the add-on
set, then opens the backends and hands off to a LaunchRuntime launcher.

LaunchRuntime::launch runs the one imperative sequence over a
LaunchContext (executor, data dir, config): install add-ons, build the
engine and linker, boot the supervisor, open subscriptions through the
task executor, spawn the event loop, and return a RuntimeHandle owning
the event-loop task handle, a shutdown trigger and the add-on handles.

run_from_config becomes a one-liner over the builder; bootstrap::run
forwards to the same launcher from pre-built backends so the embed
example is unchanged. Behaviour is unchanged: the supervisor E2E and
event-loop tests still pass.
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