Skip to content

build(deps): bump alloy-json-rpc from 2.1.1 to 2.2.0 - #7

Closed
dependabot[bot] wants to merge 142 commits into
mainfrom
dependabot/cargo/alloy-json-rpc-2.2.0
Closed

build(deps): bump alloy-json-rpc from 2.1.1 to 2.2.0#7
dependabot[bot] wants to merge 142 commits into
mainfrom
dependabot/cargo/alloy-json-rpc-2.2.0

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Jul 18, 2026

Copy link
Copy Markdown
Contributor

Bumps alloy-json-rpc from 2.1.1 to 2.2.0.

Release notes

Sourced from alloy-json-rpc's releases.

v2.2.0

What's Changed

Full Changelog: alloy-rs/alloy@v2.1.1...v2.2.0

Changelog

Sourced from alloy-json-rpc's changelog.

2.2.0 - 2026-07-17

Bug Fixes

  • [ci] Work around nightly test attribute ICE (#4081)
  • Address Rust 1.97 clippy lints (#4074)

Dependencies

  • [deps] Bump trezor-client to 0.1.6 (#4079)
  • [deps] Bump the ci-weekly group with 3 updates (#4078)
  • [signer-turnkey] Bump turnkey_client to 0.7 (#4036)
  • Bump MSRV to 1.94.1 (#4068)

Features

  • [consensus] Add block gas limit validation (#4072)
  • [network] Add AnyRpcBlock header conversion (#4073)
  • [rpc-types-engine] Implement FromStr for PayloadId (#4077)

Miscellaneous Tasks

  • Release 2.2.0
  • [eips] Address nightly clippy lint (#4080)

Other

  • Rustdoc no_inline workaround (#4064)
Commits
  • d2ca48d chore: release 2.2.0
  • 1c81506 chore: release 2.2.0
  • ffaf1da chore(deps): bump trezor-client to 0.1.6 (#4079)
  • 119e9b0 fix(ci): work around nightly test attribute ICE (#4081)
  • eedfb86 feat(consensus): add block gas limit validation (#4072)
  • 35da51b chore(eips): address nightly clippy lint (#4080)
  • 154892f feat(network): add AnyRpcBlock header conversion (#4073)
  • 50519ae feat(rpc-types-engine): implement FromStr for PayloadId (#4077)
  • 843b40d chore(deps): bump the ci-weekly group with 3 updates (#4078)
  • 1c5346f chore(signer-turnkey): bump turnkey_client to 0.7 (#4036)
  • Additional commits viewable in compare view

Dependabot compatibility score

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 rebase will rebase this PR
  • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
  • @dependabot show <dependency name> ignore conditions will show all of the ignore conditions of the specified dependency
  • @dependabot ignore this major version will 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 version will 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 dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)

mfw78 and others added 30 commits February 20, 2026 20:28
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.
mfw78 and others added 18 commits July 17, 2026 04:17
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>
@dependabot dependabot Bot added dependencies Pull requests that update a dependency file rust Pull requests that update rust code labels Jul 18, 2026
@dependabot
dependabot Bot requested a review from mfw78 as a code owner July 18, 2026 08:46
@dependabot dependabot Bot added dependencies Pull requests that update a dependency file rust Pull requests that update rust code labels Jul 18, 2026
@mfw78 mfw78 closed this Jul 27, 2026
@dependabot @github

dependabot Bot commented on behalf of github Jul 27, 2026

Copy link
Copy Markdown
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 @dependabot ignore this major version or @dependabot ignore this minor version.

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.
@dependabot
dependabot Bot deleted the dependabot/cargo/alloy-json-rpc-2.2.0 branch July 27, 2026 22:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file rust Pull requests that update rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants