Skip to content

fix(runtime): whole-train hardening for the mock preset and testing DX#205

Open
mfw78 wants to merge 2 commits into
fix/m0-mock-followupsfrom
fix/m0-mock-hardening
Open

fix(runtime): whole-train hardening for the mock preset and testing DX#205
mfw78 wants to merge 2 commits into
fix/m0-mock-followupsfrom
fix/m0-mock-hardening

Conversation

@mfw78

@mfw78 mfw78 commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Summary

End of the epic #80 (mock preset and testing DX) train. This PR is a red-team pass over the whole train, hardening the TestRuntime harness and the mock chain provider so the harness cannot silently diverge from the real event loop it is meant to stand in for.

Why

A prior audit walked the composed builder path (RuntimeBuilder::new -> with_types::<MockTypes<E>> -> with_extensions -> with_module_source -> with_wasi_clocks -> with_components(Prebuilt(...)) -> launch) against the real spawned event loop and found two defects that would make the harness lie to module authors, plus a coverage gap on the chain-request leg. Fixing these closes out #80.

Changes

  • Re-arm closed mock chain streams so the reconnect subscribe resumes delivery. The real event loop treats stream end as a WS drop and re-subscribes after backoff; the mock's close_block_stream closed a fixed channel forever, so a re-subscribe after a scripted drop got futures::stream::pending() and every later push_block was silently dropped. StreamSlot<T> now drains-and-terminates the open stream on close, then re-arms a fresh pair per kind.
  • Expose module limits (ModuleLimits, including the train's [limits.poison] knob) on TestRuntimeBuilder via a new limits() method, threaded into the launch config. Previously TestRuntimeBuilder pinned EngineConfig::default(), so the harness could not exercise the config knob the train itself shipped.
  • Add harness_serves_chain_requests_to_the_module, locking the ChainProvider::request leg from a guest (price-alert module, programmed oracle answer, asserts the triggered log and the recorded eth_call). Previously only block and chain-log dispatch were locked.
  • Add harness_resumes_dispatch_after_a_dropped_block_stream, a composed anti-lie lock across the reconnect task and the re-armed mock stream (close, real backoff, block 42 dispatches).
  • Generalize the wasm test helper to module_wasm_or_skip(file).

Test plan

  • All module wasms built in-tree first; zero SKIP: lines in the run.
  • cargo fmt --all --check
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • cargo test -p nexum-runtime (196 passed, 0 failed)
  • RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps
  • No-feature cone check: cargo check -p nexum-runtime builds with test-utils off by default; the feature is enabled only by the crate's own dev-dependency, nexum-cli and shepherd-cow-host depend featureless, and the Cargo.lock delta is limited to the self dev-dependency entry.

Deferred documentation

  • Harness module rustdoc does not yet mention the limits knob in its narrative paragraph, though the method doc covers it; a follow-up docs pass should add it.
  • A short testing-DX guide walking the price-alert-style flow (inline manifest, program chain, inject, assert on logs) is not yet written.

Notes

Also, as a DX read on the harness (writing the price-alert test as a module author would): about 30 lines including the manifest, genuinely low ceremony. One wrinkle worth a follow-up: mock programming happens via builder.chain() on a bound variable because on_method returns &MockChainProvider, so the builder chain cannot stay a single expression.

Follow-ups filed against #80 rather than blocking this PR:

  • A fixture module that reads WASI clocks (e.g. logs SystemTime::now()) to prove ManualClock guest-visibility end to end; today the override is locked at plumbing level only.
  • A clock seam for the supervisor's own poison window and restart backoff, which currently read host Instant and cost the reconnect e2e a real 1s wait.
  • A per-chain/per-(module,filter) mock stream registry; the current single global slot per kind cannot model multi-chain or multi-module assemblies, and a second subscriber parks on pending.
  • A configure_chain(|c| ...)-style builder hook to restore single-expression harness setup.

AI Assistance: Claude Code (Fable 5 red-team and fixes, Sonnet 5 PR authoring) used for the full change.

mfw78 added 2 commits July 5, 2026 23:23
…cribe

Closing a mock stream previously parked every later subscribe call on a
pending stream and silently dropped subsequent pushes, so a harness test
that scripted a connection drop diverged from the real provider, whose
subscription reopens after backoff and resumes delivery. Each stream
slot now re-arms on close: the open stream drains and terminates, and
the next subscribe call receives a fresh stream fed by the pushes made
since.
… and reconnect legs

The [limits] knobs (fuel, memory, outbound HTTP, log retention, poison
thresholds) were unreachable through TestRuntimeBuilder, which pinned
the default EngineConfig. A limits setter now threads them into the
launch, locked by a log-retention test observable through the pipeline.

Two end-to-end gaps close alongside: price-alert drives the
chain-request leg (programmed eth_call response to alert log line), and
a scripted block-stream drop proves dispatch resumes once the event
loop reconnects onto the re-armed mock. The wasm fixture helper takes
the artifact name so harness tests can load any built module.
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