build(deps): bump alloy-json-rpc from 2.1.1 to 2.2.0 - #7
Closed
dependabot[bot] wants to merge 142 commits into
Closed
build(deps): bump alloy-json-rpc from 2.1.1 to 2.2.0#7dependabot[bot] wants to merge 142 commits into
dependabot[bot] wants to merge 142 commits into
Conversation
Set up the Nexum runtime project structure: - Nix dev shell (flake.nix) with nightly Rust, wasm32-wasip2 target, wasm-tools, wabt, and just - Cargo workspace with nexum-runtime (host) and example (guest module) - WIT packages: web3:runtime@0.1.0 (csn, local-store, remote-store, msg, logging, headless-module world) and shepherd:cow@0.1.0 (cow, order, shepherd-module world) - Minimal wasmtime 41 host that loads a WASM component, calls init, and dispatches a test block event - Example guest module using wit-bindgen 0.53 that logs init and block events - Justfile with build-runtime, build-module, run, and check recipes
Switch wasmtime bindgen to async imports/exports, link async WASI, instantiate the component asynchronously, and add per-call elapsed-time logs to every host implementation to aid latency profiling.
Shepherd is the internal codename for the CoW Protocol distribution; the underlying engine ships as nxm-engine. Rename the crate directory, workspace member, package name, just recipes, README layout entry, docs reference, and the CLI usage/log strings inside main.rs. The 'shepherd:cow' WIT package and repo name are unchanged — Shepherd remains the CoW distribution of the nxm engine.
CI runs cargo fmt --check and clippy with -D warnings. Two issues needed addressing before the first PR could go green: - Source had drifted from rustfmt output (single-call vec!, fluent builder chain on one line, etc.). Apply 'cargo fmt --all'. - modules/example: wit_bindgen::generate! expands to host-import shims whose arity matches the WIT signatures, exceeding clippy's too-many-arguments threshold. Allow the lint at crate level with a comment explaining the source.
Updates the requirements on [wit-bindgen](https://github.com/bytecodealliance/wit-bindgen) to permit the latest version. - [Release notes](https://github.com/bytecodealliance/wit-bindgen/releases) - [Commits](bytecodealliance/wit-bindgen@v0.53.0...v0.57.1) --- updated-dependencies: - dependency-name: wit-bindgen dependency-version: 0.57.1 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…lities) (#6) * docs: add 0.1 to 0.2 migration guide * wit: rename web3:runtime to nexum:runtime, unify error model, add identity/clock/random/http/query-module * chore: rename crate nxm-engine to nexum-engine; bump to 0.2.0 * build: update justfile and CI for nexum-runtime + nexum-engine rename * docs: rename to nexum:runtime, unify error model, mark non-server platforms as planned * runtime: update engine + example to 0.2 WIT - Engine main.rs targets world `shepherd` (formerly `shepherd-module`), generates against nexum:runtime@0.2.0 + shepherd:cow@0.2.0. - Replaces per-domain error records (JsonRpcError, MsgError, StoreError, ApiError, bare String) with unified HostError + HostErrorKind across every host impl. - Adds Identity host stub (was missing in 0.1 despite being doc'd). - Adds chain::request_batch stub that falls back to per-call dispatch. - Renames feed_get/feed_set -> read_feed/write_feed. - Drops separate cow + order interfaces, replaced by single cow_api with request and submit_order. - Block ts in test event is now ms (1_700_000_000_000) per types.wit documented unit. - Example module targets event-module world, matches Event::Tick instead of Event::Timer, returns HostError from init/on_event. * example: drop empty-name guard from init The guard was inconsistent: missing 'name' key silently defaulted to 'unknown' and returned Ok, but an explicit empty string returned Err — and both paths logged a success-shaped 'name=...' line BEFORE the guard ran. The example is a hello-world; a config-validation guard belongs in a real module, not a starter. Drop it. Resolves PR #6 finding #9. * ci: add wit deps sync check Both wit/nexum-runtime/ and wit/shepherd-cow/deps/nexum-runtime/ are committed to the tree; the latter is regenerated by 'just sync-wit'. Without a CI guard, a contributor can edit one without the other and the divergence only surfaces at runtime (interface-mismatch on module instantiation) — not at build time. The new job re-runs the same copy that 'just sync-wit' does, then fails if the deps tree differs from the freshly-copied canonical. Resolves PR #6 finding #8. * docs/01: align identity::sign to personal_sign semantics The shipped wit/nexum-runtime/identity.wit defines sign() as personal_sign — host MUST prepend the EIP-191 prefix ('\x19Ethereum Signed Message:\n<len>') before hashing. docs/01 described it as 'sign raw bytes' which is a transaction-signing footgun (a raw signer can be tricked into signing EIP-155 / EIP-712 payloads disguised as plain bytes) AND diverges from the WIT — two compliant hosts implementing different specs produce mutually unverifiable signatures. Align docs/01 to the WIT. Note that raw-bytes signing, gated by an explicit capability, is on the 0.3 roadmap. Resolves PR #6 finding #1. * docs: align chain::request-batch and http WIT snippets to shipped types Both snippets in the migration guide and docs/01 showed list<tuple<string, string>> shapes that don't match the shipped WIT. Adopters who copy the doc snippets get wit-bindgen type errors against the real interfaces. - chain::request-batch now uses the nominal record rpc-request and variant rpc-result (matching wit/nexum-runtime/chain.wit). - http now uses the nominal record header and adds the timeout-ms field (matching wit/nexum-runtime/http.wit), plus *.domain wildcard syntax in the allowlist example and the docstring on how non-2xx surfaces. Resolves PR #6 findings #2 and #3. * docs: defer typed config-value variant to 0.3 The shipped wit/nexum-runtime/types.wit ships 0.1's stringly-typed `type config = list<tuple<string, string>>`, but five doc locations plus the migration TL;DR promised a typed `config-value` variant for 0.2 (string/integer/boolean/list). Per architectural triage, defer the typed variant to 0.3 alongside the manifest parser work — the typing story lands as one coherent feature. Updates: docs/00, docs/01, docs/02 (two places), docs/08, migration guide TL;DR row + 'Typed config' subsection. Resolves PR #6 finding #4. * docs/migration: replace cargo-nexum vapor with real commands §9 verification checklist referenced 'cargo nexum check' and 'cargo nexum run --mock'; §11 promised 'cargo nexum migrate --from 0.1' as shipping with 0.2. None of these exist — there is no cargo-nexum crate in the workspace. Rewrite §9 to use real `cargo` + `just` invocations. Drop the §11 codemod promise; the sed cheat sheet in §8 already does the mechanical work. Add a clear 'no cargo-nexum toolchain in 0.2; coming in 0.3' note so adopters set the right expectation. Resolves PR #6 finding #7. * wit+engine: import clock/random/http in event-module; add host impls The new 0.2 capability interfaces (clock, random, http) shipped as standalone WIT files but were not imported by any world, so modules built against event-module / shepherd could not 'use' them. Their existence was advertised in the migration guide but was structurally unreachable. - event-module.wit: add 'import clock', 'import random', 'import http' (grouped as ambient host services after the six core primitives). shepherd world inherits via 'include event-module'. query-module remains intentionally sandboxed (no caps, no chain, no network). - Engine: add host impls for clock (SystemTime + monotonic_baseline: Instant), random (getrandom 0.4 fill), http (returns Unsupported for 0.2; real fetch is 0.3 — allowlist enforcement lands with #6). - Cargo.toml: add getrandom 0.4, plus serde 1 + toml 1 (for #6). Resolves PR #6 finding #5. * engine: minimal nexum.toml manifest parser with [capabilities] enforcement Per architectural triage, ship minimal manifest enforcement in 0.2 and defer optional-capability trap stubs to 0.3. The migration guide §3 promised four mechanisms; this commit lands the two security-critical ones (required-capability check + http allowlist enforcement) plus the deprecation warning, and explicitly defers per-import trap stubs. Manifest schema (parsed in crates/nexum-engine/src/manifest.rs): [module] name, version, component (sha256; parsed, not yet verified) [capabilities] required (validated against KNOWN_CAPABILITIES; engine rejects unknown names) optional (parsed + logged; trap-stub fallback is 0.3) [capabilities.http] allow (exact host or *.suffix wildcard) [config] TOML scalars flattened to strings (typed variant on 0.3 roadmap) CLI: nexum-engine <component.wasm> [<nexum.toml>]. If the second arg is omitted, the engine looks for nexum.toml next to the .wasm. If neither exists, it emits the deprecation warning and proceeds with empty config and empty allowlist (= effectively denies all HTTP). http::fetch now performs the per-module allowlist check (host extracted via a stdlib URL parser, exact or *.suffix match). Allowlist denial returns HostError { kind: Denied }; real network fetch is still 0.3. Includes unit tests for extract_host and host_allowed. Resolves PR #6 finding #6. * style: cargo fmt (nightly) CI rustfmt (nightly) collapses several multi-line signatures that the hand-formatted code had as multi-line. * rename WIT package nexum:runtime -> nexum:host (resolve engine/runtime naming overload) The 0.2 release shipped two similarly-named things — `nexum-engine` (the Rust crate hosting WASM components) and `nexum:runtime` (the WIT package). Both contain 'runtime' or a synonym, but they're at different layers: engine = implementation, runtime = contract. Worse, README.md described the engine crate as 'Host runtime — wasmtime-based component loader' — explicitly calling the *engine* 'runtime' while a sibling directory was literally named `nexum-runtime`. The word 'runtime' overwhelmingly means 'the thing that runs code' in programming usage; putting it on the contract side inverted the convention. Rename the WIT package to `nexum:host` — the host-imports surface a guest sees. Distinction becomes self-documenting: engine (nexum-engine) = a concrete implementation host (nexum:host) = the WIT contract every engine implements Touchpoints: - wit/nexum-runtime/ -> wit/nexum-host/ (12 files renamed via mv) - wit/shepherd-cow/deps/nexum-runtime/ -> wit/shepherd-cow/deps/nexum-host/ (regenerated by 'just sync-wit'; also covered by CI guard) - Every 'package nexum:runtime@0.2.0' -> 'package nexum:host@0.2.0' (12 files) - Every 'use nexum:runtime/...' -> 'use nexum:host/...' (shepherd-cow WIT) - Every 'nexum::runtime::...' -> 'nexum::host::...' (engine + example Rust) - justfile sync-wit paths - .github/workflows/ci.yml wit-deps-sync paths - All design docs (00..08), migration guide - README.md + docs/00 add an explicit 'Engine vs. host' callout so the distinction is directly apparent to a new reader. - A few stray 'host runtime' prose mentions in docs/05 and docs/07 changed to 'host engine' for consistency. Migration guide updated so a 0.1 -> 0.2 reader never encounters the mid-development 'nexum:runtime' name — the new package is `nexum:host` end-to-end. Build + run smoke clean; nightly rustfmt clean. * wit: drop deps/ vendoring; list both packages explicitly in bindgen The 0.2 release vendored a copy of wit/nexum-host/ under wit/shepherd-cow/deps/nexum-host/ because the engine's bindgen target (shepherd:cow/shepherd) imports from nexum:host via 'include', and wit-parser's default cross-package resolution looks at <pkg>/deps/. Maintained by 'just sync-wit' + a CI guard to catch drift. That whole pipeline was treating the symptom rather than the cause. Both bindgen macros (wasmtime::component::bindgen! and wit_bindgen::generate!) accept 'path' as an array of dirs, each holding one package. Listing both packages explicitly resolves the cross-package reference natively with no vendored copy. Changes: - crates/nexum-engine/src/main.rs: bindgen path is now ["../../wit/nexum-host", "../../wit/shepherd-cow"]; world fully qualified as "shepherd:cow/shepherd". - modules/example/src/lib.rs: world fully qualified as "nexum:host/event-module". Path stays single (the example doesn't import shepherd:cow). - wit/shepherd-cow/deps/ deleted entirely (12 files). - justfile: sync-wit recipe removed; build-runtime renamed to build-engine (clearer; matches the engine vs host vocabulary established earlier); check + run no longer depend on sync-wit. - .github/workflows/ci.yml: wit-deps-sync job removed (nothing to sync, nothing to drift). - docs/migration/0.1-to-0.2.md: checklist item about vendored deps rewritten to point at the new bindgen pattern. Result: 1 source of truth per WIT package, zero ceremony to keep them aligned, ~360 fewer lines of vendored bytes in the repo.
Updates the requirements on [wit-bindgen](https://github.com/bytecodealliance/wit-bindgen) to permit the latest version. - [Release notes](https://github.com/bytecodealliance/wit-bindgen/releases) - [Commits](bytecodealliance/wit-bindgen@v0.57.0...v0.58.0) --- updated-dependencies: - dependency-name: wit-bindgen dependency-version: 0.58.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Adds a `[workspace.dependencies]` table to the root manifest
consolidating every dep used by 2+ crates across the full nullis-
shepherd stack (anyhow, thiserror, tokio, futures, serde, serde_json,
tracing, tracing-subscriber, strum, alloy-*, cowprotocol, reqwest,
wit-bindgen, clap). Per-crate manifests inherit with `dep.workspace
= true`, and may add features per call site via `dep = { workspace
= true, features = ["extra"] }`. Single-consumer deps (wasmtime,
toml, redb, getrandom, url, hex, axum, rand, ...) stay per-crate.
Adds `[workspace.lints]` with light-touch defaults: `dbg_macro` and
`todo` denied via clippy, `unsafe_op_in_unsafe_fn` warned via rust.
`unsafe_code = deny` cannot be applied workspace-wide because every
wit-bindgen guest module emits an `unsafe extern "C"` shim.
Also pre-declares `auto_impl` and `derive_more` in the workspace deps
table so future `Arc<dyn Trait>` boundaries and newtype-heavy crates
can opt in without touching the root manifest.
The version-drift failure mode (cowprotocol pinned to `1.0.0-alpha`
in nexum-engine but `1.0.0-alpha.3` in shepherd-sdk, flagged in the
2026-06-25 audit) is now impossible by construction: every consumer
inherits the single workspace pin.
Audit reference: milestone-rubric-grant-audit-2026-06-25.md, judgment
calls 1 + 3.
New `modules/examples/price-alert/` — first canonical SDK example.
A Shepherd module that polls a Chainlink AggregatorV3 price oracle
on every block (throttled by `every_n_blocks`) and emits a Warn-
level log when the answer crosses a config-supplied threshold.
Demonstrates the three load-bearing patterns of a Shepherd module:
- `chain::request` + ABI decode via `alloy_sol_types` (sol!
interface AggregatorV3 declares `latestRoundData`, decode via
`abi_decode_returns`).
- shepherd-sdk helpers (`chain::eth_call_params` +
`chain::parse_eth_call_result`; the SDK's prelude is *not*
used here because the module needs none of the CoW types).
- `[config]` driven behaviour parsed once in `init` and stored
in `OnceLock<Settings>` for read-only access on every event.
Module-internal:
- `Settings` (renamed from `Config` to avoid clashing with the
wit-bindgen-generated `Config` type alias for the `init` arg).
- `Direction { Above, Below }` deciding which side of the
threshold fires.
- `scale_threshold(decimal, decimals)` hand-rolled because alloy
does not ship a `Decimal::parse_units`-style helper; handles
optional sign, missing decimal point, short / long fractional,
rejects non-digit garbage. Locked by 5 unit tests.
- `classify(answer, threshold, direction)` pure 1-liner with 2
edge tests (at-or-above vs. at-or-below behaviour at the
boundary).
- `parse_config(entries)` returns `Result<Settings, String>` with
human-readable errors; 4 unit tests cover happy path, defaults,
unknown direction, missing key.
module.toml:
- `capabilities = ["logging", "chain"]` (no local-store; no
cow-api).
- `[[subscription]]` block on Sepolia (chain_id 11155111).
- `[config]` ships defaults pointing at the canonical Sepolia
ETH/USD feed with a 2500.00 USD threshold + "below" direction.
11 host tests; clippy clean on host + wasm32-wasip2. .wasm is
206 KB optimised — comparable to the M2 modules (twap 305 KB,
ethflow 275 KB) and dominated by alloy-sol-types + wit-bindgen
runtime.
New `modules/examples/balance-tracker/` — second canonical SDK
example. Subscribes to blocks, reads `eth_getBalance(addr)` for a
configured address list, persists each reading under
`balance:{addr}` in local-store, and emits a Warn-level log when
the delta against the prior reading exceeds `change_threshold`
wei.
Demonstrates:
- `chain::request` with a non-`eth_call` method (raw JSON-RPC
with hand-built params), to balance the price-alert example's
sol! / `eth_call` flow.
- `local-store` `get` / `set` per-key persistence with U256 LE
serialisation as the wire format.
- The "diff against last seen" pattern reusable across indexer
modules (transfer monitors, allowance trackers, …).
Module-internal:
- `Settings { addresses: Vec<Address>, change_threshold: U256 }`
parsed from `[config]` once at `init` and stored in
`OnceLock<Settings>`.
- `parse_balance_hex(json)` — strips JSON quotes and the `0x`
prefix, decodes the remaining hex into a U256. Handles `"0x"`
(zero balance), rejects unquoted / non-hex bodies.
- `parse_addresses(raw)` — comma-separated list with whitespace
tolerance and empty-segment skipping; rejects empty lists.
- `abs_diff` + `parse_u256_le` + `u256_to_le_bytes` — pure utilities
with edge-case coverage.
module.toml:
- `capabilities = ["logging", "chain", "local-store"]` (the
superset that distinguishes this example from price-alert,
which only needs chain + logging).
- `[[subscription]]` block on Sepolia (chain_id 11155111).
- `[config]` ships defaults pointing at two anvil-style EOAs and
a 0.1 ETH change threshold.
13 host tests; clippy clean on host + wasm32-wasip2. `.wasm` is
99 KB optimised — about half of price-alert's 206 KB because it
does not pull `alloy-sol-types` into the link tree (no ABI work;
all decoding is hex/U256).
QA pass against the team's rust-idiomatic skill ahead of M4. All
mandatory rules now hold; the cleanup is mostly mechanical with a
handful of small typing improvements where the rule asked for one
thiserror enum per error type.
Replaced every U+2014 with " - " across .rs / .toml / .md:
- 51 source-file occurrences
- 5 Cargo.toml comments
- 366 occurrences across docs/*.md (most in ADRs and the
deployment / tutorial / sdk landings)
Grep gate: `grep -rn '—' crates/ modules/ docs/` returns 0.
Added to every crate root that previously lacked it:
- crates/shepherd-sdk/src/lib.rs
- crates/shepherd-sdk-test/src/lib.rs
- modules/{example,twap-monitor,ethflow-watcher}/src/lib.rs
- modules/examples/{price-alert,balance-tracker}/src/lib.rs
`crates/nexum-engine/src/main.rs` already had it.
- shepherd-sdk dropped `serde` (only `serde_json` is actually
imported; cowprotocol re-exports carry their own serde derive
transitively).
- balance-tracker dropped its direct `alloy-primitives` dep —
now goes through `shepherd_sdk::prelude::{Address, U256,
address}`. Tests adapt.
- `shepherd_sdk::host::HostError` gains `#[derive(thiserror::
Error)]` + `#[error("{domain}: {message} (code={code},
kind={kind:?})")]`. Was a plain struct without Display.
Added `thiserror = "2"` as a dep.
- `modules/twap-monitor::BuildError`: hand-rolled Display impl
replaced with `#[derive(thiserror::Error)]` + per-variant
`#[error(...)]` + `#[from] cowprotocol::Error`. The map_err
at the call site collapses to `?`.
- `modules/ethflow-watcher::BuildError`: same conversion (4
variants, one of them `#[from]`).
Both modules add `thiserror = "2"` as a direct dep.
- `cargo clippy --all-targets --workspace -- -D warnings` clean.
- `cargo test --workspace`: 121 tests pass.
- nexum-engine 41, shepherd-sdk 27, shepherd-sdk-test 8 + 1
doctest, twap-monitor 13, ethflow-watcher 7, price-alert
11, balance-tracker 13.
- `#[non_exhaustive]` is *not* applied to public enums
(`HostErrorKind`, `LogLevel`, `RetryAction`, `PollOutcome`).
The first two mirror the WIT 0.2 enums (locked at the WIT
contract layer); the last two are intentional 3- and 5-arm
contracts with no expected growth. If a future kind shows
up, the rule applies then.
- `parse_config` / `parse_settings` in the example modules
return `Result<T, String>` rather than a typed enum. The
rule's "no string-wrapping" applies to error variants that
*wrap* an upstream `std::error::Error`; one-shot config
parsers with bespoke per-field messages are pragmatic. The
error surface is internal to the module's `init` and not
part of the orderbook retry contract.
Validates the host-trait pattern from the M3 tutorial end-to-end on
a real module. The price-alert example now matches the recipe the
tutorial recommends:
modules/examples/price-alert/
├── Cargo.toml adds shepherd-sdk-test as dev-dep
└── src/
├── lib.rs wit_bindgen::generate! + WitBindgenHost
│ adapter + From conversions + Guest impl
└── strategy.rs pure logic against `&impl Host`
+ parse_config + scale_threshold + tests
Strategy logic now takes `&impl shepherd_sdk::host::Host` and never
calls `nexum::host::*` free functions directly. The wit-bindgen
boilerplate (WitBindgenHost struct, ChainHost / LocalStoreHost /
CowApiHost / LoggingHost impls, convert_err / sdk_err_into_wit /
convert_level helpers) lives in lib.rs - mechanical and identical
across modules, a future declarative macro in shepherd-sdk will
elide it.
parse_config now returns `Result<Settings, shepherd_sdk::host::
HostError>` instead of `Result<T, String>`. Carrying the SDK error
through the strategy / adapter / Guest seam means the same domain /
kind / code / message / data fields surface to the operator
verbatim.
Tests: 16 (was 11) - all strategy tests now run against
shepherd_sdk_test::MockHost rather than calling wit-bindgen
directly. The 5 new ones lock the on_block behaviour end-to-end:
- idle when price is on the safe side of the trigger
- triggers below threshold (Direction::Below)
- triggers above threshold (Direction::Above)
- warns + continues on RPC timeout (no propagation into the
supervisor)
- warns on undecodable oracle response
- respects `every_n_blocks` throttle
cargo clippy --all-targets --workspace -- -D warnings clean. .wasm
210 KB (was 206 KB; +4 KB for the adapter boilerplate, which
deduplicates against shepherd-sdk so future modules add no extra
cost).
Pre-upstream QA pass against the M2 + M3 + M2-host-trait stacks.
Two findings applied here as a single tip-level commit instead of
rewriting each stacked PR (mfw78 prefers history preservation over
amended PRs):
1. `cargo fmt --all` across the workspace. Bulk of the churn is in
M1 `crates/nexum-engine/src/supervisor/tests.rs` (386 line diff,
pre-existing drift); the rest is M2/M3 leaf modules my own
recent PRs introduced. No semantic changes.
2. One em-dash slipped past the rust-idiomatic sweep in
`modules/examples/price-alert/src/strategy.rs:4` (a module-level
doc comment). Replaced with ASCII ` - `.
Three em-dashes remain in `wit/**.wit` files, all in mfw78's M1
prose. Intentionally left alone - the rust-idiomatic skill is a
Bleu-internal preference and should not rewrite his upstream
authoring style. Tracked as a separate question for him in the QA
sign-off report.
QA matrix on this commit:
- `cargo fmt --all --check`: clean
- `cargo clippy --all-targets --workspace -- -D warnings`: clean
- `cargo test --workspace`: 145 host tests + 1 doctest passing
(twap 20, ethflow 12, balance 13, price 16, stop-loss 7,
shepherd-sdk 27, shepherd-sdk-test 8, nexum-engine 41, doctest 1)
- `cargo build --target wasm32-wasip2 --release -p <module>`:
clean for all 5 modules. Sizes:
twap-monitor 313,926 B
ethflow-watcher 281,518 B
stop-loss 311,290 B
price-alert 215,080 B
balance-tracker 101,518 B
- Em-dashes in `crates/` + `modules/` + `docs/`: 0
- `warn(unused_crate_dependencies)` on every crate root: present
(sdk, sdk-test, nexum-engine, twap, ethflow, price-alert,
balance-tracker, stop-loss)
Outstanding (deferred):
- BLEU-853 / COW-1029: `#[non_exhaustive]` batch on SDK public
enums (HostErrorKind, LogLevel, PollOutcome, RetryAction). Held
until just before upstream cut so wit-bindgen stays bridge-able.
- WIT-file em-dashes in upstream prose - ask mfw78.
12 review threads addressed end-to-end. Net diff is -720 lines despite adding ~200 lines of new helpers + tests, because the WitBindgenHost adapter deduplication alone wipes ~400 lines. Per-thread: #1 (balance-tracker architecture): refactored to match the M3 host-trait+adapter split the other 4 modules use. Created `strategy.rs` with `on_block(&impl Host, ...)`, moved check_one / fetch_balance / parse_balance_hex / parse_settings into it, converted parse_config to use SDK config helpers + typed HostError instead of String. Added 3 MockHost-driven tests covering first-seen-above-threshold, below-threshold-persist, and error-does-not-abort-loop. #2 + #3 (WitBindgenHost dedup): new `shepherd_sdk::bind_host_via_wit_bindgen!()` declarative macro. Single source of truth in `crates/shepherd-sdk/src/wit_bindgen_macro.rs`; the 4 trait impls + convert_err / sdk_err_into_wit / convert_level collapse to one macro invocation per module. Migrated all 5 modules (twap-monitor, ethflow-watcher, price-alert, stop-loss, balance-tracker). Each module's lib.rs lost ~80 lines. #4 (scale_decimal + config_get dup): new `shepherd_sdk::config` with `get_required`, `get_optional`, `scale_decimal`, and a typed `ConfigError` enum (host-neutral). price-alert + stop-loss consume the SDK helpers; their local duplicates were deleted. Module-level decimal-parsing tests removed (covered by 7 SDK tests + 4 proptest cases now). #5 (Chainlink dup): new `shepherd_sdk::chain::chainlink` with `read_latest_answer(host, chain_id, oracle, domain) -> Option<I256>`. Encapsulates the eth_call → parse → ABI decode flow + Warn logging. price-alert + stop-loss now call the helper; their local AggregatorV3 sol! definitions + read_oracle / on_block oracle plumbing was deleted. SDK ships with 3 StubHost tests covering happy path, host error, and garbage-hex. #6 (WIT world capability elision): added new "Capability enforcement vs. the WIT world" section to ADR-0009 documenting that price-alert + balance-tracker compile against the shepherd:cow/shepherd supertype but their manifests omit cow-api, and that boot success depends on wasm-tools' unused- import elision. Flagged as load-bearing; M5 macro hardening path documented. #7 (poll-time revert classification inert): filed COW-1082 for the host-side fix (forward structured eth_call error data into HostError.data; analogous to COW-1075 for orderbook). #8 (classify_api_error retry-default unbounded): filed COW-1083 for the rate-limit / max-retry follow-up on the backoff: marker. #9 (RetryAction::Backoff dead variant): no code change; replied to thread clarifying it is reserved API surface waiting on a richer upstream retry_hint shape (open question for mfw78). #10 (no proptest anywhere): added `proptest` to shepherd-sdk dev-dependencies. New `crates/shepherd-sdk/src/proptests.rs` with 6 properties covering eth_call_params/parse_eth_call_result round-trip, parse_eth_call_result rejection on unquoted input, config::scale_decimal round-trip + sign-preservation, U256 LE byte round-trip, and no-panic guards for decode_revert_hex + gpv2_to_order_data marker dispatch. #11 (ethflow chain capability least-privilege): moved `chain` from required to optional in `modules/ethflow-watcher/module.toml`, mirroring the M2 mirror fix already applied. #12 (ADR-0009 test-count census): dropped the "145 host tests (twap 20, ethflow 12, ...)" breakdown; kept the qualitative claim. CI is now the authoritative count. Drive-by: alloy-sol-types moved from regular to dev-dependencies in price-alert and stop-loss now that the Chainlink ABI helper is inside shepherd-sdk and the modules only use sol! in their test helpers. Validation: - cargo test --workspace: every crate green; 5 modules + SDK + sdk-test + engine all pass. 8 host tests gained on balance-tracker; 6 proptest props gained on shepherd-sdk; 3 Chainlink helper tests gained. - cargo clippy --workspace --all-targets -- -D warnings: clean. - cargo fmt --check: clean. - cargo build --target wasm32-wasip2 --release for all 5 modules: clean. - Zero em-dashes in source code added.
…tParseError Audit reference: milestone-rubric-grant-audit-2026-06-25.md, Major #7 (`Result<_, String>` survivor in `modules/examples/balance-tracker/ src/strategy.rs:149`). The rubric prohibits stringly-typed errors in library APIs. The formatting strings now live on `#[error(...)]` annotations on the `AddressListParseError` variants, preserving the exact wording the previous `format!("address #{i} ({trimmed:?}): {e}")` / `"expected at least one address"` calls produced, so any operator-facing log strings stay stable. `thiserror = "2"` lands as a direct dep on the balance-tracker module. The audit also notes shepherd-backtest already migrated to a typed `AddressParseError`; consolidating both into a shared `shepherd_sdk::cow::AddressParse` is a separate refactor flagged as a P3 medium-confidence consolidation in the audit and deferred for Bruno's judgment call on whether the shared crate is the right home.
…ker (audit JC5) The balance-tracker strategy carried a local `AddressListParseError` + `parse_addresses` pair (PR #20 in nullislabs/shepherd, COW review). The same shape is wanted by shepherd-backtest's address-list config parsing and by future strategy modules. Hoist the helper into `shepherd_sdk::address` so every consumer picks up the same Display wording and #[non_exhaustive] evolution guarantee in lockstep. Surface: shepherd_sdk::address::AddressParse (replaces local enums) shepherd_sdk::address::parse_address_list (replaces per-module fn) balance-tracker now consumes the SDK helper and drops its local `thiserror` dependency. Test coverage moved with the implementation (SDK retains the four cases the strategy crate exercised).
- Bump wit-bindgen 0.57 → 0.58, alloy-primitives 1.5 → 1.6, alloy-sol-types 1.5 → 1.6 across all module Cargo.toml files - Add ModuleLimits parameter to boot_production_module test helper (required by the configurable limits added in dev/m1-prs) - Apply cargo fmt
QA pass against the team's rust-idiomatic skill ahead of M4. All
mandatory rules now hold; the cleanup is mostly mechanical with a
handful of small typing improvements where the rule asked for one
thiserror enum per error type.
Replaced every U+2014 with " - " across .rs / .toml / .md:
- 51 source-file occurrences
- 5 Cargo.toml comments
- 366 occurrences across docs/*.md (most in ADRs and the
deployment / tutorial / sdk landings)
Grep gate: `grep -rn '—' crates/ modules/ docs/` returns 0.
Added to every crate root that previously lacked it:
- crates/shepherd-sdk/src/lib.rs
- crates/shepherd-sdk-test/src/lib.rs
- modules/{example,twap-monitor,ethflow-watcher}/src/lib.rs
- modules/examples/{price-alert,balance-tracker}/src/lib.rs
`crates/nexum-engine/src/main.rs` already had it.
- shepherd-sdk dropped `serde` (only `serde_json` is actually
imported; cowprotocol re-exports carry their own serde derive
transitively).
- balance-tracker dropped its direct `alloy-primitives` dep —
now goes through `shepherd_sdk::prelude::{Address, U256,
address}`. Tests adapt.
- `shepherd_sdk::host::HostError` gains `#[derive(thiserror::
Error)]` + `#[error("{domain}: {message} (code={code},
kind={kind:?})")]`. Was a plain struct without Display.
Added `thiserror = "2"` as a dep.
- `modules/twap-monitor::BuildError`: hand-rolled Display impl
replaced with `#[derive(thiserror::Error)]` + per-variant
`#[error(...)]` + `#[from] cowprotocol::Error`. The map_err
at the call site collapses to `?`.
- `modules/ethflow-watcher::BuildError`: same conversion (4
variants, one of them `#[from]`).
Both modules add `thiserror = "2"` as a direct dep.
- `cargo clippy --all-targets --workspace -- -D warnings` clean.
- `cargo test --workspace`: 121 tests pass.
- nexum-engine 41, shepherd-sdk 27, shepherd-sdk-test 8 + 1
doctest, twap-monitor 13, ethflow-watcher 7, price-alert
11, balance-tracker 13.
- `#[non_exhaustive]` is *not* applied to public enums
(`HostErrorKind`, `LogLevel`, `RetryAction`, `PollOutcome`).
The first two mirror the WIT 0.2 enums (locked at the WIT
contract layer); the last two are intentional 3- and 5-arm
contracts with no expected growth. If a future kind shows
up, the rule applies then.
- `parse_config` / `parse_settings` in the example modules
return `Result<T, String>` rather than a typed enum. The
rule's "no string-wrapping" applies to error variants that
*wrap* an upstream `std::error::Error`; one-shot config
parsers with bespoke per-field messages are pragmatic. The
error surface is internal to the module's `init` and not
part of the orderbook retry contract.
…1036)
Locks the M1 fuel + memory wiring (BLEU-818) against regression with
two evil-by-design wasm fixtures and three supervisor integration
tests that exercise the full trap path: dispatch -> wasmtime trap ->
supervisor catches -> module marked dead -> engine continues.
## New fixtures
`modules/fixtures/fuel-bomb/` (66 KB wasm)
on_event runs an unbounded `wrapping_add` loop with
`std::hint::black_box` so the optimiser cannot elide it.
wasmtime exhausts the per-event DEFAULT_FUEL_PER_EVENT (1B) and
traps with OutOfFuel.
`modules/fixtures/memory-bomb/` (67 KB wasm)
on_event allocates 128 MiB which exceeds the per-store
DEFAULT_MEMORY_LIMIT (64 MiB). wasmtime rejects the memory.grow
and traps the module.
Both fixtures live under `modules/fixtures/` so they are obviously
test-only - the M2 / M3 testnet configs never reference them. Both
declare only the `logging` capability + a single block subscription.
## New supervisor integration tests
`resource_limit_fuel_bomb_traps_and_marks_module_dead`
Boots fuel-bomb alone, dispatches a block, asserts:
- dispatched == 0 (trap, not delivery)
- alive_count() == 0 (module marked dead)
- second dispatch returns 0 (dead module excluded)
-> proves the fuel limit fires + the supervisor catches the
trap without panicking.
`resource_limit_memory_bomb_traps_and_marks_module_dead`
Same shape for the 64 MiB cap. The wasm32 allocator surfaces
"memory allocation of 134217728 bytes failed" (the trap firing).
`resource_limit_dead_bomb_does_not_starve_healthy_module`
Strongest isolation test: loads fuel-bomb + the M1 example
module side-by-side, dispatches a block, asserts:
- dispatched == 1 (example survived + accepted the dispatch
even though the bomb trapped on the same block)
- alive_count() == 1 (only example alive)
- second dispatch == 1 (dead bomb skipped, example continues)
-> proves a rogue module cannot starve the supervisor or
starve sibling modules.
## Validation
- `cargo test -p nexum-engine resource_limit` -> 3 passed.
- `cargo test --workspace` -> 154 host tests + 6 doctests passing
(was 151 + 6; +3 from the new tests).
- `cargo clippy --all-targets --workspace -- -D warnings` clean.
- `cargo fmt --all --check` clean.
- `cargo build --target wasm32-wasip2 --release -p {fuel-bomb,memory-bomb}`
clean.
- 0 em-dashes in new files.
## Out of scope
- Fuel + memory limits made configurable per-module via `engine.toml`
(today they are workspace constants in `runtime/limits.rs`).
Already noted in the source comments as "configurable in 0.3";
acknowledged not addressed here.
- Adversarial fuzz of the resource-limit defaults under sustained
load. That is COW-1065 (security review) territory.
- CI integration of the fixtures into the build matrix (PR #27).
Not needed - `cargo test --workspace` already builds them in the
test profile, and the `module_wasm_or_skip` guard means CI does
not need a separate fixture-build job.
Linear: COW-1036. Second M4 issue landed; stacks on #35 (COW-1029).
…tiation (COW-1033)
When a module traps in `on_event` (OutOfFuel, MemoryOutOfBounds,
unhandled host error), the supervisor now:
1. Marks the module `alive = false` and increments `failure_count`.
2. Schedules a `next_attempt` instant via the new
`runtime::restart_policy::backoff_for` (1s → 2s → 4s → ... cap
5 min). All dispatches before that instant skip the module.
3. On the first dispatch past the backoff window, the supervisor
tears down the trapped wasmtime Store + component instance and
re-instantiates from the cached `Component`. The instance state
resets but host-side persistent state (local-store) survives
so a module's progress counters live across restarts.
4. On a successful `on_event` after recovery, `failure_count` resets
to 0 + `next_attempt = None`.
A wasmtime trap leaves the component instance poisoned: subsequent
`call_on_event` returns "wasm trap: cannot enter component instance".
Just refueling the Store does not recover. The supervisor caches
the `Component`, `init_config`, and `http_allowlist` on
`LoadedModule` at boot so a restart only needs a fresh Store +
re-instantiation - the compiled component bytes are reused.
- `crates/nexum-engine/src/runtime/restart_policy.rs`: `backoff_for(failure_count) -> Duration` with the 1s → 5min schedule. 4 unit tests covering the steady-state, first-failure, doubling, and cap arms.
- `Supervisor` gains four cached backends (`engine`, `cow_pool`, `provider_pool`, `local_store`) so `reinstantiate_one(idx)` can rebuild the wasi Linker + HostState + Store + bindings on demand.
- `LoadedModule` gains `component: Component`, `init_config: Config`, `http_allowlist: Vec<String>` (all cloned at boot), plus `failure_count: u32` and `next_attempt: Option<Instant>` for the schedule.
`dispatch_block` and `dispatch_log` now restructure into two
phases:
1. **Phase 1 (restart sweep)**: walk modules, collect indices of
dead-but-due modules, call `reinstantiate_one` on each. Failed
restarts bump the backoff again. Successful restarts flip
`alive = true` so phase 2 dispatches the next event to them.
2. **Phase 2 (steady-state dispatch)**: unchanged from before -
walk modules, dispatch where subscribed + alive. Trap path
sets `next_attempt` + bumps `failure_count`; success path
resets both.
The structured logs from COW-1035 gain `failure_count` + `backoff_ms`
on trap + `restart attempt` info lines on each restart. The
`shepherd_module_restarts_total{module}` Prometheus counter from
COW-1034 increments on every restart attempt.
`modules/fixtures/flaky-bomb/` (test-only): traps via OutOfFuel on
the first N events (N from `[config].fail_first_n`) and recovers
afterwards. Uses local-store for the attempt counter because the
wasm instance state resets on each reinstantiation; the counter
persists in the host-side store so the module deterministically
recovers after the configured N.
`supervisor::tests::restart_flaky_module_recovers_after_backoff`
(new): boots flaky-bomb with fail_first_n=1, dispatches, observes:
- Dispatch 1: trap. alive=false, failure_count=1, next_attempt=+1s.
- Immediate redispatch: skipped (still in backoff).
- Sleep 1.1s.
- Dispatch 3: restart fires, fresh instance attempts again. With
attempt=2 > N=1, returns Ok. alive=true, failure_count=0,
next_attempt=None.
- Dispatch 4: steady-state, dispatches normally.
Test wall-clock ~1.4s.
- `cargo test --workspace` -> 159 host tests + 6 doctests passing.
+4 from `restart_policy` unit tests + 1 from the new integration
test (was 154 + 6).
- `cargo clippy --all-targets --workspace -- -D warnings` clean.
- `cargo fmt --all --check` clean.
- All existing resource-limit tests (COW-1036) still pass against
the new dispatch shape: their assertions are against state
*immediately* after the trap (before backoff elapses), so the
restart machinery is transparent.
- The `init_failure_marks_module_dead_and_excludes_from_dispatch`
test (COW-1070) still passes: init-failed modules carry
`next_attempt = None` so the restart sweep never picks them up.
- Persistence of `failure_count` / `next_attempt` across full
engine restarts. The schedule resets on every boot; cross-engine
persistence is a 0.3 follow-up.
- WS reconnect-with-backoff for upstream RPC drops - that is
COW-1071, a separate axis.
- Operator-tunable backoff via `engine.toml::[engine.restart]`.
The current constants are workspace literals in
`runtime::restart_policy`; configurable in 0.3.
- Module-side `on_restart` hook. Modules just see a fresh `init`
call after a restart, same as boot.
Linear: COW-1033. Fifth M4 issue landed; stacks on #38 (COW-1034).
…-1064 dry run
Reconfigures the M3 example modules' manifests to the pinned
identities for the 2026-06-18 COW-1064 E2E dry run (Bruno's test
EOA + Safe on Sepolia) and adds a `docs/operations/e2e-cow-1064-
prep.md` companion to the runbook that captures every
copy-paste-able value the operator needs to drive the on-chain
side of the run without re-deriving any UID, address, or
calldata.
## Module config pinning
`modules/examples/stop-loss/module.toml`:
- owner -> 0x7bF140727D27ea64b607E042f1225680B40ECa6A (test EOA)
- sell_token -> WETH9 Sepolia (was a mainnet KNC address — bug
that would have failed the orderbook accept regardless)
- buy_token -> COW Sepolia (verified on-chain: name="CoW
Protocol Token", symbol="COW", decimals=18)
- sell_amount -> 0.005 WETH (fits 0.01 WETH wrap budget)
- buy_amount -> 20 COW (conservative quote)
- trigger_price -> $2000 (above the Sepolia Chainlink mocked
answer ~$1681 so the strategy fires on the first block)
`modules/examples/balance-tracker/module.toml`:
- addresses -> EOA + Safe (was the hardhat default accounts)
- change_threshold -> 0.001 ETH (was 0.1; lower so the small
E2E gas-side transfers show as Warn diffs)
## OrderUid pinning + regression test
`modules/examples/stop-loss/src/strategy.rs` gains
`cow_1064_e2e_settings_yield_expected_uid`: an integration
test that constructs `Settings` from the exact same constants
as the new manifest and asserts the resulting `build_creation`
UID against:
0xc2b9cb4ea1ee5a86d8049ac09d8f494bf04cca0a68407285f31e2e6379800be8
7bf140727d27ea64b607e042f1225680b40eca6a
ffffffff
(orderDigest || owner || validTo per packOrderUidParams.)
If anything drifts — manifest values, EIP-712 type-hash,
domain separator — the test fires before the run starts, not
during the run.
## Run-prep punch list
`docs/operations/e2e-cow-1064-prep.md` (~ 280 lines):
1. **Pinned identities table** — every address the runbook
references (EOA, Safe, ComposableCoW, TWAP handler,
EthFlow, GPv2Settlement, GPv2VaultRelayer, WETH, COW
token, domain separator). All verified via `eth_getCode`
on Sepolia before commit.
2. **Per-module config pinning** — stop-loss + balance-
tracker effective values in table form.
3. **OrderUid decomposition** — orderDigest (32) + owner (20)
+ validTo (4) breakdown so an operator reading
`setPreSignature` calldata can sanity-check the UID
without redoing the EIP-712 math.
4. **Four on-chain actions** — each as a numbered step with
the exact contract + function + arguments + Etherscan
write-UI URL:
- Action 1: wrap 0.01 ETH -> 0.01 WETH9 (optional, only
for `submitted:` path; `backoff:` works without).
- Action 2: setPreSignature + WETH allowance to
GPv2VaultRelayer (optional, paired with action 1).
- Action 3: TWAP create() via Safe TX Builder; the full
516-byte calldata pinned verbatim (selector 0x6bfae1ca
+ tuple-encoded ConditionalOrderParams + dispatch=true).
- Action 4: EthFlow swap via cow-swap UI on Sepolia
(UI-driven for the quote endpoint hit; calldata
fallback link if UI flakes).
5. **Validation snippets** — `cast` invocations to check EOA
+ Safe balances, WETH balance, allowance,
`preSignature(bytes)` lookup, and a `journalctl + jq`
one-liner that tails per-module terminal markers in real
time.
6. **Re-derivation recipes** — Python + `cargo test`
commands to regenerate every pinned value if config drift
ever forces a re-run with different identities.
7. **Per-run acceptance checklist** — 9 box-checks that
double-pin section 7 of the e2e-report template, scoped
to THIS specific run.
## Workspace impact
- `cargo test -p stop-loss --lib` -> 8 passed (was 7; +1
for the new pinning test).
- `cargo fmt --all --check` clean.
- No production-code changes outside the test module.
Linear: COW-1064. Twelfth M4 deliverable; stacks on #45.
* chore: strip Linear project-management artifacts Remove all COW-#### and BLEU-#### Linear issue references, linear.app URLs, and issue-tracker "Linear" mentions from documentation, rustdoc, comments, config, scripts, and tooling. Delete the throwaway milestone PR-message and features drafts. Rename the two docs that carried an issue id in their filename (qa-signoff, e2e-prep) and the derived test identifiers (NEXUM_TEST_ env vars, e2e_settings_yield_expected_uid). Preserve the CoW Protocol domain vocabulary (cow_orderbook, cow_api, the COW token, ComposableCoW), the WASM "Linear-memory" concept, and the real bleu/cow-rs git dependency and its ADR. * fix(build): repair pre-existing develop build failures Drop serde_json, strum, and thiserror from ethflow-watcher; they are declared but never used, and -D warnings promotes unused_crate_dependencies to a hard error. Move alloy-sol-types from price-alert's dev-dependencies to dependencies, where its production sol! block for the Chainlink AggregatorV3 interface needs it. Both failures predate this branch and surfaced only once CI was enabled on develop.
Hoist every external dependency into [workspace.dependencies] and have the core library and tool crates inherit via `dep.workspace = true`, so versions live in exactly one place. Standalone guest modules (twap, ethflow, examples, fixtures) express-declare their own external deps and inherit only local crates, mirroring how a third-party module author would build against the contract; convert modules/example off its lone `wit-bindgen.workspace = true`. Declare serde_json and strum with a wasm-lean base (default-features = false, alloc/derive only) and let the host binaries add `std`, so the guest wasm builds stay minimal while the engine keeps std json. De-drift the version skew this exposed: alloy-core stragglers 1.5 -> 1.6, wit-bindgen 0.57 -> 0.58, strum 0.26 -> 0.27. No major bumps here; alloy-provider (2.x), wasmtime (46), redb (4), reqwest (0.13) and the cowprotocol migration land as separate PRs. Also fix the workspace repository casing (nullisLabs -> nullislabs) and pin CI actions forward (actions/checkout v7.0.0, dtolnay/rust-toolchain), normalising the one job left on checkout v6.0.2. Supersedes the open dependency bump PRs for those actions.
…rary target (#95)
Drop continue-on-error from the venue-agnostic job, add privileged-field and host-WIT namespace scans to the zero-leak script, and pin the invariant with two integration tests: the echo venue boots and routes a worker's submission purely through the generic extension seam, and the nexum-runtime crate graph names no venue-shaped crate. A missing wasm fixture hard-fails the boot oracle under CI so the gate cannot skip itself.
Replace the convert_fault / sdk_fault_into_wit / convert_level shim functions with From impls emitted by the macro (orphan-legal in the per-cdylib expansion), so module glue converts via ? and Into. No behaviour change.
Add HostTransport, an alloy Transport dispatching JSON-RPC packets through ChainHost::request, and host.provider(chain) minting a RootProvider over it, driven by a single-poll block_on. Methods outside the typed ChainMethod read surface (mirrored guest-side) fail as -32601 before reaching the host; node errors carry code, message and decoded revert bytes; host faults surface as typed transport errors. Chain and ChainId newtypes replace bare u64 ids at the SDK edge. The hoisted alloy-provider entry drops its native transport features so the wasm guest build stays transport-free; the engine and load-gen re-add theirs at the call site.
#[module] stays L1 in nexum-module-macros; #[venue] and derive(IntentBody) move to videre-macros with the venue-world synthesis. nexum-sdk re-exports module from nexum-module-macros; videre-sdk re-exports venue and IntentBody from videre-macros. No macro behaviour change.
Answer existence, value size and key cardinality without transferring the payload across the wasm boundary. The SDK trait ships default bodies over get/list-keys so backends opt into a cheaper path; the redb backend answers contains and len from the entry in place and count from a bounded range scan.
#[videre_sdk::venue] now takes the impl VenueAdapter block itself: it asserts the manifest kind, keeps the manifest-derived world narrowing, remaps the type interfaces onto the SDK bindings for type identity, and expands to the demoted internal export codegen. export_venue_adapter! is no longer public API; echo-venue and flaky-venue author through the trait, the golden bridges are gone, and the cow body codec is held to the kit's typed vectors.
Add an Order typestate builder over the 12-field CoW OrderBody with SellToken and BuyToken newtypes, so a keeper cannot swap sides or skip a required field: sell/buy entry fixes the kind, the counter-side limit and expiry are compile-time required states, and the optionals default. Seal the extension traits: Host and HostFault (nexum-sdk), RuntimeTypes and Runtime (nexum-runtime), and VenueTransport (videre-sdk, the pool seam's successor) each gain a doc-hidden sealing marker an implementor opts into, so the surfaces can grow without silent downstream breakage. Apply #[non_exhaustive] uniformly across the public error and label enums, adding wildcard folds at the cross-crate match sites. Emit the mirrored vocabularies from single-source consts in nexum-world: the capability name consts and CORE_IFACES feed both the world-synthesis table and the runtime registry, and the fault-label consts feed the runtime's label projection with the SDK's strum labels pinned to them in test. Fix the operator logs that debug-formatted venue faults: the SDK Fault's rate-limited Display now carries the retry-after hint, the runtime's fault_message keeps it, and the venue registry renders wire errors through message projections instead of the bindgen's debug Display, so rate-limited retry-after-ms survives to the log line. The golden-bridge sweep found no residue.
…dleware cow-venue gains the adapter slice: CowAdapter under #[videre_sdk::venue] decodes CowIntentBody, derives the value-flow header, and speaks the orderbook REST API over scoped wasi:http. A signed order posts EIP-1271 and its receipt is the canonical 56-byte UID; an unsigned order posts pre-sign and returns requires-signing carrying the setPreSignature call. An already-held rejection is success with the client-derived UID, and errorType rejections project onto venue-error through the shipped classification table, so the retry hint survives the collapse. The chain-edge order assembly (gpv2_to_order_data, order_data_to_body, order_uid_hex, build_order_creation) moves into the new assembly slice the adapter owns; shepherd-sdk re-exports it for the legacy keeper run. videre-sdk gains TimedFetch, the per-request timeout middleware every adapter request rides. Codec vectors and header goldens are published under tests/vectors with a conformance suite, and the built component bundles into the shepherd distribution via the engine adapters stanza (example and docker configs, justfile, Dockerfile, CI wasm build).
Delete shepherd-cow-host, the shepherd:cow legacy worlds (cow-api, cow-ext, shepherd; cow-events stays as the event-ABI package of record), the shepherd-sdk cow surface, and the shepherd-sdk-test mocks. The shepherd binary composes the core lattice with the videre platform alone. The keeper sweep moves to composable-cow behind the sweep slice; stop-loss migrates onto #[videre_sdk::keeper] and pool submit through the typed CowClient, keyed on the venue-and-body intent-id. twap gates topic-0 on the sol decoder pinned against cow-events; the WIT layering gate moves to cow-venue. ADR-0005/0006 marked superseded.
…nature A first-time user's Safe wiring and ComposableCoW create() land in one block, so the orderbook rejects the first submission against its own head and the keeper dropped a valid watch. The classification table gains a drop-on-repeat action, the retry ledger records the refused block and gates one next-block retry, and a denied refusal re-enters the table by its errorType prefix so the grace survives the coarse venue-error collapse. A repeat on a later block still drops.
Replace the four unsupported remote-store stubs with a Bee-backed backend behind the component seam: a shared RemoteStore handle built from the new [remote_store] engine table (API URL, postage batch, feed-signing key), threaded through Components and every module store. Uploads and feed updates are stamped with the configured batch; feed updates are signed host-side. Faults classify by failure: missing configuration is unsupported, malformed guest input invalid-input, a lookup miss unavailable, HTTP 429 rate-limited, 402/403 denied, and a transport timeout timeout. An absent table leaves every call unsupported so guests can probe-then-skip.
Move every crate, WIT package, module, fixture, script, and deploy artefact into nexum/ (L1), videre/ (L2), and shepherd/ (L3), matching the carve boundaries. One umbrella workspace root and a single hoisted dependency table and Cargo.lock remain; intra-group relative paths are preserved byte-for-byte. Cross-group edges are rewritten path-deps plus wit/ symlinks into the owning group, standing in for the wit-deps vendoring that lands with the carve.
…t-deps manifest The runtime bindgen and every L1 fixture module already resolve wit/nexum-host inside the nexum group; check in an empty wit/deps.toml plus its lock to pin the leaf, and teach the zero-leak gate to fail if either ever declares a dependency.
Every cross-group dep in the videre and shepherd manifests now carries its final form: nexum-* crates pinned to the nexum-runtime repo at v0.1.0, videre-* crates pinned to the videre-nexum-module repo at v0.1.0. A root umbrella [patch] resolves each pinned crate from its in-tree group, so the monorepo builds green before the remotes are populated and the carve reduces to deleting the patch tables. Each group also gains a Cargo.repo.toml: the patch-free workspace root the standalone repo renames to Cargo.toml at the carve, carrying only the hoisted dep-table subset its members inherit.
Carve the nexum group from the shepherd monorepo with history. Rename Cargo.repo.toml to Cargo.toml, add flake, CI (L1 jobs only), justfile and the 1.94-pinned lockfile. Standalone gate green: fmt, clippy, venue-agnostic zero-leak, 10 guest wasms, tests, rustdoc.
Bumps [alloy-json-rpc](https://github.com/alloy-rs/alloy) from 2.1.1 to 2.2.0. - [Release notes](https://github.com/alloy-rs/alloy/releases) - [Changelog](https://github.com/alloy-rs/alloy/blob/main/CHANGELOG.md) - [Commits](alloy-rs/alloy@v2.1.1...v2.2.0) --- updated-dependencies: - dependency-name: alloy-json-rpc dependency-version: 2.2.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
Contributor
Author
|
OK, I won't notify you again about this release, but will get in touch when a new version is available. If you'd rather skip all updates until the next major or minor version, let me know by commenting If you change your mind, just re-open this PR and I'll resolve any conflicts on it. |
mfw78
added a commit
that referenced
this pull request
Jul 27, 2026
…lities) (#6) * docs: add 0.1 to 0.2 migration guide * wit: rename web3:runtime to nexum:runtime, unify error model, add identity/clock/random/http/query-module * chore: rename crate nxm-engine to nexum-engine; bump to 0.2.0 * build: update justfile and CI for nexum-runtime + nexum-engine rename * docs: rename to nexum:runtime, unify error model, mark non-server platforms as planned * runtime: update engine + example to 0.2 WIT - Engine main.rs targets world `shepherd` (formerly `shepherd-module`), generates against nexum:runtime@0.2.0 + shepherd:cow@0.2.0. - Replaces per-domain error records (JsonRpcError, MsgError, StoreError, ApiError, bare String) with unified HostError + HostErrorKind across every host impl. - Adds Identity host stub (was missing in 0.1 despite being doc'd). - Adds chain::request_batch stub that falls back to per-call dispatch. - Renames feed_get/feed_set -> read_feed/write_feed. - Drops separate cow + order interfaces, replaced by single cow_api with request and submit_order. - Block ts in test event is now ms (1_700_000_000_000) per types.wit documented unit. - Example module targets event-module world, matches Event::Tick instead of Event::Timer, returns HostError from init/on_event. * example: drop empty-name guard from init The guard was inconsistent: missing 'name' key silently defaulted to 'unknown' and returned Ok, but an explicit empty string returned Err — and both paths logged a success-shaped 'name=...' line BEFORE the guard ran. The example is a hello-world; a config-validation guard belongs in a real module, not a starter. Drop it. Resolves PR #6 finding #9. * ci: add wit deps sync check Both wit/nexum-runtime/ and wit/shepherd-cow/deps/nexum-runtime/ are committed to the tree; the latter is regenerated by 'just sync-wit'. Without a CI guard, a contributor can edit one without the other and the divergence only surfaces at runtime (interface-mismatch on module instantiation) — not at build time. The new job re-runs the same copy that 'just sync-wit' does, then fails if the deps tree differs from the freshly-copied canonical. Resolves PR #6 finding #8. * docs/01: align identity::sign to personal_sign semantics The shipped wit/nexum-runtime/identity.wit defines sign() as personal_sign — host MUST prepend the EIP-191 prefix ('\x19Ethereum Signed Message:\n<len>') before hashing. docs/01 described it as 'sign raw bytes' which is a transaction-signing footgun (a raw signer can be tricked into signing EIP-155 / EIP-712 payloads disguised as plain bytes) AND diverges from the WIT — two compliant hosts implementing different specs produce mutually unverifiable signatures. Align docs/01 to the WIT. Note that raw-bytes signing, gated by an explicit capability, is on the 0.3 roadmap. Resolves PR #6 finding #1. * docs: align chain::request-batch and http WIT snippets to shipped types Both snippets in the migration guide and docs/01 showed list<tuple<string, string>> shapes that don't match the shipped WIT. Adopters who copy the doc snippets get wit-bindgen type errors against the real interfaces. - chain::request-batch now uses the nominal record rpc-request and variant rpc-result (matching wit/nexum-runtime/chain.wit). - http now uses the nominal record header and adds the timeout-ms field (matching wit/nexum-runtime/http.wit), plus *.domain wildcard syntax in the allowlist example and the docstring on how non-2xx surfaces. Resolves PR #6 findings #2 and #3. * docs: defer typed config-value variant to 0.3 The shipped wit/nexum-runtime/types.wit ships 0.1's stringly-typed `type config = list<tuple<string, string>>`, but five doc locations plus the migration TL;DR promised a typed `config-value` variant for 0.2 (string/integer/boolean/list). Per architectural triage, defer the typed variant to 0.3 alongside the manifest parser work — the typing story lands as one coherent feature. Updates: docs/00, docs/01, docs/02 (two places), docs/08, migration guide TL;DR row + 'Typed config' subsection. Resolves PR #6 finding #4. * docs/migration: replace cargo-nexum vapor with real commands §9 verification checklist referenced 'cargo nexum check' and 'cargo nexum run --mock'; §11 promised 'cargo nexum migrate --from 0.1' as shipping with 0.2. None of these exist — there is no cargo-nexum crate in the workspace. Rewrite §9 to use real `cargo` + `just` invocations. Drop the §11 codemod promise; the sed cheat sheet in §8 already does the mechanical work. Add a clear 'no cargo-nexum toolchain in 0.2; coming in 0.3' note so adopters set the right expectation. Resolves PR #6 finding #7. * wit+engine: import clock/random/http in event-module; add host impls The new 0.2 capability interfaces (clock, random, http) shipped as standalone WIT files but were not imported by any world, so modules built against event-module / shepherd could not 'use' them. Their existence was advertised in the migration guide but was structurally unreachable. - event-module.wit: add 'import clock', 'import random', 'import http' (grouped as ambient host services after the six core primitives). shepherd world inherits via 'include event-module'. query-module remains intentionally sandboxed (no caps, no chain, no network). - Engine: add host impls for clock (SystemTime + monotonic_baseline: Instant), random (getrandom 0.4 fill), http (returns Unsupported for 0.2; real fetch is 0.3 — allowlist enforcement lands with #6). - Cargo.toml: add getrandom 0.4, plus serde 1 + toml 1 (for #6). Resolves PR #6 finding #5. * engine: minimal nexum.toml manifest parser with [capabilities] enforcement Per architectural triage, ship minimal manifest enforcement in 0.2 and defer optional-capability trap stubs to 0.3. The migration guide §3 promised four mechanisms; this commit lands the two security-critical ones (required-capability check + http allowlist enforcement) plus the deprecation warning, and explicitly defers per-import trap stubs. Manifest schema (parsed in crates/nexum-engine/src/manifest.rs): [module] name, version, component (sha256; parsed, not yet verified) [capabilities] required (validated against KNOWN_CAPABILITIES; engine rejects unknown names) optional (parsed + logged; trap-stub fallback is 0.3) [capabilities.http] allow (exact host or *.suffix wildcard) [config] TOML scalars flattened to strings (typed variant on 0.3 roadmap) CLI: nexum-engine <component.wasm> [<nexum.toml>]. If the second arg is omitted, the engine looks for nexum.toml next to the .wasm. If neither exists, it emits the deprecation warning and proceeds with empty config and empty allowlist (= effectively denies all HTTP). http::fetch now performs the per-module allowlist check (host extracted via a stdlib URL parser, exact or *.suffix match). Allowlist denial returns HostError { kind: Denied }; real network fetch is still 0.3. Includes unit tests for extract_host and host_allowed. Resolves PR #6 finding #6. * style: cargo fmt (nightly) CI rustfmt (nightly) collapses several multi-line signatures that the hand-formatted code had as multi-line. * rename WIT package nexum:runtime -> nexum:host (resolve engine/runtime naming overload) The 0.2 release shipped two similarly-named things — `nexum-engine` (the Rust crate hosting WASM components) and `nexum:runtime` (the WIT package). Both contain 'runtime' or a synonym, but they're at different layers: engine = implementation, runtime = contract. Worse, README.md described the engine crate as 'Host runtime — wasmtime-based component loader' — explicitly calling the *engine* 'runtime' while a sibling directory was literally named `nexum-runtime`. The word 'runtime' overwhelmingly means 'the thing that runs code' in programming usage; putting it on the contract side inverted the convention. Rename the WIT package to `nexum:host` — the host-imports surface a guest sees. Distinction becomes self-documenting: engine (nexum-engine) = a concrete implementation host (nexum:host) = the WIT contract every engine implements Touchpoints: - wit/nexum-runtime/ -> wit/nexum-host/ (12 files renamed via mv) - wit/shepherd-cow/deps/nexum-runtime/ -> wit/shepherd-cow/deps/nexum-host/ (regenerated by 'just sync-wit'; also covered by CI guard) - Every 'package nexum:runtime@0.2.0' -> 'package nexum:host@0.2.0' (12 files) - Every 'use nexum:runtime/...' -> 'use nexum:host/...' (shepherd-cow WIT) - Every 'nexum::runtime::...' -> 'nexum::host::...' (engine + example Rust) - justfile sync-wit paths - .github/workflows/ci.yml wit-deps-sync paths - All design docs (00..08), migration guide - README.md + docs/00 add an explicit 'Engine vs. host' callout so the distinction is directly apparent to a new reader. - A few stray 'host runtime' prose mentions in docs/05 and docs/07 changed to 'host engine' for consistency. Migration guide updated so a 0.1 -> 0.2 reader never encounters the mid-development 'nexum:runtime' name — the new package is `nexum:host` end-to-end. Build + run smoke clean; nightly rustfmt clean. * wit: drop deps/ vendoring; list both packages explicitly in bindgen The 0.2 release vendored a copy of wit/nexum-host/ under wit/shepherd-cow/deps/nexum-host/ because the engine's bindgen target (shepherd:cow/shepherd) imports from nexum:host via 'include', and wit-parser's default cross-package resolution looks at <pkg>/deps/. Maintained by 'just sync-wit' + a CI guard to catch drift. That whole pipeline was treating the symptom rather than the cause. Both bindgen macros (wasmtime::component::bindgen! and wit_bindgen::generate!) accept 'path' as an array of dirs, each holding one package. Listing both packages explicitly resolves the cross-package reference natively with no vendored copy. Changes: - crates/nexum-engine/src/main.rs: bindgen path is now ["../../wit/nexum-host", "../../wit/shepherd-cow"]; world fully qualified as "shepherd:cow/shepherd". - modules/example/src/lib.rs: world fully qualified as "nexum:host/event-module". Path stays single (the example doesn't import shepherd:cow). - wit/shepherd-cow/deps/ deleted entirely (12 files). - justfile: sync-wit recipe removed; build-runtime renamed to build-engine (clearer; matches the engine vs host vocabulary established earlier); check + run no longer depend on sync-wit. - .github/workflows/ci.yml: wit-deps-sync job removed (nothing to sync, nothing to drift). - docs/migration/0.1-to-0.2.md: checklist item about vendored deps rewritten to point at the new bindgen pattern. Result: 1 source of truth per WIT package, zero ceremony to keep them aligned, ~360 fewer lines of vendored bytes in the repo.
mfw78
pushed a commit
that referenced
this pull request
Jul 27, 2026
12 review threads addressed end-to-end. Net diff is -720 lines despite adding ~200 lines of new helpers + tests, because the WitBindgenHost adapter deduplication alone wipes ~400 lines. Per-thread: #1 (balance-tracker architecture): refactored to match the M3 host-trait+adapter split the other 4 modules use. Created `strategy.rs` with `on_block(&impl Host, ...)`, moved check_one / fetch_balance / parse_balance_hex / parse_settings into it, converted parse_config to use SDK config helpers + typed HostError instead of String. Added 3 MockHost-driven tests covering first-seen-above-threshold, below-threshold-persist, and error-does-not-abort-loop. #2 + #3 (WitBindgenHost dedup): new `shepherd_sdk::bind_host_via_wit_bindgen!()` declarative macro. Single source of truth in `crates/shepherd-sdk/src/wit_bindgen_macro.rs`; the 4 trait impls + convert_err / sdk_err_into_wit / convert_level collapse to one macro invocation per module. Migrated all 5 modules (twap-monitor, ethflow-watcher, price-alert, stop-loss, balance-tracker). Each module's lib.rs lost ~80 lines. #4 (scale_decimal + config_get dup): new `shepherd_sdk::config` with `get_required`, `get_optional`, `scale_decimal`, and a typed `ConfigError` enum (host-neutral). price-alert + stop-loss consume the SDK helpers; their local duplicates were deleted. Module-level decimal-parsing tests removed (covered by 7 SDK tests + 4 proptest cases now). #5 (Chainlink dup): new `shepherd_sdk::chain::chainlink` with `read_latest_answer(host, chain_id, oracle, domain) -> Option<I256>`. Encapsulates the eth_call → parse → ABI decode flow + Warn logging. price-alert + stop-loss now call the helper; their local AggregatorV3 sol! definitions + read_oracle / on_block oracle plumbing was deleted. SDK ships with 3 StubHost tests covering happy path, host error, and garbage-hex. #6 (WIT world capability elision): added new "Capability enforcement vs. the WIT world" section to ADR-0009 documenting that price-alert + balance-tracker compile against the shepherd:cow/shepherd supertype but their manifests omit cow-api, and that boot success depends on wasm-tools' unused- import elision. Flagged as load-bearing; M5 macro hardening path documented. #7 (poll-time revert classification inert): filed COW-1082 for the host-side fix (forward structured eth_call error data into HostError.data; analogous to COW-1075 for orderbook). #8 (classify_api_error retry-default unbounded): filed COW-1083 for the rate-limit / max-retry follow-up on the backoff: marker. #9 (RetryAction::Backoff dead variant): no code change; replied to thread clarifying it is reserved API surface waiting on a richer upstream retry_hint shape (open question for mfw78). #10 (no proptest anywhere): added `proptest` to shepherd-sdk dev-dependencies. New `crates/shepherd-sdk/src/proptests.rs` with 6 properties covering eth_call_params/parse_eth_call_result round-trip, parse_eth_call_result rejection on unquoted input, config::scale_decimal round-trip + sign-preservation, U256 LE byte round-trip, and no-panic guards for decode_revert_hex + gpv2_to_order_data marker dispatch. #11 (ethflow chain capability least-privilege): moved `chain` from required to optional in `modules/ethflow-watcher/module.toml`, mirroring the M2 mirror fix already applied. #12 (ADR-0009 test-count census): dropped the "145 host tests (twap 20, ethflow 12, ...)" breakdown; kept the qualitative claim. CI is now the authoritative count. Drive-by: alloy-sol-types moved from regular to dev-dependencies in price-alert and stop-loss now that the Chainlink ABI helper is inside shepherd-sdk and the modules only use sol! in their test helpers. Validation: - cargo test --workspace: every crate green; 5 modules + SDK + sdk-test + engine all pass. 8 host tests gained on balance-tracker; 6 proptest props gained on shepherd-sdk; 3 Chainlink helper tests gained. - cargo clippy --workspace --all-targets -- -D warnings: clean. - cargo fmt --check: clean. - cargo build --target wasm32-wasip2 --release for all 5 modules: clean. - Zero em-dashes in source code added.
mfw78
pushed a commit
that referenced
this pull request
Jul 27, 2026
…tParseError Audit reference: milestone-rubric-grant-audit-2026-06-25.md, Major #7 (`Result<_, String>` survivor in `modules/examples/balance-tracker/ src/strategy.rs:149`). The rubric prohibits stringly-typed errors in library APIs. The formatting strings now live on `#[error(...)]` annotations on the `AddressListParseError` variants, preserving the exact wording the previous `format!("address #{i} ({trimmed:?}): {e}")` / `"expected at least one address"` calls produced, so any operator-facing log strings stay stable. `thiserror = "2"` lands as a direct dep on the balance-tracker module. The audit also notes shepherd-backtest already migrated to a typed `AddressParseError`; consolidating both into a shared `shepherd_sdk::cow::AddressParse` is a separate refactor flagged as a P3 medium-confidence consolidation in the audit and deferred for Bruno's judgment call on whether the shared crate is the right home.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bumps alloy-json-rpc from 2.1.1 to 2.2.0.
Release notes
Sourced from alloy-json-rpc's releases.
Changelog
Sourced from alloy-json-rpc's changelog.
Commits
d2ca48dchore: release 2.2.01c81506chore: release 2.2.0ffaf1dachore(deps): bump trezor-client to 0.1.6 (#4079)119e9b0fix(ci): work around nightly test attribute ICE (#4081)eedfb86feat(consensus): add block gas limit validation (#4072)35da51bchore(eips): address nightly clippy lint (#4080)154892ffeat(network): add AnyRpcBlock header conversion (#4073)50519aefeat(rpc-types-engine): implement FromStr for PayloadId (#4077)843b40dchore(deps): bump the ci-weekly group with 3 updates (#4078)1c5346fchore(signer-turnkey): bumpturnkey_clientto 0.7 (#4036)Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting
@dependabot rebase.Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
@dependabot rebasewill rebase this PR@dependabot recreatewill recreate this PR, overwriting any edits that have been made to it@dependabot show <dependency name> ignore conditionswill show all of the ignore conditions of the specified dependency@dependabot ignore this major versionwill close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this minor versionwill close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this dependencywill close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)